diff --git a/.buildkite-external-version b/.buildkite-external-version new file mode 100644 index 0000000000000..ba2906d0666cf --- /dev/null +++ b/.buildkite-external-version @@ -0,0 +1 @@ +main diff --git a/.buildkite/README.md b/.buildkite/README.md deleted file mode 100644 index b3f74f2b23137..0000000000000 --- a/.buildkite/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Buildkite - -This directory contains the Buildkite configuration files for Base Julia CI. - -The rootfs image definitions are located in the [rootfs-images](https://github.com/JuliaCI/rootfs-images) repository. - -The documentation for the Base Julia CI setup is located in the [base-buildkite-docs](https://github.com/JuliaCI/base-buildkite-docs) repository. diff --git a/.buildkite/cryptic_repo_keys/.gitignore b/.buildkite/cryptic_repo_keys/.gitignore deleted file mode 100644 index 8d18931dbcf7c..0000000000000 --- a/.buildkite/cryptic_repo_keys/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore the unencrypted repo_key -repo_key - -# Ignore any agent keys (public or private) we have stored -agent_key* diff --git a/.buildkite/cryptic_repo_keys/README.md b/.buildkite/cryptic_repo_keys/README.md deleted file mode 100644 index 93ed17ce4757b..0000000000000 --- a/.buildkite/cryptic_repo_keys/README.md +++ /dev/null @@ -1,6 +0,0 @@ -## Cryptic repository keys - -This folder contains RSA-encrypted symmetric AES keys. -These are used by buildkite agents to decrypt the secrets embedded within this repository. -Each buildkite agent contains an RSA secret key that is used to unlock the symmetric AES key that was used to encrypt the secrets within this repository. -For more information, see the [`cryptic` buildkite plugin repository](https://github.com/staticfloat/cryptic-buildkite-plugin). diff --git a/.buildkite/cryptic_repo_keys/repo_key.2297e5e7 b/.buildkite/cryptic_repo_keys/repo_key.2297e5e7 deleted file mode 100644 index 2ab9198b4ce2d..0000000000000 Binary files a/.buildkite/cryptic_repo_keys/repo_key.2297e5e7 and /dev/null differ diff --git a/.buildkite/pipelines/experimental/0_webui.yml b/.buildkite/pipelines/experimental/0_webui.yml deleted file mode 100644 index e62750d9d8cd5..0000000000000 --- a/.buildkite/pipelines/experimental/0_webui.yml +++ /dev/null @@ -1,22 +0,0 @@ -# This file represents what is put into the webUI. -# It is purely for keeping track of the changes we make to the webUI configuration; modifying this file has no effect. -# We use the `cryptic` buildkite plugin to provide secrets management, which requires some integration into the WebUI's steps. -agents: - queue: "julia" - sandbox.jl: "true" -steps: - - label: ":unlock: Unlock secrets, launch pipelines" - plugins: - - staticfloat/cryptic#v1: - # Our list of pipelines that should be launched (but don't require a signature) - # These pipelines can be modified by any contributor and CI will still run. - # Build secrets will not be available in these pipelines (or their children) - # but some of our signed pipelines can wait upon the completion of these unsigned - # pipelines. - unsigned_pipelines: - - .buildkite/pipelines/experimental/launch_unsigned_builders.yml - # Our signed pipelines must have a `signature` or `signature_file` parameter that - # verifies the treehash of the pipeline itself and the inputs listed in `inputs` - # signed_pipelines: - # - pipeline: .buildkite/pipelines/experimental/misc/foo_bar_baz.yml - # signature_file: .buildkite/pipelines/experimental/misc/foo_bar_baz.yml.signature diff --git a/.buildkite/pipelines/experimental/README.md b/.buildkite/pipelines/experimental/README.md deleted file mode 100644 index f92aac7a1af02..0000000000000 --- a/.buildkite/pipelines/experimental/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## Experimental pipeline (`master` branch only) - -This is the [`julia-master->experimental`](https://buildkite.com/julialang/julia-master-experimental) pipeline. - -We use this pipeline for builders that are not yet stable enough to go into the main pipeline. - -These builders are triggered by GitHub webhook events, such as pushes and pull requests. diff --git a/.buildkite/pipelines/experimental/launch_unsigned_builders.yml b/.buildkite/pipelines/experimental/launch_unsigned_builders.yml deleted file mode 100644 index 04d82a6e39a5e..0000000000000 --- a/.buildkite/pipelines/experimental/launch_unsigned_builders.yml +++ /dev/null @@ -1,6 +0,0 @@ -steps: - - label: ":buildkite: Launch unsigned pipelines" - commands: | - true - agents: - queue: julia diff --git a/.buildkite/pipelines/main/0_webui.yml b/.buildkite/pipelines/main/0_webui.yml deleted file mode 100644 index bc40534c15dae..0000000000000 --- a/.buildkite/pipelines/main/0_webui.yml +++ /dev/null @@ -1,23 +0,0 @@ -# This file represents what is put into the webUI. -# It is purely for keeping track of the changes we make to the webUI configuration; modifying this file has no effect. -# We use the `cryptic` buildkite plugin to provide secrets management, which requires some integration into the WebUI's steps. -agents: - queue: "julia" - sandbox.jl: "true" -steps: - - label: ":unlock: Unlock secrets, launch pipelines" - plugins: - - staticfloat/cryptici#v1: - # Our list of pipelines that should be launched (but don't require a signature) - # These pipelines can be modified by any contributor and CI will still run. - # Build secrets will not be available in these pipelines (or their children) - # but some of our signed pipelines can wait upon the completion of these unsigned - # pipelines. - unsigned_pipelines: - - .buildkite/pipelines/main/launch_unsigned_builders.yml - - # Our signed pipelines must have a `signature` or `signature_file` parameter that - # verifies the treehash of the pipeline itself and the inputs listed in `inputs` - signed_pipelines: - - pipeline: .buildkite/pipelines/main/misc/signed_pipeline_test.yml - signature_file: .buildkite/pipelines/main/misc/signed_pipeline_test.yml.signature diff --git a/.buildkite/pipelines/main/README.md b/.buildkite/pipelines/main/README.md deleted file mode 100644 index 6b9d67bd7cc3a..0000000000000 --- a/.buildkite/pipelines/main/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## Main pipeline - -This is the main pipeline. It contains most of the builders. These builders are triggered by GitHub webhook events, such as pushes and pull requests. - -We have a different main pipeline for each permanent branch. - -For example: - -| Permanent Branch | Pipeline | -| ---------------- | -------------------------------------------------------------------------------- | -| `master` | [`julia-master`](https://buildkite.com/julialang/julia-master) | -| `release-1.6` | [`julia-release-1.6`](https://buildkite.com/julialang/julia-release-1-dot-6) | -| `release-1.7` | [`julia-release-1.7`](https://buildkite.com/julialang/julia-release-1-dot-7) | - -(This is not a complete list.) diff --git a/.buildkite/pipelines/main/launch_unsigned_builders.yml b/.buildkite/pipelines/main/launch_unsigned_builders.yml deleted file mode 100644 index 2b6794ed13bd1..0000000000000 --- a/.buildkite/pipelines/main/launch_unsigned_builders.yml +++ /dev/null @@ -1,33 +0,0 @@ -# This file launches all the build jobs that _don't_ require secrets access. -# These jobs can pass their output off to jobs that do require secrets access, -# but those privileged steps require signing before they can be run. -# -# Yes, this is creating another layer of indirection; the flow now looks like: -# -# [webui] -> launch_unsigned_builders.yml -> misc/whitespace.yml -# -# when we could theoretically just have the `webui` launch `misc/whitespace.yml`, -# however this raises the bar for contributors to add new (unsigned) steps to -# our CI configuration, so I'd rather live with an extra layer of indirection -# and only need to touch the webui configuration when we need to alter -# something about the privileged steps. - -steps: - - label: ":buildkite: Launch unsigned jobs" - commands: | - # Launch the miscellaneous jobs in alphabetical order. - buildkite-agent pipeline upload .buildkite/pipelines/main/misc/doctest.yml - buildkite-agent pipeline upload .buildkite/pipelines/main/misc/embedding.yml - buildkite-agent pipeline upload .buildkite/pipelines/main/misc/llvmpasses.yml - buildkite-agent pipeline upload .buildkite/pipelines/main/misc/sanitizers.yml - - # Launch all of the platform jobs. - bash .buildkite/utilities/platforms/platforms.sh .buildkite/pipelines/main/platforms/package_linux.arches .buildkite/pipelines/main/platforms/package_linux.yml - bash .buildkite/utilities/platforms/platforms.sh .buildkite/pipelines/main/platforms/tester_linux.arches .buildkite/pipelines/main/platforms/tester_linux.yml - - # Launch the `whitespace` job last. Uploading it last actually causes it to start - # first. We want this job to start first because we want it to finish as quickly - # as possible. - buildkite-agent pipeline upload .buildkite/pipelines/main/misc/whitespace.yml - agents: - queue: julia diff --git a/.buildkite/pipelines/main/misc/doctest.yml b/.buildkite/pipelines/main/misc/doctest.yml deleted file mode 100644 index fd5feb071f076..0000000000000 --- a/.buildkite/pipelines/main/misc/doctest.yml +++ /dev/null @@ -1,34 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "doctest" - key: doctest - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/package_linux.x86_64.tar.gz - rootfs_treehash: "2a058481b567f0e91b9aa3ce4ad4f09e6419355a" - uid: 1000 - gid: 1000 - workspaces: - # Include `/cache/repos` so that our `git` version introspection works. - - "/cache/repos:/cache/repos" - commands: | - echo "--- Build Julia from source" - make --output-sync -j 6 - - echo "--- Print Julia version info" - ./julia -e 'using InteractiveUtils; InteractiveUtils.versioninfo()' - - echo "--- Build Julia docs" - make docs - - echo "--- Run Julia doctests" - JULIA_NUM_THREADS=1 make -C doc doctest=true - timeout_in_minutes: 45 diff --git a/.buildkite/pipelines/main/misc/embedding.yml b/.buildkite/pipelines/main/misc/embedding.yml deleted file mode 100644 index 1b8b84d38358a..0000000000000 --- a/.buildkite/pipelines/main/misc/embedding.yml +++ /dev/null @@ -1,31 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "embedding" - key: "embedding" - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/package_linux.x86_64.tar.gz - rootfs_treehash: "2a058481b567f0e91b9aa3ce4ad4f09e6419355a" - uid: 1000 - gid: 1000 - workspaces: - # Include `/cache/repos` so that our `git` version introspection works. - - "/cache/repos:/cache/repos" - commands: | - prefix="/tmp/prefix" - echo "+++ Build julia, deploy to $${prefix:?}" - make --output-sync -j$${JULIA_CPU_THREADS:?} JULIA_PRECOMPILE=0 prefix=$${prefix:?} install - - embedding_output="/tmp/embedding-test" - echo "+++ Run embedding tests, deploy to $${embedding_output:?}" - mkdir -p "$${embedding_output:?}" - make --output-sync -j$${JULIA_CPU_THREADS:?} -C test/embedding JULIA="$${prefix:?}/bin/julia" BIN="$${embedding_output:?}" - timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/main/misc/llvmpasses.yml b/.buildkite/pipelines/main/misc/llvmpasses.yml deleted file mode 100644 index 3acc4c05c2391..0000000000000 --- a/.buildkite/pipelines/main/misc/llvmpasses.yml +++ /dev/null @@ -1,48 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "analyzegc" - key: "analyzegc" - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/llvm_passes.x86_64.tar.gz - rootfs_treehash: "c7a289a8cc544b234b1e2d7cbcce3e6815359ecd" - workspaces: - # Include `/cache/repos` so that our `git` version introspection works. - - "/cache/repos:/cache/repos" - commands: | - echo "--- Install in-tree LLVM dependencies" - make --output-sync -j$${JULIA_CPU_THREADS:?} -C deps install-llvm install-clang install-llvm-tools install-libuv install-utf8proc install-unwind - echo "+++ run clangsa/analyzegc" - make --output-sync -j$${JULIA_CPU_THREADS:?} -C test/clangsa - make --output-sync -j$${JULIA_CPU_THREADS:?} -C src analyzegc - timeout_in_minutes: 60 - - label: "llvmpasses" - key: "llvmpasses" - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/package_linux.x86_64.tar.gz - rootfs_treehash: "2a058481b567f0e91b9aa3ce4ad4f09e6419355a" - uid: 1000 - gid: 1000 - workspaces: - - "/cache/repos:/cache/repos" - commands: | - echo "--- make release" - make --output-sync -j$${JULIA_CPU_THREADS:?} release JULIA_PRECOMPILE=0 - echo "--- make src/install-analysis-deps" - make --output-sync -j$${JULIA_CPU_THREADS:?} -C src install-analysis-deps - echo "+++ make test/llvmpasses" - make --output-sync -j$${JULIA_CPU_THREADS:?} -C test/llvmpasses - timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/main/misc/sanitizers.yml b/.buildkite/pipelines/main/misc/sanitizers.yml deleted file mode 100644 index c8ebee340a48e..0000000000000 --- a/.buildkite/pipelines/main/misc/sanitizers.yml +++ /dev/null @@ -1,47 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "asan" - key: "asan" - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/llvm_passes.x86_64.tar.gz - rootfs_treehash: "c7a289a8cc544b234b1e2d7cbcce3e6815359ecd" - uid: 1000 - gid: 1000 - workspaces: - - "/cache/repos:/cache/repos" - timeout_in_minutes: 120 - if: | # We only run the `asan` job on Julia 1.8 and later. - (pipeline.slug != "julia-release-1-dot-6") && (pipeline.slug != "julia-release-1-dot-7") - soft_fail: true # TODO: delete this line (and thus disallow failures) once JuliaLang/julia#42540 is fixed - commands: | - echo "--- Build julia-debug with ASAN" - contrib/asan/build.sh ./tmp/test-asan -j$${JULIA_CPU_THREADS:?} debug - - label: "tsan" - key: "tsan" - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/llvm_passes.x86_64.tar.gz - rootfs_treehash: "c7a289a8cc544b234b1e2d7cbcce3e6815359ecd" - uid: 1000 - gid: 1000 - workspaces: - - "/cache/repos:/cache/repos" - timeout_in_minutes: 120 - if: | # We only run the `tsan` job on Julia 1.8 and later. - (pipeline.slug != "julia-release-1-dot-6") && (pipeline.slug != "julia-release-1-dot-7") - commands: | - echo "--- Build julia-debug runtime with TSAN" - contrib/tsan/build.sh ./tmp/test-tsan -j$${JULIA_CPU_THREADS:?} julia-src-debug diff --git a/.buildkite/pipelines/main/misc/signed_pipeline_test.yml b/.buildkite/pipelines/main/misc/signed_pipeline_test.yml deleted file mode 100644 index 1d59253d43bce..0000000000000 --- a/.buildkite/pipelines/main/misc/signed_pipeline_test.yml +++ /dev/null @@ -1,18 +0,0 @@ -agents: - queue: "julia" - os: "linux" - -## pipeline that showcases decryption of environment variable -steps: - - label: ":lock: :rocket: Signed pipeline test" - # We must accept the signed job id secret in order to propagate secrets - env: - BUILDKITE_PLUGIN_CRYPTIC_BASE64_SIGNED_JOB_ID_SECRET: ${BUILDKITE_PLUGIN_CRYPTIC_BASE64_SIGNED_JOB_ID_SECRET?} - depends_on: - plugins: - - staticfloat/cryptic#v1: - variables: - - SECRET_KEY="U2FsdGVkX18tb7st0SuQAvh4Yv4xENxOAu8q9XkmOeDVKBNY4FngEwK3xmiKUqaS" - commands: | - echo "SECRET_KEY: $${SECRET_KEY}" - diff --git a/.buildkite/pipelines/main/misc/signed_pipeline_test.yml.signature b/.buildkite/pipelines/main/misc/signed_pipeline_test.yml.signature deleted file mode 100644 index b0844748c486f..0000000000000 --- a/.buildkite/pipelines/main/misc/signed_pipeline_test.yml.signature +++ /dev/null @@ -1 +0,0 @@ -Salted__���J0�Q?���rۀ�g�~�d��ۛŧ�ө��o���Ujʀ���p�)�$�U$� ���y@gZM}{�m��,۠�K��e�r� \ No newline at end of file diff --git a/.buildkite/pipelines/main/misc/whitespace.yml b/.buildkite/pipelines/main/misc/whitespace.yml deleted file mode 100644 index b97de3ac677bb..0000000000000 --- a/.buildkite/pipelines/main/misc/whitespace.yml +++ /dev/null @@ -1,21 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "whitespace" - key: "whitespace" - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/package_linux.x86_64.tar.gz - rootfs_treehash: "2a058481b567f0e91b9aa3ce4ad4f09e6419355a" - workspaces: - - "/cache/repos:/cache/repos" - timeout_in_minutes: 10 - commands: | - make --output-sync -j$${JULIA_CPU_THREADS:?} check-whitespace diff --git a/.buildkite/pipelines/main/platforms/package_linux.arches b/.buildkite/pipelines/main/platforms/package_linux.arches deleted file mode 100644 index dec82f530a832..0000000000000 --- a/.buildkite/pipelines/main/platforms/package_linux.arches +++ /dev/null @@ -1,7 +0,0 @@ -# PLATFORM LABEL GROUP ALLOW_FAIL ARCH ARCH_ROOTFS MAKE_FLAGS TIMEOUT_BK TIMEOUT_RR RETRIES IS_RR IS_ST IS_MT ROOTFS_TAG ROOTFS_HASH -linux 32 . . 32 i686 . . . . . . . v4.8 b6dffc772ab4c2cd7fd4f83459308f6f0d89b957 -linux 64 . . 64 x86_64 . . . . . . . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -# linux aarch64 . . aarch64 aarch64 . . . . . . . .... ........................................ -# linux armv7l . . armv7l armv7l . . . . . . . .... ........................................ -# linux ppc64le . . ppc64le powerpc64le . . . . . . . .... ........................................ -musl 64 . . 64 x86_64 . . . . . . . v4.8 d13a47c87c38005bd5d97132e51789cafd852f90 diff --git a/.buildkite/pipelines/main/platforms/package_linux.yml b/.buildkite/pipelines/main/platforms/package_linux.yml deleted file mode 100644 index 06245543c73b0..0000000000000 --- a/.buildkite/pipelines/main/platforms/package_linux.yml +++ /dev/null @@ -1,54 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "package_${PLATFORM?}${LABEL?}" - key: package_${PLATFORM?}${LABEL?} - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/${ROOTFS_TAG?}/package_${PLATFORM?}.${ARCH_ROOTFS?}.tar.gz - rootfs_treehash: "${ROOTFS_HASH?}" - uid: 1000 - gid: 1000 - workspaces: - # Include `/cache/repos` so that our `git` version introspection works. - - "/cache/repos:/cache/repos" - timeout_in_minutes: ${TIMEOUT_BK?} - commands: | - echo "--- Print the full and short commit hashes" - SHORT_COMMIT_LENGTH=10 - SHORT_COMMIT=`echo $${BUILDKITE_COMMIT:?} | cut -c1-$${SHORT_COMMIT_LENGTH:?}` - ARTIFACT_FILE_EXTENSION="tar.gz" - ARTIFACT_FILENAME="julia-$${SHORT_COMMIT:?}-${PLATFORM?}${ARCH?}.$${ARTIFACT_FILE_EXTENSION:?}" - JULIA_BINARYDIST_FILENAME=`make print-JULIA_BINARYDIST_FILENAME ${MAKE_FLAGS?} | cut -c27- | tr -s ' '` - JULIA_BINARYDIST="$${JULIA_BINARYDIST_FILENAME:?}.$${ARTIFACT_FILE_EXTENSION:?}" - - echo "The full commit is: $${BUILDKITE_COMMIT:?}" - echo "The short commit is: $${SHORT_COMMIT:?}" - echo "The artifact filename will be: $${ARTIFACT_FILENAME:?}" - - echo "--- Build Julia from source" - rm -rf $${ARTIFACT_FILENAME:?} - make --output-sync -j 8 ${MAKE_FLAGS?} - - echo "--- Check that the working directory is clean" - if [ -z "$(git status --short)" ]; then echo "INFO: The working directory is clean."; else echo "ERROR: The working directory is dirty."; echo "Output of git status:"; git status; exit 1; fi - - echo "--- Print Julia version info" - ./julia -e 'using InteractiveUtils; InteractiveUtils.versioninfo()' - - echo "--- Create build artifacts" - make --output-sync -j 8 binary-dist ${MAKE_FLAGS?} - ls -l $${JULIA_BINARYDIST:?} - if [[ "$${JULIA_BINARYDIST:?}" != "$${ARTIFACT_FILENAME:?}" ]]; then - mv $${JULIA_BINARYDIST:?} $${ARTIFACT_FILENAME:?} - fi - ls -l $${ARTIFACT_FILENAME:?} - echo "--- Upload build artifacts" - buildkite-agent artifact upload $${ARTIFACT_FILENAME:?} diff --git a/.buildkite/pipelines/main/platforms/tester_linux.arches b/.buildkite/pipelines/main/platforms/tester_linux.arches deleted file mode 100644 index 000bcacd10b8f..0000000000000 --- a/.buildkite/pipelines/main/platforms/tester_linux.arches +++ /dev/null @@ -1,25 +0,0 @@ -# PLATFORM LABEL GROUP ALLOW_FAIL ARCH ARCH_ROOTFS MAKE_FLAGS TIMEOUT_BK TIMEOUT_RR RETRIES IS_RR IS_ST IS_MT ROOTFS_TAG ROOTFS_HASH -linux 32_g1 g1 . 32 i686 . 120 . . . . . v4.8 b6dffc772ab4c2cd7fd4f83459308f6f0d89b957 -linux 32_g2 g2 . 32 i686 . . . 3 . . . v4.8 b6dffc772ab4c2cd7fd4f83459308f6f0d89b957 - -linux 64_g1_mt g1 . 64 x86_64 . . . . . . yes v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64_g2_mt g2 . 64 x86_64 . . . 3 . . yes v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a - -linux 64_g1_st g1 . 64 x86_64 . . . . . yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64_g2_st g2 . 64 x86_64 . . . 3 . yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a - -linux 64_g1_rrst g1 . 64 x86_64 . 300 240 . yes yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64_g2_rrst g2 . 64 x86_64 . 180 120 3 yes yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64_g3_st g3 . 64 x86_64 . . . 3 . yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a - -# linux aarch64_g1 g1 true aarch64 aarch64 . . . . . . . ---- ---------------------------------------- -# linux aarch64_g2 g2 true aarch64 aarch64 . . . . . . . ---- ---------------------------------------- - -# linux armv7l_g1 g1 true armv7l armv7l . . . . . . . ---- ---------------------------------------- -# linux armv7l_g2 g2 true armv7l armv7l . . . . . . . ---- ---------------------------------------- - -# linux ppc64le_g1 g1 true ppc64le powerpc64le . . . . . . . ---- ---------------------------------------- -# linux ppc64le_g2 g2 true ppc64le powerpc64le . . . . . . . ---- ---------------------------------------- - -musl 64_g1 g1 true 64 x86_64 . . . . . . . v4.8 d13a47c87c38005bd5d97132e51789cafd852f90 -musl 64_g2 g2 true 64 x86_64 . . . . . . . v4.8 d13a47c87c38005bd5d97132e51789cafd852f90 diff --git a/.buildkite/pipelines/main/platforms/tester_linux.yml b/.buildkite/pipelines/main/platforms/tester_linux.yml deleted file mode 100644 index 9deb81675db1c..0000000000000 --- a/.buildkite/pipelines/main/platforms/tester_linux.yml +++ /dev/null @@ -1,120 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: "tester_${PLATFORM?}${LABEL?}" - key: tester_${PLATFORM?}${LABEL?} - depends_on: package_${PLATFORM?}${ARCH?} - plugins: - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1.2: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/${ROOTFS_TAG?}/package_${PLATFORM?}.${ARCH_ROOTFS?}.tar.gz - # rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/${ROOTFS_TAG?}/tester${PLATFORM?}.${ARCH_ROOTFS?}.tar.gz - rootfs_treehash: "${ROOTFS_HASH?}" - uid: 1000 - gid: 1000 - workspaces: - # Include `/cache/repos` so that our `git` version introspection works. - - "/cache/repos:/cache/repos" - env: - JULIA_SHELL: "/bin/bash" - timeout_in_minutes: ${TIMEOUT_BK?} - retry: - automatic: - - exit_status: "*" - limit: ${RETRIES?} - soft_fail: ${ALLOW_FAIL?} - commands: | - echo "--- Print the full and short commit hashes" - SHORT_COMMIT_LENGTH=10 - SHORT_COMMIT=`echo $${BUILDKITE_COMMIT:?} | cut -c1-$${SHORT_COMMIT_LENGTH:?}` - JULIA_DIR="julia-$${SHORT_COMMIT:?}" - JULIA_BINARY="$${JULIA_DIR:?}/bin/julia" - ARTIFACT_FILE_EXTENSION="tar.gz" - ARTIFACT_FILENAME="julia-$${SHORT_COMMIT:?}-${PLATFORM?}${ARCH?}.$${ARTIFACT_FILE_EXTENSION:?}" - echo "The full commit is: $${BUILDKITE_COMMIT:?}" - echo "The short commit is: $${SHORT_COMMIT:?}" - echo "The artifact filename will be: $${ARTIFACT_FILENAME:?}" - echo "The Julia directory name will be: $${JULIA_DIR:?}" - echo "The Julia binary will be: $${JULIA_BINARY:?}" - - echo "--- Download build artifacts" - rm -rf $${ARTIFACT_FILENAME:?} - buildkite-agent artifact download $${ARTIFACT_FILENAME:?} . - - echo "--- Extract build artifacts" - rm -rf $${JULIA_DIR:?}/ - tar xzf $${ARTIFACT_FILENAME:?} $${JULIA_DIR:?}/ - - echo "--- Print Julia version info" - $${JULIA_BINARY:?} -e 'using InteractiveUtils; InteractiveUtils.versioninfo()' - echo "JULIA_CPU_THREADS is: $${JULIA_CPU_THREADS:?}" - $${JULIA_BINARY:?} -e '@info "" Sys.CPU_THREADS' - - echo "--- Set some environment variables" - export OPENBLAS_NUM_THREADS=8 - unset JULIA_DEPOT_PATH - unset JULIA_PKG_SERVER - - # Make sure that temp files and temp directories are created in a location that is - # backed by real storage. - export TMPDIR="$(pwd)/tmp" - mkdir -p $${TMPDIR:?} - - export NETWORK_RELATED_TESTS="Artifacts Downloads download LazyArtifacts LibGit2/online Pkg" - - if [[ "${GROUP?}" == "all" ]]; then - export TESTS="all LibGit2/online --ci" - elif [[ "${GROUP?}" == "all_except_pkg" ]]; then - export TESTS="all LibGit2/online --ci --skip Pkg" - elif [[ "${GROUP?}" == "g1" ]]; then - # Group 1: ALL tests EXCEPT the network-related tests. - export TESTS="all --ci --skip $${NETWORK_RELATED_TESTS:?}" - elif [[ "${GROUP?}" == "g2" ]]; then - # Group 2: ONLY the network-related tests. - # In Group 2, we use whatever the default setting is with regards to the Pkg server. - export TESTS="$${NETWORK_RELATED_TESTS:?} --ci" - elif [[ "${GROUP?}" == "g3" ]]; then - # Group 3: only Pkg. - # In Group 3, we explicitly opt-out of the Pkg server. - # The purpose of group 3 is to test the non-Pkg-server codepaths of Pkg. - export TESTS="Pkg --ci" - export JULIA_PKG_SERVER="" - else - echo "Invalid value for GROUP: ${GROUP?}" - exit 1 - fi - - export JULIA_TEST_RR_TIMEOUT="${TIMEOUT_RR?}" - - if [[ "${IS_RR?}" == "yes" ]]; then - export JULIA_CMD_FOR_TESTS="$${JULIA_BINARY:?} .buildkite/utilities/rr/rr_capture.jl $${JULIA_BINARY:?}" - export NCORES_FOR_TESTS="parse(Int, ENV[\"JULIA_RRCAPTURE_NUM_CORES\"])" - else - export JULIA_CMD_FOR_TESTS="$${JULIA_BINARY:?}" - export NCORES_FOR_TESTS="Sys.CPU_THREADS" - fi - - if [[ "${IS_ST?}" == "yes" ]]; then - export JULIA_NUM_THREADS=1 - fi - - if [[ "${IS_MT?}" == "yes" ]]; then - export JULIA_NUM_THREADS=16 - fi - - echo "--- Print the test group, list of test sets, and other useful environment variables" - echo "JULIA_CMD_FOR_TESTS is: $${JULIA_CMD_FOR_TESTS:?}" - echo "JULIA_NUM_THREADS is: $${JULIA_NUM_THREADS}" # Note: this environment variable might not be set - echo "NCORES_FOR_TESTS is: $${NCORES_FOR_TESTS:?}" - echo "OPENBLAS_NUM_THREADS is: $${OPENBLAS_NUM_THREADS:?}" - echo "GROUP is: ${GROUP?}" - echo "TESTS is: $${TESTS:?}" - - echo "--- Run the Julia test suite" - $${JULIA_CMD_FOR_TESTS:?} -e "Base.runtests(\"$${TESTS:?}\"; ncores = $${NCORES_FOR_TESTS:?})" diff --git a/.buildkite/pipelines/scheduled/0_webui.yml b/.buildkite/pipelines/scheduled/0_webui.yml deleted file mode 100644 index ad2216286bda7..0000000000000 --- a/.buildkite/pipelines/scheduled/0_webui.yml +++ /dev/null @@ -1,27 +0,0 @@ -# This file represents what is put into the webUI. -# It is purely for keeping track of the changes we make to the webUI configuration; modifying this file has no effect. -# We use the `cryptic` buildkite plugin to provide secrets management, which requires some integration into the WebUI's steps. -agents: - queue: "julia" - sandbox.jl: "true" -steps: - - label: ":unlock: Unlock secrets, launch pipelines" - plugins: - - staticfloat/cryptic#v1: - # Our list of pipelines that should be launched (but don't require a signature) - # These pipelines can be modified by any contributor and CI will still run. - # Build secrets will not be available in these pipelines (or their children) - # but some of our signed pipelines can wait upon the completion of these unsigned - # pipelines. - unsigned_pipelines: - - .buildkite/pipelines/scheduled/launch_unsigned_jobs.yml - - # Our signed pipelines must have a `signature` or `signature_file` parameter that - # verifies the treehash of the pipeline itself and the inputs listed in `inputs` - signed_pipelines: - - pipeline: .buildkite/pipelines/scheduled/coverage/coverage_linux64.yml - signature_file: .buildkite/pipelines/scheduled/coverage/coverage_linux64.yml.signature - inputs: - - .buildkite/pipelines/scheduled/coverage/coverage_linux64.yml - - .buildkite/pipelines/scheduled/coverage/run_tests_parallel.jl - - .buildkite/pipelines/scheduled/coverage/upload_coverage.jl diff --git a/.buildkite/pipelines/scheduled/README.md b/.buildkite/pipelines/scheduled/README.md deleted file mode 100644 index ca071dceb2a44..0000000000000 --- a/.buildkite/pipelines/scheduled/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## Scheduled pipeline (`master` branch only) - -This is the [`julia-master->scheduled`](https://buildkite.com/julialang/julia-master-scheduled) pipeline. - -We use this pipeline for scheduled builds. The builders in this pipeline run on a schedule once per day. They are not triggered by GitHub webhooks. diff --git a/.buildkite/pipelines/scheduled/coverage/coverage_linux64.yml b/.buildkite/pipelines/scheduled/coverage/coverage_linux64.yml deleted file mode 100644 index 8ebe53d1ab492..0000000000000 --- a/.buildkite/pipelines/scheduled/coverage/coverage_linux64.yml +++ /dev/null @@ -1,44 +0,0 @@ -agents: - queue: "julia" - # Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing - sandbox.jl: "true" - os: "linux" -steps: - - label: ":unlock: :coverage: Run coverage test" - # We must accept the signed job id secret in order to propagate secrets - env: - BUILDKITE_PLUGIN_CRYPTIC_BASE64_SIGNED_JOB_ID_SECRET: ${BUILDKITE_PLUGIN_CRYPTIC_BASE64_SIGNED_JOB_ID_SECRET?} - depends_on: - plugins: - - staticfloat/cryptic#v1: - variables: - - CODECOV_TOKEN="U2FsdGVkX19l0fhdBabbuiEdysyEabkJLRHfxm7CNRkuGbnwPV365sxxC7Czs/CVcws0N1oB4pVwALRRMe36oA==" - - COVERALLS_TOKEN="U2FsdGVkX19zopI0hMNzzi2UUOvNVFD8Y0iisFnO/ryVxU7Tit8ZEaeN+gxodRx4CosUUh192F1+q3dTMWRIvw==" - - JuliaCI/julia#v1: - # Drop default "registries" directory, so it is not persisted from execution to execution - persist_depot_dirs: packages,artifacts,compiled - version: '1.6' - - staticfloat/sandbox#v1: - rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v4.8/package_linux.x86_64.tar.gz - rootfs_treehash: "2a058481b567f0e91b9aa3ce4ad4f09e6419355a" - uid: 1000 - gid: 1000 - timeout_in_minutes: 360 # 360 minutes = 6 hours - commands: | - echo "--- Build Julia from source" - make --output-sync -j 6 - - echo "--- Print Julia version info" - ./julia -e 'using InteractiveUtils; InteractiveUtils.versioninfo()' - ./julia -e '@info "" Sys.CPU_THREADS' - # this is necessary to make sure that the LibGit2 tests passes - git config --global init.defaultBranch master - - echo "--- Run Julia tests in parallel with code coverage enabled" - export JULIA_NUM_THREADS=1 - export JULIA_WORKER_TIMEOUT=1200 # 1200 seconds = 20 minutes - ./julia -e 'import Distributed; @info "" Distributed.worker_timeout()' - ./julia .buildkite/pipelines/scheduled/coverage/run_tests_parallel.jl - - echo "--- Process and upload coverage information" - ./julia .buildkite/pipelines/scheduled/coverage/upload_coverage.jl diff --git a/.buildkite/pipelines/scheduled/coverage/coverage_linux64.yml.signature b/.buildkite/pipelines/scheduled/coverage/coverage_linux64.yml.signature deleted file mode 100644 index 4ecec8e8bb72c..0000000000000 --- a/.buildkite/pipelines/scheduled/coverage/coverage_linux64.yml.signature +++ /dev/null @@ -1 +0,0 @@ -Salted__�I�y�֌��>y�Nc kB�v�n�+�Hvrލ��Ƽ�r����/�uY�����u����I�iiE��(��v�L��!��?��v� \ No newline at end of file diff --git a/.buildkite/pipelines/scheduled/coverage/run_tests_parallel.jl b/.buildkite/pipelines/scheduled/coverage/run_tests_parallel.jl deleted file mode 100644 index b6eed225f652d..0000000000000 --- a/.buildkite/pipelines/scheduled/coverage/run_tests_parallel.jl +++ /dev/null @@ -1,29 +0,0 @@ -# Important note: even if one or more tests fail, we will still exit with status code 0. -# -# The reason for this is that we always want to upload code coverage, even if some of the -# tests fail. Therefore, even if the `coverage_linux64` builder passes, you should not -# assume that all of the tests passed. If you want to know if all of the tests are passing, -# please look at the status of the `tester_*` builders (e.g. `tester_linux64`). - -const ncores = Sys.CPU_THREADS -@info "" Sys.CPU_THREADS -@info "" ncores - -script_native_yes = """ - Base.runtests(["cmdlineargs"]; ncores = $(ncores)) -""" -script_native_no = """ - Base.runtests(["all", "--skip", "cmdlineargs"]; ncores = $(ncores)) -""" - -base_cmd = `$(Base.julia_cmd()) --code-coverage=all` -cmd_native_yes = `$(base_cmd) --sysimage-native-code=yes -e $(script_native_yes)` -cmd_native_no = `$(base_cmd) --sysimage-native-code=no -e $(script_native_no)` - -@info "Running command" cmd_native_yes -p1 = run(pipeline(cmd_native_yes; stdin, stdout, stderr); wait = false) -wait(p1) - -@info "Running command" cmd_native_no -p2 = run(pipeline(cmd_native_no; stdin, stdout, stderr); wait = false) -wait(p2) diff --git a/.buildkite/pipelines/scheduled/coverage/upload_coverage.jl b/.buildkite/pipelines/scheduled/coverage/upload_coverage.jl deleted file mode 100644 index d995e97fc17fb..0000000000000 --- a/.buildkite/pipelines/scheduled/coverage/upload_coverage.jl +++ /dev/null @@ -1,228 +0,0 @@ -empty!(Base.DEPOT_PATH) -push!(Base.DEPOT_PATH, mktempdir(; cleanup = true)) - -import Pkg -import Logging -import TOML - -Pkg.add(; name = "Coverage", uuid = "a2441757-f6aa-5fb2-8edb-039e3f45d037", version = "1") -Pkg.precompile() - -import Coverage - -function process_folders() - # `Coverage.process_folder` will have a LOT of `@info` statements that will make the log - # way too long. So before we run `Coverage.process_folder`, we disable logging for `@info` - # statements. After we run `Coverage.process_folder`, we re-enable logging for `@info` - # statements. - Logging.disable_logging(Logging.Info) - fcs_base = Coverage.process_folder("base"); - fcs_stdlib = Coverage.process_folder("stdlib"); - Logging.disable_logging(Logging.Debug) - - fcs = Coverage.merge_coverage_counts( - fcs_base, - fcs_stdlib, - ); - - return fcs -end - -function get_external_stdlib_names(stdlib_dir::AbstractString) - filename_list = filter(x -> isfile(joinpath(stdlib_dir, x)), readdir(stdlib_dir)) - # find all of the files like `Pkg.version`, `Statistics.version`, etc. - regex_matches_or_nothing = match.(Ref(r"^([\w].*?)\.version$"), filename_list) - regex_matches = filter(x -> x !== nothing, regex_matches_or_nothing) - # get the names of the external stdlibs, like `Pkg`, `Statistics`, etc. - external_stdlib_names = only.(regex_matches) - unique!(external_stdlib_names) - sort!(external_stdlib_names) - @info "# Begin list of external stdlibs" - for (i, x) in enumerate(external_stdlib_names) - @info "$(i). $(x)" - end - @info "# End list of external stdlibs" - return external_stdlib_names -end - -function get_external_stdlib_prefixes(stdlib_dir::AbstractString) - external_stdlib_names = get_external_stdlib_names(stdlib_dir) - prefixes_1 = joinpath.(Ref(stdlib_dir), external_stdlib_names, Ref("")) - prefixes_2 = joinpath.(Ref(stdlib_dir), string.(external_stdlib_names, Ref("-"))) - prefixes = vcat(prefixes_1, prefixes_2) - unique!(prefixes) - sort!(prefixes) - # example of what `prefixes` might look like: - # 4-element Vector{String}: - # "stdlib/Pkg-" - # "stdlib/Pkg/" - # "stdlib/Statistics-" - # "stdlib/Statistics/" - return prefixes -end - -function print_coverage_summary(fc::Coverage.FileCoverage) - cov_lines, tot_lines = Coverage.get_summary(fc) - if cov_lines == tot_lines == 0 - cov_pct = 0 - else - cov_pct = floor(Int, cov_lines/tot_lines * 100) - end - pad_1 = 71 - pad_2 = 15 - pad_3 = 15 - col_1 = rpad(fc.filename, pad_1) - col_2 = rpad(string(cov_pct, " %"), pad_2) - col_3 = string( - rpad(string(cov_lines), pad_3), - string(tot_lines), - ) - @info "$(col_1) $(col_2) $(col_3)" - return nothing -end - -function print_coverage_summary( - fcs::Vector{Coverage.FileCoverage}, description::AbstractString, - ) - cov_lines, tot_lines = Coverage.get_summary(fcs) - if cov_lines == tot_lines == 0 - cov_pct = 0 - else - cov_pct = floor(Int, cov_lines/tot_lines * 100) - end - @info "$(description): $(cov_pct)% ($(cov_lines)/$(tot_lines))" - return (; cov_pct) -end - -function buildkite_env(name::String) - value = String(strip(ENV[name])) - if isempty(value) - throw(ErrorException("environment variable $(name) is empty")) - end - return value -end - -function buildkite_env(name_1::String, name_2::String, default::String) - value_1 = String(strip(ENV[name_1])) - value_2 = String(strip(ENV[name_2])) - !isempty(value_1) && return value_1 - !isempty(value_2) && return value_2 - return default -end - -function buildkite_branch_and_commit() - branch = buildkite_env("BUILDKITE_BRANCH") - commit = buildkite_env("BUILDKITE_COMMIT") - head_rev_parse = String(strip(read(`git rev-parse HEAD`, String))) - if strip(commit) == "HEAD" - commit = head_rev_parse - end - if commit !== head_rev_parse - msg = "mismatch" - @error msg commit head_rev_parse - throw(ErrorException(msg)) - end - if !occursin(r"^[a-f0-9]{40}$", commit) - msg = "BUILDKITE_COMMIT does not look like a long commit SHA" - @error msg commit - throw(ErrorException(msg)) - end - return (; branch, commit) -end - -function codecov_buildkite_add_local_to_kwargs() - branch, commit = buildkite_branch_and_commit() - kwargs = Coverage.Codecov.set_defaults( - Dict(); - branch, - commit, - ) - return kwargs -end - -function coveralls_buildkite_query_git_info() - branch, commit = buildkite_branch_and_commit() - remote_name = "origin" - remote = buildkite_env("BUILDKITE_REPO") - message = buildkite_env("BUILDKITE_MESSAGE") - author_name = buildkite_env( - "BUILDKITE_BUILD_AUTHOR", - "BUILDKITE_BUILD_CREATOR", - "", - ) - author_email = buildkite_env( - "BUILDKITE_BUILD_AUTHOR_EMAIL", - "BUILDKITE_BUILD_CREATOR_EMAIL", - "", - ) - remotes = [ - Dict( - "name" => remote_name, - "url" => remote, - ) - ] - head = Dict( - "id" => commit, - "author_name" => author_name, - "author_email" => author_email, - "committer_name" => author_name, - "committer_email" => author_email, - "message" => message, - ) - git_info = Dict( - "branch" => branch, - "remotes" => remotes, - "head" => head, - ) - return git_info -end - -const fcs = process_folders() - -# Only include source code files. Exclude test files, benchmarking files, etc. -filter!(fcs) do fc - occursin(r"^base\/", fc.filename) || occursin("/src/", fc.filename) -end; - -# Exclude all external stdlibs (stdlibs that live in external repos). -const external_stdlib_prefixes = get_external_stdlib_prefixes("stdlib") -filter!(fcs) do fc - all(x -> !startswith(fc.filename, x), external_stdlib_prefixes) -end; - -# Exclude all stdlib JLLs (stdlibs of the form `stdlib/*_jll/`). -filter!(fcs) do fc - !occursin(r"^stdlib\/[A-Za-z0-9]*?_jll\/", fc.filename) -end; - -sort!(fcs; by = fc -> fc.filename); - -print_coverage_summary.(fcs); -const total_cov_pct = print_coverage_summary(fcs, "Total").cov_pct - -let - git_info = coveralls_buildkite_query_git_info() - @info "" git_info - @info "" git_info["branch"] - @info "" git_info["head"] - - # In order to upload to Coveralls, you need to have the `COVERALLS_TOKEN` environment variable defined. - Coverage.Coveralls.submit_local(fcs, git_info) -end - -let - kwargs = codecov_buildkite_add_local_to_kwargs() - @info "" kwargs - - # In order to upload to Codecov, you need to have the `CODECOV_TOKEN` environment variable defined. - Coverage.Codecov.submit_generic(fcs, kwargs) -end - -if total_cov_pct < 50 - msg = string( - "The total coverage is less than 50%. This should never happen, ", - "so it means that something has probably gone wrong with the code coverage job.", - ) - @error msg total_cov_pct - throw(ErrorException(msg)) -end diff --git a/.buildkite/pipelines/scheduled/launch_unsigned_jobs.yml b/.buildkite/pipelines/scheduled/launch_unsigned_jobs.yml deleted file mode 100644 index 300c8d8466aea..0000000000000 --- a/.buildkite/pipelines/scheduled/launch_unsigned_jobs.yml +++ /dev/null @@ -1,8 +0,0 @@ -steps: - - label: ":buildkite: Launch unsigned jobs" - commands: | - # Launch all of the `USE_BINARYBUILDER=0` jobs. - bash .buildkite/utilities/platforms/platforms.sh .buildkite/pipelines/scheduled/no_bb/no_bb_package_linux.arches .buildkite/pipelines/main/platforms/package_linux.yml - bash .buildkite/utilities/platforms/platforms.sh .buildkite/pipelines/scheduled/no_bb/no_bb_tester_linux.arches .buildkite/pipelines/main/platforms/tester_linux.yml - agents: - queue: julia diff --git a/.buildkite/pipelines/scheduled/no_bb/no_bb_package_linux.arches b/.buildkite/pipelines/scheduled/no_bb/no_bb_package_linux.arches deleted file mode 100644 index dff2aab4591e2..0000000000000 --- a/.buildkite/pipelines/scheduled/no_bb/no_bb_package_linux.arches +++ /dev/null @@ -1,2 +0,0 @@ -# PLATFORM LABEL GROUP ALLOW_FAIL ARCH ARCH_ROOTFS MAKE_FLAGS TIMEOUT_BK TIMEOUT_RR RETRIES IS_RR IS_ST IS_MT ROOTFS_TAG ROOTFS_HASH -linux 64src . . 64src x86_64 USE_BINARYBUILDER=0 180 . . . . . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a diff --git a/.buildkite/pipelines/scheduled/no_bb/no_bb_tester_linux.arches b/.buildkite/pipelines/scheduled/no_bb/no_bb_tester_linux.arches deleted file mode 100644 index 0b1fbdf63b796..0000000000000 --- a/.buildkite/pipelines/scheduled/no_bb/no_bb_tester_linux.arches +++ /dev/null @@ -1,10 +0,0 @@ -# PLATFORM LABEL GROUP ALLOW_FAIL ARCH ARCH_ROOTFS MAKE_FLAGS TIMEOUT_BK TIMEOUT_RR RETRIES IS_RR IS_ST IS_MT ROOTFS_TAG ROOTFS_HASH -linux 64src_g1_mt g1 . 64src x86_64 . . . . . . yes v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64src_g2_mt g2 . 64src x86_64 . . . 3 . . yes v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a - -linux 64src_g1_st g1 . 64src x86_64 . . . . . yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64src_g2_st g2 . 64src x86_64 . . . 3 . yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a - -linux 64src_g1_rrst g1 . 64src x86_64 . 300 240 . yes yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64src_g2_rrst g2 . 64src x86_64 . 180 120 3 yes yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a -linux 64src_g3_st g3 . 64src x86_64 . . . 3 . yes . v4.8 2a058481b567f0e91b9aa3ce4ad4f09e6419355a diff --git a/.buildkite/secrets/.gitignore b/.buildkite/secrets/.gitignore deleted file mode 100644 index 2a84f48682a04..0000000000000 --- a/.buildkite/secrets/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Ignore everything -* - -# Don't ignore this `.gitignore` file -!.gitignore - -# Don't ignore encrypted files -!*.encrypted - -# Don't ignore public keys, that's fine to include -!*.pub diff --git a/.buildkite/secrets/ssh_docs_deploy.encrypted b/.buildkite/secrets/ssh_docs_deploy.encrypted deleted file mode 100644 index 8b7e2ffe27940..0000000000000 Binary files a/.buildkite/secrets/ssh_docs_deploy.encrypted and /dev/null differ diff --git a/.buildkite/secrets/ssh_docs_deploy.pub b/.buildkite/secrets/ssh_docs_deploy.pub deleted file mode 100644 index eaea073e5257b..0000000000000 --- a/.buildkite/secrets/ssh_docs_deploy.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2HupO7+uq6NE//ZCCYS1szwDVutH8ZVtF0wjJJGmfRLzDe6l2Kcx+CY+i3k2HoxrfNlmri3RdWrfDbEruPZlWaz9HH/Hi8S8ZkyQO932dbag7u5JGKw3Mb/3x05O2QaL+0HGItUyfFMFT9NBZ1na+AH/ZPWamXR98PLh39ic1HFw2x2hacYA/4w0ylxwrojRfCqcjK/YVJUCdQ/XwsmSjs+0+rIfdVwSLbJKeHj5JYLX6CmF4zf4WzJKKDXx1k6gwaSS6oY5XOVit2I1u80cxZRiQhrMfYPKywY5+Y6gqjrGABLYSq/JJRKsgdJxs39V8O1ZjXVsGxbR+1r3F9ISH buildkite-docs-deploy diff --git a/.buildkite/utilities/platforms/platforms.sh b/.buildkite/utilities/platforms/platforms.sh deleted file mode 100755 index 9a47c18e9855b..0000000000000 --- a/.buildkite/utilities/platforms/platforms.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -ARCHES="$1" -YAML="$2" - -if [[ ! -f "${ARCHES:?}" ]] ; then - echo "Arches file does not exist: ${ARCHES:?}" - exit 1 -fi - -if [[ ! -f "${YAML:?}" ]] ; then - echo "YAML file does not exist: ${YAML:?}" - exit 1 -fi - -cat "${ARCHES:?}" | tr -s ' ' | while read _line; do - # Remove whitespace from the beginning and end of each line - line=`echo $_line | tr -s ' '` - - # Skip any line that begins with the `#` character - if [[ $line == \#* ]]; then - continue - fi - - # Skip any empty line - if [[ $line == "" ]]; then - continue - fi - - export PLATFORM=`echo $line | cut -d ' ' -f 1 | tr -s ' '` - export LABEL=`echo $line | cut -d ' ' -f 2 | tr -s ' '` - export GROUP=`echo $line | cut -d ' ' -f 3 | tr -s ' '` - - export ALLOW_FAIL=`echo $line | cut -d ' ' -f 4 | tr -s ' '` - export ARCH=`echo $line | cut -d ' ' -f 5 | tr -s ' '` - export ARCH_ROOTFS=`echo $line | cut -d ' ' -f 6 | tr -s ' '` - - export MAKE_FLAGS=`echo $line | cut -d ' ' -f 7 | tr -s ' '` - export TIMEOUT_BK=`echo $line | cut -d ' ' -f 8 | tr -s ' '` - export TIMEOUT_RR=`echo $line | cut -d ' ' -f 9 | tr -s ' '` - export RETRIES=`echo $line | cut -d ' ' -f 10 | tr -s ' '` - export IS_RR=`echo $line | cut -d ' ' -f 11 | tr -s ' '` - export IS_ST=`echo $line | cut -d ' ' -f 12 | tr -s ' '` - export IS_MT=`echo $line | cut -d ' ' -f 13 | tr -s ' '` - export ROOTFS_TAG=`echo $line | cut -d ' ' -f 14 | tr -s ' '` - export ROOTFS_HASH=`echo $line | cut -d ' ' -f 15 | tr -s ' '` - - if [[ "${IS_ST:?}" == "yes" ]]; then - if [[ "${IS_MT:?}" == "yes" ]]; then - echo "You cannot set both IS_ST and IS_MT to yes" - exit 1 - fi - fi - - if [[ "${ALLOW_FAIL:?}" == "." ]]; then - export ALLOW_FAIL="false" - fi - - if [[ "${MAKE_FLAGS:?}" == "." ]]; then - export MAKE_FLAGS="" - fi - - if [[ "${TIMEOUT_BK:?}" == "." ]]; then - export TIMEOUT_BK="90" # minutes - fi - - if [[ "${TIMEOUT_RR:?}" == "." ]]; then - export TIMEOUT_RR="60" # minutes - fi - - if [[ "${RETRIES:?}" == "." ]]; then - export RETRIES="0" - fi - - buildkite-agent pipeline upload "${YAML:?}" -done diff --git a/.buildkite/utilities/rr/rr_capture.jl b/.buildkite/utilities/rr/rr_capture.jl deleted file mode 100644 index ef46ce3bdd208..0000000000000 --- a/.buildkite/utilities/rr/rr_capture.jl +++ /dev/null @@ -1,201 +0,0 @@ -import Dates -import Pkg -import Tar - -function get_bool_from_env(name::AbstractString, default_value::Bool) - value = get(ENV, name, "$(default_value)") |> strip |> lowercase - result = parse(Bool, value)::Bool - return result -end - -const is_buildkite = get_bool_from_env("BUILDKITE", false) -const always_save_rr_trace = get_bool_from_env("JULIA_ALWAYS_SAVE_RR_TRACE", false) - -function get_from_env(name::AbstractString) - if is_buildkite - value = ENV[name] - else - value = get(ENV, name, "") - end - result = convert(String, strip(value))::String - return result -end - -function my_exit(process::Base.Process) - wait(process) - - @info( - "", - process.exitcode, - process.termsignal, - ) - - # Pass the exit code back up - if process.termsignal != 0 - ccall(:raise, Cvoid, (Cint,), process.termsignal) - - # If for some reason the signal did not cause an exit, we'll exit manually. - # We need to make sure that we exit with a non-zero exit code. - if process.exitcode != 0 - exit(process.exitcode) - else - exit(1) - end - end - exit(process.exitcode) -end - -if Base.VERSION < v"1.6" - throw(ErrorException("The `$(basename(@__FILE__))` script requires Julia 1.6 or greater")) -end - -if length(ARGS) < 1 - throw(ErrorException("Usage: julia $(basename(@__FILE__)) [command...]")) -end - -@info "We will run the command under rr" - -const build_number = get_from_env("BUILDKITE_BUILD_NUMBER") -const job_name = get_from_env("BUILDKITE_STEP_KEY") -const commit_full = get_from_env("BUILDKITE_COMMIT") -const commit_short = first(commit_full, 10) -const JULIA_TEST_RR_TIMEOUT = get(ENV, "JULIA_TEST_RR_TIMEOUT", "120") -const timeout_minutes = parse(Int, JULIA_TEST_RR_TIMEOUT) -const JULIA_TEST_NUM_CORES = get(ENV, "JULIA_TEST_NUM_CORES", "8") -const julia_test_num_cores_int = parse(Int, JULIA_TEST_NUM_CORES) -const num_cores = min( - 8, - Sys.CPU_THREADS, - julia_test_num_cores_int + 1, -) - -ENV["JULIA_RRCAPTURE_NUM_CORES"] = "$(num_cores)" - -@info( - "", - build_number, - job_name, - commit_full, - commit_short, - timeout_minutes, - num_cores, -) - -const dumps_dir = joinpath(pwd(), "dumps") -const temp_parent_dir = joinpath(pwd(), "temp_for_rr") - -mkpath(dumps_dir) -mkpath(temp_parent_dir) - -proc = nothing - -mktempdir(temp_parent_dir) do dir - Pkg.activate(dir) - Pkg.add("rr_jll") - Pkg.add("Zstd_jll") - - rr_jll = Base.require(Base.PkgId(Base.UUID((0xe86bdf43_55f7_5ea2_9fd0_e7daa2c0f2b4)), "rr_jll")) - zstd_jll = Base.require(Base.PkgId(Base.UUID((0x3161d3a3_bdf6_5164_811a_617609db77b4)), "Zstd_jll")) - rr(func) = Base.invokelatest(rr_jll.rr, func; adjust_LIBPATH=false) - rr() do rr_path - capture_script_path = joinpath(dir, "capture_output.sh") - loader = Sys.WORD_SIZE == 64 ? "/lib64/ld-linux-x86-64.so.2" : "/lib/ld-linux.so.2" - open(capture_script_path, "w") do io - write(io, """ - #!/bin/bash - - $(rr_path) record --nested=detach "\$@" > >(tee -a $(dir)/stdout.log) 2> >(tee -a $(dir)/stderr.log >&2) - """) - end - chmod(capture_script_path, 0o755) - - new_env = copy(ENV) - new_env["_RR_TRACE_DIR"] = joinpath(dir, "rr_traces") - new_env["RR_LOG"] = "all:debug" - new_env["RR_UNDER_RR_LOG"] = "all:debug" - new_env["RR_LOG_BUFFER"]="100000" - new_env["JULIA_RR"] = capture_script_path - t_start = time() - global proc = run(setenv(`$(rr_path) record --num-cores=$(num_cores) $ARGS`, new_env), (stdin, stdout, stderr); wait=false) - - # Start asynchronous timer that will kill `rr` - @async begin - sleep(timeout_minutes * 60) - - # If we've exceeded the timeout and `rr` is still running, kill it. - if isopen(proc) - println(stderr, "\n\nProcess timed out (with a timeout of $(timeout_minutes) minutes). Signalling `rr` for force-cleanup!") - kill(proc, Base.SIGTERM) - - # Give `rr` a chance to cleanup and upload. - # Note: this time period includes the time to upload the `rr` trace files - # as Buildkite artifacts, so make sure it is long enough to allow the - # uploads to finish. - cleanup_minutes = 30 - sleep(cleanup_minutes * 60) - - if isopen(proc) - println(stderr, "\n\n`rr` failed to cleanup and upload within $(cleanup_minutes) minutes, killing and exiting immediately!") - kill(proc, Base.SIGKILL) - exit(1) - end - end - end - - # Wait for `rr` to finish, either through naturally finishing its run, or `SIGTERM`. - wait(proc) - process_failed = !success(proc) - - if process_failed || always_save_rr_trace || is_buildkite - println(stderr, "`rr` returned $(proc.exitcode), packing and uploading traces...") - - if !isdir(joinpath(dir, "rr_traces")) - println(stderr, "No `rr_traces` directory! Did `rr` itself fail?") - exit(1) - end - - # Clean up non-traces - rm(joinpath(dir, "rr_traces", "latest-trace")) - rm(joinpath(dir, "rr_traces", "cpu_lock")) - - # Create a directory for the pack files to go - pack_dir = joinpath(dir, "pack") - mkdir(pack_dir) - - # Pack all traces - trace_dirs = [joinpath(dir, "rr_traces", f) for f in readdir(joinpath(dir, "rr_traces"))] - filter!(isdir, trace_dirs) - run(ignorestatus(`$(rr_path) pack --pack-dir=$pack_dir $(trace_dirs)`)) - - # Tar it up - mkpath(dumps_dir) - date_str = Dates.format(Dates.now(), Dates.dateformat"yyyy_mm_dd_HH_MM_SS") - dst_file_name = string( - "rr", - "--build_$(build_number)", - "--$(job_name)", - "--commit_$(commit_short)", - "--$(date_str)", - ".tar.zst", - ) - dst_full_path = joinpath(dumps_dir, dst_file_name) - zstd_jll.zstdmt() do zstdp - tarproc = open(`$(zstdp) -o $(dst_full_path)`, "w") - Tar.create(dir, tarproc) - close(tarproc.in) - end - - @info "The `rr` trace file has been saved to: $(dst_full_path)" - if is_buildkite - @info "Since this is a Buildkite run, we will upload the `rr` trace file." - cd(dumps_dir) do - run(`buildkite-agent artifact upload $(dst_file_name)`) - end - end - end - - end -end - -@info "Finished running the command under rr" -my_exit(proc) diff --git a/.clangd b/.clangd new file mode 100644 index 0000000000000..534bd8fa45fb9 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-I., -I.., -Iflisp, -Isupport, -I../support, -I../usr/include, -I../../usr/include, -Wall,] diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..3af8ba86153a1 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,9 @@ +# .git-blame-ignore-revs +# whitespace: end text files with single newlines +3903fa54a638d4546ef50e56f91f0705a8ab11ef +# whitespace: use only UNIX line endings (\n) +e66bfa5dd32f93e76068c00ad882c1fc839c5af8 +# whitespace: replace non-breaking space => space +100a741e7ab38c91d48cc929bb001afc8e09261f +# whitespace: replace tabs => space +b03e8ab9c7bd3e001add519571858fa04d6a249b diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d2da8839ddb39..bf1380f5a07bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,5 +2,5 @@ CODEOWNERS @JuliaLang/github-actions /.github/ @JuliaLang/github-actions /.buildkite/ @JuliaLang/github-actions -/.github/workflows/retry.yml @DilumAluthge +/.github/workflows/rerun_failed.yml @DilumAluthge /.github/workflows/statuses.yml @DilumAluthge diff --git a/.github/workflows/LabelCheck.yml b/.github/workflows/LabelCheck.yml new file mode 100644 index 0000000000000..194b0c92065c9 --- /dev/null +++ b/.github/workflows/LabelCheck.yml @@ -0,0 +1,19 @@ +name: Labels + +permissions: + contents: read +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, edited, synchronize] +jobs: + enforce-labels: + name: Check for blocking labels + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - uses: yogevbd/enforce-label-action@2.2.2 + with: + # REQUIRED_LABELS_ANY: "bug,enhancement,skip-changelog" + # REQUIRED_LABELS_ANY_DESCRIPTION: "Select at least one label ['bug','enhancement','skip-changelog']" + BANNED_LABELS: "needs docs,needs compat annotation,needs more info,needs nanosoldier run,needs news,needs pkgeval,needs tests,DO NOT MERGE" + BANNED_LABELS_DESCRIPTION: "A PR should not be merged with `needs *` or `DO NOT MERGE` labels" diff --git a/.github/workflows/rerun_failed.yml b/.github/workflows/rerun_failed.yml deleted file mode 100644 index 7d022920658a9..0000000000000 --- a/.github/workflows/rerun_failed.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Please ping @DilumAluthge when making any changes to this file. - -# Here are some steps that we take in this workflow file for security reasons: -# 1. We do not checkout any code. -# 2. We only run actions that are defined in a repository in the `JuliaLang` GitHub organization. -# 3. We do not give the `GITHUB_TOKEN` any permissions. -# 4. We only give the Buildkite API token (`BUILDKITE_API_TOKEN_RETRY`) the minimum necessary -# set of permissions. - -# Important note to Buildkite maintainers: -# In order to make this work, you need to tell Buildkite that it should NOT create a brand-new -# build when someone closes and reopens a pull request. To do so: -# 1. Go to the relevant pipeline (e.g. https://buildkite.com/julialang/julia-master). -# 2. Click on the "Pipeline Settings" button. -# 3. In the left sidebar, under "Pipeline Settings", click on "GitHub". -# 4. In the "GitHub Settings", under "Build Pull Requests", make sure that the "Skip pull -# request builds for existing commits" checkbox is checked. This is the setting that tells -# Buildkite that it should NOT create a brand-new build when someone closes and reopens a -# pull request. -# 5. At the bottom of the page, click the "Save GitHub Settings" button. - -name: Rerun Failed Buildkite Jobs - -# There are two ways that a user can rerun the failed Buildkite jobs: -# 1. Close and reopen the pull request. -# In order to use this approach, the user must be in one of the following three categories: -# (i) Author of the pull request -# (ii) Commit permissions -# (iii) Triage permissions -# 2. Post a comment on the pull request with exactly the following contents: /buildkite rerun failed -# In order to use this approach, the user must be in the following category: -# - A member of the JuliaLang GitHub organization (the membership must be publicized) - -on: - # When using the `pull_request_target` event, all PRs will get access to secret environment - # variables (such as the `BUILDKITE_API_TOKEN_RETRY` secret environment variable), even if - # the PR is from a fork. Therefore, for security reasons, we do not checkout any code in - # this workflow. - pull_request_target: - types: [ reopened ] - issue_comment: - types: [ created ] - -# We do not give the `GITHUB_TOKEN` any permissions. -# Therefore, the `GITHUB_TOKEN` only has the same access as any member of the public. -permissions: - contents: none - -jobs: - rerun-failed-buildkite-jobs: - name: Rerun Failed Buildkite Jobs - runs-on: ubuntu-latest - if: (github.repository == 'JuliaLang/julia') && ((github.event_name == 'pull_request_target' && github.event.action == 'reopened') || (github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.comment.body == '/buildkite rerun failed')) - steps: - # For security reasons, we do not checkout any code in this workflow. - - name: Check organization membership - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then - if [[ "${{ github.event.action }}" == "reopened" ]]; then - echo "This is a \"reopened\" event, so we do not need to check the user's organization membership." - echo "GOOD_TO_PROCEED=yes" >> ${GITHUB_ENV:?} - echo "PULL_REQUEST_NUMBER=${{ github.event.number }}" >> ${GITHUB_ENV:?} - else - echo "ERROR: The github.event_name is \"pull_request_target\", but the github.event.action is not \"reopened\"." - exit 1 - fi - else - curl -H "Authorization: token ${GITHUB_TOKEN:?}" "https://api.github.com/users/${{ github.event.sender.login }}" - curl -H "Authorization: token ${GITHUB_TOKEN:?}" "https://api.github.com/users/${{ github.event.sender.login }}/orgs" - export USER_IS_ORGANIZATION_MEMBER=`curl -H "Authorization: token ${GITHUB_TOKEN:?}" "https://api.github.com/users/${{ github.event.sender.login }}/orgs" | jq '[.[] | .login] | index("JuliaLang") != null' | tr -s ' '` - if [[ "${USER_IS_ORGANIZATION_MEMBER:?}" == "true" ]]; then - echo "The \"${{ github.event.sender.login }}\" user is a public member of the JuliaLang organization." - echo "GOOD_TO_PROCEED=yes" >> ${GITHUB_ENV:?} - echo "PULL_REQUEST_NUMBER=${{ github.event.issue.number }}" >> ${GITHUB_ENV:?} - else - echo "ERROR: the \"${{ github.event.sender.login }}\" user is NOT a public member of the JuliaLang organization." - echo "If you are a member, please make sure that you have publicized your membership." - exit 1 - fi - fi - - run: | - echo "GOOD_TO_PROCEED: ${{ env.GOOD_TO_PROCEED }}" - echo "PULL_REQUEST_NUMBER: ${{ env.PULL_REQUEST_NUMBER }}" - - uses: JuliaLang/buildkite-rerun-failed@057f6f2d37aa29a57b7679fd2af0df1d9f9188b4 - if: env.GOOD_TO_PROCEED == 'yes' - with: - buildkite_api_token: ${{ secrets.BUILDKITE_API_TOKEN_RETRY }} - buildkite_organization_slug: 'julialang' - buildkite_pipeline_slug: 'julia-master' - pr_number: ${{ env.PULL_REQUEST_NUMBER }} diff --git a/.github/workflows/statuses.yml b/.github/workflows/statuses.yml deleted file mode 100644 index 16c07f0f040cc..0000000000000 --- a/.github/workflows/statuses.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Please ping @DilumAluthge when making any changes to this file. - -# This is just a short-term solution until we have migrated all of CI to Buildkite. -# -# 1. TODO: delete this file once we have migrated all of CI to Buildkite. - -# Here are some steps that we take in this workflow file for security reasons: -# 1. We do not checkout any code. -# 2. We do not run any external actions. -# 3. We only give the `GITHUB_TOKEN` the minimum necessary set of permissions. - -name: Create Buildbot Statuses - -on: - push: - branches: - - 'master' - - 'release-*' - # When using the `pull_request_target` event, all PRs will get a `GITHUB_TOKEN` that has - # write permissions, even if the PR is from a fork. - # Therefore, for security reasons, we do not checkout any code in this workflow. - pull_request_target: - types: [opened, synchronize] - branches: - - 'master' - - 'release-*' - -# These are the permissions for the `GITHUB_TOKEN`. -# We should only give the token the minimum necessary set of permissions. -permissions: - statuses: write - -jobs: - create-buildbot-statuses: - name: Create Buildbot Statuses - runs-on: ubuntu-latest - if: github.repository == 'JuliaLang/julia' - steps: - # For security reasons, we do not checkout any code in this workflow. - - run: echo "SHA=${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV - if: github.event_name == 'pull_request_target' - - run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV - if: github.event_name != 'pull_request_target' - - run: echo "The SHA is ${{ env.SHA }}" - - # As we incrementally migrate individual jobs from Buildbot to Buildkite, we should - # remove them from the `context_list`. - - run: | - declare -a CONTEXT_LIST=( - "buildbot/tester_freebsd64" - "buildbot/tester_macos64" - "buildbot/tester_win32" - "buildbot/tester_win64" - ) - for CONTEXT in "${CONTEXT_LIST[@]}" - do - curl \ - -X POST \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - -d "{\"context\": \"$CONTEXT\", \"state\": \"$STATE\"}" \ - https://api.github.com/repos/JuliaLang/julia/statuses/${{ env.SHA }} - done - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - STATE: "pending" diff --git a/.gitignore b/.gitignore index ca14ec31874d4..0368b7d19efa0 100644 --- a/.gitignore +++ b/.gitignore @@ -33,9 +33,13 @@ .DS_Store .idea/* .vscode/* +*.heapsnapshot +.cache +# Buildkite: Ignore the entire .buildkite directory +/.buildkite -# Buildkite: cryptic plugin -# Ignore the unencrypted repo_key +# Buildkite: Ignore the unencrypted repo_key repo_key -# Ignore any agent keys (public or private) we have stored + +# Buildkite: Ignore any agent keys (public or private) we have stored agent_key* diff --git a/.mailmap b/.mailmap index f0d2d13c91d23..5335c88a63d7d 100644 --- a/.mailmap +++ b/.mailmap @@ -282,4 +282,4 @@ Daniel Karrasch Daniel Karrasch Roger Luo -Roger Luo \ No newline at end of file +Roger Luo diff --git a/CITATION.cff b/CITATION.cff index a25d61b69d849..c88727bcfa311 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -37,4 +37,4 @@ preferred-citation: issue: 1 year: 2017 publisher: - - name: "SIAM" + name: "SIAM" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c924b2cdabb9..099ef6b03509b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,9 +58,9 @@ A useful bug report filed as a GitHub issue provides information about how to re ### Writing tests -There are never enough tests. Track [code coverage at Coveralls](https://coveralls.io/r/JuliaLang/julia), and help improve it. +There are never enough tests. Track [code coverage at Codecov](https://codecov.io/github/JuliaLang/julia), and help improve it. -1. Go visit https://coveralls.io/r/JuliaLang/julia. +1. Go visit https://codecov.io/github/JuliaLang/julia. 2. Browse through the source files and find some untested functionality (highlighted in red) that you think you might be able to write a test for. @@ -74,7 +74,7 @@ There are never enough tests. Track [code coverage at Coveralls](https://coveral * You can see the current buildbot setup at: https://build.julialang.org/builders * [Issue 9493](https://github.com/JuliaLang/julia/issues/9493) and [issue 11885](https://github.com/JuliaLang/julia/issues/11885) have more detailed discussion on code coverage. -Coveralls shows functionality that still needs "proof of concept" tests. These are important, as are tests for tricky edge cases, such as converting between integer types when the number to convert is near the maximum of the range of one of the integer types. Even if a function already has some coverage on Coveralls, it may still benefit from tests for edge cases. +Code coverage shows functionality that still needs "proof of concept" tests. These are important, as are tests for tricky edge cases, such as converting between integer types when the number to convert is near the maximum of the range of one of the integer types. Even if a function already has some coverage on Codecov, it may still benefit from tests for edge cases. ### Improving documentation @@ -201,11 +201,12 @@ Add new code to Julia's base libraries as follows (this is the "basic" approach; Build as usual, and do `make clean testall` to test your contribution. If your contribution includes changes to Makefiles or external dependencies, make sure you can build Julia from a clean tree using `git clean -fdx` or equivalent (be careful – this command will delete any files lying around that aren't checked into git). -Note: You can run specific test files with `make`: +#### Running specific tests +There are `make` targets for running specific tests: make test-bitarray -or with the `runtests.jl` script, e.g. to run `test/bitarray.jl` and `test/math.jl`: +You can also use the `runtests.jl` script, e.g. to run `test/bitarray.jl` and `test/math.jl`: ./usr/bin/julia test/runtests.jl bitarray math @@ -242,15 +243,33 @@ If you need to restart your Julia session, just start at step 2 above. built and incorporate them automatically. You only need to rebuild Julia if you made code-changes that Revise cannot handle. -For convenience, there are also `test-revise-*` targets for every `test-*` -target that use Revise to load any modifications to Base into the current -process before running the corresponding test. This can be useful as a shortcut +For convenience, there are also `test-revise-*` targets for every [`test-*` +target](https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md#running-specific-tests) that use Revise to load any modifications to Base into the current +system image before running the corresponding test. This can be useful as a shortcut on the command line (since tests aren't always designed to be run outside the runtest harness). +### Contributing to the standard library + +The standard library (stdlib) packages are baked into the Julia system image. +When running the ordinary test workflow on the stdlib packages, the system image +version overrides the version you are developing. +To test stdlib packages, you can do the following steps: + +1. Edit the UUID field of the `Project.toml` in the stdlib package +2. Change the current directory to the directory of the stdlib you are developing +3. Start julia with `julia --project=.` +4. You can now test the package by running `pkg> test` in Pkg mode. + +Because you changed the UUID, the package manager treats the stdlib package as +different from the one in the system image, and the system image version will +not override the package. + +Be sure to change the UUID value back before making the pull request. + ### Contributing to patch releases -The process of creating a patch release is roughly as follows: +The process of [creating a patch release](https://docs.julialang.org/en/v1/devdocs/build/distributing/#Point-releasing-101) is roughly as follows: 1. Create a new branch (e.g. `backports-release-1.6`) against the relevant minor release branch (e.g. `release-1.6`). Usually a corresponding pull request is created as well. @@ -274,7 +293,7 @@ The process of creating a patch release is roughly as follows: 6. Ping `@JuliaLang/releases` to tag the patch release and update the website. 7. Open a pull request that bumps the version of the relevant minor release to the - next prerelase patch version, e.g. as in [this pull request](https://github.com/JuliaLang/julia/pull/37724). + next prerelease patch version, e.g. as in [this pull request](https://github.com/JuliaLang/julia/pull/37724). Step 2 above, i.e. backporting commits to the `backports-release-X.Y` branch, has largely been automated via [`Backporter`](https://github.com/KristofferC/Backporter): Backporter @@ -321,12 +340,10 @@ 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, and Julia's history won't have any broken intermediate commits. A reasonable number of separate well-factored commits is fine, especially for larger changes. + - 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. - 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. - - If you see any unrelated changes to submodules like `deps/libuv`, `deps/openlibm`, etc., try running `git submodule update` first. - Descriptive commit messages are good. - Using `git add -p` or `git add -i` can be useful to avoid accidentally committing unrelated changes. - - GitHub does not send notifications when you push a new commit to a pull request, so please add a comment to the pull request thread to let reviewers know when you've made changes. - When linking to specific lines of code in discussion of an issue or pull request, hit the `y` key while viewing code on GitHub to reload the page with a URL that includes the specific version that you're viewing. That way any lines of code that you refer to will still make sense in the future, even if the content of the file changes. - Whitespace can be automatically removed from existing commits with `git rebase`. - To remove whitespace for the previous commit, run @@ -346,7 +363,7 @@ please remove the `backport-X.Y` tag from the originating pull request for the c - **Community:** - **Source code:** - **Documentation:** - - **Code coverage:** + - **Code coverage:** * Design of Julia - [Julia: A Fresh Approach to Numerical Computing](https://julialang.org/assets/research/julia-fresh-approach-BEKS.pdf) diff --git a/HISTORY.md b/HISTORY.md index 74d15b48d1b28..935b203ffaa97 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,544 @@ +Julia v1.9 Release Notes +======================== + +New language features +--------------------- + +* It is now possible to assign to bindings in another module using `setproperty!(::Module, ::Symbol, x)` ([#44137]). +* Slurping in assignments is now also allowed in non-final position. This is handled via `Base.split_rest` ([#42902]). +* Character literals now support the same syntax allowed in string literals; i.e. the syntax can + represent invalid UTF-8 sequences as allowed by the `Char` type ([#44989]). +* Support for Unicode 15 ([#47392]). +* Nested combinations of tuples and named tuples of symbols are now allowed as type parameters ([#46300]). +* New builtins `getglobal(::Module, ::Symbol[, order])` and `setglobal!(::Module, ::Symbol, x[, order])` + for reading from and writing to globals. `getglobal` should now be preferred for accessing globals over + `getfield` ([#44137]). + +Language changes +---------------- + +* The `@invoke` macro introduced in 1.7 is now exported. Additionally, it now uses `Core.Typeof(x)` + rather than `Any` when a type annotation is omitted for an argument `x` so that types passed + as arguments are handled correctly ([#45807]). +* The `invokelatest` function and `@invokelatest` macro introduced in 1.7 are now exported ([#45831]). + +Compiler/Runtime improvements +----------------------------- + +* Time to first execution (TTFX, sometimes called time to first plot) is greatly reduced. Package precompilation now + saves native code into a "pkgimage", meaning that code generated during the precompilation process will not + require compilation after package load. Use of pkgimages can be disabled via `--pkgimages=no` ([#44527]) ([#47184]). +* The known quadratic behavior of type inference is now fixed and inference uses less memory in general. + Certain edge cases with auto-generated long functions (e.g. ModelingToolkit.jl with partial + differential equations and large causal models) should see significant compile-time improvements ([#45276], [#45404]). +* Non-concrete call sites can now be union-split to be inlined or statically resolved even + if there are multiple dispatch candidates. This may improve runtime performance in certain + situations where object types are not fully known statically, by statically resolving + `@nospecialize`-d call sites and avoiding excessive compilation ([#44512]). +* All uses of the `@pure` macro in `Base` have been replaced with the now-preferred `Base.@assume_effects` ([#44776]). +* `invoke(f, invokesig, args...)` calls to a less-specific method than would normally be chosen + for `f(args...)` are no longer spuriously invalidated when loading package precompile files ([#46010]). + +Command-line option changes +--------------------------- + +* In Linux and Windows, `--threads=auto` now tries to infer the usable number of CPUs from the + process affinity which is set typically in HPC and cloud environments ([#42340]). +* `--math-mode=fast` is now a no-op ([#41638]). Users are encouraged to use the @fastmath macro instead, which has more well-defined semantics. +* The `--threads` command-line option now accepts `auto|N[,auto|M]` where `M` specifies the + number of interactive threads to create (`auto` currently means 1) ([#42302]). +* New option `--heap-size-hint=` suggests a size limit to invoke garbage collection more eagerly. + The size may be specified in bytes, kilobytes (1000k), megabytes (300M), or gigabytes (1.5G) ([#45369]). + +Multi-threading changes +----------------------- + +* `Threads.@spawn` now accepts an optional first argument: `:default` or `:interactive`. + An interactive task desires low latency and implicitly agrees to be short duration or to yield frequently. + Interactive tasks will run on interactive threads, if any are specified when Julia is started ([#42302]). +* Threads started outside the Julia runtime (e.g. from C or Java) can now become able to call into Julia code + by calling `jl_adopt_thread`. This is done automatically when entering Julia code via `cfunction` or a + `@ccallable` entry point. As a consequence, the number of threads can now change during execution ([#46609]). + +Build system changes +-------------------- + + +New library functions +--------------------- + +* New function `Iterators.flatmap` ([#44792]). +* New `pkgversion(m::Module)` function to get the version of the package that loaded + a given module, similar to `pkgdir(m::Module)` ([#45607]). +* New function `stack(x)` which generalises `reduce(hcat, x::Vector{<:Vector})` to any dimensionality, + and allows any iterator of iterators. Method `stack(f, x)` generalises `mapreduce(f, hcat, x)` and + is more efficient ([#43334]). +* New macro `@allocations` which is similar to `@allocated` except reporting the total number of allocations + rather than the total size of memory allocated ([#47367]). + +New library features +-------------------- + +* `RoundFromZero` now works for non-`BigFloat` types ([#41246]). +* `Dict` can be now shrunk manually by `sizehint!` ([#45004]). +* `@time` now separates out % time spent recompiling invalidated methods ([#45015]). + +Standard library changes +------------------------ + +* A known concurrency issue in `iterate` methods on `Dict` and other derived objects such + as `keys(::Dict)`, `values(::Dict)`, and `Set` is fixed. These methods of `iterate` can + now be called on a dictionary or set shared by arbitrary tasks provided that there are no + tasks mutating the dictionary or set ([#44534]). +* Predicate function negation `!f` now returns a composed function `(!) ∘ f` instead of an anonymous function ([#44752]). +* `eachslice` now works over multiple dimensions; `eachslice`, `eachrow` and `eachcol` return + a `Slices` object, which allows dispatching to provide more efficient methods ([#32310]). +* `@kwdef` is now exported and added to the public API ([#46273]). +* An issue with order of operations in `fld1` is now fixed ([#28973]). +* Sorting is now always stable by default, as `QuickSort` was stabilized ([#45222]). +* `Base.splat` is now exported. The return value is now a `Base.Splat` instead + of an anonymous function, which allows for pretty printing ([#42717]). + +#### Package Manager + +#### LinearAlgebra + +* The methods `a / b` and `b \ a` with `a` a scalar and `b` a vector, which were equivalent to `a * pinv(b)`, + have been removed due to the risk of confusion with elementwise division ([#44358]). +* We are now wholly reliant on libblastrampoline (LBT) for calling BLAS and LAPACK. OpenBLAS is shipped by default, + but building the system image with other BLAS/LAPACK libraries is not supported. Instead, it is recommended that + the LBT mechanism be used for swapping BLAS/LAPACK with vendor provided ones ([#44360]). +* `lu` supports a new pivoting strategy `RowNonZero()` that chooses the first non-zero pivot element, for use with + new arithmetic types and for pedagogy ([#44571]). +* `normalize(x, p=2)` now supports any normed vector space `x`, including scalars ([#44925]). +* The default number of BLAS threads is now set to the number of CPU threads on ARM CPUs, and half the number + of CPU threads on other architectures ([#45412], [#46085]). + +#### Printf + +* Error messages for bad format strings have been improved, to make it clearer what and where in the + format string is wrong ([#45366]). + +#### Profile + +* New function `Profile.take_heap_snapshot(file)` that writes a file in Chrome's JSON-based `.heapsnapshot` + format ([#46862]). + +#### Random + +* `randn` and `randexp` now work for any `AbstractFloat` type defining `rand` ([#44714]). + +#### REPL + +* `Alt-e` now opens the current input in an editor. The content (if modified) will be executed + upon exiting the editor ([#33759]). +* The contextual module which is active in the REPL can be changed (it is `Main` by default), + via the `REPL.activate(::Module)` function or via typing the module in the REPL and pressing + the keybinding Alt-m ([#33872]). +* A "numbered prompt" mode which prints numbers for each input and output and stores evaluated results in `Out` can be + activated with `REPL.numbered_prompt!()`. See the manual for how to enable this at startup ([#46474]). +* Tab completion displays available keyword arguments ([#43536]) + +#### SuiteSparse + +* Code for the SuiteSparse solver wrappers has been moved to SparseArrays.jl. Solvers are now re-exported by + SuiteSparse.jl. + +#### SparseArrays + +* SuiteSparse solvers are now available as submodules of SparseArrays (). +* UMFPACK () and CHOLMOD () thread safety are improved by + avoiding globals and using locks. Multithreaded `ldiv!` of UMFPACK objects may now be performed safely. +* An experimental function `SparseArrays.allowscalar(::Bool)` allows scalar indexing of sparse arrays to be + disabled or enabled. This function is intended to help find accidental scalar indexing of `SparseMatrixCSC` + objects, which is a common source of performance issues (). + +#### Test + +* New fail-fast mode for testsets that will terminate the test run early if a failure or error occurs. + Set either via the `@testset` kwarg `failfast=true` or by setting env var `JULIA_TEST_FAILFAST` + to `"true"` i.e. in CI runs to request the job failure be posted eagerly when issues occur ([#45317]) + +#### Dates + +* Empty strings are no longer incorrectly parsed as valid `DateTime`s, `Date`s or `Time`s and instead throw an + `ArgumentError` in constructors and `parse`, while `nothing` is returned by `tryparse` ([#47117]). + +#### Distributed + +* The package environment (active project, `LOAD_PATH`, `DEPOT_PATH`) is now propagated when adding *local* workers + (e.g. with `addprocs(N::Int)` or through the `--procs=N` command line flag) ([#43270]). +* `addprocs` for local workers now accepts the `env` keyword argument for passing environment variables to worker + processes. This was already supported for remote workers ([#43270]). + +#### Unicode + +* `graphemes(s, m:n)` returns a substring of the `m`-th to `n`-th graphemes in `s` ([#44266]). + +#### DelimitedFiles + +* DelimitedFiles has been moved out as a separate package. + +Deprecated or removed +--------------------- + + +External dependencies +--------------------- + +* On Linux, now autodetects the system libstdc++ version, and automatically loads the system library if it is newer. + The old behavior of loading the bundled libstdc++ regardless of the system version can be restored by setting the + environment variable `JULIA_PROBE_LIBSTDCXX=0` ([#46976]). +* Removed `RPATH` from the julia binary. On Linux this may break libraries that have failed to set `RUNPATH`. + +Tooling Improvements +-------------------- + +* Printing of `MethodError` and methods (such as from `methods(my_func)`) is now prettified and colored consistently + with printing of methods in stacktraces ([#45069]). + + +[#28973]: https://github.com/JuliaLang/julia/issues/28973 +[#32310]: https://github.com/JuliaLang/julia/issues/32310 +[#33759]: https://github.com/JuliaLang/julia/issues/33759 +[#33872]: https://github.com/JuliaLang/julia/issues/33872 +[#41246]: https://github.com/JuliaLang/julia/issues/41246 +[#41638]: https://github.com/JuliaLang/julia/issues/41638 +[#42302]: https://github.com/JuliaLang/julia/issues/42302 +[#42340]: https://github.com/JuliaLang/julia/issues/42340 +[#42717]: https://github.com/JuliaLang/julia/issues/42717 +[#42902]: https://github.com/JuliaLang/julia/issues/42902 +[#43270]: https://github.com/JuliaLang/julia/issues/43270 +[#43334]: https://github.com/JuliaLang/julia/issues/43334 +[#44137]: https://github.com/JuliaLang/julia/issues/44137 +[#44266]: https://github.com/JuliaLang/julia/issues/44266 +[#44358]: https://github.com/JuliaLang/julia/issues/44358 +[#44360]: https://github.com/JuliaLang/julia/issues/44360 +[#44512]: https://github.com/JuliaLang/julia/issues/44512 +[#44534]: https://github.com/JuliaLang/julia/issues/44534 +[#44571]: https://github.com/JuliaLang/julia/issues/44571 +[#44714]: https://github.com/JuliaLang/julia/issues/44714 +[#44752]: https://github.com/JuliaLang/julia/issues/44752 +[#44776]: https://github.com/JuliaLang/julia/issues/44776 +[#44792]: https://github.com/JuliaLang/julia/issues/44792 +[#44925]: https://github.com/JuliaLang/julia/issues/44925 +[#44989]: https://github.com/JuliaLang/julia/issues/44989 +[#45004]: https://github.com/JuliaLang/julia/issues/45004 +[#45015]: https://github.com/JuliaLang/julia/issues/45015 +[#45069]: https://github.com/JuliaLang/julia/issues/45069 +[#45222]: https://github.com/JuliaLang/julia/issues/45222 +[#45276]: https://github.com/JuliaLang/julia/issues/45276 +[#45317]: https://github.com/JuliaLang/julia/issues/45317 +[#45366]: https://github.com/JuliaLang/julia/issues/45366 +[#45369]: https://github.com/JuliaLang/julia/issues/45369 +[#45404]: https://github.com/JuliaLang/julia/issues/45404 +[#45412]: https://github.com/JuliaLang/julia/issues/45412 +[#45607]: https://github.com/JuliaLang/julia/issues/45607 +[#45807]: https://github.com/JuliaLang/julia/issues/45807 +[#45831]: https://github.com/JuliaLang/julia/issues/45831 +[#46010]: https://github.com/JuliaLang/julia/issues/46010 +[#46085]: https://github.com/JuliaLang/julia/issues/46085 +[#46273]: https://github.com/JuliaLang/julia/issues/46273 +[#46300]: https://github.com/JuliaLang/julia/issues/46300 +[#46474]: https://github.com/JuliaLang/julia/issues/46474 +[#46609]: https://github.com/JuliaLang/julia/issues/46609 +[#46862]: https://github.com/JuliaLang/julia/issues/46862 +[#46976]: https://github.com/JuliaLang/julia/issues/46976 +[#47367]: https://github.com/JuliaLang/julia/issues/47367 +[#47392]: https://github.com/JuliaLang/julia/issues/47392 + + +Julia v1.8 Release Notes +======================== + +New language features +--------------------- + +* Mutable struct fields may now be annotated as `const` to prevent changing them after construction, + providing for greater clarity and optimization ability of these objects ([#43305]). +* Type annotations can now be added to global variables to make accessing them type stable ([#43671]). +* Empty n-dimensional arrays can now be created using multiple semicolons inside square brackets, + e.g. `[;;;]` creates a 0×0×0 `Array` ([#41618]). +* `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if + no errors were thrown ([#42211]). +* `@inline` and `@noinline` can now be placed within a function body, allowing one to annotate anonymous function ([#41312]). +* `@inline` and `@noinline` can now be applied to a function at callsite or block + to enforce the involved function calls to be (or not to be) inlined ([#41328]). +* `∀`, `∃`, and `∄` are now allowed as identifier characters ([#42314]). +* Support for Unicode 14.0.0 ([#43443]). +* `Module(:name, false, false)` can be used to create a `module` that contains no names + (it does not import `Base` or `Core` and does not contain a reference to itself) ([#40110], [#42154]). + +Language changes +---------------- + +* Newly-created Task objects (`@spawn`, `@async`, etc.) now adopt the world age for methods from their parent + Task upon creation, instead of using the global latest world at start. This is done to enable inference to + eventually optimize these calls. Places that wish for the old behavior may use `Base.invokelatest` ([#41449]). +* Unbalanced Unicode bidirectional formatting directives are now disallowed within strings and comments, + to mitigate the ["trojan source"](https://www.trojansource.codes) vulnerability ([#42918]). +* `Base.ifelse` is now defined as a generic function rather than a builtin one, allowing packages to + extend its definition ([#37343]). +* Every assignment to a global variable now first goes through a call to `convert(Any, x)` (or `convert(T, x)` + respectively if a type `T` has been declared for the global). This means great care should be taken + to ensure the invariant `convert(Any, x) === x` always holds, as this change could otherwise lead to + unexpected behavior ([#43671]). +* Builtin functions are now a bit more like generic functions, and can be enumerated with `methods` ([#43865]). + +Compiler/Runtime improvements +----------------------------- + +* Bootstrapping time has been improved by about 25% ([#41794]). +* The LLVM-based compiler has been separated from the run-time library into a new library, + `libjulia-codegen`. It is loaded by default, so normal usage should see no changes. + In deployments that do not need the compiler (e.g. system images where all needed code + is precompiled), this library (and its LLVM dependency) can simply be excluded ([#41936]). +* Conditional type constraints are now be forwarded interprocedurally (i.e. propagated from caller to callee). + This allows inference to understand e.g. `Base.ifelse(isa(x, Int), x, 0)` returns `::Int`-value + even if the type of `x` is not known ([#42529]). +* Julia-level SROA (Scalar Replacement of Aggregates) has been improved: allowing elimination of + `getfield` calls with constant global fields ([#42355]), enabling elimination of mutable structs with + uninitialized fields ([#43208]), improving performance ([#43232]), and handling more nested `getfield` + calls ([#43239]). +* Abstract call sites can now be inlined or statically resolved as long as the call site has a single + matching method ([#43113]). +* Inference now tracks various effects such as side-effectful-ness and nothrow-ness on a per-specialization basis. + Code heavily dependent on constant propagation should see significant compile-time performance improvements and + certain cases (e.g. calls to uninlinable functions that are nevertheless effect free) should see runtime performance + improvements. Effects may be overwritten manually with the `Base.@assume_effects` macro ([#43852]). + +Command-line option changes +--------------------------- + +* The default behavior of observing `@inbounds` declarations is now an option via `auto` in `--check-bounds=yes|no|auto` ([#41551]). +* New option `--strip-metadata` to remove docstrings, source location information, and local + variable names when building a system image ([#42513]). +* New option `--strip-ir` to remove the compiler's IR (intermediate representation) of source + code when building a system image. The resulting image will only work if `--compile=all` is + used, or if all needed code is precompiled ([#42925]). +* When the program file is `-` the code to be executed is read from standard in ([#43191]). + +Multi-threading changes +----------------------- + +* `Threads.@threads` now defaults to a new `:dynamic` schedule option which is similar to the previous behavior except + that iterations will be scheduled dynamically to available worker threads rather than pinned to each thread. This + behavior is more composable with (possibly nested) `@spawn` and `@threads` loops ([#43919], [#44136]). + +Build system changes +-------------------- + + +New library functions +--------------------- + +* New function `eachsplit(str)` for iteratively performing `split(str)` ([#39245]). +* New function `allequal(itr)` for testing if all elements in an iterator are equal ([#43354]). +* `hardlink(src, dst)` can be used to create hard links ([#41639]). +* `setcpuaffinity(cmd, cpus)` can be used to set CPU affinity of sub-processes ([#42469]). +* `diskstat(path=pwd())` can be used to return statistics about the disk ([#42248]). +* New `@showtime` macro to show both the line being evaluated and the `@time` report ([#42431]). +* The `LazyString` and the `lazy"str"` macro were added to support delayed construction of error messages in error paths ([#33711]). + +New library features +-------------------- + +* `@time` and `@timev` now take an optional description to allow annotating the source of time reports, + e.g. `@time "Evaluating foo" foo()` ([#42431]). +* `range` accepts either `stop` or `length` as a sole keyword argument ([#39241]). +* `precision` and `setprecision` now accept a `base` keyword argument ([#42428]). +* TCP socket objects now expose `closewrite` functionality and support half-open mode usage ([#40783]). +* `extrema` now accepts an `init` keyword argument ([#36265], [#43604]). +* `Iterators.countfrom` now accepts any type that defines `+` ([#37747]). + +Standard library changes +------------------------ + +* Keys with value `nothing` are now removed from the environment in `addenv` ([#43271]). +* `Iterators.reverse` (and hence `last`) now supports `eachline` iterators ([#42225]). +* The `length` function on certain ranges of certain element types no longer checks for integer + overflow in most cases. The new function `checked_length` is now available, which will try to use checked + arithmetic to error if the result may be wrapping. Or use a package such as SaferIntegers.jl when + constructing the range ([#40382]). +* Intersect returns a result with the eltype of the type-promoted eltypes of the two inputs ([#41769]). +* Iterating an `Iterators.Reverse` now falls back on reversing the eachindex iterator, if possible ([#43110]). + +#### InteractiveUtils + +* New macro `@time_imports` for reporting any time spent importing packages and their dependencies, highlighting + compilation and recompilation time as percentages per import ([#41612],[#45064]). + +#### LinearAlgebra + +* The BLAS submodule now supports the level-2 BLAS subroutine `spr!` ([#42830]). +* `cholesky[!]` now supports `LinearAlgebra.PivotingStrategy` (singleton type) values + as its optional `pivot` argument: the default is `cholesky(A, NoPivot())` (vs. + `cholesky(A, RowMaximum())`); the former `Val{true/false}`-based calls are deprecated ([#41640]). +* The standard library `LinearAlgebra.jl` is now completely independent of `SparseArrays.jl`, + both in terms of the source code as well as unit testing ([#43127]). As a consequence, + sparse arrays are no longer (silently) returned by methods from `LinearAlgebra` applied + to `Base` or `LinearAlgebra` objects. Specifically, this results in the following breaking + changes: + * Concatenations involving special "sparse" matrices (`*diagonal`) now return dense matrices; + As a consequence, the `D1` and `D2` fields of `SVD` objects, constructed upon `getproperty` + calls are now dense matrices. + * 3-arg `similar(::SpecialSparseMatrix, ::Type, ::Dims)` returns a dense zero matrix. + As a consequence, products of bi-, tri- and symmetric tridiagonal matrices with each + other result in dense output. Moreover, constructing 3-arg similar matrices of special + "sparse" matrices of (nonstatic) matrices now fails for the lack of `zero(::Type{Matrix{T}})`. + +#### Printf + +* Now uses `textwidth` for formatting `%s` and `%c` widths ([#41085]). + +#### Profile + +* CPU profiling now records sample metadata including thread and task. `Profile.print()` has a new `groupby` kwarg that allows + grouping by thread, task, or nested thread/task, task/thread, and `threads` and `tasks` kwargs to allow filtering. + Further, percent utilization is now reported as a total or per-thread, based on whether the thread is idle or not at + each sample. `Profile.fetch()` includes the new metadata by default. For backwards compatibility with external + profiling data consumers, it can be excluded by passing `include_meta=false` ([#41742]). +* The new `Profile.Allocs` module allows memory allocations to be profiled. The stack trace, type, and size of each + allocation is recorded, and a `sample_rate` argument allows a tunable amount of allocations to be skipped, + reducing performance overhead ([#42768]). +* A fixed duration cpu profile can now be triggered by the user during running tasks without `Profile` being loaded + first and the report will show during execution. On MacOS & FreeBSD press `ctrl-t` or raise a `SIGINFO`. + For other platforms raise a `SIGUSR1` i.e. `% kill -USR1 $julia_pid`. Not currently available on windows ([#43179]). + +#### REPL + +* `RadioMenu` now supports optional `keybindings` to directly select options ([#41576]). +* ` ?(x, y` followed by TAB displays all methods that can be called + with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.) + `MyModule.?(x, y` limits the search to `MyModule`. TAB requires that at least one + argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB + to allow any compatible methods ([#38791]). +* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints + the exception information ([#40642]). + +#### SparseArrays + +* The code for SparseArrays has been moved from the Julia repo to the external + repo at https://github.com/JuliaSparse/SparseArrays.jl. This is only a code + movement and does not impact any usage ([#43813]). +* New sparse concatenation functions `sparse_hcat`, `sparse_vcat`, and `sparse_hvcat` return + `SparseMatrixCSC` output independent from the types of the input arguments. They make + concatenation behavior available, in which the presence of some special "sparse" matrix + argument resulted in sparse output by multiple dispatch. This is no longer possible after + making `LinearAlgebra.jl` independent from `SparseArrays.jl` ([#43127]). + +#### Logging + +* The standard log levels `BelowMinLevel`, `Debug`, `Info`, `Warn`, `Error`, + and `AboveMaxLevel` are now exported from the Logging stdlib ([#40980]). + +#### Unicode + +* Added function `isequal_normalized` to check for Unicode equivalence without + explicitly constructing normalized strings ([#42493]). +* The `Unicode.normalize` function now accepts a `chartransform` keyword that can + be used to supply custom character mappings, and a `Unicode.julia_chartransform` + function is provided to reproduce the mapping used in identifier normalization + by the Julia parser ([#42561]). + +#### Test + +* `@test_throws "some message" triggers_error()` can now be used to check whether the displayed error text + contains "some message" regardless of the specific exception type. + Regular expressions, lists of strings, and matching functions are also supported ([#41888]). +* `@testset foo()` can now be used to create a test set from a given function. The name of the test set + is the name of the called function. The called function can contain `@test` and other `@testset` + definitions, including to other function calls, while recording all intermediate test results ([#42518]). +* `TestLogger` and `LogRecord` are now exported from the Test stdlib ([#44080]). + +#### Distributed + +* SSHManager now supports workers with csh/tcsh login shell, via `addprocs()` option `shell=:csh` ([#41485]). + + +Deprecated or removed +--------------------- + + +External dependencies +--------------------- + + +Tooling Improvements +--------------------- + +* `GC.enable_logging(true)` can be used to log each garbage collection, with the + time it took and the amount of memory that was collected ([#43511]). + + +[#33711]: https://github.com/JuliaLang/julia/issues/33711 +[#36265]: https://github.com/JuliaLang/julia/issues/36265 +[#37343]: https://github.com/JuliaLang/julia/issues/37343 +[#37747]: https://github.com/JuliaLang/julia/issues/37747 +[#38791]: https://github.com/JuliaLang/julia/issues/38791 +[#39241]: https://github.com/JuliaLang/julia/issues/39241 +[#39245]: https://github.com/JuliaLang/julia/issues/39245 +[#40110]: https://github.com/JuliaLang/julia/issues/40110 +[#40382]: https://github.com/JuliaLang/julia/issues/40382 +[#40642]: https://github.com/JuliaLang/julia/issues/40642 +[#40783]: https://github.com/JuliaLang/julia/issues/40783 +[#40980]: https://github.com/JuliaLang/julia/issues/40980 +[#41085]: https://github.com/JuliaLang/julia/issues/41085 +[#41312]: https://github.com/JuliaLang/julia/issues/41312 +[#41328]: https://github.com/JuliaLang/julia/issues/41328 +[#41449]: https://github.com/JuliaLang/julia/issues/41449 +[#41485]: https://github.com/JuliaLang/julia/issues/41485 +[#41551]: https://github.com/JuliaLang/julia/issues/41551 +[#41576]: https://github.com/JuliaLang/julia/issues/41576 +[#41612]: https://github.com/JuliaLang/julia/issues/41612 +[#41618]: https://github.com/JuliaLang/julia/issues/41618 +[#41639]: https://github.com/JuliaLang/julia/issues/41639 +[#41640]: https://github.com/JuliaLang/julia/issues/41640 +[#41742]: https://github.com/JuliaLang/julia/issues/41742 +[#41769]: https://github.com/JuliaLang/julia/issues/41769 +[#41794]: https://github.com/JuliaLang/julia/issues/41794 +[#41888]: https://github.com/JuliaLang/julia/issues/41888 +[#41936]: https://github.com/JuliaLang/julia/issues/41936 +[#42154]: https://github.com/JuliaLang/julia/issues/42154 +[#42211]: https://github.com/JuliaLang/julia/issues/42211 +[#42225]: https://github.com/JuliaLang/julia/issues/42225 +[#42248]: https://github.com/JuliaLang/julia/issues/42248 +[#42314]: https://github.com/JuliaLang/julia/issues/42314 +[#42355]: https://github.com/JuliaLang/julia/issues/42355 +[#42428]: https://github.com/JuliaLang/julia/issues/42428 +[#42431]: https://github.com/JuliaLang/julia/issues/42431 +[#42469]: https://github.com/JuliaLang/julia/issues/42469 +[#42493]: https://github.com/JuliaLang/julia/issues/42493 +[#42513]: https://github.com/JuliaLang/julia/issues/42513 +[#42518]: https://github.com/JuliaLang/julia/issues/42518 +[#42529]: https://github.com/JuliaLang/julia/issues/42529 +[#42561]: https://github.com/JuliaLang/julia/issues/42561 +[#42768]: https://github.com/JuliaLang/julia/issues/42768 +[#42830]: https://github.com/JuliaLang/julia/issues/42830 +[#42918]: https://github.com/JuliaLang/julia/issues/42918 +[#42925]: https://github.com/JuliaLang/julia/issues/42925 +[#43110]: https://github.com/JuliaLang/julia/issues/43110 +[#43113]: https://github.com/JuliaLang/julia/issues/43113 +[#43127]: https://github.com/JuliaLang/julia/issues/43127 +[#43179]: https://github.com/JuliaLang/julia/issues/43179 +[#43191]: https://github.com/JuliaLang/julia/issues/43191 +[#43208]: https://github.com/JuliaLang/julia/issues/43208 +[#43232]: https://github.com/JuliaLang/julia/issues/43232 +[#43239]: https://github.com/JuliaLang/julia/issues/43239 +[#43271]: https://github.com/JuliaLang/julia/issues/43271 +[#43305]: https://github.com/JuliaLang/julia/issues/43305 +[#43354]: https://github.com/JuliaLang/julia/issues/43354 +[#43443]: https://github.com/JuliaLang/julia/issues/43443 +[#43511]: https://github.com/JuliaLang/julia/issues/43511 +[#43604]: https://github.com/JuliaLang/julia/issues/43604 +[#43671]: https://github.com/JuliaLang/julia/issues/43671 +[#43813]: https://github.com/JuliaLang/julia/issues/43813 +[#43852]: https://github.com/JuliaLang/julia/issues/43852 +[#43865]: https://github.com/JuliaLang/julia/issues/43865 +[#43919]: https://github.com/JuliaLang/julia/issues/43919 +[#44080]: https://github.com/JuliaLang/julia/issues/44080 +[#44136]: https://github.com/JuliaLang/julia/issues/44136 + Julia v1.7 Release Notes ======================== @@ -38,7 +579,7 @@ Language changes same seed) unless an explicit RNG object is used. See the section on the `Random` standard library below ([#40546]). * `Iterators.peel(itr)` now returns `nothing` when `itr` is empty instead of throwing a `BoundsError` ([#39607]). -* Multiple successive semicolons in an array expresion were previously ignored (e.g., `[1 ;; 2] == [1 ; 2]`). +* Multiple successive semicolons in an array expression were previously ignored (e.g., `[1 ;; 2] == [1 ; 2]`). This syntax is now used to separate dimensions (see **New language features**). Compiler/Runtime improvements @@ -260,7 +801,7 @@ Standard library changes target; other functions — `Tar.extract`, `Tar.rewrite`, `Tar.tree_hash` — treat a hard link as a copy of the target file (). * The standard format generated by `Tar.create` and `Tar.rewrite` now includes entries for non-empty - directories; this shouldn't be neccessary, but some tools that consume tarballs (including docker) + directories; this shouldn't be necessary, but some tools that consume tarballs (including docker) are confused by the absence of these directory entries (). * `Tar` now accepts tarballs with leading spaces in octal integer header fields: this is technically not a valid format according to the POSIX spec, but old Solaris `tar` commands produced tarballs like @@ -1810,7 +2351,7 @@ Language changes * Juxtaposing binary, octal, and hexadecimal literals is deprecated, since it can lead to confusing code such as `0xapi == 0xa * pi` ([#16356]). - * Numeric literal juxtaposition now has slighty lower precedence than unary operators, + * Numeric literal juxtaposition now has slightly lower precedence than unary operators, so for example `√2x` parses as `(√2) * x` ([#27641]). * Declaring arguments as `x::ANY` to avoid specialization has been replaced @@ -3009,7 +3550,7 @@ Deprecated or removed array interface should define their own `strides` method ([#25321]). * `module_parent`, `Base.datatype_module`, and `Base.function_module` have been deprecated - in favor of `parentmodule` ([#TODO]). + in favor of `parentmodule` ([#25629]). * `rand(t::Tuple{Vararg{Int}})` is deprecated in favor of `rand(Float64, t)` or `rand(t...)`; `rand(::Tuple)` will have another meaning in the future ([#25429], [#25278]). @@ -3382,6 +3923,7 @@ Command-line option changes [#25571]: https://github.com/JuliaLang/julia/issues/25571 [#25616]: https://github.com/JuliaLang/julia/issues/25616 [#25622]: https://github.com/JuliaLang/julia/issues/25622 +[#25629]: https://github.com/JuliaLang/julia/issues/25629 [#25631]: https://github.com/JuliaLang/julia/issues/25631 [#25633]: https://github.com/JuliaLang/julia/issues/25633 [#25634]: https://github.com/JuliaLang/julia/issues/25634 @@ -3820,7 +4362,7 @@ Library improvements + Using colons (`:`) to represent a collection of indices is deprecated. They now must be explicitly converted to a specialized array of integers with the `to_indices` function. -    As a result, the type of `SubArray`s that represent views over colon indices has changed. + As a result, the type of `SubArray`s that represent views over colon indices has changed. + Logical indexing is now more efficient. Logical arrays are converted by `to_indices` to a lazy, iterable collection of indices that doesn't support indexing. A deprecation @@ -4876,7 +5418,7 @@ Library improvements for scalar indices to support indexing; all other indexing behaviors (including logical indexing, ranges of indices, vectors, colons, etc.) are implemented in default fallbacks. Similarly, they only need to implement - scalar `setindex!` to support all forms of indexed assingment ([#10525]). + scalar `setindex!` to support all forms of indexed assignment ([#10525]). * AbstractArrays that do not extend `similar` now return an `Array` by default ([#10525]). @@ -4905,7 +5447,7 @@ Library improvements * New types * Enums are now supported through the `@enum EnumName EnumValue1 - EnumValue2` syntax. Enum member values also support abitrary + EnumValue2` syntax. Enum member values also support arbitrary value assignment by the `@enum EnumName EnumValue1=1 EnumValue2=10 EnumValue3=20` syntax ([#10168]). @@ -5022,18 +5564,18 @@ Deprecated or removed * several syntax whitespace insensitivities have been deprecated ([#11891]). ```julia - # function call - f (x) - - # getindex - x [17] - rand(2) [1] - - # function definition - f (x) = x^2 - function foo (x) - x^2 - end + # function call + f (x) + + # getindex + x [17] + rand(2) [1] + + # function definition + f (x) = x^2 + function foo (x) + x^2 + end ``` * indexing with `Real`s that are not subtypes of `Integer` (`Rational`, `AbstractFloat`, etc.) has been deprecated ([#10458]). diff --git a/LICENSE.md b/LICENSE.md index 1083622cdc2eb..fdf24e7603d73 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2009-2021: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors +Copyright (c) 2009-2022: 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 9ea021e68c959..35b0657de5aa2 100644 --- a/Make.inc +++ b/Make.inc @@ -1,4 +1,4 @@ -# -*- mode: makefile-gmake -*- +# -*- mode: makefile -*- # vi:syntax=make ## Note: @@ -21,6 +21,9 @@ JULIA_PRECOMPILE ?= 1 # and LLVM_ASSERTIONS=1. FORCE_ASSERTIONS ?= 0 +# Set BOOTSTRAP_DEBUG_LEVEL to 1 to enable Julia-level stacktrace during bootstrapping. +BOOTSTRAP_DEBUG_LEVEL ?= 0 + # OPENBLAS build options OPENBLAS_TARGET_ARCH:= OPENBLAS_SYMBOLSUFFIX:= @@ -63,6 +66,7 @@ USE_SYSTEM_PATCHELF:=0 USE_SYSTEM_LIBWHICH:=0 USE_SYSTEM_ZLIB:=0 USE_SYSTEM_P7ZIP:=0 +USE_SYSTEM_LLD:=0 # Link to the LLVM shared library USE_LLVM_SHLIB := 1 @@ -73,7 +77,7 @@ JULIA_THREADS := 1 # Set to 1 to enable profiling with OProfile USE_OPROFILE_JITEVENTS ?= 0 -# USE_PERF_JITEVENTS defined below since default is OS specific +# USE_PERF_JITEVENTS, and USE_INTEL_JITEVENTS defined below since default is OS specific # assume we don't have LIBSSP support in our compiler, will enable later if likely true HAVE_SSP := 0 @@ -85,6 +89,16 @@ WITH_GC_DEBUG_ENV := 0 # Enable DTrace support WITH_DTRACE := 0 +# Enable ITTAPI integration +WITH_ITTAPI := 0 + +# Enable Tracy support +WITH_TRACY := 0 +WITH_TRACY_CALLSTACKS := 0 + +# Enable Timing Counts support +WITH_TIMING_COUNTS := 0 + # Prevent picking up $ARCH from the environment variables ARCH:= @@ -131,23 +145,6 @@ endif export BUILDROOT unexport O -# Make sure the user didn't try to specify a path that will confuse the shell / make -METACHARACTERS := ][?*{}() $$%:;&|!\#,\\`\": -ifneq (,$(findstring ',$(value BUILDROOT))) -$(error cowardly refusing to build into directory with a single-quote in the path) -endif -ifneq (,$(findstring ',$(value JULIAHOME))) -$(error cowardly refusing to build from source directory with a single-quote in the path) -endif -ifneq (,$(shell echo '$(value BUILDROOT)' | grep '[$(METACHARACTERS)]')) -$(error cowardly refusing to build into directory with a shell-metacharacter in the path\ - (got: $(value BUILDROOT))) -endif -ifneq (,$(shell echo '$(value JULIAHOME)' | grep '[$(METACHARACTERS)]')) -$(error cowardly refusing to build from source directory with a shell-metacharacter in the path\ - (got: $(value JULIAHOME))) -endif - # we include twice to pickup user definitions better # include from JULIAHOME first so that BUILDROOT can override MAYBE_HOST := @@ -188,7 +185,7 @@ endif JULIA_VERSION := $(shell cat $(JULIAHOME)/VERSION) JULIA_MAJOR_VERSION := $(shell echo $(JULIA_VERSION) | cut -d'-' -f 1 | cut -d'.' -f 1) JULIA_MINOR_VERSION := $(shell echo $(JULIA_VERSION) | cut -d'-' -f 1 | cut -d'.' -f 2) -JULIA_PATCH_VERSION := $(shell echo $(JULIA_VERSION) | cut -d'-' -f 1 | cut -d'.' -f 3) +JULIA_PATCH_VERSION := $(shell echo $(JULIA_VERSION) | cut -d'-' -f 1 | cut -d'+' -f 1 | cut -d'.' -f 3) # libjulia's SONAME will follow the format libjulia.so.$(SOMAJOR). Before v1.0.0, # SOMAJOR will be a two-decimal value, e.g. libjulia.so.0.5, whereas at and beyond @@ -299,6 +296,9 @@ private_libdir := $(libdir)/julia endif build_private_libdir := $(build_libdir)/julia +private_libexecdir := $(libexecdir)/julia +build_private_libexecdir := $(build_libexecdir)/julia + # A helper functions for dealing with lazily-evaluated, expensive operations.. Spinning # up a python process to, for exaxmple, parse a TOML file is expensive, and we must wait # until the TOML files are on-disk before we can parse them. This means that we cannot @@ -323,7 +323,7 @@ define cache_rel_path $(1)_rel_eval = $(call rel_path,$(2),$($(1))) $(1)_rel = $$(call hit_cache,$(1)_rel_eval) endef -$(foreach D,libdir private_libdir datarootdir libexecdir docdir sysconfdir includedir,$(eval $(call cache_rel_path,$(D),$(bindir)))) +$(foreach D,libdir private_libdir datarootdir libexecdir private_libexecdir docdir sysconfdir includedir,$(eval $(call cache_rel_path,$(D),$(bindir)))) $(foreach D,build_libdir build_private_libdir,$(eval $(call cache_rel_path,$(D),$(build_bindir)))) # Save a special one: reverse_private_libdir_rel: usually just `../`, but good to be general: @@ -344,6 +344,7 @@ BUILD_LLVM_CLANG := 0 # see http://lldb.llvm.org/build.html for dependencies BUILD_LLDB := 0 BUILD_LIBCXX := 0 +BUILD_LLD := 1 # Options to enable Polly and its code-generation options USE_POLLY := 0 @@ -437,8 +438,10 @@ endif # Set to 1 to enable profiling with perf ifeq ("$(OS)", "Linux") USE_PERF_JITEVENTS ?= 1 +USE_INTEL_JITEVENTS ?= 1 else USE_PERF_JITEVENTS ?= 0 +USE_INTEL_JITEVENTS ?= 0 endif JULIACODEGEN := LLVM @@ -447,8 +450,8 @@ JULIACODEGEN := LLVM ifeq ($(FORCE_ASSERTIONS), 1) # C++ code needs to include LLVM header with the same assertion flag as LLVM # Use this flag to re-enable assertion in our code after all the LLVM headers are included -CXX_DISABLE_ASSERTION := -DISABLE_ASSERTIONS := +CXX_DISABLE_ASSERTION := -DJL_VERIFY_PASSES +DISABLE_ASSERTIONS := -DJL_VERIFY_PASSES else CXX_DISABLE_ASSERTION := -DJL_NDEBUG DISABLE_ASSERTIONS := -DNDEBUG -DJL_NDEBUG @@ -456,39 +459,37 @@ endif # Compiler specific stuff -ifeq ($(USECLANG), 1) +ifeq (default,$(origin CC)) +CC := $(CROSS_COMPILE)$(CC) # attempt to add cross-compiler prefix, if the user + # is not overriding the default, to form target-triple-cc (which + # may not exist), and use that to decide what compiler the user + # is using for the target build (or default to gcc) +endif +CC_VERSION_STRING = $(shell $(CC) --version 2>/dev/null) +ifneq (,$(findstring clang,$(CC_VERSION_STRING))) +USECLANG := 1 USEGCC := 0 -else # default to gcc -USEGCC := 1 +else USECLANG := 0 +USEGCC := 1 endif FC := $(CROSS_COMPILE)gfortran -ifeq ($(OS), FreeBSD) -USEGCC := 0 -USECLANG := 1 -endif - # Note: Supporting only macOS Yosemite and above ifeq ($(OS), Darwin) APPLE_ARCH := $(shell uname -m) -USEGCC := 0 -USECLANG := 1 ifneq ($(APPLE_ARCH),arm64) -MACOSX_VERSION_MIN := 10.10 +MACOSX_VERSION_MIN := 10.14 else MACOSX_VERSION_MIN := 11.0 endif endif ifeq ($(USEGCC),1) -ifeq ($(SANITIZE),1) -$(error Sanitizers are only supported with clang. Try setting SANITIZE=0) -endif CC := $(CROSS_COMPILE)gcc CXX := $(CROSS_COMPILE)g++ -JCFLAGS := -std=gnu99 -pipe $(fPIC) -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 +JCFLAGS := -std=gnu11 -pipe $(fPIC) -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 # AArch64 needs this flag to generate the .eh_frame used by libunwind JCPPFLAGS := -fasynchronous-unwind-tables JCXXFLAGS := -pipe $(fPIC) -fno-rtti -std=c++14 @@ -503,7 +504,7 @@ endif ifeq ($(USECLANG),1) CC := $(CROSS_COMPILE)clang CXX := $(CROSS_COMPILE)clang++ -JCFLAGS := -std=gnu99 -pipe $(fPIC) -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 +JCFLAGS := -std=gnu11 -pipe $(fPIC) -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 # AArch64 needs this flag to generate the .eh_frame used by libunwind JCPPFLAGS := -fasynchronous-unwind-tables JCXXFLAGS := -pipe $(fPIC) -fno-rtti -pedantic -std=c++14 @@ -519,6 +520,8 @@ JCPPFLAGS += -D_LARGEFILE_SOURCE -D_DARWIN_USE_64_BIT_INODE=1 endif endif +JLDFLAGS := + ifeq ($(USECCACHE), 1) # Expand CC, CXX and FC here already because we want the original definition and not the ccache version. CC_ARG := $(CC) @@ -549,8 +552,8 @@ CC_BASE := $(shell echo $(CC) | cut -d' ' -f1) CC_ARG := $(shell echo $(CC) | cut -s -d' ' -f2-) CXX_BASE := $(shell echo $(CXX) | cut -d' ' -f1) CXX_ARG := $(shell echo $(CXX) | cut -s -d' ' -f2-) -FC_BASE := $(shell echo $(FC) | cut -d' ' -f1) -FC_ARG := $(shell echo $(FC) | cut -s -d' ' -f2-) +FC_BASE := $(shell echo $(FC) 2>/dev/null | cut -d' ' -f1) +FC_ARG := $(shell echo $(FC) 2>/dev/null | cut -s -d' ' -f2-) endif JFFLAGS := -O2 $(fPIC) @@ -653,8 +656,8 @@ ifeq ($(OS),FreeBSD) ifneq (,$(findstring gfortran,$(FC))) # First let's figure out what version of GCC we're dealing with -_GCCMAJOR := $(shell $(FC) -dumpversion | cut -d'.' -f1) -_GCCMINOR := $(shell $(FC) -dumpversion | cut -d'.' -f2) +_GCCMAJOR := $(shell $(FC) -dumpversion 2>/dev/null | cut -d'.' -f1) +_GCCMINOR := $(shell $(FC) -dumpversion 2>/dev/null | cut -d'.' -f2) # The ports system uses major and minor for GCC < 5 (e.g. gcc49 for GCC 4.9), otherwise major only ifeq ($(_GCCMAJOR),4) @@ -690,9 +693,12 @@ SANITIZE_LDFLAGS := ifeq ($(SANITIZE_MEMORY),1) SANITIZE_OPTS += -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer SANITIZE_LDFLAGS += $(SANITIZE_OPTS) -endif +ifneq ($(findstring $(OS),Linux FreeBSD),) +SANITIZE_LDFLAGS += -Wl,--warn-unresolved-symbols +endif # OS Linux or FreeBSD +endif # SANITIZE_MEMORY=1 ifeq ($(SANITIZE_ADDRESS),1) -SANITIZE_OPTS += -fsanitize=address -mllvm -asan-stack=0 +SANITIZE_OPTS += -fsanitize=address SANITIZE_LDFLAGS += -fsanitize=address endif ifeq ($(SANITIZE_THREAD),1) @@ -708,7 +714,7 @@ JLDFLAGS += $(SANITIZE_LDFLAGS) endif # SANITIZE TAR := $(shell which gtar 2>/dev/null || which tar 2>/dev/null) -TAR_TEST := $(shell $(TAR) --help 2>&1 | egrep 'bsdtar|strip-components') +TAR_TEST := $(shell $(TAR) --help 2>&1 | grep -E 'bsdtar|strip-components') ifeq (,$(findstring components,$(TAR_TEST))) ifneq (bsdtar,$(findstring bsdtar,$(TAR_TEST))) $(error "please install either GNU tar or bsdtar") @@ -729,7 +735,28 @@ ifeq ($(WITH_DTRACE), 1) JCXXFLAGS += -DUSE_DTRACE JCFLAGS += -DUSE_DTRACE DTRACE := dtrace -else +endif + +ifeq ($(WITH_ITTAPI), 1) +JCXXFLAGS += -DUSE_ITTAPI +JCFLAGS += -DUSE_ITTAPI +LIBITTAPI:=-littnotify +endif + +ifeq ($(WITH_TRACY), 1) +JCXXFLAGS += -DUSE_TRACY -DTRACY_ENABLE -DTRACY_FIBERS +JCFLAGS += -DUSE_TRACY -DTRACY_ENABLE -DTRACY_FIBERS +LIBTRACYCLIENT:=-lTracyClient +endif +ifeq ($(WITH_TRACY_CALLSTACKS), 1) +JCXXFLAGS += -DTRACY_CALLSTACK=32 +JCFLAGS += -DTRACY_CALLSTACK=32 +LIBTRACYCLIENT:=-lTracyClient +endif + +ifeq ($(WITH_TIMING_COUNTS), 1) +JCXXFLAGS += -DUSE_TIMING_COUNTS +JCFLAGS += -DUSE_TIMING_COUNTS endif # =========================================================================== @@ -775,6 +802,8 @@ else ifeq (cygwin, $(shell $(CC) -dumpmachine | cut -d\- -f3)) $(error "cannot build julia with cygwin-target compilers. set XC_HOST to i686-w64-mingw32 or x86_64-w64-mingw32 for mingw cross-compile") else ifeq (msys, $(shell $(CC) -dumpmachine | cut -d\- -f3)) $(error "cannot build julia with msys-target compilers. please see the README.windows document for instructions on setting up mingw-w64 compilers") +else ifneq (,$(findstring MSYS,$(shell uname))) +$(error "cannot build julia from a msys shell. please launch a mingw shell instead by setting MSYSTEM=MINGW64") endif ifeq ($(BUILD_OS),Darwin) @@ -845,7 +874,6 @@ endif # If we are running on powerpc64le or ppc64le, set certain options automatically ifneq (,$(filter $(ARCH), powerpc64le ppc64le)) JCFLAGS += -fsigned-char -OPENBLAS_DYNAMIC_ARCH:=0 OPENBLAS_TARGET_ARCH:=POWER8 BINARY:=64 # GCC doesn't do -march= on ppc64le @@ -901,10 +929,6 @@ OPENBLAS_DYNAMIC_ARCH:=0 OPENBLAS_TARGET_ARCH:=ARMV8 USE_BLAS64:=1 BINARY:=64 -ifeq ($(OS),Darwin) -# Apple Chips are all at least A12Z -MCPU:=apple-a12 -endif endif # Set MARCH-specific flags @@ -1037,6 +1061,10 @@ PATCHELF := patchelf else PATCHELF := $(build_depsbindir)/patchelf endif +# In the standard build system we want to patch files with `--set-rpath`, but downstream +# packagers like Spack may want to use `--add-rpath` instead, leave them the possibility to +# choose the command. +PATCHELF_SET_RPATH_ARG := --set-rpath ifeq ($(USE_SYSTEM_LIBWHICH), 1) LIBWHICH := libwhich @@ -1143,8 +1171,11 @@ USE_BINARYBUILDER ?= 0 endif # Auto-detect triplet once, create different versions that we use as defaults below for each BB install target -FC_VERSION := $(shell $(FC) --version 2>/dev/null | head -1) -FC_OR_CC_VERSION := $(or $(FC_VERSION),$(shell $(CC) --version 2>/dev/null | head -1)) +FC_VERSION := $(shell $(FC) -dM -E - < /dev/null 2>/dev/null | grep __GNUC__ | cut -d' ' -f3) +ifeq ($(USEGCC)$(FC_VERSION),1) +FC_OR_CC_VERSION := $(shell $(CC) -dumpfullversion -dumpversion 2>/dev/null | cut -d'.' -f1) +# n.b. clang's __GNUC__ macro pretends to be gcc 4.2.1, so leave it as the empty string here if the compiler is not certain to be GCC +endif BB_TRIPLET_LIBGFORTRAN_CXXABI := $(shell $(call invoke_python,$(JULIAHOME)/contrib/normalize_triplet.py) $(or $(XC_HOST),$(XC_HOST),$(BUILD_MACHINE)) "$(FC_OR_CC_VERSION)" "$(or $(shell echo '\#include ' | $(CXX) $(CXXFLAGS) -x c++ -dM -E - | grep _GLIBCXX_USE_CXX11_ABI | awk '{ print $$3 }' ),1)") BB_TRIPLET_LIBGFORTRAN := $(subst $(SPACE),-,$(filter-out cxx%,$(subst -,$(SPACE),$(BB_TRIPLET_LIBGFORTRAN_CXXABI)))) BB_TRIPLET_CXXABI := $(subst $(SPACE),-,$(filter-out libgfortran%,$(subst -,$(SPACE),$(BB_TRIPLET_LIBGFORTRAN_CXXABI)))) @@ -1152,11 +1183,34 @@ BB_TRIPLET := $(subst $(SPACE),-,$(filter-out cxx%,$(filter-out libgfortran%,$(s LIBGFORTRAN_VERSION := $(subst libgfortran,,$(filter libgfortran%,$(subst -,$(SPACE),$(BB_TRIPLET_LIBGFORTRAN)))) +# CSL_NEXT_GLIBCXX_VERSION is a triple of the symbols representing support for whatever +# the next libstdc++ version would be. This is used for two things. +# 1. Whether the system libraries are new enough, if we need to use the libs bundled with CSL +# 2. To know which libstdc++ to load at runtime +# We want whichever libstdc++ library is newer, because if we don't it can cause problems. +# While what CSL bundles is quite bleeding-edge compared to what most distros ship, if someone +# tries to build an older branch of Julia, the version of CSL that ships with it may be +# relatively old. This is not a problem for code that is built in BB, but when we build Julia +# with the system compiler, that compiler uses the version of `libstdc++` that it is bundled +# with, and we can get linker errors when trying to run that `julia` executable with the +# `libstdc++` that comes from the (now old) BB-built CSL. +# To fix this, we take note when the system `libstdc++.so` is newer than whatever we +# would get from CSL (by searching for a `GLIBCXX_X.Y.Z` symbol that does not exist +# in our CSL, but would in a newer one), and default to `USE_BINARYBUILDER_CSL=0` in +# this case. This ensures that we link against a version with the symbols required. +# We also check the system libstdc++ at runtime in the cli loader library, and +# load it if it contains the version symbol that indicates that it is newer than the one +# 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\. + + # This is the set of projects that BinaryBuilder dependencies are hooked up for. # Note: we explicitly _do not_ define `CSL` here, since it requires some more # advanced techniques to decide whether it should be installed from a BB source # or not. See `deps/csl.mk` for more detail. -BB_PROJECTS := BLASTRAMPOLINE OPENBLAS LLVM LIBSUITESPARSE OPENLIBM GMP MBEDTLS LIBSSH2 NGHTTP2 MPFR CURL LIBGIT2 PCRE LIBUV LIBUNWIND DSFMT OBJCONV ZLIB P7ZIP +BB_PROJECTS := BLASTRAMPOLINE OPENBLAS LLVM LIBSUITESPARSE OPENLIBM GMP MBEDTLS LIBSSH2 NGHTTP2 MPFR CURL LIBGIT2 PCRE LIBUV LIBUNWIND DSFMT OBJCONV ZLIB P7ZIP LLD LIBTRACYCLIENT define SET_BB_DEFAULT # First, check to see if BB is disabled on a global setting ifeq ($$(USE_BINARYBUILDER),0) @@ -1208,18 +1262,16 @@ ifneq (,$(filter $(OS),WINNT emscripten)) RPATH := RPATH_ORIGIN := RPATH_ESCAPED_ORIGIN := - RPATH_LIB := else ifeq ($(OS), Darwin) RPATH := -Wl,-rpath,'@executable_path/$(build_libdir_rel)' RPATH_ORIGIN := -Wl,-rpath,'@loader_path/' RPATH_ESCAPED_ORIGIN := $(RPATH_ORIGIN) - RPATH_LIB := -Wl,-rpath,'@loader_path/' else - RPATH := -Wl,-rpath,'$$ORIGIN/$(build_libdir_rel)' -Wl,-rpath,'$$ORIGIN/$(build_private_libdir_rel)' -Wl,-rpath-link,$(build_shlibdir) -Wl,-z,origin - RPATH_ORIGIN := -Wl,-rpath,'$$ORIGIN' -Wl,-z,origin - RPATH_ESCAPED_ORIGIN := -Wl,-rpath,'\$$\$$ORIGIN' -Wl,-z,origin -Wl,-rpath-link,$(build_shlibdir) - RPATH_LIB := -Wl,-rpath,'$$ORIGIN/' -Wl,-z,origin + RPATH := -Wl,-rpath,'$$ORIGIN/$(build_libdir_rel)' -Wl,-rpath,'$$ORIGIN/$(build_private_libdir_rel)' -Wl,-rpath-link,$(build_shlibdir) -Wl,-z,origin -Wl,--enable-new-dtags + RPATH_ORIGIN := -Wl,-rpath,'$$ORIGIN' -Wl,-z,origin -Wl,--enable-new-dtags + RPATH_ESCAPED_ORIGIN := -Wl,-rpath,'\$$\$$ORIGIN' -Wl,-z,origin -Wl,-rpath-link,$(build_shlibdir) -Wl,--enable-new-dtags endif +RPATH_LIB := $(RPATH_ORIGIN) # --whole-archive ifeq ($(OS), Darwin) @@ -1230,6 +1282,9 @@ else NO_WHOLE_ARCHIVE := -Wl,--no-whole-archive endif +# Initialize these once, then add to them in OS-specific blocks +JLIBLDFLAGS := + ifeq ($(OS), Linux) OSLIBS += -Wl,--no-as-needed -ldl -lrt -lpthread -latomic -Wl,--export-dynamic,--as-needed,--no-whole-archive # Detect if ifunc is supported @@ -1237,26 +1292,24 @@ IFUNC_DETECT_SRC := 'void (*f0(void))(void) { return (void(*)(void))0L; }; void ifeq (supported, $(shell echo $(IFUNC_DETECT_SRC) | $(CC) -Werror -x c - -S -o /dev/null > /dev/null 2>&1 && echo supported)) JCPPFLAGS += -DJULIA_HAS_IFUNC_SUPPORT=1 endif -JLDFLAGS := -Wl,-Bdynamic -ifneq ($(SANITIZE),1) -ifneq ($(SANITIZE_MEMORY),1) -ifneq ($(LLVM_SANITIZE),1) +JLDFLAGS += -Wl,-Bdynamic OSLIBS += -Wl,--version-script=$(JULIAHOME)/src/julia.expmap +ifneq ($(SANITIZE),1) JLDFLAGS += -Wl,-no-undefined endif -endif -endif ifeq (-Bsymbolic-functions, $(shell $(LD) --help | grep -o -e "-Bsymbolic-functions")) -JLIBLDFLAGS := -Wl,-Bsymbolic-functions -else -JLIBLDFLAGS := +JLIBLDFLAGS += -Wl,-Bsymbolic-functions endif -else ifneq ($(OS), Darwin) -JLIBLDFLAGS := +ifeq (--enable-new-dtags, $(shell $(LD) --help | grep -o -e "--enable-new-dtags")) +JLIBLDFLAGS += -Wl,--enable-new-dtags +endif + +# Linker doesn't detect automatically that Julia doesn't need executable stack +JLIBLDFLAGS += -Wl,-z,noexecstack endif ifeq ($(OS), FreeBSD) -JLDFLAGS := -Wl,-Bdynamic +JLDFLAGS += -Wl,-Bdynamic OSLIBS += -lelf -lkvm -lrt -lpthread -latomic # Tweak order of libgcc_s in DT_NEEDED, @@ -1274,22 +1327,27 @@ SHLIB_EXT := dylib OSLIBS += -framework CoreFoundation WHOLE_ARCHIVE := -Xlinker -all_load NO_WHOLE_ARCHIVE := -JLDFLAGS := HAVE_SSP := 1 -JLIBLDFLAGS := -Wl,-compatibility_version,$(SOMAJOR) -Wl,-current_version,$(JULIA_MAJOR_VERSION).$(JULIA_MINOR_VERSION).$(JULIA_PATCH_VERSION) +JLIBLDFLAGS += -Wl,-compatibility_version,$(SOMAJOR) -Wl,-current_version,$(JULIA_MAJOR_VERSION).$(JULIA_MINOR_VERSION).$(JULIA_PATCH_VERSION) endif ifeq ($(OS), WINNT) HAVE_SSP := 1 OSLIBS += -Wl,--export-all-symbols -Wl,--version-script=$(JULIAHOME)/src/julia.expmap \ $(NO_WHOLE_ARCHIVE) -lpsapi -lkernel32 -lws2_32 -liphlpapi -lwinmm -ldbghelp -luserenv -lsecur32 -latomic -JLDFLAGS := -Wl,--stack,8388608 +JLDFLAGS += -Wl,--stack,8388608 ifeq ($(ARCH),i686) JLDFLAGS += -Wl,--large-address-aware endif JCPPFLAGS += -D_WIN32_WINNT=0x0502 UNTRUSTED_SYSTEM_LIBM := 1 -endif +# Use hard links for files on windows, rather than soft links +# https://stackoverflow.com/questions/3648819/how-to-make-a-symbolic-link-with-cygwin-in-windows-7 +# Usage: $(WIN_MAKE_HARD_LINK) +WIN_MAKE_HARD_LINK := cp --dereference --link --force +else +WIN_MAKE_HARD_LINK := true -ignore +endif # $(OS) == WINNT # Threads ifneq ($(JULIA_THREADS), 0) @@ -1372,7 +1430,6 @@ 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_CXXFLAGS += -isystem $(shell xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 endif ifeq ($(USEGCC),1) # try to help clang find the c++ files for CC by guessing the value for --prefix @@ -1398,17 +1455,17 @@ define symlink_target # (from, to-dir, to-name) CLEAN_TARGETS += clean-$$(abspath $(2)/$(3)) clean-$$(abspath $(2)/$(3)): ifeq ($(BUILD_OS), WINNT) - -cmd //C rmdir $$(call mingw_to_dos,$(2)/$(3),cd $(2) &&) + -cmd //C rmdir $$(call cygpath_w,$(2)/$(3)) else - -rm -r $$(abspath $(2)/$(3)) + rm -rf $$(abspath $(2)/$(3)) endif $$(abspath $(2)/$(3)): | $$(abspath $(2)) ifeq ($$(BUILD_OS), WINNT) - @cmd //C mklink //J $$(call mingw_to_dos,$(2)/$(3),cd $(2) &&) $$(call mingw_to_dos,$(1),) + @cmd //C mklink //J $$(call cygpath_w,$(2)/$(3)) $$(call cygpath_w,$(1)) else ifneq (,$$(findstring CYGWIN,$$(BUILD_OS))) @cmd /C mklink /J $$(call cygpath_w,$(2)/$(3)) $$(call cygpath_w,$(1)) else ifdef JULIA_VAGRANT_BUILD - @rm -r $$@ + @rm -rf $$@ @cp -R $$(abspath $(1)) $$@.tmp @mv $$@.tmp $$@ else @@ -1422,7 +1479,7 @@ WINE ?= wine # many of the following targets must be = not := because the expansion of the makefile functions (and $1) shouldn't happen until later ifeq ($(BUILD_OS), WINNT) # MSYS spawn = $(1) -cygpath_w = $(1) +cygpath_w = `cygpath -w $(1)` else ifneq (,$(findstring CYGWIN,$(BUILD_OS))) # Cygwin spawn = $(1) cygpath_w = `cygpath -w $(1)` @@ -1454,7 +1511,7 @@ JULIA_SYSIMG_release := $(build_private_libdir)/sys.$(SHLIB_EXT) JULIA_SYSIMG := $(JULIA_SYSIMG_$(JULIA_BUILD_MODE)) define dep_lib_path -$$($(PYTHON) $(call python_cygpath,$(JULIAHOME)/contrib/relative_path.py) $(1) $(2)) +$(shell $(PYTHON) $(call python_cygpath,$(JULIAHOME)/contrib/relative_path.py) $(1) $(2)) endef LIBJULIAINTERNAL_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_shlibdir)/libjulia-internal.$(JL_MAJOR_SHLIB_EXT)) @@ -1476,13 +1533,19 @@ else LIBGCC_NAME := libgcc_s_seh-1.$(SHLIB_EXT) endif endif +# On macOS, libgcc_s has soversion 1.1 always on aarch64 and only for GCC 12+ +# (-> libgfortran 5) on x86_64 ifeq ($(OS),Darwin) ifeq ($(ARCH),aarch64) -LIBGCC_NAME := libgcc_s.2.$(SHLIB_EXT) +LIBGCC_NAME := libgcc_s.1.1.$(SHLIB_EXT) +else +ifeq ($(LIBGFORTRAN_VERSION),5) +LIBGCC_NAME := libgcc_s.1.1.$(SHLIB_EXT) else LIBGCC_NAME := libgcc_s.1.$(SHLIB_EXT) endif endif +endif ifneq ($(findstring $(OS),Linux FreeBSD),) LIBGCC_NAME := libgcc_s.$(SHLIB_EXT).1 endif @@ -1495,6 +1558,19 @@ LIBGCC_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_shlibdir)/$(L endif LIBGCC_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBGCC_NAME)) +# We only bother to define this on Linux, as that's the only platform that does libstdc++ probing +# On all other platforms, the LIBSTDCXX_*_DEPLIB variables will be empty. +ifeq ($(OS),Linux) +LIBSTDCXX_NAME := libstdc++.so.6 +ifeq ($(USE_SYSTEM_CSL),1) +LIBSTDCXX_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_private_shlibdir)/$(LIBSTDCXX_NAME)) +else +LIBSTDCXX_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_shlibdir)/$(LIBSTDCXX_NAME)) +endif +LIBSTDCXX_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBSTDCXX_NAME)) +endif + + # USE_SYSTEM_LIBM and USE_SYSTEM_OPENLIBM causes it to get symlinked into build_private_shlibdir ifeq ($(USE_SYSTEM_LIBM),1) LIBM_BUILD_DEPLIB := $(call dep_lib_path,$(build_libdir),$(build_private_shlibdir)/$(LIBMNAME).$(SHLIB_EXT)) @@ -1508,6 +1584,8 @@ LIBM_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBMN # We list: # * libgcc_s, because FreeBSD needs to load ours, not the system one. # * libopenlibm, because Windows has an untrustworthy libm, and we want to use ours more than theirs +# * libstdc++, because while performing `libstdc++` probing we need to +# know the path to the bundled `libstdc++` library. # * libjulia-internal, which must always come second-to-last. # * libjulia-codegen, which must always come last # @@ -1516,11 +1594,45 @@ LIBM_INSTALL_DEPLIB := $(call dep_lib_path,$(libdir),$(private_shlibdir)/$(LIBMN # * install time relative paths are not equal to build time relative paths (../lib vs. ../lib/julia) # That second point will no longer be true for most deps once they are placed within Artifacts directories. # Note that we prefix `libjulia-codegen` and `libjulia-internal` with `@` to signify to the loader that it -# should not automatically dlopen() it in its loading loop. -LOADER_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_BUILD_DEPLIB):@$(LIBJULIACODEGEN_BUILD_DEPLIB): -LOADER_DEBUG_BUILD_DEP_LIBS = $(LIBGCC_BUILD_DEPLIB):$(LIBM_BUILD_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_BUILD_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_BUILD_DEPLIB): -LOADER_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_INSTALL_DEPLIB): -LOADER_DEBUG_INSTALL_DEP_LIBS = $(LIBGCC_INSTALL_DEPLIB):$(LIBM_INSTALL_DEPLIB):@$(LIBJULIAINTERNAL_DEBUG_INSTALL_DEPLIB):@$(LIBJULIACODEGEN_DEBUG_INSTALL_DEPLIB): +# should not automatically dlopen() it in its loading loop, it is "special" and should happen later. +# We do the same for `libstdc++`, and explicitly place it _after_ `libgcc_s`, and `libm` since `libstdc++` +# may depend on those libraries (e.g. when USE_SYSTEM_LIBM=1) + +# Helper function to join a list with colons, then place an extra at the end. +define build_deplibs +$(subst $(SPACE),:,$(strip $(1))): +endef + +LOADER_BUILD_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_BUILD_DEPLIB) \ + $(LIBM_BUILD_DEPLIB) \ + @$(LIBSTDCXX_BUILD_DEPLIB) \ + @$(LIBJULIAINTERNAL_BUILD_DEPLIB) \ + @$(LIBJULIACODEGEN_BUILD_DEPLIB) \ +) + +LOADER_DEBUG_BUILD_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_BUILD_DEPLIB) \ + $(LIBM_BUILD_DEPLIB) \ + @$(LIBSTDCXX_BUILD_DEPLIB) \ + @$(LIBJULIAINTERNAL_DEBUG_BUILD_DEPLIB) \ + @$(LIBJULIACODEGEN_DEBUG_BUILD_DEPLIB) \ +) + +LOADER_INSTALL_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_INSTALL_DEPLIB) \ + $(LIBM_INSTALL_DEPLIB) \ + @$(LIBSTDCXX_INSTALL_DEPLIB) \ + @$(LIBJULIAINTERNAL_INSTALL_DEPLIB) \ + @$(LIBJULIACODEGEN_INSTALL_DEPLIB) \ +) +LOADER_DEBUG_INSTALL_DEP_LIBS = $(call build_deplibs, \ + $(LIBGCC_INSTALL_DEPLIB) \ + $(LIBM_INSTALL_DEPLIB) \ + @$(LIBSTDCXX_INSTALL_DEPLIB) \ + @$(LIBJULIAINTERNAL_DEBUG_INSTALL_DEPLIB) \ + @$(LIBJULIACODEGEN_DEBUG_INSTALL_DEPLIB) \ +) # Colors for make ifndef VERBOSE diff --git a/Makefile b/Makefile index 086ed515c62b7..046f18492bc3e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,24 @@ JULIAHOME := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) include $(JULIAHOME)/Make.inc +# import LLVM_SHARED_LIB_NAME +include $(JULIAHOME)/deps/llvm-ver.make + +# Make sure the user didn't try to build in a path that will confuse the shell or make +METACHARACTERS := [][?*{}() $$%:;&|!\#,\\`\":]\|/\./\|/\.\./ +ifneq (,$(findstring ',$(value BUILDROOT))) +$(error cowardly refusing to build into directory with a single-quote in the path) +endif +ifneq (,$(findstring ',$(value JULIAHOME))) +$(error cowardly refusing to build from source directory with a single-quote in the path) +endif +ifneq (,$(shell echo '$(value BUILDROOT)/' | grep '$(METACHARACTERS)')) +$(error cowardly refusing to build into directory with a shell-metacharacter in the path\ + (got: $(value BUILDROOT))) +endif +ifneq (,$(shell echo '$(value JULIAHOME)/' | grep '$(METACHARACTERS)')) +$(error cowardly refusing to build from source directory with a shell-metacharacter in the path\ + (got: $(value JULIAHOME))) +endif VERSDIR := v`cut -d. -f1-2 < $(JULIAHOME)/VERSION` @@ -9,9 +28,9 @@ all: debug release # sort is used to remove potential duplicates DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_private_libdir) $(build_libexecdir) $(build_includedir) $(build_includedir)/julia $(build_sysconfdir)/julia $(build_datarootdir)/julia $(build_datarootdir)/julia/stdlib $(build_man1dir)) ifneq ($(BUILDROOT),$(JULIAHOME)) -BUILDDIRS := $(BUILDROOT) $(addprefix $(BUILDROOT)/,base src src/flisp src/support src/clangsa cli doc deps stdlib test test/clangsa test/embedding test/llvmpasses) -BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) $(BUILDROOT)/sysimage.mk -DIRS := $(DIRS) $(BUILDDIRS) +BUILDDIRS := $(BUILDROOT) $(addprefix $(BUILDROOT)/,base src src/flisp src/support src/clangsa cli doc deps stdlib test test/clangsa test/embedding test/gcext test/llvmpasses) +BUILDDIRMAKE := $(addsuffix /Makefile,$(BUILDDIRS)) $(BUILDROOT)/sysimage.mk $(BUILDROOT)/pkgimage.mk +DIRS += $(BUILDDIRS) $(BUILDDIRMAKE): | $(BUILDDIRS) @# add Makefiles to the build directories for convenience (pointing back to the source location of each) @echo '# -- This file is automatically generated in julia/Makefile -- #' > $@ @@ -22,12 +41,12 @@ configure-y: | $(BUILDDIRMAKE) configure: ifeq ("$(origin O)", "command line") @if [ "$$(ls '$(BUILDROOT)' 2> /dev/null)" ]; then \ - echo 'WARNING: configure called on non-empty directory $(BUILDROOT)'; \ + printf $(WARNCOLOR)'WARNING: configure called on non-empty directory'$(ENDCOLOR)' %s\n' '$(BUILDROOT)'; \ read -p "Proceed [y/n]? " answer; \ else \ answer=y;\ fi; \ - [ $$answer = 'y' ] && $(MAKE) configure-$$answer + [ "y$$answer" = yy ] && $(MAKE) configure-$$answer else $(error "cannot rerun configure from within a build directory") endif @@ -42,10 +61,6 @@ $(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 @@ -89,7 +104,10 @@ julia-sysimg-release julia-sysimg-debug : julia-sysimg-% : julia-sysimg-ji julia julia-debug julia-release : julia-% : julia-sysimg-% julia-src-% julia-symlink julia-libccalltest julia-libllvmcalltest julia-base-cache -debug release : % : julia-% +stdlibs-cache-release stdlibs-cache-debug : stdlibs-cache-% : julia-% + @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT) -f pkgimage.mk all-$* + +debug release : % : julia-% stdlibs-cache-% docs: julia-sysimg-$(JULIA_BUILD_MODE) @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/doc JULIA_EXECUTABLE='$(call spawn,$(JULIA_EXECUTABLE_$(JULIA_BUILD_MODE))) --startup-file=no' @@ -99,7 +117,9 @@ docs-revise: check-whitespace: ifneq ($(NO_GIT), 1) - @$(JULIAHOME)/contrib/check-whitespace.sh + @# Append the directory containing the julia we just built to the end of `PATH`, + @# to give us the best chance of being able to run this check. + @PATH="$(PATH):$(dir $(JULIA_EXECUTABLE))" julia $(call cygpath_w,$(JULIAHOME)/contrib/check-whitespace.jl) else $(warn "Skipping whitespace check because git is unavailable") endif @@ -135,8 +155,10 @@ release-candidate: release testall @echo 10. Follow packaging instructions in doc/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 12. Update checksums on AWS for tarball and packaged binaries - @echo 13. Announce on mailing lists - @echo 14. Change master to release-0.X in base/version.jl and base/version_git.sh as in 4cb1e20 + @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) + @echo 15. Announce on mailing lists + @echo 16. Change master to release-0.X in base/version.jl and base/version_git.sh as in 4cb1e20 @echo $(build_man1dir)/julia.1: $(JULIAHOME)/doc/man/julia.1 | $(build_man1dir) @@ -160,14 +182,17 @@ julia-base-cache: julia-sysimg-$(JULIA_BUILD_MODE) | $(DIRS) $(build_datarootdir $(call cygpath_w,$(build_datarootdir)/julia/base.cache)) # public libraries, that are installed in $(prefix)/lib +ifeq ($(JULIA_BUILD_MODE),release) JL_TARGETS := julia -ifeq ($(BUNDLE_DEBUG_LIBS),1) -JL_TARGETS += julia-debug +else ifeq ($(JULIA_BUILD_MODE),debug) +JL_TARGETS := julia-debug endif # private libraries, that are installed in $(prefix)/lib/julia -JL_PRIVATE_LIBS-0 := libccalltest libllvmcalltest libjulia-internal libjulia-codegen -ifeq ($(BUNDLE_DEBUG_LIBS),1) +JL_PRIVATE_LIBS-0 := libccalltest libllvmcalltest +ifeq ($(JULIA_BUILD_MODE),release) +JL_PRIVATE_LIBS-0 += libjulia-internal libjulia-codegen +else ifeq ($(JULIA_BUILD_MODE),debug) JL_PRIVATE_LIBS-0 += libjulia-internal-debug libjulia-codegen-debug endif ifeq ($(USE_GPL_LIBS), 1) @@ -190,7 +215,7 @@ else JL_PRIVATE_LIBS-$(USE_SYSTEM_ZLIB) += libz endif ifeq ($(USE_LLVM_SHLIB),1) -JL_PRIVATE_LIBS-$(USE_SYSTEM_LLVM) += libLLVM libLLVM-13jl +JL_PRIVATE_LIBS-$(USE_SYSTEM_LLVM) += libLLVM $(LLVM_SHARED_LIB_NAME) endif JL_PRIVATE_LIBS-$(USE_SYSTEM_LIBUNWIND) += libunwind @@ -212,6 +237,9 @@ JL_PRIVATE_LIBS-$(USE_SYSTEM_CSL) += libwinpthread else JL_PRIVATE_LIBS-$(USE_SYSTEM_CSL) += libpthread endif +ifeq ($(WITH_TRACY),1) +JL_PRIVATE_LIBS-0 += libTracyClient +endif ifeq ($(OS),Darwin) @@ -222,49 +250,58 @@ endif endif endif +# Note that we disable MSYS2's path munging here, as otherwise +# it replaces our `:`-separated list as a `;`-separated one. define stringreplace - $(build_depsbindir)/stringreplace $$(strings -t x - $1 | grep $2 | awk '{print $$1;}') $3 255 "$(call cygpath_w,$1)" + MSYS2_ARG_CONV_EXCL='*' $(build_depsbindir)/stringreplace $$(strings -t x - '$1' | grep "$2" | awk '{print $$1;}') "$3" 255 "$(call cygpath_w,$1)" endef -install: $(build_depsbindir)/stringreplace $(BUILDROOT)/doc/_build/html/en/index.html -ifeq ($(BUNDLE_DEBUG_LIBS),1) - @$(MAKE) $(QUIET_MAKE) all -else - @$(MAKE) $(QUIET_MAKE) release -endif - @for subdir in $(bindir) $(datarootdir)/julia/stdlib/$(VERSDIR) $(docdir) $(man1dir) $(includedir)/julia $(libdir) $(private_libdir) $(sysconfdir) $(libexecdir); do \ +install: $(build_depsbindir)/stringreplace docs + @$(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; \ done - $(INSTALL_M) $(build_bindir)/julia $(DESTDIR)$(bindir)/ -ifeq ($(BUNDLE_DEBUG_LIBS),1) - $(INSTALL_M) $(build_bindir)/julia-debug $(DESTDIR)$(bindir)/ -endif + $(INSTALL_M) $(JULIA_EXECUTABLE_$(JULIA_BUILD_MODE)) $(DESTDIR)$(bindir)/ ifeq ($(OS),WINNT) - -$(INSTALL_M) $(filter-out $(build_bindir)/libjulia-debug.dll,$(wildcard $(build_bindir)/*.dll)) $(DESTDIR)$(bindir)/ + -$(INSTALL_M) $(wildcard $(build_bindir)/*.dll) $(DESTDIR)$(bindir)/ +ifeq ($(JULIA_BUILD_MODE),release) -$(INSTALL_M) $(build_libdir)/libjulia.dll.a $(DESTDIR)$(libdir)/ - - # We have a single exception; we want 7z.dll to live in libexec, not bin, so that 7z.exe can find it. - -mv $(DESTDIR)$(bindir)/7z.dll $(DESTDIR)$(libexecdir)/ -ifeq ($(BUNDLE_DEBUG_LIBS),1) - -$(INSTALL_M) $(build_bindir)/libjulia-debug.dll $(DESTDIR)$(bindir)/ + -$(INSTALL_M) $(build_libdir)/libjulia-internal.dll.a $(DESTDIR)$(libdir)/ +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 + + # 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 ifneq ($(DARWIN_FRAMEWORK),1) ifeq ($(OS),Darwin) +ifeq ($(JULIA_BUILD_MODE),release) -cp -a $(build_libdir)/libjulia.*.dSYM $(DESTDIR)$(libdir) + -cp -a $(build_libdir)/libjulia-internal.*.dSYM $(DESTDIR)$(private_libdir) + -cp -a $(build_libdir)/libjulia-codegen.*.dSYM $(DESTDIR)$(private_libdir) -cp -a $(build_private_libdir)/sys.dylib.dSYM $(DESTDIR)$(private_libdir) -ifeq ($(BUNDLE_DEBUG_LIBS),1) +else ifeq ($(JULIA_BUILD_MODE),debug) -cp -a $(build_libdir)/libjulia-debug.*.dSYM $(DESTDIR)$(libdir) + -cp -a $(build_libdir)/libjulia-internal-debug.*.dSYM $(DESTDIR)$(private_libdir) + -cp -a $(build_libdir)/libjulia-codegen-debug.*.dSYM $(DESTDIR)$(private_libdir) -cp -a $(build_private_libdir)/sys-debug.dylib.dSYM $(DESTDIR)$(private_libdir) endif endif +# Copy over shared library file for libjulia.* for suffix in $(JL_TARGETS) ; do \ for lib in $(build_libdir)/lib$${suffix}.*$(SHLIB_EXT)*; do \ if [ "$${lib##*.}" != "dSYM" ]; then \ @@ -273,11 +310,12 @@ endif done \ done else - # libjulia in Darwin framework has special location and name +# libjulia in Darwin framework has special location and name +ifeq ($(JULIA_BUILD_MODE),release) $(INSTALL_M) $(build_libdir)/libjulia.$(SOMAJOR).$(SOMINOR).dylib $(DESTDIR)$(prefix)/$(framework_dylib) @$(DSYMUTIL) -o $(DESTDIR)$(prefix)/$(framework_resources)/$(FRAMEWORK_NAME).dSYM $(DESTDIR)$(prefix)/$(framework_dylib) @$(DSYMUTIL) -o $(DESTDIR)$(prefix)/$(framework_resources)/sys.dylib.dSYM $(build_private_libdir)/sys.dylib -ifeq ($(BUNDLE_DEBUG_LIBS),1) +else ifeq ($(JULIA_BUILD_MODE),debug) $(INSTALL_M) $(build_libdir)/libjulia-debug.$(SOMAJOR).$(SOMINOR).dylib $(DESTDIR)$(prefix)/$(framework_dylib)_debug @$(DSYMUTIL) -o $(DESTDIR)$(prefix)/$(framework_resources)/$(FRAMEWORK_NAME)_debug.dSYM $(DESTDIR)$(prefix)/$(framework_dylib)_debug @$(DSYMUTIL) -o $(DESTDIR)$(prefix)/$(framework_resources)/sys-debug.dylib.dSYM $(build_private_libdir)/sys-debug.dylib @@ -299,14 +337,21 @@ endif done \ done endif - # Install `7z` into libexec/ - $(INSTALL_M) $(build_bindir)/7z$(EXE) $(DESTDIR)$(libexecdir)/ + # Install `7z` into private_libexecdir + $(INSTALL_M) $(build_bindir)/7z$(EXE) $(DESTDIR)$(private_libexecdir)/ + + # Install `lld` into private_libexecdir + $(INSTALL_M) $(build_depsbindir)/lld$(EXE) $(DESTDIR)$(private_libexecdir)/ + + # Install `dsymutil` into private_libexecdir/ + $(INSTALL_M) $(build_depsbindir)/dsymutil$(EXE) $(DESTDIR)$(private_libexecdir)/ # Copy public headers cp -R -L $(build_includedir)/julia/* $(DESTDIR)$(includedir)/julia # Copy system image +ifeq ($(JULIA_BUILD_MODE),release) $(INSTALL_M) $(build_private_libdir)/sys.$(SHLIB_EXT) $(DESTDIR)$(private_libdir) -ifeq ($(BUNDLE_DEBUG_LIBS),1) +else ifeq ($(JULIA_BUILD_MODE),debug) $(INSTALL_M) $(build_private_libdir)/sys-debug.$(SHLIB_EXT) $(DESTDIR)$(private_libdir) endif @@ -344,7 +389,7 @@ ifneq ($(DARWIN_FRAMEWORK),1) endif else ifneq (,$(findstring $(OS),Linux FreeBSD)) for j in $(JL_TARGETS) ; do \ - $(PATCHELF) --set-rpath '$$ORIGIN/$(private_libdir_rel):$$ORIGIN/$(libdir_rel)' $(DESTDIR)$(bindir)/$$j; \ + $(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN/$(private_libdir_rel):$$ORIGIN/$(libdir_rel)' $(DESTDIR)$(bindir)/$$j; \ done endif @@ -356,32 +401,50 @@ endif RELEASE_TARGET=$(DESTDIR)$(prefix)/$(framework_dylib); \ DEBUG_TARGET=$(DESTDIR)$(prefix)/$(framework_dylib)_debug; \ fi; \ - $(call stringreplace,$${RELEASE_TARGET},sys.$(SHLIB_EXT)$$,$(private_libdir_rel)/sys.$(SHLIB_EXT)); \ - if [ "$(BUNDLE_DEBUG_LIBS)" = "1" ]; then \ + if [ "$(JULIA_BUILD_MODE)" = "release" ]; then \ + $(call stringreplace,$${RELEASE_TARGET},sys.$(SHLIB_EXT)$$,$(private_libdir_rel)/sys.$(SHLIB_EXT)); \ + elif [ "$(JULIA_BUILD_MODE)" = "debug" ]; then \ $(call stringreplace,$${DEBUG_TARGET},sys-debug.$(SHLIB_EXT)$$,$(private_libdir_rel)/sys-debug.$(SHLIB_EXT)); \ fi; endif - # Set rpath for libjulia-internal, which is moving from `../lib` to `../lib/julia`. We only need to do this for Linux/FreeBSD -ifneq (,$(findstring $(OS),Linux FreeBSD)) - $(PATCHELF) --set-rpath '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-internal.$(SHLIB_EXT) -ifeq ($(BUNDLE_DEBUG_LIBS),1) - $(PATCHELF) --set-rpath '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-internal-debug.$(SHLIB_EXT) + # Set rpath for libjulia-internal, which is moving from `../lib` to `../lib/julia`. +ifeq ($(OS), Darwin) +ifneq ($(DARWIN_FRAMEWORK),1) +ifeq ($(JULIA_BUILD_MODE),release) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-internal.$(SHLIB_EXT) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-codegen.$(SHLIB_EXT) +else ifeq ($(JULIA_BUILD_MODE),debug) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-internal-debug.$(SHLIB_EXT) + install_name_tool -add_rpath @loader_path/$(reverse_private_libdir_rel)/ $(DESTDIR)$(private_libdir)/libjulia-codegen-debug.$(SHLIB_EXT) +endif +endif +else ifneq (,$(findstring $(OS),Linux FreeBSD)) +ifeq ($(JULIA_BUILD_MODE),release) + $(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-internal.$(SHLIB_EXT) + $(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-codegen.$(SHLIB_EXT) +else ifeq ($(JULIA_BUILD_MODE),debug) + $(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-internal-debug.$(SHLIB_EXT) + $(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN:$$ORIGIN/$(reverse_private_libdir_rel)' $(DESTDIR)$(private_libdir)/libjulia-codegen-debug.$(SHLIB_EXT) endif endif + # Fix rpaths for dependencies. This should be fixed in BinaryBuilder later. +ifeq ($(OS), Linux) + -$(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN' $(DESTDIR)$(private_shlibdir)/libLLVM.$(SHLIB_EXT) +endif ifneq ($(LOADER_BUILD_DEP_LIBS),$(LOADER_INSTALL_DEP_LIBS)) # Next, overwrite relative path to libjulia-internal in our loader if $$(LOADER_BUILD_DEP_LIBS) != $$(LOADER_INSTALL_DEP_LIBS) +ifeq ($(JULIA_BUILD_MODE),release) $(call stringreplace,$(DESTDIR)$(shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT),$(LOADER_BUILD_DEP_LIBS)$$,$(LOADER_INSTALL_DEP_LIBS)) - -ifeq ($(BUNDLE_DEBUG_LIBS),1) +else ifeq ($(JULIA_BUILD_MODE),debug) $(call stringreplace,$(DESTDIR)$(shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT),$(LOADER_DEBUG_BUILD_DEP_LIBS)$$,$(LOADER_DEBUG_INSTALL_DEP_LIBS)) endif endif - # On FreeBSD, remove the build's libdir from each library's RPATH ifeq ($(OS),FreeBSD) + # On FreeBSD, remove the build's libdir from each library's RPATH $(JULIAHOME)/contrib/fixup-rpath.sh "$(PATCHELF)" $(DESTDIR)$(libdir) $(build_libdir) $(JULIAHOME)/contrib/fixup-rpath.sh "$(PATCHELF)" $(DESTDIR)$(private_libdir) $(build_libdir) $(JULIAHOME)/contrib/fixup-rpath.sh "$(PATCHELF)" $(DESTDIR)$(bindir) $(build_libdir) @@ -390,7 +453,7 @@ ifeq ($(OS),FreeBSD) # don't set libgfortran's RPATH, it won't be able to find its friends on systems # that don't have the exact GCC port installed used for the build. for lib in $(DESTDIR)$(private_libdir)/libgfortran*$(SHLIB_EXT)*; do \ - $(PATCHELF) --set-rpath '$$ORIGIN' $$lib; \ + $(PATCHELF) $(PATCHELF_SET_RPATH_ARG) '$$ORIGIN' $$lib; \ done endif @@ -413,6 +476,12 @@ ifneq ($(OPENBLAS_DYNAMIC_ARCH),1) endif endif endif + +ifeq ($(USE_BINARYBUILDER_OPENBLAS),0) + # https://github.com/JuliaLang/julia/issues/46579 + USE_BINARYBUILDER_OBJCONV=0 +endif + ifneq ($(prefix),$(abspath julia-$(JULIA_COMMIT))) $(error prefix must not be set for make binary-dist) endif @@ -430,9 +499,11 @@ ifeq ($(OS), WINNT) endif cd $(BUILDROOT) && $(TAR) zcvf $(JULIA_BINARYDIST_FILENAME).tar.gz julia-$(JULIA_COMMIT) + exe: - # run Inno Setup to compile installer - $(call spawn,$(JULIAHOME)/dist-extras/inno/iscc.exe /DAppVersion=$(JULIA_VERSION) /DSourceDir="$(call cygpath_w,$(BUILDROOT)/julia-$(JULIA_COMMIT))" /DRepoDir="$(call cygpath_w,$(JULIAHOME))" /F"$(JULIA_BINARYDIST_FILENAME)" /O"$(call cygpath_w,$(BUILDROOT))" $(INNO_ARGS) $(call cygpath_w,$(JULIAHOME)/contrib/windows/build-installer.iss)) + # run Inno Setup to compile installer. + # Note that we disable MSYS2 path munging, as it interferes with the `/` options: + MSYS2_ARG_CONV_EXCL='*' $(call spawn,$(JULIAHOME)/dist-extras/inno/iscc.exe /DAppVersion=$(JULIA_VERSION) /DSourceDir="$(call cygpath_w,$(BUILDROOT)/julia-$(JULIA_COMMIT))" /DRepoDir="$(call cygpath_w,$(JULIAHOME))" /F"$(JULIA_BINARYDIST_FILENAME)" /O"$(call cygpath_w,$(BUILDROOT))" $(INNO_ARGS) $(call cygpath_w,$(JULIAHOME)/contrib/windows/build-installer.iss)) chmod a+x "$(BUILDROOT)/$(JULIA_BINARYDIST_FILENAME).exe" app: @@ -442,7 +513,7 @@ app: darwinframework: $(MAKE) -C $(JULIAHOME)/contrib/mac/framework -light-source-dist.tmp: $(BUILDROOT)/doc/_build/html/en/index.html +light-source-dist.tmp: docs ifneq ($(BUILDROOT),$(JULIAHOME)) $(error make light-source-dist does not work in out-of-tree builds) endif @@ -459,7 +530,7 @@ endif # Include all git-tracked filenames git ls-files >> light-source-dist.tmp - + # Include documentation filenames find doc/_build/html >> light-source-dist.tmp @@ -519,12 +590,13 @@ distcleanall: cleanall @-$(MAKE) -C $(BUILDROOT)/deps distcleanall @-$(MAKE) -C $(BUILDROOT)/doc cleanall -.PHONY: default debug release check-whitespace release-candidate \ +.FORCE: +.PHONY: .FORCE default debug release check-whitespace release-candidate \ julia-debug julia-release julia-stdlib julia-deps julia-deps-libs \ julia-cli-release julia-cli-debug julia-src-release julia-src-debug \ julia-symlink julia-base julia-sysimg julia-sysimg-ji julia-sysimg-release julia-sysimg-debug \ - test testall testall1 test test-* test-revise-* \ - clean distcleanall cleanall clean-* \ + test testall testall1 test \ + clean distcleanall cleanall $(CLEAN_TARGETS) \ run-julia run-julia-debug run-julia-release run \ install binary-dist light-source-dist.tmp light-source-dist \ dist full-source-dist source-dist @@ -541,12 +613,12 @@ testall: check-whitespace $(JULIA_BUILD_MODE) testall1: check-whitespace $(JULIA_BUILD_MODE) @env JULIA_CPU_THREADS=1 $(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test all JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) -test-%: check-whitespace $(JULIA_BUILD_MODE) +test-%: check-whitespace $(JULIA_BUILD_MODE) .FORCE @([ $$(( $$(date +%s) - $$(date -r $(build_private_libdir)/sys.$(SHLIB_EXT) +%s) )) -le 100 ] && \ printf '\033[93m HINT The system image was recently rebuilt. Are you aware of the test-revise-* targets? See CONTRIBUTING.md. \033[0m\n') || true @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test $* JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) -test-revise-%: +test-revise-%: .FORCE @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/test revise-$* JULIA_BUILD_MODE=$(JULIA_BUILD_MODE) # download target for some hardcoded windows dependencies @@ -557,7 +629,7 @@ win-extras: cd $(JULIAHOME)/dist-extras && \ $(JLDOWNLOAD) https://www.jrsoftware.org/download.php/is.exe && \ chmod a+x is.exe && \ - $(call spawn, $(JULIAHOME)/dist-extras/is.exe /DIR="$(call cygpath_w,$(JULIAHOME)/dist-extras/inno)" /PORTABLE=1 /CURRENTUSER /VERYSILENT) + MSYS2_ARG_CONV_EXCL='*' $(call spawn, $(JULIAHOME)/dist-extras/is.exe /DIR="$(call cygpath_w,$(JULIAHOME)/dist-extras/inno)" /PORTABLE=1 /CURRENTUSER /VERYSILENT) # various statistics about the build that may interest the user ifeq ($(USE_SYSTEM_LLVM), 1) diff --git a/NEWS.md b/NEWS.md index b82e28130852f..5c42c469e4051 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,77 +1,39 @@ -Julia v1.8 Release Notes +Julia v1.10 Release Notes ======================== New language features --------------------- -* Mutable struct fields may now be annotated as `const` to prevent changing them after construction, - providing for greater clarity and optimization ability of these objects ([#43305]). -* Type annotations can now be added to global variables to make accessing them type stable ([#43671]). -* Empty n-dimensional arrays can now be created using multiple semicolons inside square brackets, - e.g. `[;;;]` creates a 0×0×0 `Array` ([#41618]). -* `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if - no errors were thrown ([#42211]). -* `@inline` and `@noinline` annotations can now be placed within a function body ([#41312]). -* `@inline` and `@noinline` annotations can now be applied to a function call site or block - to enforce the involved function calls to be (or not to be) inlined ([#41312]). -* `∀`, `∃`, and `∄` are now allowed as identifier characters ([#42314]). -* Support for Unicode 14.0.0 ([#43443]). -* `Module(:name, false, false)` can be used to create a `module` that contains no names - (it does not import `Base` or `Core` and does not contain a reference to itself) ([#40110, #42154]). +* `⥺` (U+297A, `\leftarrowsubset`) and `⥷` (U+2977, `\leftarrowless`) + may now be used as binary operators with arrow precedence. ([#45962]) Language changes ---------------- -* Newly-created Task objects (`@spawn`, `@async`, etc.) now adopt the world age for methods from their parent - Task upon creation, instead of using the global latest world at start. This is done to enable inference to - eventually optimize these calls. Places that wish for the old behavior may use `Base.invokelatest` ([#41449]). -* Unbalanced Unicode bidirectional formatting directives are now disallowed within strings and comments, - to mitigate the ["trojan source"](https://www.trojansource.codes) vulnerability ([#42918]). -* `Base.ifelse` is now defined as a generic function rather than a builtin one, allowing packages to - extend its definition ([#37343]). -* Every assignment to a global variable now first goes through a call to `convert(Any, x)` (or `convert(T, x)` - respectively if a type `T` has been declared for the global). This means great care should be taken - to ensure the invariant `convert(Any, x) === x` always holds, as this change could otherwise lead to - unexpected behavior ([#43671]). -* Builtin functions are now a bit more like generic functions, and can be enumerated with `methods` ([#43865]). +* When a task forks a child, the parent task's task-local RNG (random number generator) is no longer affected. The seeding of child based on the parent task also takes a more disciplined approach to collision resistance, using a design based on the SplitMix and DotMix splittable RNG schemes ([#49110]). +* A new more-specific rule for methods resolves ambiguities containing Union{} in favor of + the method defined explicitly to handle the Union{} argument. This makes it possible to + define methods to explicitly handle Union{} without the ambiguities that commonly would + result previously. This also lets the runtime optimize certain method lookups in a way + that significantly improves load and inference times for heavily overloaded methods that + dispatch on Types (such as traits and constructors). +* The "h bar" `ℏ` (`\hslash` U+210F) character is now treated as equivalent to `ħ` (`\hbar` U+0127). Compiler/Runtime improvements ----------------------------- -* Bootstrapping time has been improved by about 25% ([#41794]). -* The LLVM-based compiler has been separated from the run-time library into a new library, - `libjulia-codegen`. It is loaded by default, so normal usage should see no changes. - In deployments that do not need the compiler (e.g. system images where all needed code - is precompiled), this library (and its LLVM dependency) can simply be excluded ([#41936]). -* Conditional type constraints can now be forwarded interprocedurally (i.e. propagated from caller to callee) ([#42529]). -* Julia-level SROA (Scalar Replacement of Aggregates) has been improved: allowing elimination of - `getfield` calls with constant global fields ([#42355]), enabling elimination of mutable structs with - uninitialized fields ([#43208]), improving performance ([#43232]), and handling more nested `getfield` - calls ([#43239]). -* Abstract call sites can now be inlined or statically resolved as long as the call site has a single - matching method ([#43113]). -* Inference now tracks various effects such as side-effectful-ness and nothrow-ness on a per-specialization basis. - Code heavily dependent on constant propagation should see significant compile-time performance improvements and - certain cases (e.g. calls to uninlinable functions that are nevertheless effect free) should see runtime performance - improvements. Effects may be overwritten manually with the `@Base.assume_effects` macro ([#43852]). +* The `@pure` macro is now deprecated. Use `Base.@assume_effects :foldable` instead ([#48682]). +* The mark phase of the Garbage Collector is now multi-threaded ([#48600]). Command-line option changes --------------------------- -* The default behavior of observing `@inbounds` declarations is now an option via `auto` in `--check-bounds=yes|no|auto` ([#41551]). -* New option `--strip-metadata` to remove docstrings, source location information, and local - variable names when building a system image ([#42513]). -* New option `--strip-ir` to remove the compiler's IR (intermediate representation) of source - code when building a system image. The resulting image will only work if `--compile=all` is - used, or if all needed code is precompiled ([#42925]). -* When the program file is `-` the code to be executed is read from standard in ([#43191]). +* New option `--gcthreads` to set how many threads will be used by the Garbage Collector ([#48600]). + The default is set to `N/2` where `N` is the amount of worker threads (`--threads`) used by Julia. Multi-threading changes ----------------------- -* `Threads.@threads` now defaults to a new `:dynamic` schedule option which is similar to the previous behavior except - that iterations will be scheduled dynamically to available worker threads rather than pinned to each thread. This - behavior is more composable with (possibly nested) `@spawn` and `@threads` loops ([#43919], [#44136]). Build system changes -------------------- @@ -79,124 +41,89 @@ Build system changes New library functions --------------------- - -* New function `eachsplit(str)` for iteratively performing `split(str)` ([#39245]). -* New function `allequal(itr)` for testing if all elements in an iterator are equal ([#43354]). -* `hardlink(src, dst)` can be used to create hard links ([#41639]). -* `setcpuaffinity(cmd, cpus)` can be used to set CPU affinity of sub-processes ([#42469]). -* `diskstat(path=pwd())` can be used to return statistics about the disk ([#42248]). -* New `@showtime` macro to show both the line being evaluated and the `@time` report ([#42431]). -* The `LazyString` and the `lazy"str"` macro were added to support delayed construction of error messages in error paths ([#33711]). +* `tanpi` is now defined. It computes tan(πx) more accurately than `tan(pi*x)` ([#48575]). +* `fourthroot(x)` is now defined in `Base.Math` and can be used to compute the fourth root of `x`. + It can also be accessed using the unicode character `∜`, which can be typed by `\fourthroot` ([#48899]). New library features -------------------- - -* `@time` and `@timev` now take an optional description to allow annotating the source of time reports, - e.g. `@time "Evaluating foo" foo()` ([#42431]). -* `range` accepts either `stop` or `length` as a sole keyword argument ([#39241]). -* `precision` and `setprecision` now accept a `base` keyword argument ([#42428]). -* TCP socket objects now expose `closewrite` functionality and support half-open mode usage ([#40783]). -* `extrema` now accepts an `init` keyword argument ([#36265], [#43604]). -* `Iterators.countfrom` now accepts any type that defines `+` ([#37747]). +* The `initialized=true` keyword assignment for `sortperm!` and `partialsortperm!` + is now a no-op ([#47979]). It previously exposed unsafe behavior ([#47977]). +* `binomial(x, k)` now supports non-integer `x` ([#48124]). +* A `CartesianIndex` is now treated as a "scalar" for broadcasting ([#47044]). +* `printstyled` now supports italic output ([#45164]). Standard library changes ------------------------ -* Keys with value `nothing` are now removed from the environment in `addenv` ([#43271]). -* `Iterators.reverse` (and hence `last`) now supports `eachline` iterators ([#42225]). -* The `length` function on certain ranges of certain element types no longer checks for integer - overflow in most cases. The new function `checked_length` is now available, which will try to use checked - arithmetic to error if the result may be wrapping. Or use a package such as SaferIntegers.jl when - constructing the range ([#40382]). -* Intersect returns a result with the eltype of the type-promoted eltypes of the two inputs ([#41769]). -* Iterating an `Iterators.Reverse` now falls back on reversing the eachindex iterator, if possible ([#43110]). +* `startswith` now supports seekable `IO` streams ([#43055]) +* printing integral `Rational`s will skip the denominator in `Rational`-typed IO context (e.g. in `Arrays`) ([#45396]) -#### InteractiveUtils +#### Package Manager -* New macro `@time_imports` for reporting any time spent importing packages and their dependencies ([#41612]). +* `Pkg.precompile` now accepts `timing` as a keyword argument which displays per package timing information for precompilation (e.g. `Pkg.precompile(timing=true)`) #### LinearAlgebra -* The BLAS submodule now supports the level-2 BLAS subroutine `spr!` ([#42830]). -* `cholesky[!]` now supports `LinearAlgebra.PivotingStrategy` (singleton type) values - as its optional `pivot` argument: the default is `cholesky(A, NoPivot())` (vs. - `cholesky(A, RowMaximum())`); the former `Val{true/false}`-based calls are deprecated ([#41640]). -* The standard library `LinearAlgebra.jl` is now completely independent of `SparseArrays.jl`, - both in terms of the source code as well as unit testing ([#43127]). As a consequence, - sparse arrays are no longer (silently) returned by methods from `LinearAlgebra` applied - to `Base` or `LinearAlgebra` objects. Specifically, this results in the following breaking - changes: - * Concatenations involving special "sparse" matrices (`*diagonal`) now return dense matrices; - As a consequence, the `D1` and `D2` fields of `SVD` objects, constructed upon `getproperty` - calls are now dense matrices. - * 3-arg `similar(::SpecialSparseMatrix, ::Type, ::Dims)` returns a dense zero matrix. - As a consequence, products of bi-, tri- and symmetric tridiagonal matrices with each - other result in dense output. Moreover, constructing 3-arg similar matrices of special - "sparse" matrices of (nonstatic) matrices now fails for the lack of `zero(::Type{Matrix{T}})`. +* `AbstractQ` no longer subtypes to `AbstractMatrix`. Moreover, `adjoint(Q::AbstractQ)` + no longer wraps `Q` in an `Adjoint` type, but instead in an `AdjointQ`, that itself + subtypes `AbstractQ`. This change accounts for the fact that typically `AbstractQ` + instances behave like function-based, matrix-backed linear operators, and hence don't + allow for efficient indexing. Also, many `AbstractQ` types can act on vectors/matrices + of different size, acting like a matrix with context-dependent size. With this change, + `AbstractQ` has a well-defined API that is described in detail in the + [Julia documentation](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#man-linalg-abstractq) + ([#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 + `Factorization` ([#46874]). +* New functions `hermitianpart` and `hermitianpart!` for extracting the Hermitian + (real symmetric) part of a matrix ([#31836]). +* The `norm` of the adjoint or transpose of an `AbstractMatrix` now returns the norm of the + parent matrix by default, matching the current behaviour for `AbstractVector`s ([#49020]). #### Printf - -* Now uses `textwidth` for formatting `%s` and `%c` widths ([#41085]). +* Format specifiers now support dynamic width and precision, e.g. `%*s` and `%*.*g` ([#40105]). #### Profile -* CPU profiling now records sample metadata including thread and task. `Profile.print()` has a new `groupby` kwarg that allows - grouping by thread, task, or nested thread/task, task/thread, and `threads` and `tasks` kwargs to allow filtering. - Further, percent utilization is now reported as a total or per-thread, based on whether the thread is idle or not at - each sample. `Profile.fetch()` includes the new metadata by default. For backwards compatibility with external - profiling data consumers, it can be excluded by passing `include_meta=false` ([#41742]). -* The new `Profile.Allocs` module allows memory allocations to be profiled. The stack trace, type, and size of each - allocation is recorded, and a `sample_rate` argument allows a tunable amount of allocations to be skipped, - reducing performance overhead ([#42768]). -* A fixed duration cpu profile can now be triggered by the user during running tasks without `Profile` being loaded - first and the report will show during execution. On MacOS & FreeBSD press `ctrl-t` or raise a `SIGINFO`. - For other platforms raise a `SIGUSR1` i.e. `% kill -USR1 $julia_pid`. Not currently available on windows ([#43179]). + +#### Random + #### REPL -* `RadioMenu` now supports optional `keybindings` to directly select options ([#41576]). -* ` ?(x, y` followed by TAB displays all methods that can be called - with arguments `x, y, ...`. (The space at the beginning prevents entering help-mode.) - `MyModule.?(x, y` limits the search to `MyModule`. TAB requires that at least one - argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB - to allow any compatible methods ([#38791]). -* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints - the exception information ([#40642]). + +#### SuiteSparse + #### SparseArrays -* The code for SparseArrays has been moved from the Julia repo to the external - repo at https://github.com/JuliaSparse/SparseArrays.jl. This is only a code - movement and does not impact any usage ([#43813]). -* New sparse concatenation functions `sparse_hcat`, `sparse_vcat`, and `sparse_hvcat` return - `SparseMatrixCSC` output independent from the types of the input arguments. They make - concatenation behavior available, in which the presence of some special "sparse" matrix - argument resulted in sparse output by multiple dispatch. This is no longer possible after - making `LinearAlgebra.jl` independent from `SparseArrays.jl` ([#43127]). -#### Logging +#### Test + + +* The `@test_broken` macro (or `@test` with `broken=true`) now complains if the test expression returns a + non-boolean value in the same way as a non-broken test. ([#47804]) +* When a call to `@test` fails or errors inside a function, a larger stacktrace is now printed such that the location of the test within a `@testset` can be retrieved ([#49451]) + +#### Dates + + +#### Distributed -* The standard log levels `BelowMinLevel`, `Debug`, `Info`, `Warn`, `Error`, - and `AboveMaxLevel` are now exported from the Logging stdlib ([#40980]). #### Unicode -* Added function `isequal_normalized` to check for Unicode equivalence without - explicitly constructing normalized strings ([#42493]). -* The `Unicode.normalize` function now accepts a `chartransform` keyword that can - be used to supply custom character mappings, and a `Unicode.julia_chartransform` - function is provided to reproduce the mapping used in identifier normalization - by the Julia parser ([#42561]). -#### Test +#### DelimitedFiles -* `@test_throws "some message" triggers_error()` can now be used to check whether the displayed error text - contains "some message" regardless of the specific exception type. - Regular expressions, lists of strings, and matching functions are also supported ([#41888]). -* `@testset foo()` can now be used to create a test set from a given function. The name of the test set - is the name of the called function. The called function can contain `@test` and other `@testset` - definitions, including to other function calls, while recording all intermediate test results ([#42518]). -* `TestLogger` and `LogRecord` are now exported from the Test stdlib ([#44080]). + +#### InteractiveUtils + + * `code_native` and `@code_native` now default to intel syntax instead of AT&T. + * `@time_imports` now shows the timing of any module `__init__()`s that are run ([#49529]) Deprecated or removed --------------------- @@ -207,72 +134,7 @@ External dependencies Tooling Improvements ---------------------- +-------------------- -* `GC.enable_logging(true)` can be used to log each garbage collection, with the - time it took and the amount of memory that was collected ([#43511]). -[#33711]: https://github.com/JuliaLang/julia/issues/33711 -[#36265]: https://github.com/JuliaLang/julia/issues/36265 -[#37343]: https://github.com/JuliaLang/julia/issues/37343 -[#37747]: https://github.com/JuliaLang/julia/issues/37747 -[#38791]: https://github.com/JuliaLang/julia/issues/38791 -[#39241]: https://github.com/JuliaLang/julia/issues/39241 -[#39245]: https://github.com/JuliaLang/julia/issues/39245 -[#40382]: https://github.com/JuliaLang/julia/issues/40382 -[#40642]: https://github.com/JuliaLang/julia/issues/40642 -[#40783]: https://github.com/JuliaLang/julia/issues/40783 -[#40980]: https://github.com/JuliaLang/julia/issues/40980 -[#41085]: https://github.com/JuliaLang/julia/issues/41085 -[#41312]: https://github.com/JuliaLang/julia/issues/41312 -[#41449]: https://github.com/JuliaLang/julia/issues/41449 -[#41551]: https://github.com/JuliaLang/julia/issues/41551 -[#41576]: https://github.com/JuliaLang/julia/issues/41576 -[#41612]: https://github.com/JuliaLang/julia/issues/41612 -[#41618]: https://github.com/JuliaLang/julia/issues/41618 -[#41639]: https://github.com/JuliaLang/julia/issues/41639 -[#41640]: https://github.com/JuliaLang/julia/issues/41640 -[#41742]: https://github.com/JuliaLang/julia/issues/41742 -[#41769]: https://github.com/JuliaLang/julia/issues/41769 -[#41794]: https://github.com/JuliaLang/julia/issues/41794 -[#41888]: https://github.com/JuliaLang/julia/issues/41888 -[#41936]: https://github.com/JuliaLang/julia/issues/41936 -[#42211]: https://github.com/JuliaLang/julia/issues/42211 -[#42225]: https://github.com/JuliaLang/julia/issues/42225 -[#42248]: https://github.com/JuliaLang/julia/issues/42248 -[#42314]: https://github.com/JuliaLang/julia/issues/42314 -[#42355]: https://github.com/JuliaLang/julia/issues/42355 -[#42428]: https://github.com/JuliaLang/julia/issues/42428 -[#42431]: https://github.com/JuliaLang/julia/issues/42431 -[#42469]: https://github.com/JuliaLang/julia/issues/42469 -[#42493]: https://github.com/JuliaLang/julia/issues/42493 -[#42513]: https://github.com/JuliaLang/julia/issues/42513 -[#42518]: https://github.com/JuliaLang/julia/issues/42518 -[#42529]: https://github.com/JuliaLang/julia/issues/42529 -[#42561]: https://github.com/JuliaLang/julia/issues/42561 -[#42768]: https://github.com/JuliaLang/julia/issues/42768 -[#42830]: https://github.com/JuliaLang/julia/issues/42830 -[#42918]: https://github.com/JuliaLang/julia/issues/42918 -[#42925]: https://github.com/JuliaLang/julia/issues/42925 -[#43110]: https://github.com/JuliaLang/julia/issues/43110 -[#43113]: https://github.com/JuliaLang/julia/issues/43113 -[#43127]: https://github.com/JuliaLang/julia/issues/43127 -[#43179]: https://github.com/JuliaLang/julia/issues/43179 -[#43191]: https://github.com/JuliaLang/julia/issues/43191 -[#43208]: https://github.com/JuliaLang/julia/issues/43208 -[#43232]: https://github.com/JuliaLang/julia/issues/43232 -[#43239]: https://github.com/JuliaLang/julia/issues/43239 -[#43271]: https://github.com/JuliaLang/julia/issues/43271 -[#43305]: https://github.com/JuliaLang/julia/issues/43305 -[#43354]: https://github.com/JuliaLang/julia/issues/43354 -[#43443]: https://github.com/JuliaLang/julia/issues/43443 -[#43511]: https://github.com/JuliaLang/julia/issues/43511 -[#43604]: https://github.com/JuliaLang/julia/issues/43604 -[#43671]: https://github.com/JuliaLang/julia/issues/43671 -[#43813]: https://github.com/JuliaLang/julia/issues/43813 -[#43852]: https://github.com/JuliaLang/julia/issues/43852 -[#43865]: https://github.com/JuliaLang/julia/issues/43865 -[#43919]: https://github.com/JuliaLang/julia/issues/43919 -[#44080]: https://github.com/JuliaLang/julia/issues/44080 -[#44136]: https://github.com/JuliaLang/julia/issues/44136 diff --git a/README.md b/README.md index 368d971fed77b..26fbb21a8a6a7 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,38 @@ - -Documentation: -[![Documentation][docs-img]][docs-url] - -[docs-img]: https://img.shields.io/badge/docs-v1-blue.svg "Documentation (version 1)" -[docs-url]: https://docs.julialang.org - -Continuous integration: -[![Continuous integration (master)][buildkite-master-img]][buildkite-master-url] - - -[buildkite-master-img]: https://badge.buildkite.com/f28e0d28b345f9fad5856ce6a8d64fffc7c70df8f4f2685cd8.svg?branch=master "Continuous integration (master)" -[buildkite-master-url]: https://buildkite.com/julialang/julia-master - -Code coverage: -[![Code coverage (Coveralls)][coveralls-img]][coveralls-url] -[![Code coverage (Codecov)][codecov-img]][codecov-url] - -[coveralls-img]: https://img.shields.io/coveralls/github/JuliaLang/julia/master.svg?label=coveralls "Code coverage (Coveralls)" -[coveralls-url]: https://coveralls.io/r/JuliaLang/julia?branch=master - -[codecov-img]: https://img.shields.io/codecov/c/github/JuliaLang/julia/master.svg?label=codecov "Code coverage (Codecov)" -[codecov-url]: https://codecov.io/github/JuliaLang/julia?branch=master + + + + + + + + + + + + + + + + +
Documentation + +
Continuous integration + +
Code coverage + Coverage Status + +
## The Julia Language @@ -67,7 +70,7 @@ If you would rather not compile the latest Julia from source, platform-specific tarballs with pre-compiled binaries are also [available for download](https://julialang.org/downloads/). The downloads page also provides details on the -[different tiers of support](https://julialang.org/downloads/#support-tiers) +[different tiers of support](https://julialang.org/downloads/#supported_platforms) for OS and platform combinations. If everything works correctly, you will see a Julia banner and an @@ -88,26 +91,25 @@ Then, acquire the source code by cloning the git repository: git clone https://github.com/JuliaLang/julia.git -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 changing to the Julia directory -and running: +of Julia. You can get this version by running: - git checkout v1.7.2 + git checkout v1.9.0 -Now run `make` to build the `julia` executable. +To build the `julia` executable, run `make` from within the julia directory. Building Julia requires 2GiB of disk space and approximately 4GiB of virtual memory. **Note:** The build process will fail badly if any of the build directory's parent directories have spaces or other shell meta-characters such as `$` or `:` in their names (this is due to a limitation in GNU make). -Once it is built, you can run the `julia` executable after you enter your julia directory and run +Once it is built, you can run the `julia` executable. From within the julia directory, run ./julia Your first test of Julia determines whether your build is working -properly. From the UNIX/Windows command prompt inside the `julia` -source directory, type `make testall`. You should see output that +properly. From the julia +directory, type `make testall`. You should see output that lists a series of running tests; if they complete without error, you should be in good shape to start using Julia. @@ -120,10 +122,9 @@ are included in the [build documentation](https://github.com/JuliaLang/julia/blo ### Uninstalling Julia -Julia does not install anything outside the directory it was cloned -into. Julia can be completely uninstalled by deleting this -directory. Julia packages are installed in `~/.julia` by default, and -can be uninstalled by deleting `~/.julia`. +By default, Julia does not install anything outside the directory it was cloned +into and `~/.julia`. Julia and the vast majority of Julia packages can be +completely uninstalled by deleting these two directories. ## Source Code Organization diff --git a/THIRDPARTY.md b/THIRDPARTY.md index 4a35bbdb1b7ce..51950d9e2c6a1 100644 --- a/THIRDPARTY.md +++ b/THIRDPARTY.md @@ -24,6 +24,10 @@ own licenses: - [LLVM](https://releases.llvm.org/12.0.1/LICENSE.TXT) [APACHE 2.0 with LLVM Exception] - [UTF8PROC](https://github.com/JuliaStrings/utf8proc) [MIT] +and optionally: + +- [ITTAPI](https://github.com/intel/ittapi/blob/master/LICENSES/BSD-3-Clause.txt) [BSD-3] + Julia's `stdlib` uses the following external libraries, which have their own licenses: - [DSFMT](https://github.com/MersenneTwister-Lab/dSFMT/blob/master/LICENSE.txt) [BSD-3] diff --git a/VERSION b/VERSION index 31083204c40c0..86a15e0570c4a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0-DEV +1.10.0-DEV diff --git a/base/Base.jl b/base/Base.jl index f39b227d6f663..06df2edb276fd 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -28,34 +28,57 @@ macro noinline() Expr(:meta, :noinline) 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; getfield(x, f)) -setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) # to get a decent error +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") getproperty(x::Tuple, f::Int) = (@inline; getfield(x, f)) setproperty!(x::Tuple, f::Int, v) = setfield!(x, f, v) # to get a decent error getproperty(x, f::Symbol) = (@inline; getfield(x, f)) -setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v)) +function setproperty!(x, f::Symbol, v) + ty = fieldtype(typeof(x), f) + val = v isa ty ? v : convert(ty, v) + return setfield!(x, f, val) +end dotgetproperty(x, f) = getproperty(x, f) -getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order)) -setproperty!(x::Module, f::Symbol, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error +getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getglobal(x, f, order)) +function setproperty!(x::Module, f::Symbol, v, order::Symbol=:monotonic) + @inline + ty = Core.get_binding_type(x, f) + val = v isa ty ? v : convert(ty, v) + return setglobal!(x, f, val, order) +end getproperty(x::Type, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order)) setproperty!(x::Type, f::Symbol, v, order::Symbol) = error("setfield! fields of Types should not be changed") getproperty(x::Tuple, f::Int, order::Symbol) = (@inline; getfield(x, f, order)) setproperty!(x::Tuple, f::Int, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error getproperty(x, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order)) -setproperty!(x, f::Symbol, v, order::Symbol) = (@inline; setfield!(x, f, convert(fieldtype(typeof(x), f), v), order)) +function setproperty!(x, f::Symbol, v, order::Symbol) + @inline + ty = fieldtype(typeof(x), f) + val = v isa ty ? v : convert(ty, v) + return setfield!(x, f, val, order) +end -swapproperty!(x, f::Symbol, v, order::Symbol=:notatomic) = - (@inline; Core.swapfield!(x, f, convert(fieldtype(typeof(x), f), v), order)) -modifyproperty!(x, f::Symbol, op, v, order::Symbol=:notatomic) = - (@inline; Core.modifyfield!(x, f, op, v, order)) -replaceproperty!(x, f::Symbol, expected, desired, success_order::Symbol=:notatomic, fail_order::Symbol=success_order) = - (@inline; Core.replacefield!(x, f, expected, convert(fieldtype(typeof(x), f), desired), success_order, fail_order)) +function swapproperty!(x, f::Symbol, v, order::Symbol=:not_atomic) + @inline + ty = fieldtype(typeof(x), f) + val = v isa ty ? v : convert(ty, v) + return Core.swapfield!(x, f, val, order) +end +function modifyproperty!(x, f::Symbol, op, v, order::Symbol=:not_atomic) + @inline + return Core.modifyfield!(x, f, op, v, order) +end +function replaceproperty!(x, f::Symbol, expected, 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.replacefield!(x, f, expected, val, success_order, fail_order) +end convert(::Type{Any}, Core.@nospecialize x) = x convert(::Type{T}, x::T) where {T} = x @@ -84,7 +107,7 @@ if false end """ - time_ns() + time_ns() -> UInt64 Get the time in nanoseconds. The time corresponding to 0 is undefined, and wraps every 5.8 years. """ @@ -100,13 +123,33 @@ include("generator.jl") include("reflection.jl") include("options.jl") +# define invoke(f, T, args...; kwargs...), without kwargs wrapping +# to forward to invoke +function Core.kwcall(kwargs::NamedTuple, ::typeof(invoke), f, T, args...) + @inline + # prepend kwargs and f to the invoked from the user + T = rewrap_unionall(Tuple{Core.Typeof(kwargs), Core.Typeof(f), (unwrap_unionall(T)::DataType).parameters...}, T) + return invoke(Core.kwcall, T, kwargs, f, args...) +end +# invoke does not have its own call cache, but kwcall for invoke does +setfield!(typeof(invoke).name.mt, :max_args, 3, :monotonic) # invoke, f, T, args... + +# define applicable(f, T, args...; kwargs...), without kwargs wrapping +# to forward to applicable +function Core.kwcall(kwargs::NamedTuple, ::typeof(applicable), @nospecialize(args...)) + @inline + return applicable(Core.kwcall, kwargs, args...) +end +function Core._hasmethod(@nospecialize(f), @nospecialize(t)) # this function has a special tfunc (TODO: make this a Builtin instead like applicable) + tt = rewrap_unionall(Tuple{Core.Typeof(f), (unwrap_unionall(t)::DataType).parameters...}, t) + return Core._hasmethod(tt) +end + + # core operations & types include("promotion.jl") include("tuple.jl") include("expr.jl") -Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} = (@inline; Pair{A, B}(convert(A, a)::A, convert(B, b)::B)) -#Pair{Any, B}(@nospecialize(a::Any), b) where {B} = (@inline; Pair{Any, B}(a, Base.convert(B, b)::B)) -#Pair{A, Any}(a, @nospecialize(b::Any)) where {A} = (@inline; Pair{A, Any}(Base.convert(A, a)::A, b)) include("pair.jl") include("traits.jl") include("range.jl") @@ -121,8 +164,21 @@ include("operators.jl") include("pointer.jl") include("refvalue.jl") include("refpointer.jl") + +# now replace the Pair constructor (relevant for NamedTuples) with one that calls our Base.convert +delete_method(which(Pair{Any,Any}, (Any, Any))) +@eval function (P::Type{Pair{A, B}})(@nospecialize(a), @nospecialize(b)) where {A, B} + @inline + return $(Expr(:new, :P, :(a isa A ? a : convert(A, a)), :(b isa B ? b : convert(B, b)))) +end + +# The REPL stdlib hooks into Base using this Ref +const REPL_MODULE_REF = Ref{Module}() + include("checked.jl") using .Checked +function cld end +function fld end # Lazy strings include("strings/lazy.jl") @@ -144,6 +200,7 @@ 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") @@ -174,9 +231,10 @@ include("multinverses.jl") using .MultiplicativeInverses include("abstractarraymath.jl") include("arraymath.jl") +include("slicearray.jl") # SIMD loops -@pure sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop +sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop include("simdloop.jl") using .SimdLoop @@ -260,6 +318,11 @@ include("sysinfo.jl") include("libc.jl") using .Libc: getpid, gethostname, time +# These used to be in build_h.jl and are retained for backwards compatibility. +# NOTE: keep in sync with `libblastrampoline_jll.libblastrampoline`. +const libblas_name = "libblastrampoline" * (Sys.iswindows() ? "-5" : "") +const liblapack_name = libblas_name + # Logging include("logging.jl") using .CoreLogging @@ -270,15 +333,13 @@ include("condition.jl") include("threads.jl") include("lock.jl") include("channels.jl") +include("partr.jl") include("task.jl") include("threads_overloads.jl") include("weakkeydict.jl") include("env.jl") -# BinaryPlatforms, used by Artifacts -include("binaryplatforms.jl") - # functions defined in Random function rand end function randn end @@ -295,15 +356,13 @@ include("process.jl") include("ttyhascolor.jl") include("secretbuffer.jl") -# RandomDevice support -include("randomdevice.jl") - # core math functions include("floatfuncs.jl") include("math.jl") using .Math const (√)=sqrt const (∛)=cbrt +const (∜)=fourthroot # now switch to a simple, race-y TLS, relative include for the rest of Base delete_method(which(include, (Module, String))) @@ -336,6 +395,9 @@ using .Order include("sort.jl") using .Sort +# BinaryPlatforms, used by Artifacts. Needs `Sort`. +include("binaryplatforms.jl") + # Fast math include("fastmath.jl") using .FastMath @@ -390,22 +452,21 @@ include("threadcall.jl") include("uuid.jl") include("pkgid.jl") include("toml_parser.jl") +include("linking.jl") include("loading.jl") # misc useful functions & macros include("timing.jl") include("util.jl") - +include("client.jl") include("asyncmap.jl") # deprecated functions include("deprecated.jl") - -# Some basic documentation +# +# Some additional basic documentation include("docs/basedocs.jl") -include("client.jl") - # Documentation -- should always be included last in sysimg. include("docs/Docs.jl") using .Docs @@ -417,6 +478,11 @@ end for m in methods(include) delete_method(m) end + +# This method is here only to be overwritten during the test suite to test +# various sysimg related invalidation scenarios. +a_method_to_overwrite_in_test() = inferencebarrier(1) + # These functions are duplicated in client.jl/include(::String) for # nicer stacktraces. Modifications here have to be backported there include(mod::Module, _path::AbstractString) = _include(identity, mod, _path) @@ -427,13 +493,13 @@ end_base_include = time_ns() const _sysimage_modules = PkgId[] in_sysimage(pkgid::PkgId) = pkgid in _sysimage_modules -# Precompiles for Revise +# 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))) + 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)) @@ -461,14 +527,55 @@ for match = _methods(+, (Int, Int), -1, get_world_counter()) # 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() + profile = nothing + try + while true + wait(PROFILE_PRINT_COND[]) + profile = @something(profile, require(PkgId(UUID("9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"), "Profile"))) + + invokelatest(profile.peek_report[]) + if Base.get_bool_env("JULIA_PROFILE_PEEK_HEAP_SNAPSHOT", false) === true + println(stderr, "Saving heap snapshot...") + fname = invokelatest(profile.take_heap_snapshot) + println(stderr, "Heap snapshot saved to `$(fname)`") + end + end + catch ex + if !isa(ex, InterruptException) + @error "Profile printing listener crashed" exception=ex,catch_backtrace() + end + end +end + function __init__() - # for the few uses of Libc.rand in Base: - Libc.srand() # Base library init reinit_stdio() Multimedia.reinit_displays() # since Multimedia.displays uses stdout as fallback @@ -480,9 +587,24 @@ function __init__() if haskey(ENV, "JULIA_MAX_NUM_PRECOMPILE_FILES") MAX_NUM_PRECOMPILE_FILES[] = parse(Int, ENV["JULIA_MAX_NUM_PRECOMPILE_FILES"]) end + # Profiling helper + @static if !Sys.iswindows() + # 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())) + end + # Prevent spawned Julia process from getting stuck waiting on Tracy to connect. + delete!(ENV, "JULIA_WAIT_FOR_TRACY") nothing end +# enable threads support +@eval PCRE PCRE_COMPILE_LOCK = Threads.SpinLock() + end + end # baremodule Base diff --git a/base/Enums.jl b/base/Enums.jl index 7b5e9587d5f6c..027677b432f37 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -17,7 +17,7 @@ abstract type Enum{T<:Integer} end basetype(::Type{<:Enum{T}}) where {T<:Integer} = T (::Type{T})(x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(bitcast(T2, x))::T -Base.cconvert(::Type{T}, x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(x) +Base.cconvert(::Type{T}, x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(x)::T Base.write(io::IO, x::Enum{T}) where {T<:Integer} = write(io, T(x)) Base.read(io::IO, ::Type{T}) where {T<:Enum} = T(read(io, basetype(T))) @@ -36,8 +36,8 @@ Base.print(io::IO, x::Enum) = print(io, _symbol(x)) function Base.show(io::IO, x::Enum) sym = _symbol(x) if !(get(io, :compact, false)::Bool) - from = get(io, :module, Main) - def = typeof(x).name.module + from = get(io, :module, Base.active_module()) + def = parentmodule(typeof(x)) if from === nothing || !Base.isvisible(sym, def, from) show(io, def) print(io, ".") @@ -125,6 +125,13 @@ To list all the instances of an enum use `instances`, e.g. julia> instances(Fruit) (apple, orange, kiwi) ``` + +It is possible to construct a symbol from an enum instance: + +```jldoctest fruitenum +julia> Symbol(apple) +:apple +``` """ macro enum(T::Union{Symbol,Expr}, syms...) if isempty(syms) @@ -144,8 +151,7 @@ macro enum(T::Union{Symbol,Expr}, syms...) values = Vector{basetype}() seen = Set{Symbol}() namemap = Dict{basetype,Symbol}() - lo = hi = 0 - i = zero(basetype) + lo = hi = i = zero(basetype) hasexpr = false if length(syms) == 1 && syms[1] isa Expr && syms[1].head === :block @@ -186,7 +192,6 @@ macro enum(T::Union{Symbol,Expr}, syms...) if length(values) == 1 lo = hi = i else - lo = min(lo, i) hi = max(hi, i) end i += oneunit(i) @@ -201,6 +206,9 @@ macro enum(T::Union{Symbol,Expr}, syms...) Enums.namemap(::Type{$(esc(typename))}) = $(esc(namemap)) Base.typemin(x::Type{$(esc(typename))}) = $(esc(typename))($lo) Base.typemax(x::Type{$(esc(typename))}) = $(esc(typename))($hi) + let enum_hash = hash($(esc(typename))) + Base.hash(x::$(esc(typename)), h::UInt) = hash(enum_hash, hash(Integer(x), h)) + end let insts = (Any[ $(esc(typename))(v) for v in $values ]...,) Base.instances(::Type{$(esc(typename))}) = insts end diff --git a/base/Makefile b/base/Makefile index f3ed73791085e..493302af78b02 100644 --- a/base/Makefile +++ b/base/Makefile @@ -1,9 +1,11 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) BUILDDIR := . JULIAHOME := $(abspath $(SRCDIR)/..) -include $(JULIAHOME)/deps/Versions.make include $(JULIAHOME)/Make.inc +# import LLVM_SHARED_LIB_NAME +include $(JULIAHOME)/deps/llvm-ver.make + TAGGED_RELEASE_BANNER := "" all: $(addprefix $(BUILDDIR)/,pcre_h.jl errno_h.jl build_h.jl.phony features_h.jl file_constants.jl uv_constants.jl version_git.jl.phony) @@ -47,8 +49,6 @@ else @echo "const MACHINE = \"$(XC_HOST)\"" >> $@ endif @echo "const libm_name = \"$(LIBMNAME)\"" >> $@ - @echo "const libblas_name = \"$(LIBBLASNAME)\"" >> $@ - @echo "const liblapack_name = \"$(LIBLAPACKNAME)\"" >> $@ ifeq ($(USE_BLAS64), 1) @echo "const USE_BLAS64 = true" >> $@ else @@ -60,6 +60,7 @@ else @echo "const USE_GPL_LIBS = false" >> $@ endif @echo "const libllvm_version_string = \"$$($(LLVM_CONFIG_HOST) --version)\"" >> $@ + @echo "const libllvm_name = \"$(LLVM_SHARED_LIB_NAME)\"" >> $@ @echo "const VERSION_STRING = \"$(JULIA_VERSION)\"" >> $@ @echo "const TAGGED_RELEASE_BANNER = \"$(TAGGED_RELEASE_BANNER)\"" >> $@ ifeq ($(OS),WINNT) @@ -69,6 +70,7 @@ ifeq ($(OS),WINNT) @printf 'const LIBDIR = "%s"\n' '$(subst /,\\,$(libdir_rel))' >> $@ @printf 'const LIBEXECDIR = "%s"\n' '$(subst /,\\,$(libexecdir_rel))' >> $@ @printf 'const PRIVATE_LIBDIR = "%s"\n' '$(subst /,\\,$(private_libdir_rel))' >> $@ + @printf 'const PRIVATE_LIBEXECDIR = "%s"\n' '$(subst /,\\,$(private_libexecdir_rel))' >> $@ @printf 'const INCLUDEDIR = "%s"\n' '$(subst /,\\,$(includedir_rel))' >> $@ else @echo "const SYSCONFDIR = \"$(sysconfdir_rel)\"" >> $@ @@ -77,6 +79,7 @@ else @echo "const LIBDIR = \"$(libdir_rel)\"" >> $@ @echo "const LIBEXECDIR = \"$(libexecdir_rel)\"" >> $@ @echo "const PRIVATE_LIBDIR = \"$(private_libdir_rel)\"" >> $@ + @echo "const PRIVATE_LIBEXECDIR = \"$(private_libexecdir_rel)\"" >> $@ @echo "const INCLUDEDIR = \"$(includedir_rel)\"" >> $@ endif ifeq ($(DARWIN_FRAMEWORK), 1) @@ -84,6 +87,10 @@ ifeq ($(DARWIN_FRAMEWORK), 1) @echo "const DARWIN_FRAMEWORK_NAME = \"$(FRAMEWORK_NAME)\"" >> $@ else @echo "const DARWIN_FRAMEWORK = false" >> $@ +endif +ifeq ($(OS), Darwin) + @echo "const MACOS_PRODUCT_VERSION = \"$(shell sw_vers -productVersion)\"" >> $@ + @echo "const MACOS_PLATFORM_VERSION = \"$(shell xcrun --show-sdk-version)\"" >> $@ endif @echo "const BUILD_TRIPLET = \"$(BB_TRIPLET_LIBGFORTRAN_CXXABI)\"" >> $@ @@ -171,7 +178,7 @@ $$(build_private_libdir)/$$(libname_$2): REALPATH=$$(libpath_$2); \ $$(call resolve_path,REALPATH) && \ [ -e "$$$$REALPATH" ] && \ - ([ ! -e "$$@" ] || rm "$$@") && \ + rm -f "$$@" && \ echo ln -sf "$$$$REALPATH" "$$@" && \ ln -sf "$$$$REALPATH" "$$@"; \ else \ @@ -195,9 +202,21 @@ endif $(build_bindir)/7z$(EXE): [ -e "$(7Z_PATH)" ] && \ - ([ ! -e "$@" ] || rm "$@") && \ + rm -f "$@" && \ ln -svf "$(7Z_PATH)" "$@" +symlink_lld: $(build_bindir)/lld$(EXE) + +ifneq ($(USE_SYSTEM_LLD),0) +SYMLINK_SYSTEM_LIBRARIES += symlink_lld +LLD_PATH := $(shell which lld$(EXE)) +endif + +$(build_bindir)/lld$(EXE): + [ -e "$(LLD_PATH)" ] && \ + rm -f "$@" && \ + ln -svf "$(LLD_PATH)" "$@" + # the following excludes: libuv.a, libutf8proc.a ifneq ($(USE_SYSTEM_LIBM),0) @@ -206,7 +225,21 @@ else ifneq ($(USE_SYSTEM_OPENLIBM),0) $(eval $(call symlink_system_library,OPENLIBM,$(LIBMNAME))) endif +# On macOS, libgcc_s has soversion 1.1 always on aarch64 and only for GCC 12+ +# (-> libgfortran 5) on x86_64 +ifeq ($(OS),Darwin) +ifeq ($(ARCH),aarch64) +$(eval $(call symlink_system_library,CSL,libgcc_s,1.1)) +else +ifeq ($(LIBGFORTRAN_VERSION),5) +$(eval $(call symlink_system_library,CSL,libgcc_s,1.1)) +else $(eval $(call symlink_system_library,CSL,libgcc_s,1)) +endif +endif +else +$(eval $(call symlink_system_library,CSL,libgcc_s,1)) +endif ifneq (,$(LIBGFORTRAN_VERSION)) $(eval $(call symlink_system_library,CSL,libgfortran,$(LIBGFORTRAN_VERSION))) endif @@ -256,7 +289,7 @@ $(build_private_libdir)/libLLVM.$(SHLIB_EXT): REALPATH=$(LLVM_CONFIG_HOST_LIBS) && \ $(call resolve_path,REALPATH) && \ [ -e "$$REALPATH" ] && \ - ([ ! -e "$@" ] || rm "$@") && \ + rm -f "$@" && \ echo ln -sf "$$REALPATH" "$@" && \ ln -sf "$$REALPATH" "$@" ifneq ($(USE_SYSTEM_LLVM),0) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 50b83dff86e6b..cb3956eb7c6d4 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -14,8 +14,8 @@ See also: [`AbstractVector`](@ref), [`AbstractMatrix`](@ref), [`eltype`](@ref), AbstractArray convert(::Type{T}, a::T) where {T<:AbstractArray} = a -convert(::Type{AbstractArray{T}}, a::AbstractArray) where {T} = AbstractArray{T}(a) -convert(::Type{AbstractArray{T,N}}, a::AbstractArray{<:Any,N}) where {T,N} = AbstractArray{T,N}(a) +convert(::Type{AbstractArray{T}}, a::AbstractArray) where {T} = AbstractArray{T}(a)::AbstractArray{T} +convert(::Type{AbstractArray{T,N}}, a::AbstractArray{<:Any,N}) where {T,N} = AbstractArray{T,N}(a)::AbstractArray{T,N} """ size(A::AbstractArray, [dim]) @@ -55,6 +55,9 @@ julia> A = fill(1, (5,6,7)); julia> axes(A, 2) Base.OneTo(6) + +julia> axes(A, 4) == 1:1 # all dimensions d > ndims(A) have size 1 +true ``` # Usage note @@ -101,12 +104,27 @@ end 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) | ...`. + +See also [`require_one_based_indexing`](@ref). """ -has_offset_axes(A) = _tuple_any(x->Int(first(x))::Int != 1, axes(A)) +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) -has_offset_axes(A...) = _tuple_any(has_offset_axes, A) +# 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 +""" + require_one_based_indexing(A::AbstractArray) + require_one_based_indexing(A,B...) + +Throw an `ArgumentError` if the indices of any argument start with something other than `1` along any axis. +See also [`has_offset_axes`](@ref). + +!!! compat "Julia 1.2" + This function requires at least Julia 1.2. +""" require_one_based_indexing(A...) = !has_offset_axes(A...) || throw(ArgumentError("offset arrays are not supported but got an array with index other than 1")) # Performance optimization: get rid of a branch on `d` in `axes(A, d)` @@ -121,13 +139,25 @@ axes1(iter) = oneto(length(iter)) Return an efficient array describing all valid indices for `a` arranged in the shape of `a` itself. -They keys of 1-dimensional arrays (vectors) are integers, whereas all other N-dimensional +The keys of 1-dimensional arrays (vectors) are integers, whereas all other N-dimensional arrays use [`CartesianIndex`](@ref) to describe their locations. Often the special array types [`LinearIndices`](@ref) and [`CartesianIndices`](@ref) are used to efficiently represent these arrays of integers and `CartesianIndex`es, respectively. Note that the `keys` of an array might not be the most efficient index type; for maximum performance use [`eachindex`](@ref) instead. + +# Examples +```jldoctest +julia> keys([4, 5, 6]) +3-element LinearIndices{1, Tuple{Base.OneTo{Int64}}}: + 1 + 2 + 3 + +julia> keys([4 5; 6 7]) +CartesianIndices((2, 2)) +``` """ keys(a::AbstractArray) = CartesianIndices(axes(a)) keys(a::AbstractVector) = LinearIndices(a) @@ -137,7 +167,7 @@ keys(a::AbstractVector) = LinearIndices(a) keytype(A::AbstractArray) Return the key type of an array. This is equal to the -`eltype` of the result of `keys(...)`, and is provided +[`eltype`](@ref) of the result of `keys(...)`, and is provided mainly for compatibility with the dictionary interface. # Examples @@ -153,17 +183,19 @@ CartesianIndex{2} For arrays, this function requires at least Julia 1.2. """ keytype(a::AbstractArray) = keytype(typeof(a)) +keytype(::Type{Union{}}, slurp...) = eltype(Union{}) keytype(A::Type{<:AbstractArray}) = CartesianIndex{ndims(A)} keytype(A::Type{<:AbstractVector}) = Int valtype(a::AbstractArray) = valtype(typeof(a)) +valtype(::Type{Union{}}, slurp...) = eltype(Union{}) """ valtype(T::Type{<:AbstractArray}) valtype(A::AbstractArray) -Return the value type of an array. This is identical to `eltype` and is +Return the value type of an array. This is identical to [`eltype`](@ref) and is provided mainly for compatibility with the dictionary interface. # Examples @@ -202,14 +234,14 @@ UInt8 ``` """ eltype(::Type) = Any -eltype(::Type{Bottom}) = throw(ArgumentError("Union{} does not have elements")) +eltype(::Type{Bottom}, slurp...) = throw(ArgumentError("Union{} does not have elements")) eltype(x) = eltype(typeof(x)) eltype(::Type{<:AbstractArray{E}}) where {E} = @isdefined(E) ? E : Any """ elsize(type) -Compute the memory stride in bytes between consecutive elements of `eltype` +Compute the memory stride in bytes between consecutive elements of [`eltype`](@ref) stored inside the given `type`, if the array elements are stored densely with a uniform linear stride. @@ -238,6 +270,7 @@ julia> ndims(A) """ ndims(::AbstractArray{T,N}) where {T,N} = N ndims(::Type{<:AbstractArray{<:Any,N}}) where {N} = N +ndims(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) """ length(collection) -> Integer @@ -294,31 +327,43 @@ end """ eachindex(A...) + eachindex(::IndexStyle, A::AbstractArray...) Create an iterable object for visiting each index of an `AbstractArray` `A` in an efficient manner. For array types that have opted into fast linear indexing (like `Array`), this is -simply the range `1:length(A)`. For other array types, return a specialized Cartesian -range to efficiently index into the array with indices specified for every dimension. For -other iterables, including strings and dictionaries, return an iterator object -supporting arbitrary index types (e.g. unevenly spaced or non-integer indices). +simply the range `1:length(A)` if they use 1-based indexing. +For array types that have not opted into fast linear indexing, a specialized Cartesian +range is typically returned to efficiently index into the array with indices specified +for every dimension. + +In general `eachindex` accepts arbitrary iterables, including strings and dictionaries, and returns +an iterator object supporting arbitrary index types (e.g. unevenly spaced or non-integer indices). + +If `A` is `AbstractArray` it is possible to explicitly specify the style of the indices that +should be returned by `eachindex` by passing a value having `IndexStyle` type as its first argument +(typically `IndexLinear()` if linear indices are required or `IndexCartesian()` if Cartesian +range is wanted). If you supply more than one `AbstractArray` argument, `eachindex` will create an -iterable object that is fast for all arguments (a [`UnitRange`](@ref) -if all inputs have fast linear indexing, a [`CartesianIndices`](@ref) -otherwise). +iterable object that is fast for all arguments (typically a [`UnitRange`](@ref) +if all inputs have fast linear indexing, a [`CartesianIndices`](@ref) otherwise). If the arrays have different sizes and/or dimensionalities, a `DimensionMismatch` exception will be thrown. + +See also [`pairs`](@ref)`(A)` to iterate over indices and values together, +and [`axes`](@ref)`(A, 2)` for valid indices along one dimension. + # Examples ```jldoctest -julia> A = [1 2; 3 4]; +julia> A = [10 20; 30 40]; julia> for i in eachindex(A) # linear indexing - println(i) + println("A[", i, "] == ", A[i]) end -1 -2 -3 -4 +A[1] == 10 +A[2] == 30 +A[3] == 20 +A[4] == 40 julia> for i in eachindex(view(A, 1:2, 1:1)) # Cartesian indexing println(i) @@ -455,7 +500,7 @@ first(itr, n::Integer) = collect(Iterators.take(itr, n)) # Faster method for vectors function first(v::AbstractVector, n::Integer) n < 0 && throw(ArgumentError("Number of elements must be nonnegative")) - @inbounds v[begin:min(begin + n - 1, end)] + v[range(begin, length=min(n, checked_length(v)))] end """ @@ -505,7 +550,7 @@ last(itr, n::Integer) = reverse!(collect(Iterators.take(Iterators.reverse(itr), # Faster method for arrays function last(v::AbstractVector, n::Integer) n < 0 && throw(ArgumentError("Number of elements must be nonnegative")) - @inbounds v[max(begin, end - n + 1):end] + v[range(stop=lastindex(v), length=min(n, checked_length(v)))] end """ @@ -725,6 +770,8 @@ false checkindex(::Type{Bool}, inds::AbstractUnitRange, 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) @@ -879,13 +926,12 @@ See also [`copyto!`](@ref). is available from the `Future` standard library as `Future.copy!`. """ function copy!(dst::AbstractVector, src::AbstractVector) + firstindex(dst) == firstindex(src) || throw(ArgumentError( + "vectors must have the same offset for copy! (consider using `copyto!`)")) if length(dst) != length(src) resize!(dst, length(src)) end - for i in eachindex(dst, src) - @inbounds dst[i] = src[i] - end - dst + copyto!(dst, src) end function copy!(dst::AbstractArray, src::AbstractArray) @@ -896,6 +942,10 @@ end ## from general iterable to any array +# This is `Experimental.@max_methods 1 function copyto! end`, which is not +# defined at this point in bootstrap. +typeof(function copyto! end).name.max_methods = UInt8(1) + function copyto!(dest::AbstractArray, src) destiter = eachindex(dest) y = iterate(destiter) @@ -910,9 +960,17 @@ end function copyto!(dest::AbstractArray, dstart::Integer, src) i = Int(dstart) - for x in src - dest[i] = x - i += 1 + if haslength(src) && length(dest) > 0 + @boundscheck checkbounds(dest, i:(i + length(src) - 1)) + for x in src + @inbounds dest[i] = x + i += 1 + end + else + for x in src + dest[i] = x + i += 1 + end end return dest end @@ -1011,6 +1069,10 @@ julia> y """ function copyto!(dest::AbstractArray, src::AbstractArray) isempty(src) && return dest + if dest isa BitArray + # avoid ambiguities with other copyto!(::AbstractArray, ::SourceArray) methods + return _copyto_bitarray!(dest, src) + end src′ = unalias(dest, src) copyto_unaliased!(IndexStyle(dest), dest, IndexStyle(src′), src′) end @@ -1052,7 +1114,7 @@ function copyto_unaliased!(deststyle::IndexStyle, dest::AbstractArray, srcstyle: # Dual-iterator implementation ret = iterate(iterdest) @inbounds for a in src - idx, state = ret + idx, state = ret::NTuple{2,Any} dest[idx] = a ret = iterate(iterdest, state) end @@ -1080,8 +1142,9 @@ function copyto!(dest::AbstractArray, dstart::Integer, destinds, srcinds = LinearIndices(dest), LinearIndices(src) (checkbounds(Bool, destinds, dstart) && checkbounds(Bool, destinds, dstart+n-1)) || throw(BoundsError(dest, dstart:dstart+n-1)) (checkbounds(Bool, srcinds, sstart) && checkbounds(Bool, srcinds, sstart+n-1)) || throw(BoundsError(src, sstart:sstart+n-1)) - @inbounds for i = 0:(n-1) - dest[dstart+i] = src[sstart+i] + src′ = unalias(dest, src) + @inbounds for i = 0:n-1 + dest[dstart+i] = src′[sstart+i] end return dest end @@ -1103,11 +1166,12 @@ function copyto!(B::AbstractVecOrMat{R}, ir_dest::AbstractRange{Int}, jr_dest::A end @boundscheck checkbounds(B, ir_dest, jr_dest) @boundscheck checkbounds(A, ir_src, jr_src) + A′ = unalias(B, A) jdest = first(jr_dest) for jsrc in jr_src idest = first(ir_dest) for isrc in ir_src - @inbounds B[idest,jdest] = A[isrc,jsrc] + @inbounds B[idest,jdest] = A′[isrc,jsrc] idest += step(ir_dest) end jdest += step(jr_dest) @@ -1115,10 +1179,10 @@ function copyto!(B::AbstractVecOrMat{R}, ir_dest::AbstractRange{Int}, jr_dest::A return B end -function copyto_axcheck!(dest, src) - @noinline checkaxs(axd, axs) = axd == axs || throw(DimensionMismatch("axes must agree, got $axd and $axs")) +@noinline _checkaxs(axd, axs) = axd == axs || throw(DimensionMismatch("axes must agree, got $axd and $axs")) - checkaxs(axes(dest), axes(src)) +function copyto_axcheck!(dest, src) + _checkaxs(axes(dest), axes(src)) copyto!(dest, src) end @@ -1244,7 +1308,7 @@ function unsafe_getindex(A::AbstractArray, I...) r end -struct CanonicalIndexError +struct CanonicalIndexError <: Exception func::String type::Any CanonicalIndexError(func::String, @nospecialize(type)) = new(func, type) @@ -1681,7 +1745,7 @@ function cat_shape(dims, shapes::Tuple) end return out_shape end -# The new way to compute the shape (more inferrable than combining cat_size & cat_shape, due to Varargs + issue#36454) +# The new way to compute the shape (more inferable than combining cat_size & cat_shape, due to Varargs + issue#36454) cat_size_shape(dims) = ntuple(zero, Val(length(dims))) @inline cat_size_shape(dims, X, tail...) = _cat_size_shape(dims, _cshp(1, dims, (), cat_size(X)), tail...) _cat_size_shape(dims, shape) = shape @@ -1712,21 +1776,16 @@ end _cs(d, a, b) = (a == b ? a : throw(DimensionMismatch( "mismatch in dimension $d (expected $a got $b)"))) -function dims2cat(::Val{n}) where {n} - n <= 0 && throw(ArgumentError("cat dimension must be a positive integer, but got $n")) - ntuple(i -> (i == n), Val(n)) -end - +dims2cat(::Val{dims}) where dims = dims2cat(dims) function dims2cat(dims) - if any(dims .<= 0) + if any(≤(0), dims) throw(ArgumentError("All cat dimensions must be positive integers, but got $dims")) end ntuple(in(dims), maximum(dims)) end -_cat(dims, X...) = cat_t(promote_eltypeof(X...), X...; dims=dims) +_cat(dims, X...) = _cat_t(dims, promote_eltypeof(X...), X...) -@inline cat_t(::Type{T}, X...; dims) where {T} = _cat_t(dims, T, X...) @inline function _cat_t(dims, ::Type{T}, X...) where {T} catdims = dims2cat(dims) shape = cat_size_shape(catdims, X...) @@ -1736,6 +1795,9 @@ _cat(dims, X...) = cat_t(promote_eltypeof(X...), X...; dims=dims) end return __cat(A, shape, catdims, X...) end +# this version of `cat_t` is not very kind for inference and so its usage should be avoided, +# nevertheless it is here just for compat after https://github.com/JuliaLang/julia/pull/45028 +@inline cat_t(::Type{T}, X...; dims) where {T} = _cat_t(dims, T, X...) # Why isn't this called `__cat!`? __cat(A, shape, catdims, X...) = __cat_offset!(A, shape, catdims, ntuple(zero, length(shape)), X...) @@ -1765,41 +1827,50 @@ end """ vcat(A...) -Concatenate along dimension 1. To efficiently concatenate a large vector of arrays, -use `reduce(vcat, x)`. +Concatenate arrays or numbers vertically. Equivalent to [`cat`](@ref)`(A...; dims=1)`, +and to the syntax `[a; b; c]`. + +To concatenate a large vector of arrays, `reduce(vcat, A)` calls an efficient method +when `A isa AbstractVector{<:AbstractVecOrMat}`, rather than working pairwise. + +See also [`hcat`](@ref), [`Iterators.flatten`](@ref), [`stack`](@ref). # Examples ```jldoctest -julia> a = [1 2 3 4 5] -1×5 Matrix{Int64}: - 1 2 3 4 5 +julia> v = vcat([1,2], [3,4]) +4-element Vector{Int64}: + 1 + 2 + 3 + 4 -julia> b = [6 7 8 9 10; 11 12 13 14 15] -2×5 Matrix{Int64}: - 6 7 8 9 10 - 11 12 13 14 15 +julia> v == vcat(1, 2, [3,4]) # accepts numbers +true -julia> vcat(a,b) -3×5 Matrix{Int64}: - 1 2 3 4 5 - 6 7 8 9 10 - 11 12 13 14 15 +julia> v == [1; 2; [3,4]] # syntax for the same operation +true -julia> c = ([1 2 3], [4 5 6]) -([1 2 3], [4 5 6]) +julia> summary(ComplexF64[1; 2; [3,4]]) # syntax for supplying the element type +"4-element Vector{ComplexF64}" -julia> vcat(c...) -2×3 Matrix{Int64}: - 1 2 3 - 4 5 6 +julia> vcat(range(1, 2, length=3)) # collects lazy ranges +3-element Vector{Float64}: + 1.0 + 1.5 + 2.0 + +julia> two = ([10, 20, 30]', Float64[4 5 6; 7 8 9]) # row vector and a matrix +([10 20 30], [4.0 5.0 6.0; 7.0 8.0 9.0]) -julia> vs = [[1, 2], [3, 4], [5, 6]] -3-element Vector{Vector{Int64}}: - [1, 2] - [3, 4] - [5, 6] +julia> vcat(two...) +3×3 Matrix{Float64}: + 10.0 20.0 30.0 + 4.0 5.0 6.0 + 7.0 8.0 9.0 -julia> reduce(vcat, vs) +julia> vs = [[1, 2], [3, 4], [5, 6]]; + +julia> reduce(vcat, vs) # more efficient than vcat(vs...) 6-element Vector{Int64}: 1 2 @@ -1807,127 +1878,133 @@ julia> reduce(vcat, vs) 4 5 6 + +julia> ans == collect(Iterators.flatten(vs)) +true ``` """ vcat(X...) = cat(X...; dims=Val(1)) """ hcat(A...) -Concatenate along dimension 2. To efficiently concatenate a large vector of arrays, -use `reduce(hcat, x)`. +Concatenate arrays or numbers horizontally. Equivalent to [`cat`](@ref)`(A...; dims=2)`, +and to the syntax `[a b c]` or `[a;; b;; c]`. + +For a large vector of arrays, `reduce(hcat, A)` calls an efficient method +when `A isa AbstractVector{<:AbstractVecOrMat}`. +For a vector of vectors, this can also be written [`stack`](@ref)`(A)`. + +See also [`vcat`](@ref), [`hvcat`](@ref). # Examples ```jldoctest -julia> a = [1; 2; 3; 4; 5] -5-element Vector{Int64}: - 1 - 2 - 3 - 4 - 5 +julia> hcat([1,2], [3,4], [5,6]) +2×3 Matrix{Int64}: + 1 3 5 + 2 4 6 -julia> b = [6 7; 8 9; 10 11; 12 13; 14 15] -5×2 Matrix{Int64}: - 6 7 - 8 9 - 10 11 - 12 13 - 14 15 - -julia> hcat(a,b) -5×3 Matrix{Int64}: - 1 6 7 - 2 8 9 - 3 10 11 - 4 12 13 - 5 14 15 - -julia> c = ([1; 2; 3], [4; 5; 6]) -([1, 2, 3], [4, 5, 6]) - -julia> hcat(c...) -3×2 Matrix{Int64}: - 1 4 - 2 5 - 3 6 +julia> hcat(1, 2, [30 40], [5, 6, 7]') # accepts numbers +1×7 Matrix{Int64}: + 1 2 30 40 5 6 7 -julia> x = Matrix(undef, 3, 0) # x = [] would have created an Array{Any, 1}, but need an Array{Any, 2} -3×0 Matrix{Any} +julia> ans == [1 2 [30 40] [5, 6, 7]'] # syntax for the same operation +true -julia> hcat(x, [1; 2; 3]) -3×1 Matrix{Any}: - 1 - 2 - 3 +julia> Float32[1 2 [30 40] [5, 6, 7]'] # syntax for supplying the eltype +1×7 Matrix{Float32}: + 1.0 2.0 30.0 40.0 5.0 6.0 7.0 -julia> vs = [[1, 2], [3, 4], [5, 6]] -3-element Vector{Vector{Int64}}: - [1, 2] - [3, 4] - [5, 6] +julia> ms = [zeros(2,2), [1 2; 3 4], [50 60; 70 80]]; -julia> reduce(hcat, vs) -2×3 Matrix{Int64}: - 1 3 5 - 2 4 6 +julia> reduce(hcat, ms) # more efficient than hcat(ms...) +2×6 Matrix{Float64}: + 0.0 0.0 1.0 2.0 50.0 60.0 + 0.0 0.0 3.0 4.0 70.0 80.0 + +julia> stack(ms) |> summary # disagrees on a vector of matrices +"2×2×3 Array{Float64, 3}" + +julia> hcat(Int[], Int[], Int[]) # empty vectors, each of size (0,) +0×3 Matrix{Int64} + +julia> hcat([1.1, 9.9], Matrix(undef, 2, 0)) # hcat with empty 2×0 Matrix +2×1 Matrix{Any}: + 1.1 + 9.9 ``` """ hcat(X...) = cat(X...; dims=Val(2)) -typed_vcat(::Type{T}, X...) where T = cat_t(T, X...; dims=Val(1)) -typed_hcat(::Type{T}, X...) where T = cat_t(T, X...; dims=Val(2)) +typed_vcat(::Type{T}, X...) where T = _cat_t(Val(1), T, X...) +typed_hcat(::Type{T}, X...) where T = _cat_t(Val(2), T, X...) """ cat(A...; dims) -Concatenate the input arrays along the specified dimensions in the iterable `dims`. For -dimensions not in `dims`, all input arrays should have the same size, which will also be the -size of the output array along that dimension. For dimensions in `dims`, the size of the -output array is the sum of the sizes of the input arrays along that dimension. If `dims` is -a single number, the different arrays are tightly stacked along that dimension. If `dims` is -an iterable containing several dimensions, this allows one to construct block diagonal -matrices and their higher-dimensional analogues by simultaneously increasing several -dimensions for every new input array and putting zero blocks elsewhere. For example, -`cat(matrices...; dims=(1,2))` builds a block diagonal matrix, i.e. a block matrix with -`matrices[1]`, `matrices[2]`, ... as diagonal blocks and matching zero blocks away from the -diagonal. +Concatenate the input arrays along the dimensions specified in `dims`. + +Along a dimension `d in dims`, the size of the output array is `sum(size(a,d) for +a in A)`. +Along other dimensions, all input arrays should have the same size, +which will also be the size of the output array along those dimensions. -See also [`hcat`](@ref), [`vcat`](@ref), [`hvcat`](@ref), [`repeat`](@ref). +If `dims` is a single number, the different arrays are tightly packed along that dimension. +If `dims` is an iterable containing several dimensions, the positions along these dimensions +are increased simultaneously for each input array, filling with zero elsewhere. +This allows one to construct block-diagonal matrices as `cat(matrices...; dims=(1,2))`, +and their higher-dimensional analogues. + +The special case `dims=1` is [`vcat`](@ref), and `dims=2` is [`hcat`](@ref). +See also [`hvcat`](@ref), [`hvncat`](@ref), [`stack`](@ref), [`repeat`](@ref). + +The keyword also accepts `Val(dims)`. + +!!! compat "Julia 1.8" + For multiple dimensions `dims = Val(::Tuple)` was added in Julia 1.8. # Examples ```jldoctest -julia> cat([1 2; 3 4], [pi, pi], fill(10, 2,3,1); dims=2) +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 -julia> cat(true, trues(2,2), trues(4)', dims=(1,2)) +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 ``` """ @inline cat(A...; dims) = _cat(dims, A...) -_cat(catdims, A::AbstractArray{T}...) where {T} = cat_t(T, A...; dims=catdims) +# `@constprop :aggressive` allows `catdims` to be propagated as constant improving return type inference +@constprop :aggressive _cat(catdims, A::AbstractArray{T}...) where {T} = _cat_t(catdims, T, A...) # The specializations for 1 and 2 inputs are important # especially when running with --inline=no, see #11158 +# The specializations for Union{AbstractVecOrMat,Number} are necessary +# to have more specialized methods here than in LinearAlgebra/uniformscaling.jl vcat(A::AbstractArray) = cat(A; dims=Val(1)) vcat(A::AbstractArray, B::AbstractArray) = cat(A, B; dims=Val(1)) vcat(A::AbstractArray...) = cat(A...; dims=Val(1)) +vcat(A::Union{AbstractVecOrMat,Number}...) = cat(A...; dims=Val(1)) hcat(A::AbstractArray) = cat(A; dims=Val(2)) hcat(A::AbstractArray, B::AbstractArray) = cat(A, B; dims=Val(2)) hcat(A::AbstractArray...) = cat(A...; dims=Val(2)) +hcat(A::Union{AbstractVecOrMat,Number}...) = cat(A...; dims=Val(2)) -typed_vcat(T::Type, A::AbstractArray) = cat_t(T, A; dims=Val(1)) -typed_vcat(T::Type, A::AbstractArray, B::AbstractArray) = cat_t(T, A, B; dims=Val(1)) -typed_vcat(T::Type, A::AbstractArray...) = cat_t(T, A...; dims=Val(1)) -typed_hcat(T::Type, A::AbstractArray) = cat_t(T, A; dims=Val(2)) -typed_hcat(T::Type, A::AbstractArray, B::AbstractArray) = cat_t(T, A, B; dims=Val(2)) -typed_hcat(T::Type, A::AbstractArray...) = cat_t(T, A...; dims=Val(2)) +typed_vcat(T::Type, A::AbstractArray) = _cat_t(Val(1), T, A) +typed_vcat(T::Type, A::AbstractArray, B::AbstractArray) = _cat_t(Val(1), T, A, B) +typed_vcat(T::Type, A::AbstractArray...) = _cat_t(Val(1), T, A...) +typed_hcat(T::Type, A::AbstractArray) = _cat_t(Val(2), T, A) +typed_hcat(T::Type, A::AbstractArray, B::AbstractArray) = _cat_t(Val(2), T, A, B) +typed_hcat(T::Type, A::AbstractArray...) = _cat_t(Val(2), T, A...) # 2d horizontal and vertical concatenation @@ -1935,7 +2012,7 @@ typed_hcat(T::Type, A::AbstractArray...) = cat_t(T, A...; dims=Val(2)) hvcat_rows(rows::Tuple...) = hvcat(map(length, rows), (rows...)...) typed_hvcat_rows(T::Type, rows::Tuple...) = typed_hvcat(T, map(length, rows), (rows...)...) -function hvcat(nbc::Integer, as...) +function hvcat(nbc::Int, as...) # nbc = # of block columns n = length(as) mod(n,nbc) != 0 && @@ -1945,11 +2022,12 @@ function hvcat(nbc::Integer, as...) end """ - hvcat(rows::Tuple{Vararg{Int}}, values...) + hvcat(blocks_per_row::Union{Tuple{Vararg{Int}}, Int}, values...) Horizontal and vertical concatenation in one call. This function is called for block matrix syntax. The first argument specifies the number of arguments to concatenate in each block -row. +row. If the first argument is a single integer `n`, then all block rows are assumed to have `n` +block columns. # Examples ```jldoctest @@ -1977,10 +2055,9 @@ julia> hvcat((2,2,2), a,b,c,d,e,f) 1 2 3 4 5 6 +julia> hvcat((2,2,2), a,b,c,d,e,f) == hvcat(2, a,b,c,d,e,f) +true ``` - -If the first argument is a single integer `n`, then all block rows are assumed to have `n` -block columns. """ hvcat(rows::Tuple{Vararg{Int}}, xs::AbstractVecOrMat...) = typed_hvcat(promote_eltype(xs...), rows, xs...) hvcat(rows::Tuple{Vararg{Int}}, xs::AbstractVecOrMat{T}...) where {T} = typed_hvcat(T, rows, xs...) @@ -2070,6 +2147,8 @@ end hvcat(rows::Tuple{Vararg{Int}}, xs::Number...) = typed_hvcat(promote_typeof(xs...), rows, xs...) hvcat(rows::Tuple{Vararg{Int}}, xs...) = typed_hvcat(promote_eltypeof(xs...), rows, xs...) +# the following method is needed to provide a more specific one compared to LinearAlgebra/uniformscaling.jl +hvcat(rows::Tuple{Vararg{Int}}, xs::Union{AbstractVecOrMat,Number}...) = typed_hvcat(promote_eltypeof(xs...), rows, xs...) function typed_hvcat(::Type{T}, rows::Tuple{Vararg{Int}}, xs::Number...) where T nr = length(rows) @@ -2160,14 +2239,13 @@ julia> hvncat(((3, 3), (3, 3), (6,)), true, a, b, c, d, e, f) 4 5 6 ``` - -# Examples for construction of the arguments: -```julia +# Examples for construction of the arguments +``` [a b c ; d e f ;;; g h i ; j k l ;;; m n o ; p q r ;;; s t u ; v w x] -=> dims = (2, 3, 4) +⇒ dims = (2, 3, 4) [a b ; c ;;; d ;;;;] ___ _ _ @@ -2178,7 +2256,7 @@ julia> hvncat(((3, 3), (3, 3), (6,)), true, a, b, c, d, e, f) 4 = elements in each 3d slice (4,) _____________ 4 = elements in each 4d slice (4,) - => shape = ((2, 1, 1), (3, 1), (4,), (4,)) with `rowfirst` = true +⇒ shape = ((2, 1, 1), (3, 1), (4,), (4,)) with `row_first` = true ``` """ hvncat(dimsshape::Tuple, row_first::Bool, xs...) = _hvncat(dimsshape, row_first, xs...) @@ -2208,7 +2286,8 @@ _typed_hvncat(::Type, ::Val{0}, ::AbstractArray...) = _typed_hvncat_0d_only_one( _typed_hvncat_0d_only_one() = throw(ArgumentError("a 0-dimensional array may only contain exactly one element")) -_typed_hvncat(T::Type, dim::Int, ::Bool, xs...) = _typed_hvncat(T, Val(dim), xs...) # catches from _hvncat type promoters +# `@constprop :aggressive` here to form constant `Val(dim)` type to get type stability +@constprop :aggressive _typed_hvncat(T::Type, dim::Int, ::Bool, xs...) = _typed_hvncat(T, Val(dim), xs...) # catches from _hvncat type promoters function _typed_hvncat(::Type{T}, ::Val{N}) where {T, N} N < 0 && @@ -2373,6 +2452,9 @@ function _typed_hvncat_dims(::Type{T}, dims::NTuple{N, Int}, row_first::Bool, as # validate shapes for lowest level of concatenation d = findfirst(>(1), dims) if d !== nothing # all dims are 1 + if row_first && d < 3 + d = d == 1 ? 2 : 1 + end nblocks = length(as) ÷ dims[d] for b ∈ 1:nblocks offset = ((b - 1) * dims[d]) @@ -2380,7 +2462,7 @@ function _typed_hvncat_dims(::Type{T}, dims::NTuple{N, Int}, row_first::Bool, as for i ∈ offset .+ (2:dims[d]) for dd ∈ 1:N dd == d && continue - if size(as[startelementi], dd) != size(as[i], dd) + if cat_size(as[startelementi], dd) != cat_size(as[i], dd) throw(ArgumentError("incompatible shape in element $i")) end end @@ -2473,7 +2555,7 @@ function _typed_hvncat_shape(::Type{T}, shape::NTuple{N, Tuple}, row_first, as:: shapelength == lengthas || throw(ArgumentError("number of elements does not match shape; expected $(shapelength), got $lengthas)")) # discover dimensions nd = max(N, cat_ndims(as[1])) - outdims = zeros(Int, nd) + outdims = fill(-1, nd) currentdims = zeros(Int, nd) blockcounts = zeros(Int, nd) shapepos = ones(Int, nd) @@ -2498,7 +2580,7 @@ function _typed_hvncat_shape(::Type{T}, shape::NTuple{N, Tuple}, row_first, as:: isendblock = blockcounts[d] == shapev[d][shapepos[d]] if isendblock - if outdims[d] == 0 + if outdims[d] == -1 outdims[d] = currentdims[d] elseif outdims[d] != currentdims[d] throw(ArgumentError("argument $i has a mismatched number of elements along axis $ad; \ @@ -2573,6 +2655,236 @@ end Ai end +""" + stack(iter; [dims]) + +Combine a collection of arrays (or other iterable objects) of equal size +into one larger array, by arranging them along one or more new dimensions. + +By default the axes of the elements are placed first, +giving `size(result) = (size(first(iter))..., size(iter)...)`. +This has the same order of elements as [`Iterators.flatten`](@ref)`(iter)`. + +With keyword `dims::Integer`, instead the `i`th element of `iter` becomes the slice +[`selectdim`](@ref)`(result, dims, i)`, so that `size(result, dims) == length(iter)`. +In this case `stack` reverses the action of [`eachslice`](@ref) with the same `dims`. + +The various [`cat`](@ref) functions also combine arrays. However, these all +extend the arrays' existing (possibly trivial) dimensions, rather than placing +the arrays along new dimensions. +They also accept arrays as separate arguments, rather than a single collection. + +!!! compat "Julia 1.9" + This function requires at least Julia 1.9. + +# Examples +```jldoctest +julia> vecs = (1:2, [30, 40], Float32[500, 600]); + +julia> mat = stack(vecs) +2×3 Matrix{Float32}: + 1.0 30.0 500.0 + 2.0 40.0 600.0 + +julia> mat == hcat(vecs...) == reduce(hcat, collect(vecs)) +true + +julia> vec(mat) == vcat(vecs...) == reduce(vcat, collect(vecs)) +true + +julia> stack(zip(1:4, 10:99)) # accepts any iterators of iterators +2×4 Matrix{Int64}: + 1 2 3 4 + 10 11 12 13 + +julia> vec(ans) == collect(Iterators.flatten(zip(1:4, 10:99))) +true + +julia> stack(vecs; dims=1) # unlike any cat function, 1st axis of vecs[1] is 2nd axis of result +3×2 Matrix{Float32}: + 1.0 2.0 + 30.0 40.0 + 500.0 600.0 + +julia> x = rand(3,4); + +julia> x == stack(eachcol(x)) == stack(eachrow(x), dims=1) # inverse of eachslice +true +``` + +Higher-dimensional examples: + +```jldoctest +julia> A = rand(5, 7, 11); + +julia> E = eachslice(A, dims=2); # a vector of matrices + +julia> (element = size(first(E)), container = size(E)) +(element = (5, 11), container = (7,)) + +julia> stack(E) |> size +(5, 11, 7) + +julia> stack(E) == stack(E; dims=3) == cat(E...; dims=3) +true + +julia> A == stack(E; dims=2) +true + +julia> M = (fill(10i+j, 2, 3) for i in 1:5, j in 1:7); + +julia> (element = size(first(M)), container = size(M)) +(element = (2, 3), container = (5, 7)) + +julia> stack(M) |> size # keeps all dimensions +(2, 3, 5, 7) + +julia> stack(M; dims=1) |> size # vec(container) along dims=1 +(35, 2, 3) + +julia> hvcat(5, M...) |> size # hvcat puts matrices next to each other +(14, 15) +``` +""" +stack(iter; dims=:) = _stack(dims, iter) + +""" + stack(f, args...; [dims]) + +Apply a function to each element of a collection, and `stack` the result. +Or to several collections, [`zip`](@ref)ped together. + +The function should return arrays (or tuples, or other iterators) all of the same size. +These become slices of the result, each separated along `dims` (if given) or by default +along the last dimensions. + +See also [`mapslices`](@ref), [`eachcol`](@ref). + +# Examples +```jldoctest +julia> stack(c -> (c, c-32), "julia") +2×5 Matrix{Char}: + 'j' 'u' 'l' 'i' 'a' + 'J' 'U' 'L' 'I' 'A' + +julia> stack(eachrow([1 2 3; 4 5 6]), (10, 100); dims=1) do row, n + vcat(row, row .* n, row ./ n) + end +2×9 Matrix{Float64}: + 1.0 2.0 3.0 10.0 20.0 30.0 0.1 0.2 0.3 + 4.0 5.0 6.0 400.0 500.0 600.0 0.04 0.05 0.06 +``` +""" +stack(f, iter; dims=:) = _stack(dims, f(x) for x in iter) +stack(f, xs, yzs...; dims=:) = _stack(dims, f(xy...) for xy in zip(xs, yzs...)) + +_stack(dims::Union{Integer, Colon}, iter) = _stack(dims, IteratorSize(iter), iter) + +_stack(dims, ::IteratorSize, iter) = _stack(dims, collect(iter)) + +function _stack(dims, ::Union{HasShape, HasLength}, iter) + S = @default_eltype iter + T = S != Union{} ? eltype(S) : Any # Union{} occurs for e.g. stack(1,2), postpone the error + if isconcretetype(T) + _typed_stack(dims, T, S, iter) + else # Need to look inside, but shouldn't run an expensive iterator twice: + array = iter isa Union{Tuple, AbstractArray} ? iter : collect(iter) + isempty(array) && return _empty_stack(dims, T, S, iter) + T2 = mapreduce(eltype, promote_type, array) + _typed_stack(dims, T2, eltype(array), array) + end +end + +function _typed_stack(::Colon, ::Type{T}, ::Type{S}, A, Aax=_iterator_axes(A)) where {T, S} + xit = iterate(A) + nothing === xit && return _empty_stack(:, T, S, A) + x1, _ = xit + ax1 = _iterator_axes(x1) + B = similar(_ensure_array(x1), T, ax1..., Aax...) + off = firstindex(B) + len = length(x1) + while xit !== nothing + x, state = xit + _stack_size_check(x, ax1) + copyto!(B, off, x) + off += len + xit = iterate(A, state) + end + B +end + +_iterator_axes(x) = _iterator_axes(x, IteratorSize(x)) +_iterator_axes(x, ::HasLength) = (OneTo(length(x)),) +_iterator_axes(x, ::IteratorSize) = axes(x) + +# For some dims values, stack(A; dims) == stack(vec(A)), and the : path will be faster +_typed_stack(dims::Integer, ::Type{T}, ::Type{S}, A) where {T,S} = + _typed_stack(dims, T, S, IteratorSize(S), A) +_typed_stack(dims::Integer, ::Type{T}, ::Type{S}, ::HasLength, A) where {T,S} = + _typed_stack(dims, T, S, HasShape{1}(), A) +function _typed_stack(dims::Integer, ::Type{T}, ::Type{S}, ::HasShape{N}, A) where {T,S,N} + if dims == N+1 + _typed_stack(:, T, S, A, (_vec_axis(A),)) + else + _dim_stack(dims, T, S, A) + end +end +_typed_stack(dims::Integer, ::Type{T}, ::Type{S}, ::IteratorSize, A) where {T,S} = + _dim_stack(dims, T, S, A) + +_vec_axis(A, ax=_iterator_axes(A)) = length(ax) == 1 ? only(ax) : OneTo(prod(length, ax; init=1)) + +@constprop :aggressive function _dim_stack(dims::Integer, ::Type{T}, ::Type{S}, A) where {T,S} + xit = Iterators.peel(A) + nothing === xit && return _empty_stack(dims, T, S, A) + x1, xrest = xit + ax1 = _iterator_axes(x1) + N1 = length(ax1)+1 + dims in 1:N1 || throw(ArgumentError(LazyString("cannot stack slices ndims(x) = ", N1-1, " along dims = ", dims))) + + newaxis = _vec_axis(A) + outax = ntuple(d -> d==dims ? newaxis : ax1[d - (d>dims)], N1) + B = similar(_ensure_array(x1), T, outax...) + + if dims == 1 + _dim_stack!(Val(1), B, x1, xrest) + elseif dims == 2 + _dim_stack!(Val(2), B, x1, xrest) + else + _dim_stack!(Val(dims), B, x1, xrest) + end + B +end + +function _dim_stack!(::Val{dims}, B::AbstractArray, x1, xrest) where {dims} + before = ntuple(d -> Colon(), dims - 1) + after = ntuple(d -> Colon(), ndims(B) - dims) + + i = firstindex(B, dims) + copyto!(view(B, before..., i, after...), x1) + + for x in xrest + _stack_size_check(x, _iterator_axes(x1)) + i += 1 + @inbounds copyto!(view(B, before..., i, after...), x) + end +end + +@inline function _stack_size_check(x, ax1::Tuple) + if _iterator_axes(x) != ax1 + uax1 = map(UnitRange, ax1) + uaxN = map(UnitRange, axes(x)) + throw(DimensionMismatch( + LazyString("stack expects uniform slices, got axes(x) == ", uaxN, " while first had ", uax1))) + end +end + +_ensure_array(x::AbstractArray) = x +_ensure_array(x) = 1:0 # passed to similar, makes stack's output an Array + +_empty_stack(_...) = throw(ArgumentError("`stack` on an empty collection is not allowed")) + + ## Reductions and accumulates ## function isequal(A::AbstractArray, B::AbstractArray) @@ -2600,7 +2912,7 @@ end """ isless(A::AbstractVector, B::AbstractVector) -Returns true when `A` is less than `B` in lexicographic order. +Return `true` when `A` is less than `B` in lexicographic order. """ isless(A::AbstractVector, B::AbstractVector) = cmp(A, B) < 0 @@ -2775,134 +3087,163 @@ foreach(f, itrs...) = (for z in zip(itrs...); f(z...); end; nothing) """ mapslices(f, A; dims) -Transform the given dimensions of array `A` using function `f`. `f` is called on each slice -of `A` of the form `A[...,:,...,:,...]`. `dims` is an integer vector specifying where the -colons go in this expression. The results are concatenated along the remaining dimensions. -For example, if `dims` is `[1,2]` and `A` is 4-dimensional, `f` is called on `A[:,:,i,j]` -for all `i` and `j`. +Transform the given dimensions of array `A` by applying a function `f` on each slice +of the form `A[..., :, ..., :, ...]`, with a colon at each `d` in `dims`. The results are +concatenated along the remaining dimensions. + +For example, if `dims = [1,2]` and `A` is 4-dimensional, then `f` is called on `x = A[:,:,i,j]` +for all `i` and `j`, and `f(x)` becomes `R[:,:,i,j]` in the result `R`. -See also [`eachcol`](@ref), [`eachslice`](@ref). +See also [`eachcol`](@ref) or [`eachslice`](@ref), used with [`map`](@ref) or [`stack`](@ref). # Examples ```jldoctest -julia> a = reshape(Vector(1:16),(2,2,2,2)) -2×2×2×2 Array{Int64, 4}: -[:, :, 1, 1] = - 1 3 - 2 4 +julia> A = reshape(1:30,(2,5,3)) +2×5×3 reshape(::UnitRange{$Int}, 2, 5, 3) with eltype $Int: +[:, :, 1] = + 1 3 5 7 9 + 2 4 6 8 10 -[:, :, 2, 1] = - 5 7 - 6 8 +[:, :, 2] = + 11 13 15 17 19 + 12 14 16 18 20 -[:, :, 1, 2] = - 9 11 - 10 12 +[:, :, 3] = + 21 23 25 27 29 + 22 24 26 28 30 -[:, :, 2, 2] = - 13 15 - 14 16 +julia> f(x::Matrix) = fill(x[1,1], 1,4); # returns a 1×4 matrix -julia> mapslices(sum, a, dims = [1,2]) -1×1×2×2 Array{Int64, 4}: -[:, :, 1, 1] = - 10 +julia> B = mapslices(f, A, dims=(1,2)) +1×4×3 Array{$Int, 3}: +[:, :, 1] = + 1 1 1 1 -[:, :, 2, 1] = - 26 +[:, :, 2] = + 11 11 11 11 + +[:, :, 3] = + 21 21 21 21 -[:, :, 1, 2] = - 42 +julia> f2(x::AbstractMatrix) = fill(x[1,1], 1,4); -[:, :, 2, 2] = - 58 +julia> B == stack(f2, eachslice(A, dims=3)) +true + +julia> g(x) = x[begin] // x[end-1]; # returns a number + +julia> mapslices(g, A, dims=[1,3]) +1×5×1 Array{Rational{$Int}, 3}: +[:, :, 1] = + 1//21 3//23 1//5 7//27 9//29 + +julia> map(g, eachslice(A, dims=2)) +5-element Vector{Rational{$Int}}: + 1//21 + 3//23 + 1//5 + 7//27 + 9//29 + +julia> mapslices(sum, A; dims=(1,3)) == sum(A; dims=(1,3)) +true ``` + +Notice that in `eachslice(A; dims=2)`, the specified dimension is the +one *without* a colon in the slice. This is `view(A,:,i,:)`, whereas +`mapslices(f, A; dims=(1,3))` uses `A[:,i,:]`. The function `f` may mutate +values in the slice without affecting `A`. """ function mapslices(f, A::AbstractArray; dims) - if isempty(dims) - return map(f,A) - end - if !isa(dims, AbstractVector) - dims = [dims...] - end + isempty(dims) && return map(f, A) - dimsA = [axes(A)...] - ndimsA = ndims(A) - alldims = [1:ndimsA;] - - otherdims = setdiff(alldims, dims) - - idx = Any[first(ind) for ind in axes(A)] - itershape = tuple(dimsA[otherdims]...) for d in dims - idx[d] = Slice(axes(A, d)) + d isa Integer || throw(ArgumentError("mapslices: dimension must be an integer, got $d")) + d >= 1 || throw(ArgumentError("mapslices: dimension must be ≥ 1, got $d")) + # Indexing a matrix M[:,1,:] produces a 1-column matrix, but dims=(1,3) here + # would otherwise ignore 3, and slice M[:,i]. Previously this gave error: + # BoundsError: attempt to access 2-element Vector{Any} at index [3] + d > ndims(A) && throw(ArgumentError("mapslices does not accept dimensions > ndims(A) = $(ndims(A)), got $d")) end + dim_mask = ntuple(d -> d in dims, ndims(A)) # Apply the function to the first slice in order to determine the next steps - Aslice = A[idx...] + idx1 = ntuple(d -> d in dims ? (:) : firstindex(A,d), ndims(A)) + Aslice = A[idx1...] r1 = f(Aslice) - # In some cases, we can re-use the first slice for a dramatic performance - # increase. The slice itself must be mutable and the result cannot contain - # any mutable containers. The following errs on the side of being overly - # strict (#18570 & #21123). - safe_for_reuse = isa(Aslice, StridedArray) && - (isa(r1, Number) || (isa(r1, AbstractArray) && eltype(r1) <: Number)) - # determine result size and allocate - Rsize = copy(dimsA) - # TODO: maybe support removing dimensions - if !isa(r1, AbstractArray) || ndims(r1) == 0 + res1 = if r1 isa AbstractArray && ndims(r1) > 0 + n = sum(dim_mask) + if ndims(r1) > n && any(ntuple(d -> size(r1,d+n)>1, ndims(r1)-n)) + s = size(r1)[1:n] + throw(DimensionMismatch("mapslices cannot assign slice f(x) of size $(size(r1)) into output of size $s")) + end + r1 + else # If the result of f on a single slice is a scalar then we add singleton # dimensions. When adding the dimensions, we have to respect the # index type of the input array (e.g. in the case of OffsetArrays) - tmp = similar(Aslice, typeof(r1), reduced_indices(Aslice, 1:ndims(Aslice))) - tmp[firstindex(tmp)] = r1 - r1 = tmp + _res1 = similar(Aslice, typeof(r1), reduced_indices(Aslice, 1:ndims(Aslice))) + _res1[begin] = r1 + _res1 end - nextra = max(0, length(dims)-ndims(r1)) - if eltype(Rsize) == Int - Rsize[dims] = [size(r1)..., ntuple(Returns(1), nextra)...] - else - Rsize[dims] = [axes(r1)..., ntuple(Returns(OneTo(1)), nextra)...] - end - R = similar(r1, tuple(Rsize...,)) - ridx = Any[map(first, axes(R))...] - for d in dims - ridx[d] = axes(R,d) + # Determine result size and allocate. We always pad ndims(res1) out to length(dims): + din = Ref(0) + Rsize = ntuple(ndims(A)) do d + if d in dims + axes(res1, din[] += 1) + else + axes(A,d) + end end + R = similar(res1, Rsize) + + # Determine iteration space. It will be convenient in the loop to mask N-dimensional + # CartesianIndices, with some trivial dimensions: + itershape = ntuple(d -> d in dims ? Base.OneTo(1) : axes(A,d), ndims(A)) + indices = Iterators.drop(CartesianIndices(itershape), 1) - concatenate_setindex!(R, r1, ridx...) + # That skips the first element, which we already have: + ridx = ntuple(d -> d in dims ? Slice(axes(R,d)) : firstindex(A,d), ndims(A)) + concatenate_setindex!(R, res1, ridx...) - nidx = length(otherdims) - indices = Iterators.drop(CartesianIndices(itershape), 1) # skip the first element, we already handled it - inner_mapslices!(safe_for_reuse, indices, nidx, idx, otherdims, ridx, Aslice, A, f, R) + # In some cases, we can re-use the first slice for a dramatic performance + # increase. The slice itself must be mutable and the result cannot contain + # any mutable containers. The following errs on the side of being overly + # strict (#18570 & #21123). + safe_for_reuse = isa(Aslice, StridedArray) && + (isa(r1, Number) || (isa(r1, AbstractArray) && eltype(r1) <: Number)) + + _inner_mapslices!(R, indices, f, A, dim_mask, Aslice, safe_for_reuse) + return R end -@noinline function inner_mapslices!(safe_for_reuse, indices, nidx, idx, otherdims, ridx, Aslice, A, f, R) +@noinline function _inner_mapslices!(R, indices, f, A, dim_mask, Aslice, safe_for_reuse) + must_extend = any(dim_mask .& size(R) .> 1) if safe_for_reuse # when f returns an array, R[ridx...] = f(Aslice) line copies elements, # so we can reuse Aslice for I in indices - replace_tuples!(nidx, idx, ridx, otherdims, I) + idx = ifelse.(dim_mask, Slice.(axes(A)), Tuple(I)) _unsafe_getindex!(Aslice, A, idx...) - concatenate_setindex!(R, f(Aslice), ridx...) + r = f(Aslice) + if r isa AbstractArray || must_extend + ridx = ifelse.(dim_mask, Slice.(axes(R)), Tuple(I)) + R[ridx...] = r + else + ridx = ifelse.(dim_mask, first.(axes(R)), Tuple(I)) + R[ridx...] = r + end end else # we can't guarantee safety (#18524), so allocate new storage for each slice for I in indices - replace_tuples!(nidx, idx, ridx, otherdims, I) + idx = ifelse.(dim_mask, Slice.(axes(A)), Tuple(I)) + ridx = ifelse.(dim_mask, Slice.(axes(R)), Tuple(I)) concatenate_setindex!(R, f(A[idx...]), ridx...) end end - - return R -end - -function replace_tuples!(nidx, idx, ridx, otherdims, I) - for i in 1:nidx - idx[otherdims[i]] = ridx[otherdims[i]] = I.I[i] - end end concatenate_setindex!(R, v, I...) = (R[I...] .= (v,); R) @@ -3180,8 +3521,9 @@ function circshift!(a::AbstractVector, shift::Integer) n == 0 && return shift = mod(shift, n) shift == 0 && return - reverse!(a, 1, shift) - reverse!(a, shift+1, length(a)) + l = lastindex(a) + reverse!(a, firstindex(a), l-shift) + reverse!(a, l-shift+1, lastindex(a)) reverse!(a) return a end diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 9690fc0f2e4c4..70c304d9060c1 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -516,113 +516,3 @@ function repeat_inner(arr, inner) end end#module - -""" - eachrow(A::AbstractVecOrMat) - -Create a generator that iterates over the first dimension of vector or matrix `A`, -returning the rows as `AbstractVector` views. - -See also [`eachcol`](@ref), [`eachslice`](@ref), [`mapslices`](@ref). - -!!! compat "Julia 1.1" - This function requires at least Julia 1.1. - -# Example - -```jldoctest -julia> a = [1 2; 3 4] -2×2 Matrix{Int64}: - 1 2 - 3 4 - -julia> first(eachrow(a)) -2-element view(::Matrix{Int64}, 1, :) with eltype Int64: - 1 - 2 - -julia> collect(eachrow(a)) -2-element Vector{SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: - [1, 2] - [3, 4] -``` -""" -eachrow(A::AbstractVecOrMat) = (view(A, i, :) for i in axes(A, 1)) - - -""" - eachcol(A::AbstractVecOrMat) - -Create a generator that iterates over the second dimension of matrix `A`, returning the -columns as `AbstractVector` views. - -See also [`eachrow`](@ref) and [`eachslice`](@ref). - -!!! compat "Julia 1.1" - This function requires at least Julia 1.1. - -# Example - -```jldoctest -julia> a = [1 2; 3 4] -2×2 Matrix{Int64}: - 1 2 - 3 4 - -julia> first(eachcol(a)) -2-element view(::Matrix{Int64}, :, 1) with eltype Int64: - 1 - 3 - -julia> collect(eachcol(a)) -2-element Vector{SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: - [1, 3] - [2, 4] -``` -""" -eachcol(A::AbstractVecOrMat) = (view(A, :, i) for i in axes(A, 2)) - -""" - eachslice(A::AbstractArray; dims) - -Create a generator that iterates over dimensions `dims` of `A`, returning views that select all -the data from the other dimensions in `A`. - -Only a single dimension in `dims` is currently supported. Equivalent to `(view(A,:,:,...,i,:,: -...)) for i in axes(A, dims))`, where `i` is in position `dims`. - -See also [`eachrow`](@ref), [`eachcol`](@ref), [`mapslices`](@ref), and [`selectdim`](@ref). - -!!! compat "Julia 1.1" - This function requires at least Julia 1.1. - -# Example - -```jldoctest -julia> M = [1 2 3; 4 5 6; 7 8 9] -3×3 Matrix{Int64}: - 1 2 3 - 4 5 6 - 7 8 9 - -julia> first(eachslice(M, dims=1)) -3-element view(::Matrix{Int64}, 1, :) with eltype Int64: - 1 - 2 - 3 - -julia> collect(eachslice(M, dims=2)) -3-element Vector{SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: - [1, 4, 7] - [2, 5, 8] - [3, 6, 9] -``` -""" -@inline function eachslice(A::AbstractArray; dims) - length(dims) == 1 || throw(ArgumentError("only single dimensions are supported")) - dim = first(dims) - dim <= ndims(A) || throw(DimensionMismatch("A doesn't have $dim dimensions")) - inds_before = ntuple(Returns(:), dim-1) - inds_after = ntuple(Returns(:), ndims(A)-dim) - return (view(A, inds_before..., i, inds_after...) for i in axes(A, dim)) -end diff --git a/base/abstractdict.jl b/base/abstractdict.jl index a9c04dac22b7b..9dba5369a2a66 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -189,7 +189,10 @@ empty(a::AbstractDict) = empty(a, keytype(a), valtype(a)) empty(a::AbstractDict, ::Type{V}) where {V} = empty(a, keytype(a), V) # Note: this is the form which makes sense for `Vector`. copy(a::AbstractDict) = merge!(empty(a), a) -copy!(dst::AbstractDict, src::AbstractDict) = merge!(empty!(dst), src) +function copy!(dst::AbstractDict, src::AbstractDict) + dst === src && return dst + merge!(empty!(dst), src) +end """ merge!(d::AbstractDict, others::AbstractDict...) @@ -214,6 +217,9 @@ Dict{Int64, Int64} with 3 entries: """ function merge!(d::AbstractDict, others::AbstractDict...) for other in others + if haslength(d) && haslength(other) + sizehint!(d, length(d) + length(other)) + end for (k,v) in other d[k] = v end @@ -518,6 +524,9 @@ function ==(l::AbstractDict, r::AbstractDict) return anymissing ? missing : true end +# Fallback implementation +sizehint!(d::AbstractDict, n) = d + const hasha_seed = UInt === UInt64 ? 0x6d35bb51952d5539 : 0x952d5539 function hash(a::AbstractDict, h::UInt) hv = hasha_seed @@ -527,12 +536,12 @@ function hash(a::AbstractDict, h::UInt) hash(hv, h) end -function getindex(t::AbstractDict, key) +function getindex(t::AbstractDict{<:Any,V}, key) where V v = get(t, key, secret_table_token) if v === secret_table_token throw(KeyError(key)) end - return v + return v::V end # t[k1,k2,ks...] is syntactic sugar for t[(k1,k2,ks...)]. (Note @@ -542,21 +551,21 @@ setindex!(t::AbstractDict, v, k1, k2, ks...) = setindex!(t, v, tuple(k1,k2,ks... get!(t::AbstractDict, key, default) = get!(() -> default, t, key) function get!(default::Callable, t::AbstractDict{K,V}, key) where K where V - haskey(t, key) && return t[key] - val = default() - t[key] = val - return val + key = convert(K, key) + if haskey(t, key) + return t[key] + else + return t[key] = convert(V, default()) + end end push!(t::AbstractDict, p::Pair) = setindex!(t, p.second, p.first) -push!(t::AbstractDict, p::Pair, q::Pair) = push!(push!(t, p), q) -push!(t::AbstractDict, p::Pair, q::Pair, r::Pair...) = push!(push!(push!(t, p), q), r...) # AbstractDicts are convertible convert(::Type{T}, x::T) where {T<:AbstractDict} = x function convert(::Type{T}, x::AbstractDict) where T<:AbstractDict - h = T(x) + h = T(x)::T if length(h) != length(x) error("key collision during dictionary conversion") end @@ -564,7 +573,7 @@ function convert(::Type{T}, x::AbstractDict) where T<:AbstractDict end # hashing objects by identity -_tablesz(x::Integer) = x < 16 ? 16 : one(x)<<((sizeof(x)<<3)-leading_zeros(x-1)) +_tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<(top_set_bit(x-one(T))) TP{K,V} = Union{Type{Tuple{K,V}},Type{Pair{K,V}}} diff --git a/base/abstractset.jl b/base/abstractset.jl index bec4a84b19d15..5d0d65dad2de6 100644 --- a/base/abstractset.jl +++ b/base/abstractset.jl @@ -1,9 +1,12 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license eltype(::Type{<:AbstractSet{T}}) where {T} = @isdefined(T) ? T : Any -sizehint!(s::AbstractSet, n) = nothing +sizehint!(s::AbstractSet, n) = s -copy!(dst::AbstractSet, src::AbstractSet) = union!(empty!(dst), src) +function copy!(dst::AbstractSet, src::AbstractSet) + dst === src && return dst + union!(empty!(dst), src) +end ## set operations (union, intersection, symmetric difference) @@ -245,7 +248,6 @@ end Construct the symmetric difference of elements in the passed in sets. When `s` is not an `AbstractSet`, the order is maintained. -Note that in this case the multiplicity of elements matters. See also [`symdiff!`](@ref), [`setdiff`](@ref), [`union`](@ref) and [`intersect`](@ref). @@ -258,11 +260,6 @@ julia> symdiff([1,2,3], [3,4,5], [4,5,6]) 6 julia> symdiff([1,2,1], [2, 1, 2]) -2-element Vector{Int64}: - 1 - 2 - -julia> symdiff(unique([1,2,1]), unique([2, 1, 2])) Int64[] ``` """ @@ -283,7 +280,9 @@ function symdiff!(s::AbstractSet, itrs...) return s end -function symdiff!(s::AbstractSet, itr) +symdiff!(s::AbstractSet, itr) = symdiff!(s::AbstractSet, Set(itr)) + +function symdiff!(s::AbstractSet, itr::AbstractSet) for x in itr x in s ? delete!(s, x) : push!(s, x) end @@ -431,7 +430,7 @@ issetequal(a::AbstractSet, b) = issetequal(a, Set(b)) function issetequal(a, b::AbstractSet) if haslength(a) # check b for too many unique elements - length(a) < length(b) && return false + length(a) < length(b) && return false end return issetequal(Set(a), b) end @@ -477,6 +476,27 @@ function isdisjoint(a, b) _isdisjoint(a, b) end +function isdisjoint(a::AbstractRange{T}, b::AbstractRange{T}) where T + (isempty(a) || isempty(b)) && return true + fa, la = extrema(a) + fb, lb = extrema(b) + if (la < fb) | (lb < fa) + return true + else + return _overlapping_range_isdisjoint(a, b) + end +end + +_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 + if abs(step(a)) == abs(step(b)) + return mod(minimum(a), step(a)) != mod(minimum(b), step(a)) + else + return invoke(isdisjoint, Tuple{Any,Any}, a, b) + end +end + ## partial ordering of sets by containment ==(a::AbstractSet, b::AbstractSet) = length(a) == length(b) && a ⊆ b diff --git a/base/accumulate.jl b/base/accumulate.jl index 663bd850695a8..eeb9759e125c7 100644 --- a/base/accumulate.jl +++ b/base/accumulate.jl @@ -280,7 +280,7 @@ function accumulate(op, A; dims::Union{Nothing,Integer}=nothing, kw...) elseif keys(nt) === (:init,) out = similar(A, promote_op(op, typeof(nt.init), eltype(A))) else - throw(ArgumentError("acccumulate does not support the keyword arguments $(setdiff(keys(nt), (:init,)))")) + throw(ArgumentError("accumulate does not support the keyword arguments $(setdiff(keys(nt), (:init,)))")) end accumulate!(op, out, A; dims=dims, kw...) end @@ -341,7 +341,7 @@ function accumulate!(op, B, A; dims::Union{Integer, Nothing} = nothing, kw...) elseif keys(kw) === (:init,) _accumulate!(op, B, A, dims, Some(nt.init)) else - throw(ArgumentError("acccumulate! does not support the keyword arguments $(setdiff(keys(nt), (:init,)))")) + throw(ArgumentError("accumulate! does not support the keyword arguments $(setdiff(keys(nt), (:init,)))")) end end diff --git a/base/array.jl b/base/array.jl index cf5bbc05e412a..68e3e38992731 100644 --- a/base/array.jl +++ b/base/array.jl @@ -36,7 +36,7 @@ const AbstractMatrix{T} = AbstractArray{T,2} Union type of [`AbstractVector{T}`](@ref) and [`AbstractMatrix{T}`](@ref). """ const AbstractVecOrMat{T} = Union{AbstractVector{T}, AbstractMatrix{T}} -const RangeIndex = Union{Int, AbstractRange{Int}, AbstractUnitRange{Int}} +const RangeIndex = Union{<:BitInteger, AbstractRange{<:BitInteger}} const DimOrInd = Union{Integer, AbstractUnitRange} const IntOrInd = Union{Int, AbstractUnitRange} const DimsOrInds{N} = NTuple{N,DimOrInd} @@ -120,10 +120,52 @@ const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}} ## Basic functions ## -import Core: arraysize, arrayset, arrayref, const_arrayref +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...)` +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)) +end +function _safeindex(__module__, ex) + isa(ex, Expr) || return ex + if ex.head === :(=) + lhs = arrayref(true, 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) + 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) + end + return Expr(:call, GlobalRef(__module__, :__inbounds_setindex!), xs, _safeindex(__module__, rhs), args...) + end + elseif ex.head === :ref # xs[i] + return Expr(:call, GlobalRef(__module__, :__inbounds_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) + end + return Expr(ex.head, args...) +end vect() = Vector{Any}() -vect(X::T...) where {T} = T[ X[i] for i = 1:length(X) ] +function vect(X::T...) where T + @_terminates_locally_meta + vec = Vector{T}(undef, length(X)) + @_safeindex for i = 1:length(X) + vec[i] = X[i] + end + return vec +end """ vect(X...) @@ -142,19 +184,17 @@ julia> a = Base.vect(UInt8(1), 2.5, 1//2) """ function vect(X...) T = promote_typeof(X...) - #T[ X[i] for i=1:length(X) ] - # TODO: this is currently much faster. should figure out why. not clear. - return copyto!(Vector{T}(undef, length(X)), X) + return T[X...] end -size(a::Array, d::Integer) = arraysize(a, convert(Int, d)) +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) asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...) -allocatedinline(T::Type) = (@_pure_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0)) +allocatedinline(@nospecialize T::Type) = (@_total_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0)) """ Base.isbitsunion(::Type{T}) @@ -179,11 +219,11 @@ function _unsetindex!(A::Array{T}, i::Int) where {T} t = @_gc_preserve_begin A p = Ptr{Ptr{Cvoid}}(pointer(A, i)) if !allocatedinline(T) - unsafe_store!(p, C_NULL) + Intrinsics.atomic_pointerset(p, C_NULL, :monotonic) elseif T isa DataType if !datatype_pointerfree(T) - for j = 1:(Core.sizeof(T) ÷ Core.sizeof(Ptr{Cvoid})) - unsafe_store!(p, C_NULL, j) + for j = 1:Core.sizeof(Ptr{Cvoid}):Core.sizeof(T) + Intrinsics.atomic_pointerset(p + j - 1, C_NULL, :monotonic) end end end @@ -212,14 +252,16 @@ function bitsunionsize(u::Union) return sz end -length(a::Array) = arraylen(a) +# Deprecate this, as it seems to have no documented meaning and is unused here, +# but is frequently accessed in packages elsize(@nospecialize _::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T) +elsize(::Type{Union{}}, slurp...) = 0 sizeof(a::Array) = Core.sizeof(a) function isassigned(a::Array, i::Int...) @inline + @boundscheck checkbounds(Bool, a, i...) || return false ii = (_sub2ind(size(a), i...) % UInt) - 1 - @boundscheck ii < length(a) % UInt || return false ccall(:jl_array_isassigned, Cint, (Any, UInt), a, ii) == 1 end @@ -270,7 +312,7 @@ end """ unsafe_copyto!(dest::Array, do, src::Array, so, N) -Copy `N` elements from a source array to a destination, starting at offset `so` in the +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). The `unsafe` prefix on this function indicates that no validation is performed to ensure @@ -310,8 +352,8 @@ unsafe_copyto!(dest::Array, doffs, src::Array, soffs, n) = """ copyto!(dest, do, src, so, N) -Copy `N` elements from collection `src` starting at offset `so`, to array `dest` starting at -offset `do`. Return `dest`. +Copy `N` elements from collection `src` starting at the linear index `so`, to array `dest` starting at +the index `do`. Return `dest`. """ function copyto!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer) return _copyto_impl!(dest, doffs, src, soffs, n) @@ -324,10 +366,9 @@ end function _copyto_impl!(dest::Array, doffs::Integer, src::Array, soffs::Integer, n::Integer) n == 0 && return dest - n > 0 || _throw_argerror() - if soffs < 1 || doffs < 1 || soffs+n-1 > length(src) || doffs+n-1 > length(dest) - throw(BoundsError()) - end + n > 0 || _throw_argerror("Number of elements to copy must be nonnegative.") + @boundscheck checkbounds(dest, doffs:doffs+n-1) + @boundscheck checkbounds(src, soffs:soffs+n-1) unsafe_copyto!(dest, doffs, src, soffs, n) return dest end @@ -335,10 +376,7 @@ end # Outlining this because otherwise a catastrophic inference slowdown # occurs, see discussion in #27874. # It is also mitigated by using a constant string. -function _throw_argerror() - @noinline - throw(ArgumentError("Number of elements to copy must be nonnegative.")) -end +_throw_argerror(s) = (@noinline; throw(ArgumentError(s))) copyto!(dest::Array, src::Array) = copyto!(dest, 1, src, 1, length(src)) @@ -348,7 +386,7 @@ copyto!(dest::Array{T}, src::Array{T}) where {T} = copyto!(dest, 1, src, 1, leng # N.B: The generic definition in multidimensional.jl covers, this, this is just here # for bootstrapping purposes. function fill!(dest::Array{T}, x) where T - xT = convert(T, x) + xT = x isa T ? x : convert(T, x)::T for i in eachindex(dest) @inbounds dest[i] = xT end @@ -362,7 +400,7 @@ Create a shallow copy of `x`: the outer structure is copied, but not all interna For example, copying an array produces a new array with identically-same elements as the original. -See also [`copy!`](@ref Base.copy!), [`copyto!`](@ref). +See also [`copy!`](@ref Base.copy!), [`copyto!`](@ref), [`deepcopy`](@ref). """ copy @@ -401,21 +439,27 @@ julia> getindex(Int8, 1, 2, 3) ``` """ function getindex(::Type{T}, vals...) where T + @inline + @_effect_free_terminates_locally_meta a = Vector{T}(undef, length(vals)) - @inbounds for i = 1:length(vals) - a[i] = vals[i] + if vals isa NTuple + @_safeindex for i in 1:length(vals) + a[i] = vals[i] + end + else + # use afoldl to avoid type instability inside loop + afoldl(1, vals...) do i, v + @inbounds a[i] = v + return i + 1 + end end return a end -getindex(::Type{T}) where {T} = (@inline; Vector{T}()) -getindex(::Type{T}, x) where {T} = (@inline; a = Vector{T}(undef, 1); @inbounds a[1] = x; a) -getindex(::Type{T}, x, y) where {T} = (@inline; a = Vector{T}(undef, 2); @inbounds (a[1] = x; a[2] = y); a) -getindex(::Type{T}, x, y, z) where {T} = (@inline; a = Vector{T}(undef, 3); @inbounds (a[1] = x; a[2] = y; a[3] = z); a) - function getindex(::Type{Any}, @nospecialize vals...) + @_effect_free_terminates_locally_meta a = Vector{Any}(undef, length(vals)) - @inbounds for i = 1:length(vals) + @_safeindex for i = 1:length(vals) a[i] = vals[i] end return a @@ -423,7 +467,7 @@ end getindex(::Type{Any}) = Vector{Any}() function fill!(a::Union{Array{UInt8}, Array{Int8}}, x::Integer) - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), a, convert(eltype(a), x), length(a)) + ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), a, x isa eltype(a) ? x : convert(eltype(a), x), length(a)) return a end @@ -449,7 +493,7 @@ the `value` that was passed; this means that if the `value` is itself modified, all elements of the `fill`ed array will reflect that modification because they're _still_ that very `value`. This is of no concern with `fill(1.0, (5,5))` as the `value` `1.0` is immutable and cannot itself be modified, but can be unexpected -with mutable values like — most commonly — arrays. For example, `fill([], 3)` +with mutable values like — most commonly — arrays. For example, `fill([], 3)` places _the very same_ empty array in all three locations of the returned vector: ```jldoctest @@ -611,8 +655,7 @@ 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) -convert(::Type{Union{}}, a::AbstractArray) = throw(MethodError(convert, (Union{}, a))) +convert(::Type{T}, a::AbstractArray) where {T<:Array} = a isa T ? a : T(a)::T promote_rule(a::Type{Array{T,n}}, b::Type{Array{S,n}}) where {T,n,S} = el_same(promote_type(T,S), a, b) @@ -842,8 +885,8 @@ function collect_to!(dest::AbstractArray{T}, itr, offs, st) where T y = iterate(itr, st) y === nothing && break el, st = y - if el isa T || typeof(el) === T - @inbounds dest[i] = el::T + if el isa T + @inbounds dest[i] = el i += 1 else new = setindex_widen_up_to(dest, el, i) @@ -879,8 +922,8 @@ function grow_to!(dest, itr, st) y = iterate(itr, st) while y !== nothing el, st = y - if el isa T || typeof(el) === T - push!(dest, el::T) + if el isa T + push!(dest, el) else new = push_widen(dest, el) return grow_to!(new, itr, st) @@ -917,10 +960,6 @@ julia> getindex(A, "a") """ function getindex end -# 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...)) - # Faster contiguous indexing using copyto! for AbstractUnitRange and Colon function getindex(A::Array, I::AbstractUnitRange{<:Integer}) @inline @@ -957,12 +996,30 @@ end Store the given value at the given key or index within a collection. The syntax `a[i,j,...] = x` is converted by the compiler to `(setindex!(a, x, i, j, ...); x)`. + +# Examples +```jldoctest +julia> a = Dict("a"=>1) +Dict{String, Int64} with 1 entry: + "a" => 1 + +julia> setindex!(a, 2, "b") +Dict{String, Int64} with 2 entries: + "b" => 2 + "a" => 1 +``` """ function setindex! end -@eval setindex!(A::Array{T}, x, i1::Int) where {T} = arrayset($(Expr(:boundscheck)), A, convert(T,x)::T, i1) +@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, convert(T,x)::T, i1, i2, I...)) + (@inline; arrayset($(Expr(:boundscheck)), A, x isa T ? x : convert(T,x)::T, i1, i2, I...)) + +__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...)) # This is redundant with the abstract fallbacks but needed and helpful for bootstrap function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int}) @@ -1049,17 +1106,28 @@ See also [`pushfirst!`](@ref). """ function push! end -function push!(a::Array{T,1}, item) where T +function push!(a::Vector{T}, item) where T # convert first so we don't grow the array if the assignment won't work - itemT = convert(T, item) + itemT = item isa T ? item : convert(T, item)::T _growend!(a, 1) - @inbounds a[end] = itemT + @_safeindex a[length(a)] = itemT return a end -function push!(a::Array{Any,1}, @nospecialize item) +# specialize and optimize the single argument case +function push!(a::Vector{Any}, @nospecialize x) _growend!(a, 1) - arrayset(true, a, item, length(a)) + @_safeindex a[length(a)] = x + return a +end +function push!(a::Vector{Any}, @nospecialize x...) + @_terminates_locally_meta + na = length(a) + nx = length(x) + _growend!(a, nx) + @_safeindex for i = 1:nx + a[na+i] = x[i] + end return a end @@ -1099,6 +1167,8 @@ See [`sizehint!`](@ref) for notes about the performance model. See also [`vcat`](@ref) for vectors, [`union!`](@ref) for sets, 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) @@ -1112,17 +1182,21 @@ push!(a::AbstractVector, iter...) = append!(a, iter) append!(a::AbstractVector, iter...) = foldl(append!, iter, init=a) -function _append!(a, ::Union{HasLength,HasShape}, iter) +function _append!(a::AbstractVector, ::Union{HasLength,HasShape}, iter) + @_terminates_locally_meta n = length(a) i = lastindex(a) resize!(a, n+Int(length(iter))::Int) - @inbounds for (i, item) in zip(i+1:lastindex(a), iter) - a[i] = item + 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 end a end - -function _append!(a, ::IteratorSize, iter) +function _append!(a::AbstractVector, ::IteratorSize, iter) for item in iter push!(a, item) end @@ -1177,17 +1251,18 @@ pushfirst!(a::Vector, iter...) = prepend!(a, iter) prepend!(a::AbstractVector, iter...) = foldr((v, a) -> prepend!(a, v), iter, init=a) -function _prepend!(a, ::Union{HasLength,HasShape}, iter) +function _prepend!(a::Vector, ::Union{HasLength,HasShape}, iter) + @_terminates_locally_meta require_one_based_indexing(a) n = length(iter) _growbeg!(a, n) i = 0 for item in iter - @inbounds a[i += 1] = item + @_safeindex a[i += 1] = item end a end -function _prepend!(a, ::IteratorSize, iter) +function _prepend!(a::Vector, ::IteratorSize, iter) n = 0 for item in iter n += 1 @@ -1233,7 +1308,7 @@ function resize!(a::Vector, nl::Integer) _growend!(a, nl-l) elseif nl != l if nl < 0 - throw(ArgumentError("new length must be ≥ 0")) + _throw_argerror("new length must be ≥ 0") end _deleteend!(a, l-nl) end @@ -1241,9 +1316,14 @@ function resize!(a::Vector, nl::Integer) end """ - sizehint!(s, n) + sizehint!(s, n) -> 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. -Suggest that collection `s` reserve capacity for at least `n` elements. This can improve performance. +See also [`resize!`](@ref). # Notes on the performance model @@ -1308,7 +1388,7 @@ julia> pop!(Dict(1=>2)) """ function pop!(a::Vector) if isempty(a) - throw(ArgumentError("array must be non-empty")) + _throw_argerror("array must be non-empty") end item = a[end] _deleteend!(a, 1) @@ -1382,10 +1462,27 @@ julia> pushfirst!([1, 2, 3, 4], 5, 6) 4 ``` """ -function pushfirst!(a::Array{T,1}, item) where T - item = convert(T, item) +function pushfirst!(a::Vector{T}, item) where T + item = item isa T ? item : convert(T, item)::T _growbeg!(a, 1) - a[1] = item + @_safeindex a[1] = item + return a +end + +# specialize and optimize the single argument case +function pushfirst!(a::Vector{Any}, @nospecialize x) + _growbeg!(a, 1) + @_safeindex a[1] = x + return a +end +function pushfirst!(a::Vector{Any}, @nospecialize x...) + @_terminates_locally_meta + na = length(a) + nx = length(x) + _growbeg!(a, nx) + @_safeindex for i = 1:nx + a[i] = x[i] + end return a end @@ -1423,7 +1520,7 @@ julia> A """ function popfirst!(a::Vector) if isempty(a) - throw(ArgumentError("array must be non-empty")) + _throw_argerror("array must be non-empty") end item = a[1] _deletebeg!(a, 1) @@ -1453,7 +1550,7 @@ julia> insert!(Any[1:6;], 3, "here") """ function insert!(a::Array{T,1}, i::Integer, item) where T # Throw convert error before changing the shape of the array - _item = convert(T, item) + _item = item isa T ? item : convert(T, item)::T _growat!(a, i, 1) # _growat! already did bound check @inbounds a[i] = _item @@ -1466,7 +1563,7 @@ end Remove the item at the given `i` and return the modified `a`. Subsequent items are shifted to fill the resulting gap. -See also: [`delete!`](@ref), [`popat!`](@ref), [`splice!`](@ref). +See also: [`keepat!`](@ref), [`delete!`](@ref), [`popat!`](@ref), [`splice!`](@ref). # Examples ```jldoctest @@ -1563,7 +1660,7 @@ function _deleteat!(a::Vector, inds, dltd=Nowhere()) (i,s) = y if !(q <= i <= n) if i < q - throw(ArgumentError("indices must be unique and sorted")) + _throw_argerror("indices must be unique and sorted") else throw(BoundsError()) end @@ -1757,7 +1854,7 @@ function ==(a::Arr, b::Arr) where Arr <: BitIntegerArray{1} end """ - reverse(v [, start=1 [, stop=length(v) ]] ) + reverse(v [, start=firstindex(v) [, stop=lastindex(v) ]] ) Return a copy of `v` reversed from start to stop. See also [`Iterators.reverse`](@ref) for reverse-order iteration without making a copy, and in-place [`reverse!`](@ref). @@ -1819,7 +1916,7 @@ for (f,_f) in ((:reverse,:_reverse), (:reverse!,:_reverse!)) $_f(A::AbstractVector, ::Colon) = $f(A, firstindex(A), lastindex(A)) $_f(A::AbstractVector, dim::Tuple{Integer}) = $_f(A, first(dim)) function $_f(A::AbstractVector, dim::Integer) - dim == 1 || throw(ArgumentError("invalid dimension $dim ≠ 1")) + dim == 1 || _throw_argerror(LazyString("invalid dimension ", dim, " ≠ 1")) return $_f(A, :) end end @@ -1830,8 +1927,13 @@ function reverseind(a::AbstractVector, i::Integer) first(li) + last(li) - i end +# This implementation of `midpoint` is performance-optimized but safe +# only if `lo <= hi`. +midpoint(lo::T, hi::T) where T<:Integer = lo + ((hi - lo) >>> 0x01) +midpoint(lo::Integer, hi::Integer) = midpoint(promote(lo, hi)...) + """ - reverse!(v [, start=1 [, stop=length(v) ]]) -> v + reverse!(v [, start=firstindex(v) [, stop=lastindex(v) ]]) -> v In-place version of [`reverse`](@ref). @@ -1858,22 +1960,23 @@ julia> A """ function reverse!(v::AbstractVector, start::Integer, stop::Integer=lastindex(v)) s, n = Int(start), Int(stop) - liv = LinearIndices(v) - if n <= s # empty case; ok - elseif !(first(liv) ≤ s ≤ last(liv)) - throw(BoundsError(v, s)) - elseif !(first(liv) ≤ n ≤ last(liv)) - throw(BoundsError(v, n)) - end - r = n - @inbounds for i in s:div(s+n-1, 2) - v[i], v[r] = v[r], v[i] - r -= 1 + if n > s # non-empty and non-trivial + liv = LinearIndices(v) + if !(first(liv) ≤ s ≤ last(liv)) + throw(BoundsError(v, s)) + elseif !(first(liv) ≤ n ≤ last(liv)) + throw(BoundsError(v, n)) + end + r = n + @inbounds for i in s:midpoint(s, n-1) + v[i], v[r] = v[r], v[i] + r -= 1 + end end return v end -# concatenations of homogeneous combinations of vectors, horizontal and vertical +# concatenations of (in)homogeneous combinations of vectors, horizontal and vertical vcat() = Vector{Any}() hcat() = Vector{Any}() @@ -1887,6 +1990,7 @@ function hcat(V::Vector{T}...) where T end return [ V[j][i]::T for i=1:length(V[1]), j=1:length(V) ] end +hcat(A::Vector...) = cat(A...; dims=Val(2)) # more special than SparseArrays's hcat function vcat(arrays::Vector{T}...) where T n = 0 @@ -1903,6 +2007,19 @@ function vcat(arrays::Vector{T}...) where T end return arr end +vcat(A::Vector...) = cat(A...; dims=Val(1)) # more special than SparseArrays's vcat + +# disambiguation with LinAlg/special.jl +# Union{Number,Vector,Matrix} is for LinearAlgebra._DenseConcatGroup +# VecOrMat{T} is for LinearAlgebra._TypedDenseConcatGroup +hcat(A::Union{Number,Vector,Matrix}...) = cat(A...; dims=Val(2)) +hcat(A::VecOrMat{T}...) where {T} = typed_hcat(T, A...) +vcat(A::Union{Number,Vector,Matrix}...) = cat(A...; dims=Val(1)) +vcat(A::VecOrMat{T}...) where {T} = typed_vcat(T, A...) +hvcat(rows::Tuple{Vararg{Int}}, xs::Union{Number,Vector,Matrix}...) = + typed_hvcat(promote_eltypeof(xs...), rows, xs...) +hvcat(rows::Tuple{Vararg{Int}}, xs::VecOrMat{T}...) where {T} = + typed_hvcat(T, rows, xs...) _cat(n::Integer, x::Integer...) = reshape([x...], (ntuple(Returns(1), n-1)..., length(x))) @@ -2074,7 +2191,7 @@ findfirst(p::Union{Fix2{typeof(isequal),T},Fix2{typeof(==),T}}, r::AbstractUnitR function findfirst(p::Union{Fix2{typeof(isequal),T},Fix2{typeof(==),T}}, r::StepRange{T,S}) where {T,S} isempty(r) && return nothing minimum(r) <= p.x <= maximum(r) || return nothing - d = convert(S, p.x - first(r)) + d = convert(S, p.x - first(r))::S iszero(d % step(r)) || return nothing return d ÷ step(r) + 1 end @@ -2295,11 +2412,15 @@ julia> findall(x -> x >= 0, d) ``` """ -findall(testf::Function, A) = collect(first(p) for p in pairs(A) if testf(last(p))) +function findall(testf::Function, A) + T = eltype(keys(A)) + gen = (first(p) for p in pairs(A) if testf(last(p))) + isconcretetype(T) ? collect(T, gen) : collect(gen) +end # Broadcasting is much faster for small testf, and computing # integer indices from logical index using findall has a negligible cost -findall(testf::Function, A::AbstractArray) = findall(testf.(A)) +findall(testf::F, A::AbstractArray) where {F<:Function} = findall(testf.(A)) """ findall(A) @@ -2346,19 +2467,42 @@ function findall(A) end # Allocating result upfront is faster (possible only when collection can be iterated twice) -function findall(A::AbstractArray{Bool}) - n = count(A) +function _findall(f::Function, A::AbstractArray{Bool}) + n = count(f, A) I = Vector{eltype(keys(A))}(undef, n) + isempty(I) && return I + _findall(f, I, A) +end + +function _findall(f::Function, I::Vector, A::AbstractArray{Bool}) cnt = 1 - for (i,a) in pairs(A) - if a - I[cnt] = i - cnt += 1 - end + len = length(I) + for (k, v) in pairs(A) + @inbounds I[cnt] = k + cnt += f(v) + cnt > len && return I end - I + # In case of impure f, this line could potentially be hit. In that case, + # we can't assume I is the correct length. + resize!(I, cnt - 1) end +function _findall(f::Function, I::Vector, A::AbstractVector{Bool}) + i = firstindex(A) + cnt = 1 + len = length(I) + while cnt ≤ len + @inbounds I[cnt] = i + cnt += f(@inbounds A[i]) + i = nextind(A, i) + end + cnt - 1 == len ? I : resize!(I, cnt - 1) +end + +findall(f::Function, A::AbstractArray{Bool}) = _findall(f, A) +findall(f::Fix2{typeof(in)}, A::AbstractArray{Bool}) = _findall(f, A) +findall(A::AbstractArray{Bool}) = _findall(identity, A) + findall(x::Bool) = x ? [1] : Vector{Int}() findall(testf::Function, x::Number) = testf(x) ? [1] : Vector{Int}() findall(p::Fix2{typeof(in)}, x::Number) = x in p.x ? [1] : Vector{Int}() @@ -2527,7 +2671,7 @@ function filter(f, a::Array{T, N}) where {T, N} b = Vector{T}(undef, length(a)) for ai in a @inbounds b[j] = ai - j = ifelse(f(ai), j+1, j) + j = ifelse(f(ai)::Bool, j+1, j) end resize!(b, j-1) sizehint!(b, length(b)) @@ -2542,7 +2686,7 @@ function filter(f, a::AbstractArray) for idx in eachindex(a) @inbounds idxs[j] = idx ai = @inbounds a[idx] - j = ifelse(f(ai), j+1, j) + j = ifelse(f(ai)::Bool, j+1, j) end resize!(idxs, j-1) res = a[idxs] @@ -2572,7 +2716,7 @@ function filter!(f, a::AbstractVector) j = firstindex(a) for ai in a @inbounds a[j] = ai - j = ifelse(f(ai), nextind(a, j), j) + j = ifelse(f(ai)::Bool, nextind(a, j), j) end j > lastindex(a) && return a if a isa Vector @@ -2584,6 +2728,33 @@ function filter!(f, a::AbstractVector) return a end +""" + filter(f) + +Create a function that filters its arguments with function `f` using [`filter`](@ref), i.e. +a function equivalent to `x -> filter(f, x)`. + +The returned function is of type `Base.Fix1{typeof(filter)}`, which can be +used to implement specialized methods. + +# Examples +```jldoctest +julia> (1, 2, Inf, 4, NaN, 6) |> filter(isfinite) +(1, 2, 4, 6) + +julia> map(filter(iseven), [1:3, 2:4, 3:5]) +3-element Vector{Vector{Int64}}: + [2] + [2, 4] + [4] +``` +!!! compat "Julia 1.9" + This method requires at least Julia 1.9. +""" +function filter(f) + Fix1(filter, f) +end + """ keepat!(a::Vector, inds) keepat!(a::BitVector, inds) @@ -2637,7 +2808,8 @@ keepat!(a::Vector, m::AbstractVector{Bool}) = _keepat!(a, m) # set-like operators for vectors # These are moderately efficient, preserve order, and remove dupes. -_unique_filter!(pred, update!, state) = function (x) +_unique_filter!(pred::P, update!::U, state) where {P,U} = function (x) + # P, U force specialization if pred(x, state) update!(state, x) true @@ -2663,7 +2835,7 @@ union!(v::AbstractVector{T}, itrs...) where {T} = symdiff!(v::AbstractVector{T}, itrs...) where {T} = _grow!(_shrink_filter!(symdiff!(Set{T}(), v, itrs...)), v, itrs) -function _shrink!(shrinker!, v::AbstractVector, itrs) +function _shrink!(shrinker!::F, v::AbstractVector, itrs) where F seen = Set{eltype(v)}() filter!(_grow_filter!(seen), v) shrinker!(seen, itrs...) @@ -2675,7 +2847,7 @@ setdiff!( v::AbstractVector, itrs...) = _shrink!(setdiff!, v, itrs) vectorfilter(T::Type, f, v) = T[x for x in v if f(x)] -function _shrink(shrinker!, itr, itrs) +function _shrink(shrinker!::F, itr, itrs) where F T = promote_eltype(itr, itrs...) keep = shrinker!(Set{T}(itr), itrs...) vectorfilter(T, _shrink_filter!(keep), itr) diff --git a/base/arrayshow.jl b/base/arrayshow.jl index 0d480b64bb32d..a05a8d4dac51c 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -40,7 +40,7 @@ centered cdot, used in printing of structural zeros of structured matrices. Accept keyword args `c` for alternate single character marker. """ function replace_with_centered_mark(s::AbstractString;c::AbstractChar = '⋅') - N = length(s) + N = textwidth(ANSIIterator(s)) return join(setindex!([" " for i=1:N],string(c),ceil(Int,N/2))) end @@ -202,7 +202,7 @@ function _print_matrix(io, @nospecialize(X::AbstractVecOrMat), pre, sep, post, h if n > maxpossiblecols colsA = [colsA[(0:maxpossiblecols-1) .+ firstindex(colsA)]; colsA[(end-maxpossiblecols+1):end]] else - colsA = [colsA;] + colsA = [colsA;] end A = alignment(io, X, rowsA, colsA, screenwidth, screenwidth, sepsize, ncols) # Nine-slicing is accomplished using print_matrix_row repeatedly @@ -278,7 +278,7 @@ show_nd(io::IO, a::AbstractArray, print_matrix::Function, show_full::Bool) = _show_nd(io, inferencebarrier(a), print_matrix, show_full, map(unitrange, axes(a))) function _show_nd(io::IO, @nospecialize(a::AbstractArray), print_matrix::Function, show_full::Bool, axs::Tuple{Vararg{AbstractUnitRange}}) - limit::Bool = get(io, :limit, false) + limit = get(io, :limit, false)::Bool if isempty(a) return end @@ -361,7 +361,7 @@ print_array(io::IO, X::AbstractArray) = show_nd(io, X, print_matrix, true) # typeinfo aware # implements: show(io::IO, ::MIME"text/plain", X::AbstractArray) function show(io::IO, ::MIME"text/plain", X::AbstractArray) - if isempty(X) && (get(io, :compact, false) || X isa Vector) + if isempty(X) && (get(io, :compact, false)::Bool || X isa Vector) return show(io, X) end # 0) show summary before setting :compact @@ -374,12 +374,12 @@ function show(io::IO, ::MIME"text/plain", X::AbstractArray) if !haskey(io, :compact) && length(axes(X, 2)) > 1 io = IOContext(io, :compact => true) end - if get(io, :limit, false) && eltype(X) === Method + if get(io, :limit, false)::Bool && eltype(X) === Method # override usual show method for Vector{Method}: don't abbreviate long lists io = IOContext(io, :limit => false) end - if get(io, :limit, false) && displaysize(io)[1]-4 <= 0 + if get(io, :limit, false)::Bool && displaysize(io)[1]-4 <= 0 return print(io, " …") else println(io) @@ -462,8 +462,10 @@ function _show_nonempty(io::IO, @nospecialize(X::AbstractMatrix), prefix::String end -_show_nonempty(io::IO, X::AbstractArray, prefix::String) = +function _show_nonempty(io::IO, X::AbstractArray, prefix::String) + print(io, prefix) show_nd(io, X, (io, slice) -> _show_nonempty(io, inferencebarrier(slice), prefix, true, axes(slice)), false) +end # a specific call path is used to show vectors (show_vector) _show_nonempty(::IO, ::AbstractVector, ::String) = @@ -516,7 +518,7 @@ function show_vector(io::IO, v, opn='[', cls=']') if !implicit io = IOContext(io, :typeinfo => eltype(v)) end - limited = get(io, :limit, false) + limited = get(io, :limit, false)::Bool if limited && length(v) > 20 axs1 = axes1(v) @@ -538,10 +540,12 @@ end # returning Any, as this would cause incorrect printing in e.g. `Vector[Any[1]]`, # because eltype(Vector) == Any so `Any` wouldn't be printed in `Any[1]`) typeinfo_eltype(typeinfo) = nothing # element type not precisely known +typeinfo_eltype(typeinfo::Type{Union{}}, slurp...) = nothing typeinfo_eltype(typeinfo::Type{<:AbstractArray{T}}) where {T} = eltype(typeinfo) typeinfo_eltype(typeinfo::Type{<:AbstractDict{K,V}}) where {K,V} = eltype(typeinfo) typeinfo_eltype(typeinfo::Type{<:AbstractSet{T}}) where {T} = eltype(typeinfo) + # types that can be parsed back accurately from their un-decorated representations function typeinfo_implicit(@nospecialize(T)) if T === Float64 || T === Int || T === Char || T === String || T === Symbol || @@ -568,11 +572,11 @@ function typeinfo_prefix(io::IO, X) if X isa AbstractDict if eltype_X == eltype_ctx - sprint(show_type_name, typeof(X).name), false + sprint(show_type_name, typeof(X).name; context=io), false elseif !isempty(X) && typeinfo_implicit(keytype(X)) && typeinfo_implicit(valtype(X)) - sprint(show_type_name, typeof(X).name), true + sprint(show_type_name, typeof(X).name; context=io), true else - string(typeof(X)), false + sprint(print, typeof(X); context=io), false end else # Types hard-coded here are those which are created by default for a given syntax @@ -581,9 +585,9 @@ function typeinfo_prefix(io::IO, X) elseif !isempty(X) && typeinfo_implicit(eltype_X) "", true elseif print_without_params(eltype_X) - sprint(show_type_name, unwrap_unionall(eltype_X).name), false # Print "Array" rather than "Array{T,N}" + sprint(show_type_name, unwrap_unionall(eltype_X).name; context=io), false # Print "Array" rather than "Array{T,N}" else - string(eltype_X), false + sprint(print, eltype_X; context=io), false end end end diff --git a/base/asyncevent.jl b/base/asyncevent.jl index 0736bd463111f..a26945bbb1105 100644 --- a/base/asyncevent.jl +++ b/base/asyncevent.jl @@ -14,9 +14,9 @@ Use [`isopen`](@ref) to check whether it is still active. This provides an implicit acquire & release memory ordering between the sending and waiting threads. """ mutable struct AsyncCondition - handle::Ptr{Cvoid} + @atomic handle::Ptr{Cvoid} cond::ThreadSynchronizer - isopen::Bool + @atomic isopen::Bool @atomic set::Bool function AsyncCondition() @@ -45,13 +45,22 @@ the async condition object itself. """ function AsyncCondition(cb::Function) async = AsyncCondition() - t = @task while _trywait(async) - cb(async) - isopen(async) || return + t = @task begin + unpreserve_handle(async) + while _trywait(async) + cb(async) + isopen(async) || return + end + end + # here we are mimicking parts of _trywait, in coordination with task `t` + preserve_handle(async) + @lock async.cond begin + if async.set + schedule(t) + else + _wait2(async.cond, t) + end end - lock(async.cond) - _wait2(async.cond, t) - unlock(async.cond) return async end @@ -77,9 +86,9 @@ once. When the timer is closed (by [`close`](@ref)) waiting tasks are woken with """ mutable struct Timer - handle::Ptr{Cvoid} + @atomic handle::Ptr{Cvoid} cond::ThreadSynchronizer - isopen::Bool + @atomic isopen::Bool @atomic set::Bool function Timer(timeout::Real; interval::Real = 0.0) @@ -115,6 +124,7 @@ function _trywait(t::Union{Timer, AsyncCondition}) # 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 iolock_begin() set = t.set @@ -123,14 +133,12 @@ function _trywait(t::Union{Timer, AsyncCondition}) lock(t.cond) try set = t.set - if !set - if t.handle != C_NULL - iolock_end() - set = wait(t.cond) - unlock(t.cond) - iolock_begin() - lock(t.cond) - end + if !set && t.isopen && t.handle != C_NULL + iolock_end() + set = wait(t.cond) + unlock(t.cond) + iolock_begin() + lock(t.cond) end finally unlock(t.cond) @@ -149,12 +157,12 @@ function wait(t::Union{Timer, AsyncCondition}) end -isopen(t::Union{Timer, AsyncCondition}) = t.isopen +isopen(t::Union{Timer, AsyncCondition}) = t.isopen && t.handle != C_NULL function close(t::Union{Timer, AsyncCondition}) iolock_begin() - if t.handle != C_NULL && isopen(t) - t.isopen = false + if isopen(t) + @atomic :monotonic t.isopen = false ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) end iolock_end() @@ -166,12 +174,12 @@ function uvfinalize(t::Union{Timer, AsyncCondition}) lock(t.cond) try if t.handle != C_NULL - disassociate_julia_struct(t.handle) # not going to call the usual close hooks + disassociate_julia_struct(t.handle) # not going to call the usual close hooks anymore if t.isopen - t.isopen = false - ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t) + @atomic :monotonic t.isopen = false + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t.handle) end - t.handle = C_NULL + @atomic :monotonic t.handle = C_NULL notify(t.cond, false) end finally @@ -184,9 +192,9 @@ end function _uv_hook_close(t::Union{Timer, AsyncCondition}) lock(t.cond) try - t.isopen = false - t.handle = C_NULL - notify(t.cond, t.set) + @atomic :monotonic t.isopen = false + Libc.free(@atomicswap :monotonic t.handle = C_NULL) + notify(t.cond, false) finally unlock(t.cond) end @@ -242,8 +250,8 @@ Create a timer that runs the function `callback` at each timer expiration. Waiting tasks are woken and the function `callback` is called after an initial delay of `delay` seconds, and then repeating with the given `interval` in seconds. If `interval` is equal to `0`, the callback is only run once. The function `callback` is called with a single argument, the timer -itself. Stop a timer by calling `close`. The `cb` may still be run one final time, if the timer has -already expired. +itself. Stop a timer by calling `close`. The `callback` may still be run one final time, if the timer +has already expired. # Examples @@ -266,62 +274,56 @@ julia> begin """ function Timer(cb::Function, timeout::Real; interval::Real=0.0) timer = Timer(timeout, interval=interval) - t = @task while _trywait(timer) - try - cb(timer) - catch err - write(stderr, "Error in Timer:\n") - showerror(stderr, err, catch_backtrace()) - return + t = @task begin + unpreserve_handle(timer) + while _trywait(timer) + try + cb(timer) + catch err + write(stderr, "Error in Timer:\n") + showerror(stderr, err, catch_backtrace()) + return + end + isopen(timer) || return + end + end + # here we are mimicking parts of _trywait, in coordination with task `t` + preserve_handle(timer) + @lock timer.cond begin + if timer.set + schedule(t) + else + _wait2(timer.cond, t) end - isopen(timer) || return end - lock(timer.cond) - _wait2(timer.cond, t) - unlock(timer.cond) return timer end """ - timedwait(callback::Function, timeout::Real; pollint::Real=0.1) + timedwait(testcb, timeout::Real; pollint::Real=0.1) -Waits until `callback` returns `true` or `timeout` seconds have passed, whichever is earlier. -`callback` is polled every `pollint` seconds. The minimum value for `timeout` and `pollint` -is `0.001`, that is, 1 millisecond. +Waits 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. -Returns :ok or :timed_out +Return `:ok` or `:timed_out`. """ -function timedwait(testcb::Function, timeout::Real; pollint::Real=0.1) +function timedwait(testcb, timeout::Real; pollint::Real=0.1) pollint >= 1e-3 || throw(ArgumentError("pollint must be ≥ 1 millisecond")) start = time_ns() ns_timeout = 1e9 * timeout - done = Channel(1) - function timercb(aw) - try - if testcb() - put!(done, (:ok, nothing)) - elseif (time_ns() - start) > ns_timeout - put!(done, (:timed_out, nothing)) - end - catch e - put!(done, (:error, CapturedException(e, catch_backtrace()))) - finally - isready(done) && close(aw) - end - nothing - end - - try - testcb() && return :ok - catch e - throw(CapturedException(e, catch_backtrace())) - end - - t = Timer(timercb, pollint, interval = pollint) - ret, e = fetch(done) - close(t) - ret === :error && throw(e) + testcb() && return :ok - return ret + t = Timer(pollint, interval=pollint) + while _trywait(t) # stop if we ever get closed + if testcb() + close(t) + return :ok + elseif (time_ns() - start) > ns_timeout + close(t) + break + end + end + return :timed_out end diff --git a/base/atomics.jl b/base/atomics.jl index e6d62c3fc807b..7312206c19896 100644 --- a/base/atomics.jl +++ b/base/atomics.jl @@ -20,7 +20,7 @@ export # - LLVM doesn't currently support atomics on floats for ppc64 # C++20 is adding limited support for atomics on float, but as of # now Clang does not support that yet. -if Sys.ARCH == :i686 || startswith(string(Sys.ARCH), "arm") || +if Sys.ARCH === :i686 || startswith(string(Sys.ARCH), "arm") || Sys.ARCH === :powerpc64le || Sys.ARCH === :ppc64le const inttypes = (Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64) diff --git a/base/baseext.jl b/base/baseext.jl index 8ebd599312453..625a82ff29234 100644 --- a/base/baseext.jl +++ b/base/baseext.jl @@ -16,7 +16,7 @@ VecElement # hook up VecElement constructor to Base.convert VecElement{T}(arg) where {T} = VecElement{T}(convert(T, arg)) convert(::Type{T}, arg::T) where {T<:VecElement} = arg -convert(::Type{T}, arg) where {T<:VecElement} = T(arg) +convert(::Type{T}, arg) where {T<:VecElement} = T(arg)::T # ## dims-type-converting Array constructors for convenience # type and dimensionality specified, accepting dims as series of Integers diff --git a/base/binaryplatforms.jl b/base/binaryplatforms.jl index efc58dc6c6b7a..a4935d060b74a 100644 --- a/base/binaryplatforms.jl +++ b/base/binaryplatforms.jl @@ -40,10 +40,10 @@ struct Platform <: AbstractPlatform # The "compare strategy" allows selective overriding on how a tag is compared compare_strategies::Dict{String,Function} - function Platform(arch::String, os::String; + # Passing `tags` as a `Dict` avoids the need to infer different NamedTuple specializations + function Platform(arch::String, os::String, _tags::Dict{String}; validate_strict::Bool = false, - compare_strategies::Dict{String,<:Function} = Dict{String,Function}(), - kwargs...) + compare_strategies::Dict{String,<:Function} = Dict{String,Function}()) # A wee bit of normalization os = lowercase(os) arch = CPUID.normalize_arch(arch) @@ -52,8 +52,9 @@ struct Platform <: AbstractPlatform "arch" => arch, "os" => os, ) - for (tag, value) in kwargs - tag = lowercase(string(tag::Symbol)) + for (tag, value) in _tags + value = value::Union{String,VersionNumber,Nothing} + tag = lowercase(tag) if tag ∈ ("arch", "os") throw(ArgumentError("Cannot double-pass key $(tag)")) end @@ -70,8 +71,8 @@ struct Platform <: AbstractPlatform if tag ∈ ("libgfortran_version", "libstdcxx_version", "os_version") if isa(value, VersionNumber) value = string(value) - elseif isa(value, AbstractString) - v = tryparse(VersionNumber, String(value)::String) + elseif isa(value, String) + v = tryparse(VersionNumber, value) if isa(v, VersionNumber) value = string(v) end @@ -110,6 +111,19 @@ struct Platform <: AbstractPlatform end end +# Keyword interface (to avoid inference of specialized NamedTuple methods, use the Dict interface for `tags`) +function Platform(arch::String, os::String; + validate_strict::Bool = false, + compare_strategies::Dict{String,<:Function} = Dict{String,Function}(), + kwargs...) + tags = Dict{String,Any}(String(tag)::String=>tagvalue(value) for (tag, value) in kwargs) + return Platform(arch, os, tags; validate_strict, compare_strategies) +end + +tagvalue(v::Union{String,VersionNumber,Nothing}) = v +tagvalue(v::Symbol) = String(v) +tagvalue(v::AbstractString) = convert(String, v)::String + # Simple tag insertion that performs a little bit of validation function add_tag!(tags::Dict{String,String}, tag::String, value::String) # I know we said only alphanumeric and dots, but let's be generous so that we can expand @@ -245,14 +259,14 @@ end function set_compare_strategy!(p::Platform, key::String, f::Function) if !haskey(p.tags, key) - throw(ArgumentError("Cannot set comparison strategy for nonexistant tag $(key)!")) + throw(ArgumentError("Cannot set comparison strategy for nonexistent tag $(key)!")) end p.compare_strategies[key] = f end function get_compare_strategy(p::Platform, key::String, default = compare_default) if !haskey(p.tags, key) - throw(ArgumentError("Cannot get comparison strategy for nonexistant tag $(key)!")) + throw(ArgumentError("Cannot get comparison strategy for nonexistent tag $(key)!")) end return get(p.compare_strategies, key, default) end @@ -264,7 +278,7 @@ get_compare_strategy(p::AbstractPlatform, key::String, default = compare_default compare_default(a::String, b::String, a_requested::Bool, b_requested::Bool) Default comparison strategy that falls back to `a == b`. This only ever happens if both -`a` and `b` request this strategy, as any other strategy is preferrable to this one. +`a` and `b` request this strategy, as any other strategy is preferable to this one. """ function compare_default(a::String, b::String, a_requested::Bool, b_requested::Bool) return a == b @@ -570,6 +584,7 @@ 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.isunix(p::AbstractPlatform) = Sys.isbsd(p) || Sys.islinux(p) const arch_mapping = Dict( "x86_64" => "(x86_|amd)64", @@ -608,7 +623,8 @@ const arch_march_isa_mapping = let "armv8_0" => get_set("aarch64", "armv8.0-a"), "armv8_1" => get_set("aarch64", "armv8.1-a"), "armv8_2_crypto" => get_set("aarch64", "armv8.2-a+crypto"), - "armv8_4_crypto_sve" => get_set("aarch64", "armv8.4-a+crypto+sve"), + "a64fx" => get_set("aarch64", "a64fx"), + "apple_m1" => get_set("aarch64", "apple_m1"), ], "powerpc64le" => [ "power8" => get_set("powerpc64le", "power8"), @@ -652,7 +668,7 @@ const libstdcxx_version_mapping = Dict{String,String}( Parses a string platform triplet back into a `Platform` object. """ -function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict::Bool = false) +function Base.parse(::Type{Platform}, triplet::String; validate_strict::Bool = false) # 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], "|"), ")") @@ -698,21 +714,22 @@ function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict:: end # Extract the information we're interested in: + tags = Dict{String,Any}() arch = get_field(m, arch_mapping) os = get_field(m, os_mapping) - libc = get_field(m, libc_mapping) - call_abi = get_field(m, call_abi_mapping) - libgfortran_version = get_field(m, libgfortran_version_mapping) - libstdcxx_version = get_field(m, libstdcxx_version_mapping) - cxxstring_abi = get_field(m, cxxstring_abi_mapping) + tags["libc"] = get_field(m, libc_mapping) + tags["call_abi"] = get_field(m, call_abi_mapping) + tags["libgfortran_version"] = get_field(m, libgfortran_version_mapping) + tags["libstdcxx_version"] = get_field(m, libstdcxx_version_mapping) + tags["cxxstring_abi"] = get_field(m, cxxstring_abi_mapping) function split_tags(tagstr) tag_fields = split(tagstr, "-"; keepempty=false) if isempty(tag_fields) return Pair{String,String}[] end - return map(v -> Symbol(v[1]) => v[2], split.(tag_fields, "+")) + return map(v -> String(v[1]) => String(v[2]), split.(tag_fields, "+")) end - tags = split_tags(m["tags"]) + merge!(tags, Dict(split_tags(m["tags"]))) # Special parsing of os version number, if any exists function extract_os_version(os_name, pattern) @@ -724,26 +741,19 @@ function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict:: end os_version = nothing if os == "macos" - os_version = extract_os_version("macos", r".*darwin([\d\.]+)") + os_version = extract_os_version("macos", r".*darwin([\d\.]+)"sa) end if os == "freebsd" - os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)") + os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)"sa) end + tags["os_version"] = os_version - return Platform( - arch, os; - validate_strict, - libc, - call_abi, - libgfortran_version, - cxxstring_abi, - libstdcxx_version, - os_version, - tags..., - ) + return Platform(arch, os, tags; validate_strict) end throw(ArgumentError("Platform `$(triplet)` is not an officially supported platform")) end +Base.parse(::Type{Platform}, triplet::AbstractString; kwargs...) = + parse(Platform, convert(String, triplet)::String; kwargs...) function Base.tryparse(::Type{Platform}, triplet::AbstractString) try @@ -788,13 +798,13 @@ function parse_dl_name_version(path::String, os::String) local dlregex if os == "windows" # On Windows, libraries look like `libnettle-6.dll` - dlregex = r"^(.*?)(?:-((?:[\.\d]+)*))?\.dll$" + dlregex = r"^(.*?)(?:-((?:[\.\d]+)*))?\.dll$"sa elseif os == "macos" # On OSX, libraries look like `libnettle.6.3.dylib` - dlregex = r"^(.*?)((?:\.[\d]+)*)\.dylib$" + dlregex = r"^(.*?)((?:\.[\d]+)*)\.dylib$"sa else # On Linux and FreeBSD, libraries look like `libnettle.so.6.3.0` - dlregex = r"^(.*?)\.so((?:\.[\d]+)*)$" + dlregex = r"^(.*?)\.so((?:\.[\d]+)*)$"sa end m = match(dlregex, basename(path)) @@ -863,7 +873,7 @@ function detect_libstdcxx_version(max_minor_version::Int=30) end # Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against - hdl = Libdl.dlopen(first(libstdcxx_paths)) + hdl = Libdl.dlopen(first(libstdcxx_paths))::Ptr{Cvoid} # Try all GLIBCXX versions down to GCC v4.8: # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html for minor_version in max_minor_version:-1:18 @@ -894,7 +904,7 @@ function detect_cxxstring_abi() end function open_libllvm(f::Function) - for lib_name in ("libLLVM-13jl", "libLLVM", "LLVM", "libLLVMSupport") + for lib_name in (Base.libllvm_name, "libLLVM", "LLVM", "libLLVMSupport") hdl = Libdl.dlopen_e(lib_name) if hdl != C_NULL try @@ -1006,19 +1016,19 @@ function platforms_match(a::AbstractPlatform, b::AbstractPlatform) # Throw an error if `a` and `b` have both set non-default comparison strategies for `k` # and they're not the same strategy. - if a_comp != compare_default && b_comp != compare_default && a_comp != b_comp + if a_comp !== compare_default && b_comp !== compare_default && a_comp !== b_comp throw(ArgumentError("Cannot compare Platform objects with two different non-default comparison strategies for the same key \"$(k)\"")) end # Select the custom comparator, if we have one. comparator = a_comp - if b_comp != compare_default + if b_comp !== compare_default comparator = b_comp end # Call the comparator, passing in which objects requested this comparison (one, the other, or both) # For some comparators this doesn't matter, but for non-symmetrical comparisons, it does. - if !comparator(ak, bk, a_comp == comparator, b_comp == comparator) + if !(comparator(ak, bk, a_comp === comparator, b_comp === comparator)::Bool) return false end end @@ -1057,14 +1067,35 @@ function select_platform(download_info::Dict, platform::AbstractPlatform = HostP return nothing end - # At this point, we may have multiple possibilities. E.g. if, in the future, - # Julia can be built without a direct dependency on libgfortran, we may match - # multiple tarballs that vary only within their libgfortran ABI. To narrow it - # down, we just sort by triplet, then pick the last one. This has the effect - # of generally choosing the latest release (e.g. a `libgfortran5` tarball - # rather than a `libgfortran3` tarball) - p = last(sort(ps, by = p -> triplet(p))) - return download_info[p] + # At this point, we may have multiple possibilities. We now engage a multi- + # stage selection algorithm, where we first sort the matches by how complete + # the match is, e.g. preferring matches where the intersection of tags is + # equal to the union of the tags: + function match_loss(a, b) + a_tags = Set(keys(tags(a))) + b_tags = Set(keys(tags(b))) + return length(union(a_tags, b_tags)) - length(intersect(a_tags, b_tags)) + end + + # We prefer these better matches, and secondarily reverse-sort by triplet so + # as to generally choose the latest release (e.g. a `libgfortran5` tarball + # over a `libgfortran3` tarball). + ps = sort(ps, lt = (a, b) -> begin + loss_a = match_loss(a, platform) + loss_b = match_loss(b, platform) + if loss_a != loss_b + return loss_a < loss_b + end + return triplet(a) > triplet(b) + end) + + # @invokelatest here to not get invalidated by new defs of `==(::Function, ::Function)` + return @invokelatest getindex(download_info, first(ps)) end +# precompiles to reduce latency (see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1025692379) +Dict{Platform,String}()[HostPlatform()] = "" +Platform("x86_64", "linux", Dict{String,Any}(); validate_strict=true) +Platform("x86_64", "linux", Dict{String,String}(); validate_strict=false) # called this way from Artifacts.unpack_platform + end # module diff --git a/base/bitarray.jl b/base/bitarray.jl index 33e2715572018..f29b30d0ac8c0 100644 --- a/base/bitarray.jl +++ b/base/bitarray.jl @@ -18,7 +18,7 @@ the functions [`trues`](@ref) and [`falses`](@ref). !!! note Due to its packed storage format, concurrent access to the elements of a `BitArray` - where at least one of them is a write is not thread safe. + where at least one of them is a write is not thread-safe. """ mutable struct BitArray{N} <: AbstractArray{Bool, N} @@ -458,10 +458,11 @@ function unsafe_copyto!(dest::BitArray, doffs::Integer, src::Union{BitArray,Arra return dest end -copyto!(dest::BitArray, doffs::Integer, src::Array, soffs::Integer, n::Integer) = +copyto!(dest::BitArray, doffs::Integer, src::Union{BitArray,Array}, soffs::Integer, n::Integer) = _copyto_int!(dest, Int(doffs), src, Int(soffs), Int(n)) -function _copyto_int!(dest::BitArray, doffs::Int, src::Array, soffs::Int, n::Int) +function _copyto_int!(dest::BitArray, doffs::Int, src::Union{BitArray,Array}, soffs::Int, n::Int) n == 0 && return dest + n < 0 && throw(ArgumentError("Number of elements to copy must be nonnegative.")) soffs < 1 && throw(BoundsError(src, soffs)) doffs < 1 && throw(BoundsError(dest, doffs)) soffs+n-1 > length(src) && throw(BoundsError(src, length(src)+1)) @@ -501,40 +502,42 @@ function Array{T,N}(B::BitArray{N}) where {T,N} end BitArray(A::AbstractArray{<:Any,N}) where {N} = BitArray{N}(A) + function BitArray{N}(A::AbstractArray{T,N}) where N where T B = BitArray(undef, convert(Dims{N}, size(A)::Dims{N})) - Bc = B.chunks - l = length(B) + _checkaxs(axes(B), axes(A)) + _copyto_bitarray!(B, A) + return B::BitArray{N} +end + +function _copyto_bitarray!(B::BitArray, A::AbstractArray) + l = length(A) l == 0 && return B - ind = 1 + l > length(B) && throw(BoundsError(B, length(B)+1)) + Bc = B.chunks + nc = num_bit_chunks(l) + Ai = first(eachindex(A)) @inbounds begin - for i = 1:length(Bc)-1 + for i = 1:nc-1 c = UInt64(0) for j = 0:63 - c |= (UInt64(convert(Bool, A[ind])::Bool) << j) - ind += 1 + c |= (UInt64(convert(Bool, A[Ai])::Bool) << j) + Ai = nextind(A, Ai) end Bc[i] = c end c = UInt64(0) - for j = 0:_mod64(l-1) - c |= (UInt64(convert(Bool, A[ind])::Bool) << j) - ind += 1 + tail = _mod64(l - 1) + 1 + for j = 0:tail-1 + c |= (UInt64(convert(Bool, A[Ai])::Bool) << j) + Ai = nextind(A, Ai) end - Bc[end] = c + msk = _msk_end(tail) + Bc[nc] = (c & msk) | (Bc[nc] & ~msk) end return B end -function BitArray{N}(A::Array{Bool,N}) where N - B = BitArray(undef, size(A)) - Bc = B.chunks - l = length(B) - l == 0 && return B - copy_to_bitarray_chunks!(Bc, 1, A, 1, l) - return B::BitArray{N} -end - reinterpret(::Type{Bool}, B::BitArray, dims::NTuple{N,Int}) where {N} = reinterpret(B, dims) reinterpret(B::BitArray, dims::NTuple{N,Int}) where {N} = reshape(B, dims) @@ -574,7 +577,7 @@ julia> BitArray(x+y == 3 for x = 1:2 for y = 1:3) BitArray(itr) = gen_bitarray(IteratorSize(itr), itr) BitArray{N}(itr) where N = gen_bitarrayN(BitArray{N}, IteratorSize(itr), itr) -convert(T::Type{<:BitArray}, a::AbstractArray) = a isa T ? a : T(a) +convert(::Type{T}, a::AbstractArray) where {T<:BitArray} = a isa T ? a : T(a)::T # generic constructor from an iterable without compile-time info # (we pass start(itr) explicitly to avoid a type-instability with filters) @@ -721,24 +724,25 @@ function _unsafe_setindex!(B::BitArray, X::AbstractArray, I::BitArray) lx = length(X) last_chunk_len = _mod64(length(B)-1)+1 - c = 1 + Xi = first(eachindex(X)) + lastXi = last(eachindex(X)) for i = 1:lc @inbounds Imsk = Ic[i] @inbounds C = Bc[i] u = UInt64(1) for j = 1:(i < lc ? 64 : last_chunk_len) if Imsk & u != 0 - lx < c && throw_setindex_mismatch(X, c) - @inbounds x = convert(Bool, X[c]) + Xi > lastXi && throw_setindex_mismatch(X, count(I)) + @inbounds x = convert(Bool, X[Xi]) C = ifelse(x, C | u, C & ~u) - c += 1 + Xi = nextind(X, Xi) end u <<= 1 end @inbounds Bc[i] = C end - if length(X) != c-1 - throw_setindex_mismatch(X, c-1) + if Xi != nextind(X, lastXi) + throw_setindex_mismatch(X, count(I)) end return B end @@ -1541,12 +1545,12 @@ function unsafe_bitfindprev(Bc::Vector{UInt64}, start::Int) @inbounds begin if Bc[chunk_start] & mask != 0 - return (chunk_start-1) << 6 + (64 - leading_zeros(Bc[chunk_start] & mask)) + return (chunk_start-1) << 6 + (top_set_bit(Bc[chunk_start] & mask)) end for i = (chunk_start-1):-1:1 if Bc[i] != 0 - return (i-1) << 6 + (64 - leading_zeros(Bc[i])) + return (i-1) << 6 + (top_set_bit(Bc[i])) end end end @@ -1775,26 +1779,42 @@ end # map across the chunks. Otherwise, fall-back to the AbstractArray method that # iterates bit-by-bit. function bit_map!(f::F, dest::BitArray, A::BitArray) where F - size(A) == size(dest) || throw(DimensionMismatch("sizes of dest and A must match")) + length(A) <= length(dest) || throw(DimensionMismatch("length of destination must be >= length of collection")) isempty(A) && return dest destc = dest.chunks Ac = A.chunks - for i = 1:(length(Ac)-1) + len_Ac = length(Ac) + for i = 1:(len_Ac-1) destc[i] = f(Ac[i]) end - destc[end] = f(Ac[end]) & _msk_end(A) + # the last effected UInt64's original content + dest_last = destc[len_Ac] + _msk = _msk_end(A) + # first zero out the bits mask is going to change + destc[len_Ac] = (dest_last & (~_msk)) + # then update bits by `or`ing with a masked RHS + destc[len_Ac] |= f(Ac[len_Ac]) & _msk dest end function bit_map!(f::F, dest::BitArray, A::BitArray, B::BitArray) where F - size(A) == size(B) == size(dest) || throw(DimensionMismatch("sizes of dest, A, and B must all match")) + min_bitlen = min(length(A), length(B)) + min_bitlen <= length(dest) || throw(DimensionMismatch("length of destination must be >= length of smallest input collection")) isempty(A) && return dest + isempty(B) && return dest destc = dest.chunks Ac = A.chunks Bc = B.chunks - for i = 1:(length(Ac)-1) + len_Ac = min(length(Ac), length(Bc)) + for i = 1:len_Ac-1 destc[i] = f(Ac[i], Bc[i]) end - destc[end] = f(Ac[end], Bc[end]) & _msk_end(A) + # the last effected UInt64's original content + dest_last = destc[len_Ac] + _msk = _msk_end(min_bitlen) + # first zero out the bits mask is going to change + destc[len_Ac] = (dest_last & ~(_msk)) + # then update bits by `or`ing with a masked RHS + destc[len_Ac] |= f(Ac[end], Bc[end]) & _msk dest end @@ -1812,7 +1832,7 @@ function hcat(B::BitVector...) height = length(B[1]) for j = 2:length(B) length(B[j]) == height || - throw(DimensionMismatch("dimensions must match")) + throw(DimensionMismatch("dimensions must match: $j-th argument has length $(length(B[j])), should have $height")) end M = BitMatrix(undef, height, length(B)) for j = 1:length(B) @@ -1845,7 +1865,7 @@ function hcat(A::Union{BitMatrix,BitVector}...) nd = ndims(Aj) ncols += (nd==2 ? size(Aj,2) : 1) size(Aj, 1) == nrows || - throw(DimensionMismatch("row lengths must match")) + throw(DimensionMismatch("row lengths must match: $j-th element has first dim $(size(Aj, 1)), should have $nrows")) end B = BitMatrix(undef, nrows, ncols) @@ -1871,7 +1891,7 @@ function vcat(A::BitMatrix...) ncols = size(A[1], 2) for j = 2:nargs size(A[j], 2) == ncols || - throw(DimensionMismatch("column lengths must match")) + throw(DimensionMismatch("column lengths must match: $j-th element has second dim $(size(A[j], 2)), should have $ncols")) end B = BitMatrix(undef, nrows, ncols) Bc = B.chunks @@ -1913,3 +1933,10 @@ function read!(s::IO, B::BitArray) end sizeof(B::BitArray) = sizeof(B.chunks) + +function _split_rest(a::Union{Vector, BitVector}, n::Int) + _check_length_split_rest(length(a), n) + last_n = a[end-n+1:end] + resize!(a, length(a) - n) + return a, last_n +end diff --git a/base/bitset.jl b/base/bitset.jl index 0abd9d4b782d2..5ce07389c771e 100644 --- a/base/bitset.jl +++ b/base/bitset.jl @@ -15,7 +15,7 @@ mutable struct BitSet <: AbstractSet{Int} # 1st stored Int equals 64*offset offset::Int - BitSet() = new(sizehint!(zeros(UInt64, 0), 4), NO_OFFSET) + BitSet() = new(resize!(Vector{UInt64}(undef, 4), 0), NO_OFFSET) end """ @@ -38,8 +38,6 @@ end @inline intoffset(s::BitSet) = s.offset << 6 -eltype(::Type{BitSet}) = Int - empty(s::BitSet, ::Type{Int}=Int) = BitSet() emptymutable(s::BitSet, ::Type{Int}=Int) = BitSet() @@ -125,7 +123,7 @@ end function union!(s::BitSet, r::AbstractUnitRange{<:Integer}) isempty(r) && return s - a, b = _check_bitset_bounds(first(r)), _check_bitset_bounds(last(r)) + a, b = Int(first(r)), Int(last(r)) cidxa = _div64(a) cidxb = _div64(b) if s.offset == NO_OFFSET @@ -137,20 +135,10 @@ function union!(s::BitSet, r::AbstractUnitRange{<:Integer}) # grow s.bits as necessary if diffb >= len - _growend!(s.bits, diffb - len + 1) - # we set only some values to CHK0, those which will not be - # fully overwritten (i.e. only or'ed with `|`) - s.bits[end] = CHK0 # end == diffb + 1 - if diffa >= len - s.bits[diffa + 1] = CHK0 - end + _growend0!(s.bits, diffb - len + 1) end if diffa < 0 - _growbeg!(s.bits, -diffa) - s.bits[1] = CHK0 - if diffb < 0 - s.bits[diffb - diffa + 1] = CHK0 - end + _growbeg0!(s.bits, -diffa) s.offset = cidxa # s.offset += diffa diffb -= diffa diffa = 0 @@ -257,20 +245,7 @@ function _matched_map!(f, a1::Bits, b1::Int, a2::Bits, b2::Int, b1 # the new offset end - -@noinline _throw_bitset_bounds_err() = - throw(ArgumentError("elements of BitSet must be between typemin(Int) and typemax(Int)")) - -@inline _is_convertible_Int(n) = typemin(Int) <= n <= typemax(Int) - -@inline _check_bitset_bounds(n) = - _is_convertible_Int(n) ? Int(n) : _throw_bitset_bounds_err() - -@inline _check_bitset_bounds(n::Int) = n - -@noinline _throw_keyerror(n) = throw(KeyError(n)) - -@inline push!(s::BitSet, n::Integer) = _setint!(s, _check_bitset_bounds(n), true) +@inline push!(s::BitSet, n::Integer) = _setint!(s, Int(n), true) push!(s::BitSet, ns::Integer...) = (for n in ns; push!(s, n); end; s) @@ -281,7 +256,7 @@ push!(s::BitSet, ns::Integer...) = (for n in ns; push!(s, n); end; s) delete!(s, n) n else - _throw_keyerror(n) + throw(KeyError(n)) end end @@ -294,6 +269,7 @@ end end end +@inline _is_convertible_Int(n) = typemin(Int) <= n <= typemax(Int) @inline delete!(s::BitSet, n::Int) = _setint!(s, n, false) @inline delete!(s::BitSet, n::Integer) = _is_convertible_Int(n) ? delete!(s, Int(n)) : s @@ -326,8 +302,15 @@ function symdiff!(s::BitSet, ns) return s end +function symdiff!(s::BitSet, ns::AbstractSet) + for x in ns + int_symdiff!(s, x) + end + return s +end + function int_symdiff!(s::BitSet, n::Integer) - n0 = _check_bitset_bounds(n) + n0 = Int(n) val = !(n0 in s) _setint!(s, n0, val) s diff --git a/base/bool.jl b/base/bool.jl index 7648df3e0250e..d7dcf76caa91b 100644 --- a/base/bool.jl +++ b/base/bool.jl @@ -112,7 +112,8 @@ nand(x...) = ~(&)(x...) Bitwise nor (not or) of `x` and `y`. Implements [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic), -returning [`missing`](@ref) if one of the arguments is `missing`. +returning [`missing`](@ref) if one of the arguments is `missing` and the +other is not `true`. The infix operation `a ⊽ b` is a synonym for `nor(a,b)`, and `⊽` can be typed by tab-completing `\\nor` or `\\barvee` in the Julia REPL. @@ -131,6 +132,9 @@ false julia> false ⊽ false true +julia> false ⊽ missing +missing + julia> [true; true; false] .⊽ [true; false; false] 3-element BitVector: 0 diff --git a/base/boot.jl b/base/boot.jl index 90322b69a54d9..43ced22c043d5 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -109,10 +109,10 @@ #struct LineInfoNode # module::Module -# method::Symbol +# method::Any (Union{Symbol, Method, MethodInstance}) # file::Symbol -# line::Int -# inlined_at::Int +# line::Int32 +# inlined_at::Int32 #end #struct GotoNode @@ -193,6 +193,8 @@ export # object model functions fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, nfields, throw, tuple, ===, isdefined, eval, + # access to globals + getglobal, setglobal!, # ifelse, sizeof # not exported, to avoid conflicting with Base # type reflection <:, typeof, isa, typeassert, @@ -201,7 +203,7 @@ export # constants nothing, Main -const getproperty = getfield +const getproperty = getfield # TODO: use `getglobal` for modules instead const setproperty! = setfield! abstract type Number end @@ -222,7 +224,7 @@ primitive type Char <: AbstractChar 32 end primitive type Int8 <: Signed 8 end #primitive type UInt8 <: Unsigned 8 end primitive type Int16 <: Signed 16 end -primitive type UInt16 <: Unsigned 16 end +#primitive type UInt16 <: Unsigned 16 end #primitive type Int32 <: Signed 32 end #primitive type UInt32 <: Unsigned 32 end #primitive type Int64 <: Signed 64 end @@ -243,7 +245,6 @@ ccall(:jl_toplevel_eval_in, Any, (Any, Any), (f::typeof(Typeof))(x) = ($(_expr(:meta,:nospecialize,:x)); isa(x,Type) ? Type{x} : typeof(x)) end) - macro nospecialize(x) _expr(:meta, :nospecialize, x) end @@ -254,11 +255,25 @@ TypeVar(n::Symbol, @nospecialize(lb), @nospecialize(ub)) = _typevar(n, lb, ub) UnionAll(v::TypeVar, @nospecialize(t)) = ccall(:jl_type_unionall, Any, (Any, Any), v, t) -const Vararg = ccall(:jl_toplevel_eval_in, Any, (Any, Any), Core, _expr(:new, TypeofVararg)) +# 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 +convert(::Type{Any}, @nospecialize(x)) = x +convert(::Type{T}, x::T) where {T} = x +cconvert(::Type{T}, x) where {T} = convert(T, x) +unsafe_convert(::Type{T}, x::T) where {T} = x -# let the compiler assume that calling Union{} as a constructor does not need -# to be considered ever (which comes up often as Type{<:T}) -Union{}(a...) = throw(MethodError(Union{}, a)) +# dispatch token indicating a kwarg (keyword sorter) call +function kwcall end +# deprecated internal functions: +kwfunc(@nospecialize(f)) = kwcall +kwftype(@nospecialize(t)) = typeof(kwcall) + +# Let the compiler assume that calling Union{} as a constructor does not need +# to be considered ever (which comes up often as Type{<:T} inference, and +# occasionally in user code from eltype). +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...) @@ -367,10 +382,6 @@ include(m::Module, fname::String) = ccall(:jl_load_, Any, (Any, Any), m, fname) eval(m::Module, @nospecialize(e)) = ccall(:jl_toplevel_eval_in, Any, (Any, Any), m, e) -kwfunc(@nospecialize(f)) = ccall(:jl_get_keyword_sorter, Any, (Any,), f) - -kwftype(@nospecialize(t)) = typeof(ccall(:jl_get_kwsorter, Any, (Any,), t)) - mutable struct Box contents::Any Box(@nospecialize(x)) = new(x) @@ -408,47 +419,52 @@ eval(Core, quote isa(f, String) && (f = Symbol(f)) return $(Expr(:new, :LineNumberNode, :l, :f)) end - LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int, inlined_at::Int) = + LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) = $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)) - GlobalRef(m::Module, s::Symbol) = $(Expr(:new, :GlobalRef, :m, :s)) SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n)) - TypedSlot(n::Int, @nospecialize(t)) = $(Expr(:new, :TypedSlot, :n, :t)) PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values)) PiNode(@nospecialize(val), @nospecialize(typ)) = $(Expr(:new, :PiNode, :val, :typ)) PhiCNode(values::Array{Any, 1}) = $(Expr(:new, :PhiCNode, :values)) UpsilonNode(@nospecialize(val)) = $(Expr(:new, :UpsilonNode, :val)) UpsilonNode() = $(Expr(:new, :UpsilonNode)) - function CodeInstance( - mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), - @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, - ipo_effects::UInt8, effects::UInt8, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), - relocatability::UInt8) - return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt8, UInt8, Any, UInt8), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, argescapes, - relocatability) - end Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)) - PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields)) - PartialOpaque(@nospecialize(typ), @nospecialize(env), parent::MethodInstance, source::Method) = $(Expr(:new, :PartialOpaque, :typ, :env, :parent, :source)) - InterConditional(slot::Int, @nospecialize(vtype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :vtype, :elsetype)) + # NOTE the main constructor is defined within `Core.Compiler` + _PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields)) + PartialOpaque(@nospecialize(typ), @nospecialize(env), parent::MethodInstance, source) = $(Expr(:new, :PartialOpaque, :typ, :env, :parent, :source)) + InterConditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :thentype, :elsetype)) MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers)) end) +function CodeInstance( + mi::MethodInstance, @nospecialize(rettype), @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) + 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) +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) function _Task(@nospecialize(f), reserved_stack::Int, completion_future) return ccall(:jl_new_task, Ref{Task}, (Any, Any, Int), f, completion_future, reserved_stack) end -# 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 -convert(::Type{Any}, @nospecialize(x)) = x -convert(::Type{T}, x::T) where {T} = x -cconvert(::Type{T}, x) where {T} = convert(T, x) -unsafe_convert(::Type{T}, x::T) where {T} = x +_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, + #=:consistent=#true, + #=:effect_free=#true, + #=:nothrow=#false, + #=:terminates_globally=#true, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false)) +end const NTuple{N,T} = Tuple{Vararg{T,N}} @@ -477,7 +493,6 @@ 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) - (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) @@ -486,32 +501,36 @@ 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) # primitive Symbol constructors -eval(Core, :(function Symbol(s::String) - $(Expr(:meta, :pure)) - return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), - ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s), - sizeof(s)) -end)) + +## Helper for proper GC rooting without unsafe_convert +eval(Core, quote + _Symbol(ptr::Ptr{UInt8}, sz::Int, root::Any) = $(Expr(:foreigncall, QuoteNode(:jl_symbol_n), + Ref{Symbol}, svec(Ptr{UInt8}, Int), 0, QuoteNode(:ccall), :ptr, :sz, :root)) +end) + +function Symbol(s::String) + @_foldable_meta + return _Symbol(ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s), sizeof(s), s) +end function Symbol(a::Array{UInt8,1}) - return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), - ccall(:jl_array_ptr, Ptr{UInt8}, (Any,), a), - Intrinsics.arraylen(a)) + return _Symbol(ccall(:jl_array_ptr, Ptr{UInt8}, (Any,), a), Intrinsics.arraylen(a), a) end Symbol(s::Symbol) = s # module providing the IR object model module IR + export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, - NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, Argument, + NewvarNode, SSAValue, SlotNumber, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct + Const, PartialStruct, InterConditional import Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, - NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot, Argument, + NewvarNode, SSAValue, SlotNumber, Argument, PiNode, PhiNode, PhiCNode, UpsilonNode, LineInfoNode, - Const, PartialStruct + Const, PartialStruct, InterConditional -end +end # module IR # docsystem basics const unescape = Symbol("hygienic-scope") @@ -570,28 +589,25 @@ println(@nospecialize a...) = println(stdout, a...) struct GeneratedFunctionStub gen - argnames::Array{Any,1} - spnames::Union{Nothing, Array{Any,1}} - line::Int - file::Symbol - expand_early::Bool + argnames::SimpleVector + spnames::SimpleVector end -# invoke and wrap the results of @generated -function (g::GeneratedFunctionStub)(@nospecialize args...) +# invoke and wrap the results of @generated expression +function (g::GeneratedFunctionStub)(world::UInt, source::LineNumberNode, @nospecialize args...) + # args is (spvals..., argtypes...) body = g.gen(args...) - if body isa CodeInfo - return body - end - lam = Expr(:lambda, g.argnames, - Expr(Symbol("scope-block"), + file = source.file + file isa Symbol || (file = :none) + lam = Expr(:lambda, Expr(:argnames, g.argnames...).args, + Expr(:var"scope-block", Expr(:block, - LineNumberNode(g.line, g.file), - Expr(:meta, :push_loc, g.file, Symbol("@generated body")), + source, + Expr(:meta, :push_loc, file, :var"@generated body"), Expr(:return, body), Expr(:meta, :pop_loc)))) spnames = g.spnames - if spnames === nothing + if spnames === svec() return lam else return Expr(Symbol("with-static-parameters"), lam, spnames...) @@ -600,7 +616,8 @@ end NamedTuple() = NamedTuple{(),Tuple{}}(()) -NamedTuple{names}(args::Tuple) where {names} = NamedTuple{names,typeof(args)}(args) +eval(Core, :(NamedTuple{names}(args::Tuple) where {names} = + $(Expr(:splatnew, :(NamedTuple{names,typeof(args)}), :args)))) using .Intrinsics: sle_int, add_int @@ -823,8 +840,15 @@ struct Pair{A, B} # but also mark the whole function with `@inline` to ensure we will inline it whenever possible # (even if `convert(::Type{A}, a::A)` for some reason was expensive) Pair(a, b) = new{typeof(a), typeof(b)}(a, b) - Pair{A, B}(a::A, b::B) where {A, B} = new(a, b) - Pair{Any, Any}(@nospecialize(a::Any), @nospecialize(b::Any)) = new(a, b) + function Pair{A, B}(@nospecialize(a), @nospecialize(b)) where {A, B} + @inline + return new(a::A, b::B) + end +end + +function _hasmethod(@nospecialize(tt)) # this function has a special tfunc + world = ccall(:jl_get_tls_world_age, UInt, ()) + return Intrinsics.not_int(ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tt, nothing, world) === nothing) end ccall(:jl_set_istopmod, Cvoid, (Any, Bool), Core, true) diff --git a/base/broadcast.jl b/base/broadcast.jl index fb9ba9555cfd9..1e057789509ed 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -8,7 +8,7 @@ Module containing the broadcasting implementation. module Broadcast using .Base.Cartesian -using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, promote_typejoin_union, @pure, +using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, promote_typejoin_union, _msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, unalias, negate import .Base: copy, copyto!, axes export broadcast, broadcast!, BroadcastStyle, broadcast_axes, broadcastable, dotview, @__dot__, BroadcastFunction @@ -34,6 +34,9 @@ that you may be able to leverage; see the """ abstract type BroadcastStyle end +struct Unknown <: BroadcastStyle end +BroadcastStyle(::Type{Union{}}, slurp...) = Unknown() # ambiguity resolution + """ `Broadcast.Style{C}()` defines a [`BroadcastStyle`](@ref) signaling through the type parameter `C`. You can use this as an alternative to creating custom subtypes of `BroadcastStyle`, @@ -45,9 +48,6 @@ struct Style{T} <: BroadcastStyle end BroadcastStyle(::Type{<:Tuple}) = Style{Tuple}() -struct Unknown <: BroadcastStyle end -BroadcastStyle(::Type{Union{}}) = Unknown() # ambiguity resolution - """ `Broadcast.AbstractArrayStyle{N} <: BroadcastStyle` is the abstract supertype for any style associated with an `AbstractArray` type. @@ -137,7 +137,7 @@ BroadcastStyle(a::AbstractArrayStyle, ::Style{Tuple}) = a BroadcastStyle(::A, ::A) where A<:ArrayStyle = A() BroadcastStyle(::ArrayStyle, ::ArrayStyle) = Unknown() BroadcastStyle(::A, ::A) where A<:AbstractArrayStyle = A() -Base.@pure function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N} +function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N} if Base.typename(A) === Base.typename(B) return A(Val(max(M, N))) end @@ -167,16 +167,28 @@ BroadcastStyle(a::AbstractArrayStyle{M}, ::DefaultArrayStyle{N}) where {M,N} = # copyto!(dest::AbstractArray, bc::Broadcasted{MyStyle}) struct Broadcasted{Style<:Union{Nothing,BroadcastStyle}, Axes, F, Args<:Tuple} <: Base.AbstractBroadcasted + style::Style f::F args::Args axes::Axes # the axes of the resulting object (may be bigger than implied by `args` if this is nested inside a larger `Broadcasted`) -end -Broadcasted(f::F, args::Args, axes=nothing) where {F, Args<:Tuple} = - Broadcasted{typeof(combine_styles(args...))}(f, args, axes) -function Broadcasted{Style}(f::F, args::Args, axes=nothing) where {Style, F, Args<:Tuple} - # using Core.Typeof rather than F preserves inferrability when f is a type - Broadcasted{Style, typeof(axes), Core.Typeof(f), Args}(f, args, axes) + Broadcasted(style::Union{Nothing,BroadcastStyle}, f::Tuple, args::Tuple) = error() # disambiguation: tuple is not callable + function Broadcasted(style::Union{Nothing,BroadcastStyle}, f::F, args::Tuple, axes=nothing) where {F} + # using Core.Typeof rather than F preserves inferrability when f is a type + return new{typeof(style), typeof(axes), Core.Typeof(f), typeof(args)}(style, f, args, axes) + end + + function Broadcasted(f::F, args::Tuple, axes=nothing) where {F} + Broadcasted(combine_styles(args...)::BroadcastStyle, f, args, axes) + end + + function Broadcasted{Style}(f::F, args, axes=nothing) where {Style, F} + return new{Style, typeof(axes), Core.Typeof(f), typeof(args)}(Style()::Style, f, args, axes) + end + + function Broadcasted{Style,Axes,F,Args}(f, args, axes) where {Style,Axes,F,Args} + return new{Style, Axes, F, Args}(Style()::Style, f, args, axes) + end end struct AndAnd end @@ -194,16 +206,16 @@ function broadcasted(::OrOr, a, bc::Broadcasted) broadcasted((a, args...) -> a || bcf.f(args...), a, bcf.args...) end -Base.convert(::Type{Broadcasted{NewStyle}}, bc::Broadcasted{Style,Axes,F,Args}) where {NewStyle,Style,Axes,F,Args} = - Broadcasted{NewStyle,Axes,F,Args}(bc.f, bc.args, bc.axes) +Base.convert(::Type{Broadcasted{NewStyle}}, bc::Broadcasted{<:Any,Axes,F,Args}) where {NewStyle,Axes,F,Args} = + Broadcasted{NewStyle,Axes,F,Args}(bc.f, bc.args, bc.axes)::Broadcasted{NewStyle,Axes,F,Args} function Base.show(io::IO, bc::Broadcasted{Style}) where {Style} print(io, Broadcasted) # Only show the style parameter if we have a set of axes — representing an instantiated # "outermost" Broadcasted. The styles of nested Broadcasteds represent an intermediate # computation that is not relevant for dispatch, confusing, and just extra line noise. - bc.axes isa Tuple && print(io, '{', Style, '}') - print(io, '(', bc.f, ", ", bc.args, ')') + bc.axes isa Tuple && print(io, "{", Style, "}") + print(io, "(", bc.f, ", ", bc.args, ")") nothing end @@ -231,7 +243,7 @@ BroadcastStyle(::Type{<:Broadcasted{Style}}) where {Style} = Style() BroadcastStyle(::Type{<:Broadcasted{S}}) where {S<:Union{Nothing,Unknown}} = throw(ArgumentError("Broadcasted{Unknown} wrappers do not have a style assigned")) -argtype(::Type{Broadcasted{Style,Axes,F,Args}}) where {Style,Axes,F,Args} = Args +argtype(::Type{BC}) where {BC<:Broadcasted} = fieldtype(BC, :args) argtype(bc::Broadcasted) = argtype(typeof(bc)) @inline Base.eachindex(bc::Broadcasted) = _eachindex(axes(bc)) @@ -244,7 +256,7 @@ Base.IndexStyle(::Type{<:Broadcasted{<:Any}}) = IndexCartesian() Base.LinearIndices(bc::Broadcasted{<:Any,<:Tuple{Any}}) = LinearIndices(axes(bc))::LinearIndices{1} -Base.ndims(::Broadcasted{<:Any,<:NTuple{N,Any}}) where {N} = N +Base.ndims(bc::Broadcasted) = ndims(typeof(bc)) Base.ndims(::Type{<:Broadcasted{<:Any,<:NTuple{N,Any}}}) where {N} = N Base.size(bc::Broadcasted) = map(length, axes(bc)) @@ -261,7 +273,20 @@ Base.@propagate_inbounds function Base.iterate(bc::Broadcasted, s) return (bc[i], (s[1], newstate)) end -Base.IteratorSize(::Type{<:Broadcasted{<:Any,<:NTuple{N,Base.OneTo}}}) where {N} = Base.HasShape{N}() +Base.IteratorSize(::Type{T}) where {T<:Broadcasted} = Base.HasShape{ndims(T)}() +Base.ndims(BC::Type{<:Broadcasted{<:Any,Nothing}}) = _maxndims(fieldtype(BC, :args)) +Base.ndims(::Type{<:Broadcasted{<:AbstractArrayStyle{N},Nothing}}) where {N<:Integer} = N + +_maxndims(T::Type{<:Tuple}) = reduce(max, (ntuple(n -> _ndims(fieldtype(T, n)), Base._counttuple(T)))) +_maxndims(::Type{<:Tuple{T}}) where {T} = ndims(T) +_maxndims(::Type{<:Tuple{T}}) where {T<:Tuple} = _ndims(T) +function _maxndims(::Type{<:Tuple{T, S}}) where {T, S} + return T<:Tuple || S<:Tuple ? max(_ndims(T), _ndims(S)) : max(ndims(T), ndims(S)) +end + +_ndims(x) = ndims(x) +_ndims(::Type{<:Tuple}) = 1 + Base.IteratorEltype(::Type{<:Broadcasted}) = Base.EltypeUnknown() ## Instantiation fills in the "missing" fields in Broadcasted. @@ -276,14 +301,14 @@ Custom [`BroadcastStyle`](@ref)s may override this default in cases where it is to compute and verify the resulting `axes` on-demand, leaving the `axis` field of the `Broadcasted` object empty (populated with [`nothing`](@ref)). """ -@inline function instantiate(bc::Broadcasted{Style}) where {Style} +@inline function instantiate(bc::Broadcasted) if bc.axes isa Nothing # Not done via dispatch to make it easier to extend instantiate(::Broadcasted{Style}) axes = combine_axes(bc.args...) else axes = bc.axes check_broadcast_axes(axes, bc.args...) end - return Broadcasted{Style}(bc.f, bc.args, axes) + return Broadcasted(bc.style, bc.f, bc.args, axes) end instantiate(bc::Broadcasted{<:AbstractArrayStyle{0}}) = bc # Tuples don't need axes, but when they have axes (for .= assignment), we need to check them (#33020) @@ -312,7 +337,7 @@ becomes This is an optional operation that may make custom implementation of broadcasting easier in some cases. """ -function flatten(bc::Broadcasted{Style}) where {Style} +function flatten(bc::Broadcasted) isflat(bc) && return bc # concatenate the nested arguments into {a, b, c, d} args = cat_nested(bc) @@ -328,7 +353,7 @@ function flatten(bc::Broadcasted{Style}) where {Style} newf = @inline function(args::Vararg{Any,N}) where N f(makeargs(args...)...) end - return Broadcasted{Style}(newf, args, bc.axes) + return Broadcasted(bc.style, newf, args, bc.axes) end end @@ -499,6 +524,20 @@ julia> Broadcast.combine_axes(1, 1, 1) @inline combine_axes(A, B) = broadcast_shape(axes(A), axes(B)) combine_axes(A) = axes(A) +""" + broadcast_shape(As...) -> Tuple + +Determine the result axes for broadcasting across all axes (size Tuples) in `As`. + +```jldoctest +julia> Broadcast.broadcast_shape((1,2), (2,1)) +(2, 2) + +julia> Broadcast.broadcast_shape((1,), (1,5), (4,5,3)) +(4, 5, 3) +``` +""" +function broadcast_shape end # shape (i.e., tuple-of-indices) inputs broadcast_shape(shape::Tuple) = shape broadcast_shape(shape::Tuple, shape1::Tuple, shapes::Tuple...) = broadcast_shape(_bcs(shape, shape1), shapes...) @@ -697,7 +736,7 @@ julia> Broadcast.broadcastable("hello") # Strings break convention of matching i Base.RefValue{String}("hello") ``` """ -broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair,IO}) = Ref(x) +broadcastable(x::Union{Symbol,AbstractString,Function,UndefInitializer,Nothing,RoundingMode,Missing,Val,Ptr,AbstractPattern,Pair,IO,CartesianIndex}) = Ref(x) broadcastable(::Type{T}) where {T} = Ref{Type{T}}(T) broadcastable(x::Union{AbstractArray,Number,AbstractChar,Ref,Tuple,Broadcasted}) = x # Default to collecting iterables — which will error for non-iterables @@ -705,17 +744,21 @@ broadcastable(x) = collect(x) broadcastable(::Union{AbstractDict, NamedTuple}) = throw(ArgumentError("broadcasting over dictionaries and `NamedTuple`s is reserved")) ## Computation of inferred result type, for empty and concretely inferred cases only -_broadcast_getindex_eltype(bc::Broadcasted) = Base._return_type(bc.f, eltypes(bc.args)) +_broadcast_getindex_eltype(bc::Broadcasted) = combine_eltypes(bc.f, bc.args) _broadcast_getindex_eltype(A) = eltype(A) # Tuple, Array, etc. eltypes(::Tuple{}) = Tuple{} -eltypes(t::Tuple{Any}) = Tuple{_broadcast_getindex_eltype(t[1])} -eltypes(t::Tuple{Any,Any}) = Tuple{_broadcast_getindex_eltype(t[1]), _broadcast_getindex_eltype(t[2])} -eltypes(t::Tuple) = Tuple{_broadcast_getindex_eltype(t[1]), eltypes(tail(t)).types...} +eltypes(t::Tuple{Any}) = Iterators.TupleOrBottom(_broadcast_getindex_eltype(t[1])) +eltypes(t::Tuple{Any,Any}) = Iterators.TupleOrBottom(_broadcast_getindex_eltype(t[1]), _broadcast_getindex_eltype(t[2])) +# eltypes(t::Tuple) = (TT = eltypes(tail(t)); TT === Union{} ? Union{} : Iterators.TupleOrBottom(_broadcast_getindex_eltype(t[1]), TT.parameters...)) +eltypes(t::Tuple) = Iterators.TupleOrBottom(ntuple(i -> _broadcast_getindex_eltype(t[i]), Val(length(t)))...) # Inferred eltype of result of broadcast(f, args...) -combine_eltypes(f, args::Tuple) = - promote_typejoin_union(Base._return_type(f, eltypes(args))) +function combine_eltypes(f, args::Tuple) + argT = eltypes(args) + argT === Union{} && return Union{} + return promote_typejoin_union(Base._return_type(f, argT)) +end ## Broadcasting core @@ -864,11 +907,11 @@ materialize(x) = x return materialize!(dest, instantiate(Broadcasted(identity, (x,), axes(dest)))) end -@inline function materialize!(dest, bc::Broadcasted{Style}) where {Style} +@inline function materialize!(dest, bc::Broadcasted{<:Any}) return materialize!(combine_styles(dest, bc), dest, bc) end -@inline function materialize!(::BroadcastStyle, dest, bc::Broadcasted{Style}) where {Style} - return copyto!(dest, instantiate(Broadcasted{Style}(bc.f, bc.args, axes(dest)))) +@inline function materialize!(::BroadcastStyle, dest, bc::Broadcasted{<:Any}) + return copyto!(dest, instantiate(Broadcasted(bc.style, bc.f, bc.args, axes(dest)))) end ## general `copy` methods @@ -878,7 +921,7 @@ copy(bc::Broadcasted{<:Union{Nothing,Unknown}}) = const NonleafHandlingStyles = Union{DefaultArrayStyle,ArrayConflict} -@inline function copy(bc::Broadcasted{Style}) where {Style} +@inline function copy(bc::Broadcasted) ElType = combine_eltypes(bc.f, bc.args) if Base.isconcretetype(ElType) # We can trust it and defer to the simpler `copyto!` @@ -937,7 +980,7 @@ broadcast_unalias(::Nothing, src) = src # Preprocessing a `Broadcasted` does two things: # * unaliases any arguments from `dest` # * "extrudes" the arguments where it is advantageous to pre-compute the broadcasted indices -@inline preprocess(dest, bc::Broadcasted{Style}) where {Style} = Broadcasted{Style}(bc.f, preprocess_args(dest, bc.args), bc.axes) +@inline preprocess(dest, bc::Broadcasted) = Broadcasted(bc.style, bc.f, preprocess_args(dest, bc.args), bc.axes) preprocess(dest, x) = extrude(broadcast_unalias(dest, x)) @inline preprocess_args(dest, args::Tuple) = (preprocess(dest, args[1]), preprocess_args(dest, tail(args))...) @@ -973,14 +1016,14 @@ end destc = dest.chunks cind = 1 bc′ = preprocess(dest, bc) - for P in Iterators.partition(eachindex(bc′), bitcache_size) + @inbounds for P in Iterators.partition(eachindex(bc′), bitcache_size) ind = 1 @simd for I in P - @inbounds tmp[ind] = bc′[I] + tmp[ind] = bc′[I] ind += 1 end @simd for i in ind:bitcache_size - @inbounds tmp[i] = false + tmp[i] = false end dumpbitcache(destc, cind, tmp) cind += bitcache_chunks @@ -1007,11 +1050,11 @@ ischunkedbroadcast(R, args::Tuple{<:BroadcastedChunkableOp,Vararg{Any}}) = ischu ischunkedbroadcast(R, args::Tuple{}) = true # Convert compatible functions to chunkable ones. They must also be green-lighted as ChunkableOps -liftfuncs(bc::Broadcasted{Style}) where {Style} = Broadcasted{Style}(bc.f, map(liftfuncs, bc.args), bc.axes) -liftfuncs(bc::Broadcasted{Style,<:Any,typeof(sign)}) where {Style} = Broadcasted{Style}(identity, map(liftfuncs, bc.args), bc.axes) -liftfuncs(bc::Broadcasted{Style,<:Any,typeof(!)}) where {Style} = Broadcasted{Style}(~, map(liftfuncs, bc.args), bc.axes) -liftfuncs(bc::Broadcasted{Style,<:Any,typeof(*)}) where {Style} = Broadcasted{Style}(&, map(liftfuncs, bc.args), bc.axes) -liftfuncs(bc::Broadcasted{Style,<:Any,typeof(==)}) where {Style} = Broadcasted{Style}((~)∘(xor), map(liftfuncs, bc.args), bc.axes) +liftfuncs(bc::Broadcasted{<:Any,<:Any,<:Any}) = Broadcasted(bc.style, bc.f, map(liftfuncs, bc.args), bc.axes) +liftfuncs(bc::Broadcasted{<:Any,<:Any,typeof(sign)}) = Broadcasted(bc.style, identity, map(liftfuncs, bc.args), bc.axes) +liftfuncs(bc::Broadcasted{<:Any,<:Any,typeof(!)}) = Broadcasted(bc.style, ~, map(liftfuncs, bc.args), bc.axes) +liftfuncs(bc::Broadcasted{<:Any,<:Any,typeof(*)}) = Broadcasted(bc.style, &, map(liftfuncs, bc.args), bc.axes) +liftfuncs(bc::Broadcasted{<:Any,<:Any,typeof(==)}) = Broadcasted(bc.style, (~)∘(xor), map(liftfuncs, bc.args), bc.axes) liftfuncs(x) = x liftchunks(::Tuple{}) = () @@ -1053,7 +1096,7 @@ function copyto_nonleaf!(dest, bc::Broadcasted, iter, state, count) y === nothing && break I, state = y @inbounds val = bc[I] - if val isa T || typeof(val) === T + if val isa T @inbounds dest[I] = val else # This element type doesn't fit in dest. Allocate a new dest with wider eltype, @@ -1170,9 +1213,9 @@ end end Base.@propagate_inbounds dotview(B::BitArray, i::BitArray) = BitMaskedBitArray(B, i) Base.show(io::IO, B::BitMaskedBitArray) = foreach(arg->show(io, arg), (typeof(B), (B.parent, B.mask))) -# Override materialize! to prevent the BitMaskedBitArray from escaping to an overrideable method +# Override materialize! to prevent the BitMaskedBitArray from escaping to an overridable method @inline materialize!(B::BitMaskedBitArray, bc::Broadcasted{<:Any,<:Any,typeof(identity),Tuple{Bool}}) = fill!(B, bc.args[1]) -@inline materialize!(B::BitMaskedBitArray, bc::Broadcasted{<:Any}) = materialize!(SubArray(B.parent, to_indices(B.parent, (B.mask,))), bc) +@inline materialize!(B::BitMaskedBitArray, bc::Broadcasted{<:Any}) = materialize!(@inbounds(view(B.parent, B.mask)), bc) function Base.fill!(B::BitMaskedBitArray, b::Bool) Bc = B.parent.chunks Ic = B.mask.chunks @@ -1284,7 +1327,7 @@ end return broadcasted((args...) -> f(args...; kwargs...), args...) end end -@inline function broadcasted(f, args...) +@inline function broadcasted(f::F, args...) where {F} args′ = map(broadcastable, args) broadcasted(combine_styles(args′...), f, args′...) end @@ -1292,18 +1335,18 @@ end # the totally generic varargs broadcasted(f, args...) method above loses Type{T}s in # mapping broadcastable across the args. These additional methods with explicit # arguments ensure we preserve Type{T}s in the first or second argument position. -@inline function broadcasted(f, arg1, args...) +@inline function broadcasted(f::F, arg1, args...) where {F} arg1′ = broadcastable(arg1) args′ = map(broadcastable, args) broadcasted(combine_styles(arg1′, args′...), f, arg1′, args′...) end -@inline function broadcasted(f, arg1, arg2, args...) +@inline function broadcasted(f::F, arg1, arg2, args...) where {F} arg1′ = broadcastable(arg1) arg2′ = broadcastable(arg2) args′ = map(broadcastable, args) broadcasted(combine_styles(arg1′, arg2′, args′...), f, arg1′, arg2′, args′...) end -@inline broadcasted(::S, f, args...) where S<:BroadcastStyle = Broadcasted{S}(f, args) +@inline broadcasted(style::BroadcastStyle, f::F, args...) where {F} = Broadcasted(style, f, args) """ BroadcastFunction{F} <: Function diff --git a/base/c.jl b/base/c.jl index 3606d0fa0a9bc..d94447650b9fb 100644 --- a/base/c.jl +++ b/base/c.jl @@ -129,7 +129,7 @@ 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 interopability with C, see the +about string interoperability with C, see the [manual](@ref man-bits-types). """ @@ -142,7 +142,7 @@ 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 interopability with C, see the +about string interoperability with C, see the [manual](@ref man-bits-types). """ Cstring @@ -565,9 +565,9 @@ end """ ccall_macro_parse(expression) -`ccall_macro_parse` is an implementation detail of `@ccall +`ccall_macro_parse` is an implementation detail of `@ccall`. -it takes an expression like `:(printf("%d"::Cstring, value::Cuint)::Cvoid)` +It takes an expression like `:(printf("%d"::Cstring, value::Cuint)::Cvoid)` returns: a tuple of `(function_name, return_type, arg_types, args)` The above input outputs this: @@ -734,6 +734,6 @@ macro ccall(expr) return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) end -macro ccall_effects(effects, expr) +macro ccall_effects(effects::UInt8, expr) return ccall_macro_lower((:ccall, effects), ccall_macro_parse(expr)...) end diff --git a/base/channels.jl b/base/channels.jl index da7b1d24583ca..1b5b427f92671 100644 --- a/base/channels.jl +++ b/base/channels.jl @@ -183,7 +183,8 @@ Close a channel. An exception (optionally given by `excp`), is thrown by: * [`put!`](@ref) on a closed channel. * [`take!`](@ref) and [`fetch`](@ref) on an empty, closed channel. """ -function close(c::Channel, excp::Exception=closed_exception()) +close(c::Channel) = close(c, closed_exception()) # nospecialize on default arg seems to confuse makedocs +function close(c::Channel, @nospecialize(excp::Exception)) lock(c) try c.excp = excp @@ -252,6 +253,7 @@ Stacktrace: """ function bind(c::Channel, task::Task) T = Task(() -> close_chnl_on_taskdone(task, c)) + T.sticky = false _wait2(task, T) return c end @@ -380,8 +382,26 @@ end """ fetch(c::Channel) -Wait for and get the first available item from the channel. Does not -remove the item. `fetch` is unsupported on an unbuffered (0-size) channel. +Waits for and returns (without removing) the first available item from the `Channel`. +Note: `fetch` is unsupported on an unbuffered (0-size) `Channel`. + +# Examples + +Buffered channel: +```jldoctest +julia> c = Channel(3) do ch + foreach(i -> put!(ch, i), 1:3) + end; + +julia> fetch(c) +1 + +julia> collect(c) # item is not removed +3-element Vector{Any}: + 1 + 2 + 3 +``` """ fetch(c::Channel) = isbuffered(c) ? fetch_buffered(c) : fetch_unbuffered(c) function fetch_buffered(c::Channel) @@ -402,10 +422,32 @@ fetch_unbuffered(c::Channel) = throw(ErrorException("`fetch` is not supported on """ take!(c::Channel) -Remove and return a value from a [`Channel`](@ref). Blocks until data is available. +Removes and returns a value from a [`Channel`](@ref) in order. Blocks until data is available. +For unbuffered channels, blocks until a [`put!`](@ref) is performed by a different task. -For unbuffered channels, blocks until a [`put!`](@ref) is performed by a different -task. +# Examples + +Buffered channel: +```jldoctest +julia> c = Channel(1); + +julia> put!(c, 1); + +julia> take!(c) +1 +``` + +Unbuffered channel: +```jldoctest +julia> c = Channel(0); + +julia> task = Task(() -> put!(c, 1)); + +julia> schedule(task); + +julia> take!(c) +1 +``` """ take!(c::Channel) = isbuffered(c) ? take_buffered(c) : take_unbuffered(c) function take_buffered(c::Channel) @@ -439,11 +481,41 @@ end """ isready(c::Channel) -Determine whether a [`Channel`](@ref) has a value stored to it. Returns -immediately, does not block. +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). + +# Examples + +Buffered channel: +```jldoctest +julia> c = Channel(1); + +julia> isready(c) +false + +julia> put!(c, 1); + +julia> isready(c) +true +``` + +Unbuffered channel: +```jldoctest +julia> c = Channel(); + +julia> isready(c) # no tasks waiting to put! +false + +julia> task = Task(() -> put!(c, 1)); + +julia> schedule(task); # schedule a put! task + +julia> isready(c) +true +``` -For unbuffered channels returns `true` if there are tasks waiting -on a [`put!`](@ref). """ isready(c::Channel) = n_avail(c) > 0 isempty(c::Channel) = n_avail(c) == 0 @@ -457,6 +529,30 @@ lock(f, c::Channel) = lock(f, c.cond_take) unlock(c::Channel) = unlock(c.cond_take) trylock(c::Channel) = trylock(c.cond_take) +""" + wait(c::Channel) + +Blocks until the `Channel` [`isready`](@ref). + +```jldoctest +julia> c = Channel(1); + +julia> isready(c) +false + +julia> task = Task(() -> wait(c)); + +julia> schedule(task); + +julia> istaskdone(task) # task is blocked because channel is not ready +false + +julia> put!(c, 1); + +julia> istaskdone(task) # task is now unblocked +true +``` +""" function wait(c::Channel) isready(c) && return lock(c) @@ -493,14 +589,18 @@ function show(io::IO, ::MIME"text/plain", c::Channel) end function iterate(c::Channel, state=nothing) - try - return (take!(c), nothing) - catch e - if isa(e, InvalidStateException) && e.state === :closed - return nothing - else - rethrow() + if isopen(c) || isready(c) + try + return (take!(c), nothing) + catch e + if isa(e, InvalidStateException) && e.state === :closed + return nothing + else + rethrow() + end end + else + return nothing end end diff --git a/base/char.jl b/base/char.jl index c8b1c28166bbf..08d661c41de56 100644 --- a/base/char.jl +++ b/base/char.jl @@ -181,9 +181,9 @@ end end convert(::Type{AbstractChar}, x::Number) = Char(x) # default to Char -convert(::Type{T}, x::Number) where {T<:AbstractChar} = T(x) -convert(::Type{T}, x::AbstractChar) where {T<:Number} = T(x) -convert(::Type{T}, c::AbstractChar) where {T<:AbstractChar} = T(c) +convert(::Type{T}, x::Number) where {T<:AbstractChar} = T(x)::T +convert(::Type{T}, x::AbstractChar) where {T<:Number} = T(x)::T +convert(::Type{T}, c::AbstractChar) where {T<:AbstractChar} = T(c)::T convert(::Type{T}, c::T) where {T<:AbstractChar} = c rem(x::AbstractChar, ::Type{T}) where {T<:Number} = rem(codepoint(x), T) @@ -318,7 +318,7 @@ end function show(io::IO, ::MIME"text/plain", c::T) where {T<:AbstractChar} show(io, c) - get(io, :compact, false) && return + get(io, :compact, false)::Bool && return if !ismalformed(c) print(io, ": ") if isoverlong(c) diff --git a/base/checked.jl b/base/checked.jl index ad92a44e1e5bc..d5b4112397e84 100644 --- a/base/checked.jl +++ b/base/checked.jl @@ -2,6 +2,14 @@ # Support for checked integer arithmetic +""" + Checked + +The Checked module provides arithmetic functions for the built-in signed and unsigned +Integer types which throw an error when an overflow occurs. They are named like `checked_sub`, +`checked_div`, etc. In addition, `add_with_overflow`, `sub_with_overflow`, `mul_with_overflow` +return both the unchecked results and a boolean value denoting the presence of an overflow. +""" module Checked export checked_neg, checked_abs, checked_add, checked_sub, checked_mul, @@ -34,12 +42,12 @@ const UnsignedInt = Union{UInt8,UInt16,UInt32,UInt64,UInt128} # LLVM has several code generation bugs for checked integer arithmetic (see e.g. # #4905). We thus distinguish between operations that can be implemented via -# intrinsics, and operations for which we have to provide work-arounds. +# intrinsics, and operations for which we have to provide workarounds. # Note: As far as this code has been tested, most checked_* functions are # working fine in LLVM. (Note that division is still handled via `base/int.jl`, # which always checks for overflow, and which provides its own sets of -# work-arounds for LLVM codegen bugs.) However, the comments in `base/int.jl` +# workarounds for LLVM codegen bugs.) However, the comments in `base/int.jl` # and in issue #4905 are more pessimistic. For the time being, we thus retain # the ability to handle codegen bugs in LLVM, until the code here has been # tested on more systems and architectures. It also seems that things depend on @@ -115,9 +123,10 @@ function checked_abs end function checked_abs(x::SignedInt) r = ifelse(x<0, -x, x) - r<0 && throw(OverflowError(string("checked arithmetic: cannot compute |x| for x = ", x, "::", typeof(x)))) - r - end + r<0 || return r + msg = LazyString("checked arithmetic: cannot compute |x| for x = ", x, "::", typeof(x)) + throw(OverflowError(msg)) +end checked_abs(x::UnsignedInt) = x checked_abs(x::Bool) = x @@ -151,7 +160,7 @@ end throw_overflowerr_binaryop(op, x, y) = (@noinline; - throw(OverflowError(Base.invokelatest(string, x, " ", op, " ", y, " overflowed for type ", typeof(x))))) + throw(OverflowError(LazyString(x, " ", op, " ", y, " overflowed for type ", typeof(x))))) """ Base.checked_add(x, y) diff --git a/base/client.jl b/base/client.jl index 124bfd281c6a1..dd529dad5281e 100644 --- a/base/client.jl +++ b/base/client.jl @@ -66,7 +66,15 @@ function repl_cmd(cmd, out) end cmd = `$shell -c $shell_escape_cmd` end - run(ignorestatus(cmd)) + try + run(ignorestatus(cmd)) + catch + # Windows doesn't shell out right now (complex issue), so Julia tries to run the program itself + # Julia throws an exception if it can't find the program, but the stack trace isn't useful + lasterr = current_exceptions() + lasterr = ExceptionStack([(exception = e[1], backtrace = [] ) for e in lasterr]) + invokelatest(display_error, lasterr) + end end nothing end @@ -124,14 +132,14 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) end if lasterr !== nothing lasterr = scrub_repl_backtrace(lasterr) - istrivialerror(lasterr) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) + istrivialerror(lasterr) || setglobal!(Base.MainInclude, :err, lasterr) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing else ast = Meta.lower(Main, ast) value = Core.eval(Main, ast) - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, value) + setglobal!(Base.MainInclude, :ans, value) if !(value === nothing) && show_value if have_color print(answer_color()) @@ -151,7 +159,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) end errcount += 1 lasterr = scrub_repl_backtrace(current_exceptions()) - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) + setglobal!(Base.MainInclude, :err, lasterr) if errcount > 2 @error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount break @@ -207,10 +215,6 @@ function incomplete_tag(ex::Expr) end function exec_options(opts) - if !isempty(ARGS) - idxs = findall(x -> x == "--", ARGS) - length(idxs) > 0 && deleteat!(ARGS, idxs[1]) - end quiet = (opts.quiet != 0) startup = (opts.startupfile != 2) history_file = (opts.historyfile != 0) @@ -374,30 +378,27 @@ function __atreplinit(repl) end _atreplinit(repl) = invokelatest(__atreplinit, repl) -# The REPL stdlib hooks into Base using this Ref -const REPL_MODULE_REF = Ref{Module}() - -function load_InteractiveUtils() +function load_InteractiveUtils(mod::Module=Main) # load interactive-only libraries - if !isdefined(Main, :InteractiveUtils) + if !isdefined(mod, :InteractiveUtils) try let InteractiveUtils = require(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils")) - Core.eval(Main, :(const InteractiveUtils = $InteractiveUtils)) - Core.eval(Main, :(using .InteractiveUtils)) + Core.eval(mod, :(const InteractiveUtils = $InteractiveUtils)) + Core.eval(mod, :(using .InteractiveUtils)) return InteractiveUtils end catch ex - @warn "Failed to import InteractiveUtils into module Main" exception=(ex, catch_backtrace()) + @warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace()) end return nothing end - return getfield(Main, :InteractiveUtils) + return getfield(mod, :InteractiveUtils) end +global active_repl + # run the requested sort of evaluation loop on stdio function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool) - global active_repl - load_InteractiveUtils() if interactive && isassigned(REPL_MODULE_REF) @@ -406,17 +407,18 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_fil term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr) banner && Base.banner(term) if term.term_type == "dumb" - active_repl = REPL.BasicREPL(term) + repl = REPL.BasicREPL(term) quiet || @warn "Terminal not fully functional" else - active_repl = REPL.LineEditREPL(term, get(stdout, :color, false), true) - active_repl.history_file = history_file + repl = REPL.LineEditREPL(term, get(stdout, :color, false), true) + repl.history_file = history_file end + global active_repl = repl # Make sure any displays pushed in .julia/config/startup.jl ends up above the # REPLDisplay - pushdisplay(REPL.REPLDisplay(active_repl)) - _atreplinit(active_repl) - REPL.run_repl(active_repl, backend->(global active_repl_backend = backend)) + pushdisplay(REPL.REPLDisplay(repl)) + _atreplinit(repl) + REPL.run_repl(repl, backend->(global active_repl_backend = backend)) end else # otherwise provide a simple fallback @@ -476,6 +478,25 @@ function include(fname::AbstractString) Base._include(identity, Main, fname) end eval(x) = Core.eval(Main, x) + +""" + ans + +A variable referring to the last computed value, automatically imported to the interactive prompt. +""" +global ans = nothing + +""" + err + +A variable referring to the last thrown errors, automatically imported to the interactive prompt. +The thrown errors are collected in a stack of exceptions. +""" +global err = nothing + +# weakly exposes ans and err variables to Main +export ans, err + end """ @@ -514,10 +535,8 @@ MainInclude.include function _start() empty!(ARGS) append!(ARGS, Core.ARGS) - if ccall(:jl_generating_output, Cint, ()) != 0 && JLOptions().incremental == 0 - # clear old invalid pointers - PCRE.__init__() - end + # clear any postoutput hooks that were saved in the sysimage + empty!(Base.postoutput_hooks) try exec_options(JLOptions()) catch diff --git a/base/cmd.jl b/base/cmd.jl index 5094dea908440..9e274b61b5e9e 100644 --- a/base/cmd.jl +++ b/base/cmd.jl @@ -130,7 +130,7 @@ function show(io::IO, cmd::Cmd) print(io, '`') if print_cpus print(io, ", ") - show(io, collect(Int, cmd.cpus)) + show(io, collect(Int, something(cmd.cpus))) print(io, ")") end print_env && (print(io, ","); show(io, cmd.env)) @@ -230,7 +230,7 @@ function cstr(s) if Base.containsnul(s) throw(ArgumentError("strings containing NUL cannot be passed to spawned processes")) end - return String(s) + return String(s)::String end # convert various env representations into an array of "key=val" strings @@ -262,6 +262,15 @@ setenv(cmd::Cmd, env::Pair{<:AbstractString}...; dir=cmd.dir) = setenv(cmd, env; dir=dir) setenv(cmd::Cmd; dir=cmd.dir) = Cmd(cmd; dir=dir) +# split environment entry string into before and after first `=` (key and value) +function splitenv(e::String) + i = findnext('=', e, 2) + if i === nothing + throw(ArgumentError("malformed environment entry")) + end + e[1:prevind(e, i)], e[nextind(e, i):end] +end + """ addenv(command::Cmd, env...; inherit::Bool = true) @@ -282,7 +291,7 @@ function addenv(cmd::Cmd, env::Dict; inherit::Bool = true) merge!(new_env, ENV) end else - for (k, v) in eachsplit.(cmd.env, "=") + for (k, v) in splitenv.(cmd.env) new_env[string(k)::String] = string(v)::String end end @@ -301,7 +310,7 @@ function addenv(cmd::Cmd, pairs::Pair{<:AbstractString}...; inherit::Bool = true end function addenv(cmd::Cmd, env::Vector{<:AbstractString}; inherit::Bool = true) - return addenv(cmd, Dict(k => v for (k, v) in eachsplit.(env, "=")); inherit) + return addenv(cmd, Dict(k => v for (k, v) in splitenv.(env)); inherit) end """ @@ -453,7 +462,7 @@ function cmd_gen(parsed) (ignorestatus, flags, env, dir) = (cmd.ignorestatus, cmd.flags, cmd.env, cmd.dir) append!(args, cmd.exec) for arg in tail(parsed) - append!(args, arg_gen(arg...)::Vector{String}) + append!(args, Base.invokelatest(arg_gen, arg...)::Vector{String}) end return Cmd(Cmd(args), ignorestatus, flags, env, dir) else diff --git a/base/combinatorics.jl b/base/combinatorics.jl index 2dd69fbce4c42..d09a5b6c0ce83 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -164,8 +164,10 @@ end Permute vector `v` in-place, according to permutation `p`. No checking is done to verify that `p` is a permutation. -To return a new permutation, use `v[p]`. Note that this is generally faster than -`permute!(v,p)` for large vectors. +To return a new permutation, use `v[p]`. This is generally faster than `permute!(v, p)`; +it is even faster to write into a pre-allocated output array with `u .= @view v[p]`. +(Even though `permute!` overwrites `v` in-place, it internally requires some allocation +to keep track of which elements have been moved.) See also [`invpermute!`](@ref). @@ -185,7 +187,7 @@ julia> A 1 ``` """ -permute!(a, p::AbstractVector) = permute!!(a, copymutable(p)) +permute!(v, p::AbstractVector) = (v .= v[p]) function invpermute!!(a, p::AbstractVector{<:Integer}) require_one_based_indexing(a, p) @@ -216,6 +218,10 @@ end Like [`permute!`](@ref), but the inverse of the given permutation is applied. +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.) + # Examples ```jldoctest julia> A = [1, 1, 3, 4]; @@ -232,7 +238,7 @@ julia> A 1 ``` """ -invpermute!(a, p::AbstractVector) = invpermute!!(a, copymutable(p)) +invpermute!(v, p::AbstractVector) = (v[p] = v; v) """ invperm(v) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 6a9837547834b..097eb7a5d098e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1,57 +1,31 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -############# -# constants # -############# - -const _REF_NAME = Ref.body.name - -######### -# logic # -######### - # See if the inference result of the current statement's result value might affect # the final answer for the method (aside from optimization potential and exceptions). # To do that, we need to check both for slot assignment and SSA usage. -call_result_unused(frame::InferenceState) = - isexpr(frame.src.code[frame.currpc], :call) && isempty(frame.ssavalue_uses[frame.currpc]) - -function get_max_methods(mod::Module, interp::AbstractInterpreter) - max_methods = ccall(:jl_get_module_max_methods, Cint, (Any,), mod) % Int - max_methods < 0 ? InferenceParams(interp).MAX_METHODS : max_methods -end - -const empty_bitset = BitSet() - -function should_infer_for_effects(sv::InferenceState) - sv.ipo_effects.terminates === ALWAYS_TRUE && - sv.ipo_effects.effect_free === ALWAYS_TRUE -end +call_result_unused(sv::InferenceState, currpc::Int) = + isexpr(sv.src.code[currpc], :call) && isempty(sv.ssavalue_uses[currpc]) +call_result_unused(si::StmtInfo) = !si.used function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), - arginfo::ArgInfo, @nospecialize(atype), - sv::InferenceState, max_methods::Int = get_max_methods(sv.mod, interp)) - if !should_infer_for_effects(sv) && - sv.params.unoptimize_throw_blocks && - is_stmt_throw_block(get_curr_ssaflag(sv)) - # Disable inference of calls in throw blocks, since we're unlikely to - # need their types. There is one exception however: If up until now, the - # function has not seen any side effects, we would like to make sure there - # aren't any in the throw block either to enable other optimizations. + arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype), + sv::AbsIntState, max_methods::Int) + ⊑ₚ = ⊑(ipo_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. - tristate_merge!(sv, Effects(Effects(), consistent=ALWAYS_TRUE)) - return CallMeta(Any, false) + effects = Effects(; consistent=ALWAYS_TRUE, nonoverlayed=!isoverlayed(method_table(interp))) + return CallMeta(Any, effects, NoCallInfo()) end argtypes = arginfo.argtypes - matches = find_matching_methods(argtypes, atype, method_table(interp, sv), InferenceParams(interp).MAX_UNION_SPLITTING, max_methods) + matches = find_matching_methods(typeinf_lattice(interp), argtypes, atype, method_table(interp), + InferenceParams(interp).max_union_splitting, max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) - tristate_merge!(sv, Effects()) - return CallMeta(Any, false) + return CallMeta(Any, Effects(), NoCallInfo()) end (; valid_worlds, applicable, info) = matches @@ -62,111 +36,107 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), 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{InferenceResult,Nothing,ConstResult}[] + const_results = Union{Nothing,ConstResult}[] multiple_matches = napplicable > 1 - - if f !== nothing && napplicable == 1 && is_method_pure(applicable[1]::MethodMatch) - val = pure_eval_call(f, argtypes) - if val !== nothing - # TODO: add some sort of edge(s) - return CallMeta(val, MethodResultPure(info)) - end + fargs = arginfo.fargs + all_effects = EFFECTS_TOTAL + if !matches.nonoverlayed + # currently we don't have a good way to execute the overlayed method definition, + # so we should give up concrete eval when any of the matched methods is overlayed + f = nothing + all_effects = Effects(all_effects; nonoverlayed=false) end - fargs = arginfo.fargs + 𝕃ₚ = ipo_lattice(interp) for i in 1:napplicable match = applicable[i]::MethodMatch method = match.method sig = match.spec_types - if bail_out_toplevel_call(interp, sig, sv) + if bail_out_toplevel_call(interp, InferenceLoopState(sig, rettype, all_effects), sv) # only infer concrete call sites in top-level expressions add_remark!(interp, sv, "Refusing to infer non-concrete call site in top-level expression") - rettype = Any break end this_rt = Bottom splitunions = false # TODO: this used to trigger a bug in inference recursion detection, and is unmaintained now # sigtuple = unwrap_unionall(sig)::DataType - # splitunions = 1 < unionsplitcost(sigtuple.parameters) * napplicable <= InferenceParams(interp).MAX_UNION_SPLITTING + # splitunions = 1 < unionsplitcost(sigtuple.parameters) * napplicable <= InferenceParams(interp).max_union_splitting if splitunions splitsigs = switchtupleunion(sig) for sig_n in splitsigs - result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, sv) - rt, edge = result.rt, result.edge - if edge !== nothing - push!(edges, edge) - end + result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, si, sv) + (; rt, edge, effects) = result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) - const_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv) - effects = result.edge_effects - if const_result !== nothing - (;rt, effects, const_result) = const_result + const_call_result = abstract_call_method_with_const_args(interp, + result, f, this_arginfo, si, match, sv) + const_result = nothing + if const_call_result !== nothing + if const_call_result.rt ⊑ₚ rt + rt = const_call_result.rt + (; effects, const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") + end end - tristate_merge!(sv, effects) + all_effects = merge_effects(all_effects, effects) push!(const_results, const_result) - if const_result !== nothing - any_const_result = true - end + any_const_result |= const_result !== nothing + edge === nothing || push!(edges, edge) this_rt = tmerge(this_rt, rt) if bail_out_call(interp, this_rt, sv) break end end + this_conditional = ignorelimited(this_rt) + this_rt = widenwrappedconditional(this_rt) else - if infer_compilation_signature(interp) - # Also infer the compilation signature for this method, so it's available - # to the compiler in case it ends up needing it (which is likely). - csig = get_compileable_sig(method, sig, match.sparams) - if csig !== nothing && csig !== sig - # The result of this inference is not directly used, so temporarily empty - # the use set for the current SSA value. - saved_uses = sv.ssavalue_uses[sv.currpc] - sv.ssavalue_uses[sv.currpc] = empty_bitset - abstract_call_method(interp, method, csig, match.sparams, multiple_matches, sv) - sv.ssavalue_uses[sv.currpc] = saved_uses - end - end - - result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, sv) - this_rt, edge = result.rt, result.edge - if edge !== nothing - push!(edges, edge) - end + result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, si, sv) + (; rt, edge, effects) = result + this_conditional = ignorelimited(rt) + this_rt = widenwrappedconditional(rt) # 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_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv) - effects = result.edge_effects - if const_result !== nothing - this_rt = const_result.rt - (; effects, const_result) = const_result + const_call_result = abstract_call_method_with_const_args(interp, + result, f, this_arginfo, si, match, sv) + const_result = nothing + 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 + this_conditional = this_const_conditional + this_rt = this_const_rt + (; effects, const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") + end end - tristate_merge!(sv, effects) + all_effects = merge_effects(all_effects, effects) push!(const_results, const_result) - if const_result !== nothing - any_const_result = true - end + any_const_result |= const_result !== nothing + edge === nothing || push!(edges, edge) end - this_conditional = ignorelimited(this_rt) - this_rt = widenwrappedconditional(this_rt) - @assert !(this_conditional isa Conditional) "invalid lattice element returned from inter-procedural context" + @assert !(this_conditional isa Conditional || this_rt isa MustAlias) "invalid lattice element returned from inter-procedural context" seen += 1 - rettype = tmerge(rettype, this_rt) - if this_conditional !== Bottom && is_lattice_bool(rettype) && fargs !== nothing + rettype = tmerge(𝕃ₚ, rettype, this_rt) + 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.vtype) + 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, rettype, sv) + if bail_out_call(interp, InferenceLoopState(sig, rettype, all_effects), sv) + add_remark!(interp, sv, "Call inference reached maximally imprecise information. Bailing on.") break end end @@ -176,17 +146,36 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), info = ConstCallInfo(info, const_results) end - if seen != napplicable - tristate_merge!(sv, Effects()) + if seen ≠ napplicable + # there is unanalyzed candidate, widen type and effects to the top + rettype = Any + all_effects = Effects() elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) : - (!_all(b->b, matches.fullmatches) || 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. - tristate_merge!(sv, Effects(EFFECTS_TOTAL, nothrow=TRISTATE_UNKNOWN)) + all_effects = Effects(all_effects; nothrow=false) end - rettype = from_interprocedural!(rettype, sv, arginfo, conditionals) + rettype = from_interprocedural!(𝕃ₚ, rettype, sv, arginfo, conditionals) - if call_result_unused(sv) && !(rettype === Bottom) + # Also considering inferring the compilation signature for this method, so + # it is available to the compiler in case it ends up needing it. + if (isa(sv, InferenceState) && infer_compilation_signature(interp) && + (1 == seen == napplicable) && rettype !== Any && rettype !== Bottom && + !is_removable_if_unused(all_effects)) + match = applicable[1]::MethodMatch + method = match.method + sig = match.spec_types + mi = specialize_method(match; preexisting=true) + if mi !== nothing && !const_prop_methodinstance_heuristic(interp, mi, arginfo, sv) + csig = get_compileable_sig(method, sig, match.sparams) + if csig !== nothing && csig !== sig + abstract_call_method(interp, method, csig, match.sparams, multiple_matches, StmtInfo(false), sv) + end + end + end + + if call_result_unused(si) && !(rettype === Bottom) add_remark!(interp, sv, "Call result type was widened because the return value is unused") # We're mainly only here because the optimizer might want this code, # but we ourselves locally don't typically care about it locally @@ -196,14 +185,20 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # and avoid keeping track of a more complex result type. rettype = Any end - add_call_backedges!(interp, rettype, edges, matches, atype, sv) - if !isempty(sv.pclimitations) # remove self, if present - delete!(sv.pclimitations, sv) - for caller in sv.callers_in_cycle - delete!(sv.pclimitations, caller) + add_call_backedges!(interp, rettype, all_effects, edges, matches, atype, sv) + if isa(sv, InferenceState) + # TODO (#48913) implement a proper recursion handling for irinterp: + # This works just because currently the `:terminate` condition guarantees that + # irinterp doesn't fail into unresolved cycles, but it's not a good solution. + # We should revisit this once we have a better story for handling cycles in irinterp. + if !isempty(sv.pclimitations) # remove self, if present + delete!(sv.pclimitations, sv) + for caller in callers_in_cycle(sv) + delete!(sv.pclimitations, caller) + end end end - return CallMeta(rettype, info) + return CallMeta(rettype, all_effects, info) end struct FailedMethodMatch @@ -214,8 +209,9 @@ struct MethodMatches applicable::Vector{Any} info::MethodMatchInfo valid_worlds::WorldRange - mt::Core.MethodTable + mt::MethodTable fullmatch::Bool + nonoverlayed::Bool end any_ambig(info::MethodMatchInfo) = info.results.ambig any_ambig(m::MethodMatches) = any_ambig(m.info) @@ -225,39 +221,44 @@ struct UnionSplitMethodMatches applicable_argtypes::Vector{Vector{Any}} info::UnionSplitInfo valid_worlds::WorldRange - mts::Vector{Core.MethodTable} + mts::Vector{MethodTable} fullmatches::Vector{Bool} + nonoverlayed::Bool end -any_ambig(m::UnionSplitMethodMatches) = _any(any_ambig, m.info.matches) +any_ambig(m::UnionSplitMethodMatches) = any(any_ambig, m.info.matches) -function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), method_table::MethodTableView, - union_split::Int, max_methods::Int) +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) <= union_split - split_argtypes = switchtupleunion(argtypes) + 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 = Core.MethodTable[] + mts = MethodTable[] fullmatches = Bool[] + nonoverlayed = true for i in 1:length(split_argtypes) arg_n = split_argtypes[i]::Vector{Any} sig_n = argtypes_to_type(arg_n) mt = ccall(:jl_method_table_for, Any, (Any,), sig_n) mt === nothing && return FailedMethodMatch("Could not identify method table for call") - mt = mt::Core.MethodTable - matches = findall(sig_n, method_table; limit = max_methods) - if matches === missing + mt = mt::MethodTable + result = findall(sig_n, method_table; limit = max_methods) + if result === nothing return FailedMethodMatch("For one of the union split cases, too many methods matched") end + (; matches, overlayed) = result + nonoverlayed &= !overlayed push!(infos, MethodMatchInfo(matches)) for m in matches push!(applicable, m) push!(applicable_argtypes, arg_n) end valid_worlds = intersect(valid_worlds, matches.valid_worlds) - thisfullmatch = _any(match->(match::MethodMatch).fully_covers, matches) + thisfullmatch = any(match::MethodMatch->match.fully_covers, matches) found = false for (i, mt′) in enumerate(mts) if mt′ === mt @@ -276,30 +277,33 @@ function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), meth UnionSplitInfo(infos), valid_worlds, mts, - fullmatches) + fullmatches, + nonoverlayed) else mt = ccall(:jl_method_table_for, Any, (Any,), atype) if mt === nothing return FailedMethodMatch("Could not identify method table for call") end - mt = mt::Core.MethodTable - matches = findall(atype, method_table; limit = max_methods) - if matches === missing + mt = mt::MethodTable + result = findall(atype, method_table; limit = max_methods) + if result === 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 - fullmatch = _any(match->(match::MethodMatch).fully_covers, matches) + (; matches, overlayed) = result + fullmatch = any(match::MethodMatch->match.fully_covers, matches) return MethodMatches(matches.matches, MethodMatchInfo(matches), matches.valid_worlds, mt, - fullmatch) + fullmatch, + !overlayed) end end """ - from_interprocedural!(rt, sv::InferenceState, arginfo::ArgInfo, maybecondinfo) -> newrt + from_interprocedural!(𝕃ₚ::AbstractLattice, rt, sv::AbsIntState, arginfo::ArgInfo, maybecondinfo) -> newrt Converts inter-procedural return type `rt` into a local lattice element `newrt`, that is appropriate in the context of current local analysis frame `sv`, especially: @@ -318,16 +322,18 @@ In such cases `maybecondinfo` should be either of: When we deal with multiple `MethodMatch`es, it's better to precompute `maybecondinfo` by `tmerge`ing argument signature type of each method call. """ -function from_interprocedural!(@nospecialize(rt), sv::InferenceState, arginfo::ArgInfo, @nospecialize(maybecondinfo)) +function from_interprocedural!(𝕃ₚ::AbstractLattice, @nospecialize(rt), sv::AbsIntState, arginfo::ArgInfo, @nospecialize(maybecondinfo)) rt = collect_limitations!(rt, sv) - if is_lattice_bool(rt) + if isa(rt, InterMustAlias) + rt = from_intermustalias(rt, arginfo) + elseif is_lattice_bool(𝕃ₚ, rt) if maybecondinfo === nothing rt = widenconditional(rt) else - rt = from_interconditional(rt, sv, arginfo, maybecondinfo) + rt = from_interconditional(𝕃ₚ, rt, sv, arginfo, maybecondinfo) end end - @assert !(rt isa InterConditional) "invalid lattice element returned from inter-procedural context" + @assert !(rt isa InterConditional || rt isa InterMustAlias) "invalid lattice element returned from inter-procedural context" return rt end @@ -339,60 +345,104 @@ function collect_limitations!(@nospecialize(typ), sv::InferenceState) return typ end -function from_interconditional(@nospecialize(typ), sv::InferenceState, (; fargs, argtypes)::ArgInfo, @nospecialize(maybecondinfo)) +function from_intermustalias(rt::InterMustAlias, arginfo::ArgInfo) + fargs = arginfo.fargs + if fargs !== nothing && 1 ≤ rt.slot ≤ length(fargs) + arg = fargs[rt.slot] + if isa(arg, SlotNumber) + argtyp = widenslotwrapper(arginfo.argtypes[rt.slot]) + if rt.vartyp ⊑ argtyp + return MustAlias(arg, rt.vartyp, rt.fldidx, rt.fldtyp) + else + # TODO optimize this case? + end + end + end + return widenmustalias(rt) +end + +function from_interconditional(𝕃ₚ::AbstractLattice, + typ, sv::AbsIntState, arginfo::ArgInfo, maybecondinfo) + @nospecialize typ maybecondinfo + has_conditional(𝕃ₚ, sv) || return widenconditional(typ) + (; fargs, argtypes) = arginfo fargs === nothing && return widenconditional(typ) + 𝕃 = widenlattice(𝕃ₚ) slot = 0 - vtype = elsetype = Any + alias = nothing + thentype = elsetype = Any condval = maybe_extract_const_bool(typ) for i in 1:length(fargs) # find the first argument which supports refinement, # and intersect all equivalent arguments with it - arg = ssa_def_slot(fargs[i], sv) - arg isa SlotNumber || continue # can't refine - old = argtypes[i] - old isa Type || continue # unlikely to refine - id = slot_id(arg) + argtyp = argtypes[i] + if alias === nothing + if argtyp isa MustAlias + old = argtyp.fldtyp + id = argtyp.slot + elseif alias === nothing && argtyp isa Type + arg = ssa_def_slot(fargs[i], sv) + arg isa SlotNumber || continue # can't refine + old = argtyp + id = slot_id(arg) + else + continue # unlikely to refine + end + elseif argtyp isa MustAlias && issubalias(argtyp, alias) + old = alias.fldtyp + id = alias.slot + else + continue + end if slot == 0 || id == slot if isa(maybecondinfo, Tuple{Vector{Any},Vector{Any}}) # if we have already computed argument refinement information, apply that now to get the result - new_vtype = maybecondinfo[1][i] + new_thentype = maybecondinfo[1][i] new_elsetype = maybecondinfo[2][i] else # otherwise compute it on the fly cnd = conditional_argtype(typ, maybecondinfo, argtypes, i) - new_vtype = cnd.vtype + new_thentype = cnd.thentype new_elsetype = cnd.elsetype end if condval === false - vtype = Bottom - elseif new_vtype ⊑ vtype - vtype = new_vtype + thentype = Bottom + elseif ⊑(𝕃, new_thentype, thentype) + thentype = new_thentype else - vtype = tmeet(vtype, widenconst(new_vtype)) + thentype = tmeet(𝕃, thentype, widenconst(new_thentype)) end if condval === true elsetype = Bottom - elseif new_elsetype ⊑ elsetype + elseif ⊑(𝕃, new_elsetype, elsetype) elsetype = new_elsetype else - elsetype = tmeet(elsetype, widenconst(new_elsetype)) + elsetype = tmeet(𝕃, elsetype, widenconst(new_elsetype)) end - if (slot > 0 || condval !== false) && vtype ⋤ old + if (slot > 0 || condval !== false) && ⋤(𝕃, thentype, old) slot = id - elseif (slot > 0 || condval !== true) && elsetype ⋤ old + if argtyp isa MustAlias + alias = argtyp + end + elseif (slot > 0 || condval !== true) && ⋤(𝕃, elsetype, old) slot = id - else # reset: no new useful information for this slot - vtype = elsetype = Any - if slot > 0 - slot = 0 + if argtyp isa MustAlias + alias = argtyp end + else # reset: no new useful information for this slot + slot = 0 + alias = nothing + thentype = elsetype = Any end end end - if vtype === Bottom && elsetype === Bottom + if thentype === Bottom && elsetype === Bottom return Bottom # accidentally proved this call to be dead / throw ! elseif slot > 0 - return Conditional(SlotNumber(slot), vtype, elsetype) # record a Conditional improvement to this slot + if alias !== nothing + return form_mustalias_conditional(alias, thentype, elsetype) + end + return Conditional(slot, thentype, elsetype) # record a Conditional improvement to this slot end return widenconditional(typ) end @@ -401,103 +451,77 @@ function conditional_argtype(@nospecialize(rt), @nospecialize(sig), argtypes::Ve if isa(rt, InterConditional) && rt.slot == i return rt else - vtype = elsetype = tmeet(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 && (vtype = Bottom) - return InterConditional(i, vtype, elsetype) + condval === false && (thentype = Bottom) + return InterConditional(i, thentype, elsetype) end end -function add_call_backedges!(interp::AbstractInterpreter, @nospecialize(rettype), edges::Vector{MethodInstance}, - matches::Union{MethodMatches,UnionSplitMethodMatches}, @nospecialize(atype), - sv::InferenceState) - # for `NativeInterpreter`, we don't add backedges when a new method couldn't refine (widen) this type - rettype === Any && return +function add_call_backedges!(interp::AbstractInterpreter, @nospecialize(rettype), all_effects::Effects, + edges::Vector{MethodInstance}, matches::Union{MethodMatches,UnionSplitMethodMatches}, @nospecialize(atype), + sv::AbsIntState) + # don't bother to add backedges when both type and effects information are already + # maximized to the top since a new method couldn't refine or widen them anyway + if rettype === Any + # ignore the `:nonoverlayed` property if `interp` doesn't use overlayed method table + # since it will never be tainted anyway + if !isoverlayed(method_table(interp)) + all_effects = Effects(all_effects; nonoverlayed=false) + end + if (# ignore the `:noinbounds` property if `:consistent`-cy is tainted already + (sv isa InferenceState && sv.ipo_effects.consistent === ALWAYS_FALSE) || + all_effects.consistent === ALWAYS_FALSE || + # or this `:noinbounds` doesn't taint it + !stmt_taints_inbounds_consistency(sv)) + all_effects = Effects(all_effects; noinbounds=false) + end + all_effects === Effects() && return nothing + end for edge in edges - add_backedge!(edge, sv) + add_backedge!(sv, edge) end # also need an edge to the method table in case something gets # added that did not intersect with any existing method if isa(matches, MethodMatches) - matches.fullmatch || add_mt_backedge!(matches.mt, atype, sv) + matches.fullmatch || add_mt_backedge!(sv, matches.mt, atype) else for (thisfullmatch, mt) in zip(matches.fullmatches, matches.mts) - thisfullmatch || add_mt_backedge!(mt, atype, sv) + thisfullmatch || add_mt_backedge!(sv, mt, atype) end end + return nothing end const RECURSION_UNUSED_MSG = "Bounded recursion detected with unused result. Annotated return type may be wider than true result." const RECURSION_MSG = "Bounded recursion detected. Call was widened to force convergence." +const RECURSION_MSG_HARDLIMIT = "Bounded recursion detected under hardlimit. Call was widened to force convergence." -function abstract_call_method(interp::AbstractInterpreter, method::Method, @nospecialize(sig), sparams::SimpleVector, hardlimit::Bool, sv::InferenceState) +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 - topmost = nothing + sigtuple = unwrap_unionall(sig) + sigtuple isa DataType || return MethodCallResult(Any, false, false, nothing, Effects()) + # Limit argument type tuple growth of functions: # look through the parents list to see if there's a call to the same method # and from the same method. # Returns the topmost occurrence of that repeated edge. - edgecycle = false - edgelimited = false - # The `method_for_inference_heuristics` will expand the given method's generator if - # 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). - callee_method2 = method_for_inference_heuristics(method, sig, sparams) # Union{Method, Nothing} - sv_method2 = sv.src.method_for_inference_limit_heuristics # limit only if user token match - sv_method2 isa Method || (sv_method2 = nothing) # Union{Method, Nothing} - - function matches_sv(parent::InferenceState) - parent_method2 = parent.src.method_for_inference_limit_heuristics # limit only if user token match - parent_method2 isa Method || (parent_method2 = nothing) # Union{Method, Nothing} - return parent.linfo.def === sv.linfo.def && sv_method2 === parent_method2 - end - - function edge_matches_sv(frame::InferenceState) - inf_method2 = frame.src.method_for_inference_limit_heuristics # limit only if user token match - inf_method2 isa Method || (inf_method2 = nothing) # Union{Method, Nothing} - if callee_method2 !== inf_method2 - return false - end - if !hardlimit - # if this is a soft limit, - # also inspect the parent of this edge, - # to see if they are the same Method as sv - # in which case we'll need to ensure it is convergent - # otherwise, we don't - - # check in the cycle list first - # all items in here are mutual parents of all others - if !_any(matches_sv, frame.callers_in_cycle) - let parent = frame.parent - parent !== nothing || return false - parent = parent::InferenceState - (parent.cached || parent.parent !== nothing) || return false - matches_sv(parent) || return false - end - end - - # If the method defines a recursion relation, give it a chance - # to tell us that this recursion is actually ok. - if isdefined(method, :recursion_relation) - if Core._apply_pure(method.recursion_relation, Any[method, callee_method2, sig, frame.linfo.specTypes]) - return false - end - end - end - return true - end + edgecycle = edgelimited = false + topmost = nothing - for infstate in InfStackUnwind(sv) - if method === infstate.linfo.def - if infstate.linfo.specTypes == sig + for sv′ in AbsIntStackUnwind(sv) + infmi = frame_instance(sv′) + if method === infmi.def + if infmi.specTypes::Type == sig::Type # avoid widening when detecting self-recursion # TODO: merge call cycle and return right away - if call_result_unused(sv) + if call_result_unused(si) add_remark!(interp, sv, RECURSION_UNUSED_MSG) # since we don't use the result (typically), # we have a self-cycle in the call-graph, but not in the inference graph (typically): @@ -510,42 +534,43 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp break end topmost === nothing || continue - if edge_matches_sv(infstate) - topmost = infstate + if edge_matches_sv(interp, sv′, method, sig, sparams, hardlimit, sv) + topmost = sv′ edgecycle = true end end end + washardlimit = hardlimit if topmost !== nothing - sigtuple = unwrap_unionall(sig)::DataType msig = unwrap_unionall(method.sig)::DataType spec_len = length(msig.parameters) + 1 ls = length(sigtuple.parameters) + mi = frame_instance(sv) - if method === sv.linfo.def + if method === mi.def # Under direct self-recursion, permit much greater use of reducers. # here we assume that complexity(specTypes) :>= complexity(sig) - comparison = sv.linfo.specTypes - l_comparison = length(unwrap_unionall(comparison).parameters)::Int + comparison = mi.specTypes + l_comparison = length((unwrap_unionall(comparison)::DataType).parameters) spec_len = max(spec_len, l_comparison) else comparison = method.sig end if isdefined(method, :recursion_relation) - # We don't recquire the recursion_relation to be transitive, so + # We don't require the recursion_relation to be transitive, so # apply a hard limit hardlimit = true end # see if the type is actually too big (relative to the caller), and limit it if required - newsig = limit_type_size(sig, comparison, hardlimit ? comparison : sv.linfo.specTypes, InferenceParams(interp).TUPLE_COMPLEXITY_LIMIT_DEPTH, spec_len) + newsig = limit_type_size(sig, comparison, hardlimit ? comparison : mi.specTypes, InferenceParams(interp).tuple_complexity_limit_depth, spec_len) if newsig !== sig # continue inference, but note that we've limited parameter complexity # on this call (to ensure convergence), so that we don't cache this result - if call_result_unused(sv) + if call_result_unused(si) add_remark!(interp, sv, RECURSION_UNUSED_MSG) # if we don't (typically) actually care about this result, # don't bother trying to examine some complex abstract signature @@ -554,10 +579,17 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) return MethodCallResult(Any, true, true, nothing, Effects()) end - add_remark!(interp, sv, RECURSION_MSG) - topmost = topmost::InferenceState - parentframe = topmost.parent - poison_callstack(sv, parentframe === nothing ? topmost : parentframe) + add_remark!(interp, sv, washardlimit ? RECURSION_MSG_HARDLIMIT : RECURSION_MSG) + # TODO (#48913) implement a proper recursion handling for irinterp: + # This works just because currently the `:terminate` condition guarantees that + # irinterp doesn't fail into unresolved cycles, but it's not a good solution. + # We should revisit this once we have a better story for handling cycles in irinterp. + if isa(topmost, InferenceState) + parentframe = frame_parent(topmost) + if isa(sv, InferenceState) && isa(parentframe, InferenceState) + poison_callstack!(sv, parentframe === nothing ? topmost : parentframe) + end + end sig = newsig sparams = svec() edgelimited = true @@ -582,7 +614,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp # while !(newsig in seen) # push!(seen, newsig) # lsig = length((unwrap_unionall(sig)::DataType).parameters) - # newsig = limit_type_size(newsig, sig, sv.linfo.specTypes, InferenceParams(interp).TUPLE_COMPLEXITY_LIMIT_DEPTH, lsig) + # newsig = limit_type_size(newsig, sig, sv.linfo.specTypes, InferenceParams(interp).tuple_complexity_limit_depth, lsig) # recomputed = ccall(:jl_type_intersection_with_env, Any, (Any, Any), newsig, method.sig)::SimpleVector # newsig = recomputed[2] # end @@ -590,65 +622,229 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp sparams = recomputed[2]::SimpleVector end - rt, edge, edge_effects = typeinf_edge(interp, method, sig, sparams, sv) + (; rt, edge, effects) = typeinf_edge(interp, method, sig, sparams, sv) + if edge === nothing edgecycle = edgelimited = true end - if edgecycle - # Some sort of recursion was detected. Even if we did not limit types, - # we cannot guarantee that the call will terminate. - edge_effects = tristate_merge(edge_effects, - Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) + + # we look for the termination effect override here as well, since the :terminates effect + # may have been tainted due to recursion at this point even if it's overridden + if is_effect_overridden(sv, :terminates_globally) + # this frame is known to terminate + effects = Effects(effects, terminates=true) + elseif is_effect_overridden(method, :terminates_globally) + # this edge is known to terminate + effects = Effects(effects; terminates=true) + elseif edgecycle + # Some sort of recursion was detected. + if edge !== nothing && !edgelimited && !is_edge_recursed(edge, sv) + # no `MethodInstance` cycles -- don't taint :terminate + else + # we cannot guarantee that the call will terminate + effects = Effects(effects; terminates=false) + end + end + + return MethodCallResult(rt, edgecycle, edgelimited, edge, effects) +end + +function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, + method::Method, @nospecialize(sig), sparams::SimpleVector, + hardlimit::Bool, sv::AbsIntState) + # The `method_for_inference_heuristics` will expand the given method's generator if + # 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) + 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 + inf_method2 isa Method || (inf_method2 = nothing) + if callee_method2 !== inf_method2 + return false + end + if !hardlimit || InferenceParams(interp).ignore_recursion_hardlimit + # if this is a soft limit, + # also inspect the parent of this edge, + # to see if they are the same Method as sv + # in which case we'll need to ensure it is convergent + # otherwise, we don't + + # check in the cycle list first + # all items in here are mutual parents of all others + if !any(p::AbsIntState->matches_sv(p, sv), callers_in_cycle(frame)) + let parent = frame_parent(frame) + parent !== nothing || return false + (is_cached(parent) || frame_parent(parent) !== nothing) || return false + matches_sv(parent, sv) || return false + end + end + + # If the method defines a recursion relation, give it a chance + # to tell us that this recursion is actually ok. + if isdefined(method, :recursion_relation) + if Core._apply_pure(method.recursion_relation, Any[method, callee_method2, sig, frame_instance(frame).specTypes]) + return false + end + end + end + return true +end + +# This function is used for computing alternate limit heuristics +function method_for_inference_heuristics(method::Method, @nospecialize(sig), sparams::SimpleVector, world::UInt) + if isdefined(method, :generator) && !(method.generator isa Core.GeneratedFunctionStub) && may_invoke_generator(method, sig, sparams) + method_instance = specialize_method(method, sig, sparams) + if isa(method_instance, MethodInstance) + cinfo = get_staged(method_instance, world) + if isa(cinfo, CodeInfo) + method2 = cinfo.method_for_inference_limit_heuristics + if method2 isa Method + return method2 + end + end + end + end + return nothing +end + +function matches_sv(parent::AbsIntState, sv::AbsIntState) + sv_method2 = method_for_inference_limit_heuristics(sv) # limit only if user token match + sv_method2 isa Method || (sv_method2 = nothing) + parent_method2 = method_for_inference_limit_heuristics(parent) # limit only if user token match + parent_method2 isa Method || (parent_method2 = nothing) + return frame_instance(parent).def === frame_instance(sv).def && sv_method2 === parent_method2 +end + +function is_edge_recursed(edge::MethodInstance, caller::AbsIntState) + return any(AbsIntStackUnwind(caller)) do sv::AbsIntState + return edge === frame_instance(sv) end - return MethodCallResult(rt, edgecycle, edgelimited, edge, edge_effects) end -# keeps result and context information of abstract method call, will be used by succeeding constant-propagation +function is_method_recursed(method::Method, caller::AbsIntState) + return any(AbsIntStackUnwind(caller)) do sv::AbsIntState + return method === frame_instance(sv).def + end +end + +function is_constprop_edge_recursed(edge::MethodInstance, caller::AbsIntState) + return any(AbsIntStackUnwind(caller)) do sv::AbsIntState + return edge === frame_instance(sv) && is_constproped(sv) + end +end + +function is_constprop_method_recursed(method::Method, caller::AbsIntState) + return any(AbsIntStackUnwind(caller)) do sv::AbsIntState + return method === frame_instance(sv).def && is_constproped(sv) + end +end + +# keeps result and context information of abstract_method_call, which will later be used for +# backedge computation, and concrete evaluation or constant-propagation struct MethodCallResult rt edgecycle::Bool edgelimited::Bool edge::Union{Nothing,MethodInstance} - edge_effects::Effects + effects::Effects function MethodCallResult(@nospecialize(rt), edgecycle::Bool, edgelimited::Bool, edge::Union{Nothing,MethodInstance}, - edge_effects::Effects) - return new(rt, edgecycle, edgelimited, edge, edge_effects) + effects::Effects) + return new(rt, edgecycle, edgelimited, edge, effects) end end -function is_all_const_arg((; argtypes)::ArgInfo) - for a in argtypes - if !isa(a, Const) && !isconstType(a) && !issingletontype(a) +# - true: eligible for concrete evaluation +# - false: eligible for semi-concrete evaluation +# - nothing: not eligible for either of it +function concrete_eval_eligible(interp::AbstractInterpreter, + @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState) + # disable all concrete-evaluation if this function call is tainted by some overlayed + # method since currently there is no direct way to execute overlayed methods + if inbounds_option() === :off + # Disable concrete evaluation in `--check-bounds=no` mode, since we cannot be sure + # that inferred effects are accurate. + return nothing + elseif !result.effects.noinbounds && stmt_taints_inbounds_consistency(sv) + # If the current statement is @inbounds or we propagate inbounds, the call's consistency + # is tainted and not consteval eligible. + add_remark!(interp, sv, "[constprop] Concrete evel disabled for inbounds") + return nothing + end + isoverlayed(method_table(interp)) && !is_nonoverlayed(result.effects) && return nothing + if result.edge !== nothing && is_foldable(result.effects) + if f !== nothing && is_all_const_arg(arginfo, #=start=#2) + return true + else return false end end + return nothing +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 + end return true end -function concrete_eval_const_proven_total_or_error(interp::AbstractInterpreter, - @nospecialize(f), (; argtypes)::ArgInfo, _::InferenceState) - args = Any[ (a = widenconditional(argtypes[i]); - isa(a, Const) ? a.val : - isconstType(a) ? (a::DataType).parameters[1] : - (a::DataType).instance) for i in 2:length(argtypes) ] - try - value = Core._call_in_world_total(get_world_counter(interp), f, args...) - return Const(value) - catch e - return nothing +collect_const_args(arginfo::ArgInfo, start::Int) = collect_const_args(arginfo.argtypes, start) +function collect_const_args(argtypes::Vector{Any}, start::Int) + return Any[ let a = widenslotwrapper(argtypes[i]) + isa(a, Const) ? a.val : + isconstType(a) ? (a::DataType).parameters[1] : + (a::DataType).instance + end for i = start:length(argtypes) ] +end + +struct InvokeCall + types # ::Type + lookupsig # ::Type + InvokeCall(@nospecialize(types), @nospecialize(lookupsig)) = new(types, lookupsig) +end + +function concrete_eval_call(interp::AbstractInterpreter, + @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, si::StmtInfo, + sv::AbsIntState, invokecall::Union{Nothing,InvokeCall}=nothing) + eligible = concrete_eval_eligible(interp, f, result, arginfo, sv) + eligible === nothing && return false + if eligible + args = collect_const_args(arginfo, #=start=#2) + if invokecall !== nothing + # this call should be `invoke`d, rewrite `args` back now + pushfirst!(args, f, invokecall.types) + f = invoke + end + world = get_world_counter(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(Union{}, ConcreteResult(edge, result.effects), result.effects, edge) + end + return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) + else # eligible for semi-concrete evaluation + return true end end -function const_prop_enabled(interp::AbstractInterpreter, sv::InferenceState, match::MethodMatch) +any_conditional(argtypes::Vector{Any}) = any(@nospecialize(x)->isa(x, Conditional), argtypes) +any_conditional(arginfo::ArgInfo) = any_conditional(arginfo.argtypes) + +function const_prop_enabled(interp::AbstractInterpreter, sv::AbsIntState, match::MethodMatch) if !InferenceParams(interp).ipo_constant_propagation add_remark!(interp, sv, "[constprop] Disabled by parameter") return false end - method = match.method - if method.constprop == 0x02 + if is_no_constprop(match.method) add_remark!(interp, sv, "[constprop] Disabled by method parameter") return false end @@ -657,79 +853,179 @@ end struct ConstCallResults rt::Any - const_result::Union{InferenceResult, ConstResult} + const_result::ConstResult effects::Effects + edge::MethodInstance ConstCallResults(@nospecialize(rt), - const_result::Union{InferenceResult, ConstResult}, - effects::Effects) = - new(rt, const_result, effects) + const_result::ConstResult, + effects::Effects, + edge::MethodInstance) = + new(rt, const_result, effects, edge) +end + +# TODO implement MustAlias forwarding + +struct ConditionalArgtypes <: ForwardableArgtypes + arginfo::ArgInfo + sv::InferenceState +end + +""" + matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ConditionalArgtypes) + +The implementation is able to forward `Conditional` of `argtypes`, +as well as the other general extended lattice inforamtion. +""" +function matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::ConditionalArgtypes) + (; arginfo, sv) = argtypes + (; fargs, argtypes) = arginfo + given_argtypes = Vector{Any}(undef, length(argtypes)) + def = linfo.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] + # forward `Conditional` if it conveys a constraint on any other argument + if isa(argtype, Conditional) && fargs !== nothing + cnd = argtype + slotid = find_constrained_arg(cnd, fargs, sv) + if slotid !== nothing + # using union-split signature, we may be able to narrow down `Conditional` + sigt = widenconst(slotid > nargs ? argtypes[slotid] : cache_argtypes[slotid]) + thentype = tmeet(cnd.thentype, sigt) + elsetype = tmeet(cnd.elsetype, sigt) + if thentype === Bottom && elsetype === Bottom + # we accidentally proved this method match is impossible + # TODO bail out here immediately rather than just propagating Bottom ? + given_argtypes[i] = Bottom + else + if condargs === nothing + condargs = Tuple{Int,Int}[] + end + push!(condargs, (slotid, i)) + given_argtypes[i] = Conditional(slotid, thentype, elsetype) + end + continue + end + end + given_argtypes[i] = widenslotwrapper(argtype) + end + if condargs !== nothing + given_argtypes = let condargs=condargs + va_process_argtypes(𝕃, given_argtypes, linfo) 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 + isva_given_argtypes[i] = widenconditional(isva_given_argtypes[i]) + end + end + end + end + else + given_argtypes = va_process_argtypes(𝕃, given_argtypes, linfo) + end + return pick_const_args!(𝕃, cache_argtypes, overridden_by_const, given_argtypes) +end + +# check if there is a cycle and duplicated inference of `mi` +function is_constprop_recursed(result::MethodCallResult, mi::MethodInstance, sv::AbsIntState) + result.edgecycle || return false + if result.edgelimited + return is_constprop_method_recursed(mi.def::Method, sv) + else + # if the type complexity limiting didn't decide to limit the call signature (as + # indicated by `result.edgelimited === false`), we can relax the cycle detection + # by comparing `MethodInstance`s and allow inference to propagate different + # constant elements if the recursion is finite over the lattice + return is_constprop_edge_recursed(mi, sv) + end end -function abstract_call_method_with_const_args(interp::AbstractInterpreter, result::MethodCallResult, - @nospecialize(f), arginfo::ArgInfo, match::MethodMatch, - sv::InferenceState) +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 f !== nothing && result.edge !== nothing && is_total_or_error(result.edge_effects) && is_all_const_arg(arginfo) - rt = concrete_eval_const_proven_total_or_error(interp, f, arginfo, sv) - add_backedge!(result.edge, sv) - if rt === nothing - # The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Union{}, ConstResult(result.edge), result.edge_effects) - end - if is_inlineable_constant(rt.val) || call_result_unused(sv) - # If the constant is not inlineable, still do the const-prop, since the - # code that led to the creation of the Const may be inlineable in the same - # circumstance and may be optimizable. - return ConstCallResults(rt, ConstResult(result.edge, rt.val), EFFECTS_TOTAL) + if is_removable_if_unused(result.effects) + if isa(result.rt, Const) || call_result_unused(si) + add_remark!(interp, sv, "[constprop] No more information to be gained") + return nothing end end - mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv) + res = concrete_eval_call(interp, f, result, arginfo, si, sv, invokecall) + isa(res, ConstCallResults) && return res + mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, si, match, sv) mi === nothing && return nothing - # try constant prop' - inf_cache = get_inference_cache(interp) - inf_result = cache_lookup(mi, arginfo.argtypes, inf_cache) - if inf_result === nothing - # if there might be a cycle, check to make sure we don't end up - # calling ourselves here. - let result = result # prevent capturing - if result.edgecycle && _any(InfStackUnwind(sv)) do infstate - # if the type complexity limiting didn't decide to limit the call signature (`result.edgelimited = false`) - # we can relax the cycle detection by comparing `MethodInstance`s and allow inference to - # propagate different constant elements if the recursion is finite over the lattice - return (result.edgelimited ? match.method === infstate.linfo.def : mi === infstate.linfo) && - any(infstate.result.overridden_by_const) + if is_constprop_recursed(result, mi, sv) + add_remark!(interp, sv, "[constprop] Edge cycle encountered") + return nothing + end + # try semi-concrete evaluation + if res::Bool && !any_conditional(arginfo) + world = frame_world(sv) + mi_cache = WorldView(code_cache(interp), world) + code = get(mi_cache, mi, nothing) + if code !== nothing + irsv = IRInterpretationState(interp, code, mi, arginfo.argtypes, world) + if irsv !== nothing + irsv.parent = sv + rt, nothrow = ir_abstract_constant_propagation(interp, irsv) + @assert !(rt isa Conditional || rt isa MustAlias) "invalid lattice element returned from irinterp" + 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 + # state = InliningState(interp) + # ir = ssa_inlining_pass!(irsv.ir, state, propagate_inbounds(irsv)) + new_effects = Effects(result.effects; nothrow) + return ConstCallResults(rt, SemiConcreteResult(mi, ir, new_effects), new_effects, mi) end - add_remark!(interp, sv, "[constprop] Edge cycle encountered") - return nothing end end - inf_result = InferenceResult(mi, (arginfo, sv)) + end + # try constant prop' + 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) - frame === nothing && return nothing # this is probably a bad generated function (unsound), but just ignore it + 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 - typeinf(interp, frame) || return nothing + if !typeinf(interp, frame) + add_remark!(interp, sv, "[constprop] Fresh constant inference hit a cycle") + return nothing + end + @assert inf_result.result !== nothing + else + # 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 end - result = inf_result.result - # if constant inference hits a cycle, just bail out - isa(result, InferenceState) && return nothing - add_backedge!(mi, sv) - return ConstCallResults(result, inf_result, inf_result.ipo_effects) + return ConstCallResults(inf_result.result, ConstPropResult(inf_result), inf_result.ipo_effects, mi) end -# if there's a possibility we could get a better result (hopefully without doing too much work) -# returns `MethodInstance` with constant arguments, returns nothing otherwise -function maybe_get_const_prop_profitable(interp::AbstractInterpreter, result::MethodCallResult, - @nospecialize(f), arginfo::ArgInfo, match::MethodMatch, - sv::InferenceState) +# if there's a possibility we could get a better result with these constant arguments +# (hopefully without doing too much work), returns `MethodInstance`, or nothing otherwise +function maybe_get_const_prop_profitable(interp::AbstractInterpreter, + result::MethodCallResult, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo, + match::MethodMatch, sv::AbsIntState) method = match.method force = force_const_prop(interp, f, method) - force || const_prop_entry_heuristic(interp, result, sv) || return nothing + force || const_prop_entry_heuristic(interp, result, si, sv) || return nothing nargs::Int = method.nargs method.isva && (nargs -= 1) length(arginfo.argtypes) < nargs && return nothing @@ -737,9 +1033,8 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, result::Me add_remark!(interp, sv, "[constprop] Disabled by argument and rettype heuristics") return nothing end - all_overridden = is_all_overridden(arginfo, sv) - if !force && !const_prop_function_heuristic(interp, f, arginfo, nargs, all_overridden, - sv.ipo_effects.nothrow === ALWAYS_TRUE, sv) + all_overridden = is_all_overridden(interp, arginfo, sv) + if !force && !const_prop_function_heuristic(interp, f, arginfo, nargs, all_overridden, sv) add_remark!(interp, sv, "[constprop] Disabled by function heuristic") return nothing end @@ -750,15 +1045,15 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, result::Me return nothing end mi = mi::MethodInstance - if !force && !const_prop_methodinstance_heuristic(interp, match, mi, arginfo, sv) + if !force && !const_prop_methodinstance_heuristic(interp, mi, arginfo, sv) add_remark!(interp, sv, "[constprop] Disabled by method instance heuristic") return nothing end return mi end -function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodCallResult, sv::InferenceState) - if call_result_unused(sv) && result.edgecycle +function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodCallResult, si::StmtInfo, sv::AbsIntState) + if call_result_unused(si) && result.edgecycle add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (edgecycle with unused result)") return false end @@ -774,7 +1069,7 @@ function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodC else return true end - elseif isa(rt, PartialStruct) || isa(rt, InterConditional) + 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) @@ -783,54 +1078,47 @@ function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodC add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (limited accuracy)") return false else - add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (unimprovable return type)") + 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 + end + add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (unimprovable result)") return false end end # determines heuristically whether if constant propagation can be worthwhile # by checking if any of given `argtypes` is "interesting" enough to be propagated -function const_prop_argument_heuristic(_::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, sv::InferenceState) +function const_prop_argument_heuristic(interp::AbstractInterpreter, arginfo::ArgInfo, sv::AbsIntState) + 𝕃ᵢ = typeinf_lattice(interp) + argtypes = arginfo.argtypes for i in 1:length(argtypes) a = argtypes[i] - if isa(a, Conditional) && fargs !== nothing - is_const_prop_profitable_conditional(a, fargs, sv) && return true + if has_conditional(𝕃ᵢ, sv) && isa(a, Conditional) && arginfo.fargs !== nothing + is_const_prop_profitable_conditional(a, arginfo.fargs, sv) && return true else - a = widenconditional(a) - has_nontrivial_const_info(a) && is_const_prop_profitable_arg(a) && return true + a = widenslotwrapper(a) + has_nontrivial_extended_info(𝕃ᵢ, a) && is_const_prop_profitable_arg(𝕃ᵢ, a) && return true end end return false end -function is_const_prop_profitable_arg(@nospecialize(arg)) - # have new information from argtypes that wasn't available from the signature - if isa(arg, PartialStruct) - for b in arg.fields - isconstType(b) && return true - is_const_prop_profitable_arg(b) && return true - end - end - isa(arg, PartialOpaque) && return true - isa(arg, Const) || return true - val = arg.val - # don't consider mutable values or Strings useful constants - return isa(val, Symbol) || isa(val, Type) || (!isa(val, String) && !ismutable(val)) -end - function is_const_prop_profitable_conditional(cnd::Conditional, fargs::Vector{Any}, sv::InferenceState) slotid = find_constrained_arg(cnd, fargs, sv) if slotid !== nothing return true end # as a minor optimization, we just check the result is a constant or not, - # since both `has_nontrivial_const_info`/`is_const_prop_profitable_arg` return `true` + # since both `has_nontrivial_extended_info`/`is_const_prop_profitable_arg` return `true` # for `Const(::Bool)` return isa(widenconditional(cnd), Const) end function find_constrained_arg(cnd::Conditional, fargs::Vector{Any}, sv::InferenceState) - slot = slot_id(cnd.var) + slot = cnd.slot for i in 1:length(fargs) arg = ssa_def_slot(fargs[i], sv) if isa(arg, SlotNumber) && slot_id(arg) == slot @@ -841,44 +1129,47 @@ function find_constrained_arg(cnd::Conditional, fargs::Vector{Any}, sv::Inferenc end # checks if all argtypes has additional information other than what `Type` can provide -function is_all_overridden((; fargs, argtypes)::ArgInfo, sv::InferenceState) - for a in argtypes - if isa(a, Conditional) && fargs !== nothing +function is_all_overridden(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, sv::AbsIntState) + 𝕃ᵢ = typeinf_lattice(interp) + for i in 1:length(argtypes) + a = argtypes[i] + if has_conditional(𝕃ᵢ, sv) && isa(a, Conditional) && fargs !== nothing is_const_prop_profitable_conditional(a, fargs, sv) || return false else - a = widenconditional(a) - is_forwardable_argtype(a) || return false + is_forwardable_argtype(𝕃ᵢ, widenslotwrapper(a)) || return false end end return true end function force_const_prop(interp::AbstractInterpreter, @nospecialize(f), method::Method) - return method.constprop == 0x01 || + return is_aggressive_constprop(method) || InferenceParams(interp).aggressive_constant_propagation || istopfunction(f, :getproperty) || istopfunction(f, :setproperty!) end -function const_prop_function_heuristic( - _::AbstractInterpreter, @nospecialize(f), (; argtypes)::ArgInfo, - nargs::Int, all_overridden::Bool, still_nothrow::Bool, _::InferenceState) +function const_prop_function_heuristic(interp::AbstractInterpreter, @nospecialize(f), + arginfo::ArgInfo, nargs::Int, all_overridden::Bool, sv::AbsIntState) + argtypes = arginfo.argtypes if nargs > 1 + 𝕃ᵢ = typeinf_lattice(interp) if istopfunction(f, :getindex) || istopfunction(f, :setindex!) arrty = argtypes[2] # don't propagate constant index into indexing of non-constant array if arrty isa Type && arrty <: AbstractArray && !issingletontype(arrty) # For static arrays, allow the constprop if we could possibly # deduce nothrow as a result. + still_nothrow = isa(sv, InferenceState) ? is_nothrow(sv.ipo_effects) : false if !still_nothrow || ismutabletype(arrty) return false end - elseif arrty ⊑ Array + elseif ⊑(𝕃ᵢ, arrty, Array) return false end elseif istopfunction(f, :iterate) itrty = argtypes[2] - if itrty ⊑ Array + if ⊑(𝕃ᵢ, itrty, Array) return false end end @@ -906,10 +1197,9 @@ end # This is a heuristic to avoid trying to const prop through complicated functions # where we would spend a lot of time, but are probably unlikely to get an improved # result anyway. -function const_prop_methodinstance_heuristic( - interp::AbstractInterpreter, match::MethodMatch, mi::MethodInstance, - (; argtypes)::ArgInfo, sv::InferenceState) - method = match.method +function const_prop_methodinstance_heuristic(interp::AbstractInterpreter, + mi::MethodInstance, arginfo::ArgInfo, sv::AbsIntState) + method = mi.def::Method if method.is_for_opaque_closure # Not inlining an opaque closure can be very expensive, so be generous # with the const-prop-ability. It is quite possible that we can't infer @@ -917,26 +1207,32 @@ function const_prop_methodinstance_heuristic( # isn't particularly helpful here. return true end - # Peek at the inferred result for the function to determine if the optimizer - # was able to cut it down to something simple (inlineable in particular). - # If so, there's a good chance we might be able to const prop all the way - # through and learn something new. - if isdefined(method, :source) && ccall(:jl_ir_flag_inlineable, Bool, (Any,), method.source) + # now check if the source of this method instance is inlineable, since the extended type + # information we have here would be discarded if it is not inlined into a callee context + # (modulo the inferred return type that can be potentially refined) + if is_declared_inline(method) + # this method is declared as `@inline` and will be inlined return true + end + flag = get_curr_ssaflag(sv) + if is_stmt_inline(flag) + # force constant propagation for a call that is going to be inlined + # since the inliner will try to find this constant result + # if these constant arguments arrive there + return true + elseif is_stmt_noinline(flag) + # this call won't be inlined, thus this constant-prop' will most likely be unfruitful + return false else - flag = get_curr_ssaflag(sv) - if is_stmt_inline(flag) - # force constant propagation for a call that is going to be inlined - # since the inliner will try to find this constant result - # if these constant arguments arrive there - return true - elseif is_stmt_noinline(flag) - # this call won't be inlined, thus this constant-prop' will most likely be unfruitful - return false - else - code = get(code_cache(interp), mi, nothing) - if isdefined(code, :inferred) && inlining_policy( - interp, code.inferred, IR_FLAG_NULL, mi, argtypes) !== nothing + # Peek at the inferred result for the method to determine if the optimizer + # was able to cut it down to something simple (inlineable in particular). + # If so, there will be a good chance we might be able to const prop + # all the way through and learn something new. + code = get(code_cache(interp), mi, nothing) + 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 return true end end @@ -947,45 +1243,90 @@ end # This is only for use with `Conditional`. # In general, usage of this is wrong. function ssa_def_slot(@nospecialize(arg), sv::InferenceState) + code = sv.src.code init = sv.currpc while isa(arg, SSAValue) init = arg.id - arg = sv.src.code[init] - end - arg isa SlotNumber || return nothing - for i = init:(sv.currpc - 1) - # conservatively make sure there isn't potentially another conflicting assignment to - # the same slot between the def and usage + arg = code[init] + end + if arg isa SlotNumber + # found this kind of pattern: + # %init = SlotNumber(x) + # [...] + # goto if not isa(%init, T) + # now conservatively make sure there isn't potentially another conflicting assignment + # to the same slot between the def and usage # we can assume the IR is sorted, since the front-end only creates SSA values in order - e = sv.src.code[i] - e isa Expr || continue - if e.head === :(=) && e.args[1] === arg - return nothing + for i = init:(sv.currpc-1) + e = code[i] + if isexpr(e, :(=)) && e.args[1] === arg + return nothing + end + end + else + # there might still be the following kind of pattern (see #45499): + # %init = ... + # [...] + # SlotNumber(x) = %init + # [...] + # goto if not isa(%init, T) + # let's check if there is a slot assigned to the def SSA value but also there isn't + # any potentially conflicting assignment to the same slot + arg = nothing + def = SSAValue(init) + for i = (init+1):(sv.currpc-1) + e = code[i] + if isexpr(e, :(=)) + lhs = e.args[1] + if isa(lhs, SlotNumber) + lhs === arg && return nothing + rhs = e.args[2] + if rhs === def + arg = lhs + end + end + end end end return arg end +struct AbstractIterationResult + cti::Vector{Any} + info::MaybeAbstractIterationInfo + ai_effects::Effects +end +AbstractIterationResult(cti::Vector{Any}, info::MaybeAbstractIterationInfo) = + AbstractIterationResult(cti, info, EFFECTS_TOTAL) + # `typ` is the inferred type for expression `arg`. # if the expression constructs a container (e.g. `svec(x,y,z)`), # refine its type to an array of element types. # Union of Tuples of the same length is converted to Tuple of Unions. # returns an array of types -function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(typ), sv::InferenceState) - if isa(typ, PartialStruct) && typ.typ.name === Tuple.name - return typ.fields, nothing +function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(typ), + sv::AbsIntState) + if isa(typ, PartialStruct) + widet = typ.typ + if isa(widet, DataType) + if widet.name === Tuple.name + return AbstractIterationResult(typ.fields, nothing) + elseif widet.name === _NAMEDTUPLE_NAME + return AbstractIterationResult(typ.fields, nothing) + end + end end if isa(typ, Const) val = typ.val - if isa(val, SimpleVector) || isa(val, Tuple) - return Any[ Const(val[i]) for i in 1:length(val) ], nothing # avoid making a tuple Generator here! + if isa(val, SimpleVector) || isa(val, Tuple) || isa(val, NamedTuple) + return AbstractIterationResult(Any[ Const(val[i]) for i in 1:length(val) ], nothing) # avoid making a tuple Generator here! end end tti0 = widenconst(typ) tti = unwrap_unionall(tti0) - if isa(tti, DataType) && tti.name === NamedTuple_typename + if isa(tti, DataType) && tti.name === _NAMEDTUPLE_NAME # A NamedTuple iteration is the same as the iteration of its Tuple parameter: # compute a new `tti == unwrap_unionall(tti0)` based on that Tuple type tti = unwraptv(tti.parameters[2]) @@ -993,13 +1334,13 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) end if isa(tti, Union) utis = uniontypes(tti) - if _any(@nospecialize(t) -> !isa(t, DataType) || !(t <: Tuple) || !isknownlength(t), utis) - return Any[Vararg{Any}], nothing + if any(@nospecialize(t) -> !isa(t, DataType) || !(t <: Tuple) || !isknownlength(t), utis) + return AbstractIterationResult(Any[Vararg{Any}], nothing, Effects()) end ltp = length((utis[1]::DataType).parameters) for t in utis if length((t::DataType).parameters) != ltp - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing) end end result = Any[ Union{} for _ in 1:ltp ] @@ -1010,74 +1351,84 @@ function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft) result[j] = tmerge(result[j], rewrap_unionall(tps[j], tti0)) end end - return result, nothing + return AbstractIterationResult(result, nothing) elseif tti0 <: Tuple if isa(tti0, DataType) - return Any[ p for p in tti0.parameters ], nothing + return AbstractIterationResult(Any[ p for p in tti0.parameters ], nothing) elseif !isa(tti, DataType) - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing) else len = length(tti.parameters) last = tti.parameters[len] va = isvarargtype(last) elts = Any[ fieldtype(tti0, i) for i = 1:len ] if va - elts[len] = Vararg{elts[len]} + if elts[len] === Union{} + pop!(elts) + else + elts[len] = Vararg{elts[len]} + end end - return elts, nothing + return AbstractIterationResult(elts, nothing) end - elseif tti0 === SimpleVector || tti0 === Any - return Any[Vararg{Any}], nothing + elseif tti0 === SimpleVector + return AbstractIterationResult(Any[Vararg{Any}], nothing) + elseif tti0 === Any + return AbstractIterationResult(Any[Vararg{Any}], nothing, Effects()) elseif tti0 <: Array - return Any[Vararg{eltype(tti0)}], nothing + if eltype(tti0) === Union{} + return AbstractIterationResult(Any[], nothing) + end + return AbstractIterationResult(Any[Vararg{eltype(tti0)}], nothing) else return abstract_iteration(interp, itft, typ, sv) end end # simulate iteration protocol on container type up to fixpoint -function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(itertype), sv::InferenceState) +function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(itertype), sv::AbsIntState) if isa(itft, Const) iteratef = itft.val else - return Any[Vararg{Any}], nothing + return AbstractIterationResult(Any[Vararg{Any}], nothing, Effects()) end @assert !isvarargtype(itertype) - call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), sv) + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[itft, itertype]), StmtInfo(true), sv) stateordonet = call.rt info = call.info # 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 Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, info)]) + stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, call.effects, info)], true)) valtype = statetype = Bottom ret = Any[] calls = CallMeta[call] stateordonet_widened = widenconst(stateordonet) + 𝕃ᵢ = typeinf_lattice(interp) - # Try to unroll the iteration up to MAX_TUPLE_SPLAT, which covers any finite + # Try to unroll the iteration up to max_tuple_splat, which covers any finite # length iterators, or interesting prefix while true if stateordonet_widened === Nothing - return ret, AbstractIterationInfo(calls) + return AbstractIterationResult(ret, AbstractIterationInfo(calls, true)) end - if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).MAX_TUPLE_SPLAT + if Nothing <: stateordonet_widened || length(ret) >= InferenceParams(interp).max_tuple_splat break end if !isa(stateordonet_widened, DataType) || !(stateordonet_widened <: Tuple) || isvatuple(stateordonet_widened) || length(stateordonet_widened.parameters) != 2 break end - nstatetype = getfield_tfunc(stateordonet, Const(2)) + nstatetype = getfield_tfunc(𝕃ᵢ, stateordonet, Const(2)) # If there's no new information in this statetype, don't bother continuing, # the iterator won't be finite. - if nstatetype ⊑ statetype - return Any[Bottom], nothing + if ⊑(𝕃ᵢ, nstatetype, statetype) + return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(calls, false), EFFECTS_THROWS) end - valtype = getfield_tfunc(stateordonet, Const(1)) + valtype = getfield_tfunc(𝕃ᵢ, stateordonet, Const(1)) push!(ret, valtype) statetype = nstatetype - call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), sv) + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv) stateordonet = call.rt stateordonet_widened = widenconst(stateordonet) push!(calls, call) @@ -1086,8 +1437,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # the precise (potentially const) state type # statetype and valtype are reinitialized in the first iteration below from the # (widened) stateordonet, which has not yet been fully analyzed in the loop above - statetype = Bottom - valtype = Bottom + valtype = statetype = Bottom may_have_terminated = Nothing <: stateordonet_widened while valtype !== Any nounion = typeintersect(stateordonet_widened, Tuple{Any,Any}) @@ -1102,7 +1452,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # ... but cannot terminate if !may_have_terminated # ... and cannot have terminated prior to this loop - return Any[Bottom], nothing + return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(calls, false), Effects()) else # iterator may have terminated prior to this loop, but not during it valtype = Bottom @@ -1112,49 +1462,47 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - stateordonet = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), sv).rt + call = abstract_call_known(interp, iteratef, ArgInfo(nothing, Any[Const(iteratef), itertype, statetype]), StmtInfo(true), sv) + push!(calls, call) + stateordonet = call.rt stateordonet_widened = widenconst(stateordonet) end if valtype !== Union{} push!(ret, Vararg{valtype}) end - return ret, nothing + return AbstractIterationResult(ret, AbstractIterationInfo(calls, false)) end # do apply(af, fargs...), where af is a function value -function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState, - max_methods::Int = get_max_methods(sv.mod, interp)) +function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, + 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, false) + (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) aargtypes = argtype_tail(argtypes, 4) aftw = widenconst(aft) if !isa(aft, Const) && !isa(aft, PartialOpaque) && (!isType(aftw) || has_free_typevars(aftw)) if !isconcretetype(aftw) || (aftw <: Builtin) add_remark!(interp, sv, "Core._apply_iterate called on a function of a non-concrete type") - tristate_merge!(sv, Effects()) # 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, false) + return CallMeta(Any, Effects(), NoCallInfo()) end end res = Union{} nargs = length(aargtypes) - splitunions = 1 < unionsplitcost(aargtypes) <= InferenceParams(interp).MAX_APPLY_UNION_ENUM + splitunions = 1 < unionsplitcost(typeinf_lattice(interp), aargtypes) <= InferenceParams(interp).max_apply_union_enum ctypes = [Any[aft]] infos = Vector{MaybeAbstractIterationInfo}[MaybeAbstractIterationInfo[]] + effects = EFFECTS_TOTAL for i = 1:nargs ctypes´ = Vector{Any}[] infos′ = Vector{MaybeAbstractIterationInfo}[] for ti in (splitunions ? uniontypes(aargtypes[i]) : Any[aargtypes[i]]) if !isvarargtype(ti) - cti_info = precise_container_type(interp, itft, ti, sv) - cti = cti_info[1]::Vector{Any} - info = cti_info[2]::MaybeAbstractIterationInfo + (;cti, info, ai_effects) = precise_container_type(interp, itft, ti, sv) else - cti_info = precise_container_type(interp, itft, unwrapva(ti), sv) - cti = cti_info[1]::Vector{Any} - info = cti_info[2]::MaybeAbstractIterationInfo + (;cti, info, ai_effects) = precise_container_type(interp, itft, unwrapva(ti), sv) # We can't represent a repeating sequence of the same types, # so tmerge everything together to get one type that represents # everything. @@ -1167,17 +1515,22 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv:: end cti = Any[Vararg{argt}] end - if _any(t -> t === Bottom, cti) + effects = merge_effects(effects, ai_effects) + if info !== nothing + for call in info.each + effects = merge_effects(effects, call.effects) + end + end + if any(@nospecialize(t) -> t === Bottom, cti) continue end for j = 1:length(ctypes) ct = ctypes[j]::Vector{Any} if isvarargtype(ct[end]) - # This is vararg, we're not gonna be able to do any inling, + # This is vararg, we're not gonna be able to do any inlining, # drop the info info = nothing - - tail = tuple_tail_elem(unwrapva(ct[end]), cti) + tail = tuple_tail_elem(typeinf_lattice(interp), unwrapva(ct[end]), cti) push!(ctypes´, push!(ct[1:(end - 1)], tail)) else push!(ctypes´, append!(ct[:], cti)) @@ -1190,62 +1543,40 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv:: end retinfos = ApplyCallInfo[] retinfo = UnionSplitApplyCallInfo(retinfos) - for i = 1:length(ctypes) + napplicable = length(ctypes) + seen = 0 + for i = 1:napplicable ct = ctypes[i] arginfo = infos[i] lct = length(ct) # truncate argument list at the first Vararg for i = 1:lct-1 - if isvarargtype(ct[i]) - ct[i] = tuple_tail_elem(ct[i], ct[(i+1):lct]) + cti = ct[i] + if isvarargtype(cti) + ct[i] = tuple_tail_elem(typeinf_lattice(interp), unwrapva(cti), ct[(i+1):lct]) resize!(ct, i) break end end - call = abstract_call(interp, ArgInfo(nothing, ct), sv, max_methods) + call = abstract_call(interp, ArgInfo(nothing, ct), si, sv, max_methods) + seen += 1 push!(retinfos, ApplyCallInfo(call.info, arginfo)) res = tmerge(res, call.rt) - if bail_out_apply(interp, res, sv) - if i != length(ctypes) - # No point carrying forward the info, we're not gonna inline it anyway - retinfo = false - end + 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.") break end end + if seen ≠ napplicable + # there is unanalyzed candidate, widen type and effects to the top + res = 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, retinfo) -end - -function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector) - if isdefined(method, :generator) - method.generator.expand_early || return false - mi = specialize_method(method, sig, sparams) - isa(mi, MethodInstance) || return false - staged = get_staged(mi) - (staged isa CodeInfo && (staged::CodeInfo).pure) || return false - return true - end - return method.pure -end -is_method_pure(match::MethodMatch) = is_method_pure(match.method, match.spec_types, match.sparams) - -function pure_eval_call(@nospecialize(f), argtypes::Vector{Any}) - for i = 2:length(argtypes) - a = widenconditional(argtypes[i]) - if !(isa(a, Const) || isconstType(a)) - return nothing - end - end - args = Any[ (a = widenconditional(argtypes[i]); - isa(a, Const) ? a.val : (a::DataType).parameters[1]) for i in 2:length(argtypes) ] - try - value = Core._apply_pure(f, args) - return Const(value) - catch - return nothing - end + return CallMeta(res, effects, retinfo) end function argtype_by_index(argtypes::Vector{Any}, i::Int) @@ -1266,11 +1597,72 @@ function argtype_tail(argtypes::Vector{Any}, i::Int) return argtypes[i:n] end +struct ConditionalTypes + thentype + elsetype + ConditionalTypes(thentype, elsetype) = (@nospecialize; new(thentype, elsetype)) +end + +@inline function isa_condition(@nospecialize(xt), @nospecialize(ty), max_union_splitting::Int, + @nospecialize(rt)) + if isa(rt, Const) + xt = widenslotwrapper(xt) + if rt.val === false + return ConditionalTypes(Bottom, xt) + elseif rt.val === true + return ConditionalTypes(xt, Bottom) + end + end + return isa_condition(xt, ty, max_union_splitting) +end +@inline function isa_condition(@nospecialize(xt), @nospecialize(ty), max_union_splitting::Int) + tty_ub, isexact_tty = instanceof_tfunc(ty) + tty = widenconst(xt) + if isexact_tty && !isa(tty_ub, TypeVar) + tty_lb = tty_ub # TODO: this would be wrong if !isexact_tty, but instanceof_tfunc doesn't preserve this info + if !has_free_typevars(tty_lb) && !has_free_typevars(tty_ub) + thentype = typeintersect(tty, tty_ub) + if iskindtype(tty_ub) && thentype !== Bottom + # `typeintersect` may be unable narrow down `Type`-type + thentype = tty_ub + end + valid_as_lattice(thentype) || (thentype = Bottom) + elsetype = typesubtract(tty, tty_lb, max_union_splitting) + return ConditionalTypes(thentype, elsetype) + end + end + return nothing +end + +@inline function egal_condition(c::Const, @nospecialize(xt), max_union_splitting::Int, + @nospecialize(rt)) + thentype = c + elsetype = widenslotwrapper(xt) + if rt === Const(false) + 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 + elsetype = typesubtract(elsetype, typeof(c.val), max_union_splitting) + end + return ConditionalTypes(thentype, elsetype) +end +@inline function egal_condition(c::Const, @nospecialize(xt), max_union_splitting::Int) + thentype = c + elsetype = widenslotwrapper(xt) + if 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) +end + function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs, argtypes)::ArgInfo, - sv::InferenceState, max_methods::Int) + sv::AbsIntState, max_methods::Int) @nospecialize f la = length(argtypes) - if f === Core.ifelse && fargs isa Vector{Any} && la == 4 + 𝕃ᵢ = typeinf_lattice(interp) + ⊑ᵢ = ⊑(𝕃ᵢ) + if has_conditional(𝕃ᵢ, sv) && f === Core.ifelse && fargs isa Vector{Any} && la == 4 cnd = argtypes[2] if isa(cnd, Conditional) newcnd = widenconditional(cnd) @@ -1283,105 +1675,145 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs # try to simulate this as a real conditional (`cnd ? x : y`), so that the penalty for using `ifelse` instead isn't too high a = ssa_def_slot(fargs[3], sv) b = ssa_def_slot(fargs[4], sv) - if isa(a, SlotNumber) && slot_id(cnd.var) == slot_id(a) - tx = (cnd.vtype ⊑ tx ? cnd.vtype : tmeet(tx, widenconst(cnd.vtype))) + if isa(a, SlotNumber) && cnd.slot == slot_id(a) + tx = (cnd.thentype ⊑ᵢ tx ? cnd.thentype : tmeet(𝕃ᵢ, tx, widenconst(cnd.thentype))) end - if isa(b, SlotNumber) && slot_id(cnd.var) == slot_id(b) - ty = (cnd.elsetype ⊑ ty ? cnd.elsetype : tmeet(ty, widenconst(cnd.elsetype))) + if isa(b, SlotNumber) && cnd.slot == slot_id(b) + ty = (cnd.elsetype ⊑ᵢ ty ? cnd.elsetype : tmeet(𝕃ᵢ, ty, widenconst(cnd.elsetype))) end - return tmerge(tx, ty) + return tmerge(𝕃ᵢ, tx, ty) end end end rt = builtin_tfunction(interp, f, argtypes[2:end], sv) - if (rt === Bool || (isa(rt, Const) && isa(rt.val, Bool))) && isa(fargs, Vector{Any}) + 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] + if isa(var, SlotNumber) + vartyp = widenslotwrapper(argtypes[2]) + fldidx = maybe_const_fldidx(vartyp, a3.val) + if fldidx !== nothing + # wrap this aliasable field into `MustAlias` for possible constraint propagations + return MustAlias(var, vartyp, fldidx, rt) + end + end + end + end + elseif has_conditional(𝕃ᵢ, sv) && (rt === Bool || (isa(rt, Const) && isa(rt.val, Bool))) && isa(fargs, Vector{Any}) # perform very limited back-propagation of type information for `is` and `isa` if f === isa + # try splitting value argument, based on types a = ssa_def_slot(fargs[2], sv) + a2 = argtypes[2] + a3 = argtypes[3] if isa(a, SlotNumber) - aty = widenconst(argtypes[2]) - if rt === Const(false) - return Conditional(a, Union{}, aty) - elseif rt === Const(true) - return Conditional(a, aty, Union{}) + cndt = isa_condition(a2, a3, InferenceParams(interp).max_union_splitting, rt) + if cndt !== nothing + return Conditional(a, cndt.thentype, cndt.elsetype) end - tty_ub, isexact_tty = instanceof_tfunc(argtypes[3]) - if isexact_tty && !isa(tty_ub, TypeVar) - tty_lb = tty_ub # TODO: this would be wrong if !isexact_tty, but instanceof_tfunc doesn't preserve this info - if !has_free_typevars(tty_lb) && !has_free_typevars(tty_ub) - ifty = typeintersect(aty, tty_ub) - valid_as_lattice(ifty) || (ifty = Union{}) - elty = typesubtract(aty, tty_lb, InferenceParams(interp).MAX_UNION_SPLITTING) - return Conditional(a, ifty, elty) + end + if isa(a2, MustAlias) + if !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) + cndt = isa_condition(a2, a3, InferenceParams(interp).max_union_splitting) + if cndt !== nothing + return form_mustalias_conditional(a2, cndt.thentype, cndt.elsetype) end end end + # try splitting type argument, based on value + if isdispatchelem(widenconst(a2)) && a3 isa Union && !has_free_typevars(a3) && !isa(rt, Const) + b = ssa_def_slot(fargs[3], sv) + if isa(b, SlotNumber) + # !(x isa T) implies !(Type{a2} <: T) + # TODO: complete splitting, based on which portions of the Union a3 for which isa_tfunc returns Const(true) or Const(false) instead of Bool + elsetype = typesubtract(a3, Type{widenconst(a2)}, InferenceParams(interp).max_union_splitting) + return Conditional(b, a3, elsetype) + end + end elseif f === (===) a = ssa_def_slot(fargs[2], sv) b = ssa_def_slot(fargs[3], sv) aty = argtypes[2] bty = argtypes[3] # if doing a comparison to a singleton, consider returning a `Conditional` instead - if isa(aty, Const) && isa(b, SlotNumber) - if rt === Const(false) - aty = Union{} - elseif rt === Const(true) - bty = Union{} - elseif bty isa Type && isdefined(typeof(aty.val), :instance) # can only widen a if it is a singleton - bty = typesubtract(bty, typeof(aty.val), InferenceParams(interp).MAX_UNION_SPLITTING) + if isa(aty, Const) + if isa(b, SlotNumber) + cndt = egal_condition(aty, bty, InferenceParams(interp).max_union_splitting, rt) + return Conditional(b, cndt.thentype, cndt.elsetype) + elseif isa(bty, MustAlias) && !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) + cndt = egal_condition(aty, bty.fldtyp, InferenceParams(interp).max_union_splitting) + return form_mustalias_conditional(bty, cndt.thentype, cndt.elsetype) end - return Conditional(b, aty, bty) - end - if isa(bty, Const) && isa(a, SlotNumber) - if rt === Const(false) - bty = Union{} - elseif rt === Const(true) - aty = Union{} - elseif aty isa Type && isdefined(typeof(bty.val), :instance) # same for b - aty = typesubtract(aty, typeof(bty.val), InferenceParams(interp).MAX_UNION_SPLITTING) + elseif isa(bty, Const) + if isa(a, SlotNumber) + cndt = egal_condition(bty, aty, InferenceParams(interp).max_union_splitting, rt) + return Conditional(a, cndt.thentype, cndt.elsetype) + elseif isa(aty, MustAlias) && !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) + cndt = egal_condition(bty, aty.fldtyp, InferenceParams(interp).max_union_splitting) + return form_mustalias_conditional(aty, cndt.thentype, cndt.elsetype) + end + end + # TODO enable multiple constraints propagation here, there are two possible improvements: + # 1. propagate constraints for both lhs and rhs + # 2. we can propagate both constraints on aliased fields and slots + # As for 2, for now, we prioritize constraints on aliased fields, since currently + # different slots that represent the same object can't share same field constraint, + # and thus binding `MustAlias` to the other slot is less likely useful + if !isa(rt, Const) # skip refinement when the field is known precisely (just optimization) + if isa(bty, MustAlias) + thentype = widenslotwrapper(aty) + elsetype = bty.fldtyp + if thentype ⊏ elsetype + return form_mustalias_conditional(bty, thentype, elsetype) + end + elseif isa(aty, MustAlias) + thentype = widenslotwrapper(bty) + elsetype = aty.fldtyp + if thentype ⊏ elsetype + return form_mustalias_conditional(aty, thentype, elsetype) + end end - return Conditional(a, bty, aty) end # narrow the lattice slightly (noting the dependency on one of the slots), to promote more effective smerge if isa(b, SlotNumber) - return Conditional(b, rt === Const(false) ? Union{} : bty, rt === Const(true) ? Union{} : bty) - end - if isa(a, SlotNumber) - return Conditional(a, rt === Const(false) ? Union{} : aty, rt === Const(true) ? Union{} : aty) + thentype = rt === Const(false) ? Bottom : widenslotwrapper(bty) + elsetype = rt === Const(true) ? Bottom : widenslotwrapper(bty) + return Conditional(b, thentype, elsetype) + elseif isa(a, SlotNumber) + thentype = rt === Const(false) ? Bottom : widenslotwrapper(aty) + elsetype = rt === Const(true) ? Bottom : widenslotwrapper(aty) + return Conditional(a, thentype, elsetype) end elseif f === Core.Compiler.not_int aty = argtypes[2] if isa(aty, Conditional) - ifty = aty.elsetype - elty = aty.vtype - if rt === Const(false) - ifty = Union{} - elseif rt === Const(true) - elty = Union{} - end - return Conditional(aty.var, ifty, elty) + thentype = rt === Const(false) ? Bottom : aty.elsetype + elsetype = rt === Const(true) ? Bottom : aty.thentype + return Conditional(aty.slot, thentype, elsetype) end elseif f === isdefined uty = argtypes[2] a = ssa_def_slot(fargs[2], sv) if isa(uty, Union) && isa(a, SlotNumber) fld = argtypes[3] - vtype = Union{} - elsetype = Union{} + thentype = Bottom + elsetype = Bottom for ty in uniontypes(uty) - cnd = isdefined_tfunc(ty, fld) + cnd = isdefined_tfunc(𝕃ᵢ, ty, fld) if isa(cnd, Const) if cnd.val::Bool - vtype = tmerge(vtype, ty) + thentype = tmerge(thentype, ty) else elsetype = tmerge(elsetype, ty) end else - vtype = tmerge(vtype, ty) + thentype = tmerge(thentype, ty) elsetype = tmerge(elsetype, ty) end end - return Conditional(a, vtype, elsetype) + return Conditional(a, thentype, elsetype) end end end @@ -1389,63 +1821,69 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs return rt end -function abstract_call_unionall(argtypes::Vector{Any}) +function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{Any}) if length(argtypes) == 3 canconst = true + a2 = argtypes[2] a3 = argtypes[3] + ⊑ᵢ = ⊑(typeinf_lattice(interp)) + nothrow = a2 ⊑ᵢ TypeVar && (a3 ⊑ᵢ Type || a3 ⊑ᵢ TypeVar) if isa(a3, Const) body = a3.val elseif isType(a3) body = a3.parameters[1] canconst = false else - return Any + return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo()) end - if !isa(body, Type) && !isa(body, TypeVar) - return Any + if !(isa(body, Type) || isa(body, TypeVar)) + return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) end if has_free_typevars(body) - a2 = argtypes[2] if isa(a2, Const) tv = a2.val elseif isa(a2, PartialTypeVar) tv = a2.tv canconst = false else - return Any + return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) end - !isa(tv, TypeVar) && return Any + isa(tv, TypeVar) || return CallMeta(Any, EFFECTS_THROWS, NoCallInfo()) body = UnionAll(tv, body) end ret = canconst ? Const(body) : Type{body} - return ret + return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow), NoCallInfo()) end - return Any + return CallMeta(Any, EFFECTS_UNKNOWN, NoCallInfo()) end -function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, sv::InferenceState) +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, false) + ft === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3)) - types === Bottom && return CallMeta(Bottom, false) - isexact || return CallMeta(Any, false) + isexact || return CallMeta(Any, Effects(), NoCallInfo()) + unwrapped = unwrap_unionall(types) + if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name + return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + end argtype = argtypes_to_type(argtype_tail(argtypes, 4)) nargtype = typeintersect(types, argtype) - nargtype === Bottom && return CallMeta(Bottom, false) - nargtype isa DataType || return CallMeta(Any, false) # other cases are not implemented below - isdispatchelem(ft) || return CallMeta(Any, false) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + nargtype === Bottom && return CallMeta(Bottom, 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 ft = ft::DataType - types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types)::Type + lookupsig = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type nargtype = Tuple{ft, nargtype.parameters...} argtype = Tuple{ft, argtype.parameters...} - result = findsup(types, method_table(interp)) - result === nothing && return CallMeta(Any, false) - method, valid_worlds = result + match, valid_worlds, overlayed = findsup(lookupsig, method_table(interp)) + match === nothing && return CallMeta(Any, Effects(), NoCallInfo()) update_valid_age!(sv, valid_worlds) - (ti, env::SimpleVector) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector - (; rt, edge) = result = abstract_call_method(interp, method, ti, env, false, sv) - edge !== nothing && add_backedge!(edge::MethodInstance, sv) + 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 match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing sig = match.spec_types @@ -1457,11 +1895,22 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn # t, a = ti.parameters[i], argtypes′[i] # argtypes′[i] = t ⊑ a ? t : a # end - const_result = abstract_call_method_with_const_args(interp, result, singleton_type(ft′), arginfo, match, sv) - if const_result !== nothing - (;rt, const_result) = const_result + 𝕃ₚ = ipo_lattice(interp) + f = overlayed ? nothing : singleton_type(ft′) + invokecall = InvokeCall(types, lookupsig) + const_call_result = abstract_call_method_with_const_args(interp, + result, f, arginfo, si, match, sv, invokecall) + const_result = nothing + if const_call_result !== nothing + if ⊑(𝕃ₚ, const_call_result.rt, rt) + (; rt, effects, const_result, edge) = const_call_result + end end - return CallMeta(from_interprocedural!(rt, sv, arginfo, sig), InvokeCallInfo(match, const_result)) + rt = from_interprocedural!(𝕃ₚ, rt, sv, arginfo, sig) + effects = Effects(effects; nonoverlayed=!overlayed) + info = InvokeCallInfo(match, const_result) + edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) + return CallMeta(rt, effects, info) end function invoke_rewrite(xs::Vector{Any}) @@ -1471,54 +1920,53 @@ function invoke_rewrite(xs::Vector{Any}) return newxs end +function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState) + 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)) + end + return CallMeta(Nothing, Effects(), NoCallInfo()) +end + # call where the function is known exactly function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), - arginfo::ArgInfo, sv::InferenceState, - max_methods::Int = get_max_methods(sv.mod, interp)) + arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, + 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, sv, max_methods) + return abstract_apply(interp, argtypes, si, sv, max_methods) elseif f === invoke - call = abstract_invoke(interp, arginfo, sv) - if call.info === false - if call.rt === Bottom - tristate_merge!(sv, Effects(EFFECTS_TOTAL, nothrow=ALWAYS_FALSE)) - else - tristate_merge!(sv, Effects()) - end - end - return call + return abstract_invoke(interp, arginfo, si, sv) elseif f === modifyfield! - tristate_merge!(sv, Effects()) # TODO - return abstract_modifyfield!(interp, argtypes, sv) + return abstract_modifyfield!(interp, argtypes, si, sv) + elseif f === Core.finalizer + return abstract_finalizer(interp, argtypes, sv) + elseif f === applicable + return abstract_applicable(interp, argtypes, sv, max_methods) end rt = abstract_call_builtin(interp, f, arginfo, sv, max_methods) - tristate_merge!(sv, builtin_effects(f, argtypes, rt)) - return CallMeta(rt, false) + effects = builtin_effects(𝕃ᵢ, f, arginfo, rt) + if f === getfield && (fargs !== nothing && isexpr(fargs[end], :boundscheck)) && !is_nothrow(effects) && isa(sv, InferenceState) + # As a special case, we delayed tainting `noinbounds` for getfield calls in case we can prove + # in-boundedness indepedently. Here we need to put that back in other cases. + # N.B.: This isn't about the effects of the call itself, but a delayed contribution of the :boundscheck + # statement, so we need to merge this directly into sv, rather than modifying thte effects. + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; noinbounds=false, + consistent = (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 ? ALWAYS_FALSE : ALWAYS_TRUE)) + end + return CallMeta(rt, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information - tristate_merge!(sv, Effects()) - return CallMeta(Any, false) - elseif f === Core.kwfunc - if la == 2 - aty = argtypes[2] - if !isvarargtype(aty) - ft = widenconst(aty) - if isa(ft, DataType) && isdefined(ft.name, :mt) && isdefined(ft.name.mt, :kwsorter) - return CallMeta(Const(ft.name.mt.kwsorter), MethodResultPure()) - end - end - end - tristate_merge!(sv, Effects()) # TODO - return CallMeta(Any, false) + return CallMeta(typeof(f).parameters[2], Effects(), NoCallInfo()) elseif f === TypeVar # Manually look through the definition of TypeVar to # make sure to be able to get `PartialTypeVar`s out. - tristate_merge!(sv, Effects()) # TODO - (la < 2 || la > 4) && return CallMeta(Union{}, false) + (la < 2 || la > 4) && return CallMeta(Union{}, EFFECTS_UNKNOWN, NoCallInfo()) n = argtypes[2] ub_var = Const(Any) lb_var = Const(Union{}) @@ -1528,36 +1976,36 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif la == 3 ub_var = argtypes[3] end - return CallMeta(typevar_tfunc(n, lb_var, ub_var), false) + 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, NoCallInfo()) elseif f === UnionAll - tristate_merge!(sv, Effects()) # TODO - return CallMeta(abstract_call_unionall(argtypes), false) + return abstract_call_unionall(interp, argtypes) elseif f === Tuple && la == 2 - tristate_merge!(sv, Effects()) # TODO aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) if !isconcretetype(ty) - return CallMeta(Tuple, false) + return CallMeta(Tuple, EFFECTS_UNKNOWN, NoCallInfo()) end elseif is_return_type(f) - tristate_merge!(sv, Effects()) # TODO - return return_type_tfunc(interp, argtypes, sv) + return return_type_tfunc(interp, argtypes, si, sv) elseif la == 2 && istopfunction(f, :!) # handle Conditional propagation through !Bool aty = argtypes[2] if isa(aty, Conditional) - call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Bool]), Tuple{typeof(f), Bool}, sv, max_methods) # make sure we've inferred `!(::Bool)` - return CallMeta(Conditional(aty.var, aty.elsetype, aty.vtype), call.info) + 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) end elseif la == 3 && istopfunction(f, :!==) # mark !== as exactly a negated call to === - rty = abstract_call_known(interp, (===), arginfo, sv, max_methods).rt + rty = abstract_call_known(interp, (===), arginfo, si, sv, max_methods).rt if isa(rty, Conditional) - return CallMeta(Conditional(rty.var, rty.elsetype, rty.vtype), false) # swap if-else + return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), EFFECTS_TOTAL, NoCallInfo()) # swap if-else elseif isa(rty, Const) - return CallMeta(Const(rty.val === false), MethodResultPure()) + return CallMeta(Const(rty.val === false), EFFECTS_TOTAL, MethodResultPure()) end - return CallMeta(rty, false) + return CallMeta(rty, EFFECTS_TOTAL, NoCallInfo()) elseif la == 3 && istopfunction(f, :(>:)) # mark issupertype as a exact alias for issubtype # swap T1 and T2 arguments and call <: @@ -1567,49 +2015,48 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), fargs = nothing end argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] - return CallMeta(abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), sv, max_methods).rt, false) - elseif la == 2 && - (a2 = argtypes[2]; isa(a2, Const)) && (svecval = a2.val; isa(svecval, SimpleVector)) && - istopfunction(f, :length) - # mark length(::SimpleVector) as @pure - return CallMeta(Const(length(svecval)), MethodResultPure()) - elseif la == 3 && - (a2 = argtypes[2]; isa(a2, Const)) && (svecval = a2.val; isa(svecval, SimpleVector)) && - (a3 = argtypes[3]; isa(a3, Const)) && (idx = a3.val; isa(idx, Int)) && - istopfunction(f, :getindex) - # mark getindex(::SimpleVector, i::Int) as @pure - if 1 <= idx <= length(svecval) && isassigned(svecval, idx) - return CallMeta(Const(getindex(svecval, idx)), MethodResultPure()) - end + return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) elseif la == 2 && istopfunction(f, :typename) - return CallMeta(typename_static(argtypes[2]), MethodResultPure()) - elseif max_methods > 1 && istopfunction(f, :copyto!) - max_methods = 1 - elseif la == 3 && istopfunction(f, :typejoin) - val = pure_eval_call(f, argtypes) - return CallMeta(val === nothing ? Type : val, MethodResultPure()) + return CallMeta(typename_static(argtypes[2]), EFFECTS_TOTAL, MethodResultPure()) + elseif f === Core._hasmethod + return _hasmethod_tfunc(interp, argtypes, sv) end atype = argtypes_to_type(argtypes) - return abstract_call_gf_by_type(interp, f, arginfo, atype, sv, max_methods) + return abstract_call_gf_by_type(interp, f, arginfo, si, atype, sv, max_methods) end -function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::PartialOpaque, arginfo::ArgInfo, sv::InferenceState) +function abstract_call_opaque_closure(interp::AbstractInterpreter, + closure::PartialOpaque, arginfo::ArgInfo, si::StmtInfo, sv::InferenceState, check::Bool=true) sig = argtypes_to_type(arginfo.argtypes) - (; rt, edge) = result = abstract_call_method(interp, closure.source, sig, Core.svec(), false, sv) - edge !== nothing && add_backedge!(edge, sv) + result = abstract_call_method(interp, closure.source::Method, sig, Core.svec(), false, si, sv) + (; rt, edge, effects) = 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 if !result.edgecycle - const_result = abstract_call_method_with_const_args(interp, result, nothing, - arginfo, match, sv) - if const_result !== nothing - (;rt, const_result) = const_result + const_call_result = abstract_call_method_with_const_args(interp, result, + nothing, arginfo, si, match, sv) + if const_call_result !== nothing + if const_call_result.rt ⊑ₚ rt + (; rt, effects, const_result, edge) = const_call_result + end + end + end + if check # analyze implicit type asserts on argument and return type + ftt = closure.typ + (aty, rty) = (unwrap_unionall(ftt)::DataType).parameters + rty = rewrap_unionall(rty isa TypeVar ? rty.lb : rty, ftt) + if !(rt ⊑ₚ rty && tuple_tfunc(𝕃ₚ, arginfo.argtypes[2:end]) ⊑ₚ rewrap_unionall(aty, ftt)) + effects = Effects(effects; nothrow=false) end end + rt = from_interprocedural!(𝕃ₚ, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) - return CallMeta(from_interprocedural!(rt, sv, arginfo, match.spec_types), info) + edge !== nothing && add_backedge!(sv, edge) + return CallMeta(rt, effects, info) end function most_general_argtypes(closure::PartialOpaque) @@ -1619,42 +2066,47 @@ function most_general_argtypes(closure::PartialOpaque) if !isa(argt, DataType) || argt.name !== typename(Tuple) argt = Tuple end - return most_general_argtypes(closure.source, argt, false) + return Any[argt.parameters...] end # call where the function is any lattice element -function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, - sv::InferenceState, max_methods::Int = get_max_methods(sv.mod, interp)) +function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, si::StmtInfo, + sv::AbsIntState, max_methods::Union{Int, Nothing} = nothing) argtypes = arginfo.argtypes - ft = argtypes[1] + ft = widenslotwrapper(argtypes[1]) f = singleton_type(ft) - if isa(ft, PartialOpaque) - newargtypes = copy(argtypes) - newargtypes[1] = ft.env - tristate_merge!(sv, Effects()) # TODO - return abstract_call_opaque_closure(interp, ft, ArgInfo(arginfo.fargs, newargtypes), sv) - elseif (uft = unwrap_unionall(widenconst(ft)); isa(uft, DataType) && uft.name === typename(Core.OpaqueClosure)) - tristate_merge!(sv, Effects()) # TODO - return CallMeta(rewrap_unionall((uft::DataType).parameters[2], widenconst(ft)), false) - elseif f === nothing - # non-constant function, but the number of arguments is known - # and the ft is not a Builtin or IntrinsicFunction - if hasintersect(widenconst(ft), Union{Builtin, Core.OpaqueClosure}) - tristate_merge!(sv, Effects()) + if f === nothing + if isa(ft, PartialOpaque) + newargtypes = copy(argtypes) + newargtypes[1] = ft.env + return abstract_call_opaque_closure(interp, + ft, ArgInfo(arginfo.fargs, newargtypes), si, sv, #=check=#true) + end + wft = widenconst(ft) + if hasintersect(wft, Builtin) add_remark!(interp, sv, "Could not identify method table for call") - return CallMeta(Any, false) + return CallMeta(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()) + end + return CallMeta(Any, Effects(), NoCallInfo()) end - return abstract_call_gf_by_type(interp, nothing, arginfo, argtypes_to_type(argtypes), sv, max_methods) + # non-constant function, but the number of arguments is known and the `f` is not a builtin or intrinsic + max_methods = max_methods === nothing ? get_max_methods(interp, sv) : max_methods + return abstract_call_gf_by_type(interp, nothing, arginfo, si, argtypes_to_type(argtypes), sv, max_methods) end - return abstract_call_known(interp, f, arginfo, sv, max_methods) + max_methods = max_methods === nothing ? get_max_methods(interp, f, sv) : max_methods + return abstract_call_known(interp, f, arginfo, si, sv, max_methods) end function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool) isref = false - if T === Bottom + if unwrapva(T) === Bottom return Bottom elseif isa(T, Type) - if isa(T, DataType) && (T::DataType).name === _REF_NAME + if isa(T, DataType) && (T::DataType).name === Ref.body.name isref = true T = T.parameters[1] if isreturn && T === Any @@ -1685,48 +2137,95 @@ function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool) return unwraptv(T) end -function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::VarTable, sv::InferenceState) +function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) f = abstract_eval_value(interp, e.args[2], vtypes, sv) # rt = sp_type_rewrap(e.args[3], sv.linfo, true) - at = Any[ sp_type_rewrap(argt, sv.linfo, false) for argt in e.args[4]::SimpleVector ] - pushfirst!(at, f) + atv = e.args[4]::SimpleVector + at = Vector{Any}(undef, length(atv) + 1) + at[1] = f + for i = 1:length(atv) + at[i + 1] = sp_type_rewrap(at[i], frame_instance(sv), false) + at[i + 1] === Bottom && return + end # this may be the wrong world for the call, # but some of the result is likely to be valid anyways # and that may help generate better codegen - abstract_call(interp, ArgInfo(nothing, at), sv) + abstract_call(interp, ArgInfo(nothing, at), StmtInfo(false), sv) nothing end -function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::VarTable, sv::InferenceState) - if e.head === :static_parameter +function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) + rt = Any + head = e.head + if head === :static_parameter n = e.args[1]::Int - t = Any + nothrow = false if 1 <= n <= length(sv.sptypes) - t = sv.sptypes[n] + sp = sv.sptypes[n] + rt = sp.typ + nothrow = !sp.undef end - return t - elseif e.head === :boundscheck - return Bool - else - return Any + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow)) + return rt + elseif head === :boundscheck + if isa(sv, InferenceState) + stmt = sv.src.code[sv.currpc] + if isexpr(stmt, :call) + f = abstract_eval_value(interp, stmt.args[1], vtypes, sv) + if f isa Const && f.val === getfield + # boundscheck of `getfield` call is analyzed by tfunc potentially without + # tainting :inbounds or :consistent when it's known to be nothrow + @goto delay_effects_analysis + end + end + # If there is no particular `@inbounds` for this function, then we only taint `:noinbounds`, + # which will subsequently taint `:consistent`-cy if this function is called from another + # function that uses `@inbounds`. However, if this `:boundscheck` is itself within an + # `@inbounds` region, its value depends on `--check-bounds`, so we need to taint + # `:consistent`-cy here also. + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; noinbounds=false, + consistent = (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 ? ALWAYS_FALSE : ALWAYS_TRUE)) + end + @label delay_effects_analysis + rt = Bool + elseif head === :inbounds + @assert false && "Expected this to have been moved into flags" + elseif head === :the_exception + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) end + return rt end -function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) +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) - elseif isa(e, SlotNumber) || isa(e, Argument) - return vtypes[slot_id(e)].typ + elseif isa(e, SlotNumber) + if vtypes !== nothing + vtyp = vtypes[slot_id(e)] + if vtyp.undef + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false)) + end + return vtyp.typ + end + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false)) + return Any + elseif isa(e, Argument) + if vtypes !== nothing + return vtypes[slot_id(e)].typ + else + @assert isa(sv, IRInterpretationState) + return sv.ir.argtypes[e.n] # TODO frame_argtypes(sv)[e.n] and remove the assertion + end elseif isa(e, GlobalRef) - return abstract_eval_global(e.mod, e.name, sv) + return abstract_eval_globalref(interp, e, sv) end return Const(e) end -function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) +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) else @@ -1735,7 +2234,7 @@ function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtyp end end -function collect_argtypes(interp::AbstractInterpreter, ea::Vector{Any}, vtypes::VarTable, sv::InferenceState) +function collect_argtypes(interp::AbstractInterpreter, ea::Vector{Any}, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) n = length(ea) argtypes = Vector{Any}(undef, n) @inbounds for i = 1:n @@ -1748,151 +2247,179 @@ function collect_argtypes(interp::AbstractInterpreter, ea::Vector{Any}, vtypes:: return argtypes end -function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) - if !isa(e, Expr) - if isa(e, PhiNode) - rt = Union{} - for val in e.values - rt = tmerge(rt, abstract_eval_special_value(interp, val, vtypes, sv)) - end - return rt - end - return abstract_eval_special_value(interp, e, vtypes, sv) +struct RTEffects + rt + effects::Effects + RTEffects(@nospecialize(rt), effects::Effects) = new(rt, 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? + if is_removable_if_unused(effects) + add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) + else + sub_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) end - e = e::Expr + return RTEffects(rt, effects) +end + +function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + ea = e.args + argtypes = collect_argtypes(interp, ea, vtypes, sv) + if argtypes === nothing + return RTEffects(Bottom, Effects()) + end + arginfo = ArgInfo(ea, argtypes) + return abstract_call(interp, arginfo, sv) +end + +function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, + sv::AbsIntState) + effects = EFFECTS_UNKNOWN ehead = e.head + 𝕃ᵢ = typeinf_lattice(interp) + ⊑ᵢ = ⊑(𝕃ᵢ) if ehead === :call - ea = e.args - argtypes = collect_argtypes(interp, ea, vtypes, sv) - if argtypes === nothing - t = Bottom - else - callinfo = abstract_call(interp, ArgInfo(ea, argtypes), sv) - sv.stmt_info[sv.currpc] = callinfo.info - t = callinfo.rt - end + (; 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)) - is_nothrow = true - if isconcretedispatch(t) - fcount = fieldcount(t) + ut = unwrap_unionall(t) + consistent = ALWAYS_FALSE + nothrow = false + if isa(ut, DataType) && !isabstracttype(ut) + ismutable = ismutabletype(ut) + fcount = datatype_fieldcount(ut) nargs = length(e.args) - 1 - is_nothrow && (is_nothrow = fcount ≥ nargs) - ats = Vector{Any}(undef, nargs) - local anyrefine = false - local allconst = true - for i = 2:length(e.args) - at = widenconditional(abstract_eval_value(interp, e.args[i], vtypes, sv)) - ft = fieldtype(t, i-1) - is_nothrow && (is_nothrow = at ⊑ ft) - at = tmeet(at, ft) - if at === Bottom - t = Bottom - tristate_merge!(sv, Effects( - ALWAYS_TRUE, # N.B depends on !ismutabletype(t) above - ALWAYS_TRUE, ALWAYS_FALSE, ALWAYS_TRUE)) - @goto t_computed - elseif !isa(at, Const) - allconst = false - end - if !anyrefine - anyrefine = has_nontrivial_const_info(at) || # constant information - at ⋤ ft # just a type-level information, but more precise than the declared type + 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 `:consistent`-cy + consistent = ALWAYS_FALSE + elseif ismutable + # mutable object isn't `:consistent`, but we can still give the return + # type information a chance to refine this `:consistent`-cy later + consistent = CONSISTENT_IF_NOTRETURNED + else + consistent = 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 end - ats[i-1] = at - end - # For now, don't allow: - # - Const/PartialStruct of mutables - # - partially initialized Const/PartialStruct - if !ismutabletype(t) && fcount == nargs - if allconst - argvals = Vector{Any}(undef, nargs) - for j in 1:nargs - argvals[j] = (ats[j]::Const).val + # 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) end - t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs)) - elseif anyrefine - t = PartialStruct(t, ats) end + else + t = refine_partial_type(t) end - else - is_nothrow = false end - tristate_merge!(sv, Effects(EFFECTS_TOTAL, - consistent = !ismutabletype(t) ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)) + effects = Effects(EFFECTS_TOTAL; consistent, nothrow) elseif ehead === :splatnew t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv)) - is_nothrow = false # TODO: More precision - if length(e.args) == 2 && isconcretetype(t) && !ismutabletype(t) + 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->getfield(at.val::Tuple, i) isa fieldtype(t, i), 1:n); end - is_nothrow = isexact && isconcretedispatch(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) + end)) + nothrow = isexact t = Const(ccall(:jl_new_structt, Any, (Any, Any), t, at.val)) - elseif isa(at, PartialStruct) && at ⊑ Tuple && n == length(at.fields::Vector{Any}) && - let t = t, at = at; _all(i->(at.fields::Vector{Any})[i] ⊑ fieldtype(t, i), 1:n); end - is_nothrow = isexact && isconcretedispatch(t) + 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}) end + else + t = refine_partial_type(t) end - tristate_merge!(sv, Effects(EFFECTS_TOTAL, - consistent = ismutabletype(t) ? ALWAYS_FALSE : ALWAYS_TRUE, - nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)) + consistent = !ismutabletype(t) ? ALWAYS_TRUE : CONSISTENT_IF_NOTRETURNED + effects = Effects(EFFECTS_TOTAL; consistent, nothrow) elseif ehead === :new_opaque_closure - tristate_merge!(sv, Effects()) # TODO 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 - t = _opaque_closure_tfunc(argtypes[1], argtypes[2], argtypes[3], - argtypes[4], argtypes[5:end], sv.linfo) - if isa(t, PartialOpaque) + 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), sv) + ArgInfo(nothing, argtypes), StmtInfo(true), sv, #=check=#false) sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo) end end end elseif ehead === :foreigncall - abstract_eval_value(interp, e.args[1], vtypes, sv) - t = sp_type_rewrap(e.args[2], sv.linfo, true) - for i = 3:length(e.args) - if abstract_eval_value(interp, e.args[i], vtypes, sv) === Bottom - t = Bottom + (; rt, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv) + t = rt + if isa(sv, InferenceState) + # mark this call statement as DCE-elgible + if is_removable_if_unused(effects) + add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) + else + sub_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE) end end - cconv = e.args[5] - if isa(cconv, QuoteNode) && isa(cconv.value, Tuple{Symbol, UInt8}) - effects = cconv.value[2] - effects = decode_effects_override(effects) - tristate_merge!(sv, Effects( - effects.consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - effects.effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - effects.nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - effects.terminates_globally ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - )) - else - tristate_merge!(sv, Effects()) - end elseif ehead === :cfunction - tristate_merge!(sv, Effects()) + effects = EFFECTS_UNKNOWN t = e.args[1] isa(t, Type) || (t = Any) abstract_eval_cfunction(interp, e, vtypes, sv) elseif ehead === :method - tristate_merge!(sv, Effects()) t = (length(e.args) == 1) ? Any : Nothing + effects = EFFECTS_UNKNOWN elseif ehead === :copyast - tristate_merge!(sv, Effects()) + 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 @@ -1903,7 +2430,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), elseif ehead === :isdefined sym = e.args[1] t = Bool - if isa(sym, SlotNumber) + effects = EFFECTS_TOTAL + if isa(sym, SlotNumber) && vtypes !== nothing vtyp = vtypes[slot_id(sym)] if vtyp.typ === Bottom t = Const(false) # never assigned previously @@ -1911,85 +2439,231 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = Const(true) # definitely assigned previously end elseif isa(sym, Symbol) - if isdefined(sv.mod, sym) + if isdefined(frame_module(sv), sym) t = Const(true) + elseif InferenceParams(interp).assume_bindings_static + t = Const(false) end elseif isa(sym, GlobalRef) if isdefined(sym.mod, sym.name) t = Const(true) + elseif InferenceParams(interp).assume_bindings_static + t = Const(false) end - elseif isa(sym, Expr) && sym.head === :static_parameter + elseif isexpr(sym, :static_parameter) n = sym.args[1]::Int if 1 <= n <= length(sv.sptypes) - spty = sv.sptypes[n] - if isa(spty, Const) + sp = sv.sptypes[n] + if !sp.undef t = Const(true) + elseif sp.typ === Bottom + t = Const(false) end end end + elseif false + @label always_throw + t = Bottom + effects = EFFECTS_THROWS else t = abstract_eval_value_expr(interp, e, vtypes, sv) + effects = EFFECTS_TOTAL + end + return RTEffects(t, effects) +end + +# refine the result of instantiation of partially-known type `t` if some invariant can be assumed +function refine_partial_type(@nospecialize t) + t′ = unwrap_unionall(t) + if isa(t′, DataType) && t′.name === _NAMEDTUPLE_NAME && length(t′.parameters) == 2 && + (t′.parameters[1] === () || t′.parameters[2] === Tuple{}) + # 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()) + end + return t +end + +function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) + abstract_eval_value(interp, e.args[1], vtypes, sv) + mi = frame_instance(sv) + 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) + 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})) + override = decode_effects_override(v[2]) + effects = Effects( + override.consistent ? ALWAYS_TRUE : effects.consistent, + override.effect_free ? ALWAYS_TRUE : effects.effect_free, + override.nothrow ? true : effects.nothrow, + override.terminates_globally ? true : effects.terminates, + override.notaskstate ? true : effects.notaskstate, + override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, + effects.nonoverlayed, + effects.noinbounds) + end + return RTEffects(t, effects) +end + +function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) + rt = Union{} + 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)) end - @label t_computed - @assert !isa(t, TypeVar) "unhandled TypeVar" - if isa(t, DataType) && isdefined(t, :instance) - # replace singleton types with their equivalent Const object - t = Const(t.instance) + return rt +end + +function stmt_taints_inbounds_consistency(sv::AbsIntState) + propagate_inbounds(sv) && return true + return (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 +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) + 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.noinbounds + if !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; noinbounds=true) + end + if (get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) != 0 + effects = Effects(effects; consistent=ALWAYS_FALSE) + end end + merge_effects!(interp, sv, effects) + e = e::Expr + @assert !isa(rt, TypeVar) "unhandled TypeVar" + rt = maybe_singleton_const(rt) if !isempty(sv.pclimitations) - if t isa Const || t === Union{} + if rt isa Const || rt === Union{} empty!(sv.pclimitations) else - t = LimitedAccuracy(t, sv.pclimitations) + rt = LimitedAccuracy(rt, sv.pclimitations) sv.pclimitations = IdSet{InferenceState}() end end - return t + return rt end -function abstract_eval_global(M::Module, s::Symbol) - if isdefined(M,s) - if isconst(M,s) - return Const(getfield(M,s)) - end +function isdefined_globalref(g::GlobalRef) + return ccall(:jl_globalref_boundp, Cint, (Any,), g) != 0 +end + +function abstract_eval_globalref(g::GlobalRef) + if isdefined_globalref(g) && isconst(g) + return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) end - ty = ccall(:jl_binding_type, Any, (Any, Any), M, s) + ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name) ty === nothing && return Any return ty end - -function abstract_eval_global(M::Module, s::Symbol, frame::InferenceState) - ty = abstract_eval_global(M, s) - isa(ty, Const) && return ty - if isdefined(M,s) - tristate_merge!(frame, Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE)) - else - tristate_merge!(frame, Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE, nothrow=ALWAYS_FALSE)) +abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref(GlobalRef(M, s)) + +function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) + rt = abstract_eval_globalref(g) + consistent = inaccessiblememonly = ALWAYS_FALSE + nothrow = false + if isa(rt, Const) + consistent = ALWAYS_TRUE + if is_mutation_free_argtype(rt) + inaccessiblememonly = ALWAYS_TRUE + nothrow = true + else + nothrow = true + end + elseif isdefined_globalref(g) + nothrow = true + elseif InferenceParams(interp).assume_bindings_static + consistent = inaccessiblememonly = ALWAYS_TRUE + rt = Union{} end - return ty + merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) + return rt end -abstract_eval_ssavalue(s::SSAValue, sv::InferenceState) = abstract_eval_ssavalue(s, sv.src) -function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo) - typ = (src.ssavaluetypes::Vector{Any})[s.id] +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) + inaccessiblememonly = ALWAYS_FALSE + merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; effect_free, nothrow, inaccessiblememonly)) + return nothing +end + +abstract_eval_ssavalue(s::SSAValue, sv::InferenceState) = abstract_eval_ssavalue(s, sv.ssavaluetypes) + +function abstract_eval_ssavalue(s::SSAValue, ssavaluetypes::Vector{Any}) + typ = ssavaluetypes[s.id] if typ === NOT_FOUND return Bottom end return typ end -function widenreturn(@nospecialize(rt), @nospecialize(bestguess), nslots::Int, slottypes::Vector{Any}, changes::VarTable) - if !(bestguess ⊑ Bool) || bestguess === Bool +struct BestguessInfo{Interp<:AbstractInterpreter} + interp::Interp + bestguess + nargs::Int + slottypes::Vector{Any} + changes::VarTable + function BestguessInfo(interp::Interp, @nospecialize(bestguess), nargs::Int, + slottypes::Vector{Any}, changes::VarTable) where Interp<:AbstractInterpreter + new{Interp}(interp, bestguess, nargs, slottypes, changes) + end +end + +function widenreturn(@nospecialize(rt), info::BestguessInfo) + return widenreturn(typeinf_lattice(info.interp), rt, info) +end + +function widenreturn(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) + return widenreturn(widenlattice(𝕃ᵢ), rt, info) +end +function widenreturn_noslotwrapper(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) + return widenreturn_noslotwrapper(widenlattice(𝕃ᵢ), rt, info) +end + +function widenreturn(𝕃ᵢ::MustAliasesLattice, @nospecialize(rt), info::BestguessInfo) + if isa(rt, MustAlias) + if 1 ≤ rt.slot ≤ info.nargs + rt = InterMustAlias(rt) + else + rt = widenmustalias(rt) + end + end + isa(rt, InterMustAlias) && return rt + return widenreturn(widenlattice(𝕃ᵢ), rt, info) +end + +function widenreturn(𝕃ᵢ::ConditionalsLattice, @nospecialize(rt), info::BestguessInfo) + ⊑ᵢ = ⊑(𝕃ᵢ) + if !(⊑(ipo_lattice(info.interp), info.bestguess, Bool)) || info.bestguess === Bool # give up inter-procedural constraint back-propagation # when tmerge would widen the result anyways (as an optimization) rt = widenconditional(rt) else if isa(rt, Conditional) - id = slot_id(rt.var) - if 1 ≤ id ≤ nslots - old_id_type = widenconditional(slottypes[id]) # same as `(states[1]::VarTable)[id].typ` - if (!(rt.vtype ⊑ old_id_type) || old_id_type ⊑ rt.vtype) && - (!(rt.elsetype ⊑ old_id_type) || old_id_type ⊑ rt.elsetype) + id = rt.slot + if 1 ≤ id ≤ info.nargs + old_id_type = widenconditional(info.slottypes[id]) # same as `(states[1]::VarTable)[id].typ` + if (!(rt.thentype ⊑ᵢ old_id_type) || old_id_type ⊑ᵢ rt.thentype) && + (!(rt.elsetype ⊑ᵢ old_id_type) || old_id_type ⊑ᵢ rt.elsetype) # discard this `Conditional` since it imposes # no new constraint on the argument type # (the caller will recreate it if needed) @@ -2003,41 +2677,72 @@ function widenreturn(@nospecialize(rt), @nospecialize(bestguess), nslots::Int, s end end if isa(rt, Conditional) - rt = InterConditional(slot_id(rt.var), rt.vtype, rt.elsetype) - elseif is_lattice_bool(rt) - if isa(bestguess, InterConditional) - # if the bestguess so far is already `Conditional`, try to convert - # this `rt` into `Conditional` on the slot to avoid overapproximation - # due to conflict of different slots - rt = bool_rt_to_conditional(rt, slottypes, changes, bestguess.slot) - else - # pick up the first "interesting" slot, convert `rt` to its `Conditional` - # TODO: ideally we want `Conditional` and `InterConditional` to convey - # constraints on multiple slots - for slot_id in 1:nslots - rt = bool_rt_to_conditional(rt, slottypes, changes, slot_id) - rt isa InterConditional && break - end + rt = InterConditional(rt.slot, rt.thentype, rt.elsetype) + elseif is_lattice_bool(𝕃ᵢ, rt) + rt = bool_rt_to_conditional(rt, info) + end + end + if isa(rt, Conditional) + rt = InterConditional(rt) + end + isa(rt, InterConditional) && return rt + return widenreturn(widenlattice(𝕃ᵢ), rt, info) +end +function bool_rt_to_conditional(@nospecialize(rt), info::BestguessInfo) + bestguess = info.bestguess + if isa(bestguess, InterConditional) + # if the bestguess so far is already `Conditional`, try to convert + # this `rt` into `Conditional` on the slot to avoid overapproximation + # due to conflict of different slots + rt = bool_rt_to_conditional(rt, bestguess.slot, info) + else + # pick up the first "interesting" slot, convert `rt` to its `Conditional` + # TODO: ideally we want `Conditional` and `InterConditional` to convey + # constraints on multiple slots + for slot_id = 1:info.nargs + rt = bool_rt_to_conditional(rt, slot_id, info) + rt isa InterConditional && break + end + end + return rt +end +function bool_rt_to_conditional(@nospecialize(rt), slot_id::Int, info::BestguessInfo) + ⊑ᵢ = ⊑(typeinf_lattice(info.interp)) + old = info.slottypes[slot_id] + new = widenslotwrapper(info.changes[slot_id].typ) # avoid nested conditional + if new ⊑ᵢ old && !(old ⊑ᵢ new) + if isa(rt, Const) + val = rt.val + if val === true + return InterConditional(slot_id, new, Bottom) + elseif val === false + return InterConditional(slot_id, Bottom, new) end + elseif rt === Bool + return InterConditional(slot_id, new, new) end end + return rt +end - # only propagate information we know we can store - # and is valid and good inter-procedurally - isa(rt, Conditional) && return InterConditional(slot_id(rt.var), rt.vtype, rt.elsetype) - isa(rt, InterConditional) && return rt - isa(rt, Const) && return rt - isa(rt, Type) && return rt +function widenreturn(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) + return widenreturn_partials(𝕃ᵢ, rt, info) +end +function widenreturn_noslotwrapper(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) + return widenreturn_partials(𝕃ᵢ, rt, info) +end +function widenreturn_partials(𝕃ᵢ::PartialsLattice, @nospecialize(rt), info::BestguessInfo) if isa(rt, PartialStruct) fields = copy(rt.fields) local anyrefine = false + 𝕃 = typeinf_lattice(info.interp) for i in 1:length(fields) a = fields[i] - a = isvarargtype(a) ? a : widenreturn(a, bestguess, nslots, slottypes, changes) + a = isvarargtype(a) ? a : widenreturn_noslotwrapper(𝕃, a, info) if !anyrefine # TODO: consider adding && const_prop_profitable(a) here? - anyrefine = has_const_info(a) || - a ⊏ fieldtype(rt.typ, i) + anyrefine = has_extended_info(a) || + ⊏(𝕃, a, fieldtype(rt.typ, i)) end fields[i] = a end @@ -2046,284 +2751,351 @@ function widenreturn(@nospecialize(rt), @nospecialize(bestguess), nslots::Int, s if isa(rt, PartialOpaque) return rt # XXX: this case was missed in #39512 end + return widenreturn(widenlattice(𝕃ᵢ), rt, info) +end + +function widenreturn(::ConstsLattice, @nospecialize(rt), ::BestguessInfo) + return widenreturn_consts(rt) +end +function widenreturn_noslotwrapper(::ConstsLattice, @nospecialize(rt), ::BestguessInfo) + return widenreturn_consts(rt) +end +function widenreturn_consts(@nospecialize(rt)) + isa(rt, Const) && return rt return widenconst(rt) end -function handle_control_backedge!(frame::InferenceState, from::Int, to::Int) +function widenreturn(::JLTypeLattice, @nospecialize(rt), ::BestguessInfo) + return widenconst(rt) +end +function widenreturn_noslotwrapper(::JLTypeLattice, @nospecialize(rt), ::BestguessInfo) + return widenconst(rt) +end + +function handle_control_backedge!(interp::AbstractInterpreter, frame::InferenceState, from::Int, to::Int) if from > to - def = frame.linfo.def - if isa(def, Method) - effects = decode_effects_override(def.purity) - if effects.terminates_globally || effects.terminates_locally - return nothing - end + if is_effect_overridden(frame, :terminates_locally) + # this backedge is known to terminate + else + merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; terminates=false)) end - tristate_merge!(frame, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) end return nothing end +struct BasicStmtChange + changes::Union{Nothing,StateUpdate} + type::Any # ::Union{Type, Nothing} - `nothing` if this statement may not be used as an SSA Value + # TODO effects::Effects + BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize type) = new(changes, type) +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) + elseif !isa(stmt, Expr) + t = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, t) + 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) + end + lhs = stmt.args[1] + if isa(lhs, SlotNumber) + changes = StateUpdate(lhs, VarState(t, false), pc_vartable, false) + elseif isa(lhs, GlobalRef) + handle_global_assignment!(interp, frame, lhs, t) + elseif !isa(lhs, SSAValue) + merge_effects!(interp, frame, EFFECTS_UNKNOWN) + end + return BasicStmtChange(changes, t) + 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) + elseif (hd === :code_coverage_effect || ( + hd !== :boundscheck && # :boundscheck can be narrowed to Bool + is_meta_expr(stmt))) + return BasicStmtChange(nothing, Nothing) + else + t = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, t) + end +end + +function update_bbstate!(𝕃ᵢ::AbstractLattice, frame::InferenceState, bb::Int, vartable::VarTable) + bbtable = frame.bb_vartables[bb] + if bbtable === nothing + # if a basic block hasn't been analyzed yet, + # we can update its state a bit more aggressively + frame.bb_vartables[bb] = copy(vartable) + return true + else + return stupdate!(𝕃ᵢ, bbtable, vartable) + end +end + +function init_vartable!(vartable::VarTable, frame::InferenceState) + nargtypes = length(frame.result.argtypes) + for i = 1:length(vartable) + vartable[i] = VarState(Bottom, i > nargtypes) + end + return vartable +end + # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) - @assert !frame.inferred + @assert !is_inferred(frame) frame.dont_work_on_me = true # mark that this function is currently on the stack W = frame.ip - states = frame.stmt_types - n = frame.nstmts - nargs = frame.nargs - def = frame.linfo.def - isva = isa(def, Method) && def.isva - nslots = nargs - isva + nargs = narguments(frame, #=include_va=#false) slottypes = frame.slottypes - ssavaluetypes = frame.src.ssavaluetypes::Vector{Any} - while frame.pc´´ <= n - # make progress on the active ip set - local pc::Int = frame.pc´´ - while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 - local pc´::Int = pc + 1 # next program-counter (after executing instruction) - if pc == frame.pc´´ - # want to update pc´´ to point at the new lowest instruction in W - frame.pc´´ = pc´ - end - delete!(W, pc) - frame.currpc = pc - edges = frame.stmt_edges[pc] - edges === nothing || empty!(edges) - frame.stmt_info[pc] = nothing - stmt = frame.src.code[pc] - changes = states[pc]::VarTable - t = nothing - - hd = isa(stmt, Expr) ? stmt.head : nothing - - if isa(stmt, NewvarNode) - sn = slot_id(stmt.slot) - changes[sn] = VarState(Bottom, true) - elseif isa(stmt, GotoNode) - l = (stmt::GotoNode).label - handle_control_backedge!(frame, pc, l) - pc´ = l - elseif isa(stmt, GotoIfNot) - condx = stmt.cond - condt = abstract_eval_value(interp, condx, changes, frame) - if condt === Bottom - empty!(frame.pclimitations) - break - end - if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condx, 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)) - end - condval = maybe_extract_const_bool(condt) - l = stmt.dest::Int - if !isempty(frame.pclimitations) - # we can't model the possible effect of control - # dependencies on the return value, so we propagate it - # directly to all the return values (unless we error first) - condval isa Bool || union!(frame.limitations, frame.pclimitations) - empty!(frame.pclimitations) - end - # constant conditions - if condval === true - elseif condval === false - handle_control_backedge!(frame, pc, l) - pc´ = l - else - # general case - changes_else = changes - if isa(condt, Conditional) - changes_else = conditional_changes(changes_else, condt.elsetype, condt.var) - changes = conditional_changes(changes, condt.vtype, condt.var) + ssavaluetypes = frame.ssavaluetypes + bbs = frame.cfg.blocks + nbbs = length(bbs) + 𝕃ₚ, 𝕃ᵢ = ipo_lattice(interp), typeinf_lattice(interp) + + currbb = frame.currbb + if currbb != 1 + currbb = frame.currbb = _bits_findnext(W.bits, 1)::Int # next basic block + end + + states = frame.bb_vartables + currstate = copy(states[currbb]::VarTable) + while currbb <= nbbs + delete!(W, currbb) + bbstart = first(bbs[currbb].stmts) + bbend = last(bbs[currbb].stmts) + + for currpc in bbstart:bbend + frame.currpc = currpc + empty_backedges!(frame, currpc) + stmt = frame.src.code[currpc] + # If we're at the end of the basic block ... + if currpc == bbend + # Handle control flow + if isa(stmt, GotoNode) + succs = bbs[currbb].succs + @assert length(succs) == 1 + nextbb = succs[1] + ssavaluetypes[currpc] = Any + handle_control_backedge!(interp, frame, currpc, stmt.label) + @goto branch + elseif isa(stmt, GotoIfNot) + condx = stmt.cond + condt = abstract_eval_value(interp, condx, currstate, frame) + if condt === Bottom + ssavaluetypes[currpc] = Bottom + empty!(frame.pclimitations) + @goto find_next_bb end - newstate_else = stupdate!(states[l], changes_else) - if newstate_else !== nothing - handle_control_backedge!(frame, pc, l) - # add else branch to active IP list - if l < frame.pc´´ - frame.pc´´ = l - end - push!(W, l) - states[l] = newstate_else + orig_condt = condt + if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condx, 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)) end - end - elseif isa(stmt, ReturnNode) - pc´ = n + 1 - bestguess = frame.bestguess - rt = abstract_eval_value(interp, stmt.val, changes, frame) - rt = widenreturn(rt, bestguess, nslots, slottypes, changes) - # narrow representation of bestguess slightly to prepare for tmerge with rt - if rt isa InterConditional && bestguess isa Const - let slot_id = rt.slot - old_id_type = slottypes[slot_id] - if bestguess.val === true && rt.elsetype !== Bottom - bestguess = InterConditional(slot_id, old_id_type, Bottom) - elseif bestguess.val === false && rt.vtype !== Bottom - bestguess = InterConditional(slot_id, Bottom, old_id_type) - end + condval = maybe_extract_const_bool(condt) + if !isempty(frame.pclimitations) + # we can't model the possible effect of control + # dependencies on the return + # directly to all the return values (unless we error first) + condval isa Bool || union!(frame.limitations, frame.pclimitations) + empty!(frame.pclimitations) end - end - # copy limitations to return value - if !isempty(frame.pclimitations) - union!(frame.limitations, frame.pclimitations) - empty!(frame.pclimitations) - end - if !isempty(frame.limitations) - rt = LimitedAccuracy(rt, copy(frame.limitations)) - end - if tchanged(rt, bestguess) - # new (wider) return type for frame - bestguess = tmerge(bestguess, rt) - # TODO: if bestguess isa InterConditional && !interesting(bestguess); bestguess = widenconditional(bestguess); end - frame.bestguess = bestguess - for (caller, caller_pc) in frame.cycle_backedges - # notify backedges of updated type information - typeassert(caller.stmt_types[caller_pc], VarTable) # we must have visited this statement before - if !((caller.src.ssavaluetypes::Vector{Any})[caller_pc] === Any) - # no reason to revisit if that call-site doesn't affect the final result - if caller_pc < caller.pc´´ - caller.pc´´ = caller_pc + ssavaluetypes[currpc] = Any + if condval === true + @goto fallthrough + else + succs = bbs[currbb].succs + if length(succs) == 1 + @assert condval === false || (stmt.dest === currpc + 1) + nextbb = succs[1] + @goto branch + end + @assert length(succs) == 2 + truebb = currbb + 1 + falsebb = succs[1] == truebb ? succs[2] : succs[1] + if condval === false + nextbb = falsebb + handle_control_backedge!(interp, frame, currpc, stmt.dest) + @goto branch + else + if !⊑(𝕃ᵢ, orig_condt, Bool) + merge_effects!(interp, frame, EFFECTS_THROWS) + if !hasintersect(widenconst(orig_condt), Bool) + ssavaluetypes[currpc] = Bottom + @goto find_next_bb + end + end + + # We continue with the true branch, but process the false + # branch here. + if isa(condt, Conditional) + else_change = conditional_change(𝕃ᵢ, currstate, condt.elsetype, condt.slot) + if else_change !== nothing + false_vartable = stoverwrite1!(copy(currstate), else_change) + else + false_vartable = currstate + end + changed = update_bbstate!(𝕃ᵢ, frame, falsebb, false_vartable) + then_change = conditional_change(𝕃ᵢ, currstate, condt.thentype, condt.slot) + if then_change !== nothing + stoverwrite1!(currstate, then_change) + end + else + changed = update_bbstate!(𝕃ᵢ, frame, falsebb, currstate) end - push!(caller.ip, caller_pc) + if changed + handle_control_backedge!(interp, frame, currpc, stmt.dest) + push!(W, falsebb) + end + @goto fallthrough end end - end - elseif hd === :enter - stmt = stmt::Expr - l = stmt.args[1]::Int - # propagate type info to exception handler - old = states[l] - newstate_catch = stupdate!(old, changes) - if newstate_catch !== nothing - if l < frame.pc´´ - frame.pc´´ = l - end - push!(W, l) - states[l] = newstate_catch - end - typeassert(states[l], VarTable) - elseif hd === :leave - else - if hd === :(=) - stmt = stmt::Expr - t = abstract_eval_statement(interp, stmt.args[2], changes, frame) - if t === Bottom - break + elseif isa(stmt, ReturnNode) + bestguess = frame.bestguess + rt = abstract_eval_value(interp, stmt.val, currstate, frame) + rt = widenreturn(rt, BestguessInfo(interp, bestguess, nargs, slottypes, currstate)) + # narrow representation of bestguess slightly to prepare for tmerge with rt + if rt isa InterConditional && bestguess isa Const + let slot_id = rt.slot + old_id_type = slottypes[slot_id] + if bestguess.val === true && rt.elsetype !== Bottom + bestguess = InterConditional(slot_id, old_id_type, Bottom) + elseif bestguess.val === false && rt.thentype !== Bottom + bestguess = InterConditional(slot_id, Bottom, old_id_type) + end + end end - ssavaluetypes[pc] = t - lhs = stmt.args[1] - if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(t, false), changes, false) - elseif isa(lhs, GlobalRef) - tristate_merge!(frame, Effects(EFFECTS_TOTAL, - effect_free=ALWAYS_FALSE, - nothrow=TRISTATE_UNKNOWN)) - elseif !isa(lhs, SSAValue) - tristate_merge!(frame, Effects()) + # copy limitations to return value + if !isempty(frame.pclimitations) + union!(frame.limitations, frame.pclimitations) + empty!(frame.pclimitations) end - elseif hd === :method - stmt = stmt::Expr - fname = stmt.args[1] - if isa(fname, SlotNumber) - changes = StateUpdate(fname, VarState(Any, false), changes, false) + if !isempty(frame.limitations) + rt = LimitedAccuracy(rt, copy(frame.limitations)) end - elseif hd === :code_coverage_effect || - (hd !== :boundscheck && # :boundscheck can be narrowed to Bool - hd !== nothing && is_meta_expr_head(hd)) - # these do not generate code - else - t = abstract_eval_statement(interp, stmt, changes, frame) - if t === Bottom - break + if !⊑(𝕃ₚ, rt, bestguess) + # new (wider) return type for frame + bestguess = tmerge(𝕃ₚ, bestguess, rt) + # TODO: if bestguess isa InterConditional && !interesting(bestguess); bestguess = widenconditional(bestguess); end + frame.bestguess = bestguess + 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 + end end - if !isempty(frame.ssavalue_uses[pc]) - record_ssa_assign(pc, t, frame) - else - ssavaluetypes[pc] = t + 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) end + ssavaluetypes[currpc] = Any + @goto fallthrough end - if isa(changes, StateUpdate) - let cur_hand = frame.handler_at[pc], l, enter - while cur_hand != 0 - enter = frame.src.code[cur_hand] - l = (enter::Expr).args[1]::Int - # propagate new type info to exception handler - # the handling for Expr(:enter) propagates all changes from before the try/catch - # so this only needs to propagate any changes - if stupdate1!(states[l]::VarTable, changes::StateUpdate) !== false - if l < frame.pc´´ - frame.pc´´ = l - end - push!(W, l) - end - cur_hand = frame.handler_at[cur_hand] + # Fall through terminator - treat as regular stmt + end + # Process non control-flow statements + (; changes, type) = abstract_eval_basic_statement(interp, + stmt, currstate, frame) + if type === Bottom + ssavaluetypes[currpc] = Bottom + @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 + ssavaluetypes[currpc] = Any + continue + end + if !isempty(frame.ssavalue_uses[currpc]) + record_ssa_assign!(𝕃ᵢ, currpc, type, frame) + else + ssavaluetypes[currpc] = type + end + end # for currpc in bbstart:bbend - @assert isempty(frame.pclimitations) "unhandled LimitedAccuracy" + # Case 1: Fallthrough termination + begin @label fallthrough + nextbb = currbb + 1 + end - if t === nothing - # mark other reached expressions as `Any` to indicate they don't throw - ssavaluetypes[pc] = Any + # Case 2: Directly branch to a different BB + begin @label branch + if update_bbstate!(𝕃ᵢ, frame, nextbb, currstate) + push!(W, nextbb) end + end - pc´ > n && break # can't proceed with the fast-path fall-through - newstate = stupdate!(states[pc´], changes) - if isa(stmt, GotoNode) && frame.pc´´ < pc´ - # if we are processing a goto node anyways, - # (such as a terminator for a loop, if-else, or try block), - # consider whether we should jump to an older backedge first, - # to try to traverse the statements in approximate dominator order - if newstate !== nothing - states[pc´] = newstate - end - push!(W, pc´) - break - elseif newstate !== nothing - states[pc´] = newstate - pc = pc´ - elseif pc´ in W - pc = pc´ + # Case 3: Control flow ended along the current path (converged, return or throw) + begin @label find_next_bb + currbb = frame.currbb = _bits_findnext(W.bits, 1)::Int # next basic block + currbb == -1 && break # the working set is empty + currbb > nbbs && break + + nexttable = states[currbb] + if nexttable === nothing + init_vartable!(currstate, frame) else - break + stoverwrite!(currstate, nexttable) end end - frame.pc´´ = _bits_findnext(W.bits, frame.pc´´)::Int # next program-counter - end + end # while currbb <= nbbs + frame.dont_work_on_me = false nothing end -function conditional_changes(changes::VarTable, @nospecialize(typ), var::SlotNumber) - oldtyp = changes[slot_id(var)].typ - # approximate test for `typ ∩ oldtyp` being better than `oldtyp` - # since we probably formed these types with `typesubstract`, the comparison is likely simple - if ignorelimited(typ) ⊑ ignorelimited(oldtyp) - # typ is better unlimited, but we may still need to compute the tmeet with the limit "causes" since we ignored those in the comparison - oldtyp isa LimitedAccuracy && (typ = tmerge(typ, LimitedAccuracy(Bottom, oldtyp.causes))) - return StateUpdate(var, VarState(typ, false), changes, true) +function conditional_change(𝕃ᵢ::AbstractLattice, state::VarTable, @nospecialize(typ), slot::Int) + vtype = state[slot] + oldtyp = vtype.typ + if iskindtype(typ) + # this code path corresponds to the special handling for `isa(x, iskindtype)` check + # implemented within `abstract_call_builtin` + elseif ⊑(𝕃ᵢ, ignorelimited(typ), ignorelimited(oldtyp)) + # approximate test for `typ ∩ oldtyp` being better than `oldtyp` + # since we probably formed these types with `typesubstract`, + # the comparison is likely simple + else + return nothing end - return changes -end - -function bool_rt_to_conditional(@nospecialize(rt), slottypes::Vector{Any}, state::VarTable, slot_id::Int) - old = slottypes[slot_id] - new = widenconditional(state[slot_id].typ) # avoid nested conditional - if new ⊑ old && !(old ⊑ new) - if isa(rt, Const) - val = rt.val - if val === true - return InterConditional(slot_id, new, Bottom) - elseif val === false - return InterConditional(slot_id, Bottom, new) - end - elseif rt === Bool - return InterConditional(slot_id, new, new) - end + if oldtyp isa LimitedAccuracy + # typ is better unlimited, but we may still need to compute the tmeet with the limit + # "causes" since we ignored those in the comparison + typ = tmerge(𝕃ᵢ, typ, LimitedAccuracy(Bottom, oldtyp.causes)) end - return rt + return StateUpdate(SlotNumber(slot), VarState(typ, vtype.undef), state, true) end # make as much progress on `frame` as possible (by handling cycles) @@ -2336,14 +3108,14 @@ function typeinf_nocycle(interp::AbstractInterpreter, frame::InferenceState) no_active_ips_in_callers = true for caller in frame.callers_in_cycle caller.dont_work_on_me && return false # cycle is above us on the stack - if caller.pc´´ <= caller.nstmts # equivalent to `isempty(caller.ip)` + if !isempty(caller.ip) # Note that `typeinf_local(interp, caller)` can potentially modify the other frames # `frame.callers_in_cycle`, which is why making incremental progress requires the # outer while loop. typeinf_local(interp, caller) no_active_ips_in_callers = false end - caller.valid_worlds = intersect(caller.valid_worlds, frame.valid_worlds) + update_valid_age!(caller, frame.valid_worlds) end end return true diff --git a/base/compiler/abstractlattice.jl b/base/compiler/abstractlattice.jl new file mode 100644 index 0000000000000..a84050816cb21 --- /dev/null +++ b/base/compiler/abstractlattice.jl @@ -0,0 +1,321 @@ +# TODO add more documentations + +function widenlattice end +function is_valid_lattice_norec end + +""" + struct JLTypeLattice <: AbstractLattice + +A singleton type representing the lattice of Julia types, without any inference extensions. +""" +struct JLTypeLattice <: AbstractLattice; end +widenlattice(::JLTypeLattice) = error("Type lattice is the least-precise lattice available") +is_valid_lattice_norec(::JLTypeLattice, @nospecialize(elem)) = isa(elem, Type) + +""" + struct ConstsLattice <: AbstractLattice + +A lattice extending `JLTypeLattice` and adjoining `Const` and `PartialTypeVar`. +""" +struct ConstsLattice <: AbstractLattice; end +widenlattice(::ConstsLattice) = JLTypeLattice() +is_valid_lattice_norec(::ConstsLattice, @nospecialize(elem)) = isa(elem, Const) || isa(elem, PartialTypeVar) + +""" + struct PartialsLattice{𝕃<:AbstractLattice} <: AbstractLattice + +A lattice extending a base lattice `𝕃` and adjoining `PartialStruct` and `PartialOpaque`. +""" +struct PartialsLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +widenlattice(𝕃::PartialsLattice) = 𝕃.parent +is_valid_lattice_norec(::PartialsLattice, @nospecialize(elem)) = isa(elem, PartialStruct) || isa(elem, PartialOpaque) + +""" + struct ConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + +A lattice extending a base lattice `𝕃` and adjoining `Conditional`. +""" +struct ConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +widenlattice(𝕃::ConditionalsLattice) = 𝕃.parent +is_valid_lattice_norec(::ConditionalsLattice, @nospecialize(elem)) = isa(elem, Conditional) + +""" + struct InterConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + +A lattice extending a base lattice `𝕃` and adjoining `InterConditional`. +""" +struct InterConditionalsLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +widenlattice(𝕃::InterConditionalsLattice) = 𝕃.parent +is_valid_lattice_norec(::InterConditionalsLattice, @nospecialize(elem)) = isa(elem, InterConditional) + +""" + struct MustAliasesLattice{𝕃<:AbstractLattice} + +A lattice extending lattice `𝕃` and adjoining `MustAlias`. +""" +struct MustAliasesLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +widenlattice(𝕃::MustAliasesLattice) = 𝕃.parent +is_valid_lattice_norec(::MustAliasesLattice, @nospecialize(elem)) = isa(elem, MustAlias) + +""" + struct InterMustAliasesLattice{𝕃<:AbstractLattice} + +A lattice extending lattice `𝕃` and adjoining `InterMustAlias`. +""" +struct InterMustAliasesLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +widenlattice(𝕃::InterMustAliasesLattice) = 𝕃.parent +is_valid_lattice_norec(::InterMustAliasesLattice, @nospecialize(elem)) = isa(elem, InterMustAlias) + +const AnyConditionalsLattice{𝕃<:AbstractLattice} = Union{ConditionalsLattice{𝕃}, InterConditionalsLattice{𝕃}} +const AnyMustAliasesLattice{𝕃<:AbstractLattice} = Union{MustAliasesLattice{𝕃}, InterMustAliasesLattice{𝕃}} + +const SimpleInferenceLattice = typeof(PartialsLattice(ConstsLattice())) +const BaseInferenceLattice = typeof(ConditionalsLattice(SimpleInferenceLattice.instance)) +const IPOResultLattice = typeof(InterConditionalsLattice(SimpleInferenceLattice.instance)) + +""" + struct InferenceLattice{𝕃<:AbstractLattice} <: AbstractLattice + +The full lattice used for abstract interpretation during inference. +Extends a base lattice `𝕃` and adjoins `LimitedAccuracy`. +""" +struct InferenceLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +widenlattice(𝕃::InferenceLattice) = 𝕃.parent +is_valid_lattice_norec(::InferenceLattice, @nospecialize(elem)) = isa(elem, LimitedAccuracy) + +""" + struct OptimizerLattice{𝕃<:AbstractLattice} <: AbstractLattice + +The lattice used by the optimizer. +Extends a base lattice `𝕃` and adjoins `MaybeUndef`. +""" +struct OptimizerLattice{𝕃<:AbstractLattice} <: AbstractLattice + parent::𝕃 +end +OptimizerLattice() = OptimizerLattice(SimpleInferenceLattice.instance) +widenlattice(𝕃::OptimizerLattice) = 𝕃.parent +is_valid_lattice_norec(::OptimizerLattice, @nospecialize(elem)) = isa(elem, MaybeUndef) + +""" + tmeet(𝕃::AbstractLattice, a, b::Type) + +Compute the lattice meet of lattice elements `a` and `b` over the lattice `𝕃`. +If `𝕃` is `JLTypeLattice`, this is equivalent to type intersection. +Note that currently `b` is restricted to being a type +(interpreted as a lattice element in the `JLTypeLattice` sub-lattice of `𝕃`). +""" +function tmeet end + +function tmeet(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) + ti = typeintersect(a, b) + valid_as_lattice(ti) || return Bottom + return ti +end + +""" + tmerge(𝕃::AbstractLattice, a, b) + +Compute a lattice join of elements `a` and `b` over the lattice `𝕃`. +Note that the computed element need not be the least upper bound of `a` and +`b`, but rather, we impose additional limitations on the complexity of the +joined element, ideally without losing too much precision in common cases and +remaining mostly associative and commutative. +""" +function tmerge end + +""" + tmerge_field(𝕃::AbstractLattice, a, b) -> nothing or lattice element + +Compute a lattice join of elements `a` and `b` over the lattice `𝕃`, +where `a` and `b` are fields of `PartialStruct` or `Const`. +This is an opt-in interface to allow external lattice implementation to provide its own +field-merge strategy. If it returns `nothing`, `tmerge(::PartialsLattice, ...)` +will use the default aggressive type merge implementation that does not use `tmerge` +recursively to reach convergence. +""" +function tmerge_field end + +function tmerge_field(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) + return tmerge_field(widenlattice(𝕃), a, b) +end +tmerge_field(::JLTypeLattice, @nospecialize(a), @nospecialize(b)) = nothing + +""" + ⊑(𝕃::AbstractLattice, a, b) + +Compute the lattice ordering (i.e. less-than-or-equal) relationship between +lattice elements `a` and `b` over the lattice `𝕃`. +If `𝕃` is `JLTypeLattice`, this is equivalent to subtyping. +""" +function ⊑ end + +⊑(::JLTypeLattice, @nospecialize(a::Type), @nospecialize(b::Type)) = a <: b + +""" + ⊏(𝕃::AbstractLattice, a, b) -> Bool + +The strict partial order over the type inference lattice. +This is defined as the irreflexive kernel of `⊑`. +""" +⊏(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = ⊑(𝕃, a, b) && !⊑(𝕃, b, a) + +""" + ⋤(𝕃::AbstractLattice, a, b) -> Bool + +This order could be used as a slightly more efficient version of the strict order `⊏`, +where we can safely assume `a ⊑ b` holds. +""" +⋤(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) = !⊑(𝕃, b, a) + +""" + is_lattice_equal(𝕃::AbstractLattice, a, b) -> Bool + +Check if two lattice elements are partial order equivalent. +This is basically `a ⊑ b && b ⊑ a` in the lattice of `𝕃` +but (optionally) with extra performance optimizations. +""" +function is_lattice_equal(𝕃::AbstractLattice, @nospecialize(a), @nospecialize(b)) + a === b && return true + return ⊑(𝕃, a, b) && ⊑(𝕃, b, a) +end + +""" + has_nontrivial_extended_info(𝕃::AbstractLattice, t) -> Bool + +Determines whether the given lattice element `t` of `𝕃` has non-trivial extended lattice +information that would not be available from the type itself. +""" +has_nontrivial_extended_info(𝕃::AbstractLattice, @nospecialize t) = + has_nontrivial_extended_info(widenlattice(𝕃), t) +function has_nontrivial_extended_info(𝕃::PartialsLattice, @nospecialize t) + isa(t, PartialStruct) && return true + isa(t, PartialOpaque) && return true + return has_nontrivial_extended_info(widenlattice(𝕃), t) +end +function has_nontrivial_extended_info(𝕃::ConstsLattice, @nospecialize t) + isa(t, PartialTypeVar) && return true + if isa(t, Const) + val = t.val + return !issingletontype(typeof(val)) && !(isa(val, Type) && hasuniquerep(val)) + end + return has_nontrivial_extended_info(widenlattice(𝕃), t) +end +has_nontrivial_extended_info(::JLTypeLattice, @nospecialize(t)) = false + +""" + is_const_prop_profitable_arg(𝕃::AbstractLattice, t) -> Bool + +Determines whether the given lattice element `t` of `𝕃` has new extended lattice information +that should be forwarded along with constant propagation. +""" +is_const_prop_profitable_arg(𝕃::AbstractLattice, @nospecialize t) = + is_const_prop_profitable_arg(widenlattice(𝕃), t) +function is_const_prop_profitable_arg(𝕃::PartialsLattice, @nospecialize t) + if isa(t, PartialStruct) + return true # might be a bit aggressive, may want to enable some check like follows: + # for i = 1:length(t.fields) + # fld = t.fields[i] + # isconstType(fld) && return true + # is_const_prop_profitable_arg(fld) && return true + # fld ⊏ fieldtype(t.typ, i) && return true + # end + # return false + end + isa(t, PartialOpaque) && return true + return is_const_prop_profitable_arg(widenlattice(𝕃), t) +end +function is_const_prop_profitable_arg(𝕃::ConstsLattice, @nospecialize t) + if isa(t, Const) + # don't consider mutable values useful constants + val = t.val + return isa(val, Symbol) || isa(val, Type) || !ismutable(val) + end + isa(t, PartialTypeVar) && return false # this isn't forwardable + return is_const_prop_profitable_arg(widenlattice(𝕃), t) +end +is_const_prop_profitable_arg(::JLTypeLattice, @nospecialize t) = false + +is_forwardable_argtype(𝕃::AbstractLattice, @nospecialize(x)) = + is_forwardable_argtype(widenlattice(𝕃), x) +function is_forwardable_argtype(𝕃::ConditionalsLattice, @nospecialize x) + isa(x, Conditional) && return true + return is_forwardable_argtype(widenlattice(𝕃), x) +end +function is_forwardable_argtype(𝕃::PartialsLattice, @nospecialize x) + isa(x, PartialStruct) && return true + isa(x, PartialOpaque) && return true + return is_forwardable_argtype(widenlattice(𝕃), x) +end +function is_forwardable_argtype(𝕃::ConstsLattice, @nospecialize x) + isa(x, Const) && return true + return is_forwardable_argtype(widenlattice(𝕃), x) +end +function is_forwardable_argtype(::JLTypeLattice, @nospecialize x) + return false +end + +""" + widenreturn(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) -> new_bestguess + widenreturn_noslotwrapper(𝕃ᵢ::AbstractLattice, @nospecialize(rt), info::BestguessInfo) -> new_bestguess + +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. + +External lattice `𝕃ᵢ::ExternalLattice` may overload: +- `widenreturn(𝕃ᵢ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` +- `widenreturn_noslotwrapper(𝕃ᵢ::ExternalLattice, @nospecialize(rt), info::BestguessInfo)` +""" +function widenreturn end, function widenreturn_noslotwrapper end + +is_valid_lattice(𝕃::AbstractLattice, @nospecialize(elem)) = + is_valid_lattice_norec(𝕃, elem) && is_valid_lattice(widenlattice(𝕃), elem) +is_valid_lattice(𝕃::JLTypeLattice, @nospecialize(elem)) = is_valid_lattice_norec(𝕃, elem) + +has_conditional(𝕃::AbstractLattice) = has_conditional(widenlattice(𝕃)) +has_conditional(::AnyConditionalsLattice) = true +has_conditional(::JLTypeLattice) = false + +has_mustalias(𝕃::AbstractLattice) = has_mustalias(widenlattice(𝕃)) +has_mustalias(::AnyMustAliasesLattice) = true +has_mustalias(::JLTypeLattice) = false + +has_extended_unionsplit(𝕃::AbstractLattice) = has_extended_unionsplit(widenlattice(𝕃)) +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) + +# Fallbacks for external packages using these methods +const fallback_lattice = InferenceLattice(BaseInferenceLattice.instance) +const fallback_ipo_lattice = InferenceLattice(IPOResultLattice.instance) + +⊑(@nospecialize(a), @nospecialize(b)) = ⊑(fallback_lattice, a, b) +tmeet(@nospecialize(a), @nospecialize(b)) = tmeet(fallback_lattice, a, b) +tmerge(@nospecialize(a), @nospecialize(b)) = tmerge(fallback_lattice, a, b) +⊏(@nospecialize(a), @nospecialize(b)) = ⊏(fallback_lattice, a, b) +⋤(@nospecialize(a), @nospecialize(b)) = ⋤(fallback_lattice, a, b) +is_lattice_equal(@nospecialize(a), @nospecialize(b)) = is_lattice_equal(fallback_lattice, a, b) + +# Widenlattice with argument +widenlattice(::JLTypeLattice, @nospecialize(t)) = widenconst(t) +function widenlattice(𝕃::AbstractLattice, @nospecialize(t)) + is_valid_lattice_norec(𝕃, t) && return t + widenlattice(widenlattice(𝕃), t) +end diff --git a/base/compiler/bootstrap.jl b/base/compiler/bootstrap.jl index f335cf31a8467..1f62d21c9d2d9 100644 --- a/base/compiler/bootstrap.jl +++ b/base/compiler/bootstrap.jl @@ -7,9 +7,7 @@ time() = ccall(:jl_clock_now, Float64, ()) -let - world = get_world_counter() - interp = NativeInterpreter(world) +let interp = NativeInterpreter() analyze_escapes_tt = Tuple{typeof(analyze_escapes), IRCode, Int, Bool, typeof(null_escape_cache)} fs = Any[ @@ -38,8 +36,9 @@ let else tt = Tuple{typeof(f), Vararg{Any}} end - for m in _methods_by_ftype(tt, 10, typemax(UInt)) + for m in _methods_by_ftype(tt, 10, get_world_counter())::Vector # remove any TypeVars from the intersection + m = m::MethodMatch typ = Any[m.spec_types.parameters...] for i = 1:length(typ) typ[i] = unwraptv(typ[i]) diff --git a/base/compiler/cicache.jl b/base/compiler/cicache.jl index 294b1f0055f79..8332777e6d5bc 100644 --- a/base/compiler/cicache.jl +++ b/base/compiler/cicache.jl @@ -7,11 +7,11 @@ 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 end function setindex!(cache::InternalCodeCache, ci::CodeInstance, mi::MethodInstance) ccall(:jl_mi_cache_insert, Cvoid, (Any, Any), mi, ci) + return cache end const GLOBAL_CI_CACHE = InternalCodeCache() @@ -49,11 +49,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) - ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, first(wvc.worlds), last(wvc.worlds))::Union{Nothing, CodeInstance} !== nothing + return ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), 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))::Union{Nothing, CodeInstance} + r = ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, first(wvc.worlds), last(wvc.worlds)) if r === nothing return default end @@ -66,5 +66,7 @@ function getindex(wvc::WorldView{InternalCodeCache}, mi::MethodInstance) return r::CodeInstance end -setindex!(wvc::WorldView{InternalCodeCache}, ci::CodeInstance, mi::MethodInstance) = +function setindex!(wvc::WorldView{InternalCodeCache}, ci::CodeInstance, mi::MethodInstance) setindex!(wvc.cache, ci, mi) + return wvc +end diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 18232b37008f6..0a1b852b052f9 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -6,7 +6,7 @@ using Core.Intrinsics, Core.IR import Core: print, println, show, write, unsafe_write, stdout, stderr, _apply_iterate, svec, apply_type, Builtin, IntrinsicFunction, - MethodInstance, CodeInstance, MethodMatch, PartialOpaque, + MethodInstance, CodeInstance, MethodTable, MethodMatch, PartialOpaque, TypeofVararg const getproperty = Core.getfield @@ -31,6 +31,9 @@ macro noinline() Expr(:meta, :noinline) 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 +abstract type MethodTableView end + # essential files and libraries include("essentials.jl") include("ctypes.jl") @@ -38,9 +41,16 @@ include("generator.jl") include("reflection.jl") include("options.jl") +ntuple(f, ::Val{0}) = () +ntuple(f, ::Val{1}) = (@inline; (f(1),)) +ntuple(f, ::Val{2}) = (@inline; (f(1), f(2))) +ntuple(f, ::Val{3}) = (@inline; (f(1), f(2), f(3))) +ntuple(f, ::Val{n}) where {n} = ntuple(f, n::Int) +ntuple(f, n) = (Any[f(i) for i = 1:n]...,) + # core operations & types function return_type end # promotion.jl expects this to exist -is_return_type(@Core.nospecialize(f)) = f === return_type +is_return_type(Core.@nospecialize(f)) = f === return_type include("promotion.jl") include("tuple.jl") include("pair.jl") @@ -58,6 +68,25 @@ include("operators.jl") include("pointer.jl") include("refvalue.jl") +# the same constructor as defined in float.jl, but with a different name to avoid redefinition +_Bool(x::Real) = x==0 ? false : x==1 ? true : throw(InexactError(:Bool, Bool, x)) +# fld(x,y) == div(x,y) - ((x>=0) != (y>=0) && rem(x,y) != 0 ? 1 : 0) +fld(x::T, y::T) where {T<:Unsigned} = div(x, y) +function fld(x::T, y::T) where T<:Integer + d = div(x, y) + return d - (signbit(x ⊻ y) & (d * y != x)) +end +# cld(x,y) = div(x,y) + ((x>0) == (y>0) && rem(x,y) != 0 ? 1 : 0) +function cld(x::T, y::T) where T<:Unsigned + d = div(x, y) + return d + (d * y != x) +end +function cld(x::T, y::T) where T<:Integer + d = div(x, y) + return d + (((x > 0) == (y > 0)) & (d * y != x)) +end + + # checked arithmetic const checked_add = + const checked_sub = - @@ -89,27 +118,15 @@ using .Iterators: zip, enumerate using .Iterators: Flatten, Filter, product # for generators include("namedtuple.jl") -ntuple(f, ::Val{0}) = () -ntuple(f, ::Val{1}) = (@inline; (f(1),)) -ntuple(f, ::Val{2}) = (@inline; (f(1), f(2))) -ntuple(f, ::Val{3}) = (@inline; (f(1), f(2), f(3))) -ntuple(f, ::Val{n}) where {n} = ntuple(f, n::Int) -ntuple(f, n) = (Any[f(i) for i = 1:n]...,) - # core docsystem include("docs/core.jl") import Core.Compiler.CoreDocs Core.atdoc!(CoreDocs.docm) # sorting -function sort end -function sort! end -function issorted end -function sortperm end include("ordering.jl") using .Order -include("sort.jl") -using .Sort +include("compiler/sort.jl") # We don't include some.jl, but this definition is still useful. something(x::Nothing, y...) = something(y...) @@ -120,11 +137,21 @@ something(x::Any, y...) = x ############ include("compiler/cicache.jl") +include("compiler/methodtable.jl") +include("compiler/effects.jl") include("compiler/types.jl") include("compiler/utilities.jl") include("compiler/validation.jl") -include("compiler/methodtable.jl") +function argextype end # imported by EscapeAnalysis +function stmt_effect_free end # imported by EscapeAnalysis +function alloc_array_ndims end # imported by EscapeAnalysis +function try_compute_field end # imported by EscapeAnalysis +include("compiler/ssair/basicblock.jl") +include("compiler/ssair/domtree.jl") +include("compiler/ssair/ir.jl") + +include("compiler/abstractlattice.jl") include("compiler/inferenceresult.jl") include("compiler/inferencestate.jl") @@ -136,20 +163,7 @@ include("compiler/stmtinfo.jl") include("compiler/abstractinterpretation.jl") include("compiler/typeinfer.jl") -include("compiler/optimize.jl") # TODO: break this up further + extract utilities - -# required for bootstrap -# TODO: find why this is needed and remove it. -function extrema(x::Array) - isempty(x) && throw(ArgumentError("collection must be non-empty")) - vmin = vmax = x[1] - for i in 2:length(x) - xi = x[i] - vmax = max(vmax, xi) - vmin = min(vmin, xi) - end - return vmin, vmax -end +include("compiler/optimize.jl") include("compiler/bootstrap.jl") ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel) diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl new file mode 100644 index 0000000000000..ec64b7601bc76 --- /dev/null +++ b/base/compiler/effects.jl @@ -0,0 +1,265 @@ +""" + effects::Effects + +Represents computational effects of a method call. + +The effects are a composition of different effect bits that represent some program property +of the method being analyzed. They are represented as `Bool` or `UInt8` bits with the +following meanings: +- `effects.consistent::UInt8`: + * `ALWAYS_TRUE`: this method is guaranteed to return or terminate consistently. + * `ALWAYS_FALSE`: this method may be not return or terminate consistently, and there is + no need for further analysis with respect to this effect property as this conclusion + will not be refined anyway. + * `CONSISTENT_IF_NOTRETURNED`: the `:consistent`-cy of this method can later be refined to + `ALWAYS_TRUE` in a case when the return value of this method never involves newly + allocated mutable objects. + * `CONSISTENT_IF_INACCESSIBLEMEMONLY`: the `:consistent`-cy of this method can later be + refined to `ALWAYS_TRUE` in a case when `:inaccessiblememonly` is proven. +- `effect_free::UInt8`: + * `ALWAYS_TRUE`: this method is free from externally semantically visible side effects. + * `ALWAYS_FALSE`: this method may not be free from externally semantically visible side effects, and there is + no need for further analysis with respect to this effect property as this conclusion + will not be refined anyway. + * `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. +- `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 + behavior. Note that this currently implies that `noyield` as well, since + yielding modifies the state of the current task, though this may be split + in the future. +- `inaccessiblememonly::UInt8`: + * `ALWAYS_TRUE`: this method does not access or modify externally accessible mutable memory. + This state corresponds to LLVM's `inaccessiblememonly` function attribute. + * `ALWAYS_FALSE`: this method may access or modify externally accessible mutable memory. + * `INACCESSIBLEMEM_OR_ARGMEMONLY`: this method does not access or modify externally accessible mutable memory, + 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. +- `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 +on the definitions of these properties. + +Along the abstract interpretation, `Effects` at each statement are analyzed locally and they +are merged into the single global `Effects` that represents the entire effects of the +analyzed method (see the implementation of `merge_effects!`). Each effect property is +initialized with `ALWAYS_TRUE`/`true` and then transitioned towards `ALWAYS_FALSE`/`false`. +Note that within the current flow-insensitive analysis design, effects detected by local +analysis on each statement usually taint the global conclusion conservatively. + +## Key for `show` output of Effects: + +The output represents the state of different effect properties in the following order: + +1. `consistent` (`c`): + - `+c` (green): `ALWAYS_TRUE` + - `-c` (red): `ALWAYS_FALSE` + - `?c` (yellow): `CONSISTENT_IF_NOTRETURNED` and/or `CONSISTENT_IF_INACCESSIBLEMEMONLY` +2. `effect_free` (`e`): + - `+e` (green): `ALWAYS_TRUE` + - `-e` (red): `ALWAYS_FALSE` + - `?e` (yellow): `EFFECT_FREE_IF_INACCESSIBLEMEMONLY` +3. `nothrow` (`n`): + - `+n` (green): `true` + - `-n` (red): `false` +4. `terminates` (`t`): + - `+t` (green): `true` + - `-t` (red): `false` +5. `notaskstate` (`s`): + - `+s` (green): `true` + - `-s` (red): `false` +6. `inaccessiblememonly` (`m`): + - `+m` (green): `ALWAYS_TRUE` + - `-m` (red): `ALWAYS_FALSE` + - `?m` (yellow): `INACCESSIBLEMEM_OR_ARGMEMONLY` +7. `noinbounds` (`i`): + - `+i` (green): `true` + - `-i` (red): `false` + +Additionally, if the `nonoverlayed` property is false, a red prime symbol (′) is displayed after the tuple. +""" +struct Effects + consistent::UInt8 + effect_free::UInt8 + nothrow::Bool + terminates::Bool + notaskstate::Bool + inaccessiblememonly::UInt8 + nonoverlayed::Bool + noinbounds::Bool + function Effects( + consistent::UInt8, + effect_free::UInt8, + nothrow::Bool, + terminates::Bool, + notaskstate::Bool, + inaccessiblememonly::UInt8, + nonoverlayed::Bool, + noinbounds::Bool) + return new( + consistent, + effect_free, + nothrow, + terminates, + notaskstate, + inaccessiblememonly, + nonoverlayed, + noinbounds) + end +end + +const ALWAYS_TRUE = 0x00 +const ALWAYS_FALSE = 0x01 + +# :consistent-cy bits +const CONSISTENT_IF_NOTRETURNED = 0x01 << 1 +const CONSISTENT_IF_INACCESSIBLEMEMONLY = 0x01 << 2 + +# :effect_free-ness bits +const EFFECT_FREE_IF_INACCESSIBLEMEMONLY = 0x01 << 1 + +# :inaccessiblememonly bits +const INACCESSIBLEMEM_OR_ARGMEMONLY = 0x01 << 1 + +const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, true, true) +const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, true, true) +const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, true, false) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) +const _EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, false, false) # unknown really + +function Effects(e::Effects = _EFFECTS_UNKNOWN; + consistent::UInt8 = e.consistent, + effect_free::UInt8 = e.effect_free, + nothrow::Bool = e.nothrow, + terminates::Bool = e.terminates, + notaskstate::Bool = e.notaskstate, + inaccessiblememonly::UInt8 = e.inaccessiblememonly, + nonoverlayed::Bool = e.nonoverlayed, + noinbounds::Bool = e.noinbounds) + return Effects( + consistent, + effect_free, + nothrow, + terminates, + notaskstate, + inaccessiblememonly, + nonoverlayed, + noinbounds) +end + +function merge_effects(old::Effects, new::Effects) + return Effects( + merge_effectbits(old.consistent, new.consistent), + merge_effectbits(old.effect_free, new.effect_free), + merge_effectbits(old.nothrow, new.nothrow), + merge_effectbits(old.terminates, new.terminates), + merge_effectbits(old.notaskstate, new.notaskstate), + merge_effectbits(old.inaccessiblememonly, new.inaccessiblememonly), + merge_effectbits(old.nonoverlayed, new.nonoverlayed), + merge_effectbits(old.noinbounds, new.noinbounds)) +end + +function merge_effectbits(old::UInt8, new::UInt8) + if old === ALWAYS_FALSE || new === ALWAYS_FALSE + return ALWAYS_FALSE + end + return old | new +end +merge_effectbits(old::Bool, new::Bool) = old & new + +is_consistent(effects::Effects) = effects.consistent === ALWAYS_TRUE +is_effect_free(effects::Effects) = effects.effect_free === ALWAYS_TRUE +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_nonoverlayed(effects::Effects) = effects.nonoverlayed + +# implies `is_notaskstate` & `is_inaccessiblememonly`, but not explicitly checked here +is_foldable(effects::Effects) = + is_consistent(effects) && + is_effect_free(effects) && + is_terminates(effects) + +is_foldable_nothrow(effects::Effects) = + is_foldable(effects) && + is_nothrow(effects) + +is_removable_if_unused(effects::Effects) = + is_effect_free(effects) && + is_terminates(effects) && + is_nothrow(effects) + +is_finalizer_inlineable(effects::Effects) = + is_nothrow(effects) && + is_notaskstate(effects) + +is_consistent_if_notreturned(effects::Effects) = !iszero(effects.consistent & CONSISTENT_IF_NOTRETURNED) +is_consistent_if_inaccessiblememonly(effects::Effects) = !iszero(effects.consistent & CONSISTENT_IF_INACCESSIBLEMEMONLY) + +is_effect_free_if_inaccessiblememonly(effects::Effects) = !iszero(effects.effect_free & EFFECT_FREE_IF_INACCESSIBLEMEMONLY) + +is_inaccessiblemem_or_argmemonly(effects::Effects) = effects.inaccessiblememonly === INACCESSIBLEMEM_OR_ARGMEMONLY + +function encode_effects(e::Effects) + return ((e.consistent % UInt32) << 0) | + ((e.effect_free % UInt32) << 3) | + ((e.nothrow % UInt32) << 5) | + ((e.terminates % UInt32) << 6) | + ((e.notaskstate % UInt32) << 7) | + ((e.inaccessiblememonly % UInt32) << 8) | + ((e.nonoverlayed % UInt32) << 10)| + ((e.noinbounds % UInt32) << 11) +end + +function decode_effects(e::UInt32) + return Effects( + UInt8((e >> 0) & 0x07), + UInt8((e >> 3) & 0x03), + _Bool((e >> 5) & 0x01), + _Bool((e >> 6) & 0x01), + _Bool((e >> 7) & 0x01), + UInt8((e >> 8) & 0x03), + _Bool((e >> 10) & 0x01), + _Bool((e >> 11) & 0x01)) +end + +struct EffectsOverride + consistent::Bool + effect_free::Bool + nothrow::Bool + terminates_globally::Bool + terminates_locally::Bool + notaskstate::Bool + inaccessiblememonly::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)) + return e +end + +function decode_effects_override(e::UInt8) + 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) +end diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index 8e3d3d5917fd0..3a96b21d7c40a 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -1,64 +1,92 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -function is_argtype_match(@nospecialize(given_argtype), - @nospecialize(cache_argtype), - overridden_by_const::Bool) - if is_forwardable_argtype(given_argtype) - return is_lattice_equal(given_argtype, cache_argtype) - end - return !overridden_by_const +""" + 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)) end -function is_forwardable_argtype(@nospecialize x) - return isa(x, Const) || - isa(x, Conditional) || - isa(x, PartialStruct) || - isa(x, PartialOpaque) +struct SimpleArgtypes <: ForwardableArgtypes + argtypes::Vector{Any} end -# 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( - linfo::MethodInstance, (arginfo, sv)#=::Tuple{ArgInfo,InferenceState}=#) - (; fargs, argtypes) = arginfo - @assert isa(linfo.def, Method) # ensure the next line works - nargs::Int = linfo.def.nargs - cache_argtypes, overridden_by_const = matching_cache_argtypes(linfo, nothing) +""" + matching_cache_argtypes(𝕃::AbstractLattice, linfo::MethodInstance, argtypes::SimpleArgtypes) + +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) + (; argtypes) = simple_argtypes given_argtypes = Vector{Any}(undef, length(argtypes)) - local condargs = nothing - for i in 1:length(argtypes) - argtype = argtypes[i] - # forward `Conditional` if it conveys a constraint on any other argument - if isa(argtype, Conditional) && fargs !== nothing - cnd = argtype - slotid = find_constrained_arg(cnd, fargs, sv) - if slotid !== nothing - # using union-split signature, we may be able to narrow down `Conditional` - sigt = widenconst(slotid > nargs ? argtypes[slotid] : cache_argtypes[slotid]) - vtype = tmeet(cnd.vtype, sigt) - elsetype = tmeet(cnd.elsetype, sigt) - if vtype === Bottom && elsetype === Bottom - # we accidentally proved this method match is impossible - # TODO bail out here immediately rather than just propagating Bottom ? - given_argtypes[i] = Bottom - else - if condargs === nothing - condargs = Tuple{Int,Int}[] - end - push!(condargs, (slotid, i)) - given_argtypes[i] = Conditional(SlotNumber(slotid), vtype, elsetype) - end - continue - end + 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) +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) + 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` + cache_argtypes[i] = given_argtype + overridden_by_const[i] = true end - given_argtypes[i] = widenconditional(argtype) end - isva = linfo.def.isva + return cache_argtypes, overridden_by_const +end + +function is_argtype_match(𝕃::AbstractLattice, + @nospecialize(given_argtype), + @nospecialize(cache_argtype), + overridden_by_const::Bool) + if is_forwardable_argtype(𝕃, given_argtype) + return is_lattice_equal(𝕃, given_argtype, cache_argtype) + 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(@nospecialize(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) if isva || isvarargtype(given_argtypes[end]) isva_given_argtypes = Vector{Any}(undef, nargs) - for i = 1:(nargs - isva) + for i = 1:(nargs-isva) isva_given_argtypes[i] = argtype_by_index(given_argtypes, i) end if isva @@ -67,29 +95,13 @@ function matching_cache_argtypes( else last = nargs end - isva_given_argtypes[nargs] = tuple_tfunc(given_argtypes[last:end]) - # invalidate `Conditional` imposed on varargs - if condargs !== nothing - for (slotid, i) in condargs - if slotid ≥ last - isva_given_argtypes[i] = widenconditional(isva_given_argtypes[i]) - end - end - end + isva_given_argtypes[nargs] = tuple_tfunc(𝕃, given_argtypes[last:end]) + va_handler!(isva_given_argtypes, last) end - given_argtypes = isva_given_argtypes + return isva_given_argtypes end - @assert length(given_argtypes) == nargs - for i in 1:nargs - 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` - cache_argtypes[i] = given_argtype - overridden_by_const[i] = true - end - end - return cache_argtypes, overridden_by_const + @assert length(given_argtypes) == nargs "invalid `given_argtypes` for `mi`" + return given_argtypes end function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(specTypes), @@ -98,18 +110,16 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe isva = !toplevel && method.isva linfo_argtypes = Any[(unwrap_unionall(specTypes)::DataType).parameters...] nargs::Int = toplevel ? 0 : method.nargs - if !withfirst - # For opaque closure, the closure environment is processed elsewhere - nargs -= 1 - end + # 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. if !toplevel && isva - if specTypes == Tuple + if specTypes::Type == Tuple + linfo_argtypes = Any[Any for i = 1:nargs] if nargs > 1 - linfo_argtypes = Any[Any for i = 1:nargs] - linfo_argtypes[end] = Vararg{Any} + linfo_argtypes[end] = Tuple end vargtype = Tuple else @@ -131,14 +141,14 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe end for i in 1:length(vargtype_elements) atyp = vargtype_elements[i] - if isa(atyp, DataType) && isdefined(atyp, :instance) + if issingletontype(atyp) # replace singleton types with their equivalent Const object vargtype_elements[i] = Const(atyp.instance) elseif isconstType(atyp) vargtype_elements[i] = Const(atyp.parameters[1]) end end - vargtype = tuple_tfunc(vargtype_elements) + vargtype = tuple_tfunc(fallback_lattice, vargtype_elements) end end cache_argtypes[nargs] = vargtype @@ -160,7 +170,7 @@ function most_general_argtypes(method::Union{Method, Nothing}, @nospecialize(spe tail_index -= 1 end atyp = unwraptv(atyp) - if isa(atyp, DataType) && isdefined(atyp, :instance) + if issingletontype(atyp) # replace singleton types with their equivalent Const object atyp = Const(atyp.instance) elseif isconstType(atyp) @@ -193,13 +203,7 @@ function elim_free_typevars(@nospecialize t) end end -function matching_cache_argtypes(linfo::MethodInstance, ::Nothing) - mthd = isa(linfo.def, Method) ? linfo.def::Method : nothing - cache_argtypes = most_general_argtypes(mthd, linfo.specTypes) - return cache_argtypes, falses(length(cache_argtypes)) -end - -function cache_lookup(linfo::MethodInstance, given_argtypes::Vector{Any}, cache::Vector{InferenceResult}) +function cache_lookup(lattice::AbstractLattice, linfo::MethodInstance, given_argtypes::Vector{Any}, cache::Vector{InferenceResult}) method = linfo.def::Method nargs::Int = method.nargs method.isva && (nargs -= 1) @@ -210,7 +214,7 @@ function cache_lookup(linfo::MethodInstance, given_argtypes::Vector{Any}, cache: cache_argtypes = cached_result.argtypes cache_overridden_by_const = cached_result.overridden_by_const for i in 1:nargs - if !is_argtype_match(given_argtypes[i], + if !is_argtype_match(lattice, widenmustalias(given_argtypes[i]), cache_argtypes[i], cache_overridden_by_const[i]) cache_match = false @@ -218,7 +222,7 @@ function cache_lookup(linfo::MethodInstance, given_argtypes::Vector{Any}, cache: end end if method.isva && cache_match - cache_match = is_argtype_match(tuple_tfunc(given_argtypes[(nargs + 1):end]), + cache_match = is_argtype_match(lattice, tuple_tfunc(lattice, given_argtypes[(nargs + 1):end]), cache_argtypes[end], cache_overridden_by_const[end]) end diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 17539f7621c74..97a7ed66ab9b5 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -1,15 +1,190 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -const LineNum = Int +# data structures +# =============== + +mutable struct BitSetBoundedMinPrioritySet <: AbstractSet{Int} + elems::BitSet + min::Int + # Stores whether min is exact or a lower bound + # If exact, it is not set in elems + min_exact::Bool + max::Int +end + +function BitSetBoundedMinPrioritySet(max::Int) + bs = BitSet() + bs.offset = 0 + BitSetBoundedMinPrioritySet(bs, max+1, true, max) +end + +@noinline function _advance_bsbmp!(bsbmp::BitSetBoundedMinPrioritySet) + @assert !bsbmp.min_exact + bsbmp.min = _bits_findnext(bsbmp.elems.bits, bsbmp.min)::Int + bsbmp.min < 0 && (bsbmp.min = bsbmp.max + 1) + bsbmp.min_exact = true + delete!(bsbmp.elems, bsbmp.min) + return nothing +end + +function isempty(bsbmp::BitSetBoundedMinPrioritySet) + if bsbmp.min > bsbmp.max + return true + end + bsbmp.min_exact && return false + _advance_bsbmp!(bsbmp) + return bsbmp.min > bsbmp.max +end + +function popfirst!(bsbmp::BitSetBoundedMinPrioritySet) + bsbmp.min_exact || _advance_bsbmp!(bsbmp) + m = bsbmp.min + m > bsbmp.max && throw(ArgumentError("BitSetBoundedMinPrioritySet must be non-empty")) + bsbmp.min = m+1 + bsbmp.min_exact = false + return m +end + +function push!(bsbmp::BitSetBoundedMinPrioritySet, idx::Int) + if idx <= bsbmp.min + if bsbmp.min_exact && bsbmp.min < bsbmp.max && idx != bsbmp.min + push!(bsbmp.elems, bsbmp.min) + end + bsbmp.min = idx + bsbmp.min_exact = true + return nothing + end + push!(bsbmp.elems, idx) + return nothing +end -# The type of a variable load is either a value or an UndefVarError -# (only used in abstractinterpret, doesn't appear in optimize) -struct VarState - typ - undef::Bool - VarState(@nospecialize(typ), undef::Bool) = new(typ, undef) +function in(idx::Int, bsbmp::BitSetBoundedMinPrioritySet) + if bsbmp.min_exact && idx == bsbmp.min + return true + end + return idx in bsbmp.elems end +function append!(bsbmp::BitSetBoundedMinPrioritySet, itr) + for val in itr + push!(bsbmp, val) + end +end + +mutable struct TwoPhaseVectorView <: AbstractVector{Int} + const data::Vector{Int} + count::Int + const range::UnitRange{Int} +end +size(tpvv::TwoPhaseVectorView) = (tpvv.count,) +function getindex(tpvv::TwoPhaseVectorView, i::Int) + checkbounds(tpvv, i) + @inbounds tpvv.data[first(tpvv.range) + i - 1] +end +function push!(tpvv::TwoPhaseVectorView, v::Int) + tpvv.count += 1 + tpvv.data[first(tpvv.range) + tpvv.count - 1] = v + return nothing +end + +""" + mutable struct TwoPhaseDefUseMap + +This struct is intended as a memory- and GC-pressure-efficient mechanism +for incrementally computing def-use maps. The idea is that the def-use map +is constructed into two passes over the IR. In the first, we simply count the +the number of uses, computing the number of uses for each def as well as the +total number of uses. In the second pass, we actually fill in the def-use +information. + +The idea is that either of these two phases can be combined with other useful +work that needs to scan the instruction stream anyway, while avoiding the +significant allocation pressure of e.g. allocating an array for every SSA value +or attempting to dynamically move things around as new uses are discovered. + +The def-use map is presented as a vector of vectors. For every def, indexing +into the map will return a vector of uses. +""" +mutable struct TwoPhaseDefUseMap <: AbstractVector{TwoPhaseVectorView} + ssa_uses::Vector{Int} + data::Vector{Int} + complete::Bool +end + +function complete!(tpdum::TwoPhaseDefUseMap) + cumsum = 0 + for i = 1:length(tpdum.ssa_uses) + this_val = cumsum + 1 + cumsum += tpdum.ssa_uses[i] + tpdum.ssa_uses[i] = this_val + end + resize!(tpdum.data, cumsum) + fill!(tpdum.data, 0) + tpdum.complete = true +end + +function TwoPhaseDefUseMap(nssas::Int) + ssa_uses = zeros(Int, nssas) + data = Int[] + complete = false + return TwoPhaseDefUseMap(ssa_uses, data, complete) +end + +function count!(tpdum::TwoPhaseDefUseMap, arg::SSAValue) + @assert !tpdum.complete + tpdum.ssa_uses[arg.id] += 1 +end + +function kill_def_use!(tpdum::TwoPhaseDefUseMap, def::Int, use::Int) + if !tpdum.complete + tpdum.ssa_uses[def] -= 1 + else + range = tpdum.ssa_uses[def]:(def == length(tpdum.ssa_uses) ? length(tpdum.data) : (tpdum.ssa_uses[def + 1] - 1)) + # TODO: Sorted + useidx = findfirst(idx->tpdum.data[idx] == use, range) + @assert useidx !== nothing + idx = range[useidx] + while idx < lastindex(range) + ndata = tpdum.data[idx+1] + ndata == 0 && break + tpdum.data[idx] = ndata + end + tpdum.data[idx + 1] = 0 + end +end +kill_def_use!(tpdum::TwoPhaseDefUseMap, def::SSAValue, use::Int) = + kill_def_use!(tpdum, def.id, use) + +function getindex(tpdum::TwoPhaseDefUseMap, idx::Int) + @assert tpdum.complete + range = tpdum.ssa_uses[idx]:(idx == length(tpdum.ssa_uses) ? length(tpdum.data) : (tpdum.ssa_uses[idx + 1] - 1)) + # TODO: Make logarithmic + nelems = 0 + for i in range + tpdum.data[i] == 0 && break + nelems += 1 + end + return TwoPhaseVectorView(tpdum.data, nelems, range) +end + +mutable struct LazyGenericDomtree{IsPostDom} + ir::IRCode + domtree::GenericDomTree{IsPostDom} + LazyGenericDomtree{IsPostDom}(ir::IRCode) where {IsPostDom} = new{IsPostDom}(ir) +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) +end + +const LazyDomtree = LazyGenericDomtree{false} +const LazyPostDomtree = LazyGenericDomtree{true} + +# InferenceState +# ============== + """ const VarTable = Vector{VarState} @@ -21,144 +196,125 @@ to enable flow-sensitive analysis. const VarTable = Vector{VarState} mutable struct InferenceState - params::InferenceParams - result::InferenceResult # remember where to put the result + #= information about this method instance =# linfo::MethodInstance - sptypes::Vector{Any} # types of static parameter - slottypes::Vector{Any} + world::UInt mod::Module - currpc::LineNum + sptypes::Vector{VarState} + slottypes::Vector{Any} + src::CodeInfo + cfg::CFG + method_info::MethodInfo + + #= intermediate states for local abstract interpretation =# + currbb::Int + currpc::Int + ip::BitSet#=TODO BoundedMinPrioritySet=# # current active instruction pointers + handler_at::Vector{Int} # current exception handler info + 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 + ssavaluetypes::Vector{Any} + stmt_edges::Vector{Union{Nothing,Vector{Any}}} + stmt_info::Vector{CallInfo} + + #= intermediate states for interprocedural abstract interpretation =# pclimitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on currpc ssavalue limitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on return - - # info on the state of inference and the linfo - src::CodeInfo - world::UInt - valid_worlds::WorldRange - nargs::Int - stmt_types::Vector{Union{Nothing, VarTable}} - stmt_edges::Vector{Union{Nothing, Vector{Any}}} - stmt_info::Vector{Any} - # return type - bestguess #::Type - # current active instruction pointers - ip::BitSet - pc´´::LineNum - nstmts::Int - # current exception handler info - handler_at::Vector{LineNum} - # ssavalue sparsity and restart info - ssavalue_uses::Vector{BitSet} - - cycle_backedges::Vector{Tuple{InferenceState, LineNum}} # call-graph backedges connecting from callee to caller + cycle_backedges::Vector{Tuple{InferenceState, Int}} # call-graph backedges connecting from callee to caller callers_in_cycle::Vector{InferenceState} - parent::Union{Nothing, InferenceState} - - # TODO: move these to InferenceResult / Params? - cached::Bool - inferred::Bool dont_work_on_me::Bool + parent # ::Union{Nothing,AbsIntState} - # Inferred purity flags + #= results =# + result::InferenceResult # remember where to put the result + valid_worlds::WorldRange + bestguess #::Type ipo_effects::Effects - # The place to look up methods while working on this function. - # In particular, we cache method lookup results for the same function to - # fast path repeated queries. - method_table::CachedMethodTable{InternalMethodTable} + #= 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? + insert_coverage::Bool # The interpreter that created this inference state. Not looked at by # NativeInterpreter. But other interpreters may use this to detect cycles 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, interp::AbstractInterpreter) - (; def) = linfo = result.linfo + function InferenceState(result::InferenceResult, src::CodeInfo, cache::Symbol, + interp::AbstractInterpreter) + linfo = result.linfo + world = get_world_counter(interp) + def = linfo.def + mod = isa(def, Method) ? def.module : def + sptypes = sptypes_from_meth_instance(linfo) code = src.code::Vector{Any} + cfg = compute_basic_blocks(code) + method_info = MethodInfo(src) - params = InferenceParams(interp) - - sp = sptypes_from_meth_instance(linfo::MethodInstance) - + currbb = currpc = 1 + ip = BitSet(1) # TODO BitSetBoundedMinPrioritySet(1) + handler_at = compute_trycatch(code, BitSet()) nssavalues = src.ssavaluetypes::Int - src.ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ] - stmt_info = Any[ nothing for i = 1:length(code) ] - - n = length(code) - s_types = Union{Nothing, VarTable}[ nothing for i = 1:n ] - s_edges = Union{Nothing, Vector{Any}}[ nothing for i = 1:n ] + ssavalue_uses = find_ssavalue_uses(code, nssavalues) + nstmts = length(code) + stmt_edges = Union{Nothing, Vector{Any}}[ nothing for i = 1:nstmts ] + stmt_info = CallInfo[ NoCallInfo() for i = 1:nstmts ] - # initial types nslots = length(src.slotflags) - argtypes = result.argtypes - nargs = length(argtypes) - s_argtypes = VarTable(undef, nslots) slottypes = Vector{Any}(undef, nslots) - for i in 1:nslots - at = (i > nargs) ? Bottom : argtypes[i] - s_argtypes[i] = VarState(at, i > nargs) - slottypes[i] = at + bb_vartables = Union{Nothing,VarTable}[ nothing for i = 1:length(cfg.blocks) ] + bb_vartable1 = bb_vartables[1] = VarTable(undef, nslots) + argtypes = result.argtypes + nargtypes = length(argtypes) + for i = 1:nslots + argtyp = (i > nargtypes) ? Bottom : argtypes[i] + slottypes[i] = argtyp + bb_vartable1[i] = VarState(argtyp, i > nargtypes) + end + src.ssavaluetypes = ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ] + + pclimitations = IdSet{InferenceState}() + limitations = IdSet{InferenceState}() + cycle_backedges = Vector{Tuple{InferenceState,Int}}() + callers_in_cycle = Vector{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) + bestguess = Bottom + ipo_effects = EFFECTS_TOTAL + + insert_coverage = should_insert_coverage(mod, src) + if insert_coverage + ipo_effects = Effects(ipo_effects; effect_free = ALWAYS_FALSE) end - s_types[1] = s_argtypes - - ssavalue_uses = find_ssavalue_uses(code, nssavalues) - - # exception handlers - ip = BitSet() - handler_at = compute_trycatch(src.code, ip) - push!(ip, 1) - # `throw` block deoptimization - params.unoptimize_throw_blocks && mark_throw_blocks!(src, handler_at) + restrict_abstract_call_sites = isa(linfo.def, Module) + @assert cache === :no || cache === :local || cache === :global + cached = cache === :global - mod = isa(def, Method) ? def.module : def - valid_worlds = WorldRange(src.min_world, - src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) - - # TODO: Currently, any :inbounds declaration taints consistency, - # because we cannot be guaranteed whether or not boundschecks - # will be eliminated and if they are, we cannot be guaranteed - # that no undefined behavior will occur (the effects assumptions - # are stronger than the inbounds assumptions, since the latter - # requires dynamic reachability, while the former is global). - inbounds = inbounds_option() - inbounds_taints_consistency = !(inbounds === :on || (inbounds === :default && !any_inbounds(code))) - consistent = inbounds_taints_consistency ? TRISTATE_UNKNOWN : ALWAYS_TRUE + # some more setups + InferenceParams(interp).unoptimize_throw_blocks && mark_throw_blocks!(src, handler_at) + cache !== :no && push!(get_inference_cache(interp), result) - @assert cache === :no || cache === :local || cache === :global - frame = new( - params, result, linfo, - sp, slottypes, mod, 0, - IdSet{InferenceState}(), IdSet{InferenceState}(), - src, get_world_counter(interp), valid_worlds, - nargs, s_types, s_edges, stmt_info, - Union{}, ip, 1, n, handler_at, - ssavalue_uses, - Vector{Tuple{InferenceState,LineNum}}(), # cycle_backedges - Vector{InferenceState}(), # callers_in_cycle - #=parent=#nothing, - cache === :global, false, false, - Effects(consistent, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, - inbounds_taints_consistency), - CachedMethodTable(method_table(interp)), + 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, + pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, + result, valid_worlds, bestguess, ipo_effects, + restrict_abstract_call_sites, cached, insert_coverage, interp) - result.result = frame - cache !== :no && push!(get_inference_cache(interp), result) - return frame end end -Effects(state::InferenceState) = state.ipo_effects -function any_inbounds(code::Vector{Any}) - for i=1:length(code) - stmt = code[i] - if isa(stmt, Expr) && stmt.head === :inbounds - return true - end - end - return false -end +is_inferred(sv::InferenceState) = is_inferred(sv.result) +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) # The goal initially is to record the frame like this for the state at exit: @@ -244,171 +400,223 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) return handler_at end -""" - Iterate through all callers of the given InferenceState in the abstract - interpretation stack (including the given InferenceState itself), vising - children before their parents (i.e. ascending the tree from the given - InferenceState). Note that cycles may be visited in any order. -""" -struct InfStackUnwind - inf::InferenceState -end -iterate(unw::InfStackUnwind) = (unw.inf, (unw.inf, 0)) -function iterate(unw::InfStackUnwind, (infstate, cyclei)::Tuple{InferenceState, Int}) - # iterate through the cycle before walking to the parent - if cyclei < length(infstate.callers_in_cycle) - cyclei += 1 - infstate = infstate.callers_in_cycle[cyclei] - else - cyclei = 0 - infstate = infstate.parent +# check if coverage mode is enabled +function should_insert_coverage(mod::Module, src::CodeInfo) + 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 - infstate === nothing && return nothing - (infstate::InferenceState, (infstate, cyclei)) + return false end -method_table(interp::AbstractInterpreter, sv::InferenceState) = sv.method_table - function InferenceState(result::InferenceResult, cache::Symbol, interp::AbstractInterpreter) # prepare an InferenceState object for inferring lambda - src = retrieve_code_info(result.linfo) + world = get_world_counter(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) end +""" + constrains_param(var::TypeVar, sig, covariant::Bool, type_constrains::Bool) + +Check if `var` will be constrained to have a definite value +in any concrete leaftype subtype of `sig`. + +It is used as a helper to determine whether type intersection is guaranteed to be able to +find a value for a particular type parameter. +A necessary condition for type intersection to not assign a parameter is that it only +appears in a `Union[All]` and during subtyping some other union component (that does not +constrain the type parameter) is selected. + +The `type_constrains` flag determines whether Type{T} is considered to be constraining +`T`. This is not true in general, because of the existence of types with free type +parameters, however, some callers would like to ignore this corner case. +""" +function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool, type_constrains::Bool=false) + typ === var && return true + while typ isa UnionAll + covariant && constrains_param(var, typ.var.ub, covariant, type_constrains) && return true + # typ.var.lb doesn't constrain var + typ = typ.body + end + if typ isa Union + # for unions, verify that both options would constrain var + ba = constrains_param(var, typ.a, covariant, type_constrains) + bb = constrains_param(var, typ.b, covariant, type_constrains) + (ba && bb) && return true + elseif typ isa DataType + # return true if any param constrains var + fc = length(typ.parameters) + if fc > 0 + if typ.name === Tuple.name + # vararg tuple needs special handling + for i in 1:(fc - 1) + p = typ.parameters[i] + constrains_param(var, p, covariant, type_constrains) && return true + end + lastp = typ.parameters[fc] + vararg = unwrap_unionall(lastp) + if vararg isa Core.TypeofVararg && isdefined(vararg, :N) + constrains_param(var, vararg.N, covariant, type_constrains) && return true + # T = vararg.parameters[1] doesn't constrain var + else + constrains_param(var, lastp, covariant, type_constrains) && return true + end + else + if typ.name === typename(Type) && typ.parameters[1] === var && var.ub === Any + # Types with free type parameters are <: Type cause the typevar + # to be unconstrained because Type{T} with free typevars is illegal + return type_constrains + end + for i in 1:fc + p = typ.parameters[i] + constrains_param(var, p, false, type_constrains) && return true + end + end + end + end + return false +end + +const EMPTY_SPTYPES = VarState[] + function sptypes_from_meth_instance(linfo::MethodInstance) - toplevel = !isa(linfo.def, Method) - if !toplevel && isempty(linfo.sparam_vals) && isa(linfo.def.sig, UnionAll) + def = linfo.def + isa(def, Method) || return EMPTY_SPTYPES # toplevel + sig = def.sig + if isempty(linfo.sparam_vals) + isa(sig, UnionAll) || return EMPTY_SPTYPES # linfo is unspecialized - sp = Any[] - sig = linfo.def.sig - while isa(sig, UnionAll) - push!(sp, sig.var) - sig = sig.body + spvals = Any[] + sig′ = sig + while isa(sig′, UnionAll) + push!(spvals, sig′.var) + sig′ = sig′.body end else - sp = collect(Any, linfo.sparam_vals) + spvals = linfo.sparam_vals end - for i = 1:length(sp) - v = sp[i] + nvals = length(spvals) + sptypes = Vector{VarState}(undef, nvals) + for i = 1:nvals + v = spvals[i] if v isa TypeVar - fromArg = 0 - # if this parameter came from arg::Type{T}, then `arg` is more precise than - # Type{T} where lb<:T<:ub - sig = linfo.def.sig temp = sig for j = 1:i-1 temp = temp.body end - Pi = temp.var - while temp isa UnionAll - temp = temp.body - end - sigtypes = (temp::DataType).parameters + vᵢ = (temp::UnionAll).var + sigtypes = (unwrap_unionall(temp)::DataType).parameters for j = 1:length(sigtypes) - tj = sigtypes[j] - if isType(tj) && tj.parameters[1] === Pi - fromArg = j - break + sⱼ = sigtypes[j] + 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) + @goto ty_computed end end - if fromArg > 0 - ty = fieldtype(linfo.specTypes, fromArg) + ub = unwraptv_ub(v) + if has_free_typevars(ub) + ub = Any + end + lb = unwraptv_lb(v) + if has_free_typevars(lb) + lb = Bottom + end + if Any === ub && lb === Bottom + ty = Any else - ub = v.ub - while ub isa TypeVar - ub = ub.ub - end - if has_free_typevars(ub) - ub = Any - end - lb = v.lb - while lb isa TypeVar - lb = lb.lb - end - if has_free_typevars(lb) - lb = Bottom - end - if Any <: ub && lb <: Bottom - ty = Any - else - tv = TypeVar(v.name, lb, ub) - ty = UnionAll(tv, Type{tv}) - end + tv = TypeVar(v.name, lb, ub) + ty = UnionAll(tv, Type{tv}) end + @label ty_computed + undef = !(let sig=sig + # if the specialized signature `linfo.specTypes` doesn't contain any free + # 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 + end + @assert !has_free_typevars(sig) + constrains_param(v, sig, #=covariant=#true) + end) elseif isvarargtype(v) ty = Int + undef = false else ty = Const(v) + undef = false end - sp[i] = ty + sptypes[i] = VarState(ty, undef) end - return sp -end - -_topmod(sv::InferenceState) = _topmod(sv.mod) - -# work towards converging the valid age range for sv -function update_valid_age!(sv::InferenceState, worlds::WorldRange) - sv.valid_worlds = intersect(worlds, sv.valid_worlds) - @assert(sv.world in sv.valid_worlds, "invalid age range update") - nothing + return sptypes end -update_valid_age!(edge::InferenceState, sv::InferenceState) = update_valid_age!(sv, edge.valid_worlds) +_topmod(sv::InferenceState) = _topmod(frame_module(sv)) -function record_ssa_assign(ssa_id::Int, @nospecialize(new), frame::InferenceState) - ssavaluetypes = frame.src.ssavaluetypes::Vector{Any} +function record_ssa_assign!(𝕃ᵢ::AbstractLattice, ssa_id::Int, @nospecialize(new), frame::InferenceState) + ssavaluetypes = frame.ssavaluetypes old = ssavaluetypes[ssa_id] - if old === NOT_FOUND || !(new ⊑ old) + if old === NOT_FOUND || !⊑(𝕃ᵢ, new, old) # typically, we expect that old ⊑ new (that output information only # gets less precise with worse input information), but to actually # guarantee convergence we need to use tmerge here to ensure that is true - ssavaluetypes[ssa_id] = old === NOT_FOUND ? new : tmerge(old, new) + ssavaluetypes[ssa_id] = old === NOT_FOUND ? new : tmerge(𝕃ᵢ, old, new) W = frame.ip - s = frame.stmt_types for r in frame.ssavalue_uses[ssa_id] - if s[r] !== nothing # s[r] === nothing => unreached statement - if r < frame.pc´´ - frame.pc´´ = r + 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 + push!(W, usebb) end - push!(W, r) end end end - nothing + return nothing end -function add_cycle_backedge!(frame::InferenceState, caller::InferenceState, currpc::Int) - update_valid_age!(frame, caller) +function add_cycle_backedge!(caller::InferenceState, frame::InferenceState, currpc::Int) + update_valid_age!(caller, frame.valid_worlds) backedge = (caller, currpc) contains_is(frame.cycle_backedges, backedge) || push!(frame.cycle_backedges, backedge) - add_backedge!(frame.linfo, caller) + add_backedge!(caller, frame.linfo) return frame end -# temporarily accumulate our edges to later add as backedges in the callee -function add_backedge!(li::MethodInstance, caller::InferenceState) - isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs - edges = caller.stmt_edges[caller.currpc] +function get_stmt_edges!(caller::InferenceState, currpc::Int=caller.currpc) + stmt_edges = caller.stmt_edges + edges = stmt_edges[currpc] if edges === nothing - edges = caller.stmt_edges[caller.currpc] = [] + edges = stmt_edges[currpc] = [] end - push!(edges, li) - nothing + return edges end -# used to temporarily accumulate our no method errors to later add as backedges in the callee method table -function add_mt_backedge!(mt::Core.MethodTable, @nospecialize(typ), caller::InferenceState) - isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs - edges = caller.stmt_edges[caller.currpc] - if edges === nothing - edges = caller.stmt_edges[caller.currpc] = [] - end - push!(edges, mt) - push!(edges, typ) - nothing +function empty_backedges!(frame::InferenceState, currpc::Int=frame.currpc) + edges = frame.stmt_edges[currpc] + edges === nothing || empty!(edges) + return nothing end function print_callstack(sv::InferenceState) @@ -424,4 +632,262 @@ function print_callstack(sv::InferenceState) end end +function narguments(sv::InferenceState, include_va::Bool=true) + def = sv.linfo.def + nargs = length(sv.result.argtypes) + if !include_va + nargs -= isa(def, Method) && def.isva + end + return nargs +end + +# IRInterpretationState +# ===================== + +# TODO add `result::InferenceResult` and put the irinterp result into the inference cache? +mutable struct IRInterpretationState + const method_info::MethodInfo + const ir::IRCode + const mi::MethodInstance + const world::UInt + curridx::Int + const argtypes_refined::Vector{Bool} + const sptypes::Vector{VarState} + const tpdum::TwoPhaseDefUseMap + const ssa_refined::BitSet + const lazydomtree::LazyDomtree + valid_worlds::WorldRange + const edges::Vector{Any} + parent # ::Union{Nothing,AbsIntState} + + function IRInterpretationState(interp::AbstractInterpreter, + method_info::MethodInfo, ir::IRCode, mi::MethodInstance, argtypes::Vector{Any}, + world::UInt, min_world::UInt, max_world::UInt) + curridx = 1 + given_argtypes = Vector{Any}(undef, length(argtypes)) + 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)] + empty!(ir.argtypes) + append!(ir.argtypes, given_argtypes) + tpdum = TwoPhaseDefUseMap(length(ir.stmts)) + ssa_refined = BitSet() + lazydomtree = LazyDomtree(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) + end +end + +function IRInterpretationState(interp::AbstractInterpreter, + code::CodeInstance, mi::MethodInstance, argtypes::Vector{Any}, world::UInt) + @assert code.def === mi + src = @atomic :monotonic code.inferred + if isa(src, String) + src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src)::CodeInfo + else + isa(src, CodeInfo) || return nothing + end + method_info = MethodInfo(src) + ir = inflate_ir(src, mi) + return IRInterpretationState(interp, method_info, ir, mi, argtypes, world, + src.min_world, src.max_world) +end + +# AbsIntState +# =========== + +const AbsIntState = Union{InferenceState,IRInterpretationState} + +frame_instance(sv::InferenceState) = sv.linfo +frame_instance(sv::IRInterpretationState) = sv.mi + +function frame_module(sv::AbsIntState) + mi = frame_instance(sv) + def = mi.def + isa(def, Module) && return def + return def.module +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) +is_constproped(::IRInterpretationState) = true + +is_cached(sv::InferenceState) = sv.cached +is_cached(::IRInterpretationState) = false + +method_info(sv::InferenceState) = sv.method_info +method_info(sv::IRInterpretationState) = sv.method_info + +propagate_inbounds(sv::AbsIntState) = method_info(sv).propagate_inbounds +method_for_inference_limit_heuristics(sv::AbsIntState) = method_info(sv).method_for_inference_limit_heuristics + +frame_world(sv::InferenceState) = sv.world +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 + 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) +is_effect_overridden(override::EffectsOverride, effect::Symbol) = getfield(override, effect) + +has_conditional(𝕃::AbstractLattice, ::InferenceState) = has_conditional(𝕃) +has_conditional(::AbstractLattice, ::IRInterpretationState) = false + +# work towards converging the valid age range for sv +function update_valid_age!(sv::AbsIntState, valid_worlds::WorldRange) + valid_worlds = sv.valid_worlds = intersect(valid_worlds, sv.valid_worlds) + @assert sv.world in valid_worlds "invalid age range update" + return valid_worlds +end + +""" + AbsIntStackUnwind(sv::AbsIntState) + +Iterate through all callers of the given `AbsIntState` in the abstract interpretation stack +(including the given `AbsIntState` itself), visiting children before their parents (i.e. +ascending the tree from the given `AbsIntState`). +Note that cycles may be visited in any order. +""" +struct AbsIntStackUnwind + sv::AbsIntState +end +iterate(unw::AbsIntStackUnwind) = (unw.sv, (unw.sv, 0)) +function iterate(unw::AbsIntStackUnwind, (sv, cyclei)::Tuple{AbsIntState, Int}) + # iterate through the cycle before walking to the parent + if cyclei < length(callers_in_cycle(sv)) + cyclei += 1 + parent = callers_in_cycle(sv)[cyclei] + else + cyclei = 0 + parent = frame_parent(sv) + end + parent === nothing && return nothing + return (parent, (parent, cyclei)) +end + +# temporarily accumulate our edges to later add as backedges in the callee +function add_backedge!(caller::InferenceState, mi::MethodInstance) + isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance + return push!(get_stmt_edges!(caller), mi) +end +function add_backedge!(irsv::IRInterpretationState, mi::MethodInstance) + return push!(irsv.edges, mi) +end + +function add_invoke_backedge!(caller::InferenceState, @nospecialize(invokesig::Type), mi::MethodInstance) + isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance + return push!(get_stmt_edges!(caller), invokesig, mi) +end +function add_invoke_backedge!(irsv::IRInterpretationState, @nospecialize(invokesig::Type), mi::MethodInstance) + return push!(irsv.edges, invokesig, mi) +end + +# used to temporarily accumulate our no method errors to later add as backedges in the callee method table +function add_mt_backedge!(caller::InferenceState, mt::MethodTable, @nospecialize(typ)) + isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance + return push!(get_stmt_edges!(caller), mt, typ) +end +function add_mt_backedge!(irsv::IRInterpretationState, mt::MethodTable, @nospecialize(typ)) + return push!(irsv.edges, mt, typ) +end + get_curr_ssaflag(sv::InferenceState) = sv.src.ssaflags[sv.currpc] +get_curr_ssaflag(sv::IRInterpretationState) = sv.ir.stmts[sv.curridx][:flag] + +add_curr_ssaflag!(sv::InferenceState, flag::UInt8) = sv.src.ssaflags[sv.currpc] |= flag +add_curr_ssaflag!(sv::IRInterpretationState, flag::UInt8) = sv.ir.stmts[sv.curridx][:flag] |= flag + +sub_curr_ssaflag!(sv::InferenceState, flag::UInt8) = sv.src.ssaflags[sv.currpc] &= ~flag +sub_curr_ssaflag!(sv::IRInterpretationState, flag::UInt8) = sv.ir.stmts[sv.curridx][:flag] &= ~flag + +merge_effects!(::AbstractInterpreter, caller::InferenceState, effects::Effects) = + caller.ipo_effects = merge_effects(caller.ipo_effects, effects) +merge_effects!(::AbstractInterpreter, ::IRInterpretationState, ::Effects) = return + +struct InferenceLoopState + sig + rt + effects::Effects + function InferenceLoopState(@nospecialize(sig), @nospecialize(rt), effects::Effects) + new(sig, rt, effects) + end +end + +bail_out_toplevel_call(::AbstractInterpreter, state::InferenceLoopState, sv::InferenceState) = + sv.restrict_abstract_call_sites && !isdispatchtuple(state.sig) +bail_out_toplevel_call(::AbstractInterpreter, ::InferenceLoopState, ::IRInterpretationState) = false + +bail_out_call(::AbstractInterpreter, state::InferenceLoopState, ::InferenceState) = + state.rt === Any && !is_foldable(state.effects) +bail_out_call(::AbstractInterpreter, state::InferenceLoopState, ::IRInterpretationState) = + state.rt === Any && !is_foldable(state.effects) + +bail_out_apply(::AbstractInterpreter, state::InferenceLoopState, ::InferenceState) = + state.rt === Any +bail_out_apply(::AbstractInterpreter, state::InferenceLoopState, ::IRInterpretationState) = + state.rt === Any + +function should_infer_this_call(interp::AbstractInterpreter, sv::InferenceState) + if InferenceParams(interp).unoptimize_throw_blocks + # Disable inference of calls in throw blocks, since we're unlikely to + # need their types. There is one exception however: If up until now, the + # function has not seen any side effects, we would like to make sure there + # aren't any in the throw block either to enable other optimizations. + if is_stmt_throw_block(get_curr_ssaflag(sv)) + should_infer_for_effects(sv) || return false + end + end + return true +end +function should_infer_for_effects(sv::InferenceState) + effects = sv.ipo_effects + return is_terminates(effects) && is_effect_free(effects) +end +should_infer_this_call(::AbstractInterpreter, ::IRInterpretationState) = true + +add_remark!(::AbstractInterpreter, ::InferenceState, remark) = return +add_remark!(::AbstractInterpreter, ::IRInterpretationState, remark) = return + +function get_max_methods(interp::AbstractInterpreter, @nospecialize(f), sv::AbsIntState) + fmax = get_max_methods_for_func(f) + fmax !== nothing && return fmax + return get_max_methods(interp, sv) +end +function get_max_methods(interp::AbstractInterpreter, @nospecialize(f)) + fmax = get_max_methods_for_func(f) + fmax !== nothing && return fmax + return get_max_methods(interp) +end +function get_max_methods(interp::AbstractInterpreter, sv::AbsIntState) + mmax = get_max_methods_for_module(sv) + mmax !== nothing && return mmax + return get_max_methods(interp) +end +get_max_methods(interp::AbstractInterpreter) = InferenceParams(interp).max_methods + +function get_max_methods_for_func(@nospecialize(f)) + if f !== nothing + fmm = typeof(f).name.max_methods + fmm !== UInt8(0) && return Int(fmm) + end + return nothing +end +get_max_methods_for_module(sv::AbsIntState) = get_max_methods_for_module(frame_module(sv)) +function get_max_methods_for_module(mod::Module) + max_methods = ccall(:jl_get_module_max_methods, Cint, (Any,), mod) % Int + max_methods < 0 && return nothing + return max_methods +end diff --git a/base/compiler/methodtable.jl b/base/compiler/methodtable.jl index 93020ae6a2639..8c79b2d8a8468 100644 --- a/base/compiler/methodtable.jl +++ b/base/compiler/methodtable.jl @@ -1,7 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -abstract type MethodTableView; end - struct MethodLookupResult # Really Vector{Core.MethodMatch}, but it's easier to represent this as # and work with Vector{Any} on the C side. @@ -18,6 +16,11 @@ function iterate(result::MethodLookupResult, args...) end getindex(result::MethodLookupResult, idx::Int) = getindex(result.matches, idx)::MethodMatch +struct MethodMatchResult + matches::MethodLookupResult + overlayed::Bool +end + """ struct InternalMethodTable <: MethodTableView @@ -36,7 +39,13 @@ external table, e.g., to override existing method. """ struct OverlayMethodTable <: MethodTableView world::UInt - mt::Core.MethodTable + mt::MethodTable +end + +struct MethodMatchKey + sig # ::Type + limit::Int + MethodMatchKey(@nospecialize(sig), limit::Int) = new(sig, limit) end """ @@ -45,81 +54,120 @@ end Overlays another method table view with an additional local fast path cache that can respond to repeated, identical queries faster than the original method table. """ -struct CachedMethodTable{T} <: MethodTableView - cache::IdDict{Any, Union{Missing, MethodLookupResult}} +struct CachedMethodTable{T<:MethodTableView} <: MethodTableView + cache::IdDict{MethodMatchKey, Union{Nothing,MethodMatchResult}} table::T end -CachedMethodTable(table::T) where T = - CachedMethodTable{T}(IdDict{Any, Union{Missing, MethodLookupResult}}(), - table) +CachedMethodTable(table::T) where T = CachedMethodTable{T}(IdDict{MethodMatchKey, Union{Nothing,MethodMatchResult}}(), table) """ - findall(sig::Type, view::MethodTableView; limit=typemax(Int)) - -Find all methods in the given method table `view` that are applicable to the -given signature `sig`. If no applicable methods are found, an empty result is -returned. If the number of applicable methods exceeded the specified limit, -`missing` is returned. + findall(sig::Type, view::MethodTableView; limit::Int=-1) -> + MethodMatchResult(matches::MethodLookupResult, overlayed::Bool) or nothing + +Find all methods in the given method table `view` that are applicable to the given signature `sig`. +If no applicable methods are found, an empty result is returned. +If the number of applicable methods exceeded the specified `limit`, `nothing` is returned. +Note that the default setting `limit=-1` does not limit the number of applicable methods. +`overlayed` indicates if any of the matching methods comes from an overlayed method table. """ -function findall(@nospecialize(sig::Type), table::InternalMethodTable; limit::Int=typemax(Int)) - _min_val = RefValue{UInt}(typemin(UInt)) - _max_val = RefValue{UInt}(typemax(UInt)) - _ambig = RefValue{Int32}(0) - ms = _methods_by_ftype(sig, nothing, limit, table.world, false, _min_val, _max_val, _ambig) - if ms === false - return missing +function findall(@nospecialize(sig::Type), table::InternalMethodTable; limit::Int=-1) + result = _findall(sig, nothing, table.world, limit) + result === nothing && return nothing + return MethodMatchResult(result, false) +end + +function findall(@nospecialize(sig::Type), table::OverlayMethodTable; limit::Int=-1) + result = _findall(sig, table.mt, table.world, limit) + result === nothing && return nothing + nr = length(result) + if nr ≥ 1 && result[nr].fully_covers + # no need to fall back to the internal method table + return MethodMatchResult(result, true) end - return MethodLookupResult(ms::Vector{Any}, WorldRange(_min_val[], _max_val[]), _ambig[] != 0) + # fall back to the internal method table + fallback_result = _findall(sig, nothing, table.world, limit) + fallback_result === nothing && return nothing + # merge the fallback match results with the internal method table + return MethodMatchResult( + MethodLookupResult( + vcat(result.matches, fallback_result.matches), + WorldRange( + max(result.valid_worlds.min_world, fallback_result.valid_worlds.min_world), + min(result.valid_worlds.max_world, fallback_result.valid_worlds.max_world)), + result.ambig | fallback_result.ambig), + !isempty(result)) end -function findall(@nospecialize(sig::Type), table::OverlayMethodTable; limit::Int=typemax(Int)) +function _findall(@nospecialize(sig::Type), mt::Union{Nothing,MethodTable}, world::UInt, limit::Int) _min_val = RefValue{UInt}(typemin(UInt)) _max_val = RefValue{UInt}(typemax(UInt)) _ambig = RefValue{Int32}(0) - ms = _methods_by_ftype(sig, table.mt, limit, table.world, false, _min_val, _max_val, _ambig) - if ms === false - return missing - elseif isempty(ms) - # fall back to the internal method table - _min_val[] = typemin(UInt) - _max_val[] = typemax(UInt) - ms = _methods_by_ftype(sig, nothing, limit, table.world, false, _min_val, _max_val, _ambig) - end - if ms === false - return missing - end - return MethodLookupResult(ms::Vector{Any}, WorldRange(_min_val[], _max_val[]), _ambig[] != 0) + ms = _methods_by_ftype(sig, mt, limit, world, false, _min_val, _max_val, _ambig) + isa(ms, Vector) || return nothing + return MethodLookupResult(ms, WorldRange(_min_val[], _max_val[]), _ambig[] != 0) end -function findall(@nospecialize(sig::Type), table::CachedMethodTable; limit::Int=typemax(Int)) - box = Core.Box(sig) - return get!(table.cache, sig) do - findall(box.contents, table.table; limit=limit) +function findall(@nospecialize(sig::Type), table::CachedMethodTable; limit::Int=-1) + if isconcretetype(sig) + # as for concrete types, we cache result at on the next level + return findall(sig, table.table; limit) + end + key = MethodMatchKey(sig, limit) + if haskey(table.cache, key) + return table.cache[key] + else + return table.cache[key] = findall(sig, table.table; limit) end end """ - findsup(sig::Type, view::MethodTableView)::Union{Tuple{MethodMatch, WorldRange}, Nothing} - -Find the (unique) method `m` such that `sig <: m.sig`, while being more -specific than any other method with the same property. In other words, find -the method which is the least upper bound (supremum) under the specificity/subtype -relation of the queried `signature`. If `sig` is concrete, this is equivalent to -asking for the method that will be called given arguments whose types match the -given signature. This query is also used to implement `invoke`. - -Such a method `m` need not exist. It is possible that no method is an -upper bound of `sig`, or it is possible that among the upper bounds, there -is no least element. In both cases `nothing` is returned. + findsup(sig::Type, view::MethodTableView) -> + (match::MethodMatch, valid_worlds::WorldRange, overlayed::Bool) or nothing + +Find the (unique) method such that `sig <: match.method.sig`, while being more +specific than any other method with the same property. In other words, find the method +which is the least upper bound (supremum) under the specificity/subtype relation of +the queried `sig`nature. If `sig` is concrete, this is equivalent to asking for the method +that will be called given arguments whose types match the given signature. +Note that this query is also used to implement `invoke`. + +Such a matching method `match` doesn't necessarily exist. +It is possible that no method is an upper bound of `sig`, or +it is possible that among the upper bounds, there is no least element. +In both cases `nothing` is returned. + +`overlayed` indicates if any of the matching methods comes from an overlayed method table. """ function findsup(@nospecialize(sig::Type), table::InternalMethodTable) + return (_findsup(sig, nothing, table.world)..., false) +end + +function findsup(@nospecialize(sig::Type), table::OverlayMethodTable) + match, valid_worlds = _findsup(sig, table.mt, table.world) + match !== nothing && return match, valid_worlds, true + # fall back to the internal method table + fallback_match, fallback_valid_worlds = _findsup(sig, nothing, table.world) + return ( + fallback_match, + WorldRange( + max(valid_worlds.min_world, fallback_valid_worlds.min_world), + min(valid_worlds.max_world, fallback_valid_worlds.max_world)), + false) +end + +function _findsup(@nospecialize(sig::Type), mt::Union{Nothing,MethodTable}, world::UInt) min_valid = RefValue{UInt}(typemin(UInt)) max_valid = RefValue{UInt}(typemax(UInt)) - result = ccall(:jl_gf_invoke_lookup_worlds, Any, (Any, UInt, Ptr{Csize_t}, Ptr{Csize_t}), - sig, table.world, min_valid, max_valid)::Union{MethodMatch, Nothing} - result === nothing && return nothing - (result.method, WorldRange(min_valid[], max_valid[])) + match = ccall(:jl_gf_invoke_lookup_worlds, Any, (Any, Any, UInt, Ptr{Csize_t}, Ptr{Csize_t}), + sig, mt, world, min_valid, max_valid)::Union{MethodMatch, Nothing} + valid_worlds = WorldRange(min_valid[], max_valid[]) + return match, valid_worlds end # This query is not cached findsup(@nospecialize(sig::Type), table::CachedMethodTable) = findsup(sig, table.table) + +isoverlayed(::MethodTableView) = error("unsatisfied MethodTableView interface") +isoverlayed(::InternalMethodTable) = false +isoverlayed(::OverlayMethodTable) = true +isoverlayed(mt::CachedMethodTable) = isoverlayed(mt.table) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 0616204dce748..71eeb15d53eb0 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -24,12 +24,44 @@ const IR_FLAG_INLINE = 0x01 << 1 # This statement is marked as @noinline by user const IR_FLAG_NOINLINE = 0x01 << 2 const IR_FLAG_THROW_BLOCK = 0x01 << 3 -# This statement may be removed if its result is unused. In particular it must -# thus be both pure and effect free. +# 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 = 0x01 << 4 +# This statement was proven not to throw +const IR_FLAG_NOTHROW = 0x01 << 5 +# This is :consistent +const IR_FLAG_CONSISTENT = 0x01 << 6 +# 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 = 0x01 << 7 const TOP_TUPLE = GlobalRef(Core, :tuple) +# This corresponds to the type of `CodeInfo`'s `inlining_cost` field +const InlineCostType = UInt16 +const MAX_INLINE_COST = typemax(InlineCostType) +const MIN_INLINE_COST = InlineCostType(10) +const MaybeCompressed = Union{CodeInfo, String} + +is_inlineable(@nospecialize src::MaybeCompressed) = + ccall(:jl_ir_inlining_cost, InlineCostType, (Any,), src) != MAX_INLINE_COST +set_inlineable!(src::CodeInfo, val::Bool) = + src.inlining_cost = (val ? MIN_INLINE_COST : MAX_INLINE_COST) + +function inline_cost_clamp(x::Int)::InlineCostType + x > MAX_INLINE_COST && return MAX_INLINE_COST + x < MIN_INLINE_COST && return MIN_INLINE_COST + return convert(InlineCostType, x) +end + +is_declared_inline(@nospecialize src::MaybeCompressed) = + ccall(:jl_ir_flag_inlining, UInt8, (Any,), src) == 1 + +is_declared_noinline(@nospecialize src::MaybeCompressed) = + ccall(:jl_ir_flag_inlining, UInt8, (Any,), src) == 2 + ##################### # OptimizationState # ##################### @@ -45,109 +77,151 @@ EdgeTracker() = EdgeTracker(Any[], 0:typemax(UInt)) intersect!(et::EdgeTracker, range::WorldRange) = et.valid_worlds[] = intersect(et.valid_worlds[], range) -push!(et::EdgeTracker, mi::MethodInstance) = push!(et.edges, mi) -function push!(et::EdgeTracker, ci::CodeInstance) - intersect!(et, WorldRange(min_world(li), max_world(li))) - push!(et, ci.def) +function add_backedge!(et::EdgeTracker, mi::MethodInstance) + push!(et.edges, mi) + return nothing end - -struct InliningState{S <: Union{EdgeTracker, Nothing}, MICache, I<:AbstractInterpreter} - params::OptimizationParams - et::S - mi_cache::MICache # TODO move this to `OptimizationState` (as used by EscapeAnalysis as well) - interp::I +function add_invoke_backedge!(et::EdgeTracker, @nospecialize(invokesig), mi::MethodInstance) + push!(et.edges, invokesig, mi) + return nothing end -function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), stmt_flag::UInt8, - mi::MethodInstance, argtypes::Vector{Any}) - if isa(src, CodeInfo) || isa(src, Vector{UInt8}) - src_inferred = ccall(:jl_ir_flag_inferred, Bool, (Any,), src) - src_inlineable = is_stmt_inline(stmt_flag) || ccall(:jl_ir_flag_inlineable, Bool, (Any,), src) - return src_inferred && src_inlineable ? src : nothing +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::UInt8, mi::MethodInstance, + argtypes::Vector{Any}) + 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(mi, argtypes, get_inference_cache(interp)) + 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 = ccall(:jl_ir_flag_inferred, Bool, (Any,), src) + src_inferred = is_source_inferred(src) return src_inferred ? src : nothing else return nothing end + 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 end return nothing end +struct InliningState{Interp<:AbstractInterpreter} + et::Union{EdgeTracker,Nothing} + world::UInt + interp::Interp +end +function InliningState(sv::InferenceState, interp::AbstractInterpreter) + et = EdgeTracker(sv.stmt_edges[1]::Vector{Any}, sv.valid_worlds) + return InliningState(et, sv.world, interp) +end +function InliningState(interp::AbstractInterpreter) + return InliningState(nothing, get_world_counter(interp), interp) +end + +# get `code_cache(::AbstractInterpreter)` from `state::InliningState` +code_cache(state::InliningState) = WorldView(code_cache(state.interp), state.world) + include("compiler/ssair/driver.jl") -mutable struct OptimizationState +mutable struct OptimizationState{Interp<:AbstractInterpreter} linfo::MethodInstance src::CodeInfo ir::Union{Nothing, IRCode} - stmt_info::Vector{Any} + stmt_info::Vector{CallInfo} mod::Module - sptypes::Vector{Any} # static parameters + sptypes::Vector{VarState} slottypes::Vector{Any} - inlining::InliningState - function OptimizationState(frame::InferenceState, params::OptimizationParams, interp::AbstractInterpreter) - s_edges = frame.stmt_edges[1]::Vector{Any} - inlining = InliningState(params, - EdgeTracker(s_edges, frame.valid_worlds), - WorldView(code_cache(interp), frame.world), - interp) - return new(frame.linfo, - frame.src, nothing, frame.stmt_info, frame.mod, - frame.sptypes, frame.slottypes, inlining) + inlining::InliningState{Interp} + cfg::Union{Nothing,CFG} + insert_coverage::Bool +end +function OptimizationState(sv::InferenceState, interp::AbstractInterpreter, + recompute_cfg::Bool=true) + inlining = InliningState(sv, interp) + cfg = recompute_cfg ? nothing : sv.cfg + return OptimizationState(sv.linfo, sv.src, nothing, sv.stmt_info, sv.mod, + sv.sptypes, sv.slottypes, inlining, cfg, sv.insert_coverage) +end +function OptimizationState(linfo::MethodInstance, src::CodeInfo, interp::AbstractInterpreter) + # prepare src for running optimization passes if it isn't already + nssavalues = src.ssavaluetypes + if nssavalues isa Int + src.ssavaluetypes = Any[ Any for i = 1:nssavalues ] + else + nssavalues = length(src.ssavaluetypes::Vector{Any}) end - function OptimizationState(linfo::MethodInstance, src::CodeInfo, params::OptimizationParams, interp::AbstractInterpreter) - # prepare src for running optimization passes - # if it isn't already - nssavalues = src.ssavaluetypes - if nssavalues isa Int - src.ssavaluetypes = Any[ Any for i = 1:nssavalues ] - else - nssavalues = length(src.ssavaluetypes::Vector{Any}) - end - nslots = length(src.slotflags) - slottypes = src.slottypes - if slottypes === nothing - slottypes = Any[ Any for i = 1:nslots ] - end - stmt_info = Any[nothing for i = 1:nssavalues] - # cache some useful state computations - def = linfo.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 - inlining = InliningState(params, - nothing, - WorldView(code_cache(interp), get_world_counter()), - interp) - return new(linfo, - src, nothing, stmt_info, mod, - sptypes_from_meth_instance(linfo), slottypes, inlining) + sptypes = sptypes_from_meth_instance(linfo) + nslots = length(src.slotflags) + slottypes = src.slottypes + if slottypes === nothing + slottypes = Any[ Any for i = 1:nslots ] end + stmt_info = CallInfo[ NoCallInfo() for i = 1:nssavalues ] + # cache some useful state computations + def = linfo.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 + inlining = InliningState(interp) + return OptimizationState(linfo, src, nothing, stmt_info, mod, sptypes, slottypes, inlining, nothing, false) end - -function OptimizationState(linfo::MethodInstance, params::OptimizationParams, interp::AbstractInterpreter) - src = retrieve_code_info(linfo) +function OptimizationState(linfo::MethodInstance, interp::AbstractInterpreter) + world = get_world_counter(interp) + src = retrieve_code_info(linfo, world) src === nothing && return nothing - return OptimizationState(linfo, src, params, interp) + return OptimizationState(linfo, src, interp) end function ir_to_codeinf!(opt::OptimizationState) (; linfo, src) = opt - optdef = linfo.def - replace_code_newstyle!(src, opt.ir::IRCode, isa(optdef, Method) ? Int(optdef.nargs) : 0) + src = ir_to_codeinf!(src, opt.ir::IRCode) opt.ir = nothing + validate_code_in_debug_mode(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 - # finish updating the result struct - validate_code_in_debug_mode(linfo, src, "optimized") + return src +end + +# widen all Const elements in type annotations +function widen_all_consts!(src::CodeInfo) + ssavaluetypes = src.ssavaluetypes::Vector{Any} + for i = 1:length(ssavaluetypes) + ssavaluetypes[i] = widenconst(ssavaluetypes[i]) + end + + for i = 1:length(src.code) + x = src.code[i] + if isa(x, PiNode) + src.code[i] = PiNode(x.val, widenconst(x.typ)) + end + end + + src.rettype = widenconst(src.rettype) + return src end @@ -161,191 +235,138 @@ is_stmt_inline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_INLINE ≠ 0 is_stmt_noinline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_NOINLINE ≠ 0 is_stmt_throw_block(stmt_flag::UInt8) = stmt_flag & IR_FLAG_THROW_BLOCK ≠ 0 -# These affect control flow within the function (so may not be removed -# if there is no usage within the function), but don't affect the purity -# of the function as a whole. -function stmt_affects_purity(@nospecialize(stmt), ir) - if isa(stmt, GotoNode) || isa(stmt, ReturnNode) - return false - end - if isa(stmt, GotoIfNot) - t = argextype(stmt.cond, ir) - return !(t ⊑ Bool) - end - if isa(stmt, Expr) - return stmt.head !== :loopinfo && stmt.head !== :enter - end - return true -end - """ - stmt_effect_free(stmt, rt, src::Union{IRCode,IncrementalCompact}) + stmt_effect_flags(stmt, rt, src::Union{IRCode,IncrementalCompact}) -> + (consistent::Bool, effect_free_and_nothrow::Bool, nothrow::Bool) -Determine whether a `stmt` is "side-effect-free", i.e. may be removed if it has no uses. +Returns a tuple of `(:consistent, :effect_free_and_nothrow, :nothrow)` flags for a given statement. """ -function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src::Union{IRCode,IncrementalCompact}) - isa(stmt, PiNode) && return true - isa(stmt, PhiNode) && return true - isa(stmt, ReturnNode) && return false - isa(stmt, GotoNode) && return false - isa(stmt, GotoIfNot) && return false - isa(stmt, Slot) && return false # Slots shouldn't occur in the IR at this point, but let's be defensive here - isa(stmt, GlobalRef) && return isdefined(stmt.mod, stmt.name) - if isa(stmt, Expr) +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, 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) + return (consistent, nothrow, nothrow) + elseif isa(stmt, Expr) (; head, args) = stmt if head === :static_parameter - etyp = (isa(src, IRCode) ? src.sptypes : src.ir.sptypes)[args[1]::Int] # if we aren't certain enough about the type, it might be an UndefVarError at runtime - return isa(etyp, Const) + sptypes = isa(src, IRCode) ? src.sptypes : src.ir.sptypes + nothrow = !sptypes[args[1]::Int].undef + return (true, nothrow, nothrow) end if head === :call f = argextype(args[1], src) f = singleton_type(f) - f === nothing && return false - is_return_type(f) && return true - if isa(f, IntrinsicFunction) - intrinsic_effect_free_if_nothrow(f) || return false - return intrinsic_nothrow(f, - Any[argextype(args[i], src) for i = 2:length(args)]) + f === nothing && return (false, false, false) + if f === UnionAll + # TODO: This is a weird special case - should be determined in inference + argtypes = Any[argextype(args[arg], src) for arg in 2:length(args)] + nothrow = _builtin_nothrow(𝕃ₒ, f, argtypes, rt) + return (true, nothrow, nothrow) end - contains_is(_PURE_BUILTINS, f) && return true - # `get_binding_type` sets the type to Any if the binding doesn't exist yet - if f === Core.get_binding_type - length(args) == 3 || return false - M, s = argextype(args[2], src), argextype(args[3], src) - return get_binding_type_effect_free(M, s) + if f === Intrinsics.cglobal + # TODO: these are not yet linearized + return (false, false, false) end - contains_is(_EFFECT_FREE_BUILTINS, f) || return false - rt === Bottom && return false - return _builtin_nothrow(f, Any[argextype(args[i], src) for i = 2:length(args)], rt) + 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) + consistent = is_consistent(effects) + effect_free = is_effect_free(effects) + nothrow = is_nothrow(effects) + return (consistent, effect_free & nothrow, nothrow) elseif head === :new - typ = argextype(args[1], src) + atyp = argextype(args[1], src) # `Expr(:new)` of unknown type could raise arbitrary TypeError. - typ, isexact = instanceof_tfunc(typ) - isexact || return false - isconcretedispatch(typ) || return false + typ, isexact = instanceof_tfunc(atyp) + if !isexact + atyp = unwrap_unionall(widenconst(atyp)) + if isType(atyp) && isTypeDataType(atyp.parameters[1]) + typ = atyp.parameters[1] + else + return (false, false, false) + end + isabstracttype(typ) && return (false, false, false) + else + isconcretedispatch(typ) || return (false, false, false) + end typ = typ::DataType - fieldcount(typ) >= length(args) - 1 || return false + fcount = datatype_fieldcount(typ) + fcount === nothing && return (false, false, false) + fcount >= length(args) - 1 || return (false, false, false) for fld_idx in 1:(length(args) - 1) eT = argextype(args[fld_idx + 1], src) fT = fieldtype(typ, fld_idx) - eT ⊑ fT || return false + # Currently, we cannot represent any type equality constraints + # in the lattice, so if we see any type of type parameter, + # there is very little we can say about it + if !isexact && has_free_typevars(fT) + return (false, false, false) + end + ⊑(𝕃ₒ, eT, fT) || return (false, false, false) end - return true + return (false, true, true) elseif head === :foreigncall - return foreigncall_effect_free(stmt, src) + effects = foreigncall_effects(stmt) do @nospecialize x + argextype(x, src) + end + consistent = is_consistent(effects) + effect_free = is_effect_free(effects) + nothrow = is_nothrow(effects) + return (consistent, effect_free & nothrow, nothrow) elseif head === :new_opaque_closure - length(args) < 4 && return false + length(args) < 4 && return (false, false, false) typ = argextype(args[1], src) typ, isexact = instanceof_tfunc(typ) - isexact || return false - typ ⊑ Tuple || return false + isexact || return (false, false, false) + ⊑(𝕃ₒ, typ, Tuple) || return (false, false, false) rt_lb = argextype(args[2], src) rt_ub = argextype(args[3], src) - src = argextype(args[4], src) - if !(rt_lb ⊑ Type && rt_ub ⊑ Type && src ⊑ Method) - return false + source = argextype(args[4], src) + if !(⊑(𝕃ₒ, rt_lb, Type) && ⊑(𝕃ₒ, rt_ub, Type) && ⊑(𝕃ₒ, source, Method)) + return (false, false, false) end - return true + return (false, true, true) elseif head === :isdefined || head === :the_exception || head === :copyast || head === :inbounds || head === :boundscheck - return true + return (true, true, true) else # e.g. :loopinfo - return false + return (false, false, false) end end - return true -end - -function foreigncall_effect_free(stmt::Expr, src::Union{IRCode,IncrementalCompact}) - args = stmt.args - name = args[1] - isa(name, QuoteNode) && (name = name.value) - isa(name, Symbol) || return false - ndims = alloc_array_ndims(name) - if ndims !== nothing - if ndims == 0 - return new_array_no_throw(args, src) - else - return alloc_array_no_throw(args, ndims, src) - end - end - return false -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 - -const FOREIGNCALL_ARG_START = 6 - -function alloc_array_no_throw(args::Vector{Any}, ndims::Int, src::Union{IRCode,IncrementalCompact}) - length(args) ≥ ndims+FOREIGNCALL_ARG_START || return false - atype = instanceof_tfunc(argextype(args[FOREIGNCALL_ARG_START], src))[1] - dims = Csize_t[] - for i in 1:ndims - dim = argextype(args[i+FOREIGNCALL_ARG_START], src) - isa(dim, Const) || return false - dimval = dim.val - isa(dimval, Int) || return false - push!(dims, reinterpret(Csize_t, dimval)) - end - return _new_array_no_throw(atype, ndims, dims) -end - -function new_array_no_throw(args::Vector{Any}, src::Union{IRCode,IncrementalCompact}) - length(args) ≥ FOREIGNCALL_ARG_START+1 || return false - atype = instanceof_tfunc(argextype(args[FOREIGNCALL_ARG_START], src))[1] - dims = argextype(args[FOREIGNCALL_ARG_START+1], src) - 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_no_throw(atype, ndims, dims) -end - -function _new_array_no_throw(@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 + isa(stmt, UnoptSlot) && error("unexpected IR elements") + return (true, true, true) end """ argextype(x, src::Union{IRCode,IncrementalCompact}) -> t - argextype(x, src::CodeInfo, sptypes::Vector{Any}) -> t + argextype(x, src::CodeInfo, sptypes::Vector{VarState}) -> t Return the type of value `x` in the context of inferred source `src`. Note that `t` might be an extended lattice element. Use `widenconst(t)` to get the native Julia type of `x`. """ -argextype(@nospecialize(x), ir::IRCode, sptypes::Vector{Any} = ir.sptypes) = +argextype(@nospecialize(x), ir::IRCode, sptypes::Vector{VarState} = ir.sptypes) = argextype(x, ir, sptypes, ir.argtypes) -function argextype(@nospecialize(x), compact::IncrementalCompact, sptypes::Vector{Any} = compact.ir.sptypes) +function argextype(@nospecialize(x), compact::IncrementalCompact, sptypes::Vector{VarState} = compact.ir.sptypes) isa(x, AnySSAValue) && return types(compact)[x] return argextype(x, compact, sptypes, compact.ir.argtypes) end -argextype(@nospecialize(x), src::CodeInfo, sptypes::Vector{Any}) = argextype(x, src, sptypes, src.slottypes::Vector{Any}) +argextype(@nospecialize(x), src::CodeInfo, sptypes::Vector{VarState}) = argextype(x, src, sptypes, src.slottypes::Vector{Any}) function argextype( @nospecialize(x), src::Union{IRCode,IncrementalCompact,CodeInfo}, - sptypes::Vector{Any}, slottypes::Vector{Any}) + sptypes::Vector{VarState}, slottypes::Vector{Any}) if isa(x, Expr) if x.head === :static_parameter - return sptypes[x.args[1]::Int] + return sptypes[x.args[1]::Int].typ elseif x.head === :boundscheck return Bool elseif x.head === :copyast @@ -363,7 +384,7 @@ function argextype( elseif isa(x, QuoteNode) return Const(x.value) elseif isa(x, GlobalRef) - return abstract_eval_global(x.mod, x.name) + return abstract_eval_globalref(x) elseif isa(x, PhiNode) return Any elseif isa(x, PiNode) @@ -372,82 +393,27 @@ function argextype( return Const(x) end end +abstract_eval_ssavalue(s::SSAValue, src::CodeInfo) = abstract_eval_ssavalue(s, src.ssavaluetypes::Vector{Any}) abstract_eval_ssavalue(s::SSAValue, src::Union{IRCode,IncrementalCompact}) = types(src)[s] -struct ConstAPI - val - ConstAPI(@nospecialize val) = new(val) -end - """ finish(interp::AbstractInterpreter, opt::OptimizationState, - params::OptimizationParams, ir::IRCode, caller::InferenceResult) -> analyzed::Union{Nothing,ConstAPI} - -Post process information derived by Julia-level optimizations for later uses: -- computes "purity", i.e. side-effect-freeness -- computes inlining cost + ir::IRCode, caller::InferenceResult) -In a case when the purity is proven, `finish` can return `ConstAPI` object wrapping the constant -value so that the runtime system will use the constant calling convention for the method calls. +Post-process information derived by Julia-level optimizations for later use. +In particular, this function determines the inlineability of the optimized code. """ function finish(interp::AbstractInterpreter, opt::OptimizationState, - params::OptimizationParams, ir::IRCode, caller::InferenceResult) + ir::IRCode, caller::InferenceResult) (; src, linfo) = opt (; def, specTypes) = linfo - analyzed = nothing # `ConstAPI` if this call can use constant calling convention - force_noinline = _any(@nospecialize(x) -> isexpr(x, :meta) && x.args[1] === :noinline, ir.meta) + force_noinline = is_declared_noinline(src) # compute inlining and other related optimizations result = caller.result @assert !(result isa LimitedAccuracy) - result = isa(result, InterConditional) ? widenconditional(result) : result - if (isa(result, Const) || isconstType(result)) - proven_pure = false - # must be proven pure to use constant calling convention; - # otherwise we might skip throwing errors (issue #20704) - # TODO: Improve this analysis; if a function is marked @pure we should really - # only care about certain errors (e.g. method errors and type errors). - if length(ir.stmts) < 15 - proven_pure = true - for i in 1:length(ir.stmts) - node = ir.stmts[i] - stmt = node[:inst] - if stmt_affects_purity(stmt, ir) && !stmt_effect_free(stmt, node[:type], ir) - proven_pure = false - break - end - end - if proven_pure - for fl in src.slotflags - if (fl & SLOT_USEDUNDEF) != 0 - proven_pure = false - break - end - end - end - end - - if proven_pure - # use constant calling convention - # Do not emit `jl_fptr_const_return` if coverage is enabled - # so that we don't need to add coverage support - # to the `jl_call_method_internal` fast path - # Still set pure flag to make sure `inference` tests pass - # and to possibly enable more optimization in the future - src.pure = true - if isa(result, Const) - val = result.val - if is_inlineable_constant(val) - analyzed = ConstAPI(val) - end - else - @assert isconstType(result) - analyzed = ConstAPI(result.parameters[1]) - end - force_noinline || (src.inlineable = true) - end - end + result = widenslotwrapper(result) opt.ir = ir @@ -466,23 +432,25 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, else force_noinline = true end - if !src.inlineable && result === Bottom + if !is_declared_inline(src) && result === Bottom force_noinline = true end end if force_noinline - src.inlineable = false + set_inlineable!(src, false) elseif isa(def, Method) - if src.inlineable && isdispatchtuple(specTypes) + if is_declared_inline(src) && isdispatchtuple(specTypes) # obey @inline declaration if a dispatch barrier would not help + set_inlineable!(src, true) else # compute the cost (size) of inlining this code + params = OptimizationParams(interp) cost_threshold = default = params.inline_cost_threshold - if result ⊑ Tuple && !isconcretetype(widenconst(result)) + if ⊑(optimizer_lattice(interp), result, Tuple) && !isconcretetype(widenconst(result)) cost_threshold += params.inline_tupleret_bonus end # if the method is declared as `@inline`, increase the cost threshold 20x - if src.inlineable + if is_declared_inline(src) cost_threshold += 19*default end # a few functions get special treatment @@ -492,18 +460,16 @@ function finish(interp::AbstractInterpreter, opt::OptimizationState, cost_threshold += 4*default end end - src.inlineable = inline_worthy(ir, params, union_penalties, cost_threshold) + src.inlining_cost = inline_cost(ir, params, union_penalties, cost_threshold) end end - - return analyzed + return nothing end # run the optimization work -function optimize(interp::AbstractInterpreter, opt::OptimizationState, - params::OptimizationParams, caller::InferenceResult) +function optimize(interp::AbstractInterpreter, opt::OptimizationState, caller::InferenceResult) @timeit "optimizer" ir = run_passes(opt.src, opt, caller) - return finish(interp, opt, params, ir, caller) + return finish(interp, opt, ir, caller) end using .EscapeAnalysis @@ -532,50 +498,84 @@ function ipo_escape_cache(mi_cache::MICache) where MICache end null_escape_cache(linfo::Union{InferenceResult,MethodInstance}) = nothing -function run_passes(ci::CodeInfo, sv::OptimizationState, caller::InferenceResult) - @timeit "convert" ir = convert_to_ircode(ci, sv) - @timeit "slot2reg" ir = slot2reg(ir, ci, sv) +macro pass(name, expr) + optimize_until = esc(:optimize_until) + stage = esc(:__stage__) + macrocall = :(@timeit $(esc(name)) $(esc(expr))) + macrocall.args[2] = __source__ # `@timeit` may want to use it + quote + $macrocall + matchpass($optimize_until, ($stage += 1), $(esc(name))) && $(esc(:(@goto __done__))) + end +end + +matchpass(optimize_until::Int, stage, _) = optimize_until == stage +matchpass(optimize_until::String, _, name) = optimize_until == name +matchpass(::Nothing, _, _) = false + +function run_passes( + ci::CodeInfo, + sv::OptimizationState, + caller::InferenceResult, + optimize_until = nothing, # run all passes by default +) + __stage__ = 0 # used by @pass + # NOTE: The pass name MUST be unique for `optimize_until::AbstractString` to work + @pass "convert" ir = convert_to_ircode(ci, sv) + @pass "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, ir.linetable, sv.inlining, ci.propagate_inbounds) + @pass "compact 1" ir = compact!(ir) + @pass "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) - @timeit "ADCE" ir = adce_pass!(ir) - @timeit "type lift" ir = type_lift_pass!(ir) - @timeit "compact 3" ir = compact!(ir) + @pass "compact 2" ir = compact!(ir) + @pass "SROA" ir = sroa_pass!(ir, sv.inlining) + @pass "ADCE" ir = adce_pass!(ir, sv.inlining) + @pass "type lift" ir = type_lift_pass!(ir) + @pass "compact 3" ir = compact!(ir) if JLOptions().debug_level == 2 @timeit "verify 3" (verify_ir(ir); verify_linetable(ir.linetable)) end + @label __done__ # used by @pass return ir end function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) - code = copy_exprargs(ci.code) - coverage = coverage_enabled(sv.mod) + linetable = ci.linetable + if !isa(linetable, Vector{LineInfoNode}) + linetable = collect(LineInfoNode, linetable::Vector{Any})::Vector{LineInfoNode} + end + # Go through and add an unreachable node after every # Union{} call. Then reindex labels. - idx = 1 - oldidx = 1 - changemap = fill(0, length(code)) - labelmap = coverage ? fill(0, length(code)) : changemap - prevloc = zero(eltype(ci.codelocs)) + code = copy_exprargs(ci.code) stmtinfo = sv.stmt_info codelocs = ci.codelocs ssavaluetypes = ci.ssavaluetypes::Vector{Any} ssaflags = ci.ssaflags + meta = Expr[] + idx = 1 + oldidx = 1 + nstmts = length(code) + ssachangemap = labelchangemap = nothing + prevloc = zero(eltype(ci.codelocs)) while idx <= length(code) codeloc = codelocs[idx] - if coverage && codeloc != prevloc && codeloc != 0 + if sv.insert_coverage && codeloc != prevloc && codeloc != 0 # 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) insert!(ssavaluetypes, idx, Nothing) - insert!(stmtinfo, idx, nothing) + insert!(stmtinfo, idx, NoCallInfo()) insert!(ssaflags, idx, IR_FLAG_NULL) - changemap[oldidx] += 1 - if oldidx < length(labelmap) - labelmap[oldidx + 1] += 1 + if ssachangemap === nothing + ssachangemap = fill(0, nstmts) + end + if labelchangemap === nothing + labelchangemap = fill(0, nstmts) + end + ssachangemap[oldidx] += 1 + if oldidx < length(labelchangemap) + labelchangemap[oldidx + 1] += 1 end idx += 1 prevloc = codeloc @@ -586,11 +586,17 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) insert!(code, idx + 1, ReturnNode()) insert!(codelocs, idx + 1, codelocs[idx]) insert!(ssavaluetypes, idx + 1, Union{}) - insert!(stmtinfo, idx + 1, nothing) - insert!(ssaflags, idx + 1, ssaflags[idx]) - if oldidx < length(changemap) - changemap[oldidx + 1] += 1 - coverage && (labelmap[oldidx + 1] += 1) + insert!(stmtinfo, idx + 1, NoCallInfo()) + insert!(ssaflags, idx + 1, IR_FLAG_NOTHROW) + if ssachangemap === nothing + ssachangemap = fill(0, nstmts) + end + if labelchangemap === nothing + labelchangemap = sv.insert_coverage ? fill(0, nstmts) : ssachangemap + end + if oldidx < length(ssachangemap) + ssachangemap[oldidx + 1] += 1 + sv.insert_coverage && (labelchangemap[oldidx + 1] += 1) end idx += 1 end @@ -598,32 +604,33 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) idx += 1 oldidx += 1 end - renumber_ir_elements!(code, changemap, labelmap) - meta = Any[] + cfg = sv.cfg + if ssachangemap !== nothing && labelchangemap !== nothing + renumber_ir_elements!(code, ssachangemap, labelchangemap) + cfg = nothing # recompute CFG + end + for i = 1:length(code) - code[i] = remove_meta!(code[i], meta) + code[i] = process_meta!(meta, code[i]) end strip_trailing_junk!(ci, code, stmtinfo) - cfg = compute_basic_blocks(code) types = Any[] stmts = InstructionStream(code, types, stmtinfo, codelocs, ssaflags) - linetable = ci.linetable - isa(linetable, Vector{LineInfoNode}) || (linetable = collect(LineInfoNode, linetable::Vector{Any})) - ir = IRCode(stmts, cfg, linetable, sv.slottypes, meta, sv.sptypes) - return ir + if cfg === nothing + cfg = compute_basic_blocks(code) + end + # 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, cfg, linetable, argtypes, meta, sv.sptypes) end -function remove_meta!(@nospecialize(stmt), meta::Vector{Any}) - if isa(stmt, Expr) - head = stmt.head - if head === :meta - args = stmt.args - if length(args) > 0 - push!(meta, stmt) - end - return nothing - end +function process_meta!(meta::Vector{Expr}, @nospecialize stmt) + if isexpr(stmt, :meta) && length(stmt.args) ≥ 1 + push!(meta, stmt) + return nothing end return stmt end @@ -634,7 +641,11 @@ function slot2reg(ir::IRCode, ci::CodeInfo, sv::OptimizationState) nargs = isa(svdef, Method) ? Int(svdef.nargs) : 0 @timeit "domtree 1" domtree = construct_domtree(ir.cfg.blocks) defuse_insts = scan_slot_def_use(nargs, ci, ir.stmts.inst) - @timeit "construct_ssa" ir = construct_ssa!(ci, ir, domtree, defuse_insts, sv.slottypes) # consumes `ir` + 𝕃ₒ = optimizer_lattice(sv.inlining.interp) + @timeit "construct_ssa" ir = construct_ssa!(ci, ir, domtree, defuse_insts, sv.slottypes, 𝕃ₒ) # consumes `ir` + # NOTE now we have converted `ir` to the SSA form and eliminated slots + # let's resize `argtypes` now and remove unnecessary types for the eliminated slots + resize!(ir.argtypes, nargs) return ir end @@ -646,7 +657,7 @@ plus_saturate(x::Int, y::Int) = max(x, y, x+y) # known return type isknowntype(@nospecialize T) = (T === Union{}) || isa(T, Const) || isconcretetype(widenconst(T)) -function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptypes::Vector{Any}, +function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptypes::Vector{VarState}, union_penalties::Bool, params::OptimizationParams, error_path::Bool = false) head = ex.head if is_meta_expr_head(head) @@ -671,11 +682,11 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp end return T_IFUNC_COST[iidx] end - if isa(f, Builtin) + if isa(f, Builtin) && f !== invoke # The efficiency of operations like a[i] and s.b # depend strongly on whether the result can be # inferred, so check the type of ex - if f === Core.getfield || f === Core.tuple + if f === Core.getfield || f === Core.tuple || f === Core.getglobal # we might like to penalize non-inferrability, but # tuple iteration/destructuring makes that impossible # return plus_saturate(argcost, isknowntype(extyp) ? 1 : params.inline_nonleaf_penalty) @@ -706,7 +717,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return 0 end return error_path ? params.inline_error_path_cost : params.inline_nonleaf_penalty - elseif head === :foreigncall || head === :invoke || head == :invoke_modify + elseif head === :foreigncall || head === :invoke || head === :invoke_modify # Calls whose "return type" is Union{} do not actually return: # they are errors. Since these are not part of the typical # run-time of the function, we omit them from @@ -737,7 +748,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp return 0 end -function statement_or_branch_cost(@nospecialize(stmt), line::Int, src::Union{CodeInfo, IRCode}, sptypes::Vector{Any}, +function statement_or_branch_cost(@nospecialize(stmt), line::Int, src::Union{CodeInfo, IRCode}, sptypes::Vector{VarState}, union_penalties::Bool, params::OptimizationParams) thiscost = 0 dst(tgt) = isa(src, IRCode) ? first(src.cfg.blocks[tgt].stmts) : tgt @@ -755,19 +766,19 @@ function statement_or_branch_cost(@nospecialize(stmt), line::Int, src::Union{Cod return thiscost end -function inline_worthy(ir::IRCode, - params::OptimizationParams, union_penalties::Bool=false, cost_threshold::Integer=params.inline_cost_threshold) +function inline_cost(ir::IRCode, params::OptimizationParams, union_penalties::Bool=false, + cost_threshold::Integer=params.inline_cost_threshold)::InlineCostType bodycost::Int = 0 for line = 1:length(ir.stmts) stmt = ir.stmts[line][:inst] thiscost = statement_or_branch_cost(stmt, line, ir, ir.sptypes, union_penalties, params) bodycost = plus_saturate(bodycost, thiscost) - bodycost > cost_threshold && return false + bodycost > cost_threshold && return MAX_INLINE_COST end - return true + return inline_cost_clamp(bodycost) end -function statement_costs!(cost::Vector{Int}, body::Vector{Any}, src::Union{CodeInfo, IRCode}, sptypes::Vector{Any}, unionpenalties::Bool, params::OptimizationParams) +function statement_costs!(cost::Vector{Int}, body::Vector{Any}, src::Union{CodeInfo, IRCode}, sptypes::Vector{VarState}, unionpenalties::Bool, params::OptimizationParams) maxcost = 0 for line = 1:length(body) stmt = body[line] @@ -781,31 +792,33 @@ function statement_costs!(cost::Vector{Int}, body::Vector{Any}, src::Union{CodeI return maxcost end -function renumber_ir_elements!(body::Vector{Any}, changemap::Vector{Int}) - return renumber_ir_elements!(body, changemap, changemap) +function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}) + return renumber_ir_elements!(body, ssachangemap, ssachangemap) end -function cumsum_ssamap!(ssamap::Vector{Int}) +function cumsum_ssamap!(ssachangemap::Vector{Int}) + any_change = false rel_change = 0 - for i = 1:length(ssamap) - rel_change += ssamap[i] - if ssamap[i] == -1 + for i = 1:length(ssachangemap) + val = ssachangemap[i] + any_change |= val ≠ 0 + rel_change += val + if val == -1 # Keep a marker that this statement was deleted - ssamap[i] = typemin(Int) + ssachangemap[i] = typemin(Int) else - ssamap[i] = rel_change + ssachangemap[i] = rel_change end end + return any_change end function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}, labelchangemap::Vector{Int}) - cumsum_ssamap!(labelchangemap) + any_change = cumsum_ssamap!(labelchangemap) if ssachangemap !== labelchangemap - cumsum_ssamap!(ssachangemap) - end - if labelchangemap[end] == 0 && ssachangemap[end] == 0 - return + any_change |= cumsum_ssamap!(ssachangemap) end + any_change || return for i = 1:length(body) el = body[i] if isa(el, GotoNode) @@ -815,7 +828,8 @@ function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}, lab if isa(cond, SSAValue) cond = SSAValue(cond.id + ssachangemap[cond.id]) end - body[i] = GotoIfNot(cond, el.dest + labelchangemap[el.dest]) + was_deleted = labelchangemap[el.dest] == typemin(Int) + body[i] = was_deleted ? cond : GotoIfNot(cond, el.dest + labelchangemap[el.dest]) elseif isa(el, ReturnNode) if isdefined(el, :val) val = el.val diff --git a/base/compiler/sort.jl b/base/compiler/sort.jl new file mode 100644 index 0000000000000..71d2f8a51cd59 --- /dev/null +++ b/base/compiler/sort.jl @@ -0,0 +1,100 @@ +# 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 + +# 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. +function searchsortedfirst(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer + hi = hi + T(1) + len = hi - lo + @inbounds while len != 0 + half_len = len >>> 0x01 + m = lo + half_len + if lt(o, v[m], x) + lo = m + 1 + len -= half_len + 1 + else + hi = m + len = half_len + end + end + return lo +end + +# index of the last value of vector a that is less than or equal to x; +# returns firstindex(v)-1 if x is less than all values of v. +function searchsortedlast(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer + u = T(1) + lo = lo - u + hi = hi + u + @inbounds while lo < hi - u + m = midpoint(lo, hi) + if lt(o, x, v[m]) + hi = m + else + lo = m + end + end + return lo +end + +# returns the range of indices of v equal to x +# if v does not contain x, returns a 0-length range +# indicating the insertion point of x +function searchsorted(v::AbstractVector, x, ilo::T, ihi::T, o::Ordering)::UnitRange{keytype(v)} where T<:Integer + u = T(1) + lo = ilo - u + hi = ihi + u + @inbounds while lo < hi - u + m = midpoint(lo, hi) + if lt(o, v[m], x) + lo = m + elseif lt(o, x, v[m]) + hi = m + else + a = searchsortedfirst(v, x, max(lo,ilo), m, o) + b = searchsortedlast(v, x, m, min(hi,ihi), o) + return a : b + end + end + return (lo + 1) : (hi - 1) +end + +for s in [:searchsortedfirst, :searchsortedlast, :searchsorted] + @eval begin + $s(v::AbstractVector, x, o::Ordering) = $s(v,x,firstindex(v),lastindex(v),o) + $s(v::AbstractVector, x; + lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) = + $s(v,x,ord(lt,by,rev,order)) + end +end + +# An unstable sorting algorithm for internal use +function sort!(v::Vector; by::Function=identity, (<)::Function=<) + isempty(v) && return v # This branch is hit 95% of the time + + # Of the remaining 5%, this branch is hit less than 1% of the time + if length(v) > 200 # Heap sort prevents quadratic runtime + o = ord(<, by, true) + heapify!(v, o) + for i in lastindex(v):-1:2 + y = v[i] + v[i] = v[1] + percolate_down!(v, 1, y, o, i-1) + end + return v + end + + @inbounds for i in 2:length(v) # Insertion sort + x = v[i] + y = by(x) + while i > 1 && y < by(v[i-1]) + v[i] = v[i-1] + i -= 1 + end + v[i] = x + end + + v +end diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index 407b447a228a3..8bc173add6eaa 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -27,7 +27,7 @@ import ._TOP_MOD: # Base definitions pop!, push!, pushfirst!, empty!, delete!, max, min, enumerate, unwrap_unionall, ismutabletype import Core.Compiler: # Core.Compiler specific definitions - Bottom, InferenceResult, IRCode, IR_FLAG_EFFECT_FREE, + Bottom, OptimizerLattice, InferenceResult, IRCode, IR_FLAG_NOTHROW, isbitstype, isexpr, is_meta_expr_head, println, widenconst, argextype, singleton_type, fieldcount_noerror, try_compute_field, try_compute_fieldidx, hasintersect, ⊑, intrinsic_nothrow, array_builtin_common_typecheck, arrayset_typecheck, @@ -42,6 +42,7 @@ end const AInfo = IdSet{Any} const LivenessSet = BitSet +const 𝕃ₒ = OptimizerLattice() """ x::EscapeInfo @@ -772,7 +773,7 @@ A preparatory linear scan before the escape analysis on `ir` to find: This array dimension analysis to compute `arrayinfo` is very local and doesn't account for flow-sensitivity nor complex aliasing. Ideally this dimension analysis should be done as a part of type inference that - propagates array dimenstions in a flow sensitive way. + propagates array dimensions in a flow sensitive way. """ function compute_frameinfo(ir::IRCode, call_resolved::Bool) nstmts, nnewnodes = length(ir.stmts), length(ir.new_nodes.stmts) @@ -787,9 +788,10 @@ function compute_frameinfo(ir::IRCode, call_resolved::Bool) stmt = inst[:inst] if !call_resolved # TODO don't call `check_effect_free!` in the inlinear - check_effect_free!(ir, idx, stmt, inst[:type]) + check_effect_free!(ir, idx, stmt, inst[:type], 𝕃ₒ) end if callinfo !== nothing && isexpr(stmt, :call) + # TODO: pass effects here callinfo[idx] = resolve_call(ir, stmt, inst[:info]) elseif isexpr(stmt, :enter) @assert idx ≤ nstmts "try/catch inside new_nodes unsupported" @@ -1078,7 +1080,7 @@ end error("unexpected assignment found: inspect `Main.pc` and `Main.pc`") end -is_effect_free(ir::IRCode, pc::Int) = getinst(ir, pc)[:flag] & IR_FLAG_EFFECT_FREE ≠ 0 +is_nothrow(ir::IRCode, pc::Int) = getinst(ir, pc)[:flag] & IR_FLAG_NOTHROW ≠ 0 # 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 @@ -1259,7 +1261,7 @@ function escape_foreigncall!(astate::AnalysisState, pc::Int, args::Vector{Any}) # end end # NOTE array allocations might have been proven as nothrow (https://github.com/JuliaLang/julia/pull/43565) - nothrow = is_effect_free(astate.ir, pc) + nothrow = is_nothrow(astate.ir, pc) name_info = nothrow ? ⊥ : ThrownEscape(pc) add_escape_change!(astate, name, name_info) add_liveness_change!(astate, name, pc) @@ -1290,7 +1292,7 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any}, callinf # now cascade to the builtin handling escape_call!(astate, pc, args) return - elseif isa(info, CallInfo) + elseif isa(info, EACallInfo) for linfo in info.linfos escape_invoke!(astate, pc, args, linfo, 1) end @@ -1333,9 +1335,9 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any}) return # ThrownEscape is already checked else # we escape statements with the `ThrownEscape` property using the effect-freeness - # computed by `stmt_effect_free` invoked within inlining + # computed by `stmt_effect_flags` invoked within inlining # TODO throwness ≠ "effect-free-ness" - if is_effect_free(astate.ir, pc) + if is_nothrow(astate.ir, pc) add_liveness_changes!(astate, pc, args, 2) else add_fallback_changes!(astate, pc, args, 2) @@ -1441,7 +1443,7 @@ function escape_new!(astate::AnalysisState, pc::Int, args::Vector{Any}) add_liveness_change!(astate, arg, pc) end end - if !is_effect_free(astate.ir, pc) + if !is_nothrow(astate.ir, pc) add_thrown_escapes!(astate, pc, args) end end @@ -1503,6 +1505,8 @@ function escape_builtin!(::typeof(getfield), astate::AnalysisState, pc::Int, arg if isa(obj, SSAValue) || isa(obj, Argument) objinfo = estate[obj] else + # unanalyzable object, so the return value is also unanalyzable + add_escape_change!(astate, SSAValue(pc), ⊤) return false end AliasInfo = objinfo.AliasInfo @@ -1594,12 +1598,16 @@ 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 - argtypes = Any[] - for i = 2:length(args) - push!(argtypes, argextype(args[i], ir)) + if length(args) == 4 && setfield!_nothrow(𝕃ₒ, + argextype(args[2], ir), argextype(args[3], ir), argextype(args[4], ir)) + return true + elseif length(args) == 3 && setfield!_nothrow(𝕃ₒ, + argextype(args[2], ir), argextype(args[3], ir)) + return true + else + add_thrown_escapes!(astate, pc, args, 2) + return true end - setfield!_nothrow(argtypes) || add_thrown_escapes!(astate, pc, args, 2) - return true end function escape_builtin!(::typeof(arrayref), astate::AnalysisState, pc::Int, args::Vector{Any}) @@ -1622,6 +1630,8 @@ function escape_builtin!(::typeof(arrayref), astate::AnalysisState, pc::Int, arg 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 @@ -1875,13 +1885,13 @@ end # # COMBAK do we want to enable this (and also backport this to Base for array allocations?) # import Core.Compiler: Cint, svec # function validate_foreigncall_args(args::Vector{Any}, -# name::Symbol, @nospecialize(rt), argtypes::SimpleVector, nreq::Int, convension::Symbol) +# 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]) === convension || return false +# normalize(args[5]) === convention || return false # return true # end diff --git a/base/compiler/ssair/EscapeAnalysis/interprocedural.jl b/base/compiler/ssair/EscapeAnalysis/interprocedural.jl index 9880c13db4ad1..26b0e5b404641 100644 --- a/base/compiler/ssair/EscapeAnalysis/interprocedural.jl +++ b/base/compiler/ssair/EscapeAnalysis/interprocedural.jl @@ -1,24 +1,26 @@ # TODO this file contains many duplications with the inlining analysis code, factor them out import Core.Compiler: - MethodInstance, InferenceResult, Signature, ConstResult, - MethodResultPure, MethodMatchInfo, UnionSplitInfo, ConstCallInfo, InvokeCallInfo, - call_sig, argtypes_to_type, is_builtin, is_return_type, istopfunction, validate_sparams, - specialize_method, invoke_rewrite + MethodInstance, InferenceResult, Signature, ConstPropResult, ConcreteResult, + SemiConcreteResult, CallInfo, NoCallInfo, MethodResultPure, MethodMatchInfo, + UnionSplitInfo, ConstCallInfo, InvokeCallInfo, + call_sig, argtypes_to_type, is_builtin, is_return_type, istopfunction, + validate_sparams, specialize_method, invoke_rewrite const Linfo = Union{MethodInstance,InferenceResult} -struct CallInfo +struct EACallInfo linfos::Vector{Linfo} nothrow::Bool end -function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info)) +function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info::CallInfo)) + # TODO: if effect free, return true sig = call_sig(ir, stmt) if sig === nothing return missing end # TODO handle _apply_iterate - if is_builtin(sig) && sig.f !== invoke + if is_builtin(𝕃ₒ, sig) && sig.f !== invoke return false end # handling corresponding to late_inline_special_case! @@ -36,7 +38,7 @@ function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info)) end if info isa MethodResultPure return true - elseif info === false + elseif info === NoCallInfo return missing end # TODO handle OpaqueClosureCallInfo @@ -62,13 +64,17 @@ function analyze_invoke_call(sig::Signature, info::InvokeCallInfo) return missing end result = info.result - if isa(result, InferenceResult) - return CallInfo(Linfo[result], true) + if isa(result, ConstPropResult) + return EACallInfo(Linfo[result.result], true) + elseif isa(result, ConcreteResult) + return EACallInfo(Linfo[result.mi], true) + elseif isa(result, SemiConcreteResult) + return EACallInfo(Linfo[result.mi], true) else argtypes = invoke_rewrite(sig.argtypes) mi = analyze_match(match, length(argtypes)) mi === nothing && return missing - return CallInfo(Linfo[mi], true) + return EACallInfo(Linfo[mi], true) end end @@ -95,16 +101,18 @@ function analyze_const_call(sig::Signature, cinfo::ConstCallInfo) mi = analyze_match(match, length(sig.argtypes)) mi === nothing && return missing push!(linfos, mi) - elseif isa(result, ConstResult) + elseif isa(result, ConcreteResult) # TODO we may want to feedback information that this call always throws if !isdefined(result, :result) push!(linfos, result.mi) - else - push!(linfos, result) + elseif isa(result, SemiConcreteResult) + push!(linfos, result.mi) + elseif isa(result, ConstPropResult) + push!(linfos, result.result) end nothrow &= match.fully_covers end end - return CallInfo(linfos, nothrow) + return EACallInfo(linfos, nothrow) end function analyze_call(sig::Signature, infos::Vector{MethodMatchInfo}) @@ -127,7 +135,7 @@ function analyze_call(sig::Signature, infos::Vector{MethodMatchInfo}) nothrow &= match.fully_covers end end - return CallInfo(linfos, nothrow) + return EACallInfo(linfos, nothrow) end function analyze_match(match::MethodMatch, npassedargs::Int) diff --git a/base/compiler/ssair/domtree.jl b/base/compiler/ssair/domtree.jl index fd49a7e118eb7..1edb8d2d5c6d4 100644 --- a/base/compiler/ssair/domtree.jl +++ b/base/compiler/ssair/domtree.jl @@ -109,10 +109,16 @@ end length(D::DFSTree) = length(D.from_pre) -function DFS!(D::DFSTree, blocks::Vector{BasicBlock}) +function DFS!(D::DFSTree, blocks::Vector{BasicBlock}, is_post_dominator::Bool) copy!(D, DFSTree(length(blocks))) - to_visit = Tuple{BBNumber, PreNumber, Bool}[(1, 0, false)] - pre_num = 1 + 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)] + else + to_visit = Tuple{BBNumber, PreNumber, Bool}[(1, 0, false)] + end + pre_num = is_post_dominator ? 0 : 1 post_num = 1 while !isempty(to_visit) # Because we want the postorder number as well as the preorder number, @@ -123,12 +129,14 @@ function DFS!(D::DFSTree, blocks::Vector{BasicBlock}) if pushed_children # Going up the DFS tree, so all we need to do is record the # postorder number, then move on - D.to_post[current_node_bb] = post_num - D.from_post[post_num] = current_node_bb + if current_node_bb != -1 + D.to_post[current_node_bb] = post_num + D.from_post[post_num] = current_node_bb + end post_num += 1 pop!(to_visit) - elseif D.to_pre[current_node_bb] != 0 + elseif current_node_bb != -1 && D.to_pre[current_node_bb] != 0 # Node has already been visited, move on pop!(to_visit) continue @@ -136,15 +144,30 @@ function DFS!(D::DFSTree, blocks::Vector{BasicBlock}) # Going down the DFS tree # Record preorder number - D.to_pre[current_node_bb] = pre_num - D.from_pre[pre_num] = current_node_bb - D.to_parent_pre[pre_num] = parent_pre + if current_node_bb != -1 + D.to_pre[current_node_bb] = pre_num + D.from_pre[pre_num] = current_node_bb + D.to_parent_pre[pre_num] = parent_pre + end # Record that children (will) have been pushed to_visit[end] = (current_node_bb, parent_pre, true) + if is_post_dominator && current_node_bb == -1 + edges = Int[bb for bb in 1:length(blocks) if isempty(blocks[bb].succs)] + else + edges = is_post_dominator ? blocks[current_node_bb].preds : + blocks[current_node_bb].succs + end + # Push children to the stack - for succ_bb in blocks[current_node_bb].succs + for succ_bb in edges + if succ_bb == 0 + # Edge 0 indicates an error entry, but shouldn't affect + # the post-dominator tree. + @assert is_post_dominator + continue + end push!(to_visit, (succ_bb, pre_num, false)) end @@ -161,7 +184,7 @@ function DFS!(D::DFSTree, blocks::Vector{BasicBlock}) return D end -DFS(blocks::Vector{BasicBlock}) = DFS!(DFSTree(0), blocks) +DFS(blocks::Vector{BasicBlock}, is_post_dominator::Bool=false) = DFS!(DFSTree(0), blocks, is_post_dominator) """ Keeps the per-BB state of the Semi NCA algorithm. In the original formulation, @@ -184,7 +207,7 @@ end DomTreeNode() = DomTreeNode(1, Vector{BBNumber}()) "Data structure that encodes which basic block dominates which." -struct DomTree +struct GenericDomTree{IsPostDom} # These can be reused when updating domtree dynamically dfs_tree::DFSTree snca_state::Vector{SNCAData} @@ -195,19 +218,25 @@ struct DomTree # The nodes in the tree (ordered by BB indices) nodes::Vector{DomTreeNode} end +const DomTree = GenericDomTree{false} +const PostDomTree = GenericDomTree{true} -function DomTree() - return DomTree(DFSTree(0), SNCAData[], BBNumber[], DomTreeNode[]) +function (T::Type{<:GenericDomTree})() + return T(DFSTree(0), SNCAData[], BBNumber[], DomTreeNode[]) end function construct_domtree(blocks::Vector{BasicBlock}) return update_domtree!(blocks, DomTree(), true, 0) end -function update_domtree!(blocks::Vector{BasicBlock}, domtree::DomTree, - recompute_dfs::Bool, max_pre::PreNumber) +function construct_postdomtree(blocks::Vector{BasicBlock}) + return update_domtree!(blocks, PostDomTree(), true, 0) +end + +function update_domtree!(blocks::Vector{BasicBlock}, domtree::GenericDomTree{IsPostDom}, + recompute_dfs::Bool, max_pre::PreNumber) where {IsPostDom} if recompute_dfs - DFS!(domtree.dfs_tree, blocks) + DFS!(domtree.dfs_tree, blocks, IsPostDom) end if max_pre == 0 @@ -219,16 +248,24 @@ function update_domtree!(blocks::Vector{BasicBlock}, domtree::DomTree, return domtree end -function compute_domtree_nodes!(domtree::DomTree) +function compute_domtree_nodes!(domtree::GenericDomTree{IsPostDom}) where {IsPostDom} # Compute children copy!(domtree.nodes, DomTreeNode[DomTreeNode() for _ in 1:length(domtree.idoms_bb)]) for (idx, idom) in Iterators.enumerate(domtree.idoms_bb) - (idx == 1 || idom == 0) && continue + ((!IsPostDom && idx == 1) || idom == 0) && continue push!(domtree.nodes[idom].children, idx) end + # n.b. now issorted(domtree.nodes[*].children) since idx is sorted above # Recursively set level - update_level!(domtree.nodes, 1, 1) + if IsPostDom + for (node, idom) in enumerate(domtree.idoms_bb) + idom == 0 || continue + update_level!(domtree.nodes, node, 1) + end + else + update_level!(domtree.nodes, 1, 1) + end return domtree.nodes end @@ -243,13 +280,18 @@ function update_level!(nodes::Vector{DomTreeNode}, node::BBNumber, level::Int) end end +dom_edges(domtree::DomTree, blocks::Vector{BasicBlock}, idx::BBNumber) = + blocks[idx].preds +dom_edges(domtree::PostDomTree, blocks::Vector{BasicBlock}, idx::BBNumber) = + blocks[idx].succs + """ The main Semi-NCA algorithm. Matches Figure 2.8 in [LG05]. Note that the pseudocode in [LG05] is not entirely accurate. The best way to understand what's happening is to read [LT79], then the description of SLT in [LG05] (warning: inconsistent notation), then the description of Semi-NCA. """ -function SNCA!(domtree::DomTree, blocks::Vector{BasicBlock}, max_pre::PreNumber) +function SNCA!(domtree::GenericDomTree{IsPostDom}, blocks::Vector{BasicBlock}, max_pre::PreNumber) where {IsPostDom} D = domtree.dfs_tree state = domtree.snca_state # There may be more blocks than are reachable in the DFS / dominator tree @@ -288,13 +330,14 @@ function SNCA!(domtree::DomTree, blocks::Vector{BasicBlock}, max_pre::PreNumber) # Calculate semidominators, but only for blocks with preorder number up to # max_pre ancestors = copy(D.to_parent_pre) - for w::PreNumber in reverse(2:max_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) last_linked = PreNumber(w + 1) - for v ∈ blocks[D.from_pre[w]].preds + for v ∈ dom_edges(domtree, blocks, D.from_pre[w]) # For the purpose of the domtree, ignore virtual predecessors into # catch blocks. v == 0 && continue @@ -330,7 +373,7 @@ function SNCA!(domtree::DomTree, blocks::Vector{BasicBlock}, max_pre::PreNumber) # ancestor in the (immediate) dominator tree between its semidominator and # its parent (see Lemma 2.6 in [LG05]). idoms_pre = copy(D.to_parent_pre) - for v in 2:n_nodes + for v in (IsPostDom ? (1:n_nodes) : (2:n_nodes)) idom = idoms_pre[v] vsemi = state[v].semi while idom > vsemi @@ -342,10 +385,11 @@ function SNCA!(domtree::DomTree, blocks::Vector{BasicBlock}, max_pre::PreNumber) # Express idoms in BB indexing resize!(domtree.idoms_bb, n_blocks) for i::BBNumber in 1:n_blocks - if i == 1 || D.to_pre[i] == 0 + if (!IsPostDom && i == 1) || D.to_pre[i] == 0 domtree.idoms_bb[i] = 0 else - domtree.idoms_bb[i] = D.from_pre[idoms_pre[D.to_pre[i]]] + ip = idoms_pre[D.to_pre[i]] + domtree.idoms_bb[i] = ip == 0 ? 0 : D.from_pre[ip] end end end @@ -541,12 +585,28 @@ function rename_nodes!(D::DFSTree, rename_bb::Vector{BBNumber}) end """ -Checks if bb1 dominates bb2. -bb1 and bb2 are indexes into the CFG blocks. -bb1 dominates bb2 if the only way to enter bb2 is via bb1. -(Other blocks may be in between, e.g bb1->bbX->bb2). + dominates(domtree::DomTree, bb1::Int, bb2::Int) -> Bool + +Checks if `bb1` dominates `bb2`. +`bb1` and `bb2` are indexes into the `CFG` blocks. +`bb1` dominates `bb2` if the only way to enter `bb2` is via `bb1`. +(Other blocks may be in between, e.g `bb1->bbx->bb2`). """ -function dominates(domtree::DomTree, bb1::BBNumber, bb2::BBNumber) +dominates(domtree::DomTree, bb1::BBNumber, bb2::BBNumber) = + _dominates(domtree, bb1, bb2) + +""" + postdominates(domtree::DomTree, bb1::Int, bb2::Int) -> Bool + +Checks if `bb1` post-dominates `bb2`. +`bb1` and `bb2` are indexes into the `CFG` blocks. +`bb1` post-dominates `bb2` if every pass from `bb2` to the exit is via `bb1`. +(Other blocks may be in between, e.g `bb2->bbx->bb1->exit`). +""" +postdominates(domtree::PostDomTree, bb1::BBNumber, bb2::BBNumber) = + _dominates(domtree, bb1, bb2) + +function _dominates(domtree::GenericDomTree, bb1::BBNumber, bb2::BBNumber) bb1 == bb2 && return true target_level = domtree.nodes[bb1].level source_level = domtree.nodes[bb2].level @@ -581,19 +641,48 @@ function iterate(doms::DominatedBlocks, state::Nothing=nothing) return (bb, nothing) end -function naive_idoms(blocks::Vector{BasicBlock}) +""" + nearest_common_dominator(domtree::GenericDomTree, a::BBNumber, b::BBNumber) + +Compute the nearest common (post-)dominator of `a` and `b`. +""" +function nearest_common_dominator(domtree::GenericDomTree, a::BBNumber, b::BBNumber) + alevel = domtree.nodes[a].level + blevel = domtree.nodes[b].level + # W.l.g. assume blevel <= alevel + if alevel < blevel + a, b = b, a + alevel, blevel = blevel, alevel + end + while alevel > blevel + a = domtree.idoms_bb[a] + alevel -= 1 + end + while a != b && a != 0 + a = domtree.idoms_bb[a] + b = domtree.idoms_bb[b] + end + @assert a == b + return a +end + +function naive_idoms(blocks::Vector{BasicBlock}, is_post_dominator::Bool=false) nblocks = length(blocks) # The extra +1 helps us detect unreachable blocks below dom_all = BitSet(1:nblocks+1) - dominators = BitSet[n == 1 ? BitSet(1) : copy(dom_all) for n = 1:nblocks] + dominators = is_post_dominator ? + BitSet[isempty(blocks[n].succs) ? BitSet(n) : copy(dom_all) for n = 1:nblocks] : + BitSet[n == 1 ? BitSet(1) : copy(dom_all) for n = 1:nblocks] changed = true + relevant_blocks = (is_post_dominator ? (1:nblocks) : (2:nblocks)) while changed changed = false - for n = 2:nblocks - if isempty(blocks[n].preds) + for n in relevant_blocks + edges = is_post_dominator ? blocks[n].succs : blocks[n].preds + if isempty(edges) continue end - firstp, rest = Iterators.peel(Iterators.filter(p->p != 0, blocks[n].preds))::NTuple{2,Any} + firstp, rest = Iterators.peel(Iterators.filter(p->p != 0, edges))::NTuple{2,Any} new_doms = copy(dominators[firstp]) for p in rest intersect!(new_doms, dominators[p]) @@ -605,7 +694,7 @@ function naive_idoms(blocks::Vector{BasicBlock}) end # Compute idoms idoms = fill(0, nblocks) - for i = 2:nblocks + for i in relevant_blocks if dominators[i] == dom_all idoms[i] = 0 continue diff --git a/base/compiler/ssair/driver.jl b/base/compiler/ssair/driver.jl index 7759d8d80b9cc..1946a76714e57 100644 --- a/base/compiler/ssair/driver.jl +++ b/base/compiler/ssair/driver.jl @@ -3,22 +3,22 @@ if false import Base: Base, @show else - macro show(s) - return :(println(stdout, $(QuoteNode(s)), " = ", $(esc(s)))) + macro show(ex...) + blk = Expr(:block) + for s in ex + push!(blk.args, :(println(stdout, $(QuoteNode(s)), " = ", + begin local value = $(esc(s)) end))) + end + isempty(ex) || push!(blk.args, :value) + blk end end -function argextype end # imported by EscapeAnalysis -function stmt_effect_free end # imported by EscapeAnalysis -function alloc_array_ndims end # imported by EscapeAnalysis -function try_compute_field end # imported by EscapeAnalysis - -include("compiler/ssair/basicblock.jl") -include("compiler/ssair/domtree.jl") -include("compiler/ssair/ir.jl") +include("compiler/ssair/heap.jl") include("compiler/ssair/slot2ssa.jl") include("compiler/ssair/inlining.jl") include("compiler/ssair/verify.jl") include("compiler/ssair/legacy.jl") include("compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl") include("compiler/ssair/passes.jl") +include("compiler/ssair/irinterp.jl") diff --git a/base/compiler/ssair/heap.jl b/base/compiler/ssair/heap.jl new file mode 100644 index 0000000000000..6e9883bc4ec60 --- /dev/null +++ b/base/compiler/ssair/heap.jl @@ -0,0 +1,74 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# Heap operations on flat vectors +# ------------------------------- + + +# Binary heap indexing +heapleft(i::Integer) = 2i +heapright(i::Integer) = 2i + 1 +heapparent(i::Integer) = div(i, 2) + + +# Binary min-heap percolate down. +function percolate_down!(xs::Vector, i::Integer, x, o::Ordering, len::Integer=length(xs)) + @inbounds while (l = heapleft(i)) <= len + r = heapright(i) + j = r > len || lt(o, xs[l], xs[r]) ? l : r + lt(o, xs[j], x) || break + xs[i] = xs[j] + i = j + end + xs[i] = x +end + +# Binary min-heap percolate up. +function percolate_up!(xs::Vector, i::Integer, x, o::Ordering) + @inbounds while (j = heapparent(i)) >= 1 + lt(o, x, xs[j]) || break + xs[i] = xs[j] + i = j + end + xs[i] = x +end + +""" + heappop!(v, ord) + +Given a binary heap-ordered array, remove and return the lowest ordered element. +For efficiency, this function does not check that the array is indeed heap-ordered. +""" +function heappop!(xs::Vector, o::Ordering) + x = xs[1] + y = pop!(xs) + if !isempty(xs) + percolate_down!(xs, 1, y, o) + end + return x +end + +""" + heappush!(v, x, ord) + +Given a binary heap-ordered array, push a new element `x`, preserving the heap property. +For efficiency, this function does not check that the array is indeed heap-ordered. +""" +function heappush!(xs::Vector, x, o::Ordering) + push!(xs, x) + i = lastindex(xs) + percolate_up!(xs, i, @inbounds(xs[i]), o) + return xs +end + + +""" + heapify!(v, ord::Ordering) + +Turn an arbitrary vector into a binary min-heap in linear time. +""" +function heapify!(xs::Vector, o::Ordering) + for i in heapparent(lastindex(xs)):-1:1 + percolate_down!(xs, i, @inbounds(xs[i]), o) + end + return xs +end diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index fc3c3a60115e6..3c444894dd4b6 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1,7 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -@nospecialize - struct Signature f::Any ft::Any @@ -9,8 +7,10 @@ struct Signature Signature(@nospecialize(f), @nospecialize(ft), argtypes::Vector{Any}) = new(f, ft, argtypes) end -struct ResolvedInliningSpec - # The LineTable and IR of the inlinee +struct InliningTodo + # The MethodInstance to be inlined + mi::MethodInstance + # The IR of the inlinee ir::IRCode # If the function being inlined is a single basic block we can use a # simpler inlining algorithm. This flag determines whether that's allowed @@ -18,47 +18,29 @@ struct ResolvedInliningSpec # Effects of the call statement effects::Effects end - -""" -Represents a callsite that our analysis has determined is legal to inline, -but did not resolve during the analysis step to allow the outer inlining -pass to apply its own inlining policy decisions. -""" -struct DelayedInliningSpec - match::Union{MethodMatch, InferenceResult} - argtypes::Vector{Any} -end - -struct InliningTodo - # The MethodInstance to be inlined - mi::MethodInstance - spec::Union{ResolvedInliningSpec, DelayedInliningSpec} +function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) + return InliningTodo(mi, ir, linear_inline_eligible(ir), effects) end -InliningTodo(mi::MethodInstance, match::MethodMatch, argtypes::Vector{Any}) = - InliningTodo(mi, DelayedInliningSpec(match, argtypes)) - -InliningTodo(result::InferenceResult, argtypes::Vector{Any}) = - InliningTodo(result.linfo, DelayedInliningSpec(result, argtypes)) - struct ConstantCase val::Any - ConstantCase(val) = new(val) + ConstantCase(@nospecialize val) = new(val) end struct SomeCase val::Any - SomeCase(val) = new(val) + SomeCase(@nospecialize val) = new(val) end struct InvokeCase invoke::MethodInstance effects::Effects + info::CallInfo end struct InliningCase - sig # ::Type - item # Union{InliningTodo, MethodInstance, ConstantCase} + sig # Type + item # Union{InliningTodo, InvokeCase, ConstantCase} function InliningCase(@nospecialize(sig), @nospecialize(item)) @assert isa(item, Union{InliningTodo, InvokeCase, ConstantCase}) "invalid inlining item" return new(sig, item) @@ -67,22 +49,37 @@ end struct UnionSplit fully_covered::Bool - atype # ::Type + atype::DataType cases::Vector{InliningCase} bbs::Vector{Int} - UnionSplit(fully_covered::Bool, atype, cases::Vector{InliningCase}) = + UnionSplit(fully_covered::Bool, atype::DataType, cases::Vector{InliningCase}) = new(fully_covered, atype, cases, Int[]) end -@specialize +struct InliningEdgeTracker + et::Union{Nothing,EdgeTracker} + invokesig::Union{Nothing,Vector{Any}} +end +InliningEdgeTracker(et::Union{Nothing,EdgeTracker}) = InliningEdgeTracker(et, nothing) + +function add_inlining_backedge!((; et, invokesig)::InliningEdgeTracker, mi::MethodInstance) + if et !== nothing + if invokesig === nothing + add_backedge!(et, mi) + else + add_invoke_backedge!(et, invoke_signature(invokesig), mi) + end + end + return nothing +end -function ssa_inlining_pass!(ir::IRCode, linetable::Vector{LineInfoNode}, state::InliningState, propagate_inbounds::Bool) - # Go through the function, performing simple ininlingin (e.g. replacing call by constants +function ssa_inlining_pass!(ir::IRCode, state::InliningState, propagate_inbounds::Bool) + # Go through the function, performing simple inlining (e.g. replacing call by constants # and analyzing legality of inlining). @timeit "analysis" todo = assemble_inline_todo!(ir, state) isempty(todo) && return ir # Do the actual inlining for every call we identified - @timeit "execution" ir = batch_inline!(todo, ir, linetable, propagate_inbounds, state.params) + @timeit "execution" ir = batch_inline!(ir, todo, propagate_inbounds, OptimizationParams(state.interp)) return ir end @@ -124,8 +121,8 @@ function inline_into_block!(state::CFGInliningState, block::Int) return end -function cfg_inline_item!(ir::IRCode, idx::Int, spec::ResolvedInliningSpec, state::CFGInliningState, from_unionsplit::Bool=false) - inlinee_cfg = spec.ir.cfg +function cfg_inline_item!(ir::IRCode, idx::Int, todo::InliningTodo, state::CFGInliningState, from_unionsplit::Bool=false) + inlinee_cfg = todo.ir.cfg # Figure out if we need to split the BB need_split_before = false need_split = true @@ -200,7 +197,7 @@ function cfg_inline_item!(ir::IRCode, idx::Int, spec::ResolvedInliningSpec, stat for (old_block, new_block) in enumerate(bb_rename_range) if (length(state.new_cfg_blocks[new_block].succs) == 0) terminator_idx = last(inlinee_cfg.blocks[old_block].stmts) - terminator = spec.ir[SSAValue(terminator_idx)][:inst] + terminator = todo.ir[SSAValue(terminator_idx)][:inst] if isa(terminator, ReturnNode) && isdefined(terminator, :val) any_edges = true push!(state.new_cfg_blocks[new_block].succs, post_bb_id) @@ -233,15 +230,14 @@ function cfg_inline_unionsplit!(ir::IRCode, idx::Int, push!(state.new_cfg_blocks[cond_bb].succs, cond_bb+1) case = cases[i].item if isa(case, InliningTodo) - spec = case.spec::ResolvedInliningSpec - if !spec.linear_inline_eligible - cfg_inline_item!(ir, idx, spec, state, true) + if !case.linear_inline_eligible + cfg_inline_item!(ir, idx, case, state, true) end end push!(from_bbs, length(state.new_cfg_blocks)) # TODO: Right now we unconditionally generate a fallback block # in case of subtyping errors - This is probably unnecessary. - if i != length(cases) || (!fully_covered || !params.trust_inference) + if i != length(cases) || (!fully_covered || (!params.trust_inference)) # This block will have the next condition or the final else case push!(state.new_cfg_blocks, BasicBlock(StmtRange(idx, idx))) push!(state.new_cfg_blocks[cond_bb].succs, length(state.new_cfg_blocks)) @@ -306,28 +302,49 @@ function finish_cfg_inline!(state::CFGInliningState) end end -function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any}, - linetable::Vector{LineInfoNode}, item::InliningTodo, - boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) - # Ok, do the inlining here - spec = item.spec::ResolvedInliningSpec - sparam_vals = item.mi.sparam_vals - def = item.mi.def::Method - inline_cfg = spec.ir.cfg +# 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 - inlined_at = Int(compact.result[idx][:line]) topline::Int32 = linetable_offset + Int32(1) - coverage = coverage_enabled(def.module) - push!(linetable, LineInfoNode(def.module, def.name, def.file, Int(def.line), inlined_at)) - oldlinetable = spec.ir.linetable - for oldline in 1:length(oldlinetable) + coverage_by_path = JLOptions().code_coverage == 3 + push!(linetable, LineInfoNode(inlinee_def.module, inlinee, 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 newentry === linetable[topline] + if inline_node_is_duplicate(linetable[topline], newentry) continue else linetable_offset += 1 @@ -335,21 +352,52 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector end push!(linetable, newentry) end - if coverage && spec.ir.stmts[1][:line] + linetable_offset != topline - insert_node_here!(compact, NewInstruction(Expr(:code_coverage_effect), Nothing, topline)) + if coverage && inlinee_ir.stmts[1][:line] + linetable_offset != topline + extra_coverage_line = topline + end + return linetable_offset, extra_coverage_line +end + +function ir_prepare_inlining!(insert_node!::Inserter, inline_target::Union{IRCode, IncrementalCompact}, + linetable::Vector{LineInfoNode}, ir′::IRCode, sparam_vals::SimpleVector, + mi::MethodInstance, inlined_at::Int32, argexprs::Vector{Any}) + def = mi.def::Method + 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)) + end + sp_ssa = nothing + if !validate_sparams(sparam_vals) + # N.B. This works on the caller-side argexprs, (i.e. before the va fixup below) + sp_ssa = insert_node!( + effect_free(NewInstruction(Expr(:call, Core._compute_sparams, def, argexprs...), SimpleVector, topline))) end if def.isva nargs_def = Int(def.nargs::Int32) if nargs_def > 0 - argexprs = fix_va_argexprs!(compact, argexprs, nargs_def, topline) + argexprs = fix_va_argexprs!(insert_node!, inline_target, argexprs, nargs_def, topline) end end if def.is_for_opaque_closure # Replace the first argument by a load of the capture environment - argexprs[1] = insert_node_here!(compact, + argexprs[1] = insert_node!( NewInstruction(Expr(:call, GlobalRef(Core, :getfield), argexprs[1], QuoteNode(:captures)), - spec.ir.argtypes[1], topline)) + ir′.argtypes[1], topline)) end + return (Pair{Union{Nothing, SSAValue}, Vector{Any}}(sp_ssa, argexprs), linetable_offset) +end + +function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any}, + linetable::Vector{LineInfoNode}, item::InliningTodo, + boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}}) + # Ok, do the inlining here + sparam_vals = item.mi.sparam_vals + inlined_at = compact.result[idx][:line] + + ((sp_ssa, argexprs), linetable_offset) = ir_prepare_inlining!(InsertHere(compact), + compact, linetable, item.ir, sparam_vals, item.mi, inlined_at, argexprs) + if boundscheck === :default || boundscheck === :propagate if (compact.result[idx][:flag] & IR_FLAG_INBOUNDS) != 0 boundscheck = :off @@ -358,61 +406,54 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector # If the iterator already moved on to the next basic block, # temporarily re-open in again. local return_value + def = item.mi.def::Method sig = def.sig # Special case inlining that maintains the current basic block if there's only one BB in the target - if spec.linear_inline_eligible + new_new_offset = length(compact.new_new_nodes) + late_fixup_offset = length(compact.late_fixup) + if item.linear_inline_eligible #compact[idx] = nothing - inline_compact = IncrementalCompact(compact, spec.ir, compact.result_idx) + inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) for ((_, 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 - stmt′ = ssa_substitute!(idx′, stmt′, argexprs, sig, sparam_vals, linetable_offset, boundscheck, compact) + stmt′ = ssa_substitute!(InsertBefore(inline_compact, SSAValue(idx′)), inline_compact[SSAValue(idx′)], stmt′, argexprs, sig, sparam_vals, sp_ssa, linetable_offset, boundscheck) if isa(stmt′, ReturnNode) val = stmt′.val - isa(val, SSAValue) && (compact.used_ssas[val.id] += 1) return_value = SSAValue(idx′) inline_compact[idx′] = val 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 break end inline_compact[idx′] = stmt′ end - just_fixup!(inline_compact) + just_fixup!(inline_compact, new_new_offset, late_fixup_offset) compact.result_idx = inline_compact.result_idx else bb_offset, post_bb_id = popfirst!(todo_bbs) # This implements the need_split_before flag above - need_split_before = !isempty(spec.ir.cfg.blocks[1].preds) + need_split_before = !isempty(item.ir.cfg.blocks[1].preds) if need_split_before finish_current_bb!(compact, 0) end pn = PhiNode() #compact[idx] = nothing - inline_compact = IncrementalCompact(compact, spec.ir, compact.result_idx) + inline_compact = IncrementalCompact(compact, item.ir, compact.result_idx) for ((_, idx′), stmt′) in inline_compact inline_compact[idx′] = nothing - stmt′ = ssa_substitute!(idx′, stmt′, argexprs, sig, sparam_vals, linetable_offset, boundscheck, compact) + stmt′ = ssa_substitute!(InsertBefore(inline_compact, SSAValue(idx′)), inline_compact[SSAValue(idx′)], stmt′, argexprs, sig, sparam_vals, sp_ssa, linetable_offset, boundscheck) if isa(stmt′, ReturnNode) if isdefined(stmt′, :val) val = stmt′.val - # GlobalRefs can have side effects, but are currently - # allowed in arguments of ReturnNodes + @assert !isa(val, Expr) # GlobalRefs with side-effects are disallowed in value position in IRCode push!(pn.edges, inline_compact.active_result_bb-1) - if isa(val, GlobalRef) || isa(val, Expr) - stmt′ = val - inline_compact.result[idx′][:type] = - argextype(val, isa(val, Expr) ? compact : inline_compact) - insert_node_here!(inline_compact, NewInstruction(GotoNode(post_bb_id), - Any, compact.result[idx′][:line]), - true) - push!(pn.values, SSAValue(idx′)) - else - push!(pn.values, val) - stmt′ = GotoNode(post_bb_id) - end + push!(pn.values, val) + stmt′ = GotoNode(post_bb_id) end elseif isa(stmt′, GotoNode) stmt′ = GotoNode(stmt′.label + bb_offset) @@ -425,16 +466,9 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector end inline_compact[idx′] = stmt′ end - just_fixup!(inline_compact) + just_fixup!(inline_compact, new_new_offset, late_fixup_offset) compact.result_idx = inline_compact.result_idx compact.active_result_bb = inline_compact.active_result_bb - for i = 1:length(pn.values) - isassigned(pn.values, i) || continue - v = pn.values[i] - if isa(v, SSAValue) - compact.used_ssas[v.id] += 1 - end - end if length(pn.edges) == 1 return_value = pn.values[1] else @@ -445,7 +479,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector return_value end -function fix_va_argexprs!(compact::IncrementalCompact, +function fix_va_argexprs!(insert_node!::Inserter, inline_target::Union{IRCode, IncrementalCompact}, argexprs::Vector{Any}, nargs_def::Int, line_idx::Int32) newargexprs = argexprs[1:(nargs_def-1)] tuple_call = Expr(:call, TOP_TUPLE) @@ -453,15 +487,76 @@ function fix_va_argexprs!(compact::IncrementalCompact, for i in nargs_def:length(argexprs) arg = argexprs[i] push!(tuple_call.args, arg) - push!(tuple_typs, argextype(arg, compact)) + push!(tuple_typs, argextype(arg, inline_target)) end - tuple_typ = tuple_tfunc(tuple_typs) - push!(newargexprs, insert_node_here!(compact, NewInstruction(tuple_call, tuple_typ, line_idx))) + tuple_typ = tuple_tfunc(OptimizerLattice(), tuple_typs) + tuple_inst = NewInstruction(tuple_call, tuple_typ, line_idx) + push!(newargexprs, insert_node!(tuple_inst)) return newargexprs end const FATAL_TYPE_BOUND_ERROR = ErrorException("fatal error in type inference (type bound)") +""" + ir_inline_unionsplit! + +The core idea of this function is to simulate the dispatch semantics by generating +(flat) `isa`-checks corresponding to the signatures of union-split dispatch candidates, +and then inline their bodies into each `isa`-conditional block. +This `isa`-based virtual dispatch requires few pre-conditions to hold in order to simulate +the actual semantics correctly. + +The first one is that these dispatch candidates need to be processed in order of their specificity, +and the corresponding `isa`-checks should reflect the method specificities, since now their +signatures are not necessarily concrete. +For example, given the following definitions: + + f(x::Int) = ... + f(x::Number) = ... + f(x::Any) = ... + +and a callsite: + + f(x::Any) + +then a correct `isa`-based virtual dispatch would be: + + if isa(x, Int) + [inlined/resolved f(x::Int)] + elseif isa(x, Number) + [inlined/resolved f(x::Number)] + else # implies `isa(x, Any)`, which fully covers this call signature, + # otherwise we need to insert a fallback dynamic dispatch case also + [inlined/resolved f(x::Any)] + end + +Fortunately, `ml_matches` should already sorted them in that way, except cases when there is +any ambiguity, from which we already bail out at this point. + +Another consideration is type equality constraint from type variables: the `isa`-checks are +not enough to simulate the dispatch semantics in cases like: +Given a definition: + + g(x::T, y::T) where T<:Integer = ... + +transform a callsite: + + g(x::Any, y::Any) + +into the optimized form: + + if isa(x, Integer) && isa(y, Integer) + [inlined/resolved g(x::Integer, y::Integer)] + else + g(x, y) # fallback dynamic dispatch + end + +But again, we should already bail out from such cases at this point, essentially by +excluding cases where `case.sig::UnionAll`. + +In short, here we can process the dispatch candidates in order, assuming we haven't changed +their order somehow somewhere up to this point. +""" function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any}, linetable::Vector{LineInfoNode}, (; fully_covered, atype, cases, bbs)::UnionSplit, @@ -471,19 +566,19 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, join_bb = bbs[end] pn = PhiNode() local bb = compact.active_result_bb - @assert length(bbs) >= length(cases) - for i in 1:length(cases) + ncases = length(cases) + @assert length(bbs) >= ncases + for i = 1:ncases ithcase = cases[i] - metharg = ithcase.sig + mtype = ithcase.sig::DataType # checked within `handle_cases!` case = ithcase.item next_cond_bb = bbs[i] - @assert isa(metharg, DataType) cond = true - aparams, mparams = atype.parameters::SimpleVector, metharg.parameters::SimpleVector - @assert length(aparams) == length(mparams) - if i != length(cases) || !fully_covered || !params.trust_inference - for i in 1:length(aparams) - a, m = aparams[i], mparams[i] + nparams = fieldcount(atype) + @assert nparams == fieldcount(mtype) + if i != ncases || !fully_covered || !params.trust_inference + for i = 1:nparams + a, m = fieldtype(atype, i), fieldtype(mtype, i) # If this is always true, we don't need to check for it a <: m && continue # Generate isa check @@ -503,10 +598,10 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, argexprs′ = argexprs if !isa(case, ConstantCase) argexprs′ = copy(argexprs) - for i = 1:length(mparams) + for i = 1:nparams argex = argexprs[i] (isa(argex, SSAValue) || isa(argex, Argument)) || continue - a, m = aparams[i], mparams[i] + a, m = fieldtype(atype, i), fieldtype(mtype, i) if !(a <: m) argexprs′[i] = insert_node_here!(compact, NewInstruction(PiNode(argex, m), m, line)) @@ -516,15 +611,14 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, if isa(case, InliningTodo) val = ir_inline_item!(compact, idx, argexprs′, linetable, case, boundscheck, todo_bbs) elseif isa(case, InvokeCase) - effect_free = is_removable_if_unused(case.effects) - val = insert_node_here!(compact, - NewInstruction(Expr(:invoke, case.invoke, argexprs′...), typ, nothing, - line, effect_free ? IR_FLAG_EFFECT_FREE : IR_FLAG_NULL, effect_free)) + inst = Expr(:invoke, case.invoke, argexprs′...) + flag = flags_for_effects(case.effects) + val = insert_node_here!(compact, NewInstruction(inst, typ, case.info, line, flag)) else case = case::ConstantCase val = case.val end - if !isempty(compact.result_bbs[bb].preds) + if !isempty(compact.cfg_transform.result_bbs[bb].preds) push!(pn.edges, bb) push!(pn.values, val) insert_node_here!(compact, @@ -556,18 +650,17 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, return insert_node_here!(compact, NewInstruction(pn, typ, line)) end -function batch_inline!(todo::Vector{Pair{Int, Any}}, ir::IRCode, linetable::Vector{LineInfoNode}, propagate_inbounds::Bool, params::OptimizationParams) +function batch_inline!(ir::IRCode, todo::Vector{Pair{Int,Any}}, propagate_inbounds::Bool, params::OptimizationParams) # Compute the new CFG first (modulo statement ranges, which will be computed below) state = CFGInliningState(ir) for (idx, item) in todo if isa(item, UnionSplit) - cfg_inline_unionsplit!(ir, idx, item::UnionSplit, state, params) + cfg_inline_unionsplit!(ir, idx, item, state, params) else item = item::InliningTodo - spec = item.spec::ResolvedInliningSpec # A linear inline does not modify the CFG - spec.linear_inline_eligible && continue - cfg_inline_item!(ir, idx, spec, state, false) + item.linear_inline_eligible && continue + cfg_inline_item!(ir, idx, item, state, false) end end finish_cfg_inline!(state) @@ -577,14 +670,12 @@ function batch_inline!(todo::Vector{Pair{Int, Any}}, ir::IRCode, linetable::Vect boundscheck = :propagate end - let compact = IncrementalCompact(ir, false) - compact.result_bbs = state.new_cfg_blocks + let compact = IncrementalCompact(ir, CFGTransformState!(state.new_cfg_blocks, false)) # This needs to be a minimum and is more of a size hint nn = 0 for (_, item) in todo if isa(item, InliningTodo) - spec = item.spec::ResolvedInliningSpec - nn += (length(spec.ir.stmts) + length(spec.ir.new_nodes)) + nn += (length(item.ir.stmts) + length(item.ir.new_nodes)) end end nnewnodes = length(compact.result) + nn @@ -593,9 +684,14 @@ function batch_inline!(todo::Vector{Pair{Int, Any}}, ir::IRCode, linetable::Vect for ((old_idx, idx), stmt) in compact if old_idx == inline_idx stmt = stmt::Expr - argexprs = copy(stmt.args) + if stmt.head === :invoke + argexprs = stmt.args[2:end] + else + @assert stmt.head === :call + argexprs = copy(stmt.args) + end refinish = false - if compact.result_idx == first(compact.result_bbs[compact.active_result_bb].stmts) + if compact.result_idx == first(compact.cfg_transform.result_bbs[compact.active_result_bb].stmts) compact.active_result_bb -= 1 refinish = true end @@ -612,9 +708,9 @@ function batch_inline!(todo::Vector{Pair{Int, Any}}, ir::IRCode, linetable::Vect end end if isa(item, InliningTodo) - compact.ssa_rename[old_idx] = ir_inline_item!(compact, idx, argexprs, linetable, item, boundscheck, state.todo_bbs) + compact.ssa_rename[old_idx] = ir_inline_item!(compact, idx, argexprs, ir.linetable, item, boundscheck, state.todo_bbs) elseif isa(item, UnionSplit) - compact.ssa_rename[old_idx] = ir_inline_unionsplit!(compact, idx, argexprs, linetable, item, boundscheck, state.todo_bbs, params) + compact.ssa_rename[old_idx] = ir_inline_unionsplit!(compact, idx, argexprs, ir.linetable, item, boundscheck, state.todo_bbs, params) end compact[idx] = nothing refinish && finish_current_bb!(compact, 0) @@ -640,9 +736,9 @@ function batch_inline!(todo::Vector{Pair{Int, Any}}, ir::IRCode, linetable::Vect end # This assumes the caller has verified that all arguments to the _apply_iterate call are Tuples. -function rewrite_apply_exprargs!( +function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, argtypes::Vector{Any}, - arginfos::Vector{MaybeAbstractIterationInfo}, arg_start::Int, istate::InliningState, todo::Vector{Pair{Int, Any}}) + arginfos::Vector{MaybeAbstractIterationInfo}, arg_start::Int, istate::InliningState) flag = ir.stmts[idx][:flag] argexprs = stmt.args new_argexprs = Any[argexprs[arg_start]] @@ -652,7 +748,7 @@ function rewrite_apply_exprargs!( def = argexprs[i] def_type = argtypes[i] thisarginfo = arginfos[i-arg_start] - if thisarginfo === nothing + if thisarginfo === nothing || !thisarginfo.complete if def_type isa PartialStruct # def_type.typ <: Tuple is assumed def_argtypes = def_type.fields @@ -664,7 +760,7 @@ function rewrite_apply_exprargs!( end else ti = widenconst(def_type)::DataType # checked by `is_valid_type_for_apply_rewrite` - if ti.name === NamedTuple_typename + if ti.name === _NAMEDTUPLE_NAME ti = ti.parameters[2]::DataType # checked by `is_valid_type_for_apply_rewrite` end for p in ti.parameters @@ -698,19 +794,10 @@ function rewrite_apply_exprargs!( state1 = insert_node!(ir, idx, NewInstruction(new_stmt, call.rt)) new_sig = call_sig(ir, new_stmt)::Signature new_info = call.info - if isa(new_info, ConstCallInfo) - handle_const_call!( - ir, state1.id, new_stmt, new_info, flag, - new_sig, istate, todo) - elseif isa(new_info, MethodMatchInfo) || isa(new_info, UnionSplitInfo) - new_infos = isa(new_info, MethodMatchInfo) ? MethodMatchInfo[new_info] : new_info.matches - # See if we can inline this call to `iterate` - analyze_single_call!( - ir, state1.id, new_stmt, new_infos, flag, - new_sig, istate, todo) - end + # See if we can inline this call to `iterate` + handle_call!(todo, ir, state1.id, new_stmt, new_info, flag, new_sig, istate) if i != length(thisarginfo.each) - valT = getfield_tfunc(call.rt, Const(1)) + valT = getfield_tfunc(optimizer_lattice(istate.interp), call.rt, Const(1)) val_extracted = insert_node!(ir, idx, NewInstruction( Expr(:call, GlobalRef(Core, :getfield), state1, 1), valT)) @@ -718,7 +805,7 @@ function rewrite_apply_exprargs!( push!(new_argtypes, valT) state_extracted = insert_node!(ir, idx, NewInstruction( Expr(:call, GlobalRef(Core, :getfield), state1, 2), - getfield_tfunc(call.rt, Const(2)))) + getfield_tfunc(optimizer_lattice(istate.interp), call.rt, Const(2)))) state = Core.svec(state_extracted) end end @@ -728,101 +815,146 @@ function rewrite_apply_exprargs!( return new_argtypes end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, match::MethodMatch, effects::Effects) - mi = specialize_method(match; compilesig=true) - mi !== nothing && et !== nothing && push!(et, mi::MethodInstance) - mi === nothing && return nothing - return InvokeCase(mi, effects) +function compileable_specialization(mi::MethodInstance, effects::Effects, + et::InliningEdgeTracker, @nospecialize(info::CallInfo); compilesig_invokes::Bool=true) + mi_invoke = mi + if compilesig_invokes + method, atype, sparams = mi.def::Method, mi.specTypes, mi.sparam_vals + new_atype = get_compileable_sig(method, atype, sparams) + new_atype === nothing && return nothing + if atype !== new_atype + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), new_atype, method.sig)::SimpleVector + if sparams === sp_[2]::SimpleVector + mi_invoke = specialize_method(method, new_atype, sparams) + mi_invoke === nothing && return nothing + end + end + else + # If this caller does not want us to optimize calls to use their + # declared compilesig, then it is also likely they would handle sparams + # incorrectly if there were any unknown typevars, so we conservatively return nothing + if any(@nospecialize(t)->isa(t, TypeVar), mi.sparam_vals) + return nothing + end + end + add_inlining_backedge!(et, mi) + return InvokeCase(mi_invoke, effects, info) end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, linfo::MethodInstance, effects::Effects) - mi = specialize_method(linfo.def::Method, linfo.specTypes, linfo.sparam_vals; compilesig=true) - mi !== nothing && et !== nothing && push!(et, mi::MethodInstance) - mi === nothing && return nothing - return InvokeCase(mi, effects) +function compileable_specialization(match::MethodMatch, effects::Effects, + et::InliningEdgeTracker, @nospecialize(info::CallInfo); compilesig_invokes::Bool=true) + mi = specialize_method(match) + return compileable_specialization(mi, effects, et, info; compilesig_invokes) end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, (; linfo)::InferenceResult, effects::Effects) - return compileable_specialization(et, linfo, effects) +struct CachedResult + src::Any + effects::Effects + CachedResult(@nospecialize(src), effects::Effects) = new(src, effects) +end +@inline function get_cached_result(state::InliningState, mi::MethodInstance) + code = get(code_cache(state), mi, nothing) + if code isa CodeInstance + 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) + end + return CachedResult(nothing, Effects()) end -function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) - mi = todo.mi - (; match, argtypes) = todo.spec::DelayedInliningSpec - et = state.et +# 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::UInt8, + state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing) + et = InliningEdgeTracker(state.et, invokesig) #XXX: update_valid_age!(min_valid[1], max_valid[1], sv) - if isa(match, InferenceResult) - inferred_src = match.src - if isa(inferred_src, ConstAPI) - # use constant calling convention - et !== nothing && push!(et, mi) - return ConstantCase(quoted(inferred_src.val)) - else - src = inferred_src + 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 - effects = match.ipo_effects else - code = get(state.mi_cache, mi, nothing) - if code isa CodeInstance - if use_const_api(code) - # in this case function can be inlined to a constant - et !== nothing && push!(et, mi) - return ConstantCase(quoted(code.rettype_const)) - else - src = code.inferred - end - effects = decode_effects(code.ipo_purity_bits) - else - effects = Effects() - src = code + 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 end # the duplicated check might have been done already within `analyze_method!`, but still # we need it here too since we may come here directly using a constant-prop' result - if !state.params.inlining || is_stmt_noinline(flag) - return compileable_specialization(et, match, effects) + if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) + return compileable_specialization(mi, effects, et, info; + compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) end - src = inlining_policy(state.interp, src, flag, mi, argtypes) + 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) - if src === nothing - return compileable_specialization(et, match, effects) - end + add_inlining_backedge!(et, mi) + return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) +end - if isa(src, IRCode) - src = copy(src) +# the special resolver for :invoke-d call +function resolve_todo(mi::MethodInstance, argtypes::Vector{Any}, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState) + if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) + return nothing end - et !== nothing && push!(et, mi) - return InliningTodo(mi, src, effects) -end + et = InliningEdgeTracker(state.et, nothing) -function resolve_todo((; fully_covered, atype, cases, #=bbs=#)::UnionSplit, state::InliningState, flag::UInt8) - ncases = length(cases) - newcases = Vector{InliningCase}(undef, ncases) - for i in 1:ncases - (; sig, item) = cases[i] - newitem = resolve_todo(item, state, flag) - push!(newcases, InliningCase(sig, newitem)) + cached_result = get_cached_result(state, mi) + if cached_result isa ConstantCase + add_inlining_backedge!(et, mi) + return cached_result end - return UnionSplit(fully_covered, atype, newcases) + (; src, effects) = cached_result + + src = inlining_policy(state.interp, src, info, flag, mi, argtypes) + + src === nothing && return nothing + + add_inlining_backedge!(et, mi) + return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) end function validate_sparams(sparams::SimpleVector) for i = 1:length(sparams) - (isa(sparams[i], TypeVar) || isvarargtype(sparams[i])) && return false + spᵢ = sparams[i] + (isa(spᵢ, TypeVar) || isvarargtype(spᵢ)) && return false end return true end +function may_have_fcalls(m::Method) + isdefined(m, :source) || return true + src = m.source + isa(src, MaybeCompressed) || return true + return ccall(:jl_ir_flag_has_fcall, Bool, (Any,), src) +end + function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, - flag::UInt8, state::InliningState) + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; + allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing) method = match.method - methsig = method.sig + spec_types = match.spec_types - # Check that we habe the correct number of arguments + # Check that we have the correct number of arguments na = Int(method.nargs) npassedargs = length(argtypes) if na != npassedargs && !(na > 0 && method.isva) @@ -832,96 +964,102 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, # call this function return nothing end + 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])) + return nothing + end + end - # Bail out if any static parameters are left as TypeVar - validate_sparams(match.sparams) || return nothing - - et = state.et - - # See if there exists a specialization for this method signature - mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance} - isa(mi, MethodInstance) || return compileable_specialization(et, match, Effects()) + if !validate_sparams(match.sparams) + (allow_typevars && !may_have_fcalls(match.method)) || return nothing + end - todo = InliningTodo(mi, match, argtypes) - # If we don't have caches here, delay resolving this MethodInstance - # until the batch inlining step (or an external post-processing pass) - state.mi_cache === nothing && return todo - return resolve_todo(todo, state, flag) + # 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) end -function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) - return InliningTodo(mi, ResolvedInliningSpec(ir, linear_inline_eligible(ir), effects)) +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) end +retrieve_ir_for_inlining(mi::MethodInstance, src::CodeInfo) = inflate_ir(src, mi) +retrieve_ir_for_inlining(mi::MethodInstance, ir::IRCode) = copy(ir) -function InliningTodo(mi::MethodInstance, src::Union{CodeInfo, Array{UInt8, 1}}, effects::Effects) - if !isa(src, CodeInfo) - src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src::Vector{UInt8})::CodeInfo +function flags_for_effects(effects::Effects) + flags::UInt8 = 0 + if is_consistent(effects) + flags |= IR_FLAG_CONSISTENT end - - @timeit "inline IR inflation" begin; - return InliningTodo(mi, inflate_ir(src, mi)::IRCode, effects) + if is_removable_if_unused(effects) + flags |= IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + elseif is_nothrow(effects) + flags |= IR_FLAG_NOTHROW end + return flags end -function handle_single_case!( - ir::IRCode, idx::Int, stmt::Expr, - @nospecialize(case), todo::Vector{Pair{Int, Any}}, params::OptimizationParams, isinvoke::Bool = false) +function handle_single_case!(todo::Vector{Pair{Int,Any}}, + ir::IRCode, idx::Int, stmt::Expr, @nospecialize(case), params::OptimizationParams, + isinvoke::Bool = false) if isa(case, ConstantCase) ir[SSAValue(idx)][:inst] = case.val elseif isa(case, InvokeCase) - is_total(case.effects) && inline_const_if_inlineable!(ir[SSAValue(idx)]) && return nothing + 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) - if is_removable_if_unused(case.effects) - ir[SSAValue(idx)][:flag] |= IR_FLAG_EFFECT_FREE - end + ir[SSAValue(idx)][:flag] |= flags_for_effects(case.effects) elseif case === nothing # Do, well, nothing else isinvoke && rewrite_invoke_exprargs!(stmt) push!(todo, idx=>(case::InliningTodo)) end - nothing + return nothing end rewrite_invoke_exprargs!(expr::Expr) = (expr.args = invoke_rewrite(expr.args); expr) function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::OptimizationParams) - if isa(typ, Const) && isa(typ.val, SimpleVector) - length(typ.val) > params.MAX_TUPLE_SPLAT && return false - for p in typ.val + if isa(typ, Const) && (v = typ.val; isa(v, SimpleVector)) + length(v) > params.max_tuple_splat && return false + for p in v is_inlineable_constant(p) || return false end return true end typ = widenconst(typ) - if isa(typ, DataType) && typ.name === NamedTuple_typename + if isa(typ, DataType) && typ.name === _NAMEDTUPLE_NAME typ = typ.parameters[2] typ = unwraptv(typ) end isa(typ, DataType) || return false if typ.name === Tuple.name - return !isvatuple(typ) && length(typ.parameters) <= params.MAX_TUPLE_SPLAT + return !isvatuple(typ) && length(typ.parameters) <= params.max_tuple_splat else return false end end -function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt)) - nf = nfields_tfunc(rt) +function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt), state::InliningState) + 𝕃ₒ = optimizer_lattice(state.interp) + nf = nfields_tfunc(𝕃ₒ, rt) if nf isa Const eargs = stmt.args tup = eargs[2] tt = argextype(tup, ir) - tnf = nfields_tfunc(tt) + tnf = nfields_tfunc(𝕃ₒ, tt) # TODO: hoisting this tnf.val === nf.val check into codegen # would enable us to almost always do this transform if tnf isa Const && tnf.val === nf.val n = tnf.val::Int new_argexprs = Any[eargs[1]] for j = 1:n - atype = getfield_tfunc(tt, Const(j)) + atype = getfield_tfunc(𝕃ₒ, tt, Const(j)) new_call = Expr(:call, Core.getfield, tup, j) new_argexpr = insert_node!(ir, idx, NewInstruction(new_call, atype)) push!(new_argexprs, new_argexpr) @@ -930,19 +1068,26 @@ function inline_splatnew!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(rt)) stmt.args = new_argexprs end end - nothing + return nothing end function call_sig(ir::IRCode, stmt::Expr) isempty(stmt.args) && return nothing - ft = argextype(stmt.args[1], ir) + if stmt.head === :call + offset = 1 + elseif stmt.head === :invoke + offset = 2 + else + return nothing + end + ft = argextype(stmt.args[offset], ir) has_free_typevars(ft) && return nothing f = singleton_type(ft) f === Core.Intrinsics.llvmcall && return nothing f === Core.Intrinsics.cglobal && return nothing argtypes = Vector{Any}(undef, length(stmt.args)) argtypes[1] = ft - for i = 2:length(stmt.args) + for i = (offset+1):length(stmt.args) a = argextype(stmt.args[i], ir) (a === Bottom || isvarargtype(a)) && return nothing argtypes[i] = a @@ -950,22 +1095,21 @@ function call_sig(ir::IRCode, stmt::Expr) return Signature(f, ft, argtypes) end -function inline_apply!( - ir::IRCode, idx::Int, stmt::Expr, sig::Signature, - state::InliningState, todo::Vector{Pair{Int, Any}}) +function inline_apply!(todo::Vector{Pair{Int,Any}}, + ir::IRCode, idx::Int, stmt::Expr, sig::Signature, state::InliningState) while sig.f === Core._apply_iterate info = ir.stmts[idx][:info] if isa(info, UnionSplitApplyCallInfo) if length(info.infos) != 1 # TODO: Handle union split applies? - new_info = info = false + new_info = info = NoCallInfo() else info = info.infos[1] new_info = info.call end else - @assert info === nothing || info === false - new_info = info = false + @assert info === NoCallInfo() + new_info = info = NoCallInfo() end arg_start = 3 argtypes = sig.argtypes @@ -977,10 +1121,11 @@ function inline_apply!( # if one argument is a tuple already, and the rest are empty, we can just return it # e.g. rewrite `((t::Tuple)...,)` to `t` nonempty_idx = 0 + 𝕃ₒ = optimizer_lattice(state.interp) for i = (arg_start + 1):length(argtypes) ti = argtypes[i] - ti ⊑ Tuple{} && continue - if ti ⊑ Tuple && nonempty_idx == 0 + ⊑(𝕃ₒ, ti, Tuple{}) && continue + if ⊑(𝕃ₒ, ti, Tuple) && nonempty_idx == 0 nonempty_idx = i continue end @@ -997,10 +1142,10 @@ function inline_apply!( arginfos = MaybeAbstractIterationInfo[] for i = (arg_start + 1):length(argtypes) thisarginfo = nothing - if !is_valid_type_for_apply_rewrite(argtypes[i], state.params) - if isa(info, ApplyCallInfo) && info.arginfo[i-arg_start] !== nothing - thisarginfo = info.arginfo[i-arg_start] - else + if !is_valid_type_for_apply_rewrite(argtypes[i], OptimizationParams(state.interp)) + isa(info, ApplyCallInfo) || return nothing + thisarginfo = info.arginfo[i-arg_start] + if thisarginfo === nothing || !thisarginfo.complete return nothing end end @@ -1008,9 +1153,8 @@ function inline_apply!( end # Independent of whether we can inline, the above analysis allows us to rewrite # this apply call to a regular call - argtypes = rewrite_apply_exprargs!( - ir, idx, stmt, argtypes, - arginfos, arg_start, state, todo) + argtypes = rewrite_apply_exprargs!(todo, + ir, idx, stmt, argtypes, arginfos, arg_start, state) ir.stmts[idx][:info] = new_info has_free_typevars(ft) && return nothing f = singleton_type(ft) @@ -1020,41 +1164,49 @@ function inline_apply!( end # TODO: this test is wrong if we start to handle Unions of function types later -is_builtin(s::Signature) = - isa(s.f, IntrinsicFunction) || - s.ft ⊑ IntrinsicFunction || - isa(s.f, Builtin) || - s.ft ⊑ Builtin +function is_builtin(𝕃ₒ::AbstractLattice, s::Signature) + isa(s.f, IntrinsicFunction) && return true + ⊑(𝕃ₒ, s.ft, IntrinsicFunction) && return true + isa(s.f, Builtin) && return true + ⊑(𝕃ₒ, s.ft, Builtin) && return true + return false +end -function inline_invoke!( +function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, info::InvokeCallInfo, flag::UInt8, - sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) + sig::Signature, state::InliningState) match = info.match if !match.fully_covers # TODO: We could union split out the signature check and continue on return nothing end result = info.result - if isa(result, ConstResult) - item = const_result_item(result, state) + invokesig = sig.argtypes + if isa(result, ConcreteResult) + item = concrete_result_item(result, info, state; invokesig) else argtypes = invoke_rewrite(sig.argtypes) - if isa(result, InferenceResult) - (; mi) = item = InliningTodo(result, argtypes) + if isa(result, ConstPropResult) + mi = result.result.linfo validate_sparams(mi.sparam_vals) || return nothing - if argtypes_to_type(argtypes) <: mi.def.sig - state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) - handle_single_case!(ir, idx, stmt, item, todo, state.params, true) + if Union{} !== argtypes_to_type(argtypes) <: mi.def.sig + item = resolve_todo(mi, result.result, argtypes, info, flag, state; invokesig) + handle_single_case!(todo, ir, idx, stmt, item, OptimizationParams(state.interp), true) return nothing end end - item = analyze_method!(match, argtypes, flag, state) + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig) end - handle_single_case!(ir, idx, stmt, item, todo, state.params, true) + handle_single_case!(todo, ir, idx, stmt, item, OptimizationParams(state.interp), true) return nothing end -function narrow_opaque_closure!(ir::IRCode, stmt::Expr, @nospecialize(info), state::InliningState) +function invoke_signature(argtypes::Vector{Any}) + ft, argtyps = widenconst(argtypes[2]), instanceof_tfunc(widenconst(argtypes[3]))[1] + return rewrap_unionall(Tuple{ft, unwrap_unionall(argtyps).parameters...}, argtyps) +end + +function narrow_opaque_closure!(ir::IRCode, stmt::Expr, @nospecialize(info::CallInfo), state::InliningState) if isa(info, OpaqueClosureCreateInfo) lbt = argextype(stmt.args[2], ir) lb, exact = instanceof_tfunc(lbt) @@ -1063,9 +1215,10 @@ function narrow_opaque_closure!(ir::IRCode, stmt::Expr, @nospecialize(info), sta ub, exact = instanceof_tfunc(ubt) exact || return # Narrow opaque closure type - newT = widenconst(tmeet(tmerge(lb, info.unspec.rt), ub)) + 𝕃ₒ = optimizer_lattice(state.interp) + newT = widenconst(tmeet(𝕃ₒ, tmerge(𝕃ₒ, lb, info.unspec.rt), ub)) if newT != ub - # N.B.: Narrowing the ub requires a backdge on the mi whose type + # N.B.: Narrowing the ub requires a backedge on the mi whose type # information we're using, since a change in that function may # invalidate ub result. stmt.args[3] = newT @@ -1076,32 +1229,44 @@ 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)) - if stmt_effect_free(stmt, rt, ir) - ir.stmts[idx][:flag] |= IR_FLAG_EFFECT_FREE - return true +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 - return false + 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 + return effect_free_and_nothrow end # Handles all analysis and inlining of intrinsics and builtins. In particular, # this method does not access the method table or otherwise process generic # functions. -function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vector{Pair{Int, Any}}) +function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, state::InliningState) stmt = ir.stmts[idx][:inst] rt = ir.stmts[idx][:type] if !(stmt isa Expr) - check_effect_free!(ir, idx, stmt, rt) + check_effect_free!(ir, idx, stmt, rt, state) return nothing end head = stmt.head if head !== :call if head === :splatnew - inline_splatnew!(ir, idx, stmt, rt) + inline_splatnew!(ir, idx, stmt, rt, state) elseif head === :new_opaque_closure narrow_opaque_closure!(ir, stmt, ir.stmts[idx][:info], state) + elseif head === :invoke + sig = call_sig(ir, stmt) + sig === nothing && return nothing + return stmt, sig end - check_effect_free!(ir, idx, stmt, rt) + check_effect_free!(ir, idx, stmt, rt, state) return nothing end @@ -1109,77 +1274,103 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto sig === nothing && return nothing # Handle _apply_iterate - sig = inline_apply!(ir, idx, stmt, sig, state, todo) + sig = inline_apply!(todo, ir, idx, stmt, sig, state) sig === nothing && return nothing # Check if we match any of the early inliners - earlyres = early_inline_special_case(ir, stmt, rt, sig, state.params) + earlyres = early_inline_special_case(ir, stmt, rt, sig, state) if isa(earlyres, SomeCase) ir.stmts[idx][:inst] = earlyres.val return nothing end - if (sig.f === modifyfield! || sig.ft ⊑ typeof(modifyfield!)) && 5 <= length(stmt.args) <= 6 - let info = ir.stmts[idx][:info] - info isa MethodResultPure && (info = info.info) - info isa ConstCallInfo && (info = info.call) - info isa MethodMatchInfo || return nothing - length(info.results) == 1 || return nothing - match = info.results[1]::MethodMatch - match.fully_covers || return nothing - case = compileable_specialization(state.et, match, Effects()) - case === nothing && return nothing - stmt.head = :invoke_modify - pushfirst!(stmt.args, case.invoke) - ir.stmts[idx][:inst] = stmt - end - return nothing - end - if check_effect_free!(ir, idx, stmt, rt) - if sig.f === typeassert || sig.ft ⊑ typeof(typeassert) + if check_effect_free!(ir, idx, stmt, rt, state) + if sig.f === typeassert || ⊑(optimizer_lattice(state.interp), sig.ft, typeof(typeassert)) # typeassert is a no-op if effect free ir.stmts[idx][:inst] = stmt.args[2] return nothing end end - if sig.f !== Core.invoke && is_builtin(sig) - # No inlining for builtins (other invoke/apply/typeassert) + 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 end # Special case inliners for regular functions - lateres = late_inline_special_case!(ir, idx, stmt, rt, sig, state.params) + lateres = late_inline_special_case!(ir, idx, stmt, rt, sig, state) if isa(lateres, SomeCase) ir[SSAValue(idx)][:inst] = lateres.val - check_effect_free!(ir, idx, lateres.val, rt) - return nothing - elseif is_return_type(sig.f) - check_effect_free!(ir, idx, stmt, rt) + check_effect_free!(ir, idx, lateres.val, rt, state) return nothing end return stmt, sig end -# TODO inline non-`isdispatchtuple`, union-split callsites? -function analyze_single_call!( - ir::IRCode, idx::Int, stmt::Expr, infos::Vector{MethodMatchInfo}, flag::UInt8, - sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) - argtypes = sig.argtypes +function handle_any_const_result!(cases::Vector{InliningCase}, + @nospecialize(result), match::MethodMatch, argtypes::Vector{Any}, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; + allow_abstract::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) + else + @assert result === nothing + return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars) + end +end + +function info_effects(@nospecialize(result), match::MethodMatch, state::InliningState) + if isa(result, ConcreteResult) + return result.effects + elseif isa(result, SemiConcreteResult) + return result.effects + elseif isa(result, ConstPropResult) + return result.result.ipo_effects + else + mi = specialize_method(match; preexisting=true) + if isa(mi, MethodInstance) + code = get(code_cache(state), mi, nothing) + if code isa CodeInstance + return decode_effects(code.ipo_purity_bits) + end + end + return Effects() + end +end + +function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig::Signature, + state::InliningState) + nunion = nsplit(info) + nunion === nothing && return nothing cases = InliningCase[] - local only_method = nothing # keep track of whether there is one matching method + argtypes = sig.argtypes + local handled_all_cases::Bool = true + local revisit_idx = nothing + local only_method = nothing local meth::MethodLookupResult - local fully_covered = true - for i in 1:length(infos) - meth = infos[i].results + local all_result_count = 0 + local joint_effects::Effects = EFFECTS_TOTAL + local fully_covered::Bool = true + for i = 1:nunion + meth = getsplit(info, i) if meth.ambig # Too many applicable methods # Or there is a (partial?) ambiguity return nothing elseif length(meth) == 0 # No applicable methods; try next union split - fully_covered = false + handled_all_cases = false continue else if length(meth) == 1 && only_method !== false @@ -1192,140 +1383,266 @@ function analyze_single_call!( only_method = false end end - for match in meth - fully_covered &= handle_match!(match, argtypes, flag, state, cases) - fully_covered &= match.fully_covers + local split_fully_covered::Bool = 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) + else + handled_all_cases = false + revisit_idx = nothing + end + else + handled_all_cases &= handle_any_const_result!(cases, + result, match, argtypes, info, flag, state; allow_abstract=true, allow_typevars=false) + end end - end - - # if the signature is fully covered and there is only one applicable method, - # we can try to inline it even if the signature is not a dispatch tuple - atype = argtypes_to_type(argtypes) - if length(cases) == 0 && only_method isa Method - if length(infos) > 1 - (metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), - atype, only_method.sig)::SimpleVector + fully_covered &= split_fully_covered + end + + joint_effects = Effects(joint_effects; nothrow=fully_covered) + + 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) end - item = analyze_method!(match, argtypes, flag, state) - item === nothing && return nothing - push!(cases, InliningCase(match.spec_types, item)) - fully_covered = match.fully_covers + 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 + # if we've not seen all candidates, union split is valid only for dispatch tuples + filter!(case::InliningCase->isdispatchtuple(case.sig), cases) end - handle_cases!(ir, idx, stmt, atype, cases, fully_covered, todo, state.params) + return cases, (handled_all_cases & fully_covered), joint_effects end -# similar to `analyze_single_call!`, but with constant results -function handle_const_call!( - ir::IRCode, idx::Int, stmt::Expr, cinfo::ConstCallInfo, flag::UInt8, - sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) - argtypes = sig.argtypes - (; call, results) = cinfo - infos = isa(call, MethodMatchInfo) ? MethodMatchInfo[call] : call.matches - cases = InliningCase[] - local fully_covered = true - local j = 0 - for i in 1:length(infos) - meth = infos[i].results - if meth.ambig - # Too many applicable methods - # Or there is a (partial?) ambiguity - return nothing - elseif length(meth) == 0 - # No applicable methods; try next union split - fully_covered = false - continue - end - for match in meth - j += 1 - result = results[j] - if isa(result, ConstResult) - case = const_result_item(result, state) - push!(cases, InliningCase(result.mi.specTypes, case)) - elseif isa(result, InferenceResult) - fully_covered &= handle_inf_result!(result, argtypes, flag, state, cases) - else - @assert result === nothing - fully_covered &= handle_match!(match, argtypes, flag, state, cases) - end - fully_covered &= match.fully_covers - end - end - - # if the signature is fully covered and there is only one applicable method, - # we can try to inline it even if the signature is not a dispatch tuple - atype = argtypes_to_type(argtypes) - if length(cases) == 0 && length(results) == 1 && isa(results[1], InferenceResult) - (; mi) = item = InliningTodo(results[1]::InferenceResult, argtypes) - state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) - validate_sparams(mi.sparam_vals) || return nothing - item === nothing && return nothing - push!(cases, InliningCase(mi.specTypes, item)) - fully_covered = atype <: mi.specTypes - end - - handle_cases!(ir, idx, stmt, atype, cases, fully_covered, todo, state.params) +function handle_call!(todo::Vector{Pair{Int,Any}}, + ir::IRCode, idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt8, sig::Signature, + state::InliningState) + 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, OptimizationParams(state.interp)) end -function handle_match!( - match::MethodMatch, argtypes::Vector{Any}, flag::UInt8, state::InliningState, - cases::Vector{InliningCase}) +function handle_match!(cases::Vector{InliningCase}, + match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8, + state::InliningState; + allow_abstract::Bool, allow_typevars::Bool) spec_types = match.spec_types - isdispatchtuple(spec_types) || return false - item = analyze_method!(match, argtypes, flag, state) + 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->case.sig === spec_types, cases) && return true + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars) item === nothing && return false - _any(case->case.sig === spec_types, cases) && return true push!(cases, InliningCase(spec_types, item)) return true end -function handle_inf_result!( - result::InferenceResult, argtypes::Vector{Any}, flag::UInt8, state::InliningState, - cases::Vector{InliningCase}) - (; mi) = item = InliningTodo(result, argtypes) +function handle_const_prop_result!(cases::Vector{InliningCase}, + result::ConstPropResult, argtypes::Vector{Any}, @nospecialize(info::CallInfo), + flag::UInt8, state::InliningState; + allow_abstract::Bool, allow_typevars::Bool) + mi = result.result.linfo spec_types = mi.specTypes - isdispatchtuple(spec_types) || return false - validate_sparams(mi.sparam_vals) || return false - state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) + allow_abstract || isdispatchtuple(spec_types) || return false + 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 === nothing && return false push!(cases, InliningCase(spec_types, item)) return true end -function const_result_item(result::ConstResult, state::InliningState) - if !isdefined(result, :result) || !is_inlineable_constant(result.result) - return compileable_specialization(state.et, result.mi, EFFECTS_TOTAL) +function semiconcrete_result_item(result::SemiConcreteResult, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState) + mi = result.mi + if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) + et = InliningEdgeTracker(state.et, nothing) + return compileable_specialization(mi, result.effects, et, info; + compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) else - return ConstantCase(quoted(result.result)) + return InliningTodo(mi, retrieve_ir_for_inlining(mi, result.ir), result.effects) end end -function handle_cases!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(atype), - cases::Vector{InliningCase}, fully_covered::Bool, todo::Vector{Pair{Int, Any}}, - params::OptimizationParams) +function handle_semi_concrete_result!(cases::Vector{InliningCase}, result::SemiConcreteResult, + @nospecialize(info::CallInfo), flag::UInt8, state::InliningState; + allow_abstract::Bool) + mi = result.mi + spec_types = mi.specTypes + allow_abstract || isdispatchtuple(spec_types) || return false + validate_sparams(mi.sparam_vals) || return false + item = semiconcrete_result_item(result, info, flag, state) + item === nothing && return false + push!(cases, InliningCase(spec_types, item)) + return true +end + +function handle_concrete_result!(cases::Vector{InliningCase}, result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState) + case = concrete_result_item(result, info, state) + case === nothing && return false + push!(cases, InliningCase(result.mi.specTypes, case)) + return true +end + +may_inline_concrete_result(result::ConcreteResult) = + isdefined(result, :result) && is_inlineable_constant(result.result) + +function concrete_result_item(result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState; + invokesig::Union{Nothing,Vector{Any}}=nothing) + if !may_inline_concrete_result(result) + et = InliningEdgeTracker(state.et, invokesig) + return compileable_specialization(result.mi, result.effects, et, info; + compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) + end + @assert result.effects === EFFECTS_TOTAL + return ConstantCase(quoted(result.result)) +end + +function handle_cases!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, + @nospecialize(atype), cases::Vector{InliningCase}, fully_covered::Bool, + joint_effects::Effects, params::OptimizationParams) # 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 - handle_single_case!(ir, idx, stmt, cases[1].item, todo, params) + handle_single_case!(todo, ir, idx, stmt, cases[1].item, params) 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)) + else + ir[SSAValue(idx)][:flag] |= flags_for_effects(joint_effects) end return nothing end -function handle_const_opaque_closure_call!( - ir::IRCode, idx::Int, stmt::Expr, result::InferenceResult, flag::UInt8, - sig::Signature, state::InliningState, todo::Vector{Pair{Int, Any}}) - item = InliningTodo(result, sig.argtypes) - isdispatchtuple(item.mi.specTypes) || return - validate_sparams(item.mi.sparam_vals) || return - state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) - handle_single_case!(ir, idx, stmt, item, todo, state.params) +function handle_opaque_closure_call!(todo::Vector{Pair{Int,Any}}, + ir::IRCode, idx::Int, stmt::Expr, info::OpaqueClosureCallInfo, + flag::UInt8, sig::Signature, state::InliningState) + result = info.result + 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) + elseif isa(result, ConcreteResult) + item = concrete_result_item(result, info, 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 + end + handle_single_case!(todo, ir, idx, stmt, item, OptimizationParams(state.interp)) + return nothing +end + +function handle_modifyfield!_call!(ir::IRCode, idx::Int, stmt::Expr, info::ModifyFieldInfo, state::InliningState) + info = info.info + info isa MethodResultPure && (info = info.info) + info isa ConstCallInfo && (info = info.call) + info isa MethodMatchInfo || return nothing + length(info.results) == 1 || return nothing + match = info.results[1]::MethodMatch + match.fully_covers || return nothing + case = compileable_specialization(match, Effects(), InliningEdgeTracker(state.et), info; + compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) + case === nothing && return nothing + stmt.head = :invoke_modify + pushfirst!(stmt.args, case.invoke) + ir.stmts[idx][:inst] = stmt + return nothing +end + +function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::FinalizerInfo, + 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) + ir[SSAValue(idx)] = nothing + return nothing + end + + # Only inline finalizers that are known nothrow and notls. + # This avoids having to set up state for finalizer isolation + is_finalizer_inlineable(info.effects) || return nothing + + ft = argextype(stmt.args[2], ir) + has_free_typevars(ft) && return nothing + f = singleton_type(ft) + argtypes = Vector{Any}(undef, 2) + argtypes[1] = ft + argtypes[2] = argextype(stmt.args[3], ir) + sig = Signature(f, ft, argtypes) + + cases = compute_inlining_cases(info.info, #=flag=#UInt8(0), sig, state) + cases === nothing && return nothing + cases, all_covered, _ = cases + if all_covered && length(cases) == 1 + # NOTE we don't append `item1` to `stmt` here so that we don't serialize + # `Core.Compiler` data structure into the global cache + item1 = cases[1].item + if isa(item1, InliningTodo) + push!(stmt.args, true) + push!(stmt.args, item1.mi) + elseif isa(item1, InvokeCase) + push!(stmt.args, false) + push!(stmt.args, item1.invoke) + elseif isa(item1, ConstantCase) + push!(stmt.args, nothing) + push!(stmt.args, item1.val) + end + end + return nothing +end + +function handle_invoke_expr!(todo::Vector{Pair{Int,Any}}, + idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt8, 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 return nothing end @@ -1340,75 +1657,49 @@ function inline_const_if_inlineable!(inst::Instruction) end function assemble_inline_todo!(ir::IRCode, state::InliningState) - # todo = (inline_idx, (isva, isinvoke, na), method, spvals, inline_linetable, inline_ir, lie) todo = Pair{Int, Any}[] - et = state.et for idx in 1:length(ir.stmts) - simpleres = process_simple!(ir, idx, state, todo) + simpleres = process_simple!(todo, ir, idx, state) simpleres === nothing && continue stmt, sig = simpleres + flag = ir.stmts[idx][:flag] info = ir.stmts[idx][:info] + # `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) + continue + end + # Check whether this call was @pure and evaluates to a constant if info isa MethodResultPure inline_const_if_inlineable!(ir[SSAValue(idx)]) && continue info = info.info end - if info === false + if info === NoCallInfo() # Inference determined this couldn't be analyzed. Don't question it. continue end - flag = ir.stmts[idx][:flag] - + # handle special cased builtins if isa(info, OpaqueClosureCallInfo) - result = info.result - if isa(result, InferenceResult) - handle_const_opaque_closure_call!( - ir, idx, stmt, result, flag, - sig, state, todo) - else - if isa(result, ConstResult) - item = const_result_item(result, state) - else - item = analyze_method!(info.match, sig.argtypes, flag, state) - end - handle_single_case!(ir, idx, stmt, item, todo, state.params) - end - continue - end - - # Handle invoke - if sig.f === Core.invoke - if isa(info, InvokeCallInfo) - inline_invoke!(ir, idx, stmt, info, flag, sig, state, todo) - end - continue - end - - # if inference arrived here with constant-prop'ed result(s), - # we can perform a specialized analysis for just this case - if isa(info, ConstCallInfo) - handle_const_call!( - ir, idx, stmt, info, flag, - sig, state, todo) - continue - end - - # Ok, now figure out what method to call - if isa(info, MethodMatchInfo) - infos = MethodMatchInfo[info] - elseif isa(info, UnionSplitInfo) - infos = info.matches + 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, InvokeCallInfo) + handle_invoke_call!(todo, ir, idx, stmt, info, flag, sig, state) + elseif isa(info, FinalizerInfo) + handle_finalizer_call!(ir, idx, stmt, info, state) else - continue # isa(info, ReturnTypeCallInfo), etc. + # cascade to the generic (and extendable) handler + handle_call!(todo, ir, idx, stmt, info, flag, sig, state) end - - analyze_single_call!(ir, idx, stmt, infos, flag, sig, state, todo) end - todo + + return todo end function linear_inline_eligible(ir::IRCode) @@ -1419,18 +1710,10 @@ function linear_inline_eligible(ir::IRCode) return true end -# Check for a number of functions known to be pure -function ispuretopfunction(@nospecialize(f)) - return istopfunction(f, :typejoin) || - istopfunction(f, :isbits) || - istopfunction(f, :isbitstype) || - istopfunction(f, :promote_type) -end - function early_inline_special_case( ir::IRCode, stmt::Expr, @nospecialize(type), sig::Signature, - params::OptimizationParams) - params.inlining || return nothing + state::InliningState) + OptimizationParams(state.interp).inlining || return nothing (; f, ft, argtypes) = sig if isa(type, Const) # || isconstType(type) @@ -1440,10 +1723,10 @@ function early_inline_special_case( if is_pure_intrinsic_infer(f) && intrinsic_nothrow(f, argtypes[2:end]) return SomeCase(quoted(val)) end - elseif ispuretopfunction(f) || contains_is(_PURE_BUILTINS, f) + elseif contains_is(_PURE_BUILTINS, f) return SomeCase(quoted(val)) elseif contains_is(_EFFECT_FREE_BUILTINS, f) - if _builtin_nothrow(f, argtypes[2:end], type) + if _builtin_nothrow(optimizer_lattice(state.interp), f, argtypes[2:end], type) return SomeCase(quoted(val)) end elseif f === Core.get_binding_type @@ -1453,6 +1736,27 @@ function early_inline_special_case( end end end + if f === compilerbarrier + # check if this `compilerbarrier` has already imposed a barrier on abstract interpretation + # so that it can be eliminated here + length(argtypes) == 3 || return nothing + setting = argtypes[2] + isa(setting, Const) || return nothing + setting = setting.val + isa(setting, Symbol) || return nothing + setting === :const || setting === :conditional || setting === :type || return nothing + # barriered successfully already, eliminate it + return SomeCase(stmt.args[3]) + elseif f === Core.ifelse && length(argtypes) == 4 + cond = argtypes[2] + if isa(cond, Const) + if cond.val === true + return SomeCase(stmt.args[3]) + elseif cond.val === false + return SomeCase(stmt.args[4]) + end + end + end return nothing end @@ -1461,8 +1765,8 @@ end # NOTE we manually inline the method bodies, and so the logic here needs to precisely sync with their definitions function late_inline_special_case!( ir::IRCode, idx::Int, stmt::Expr, @nospecialize(type), sig::Signature, - params::OptimizationParams) - params.inlining || return nothing + state::InliningState) + OptimizationParams(state.interp).inlining || return nothing (; f, ft, argtypes) = sig if length(argtypes) == 3 && istopfunction(f, :!==) # special-case inliner for !== that precedes _methods_by_ftype union splitting @@ -1477,17 +1781,17 @@ function late_inline_special_case!( elseif length(argtypes) == 3 && istopfunction(f, :(>:)) # special-case inliner for issupertype # that works, even though inference generally avoids inferring the `>:` Method - if isa(type, Const) && _builtin_nothrow(<:, Any[argtypes[3], argtypes[2]], type) + if isa(type, Const) && _builtin_nothrow(optimizer_lattice(state.interp), <:, Any[argtypes[3], argtypes[2]], type) return SomeCase(quoted(type.val)) end subtype_call = Expr(:call, GlobalRef(Core, :(<:)), stmt.args[3], stmt.args[2]) return SomeCase(subtype_call) - elseif f === TypeVar && 2 <= length(argtypes) <= 4 && (argtypes[2] ⊑ Symbol) + elseif f === TypeVar && 2 <= length(argtypes) <= 4 && ⊑(optimizer_lattice(state.interp), argtypes[2], Symbol) typevar_call = Expr(:call, GlobalRef(Core, :_typevar), stmt.args[2], length(stmt.args) < 4 ? Bottom : stmt.args[3], length(stmt.args) == 2 ? Any : stmt.args[end]) return SomeCase(typevar_call) - elseif f === UnionAll && length(argtypes) == 3 && (argtypes[2] ⊑ TypeVar) + elseif f === UnionAll && length(argtypes) == 3 && ⊑(optimizer_lattice(state.interp), argtypes[2], TypeVar) unionall_call = Expr(:foreigncall, QuoteNode(:jl_type_unionall), Any, svec(Any, Any), 0, QuoteNode(:ccall), stmt.args[2], stmt.args[3]) return SomeCase(unionall_call) @@ -1501,16 +1805,35 @@ function late_inline_special_case!( return nothing end -function ssa_substitute!(idx::Int, @nospecialize(val), arg_replacements::Vector{Any}, +function ssa_substitute!(insert_node!::Inserter, + subst_inst::Instruction, @nospecialize(val), arg_replacements::Vector{Any}, @nospecialize(spsig), spvals::SimpleVector, - linetable_offset::Int32, boundscheck::Symbol, compact::IncrementalCompact) - compact.result[idx][:flag] &= ~IR_FLAG_INBOUNDS - compact.result[idx][:line] += linetable_offset - return ssa_substitute_op!(val, arg_replacements, spsig, spvals, boundscheck) + spvals_ssa::Union{Nothing, SSAValue}, + linetable_offset::Int32, boundscheck::Symbol) + subst_inst[:flag] &= ~IR_FLAG_INBOUNDS + subst_inst[:line] += linetable_offset + return ssa_substitute_op!(insert_node!, subst_inst, + val, arg_replacements, spsig, spvals, spvals_ssa, boundscheck) end -function ssa_substitute_op!(@nospecialize(val), arg_replacements::Vector{Any}, - @nospecialize(spsig), spvals::SimpleVector, boundscheck::Symbol) +function insert_spval!(insert_node!::Inserter, spvals_ssa::SSAValue, spidx::Int, do_isdefined::Bool) + ret = insert_node!( + effect_free(NewInstruction(Expr(:call, Core._svec_ref, false, spvals_ssa, spidx), Any))) + tcheck_not = nothing + if do_isdefined + tcheck = insert_node!( + effect_free(NewInstruction(Expr(:call, Core.isa, ret, Core.TypeVar), Bool))) + tcheck_not = insert_node!( + effect_free(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), arg_replacements::Vector{Any}, + @nospecialize(spsig), spvals::SimpleVector, + spvals_ssa::Union{Nothing, SSAValue}, + boundscheck::Symbol) if isa(val, Argument) return arg_replacements[val.n] end @@ -1518,14 +1841,36 @@ function ssa_substitute_op!(@nospecialize(val), arg_replacements::Vector{Any}, e = val::Expr head = e.head if head === :static_parameter - return quoted(spvals[e.args[1]::Int]) - elseif head === :cfunction + spidx = e.args[1]::Int + val = spvals[spidx] + if !isa(val, TypeVar) && val !== Vararg + return quoted(val) + else + flag = subst_inst[:flag] + maybe_undef = (flag & IR_FLAG_NOTHROW) == 0 && isa(val, TypeVar) + (ret, tcheck_not) = insert_spval!(insert_node!, spvals_ssa::SSAValue, spidx, maybe_undef) + if maybe_undef + insert_node!( + non_effect_free(NewInstruction(Expr(:throw_undef_if_not, val.name, tcheck_not), Nothing))) + end + return ret + end + elseif head === :isdefined && isa(e.args[1], Expr) && e.args[1].head === :static_parameter + spidx = (e.args[1]::Expr).args[1]::Int + val = spvals[spidx] + if !isa(val, TypeVar) + return true + else + (_, tcheck_not) = insert_spval!(insert_node!, spvals_ssa::SSAValue, spidx, true) + return tcheck_not + end + elseif head === :cfunction && spvals_ssa === nothing @assert !isa(spsig, UnionAll) || !isempty(spvals) e.args[3] = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), e.args[3], spsig, spvals) e.args[4] = svec(Any[ ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), argt, spsig, spvals) for argt in e.args[4]::SimpleVector ]...) - elseif head === :foreigncall + elseif head === :foreigncall && spvals_ssa === nothing @assert !isa(spsig, UnionAll) || !isempty(spvals) for i = 1:length(e.args) if i == 2 @@ -1546,9 +1891,10 @@ function ssa_substitute_op!(@nospecialize(val), arg_replacements::Vector{Any}, end end end + isa(val, Union{SSAValue, NewSSAValue}) && return val # avoid infinite loop urs = userefs(val) for op in urs - op[] = ssa_substitute_op!(op[], arg_replacements, spsig, spvals, boundscheck) + op[] = ssa_substitute_op!(insert_node!, subst_inst, op[], arg_replacements, spsig, spvals, spvals_ssa, boundscheck) end return urs[] end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index a86e125fcb307..c5415add51cc5 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -28,12 +28,23 @@ function cfg_delete_edge!(cfg::CFG, from::Int, to::Int) nothing end +function bb_ordering() + lt=(<=) + by=x->first(x.stmts) + ord(lt, by, nothing, Forward) +end + function block_for_inst(index::Vector{Int}, inst::Int) return searchsortedfirst(index, inst, lt=(<=)) end + +function block_for_inst(index::Vector{BasicBlock}, inst::Int) + return searchsortedfirst(index, BasicBlock(StmtRange(inst, inst)), bb_ordering())-1 +end + block_for_inst(cfg::CFG, inst::Int) = block_for_inst(cfg.index, inst) -function basic_blocks_starts(stmts::Vector{Any}) +@inline function basic_blocks_starts(stmts::Vector{Any}) jump_dests = BitSet() push!(jump_dests, 1) # function entry point # First go through and compute jump destinations @@ -80,15 +91,14 @@ function basic_blocks_starts(stmts::Vector{Any}) end function compute_basic_blocks(stmts::Vector{Any}) - bb_starts = basic_blocks_starts(stmts) # Compute ranges + bb_starts = basic_blocks_starts(stmts) # ::BitSet and already sorted pop!(bb_starts, 1) - basic_block_index = collect(bb_starts) - blocks = BasicBlock[] - sizehint!(blocks, length(basic_block_index)) + basic_block_index = Int[bb for bb in bb_starts] + blocks = Vector{BasicBlock}(undef, length(basic_block_index)) let first = 1 - for last in basic_block_index - push!(blocks, BasicBlock(StmtRange(first, last - 1))) + for (i, last) in enumerate(basic_block_index) + blocks[i] = BasicBlock(StmtRange(first, last - 1)) first = last end end @@ -115,19 +125,14 @@ function compute_basic_blocks(stmts::Vector{Any}) push!(blocks[block′].preds, num) push!(b.succs, block′) end - elseif isa(terminator, Expr) - if terminator.head === :enter - # :enter gets a virtual edge to the exception handler and - # the exception handler gets a virtual edge from outside - # the function. - # See the devdocs on exception handling in SSA form (or - # bug Keno to write them, if you're reading this and they - # don't exist) - 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′) - end + elseif isexpr(terminator, :enter) + # :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′) end # statement fall-through if num + 1 <= length(blocks) @@ -139,7 +144,7 @@ function compute_basic_blocks(stmts::Vector{Any}) end # this function assumes insert position exists -function first_insert_for_bb(code, cfg::CFG, block::Int) +function first_insert_for_bb(code::Vector{Any}, cfg::CFG, block::Int) for idx in cfg.blocks[block].stmts stmt = code[idx] if !isa(stmt, PhiNode) @@ -149,49 +154,49 @@ function first_insert_for_bb(code, cfg::CFG, block::Int) error("any insert position isn't found") end -# SSA-indexed nodes +# SSA values that need renaming +struct OldSSAValue + id::Int +end -struct NewInstruction - stmt::Any - type::Any - info::Any - # If nothing, copy the line from previous statement - # in the insertion location - line::Union{Int32, Nothing} - flag::UInt8 +## TODO: This description currently omits the use of NewSSAValue during slot2ssa, +## which doesn't use IncrementalCompact, but does something similar and also uses +## NewSSAValue to refer to new_nodes. Ideally that use of NewSSAValue would go away +## during a refactor. +""" + struct NewSSAValue + +`NewSSAValue`s occur in the context of IncrementalCompact. Their meaning depends +on where they appear: - ## Insertion options +1. In already-compacted nodes, + i. a `NewSSAValue` with positive `id` has the same meaning as a regular SSAValue. + ii. a `NewSSAValue` with negative `id` refers to post-compaction `new_node` node. - # The IR_FLAG_EFFECT_FREE flag has already been computed (or forced). - # Don't bother redoing so on insertion. - effect_free_computed::Bool - NewInstruction(@nospecialize(stmt), @nospecialize(type), @nospecialize(info), - line::Union{Int32, Nothing}, flag::UInt8, effect_free_computed::Bool) = - new(stmt, type, info, line, flag, effect_free_computed) +2. In non-compacted nodes, + i. a `NewSSAValue` with positive `id` refers to the index of an already-compacted instructions. + ii. a `NewSSAValue` with negative `id` has the same meaning as in compacted nodes. +""" +struct NewSSAValue + id::Int end -NewInstruction(@nospecialize(stmt), @nospecialize(type)) = - NewInstruction(stmt, type, nothing) -NewInstruction(@nospecialize(stmt), @nospecialize(type), line::Union{Nothing, Int32}) = - NewInstruction(stmt, type, nothing, line, IR_FLAG_NULL, false) -effect_free(inst::NewInstruction) = - NewInstruction(inst.stmt, inst.type, inst.info, inst.line, inst.flag | IR_FLAG_EFFECT_FREE, true) -non_effect_free(inst::NewInstruction) = - NewInstruction(inst.stmt, inst.type, inst.info, inst.line, inst.flag & ~IR_FLAG_EFFECT_FREE, true) +const AnySSAValue = Union{SSAValue, OldSSAValue, NewSSAValue} +# SSA-indexed nodes struct InstructionStream inst::Vector{Any} type::Vector{Any} - info::Vector{Any} + info::Vector{CallInfo} line::Vector{Int32} flag::Vector{UInt8} end function InstructionStream(len::Int) - insts = Array{Any}(undef, len) - types = Array{Any}(undef, len) - info = Array{Any}(undef, len) - fill!(info, nothing) + insts = Vector{Any}(undef, len) + types = Vector{Any}(undef, len) + info = Vector{CallInfo}(undef, len) + fill!(info, NoCallInfo()) lines = fill(Int32(0), len) flags = fill(IR_FLAG_NULL, len) return InstructionStream(insts, types, info, lines, flags) @@ -199,7 +204,7 @@ end InstructionStream() = InstructionStream(0) length(is::InstructionStream) = length(is.inst) isempty(is::InstructionStream) = isempty(is.inst) -function add!(is::InstructionStream) +function add_new_idx!(is::InstructionStream) ninst = length(is) + 1 resize!(is, ninst) return ninst @@ -222,7 +227,7 @@ function resize!(stmts::InstructionStream, len) for i in (old_length + 1):len stmts.line[i] = 0 stmts.flag[i] = IR_FLAG_NULL - stmts.info[i] = nothing + stmts.info[i] = NoCallInfo() end return stmts end @@ -231,7 +236,7 @@ struct Instruction data::InstructionStream idx::Int end -Instruction(is::InstructionStream) = Instruction(is, add!(is)) +Instruction(is::InstructionStream) = Instruction(is, add_new_idx!(is)) @inline function getindex(node::Instruction, fld::Symbol) isdefined(node, fld) && return getfield(node, fld) @@ -251,6 +256,10 @@ function setindex!(is::InstructionStream, newval::Instruction, idx::Int) is.flag[idx] = newval[:flag] return is end +function setindex!(is::InstructionStream, newval::Union{AnySSAValue, Nothing}, idx::Int) + is.inst[idx] = newval + return is +end function setindex!(node::Instruction, newval::Instruction) node.data[node.idx] = newval return node @@ -269,32 +278,94 @@ end NewNodeStream(len::Int=0) = NewNodeStream(InstructionStream(len), fill(NewNodeInfo(0, false), len)) length(new::NewNodeStream) = length(new.stmts) isempty(new::NewNodeStream) = isempty(new.stmts) -function add!(new::NewNodeStream, pos::Int, attach_after::Bool) +function add_inst!(new::NewNodeStream, pos::Int, attach_after::Bool) push!(new.info, NewNodeInfo(pos, attach_after)) return Instruction(new.stmts) end copy(nns::NewNodeStream) = NewNodeStream(copy(nns.stmts), copy(nns.info)) +struct NewInstruction + stmt::Any + type::Any + info::CallInfo + line::Union{Int32,Nothing} # if nothing, copy the line from previous statement in the insertion location + flag::Union{UInt8,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{UInt8,Nothing}) + return new(stmt, type, info, line, flag) + end +end +function NewInstruction(@nospecialize(stmt), @nospecialize(type), line::Union{Int32,Nothing}=nothing) + return NewInstruction(stmt, type, NoCallInfo(), line, nothing) +end +@nospecialize +function NewInstruction(newinst::NewInstruction; + stmt::Any=newinst.stmt, + type::Any=newinst.type, + info::CallInfo=newinst.info, + line::Union{Int32,Nothing}=newinst.line, + flag::Union{UInt8,Nothing}=newinst.flag) + return NewInstruction(stmt, type, info, line, flag) +end +function NewInstruction(inst::Instruction; + stmt::Any=inst[:inst], + type::Any=inst[:type], + info::CallInfo=inst[:info], + line::Union{Int32,Nothing}=inst[:line], + flag::Union{UInt8,Nothing}=inst[:flag]) + return NewInstruction(stmt, type, info, line, flag) +end +@specialize +effect_free(newinst::NewInstruction) = NewInstruction(newinst; flag=add_flag(newinst, IR_FLAG_EFFECT_FREE)) +non_effect_free(newinst::NewInstruction) = NewInstruction(newinst; flag=sub_flag(newinst, IR_FLAG_EFFECT_FREE)) +with_flags(newinst::NewInstruction, flags::UInt8) = NewInstruction(newinst; flag=add_flag(newinst, flags)) +without_flags(newinst::NewInstruction, flags::UInt8) = NewInstruction(newinst; flag=sub_flag(newinst, flags)) +function add_flag(newinst::NewInstruction, newflag::UInt8) + flag = newinst.flag + flag === nothing && return newflag + return flag | newflag +end +function sub_flag(newinst::NewInstruction, newflag::UInt8) + flag = newinst.flag + flag === nothing && return IR_FLAG_NULL + return flag & ~newflag +end + struct IRCode stmts::InstructionStream argtypes::Vector{Any} - sptypes::Vector{Any} + sptypes::Vector{VarState} linetable::Vector{LineInfoNode} cfg::CFG new_nodes::NewNodeStream - meta::Vector{Any} + meta::Vector{Expr} - function IRCode(stmts::InstructionStream, cfg::CFG, linetable::Vector{LineInfoNode}, argtypes::Vector{Any}, meta::Vector{Any}, sptypes::Vector{Any}) + 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) 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) end - global copy - copy(ir::IRCode) = new(copy(ir.stmts), copy(ir.argtypes), copy(ir.sptypes), + 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 +""" + IRCode() + +Create an empty IRCode object with a single `return nothing` statement. This method is mostly intended +for debugging and unit testing of IRCode APIs. The compiler itself should generally obtain an IRCode +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[]) + ir[SSAValue(1)][:inst] = ReturnNode(nothing) + ir[SSAValue(1)][:type] = Nothing + ir[SSAValue(1)][:flag] = 0x00 + ir[SSAValue(1)][:line] = Int32(0) + return ir +end + function block_for_inst(ir::IRCode, inst::Int) if inst > length(ir.stmts) inst = ir.new_nodes.info[inst - length(ir.stmts)].pos @@ -310,7 +381,7 @@ function getindex(x::IRCode, s::SSAValue) end end -function setindex!(x::IRCode, repl::Instruction, s::SSAValue) +function setindex!(x::IRCode, repl::Union{Instruction, Nothing, AnySSAValue}, s::SSAValue) if s.id <= length(x.stmts) x.stmts[s.id] = repl else @@ -319,83 +390,68 @@ function setindex!(x::IRCode, repl::Instruction, s::SSAValue) return x end -# SSA values that need renaming -struct OldSSAValue - id::Int -end - -# SSA values that are in `new_new_nodes` of an `IncrementalCompact` and are to -# be actually inserted next time (they become `new_nodes` next time) -struct NewSSAValue - id::Int -end - -const AnySSAValue = Union{SSAValue, OldSSAValue, NewSSAValue} - -mutable struct UseRef +mutable struct UseRefIterator stmt::Any - op::Int - UseRef(@nospecialize(a)) = new(a, 0) - UseRef(@nospecialize(a), op::Int) = new(a, op) -end -struct UseRefIterator - use::Tuple{UseRef, Nothing} relevant::Bool - UseRefIterator(@nospecialize(a), relevant::Bool) = new((UseRef(a), nothing), relevant) + UseRefIterator(@nospecialize(a), relevant::Bool) = new(a, relevant) end -getindex(it::UseRefIterator) = it.use[1].stmt +getindex(it::UseRefIterator) = it.stmt -# TODO: stack-allocation -#struct UseRef -# urs::UseRefIterator -# use::Int -#end +struct UseRef + urs::UseRefIterator + op::Int + UseRef(urs::UseRefIterator) = new(urs, 0) + UseRef(urs::UseRefIterator, op::Int) = new(urs, op) +end struct OOBToken end; const OOB_TOKEN = OOBToken() struct UndefToken end; const UNDEF_TOKEN = UndefToken() -function getindex(x::UseRef) - stmt = x.stmt +@noinline function _useref_getindex(@nospecialize(stmt), op::Int) if isa(stmt, Expr) && stmt.head === :(=) rhs = stmt.args[2] if isa(rhs, Expr) if is_relevant_expr(rhs) - x.op > length(rhs.args) && return OOB_TOKEN - return rhs.args[x.op] + op > length(rhs.args) && return OOB_TOKEN + return rhs.args[op] end end - x.op == 1 || return OOB_TOKEN + op == 1 || return OOB_TOKEN return rhs elseif isa(stmt, Expr) # @assert is_relevant_expr(stmt) - x.op > length(stmt.args) && return OOB_TOKEN - return stmt.args[x.op] + op > length(stmt.args) && return OOB_TOKEN + return stmt.args[op] elseif isa(stmt, GotoIfNot) - x.op == 1 || return OOB_TOKEN + op == 1 || return OOB_TOKEN return stmt.cond elseif isa(stmt, ReturnNode) isdefined(stmt, :val) || return OOB_TOKEN - x.op == 1 || return OOB_TOKEN + op == 1 || return OOB_TOKEN return stmt.val elseif isa(stmt, PiNode) isdefined(stmt, :val) || return OOB_TOKEN - x.op == 1 || return OOB_TOKEN + op == 1 || return OOB_TOKEN return stmt.val + elseif isa(stmt, Union{SSAValue, NewSSAValue, GlobalRef}) + op == 1 || return OOB_TOKEN + return stmt elseif isa(stmt, UpsilonNode) isdefined(stmt, :val) || return OOB_TOKEN - x.op == 1 || return OOB_TOKEN + op == 1 || return OOB_TOKEN return stmt.val elseif isa(stmt, PhiNode) - x.op > length(stmt.values) && return OOB_TOKEN - isassigned(stmt.values, x.op) || return UNDEF_TOKEN - return stmt.values[x.op] + op > length(stmt.values) && return OOB_TOKEN + isassigned(stmt.values, op) || return UNDEF_TOKEN + return stmt.values[op] elseif isa(stmt, PhiCNode) - x.op > length(stmt.values) && return OOB_TOKEN - isassigned(stmt.values, x.op) || return UNDEF_TOKEN - return stmt.values[x.op] + op > length(stmt.values) && return OOB_TOKEN + isassigned(stmt.values, op) || return UNDEF_TOKEN + return stmt.values[op] else return OOB_TOKEN end end +@inline getindex(x::UseRef) = _useref_getindex(x.urs.stmt, x.op) function is_relevant_expr(e::Expr) return e.head in (:call, :invoke, :invoke_modify, @@ -407,145 +463,159 @@ function is_relevant_expr(e::Expr) :new_opaque_closure) end -function setindex!(x::UseRef, @nospecialize(v)) - stmt = x.stmt +@noinline function _useref_setindex!(@nospecialize(stmt), op::Int, @nospecialize(v)) if isa(stmt, Expr) && stmt.head === :(=) rhs = stmt.args[2] if isa(rhs, Expr) if is_relevant_expr(rhs) - x.op > length(rhs.args) && throw(BoundsError()) - rhs.args[x.op] = v - return v + op > length(rhs.args) && throw(BoundsError()) + rhs.args[op] = v + return stmt end end - x.op == 1 || throw(BoundsError()) + op == 1 || throw(BoundsError()) stmt.args[2] = v elseif isa(stmt, Expr) # @assert is_relevant_expr(stmt) - x.op > length(stmt.args) && throw(BoundsError()) - stmt.args[x.op] = v + op > length(stmt.args) && throw(BoundsError()) + stmt.args[op] = v elseif isa(stmt, GotoIfNot) - x.op == 1 || throw(BoundsError()) - x.stmt = GotoIfNot(v, stmt.dest) + op == 1 || throw(BoundsError()) + stmt = GotoIfNot(v, stmt.dest) elseif isa(stmt, ReturnNode) - x.op == 1 || throw(BoundsError()) - x.stmt = typeof(stmt)(v) + op == 1 || throw(BoundsError()) + stmt = typeof(stmt)(v) + elseif isa(stmt, Union{SSAValue, NewSSAValue, GlobalRef}) + op == 1 || throw(BoundsError()) + stmt = v elseif isa(stmt, UpsilonNode) - x.op == 1 || throw(BoundsError()) - x.stmt = typeof(stmt)(v) + op == 1 || throw(BoundsError()) + stmt = typeof(stmt)(v) elseif isa(stmt, PiNode) - x.op == 1 || throw(BoundsError()) - x.stmt = typeof(stmt)(v, stmt.typ) + op == 1 || throw(BoundsError()) + stmt = typeof(stmt)(v, stmt.typ) elseif isa(stmt, PhiNode) - x.op > length(stmt.values) && throw(BoundsError()) - isassigned(stmt.values, x.op) || throw(BoundsError()) - stmt.values[x.op] = v + op > length(stmt.values) && throw(BoundsError()) + isassigned(stmt.values, op) || throw(BoundsError()) + stmt.values[op] = v elseif isa(stmt, PhiCNode) - x.op > length(stmt.values) && throw(BoundsError()) - isassigned(stmt.values, x.op) || throw(BoundsError()) - stmt.values[x.op] = v + op > length(stmt.values) && throw(BoundsError()) + isassigned(stmt.values, op) || throw(BoundsError()) + stmt.values[op] = v else throw(BoundsError()) end + return stmt +end + +@inline function setindex!(x::UseRef, @nospecialize(v)) + x.urs.stmt = _useref_setindex!(x.urs.stmt, x.op, v) return x end function userefs(@nospecialize(x)) relevant = (isa(x, Expr) && is_relevant_expr(x)) || - isa(x, GotoIfNot) || isa(x, ReturnNode) || + isa(x, GotoIfNot) || isa(x, ReturnNode) || isa(x, SSAValue) || isa(x, NewSSAValue) || isa(x, PiNode) || isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) return UseRefIterator(x, relevant) end -iterate(it::UseRefIterator) = (it.use[1].op = 0; iterate(it, nothing)) -@noinline function iterate(it::UseRefIterator, ::Nothing) - it.relevant || return nothing - use = it.use[1] +@noinline function _advance(@nospecialize(stmt), op) while true - use.op += 1 - y = use[] + op += 1 + y = _useref_getindex(stmt, op) y === OOB_TOKEN && return nothing - y === UNDEF_TOKEN || return it.use + y === UNDEF_TOKEN || return op end end +@inline function iterate(it::UseRefIterator, op::Int=0) + it.relevant || return nothing + op = _advance(it.stmt, op) + op === nothing && return nothing + return (UseRef(it, op), op) +end + # This function is used from the show code, which may have a different # `push!`/`used` type since it's in Base. -function scan_ssa_use!(push!, used, @nospecialize(stmt)) - if isa(stmt, SSAValue) - push!(used, stmt.id) - end - for useref in userefs(stmt) - val = useref[] - if isa(val, SSAValue) - push!(used, val.id) - end - end -end +scan_ssa_use!(@specialize(push!), used, @nospecialize(stmt)) = foreachssa(ssa::SSAValue -> push!(used, ssa.id), stmt) # Manually specialized copy of the above with push! === Compiler.push! -function scan_ssa_use!(used::IdSet, @nospecialize(stmt)) - if isa(stmt, SSAValue) - push!(used, stmt.id) - end - for useref in userefs(stmt) - val = useref[] - if isa(val, SSAValue) - push!(used, val.id) +scan_ssa_use!(used::IdSet, @nospecialize(stmt)) = foreachssa(ssa::SSAValue -> push!(used, ssa.id), stmt) + +function insert_node!(ir::IRCode, pos::SSAValue, newinst::NewInstruction, attach_after::Bool=false) + posid = pos.id + if pos.id > length(ir.stmts) + if attach_after + info = ir.new_nodes.info[pos.id-length(ir.stmts)]; + posid = info.pos + attach_after = info.attach_after + else + error("Cannot attach before a pending node.") end end + node = add_inst!(ir.new_nodes, posid, attach_after) + newline = something(newinst.line, ir[pos][:line]) + newflag = recompute_inst_flag(newinst, ir) + node = inst_from_newinst!(node, newinst, newline, newflag) + return SSAValue(length(ir.stmts) + node.idx) end +insert_node!(ir::IRCode, pos::Int, newinst::NewInstruction, attach_after::Bool=false) = + insert_node!(ir, SSAValue(pos), newinst, attach_after) -function ssamap(f, @nospecialize(stmt)) - urs = userefs(stmt) - for op in urs - val = op[] - if isa(val, SSAValue) - op[] = f(val) - end - end - return urs[] +struct CFGTransformState + cfg_transforms_enabled::Bool + fold_constant_branches::Bool + result_bbs::Vector{BasicBlock} + bb_rename_pred::Vector{Int} + bb_rename_succ::Vector{Int} end -function foreachssa(f, @nospecialize(stmt)) - for op in userefs(stmt) - val = op[] - if isa(val, SSAValue) - f(val) +# N.B.: Takes ownership of the CFG array +function CFGTransformState!(blocks::Vector{BasicBlock}, allow_cfg_transforms::Bool=false) + if allow_cfg_transforms + bb_rename = Vector{Int}(undef, length(blocks)) + cur_bb = 1 + domtree = construct_domtree(blocks) + for i = 1:length(bb_rename) + if bb_unreachable(domtree, i) + bb_rename[i] = -1 + else + bb_rename[i] = cur_bb + cur_bb += 1 + end end - end -end - -function insert_node!(ir::IRCode, pos::Int, inst::NewInstruction, attach_after::Bool=false) - node = add!(ir.new_nodes, pos, attach_after) - node[:line] = something(inst.line, ir.stmts[pos][:line]) - flag = inst.flag - if !inst.effect_free_computed - if stmt_effect_free(inst.stmt, inst.type, ir) - flag |= IR_FLAG_EFFECT_FREE + for i = 1:length(bb_rename) + bb_rename[i] == -1 && continue + preds, succs = blocks[i].preds, blocks[i].succs + # Rename preds + for j = 1:length(preds) + if preds[j] != 0 + preds[j] = bb_rename[preds[j]] + end + end + # Dead blocks get removed from the predecessor list + filter!(x->x !== -1, preds) + # Rename succs + for j = 1:length(succs) + succs[j] = bb_rename[succs[j]] + end end + let blocks = blocks, bb_rename = bb_rename + result_bbs = BasicBlock[blocks[i] for i = 1:length(blocks) if bb_rename[i] != -1] + end + else + bb_rename = Vector{Int}() + result_bbs = blocks end - node[:inst], node[:type], node[:flag] = inst.stmt, inst.type, flag - return SSAValue(length(ir.stmts) + node.idx) -end - -# For bootstrapping -function my_sortperm(v) - p = Vector{Int}(undef, length(v)) - for i = 1:length(v) - p[i] = i - end - sort!(p, Sort.DEFAULT_UNSTABLE, Order.Perm(Sort.Forward,v)) - p + return CFGTransformState(allow_cfg_transforms, allow_cfg_transforms, result_bbs, bb_rename, bb_rename) end mutable struct IncrementalCompact ir::IRCode result::InstructionStream - result_bbs::Vector{BasicBlock} + cfg_transform::CFGTransformState ssa_rename::Vector{Any} - bb_rename_pred::Vector{Int} - bb_rename_succ::Vector{Int} used_ssas::Vector{Int} late_fixup::Vector{Int} @@ -553,164 +623,233 @@ mutable struct IncrementalCompact new_nodes_idx::Int # This supports insertion while compacting new_new_nodes::NewNodeStream # New nodes that were before the compaction point at insertion time - # TODO: Switch these two to a min-heap of some sort + new_new_used_ssas::Vector{Int} pending_nodes::NewNodeStream # New nodes that were after the compaction point at insertion time - pending_perm::Vector{Int} + pending_perm::Vector{Int} # pending_nodes.info[pending_perm] is in min-heap order by pos # State idx::Int result_idx::Int + active_bb::Int active_result_bb::Int renamed_new_nodes::Bool - cfg_transforms_enabled::Bool - fold_constant_branches::Bool - function IncrementalCompact(code::IRCode, allow_cfg_transforms::Bool=false) + function IncrementalCompact(code::IRCode, cfg_transform::CFGTransformState) # Sort by position with attach after nodes after regular ones - perm = my_sortperm(Int[let new_node = code.new_nodes.info[i] - (new_node.pos * 2 + Int(new_node.attach_after)) - end for i in 1:length(code.new_nodes)]) - new_len = length(code.stmts) + length(code.new_nodes) + info = code.new_nodes.info + perm = sort!(collect(eachindex(info)); by=i->(2info[i].pos+info[i].attach_after, i)) + new_len = length(code.stmts) + length(info) result = InstructionStream(new_len) used_ssas = fill(0, new_len) + new_new_used_ssas = Vector{Int}() blocks = code.cfg.blocks - if allow_cfg_transforms - bb_rename = Vector{Int}(undef, length(blocks)) - cur_bb = 1 - domtree = construct_domtree(blocks) - for i = 1:length(bb_rename) - if bb_unreachable(domtree, i) - bb_rename[i] = -1 - else - bb_rename[i] = cur_bb - cur_bb += 1 - end - end - for i = 1:length(bb_rename) - bb_rename[i] == -1 && continue - preds, succs = blocks[i].preds, blocks[i].succs - # Rename preds - for j = 1:length(preds) - if preds[j] != 0 - preds[j] = bb_rename[preds[j]] - end - end - # Dead blocks get removed from the predecessor list - filter!(x->x !== -1, preds) - # Rename succs - for j = 1:length(succs) - succs[j] = bb_rename[succs[j]] - end - end - let blocks = blocks, bb_rename = bb_rename - result_bbs = BasicBlock[blocks[i] for i = 1:length(blocks) if bb_rename[i] != -1] - end - else - bb_rename = Vector{Int}() - result_bbs = code.cfg.blocks - end ssa_rename = Any[SSAValue(i) for i = 1:new_len] late_fixup = Vector{Int}() new_new_nodes = NewNodeStream() pending_nodes = NewNodeStream() pending_perm = Int[] - return new(code, result, result_bbs, ssa_rename, bb_rename, bb_rename, used_ssas, late_fixup, perm, 1, - new_new_nodes, pending_nodes, pending_perm, - 1, 1, 1, false, allow_cfg_transforms, allow_cfg_transforms) + return new(code, result, cfg_transform, ssa_rename, used_ssas, late_fixup, perm, 1, + new_new_nodes, new_new_used_ssas, pending_nodes, pending_perm, + 1, 1, 1, 1, false) end # For inlining function IncrementalCompact(parent::IncrementalCompact, code::IRCode, result_offset) - perm = my_sortperm(Int[code.new_nodes.info[i].pos for i in 1:length(code.new_nodes)]) - new_len = length(code.stmts) + length(code.new_nodes) + info = code.new_nodes.info + perm = sort!(collect(eachindex(info)); by=i->(info[i].pos, i)) + new_len = length(code.stmts) + length(info) ssa_rename = Any[SSAValue(i) for i = 1:new_len] - used_ssas = fill(0, new_len) - late_fixup = Vector{Int}() bb_rename = Vector{Int}() - new_new_nodes = NewNodeStream() pending_nodes = NewNodeStream() pending_perm = Int[] - return new(code, parent.result, - parent.result_bbs, ssa_rename, bb_rename, bb_rename, parent.used_ssas, - late_fixup, perm, 1, - new_new_nodes, pending_nodes, pending_perm, - 1, result_offset, parent.active_result_bb, false, false, false) + return new(code, parent.result, CFGTransformState(false, false, parent.cfg_transform.result_bbs, bb_rename, bb_rename), + ssa_rename, parent.used_ssas, + parent.late_fixup, perm, 1, + parent.new_new_nodes, parent.new_new_used_ssas, pending_nodes, pending_perm, + 1, result_offset, 1, parent.active_result_bb, false) end end +function IncrementalCompact(code::IRCode, allow_cfg_transforms::Bool=false) + return IncrementalCompact(code, CFGTransformState!(code.cfg.blocks, allow_cfg_transforms)) +end + struct TypesView{T} ir::T # ::Union{IRCode, IncrementalCompact} end types(ir::Union{IRCode, IncrementalCompact}) = TypesView(ir) -function getindex(compact::IncrementalCompact, idx::Int) - if idx < compact.result_idx - return compact.result[idx][:inst] - else - return compact.ir.stmts[idx][:inst] - end -end - function getindex(compact::IncrementalCompact, ssa::SSAValue) @assert ssa.id < compact.result_idx - return compact.result[ssa.id][:inst] + return compact.result[ssa.id] end function getindex(compact::IncrementalCompact, ssa::OldSSAValue) id = ssa.id - if id <= length(compact.ir.stmts) - return compact.ir.stmts[id][:inst] + if id < compact.idx + new_idx = compact.ssa_rename[id]::Int + return compact.result[new_idx] + elseif id <= length(compact.ir.stmts) + return compact.ir.stmts[id] end id -= length(compact.ir.stmts) if id <= length(compact.ir.new_nodes) - return compact.ir.new_nodes.stmts[id][:inst] + return compact.ir.new_nodes.stmts[id] end id -= length(compact.ir.new_nodes) - return compact.pending_nodes.stmts[id][:inst] + return compact.pending_nodes.stmts[id] end function getindex(compact::IncrementalCompact, ssa::NewSSAValue) - return compact.new_new_nodes.stmts[ssa.id][:inst] + if ssa.id < 0 + return compact.new_new_nodes.stmts[-ssa.id] + else + return compact[SSAValue(ssa.id)] + end end -function count_added_node!(compact::IncrementalCompact, @nospecialize(v)) - needs_late_fixup = isa(v, NewSSAValue) - if isa(v, SSAValue) - compact.used_ssas[v.id] += 1 +function block_for_inst(compact::IncrementalCompact, idx::SSAValue) + id = idx.id + if id < compact.result_idx # if ssa within result + return searchsortedfirst(compact.cfg_transform.result_bbs, BasicBlock(StmtRange(id, id)), + 1, compact.active_result_bb, bb_ordering())-1 + else + return block_for_inst(compact.ir.cfg, id) + end +end + +function block_for_inst(compact::IncrementalCompact, idx::OldSSAValue) + id = idx.id + if id < compact.idx # if ssa within result + id = compact.ssa_rename[id]::Int + return block_for_inst(compact, SSAValue(id)) else - for ops in userefs(v) - val = ops[] - if isa(val, SSAValue) - compact.used_ssas[val.id] += 1 - elseif isa(val, NewSSAValue) - needs_late_fixup = true + return block_for_inst(compact.ir.cfg, id) + end +end + +function block_for_inst(compact::IncrementalCompact, idx::NewSSAValue) + if idx.id > 0 + @assert idx.id < compact.result_idx + return block_for_inst(compact, SSAValue(idx.id)) + else + return block_for_inst(compact, SSAValue(compact.new_new_nodes.info[-idx.id].pos)) + end +end + +function dominates_ssa(compact::IncrementalCompact, domtree::DomTree, x::AnySSAValue, y::AnySSAValue) + xb = block_for_inst(compact, x) + yb = block_for_inst(compact, y) + if xb == yb + xinfo = yinfo = nothing + if isa(x, OldSSAValue) + x′ = compact.ssa_rename[x.id]::SSAValue + elseif isa(x, NewSSAValue) + if x.id > 0 + x′ = SSAValue(x.id) + else + xinfo = compact.new_new_nodes.info[-x.id] + x′ = SSAValue(xinfo.pos) + end + else + x′ = x + end + if isa(y, OldSSAValue) + y′ = compact.ssa_rename[y.id]::SSAValue + elseif isa(y, NewSSAValue) + if y.id > 0 + y′ = SSAValue(y.id) + else + yinfo = compact.new_new_nodes.info[-y.id] + y′ = SSAValue(yinfo.pos) + end + else + y′ = y + end + if x′.id == y′.id && (xinfo !== nothing || yinfo !== nothing) + if xinfo !== nothing && yinfo !== nothing + if xinfo.attach_after == yinfo.attach_after + return x.id < y.id + end + return yinfo.attach_after + elseif xinfo !== nothing + return !xinfo.attach_after + else + return (yinfo::NewNodeInfo).attach_after end end + return x′.id < y′.id end - needs_late_fixup + return dominates(domtree, xb, yb) +end + +function _count_added_node!(compact, @nospecialize(val)) + if isa(val, SSAValue) + compact.used_ssas[val.id] += 1 + return false + elseif isa(val, NewSSAValue) + @assert val.id < 0 # Newly added nodes should be canonicalized + compact.new_new_used_ssas[-val.id] += 1 + return true + end + return false +end + +function count_added_node!(compact::IncrementalCompact, @nospecialize(v)) + needs_late_fixup = false + for ops in userefs(v) + needs_late_fixup |= _count_added_node!(compact, ops[]) + end + return needs_late_fixup end function add_pending!(compact::IncrementalCompact, pos::Int, attach_after::Bool) - node = add!(compact.pending_nodes, pos, attach_after) - # TODO: switch this to `l = length(pending_nodes); splice!(pending_perm, searchsorted(pending_perm, l), l)` - push!(compact.pending_perm, length(compact.pending_nodes)) - sort!(compact.pending_perm, DEFAULT_STABLE, Order.By(x->compact.pending_nodes.info[x].pos, Order.Forward)) + node = add_inst!(compact.pending_nodes, pos, attach_after) + heappush!(compact.pending_perm, length(compact.pending_nodes), By(x -> compact.pending_nodes.info[x].pos)) + return node +end + +function inst_from_newinst!(node::Instruction, newinst::NewInstruction, + newline::Int32=newinst.line::Int32, newflag::UInt8=newinst.flag::UInt8) + node[:inst] = newinst.stmt + node[:type] = newinst.type + node[:info] = newinst.info + node[:line] = newline + node[:flag] = newflag return node end -function insert_node!(compact::IncrementalCompact, before, inst::NewInstruction, attach_after::Bool=false) - @assert inst.effect_free_computed +function recompute_inst_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 + return flag +end + +function insert_node!(compact::IncrementalCompact, @nospecialize(before), newinst::NewInstruction, attach_after::Bool=false) + newflag = recompute_inst_flag(newinst, compact) if isa(before, SSAValue) if before.id < compact.result_idx - count_added_node!(compact, inst.stmt) - line = something(inst.line, compact.result[before.id][:line]) - node = add!(compact.new_new_nodes, before.id, attach_after) - node[:inst], node[:type], node[:line], node[:flag] = inst.stmt, inst.type, line, inst.flag - return NewSSAValue(node.idx) + count_added_node!(compact, newinst.stmt) + newline = something(newinst.line, compact.result[before.id][:line]) + node = add_inst!(compact.new_new_nodes, before.id, attach_after) + node = inst_from_newinst!(node, newinst, newline, newflag) + push!(compact.new_new_used_ssas, 0) + return NewSSAValue(-node.idx) else - line = something(inst.line, compact.ir.stmts[before.id][:line]) + newline = something(newinst.line, compact.ir.stmts[before.id][:line]) node = add_pending!(compact, before.id, attach_after) - node[:inst], node[:type], node[:line], node[:flag] = inst.stmt, inst.type, line, inst.flag + node = inst_from_newinst!(node, newinst, newline, newflag) os = OldSSAValue(length(compact.ir.stmts) + length(compact.ir.new_nodes) + length(compact.pending_nodes)) push!(compact.ssa_rename, os) push!(compact.used_ssas, 0) @@ -720,43 +859,47 @@ function insert_node!(compact::IncrementalCompact, before, inst::NewInstruction, pos = before.id if pos < compact.idx renamed = compact.ssa_rename[pos]::AnySSAValue - count_added_node!(compact, inst.stmt) - line = something(inst.line, compact.result[renamed.id][:line]) - node = add!(compact.new_new_nodes, renamed.id, attach_after) - node[:inst], node[:type], node[:line], node[:flag] = inst.stmt, inst.type, line, inst.flag - return NewSSAValue(node.idx) + count_added_node!(compact, newinst.stmt) + newline = something(newinst.line, compact.result[renamed.id][:line]) + node = add_inst!(compact.new_new_nodes, renamed.id, attach_after) + node = inst_from_newinst!(node, newinst, newline, newflag) + push!(compact.new_new_used_ssas, 0) + return NewSSAValue(-node.idx) else if pos > length(compact.ir.stmts) #@assert attach_after info = compact.pending_nodes.info[pos - length(compact.ir.stmts) - length(compact.ir.new_nodes)] pos, attach_after = info.pos, info.attach_after end - line = something(inst.line, compact.ir.stmts[pos][:line]) + newline = something(newinst.line, compact.ir.stmts[pos][:line]) node = add_pending!(compact, pos, attach_after) - node[:inst], node[:type], node[:line], node[:flag] = inst.stmt, inst.type, line, inst.flag + node = inst_from_newinst!(node, newinst, newline, newflag) os = OldSSAValue(length(compact.ir.stmts) + length(compact.ir.new_nodes) + length(compact.pending_nodes)) push!(compact.ssa_rename, os) push!(compact.used_ssas, 0) return os end elseif isa(before, NewSSAValue) - before_entry = compact.new_new_nodes.info[before.id] - line = something(inst.line, compact.new_new_nodes.stmts[before.id][:line]) - new_entry = add!(compact.new_new_nodes, before_entry.pos, attach_after) - new_entry[:inst], new_entry[:type], new_entry[:line], new_entry[:flag] = inst.stmt, inst.type, line, inst.flag - return NewSSAValue(new_entry.idx) + # TODO: This is incorrect and does not maintain ordering among the new nodes + before_entry = compact.new_new_nodes.info[-before.id] + newline = something(newinst.line, compact.new_new_nodes.stmts[-before.id][:line]) + new_entry = add_inst!(compact.new_new_nodes, before_entry.pos, attach_after) + new_entry = inst_from_newinst!(new_entry, newinst, newline, newflag) + push!(compact.new_new_used_ssas, 0) + return NewSSAValue(-new_entry.idx) else error("Unsupported") end end -function insert_node_here!(compact::IncrementalCompact, inst::NewInstruction, reverse_affinity::Bool=false) - @assert inst.line !== nothing +function insert_node_here!(compact::IncrementalCompact, newinst::NewInstruction, reverse_affinity::Bool=false) + newline = newinst.line::Int32 refinish = false result_idx = compact.result_idx + result_bbs = compact.cfg_transform.result_bbs if reverse_affinity && - ((compact.active_result_bb == length(compact.result_bbs) + 1) || - result_idx == first(compact.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)) compact.active_result_bb -= 1 refinish = true end @@ -764,15 +907,9 @@ function insert_node_here!(compact::IncrementalCompact, inst::NewInstruction, re @assert result_idx == length(compact.result) + 1 resize!(compact, result_idx) end - flag = inst.flag - if !inst.effect_free_computed && stmt_effect_free(inst.stmt, inst.type, compact) - flag |= IR_FLAG_EFFECT_FREE - end - node = compact.result[result_idx] - node[:inst], node[:type], node[:line], node[:flag] = inst.stmt, inst.type, inst.line, flag - if count_added_node!(compact, inst.stmt) - push!(compact.late_fixup, result_idx) - end + newflag = recompute_inst_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 inst = SSAValue(result_idx) refinish && finish_current_bb!(compact, 0) @@ -794,22 +931,54 @@ function getindex(view::TypesView, v::OldSSAValue) return view.ir.pending_nodes.stmts[id][:type] end +function kill_current_use!(compact::IncrementalCompact, @nospecialize(val)) + if isa(val, SSAValue) + @assert compact.used_ssas[val.id] >= 1 + compact.used_ssas[val.id] -= 1 + elseif isa(val, NewSSAValue) + @assert val.id < 0 + @assert compact.new_new_used_ssas[-val.id] >= 1 + compact.new_new_used_ssas[-val.id] -= 1 + end +end + +function kill_current_uses!(compact::IncrementalCompact, @nospecialize(stmt)) + for ops in userefs(stmt) + kill_current_use!(compact, ops[]) + end +end + function setindex!(compact::IncrementalCompact, @nospecialize(v), idx::SSAValue) @assert idx.id < compact.result_idx - (compact.result[idx.id][:inst] === v) && return + (compact.result[idx.id][:inst] === v) && return compact # Kill count for current uses - for ops in userefs(compact.result[idx.id][:inst]) - val = ops[] - if isa(val, SSAValue) - @assert compact.used_ssas[val.id] >= 1 - compact.used_ssas[val.id] -= 1 - end - end + kill_current_uses!(compact, compact.result[idx.id][:inst]) compact.result[idx.id][:inst] = v # Add count for new use - if count_added_node!(compact, v) - push!(compact.late_fixup, idx.id) + count_added_node!(compact, v) && push!(compact.late_fixup, idx.id) + return compact +end + +function setindex!(compact::IncrementalCompact, @nospecialize(v), idx::OldSSAValue) + id = idx.id + if id < compact.idx + new_idx = compact.ssa_rename[id]::Int + (compact.result[new_idx][:inst] === v) && return compact + kill_current_uses!(compact, compact.result[new_idx][:inst]) + compact.result[new_idx][:inst] = v + count_added_node!(compact, v) && push!(compact.late_fixup, new_idx) + return compact + elseif id <= length(compact.ir.stmts) # ir.stmts, new_nodes, and pending_nodes uses aren't counted yet, so no need to adjust + compact.ir.stmts[id][:inst] = v + return compact end + id -= length(compact.ir.stmts) + if id <= length(compact.ir.new_nodes) + compact.ir.new_nodes.stmts[id][:inst] = v + return compact + end + id -= length(compact.ir.new_nodes) + compact.pending_nodes.stmts[id][:inst] = v return compact end @@ -822,6 +991,69 @@ function setindex!(compact::IncrementalCompact, @nospecialize(v), idx::Int) return compact end +__set_check_ssa_counts(onoff::Bool) = __check_ssa_counts__[] = onoff +const __check_ssa_counts__ = fill(false) + +should_check_ssa_counts() = __check_ssa_counts__[] + +# specifically meant to be used with body1 = compact.result and body2 = compact.new_new_nodes, with nvals == length(compact.used_ssas) +function find_ssavalue_uses1(compact) + body1, body2 = compact.result.inst, compact.new_new_nodes.stmts.inst + nvals = length(compact.used_ssas) + nvalsnew = length(compact.new_new_used_ssas) + nbody1 = compact.result_idx + nbody2 = length(body2) + + uses = zeros(Int, nvals) + usesnew = zeros(Int, nvalsnew) + function increment_uses(ssa::AnySSAValue) + if isa(ssa, NewSSAValue) + usesnew[-ssa.id] += 1 + elseif isa(ssa, SSAValue) + uses[ssa.id] += 1 + end + end + + for line in 1:(nbody1 + nbody2) + # index into the right body + if line <= nbody1 + isassigned(body1, line) || continue + e = body1[line] + else + line -= nbody1 + isassigned(body2, line) || continue + e = body2[line] + end + + foreach_anyssa(increment_uses, e) + end + + return (uses, usesnew) +end + +function _oracle_check(compact::IncrementalCompact) + (observed_used_ssas, observed_used_newssas) = Core.Compiler.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)) + end + end + for i = 1:length(observed_used_newssas) + if observed_used_newssas[i] != compact.new_new_used_ssas[i] + return (observed_used_ssas, observed_used_newssas, NewSSAValue(i)) + end + end + return (nothing, nothing, 0) +end + +function oracle_check(compact::IncrementalCompact) + (maybe_oracle_used_ssas, observed_used_newssas, oracle_error_ssa) = _oracle_check(compact) + if maybe_oracle_used_ssas !== nothing + @eval Main (compact = $compact; oracle_used_ssas = $maybe_oracle_used_ssas; observed_used_newssas = $observed_used_newssas; oracle_error_ssa = $(QuoteNode(oracle_error_ssa))) + error("Oracle check failed, inspect Main.{compact, oracle_used_ssas, observed_used_newssas, oracle_error_ssa}") + end +end + getindex(view::TypesView, idx::SSAValue) = getindex(view, idx.id) function getindex(view::TypesView, idx::Int) if isa(view.ir, IncrementalCompact) && idx < view.ir.result_idx @@ -843,16 +1075,13 @@ function getindex(view::TypesView, idx::Int) end function getindex(view::TypesView, idx::NewSSAValue) - if isa(view.ir, IncrementalCompact) - return view.ir.new_new_nodes.stmts[idx.id][:type] - else - return view.ir.new_nodes.stmts[idx.id][:type] - end + return view.ir[idx][:type] end function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int}, processed_idx::Int, result_idx::Int, ssa_rename::Vector{Any}, used_ssas::Vector{Int}, + new_new_used_ssas::Vector{Int}, do_rename_ssa::Bool) values = Vector{Any}(undef, length(old_values)) for i = 1:length(old_values) @@ -864,7 +1093,7 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} push!(late_fixup, result_idx) val = OldSSAValue(val.id) else - val = renumber_ssa2(val, ssa_rename, used_ssas, do_rename_ssa) + val = renumber_ssa2(val, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa) end else used_ssas[val.id] += 1 @@ -874,17 +1103,24 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} push!(late_fixup, result_idx) else # Always renumber these. do_rename_ssa applies only to actual SSAValues - val = renumber_ssa2(SSAValue(val.id), ssa_rename, used_ssas, true) + val = renumber_ssa2(SSAValue(val.id), ssa_rename, used_ssas, new_new_used_ssas, true) end elseif isa(val, NewSSAValue) - push!(late_fixup, result_idx) + 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 values[i] = val end return values end -function renumber_ssa2(val::SSAValue, ssanums::Vector{Any}, used_ssas::Vector{Int}, do_rename_ssa::Bool) +function renumber_ssa2(val::SSAValue, ssanums::Vector{Any}, used_ssas::Vector{Int}, + new_new_used_ssas::Vector{Int}, do_rename_ssa::Bool) id = val.id if id > length(ssanums) return val @@ -893,22 +1129,31 @@ function renumber_ssa2(val::SSAValue, ssanums::Vector{Any}, used_ssas::Vector{In val = ssanums[id] end if isa(val, SSAValue) - if used_ssas !== nothing - used_ssas[val.id] += 1 - end + used_ssas[val.id] += 1 end return val end -function renumber_ssa2!(@nospecialize(stmt), ssanums::Vector{Any}, used_ssas::Vector{Int}, late_fixup::Vector{Int}, result_idx::Int, do_rename_ssa::Bool) +function renumber_ssa2(val::NewSSAValue, ssanums::Vector{Any}, used_ssas::Vector{Int}, + new_new_used_ssas::Vector{Int}, do_rename_ssa::Bool) + if val.id < 0 + new_new_used_ssas[-val.id] += 1 + return val + else + used_ssas[val.id] += 1 + return SSAValue(val.id) + end +end + +function renumber_ssa2!(@nospecialize(stmt), ssanums::Vector{Any}, used_ssas::Vector{Int}, new_new_used_ssas::Vector{Int}, late_fixup::Vector{Int}, result_idx::Int, do_rename_ssa::Bool) urs = userefs(stmt) for op in urs val = op[] if isa(val, OldSSAValue) || isa(val, NewSSAValue) push!(late_fixup, result_idx) end - if isa(val, SSAValue) - val = renumber_ssa2(val, ssanums, used_ssas, do_rename_ssa) + if isa(val, Union{SSAValue, NewSSAValue}) + val = renumber_ssa2(val, ssanums, used_ssas, new_new_used_ssas, do_rename_ssa) end if isa(val, OldSSAValue) || isa(val, NewSSAValue) push!(late_fixup, result_idx) @@ -919,15 +1164,22 @@ function renumber_ssa2!(@nospecialize(stmt), ssanums::Vector{Any}, used_ssas::Ve end # Used in inlining before we start compacting - Only works at the CFG level -function kill_edge!(bbs::Vector{BasicBlock}, from::Int, to::Int) +function kill_edge!(bbs::Vector{BasicBlock}, from::Int, to::Int, callback=nothing) preds, succs = bbs[to].preds, bbs[from].succs deleteat!(preds, findfirst(x->x === from, preds)::Int) deleteat!(succs, findfirst(x->x === to, succs)::Int) if length(preds) == 0 for succ in copy(bbs[to].succs) - kill_edge!(bbs, to, succ) + kill_edge!(bbs, to, succ, callback) end end + if callback !== nothing + callback(from, to) + end +end + +function kill_edge!(ir::IRCode, from::Int, to::Int, callback=nothing) + kill_edge!(ir.cfg.blocks, from, to, callback) end # N.B.: from and to are non-renamed indices @@ -935,35 +1187,42 @@ function kill_edge!(compact::IncrementalCompact, active_bb::Int, from::Int, to:: # 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). - preds = compact.result_bbs[compact.bb_rename_succ[to]].preds - succs = compact.result_bbs[compact.bb_rename_pred[from]].succs - deleteat!(preds, findfirst(x->x === compact.bb_rename_pred[from], preds)::Int) - deleteat!(succs, findfirst(x->x === compact.bb_rename_succ[to], succs)::Int) + (; bb_rename_pred, bb_rename_succ, result_bbs) = compact.cfg_transform + preds = result_bbs[bb_rename_succ[to]].preds + succs = result_bbs[bb_rename_pred[from]].succs + deleteat!(preds, findfirst(x->x === bb_rename_pred[from], preds)::Int) + deleteat!(succs, findfirst(x->x === bb_rename_succ[to], succs)::Int) # Check if the block is now dead if length(preds) == 0 - for succ in copy(compact.result_bbs[compact.bb_rename_succ[to]].succs) - kill_edge!(compact, active_bb, to, findfirst(x->x === succ, compact.bb_rename_pred)::Int) + for succ in copy(result_bbs[bb_rename_succ[to]].succs) + kill_edge!(compact, active_bb, to, findfirst(x->x === succ, bb_rename_pred)::Int) end if to < active_bb # Kill all statements in the block - stmts = compact.result_bbs[compact.bb_rename_succ[to]].stmts + stmts = result_bbs[bb_rename_succ[to]].stmts for stmt in stmts compact.result[stmt][:inst] = nothing end compact.result[last(stmts)][:inst] = ReturnNode() + else + # Tell compaction to not schedule this block. A value of -2 here + # indicates that the block is not to be scheduled, but there should + # still be an (unreachable) BB inserted into the final IR to avoid + # disturbing the BB numbering. + bb_rename_succ[to] = -2 end else # Remove this edge from all phi nodes in `to` block # NOTE: It is possible for `to` to contain only `nothing` statements, # so we must be careful to stop at its last statement if to < active_bb - stmts = compact.result_bbs[compact.bb_rename_succ[to]].stmts + stmts = result_bbs[bb_rename_succ[to]].stmts idx = first(stmts) while idx <= last(stmts) stmt = compact.result[idx][:inst] stmt === nothing && continue isa(stmt, PhiNode) || break - i = findfirst(x-> x == compact.bb_rename_pred[from], stmt.edges) + i = findfirst(x-> x == bb_rename_pred[from], stmt.edges) if i !== nothing deleteat!(stmt.edges, i) deleteat!(stmt.values, i) @@ -988,29 +1247,39 @@ end function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instruction, idx::Int, processed_idx::Int, active_bb::Int, do_rename_ssa::Bool) stmt = inst[:inst] - result = compact.result - ssa_rename = compact.ssa_rename - late_fixup = compact.late_fixup - used_ssas = compact.used_ssas + (; 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 ssa_rename[idx] = SSAValue(result_idx) if stmt === nothing ssa_rename[idx] = stmt elseif isa(stmt, OldSSAValue) ssa_rename[idx] = ssa_rename[stmt.id] - elseif isa(stmt, GotoNode) && compact.cfg_transforms_enabled - result[result_idx][:inst] = GotoNode(compact.bb_rename_succ[stmt.label]) + elseif isa(stmt, GotoNode) && cfg_transforms_enabled + label = bb_rename_succ[stmt.label] + @assert label > 0 + result[result_idx][:inst] = GotoNode(label) result_idx += 1 - elseif isa(stmt, GlobalRef) || isa(stmt, GotoNode) + elseif isa(stmt, GlobalRef) + total_flags = IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE + flag = result[result_idx][:flag] + if (flag & total_flags) == total_flags + ssa_rename[idx] = stmt + else + result[result_idx][:inst] = stmt + result_idx += 1 + end + elseif isa(stmt, GotoNode) result[result_idx][:inst] = stmt result_idx += 1 - elseif isa(stmt, GotoIfNot) && compact.cfg_transforms_enabled - stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, late_fixup, result_idx, do_rename_ssa)::GotoIfNot + elseif isa(stmt, GotoIfNot) && cfg_transforms_enabled + stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa)::GotoIfNot result[result_idx][:inst] = stmt cond = stmt.cond - if compact.fold_constant_branches + if fold_constant_branches if !isa(cond, Bool) condT = widenconditional(argextype(cond, compact)) isa(condT, Const) || @goto bail + kill_current_use!(compact, cond) cond = condT.val isa(cond, Bool) || @goto bail end @@ -1019,19 +1288,32 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr kill_edge!(compact, active_bb, active_bb, stmt.dest) # Don't increment result_idx => Drop this statement else - result[result_idx][:inst] = GotoNode(compact.bb_rename_succ[stmt.dest]) + label = bb_rename_succ[stmt.dest] + @assert label > 0 + result[result_idx][:inst] = GotoNode(label) kill_edge!(compact, active_bb, active_bb, active_bb+1) result_idx += 1 end else @label bail - result[result_idx][:inst] = GotoIfNot(cond, compact.bb_rename_succ[stmt.dest]) + label = bb_rename_succ[stmt.dest] + @assert label > 0 + result[result_idx][:inst] = GotoIfNot(cond, label) result_idx += 1 end elseif isa(stmt, Expr) - stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, late_fixup, result_idx, do_rename_ssa)::Expr - if compact.cfg_transforms_enabled && isexpr(stmt, :enter) - stmt.args[1] = compact.bb_rename_succ[stmt.args[1]::Int] + stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa)::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) + cond = stmt.args[2] + if isa(cond, Bool) && cond === true + # cond was folded to true - this statement + # is dead. + return result_idx + end end result[result_idx][:inst] = stmt result_idx += 1 @@ -1039,10 +1321,11 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr # As an optimization, we eliminate any trivial pinodes. For performance, we use === # type equality. We may want to consider using == in either a separate pass or if # performance turns out ok - stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, late_fixup, result_idx, do_rename_ssa)::PiNode + stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa)::PiNode pi_val = stmt.val if isa(pi_val, SSAValue) - if stmt.typ === compact.result[pi_val.id][:type] + if stmt.typ === result[pi_val.id][:type] + used_ssas[pi_val.id] -= 1 ssa_rename[idx] = pi_val return result_idx end @@ -1061,12 +1344,14 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr result[result_idx][:inst] = stmt result_idx += 1 elseif isa(stmt, ReturnNode) || isa(stmt, UpsilonNode) || isa(stmt, GotoIfNot) - result[result_idx][:inst] = renumber_ssa2!(stmt, ssa_rename, used_ssas, late_fixup, result_idx, do_rename_ssa) + result[result_idx][:inst] = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa) result_idx += 1 elseif isa(stmt, PhiNode) - if compact.cfg_transforms_enabled + if cfg_transforms_enabled # Rename phi node edges - map!(i -> compact.bb_rename_pred[i], stmt.edges, stmt.edges) + let bb_rename_pred=bb_rename_pred + map!(i::Int32 -> bb_rename_pred[i], stmt.edges, stmt.edges) + end # Remove edges and values associated with dead blocks. Entries in # `values` can be undefined when the phi node refers to something @@ -1083,7 +1368,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr values = Vector{Any}(undef, length(stmt.values)) new_index = 1 for old_index in 1:length(stmt.edges) - if stmt.edges[old_index] != -1 + if stmt.edges[old_index] > 0 edges[new_index] = stmt.edges[old_index] if isassigned(stmt.values, old_index) values[new_index] = stmt.values[old_index] @@ -1098,7 +1383,7 @@ 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, do_rename_ssa) + values = process_phinode_values(values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa) # 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 @@ -1107,17 +1392,22 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr # 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(compact.cfg_transforms_enabled ? - compact.result_bbs[compact.bb_rename_succ[active_bb]].preds : + length(cfg_transforms_enabled ? + result_bbs[bb_rename_succ[active_bb]].preds : compact.ir.cfg.blocks[active_bb].preds) == 1 # There's only one predecessor left - just replace it - ssa_rename[idx] = values[1] + v = values[1] + @assert !isa(v, NewSSAValue) + if isa(v, SSAValue) + used_ssas[v.id] -= 1 + end + ssa_rename[idx] = v else result[result_idx][:inst] = PhiNode(edges, values) result_idx += 1 end elseif isa(stmt, PhiCNode) - result[result_idx][:inst] = PhiCNode(process_phinode_values(stmt.values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, do_rename_ssa)) + result[result_idx][:inst] = PhiCNode(process_phinode_values(stmt.values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa)) result_idx += 1 elseif isa(stmt, SSAValue) # identity assign, replace uses of this ssa value with its result @@ -1125,6 +1415,8 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr stmt = ssa_rename[stmt.id] end ssa_rename[idx] = stmt + elseif isa(stmt, NewSSAValue) + ssa_rename[idx] = SSAValue(stmt.id) else # Constant assign, replace uses of this ssa value with its result ssa_rename[idx] = stmt @@ -1143,15 +1435,16 @@ function resize!(compact::IncrementalCompact, nnewnodes) end function finish_current_bb!(compact::IncrementalCompact, active_bb, old_result_idx=compact.result_idx, unreachable=false) - if compact.active_result_bb > length(compact.result_bbs) + (;result_bbs, cfg_transforms_enabled, bb_rename_succ) = compact.cfg_transform + if compact.active_result_bb > length(result_bbs) #@assert compact.bb_rename[active_bb] == -1 return true end - bb = compact.result_bbs[compact.active_result_bb] + bb = result_bbs[compact.active_result_bb] # If this was the last statement in the BB and we decided to skip it, insert a # dummy `nothing` node, to prevent changing the structure of the CFG skipped = false - if !compact.cfg_transforms_enabled || active_bb == 0 || active_bb > length(compact.bb_rename_succ) || compact.bb_rename_succ[active_bb] != -1 + if !cfg_transforms_enabled || active_bb == 0 || active_bb > length(bb_rename_succ) || bb_rename_succ[active_bb] != -1 if compact.result_idx == first(bb.stmts) length(compact.result) < old_result_idx && resize!(compact, old_result_idx) node = compact.result[old_result_idx] @@ -1161,17 +1454,17 @@ function finish_current_bb!(compact::IncrementalCompact, active_bb, old_result_i node[:inst], node[:type], node[:line] = nothing, Nothing, 0 end compact.result_idx = old_result_idx + 1 - elseif compact.cfg_transforms_enabled && compact.result_idx - 1 == first(bb.stmts) + elseif cfg_transforms_enabled && compact.result_idx - 1 == first(bb.stmts) # Optimization: If this BB consists of only a branch, eliminate this bb end - compact.result_bbs[compact.active_result_bb] = BasicBlock(bb, StmtRange(first(bb.stmts), compact.result_idx-1)) + result_bbs[compact.active_result_bb] = BasicBlock(bb, StmtRange(first(bb.stmts), compact.result_idx-1)) compact.active_result_bb += 1 else skipped = true end - if compact.active_result_bb <= length(compact.result_bbs) - new_bb = compact.result_bbs[compact.active_result_bb] - compact.result_bbs[compact.active_result_bb] = BasicBlock(new_bb, + if compact.active_result_bb <= length(result_bbs) + new_bb = result_bbs[compact.active_result_bb] + result_bbs[compact.active_result_bb] = BasicBlock(new_bb, StmtRange(compact.result_idx, last(new_bb.stmts))) end return skipped @@ -1196,10 +1489,7 @@ function process_newnode!(compact::IncrementalCompact, new_idx::Int, new_node_en active_bb += 1 finish_current_bb!(compact, active_bb, old_result_idx) end - (old_result_idx == result_idx) && return iterate(compact, (idx, active_bb)) - return Pair{Pair{Int, Int}, Any}( - Pair{Int,Int}(new_idx,old_result_idx), - compact.result[old_result_idx][:inst]), (idx, active_bb) + return (old_result_idx, result_idx, active_bb) end struct CompactPeekIterator @@ -1242,10 +1532,22 @@ function iterate(it::CompactPeekIterator, (idx, aidx, bidx)::NTuple{3, Int}=(it. return (compact.ir.stmts[idx][:inst], (idx + 1, aidx, bidx)) end -function iterate(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}=(compact.idx, 1)) +# the returned Union{Nothing, Pair{Pair{Int,Int},Any}} cannot be stack allocated, +# so we inline this function into the caller +@inline function iterate(compact::IncrementalCompact, state=nothing) + idxs = iterate_compact(compact) + idxs === nothing && return nothing + old_result_idx = idxs[2] + return Pair{Pair{Int,Int},Any}(idxs, compact.result[old_result_idx][:inst]), nothing +end + +function iterate_compact(compact::IncrementalCompact) # Create label to dodge recursion so that we don't stack overflow @label restart + idx = compact.idx + active_bb = compact.active_bb + old_result_idx = compact.result_idx if idx > length(compact.ir.stmts) && (compact.new_nodes_idx > length(compact.perm)) return nothing @@ -1254,7 +1556,8 @@ function iterate(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}= resize!(compact, old_result_idx) end bb = compact.ir.cfg.blocks[active_bb] - if compact.cfg_transforms_enabled && active_bb > 1 && active_bb <= length(compact.bb_rename_succ) && compact.bb_rename_succ[active_bb] == -1 + (; cfg_transforms_enabled, bb_rename_succ) = compact.cfg_transform + if cfg_transforms_enabled && active_bb > 1 && active_bb <= length(bb_rename_succ) && bb_rename_succ[active_bb] <= -1 # Dead block, so kill the entire block. compact.idx = last(bb.stmts) # Pop any remaining insertion nodes @@ -1270,14 +1573,15 @@ function iterate(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}= if !(info.attach_after ? info.pos <= compact.idx - 1 : info.pos <= compact.idx) break end - popfirst!(compact.pending_perm) + heappop!(compact.pending_perm, By(x -> compact.pending_nodes.info[x].pos)) end # Move to next block compact.idx += 1 + compact.active_bb += 1 if finish_current_bb!(compact, active_bb, old_result_idx, true) - return iterate(compact, (compact.idx, active_bb + 1)) + return iterate_compact(compact) else - return Pair{Pair{Int, Int}, Any}(Pair{Int,Int}(compact.idx-1, old_result_idx), compact.result[old_result_idx][:inst]), (compact.idx, active_bb + 1) + return Pair{Int,Int}(compact.idx-1, old_result_idx) end end if compact.new_nodes_idx <= length(compact.perm) && @@ -1288,141 +1592,164 @@ function iterate(compact::IncrementalCompact, (idx, active_bb)::Tuple{Int, Int}= new_node_entry = compact.ir.new_nodes.stmts[new_idx] new_node_info = compact.ir.new_nodes.info[new_idx] new_idx += length(compact.ir.stmts) - return process_newnode!(compact, new_idx, new_node_entry, new_node_info, idx, active_bb, true) + (old_result_idx, result_idx, active_bb) = + process_newnode!(compact, new_idx, new_node_entry, new_node_info, idx, active_bb, true) + compact.active_bb = active_bb + old_result_idx == result_idx && @goto restart + return Pair{Int,Int}(new_idx, old_result_idx) elseif !isempty(compact.pending_perm) && (info = compact.pending_nodes.info[compact.pending_perm[1]]; info.attach_after ? info.pos == idx - 1 : info.pos == idx) - new_idx = popfirst!(compact.pending_perm) + new_idx = heappop!(compact.pending_perm, By(x -> compact.pending_nodes.info[x].pos)) new_node_entry = compact.pending_nodes.stmts[new_idx] new_node_info = compact.pending_nodes.info[new_idx] new_idx += length(compact.ir.stmts) + length(compact.ir.new_nodes) - return process_newnode!(compact, new_idx, new_node_entry, new_node_info, idx, active_bb, false) + (old_result_idx, result_idx, active_bb) = + process_newnode!(compact, new_idx, new_node_entry, new_node_info, idx, active_bb, false) + compact.active_bb = active_bb + old_result_idx == result_idx && @goto restart + return Pair{Int,Int}(new_idx, old_result_idx) end # This will get overwritten in future iterations if # result_idx is not, incremented, but that's ok and expected compact.result[old_result_idx] = compact.ir.stmts[idx] result_idx = process_node!(compact, old_result_idx, compact.ir.stmts[idx], idx, idx, active_bb, true) - stmt_if_any = old_result_idx == result_idx ? nothing : compact.result[old_result_idx][:inst] compact.result_idx = result_idx if idx == last(bb.stmts) && !attach_after_stmt_after(compact, idx) finish_current_bb!(compact, active_bb, old_result_idx) active_bb += 1 end compact.idx = idx + 1 + compact.active_bb = active_bb if old_result_idx == compact.result_idx idx += 1 @goto restart end @assert isassigned(compact.result.inst, old_result_idx) - return Pair{Pair{Int,Int}, Any}(Pair{Int,Int}(compact.idx-1, old_result_idx), - compact.result[old_result_idx][:inst]), (compact.idx, active_bb) + return Pair{Int,Int}(compact.idx-1, old_result_idx) end -function maybe_erase_unused!( - extra_worklist::Vector{Int}, compact::IncrementalCompact, idx::Int, - callback = null_dce_callback) - stmt = compact.result[idx][:inst] +maybe_erase_unused!(compact::IncrementalCompact, idx::Int, in_worklist::Bool, extra_worklist::Vector{Int}) = + maybe_erase_unused!(null_dce_callback, compact, idx, in_worklist, extra_worklist) +function maybe_erase_unused!(callback::Function, compact::IncrementalCompact, idx::Int, + in_worklist::Bool, extra_worklist::Vector{Int}) + nresult = length(compact.result) + inst = idx ≤ nresult ? compact.result[idx] : compact.new_new_nodes.stmts[idx-nresult] + stmt = inst[:inst] stmt === nothing && return false - if argextype(SSAValue(idx), compact) === Bottom - effect_free = false - else - effect_free = compact.result[idx][:flag] & IR_FLAG_EFFECT_FREE != 0 - end - if effect_free - for ops in userefs(stmt) - val = ops[] - # If the pass we ran inserted new nodes, it's possible for those - # to be outside our used_ssas count. - if isa(val, SSAValue) && val.id <= length(compact.used_ssas) - if compact.used_ssas[val.id] == 1 - if val.id < idx - push!(extra_worklist, val.id) - end - end - compact.used_ssas[val.id] -= 1 - callback(val) + inst[:type] === Bottom && return false + effect_free = (inst[:flag] & IR_FLAG_EFFECT_FREE) ≠ 0 + effect_free || return false + foreachssa(stmt) do val::SSAValue + if compact.used_ssas[val.id] == 1 + if val.id < idx || in_worklist + push!(extra_worklist, val.id) end end - compact.result[idx][:inst] = nothing - return true + compact.used_ssas[val.id] -= 1 + callback(val) end - return false + inst[:inst] = nothing + return true +end + +struct FixedNode + node::Any + needs_fixup::Bool + FixedNode(@nospecialize(node), needs_fixup::Bool) = new(node, needs_fixup) end -function fixup_phinode_values!(compact::IncrementalCompact, old_values::Vector{Any}) +function fixup_phinode_values!(compact::IncrementalCompact, old_values::Vector{Any}, reify_new_nodes::Bool) values = Vector{Any}(undef, length(old_values)) + fixup = false for i = 1:length(old_values) isassigned(old_values, i) || continue - val = old_values[i] - if isa(val, OldSSAValue) - val = compact.ssa_rename[val.id] - if isa(val, SSAValue) - compact.used_ssas[val.id] += 1 - end - elseif isa(val, NewSSAValue) - val = SSAValue(length(compact.result) + val.id) - end - values[i] = val + (; node, needs_fixup) = fixup_node(compact, old_values[i], reify_new_nodes) + fixup |= needs_fixup + values[i] = node end - values + return (values, fixup) end -function fixup_node(compact::IncrementalCompact, @nospecialize(stmt)) +function fixup_node(compact::IncrementalCompact, @nospecialize(stmt), reify_new_nodes::Bool) if isa(stmt, PhiNode) - return PhiNode(stmt.edges, fixup_phinode_values!(compact, stmt.values)) + (node, needs_fixup) = fixup_phinode_values!(compact, stmt.values, reify_new_nodes) + return FixedNode(PhiNode(stmt.edges, node), needs_fixup) elseif isa(stmt, PhiCNode) - return PhiCNode(fixup_phinode_values!(compact, stmt.values)) + (node, needs_fixup) = fixup_phinode_values!(compact, stmt.values, reify_new_nodes) + return FixedNode(PhiCNode(node), needs_fixup) elseif isa(stmt, NewSSAValue) - return SSAValue(length(compact.result) + stmt.id) + @assert stmt.id < 0 + if reify_new_nodes + val = SSAValue(length(compact.result) - stmt.id) + return FixedNode(val, false) + else + return FixedNode(stmt, true) + end elseif isa(stmt, OldSSAValue) - return compact.ssa_rename[stmt.id] + val = compact.ssa_rename[stmt.id] + if isa(val, SSAValue) + compact.used_ssas[val.id] += 1 + end + return FixedNode(val, false) else urs = userefs(stmt) + fixup = false for ur in urs val = ur[] - if isa(val, NewSSAValue) - val = SSAValue(length(compact.result) + val.id) - elseif isa(val, OldSSAValue) - val = compact.ssa_rename[val.id] - end - if isa(val, SSAValue) && val.id <= length(compact.used_ssas) - # If `val.id` is greater than the length of `compact.result` or - # `compact.used_ssas`, this SSA value is in `new_new_nodes`, so - # don't count the use - compact.used_ssas[val.id] += 1 + if isa(val, Union{NewSSAValue, OldSSAValue}) + (;node, needs_fixup) = fixup_node(compact, val, reify_new_nodes) + fixup |= needs_fixup + ur[] = node end - ur[] = val end - return urs[] + return FixedNode(urs[], fixup) end end -function just_fixup!(compact::IncrementalCompact) - for idx in compact.late_fixup +function just_fixup!(compact::IncrementalCompact, new_new_nodes_offset::Union{Int, Nothing} = nothing, late_fixup_offset::Union{Int, Nothing} = nothing) + if new_new_nodes_offset === late_fixup_offset === nothing # only do this appending in non_dce_finish! + resize!(compact.used_ssas, length(compact.result)) + append!(compact.used_ssas, compact.new_new_used_ssas) + empty!(compact.new_new_used_ssas) + end + off = late_fixup_offset === nothing ? 1 : (late_fixup_offset+1) + set_off = off + for i in off:length(compact.late_fixup) + idx = compact.late_fixup[i] stmt = compact.result[idx][:inst] - new_stmt = fixup_node(compact, stmt) - (stmt === new_stmt) || (compact.result[idx][:inst] = new_stmt) - end - for idx in 1:length(compact.new_new_nodes) - node = compact.new_new_nodes.stmts[idx] - stmt = node[:inst] - new_stmt = fixup_node(compact, stmt) - if new_stmt !== stmt - node[:inst] = new_stmt + (;node, needs_fixup) = fixup_node(compact, stmt, late_fixup_offset === nothing) + (stmt === node) || (compact.result[idx][:inst] = node) + if needs_fixup + compact.late_fixup[set_off] = idx + set_off += 1 + end + end + if late_fixup_offset !== nothing + resize!(compact.late_fixup, set_off-1) + end + off = new_new_nodes_offset === nothing ? 1 : (new_new_nodes_offset+1) + for idx in off:length(compact.new_new_nodes) + new_node = compact.new_new_nodes.stmts[idx] + stmt = new_node[:inst] + (;node) = fixup_node(compact, stmt, late_fixup_offset === nothing) + if node !== stmt + new_node[:inst] = node end end end -function simple_dce!(compact::IncrementalCompact, callback = null_dce_callback) +simple_dce!(compact::IncrementalCompact) = simple_dce!(null_dce_callback, compact) +function simple_dce!(callback::Function, compact::IncrementalCompact) # Perform simple DCE for unused values + @assert isempty(compact.new_new_used_ssas) # just_fixup! wasn't run? extra_worklist = Int[] for (idx, nused) in Iterators.enumerate(compact.used_ssas) - idx >= compact.result_idx && break nused == 0 || continue - maybe_erase_unused!(extra_worklist, compact, idx, callback) + maybe_erase_unused!(callback, compact, idx, false, extra_worklist) end while !isempty(extra_worklist) - maybe_erase_unused!(extra_worklist, compact, pop!(extra_worklist), callback) + maybe_erase_unused!(callback, compact, pop!(extra_worklist), true, extra_worklist) end end @@ -1432,8 +1759,8 @@ function non_dce_finish!(compact::IncrementalCompact) result_idx = compact.result_idx resize!(compact.result, result_idx - 1) just_fixup!(compact) - bb = compact.result_bbs[end] - compact.result_bbs[end] = BasicBlock(bb, + bb = compact.cfg_transform.result_bbs[end] + compact.cfg_transform.result_bbs[end] = BasicBlock(bb, StmtRange(first(bb.stmts), result_idx-1)) compact.renamed_new_nodes = true nothing @@ -1446,8 +1773,24 @@ function finish(compact::IncrementalCompact) end function complete(compact::IncrementalCompact) - result_bbs = resize!(compact.result_bbs, compact.active_result_bb-1) + result_bbs = resize!(compact.cfg_transform.result_bbs, compact.active_result_bb-1) cfg = CFG(result_bbs, Int[first(result_bbs[i].stmts) for i in 2:length(result_bbs)]) + if should_check_ssa_counts() + oracle_check(compact) + end + + # trim trailing undefined statements due to copy propagation + nundef = 0 + for i in length(compact.result):-1:1 + if isassigned(compact.result.inst, i) + break + end + nundef += 1 + end + if nundef > 0 + resize!(compact.result, length(compact.result) - nundef) + end + return IRCode(compact.ir, compact.result, cfg, compact.new_new_nodes) end @@ -1473,3 +1816,18 @@ function iterate(x::BBIdxIter, (idx, bb)::Tuple{Int, Int}=(1, 1)) end return (bb, idx), (idx + 1, next_bb) end + +# Inserters + +abstract type Inserter; end + +struct InsertHere <: Inserter + compact::IncrementalCompact +end +(i::InsertHere)(newinst::NewInstruction) = insert_node_here!(i.compact, newinst) + +struct InsertBefore{T<:Union{IRCode, IncrementalCompact}} <: Inserter + src::T + pos::SSAValue +end +(i::InsertBefore)(newinst::NewInstruction) = insert_node!(i.src, i.pos, newinst) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl new file mode 100644 index 0000000000000..8d75ad3948ee2 --- /dev/null +++ b/base/compiler/ssair/irinterp.jl @@ -0,0 +1,388 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +function collect_limitations!(@nospecialize(typ), ::IRInterpretationState) + @assert !isa(typ, LimitedAccuracy) "irinterp is unable to handle heavy recursion" + 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,Bool}(nothing, false) + argtypes = collect_argtypes(interp, inst.args[2:end], nothing, irsv) + argtypes === nothing && return Pair{Any,Bool}(Bottom, false) + effects = decode_effects(code.ipo_purity_bits) + if is_foldable(effects) && is_all_const_arg(argtypes, #=start=#1) + 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,Bool}(Bottom, false) + end + end + return Pair{Any,Bool}(Const(value), true) + else + if is_constprop_edge_recursed(mi, irsv) + return Pair{Any,Bool}(nothing, is_nothrow(effects)) + end + newirsv = IRInterpretationState(interp, code, mi, argtypes, world) + if newirsv !== nothing + newirsv.parent = irsv + return ir_abstract_constant_propagation(interp, newirsv) + end + return Pair{Any,Bool}(nothing, is_nothrow(effects)) + end +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) + return abstract_eval_phi(interp, phi, nothing, irsv) +end + +function propagate_control_effects!(interp::AbstractInterpreter, idx::Int, stmt::GotoIfNot, + irsv::IRInterpretationState, extra_reprocess::Union{Nothing,BitSet,BitSetBoundedMinPrioritySet}) + # Nothing to do for most abstract interpreters, but if the abstract + # interpreter has control-dependent lattice effects, it can override + # this method. + return false +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) +end + +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 + ir.stmts[bidx][:inst] = nothing + ir.stmts[bidx][:type] = Bottom + ir.stmts[bidx][:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + end + return + end + for sidx = ir.cfg.blocks[to].stmts + sinst = ir.stmts[sidx][:inst] + isa(sinst, Nothing) && continue # allowed between `PhiNode`s + isa(sinst, PhiNode) || break + for (eidx, edge) in enumerate(sinst.edges) + if edge == from + deleteat!(sinst.edges, eidx) + deleteat!(sinst.values, eidx) + push!(irsv.ssa_refined, sidx) + break + end + end + end +end +update_phi!(irsv::IRInterpretationState) = (from::Int, to::Int)->update_phi!(irsv, from, to) + +function kill_terminator_edges!(irsv::IRInterpretationState, term_idx::Int, bb::Int=block_for_inst(irsv.ir, term_idx)) + ir = irsv.ir + inst = ir[SSAValue(term_idx)][:inst] + if isa(inst, GotoIfNot) + kill_edge!(ir, bb, inst.dest, update_phi!(irsv)) + kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + elseif isa(inst, GotoNode) + kill_edge!(ir, bb, inst.label, update_phi!(irsv)) + elseif isa(inst, ReturnNode) + # Nothing to do + else + @assert !isexpr(inst, :enter) + kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + end +end + +function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union{Int,Nothing}, + @nospecialize(inst), @nospecialize(typ), irsv::IRInterpretationState, + extra_reprocess::Union{Nothing,BitSet,BitSetBoundedMinPrioritySet}) + ir = irsv.ir + if isa(inst, GotoIfNot) + cond = inst.cond + condval = maybe_extract_const_bool(argextype(cond, ir)) + if condval isa Bool + if isa(cond, SSAValue) + kill_def_use!(irsv.tpdum, cond, idx) + end + if bb === nothing + bb = block_for_inst(ir, idx) + end + ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW + if condval + ir.stmts[idx][:inst] = nothing + ir.stmts[idx][:type] = Any + kill_edge!(ir, bb, inst.dest, update_phi!(irsv)) + else + ir.stmts[idx][:inst] = GotoNode(inst.dest) + kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + end + return true + end + return propagate_control_effects!(interp, idx, inst, irsv, extra_reprocess) + end + rt = nothing + if isa(inst, Expr) + head = inst.head + if head === :call || head === :foreigncall || head === :new || head === :splatnew + (; rt, effects) = abstract_eval_statement_expr(interp, inst, nothing, irsv) + ir.stmts[idx][:flag] |= flags_for_effects(effects) + elseif head === :invoke + rt, nothrow = concrete_eval_invoke(interp, inst, inst.args[1]::MethodInstance, irsv) + if nothrow + ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW + end + elseif head === :throw_undef_if_not || # TODO: Terminate interpretation early if known false? + head === :gc_preserve_begin || + head === :gc_preserve_end + return false + else + error("reprocess_instruction!: unhandled expression found") + end + elseif isa(inst, PhiNode) + rt = abstract_eval_phi_stmt(interp, inst, idx, irsv) + elseif isa(inst, ReturnNode) + # Handled at the very end + return false + elseif isa(inst, PiNode) + rt = tmeet(typeinf_lattice(interp), argextype(inst.val, ir), widenconst(inst.typ)) + elseif inst === nothing + return false + elseif isa(inst, GlobalRef) + # GlobalRef is not refinable + else + error("reprocess_instruction!: unhandled instruction found") + end + if rt !== nothing + if isa(rt, Const) + ir.stmts[idx][:type] = rt + if is_inlineable_constant(rt.val) && (ir.stmts[idx][:flag] & IR_FLAG_EFFECT_FREE) != 0 + ir.stmts[idx][:inst] = quoted(rt.val) + end + return true + elseif !⊑(typeinf_lattice(interp), typ, rt) + ir.stmts[idx][:type] = rt + return true + end + end + return false +end + +# Process the terminator and add the successor to `bb_ip`. Returns whether a backedge was seen. +function process_terminator!(ir::IRCode, @nospecialize(inst), idx::Int, bb::Int, + all_rets::Vector{Int}, bb_ip::BitSetBoundedMinPrioritySet) + if isa(inst, ReturnNode) + if isdefined(inst, :val) + push!(all_rets, idx) + end + return false + elseif isa(inst, GotoNode) + backedge = inst.label <= bb + backedge || push!(bb_ip, inst.label) + return backedge + elseif isa(inst, GotoIfNot) + backedge = inst.dest <= bb + backedge || push!(bb_ip, inst.dest) + push!(bb_ip, bb+1) + return backedge + elseif isexpr(inst, :enter) + dest = inst.args[1]::Int + @assert dest > bb + push!(bb_ip, dest) + push!(bb_ip, bb+1) + return false + else + push!(bb_ip, bb+1) + return false + end +end + +default_reprocess(::AbstractInterpreter, ::IRInterpretationState) = nothing +function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState; + extra_reprocess::Union{Nothing,BitSet} = default_reprocess(interp, irsv)) + interp = switch_to_irinterp(interp) + + (; ir, tpdum, ssa_refined) = irsv + + bbs = ir.cfg.blocks + bb_ip = BitSetBoundedMinPrioritySet(length(bbs)) + push!(bb_ip, 1) + all_rets = Int[] + + # Fast path: Scan both use counts and refinement in one single pass of + # of the instructions. In the absence of backedges, this will + # converge. + while !isempty(bb_ip) + bb = popfirst!(bb_ip) + stmts = bbs[bb].stmts + lstmt = last(stmts) + for idx = stmts + irsv.curridx = idx + inst = ir.stmts[idx][:inst] + typ = ir.stmts[idx][:type] + any_refined = false + if extra_reprocess !== nothing + if idx in extra_reprocess + pop!(extra_reprocess, idx) + any_refined = true + end + end + for ur in userefs(inst) + val = ur[] + if isa(val, Argument) + any_refined |= irsv.argtypes_refined[val.n] + elseif isa(val, SSAValue) + any_refined |= val.id in ssa_refined + count!(tpdum, val) + end + end + if isa(inst, PhiNode) && idx in ssa_refined + any_refined = true + delete!(ssa_refined, idx) + end + is_terminator_or_phi = isa(inst, PhiNode) || isa(inst, GotoNode) || isa(inst, GotoIfNot) || isa(inst, ReturnNode) || isexpr(inst, :enter) + if typ === Bottom && (idx != lstmt || !is_terminator_or_phi) + continue + end + if any_refined && reprocess_instruction!(interp, + idx, bb, inst, typ, irsv, extra_reprocess) + push!(ssa_refined, idx) + inst = ir.stmts[idx][:inst] + typ = ir.stmts[idx][:type] + end + if typ === Bottom && !is_terminator_or_phi + kill_terminator_edges!(irsv, lstmt, bb) + if idx != lstmt + for idx2 in (idx+1:lstmt-1) + ir[SSAValue(idx2)] = nothing + end + ir[SSAValue(lstmt)][:inst] = ReturnNode() + end + break + end + if idx == lstmt + process_terminator!(ir, inst, idx, bb, all_rets, bb_ip) && @goto residual_scan + end + end + end + @goto compute_rt + + # Slow path + begin @label residual_scan + stmt_ip = BitSetBoundedMinPrioritySet(length(ir.stmts)) + if extra_reprocess !== nothing + append!(stmt_ip, extra_reprocess) + end + + # Slow Path Phase 1.A: Complete use scanning + while !isempty(bb_ip) + bb = popfirst!(bb_ip) + stmts = bbs[bb].stmts + lstmt = last(stmts) + for idx = stmts + irsv.curridx = idx + inst = ir.stmts[idx][:inst] + for ur in userefs(inst) + val = ur[] + if isa(val, Argument) + if irsv.argtypes_refined[val.n] + push!(stmt_ip, idx) + end + elseif isa(val, SSAValue) + count!(tpdum, val) + end + end + idx == lstmt && process_terminator!(ir, inst, idx, bb, all_rets, bb_ip) + end + end + + # Slow Path Phase 1.B: Assemble def-use map + complete!(tpdum) + push!(bb_ip, 1) + while !isempty(bb_ip) + bb = popfirst!(bb_ip) + stmts = bbs[bb].stmts + lstmt = last(stmts) + for idx = stmts + irsv.curridx = idx + inst = ir.stmts[idx][:inst] + for ur in userefs(inst) + val = ur[] + if isa(val, SSAValue) + push!(tpdum[val.id], idx) + end + end + idx == lstmt && process_terminator!(ir, inst, idx, bb, all_rets, bb_ip) + end + end + + # Slow Path Phase 2: Use def-use map to converge cycles. + # TODO: It would be possible to return to the fast path after converging + # each cycle, but that's somewhat complicated. + for val in ssa_refined + append!(stmt_ip, tpdum[val]) + end + while !isempty(stmt_ip) + idx = popfirst!(stmt_ip) + irsv.curridx = idx + inst = ir.stmts[idx][:inst] + typ = ir.stmts[idx][:type] + if reprocess_instruction!(interp, + idx, nothing, inst, typ, irsv, stmt_ip) + append!(stmt_ip, tpdum[idx]) + end + end + end + + begin @label compute_rt + ultimate_rt = Bottom + for idx in all_rets + bb = block_for_inst(ir.cfg, idx) + if bb != 1 && length(ir.cfg.blocks[bb].preds) == 0 + # Could have discovered this block is dead after the initial scan + continue + end + inst = ir.stmts[idx][:inst]::ReturnNode + rt = argextype(inst.val, ir) + ultimate_rt = tmerge(typeinf_lattice(interp), ultimate_rt, rt) + end + end + + nothrow = true + for idx = 1:length(ir.stmts) + if (ir.stmts[idx][:flag] & IR_FLAG_NOTHROW) == 0 + nothrow = false + break + end + end + + if last(irsv.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(frame_instance(irsv), irsv.edges) + end + + return Pair{Any,Bool}(maybe_singleton_const(ultimate_rt), nothrow) +end + +function ir_abstract_constant_propagation(interp::NativeInterpreter, irsv::IRInterpretationState) + if __measure_typeinf__[] + inf_frame = Timings.InferenceFrameInfo(irsv.mi, irsv.world, VarState[], Any[], length(irsv.ir.argtypes)) + Timings.enter_new_timer(inf_frame) + ret = _ir_abstract_constant_propagation(interp, irsv) + append!(inf_frame.slottypes, irsv.ir.argtypes) + Timings.exit_current_timer(inf_frame) + return ret + else + return _ir_abstract_constant_propagation(interp, irsv) + end +end +ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState) = + _ir_abstract_constant_propagation(interp, irsv) diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 3d91646fa05f7..e2c924d60cb83 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -1,17 +1,20 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -function inflate_ir(ci::CodeInfo, linfo::MethodInstance) +""" + inflate_ir!(ci::CodeInfo, linfo::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) - if ci.inferred - argtypes, _ = matching_cache_argtypes(linfo, nothing) - else - argtypes = Any[ Any for i = 1:length(ci.slotflags) ] - end - return inflate_ir(ci, sptypes, argtypes) + argtypes, _ = matching_cache_argtypes(fallback_lattice, linfo) + return inflate_ir!(ci, sptypes, argtypes) end - -function inflate_ir(ci::CodeInfo, sptypes::Vector{Any}, argtypes::Vector{Any}) - code = copy_exprargs(ci.code) # TODO: this is a huge hot-spot +function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{Any}) + code = ci.code cfg = compute_basic_blocks(code) for i = 1:length(code) stmt = code[i] @@ -22,49 +25,77 @@ function inflate_ir(ci::CodeInfo, sptypes::Vector{Any}, argtypes::Vector{Any}) 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 isa(stmt, Expr) && stmt.head === :enter + elseif isexpr(stmt, :enter) stmt.args[1] = block_for_inst(cfg, stmt.args[1]::Int) code[i] = stmt end end nstmts = length(code) - ssavaluetypes = let ssavaluetypes = ci.ssavaluetypes - ssavaluetypes isa Vector{Any} ? copy(ssavaluetypes) : Any[ Any for i = 1:(ssavaluetypes::Int) ] + ssavaluetypes = ci.ssavaluetypes + if !isa(ssavaluetypes, Vector{Any}) + ssavaluetypes = Any[ Any for i = 1:ssavaluetypes::Int ] end - stmts = InstructionStream(code, ssavaluetypes, Any[nothing for i = 1:nstmts], copy(ci.codelocs), copy(ci.ssaflags)) - ir = IRCode(stmts, cfg, collect(LineInfoNode, ci.linetable), argtypes, Any[], sptypes) - return ir + 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 + meta = Expr[] + return IRCode(stmts, cfg, linetable, argtypes, meta, sptypes) end -function replace_code_newstyle!(ci::CodeInfo, ir::IRCode, nargs::Int) +""" + 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 + +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, sptypes::Vector{VarState}, argtypes::Vector{Any}) = inflate_ir!(copy(ci), sptypes, argtypes) +function inflate_ir(ci::CodeInfo) + parent = ci.parent + isa(parent, MethodInstance) && return inflate_ir(ci, parent) + # XXX the length of `ci.slotflags` may be different from the actual number of call + # arguments, but we really don't know that information in this case + argtypes = Any[ Any for i = 1:length(ci.slotflags) ] + return inflate_ir(ci, VarState[], argtypes) +end + +function replace_code_newstyle!(ci::CodeInfo, ir::IRCode) @assert isempty(ir.new_nodes) # All but the first `nargs` slots will now be unused + nargs = length(ir.argtypes) + resize!(ci.slotnames, nargs) resize!(ci.slotflags, nargs) + resize!(ci.slottypes, nargs) stmts = ir.stmts - ci.code, ci.ssavaluetypes, ci.codelocs, ci.ssaflags, ci.linetable = - stmts.inst, stmts.type, stmts.line, stmts.flag, ir.linetable + code = ci.code = stmts.inst + ssavaluetypes = ci.ssavaluetypes = stmts.type + codelocs = ci.codelocs = stmts.line + ssaflags = ci.ssaflags = stmts.flag + linetable = ci.linetable = ir.linetable for metanode in ir.meta - push!(ci.code, metanode) - push!(ci.codelocs, 1) - push!(ci.ssavaluetypes::Vector{Any}, Any) - push!(ci.ssaflags, IR_FLAG_NULL) + push!(code, metanode) + push!(codelocs, 1) + push!(ssavaluetypes, Any) + push!(ssaflags, IR_FLAG_NULL) end # Translate BB Edges to statement edges # (and undo normalization for now) - for i = 1:length(ci.code) - stmt = ci.code[i] + for i = 1:length(code) + stmt = code[i] if isa(stmt, GotoNode) - stmt = GotoNode(first(ir.cfg.blocks[stmt.label].stmts)) + code[i] = GotoNode(first(ir.cfg.blocks[stmt.label].stmts)) elseif isa(stmt, GotoIfNot) - stmt = GotoIfNot(stmt.cond, first(ir.cfg.blocks[stmt.dest].stmts)) + code[i] = GotoIfNot(stmt.cond, first(ir.cfg.blocks[stmt.dest].stmts)) elseif isa(stmt, PhiNode) - stmt = PhiNode(Int32[last(ir.cfg.blocks[edge].stmts) for edge in stmt.edges], stmt.values) - elseif isa(stmt, Expr) && stmt.head === :enter + 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 end - ci.code[i] = stmt end end - -# used by some tests -inflate_ir(ci::CodeInfo) = inflate_ir(ci, Any[], Any[ Any for i = 1:length(ci.slotflags) ]) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index fdc50b3b481cd..4bfb5f3fcde56 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -6,32 +6,45 @@ function is_known_call(@nospecialize(x), @nospecialize(func), ir::Union{IRCode,I return singleton_type(ft) === func end +struct SSAUse + kind::Symbol + idx::Int +end +GetfieldUse(idx::Int) = SSAUse(:getfield, idx) +PreserveUse(idx::Int) = SSAUse(:preserve, idx) +NoPreserve() = SSAUse(:nopreserve, 0) +IsdefinedUse(idx::Int) = SSAUse(:isdefined, idx) +FinalizerUse(idx::Int) = SSAUse(:finalizer, idx) + """ du::SSADefUse This struct keeps track of all uses of some mutable struct allocated in the current function: -- `du.uses::Vector{Int}` are all instances of `getfield` on the struct +- `du.uses::Vector{SSAUse}` are some "usages" (like `getfield`) of the struct - `du.defs::Vector{Int}` are all instances of `setfield!` on the struct The terminology refers to the uses/defs of the "slot bundle" that the mutable struct represents. -In addition we keep track of all instances of a `:foreigncall` that preserves of this mutable -struct in `du.ccall_preserve_uses`. Somewhat counterintuitively, we don't actually need to -make sure that the struct itself is live (or even allocated) at a `ccall` site. -If there are no other places where the struct escapes (and thus e.g. where its address is taken), -it need not be allocated. We do however, need to make sure to preserve any elements of this struct. +`du.uses` tracks all instances of `getfield` and `isdefined` calls on the struct. +Additionally it also tracks all instances of a `:foreigncall` that preserves of this mutable +struct. Somewhat counterintuitively, we don't actually need to make sure that the struct +itself is live (or even allocated) at a `ccall` site. If there are no other places where +the struct escapes (and thus e.g. where its address is taken), it need not be allocated. +We do however, need to make sure to preserve any elements of this struct. """ struct SSADefUse - uses::Vector{Int} + uses::Vector{SSAUse} defs::Vector{Int} - ccall_preserve_uses::Vector{Int} end -SSADefUse() = SSADefUse(Int[], Int[], Int[]) +SSADefUse() = SSADefUse(SSAUse[], Int[]) -compute_live_ins(cfg::CFG, du::SSADefUse) = compute_live_ins(cfg, du.defs, du.uses) - -# assume `stmt == getfield(obj, field, ...)` or `stmt == setfield!(obj, field, val, ...)` -try_compute_field_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr) = - try_compute_field(ir, stmt.args[3]) +function compute_live_ins(cfg::CFG, du::SSADefUse) + uses = Int[] + for use in du.uses + use.kind === :isdefined && continue # filter out `isdefined` usages + push!(uses, use.idx) + end + compute_live_ins(cfg, sort!(du.defs), uses) +end function try_compute_field(ir::Union{IncrementalCompact,IRCode}, @nospecialize(field)) # fields are usually literals, handle them manually @@ -50,15 +63,16 @@ function try_compute_field(ir::Union{IncrementalCompact,IRCode}, @nospecialize(f return isa(field, Union{Int, Symbol}) ? field : nothing end +# assume `stmt` is a call of `getfield`/`setfield!`/`isdefined` function try_compute_fieldidx_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::Expr, typ::DataType) - field = try_compute_field_stmt(ir, stmt) + field = try_compute_field(ir, stmt.args[3]) return try_compute_fieldidx(typ, field) end -function find_curblock(domtree::DomTree, allblocks::Vector{Int}, curblock::Int) +function find_curblock(domtree::DomTree, allblocks::BitSet, curblock::Int) # TODO: This can be much faster by looking at current level and only # searching for those blocks in a sorted order - while !(curblock in allblocks) + while !(curblock in allblocks) && curblock !== 0 curblock = domtree.idoms_bb[curblock] end return curblock @@ -75,7 +89,7 @@ function val_for_def_expr(ir::IRCode, def::Int, fidx::Int) end end -function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, curblock::Int) +function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, curblock::Int) curblock = find_curblock(domtree, allblocks, curblock) def = 0 for stmt in du.defs @@ -86,7 +100,8 @@ function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector def == 0 ? phinodes[curblock] : val_for_def_expr(ir, def, fidx) end -function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int) +function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::BitSet, + du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int) def, useblock, curblock = find_def_for_use(ir, domtree, allblocks, du, use) if def == 0 if !haskey(phinodes, curblock) @@ -104,7 +119,7 @@ end # even when the allocation contains an uninitialized field, we try an extra effort to check # if this load at `idx` have any "safe" `setfield!` calls that define the field function has_safe_def( - ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, + ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, newidx::Int, idx::Int) def, _, _ = find_def_for_use(ir, domtree, allblocks, du, idx) # will throw since we already checked this `:new` site doesn't define this field @@ -122,7 +137,7 @@ function has_safe_def( # if this block has already been examined, bail out to avoid infinite cycles pred in seen && return false idx = last(ir.cfg.blocks[pred].stmts) - # NOTE `idx` isn't a load, thus we can use inclusive coondition within the `find_def_for_use` + # NOTE `idx` isn't a load, thus we can use inclusive condition within the `find_def_for_use` def, _, _ = find_def_for_use(ir, domtree, allblocks, du, idx, true) # will throw since we already checked this `:new` site doesn't define this field def == newidx && return false @@ -139,7 +154,7 @@ end # find the first dominating def for the given use function find_def_for_use( - ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, use::Int, inclusive::Bool=false) + ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, use::Int, inclusive::Bool=false) useblock = block_for_inst(ir.cfg, use) curblock = find_curblock(domtree, allblocks, useblock) local def = 0 @@ -161,11 +176,11 @@ function find_def_for_use( return def, useblock, curblock end -function collect_leaves(compact::IncrementalCompact, @nospecialize(val), @nospecialize(typeconstraint)) +function collect_leaves(compact::IncrementalCompact, @nospecialize(val), @nospecialize(typeconstraint), 𝕃ₒ::AbstractLattice) if isa(val, Union{OldSSAValue, SSAValue}) val, typeconstraint = simple_walk_constraint(compact, val, typeconstraint) end - return walk_to_defs(compact, val, typeconstraint) + return walk_to_defs(compact, val, typeconstraint, 𝕃ₒ) end function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSAValue=#), @@ -181,7 +196,7 @@ function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSA return rename end end - def = compact[defssa] + def = compact[defssa][:inst] if isa(def, PiNode) if callback(def, defssa) return defssa @@ -225,10 +240,10 @@ end Starting at `val` walk use-def chains to get all the leaves feeding into this `val` (pruning those leaves rules out by path conditions). """ -function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospecialize(typeconstraint)) +function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospecialize(typeconstraint), 𝕃ₒ::AbstractLattice) visited_phinodes = AnySSAValue[] isa(defssa, AnySSAValue) || return Any[defssa], visited_phinodes - def = compact[defssa] + def = compact[defssa][:inst] isa(def, PhiNode) || return Any[defssa], visited_phinodes visited_constraints = IdDict{AnySSAValue, Any}() worklist_defs = AnySSAValue[] @@ -240,7 +255,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe defssa = pop!(worklist_defs) typeconstraint = pop!(worklist_constraints) visited_constraints[defssa] = typeconstraint - def = compact[defssa] + def = compact[defssa][:inst] if isa(def, PhiNode) push!(visited_phinodes, defssa) possible_predecessors = Int[] @@ -271,7 +286,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe # path, with a different type constraint. We may have # to redo some work here with the wider typeconstraint push!(worklist_defs, new_def) - push!(worklist_constraints, tmerge(new_constraint, visited_constraints[new_def])) + push!(worklist_constraints, tmerge(𝕃ₒ, new_constraint, visited_constraints[new_def])) end continue end @@ -311,7 +326,7 @@ function already_inserted(compact::IncrementalCompact, old::OldSSAValue) end id -= length(compact.ir.stmts) if id < length(compact.ir.new_nodes) - error("") + return already_inserted(compact, OldSSAValue(compact.ir.new_nodes.info[id].pos)) end id -= length(compact.ir.new_nodes) @assert id <= length(compact.pending_nodes) @@ -322,7 +337,7 @@ function is_pending(compact::IncrementalCompact, old::OldSSAValue) return old.id > length(compact.ir.stmts) + length(compact.ir.new_nodes) end -function is_getfield_captures(@nospecialize(def), compact::IncrementalCompact) +function is_getfield_captures(@nospecialize(def), compact::IncrementalCompact, 𝕃ₒ::AbstractLattice) isa(def, Expr) || return false length(def.args) >= 3 || return false is_known_call(def, getfield, compact) || return false @@ -330,19 +345,19 @@ function is_getfield_captures(@nospecialize(def), compact::IncrementalCompact) isa(which, Const) || return false which.val === :captures || return false oc = argextype(def.args[2], compact) - return oc ⊑ Core.OpaqueClosure + return ⊑(𝕃ₒ, oc, Core.OpaqueClosure) end struct LiftedValue - x - LiftedValue(@nospecialize x) = new(x) + val + LiftedValue(@nospecialize val) = new(val) end const LiftedLeaves = IdDict{Any, Union{Nothing,LiftedValue}} # try to compute lifted values that can replace `getfield(x, field)` call # where `x` is an immutable struct that are defined at any of `leaves` -function lift_leaves(compact::IncrementalCompact, - @nospecialize(result_t), field::Int, leaves::Vector{Any}) +function lift_leaves(compact::IncrementalCompact, @nospecialize(result_t), field::Int, + leaves::Vector{Any}, 𝕃ₒ::AbstractLattice) # For every leaf, the lifted value lifted_leaves = LiftedLeaves() maybe_undef = false @@ -355,10 +370,7 @@ function lift_leaves(compact::IncrementalCompact, lift_arg!(compact, leaf, cache_key, def, 1+field, lifted_leaves) continue elseif isexpr(def, :new) - typ = widenconst(types(compact)[leaf]) - if isa(typ, UnionAll) - typ = unwrap_unionall(typ) - end + typ = unwrap_unionall(widenconst(types(compact)[leaf])) (isa(typ, DataType) && !isabstracttype(typ)) || return nothing @assert !ismutabletype(typ) if length(def.args) < 1+field @@ -386,7 +398,17 @@ function lift_leaves(compact::IncrementalCompact, end lift_arg!(compact, leaf, cache_key, def, 1+field, lifted_leaves) continue - elseif is_getfield_captures(def, compact) + # NOTE we can enable this, but most `:splatnew` expressions are transformed into + # `:new` expressions by the inlinear + # elseif isexpr(def, :splatnew) && length(def.args) == 2 && isa(def.args[2], AnySSAValue) + # tplssa = def.args[2]::AnySSAValue + # tplexpr = compact[tplssa][:inst] + # if is_known_call(tplexpr, tuple, compact) && 1 ≤ field < length(tplexpr.args) + # lift_arg!(compact, tplssa, cache_key, tplexpr, 1+field, lifted_leaves) + # continue + # end + # return nothing + elseif is_getfield_captures(def, compact, 𝕃ₒ) # Walk to new_opaque_closure ocleaf = def.args[2] if isa(ocleaf, AnySSAValue) @@ -418,7 +440,7 @@ function lift_leaves(compact::IncrementalCompact, elseif isa(leaf, GlobalRef) mod, name = leaf.mod, leaf.name if isdefined(mod, name) && isconst(mod, name) - leaf = getfield(mod, name) + leaf = getglobal(mod, name) else return nothing end @@ -440,16 +462,21 @@ function lift_arg!( lifted = stmt.args[argidx] if is_old(compact, leaf) && isa(lifted, SSAValue) lifted = OldSSAValue(lifted.id) + if already_inserted(compact, lifted) + lifted = compact.ssa_rename[lifted.id] + end end if isa(lifted, GlobalRef) || isa(lifted, Expr) lifted = insert_node!(compact, leaf, effect_free(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) - nothing + return nothing end function walk_to_def(compact::IncrementalCompact, @nospecialize(leaf)) @@ -459,12 +486,12 @@ function walk_to_def(compact::IncrementalCompact, @nospecialize(leaf)) leaf = simple_walk(compact, leaf) end if isa(leaf, AnySSAValue) - def = compact[leaf] + def = compact[leaf][:inst] else def = leaf end elseif isa(leaf, AnySSAValue) - def = compact[leaf] + def = compact[leaf][:inst] else def = leaf end @@ -474,7 +501,7 @@ end make_MaybeUndef(@nospecialize(typ)) = isa(typ, MaybeUndef) ? typ : MaybeUndef(typ) """ - lift_comparison!(cmp, compact::IncrementalCompact, idx::Int, stmt::Expr) + lift_comparison!(cmp, compact::IncrementalCompact, idx::Int, stmt::Expr, 𝕃ₒ::AbstractLattice) Replaces `cmp(φ(x, y)::Union{X,Y}, constant)` by `φ(cmp(x, constant), cmp(y, constant))`, where `cmp(x, constant)` and `cmp(y, constant)` can be replaced with constant `Bool`eans. @@ -489,7 +516,8 @@ end function lift_comparison! end function lift_comparison!(::typeof(===), compact::IncrementalCompact, - idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}) + idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, + 𝕃ₒ::AbstractLattice) args = stmt.args length(args) == 3 || return lhs, rhs = args[2], args[3] @@ -505,44 +533,47 @@ function lift_comparison!(::typeof(===), compact::IncrementalCompact, else return end - lift_comparison_leaves!(egal_tfunc, compact, val, cmp, lifting_cache, idx) + lift_comparison_leaves!(egal_tfunc, compact, val, cmp, lifting_cache, idx, 𝕃ₒ) end function lift_comparison!(::typeof(isa), compact::IncrementalCompact, - idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}) + idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, + 𝕃ₒ::AbstractLattice) args = stmt.args length(args) == 3 || return cmp = argextype(args[3], compact) val = args[2] - lift_comparison_leaves!(isa_tfunc, compact, val, cmp, lifting_cache, idx) + lift_comparison_leaves!(isa_tfunc, compact, val, cmp, lifting_cache, idx, 𝕃ₒ) end function lift_comparison!(::typeof(isdefined), compact::IncrementalCompact, - idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}) + idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, + 𝕃ₒ::AbstractLattice) args = stmt.args length(args) == 3 || return cmp = argextype(args[3], compact) isa(cmp, Const) || return # `isdefined_tfunc` won't return Const val = args[2] - lift_comparison_leaves!(isdefined_tfunc, compact, val, cmp, lifting_cache, idx) + lift_comparison_leaves!(isdefined_tfunc, compact, val, cmp, lifting_cache, idx, 𝕃ₒ) end function lift_comparison_leaves!(@specialize(tfunc), compact::IncrementalCompact, @nospecialize(val), @nospecialize(cmp), - lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, idx::Int) + lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, idx::Int, + 𝕃ₒ::AbstractLattice) typeconstraint = widenconst(argextype(val, compact)) if isa(val, Union{OldSSAValue, SSAValue}) val, typeconstraint = simple_walk_constraint(compact, val, typeconstraint) end isa(typeconstraint, Union) || return # bail out if there won't be a good chance for lifting - leaves, visited_phinodes = collect_leaves(compact, val, typeconstraint) + leaves, visited_phinodes = collect_leaves(compact, val, typeconstraint, 𝕃ₒ) length(leaves) ≤ 1 && return # bail out if we don't have multiple leaves # check if we can evaluate the comparison for each one of the leaves lifted_leaves = nothing for i = 1:length(leaves) leaf = leaves[i] - result = tfunc(argextype(leaf, compact), cmp) + result = tfunc(𝕃ₒ, argextype(leaf, compact), cmp) if isa(result, Const) if lifted_leaves === nothing lifted_leaves = LiftedLeaves() @@ -556,9 +587,9 @@ function lift_comparison_leaves!(@specialize(tfunc), # perform lifting lifted_val = perform_lifting!(compact, visited_phinodes, cmp, lifting_cache, Bool, - lifted_leaves::LiftedLeaves, val)::LiftedValue + lifted_leaves::LiftedLeaves, val, nothing)::LiftedValue - compact[idx] = lifted_val.x + compact[idx] = lifted_val.val end struct LiftedPhi @@ -574,33 +605,75 @@ function is_old(compact, @nospecialize(old_node_ssa)) end function perform_lifting!(compact::IncrementalCompact, - visited_phinodes::Vector{AnySSAValue}, @nospecialize(cache_key), - lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, - @nospecialize(result_t), lifted_leaves::LiftedLeaves, @nospecialize(stmt_val)) - reverse_mapping = IdDict{AnySSAValue, Int}(ssa => id for (id, ssa) in enumerate(visited_phinodes)) + visited_phinodes::Vector{AnySSAValue}, @nospecialize(cache_key), + lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, + @nospecialize(result_t), lifted_leaves::LiftedLeaves, @nospecialize(stmt_val), + lazydomtree::Union{LazyDomtree,Nothing}) + reverse_mapping = IdDict{AnySSAValue, Int}() + for id in 1:length(visited_phinodes) + reverse_mapping[visited_phinodes[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_phinodes + 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 - lifted_phis = LiftedPhi[] - for item in visited_phinodes + nphis = length(visited_phinodes) + lifted_phis = Vector{LiftedPhi}(undef, nphis) + for i = 1:nphis + item = visited_phinodes[i] # FIXME this cache is broken somehow # ckey = Pair{AnySSAValue, Any}(item, cache_key) # cached = ckey in keys(lifting_cache) cached = false if cached ssa = lifting_cache[ckey] - push!(lifted_phis, LiftedPhi(ssa, compact[ssa]::PhiNode, false)) + lifted_phis[i] = LiftedPhi(ssa, compact[ssa][:inst]::PhiNode, false) continue end n = PhiNode() ssa = insert_node!(compact, item, effect_free(NewInstruction(n, result_t))) # lifting_cache[ckey] = ssa - push!(lifted_phis, LiftedPhi(ssa, n, true)) + lifted_phis[i] = LiftedPhi(ssa, n, true) end # Fix up arguments - for (old_node_ssa, lf) in zip(visited_phinodes, lifted_phis) - old_node = compact[old_node_ssa]::PhiNode + for i = 1:nphis + (old_node_ssa, lf) = visited_phinodes[i], lifted_phis[i] + old_node = compact[old_node_ssa][:inst]::PhiNode new_node = lf.node + should_count = !isa(lf.ssa, OldSSAValue) || already_inserted(compact, lf.ssa) lf.need_argupdate || continue for i = 1:length(old_node.edges) edge = old_node.edges[i] @@ -619,25 +692,24 @@ function perform_lifting!(compact::IncrementalCompact, resize!(new_node.values, length(new_node.values)+1) continue end - val = lifted_val.x + val = lifted_val.val if isa(val, AnySSAValue) callback = (@nospecialize(pi), @nospecialize(idx)) -> true val = simple_walk(compact, val, callback) end + should_count && _count_added_node!(compact, val) push!(new_node.values, val) elseif isa(val, AnySSAValue) && val in keys(reverse_mapping) push!(new_node.edges, edge) - push!(new_node.values, lifted_phis[reverse_mapping[val]].ssa) + newval = lifted_phis[reverse_mapping[val]].ssa + should_count && _count_added_node!(compact, newval) + push!(new_node.values, newval) else # Probably ignored by path condition, skip this end end end - for lf in lifted_phis - count_added_node!(compact, lf.node) - end - # Fixup the stmt itself if isa(stmt_val, Union{SSAValue, OldSSAValue}) stmt_val = simple_walk(compact, stmt_val) @@ -652,6 +724,103 @@ function perform_lifting!(compact::IncrementalCompact, return stmt_val # N.B. should never happen end +function lift_svec_ref!(compact::IncrementalCompact, idx::Int, stmt::Expr) + length(stmt.args) != 4 && return + + vec = stmt.args[3] + val = stmt.args[4] + valT = argextype(val, compact) + (isa(valT, Const) && isa(valT.val, Int)) || return + valI = valT.val::Int + valI >= 1 || return + + if isa(vec, SimpleVector) + valI <= length(vec) || return + compact[idx] = quoted(vec[valI]) + elseif isa(vec, SSAValue) + def = compact[vec][:inst] + if is_known_call(def, Core.svec, compact) + valI <= length(def.args) - 1 || return + compact[idx] = def.args[valI+1] + elseif is_known_call(def, Core._compute_sparams, compact) + valI != 1 && return # TODO generalize this for more values of valI + res = _lift_svec_ref(def, compact) + res === nothing && return + compact[idx] = res.val + end + end + return +end + +# TODO: We could do the whole lifing 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 +@inline function _lift_svec_ref(def::Expr, compact::IncrementalCompact) + length(def.args) >= 3 || return nothing + m = argextype(def.args[2], compact) + isa(m, Const) || return nothing + m = m.val + isa(m, Method) || return nothing + + # TODO: More general structural analysis of the intersection + sig = m.sig + isa(sig, UnionAll) || return nothing + tvar = sig.var + sig = sig.body + isa(sig, DataType) || return nothing + sig.name === Tuple.name || return nothing + length(sig.parameters) >= 1 || return nothing + + i = let sig=sig + findfirst(j->has_typevar(sig.parameters[j], tvar), 1:length(sig.parameters)) + end + i === nothing && return nothing + let sig=sig + any(j->has_typevar(sig.parameters[j], tvar), i+1:length(sig.parameters)) + end && return nothing + + arg = sig.parameters[i] + + rarg = def.args[2 + i] + isa(rarg, SSAValue) || return nothing + argdef = compact[rarg][:inst] + if isexpr(argdef, :new) + rarg = argdef.args[1] + isa(rarg, SSAValue) || return nothing + argdef = compact[rarg][:inst] + else + isType(arg) || return nothing + arg = arg.parameters[1] + end + + is_known_call(argdef, Core.apply_type, compact) || return nothing + length(argdef.args) == 3 || return nothing + + applyT = argextype(argdef.args[2], compact) + isa(applyT, Const) || return nothing + applyT = applyT.val + + isa(applyT, UnionAll) || return nothing + # N.B.: At the moment we only lift the valI == 1 case, so we + # only need to look at the outermost tvar. + applyTvar = applyT.var + applyTbody = applyT.body + + arg = unwrap_unionall(arg) + applyTbody = unwrap_unionall(applyTbody) + + (isa(arg, DataType) && isa(applyTbody, DataType)) || return nothing + applyTbody.name === arg.name || return nothing + length(applyTbody.parameters) == length(arg.parameters) || return nothing + for i = 1:length(applyTbody.parameters) + if applyTbody.parameters[i] === applyTvar && arg.parameters[i] === tvar + return LiftedValue(argdef.args[3]) + end + end + return nothing +end + # NOTE we use `IdSet{Int}` instead of `BitSet` for in these passes since they work on IR after inlining, # which can be very large sometimes, and program counters in question are often very sparse const SPCSet = IdSet{Int} @@ -674,14 +843,17 @@ its argument). In a case when all usages are fully eliminated, `struct` allocation may also be erased as a result of succeeding dead code elimination. """ -function sroa_pass!(ir::IRCode) +function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) + 𝕃ₒ = inlining === nothing ? OptimizerLattice() : optimizer_lattice(inlining.interp) compact = IncrementalCompact(ir) defuses = nothing # will be initialized once we encounter mutability in order to reduce dynamic allocations lifting_cache = IdDict{Pair{AnySSAValue, Any}, AnySSAValue}() + # initialization of domtree is delayed to avoid the expensive computation in many cases + lazydomtree = LazyDomtree(ir) for ((_, idx), stmt) in compact # check whether this statement is `getfield` / `setfield!` (or other "interesting" statement) isa(stmt, Expr) || continue - is_setfield = false + is_setfield = is_isdefined = is_finalizer = false field_ordering = :unspecified if is_known_call(stmt, setfield!, compact) 4 <= length(stmt.args) <= 5 || continue @@ -697,6 +869,25 @@ function sroa_pass!(ir::IRCode) field_ordering = argextype(stmt.args[4], compact) widenconst(field_ordering) === Bool && (field_ordering = :unspecified) end + elseif is_known_call(stmt, isdefined, compact) + 3 <= length(stmt.args) <= 4 || continue + is_isdefined = true + if length(stmt.args) == 4 + field_ordering = argextype(stmt.args[4], compact) + widenconst(field_ordering) === Bool && (field_ordering = :unspecified) + end + elseif is_known_call(stmt, Core.finalizer, compact) + 3 <= length(stmt.args) <= 5 || continue + info = compact[SSAValue(idx)][:info] + if isa(info, FinalizerInfo) + is_finalizer_inlineable(info.effects) || continue + else + # Inlining performs legality checks on the finalizer to determine + # whether or not we may inline it. If so, it appends extra arguments + # at the end of the intrinsic. Detect that here. + length(stmt.args) == 5 || continue + end + is_finalizer = true elseif isexpr(stmt, :foreigncall) nccallargs = length(stmt.args[3]::SimpleVector) preserved = Int[] @@ -712,16 +903,13 @@ function sroa_pass!(ir::IRCode) def = simple_walk(compact, preserved_arg, callback) isa(def, SSAValue) || continue defidx = def.id - def = compact[defidx] + def = compact[def][:inst] if is_known_call(def, tuple, compact) record_immutable_preserve!(new_preserves, def, compact) push!(preserved, preserved_arg.id) continue elseif isexpr(def, :new) - typ = widenconst(argextype(SSAValue(defidx), compact)) - if isa(typ, UnionAll) - typ = unwrap_unionall(typ) - end + typ = unwrap_unionall(widenconst(argextype(SSAValue(defidx), compact))) if typ isa DataType && !ismutabletype(typ) record_immutable_preserve!(new_preserves, def, compact) push!(preserved, preserved_arg.id) @@ -733,44 +921,50 @@ function sroa_pass!(ir::IRCode) if defuses === nothing defuses = IdDict{Int, Tuple{SPCSet, SSADefUse}}() end - mid, defuse = get!(defuses, defidx, (SPCSet(), SSADefUse())) - push!(defuse.ccall_preserve_uses, idx) + mid, defuse = get!(()->(SPCSet(),SSADefUse()), defuses, defidx) + push!(defuse.uses, PreserveUse(idx)) union!(mid, intermediaries) end continue end if !isempty(new_preserves) + compact[idx] = nothing compact[idx] = form_new_preserves(stmt, preserved, new_preserves) end continue else # TODO: This isn't the best place to put these if is_known_call(stmt, typeassert, compact) canonicalize_typeassert!(compact, idx, stmt) + elseif is_known_call(stmt, Core._svec_ref, compact) + lift_svec_ref!(compact, idx, stmt) elseif is_known_call(stmt, (===), compact) - lift_comparison!(===, compact, idx, stmt, lifting_cache) + lift_comparison!(===, compact, idx, stmt, lifting_cache, 𝕃ₒ) elseif is_known_call(stmt, isa, compact) - lift_comparison!(isa, compact, idx, stmt, lifting_cache) - elseif is_known_call(stmt, isdefined, compact) - lift_comparison!(isdefined, compact, idx, stmt, lifting_cache) + lift_comparison!(isa, compact, idx, stmt, lifting_cache, 𝕃ₒ) end continue end - # analyze this `getfield` / `setfield!` call - - field = try_compute_field_stmt(compact, stmt) - field === nothing && continue - - val = stmt.args[2] + if is_finalizer + val = stmt.args[3] + else + # analyze `getfield` / `isdefined` / `setfield!` call + val = stmt.args[2] + end struct_typ = unwrap_unionall(widenconst(argextype(val, compact))) if isa(struct_typ, Union) && struct_typ <: Tuple struct_typ = unswitchtupleunion(struct_typ) end + if isa(struct_typ, Union) && is_isdefined + lift_comparison!(isdefined, compact, idx, stmt, lifting_cache, 𝕃ₒ) + continue + end isa(struct_typ, DataType) || continue struct_typ.name.atomicfields == C_NULL || continue # TODO: handle more - if !(field_ordering === :unspecified || (field_ordering isa Const && field_ordering.val === :not_atomic)) + if !((field_ordering === :unspecified) || + (field_ordering isa Const && field_ordering.val === :not_atomic)) continue end @@ -788,29 +982,35 @@ function sroa_pass!(ir::IRCode) if defuses === nothing defuses = IdDict{Int, Tuple{SPCSet, SSADefUse}}() end - mid, defuse = get!(defuses, def.id, (SPCSet(), SSADefUse())) + mid, defuse = get!(()->(SPCSet(),SSADefUse()), defuses, def.id) if is_setfield push!(defuse.defs, idx) + elseif is_isdefined + push!(defuse.uses, IsdefinedUse(idx)) + elseif is_finalizer + push!(defuse.uses, FinalizerUse(idx)) else - push!(defuse.uses, idx) + push!(defuse.uses, GetfieldUse(idx)) end union!(mid, intermediaries) end continue - elseif is_setfield - continue # invalid `setfield!` call, but just ignore here + elseif is_setfield || is_finalizer + continue # invalid `setfield!` or `Core.finalizer` call, but just ignore here + elseif is_isdefined + continue # TODO? end # perform SROA on immutable structs here on - field = try_compute_fieldidx(struct_typ, field) + field = try_compute_fieldidx_stmt(compact, stmt, struct_typ) field === nothing && continue - leaves, visited_phinodes = collect_leaves(compact, val, struct_typ) + leaves, visited_phinodes = collect_leaves(compact, val, struct_typ, 𝕃ₒ) isempty(leaves) && continue result_t = argextype(SSAValue(idx), compact) - lifted_result = lift_leaves(compact, result_t, field, leaves) + lifted_result = lift_leaves(compact, result_t, field, leaves, 𝕃ₒ) lifted_result === nothing && continue lifted_leaves, any_undef = lifted_result @@ -819,21 +1019,19 @@ function sroa_pass!(ir::IRCode) end val = perform_lifting!(compact, - visited_phinodes, field, lifting_cache, result_t, lifted_leaves, val) + visited_phinodes, field, lifting_cache, result_t, lifted_leaves, val, lazydomtree) # Insert the undef check if necessary - if any_undef - if val === nothing - insert_node!(compact, SSAValue(idx), - non_effect_free(NewInstruction(Expr(:throw_undef_if_not, Symbol("##getfield##"), false), Nothing))) - else - # val must be defined - end + if any_undef && val === nothing + insert_node!(compact, SSAValue(idx), non_effect_free(NewInstruction( + Expr(:throw_undef_if_not, Symbol("##getfield##"), false), Nothing))) else + # val must be defined @assert val !== nothing end - compact[idx] = val === nothing ? nothing : val.x + compact[idx] = val === nothing ? nothing : val.val + compact[SSAValue(idx)][:flag] |= IR_FLAG_REFINED end non_dce_finish!(compact) @@ -844,9 +1042,11 @@ function sroa_pass!(ir::IRCode) # but before the DCE) for our predicate within `sroa_mutables!`, but we also # try an extra effort using a callback so that reference counts are updated used_ssas = copy(compact.used_ssas) - simple_dce!(compact, (x::SSAValue) -> used_ssas[x.id] -= 1) + simple_dce!(compact) do x::SSAValue + used_ssas[x.id] -= 1 + end ir = complete(compact) - sroa_mutables!(ir, defuses, used_ssas) + sroa_mutables!(ir, defuses, used_ssas, lazydomtree, inlining) return ir else simple_dce!(compact) @@ -854,16 +1054,204 @@ function sroa_pass!(ir::IRCode) end end -function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse}}, used_ssas::Vector{Int}) - # initialization of domtree is delayed to avoid the expensive computation in many cases - local domtree = nothing +# NOTE we resolve the inlining source here as we don't want to serialize `Core.Compiler` +# data structure into the global cache (see the comment in `handle_finalizer_call!`) +function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, + mi::MethodInstance, @nospecialize(info::CallInfo), inlining::InliningState, + attach_after::Bool) + code = get(code_cache(inlining), mi, nothing) + et = InliningEdgeTracker(inlining.et) + if code isa CodeInstance + if use_const_api(code) + # No code in the function - Nothing to do + add_inlining_backedge!(et, mi) + return true + end + src = @atomic :monotonic code.inferred + else + src = nothing + end + + src = inlining_policy(inlining.interp, src, info, IR_FLAG_NULL, mi, Any[]) + src === nothing && return false + src = retrieve_ir_for_inlining(mi, src) + + # For now: Require finalizer to only have one basic block + length(src.cfg.blocks) == 1 || return false + + # Ok, we're committed to inlining the finalizer + add_inlining_backedge!(et, mi) + + # TODO: Should there be a special line number node for inlined finalizers? + inlined_at = ir[SSAValue(idx)][:line] + ((sp_ssa, argexprs), linetable_offset) = ir_prepare_inlining!(InsertBefore(ir, SSAValue(idx)), ir, + ir.linetable, src, mi.sparam_vals, mi, inlined_at, argexprs) + + # TODO: Use the actual inliner here rather than open coding this special purpose inliner. + spvals = mi.sparam_vals + ssa_rename = Vector{Any}(undef, length(src.stmts)) + for idx′ = 1:length(src.stmts) + inst = src[SSAValue(idx′)] + stmt′ = inst[:inst] + isa(stmt′, ReturnNode) && continue + stmt′ = ssamap(stmt′) do ssa::SSAValue + ssa_rename[ssa.id] + end + stmt′ = ssa_substitute_op!(InsertBefore(ir, SSAValue(idx)), inst, stmt′, argexprs, mi.specTypes, mi.sparam_vals, sp_ssa, :default) + ssa_rename[idx′] = insert_node!(ir, idx, + NewInstruction(inst; stmt=stmt′, line=inst[:line]+linetable_offset), + attach_after) + end + + return true +end + +is_nothrow(ir::IRCode, ssa::SSAValue) = (ir[ssa][:flag] & IR_FLAG_NOTHROW) ≠ 0 + +function reachable_blocks(cfg::CFG, from_bb::Int, to_bb::Union{Nothing,Int} = nothing) + worklist = Int[from_bb] + visited = BitSet(from_bb) + if to_bb !== nothing + push!(visited, to_bb) + end + function visit!(bb::Int) + if bb ∉ visited + push!(visited, bb) + push!(worklist, bb) + end + end + while !isempty(worklist) + foreach(visit!, cfg.blocks[pop!(worklist)].succs) + end + return visited +end + +function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse::SSADefUse, + inlining::InliningState, lazydomtree::LazyDomtree, + lazypostdomtree::LazyPostDomtree, @nospecialize(info::CallInfo)) + # For now, require that: + # 1. The allocation dominates the finalizer registration + # 2. The finalizer registration dominates all uses reachable from the + # finalizer registration. + # 3. The insertion block for the finalizer is the post-dominator of all + # uses and the finalizer registration block. The insertion block must + # be dominated by the finalizer registration block. + # 4. The path from the finalizer registration to the finalizer inlining + # location is nothrow + # + # TODO: We could relax item 3, by inlining the finalizer multiple times. + + # Check #1: The allocation dominates the finalizer registration + domtree = get!(lazydomtree) + finalizer_bb = block_for_inst(ir, finalizer_idx) + alloc_bb = block_for_inst(ir, idx) + dominates(domtree, alloc_bb, finalizer_bb) || return nothing + + bb_insert_block::Int = finalizer_bb + bb_insert_idx::Union{Int,Nothing} = finalizer_idx + function note_block_use!(usebb::Int, useidx::Int) + new_bb_insert_block = nearest_common_dominator(get!(lazypostdomtree), + bb_insert_block, usebb) + if new_bb_insert_block == bb_insert_block && bb_insert_idx !== nothing + bb_insert_idx = max(bb_insert_idx::Int, useidx) + elseif new_bb_insert_block == usebb + bb_insert_idx = useidx + else + bb_insert_idx = nothing + end + bb_insert_block = new_bb_insert_block + nothing + end + + # Collect all reachable blocks between the finalizer registration and the + # insertion point + blocks = reachable_blocks(ir.cfg, finalizer_bb, alloc_bb) + + # Check #2 + function check_defuse(x::Union{Int,SSAUse}) + duidx = x isa SSAUse ? x.idx : x + duidx == finalizer_idx && return true + bb = block_for_inst(ir, duidx) + # Not reachable from finalizer registration - we're ok + bb ∉ blocks && return true + note_block_use!(bb, duidx) + if dominates(domtree, finalizer_bb, bb) + return true + else + return false + end + end + all(check_defuse, defuse.uses) || return nothing + all(check_defuse, defuse.defs) || return nothing + + # Check #3 + dominates(domtree, finalizer_bb, bb_insert_block) || return nothing + + if !OptimizationParams(inlining.interp).assume_fatal_throw + # Collect all reachable blocks between the finalizer registration and the + # insertion point + blocks = finalizer_bb == bb_insert_block ? Int[finalizer_bb] : + reachable_blocks(ir.cfg, finalizer_bb, bb_insert_block) + + # Check #4 + function check_range_nothrow(ir::IRCode, s::Int, e::Int) + return all(s:e) do sidx::Int + sidx == finalizer_idx && return true + sidx == idx && return true + return is_nothrow(ir, SSAValue(sidx)) + end + end + for bb in blocks + range = ir.cfg.blocks[bb].stmts + s, e = first(range), last(range) + if bb == bb_insert_block + bb_insert_idx === nothing && continue + e = bb_insert_idx + end + if bb == finalizer_bb + s = finalizer_idx + end + check_range_nothrow(ir, s, e) || return nothing + end + end + + # Ok, legality check complete. Figure out the exact statement where we're + # gonna 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)][:inst] + argexprs = Any[finalizer_stmt.args[2], finalizer_stmt.args[3]] + flags = 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 + # No code in the function - Nothing to do + else + mi = finalizer_stmt.args[5]::MethodInstance + 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) + end + end + else + insert_node!(ir, loc, with_flags(NewInstruction(Expr(:call, argexprs...), Nothing), flags), attach_after) + end + # Erase the call to `finalizer` + ir[SSAValue(finalizer_idx)][:inst] = nothing + return nothing +end + +function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse}}, used_ssas::Vector{Int}, lazydomtree::LazyDomtree, inlining::Union{Nothing, InliningState}) + lazypostdomtree = LazyPostDomtree(ir) for (idx, (intermediaries, defuse)) in defuses intermediaries = collect(intermediaries) # Check if there are any uses we did not account for. If so, the variable # escapes and we cannot eliminate the allocation. This works, because we're guaranteed # not to include any intermediaries that have dead uses. As a result, missing uses will only ever # show up in the nuses_total count. - nleaves = length(defuse.uses) + length(defuse.defs) + length(defuse.ccall_preserve_uses) + nleaves = length(defuse.uses) + length(defuse.defs) nuses = 0 for idx in intermediaries nuses += used_ssas[idx] @@ -874,19 +1262,37 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse defexpr = ir[SSAValue(idx)][:inst] isexpr(defexpr, :new) || continue newidx = idx - typ = ir.stmts[newidx][:type] - if isa(typ, UnionAll) - typ = unwrap_unionall(typ) - end + typ = unwrap_unionall(ir.stmts[newidx][:type]) # Could still end up here if we tried to setfield! on an immutable, which would # error at runtime, but is not illegal to have in the IR. + typ = widenconst(typ) ismutabletype(typ) || continue typ = typ::DataType + # First check for any finalizer calls + finalizer_idx = nothing + for use in defuse.uses + if use.kind === :finalizer + # For now: Only allow one finalizer per allocation + finalizer_idx !== nothing && @goto skip + finalizer_idx = use.idx + end + end + if finalizer_idx !== nothing && inlining !== nothing + try_resolve_finalizer!(ir, idx, finalizer_idx, defuse, inlining, + lazydomtree, lazypostdomtree, ir[SSAValue(finalizer_idx)][:info]) + continue + end # Partition defuses by field fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)] - all_forwarded = true + all_eliminated = all_forwarded = true for use in defuse.uses - stmt = ir[SSAValue(use)][:inst] # == `getfield` call + if use.kind === :preserve + for du in fielddefuse + push!(du.uses, use) + end + continue + end + stmt = ir[SSAValue(use.idx)][:inst] # == `getfield`/`isdefined` call # We may have discovered above that this use is dead # after the getfield elim of immutables. In that case, # it would have been deleted. That's fine, just ignore @@ -911,7 +1317,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # but we should come up with semantics for well defined semantics # for uninitialized fields first. ndefuse = length(fielddefuse) - blocks = Vector{Tuple{#=phiblocks=# Vector{Int}, #=allblocks=# Vector{Int}}}(undef, ndefuse) + blocks = Vector{Tuple{#=phiblocks=# Vector{Int}, #=allblocks=# BitSet}}(undef, ndefuse) for fidx in 1:ndefuse du = fielddefuse[fidx] isempty(du.uses) && continue @@ -920,15 +1326,35 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse if isempty(ldu.live_in_bbs) phiblocks = Int[] else - domtree === nothing && (@timeit "domtree 2" domtree = construct_domtree(ir.cfg.blocks)) - phiblocks = iterated_dominance_frontier(ir.cfg, ldu, domtree) + phiblocks = iterated_dominance_frontier(ir.cfg, ldu, get!(lazydomtree)) end - allblocks = sort(vcat(phiblocks, ldu.def_bbs)) + allblocks = union!(BitSet(phiblocks), ldu.def_bbs) blocks[fidx] = phiblocks, allblocks if fidx + 1 > length(defexpr.args) - for use in du.uses - domtree === nothing && (@timeit "domtree 2" domtree = construct_domtree(ir.cfg.blocks)) - has_safe_def(ir, domtree, allblocks, du, newidx, use) || @goto skip + for i = 1:length(du.uses) + use = du.uses[i] + if use.kind === :isdefined + if has_safe_def(ir, get!(lazydomtree), allblocks, du, newidx, use.idx) + ir[SSAValue(use.idx)][:inst] = true + else + all_eliminated = false + end + continue + elseif use.kind === :preserve + if length(du.defs) == 1 # allocation with this field uninitialized + # there is nothing to preserve, just ignore this use + du.uses[i] = NoPreserve() + continue + end + end + has_safe_def(ir, get!(lazydomtree), allblocks, du, newidx, use.idx) || @goto skip + end + else # always have some definition at the allocation site + for i = 1:length(du.uses) + use = du.uses[i] + if use.kind === :isdefined + ir[SSAValue(use.idx)][:inst] = true + end end end end @@ -936,9 +1362,8 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # Compute domtree now, needed below, now that we have finished compacting the IR. # This needs to be after we iterate through the IR with `IncrementalCompact` # because removing dead blocks can invalidate the domtree. - domtree === nothing && (@timeit "domtree 2" domtree = construct_domtree(ir.cfg.blocks)) - preserve_uses = isempty(defuse.ccall_preserve_uses) ? nothing : - IdDict{Int, Vector{Any}}((idx=>Any[] for idx in SPCSet(defuse.ccall_preserve_uses))) + domtree = get!(lazydomtree) + local preserve_uses = nothing for fidx in 1:ndefuse du = fielddefuse[fidx] ftyp = fieldtype(typ, fidx) @@ -950,14 +1375,26 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse NewInstruction(PhiNode(), ftyp)) end # Now go through all uses and rewrite them - for stmt in du.uses - ir[SSAValue(stmt)][:inst] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, stmt) - end - if !isbitstype(ftyp) - if preserve_uses !== nothing - for (use, list) in preserve_uses - push!(list, compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, use)) + for use in du.uses + if use.kind === :getfield + ir[SSAValue(use.idx)][:inst] = compute_value_for_use(ir, domtree, allblocks, + du, phinodes, fidx, use.idx) + ir[SSAValue(use.idx)][:flag] |= IR_FLAG_REFINED + elseif use.kind === :isdefined + continue # already rewritten if possible + elseif use.kind === :nopreserve + continue # nothing to preserve (may happen when there are uninitialized fields) + elseif use.kind === :preserve + newval = compute_value_for_use(ir, domtree, allblocks, + du, phinodes, fidx, use.idx) + if !isbitstype(widenconst(argextype(newval, ir))) + if preserve_uses === nothing + preserve_uses = IdDict{Int, Vector{Any}}() + end + push!(get!(Vector{Any}, preserve_uses, use.idx), newval) end + else + @assert false "sroa_mutables!: unexpected use" end end for b in phiblocks @@ -969,9 +1406,16 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse end end end - for stmt in du.defs - stmt == newidx && continue - ir[SSAValue(stmt)][:inst] = nothing + all_eliminated || continue + # all "usages" (i.e. `getfield` and `isdefined` calls) are eliminated, + # now eliminate "definitions" (i.e. `setfield!`) calls + # (NOTE the allocation itself will be eliminated by DCE pass later) + for idx in du.defs + idx == newidx && continue # this is allocation + # verify this statement won't throw, otherwise it can't be eliminated safely + ssa = SSAValue(idx) + is_nothrow(ir, ssa) || continue + ir[ssa][:inst] = nothing end end preserve_uses === nothing && continue @@ -982,8 +1426,9 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse push!(intermediaries, newidx) end # Insert the new preserves - for (use, new_preserves) in preserve_uses - ir[SSAValue(use)][:inst] = form_new_preserves(ir[SSAValue(use)][:inst]::Expr, intermediaries, new_preserves) + for (useidx, new_preserves) in preserve_uses + ir[SSAValue(useidx)][:inst] = form_new_preserves(ir[SSAValue(useidx)][:inst]::Expr, + intermediaries, new_preserves) end @label skip @@ -1027,25 +1472,18 @@ function canonicalize_typeassert!(compact::IncrementalCompact, idx::Int, stmt::E NewInstruction( PiNode(stmt.args[2], compact.result[idx][:type]), compact.result[idx][:type], - compact.result[idx][:line]), true) + compact.result[idx][:line]), #=reverse_affinity=#true) compact.ssa_rename[compact.idx-1] = pi end -function adce_erase!(phi_uses::Vector{Int}, extra_worklist::Vector{Int}, compact::IncrementalCompact, idx::Int) +function adce_erase!(phi_uses::Vector{Int}, extra_worklist::Vector{Int}, compact::IncrementalCompact, idx::Int, in_worklist::Bool) # return whether this made a change if isa(compact.result[idx][:inst], PhiNode) - return maybe_erase_unused!(extra_worklist, compact, idx, val::SSAValue -> phi_uses[val.id] -= 1) - else - return maybe_erase_unused!(extra_worklist, compact, idx) - end -end - -function count_uses(@nospecialize(stmt), uses::Vector{Int}) - for ur in userefs(stmt) - use = ur[] - if isa(use, SSAValue) - uses[use.id] += 1 + return maybe_erase_unused!(compact, idx, in_worklist, extra_worklist) do val::SSAValue + phi_uses[val.id] -= 1 end + else + return maybe_erase_unused!(compact, idx, in_worklist, extra_worklist) end end @@ -1058,7 +1496,7 @@ function mark_phi_cycles!(compact::IncrementalCompact, safe_phis::SPCSet, phi::I for ur in userefs(compact.result[phi][:inst]) val = ur[] isa(val, SSAValue) || continue - isa(compact[val], PhiNode) || continue + isa(compact[val][:inst], PhiNode) || continue (val.id in safe_phis) && continue push!(worklist, val.id) end @@ -1072,7 +1510,32 @@ end function is_union_phi(compact::IncrementalCompact, idx::Int) inst = compact.result[idx] - return isa(inst[:inst], PhiNode) && is_some_union(inst[:type]) + isa(inst[:inst], PhiNode) || return false + return is_some_union(inst[:type]) +end + +function kill_phi!(compact::IncrementalCompact, phi_uses::Vector{Int}, + to_drop::Union{Vector{Int}, UnitRange{Int}}, + ssa::SSAValue, phi::PhiNode, delete_inst::Bool = false) + for d in to_drop + if isassigned(phi.values, d) + val = phi.values[d] + if !delete_inst + # Deleting the inst will update compact's use count, so + # don't do it here. + kill_current_use!(compact, val) + end + if isa(val, SSAValue) + phi_uses[val.id] -= 1 + end + end + end + if delete_inst + compact[ssa] = nothing + elseif !isempty(to_drop) + deleteat!(phi.values, to_drop) + deleteat!(phi.edges, to_drop) + end end """ @@ -1094,7 +1557,8 @@ Also note that currently this pass _needs_ to run after `sroa_pass!`, because the `typeassert` elimination depends on the transformation by `canonicalize_typeassert!` done within `sroa_pass!` which redirects references of `typeassert`ed value to the corresponding `PiNode`. """ -function adce_pass!(ir::IRCode) +function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) + 𝕃ₒ = inlining === nothing ? OptimizerLattice() : optimizer_lattice(inlining.interp) phi_uses = fill(0, length(ir.stmts) + length(ir.new_nodes)) all_phis = Int[] unionphis = Pair{Int,Any}[] # sorted @@ -1111,7 +1575,7 @@ function adce_pass!(ir::IRCode) r = searchsorted(unionphis, val.id; by = first) if !isempty(r) unionphi = unionphis[first(r)] - t = tmerge(unionphi[2], stmt.typ) + t = tmerge(𝕃ₒ, unionphi[2], stmt.typ) unionphis[first(r)] = Pair{Int,Any}(unionphi[1], t) end end @@ -1119,7 +1583,7 @@ function adce_pass!(ir::IRCode) if is_known_call(stmt, typeassert, compact) && length(stmt.args) == 3 # nullify safe `typeassert` calls ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact)) - if isexact && argextype(stmt.args[2], compact) ⊑ ty + if isexact && ⊑(𝕃ₒ, argextype(stmt.args[2], compact), ty) compact[idx] = nothing continue end @@ -1148,7 +1612,7 @@ function adce_pass!(ir::IRCode) if !isempty(r) unionphi = unionphis[first(r)] unionphis[first(r)] = Pair{Int,Any}(unionphi[1], - tmerge(unionphi[2], inst[:type])) + tmerge(𝕃ₒ, unionphi[2], inst[:type])) end end end @@ -1160,16 +1624,18 @@ function adce_pass!(ir::IRCode) phi = unionphi[1] t = unionphi[2] if t === Union{} - compact.result[phi][:inst] = nothing + stmt = compact[SSAValue(phi)][:inst]::PhiNode + kill_phi!(compact, phi_uses, 1:length(stmt.values), SSAValue(phi), stmt, true) continue elseif t === Any continue - elseif compact.result[phi][:type] ⊑ t + elseif ⊑(𝕃ₒ, compact.result[phi][:type], t) continue end to_drop = Int[] - stmt = compact[phi] + stmt = compact[SSAValue(phi)][:inst] stmt === nothing && continue + stmt = stmt::PhiNode for i = 1:length(stmt.values) if !isassigned(stmt.values, i) # Should be impossible to have something used only by PiNodes that's undef @@ -1180,19 +1646,17 @@ function adce_pass!(ir::IRCode) end end compact.result[phi][:type] = t - isempty(to_drop) && continue - deleteat!(stmt.values, to_drop) - deleteat!(stmt.edges, to_drop) + kill_phi!(compact, phi_uses, to_drop, SSAValue(phi), stmt, false) 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) + adce_erase!(phi_uses, extra_worklist, compact, idx, false) end while !isempty(extra_worklist) - adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist)) + adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) end # Go back and erase any phi cycles changed = true @@ -1211,7 +1675,7 @@ function adce_pass!(ir::IRCode) end end while !isempty(extra_worklist) - if adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist)) + if adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) changed = true end end @@ -1230,8 +1694,8 @@ function type_lift_pass!(ir::IRCode) # a phi node (or an UpsilonNode() argument to a PhiC node), so lift # all these nodes that have maybe undef values val = stmt.args[(stmt.head === :isdefined) ? 1 : 2] - if stmt.head === :isdefined && (val isa Slot || val isa GlobalRef || - isexpr(val, :static_parameter) || val isa Argument || val isa Symbol) + if stmt.head === :isdefined && (val isa GlobalRef || isexpr(val, :static_parameter) || + val isa Argument || val isa Symbol) # this is a legal node, so assume it was not introduced by # slot2ssa (at worst, we might leave in a runtime check that # shouldn't have been there) @@ -1287,6 +1751,8 @@ function type_lift_pass!(ir::IRCode) first = false end local id::Int = 0 + all_same = true + local last_val for i = 1:length(values) if !isassigned(def.values, i) val = false @@ -1310,13 +1776,15 @@ function type_lift_pass!(ir::IRCode) end else while isa(node, PiNode) - id = node.val.id + id = (node.val::SSAValue).id node = insts[id][:inst] end if isa(node, Union{PhiNode, PhiCNode}) if haskey(processed, id) val = processed[id] else + # TODO: Re-check after convergence whether all the values are the same + all_same = false push!(worklist, (id, up_id, new_phi::SSAValue, i)) continue end @@ -1327,17 +1795,26 @@ function type_lift_pass!(ir::IRCode) end end if isa(def, PhiNode) + if !@isdefined(last_val) + last_val = val + elseif all_same + all_same &= last_val === val + end values[i] = val else values[i] = insert_node!(ir, up_id, NewInstruction(UpsilonNode(val), Bool)) end end + if all_same && @isdefined(last_val) + # Decay the PhiNode back to the single value + ir[new_phi][:inst] = last_val + isa(last_val, Bool) && (processed[item] = last_val) + end if which !== SSAValue(0) phi = ir[which][:inst] if isa(phi, PhiNode) phi.values[use] = new_phi - else - phi = phi::PhiCNode + elseif isa(phi, PhiCNode) phi.values[use] = insert_node!(ir, w_up_id, NewInstruction(UpsilonNode(new_phi), Bool)) end end @@ -1364,6 +1841,8 @@ end # TODO: This is terrible, we should change the IR for GotoIfNot to gain an else case 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 @@ -1372,21 +1851,45 @@ function is_legal_bb_drop(ir::IRCode, bbidx::Int, bb::BasicBlock) stmt === nothing && return true ((stmt::GotoNode).label == bbidx + 1) && return true end - # Otherwise make sure we're not the fallthrough case of any predecessor - for pred in bb.preds - if pred == bbidx - 1 - terminator = ir[SSAValue(first(bb.stmts)-1)][:inst] - if isa(terminator, GotoIfNot) - if terminator.dest != bbidx - return false - end + return true +end + +function legalize_bb_drop_pred!(ir::IRCode, bb::BasicBlock, bbidx::Int, bbs::Vector{BasicBlock}, dropped_bbs::Vector{Int}) + (bbidx-1) in bb.preds || return true + last_fallthrough = bbidx-1 + dbi = length(dropped_bbs) + while dbi != 0 && dropped_bbs[dbi] == last_fallthrough && (last_fallthrough-1 in bbs[last_fallthrough].preds) + last_fallthrough -= 1 + dbi -= 1 + end + last_fallthrough_term_ssa = SSAValue(last(bbs[last_fallthrough].stmts)) + terminator = ir[last_fallthrough_term_ssa][:inst] + if isa(terminator, GotoIfNot) + if terminator.dest != bbidx + # The previous terminator's destination matches our fallthrough. + # If we're also a fallthrough terminator, then we just have + # to delete the GotoIfNot. + our_terminator = ir[SSAValue(last(bb.stmts))][:inst] + if terminator.dest != (isa(our_terminator, GotoNode) ? our_terminator.label : bbidx + 1) + return false end - break 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 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 + # update it. + bbs[last_fallthrough] = BasicBlock(first(bbs[last_fallthrough].stmts):last(bb.stmts), bbs[last_fallthrough].preds, bbs[last_fallthrough].succs) return true end +is_terminator(@nospecialize(inst)) = isa(inst, GotoNode) || isa(inst, GotoIfNot) || isexpr(inst, :enter) + function cfg_simplify!(ir::IRCode) bbs = ir.cfg.blocks merge_into = zeros(Int, length(bbs)) @@ -1404,28 +1907,41 @@ function cfg_simplify!(ir::IRCode) 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] + end + return pred + end # 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 + 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))][:inst], :enter) + 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 merge_into[succ] = idx merged_succ[idx] = succ end - elseif is_bb_empty(ir, bb) && is_legal_bb_drop(ir, idx, bb) + elseif merge_into[idx] == 0 && is_bb_empty(ir, bb) && is_legal_bb_drop(ir, idx, bb) # 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] for idx in bbs[succ].stmts stmt = ir[SSAValue(idx)][:inst] stmt === nothing && continue isa(stmt, PhiNode) || break for edge in stmt.edges - for pred in bb.preds + edge = ascend_eliminated_preds(edge) + for pred in preds if pred == edge found_interference = true @goto done @@ -1434,54 +1950,74 @@ function cfg_simplify!(ir::IRCode) end end @label done - if !found_interference - push!(dropped_bbs, idx) - end + found_interference && continue + legalize_bb_drop_pred!(ir, bb, idx, bbs, dropped_bbs) || continue + push!(dropped_bbs, idx) end end end - # Assign new BB numbers + # Assign new BB numbers in DFS order, dropping unreachable blocks max_bb_num = 1 - bb_rename_succ = zeros(Int, length(bbs)) - for i = 1:length(bbs) + bb_rename_succ = fill(0, 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 end - # Drop blocks with no predecessors - if i != 1 && length(ir.cfg.blocks[i].preds) == 0 - bb_rename_succ[i] = -1 - end # Mark dropped blocks for fixup if !isempty(searchsorted(dropped_bbs, i)) - bb_rename_succ[i] = -bbs[i].succs[1] + succ = bbs[i].succs[1] + push!(worklist, succ) + bb_rename_succ[i] = -succ end - bb_rename_succ[i] != 0 && continue - - curr = i - while true - bb_rename_succ[curr] = max_bb_num - max_bb_num += 1 - # Now walk the chain of blocks we merged. - # If we end in something that may fall through, - # we have to schedule that block next - curr = follow_merged_succ(curr) - terminator = ir.stmts[ir.cfg.blocks[curr].stmts[end]][:inst] - if isa(terminator, GotoNode) || isa(terminator, ReturnNode) - break + if bb_rename_succ[i] == 0 + curr = i + while true + @assert bb_rename_succ[curr] == 0 + bb_rename_succ[curr] = max_bb_num + max_bb_num += 1 + # Now walk the chain of blocks we merged. + # If we end in something that may fall through, + # we have to schedule that block next + while merged_succ[curr] != 0 + if bb_rename_succ[curr] == 0 + bb_rename_succ[curr] = -1 + end + curr = merged_succ[curr] + end + terminator = ir.stmts[ir.cfg.blocks[curr].stmts[end]][:inst] + if isa(terminator, GotoNode) || isa(terminator, ReturnNode) + break + elseif isa(terminator, GotoIfNot) + 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]) + end + end + ncurr = curr + 1 + while !isempty(searchsorted(dropped_bbs, ncurr)) + bb_rename_succ[ncurr] = -bbs[ncurr].succs[1] + ncurr += 1 + end + curr = ncurr end - curr += 1 - if !isempty(searchsorted(dropped_bbs, curr)) - break + + for succ in bbs[curr].succs + if bb_rename_succ[succ] == 0 + push!(worklist, succ) + end end end end - # Compute map from new to old blocks - result_bbs = Int[findfirst(j->i==j, bb_rename_succ) for i = 1:max_bb_num-1] - # Fixup dropped BBs resolved_all = false while !resolved_all @@ -1500,8 +2036,24 @@ function cfg_simplify!(ir::IRCode) end end - # Figure out how predecessors should be renamed + # Drop remaining unvisited bbs 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_pred[i] = -2 + end + end + + # Compute map from new to old blocks + result_bbs = zeros(Int, max_bb_num-1) + for (o, bb) in enumerate(bb_rename_succ) + bb > 0 || continue + isempty(searchsorted(dropped_bbs, o)) || continue + result_bbs[bb] = o + end + + # Figure out how predecessors should be renamed for i = 1:length(bbs) if merged_succ[i] != 0 # Block `i` should no longer be a predecessor (before renaming) @@ -1510,11 +2062,32 @@ function cfg_simplify!(ir::IRCode) continue end pred = i + is_unreachable = false + is_multi = false while pred !== 1 && !isempty(searchsorted(dropped_bbs, pred)) - pred = bbs[pred].preds[1] + preds = bbs[pred].preds + if length(preds) == 0 + is_unreachable = true + break + elseif length(preds) > 1 + # This block has multiple predecessors - the only way this is + # legal is if we proved above that our successors don't have + # any phi nodes that would interfere with the renaming. Mark + # this specially. + is_multi = true + break + end + @assert length(preds) == 1 + pred = preds[1] + end + if is_unreachable + @assert bb_rename_pred[i] == -2 + elseif is_multi + bb_rename_pred[i] = -3 + else + bbnum = follow_merge_into(pred) + bb_rename_pred[i] = bb_rename_succ[bbnum] end - bbnum = follow_merge_into(pred) - bb_rename_pred[i] = bb_rename_succ[bbnum] end # Compute new block lengths @@ -1548,7 +2121,24 @@ function cfg_simplify!(ir::IRCode) function compute_preds(i) orig_bb = result_bbs[i] preds = bbs[orig_bb].preds - return Int[bb_rename_pred[pred] for pred in preds] + res = Int[] + function scan_preds!(preds) + 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[ @@ -1565,8 +2155,10 @@ function cfg_simplify!(ir::IRCode) @assert length(new_bb.succs) <= 2 length(new_bb.succs) <= 1 && continue if new_bb.succs[1] == new_bb.succs[2] - terminator = ir[SSAValue(last(bbs[old_bb].stmts))] - @assert isa(terminator[:inst], GotoIfNot) + old_bb2 = findfirst(x->x==bbidx, bb_rename_pred) + terminator = ir[SSAValue(last(bbs[old_bb2].stmts))] + @assert terminator[:inst] isa GotoIfNot + # N.B.: The dest will be renamed in process_node! below terminator[:inst] = GotoNode(terminator[:inst].dest) pop!(new_bb.succs) new_succ = cresult_bbs[new_bb.succs[1]] @@ -1579,14 +2171,10 @@ function cfg_simplify!(ir::IRCode) end end - compact = IncrementalCompact(ir, true) # 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.fold_constant_branches = false - compact.bb_rename_succ = bb_rename_succ - compact.bb_rename_pred = bb_rename_pred - compact.result_bbs = cresult_bbs + compact = IncrementalCompact(ir, CFGTransformState(true, false, cresult_bbs, bb_rename_pred, bb_rename_succ)) result_idx = 1 for (idx, orig_bb) in enumerate(result_bbs) ms = orig_bb @@ -1597,8 +2185,65 @@ function cfg_simplify!(ir::IRCode) if isa(node[:inst], GotoNode) && merged_succ[ms] != 0 # If we merged a basic block, we need remove the trailing GotoNode (if any) compact.result[compact.result_idx][:inst] = nothing + elseif isa(node[:inst], PhiNode) + phi = node[:inst] + 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) + edges = Int32[] + values = Any[] + sizehint!(edges, length(phi.edges)); sizehint!(values, length(renamed_values)) + for old_index in 1:length(phi.edges) + old_edge = phi.edges[old_index] + new_edge = bb_rename_pred[old_edge] + if new_edge > 0 + push!(edges, new_edge) + if isassigned(renamed_values, old_index) + push!(values, renamed_values[old_index]) + else + resize!(values, length(values)+1) + 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) + append!(edges, all_new_preds) + if isassigned(renamed_values, old_index) + val = renamed_values[old_index] + for _ in 1:length(all_new_preds) + push!(values, val) + end + length(all_new_preds) == 0 && kill_current_use!(compact, val) + for _ in 2:length(all_new_preds) + count_added_node!(compact, val) + end + else + resize!(values, length(values)+length(all_new_preds)) + end + else + isassigned(renamed_values, old_index) && kill_current_use!(compact, renamed_values[old_index]) + end + end + compact.result[compact.result_idx][:inst] = PhiNode(edges, values) else - process_node!(compact, compact.result_idx, node, i, i, ms, true) + ri = process_node!(compact, compact.result_idx, node, i, i, ms, true) + if ri == compact.result_idx + # process_node! wanted this statement dropped. We don't do this, + # but we still need to erase the node + compact.result[compact.result_idx][:inst] = nothing + end end # We always increase the result index to ensure a predicatable # placement of the resulting nodes. @@ -1607,6 +2252,7 @@ function cfg_simplify!(ir::IRCode) ms = merged_succ[ms] end end + compact.idx = length(ir.stmts) compact.active_result_bb = length(bb_starts) return finish(compact) end diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 1e98dda039040..b420eb32b1205 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -1,5 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# This file is not loaded into `Core.Compiler` but rather loaded into the context of +# `Base.IRShow` and thus does not participate in bootstrapping. + @nospecialize if Pair != Base.Pair @@ -10,13 +13,21 @@ length(s::String) = Base.length(s) end import Base: show_unquoted -using Base: printstyled, with_output_color, prec_decl +using Base: printstyled, with_output_color, prec_decl, @invoke function Base.show(io::IO, cfg::CFG) + print(io, "CFG with $(length(cfg.blocks)) blocks:") for (idx, block) in enumerate(cfg.blocks) - print(io, idx, "\t=>\t") - join(io, block.succs, ", ") - println(io) + print(io, "\n bb ", idx) + if block.stmts.start == block.stmts.stop + print(io, " (stmt ", block.stmts.start, ")") + else + print(io, " (stmts ", block.stmts.start, ":", block.stmts.stop, ")") + end + if !isempty(block.succs) + print(io, " → bb ") + join(io, block.succs, ", ") + end end end @@ -37,7 +48,7 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxleng print(io, ", ") print(io, stmt.typ) print(io, ")") - elseif isexpr(stmt, :invoke) + elseif isexpr(stmt, :invoke) && length(stmt.args) >= 2 && isa(stmt.args[1], MethodInstance) stmt = stmt::Expr # TODO: why is this here, and not in Base.show_unquoted print(io, "invoke ") @@ -154,15 +165,23 @@ function should_print_ssa_type(@nospecialize node) !isa(node, QuoteNode) end -function default_expr_type_printer(io::IO, @nospecialize(typ), used::Bool) - printstyled(io, "::", typ, color=(used ? :cyan : :light_black)) - nothing +function default_expr_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...) + show_type || return nothing + printstyled(io, "::", type, color=(used ? :cyan : :light_black)) + return nothing end -normalize_method_name(m::Method) = m.name -normalize_method_name(m::MethodInstance) = (m.def::Method).name -normalize_method_name(m::Symbol) = m -normalize_method_name(m) = Symbol("") +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) # converts the linetable for line numbers @@ -329,8 +348,7 @@ function compute_ir_line_annotations(code::IRCode) return (loc_annotations, loc_methods, loc_lineno) end -Base.show(io::IO, code::IRCode) = show_ir(io, code) - +Base.show(io::IO, code::Union{IRCode, IncrementalCompact}) = show_ir(io, code) lineinfo_disabled(io::IO, linestart::String, idx::Int) = "" @@ -495,14 +513,18 @@ function DILineInfoPrinter(linetable::Vector, showtypes::Bool=false) return emit_lineinfo_update end -# line_info_preprinter(io::IO, indent::String, idx::Int) may print relevant info -# at the beginning of the line, and should at least print `indent`. It returns a -# string that will be printed after the final basic-block annotation. -# line_info_postprinter(io::IO, typ, used::Bool) prints the type-annotation at the end -# of the statement -# should_print_stmt(idx::Int) -> Bool: whether the statement at index `idx` should be -# printed as part of the IR or not -# bb_color: color used for printing the basic block brackets on the left +""" + IRShowConfig + +- `line_info_preprinter(io::IO, indent::String, idx::Int)`` may print relevant info + at the beginning of the line, and should at least print `indent`. It returns a + string that will be printed after the final basic-block annotation. +- `line_info_postprinter(io::IO; type, used::Bool, show_type::Bool, idx::Int)` prints + relevant information like type-annotation at the end of the statement +- `should_print_stmt(idx::Int) -> Bool`: whether the statement at index `idx` should be + printed as part of the IR or not +- `bb_color`: color used for printing the basic block brackets on the left +""" struct IRShowConfig line_info_preprinter line_info_postprinter @@ -522,6 +544,10 @@ function _stmt(code::IRCode, idx::Int) stmts = code.stmts return isassigned(stmts.inst, idx) ? stmts[idx][:inst] : UNDEF end +function _stmt(compact::IncrementalCompact, idx::Int) + stmts = compact.result + return isassigned(stmts.inst, idx) ? stmts[idx][:inst] : UNDEF +end function _stmt(code::CodeInfo, idx::Int) code = code.code return isassigned(code, idx) ? code[idx] : UNDEF @@ -531,6 +557,10 @@ function _type(code::IRCode, idx::Int) stmts = code.stmts return isassigned(stmts.type, idx) ? stmts[idx][:type] : UNDEF end +function _type(compact::IncrementalCompact, idx::Int) + stmts = compact.result + return isassigned(stmts.type, idx) ? stmts[idx][:type] : UNDEF +end function _type(code::CodeInfo, idx::Int) types = code.ssavaluetypes types isa Vector{Any} || return nothing @@ -556,17 +586,18 @@ end # Show a single statement, code.stmts[idx]/code.code[idx], in the context of the whole IRCode/CodeInfo. # Returns the updated value of bb_idx. -# pop_new_node!(idx::Int) -> (node_idx, new_node_inst, new_node_type) may return a new -# node at the current index `idx`, which is printed before the statement at index -# `idx`. This function is repeatedly called until it returns `nothing` -function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, config::IRShowConfig, - used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing)) +# pop_new_node!(idx::Int; attach_after=false) -> (node_idx, new_node_inst, new_node_type) +# may return a new node at the current index `idx`, which is printed before the statement +# at index `idx`. This function is repeatedly called until it returns `nothing`. +# to iterate nodes that are to be inserted after the statement, set `attach_after=true`. +function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo, IncrementalCompact}, idx::Int, config::IRShowConfig, + used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing), only_after::Bool=false) return show_ir_stmt(io, code, idx, config.line_info_preprinter, config.line_info_postprinter, - used, cfg, bb_idx; pop_new_node!, config.bb_color) + used, cfg, bb_idx; pop_new_node!, only_after, config.bb_color) end -function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info_preprinter, line_info_postprinter, - used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing), bb_color=:light_black) +function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo, IncrementalCompact}, idx::Int, line_info_preprinter, line_info_postprinter, + used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing), only_after::Bool=false, bb_color=:light_black) stmt = _stmt(code, idx) type = _type(code, idx) max_bb_idx_size = length(string(length(cfg.blocks))) @@ -586,8 +617,7 @@ function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info end i = 1 - while true - next = pop_new_node!(idx) + function print_indentation(final::Bool=true) # Compute BB guard rail if bb_idx > length(cfg.blocks) # If invariants are violated, print a special leader @@ -596,7 +626,6 @@ function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info printstyled(io, "!!! ", "─"^max_bb_idx_size, color=bb_color) else bbrange = cfg.blocks[bb_idx].stmts - bbrange = bbrange.start:bbrange.stop # Print line info update linestart = idx == first(bbrange) ? " " : sprint(io -> printstyled(io, "│ ", color=bb_color), context=io) linestart *= " "^max_bb_idx_size @@ -609,24 +638,20 @@ function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info bb_pad = max_bb_idx_size - length(bb_idx_str) bb_type = length(cfg.blocks[bb_idx].preds) <= 1 ? "─" : "┄" printstyled(io, bb_idx_str, " ", bb_type, "─"^bb_pad, color=bb_color) - elseif next === nothing && idx == last(bbrange) # print separator + elseif final && idx == last(bbrange) # print separator printstyled(io, "└", "─"^(1 + max_bb_idx_size), color=bb_color) else printstyled(io, "│ ", " "^max_bb_idx_size, color=bb_color) end end print(io, inlining_indent, " ") + end - if next === nothing - if bb_idx <= length(cfg.blocks) && idx == last(bbrange) - bb_idx += 1 - end - break - end - - # print new nodes first in the right position - node_idx, new_node_inst, new_node_type = next + # first, print new nodes that are to be inserted before the current statement + function print_new_node(node; final::Bool=true) + print_indentation(final) + node_idx, new_node_inst, new_node_type = node @assert new_node_inst !== UNDEF # we filtered these out earlier show_type = should_print_ssa_type(new_node_inst) let maxlength_idx=maxlength_idx, show_type=show_type @@ -637,44 +662,88 @@ function show_ir_stmt(io::IO, code::Union{IRCode, CodeInfo}, idx::Int, line_info if new_node_type === UNDEF # try to be robust against errors printstyled(io, "::#UNDEF", color=:red) - elseif show_type - line_info_postprinter(IOContext(io, :idx => node_idx), new_node_type, node_idx in used) + else + line_info_postprinter(io; type = new_node_type, used = node_idx in used, show_type, idx = node_idx) end println(io) + end + while (next = pop_new_node!(idx)) !== nothing + only_after || print_new_node(next; final=false) i += 1 end - if code isa CodeInfo - stmt = statement_indices_to_labels(stmt, cfg) + + # peek at the nodes to be inserted after the current statement + # (to determine of the statement itself is the final one) + next = pop_new_node!(idx; attach_after=true) + + # then, print the current statement + # FIXME: `only_after` is hack so that we can call this function to print uncompacted + # attach-after nodes when the current node has already been compated already + if !only_after + print_indentation(next===nothing) + if code isa CodeInfo + stmt = statement_indices_to_labels(stmt, cfg) + end + show_type = type !== nothing && should_print_ssa_type(stmt) + print_stmt(io, idx, stmt, used, maxlength_idx, true, show_type) + if type !== nothing # ignore types for pre-inference code + if type === UNDEF + # This is an error, but can happen if passes don't update their type information + printstyled(io, "::#UNDEF", color=:red) + else + line_info_postprinter(io; type, used = idx in used, show_type, idx) + end + end + println(io) + end + i += 1 + + # finally, print new nodes that are to be inserted after the current statement + while next !== nothing + print_new_node(next) + i += 1 + next = pop_new_node!(idx; attach_after=true) end - show_type = type !== nothing && should_print_ssa_type(stmt) - print_stmt(io, idx, stmt, used, maxlength_idx, true, show_type) - if type !== nothing # ignore types for pre-inference code - if type === UNDEF - # This is an error, but can happen if passes don't update their type information - printstyled(io, "::#UNDEF", color=:red) - elseif show_type - line_info_postprinter(IOContext(io, :idx => idx), type, idx in used) + + # increment the basic block counter + if bb_idx <= length(cfg.blocks) + bbrange = cfg.blocks[bb_idx].stmts + if bb_idx <= length(cfg.blocks) && idx == last(bbrange) + bb_idx += 1 end end - println(io) + return bb_idx end -function ircode_new_nodes_iter(code::IRCode) - stmts = code.stmts - new_nodes = code.new_nodes.stmts - new_nodes_info = code.new_nodes.info +function _new_nodes_iter(stmts, new_nodes, new_nodes_info, new_nodes_idx) new_nodes_perm = filter(i -> isassigned(new_nodes.inst, i), 1:length(new_nodes)) sort!(new_nodes_perm, by = x -> (x = new_nodes_info[x]; (x.pos, x.attach_after))) - perm_idx = Ref(1) - function (idx::Int) - perm_idx[] <= length(new_nodes_perm) || return nothing - node_idx = new_nodes_perm[perm_idx[]] - if new_nodes_info[node_idx].pos != idx + # separate iterators for the nodes that are inserted before resp. after each statement + before_iter = Ref(1) + after_iter = Ref(1) + + return function get_new_node(idx::Int; attach_after=false) + iter = attach_after ? after_iter : before_iter + iter[] <= length(new_nodes_perm) || return nothing + node_idx = new_nodes_perm[iter[]] + + # skip nodes + while node_idx < new_nodes_idx || # already compacted + idx > new_nodes_info[node_idx].pos || # not interested in + new_nodes_info[node_idx].attach_after != attach_after + iter[] += 1 + iter[] > length(new_nodes_perm) && return nothing + node_idx = new_nodes_perm[iter[]] + end + + if new_nodes_info[node_idx].pos != idx || + new_nodes_info[node_idx].attach_after != attach_after return nothing end - perm_idx[] += 1 + + iter[] += 1 new_node = new_nodes[node_idx] new_node_inst = isassigned(new_nodes.inst, node_idx) ? new_node[:inst] : UNDEF new_node_type = isassigned(new_nodes.type, node_idx) ? new_node[:type] : UNDEF @@ -683,6 +752,20 @@ function ircode_new_nodes_iter(code::IRCode) end end +function new_nodes_iter(ir::IRCode, new_nodes_idx=1) + stmts = ir.stmts + new_nodes = ir.new_nodes.stmts + new_nodes_info = ir.new_nodes.info + return _new_nodes_iter(stmts, new_nodes, new_nodes_info, new_nodes_idx) +end + +function new_nodes_iter(compact::IncrementalCompact) + stmts = compact.result + new_nodes = compact.new_new_nodes.stmts + new_nodes_info = compact.new_new_nodes.info + return _new_nodes_iter(stmts, new_nodes, new_nodes_info, 1) +end + # print only line numbers on the left, some of the method names and nesting depth on the right function inline_linfo_printer(code::IRCode) loc_annotations, loc_methods, loc_lineno = compute_ir_line_annotations(code) @@ -710,7 +793,7 @@ function inline_linfo_printer(code::IRCode) end # Print location information right aligned. If the line below is too long, it'll overwrite this, # but that's what we want. - if get(io, :color, false) + if get(io, :color, false)::Bool method_start_column = cols - max_method_width - max_loc_width - 2 filler = " "^(max_loc_width-length(annotation)) printstyled(io, "\e[$(method_start_column)G$(annotation)$(filler)$(loc_method)\e[1G", color = :light_black) @@ -720,7 +803,7 @@ function inline_linfo_printer(code::IRCode) end end -_strip_color(s::String) = replace(s, r"\e\[\d+m" => "") +_strip_color(s::String) = replace(s, r"\e\[\d+m"a => "") function statementidx_lineinfo_printer(f, code::IRCode) printer = f(code.linetable) @@ -770,39 +853,176 @@ function default_config(code::IRCode; verbose_linetable=false) end default_config(code::CodeInfo) = IRShowConfig(statementidx_lineinfo_printer(code)) -function show_ir(io::IO, code::Union{IRCode, CodeInfo}, config::IRShowConfig=default_config(code); - pop_new_node! = code isa IRCode ? ircode_new_nodes_iter(code) : Returns(nothing)) - stmts = code isa IRCode ? code.stmts : code.code - used = stmts_used(io, code) - cfg = code isa IRCode ? code.cfg : compute_basic_blocks(stmts) - bb_idx = 1 - - for idx in 1:length(stmts) - if config.should_print_stmt(code, idx, used) - bb_idx = show_ir_stmt(io, code, idx, config, used, cfg, bb_idx; pop_new_node!) +function show_ir_stmts(io::IO, ir::Union{IRCode, CodeInfo, IncrementalCompact}, inds, config::IRShowConfig, + used::BitSet, cfg::CFG, bb_idx::Int; pop_new_node! = Returns(nothing)) + for idx in inds + if config.should_print_stmt(ir, idx, used) + bb_idx = show_ir_stmt(io, ir, idx, config, used, cfg, bb_idx; pop_new_node!) elseif bb_idx <= length(cfg.blocks) && idx == cfg.blocks[bb_idx].stmts.stop bb_idx += 1 end end + return bb_idx +end +function finish_show_ir(io::IO, cfg::CFG, config::IRShowConfig) max_bb_idx_size = length(string(length(cfg.blocks))) config.line_info_preprinter(io, " "^(max_bb_idx_size + 2), 0) - nothing + return nothing +end + +function show_ir(io::IO, ir::IRCode, config::IRShowConfig=default_config(ir); + pop_new_node! = new_nodes_iter(ir)) + used = stmts_used(io, ir) + cfg = ir.cfg + maxssaid = length(ir.stmts) + Core.Compiler.length(ir.new_nodes) + let io = IOContext(io, :maxssaid=>maxssaid) + show_ir_stmts(io, ir, 1:length(ir.stmts), config, used, cfg, 1; pop_new_node!) + end + finish_show_ir(io, cfg, config) +end + +function show_ir(io::IO, ci::CodeInfo, config::IRShowConfig=default_config(ci); + pop_new_node! = Returns(nothing)) + used = stmts_used(io, ci) + cfg = compute_basic_blocks(ci.code) + let io = IOContext(io, :maxssaid=>length(ci.code)) + show_ir_stmts(io, ci, 1:length(ci.code), config, used, cfg, 1; pop_new_node!) + end + finish_show_ir(io, cfg, config) end -tristate_letter(t::TriState) = t === ALWAYS_TRUE ? '+' : t === ALWAYS_FALSE ? '!' : '?' -tristate_color(t::TriState) = t === ALWAYS_TRUE ? :green : t === ALWAYS_FALSE ? :red : :orange +function show_ir(io::IO, compact::IncrementalCompact, config::IRShowConfig=default_config(compact.ir)) + cfg = compact.ir.cfg + + + # First print everything that has already been compacted + + # merge uses in uncompacted region into compacted uses + used_compacted = BitSet(i for (i, x) in pairs(compact.used_ssas) if x != 0) + used_uncompacted = stmts_used(io, compact.ir) + for (i, ssa) = enumerate(compact.ssa_rename) + if isa(ssa, SSAValue) && ssa.id in used_uncompacted + push!(used_compacted, i) + end + end + + # while compacting, the end of the active result bb will not have been determined + # (this is done post-hoc by `finish_current_bb!`), so determine it here from scratch. + result_bbs = copy(compact.cfg_transform.result_bbs) + if compact.active_result_bb <= length(result_bbs) + # count the total number of nodes we'll add to this block + input_bb_idx = block_for_inst(compact.ir.cfg, compact.idx) + input_bb = compact.ir.cfg.blocks[input_bb_idx] + count = 0 + for input_idx in input_bb.stmts.start:input_bb.stmts.stop + pop_new_node! = new_nodes_iter(compact.ir) + while pop_new_node!(input_idx) !== nothing + count += 1 + end + while pop_new_node!(input_idx; attach_after=true) !== nothing + count += 1 + end + end + + still_to_be_inserted = (last(input_bb.stmts) - compact.idx) + count + + result_bb = result_bbs[compact.active_result_bb] + result_bbs[compact.active_result_bb] = Core.Compiler.BasicBlock(result_bb, + Core.Compiler.StmtRange(first(result_bb.stmts), compact.result_idx+still_to_be_inserted)) + end + compact_cfg = CFG(result_bbs, Int[first(result_bbs[i].stmts) for i in 2:length(result_bbs)]) + + pop_new_node! = new_nodes_iter(compact) + maxssaid = length(compact.result) + Core.Compiler.length(compact.new_new_nodes) + bb_idx = let io = IOContext(io, :maxssaid=>maxssaid) + show_ir_stmts(io, compact, 1:compact.result_idx-1, config, used_compacted, + compact_cfg, 1; pop_new_node!) + end -function Base.show(io::IO, e::Core.Compiler.Effects) + + # Print uncompacted nodes from the original IR + + # print a separator + (_, width) = displaysize(io) + stmts = compact.ir.stmts + indent = length(string(length(stmts))) + # config.line_info_preprinter(io, "", compact.idx) + printstyled(io, "─"^(width-indent-1), '\n', color=:red) + + # while compacting, the start of the active uncompacted bb will have been overwritten. + # this manifests as a stmt range end that is less than the start, so correct that. + inputs_bbs = copy(cfg.blocks) + for (i, bb) in enumerate(inputs_bbs) + if bb.stmts.stop < bb.stmts.start + inputs_bbs[i] = Core.Compiler.BasicBlock(bb, + Core.Compiler.StmtRange(last(bb.stmts), last(bb.stmts))) + # this is not entirely correct, and will result in the bb starting again, + # but is the best we can do without changing how `finish_current_bb!` works. + end + end + uncompacted_cfg = CFG(inputs_bbs, Int[first(inputs_bbs[i].stmts) for i in 2:length(inputs_bbs)]) + + pop_new_node! = new_nodes_iter(compact.ir, compact.new_nodes_idx) + maxssaid = length(compact.ir.stmts) + Core.Compiler.length(compact.ir.new_nodes) + let io = IOContext(io, :maxssaid=>maxssaid) + # first show any new nodes to be attached after the last compacted statement + if compact.idx > 1 + show_ir_stmt(io, compact.ir, compact.idx-1, config, used_uncompacted, + uncompacted_cfg, bb_idx; pop_new_node!, only_after=true) + end + + # then show the actual uncompacted IR + show_ir_stmts(io, compact.ir, compact.idx:length(stmts), config, used_uncompacted, + uncompacted_cfg, bb_idx; pop_new_node!) + end + + finish_show_ir(io, uncompacted_cfg, config) +end + +function effectbits_letter(effects::Effects, name::Symbol, suffix::Char) + ft = fieldtype(Effects, name) + if ft === UInt8 + prefix = getfield(effects, name) === ALWAYS_TRUE ? '+' : + getfield(effects, name) === ALWAYS_FALSE ? '!' : '?' + elseif ft === Bool + prefix = getfield(effects, name) ? '+' : '!' + else + error("unsupported effectbits type given") + end + return string(prefix, suffix) +end + +function effectbits_color(effects::Effects, name::Symbol) + ft = fieldtype(Effects, name) + if ft === UInt8 + color = getfield(effects, name) === ALWAYS_TRUE ? :green : + getfield(effects, name) === ALWAYS_FALSE ? :red : :yellow + elseif ft === Bool + color = getfield(effects, name) ? :green : :red + else + error("unsupported effectbits type given") + end + return color +end + +function Base.show(io::IO, e::Effects) print(io, "(") - printstyled(io, string(tristate_letter(e.consistent), 'c'); color=tristate_color(e.consistent)) + printstyled(io, effectbits_letter(e, :consistent, 'c'); color=effectbits_color(e, :consistent)) + print(io, ',') + printstyled(io, effectbits_letter(e, :effect_free, 'e'); color=effectbits_color(e, :effect_free)) + print(io, ',') + printstyled(io, effectbits_letter(e, :nothrow, 'n'); color=effectbits_color(e, :nothrow)) + print(io, ',') + printstyled(io, effectbits_letter(e, :terminates, 't'); color=effectbits_color(e, :terminates)) print(io, ',') - printstyled(io, string(tristate_letter(e.effect_free), 'e'); color=tristate_color(e.effect_free)) + printstyled(io, effectbits_letter(e, :notaskstate, 's'); color=effectbits_color(e, :notaskstate)) print(io, ',') - printstyled(io, string(tristate_letter(e.nothrow), 'n'); color=tristate_color(e.nothrow)) + printstyled(io, effectbits_letter(e, :inaccessiblememonly, 'm'); color=effectbits_color(e, :inaccessiblememonly)) print(io, ',') - printstyled(io, string(tristate_letter(e.terminates), 't'); color=tristate_color(e.terminates)) + printstyled(io, effectbits_letter(e, :noinbounds, 'i'); color=effectbits_color(e, :noinbounds)) print(io, ')') + e.nonoverlayed || printstyled(io, '′'; color=:red) end @specialize diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 3317db8b55e0b..757fa1b98bedc 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -1,5 +1,13 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +struct TypedSlot + id::Int + typ + TypedSlot(id::Int, @nospecialize(typ)) = new(id, typ) +end + +const UnoptSlot = Union{SlotNumber, TypedSlot} + mutable struct SlotInfo defs::Vector{Int} uses::Vector{Int} @@ -15,24 +23,24 @@ function scan_entry!(result::Vector{SlotInfo}, idx::Int, @nospecialize(stmt)) push!(result[slot_id(stmt.slot)].defs, idx) return elseif isexpr(stmt, :(=)) - if isa(stmt.args[1], SlotNumber) - push!(result[slot_id(stmt.args[1])].defs, idx) + arg1 = stmt.args[1] + if isa(arg1, SlotNumber) + push!(result[slot_id(arg1)].defs, idx) end stmt = stmt.args[2] end - if isa(stmt, Union{SlotNumber, TypedSlot}) + if isa(stmt, UnoptSlot) push!(result[slot_id(stmt)].uses, idx) return end for op in userefs(stmt) val = op[] - if isa(val, Union{SlotNumber, TypedSlot}) + if isa(val, UnoptSlot) push!(result[slot_id(val)].uses, idx) end end end - function scan_slot_def_use(nargs::Int, ci::CodeInfo, code::Vector{Any}) nslots = length(ci.slotflags) result = SlotInfo[SlotInfo() for i = 1:nslots] @@ -62,19 +70,15 @@ function renumber_ssa!(@nospecialize(stmt), ssanums::Vector{SSAValue}, new_ssa:: return ssamap(val->renumber_ssa(val, ssanums, new_ssa), stmt) end -function make_ssa!(ci::CodeInfo, code::Vector{Any}, idx, slot, @nospecialize(typ)) - (idx == 0) && return Argument(slot) +function make_ssa!(ci::CodeInfo, code::Vector{Any}, idx::Int, @nospecialize(typ)) stmt = code[idx] @assert isexpr(stmt, :(=)) code[idx] = stmt.args[2] (ci.ssavaluetypes::Vector{Any})[idx] = typ - idx + return SSAValue(idx) end function new_to_regular(@nospecialize(stmt), new_offset::Int) - if isa(stmt, NewSSAValue) - return SSAValue(stmt.id + new_offset) - end urs = userefs(stmt) for op in urs val = op[] @@ -85,7 +89,7 @@ function new_to_regular(@nospecialize(stmt), new_offset::Int) return urs[] end -function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, @nospecialize(stmt::Union{SlotNumber, TypedSlot}), @nospecialize(ssa)) +function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, stmt::UnoptSlot, @nospecialize(ssa)) # We don't really have the information here to get rid of these. # We'll do so later if ssa === UNDEF_TOKEN @@ -106,34 +110,34 @@ function fixup_slot!(ir::IRCode, ci::CodeInfo, idx::Int, slot::Int, @nospecializ @assert false # unreachable end -function fixemup!(cond, rename, ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt)) - if isa(stmt, Union{SlotNumber, TypedSlot}) && cond(stmt) - return fixup_slot!(ir, ci, idx, slot_id(stmt), stmt, rename(stmt)) +function fixemup!(@specialize(slot_filter), @specialize(rename_slot), ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt)) + if isa(stmt, UnoptSlot) && slot_filter(stmt) + return fixup_slot!(ir, ci, idx, slot_id(stmt), stmt, rename_slot(stmt)) end if isexpr(stmt, :(=)) - stmt.args[2] = fixemup!(cond, rename, ir, ci, idx, stmt.args[2]) + stmt.args[2] = fixemup!(slot_filter, rename_slot, ir, ci, idx, stmt.args[2]) return stmt end if isa(stmt, PhiNode) for i = 1:length(stmt.edges) isassigned(stmt.values, i) || continue val = stmt.values[i] - isa(val, Union{SlotNumber, TypedSlot}) || continue - cond(val) || continue + isa(val, UnoptSlot) || continue + slot_filter(val) || continue bb_idx = block_for_inst(ir.cfg, Int(stmt.edges[i])) from_bb_terminator = last(ir.cfg.blocks[bb_idx].stmts) - stmt.values[i] = fixup_slot!(ir, ci, from_bb_terminator, slot_id(val), val, rename(val)) + stmt.values[i] = fixup_slot!(ir, ci, from_bb_terminator, slot_id(val), val, rename_slot(val)) end return stmt end if isexpr(stmt, :isdefined) val = stmt.args[1] - if isa(val, Union{SlotNumber, TypedSlot}) + if isa(val, UnoptSlot) slot = slot_id(val) if (ci.slotflags[slot] & SLOT_USEDUNDEF) == 0 return true else - ssa = rename(val) + ssa = rename_slot(val) if ssa === UNDEF_TOKEN return false elseif !isa(ssa, SSAValue) && !isa(ssa, NewSSAValue) @@ -148,8 +152,8 @@ function fixemup!(cond, rename, ir::IRCode, ci::CodeInfo, idx::Int, @nospecializ urs = userefs(stmt) for op in urs val = op[] - if isa(val, Union{SlotNumber, TypedSlot}) && cond(val) - x = fixup_slot!(ir, ci, idx, slot_id(val), val, rename(val)) + if isa(val, UnoptSlot) && slot_filter(val) + x = fixup_slot!(ir, ci, idx, slot_id(val), val, rename_slot(val)) # We inserted an undef error node. Delete subsequent statement # to avoid confusing the optimizer if x === UNDEF_TOKEN @@ -157,7 +161,16 @@ function fixemup!(cond, rename, ir::IRCode, ci::CodeInfo, idx::Int, @nospecializ end op[] = x elseif isa(val, GlobalRef) && !(isdefined(val.mod, val.name) && isconst(val.mod, val.name)) - op[] = NewSSAValue(insert_node!(ir, idx, NewInstruction(val, Any)).id - length(ir.stmts)) + op[] = NewSSAValue(insert_node!(ir, idx, + NewInstruction(val, typ_for_val(val, ci, ir.sptypes, idx, Any[]))).id - length(ir.stmts)) + elseif isexpr(val, :static_parameter) + ty = typ_for_val(val, ci, ir.sptypes, idx, Any[]) + if isa(ty, Const) + inst = NewInstruction(quoted(ty.val), ty) + else + inst = NewInstruction(val, ty) + end + op[] = NewSSAValue(insert_node!(ir, idx, inst).id - length(ir.stmts)) end end return urs[] @@ -165,15 +178,15 @@ end function fixup_uses!(ir::IRCode, ci::CodeInfo, code::Vector{Any}, uses::Vector{Int}, slot::Int, @nospecialize(ssa)) for use in uses - code[use] = fixemup!(stmt->slot_id(stmt)==slot, stmt->ssa, ir, ci, use, code[use]) + code[use] = fixemup!(x::UnoptSlot->slot_id(x)==slot, stmt::UnoptSlot->ssa, ir, ci, use, code[use]) end end function rename_uses!(ir::IRCode, ci::CodeInfo, idx::Int, @nospecialize(stmt), renames::Vector{Any}) - return fixemup!(stmt->true, stmt->renames[slot_id(stmt)], ir, ci, idx, stmt) + return fixemup!(stmt::UnoptSlot->true, stmt::UnoptSlot->renames[slot_id(stmt)], ir, ci, idx, stmt) end -function strip_trailing_junk!(ci::CodeInfo, code::Vector{Any}, info::Vector{Any}) +function strip_trailing_junk!(ci::CodeInfo, 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} @@ -195,8 +208,8 @@ function strip_trailing_junk!(ci::CodeInfo, code::Vector{Any}, info::Vector{Any} push!(code, ReturnNode()) push!(ssavaluetypes, Union{}) push!(codelocs, 0) - push!(info, nothing) - push!(ssaflags, IR_FLAG_NULL) + push!(info, NoCallInfo()) + push!(ssaflags, IR_FLAG_NOTHROW) end nothing end @@ -206,10 +219,10 @@ struct DelayedTyp end # maybe use expr_type? -function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx::Int, slottypes::Vector{Any}) +function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{VarState}, idx::Int, slottypes::Vector{Any}) if isa(x, Expr) if x.head === :static_parameter - return sptypes[x.args[1]::Int] + return sptypes[x.args[1]::Int].typ elseif x.head === :boundscheck return Bool elseif x.head === :copyast @@ -217,12 +230,12 @@ function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx:: end return (ci.ssavaluetypes::Vector{Any})[idx] end - isa(x, GlobalRef) && return abstract_eval_global(x.mod, x.name) + isa(x, GlobalRef) && return abstract_eval_globalref(x) isa(x, SSAValue) && return (ci.ssavaluetypes::Vector{Any})[x.id] isa(x, Argument) && return slottypes[x.n] isa(x, NewSSAValue) && return DelayedTyp(x) isa(x, QuoteNode) && return Const(x.value) - isa(x, Union{Symbol, PiNode, PhiNode, SlotNumber, TypedSlot}) && error("unexpected val type") + isa(x, Union{Symbol, PiNode, PhiNode, UnoptSlot}) && error("unexpected val type") return Const(x) end @@ -274,19 +287,19 @@ needs to make sure that we always visit `B` before `A`. DOI: . """ function iterated_dominance_frontier(cfg::CFG, liveness::BlockLiveness, domtree::DomTree) - # This should be a priority queue, but TODO - sorted array for now defs = liveness.def_bbs - pq = Tuple{Int, Int}[(defs[i], domtree.nodes[defs[i]].level) for i in 1:length(defs)] - sort!(pq, by=x->x[2]) + heap = Tuple{Int, Int}[(defs[i], domtree.nodes[defs[i]].level) for i in 1:length(defs)] + heap_order = By(x -> -x[2]) + heapify!(heap, heap_order) phiblocks = Int[] # This bitset makes sure we only add a phi node to a given block once. processed = BitSet() # This bitset implements the `key insight` mentioned above. In particular, it prevents # us from visiting a subtree that we have already visited before. visited = BitSet() - while !isempty(pq) + while !isempty(heap) # We pop from the end of the array - i.e. the element with the highest level. - node, level = pop!(pq) + node, level = heappop!(heap, heap_order) worklist = Int[] push!(worklist, node) while !isempty(worklist) @@ -316,8 +329,7 @@ function iterated_dominance_frontier(cfg::CFG, liveness::BlockLiveness, domtree: # because succ_level <= level, which is the greatest level we have currently # processed. Thus, we have not yet processed any subtrees of level < succ_level. if !(succ in defs) - push!(pq, (succ, succ_level)) - sort!(pq, by=x->x[2]) + heappush!(heap, (succ, succ_level), heap_order) end end # Recurse down the current subtree @@ -331,8 +343,10 @@ function iterated_dominance_frontier(cfg::CFG, liveness::BlockLiveness, domtree: phiblocks end -function rename_incoming_edge(old_edge, old_to, result_order, bb_rename) +function rename_incoming_edge(old_edge::Int, old_to::Int, result_order::Vector{Int}, bb_rename::Vector{Int}) + old_edge == 0 && return 0 new_edge_from = bb_rename[old_edge] + new_edge_from < 0 && return new_edge_from if old_edge == old_to - 1 # Could have been a crit edge break if new_edge_from < length(result_order) && result_order[new_edge_from + 1] == 0 @@ -342,7 +356,7 @@ function rename_incoming_edge(old_edge, old_to, result_order, bb_rename) new_edge_from end -function rename_outgoing_edge(old_to, old_from, result_order, bb_rename) +function rename_outgoing_edge(old_to::Int, old_from::Int, result_order::Vector{Int}, bb_rename::Vector{Int}) new_edge_to = bb_rename[old_to] if old_from == old_to - 1 # Could have been a crit edge break @@ -353,12 +367,12 @@ function rename_outgoing_edge(old_to, old_from, result_order, bb_rename) new_edge_to end -function rename_phinode_edges(node, bb, result_order, bb_rename) +function rename_phinode_edges(node::PhiNode, bb::Int, result_order::Vector{Int}, bb_rename::Vector{Int}) new_values = Any[] new_edges = Int32[] for (idx, edge) in pairs(node.edges) edge = Int(edge) - (edge == 0 || haskey(bb_rename, edge)) || continue + (edge == 0 || bb_rename[edge] != -1) || continue new_edge_from = edge == 0 ? 0 : rename_incoming_edge(edge, bb, result_order, bb_rename) push!(new_edges, new_edge_from) if isassigned(node.values, idx) @@ -381,47 +395,42 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) # First compute the new order of basic blocks result_order = Int[] stack = Int[] + bb_rename = fill(-1, length(ir.cfg.blocks)) node = 1 ncritbreaks = 0 nnewfallthroughs = 0 while node !== -1 push!(result_order, node) + bb_rename[node] = length(result_order) cs = domtree.nodes[node].children terminator = ir.stmts[last(ir.cfg.blocks[node].stmts)][:inst] - iscondbr = isa(terminator, GotoIfNot) - let old_node = node + 1 - if length(cs) >= 1 - # Adding the nodes in reverse sorted order attempts to retain - # the original source order of the nodes as much as possible. - # This is not required for correctness, but is easier on the humans - if old_node in cs - # Schedule the fall through node first, - # so we can retain the fall through - append!(stack, reverse(sort(filter(x -> (x != old_node), cs)))) - node = node + 1 - else - append!(stack, reverse(sort(cs))) - node = pop!(stack) - end + next_node = node + 1 + node = -1 + # Adding the nodes in reverse sorted order attempts to retain + # the original source order of the nodes as much as possible. + # This is not required for correctness, but is easier on the humans + for child in Iterators.Reverse(cs) + if child == next_node + # Schedule the fall through node first, + # so we can retain the fall through + node = next_node else - if isempty(stack) - node = -1 - else - node = pop!(stack) - end + push!(stack, child) end - if node != old_node && !isa(terminator, Union{GotoNode, ReturnNode}) - if isa(terminator, GotoIfNot) - # Need to break the critical edge - ncritbreaks += 1 - push!(result_order, 0) - else - nnewfallthroughs += 1 - end + end + if node == -1 && !isempty(stack) + node = pop!(stack) + end + if node != next_node && !isa(terminator, Union{GotoNode, ReturnNode}) + if isa(terminator, GotoIfNot) + # Need to break the critical edge + ncritbreaks += 1 + push!(result_order, 0) + else + nnewfallthroughs += 1 end end end - bb_rename = IdDict{Int,Int}(i=>x for (x, i) in pairs(result_order) if i !== 0) new_bbs = Vector{BasicBlock}(undef, length(result_order)) nstmts = 0 for i in result_order @@ -482,7 +491,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) result[inst_range[end]][:inst] = GotoIfNot(terminator.cond, bb_rename[terminator.dest]) elseif !isa(terminator, ReturnNode) if isa(terminator, Expr) - if terminator.head == :enter + if terminator.head === :enter terminator.args[1] = bb_rename[terminator.args[1]] end end @@ -497,8 +506,8 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree) bb_start_off += length(inst_range) local new_preds, new_succs let bb = bb, bb_rename = bb_rename, result_order = result_order - new_preds = Int[rename_incoming_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].preds if haskey(bb_rename, i)] - new_succs = Int[rename_outgoing_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].succs if haskey(bb_rename, i)] + new_preds = Int[bb for bb in (rename_incoming_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].preds) if bb != -1] + new_succs = Int[ rename_outgoing_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].succs] end new_bbs[new_bb] = BasicBlock(inst_range, new_preds, new_succs) end @@ -529,41 +538,42 @@ function compute_live_ins(cfg::CFG, defs::Vector{Int}, uses::Vector{Int}) # We remove from `uses` any block where all uses are dominated # by a def. This prevents insertion of dead phi nodes at the top # of such a block if that block happens to be in a loop - ordered = Tuple{Int, Int, Bool}[(x, block_for_inst(cfg, x), true) for x in uses] - for x in defs - push!(ordered, (x, block_for_inst(cfg, x), false)) - end - ordered = sort(ordered, by=x->x[1]) - bb_defs = Int[] - bb_uses = Int[] - last_bb = last_def_bb = 0 - for (_, bb, is_use) in ordered - if bb != last_bb && is_use - push!(bb_uses, bb) - end - last_bb = bb - if last_def_bb != bb && !is_use - push!(bb_defs, bb) - last_def_bb = bb - end + bb_defs = Int[] # blocks with a def + bb_uses = Int[] # blocks with a use that is not dominated by a def + + # We do a sorted joint iteration over the instructions listed + # in defs and uses following a pattern similar to mergesort + last_block, block_has_def = 0, false + defs_i = uses_i = 1 + while defs_i <= lastindex(defs) || uses_i <= lastindex(uses) + is_def = uses_i > lastindex(uses) || defs_i <= lastindex(defs) && defs[defs_i] < uses[uses_i] + block = block_for_inst(cfg, is_def ? defs[defs_i] : uses[uses_i]) + defs_i += is_def + uses_i += !is_def + if last_block != block || is_def && !block_has_def + push!(is_def ? bb_defs : bb_uses, block) + block_has_def = is_def + end + last_block = block end # To obtain live ins from bb_uses, recursively add predecessors extra_liveins = BitSet() worklist = Int[] for bb in bb_uses - append!(worklist, filter(p->p != 0 && !(p in bb_defs), cfg.blocks[bb].preds)) + append!(worklist, Iterators.filter(p->p != 0 && !(p in bb_defs), cfg.blocks[bb].preds)) end while !isempty(worklist) elem = pop!(worklist) (elem in bb_uses || elem in extra_liveins) && continue push!(extra_liveins, elem) - append!(worklist, filter(p->p != 0 && !(p in bb_defs), cfg.blocks[elem].preds)) + append!(worklist, Iterators.filter(p->p != 0 && !(p in bb_defs), cfg.blocks[elem].preds)) end append!(bb_uses, extra_liveins) BlockLiveness(bb_defs, bb_uses) end -function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode, sptypes::Vector{Any}, slottypes::Vector{Any}) +function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode, + sptypes::Vector{VarState}, slottypes::Vector{Any}, nstmts::Int, 𝕃ₒ::AbstractLattice) new_typ = Union{} for i = 1:length(node.values) if isa(node, PhiNode) && !isassigned(node.values, i) @@ -580,41 +590,58 @@ function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode end @assert !isa(typ, MaybeUndef) while isa(typ, DelayedTyp) - typ = types(ir)[typ.phi::NewSSAValue] + typ = types(ir)[new_to_regular(typ.phi::NewSSAValue, nstmts)] end - new_typ = tmerge(new_typ, was_maybe_undef ? MaybeUndef(typ) : typ) + new_typ = tmerge(𝕃ₒ, new_typ, was_maybe_undef ? MaybeUndef(typ) : typ) end return new_typ end +struct TryCatchRegion + enter_block::Int + leave_block::Int +end +struct NewPhiNode + ssaval::NewSSAValue + node::PhiNode +end +struct NewPhiCNode + slot::SlotNumber + ssaval::NewSSAValue + node::PhiCNode +end + function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, - defuses::Vector{SlotInfo}, slottypes::Vector{Any}) + defuses::Vector{SlotInfo}, slottypes::Vector{Any}, + 𝕃ₒ::AbstractLattice) code = ir.stmts.inst cfg = ir.cfg - catch_entry_blocks = Tuple{Int, Int}[] + catch_entry_blocks = TryCatchRegion[] for idx in 1:length(code) stmt = code[idx] if isexpr(stmt, :enter) - push!(catch_entry_blocks, (block_for_inst(cfg, idx), block_for_inst(cfg, stmt.args[1]::Int))) + push!(catch_entry_blocks, TryCatchRegion( + block_for_inst(cfg, idx), + block_for_inst(cfg, stmt.args[1]::Int))) end end - exc_handlers = IdDict{Int, Tuple{Int, Int}}() + exc_handlers = IdDict{Int, TryCatchRegion}() # Record the correct exception handler for all cricitcal sections - for (enter_block, exc) in catch_entry_blocks - exc_handlers[enter_block+1] = (enter_block, exc) + for catch_entry_block in catch_entry_blocks + (; enter_block, leave_block) = catch_entry_block + exc_handlers[enter_block+1] = catch_entry_block # TODO: Cut off here if the terminator is a leave corresponding to this enter for block in dominated(domtree, enter_block+1) - exc_handlers[block] = (enter_block, exc) + exc_handlers[block] = catch_entry_block end end - phi_slots = Vector{Int}[Vector{Int}() for _ = 1:length(ir.cfg.blocks)] - phi_nodes = Vector{Pair{NewSSAValue,PhiNode}}[Vector{Pair{NewSSAValue,PhiNode}}() for _ = 1:length(cfg.blocks)] - phi_ssas = SSAValue[] - phicnodes = IdDict{Int, Vector{Tuple{SlotNumber, NewSSAValue, PhiCNode}}}() - for (_, exc) in catch_entry_blocks - phicnodes[exc] = Vector{Tuple{SlotNumber, NewSSAValue, PhiCNode}}() + phi_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] + new_phi_nodes = Vector{NewPhiNode}[NewPhiNode[] for _ = 1:length(cfg.blocks)] + new_phic_nodes = IdDict{Int, Vector{NewPhiCNode}}() + for (; leave_block) in catch_entry_blocks + new_phic_nodes[leave_block] = NewPhiCNode[] end @timeit "idf" for (idx, slot) in Iterators.enumerate(defuses) # No uses => no need for phi nodes @@ -636,14 +663,14 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, else val = code[slot.defs[]].args[2] typ = typ_for_val(val, ci, ir.sptypes, slot.defs[], slottypes) - ssaval = SSAValue(make_ssa!(ci, code, slot.defs[], idx, typ)) + ssaval = make_ssa!(ci, code, slot.defs[], typ) fixup_uses!(ir, ci, code, slot.uses, idx, ssaval) end continue end @timeit "liveness" (live = compute_live_ins(cfg, slot)) for li in live.live_in_bbs - cidx = findfirst(x->x[2] == li, catch_entry_blocks) + cidx = findfirst(x::TryCatchRegion->x.leave_block==li, catch_entry_blocks) if cidx !== nothing # The slot is live-in into this block. We need to # Create a PhiC node in the catch entry block and @@ -652,7 +679,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, phic_ssa = NewSSAValue( insert_node!(ir, first_insert_for_bb(code, cfg, li), NewInstruction(node, Union{})).id - length(ir.stmts)) - push!(phicnodes[li], (SlotNumber(idx), phic_ssa, node)) + push!(new_phic_nodes[li], NewPhiCNode(SlotNumber(idx), phic_ssa, node)) # Inform IDF that we now have a def in the catch block if !(li in live.def_bbs) push!(live.def_bbs, li) @@ -663,9 +690,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, for block in phiblocks push!(phi_slots[block], idx) node = PhiNode() - ssa = NewSSAValue(insert_node!(ir, + ssaval = NewSSAValue(insert_node!(ir, first_insert_for_bb(code, cfg, block), NewInstruction(node, Union{})).id - length(ir.stmts)) - push!(phi_nodes[block], ssa=>node) + push!(new_phi_nodes[block], NewPhiNode(ssaval, node)) end end # Perform SSA renaming @@ -702,7 +729,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, end # Insert phi nodes if necessary for (idx, slot) in Iterators.enumerate(phi_slots[item]) - ssaval, node = phi_nodes[item][idx] + (; ssaval, node) = new_phi_nodes[item][idx] incoming_val = incoming_vals[slot] if incoming_val === SSAValue(-1) # Optimistically omit this path. @@ -725,29 +752,29 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, if isa(typ, DelayedTyp) push!(type_refine_phi, ssaval.id) end - new_typ = isa(typ, DelayedTyp) ? Union{} : tmerge(old_entry[:type], typ) + new_typ = isa(typ, DelayedTyp) ? Union{} : tmerge(𝕃ₒ, old_entry[:type], typ) old_entry[:type] = new_typ old_entry[:inst] = node incoming_vals[slot] = ssaval end (item in visited) && continue # Record phi_C nodes if necessary - if haskey(phicnodes, item) - for (slot, ssa, _) in phicnodes[item] - incoming_vals[slot_id(slot)] = ssa + if haskey(new_phic_nodes, item) + for (; slot, ssaval) in new_phic_nodes[item] + incoming_vals[slot_id(slot)] = ssaval end end # Record initial upsilon nodes if necessary - eidx = findfirst(x->x[1] == item, catch_entry_blocks) + eidx = findfirst((; enter_block)::TryCatchRegion->enter_block==item, catch_entry_blocks) if eidx !== nothing - for (slot, _, node) in phicnodes[catch_entry_blocks[eidx][2]] + for (; slot, node) in new_phic_nodes[catch_entry_blocks[eidx].leave_block] ival = incoming_vals[slot_id(slot)] ivalundef = ival === UNDEF_TOKEN - unode = ivalundef ? UpsilonNode() : UpsilonNode(ival) - typ = ivalundef ? MaybeUndef(Union{}) : typ_for_val(ival, ci, ir.sptypes, -1, slottypes) - push!(node.values, - NewSSAValue(insert_node!(ir, first_insert_for_bb(code, cfg, item), - NewInstruction(unode, typ), true).id - length(ir.stmts))) + Υ = NewInstruction(ivalundef ? UpsilonNode() : UpsilonNode(ival), + ivalundef ? MaybeUndef(Union{}) : typ_for_val(ival, ci, ir.sptypes, -1, slottypes)) + # insert `UpsilonNode` immediately before the `:enter` expression + Υssa = insert_node!(ir, first_insert_for_bb(code, cfg, item), Υ) + push!(node.values, NewSSAValue(Υssa.id - length(ir.stmts))) end end push!(visited, item) @@ -765,30 +792,33 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, end code[idx] = stmt # Record a store - if isexpr(stmt, :(=)) && isa(stmt.args[1], SlotNumber) - id = slot_id(stmt.args[1]) - val = stmt.args[2] - typ = typ_for_val(val, ci, ir.sptypes, idx, slottypes) - # Having UNDEF_TOKEN appear on the RHS is possible if we're on a dead branch. - # Do something reasonable here, by marking the LHS as undef as well. - if val !== UNDEF_TOKEN - incoming_vals[id] = SSAValue(make_ssa!(ci, code, idx, id, typ)::Int) - else - code[idx] = nothing - incoming_vals[id] = UNDEF_TOKEN - end - eidx = item - while haskey(exc_handlers, eidx) - (eidx, exc) = exc_handlers[eidx] - cidx = findfirst(x->slot_id(x[1]) == id, phicnodes[exc]) - if cidx !== nothing - node = UpsilonNode(incoming_vals[id]) - if incoming_vals[id] === UNDEF_TOKEN - node = UpsilonNode() - typ = MaybeUndef(Union{}) + if isexpr(stmt, :(=)) + arg1 = stmt.args[1] + if isa(arg1, SlotNumber) + id = slot_id(arg1) + val = stmt.args[2] + typ = typ_for_val(val, ci, ir.sptypes, idx, slottypes) + # Having UNDEF_TOKEN appear on the RHS is possible if we're on a dead branch. + # Do something reasonable here, by marking the LHS as undef as well. + if val !== UNDEF_TOKEN + incoming_vals[id] = make_ssa!(ci, code, idx, typ) + else + code[idx] = nothing + incoming_vals[id] = UNDEF_TOKEN + end + enter_block = item + while haskey(exc_handlers, enter_block) + (; enter_block, leave_block) = exc_handlers[enter_block] + cidx = findfirst((; slot)::NewPhiCNode->slot_id(slot)==id, new_phic_nodes[leave_block]) + if cidx !== nothing + node = UpsilonNode(incoming_vals[id]) + if incoming_vals[id] === UNDEF_TOKEN + node = UpsilonNode() + typ = MaybeUndef(Union{}) + end + push!(new_phic_nodes[leave_block][cidx].node.values, + NewSSAValue(insert_node!(ir, idx, NewInstruction(node, typ), true).id - length(ir.stmts))) end - push!(phicnodes[exc][cidx][3].values, - NewSSAValue(insert_node!(ir, idx, NewInstruction(node, typ), true).id - length(ir.stmts))) end end end @@ -845,21 +875,21 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, end end end - for (_, nodes) in phicnodes - for (_, ssa, node) in nodes + for (_, nodes) in new_phic_nodes + for (; ssaval, node) in nodes new_typ = Union{} # TODO: This could just be the ones that depend on other phis - push!(type_refine_phi, ssa.id) - new_idx = ssa.id + push!(type_refine_phi, ssaval.id) + new_idx = ssaval.id node = new_nodes.stmts[new_idx] phic_values = (node[:inst]::PhiCNode).values for i = 1:length(phic_values) orig_typ = typ = typ_for_val(phic_values[i], ci, ir.sptypes, -1, slottypes) @assert !isa(typ, MaybeUndef) while isa(typ, DelayedTyp) - typ = types(ir)[typ.phi::NewSSAValue] + typ = types(ir)[new_to_regular(typ.phi::NewSSAValue, nstmts)] end - new_typ = tmerge(new_typ, typ) + new_typ = tmerge(𝕃ₒ, new_typ, typ) end node[:type] = new_typ end @@ -872,8 +902,8 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, changed = false for new_idx in type_refine_phi node = new_nodes.stmts[new_idx] - new_typ = recompute_type(node[:inst]::Union{PhiNode,PhiCNode}, ci, ir, ir.sptypes, slottypes) - if !(node[:type] ⊑ new_typ) || !(new_typ ⊑ node[:type]) + new_typ = recompute_type(node[:inst]::Union{PhiNode,PhiCNode}, ci, ir, ir.sptypes, slottypes, nstmts, 𝕃ₒ) + if !⊑(𝕃ₒ, node[:type], new_typ) || !⊑(𝕃ₒ, new_typ, node[:type]) node[:type] = new_typ changed = true end @@ -882,14 +912,14 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, for i in 1:length(result_types) rt_i = result_types[i] if rt_i isa DelayedTyp - result_types[i] = types(ir)[rt_i.phi::NewSSAValue] + result_types[i] = types(ir)[new_to_regular(rt_i.phi::NewSSAValue, nstmts)] end end for i = 1:length(new_nodes) local node = new_nodes.stmts[i] local typ = node[:type] if isa(typ, DelayedTyp) - node[:type] = types(ir)[typ.phi::NewSSAValue] + node[:type] = types(ir)[new_to_regular(typ.phi::NewSSAValue, nstmts)] end end # Renumber SSA values diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index 1578bdb9c348a..bf06d6bb3e523 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -1,17 +1,26 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +function maybe_show_ir(ir::IRCode) + if isdefined(Core, :Main) + Core.Main.Base.display(ir) + end +end + if !isdefined(@__MODULE__, Symbol("@verify_error")) macro verify_error(arg) arg isa String && return esc(:(print && println(stderr, $arg))) - (arg isa Expr && arg.head === :string) || error("verify_error macro expected a string expression") + isexpr(arg, :string) || error("verify_error macro expected a string expression") pushfirst!(arg.args, GlobalRef(Core, :stderr)) pushfirst!(arg.args, :println) arg.head = :call - return esc(arg) + return esc(quote + $arg + maybe_show_ir(ir) + end) end end -function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, use_idx::Int, print::Bool) +function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, use_idx::Int, printed_use_idx::Int, print::Bool, isforeigncall::Bool, arg_idx::Int, allow_frontend_forms::Bool) if isa(op, SSAValue) if op.id > length(ir.stmts) def_bb = block_for_inst(ir.cfg, ir.new_nodes.info[op.id - length(ir.stmts)].pos) @@ -30,21 +39,37 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, else if !dominates(domtree, def_bb, use_bb) && !(bb_unreachable(domtree, def_bb) && bb_unreachable(domtree, use_bb)) # At the moment, we allow GC preserve tokens outside the standard domination notion - #@Base.show ir - @verify_error "Basic Block $def_bb does not dominate block $use_bb (tried to use value $(op.id))" + @verify_error "Basic Block $def_bb does not dominate block $use_bb (tried to use value %$(op.id) at %$(printed_use_idx))" error("") end end + + use_inst = ir[op] + if isa(use_inst[:inst], Union{GotoIfNot, GotoNode, ReturnNode}) + @verify_error "At statement %$use_idx: Invalid use of value statement or terminator %$(op.id)" + error("") + end elseif isa(op, GlobalRef) if !isdefined(op.mod, op.name) || !isconst(op.mod, op.name) @verify_error "Unbound GlobalRef not allowed in value position" error("") end + elseif isa(op, Expr) + # Only Expr(:boundscheck) is allowed in value position + if isforeigncall && arg_idx == 1 && op.head === :call + # Allow a tuple in symbol position for foreigncall - this isn't actually + # a real call - it's interpreted in global scope by codegen. However, + # we do need to keep this a real use, because it could also be a pointer. + elseif op.head !== :boundscheck + if !allow_frontend_forms || op.head !== :opaque_closure_method + @verify_error "Expr not allowed in value position" + error("") + end + end elseif isa(op, Union{OldSSAValue, NewSSAValue}) - #@Base.show ir @verify_error "Left over SSA marker" error("") - elseif isa(op, Union{SlotNumber, TypedSlot}) + elseif isa(op, UnoptSlot) @verify_error "Left over slot detected in converted IR" error("") end @@ -60,25 +85,21 @@ function count_int(val::Int, arr::Vector{Int}) n end -function verify_ir(ir::IRCode, print::Bool=true) +function verify_ir(ir::IRCode, print::Bool=true, + allow_frontend_forms::Bool=false, + 𝕃ₒ::AbstractLattice = OptimizerLattice()) # For now require compact IR # @assert isempty(ir.new_nodes) # Verify CFG last_end = 0 - # Verify statements - domtree = construct_domtree(ir.cfg.blocks) + # Verify CFG graph. Must be well formed to construct domtree for (idx, block) in pairs(ir.cfg.blocks) - if first(block.stmts) != last_end + 1 - #ranges = [(idx,first(bb.stmts),last(bb.stmts)) for (idx, bb) in pairs(ir.cfg.blocks)] - @verify_error "First statement of BB $idx ($(first(block.stmts))) does not match end of previous ($last_end)" - error("") - end - last_end = last(block.stmts) - terminator = ir.stmts[last_end][:inst] - - bb_unreachable(domtree, idx) && continue for p in block.preds p == 0 && continue + if !(1 <= p <= length(ir.cfg.blocks)) + @verify_error "Predecessor $p of block $idx out of bounds for IR" + error("") + end c = count_int(idx, ir.cfg.blocks[p].succs) if c == 0 @verify_error "Predecessor $p of block $idx not in successor list" @@ -90,6 +111,32 @@ function verify_ir(ir::IRCode, print::Bool=true) end end end + for s in block.succs + if !(1 <= s <= length(ir.cfg.blocks)) + @verify_error "Successor $s of block $idx out of bounds for IR" + error("") + end + if !(idx in ir.cfg.blocks[s].preds) + #Base.@show ir.cfg + #Base.@show ir + #Base.@show ir.argtypes + @verify_error "Successor $s of block $idx not in predecessor list" + error("") + end + end + end + # Verify statements + domtree = construct_domtree(ir.cfg.blocks) + for (idx, block) in pairs(ir.cfg.blocks) + if first(block.stmts) != last_end + 1 + #ranges = [(idx,first(bb.stmts),last(bb.stmts)) for (idx, bb) in pairs(ir.cfg.blocks)] + @verify_error "First statement of BB $idx ($(first(block.stmts))) does not match end of previous ($last_end)" + error("") + end + last_end = last(block.stmts) + terminator = ir.stmts[last_end][:inst] + + bb_unreachable(domtree, idx) && continue if isa(terminator, ReturnNode) if !isempty(block.succs) @verify_error "Block $idx ends in return or unreachable, but has successors" @@ -127,17 +174,16 @@ function verify_ir(ir::IRCode, print::Bool=true) end isa(stmt, PhiNode) || break end - @verify_error "Block $idx successors ($(block.succs)), does not match fall-through terminator ($terminator)" - error("") - end - end - for s in block.succs - if !(idx in ir.cfg.blocks[s].preds) - #@Base.show ir.cfg - #@Base.show ir - #@Base.show ir.argtypes - @verify_error "Successor $s of block $idx not in predecessor list" - error("") + termidx = last(block.stmts) + stmttyp = ir.stmts[termidx][:type] + if isempty(block.succs) && stmttyp == Union{} + # Allow fallthrough terminators that are known to error to + # be removed from the CFG. Ideally we'd add an unreachable + # here, but that isn't always possible. + else + @verify_error "Block $idx successors ($(block.succs)), does not match fall-through terminator %$termidx ($terminator)::$stmttyp" + error("") + end end end end @@ -160,17 +206,23 @@ function verify_ir(ir::IRCode, print::Bool=true) end end if !(edge == 0 && bb == 1) && !(edge in ir.cfg.blocks[bb].preds) - #@Base.show ir.argtypes - #@Base.show ir + #Base.@show ir.argtypes + #Base.@show ir @verify_error "Edge $edge of φ node $idx not in predecessor list" error("") end edge == 0 && continue + if bb_unreachable(domtree, Int(edge)) + # TODO: Disallow? + #@verify_error "Unreachable edge from #$edge should have been cleaned up at idx $idx" + #error("") + continue + end isassigned(stmt.values, i) || continue val = stmt.values[i] phiT = ir.stmts[idx][:type] if isa(val, SSAValue) - if !(types(ir)[val] ⊑ phiT) + if !⊑(𝕃ₒ, types(ir)[val], phiT) #@verify_error """ # PhiNode $idx, has operand $(val.id), whose type is not a sub lattice element. # PhiNode type was $phiT @@ -178,11 +230,8 @@ function verify_ir(ir::IRCode, print::Bool=true) #""" #error("") end - elseif isa(val, GlobalRef) || isa(val, Expr) - @verify_error "GlobalRefs and Exprs are not allowed as PhiNode values" - error("") end - check_op(ir, domtree, val, Int(edge), last(ir.cfg.blocks[stmt.edges[i]].stmts)+1, print) + check_op(ir, domtree, val, Int(edge), last(ir.cfg.blocks[stmt.edges[i]].stmts)+1, idx, print, false, i, allow_frontend_forms) end elseif isa(stmt, PhiCNode) for i = 1:length(stmt.values) @@ -196,14 +245,25 @@ 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) + @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 - #@verify_error "Missing line number information for statement $idx of $ir" end end end + isforeigncall = false if isa(stmt, Expr) if stmt.head === :(=) if stmt.args[1] isa SSAValue @@ -219,14 +279,25 @@ function verify_ir(ir::IRCode, print::Bool=true) # blocks, which isn't allowed for regular SSA values, so # we skip the validation below. continue - elseif stmt.head === :isdefined && length(stmt.args) == 1 && stmt.args[1] isa GlobalRef - # a GlobalRef isdefined check does not evaluate its argument + elseif stmt.head === :foreigncall + isforeigncall = true + elseif stmt.head === :isdefined && length(stmt.args) == 1 && + (stmt.args[1] isa GlobalRef || isexpr(stmt.args[1], :static_parameter)) + # a GlobalRef or static_parameter isdefined check does not evaluate its argument continue + elseif stmt.head === :call + f = stmt.args[1] + if f isa GlobalRef && f.name === :cglobal + # TODO: these are not yet linearized + continue + end end end + n = 1 for op in userefs(stmt) op = op[] - check_op(ir, domtree, op, bb, idx, print) + check_op(ir, domtree, op, bb, idx, idx, print, isforeigncall, n, allow_frontend_forms) + n += 1 end end end diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index e3f69b2c43e54..9f55d56181838 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -10,23 +10,29 @@ and any additional information (`call.info`) for a given generic call. """ struct CallMeta rt::Any - info::Any + effects::Effects + info::CallInfo end +struct NoCallInfo <: CallInfo end + """ - info::MethodMatchInfo + info::MethodMatchInfo <: CallInfo Captures the result of a `:jl_matching_methods` lookup for the given call (`info.results`). This info may then be used by the optimizer to inline the matches, without having to re-consult the method table. This info is illegal on any statement that is not a call to a generic function. """ -struct MethodMatchInfo +struct MethodMatchInfo <: CallInfo results::MethodLookupResult end +nsplit_impl(info::MethodMatchInfo) = 1 +getsplit_impl(info::MethodMatchInfo, idx::Int) = (@assert idx == 1; info.results) +getresult_impl(::MethodMatchInfo, ::Int) = nothing """ - info::UnionSplitInfo + info::UnionSplitInfo <: CallInfo If inference decides to partition the method search space by splitting unions, it will issue a method lookup query for each such partition. This info indicates @@ -34,7 +40,7 @@ that such partitioning happened and wraps the corresponding `MethodMatchInfo` fo each partition (`info.matches::Vector{MethodMatchInfo}`). This info is illegal on any statement that is not a call to a generic function. """ -struct UnionSplitInfo +struct UnionSplitInfo <: CallInfo matches::Vector{MethodMatchInfo} end @@ -46,55 +52,75 @@ function nmatches(info::UnionSplitInfo) end return n end +nsplit_impl(info::UnionSplitInfo) = length(info.matches) +getsplit_impl(info::UnionSplitInfo, idx::Int) = getsplit_impl(info.matches[idx], 1) +getresult_impl(::UnionSplitInfo, ::Int) = nothing + +abstract type ConstResult end -struct ConstResult +struct ConstPropResult <: ConstResult + result::InferenceResult +end + +struct ConcreteResult <: ConstResult mi::MethodInstance + effects::Effects result - ConstResult(mi::MethodInstance) = new(mi) - ConstResult(mi::MethodInstance, @nospecialize val) = new(mi, val) + ConcreteResult(mi::MethodInstance, effects::Effects) = new(mi, effects) + ConcreteResult(mi::MethodInstance, effects::Effects, @nospecialize val) = new(mi, effects, val) +end + +struct SemiConcreteResult <: ConstResult + mi::MethodInstance + ir::IRCode + effects::Effects end """ - info::ConstCallInfo + info::ConstCallInfo <: CallInfo The precision of this call was improved using constant information. -In addition to the original call information `info.call`, this info also keeps -the inference results with constant information `info.results::Vector{Union{Nothing,InferenceResult}}`. +In addition to the original call information `info.call`, this info also keeps the results +of constant inference `info.results::Vector{Union{Nothing,ConstResult}}`. """ -struct ConstCallInfo +struct ConstCallInfo <: CallInfo call::Union{MethodMatchInfo,UnionSplitInfo} - results::Vector{Union{Nothing,InferenceResult,ConstResult}} + results::Vector{Union{Nothing,ConstResult}} end +nsplit_impl(info::ConstCallInfo) = nsplit(info.call) +getsplit_impl(info::ConstCallInfo, idx::Int) = getsplit(info.call, idx) +getresult_impl(info::ConstCallInfo, idx::Int) = info.results[idx] """ - info::MethodResultPure + info::MethodResultPure <: CallInfo This struct represents a method result constant was proven to be effect-free, including being no-throw (typically because the value was computed by calling an `@pure` function). """ -struct MethodResultPure - info::Union{MethodMatchInfo,UnionSplitInfo,Bool} +struct MethodResultPure <: CallInfo + info::CallInfo end -let instance = MethodResultPure(false) +let instance = MethodResultPure(NoCallInfo()) global MethodResultPure MethodResultPure() = instance end """ - info::AbstractIterationInfo + ainfo::AbstractIterationInfo Captures all the information for abstract iteration analysis of a single value. -Each (abstract) call to `iterate`, corresponds to one entry in `info.each::Vector{CallMeta}`. +Each (abstract) call to `iterate`, corresponds to one entry in `ainfo.each::Vector{CallMeta}`. """ struct AbstractIterationInfo each::Vector{CallMeta} + complete::Bool end const MaybeAbstractIterationInfo = Union{Nothing, AbstractIterationInfo} """ - info::ApplyCallInfo + info::ApplyCallInfo <: CallInfo This info applies to any call of `_apply_iterate(...)` and captures both the info of the actual call being applied and the info for any implicit call @@ -103,7 +129,7 @@ to be yet another `_apply_iterate`, in which case the `info.call` field will be another `ApplyCallInfo`. This info is illegal on any statement that is not an `_apply_iterate` call. """ -struct ApplyCallInfo +struct ApplyCallInfo <: CallInfo # The info for the call itself call::Any # AbstractIterationInfo for each argument, if applicable @@ -111,12 +137,12 @@ struct ApplyCallInfo end """ - info::UnionSplitApplyCallInfo + info::UnionSplitApplyCallInfo <: CallInfo Like `UnionSplitInfo`, but for `ApplyCallInfo` rather than `MethodMatchInfo`. This info is illegal on any statement that is not an `_apply_iterate` call. """ -struct UnionSplitApplyCallInfo +struct UnionSplitApplyCallInfo <: CallInfo infos::Vector{ApplyCallInfo} end @@ -127,9 +153,9 @@ Represents a resolved call to `Core.invoke`, carrying the `info.match::MethodMat the method that has been processed. Optionally keeps `info.result::InferenceResult` that keeps constant information. """ -struct InvokeCallInfo +struct InvokeCallInfo <: CallInfo match::MethodMatch - result::Union{Nothing,InferenceResult,ConstResult} + result::Union{Nothing,ConstResult} end """ @@ -139,20 +165,20 @@ Represents a resolved call of opaque closure, carrying the `info.match::MethodMa the method that has been processed. Optionally keeps `info.result::InferenceResult` that keeps constant information. """ -struct OpaqueClosureCallInfo +struct OpaqueClosureCallInfo <: CallInfo match::MethodMatch - result::Union{Nothing,InferenceResult,ConstResult} + result::Union{Nothing,ConstResult} end """ - info::OpaqueClosureCreateInfo + info::OpaqueClosureCreateInfo <: CallInfo This info may be constructed upon opaque closure construction, with `info.unspec::CallMeta` carrying out inference result of an unreal, partially specialized call (i.e. specialized on the closure environment, but not on the argument types of the opaque closure) in order to allow the optimizer to rewrite the return type parameter of the `OpaqueClosure` based on it. """ -struct OpaqueClosureCreateInfo +struct OpaqueClosureCreateInfo <: CallInfo unspec::CallMeta function OpaqueClosureCreateInfo(unspec::CallMeta) @assert isa(unspec.info, OpaqueClosureCallInfo) @@ -165,14 +191,35 @@ end # the AbstractInterpreter. """ - info::ReturnTypeCallInfo + info::ReturnTypeCallInfo <: CallInfo Represents a resolved call of `Core.Compiler.return_type`. `info.call` wraps the info corresponding to the call that `Core.Compiler.return_type` call was supposed to analyze. """ -struct ReturnTypeCallInfo - info::Any +struct ReturnTypeCallInfo <: CallInfo + info::CallInfo +end + +""" + info::FinalizerInfo <: CallInfo + +Represents the information of a potential (later) call to the finalizer on the given +object type. +""" +struct FinalizerInfo <: CallInfo + info::CallInfo # the callinfo for the finalizer call + effects::Effects # the effects for the finalizer call +end + +""" + info::ModifyFieldInfo <: CallInfo + +Represents a resolved all of `modifyfield!(obj, name, op, x, [order])`. +`info.info` wraps the call information of `op(getfield(obj, name), x)`. +""" +struct ModifyFieldInfo <: CallInfo + info::CallInfo # the callinfo for the `op(getfield(obj, name), x)` call end @specialize diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index ec56b826d1491..f894d4ab3f4a5 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -4,9 +4,51 @@ # constants # ############# -@nospecialize - -const _NAMEDTUPLE_NAME = NamedTuple.body.body.name +""" + @nospecs def + +Adds `@nospecialize` annotation to non-annotated arguments of `def`. +```julia +(Core.Compiler) julia> @macroexpand @nospecs function tfunc(𝕃::AbstractLattice, x, y::Bool, zs...) + x, ys + end +:(function tfunc(\$(Expr(:meta, :specialize, :(𝕃::AbstractLattice))), x, y::Bool, zs...) + #= REPL[3]:1 =# + \$(Expr(:meta, :nospecialize, :x, :zs)) + #= REPL[3]:2 =# + (x, ys) + end) +``` +""" +macro nospecs(ex) + is_function_def(ex) || throw(ArgumentError("expected function definition")) + args, body = ex.args + if isexpr(args, :call) + args = args.args[2:end] # skip marking `@nospecialize` on the function itself + else + @assert isexpr(args, :tuple) # anonymous function + args = args.args + end + names = Symbol[] + for arg in args + isexpr(arg, :macrocall) && continue + if isexpr(arg, :...) + arg = arg.args[1] + elseif isexpr(arg, :kw) + arg = arg.args[1] + end + isexpr(arg, :(::)) && continue + @assert arg isa Symbol + push!(names, arg) + end + @assert isexpr(body, :block) + if !isempty(names) + lin = first(body.args)::LineNumberNode + nospec = Expr(:macrocall, Symbol("@nospecialize"), lin, names...) + insert!(body.args, 2, nospec) + end + return esc(ex) +end const INT_INF = typemax(Int) # integer infinity @@ -25,6 +67,7 @@ function find_tfunc(@nospecialize f) end const DATATYPE_TYPES_FIELDINDEX = fieldindex(DataType, :types) +const DATATYPE_NAME_FIELDINDEX = fieldindex(DataType, :name) ########## # tfuncs # @@ -39,14 +82,13 @@ function add_tfunc(f::IntrinsicFunction, minarg::Int, maxarg::Int, @nospecialize T_IFUNC[idx] = (minarg, maxarg, tfunc) T_IFUNC_COST[idx] = cost end -# TODO: add @nospecialize on `f` and declare its type as `Builtin` when that's supported -function add_tfunc(f::Function, minarg::Int, maxarg::Int, @nospecialize(tfunc), cost::Int) +function add_tfunc(@nospecialize(f::Builtin), minarg::Int, maxarg::Int, @nospecialize(tfunc), cost::Int) push!(T_FFUNC_KEY, f) push!(T_FFUNC_VAL, (minarg, maxarg, tfunc)) push!(T_FFUNC_COST, cost) end -add_tfunc(throw, 1, 1, (@nospecialize(x)) -> Bottom, 0) +add_tfunc(throw, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bottom), 0) # the inverse of typeof_tfunc # returns (type, isexact, isconcrete, istype) @@ -99,25 +141,35 @@ function instanceof_tfunc(@nospecialize(t)) end return Any, false, false, false end -bitcast_tfunc(@nospecialize(t), @nospecialize(x)) = instanceof_tfunc(t)[1] -math_tfunc(@nospecialize(x)) = widenconst(x) -math_tfunc(@nospecialize(x), @nospecialize(y)) = widenconst(x) -math_tfunc(@nospecialize(x), @nospecialize(y), @nospecialize(z)) = widenconst(x) -fptoui_tfunc(@nospecialize(t), @nospecialize(x)) = bitcast_tfunc(t, x) -fptosi_tfunc(@nospecialize(t), @nospecialize(x)) = bitcast_tfunc(t, x) - ## conversion ## +# IntrinsicFunction +# ================= + +# conversion +# ---------- + +@nospecs bitcast_tfunc(𝕃::AbstractLattice, t, x) = bitcast_tfunc(widenlattice(𝕃), t, x) +@nospecs bitcast_tfunc(::JLTypeLattice, t, x) = instanceof_tfunc(t)[1] +@nospecs conversion_tfunc(𝕃::AbstractLattice, t, x) = conversion_tfunc(widenlattice(𝕃), t, x) +@nospecs conversion_tfunc(::JLTypeLattice, t, x) = instanceof_tfunc(t)[1] + add_tfunc(bitcast, 2, 2, bitcast_tfunc, 1) -add_tfunc(sext_int, 2, 2, bitcast_tfunc, 1) -add_tfunc(zext_int, 2, 2, bitcast_tfunc, 1) -add_tfunc(trunc_int, 2, 2, bitcast_tfunc, 1) -add_tfunc(fptoui, 2, 2, fptoui_tfunc, 1) -add_tfunc(fptosi, 2, 2, fptosi_tfunc, 1) -add_tfunc(uitofp, 2, 2, bitcast_tfunc, 1) -add_tfunc(sitofp, 2, 2, bitcast_tfunc, 1) -add_tfunc(fptrunc, 2, 2, bitcast_tfunc, 1) -add_tfunc(fpext, 2, 2, bitcast_tfunc, 1) - ## arithmetic ## +add_tfunc(sext_int, 2, 2, conversion_tfunc, 1) +add_tfunc(zext_int, 2, 2, conversion_tfunc, 1) +add_tfunc(trunc_int, 2, 2, conversion_tfunc, 1) +add_tfunc(fptoui, 2, 2, conversion_tfunc, 1) +add_tfunc(fptosi, 2, 2, conversion_tfunc, 1) +add_tfunc(uitofp, 2, 2, conversion_tfunc, 1) +add_tfunc(sitofp, 2, 2, conversion_tfunc, 1) +add_tfunc(fptrunc, 2, 2, conversion_tfunc, 1) +add_tfunc(fpext, 2, 2, conversion_tfunc, 1) + +# arithmetic +# ---------- + +@nospecs math_tfunc(𝕃::AbstractLattice, args...) = math_tfunc(widenlattice(𝕃), args...) +@nospecs math_tfunc(::JLTypeLattice, x, xs...) = widenconst(x) + add_tfunc(neg_int, 1, 1, math_tfunc, 1) add_tfunc(add_int, 2, 2, math_tfunc, 1) add_tfunc(sub_int, 2, 2, math_tfunc, 1) @@ -133,24 +185,29 @@ add_tfunc(add_float, 2, 2, math_tfunc, 1) add_tfunc(sub_float, 2, 2, math_tfunc, 1) add_tfunc(mul_float, 2, 2, math_tfunc, 4) add_tfunc(div_float, 2, 2, math_tfunc, 20) -add_tfunc(rem_float, 2, 2, math_tfunc, 20) add_tfunc(fma_float, 3, 3, math_tfunc, 5) add_tfunc(muladd_float, 3, 3, math_tfunc, 5) - ## fast arithmetic ## + +# fast arithmetic add_tfunc(neg_float_fast, 1, 1, math_tfunc, 1) add_tfunc(add_float_fast, 2, 2, math_tfunc, 1) add_tfunc(sub_float_fast, 2, 2, math_tfunc, 1) add_tfunc(mul_float_fast, 2, 2, math_tfunc, 2) add_tfunc(div_float_fast, 2, 2, math_tfunc, 10) -add_tfunc(rem_float_fast, 2, 2, math_tfunc, 10) - ## bitwise operators ## + +# bitwise operators +# ----------------- + +@nospecs shift_tfunc(𝕃::AbstractLattice, x, y) = shift_tfunc(widenlattice(𝕃), x, y) +@nospecs shift_tfunc(::JLTypeLattice, x, y) = widenconst(x) + add_tfunc(and_int, 2, 2, math_tfunc, 1) add_tfunc(or_int, 2, 2, math_tfunc, 1) add_tfunc(xor_int, 2, 2, math_tfunc, 1) add_tfunc(not_int, 1, 1, math_tfunc, 0) # usually used as not_int(::Bool) to negate a condition -add_tfunc(shl_int, 2, 2, math_tfunc, 1) -add_tfunc(lshr_int, 2, 2, math_tfunc, 1) -add_tfunc(ashr_int, 2, 2, math_tfunc, 1) +add_tfunc(shl_int, 2, 2, shift_tfunc, 1) +add_tfunc(lshr_int, 2, 2, shift_tfunc, 1) +add_tfunc(ashr_int, 2, 2, shift_tfunc, 1) add_tfunc(bswap_int, 1, 1, math_tfunc, 1) add_tfunc(ctpop_int, 1, 1, math_tfunc, 1) add_tfunc(ctlz_int, 1, 1, math_tfunc, 1) @@ -159,7 +216,10 @@ add_tfunc(checked_sdiv_int, 2, 2, math_tfunc, 40) add_tfunc(checked_udiv_int, 2, 2, math_tfunc, 40) add_tfunc(checked_srem_int, 2, 2, math_tfunc, 40) add_tfunc(checked_urem_int, 2, 2, math_tfunc, 40) - ## functions ## + +# functions +# --------- + add_tfunc(abs_float, 1, 1, math_tfunc, 2) add_tfunc(copysign_float, 2, 2, math_tfunc, 2) add_tfunc(flipsign_int, 2, 2, math_tfunc, 1) @@ -169,8 +229,13 @@ add_tfunc(trunc_llvm, 1, 1, math_tfunc, 10) add_tfunc(rint_llvm, 1, 1, math_tfunc, 10) add_tfunc(sqrt_llvm, 1, 1, math_tfunc, 20) add_tfunc(sqrt_llvm_fast, 1, 1, math_tfunc, 20) - ## same-type comparisons ## -cmp_tfunc(@nospecialize(x), @nospecialize(y)) = Bool + +# comparisons +# ----------- + +@nospecs cmp_tfunc(𝕃::AbstractLattice, a, b) = cmp_tfunc(widenlattice(𝕃), a, b) +@nospecs cmp_tfunc(::JLTypeLattice, a, b) = Bool + add_tfunc(eq_int, 2, 2, cmp_tfunc, 1) add_tfunc(ne_int, 2, 2, cmp_tfunc, 1) add_tfunc(slt_int, 2, 2, cmp_tfunc, 1) @@ -187,24 +252,42 @@ add_tfunc(ne_float_fast, 2, 2, cmp_tfunc, 1) add_tfunc(lt_float_fast, 2, 2, cmp_tfunc, 1) add_tfunc(le_float_fast, 2, 2, cmp_tfunc, 1) - ## checked arithmetic ## -chk_tfunc(@nospecialize(x), @nospecialize(y)) = Tuple{widenconst(x), Bool} +# checked arithmetic +# ------------------ + +@nospecs chk_tfunc(𝕃::AbstractLattice, x, y) = chk_tfunc(widenlattice(𝕃), x, y) +@nospecs chk_tfunc(::JLTypeLattice, x, y) = Tuple{widenconst(x), Bool} + add_tfunc(checked_sadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_uadd_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_ssub_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_usub_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_smul_int, 2, 2, chk_tfunc, 10) add_tfunc(checked_umul_int, 2, 2, chk_tfunc, 10) - ## other, misc intrinsics ## -add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, - (@nospecialize(fptr), @nospecialize(rt), @nospecialize(at), a...) -> instanceof_tfunc(rt)[1], 10) -cglobal_tfunc(@nospecialize(fptr)) = Ptr{Cvoid} -cglobal_tfunc(@nospecialize(fptr), @nospecialize(t)) = (isType(t) ? Ptr{t.parameters[1]} : Ptr) -cglobal_tfunc(@nospecialize(fptr), t::Const) = (isa(t.val, Type) ? Ptr{t.val} : Ptr) + +# other, misc +# ----------- + +@nospecs function llvmcall_tfunc(𝕃::AbstractLattice, fptr, rt, at, a...) + return instanceof_tfunc(rt)[1] +end +add_tfunc(Core.Intrinsics.llvmcall, 3, INT_INF, llvmcall_tfunc, 10) + +@nospecs cglobal_tfunc(𝕃::AbstractLattice, fptr) = Ptr{Cvoid} +@nospecs function cglobal_tfunc(𝕃::AbstractLattice, fptr, t) + isa(t, Const) && return isa(t.val, Type) ? Ptr{t.val} : Ptr + return isType(t) ? Ptr{t.parameters[1]} : Ptr +end add_tfunc(Core.Intrinsics.cglobal, 1, 2, cglobal_tfunc, 5) -add_tfunc(Core.Intrinsics.have_fma, 1, 1, @nospecialize(x)->Bool, 1) -function ifelse_tfunc(@nospecialize(cnd), @nospecialize(x), @nospecialize(y)) +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 +# ================= + +@nospecs function ifelse_tfunc(𝕃::AbstractLattice, cnd, x, y) + cnd = widenslotwrapper(cnd) if isa(cnd, Const) if cnd.val === true return x @@ -213,55 +296,86 @@ function ifelse_tfunc(@nospecialize(cnd), @nospecialize(x), @nospecialize(y)) else return Bottom end - elseif isa(cnd, Conditional) - # optimized (if applicable) in abstract_call - elseif !(Bool ⊑ cnd) + elseif !hasintersect(widenconst(cnd), Bool) return Bottom end - return tmerge(x, y) + return tmerge(𝕃, x, y) end add_tfunc(Core.ifelse, 3, 3, ifelse_tfunc, 1) -function egal_tfunc(@nospecialize(x), @nospecialize(y)) - xx = widenconditional(x) - yy = widenconditional(y) - if isa(x, Conditional) && isa(yy, Const) - yy.val === false && return Conditional(x.var, x.elsetype, x.vtype) - yy.val === true && return x - return Const(false) - elseif isa(y, Conditional) && isa(xx, Const) - xx.val === false && return Conditional(y.var, y.elsetype, y.vtype) - xx.val === true && return y - return Const(false) - elseif isa(xx, Const) && isa(yy, Const) - return Const(xx.val === yy.val) - elseif !hasintersect(widenconst(xx), widenconst(yy)) - return Const(false) - elseif (isa(xx, Const) && y === typeof(xx.val) && isdefined(y, :instance)) || - (isa(yy, Const) && x === typeof(yy.val) && isdefined(x, :instance)) +@nospecs function ifelse_nothrow(𝕃::AbstractLattice, cond, x, y) + ⊑ = Core.Compiler.:⊑(𝕃) + return cond ⊑ Bool +end + +@nospecs egal_tfunc(𝕃::AbstractLattice, x, y) = egal_tfunc(widenlattice(𝕃), x, y) +@nospecs function egal_tfunc(𝕃::MustAliasesLattice, x, y) + return egal_tfunc(widenlattice(𝕃), widenmustalias(x), widenmustalias(y)) +end +@nospecs function egal_tfunc(𝕃::ConditionalsLattice, x, y) + if isa(x, Conditional) + y = widenconditional(y) + if isa(y, Const) + y.val === false && return Conditional(x.slot, x.elsetype, x.thentype) + y.val === true && return x + return Const(false) + end + elseif isa(y, Conditional) + x = widenconditional(x) + if isa(x, Const) + x.val === false && return Conditional(y.slot, y.elsetype, y.thentype) + x.val === true && return y + return Const(false) + end + end + return egal_tfunc(widenlattice(𝕃), x, y) +end +@nospecs function egal_tfunc(𝕃::ConstsLattice, x, y) + if isa(x, Const) && isa(y, Const) + return Const(x.val === y.val) + elseif (isa(x, Const) && y === typeof(x.val) && issingletontype(x)) || + (isa(y, Const) && x === typeof(y.val) && issingletontype(y)) return Const(true) end + return egal_tfunc(widenlattice(𝕃), x, y) +end +@nospecs function egal_tfunc(::JLTypeLattice, x, y) + hasintersect(widenconst(x), widenconst(y)) || return Const(false) return Bool end add_tfunc(===, 2, 2, egal_tfunc, 1) -function isdefined_nothrow(argtypes::Array{Any, 1}) - length(argtypes) == 2 || return false - return hasintersect(widenconst(argtypes[1]), Module) ? - argtypes[2] ⊑ Symbol : - (argtypes[2] ⊑ Symbol || argtypes[2] ⊑ Int) +function isdefined_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}) + if length(argtypes) ≠ 2 + # TODO prove nothrow when ordering is specified + return false + end + return isdefined_nothrow(𝕃, argtypes[1], argtypes[2]) +end +@nospecs function isdefined_nothrow(𝕃::AbstractLattice, x, name) + ⊑ = Core.Compiler.:⊑(𝕃) + isvarargtype(x) && return false + isvarargtype(name) && return false + if hasintersect(widenconst(x), Module) + return name ⊑ Symbol + else + return name ⊑ Symbol || name ⊑ Int + end +end + +@nospecs function isdefined_tfunc(𝕃::AbstractLattice, arg1, sym, order) + return isdefined_tfunc(𝕃, arg1, sym) end -isdefined_tfunc(arg1, sym, order) = (@nospecialize; isdefined_tfunc(arg1, sym)) -function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) +@nospecs function isdefined_tfunc(𝕃::AbstractLattice, arg1, sym) if isa(arg1, Const) - a1 = typeof(arg1.val) + arg1t = typeof(arg1.val) else - a1 = widenconst(arg1) + arg1t = widenconst(arg1) end - if isType(a1) + if isType(arg1t) return Bool end - a1 = unwrap_unionall(a1) + a1 = unwrap_unionall(arg1t) if isa(a1, DataType) && !isabstracttype(a1) if a1 === Module hasintersect(widenconst(sym), Symbol) || return Bottom @@ -304,11 +418,14 @@ function isdefined_tfunc(@nospecialize(arg1), @nospecialize(sym)) end end elseif isa(a1, Union) - return tmerge(isdefined_tfunc(a1.a, sym), - isdefined_tfunc(a1.b, sym)) + # Results can only be `Const` or `Bool` + return tmerge(𝕃, + isdefined_tfunc(𝕃, rewrap_unionall(a1.a, arg1t), sym), + isdefined_tfunc(𝕃, rewrap_unionall(a1.b, arg1t), sym)) end return Bool end + add_tfunc(isdefined, 2, 3, isdefined_tfunc, 1) function sizeof_nothrow(@nospecialize(x)) @@ -316,8 +433,6 @@ function sizeof_nothrow(@nospecialize(x)) if !isa(x.val, Type) || x.val === DataType return true end - elseif isa(x, Conditional) - return true end xu = unwrap_unionall(x) if isa(xu, Union) @@ -364,14 +479,15 @@ function _const_sizeof(@nospecialize(x)) end return Const(size) end -function sizeof_tfunc(@nospecialize(x),) +@nospecs function sizeof_tfunc(𝕃::AbstractLattice, x) + x = widenmustalias(x) isa(x, Const) && return _const_sizeof(x.val) isa(x, Conditional) && return _const_sizeof(Bool) isconstType(x) && return _const_sizeof(x.parameters[1]) xu = unwrap_unionall(x) if isa(xu, Union) - return tmerge(sizeof_tfunc(rewrap_unionall(xu.a, x)), - sizeof_tfunc(rewrap_unionall(xu.b, x))) + return tmerge(sizeof_tfunc(𝕃, rewrap_unionall(xu.a, x)), + sizeof_tfunc(𝕃, rewrap_unionall(xu.b, x))) end # Core.sizeof operates on either a type or a value. First check which # case we're in. @@ -394,28 +510,36 @@ function sizeof_tfunc(@nospecialize(x),) return Int end add_tfunc(Core.sizeof, 1, 1, sizeof_tfunc, 1) -function nfields_tfunc(@nospecialize(x)) +@nospecs function nfields_tfunc(𝕃::AbstractLattice, x) isa(x, Const) && return Const(nfields(x.val)) isa(x, Conditional) && return Const(0) - x = unwrap_unionall(widenconst(x)) + xt = widenconst(x) + x = unwrap_unionall(xt) isconstType(x) && return Const(nfields(x.parameters[1])) if isa(x, DataType) && !isabstracttype(x) - if !(x.name === Tuple.name && isvatuple(x)) && - !(x.name === _NAMEDTUPLE_NAME && !isconcretetype(x)) + if x.name === Tuple.name + isvatuple(x) && return Int + return Const(length(x.types)) + elseif x.name === _NAMEDTUPLE_NAME + length(x.parameters) == 2 || return Int + names = x.parameters[1] + isa(names, Tuple{Vararg{Symbol}}) || return nfields_tfunc(𝕃, rewrap_unionall(x.parameters[2], xt)) + return Const(length(names)) + else return Const(isdefined(x, :types) ? length(x.types) : length(x.name.names)) end end if isa(x, Union) - na = nfields_tfunc(x.a) + na = nfields_tfunc(𝕃, x.a) na === Int && return Int - return tmerge(na, nfields_tfunc(x.b)) + return tmerge(na, nfields_tfunc(𝕃, x.b)) end return Int end add_tfunc(nfields, 1, 1, nfields_tfunc, 1) -add_tfunc(Core._expr, 1, INT_INF, (@nospecialize args...)->Expr, 100) -add_tfunc(svec, 0, INT_INF, (@nospecialize args...)->SimpleVector, 20) -function typevar_tfunc(@nospecialize(n), @nospecialize(lb_arg), @nospecialize(ub_arg)) +add_tfunc(Core._expr, 1, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->Expr), 100) +add_tfunc(svec, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->SimpleVector), 20) +@nospecs function typevar_tfunc(𝕃::AbstractLattice, n, lb_arg, ub_arg) lb = Union{} ub = Any ub_certain = lb_certain = true @@ -424,26 +548,32 @@ function typevar_tfunc(@nospecialize(n), @nospecialize(lb_arg), @nospecialize(ub isa(nval, Symbol) || return Union{} if isa(lb_arg, Const) lb = lb_arg.val - elseif isType(lb_arg) - lb = lb_arg.parameters[1] - lb_certain = false else - return TypeVar + lb_arg = widenslotwrapper(lb_arg) + if isType(lb_arg) + lb = lb_arg.parameters[1] + lb_certain = false + else + return TypeVar + end end if isa(ub_arg, Const) ub = ub_arg.val - elseif isType(ub_arg) - ub = ub_arg.parameters[1] - ub_certain = false else - return TypeVar + ub_arg = widenslotwrapper(ub_arg) + if isType(ub_arg) + ub = ub_arg.parameters[1] + ub_certain = false + else + return TypeVar + end end tv = TypeVar(nval, lb, ub) return PartialTypeVar(tv, lb_certain, ub_certain) end return TypeVar end -function typebound_nothrow(b) +@nospecs function typebound_nothrow(b) b = widenconst(b) (b ⊑ TypeVar) && return true if isType(b) @@ -451,27 +581,23 @@ function typebound_nothrow(b) end return false end -function typevar_nothrow(n, lb, ub) - (n ⊑ Symbol) || return false +@nospecs function typevar_nothrow(𝕃::AbstractLattice, n, lb, ub) + ⊑ = Core.Compiler.:⊑(𝕃) + n ⊑ Symbol || return false typebound_nothrow(lb) || return false typebound_nothrow(ub) || return false return true end add_tfunc(Core._typevar, 3, 3, typevar_tfunc, 100) -add_tfunc(applicable, 1, INT_INF, (@nospecialize(f), args...)->Bool, 100) -add_tfunc(Core.Intrinsics.arraylen, 1, 1, @nospecialize(x)->Int, 4) -function arraysize_tfunc(@nospecialize(ary), @nospecialize(dim)) +@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) -function arraysize_nothrow(argtypes::Vector{Any}) - length(argtypes) == 2 || return false - ary = argtypes[1] - dim = argtypes[2] +@nospecs function arraysize_nothrow(ary, dim) ary ⊑ Array || return false if isa(dim, Const) dimval = dim.val @@ -480,6 +606,37 @@ function arraysize_nothrow(argtypes::Vector{Any}) return false end +struct MemoryOrder x::Cint end +const MEMORY_ORDER_UNSPECIFIED = MemoryOrder(-2) +const MEMORY_ORDER_INVALID = MemoryOrder(-1) +const MEMORY_ORDER_NOTATOMIC = MemoryOrder(0) +const MEMORY_ORDER_UNORDERED = MemoryOrder(1) +const MEMORY_ORDER_MONOTONIC = MemoryOrder(2) +const MEMORY_ORDER_CONSUME = MemoryOrder(3) +const MEMORY_ORDER_ACQUIRE = MemoryOrder(4) +const MEMORY_ORDER_RELEASE = MemoryOrder(5) +const MEMORY_ORDER_ACQ_REL = MemoryOrder(6) +const MEMORY_ORDER_SEQ_CST = MemoryOrder(7) + +function get_atomic_order(order::Symbol, loading::Bool, storing::Bool) + if order === :not_atomic + return MEMORY_ORDER_NOTATOMIC + elseif order === :unordered && (loading ⊻ storing) + return MEMORY_ORDER_UNORDERED + elseif order === :monotonic && (loading | storing) + return MEMORY_ORDER_MONOTONIC + elseif order === :acquire && loading + return MEMORY_ORDER_ACQUIRE + elseif order === :release && storing + return MEMORY_ORDER_RELEASE + elseif order === :acquire_release && (loading & storing) + return MEMORY_ORDER_ACQ_REL + elseif order === :sequentially_consistent + return MEMORY_ORDER_SEQ_CST + end + return MEMORY_ORDER_INVALID +end + function pointer_eltype(@nospecialize(ptr)) a = widenconst(ptr) if !has_free_typevars(a) @@ -492,8 +649,26 @@ function pointer_eltype(@nospecialize(ptr)) end return Any end -function atomic_pointermodify_tfunc(ptr, op, v, order) - @nospecialize + +@nospecs function pointerref_tfunc(𝕃::AbstractLattice, a, i, align) + return pointer_eltype(a) +end +@nospecs function pointerset_tfunc(𝕃::AbstractLattice, a, v, i, align) + return a +end +@nospecs function atomic_fence_tfunc(𝕃::AbstractLattice, order) + return Nothing +end +@nospecs function atomic_pointerref_tfunc(𝕃::AbstractLattice, a, order) + return pointer_eltype(a) +end +@nospecs function atomic_pointerset_tfunc(𝕃::AbstractLattice, a, v, order) + return a +end +@nospecs function atomic_pointerswap_tfunc(𝕃::AbstractLattice, a, v, order) + return pointer_eltype(a) +end +@nospecs function atomic_pointermodify_tfunc(𝕃::AbstractLattice, ptr, op, v, order) a = widenconst(ptr) if !has_free_typevars(a) unw = unwrap_unionall(a) @@ -506,8 +681,7 @@ function atomic_pointermodify_tfunc(ptr, op, v, order) end return Pair end -function atomic_pointerreplace_tfunc(ptr, x, v, success_order, failure_order) - @nospecialize +@nospecs function atomic_pointerreplace_tfunc(𝕃::AbstractLattice, ptr, x, v, success_order, failure_order) a = widenconst(ptr) if !has_free_typevars(a) unw = unwrap_unionall(a) @@ -519,15 +693,37 @@ function atomic_pointerreplace_tfunc(ptr, x, v, success_order, failure_order) end return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T end -add_tfunc(pointerref, 3, 3, (a, i, align) -> (@nospecialize; pointer_eltype(a)), 4) -add_tfunc(pointerset, 4, 4, (a, v, i, align) -> (@nospecialize; a), 5) -add_tfunc(atomic_fence, 1, 1, (order) -> (@nospecialize; Nothing), 4) -add_tfunc(atomic_pointerref, 2, 2, (a, order) -> (@nospecialize; pointer_eltype(a)), 4) -add_tfunc(atomic_pointerset, 3, 3, (a, v, order) -> (@nospecialize; a), 5) -add_tfunc(atomic_pointerswap, 3, 3, (a, v, order) -> (@nospecialize; pointer_eltype(a)), 5) +add_tfunc(pointerref, 3, 3, pointerref_tfunc, 4) +add_tfunc(pointerset, 4, 4, pointerset_tfunc, 5) +add_tfunc(atomic_fence, 1, 1, atomic_fence_tfunc, 4) +add_tfunc(atomic_pointerref, 2, 2, atomic_pointerref_tfunc, 4) +add_tfunc(atomic_pointerset, 3, 3, atomic_pointerset_tfunc, 5) +add_tfunc(atomic_pointerswap, 3, 3, atomic_pointerswap_tfunc, 5) add_tfunc(atomic_pointermodify, 4, 4, atomic_pointermodify_tfunc, 5) add_tfunc(atomic_pointerreplace, 5, 5, atomic_pointerreplace_tfunc, 5) -add_tfunc(donotdelete, 0, INT_INF, (@nospecialize args...)->Nothing, 0) +add_tfunc(donotdelete, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->Nothing), 0) +@nospecs function compilerbarrier_tfunc(𝕃::AbstractLattice, setting, val) + # strongest barrier if a precise information isn't available at compiler time + # XXX we may want to have "compile-time" error instead for such case + isa(setting, Const) || return Any + setting = setting.val + isa(setting, Symbol) || return Any + if setting === :const + return widenconst(val) + elseif setting === :conditional + return widenconditional(val) + elseif setting === :type + return Any + else + return Bottom + end +end +add_tfunc(compilerbarrier, 2, 2, compilerbarrier_tfunc, 5) +add_tfunc(Core.finalizer, 2, 4, @nospecs((𝕃::AbstractLattice, args...)->Nothing), 5) + +@nospecs function compilerbarrier_nothrow(setting, val) + return isa(setting, Const) && contains_is((:type, :const, :conditional), setting.val) +end # more accurate typeof_tfunc for vararg tuples abstract only in length function typeof_concrete_vararg(t::DataType) @@ -545,7 +741,7 @@ function typeof_concrete_vararg(t::DataType) return nothing end -function typeof_tfunc(@nospecialize(t)) +@nospecs function typeof_tfunc(𝕃::AbstractLattice, t) isa(t, Const) && return Const(typeof(t.val)) t = widenconst(t) if isType(t) @@ -566,8 +762,8 @@ function typeof_tfunc(@nospecialize(t)) return Type{<:t} end elseif isa(t, Union) - a = widenconst(_typeof_tfunc(t.a)) - b = widenconst(_typeof_tfunc(t.b)) + a = widenconst(_typeof_tfunc(𝕃, t.a)) + b = widenconst(_typeof_tfunc(𝕃, t.b)) return Union{a, b} elseif isa(t, UnionAll) u = unwrap_unionall(t) @@ -581,27 +777,38 @@ function typeof_tfunc(@nospecialize(t)) return rewrap_unionall(Type{u}, t) end end - return rewrap_unionall(widenconst(typeof_tfunc(u)), t) + return rewrap_unionall(widenconst(typeof_tfunc(𝕃, u)), t) end return DataType # typeof(anything)::DataType end # helper function of `typeof_tfunc`, which accepts `TypeVar` -function _typeof_tfunc(@nospecialize(t)) +@nospecs function _typeof_tfunc(𝕃::AbstractLattice, t) if isa(t, TypeVar) - return t.ub !== Any ? _typeof_tfunc(t.ub) : DataType + return t.ub !== Any ? _typeof_tfunc(𝕃, t.ub) : DataType end - return typeof_tfunc(t) + return typeof_tfunc(𝕃, t) end add_tfunc(typeof, 1, 1, typeof_tfunc, 1) -function typeassert_tfunc(@nospecialize(v), @nospecialize(t)) +@nospecs function typeassert_tfunc(𝕃::AbstractLattice, v, t) t = instanceof_tfunc(t)[1] t === Any && return v - return tmeet(v, t) + return tmeet(𝕃, v, t) end add_tfunc(typeassert, 2, 2, typeassert_tfunc, 4) -function isa_tfunc(@nospecialize(v), @nospecialize(tt)) +@nospecs function typeassert_nothrow(𝕃::AbstractLattice, v, t) + ⊑ = Core.Compiler.:⊑(𝕃) + # ty, exact = instanceof_tfunc(t) + # return exact && v ⊑ ty + if (isType(t) && !has_free_typevars(t) && v ⊑ t.parameters[1]) || + (isa(t, Const) && isa(t.val, Type) && v ⊑ t.val) + return true + end + return false +end + +@nospecs function isa_tfunc(𝕃::AbstractLattice, v, tt) t, isexact = instanceof_tfunc(tt) if t === Bottom # check if t could be equivalent to typeof(Bottom), since that's valid in `isa`, but the set of `v` is empty @@ -610,7 +817,7 @@ function isa_tfunc(@nospecialize(v), @nospecialize(tt)) return Const(false) end if !has_free_typevars(t) - if v ⊑ t + if ⊑(𝕃, v, t) if isexact && isnotbrokensubtype(v, t) return Const(true) end @@ -636,7 +843,12 @@ function isa_tfunc(@nospecialize(v), @nospecialize(tt)) end add_tfunc(isa, 2, 2, isa_tfunc, 1) -function subtype_tfunc(@nospecialize(a), @nospecialize(b)) +@nospecs function isa_nothrow(𝕃::AbstractLattice, obj, typ) + ⊑ = Core.Compiler.:⊑(𝕃) + return typ ⊑ Type +end + +@nospecs function subtype_tfunc(𝕃::AbstractLattice, a, b) a, isexact_a = instanceof_tfunc(a) b, isexact_b = instanceof_tfunc(b) if !has_free_typevars(a) && !has_free_typevars(b) @@ -654,6 +866,11 @@ function subtype_tfunc(@nospecialize(a), @nospecialize(b)) end add_tfunc(<:, 2, 2, subtype_tfunc, 10) +@nospecs function subtype_nothrow(𝕃::AbstractLattice, lty, rty) + ⊑ = Core.Compiler.:⊑(𝕃) + return lty ⊑ Type && rty ⊑ Type +end + function fieldcount_noerror(@nospecialize t) if t isa UnionAll || t isa Union t = argument_datatype(t) @@ -685,7 +902,6 @@ function fieldcount_noerror(@nospecialize t) return isdefined(t, :types) ? length(t.types) : length(t.name.names) end - function try_compute_fieldidx(typ::DataType, @nospecialize(field)) if isa(field, Symbol) field = fieldindex(typ, field, false) @@ -701,39 +917,65 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field)) return field end -function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing, Type{Bool}} - if length(argtypes) == 2 - boundscheck = Bool - elseif length(argtypes) == 3 - boundscheck = argtypes[3] - if boundscheck === Const(:not_atomic) # TODO: this is assuming not atomic - boundscheck = Bool - end +function getfield_boundscheck((; fargs, argtypes)::ArgInfo) # Symbol + farg = nothing + if length(argtypes) == 3 + return :on elseif length(argtypes) == 4 + fargs !== nothing && (farg = fargs[4]) boundscheck = argtypes[4] + isvarargtype(boundscheck) && return :unknown + if widenconst(boundscheck) === Symbol + return :on + end + elseif length(argtypes) == 5 + fargs !== nothing && (farg = fargs[5]) + boundscheck = argtypes[5] else - return nothing + return :unknown end - widenconst(boundscheck) !== Bool && return nothing + isvarargtype(boundscheck) && return :unknown boundscheck = widenconditional(boundscheck) - if isa(boundscheck, Const) - return boundscheck.val - else - return Bool + if widenconst(boundscheck) === Bool + if isa(boundscheck, Const) + return boundscheck.val::Bool ? :on : :off + elseif farg !== nothing && isexpr(farg, :boundscheck) + return :boundscheck + end end + return :unknown end -function getfield_nothrow(argtypes::Vector{Any}) - boundscheck = getfield_boundscheck(argtypes) - boundscheck === nothing && return false - return getfield_nothrow(argtypes[1], argtypes[2], !(boundscheck === false)) +function getfield_nothrow(𝕃::AbstractLattice, arginfo::ArgInfo, boundscheck::Symbol=getfield_boundscheck(arginfo)) + (;argtypes) = arginfo + boundscheck === :unknown && 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 + return false + end + isa(ordering, Const) || return false + ordering = ordering.val + isa(ordering, Symbol) || return false + if ordering !== :not_atomic # TODO: this is assuming not atomic + return false + end + return getfield_nothrow(𝕃, argtypes[2], argtypes[3], !(boundscheck === :off)) end -function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck::Bool) - # If we don't have boundscheck and don't know the field, don't even bother +@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 if boundscheck isa(name, Const) || return false end + ⊑ = Core.Compiler.:⊑(𝕃) + # 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) @@ -749,35 +991,39 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck:: end return isdefined(sv, nval) end - if !boundscheck && !isa(sv, Module) - # If bounds checking is disabled and all fields are assigned, - # we may assume that we don't throw - for i = 1:fieldcount(typeof(sv)) - isdefined(sv, i) || return false - end - return true + 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 end - return false + return true end s0 = widenconst(s00) s = unwrap_unionall(s0) if isa(s, Union) - return getfield_nothrow(rewrap_unionall(s.a, s00), name, boundscheck) && - getfield_nothrow(rewrap_unionall(s.b, s00), name, boundscheck) - elseif isa(s, DataType) + return getfield_nothrow(𝕃, rewrap_unionall(s.a, s00), name, boundscheck) && + getfield_nothrow(𝕃, rewrap_unionall(s.b, s00), name, boundscheck) + elseif isType(s) && isTypeDataType(s.parameters[1]) + s = s0 = DataType + end + if isa(s, DataType) # Can't say anything about abstract types isabstracttype(s) && return false - s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering == :not_atomic - # If all fields are always initialized, and bounds check is disabled, we can assume - # we don't throw + # If all fields are always initialized, and bounds check is disabled, + # we can assume we don't throw if !boundscheck && s.name.n_uninitialized == 0 + name ⊑ Int || name ⊑ Symbol || return false return true end # Else we need to know what the field is isa(name, Const) || return false field = try_compute_fieldidx(s, name.val) field === nothing && return false + isfieldatomic(s, field) && return false # TODO: currently we're only testing for ordering === :not_atomic field <= datatype_min_ninitialized(s) && return true # `try_compute_fieldidx` already check for field index bound. !isvatuple(s) && isbitstype(fieldtype(s0, field)) && return true @@ -786,15 +1032,13 @@ function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck:: return false end -function getfield_tfunc(s00, name, boundscheck_or_order) - @nospecialize +@nospecs function getfield_tfunc(𝕃::AbstractLattice, s00, name, boundscheck_or_order) t = isvarargtype(boundscheck_or_order) ? unwrapva(boundscheck_or_order) : widenconst(boundscheck_or_order) hasintersect(t, Symbol) || hasintersect(t, Bool) || return Bottom - return getfield_tfunc(s00, name) + return getfield_tfunc(𝕃, s00, name) end -function getfield_tfunc(s00, name, order, boundscheck) - @nospecialize +@nospecs function getfield_tfunc(𝕃::AbstractLattice, s00, name, order, boundscheck) hasintersect(widenconst(order), Symbol) || return Bottom if isvarargtype(boundscheck) t = unwrapva(boundscheck) @@ -802,18 +1046,82 @@ function getfield_tfunc(s00, name, order, boundscheck) else hasintersect(widenconst(boundscheck), Bool) || return Bottom end - return getfield_tfunc(s00, name) + return getfield_tfunc(𝕃, s00, name) end -getfield_tfunc(@nospecialize(s00), @nospecialize(name)) = _getfield_tfunc(s00, name, false) -function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool) - if isa(s00, Conditional) +@nospecs function getfield_tfunc(𝕃::AbstractLattice, s00, name) + _getfield_tfunc(𝕃, s00, name, false) +end + +function _getfield_fieldindex(s::DataType, name::Const) + nv = name.val + if isa(nv, Symbol) + nv = fieldindex(s, nv, false) + end + if isa(nv, Int) + return nv + end + return nothing +end + +function _getfield_tfunc_const(@nospecialize(sv), name::Const) + nv = _getfield_fieldindex(typeof(sv), name) + nv === nothing && return Bottom + if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv) + return Const(getfield(sv, nv)) + end + if isconst(typeof(sv), nv) + if isdefined(sv, nv) + return Const(getfield(sv, nv)) + end + return Bottom + end + return nothing +end + +@nospecs function _getfield_tfunc(𝕃::InferenceLattice, s00, name, setfield::Bool) + if isa(s00, LimitedAccuracy) + # This will error, but it's better than duplicating the error here + s00 = widenconst(s00) + end + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) +end + +@nospecs function _getfield_tfunc(𝕃::OptimizerLattice, s00, name, setfield::Bool) + # If undef, that's a Union, but that doesn't affect the rt when tmerged + # into the unwrapped result. + isa(s00, MaybeUndef) && (s00 = s00.typ) + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) +end + +@nospecs function _getfield_tfunc(𝕃::AnyConditionalsLattice, s00, name, setfield::Bool) + if isa(s00, AnyConditional) return Bottom # Bool has no fields - elseif isa(s00, Const) || isconstType(s00) - if !isa(s00, Const) - sv = s00.parameters[1] - else - sv = s00.val + end + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) +end + +@nospecs function _getfield_tfunc(𝕃::AnyMustAliasesLattice, s00, name, setfield::Bool) + return _getfield_tfunc(widenlattice(𝕃), widenmustalias(s00), name, setfield) +end + +@nospecs function _getfield_tfunc(𝕃::PartialsLattice, s00, name, setfield::Bool) + if isa(s00, PartialStruct) + s = widenconst(s00) + sty = unwrap_unionall(s)::DataType + if isa(name, Const) + nv = _getfield_fieldindex(sty, name) + if isa(nv, Int) && 1 <= nv <= length(s00.fields) + return unwrapva(s00.fields[nv]) + end end + s00 = s + end + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) +end + +@nospecs function _getfield_tfunc(𝕃::ConstsLattice, s00, name, setfield::Bool) + if isa(s00, Const) + sv = s00.val if isa(name, Const) nv = name.val if isa(sv, Module) @@ -823,45 +1131,44 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool end return Bottom end - if isa(nv, Symbol) - nv = fieldindex(typeof(sv), nv, false) - end - if !isa(nv, Int) - return Bottom - end - if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv) - return Const(getfield(sv, nv)) - end - if isconst(typeof(sv), nv) - if isdefined(sv, nv) - return Const(getfield(sv, nv)) - end - return Union{} - end + r = _getfield_tfunc_const(sv, name) + r !== nothing && return r end - s = typeof(sv) - elseif isa(s00, PartialStruct) - s = widenconst(s00) - sty = unwrap_unionall(s)::DataType - if isa(name, Const) - nv = name.val - if isa(nv, Symbol) - nv = fieldindex(sty, nv, false) + s00 = widenconst(s00) + end + return _getfield_tfunc(widenlattice(𝕃), s00, name, setfield) +end + +@nospecs function _getfield_tfunc(𝕃::JLTypeLattice, s00, name, setfield::Bool) + s = unwrap_unionall(s00) + if isa(s, Union) + return tmerge(_getfield_tfunc(𝕃, rewrap_unionall(s.a, s00), name, setfield), + _getfield_tfunc(𝕃, rewrap_unionall(s.b, s00), name, setfield)) + end + if isType(s) + if isconstType(s) + sv = (s00::DataType).parameters[1] + if isa(name, Const) + r = _getfield_tfunc_const(sv, name) + r !== nothing && return r end - if isa(nv, Int) && 1 <= nv <= length(s00.fields) - return unwrapva(s00.fields[nv]) + s = typeof(sv) + else + sv = s.parameters[1] + if isTypeDataType(sv) && isa(name, Const) + nv = _getfield_fieldindex(DataType, name)::Int + if nv == DATATYPE_NAME_FIELDINDEX + # N.B. This only works for fields that do not depend on type + # parameters (which we do not know here). + return Const(sv.name) + end + s = DataType end end - else - s = unwrap_unionall(s00) - end - if isa(s, Union) - return tmerge(_getfield_tfunc(rewrap_unionall(s.a, s00), name, setfield), - _getfield_tfunc(rewrap_unionall(s.b, s00), name, setfield)) end isa(s, DataType) || return Any isabstracttype(s) && return Any - if s <: Tuple && !(Int <: widenconst(name)) + if s <: Tuple && !hasintersect(widenconst(name), Int) return Bottom end if s <: Module @@ -884,7 +1191,7 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool if !(_ts <: Tuple) return Any end - return _getfield_tfunc(_ts, name, setfield) + return _getfield_tfunc(𝕃, _ts, name, setfield) end ftypes = datatype_fieldtypes(s) nf = length(ftypes) @@ -914,13 +1221,8 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool end return t end - fld = name.val - if isa(fld, Symbol) - fld = fieldindex(s, fld, false) - end - if !isa(fld, Int) - return Bottom - end + fld = _getfield_fieldindex(s, name) + fld === nothing && return Bottom if s <: Tuple && fld >= nf && isvarargtype(ftypes[nf]) return rewrap_unionall(unwrapva(ftypes[nf]), s00) end @@ -936,27 +1238,88 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool return rewrap_unionall(R, s00) end -function setfield!_tfunc(o, f, v, order) - @nospecialize +@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 + 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) + 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, + # so we don't need to be conservative here + return true + end + if !isa(name, Const) + isvarargtype(name) && return false + if !hasintersect(widenconst(name), Union{Int,Symbol}) + return true # no undefined behavior if thrown + end + # field isn't known precisely, but let's check if all the fields can't be + # initialized with undefined value so to avoid being too conservative + fcnt = fieldcount_noerror(typ) + fcnt === nothing && return false + all(i::Int->is_undefref_fieldtype(fieldtype(typ,i)), (datatype_min_ninitialized(typ)+1):fcnt) && return true + return false + end + name = name.val + if isa(name, Symbol) + fidx = fieldindex(typ, name, false) + fidx === nothing && return true # no undefined behavior if thrown + elseif isa(name, Int) + fidx = name + else + return true # no undefined behavior if thrown + end + fcnt = fieldcount_noerror(typ) + fcnt === nothing && return false + 0 < fidx ≤ fcnt || return true # no undefined behavior if thrown + ftyp = fieldtype(typ, fidx) + is_undefref_fieldtype(ftyp) && return true + return fidx ≤ datatype_min_ninitialized(typ) +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., +# - 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 +function is_undefref_fieldtype(@nospecialize ftyp) + return !has_free_typevars(ftyp) && !allocatedinline(ftyp) +end + +@nospecs function setfield!_tfunc(𝕃::AbstractLattice, o, f, v, order) if !isvarargtype(order) hasintersect(widenconst(order), Symbol) || return Bottom end - return setfield!_tfunc(o, f, v) + return setfield!_tfunc(𝕃, o, f, v) end -function setfield!_tfunc(o, f, v) - @nospecialize +@nospecs function setfield!_tfunc(𝕃::AbstractLattice, o, f, v) mutability_errorcheck(o) || return Bottom - ft = _getfield_tfunc(o, f, true) + ft = _getfield_tfunc(𝕃, o, f, true) ft === Bottom && return Bottom hasintersect(widenconst(v), widenconst(ft)) || return Bottom return v end -function mutability_errorcheck(@nospecialize obj) - objt0 = widenconst(obj) +mutability_errorcheck(@nospecialize obj) = _mutability_errorcheck(widenconst(obj)) +function _mutability_errorcheck(@nospecialize objt0) objt = unwrap_unionall(objt0) if isa(objt, Union) - return mutability_errorcheck(rewrap_unionall(objt.a, objt0)) || - mutability_errorcheck(rewrap_unionall(objt.b, objt0)) + return _mutability_errorcheck(rewrap_unionall(objt.a, objt0)) || + _mutability_errorcheck(rewrap_unionall(objt.b, objt0)) elseif isa(objt, DataType) # Can't say anything about abstract types isabstracttype(objt) && return true @@ -965,87 +1328,89 @@ function mutability_errorcheck(@nospecialize obj) return true end -function setfield!_nothrow(argtypes::Vector{Any}) - if length(argtypes) == 4 - order = argtypes[4] - order === Const(:not_atomic) || return false # currently setfield!_nothrow is assuming not atomic - else - length(argtypes) == 3 || return false - end - return setfield!_nothrow(argtypes[1], argtypes[2], argtypes[3]) +@nospecs function setfield!_nothrow(𝕃::AbstractLattice, s00, name, v, order) + order === Const(:not_atomic) || return false # currently setfield!_nothrow is assuming not atomic + return setfield!_nothrow(𝕃, s00, name, v) end -function setfield!_nothrow(s00, name, v) - @nospecialize +@nospecs function setfield!_nothrow(𝕃::AbstractLattice, s00, name, v) + ⊑ = Core.Compiler.:⊑(𝕃) s0 = widenconst(s00) s = unwrap_unionall(s0) if isa(s, Union) - return setfield!_nothrow(rewrap_unionall(s.a, s00), name, v) && - setfield!_nothrow(rewrap_unionall(s.b, s00), name, v) + return setfield!_nothrow(𝕃, rewrap_unionall(s.a, s00), name, v) && + setfield!_nothrow(𝕃, rewrap_unionall(s.b, s00), name, v) elseif isa(s, DataType) # Can't say anything about abstract types isabstracttype(s) && return false ismutabletype(s) || return false - s.name.atomicfields == C_NULL || return false # TODO: currently we're only testing for ordering == :not_atomic isa(name, Const) || return false field = try_compute_fieldidx(s, name.val) field === nothing && return false # `try_compute_fieldidx` already check for field index bound. 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) return v ⊑ v_expected end return false end -swapfield!_tfunc(o, f, v, order) = (@nospecialize; getfield_tfunc(o, f)) -swapfield!_tfunc(o, f, v) = (@nospecialize; getfield_tfunc(o, f)) -modifyfield!_tfunc(o, f, op, v, order) = (@nospecialize; modifyfield!_tfunc(o, f, op, v)) -function modifyfield!_tfunc(o, f, op, v) - @nospecialize - T = _fieldtype_tfunc(o, isconcretetype(o), f) +@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v, order) + return getfield_tfunc(𝕃, o, f) +end +@nospecs function swapfield!_tfunc(𝕃::AbstractLattice, o, f, v) + 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) + T = _fieldtype_tfunc(𝕃, o, f, isconcretetype(o)) T === Bottom && return Bottom PT = Const(Pair) - return instanceof_tfunc(apply_type_tfunc(PT, T, T))[1] + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T, T))[1] end -function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState) +function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) nargs = length(argtypes) if !isempty(argtypes) && isvarargtype(argtypes[nargs]) - nargs - 1 <= 6 || return CallMeta(Bottom, false) - nargs > 3 || return CallMeta(Any, false) + nargs - 1 <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + nargs > 3 || return CallMeta(Any, EFFECTS_UNKNOWN, NoCallInfo()) else - 5 <= nargs <= 6 || return CallMeta(Bottom, false) + 5 <= nargs <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) end + 𝕃ᵢ = typeinf_lattice(interp) o = unwrapva(argtypes[2]) f = unwrapva(argtypes[3]) - RT = modifyfield!_tfunc(o, f, Any, Any) - info = false + RT = modifyfield!_tfunc(𝕃ᵢ, o, f, Any, Any) + info = NoCallInfo() if nargs >= 5 && RT !== Bottom # we may be able to refine this to a PartialStruct by analyzing `op(o.f, v)::T` # as well as compute the info for the method matches op = unwrapva(argtypes[4]) v = unwrapva(argtypes[5]) - TF = getfield_tfunc(o, f) - push!(sv.ssavalue_uses[sv.currpc], sv.currpc) # temporarily disable `call_result_unused` check for this call - callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), sv, #=max_methods=# 1) - pop!(sv.ssavalue_uses[sv.currpc], sv.currpc) + TF = getfield_tfunc(𝕃ᵢ, o, f) + 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 RT = Bottom - elseif isconcretetype(RT) && has_nontrivial_const_info(TF2) # isconcrete condition required to form a PartialStruct + elseif isconcretetype(RT) && has_nontrivial_extended_info(𝕃ᵢ, TF2) # isconcrete condition required to form a PartialStruct RT = PartialStruct(RT, Any[TF, TF2]) end - info = callinfo.info + info = ModifyFieldInfo(callinfo.info) end - return CallMeta(RT, info) + return CallMeta(RT, Effects(), info) end -replacefield!_tfunc(o, f, x, v, success_order, failure_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) -replacefield!_tfunc(o, f, x, v, success_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v)) -function replacefield!_tfunc(o, f, x, v) - @nospecialize - T = _fieldtype_tfunc(o, isconcretetype(o), f) +@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))[1] + return instanceof_tfunc(apply_type_tfunc(𝕃, PT, T))[1] end # we could use tuple_tfunc instead of widenconst, but `o` is mutable, so that is unlikely to be beneficial @@ -1057,8 +1422,9 @@ add_tfunc(swapfield!, 3, 4, swapfield!_tfunc, 3) add_tfunc(modifyfield!, 4, 5, modifyfield!_tfunc, 3) add_tfunc(replacefield!, 4, 6, replacefield!_tfunc, 3) -function fieldtype_nothrow(@nospecialize(s0), @nospecialize(name)) +@nospecs function fieldtype_nothrow(𝕃::AbstractLattice, s0, name) s0 === Bottom && return true # unreachable + ⊑ = Core.Compiler.:⊑(𝕃) if s0 === Any || s0 === Type || DataType ⊑ s0 || UnionAll ⊑ s0 # We have no idea return false @@ -1072,8 +1438,8 @@ function fieldtype_nothrow(@nospecialize(s0), @nospecialize(name)) su = unwrap_unionall(s0) if isa(su, Union) - return fieldtype_nothrow(rewrap_unionall(su.a, s0), name) && - fieldtype_nothrow(rewrap_unionall(su.b, s0), name) + return fieldtype_nothrow(𝕃, rewrap_unionall(su.a, s0), name) && + fieldtype_nothrow(𝕃, rewrap_unionall(su.b, s0), name) end s, exact = instanceof_tfunc(s0) @@ -1114,8 +1480,11 @@ function _fieldtype_nothrow(@nospecialize(s), exact::Bool, name::Const) return true end -fieldtype_tfunc(s0, name, boundscheck) = (@nospecialize; fieldtype_tfunc(s0, name)) -function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) +@nospecs function fieldtype_tfunc(𝕃::AbstractLattice, s0, name, boundscheck) + return fieldtype_tfunc(𝕃, s0, name) +end +@nospecs function fieldtype_tfunc(𝕃::AbstractLattice, s0, name) + s0 = widenmustalias(s0) if s0 === Bottom return Bottom end @@ -1134,21 +1503,21 @@ function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) su = unwrap_unionall(s0) if isa(su, Union) - return tmerge(fieldtype_tfunc(rewrap_unionall(su.a, s0), name), - fieldtype_tfunc(rewrap_unionall(su.b, s0), name)) + return tmerge(fieldtype_tfunc(𝕃, rewrap_unionall(su.a, s0), name), + fieldtype_tfunc(𝕃, rewrap_unionall(su.b, s0), name)) end s, exact = instanceof_tfunc(s0) s === Bottom && return Bottom - return _fieldtype_tfunc(s, exact, name) + return _fieldtype_tfunc(𝕃, s, name, exact) end -function _fieldtype_tfunc(@nospecialize(s), exact::Bool, @nospecialize(name)) +@nospecs function _fieldtype_tfunc(𝕃::AbstractLattice, s, name, exact::Bool) exact = exact && !has_free_typevars(s) u = unwrap_unionall(s) if isa(u, Union) - ta0 = _fieldtype_tfunc(rewrap_unionall(u.a, s), exact, name) - tb0 = _fieldtype_tfunc(rewrap_unionall(u.b, s), exact, name) + ta0 = _fieldtype_tfunc(𝕃, rewrap_unionall(u.a, s), name, exact) + tb0 = _fieldtype_tfunc(𝕃, rewrap_unionall(u.b, s), name, exact) ta0 ⊑ tb0 && return tb0 tb0 ⊑ ta0 && return ta0 ta, exacta, _, istypea = instanceof_tfunc(ta0) @@ -1247,22 +1616,11 @@ end add_tfunc(fieldtype, 2, 3, fieldtype_tfunc, 0) # Like `valid_tparam`, but in the type domain. -function valid_tparam_type(T::DataType) - T === Symbol && return true - isbitstype(T) && return true - if T <: Tuple - isconcretetype(T) || return false - for P in T.parameters - (P === Symbol || isbitstype(P)) || return false - end - return true - end - return false -end +valid_tparam_type(T::DataType) = valid_typeof_tparam(T) valid_tparam_type(U::Union) = valid_tparam_type(U.a) && valid_tparam_type(U.b) valid_tparam_type(U::UnionAll) = valid_tparam_type(unwrap_unionall(U)) -function apply_type_nothrow(argtypes::Array{Any, 1}, @nospecialize(rt)) +function apply_type_nothrow(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospecialize(rt)) rt === Type && return false length(argtypes) >= 1 || return false headtypetype = argtypes[1] @@ -1278,10 +1636,11 @@ function apply_type_nothrow(argtypes::Array{Any, 1}, @nospecialize(rt)) (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) for i = 2:length(argtypes) isa(u, UnionAll) || return false ai = widenconditional(argtypes[i]) - if ai ⊑ TypeVar || ai === DataType + if ⊑(𝕃, ai, TypeVar) || ai === DataType # We don't know anything about the bounds of this typevar, but as # long as the UnionAll is not constrained, that's ok. if !(u.var.lb === Union{} && u.var.ub === Any) @@ -1323,7 +1682,8 @@ const _tvarnames = Symbol[:_A, :_B, :_C, :_D, :_E, :_F, :_G, :_H, :_I, :_J, :_K, :_N, :_O, :_P, :_Q, :_R, :_S, :_T, :_U, :_V, :_W, :_X, :_Y, :_Z] # TODO: handle e.g. apply_type(T, R::Union{Type{Int32},Type{Float64}}) -function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) +@nospecs function apply_type_tfunc(𝕃::AbstractLattice, headtypetype, args...) + headtypetype = widenslotwrapper(headtypetype) if isa(headtypetype, Const) headtype = headtypetype.val elseif isconstType(headtypetype) @@ -1359,7 +1719,7 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) end end if largs == 1 # Union{T} --> T - u1 = typeintersect(widenconst(args[1]), Type) + u1 = typeintersect(widenconst(args[1]), Union{Type,TypeVar}) valid_as_lattice(u1) || return Bottom return u1 end @@ -1387,10 +1747,24 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) canconst = true tparams = Any[] outervars = TypeVar[] + + # first push the tailing vars from headtype into outervars + outer_start, ua = 0, headtype + while isa(ua, UnionAll) + if (outer_start += 1) > largs + push!(outervars, ua.var) + end + ua = ua.body + end + if largs > outer_start && isa(headtype, UnionAll) # e.g. !isvarargtype(ua) && !istuple + return Bottom # too many arguments + end + outer_start = outer_start - largs + 1 + varnamectr = 1 ua = headtype for i = 1:largs - ai = widenconditional(args[i]) + ai = widenslotwrapper(args[i]) if isType(ai) aip1 = ai.parameters[1] canconst &= !has_free_typevars(aip1) @@ -1403,34 +1777,61 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) push!(tparams, ai.tv) else uncertain = true - # These blocks improve type info but make compilation a bit slower. - # XXX - #unw = unwrap_unionall(ai) - #isT = isType(unw) - #if isT && isa(ai,UnionAll) && contains_is(outervars, ai.var) - # ai = rename_unionall(ai) - # unw = unwrap_unionall(ai) - #end - ai_w = widenconst(ai) - ub = ai_w isa Type && ai_w <: Type ? instanceof_tfunc(ai)[1] : Any + unw = unwrap_unionall(ai) + isT = isType(unw) + # compute our desired upper bound value + if isT + ub = rewrap_unionall(unw.parameters[1], ai) + else + ub = Any + end + if !istuple && unionall_depth(ai) > 3 + # Heuristic: if we are adding more than N unknown parameters here to the + # outer type, use the wrapper type, instead of letting it nest more + # complexity here. This is not monotonic, but seems to work out pretty well. + if isT + ub = unwrap_unionall(unw.parameters[1]) + if ub isa DataType + ub = ub.name.wrapper + unw = Type{unwrap_unionall(ub)} + ai = rewrap_unionall(unw, ub) + else + isT = false + ai = unw = ub = Any + end + else + isT = false + ai = unw = ub = Any + end + elseif !isT + # if we didn't have isType to compute ub directly, try to use instanceof_tfunc to refine this guess + ai_w = widenconst(ai) + ub = ai_w isa Type && ai_w <: Type ? instanceof_tfunc(ai)[1] : Any + end if istuple # in the last parameter of a Tuple type, if the upper bound is Any # then this could be a Vararg type. if i == largs && ub === Any - push!(tparams, Vararg) - # XXX - #elseif isT - # push!(tparams, rewrap_unionall(unw.parameters[1], ai)) - else - push!(tparams, Any) + ub = Vararg + end + push!(tparams, ub) + elseif isT + tai = ai + while isa(tai, UnionAll) + # make sure vars introduced here are unique + if contains_is(outervars, tai.var) + ai = rename_unionall(ai) + unw = unwrap_unionall(ai)::DataType + # ub = rewrap_unionall(unw, ai) + break + end + tai = tai.body + end + push!(tparams, unw.parameters[1]) + while isa(ai, UnionAll) + push!(outervars, ai.var) + ai = ai.body end - # XXX - #elseif isT - # push!(tparams, unw.parameters[1]) - # while isa(ai, UnionAll) - # push!(outervars, ai.var) - # ai = ai.body - # end else # Is this the second parameter to a NamedTuple? if isa(uw, DataType) && uw.name === _NAMEDTUPLE_NAME && isa(ua, UnionAll) && uw.parameters[2] === ua.var @@ -1450,19 +1851,40 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) push!(outervars, v) end end - if isa(ua, UnionAll) + if ua isa UnionAll ua = ua.body - else - ua = nothing + #otherwise, sometimes ua isa Vararg (Core.TypeofVararg) or Tuple (DataType) end end local appl try appl = apply_type(headtype, tparams...) catch ex - # type instantiation might fail if one of the type parameters - # doesn't match, which could happen if a type estimate is too coarse - return isvarargtype(headtype) ? TypeofVararg : Type{<:headtype} + # 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 + if !uncertain + return Union{} + end + canconst = false + uncertain = true + empty!(outervars) + outer_start = 1 + # FIXME: if these vars are substituted with TypeVar here, the result + # might be wider than the input, so should we use the `.name.wrapper` + # object here instead, to replace all of these outervars with + # unconstrained ones? Note that this code is nearly unreachable though, + # and possibly should simply return Union{} here also, since + # `apply_type` is already quite conservative about detecting and + # throwing errors. + appl = headtype + if isa(appl, UnionAll) + for _ = 1:largs + appl = appl::UnionAll + push!(outervars, appl.var) + appl = appl.body + end + end end !uncertain && canconst && return Const(appl) if isvarargtype(appl) @@ -1472,23 +1894,25 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) return Type{<:appl} end ans = Type{appl} - for i = length(outervars):-1:1 + for i = length(outervars):-1:outer_start ans = UnionAll(outervars[i], ans) end return ans end add_tfunc(apply_type, 1, INT_INF, apply_type_tfunc, 10) -function has_struct_const_info(x) - isa(x, PartialTypeVar) && return true - isa(x, Conditional) && return true - return has_nontrivial_const_info(x) -end - # convert the dispatch tuple type argtype to the real (concrete) type of # the tuple of those values -function tuple_tfunc(argtypes::Vector{Any}) - argtypes = anymap(widenconditional, argtypes) +function tuple_tfunc(𝕃::AbstractLattice, argtypes::Vector{Any}) + isempty(argtypes) && return Const(()) + argtypes = anymap(widenslotwrapper, argtypes) + if isvarargtype(argtypes[end]) && unwrapva(argtypes[end]) === Union{} + # Drop the Vararg in Tuple{...,Vararg{Union{}}} since it must be length 0. + # If there is a Vararg num also, it must be a TypeVar, and it must be + # zero, but that generally shouldn't show up here, since it implies a + # UnionAll context is missing around this. + pop!(argtypes) + end all_are_const = true for i in 1:length(argtypes) if !isa(argtypes[i], Const) @@ -1497,13 +1921,13 @@ function tuple_tfunc(argtypes::Vector{Any}) end end if all_are_const - return Const(ntuple(i -> argtypes[i].val, length(argtypes))) + return Const(ntuple(i::Int->argtypes[i].val, length(argtypes))) end params = Vector{Any}(undef, length(argtypes)) anyinfo = false for i in 1:length(argtypes) x = argtypes[i] - if has_struct_const_info(x) + if has_nontrivial_extended_info(𝕃, x) anyinfo = true else if !isvarargtype(x) @@ -1515,6 +1939,10 @@ function tuple_tfunc(argtypes::Vector{Any}) params[i] = typeof(x.val) else x = isvarargtype(x) ? x : widenconst(x) + # since there don't exist any values whose runtime type are `Tuple{Type{...}}`, + # here we should turn such `Type{...}`-parameters to valid parameters, e.g. + # (::Type{Int},) -> Tuple{DataType} (or PartialStruct for more accuracy) + # (::Union{Type{Int32},Type{Int64}}) -> Tuple{Type} if isType(x) anyinfo = true xparam = x.parameters[1] @@ -1523,6 +1951,12 @@ function tuple_tfunc(argtypes::Vector{Any}) else params[i] = Type end + elseif iskindtype(x) + params[i] = x + elseif !isvarargtype(x) && hasintersect(x, Type) + params[i] = Union{x, Type} + elseif x === Union{} + return Bottom # argtypes is malformed, but try not to crash else params[i] = x end @@ -1530,14 +1964,14 @@ function tuple_tfunc(argtypes::Vector{Any}) end typ = Tuple{params...} # replace a singleton type with its equivalent Const object - isdefined(typ, :instance) && return Const(typ.instance) + issingletontype(typ) && return Const(typ.instance) return anyinfo ? PartialStruct(typ, argtypes) : typ end -arrayref_tfunc(@nospecialize(boundscheck), @nospecialize(ary), @nospecialize idxs...) = - _arrayref_tfunc(boundscheck, ary, idxs) -function _arrayref_tfunc(@nospecialize(boundscheck), @nospecialize(ary), - @nospecialize idxs::Tuple) +@nospecs function arrayref_tfunc(𝕃::AbstractLattice, boundscheck, ary, idxs...) + return _arrayref_tfunc(𝕃, boundscheck, ary, idxs) +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) @@ -1545,15 +1979,13 @@ end add_tfunc(arrayref, 3, INT_INF, arrayref_tfunc, 20) add_tfunc(const_arrayref, 3, INT_INF, arrayref_tfunc, 20) -function arrayset_tfunc(@nospecialize(boundscheck), @nospecialize(ary), @nospecialize(item), - @nospecialize idxs...) - hasintersect(widenconst(item), _arrayref_tfunc(boundscheck, ary, idxs)) || return Bottom +@nospecs function arrayset_tfunc(𝕃::AbstractLattice, boundscheck, ary, item, idxs...) + hasintersect(widenconst(item), _arrayref_tfunc(𝕃, boundscheck, ary, idxs)) || return Bottom return ary end add_tfunc(arrayset, 4, INT_INF, arrayset_tfunc, 20) -function array_builtin_common_errorcheck(@nospecialize(boundscheck), @nospecialize(ary), - @nospecialize idxs::Tuple) +@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) @@ -1580,9 +2012,7 @@ function array_elmtype(@nospecialize ary) return Any end -function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(lb), @nospecialize(ub), - @nospecialize(source), env::Vector{Any}, linfo::MethodInstance) - +@nospecs function opaque_closure_tfunc(𝕃::AbstractLattice, arg, lb, ub, source, env::Vector{Any}, linfo::MethodInstance) argt, argt_exact = instanceof_tfunc(arg) lbt, lb_exact = instanceof_tfunc(lb) if !lb_exact @@ -1596,7 +2026,7 @@ function _opaque_closure_tfunc(@nospecialize(arg), @nospecialize(lb), @nospecial (isa(source, Const) && isa(source.val, Method)) || return t - return PartialOpaque(t, tuple_tfunc(env), linfo, source.val) + return PartialOpaque(t, tuple_tfunc(𝕃, env), linfo, source.val) end # whether getindex for the elements can potentially throw UndefRef @@ -1611,18 +2041,20 @@ function array_type_undefable(@nospecialize(arytype)) end end -function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int) +function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int, isarrayref::Bool) length(argtypes) >= 4 || return false boundscheck = argtypes[1] arytype = argtypes[2] array_builtin_common_typecheck(boundscheck, arytype, argtypes, first_idx_idx) || return false - # If we could potentially throw undef ref errors, bail out now. - arytype = widenconst(arytype) - array_type_undefable(arytype) && return false + if isarrayref + # If we could potentially throw undef ref errors, bail out now. + arytype = widenconst(arytype) + array_type_undefable(arytype) && return false + end # If we have @inbounds (first argument is false), we're allowed to assume # we don't throw bounds errors. if isa(boundscheck, Const) - !(boundscheck.val::Bool) && return true + 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 @@ -1630,8 +2062,7 @@ function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int) return false end -function array_builtin_common_typecheck( - @nospecialize(boundscheck), @nospecialize(arytype), +@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) @@ -1640,7 +2071,7 @@ function array_builtin_common_typecheck( return true end -function arrayset_typecheck(@nospecialize(arytype), @nospecialize(elmtype)) +@nospecs function arrayset_typecheck(arytype, elmtype) # Check that we can determine the element type arytype = widenconst(arytype) isa(arytype, DataType) || return false @@ -1652,79 +2083,106 @@ function arrayset_typecheck(@nospecialize(arytype), @nospecialize(elmtype)) end # Query whether the given builtin is guaranteed not to throw given the argtypes -function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecialize(rt)) +@nospecs function _builtin_nothrow(𝕃::AbstractLattice, f, argtypes::Vector{Any}, rt) + ⊑ = Core.Compiler.:⊑(𝕃) if f === arrayset - array_builtin_common_nothrow(argtypes, 4) || return true + array_builtin_common_nothrow(argtypes, 4, #=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, 3) - elseif f === arraysize - return arraysize_nothrow(argtypes) + return array_builtin_common_nothrow(argtypes, 3, #=isarrayref=#true) elseif f === Core._expr length(argtypes) >= 1 || return false return argtypes[1] ⊑ Symbol + end + + # These builtins are not-vararg, so if we have varars, here, we can't guarantee + # 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 - length(argtypes) == 3 || return false - return typevar_nothrow(argtypes[1], argtypes[2], argtypes[3]) + na == 3 || return false + return typevar_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3]) elseif f === invoke return false elseif f === getfield - return getfield_nothrow(argtypes) + return getfield_nothrow(𝕃, ArgInfo(nothing, Any[Const(f), argtypes...])) + elseif f === setfield! + if na == 3 + return setfield!_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3]) + elseif na == 4 + return setfield!_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3], argtypes[4]) + end + return false elseif f === fieldtype - length(argtypes) == 2 || return false - return fieldtype_nothrow(argtypes[1], argtypes[2]) + na == 2 || return false + return fieldtype_nothrow(𝕃, argtypes[1], argtypes[2]) elseif f === apply_type - return apply_type_nothrow(argtypes, rt) + return apply_type_nothrow(𝕃, argtypes, rt) elseif f === isa - length(argtypes) == 2 || return false - return argtypes[2] ⊑ Type + na == 2 || return false + return isa_nothrow(𝕃, nothing, argtypes[2]) elseif f === (<:) - length(argtypes) == 2 || return false - return argtypes[1] ⊑ Type && argtypes[2] ⊑ Type + na == 2 || return false + return subtype_nothrow(𝕃, argtypes[1], argtypes[2]) elseif f === UnionAll - return length(argtypes) == 2 && - (argtypes[1] ⊑ TypeVar && argtypes[2] ⊑ Type) + return na == 2 && (argtypes[1] ⊑ TypeVar && argtypes[2] ⊑ Type) elseif f === isdefined - return isdefined_nothrow(argtypes) + return isdefined_nothrow(𝕃, argtypes) elseif f === Core.sizeof - length(argtypes) == 1 || return false + na == 1 || return false return sizeof_nothrow(argtypes[1]) - elseif f === Core.kwfunc - length(argtypes) == 1 || return false - return isa(rt, Const) elseif f === Core.ifelse - length(argtypes) == 3 || return false - return argtypes[1] ⊑ Bool + na == 3 || return false + return ifelse_nothrow(𝕃, argtypes[1], nothing, nothing) elseif f === typeassert - length(argtypes) == 2 || return false - a3 = argtypes[2] - if (isType(a3) && !has_free_typevars(a3) && argtypes[1] ⊑ a3.parameters[1]) || - (isa(a3, Const) && isa(a3.val, Type) && argtypes[1] ⊑ a3.val) - return true + na == 2 || return false + return typeassert_nothrow(𝕃, argtypes[1], argtypes[2]) + elseif f === getglobal + if na == 2 + return getglobal_nothrow(argtypes[1], argtypes[2]) + elseif na == 3 + return getglobal_nothrow(argtypes[1], argtypes[2], argtypes[3]) + end + return false + elseif f === setglobal! + if na == 3 + return setglobal!_nothrow(argtypes[1], argtypes[2], argtypes[3]) + elseif na == 4 + return setglobal!_nothrow(argtypes[1], argtypes[2], argtypes[3], argtypes[4]) end return false elseif f === Core.get_binding_type - return length(argtypes) == 2 + na == 2 || return false + return get_binding_type_nothrow(𝕃, argtypes[1], argtypes[2]) elseif f === donotdelete return true + elseif f === Core.finalizer + 2 <= na <= 4 || return false + # Core.finalizer does no error checking - that's done in Base.finalizer + return true + elseif f === Core.compilerbarrier + na == 2 || return false + return compilerbarrier_nothrow(argtypes[1], nothing) end return false end # known to be always effect-free (in particular nothrow) -const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields] - -# known to be effect-free (but not necessarily nothrow) -const _EFFECT_FREE_BUILTINS = [ - fieldtype, apply_type, isa, UnionAll, - getfield, arrayref, const_arrayref, isdefined, Core.sizeof, - Core.kwfunc, Core.ifelse, Core._typevar, (<:), - typeassert, throw, arraysize +const _PURE_BUILTINS = Any[ + tuple, + svec, + ===, + typeof, + nfields, ] const _CONSISTENT_BUILTINS = Any[ - tuple, # tuple is immutable, thus tuples of egal arguments are egal + tuple, # Tuple is immutable, thus tuples of egal arguments are egal + svec, # SimpleVector is immutable, thus svecs of egal arguments are egal ===, typeof, nfields, @@ -1733,77 +2191,250 @@ const _CONSISTENT_BUILTINS = Any[ isa, UnionAll, Core.sizeof, - Core.kwfunc, Core.ifelse, (<:), typeassert, - throw + throw, + setfield!, + donotdelete ] -const _SPECIAL_BUILTINS = Any[ - Core._apply_iterate +# known to be effect-free (but not necessarily nothrow) +const _EFFECT_FREE_BUILTINS = [ + fieldtype, + apply_type, + isa, + UnionAll, + getfield, + arrayref, + arraysize, + const_arrayref, + isdefined, + Core.sizeof, + Core.ifelse, + Core._typevar, + (<:), + typeassert, + throw, + getglobal, + compilerbarrier, ] -function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt) - if isa(f, IntrinsicFunction) - return intrinsic_effects(f, argtypes) - end +const _INACCESSIBLEMEM_BUILTINS = Any[ + (<:), + (===), + apply_type, + Core.ifelse, + Core.sizeof, + svec, + fieldtype, + isa, + isdefined, + nfields, + throw, + tuple, + typeassert, + typeof, + compilerbarrier, + Core._typevar, + donotdelete +] - @assert !contains_is(_SPECIAL_BUILTINS, f) +const _ARGMEM_BUILTINS = Any[ + arrayref, + arrayset, + arraysize, + modifyfield!, + replacefield!, + setfield!, + swapfield!, +] - nothrow = false - if (f === Core.getfield || f === Core.isdefined) && length(argtypes) >= 3 - # consistent if the argtype is immutable - if isvarargtype(argtypes[2]) - return Effects(Effects(), effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE) - end - s = widenconst(argtypes[2]) - if isType(s) || !isa(s, DataType) || isabstracttype(s) - return Effects(Effects(), effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE) - end - s = s::DataType - ipo_consistent = !ismutabletype(s) - nothrow = false - if f === Core.getfield && !isvarargtype(argtypes[end]) && - getfield_boundscheck(argtypes[2:end]) !== true +const _INCONSISTENT_INTRINSICS = Any[ + Intrinsics.pointerref, # this one is volatile + Intrinsics.sqrt_llvm_fast, # this one may differ at runtime (by a few ulps) + Intrinsics.have_fma, # this one depends on the runtime environment + Intrinsics.cglobal, # cglobal lookup answer changes at runtime + # ... and list fastmath intrinsics: + # join(string.("Intrinsics.", sort(filter(endswith("_fast")∘string, names(Core.Intrinsics)))), ",\n") + Intrinsics.add_float_fast, + Intrinsics.div_float_fast, + Intrinsics.eq_float_fast, + Intrinsics.le_float_fast, + Intrinsics.lt_float_fast, + Intrinsics.mul_float_fast, + Intrinsics.ne_float_fast, + Intrinsics.neg_float_fast, + Intrinsics.sqrt_llvm_fast, + Intrinsics.sub_float_fast, + # TODO needs to revive #31193 to mark this as inconsistent to be accurate + # while preserving the currently optimizations for many math operations + # Intrinsics.muladd_float, # this is not interprocedurally consistent +] + +const _SPECIAL_BUILTINS = Any[ + Core._apply_iterate, +] + +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) + 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 + end + nothrow = isdefined_nothrow(𝕃, argtypes) + if hasintersect(widenconst(wobj), Module) + inaccessiblememonly = ALWAYS_FALSE + elseif is_mutation_free_argtype(wobj) + inaccessiblememonly = ALWAYS_TRUE + else + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY + end + return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) +end + +function getfield_effects(𝕃::AbstractLattice, arginfo::ArgInfo, @nospecialize(rt)) + (;argtypes) = arginfo + # consistent if the argtype is immutable + length(argtypes) < 3 && return EFFECTS_THROWS + obj = argtypes[2] + isvarargtype(obj) && return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE) + consistent = (is_immutable_argtype(obj) || is_mutation_free_argtype(obj)) ? + ALWAYS_TRUE : CONSISTENT_IF_INACCESSIBLEMEMONLY + # access to `isbitstype`-field initialized with undefined value leads to undefined behavior + # so should taint `:consistent`-cy while access to uninitialized non-`isbitstype` field + # throws `UndefRefError` so doesn't need to taint it + # NOTE `getfield_notundefined` conservatively checks if this field is never initialized + # with undefined value so that we don't taint `:consistent`-cy too aggressively here + if !(length(argtypes) ≥ 3 && getfield_notundefined(obj, argtypes[3])) + consistent = ALWAYS_FALSE + end + bcheck = getfield_boundscheck(arginfo) + nothrow = getfield_nothrow(𝕃, arginfo, bcheck) + if !nothrow + if !(bcheck === :on || bcheck === :boundscheck) # If we cannot independently prove inboundsness, taint consistency. # The inbounds-ness assertion requires dynamic reachability, while # :consistent needs to be true for all input values. - # N.B. We do not taint for `--check-bounds=no` here -that happens in - # InferenceState. - nothrow = getfield_nothrow(argtypes[2], argtypes[3], true) - ipo_consistent &= nothrow - end + # However, as a special exception, we do allow literal `:boundscheck`. + # `:consistent`-cy will be tainted in any caller using `@inbounds` based + # on the `:noinbounds` effect. + # N.B. We do not taint for `--check-bounds=no` here. That is handled + # in concrete evaluation. + consistent = ALWAYS_FALSE + end + end + if hasintersect(widenconst(obj), Module) + inaccessiblememonly = getglobal_effects(argtypes[2:end], rt).inaccessiblememonly + elseif is_mutation_free_argtype(obj) + inaccessiblememonly = ALWAYS_TRUE else - ipo_consistent = contains_is(_CONSISTENT_BUILTINS, f) + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY end - # If we computed nothrow above for getfield, no need to repeat the procedure here - if !nothrow - nothrow = isvarargtype(argtypes[end]) ? false : - builtin_nothrow(f, argtypes[2:end], rt) + return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) +end + +function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) + consistent = inaccessiblememonly = ALWAYS_FALSE + nothrow = false + if length(argtypes) ≥ 2 + M, s = argtypes[1], argtypes[2] + if getglobal_nothrow(M, s) + nothrow = true + # typeasserts below are already checked in `getglobal_nothrow` + Mval, sval = (M::Const).val::Module, (s::Const).val::Symbol + if isconst(Mval, sval) + consistent = ALWAYS_TRUE + if is_mutation_free_argtype(rt) + inaccessiblememonly = ALWAYS_TRUE + end + end + end end - effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) + return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) +end + +function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), arginfo::ArgInfo, @nospecialize(rt)) + if isa(f, IntrinsicFunction) + return intrinsic_effects(f, arginfo.argtypes[2:end]) + end + + @assert !contains_is(_SPECIAL_BUILTINS, f) - return Effects( - ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, - effect_free ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - ALWAYS_TRUE) + if f === getfield + return getfield_effects(𝕃, arginfo, rt) + end + argtypes = arginfo.argtypes[2:end] + + if f === isdefined + return isdefined_effects(𝕃, argtypes) + elseif f === getglobal + return getglobal_effects(argtypes, rt) + elseif f === Core.get_binding_type + length(argtypes) == 2 || return EFFECTS_THROWS + effect_free = get_binding_type_effect_free(argtypes[1], argtypes[2]) ? ALWAYS_TRUE : ALWAYS_FALSE + return Effects(EFFECTS_TOTAL; effect_free) + else + if contains_is(_CONSISTENT_BUILTINS, f) + consistent = ALWAYS_TRUE + elseif f === arrayref || f === arrayset || f === arraysize + consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY + elseif f === Core._typevar + consistent = CONSISTENT_IF_NOTRETURNED + else + consistent = ALWAYS_FALSE + end + if f === setfield! || f === arrayset + effect_free = EFFECT_FREE_IF_INACCESSIBLEMEMONLY + elseif contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) + effect_free = ALWAYS_TRUE + else + effect_free = ALWAYS_FALSE + end + nothrow = (isempty(argtypes) || !isvarargtype(argtypes[end])) && builtin_nothrow(𝕃, f, argtypes, rt) + if contains_is(_INACCESSIBLEMEM_BUILTINS, f) + inaccessiblememonly = ALWAYS_TRUE + elseif contains_is(_ARGMEM_BUILTINS, f) + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY + else + inaccessiblememonly = ALWAYS_FALSE + end + return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) + end end -function builtin_nothrow(@nospecialize(f), argtypes::Array{Any, 1}, @nospecialize(rt)) +function builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt)) rt === Bottom && return false contains_is(_PURE_BUILTINS, f) && return true - return _builtin_nothrow(f, argtypes, rt) + return _builtin_nothrow(𝕃, f, argtypes, rt) end -function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Array{Any,1}, - sv::Union{InferenceState,Nothing}) +function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any}, + sv::Union{AbsIntState, Nothing}) + 𝕃ᵢ = typeinf_lattice(interp) if f === tuple - return tuple_tfunc(argtypes) + return tuple_tfunc(𝕃ᵢ, argtypes) end if isa(f, IntrinsicFunction) - if is_pure_intrinsic_infer(f) && _all(@nospecialize(a) -> isa(a, Const), argtypes) + if is_pure_intrinsic_infer(f) && all(@nospecialize(a) -> isa(a, Const), argtypes) argvals = anymap(@nospecialize(a) -> (a::Const).val, argtypes) try return Const(f(argvals...)) @@ -1846,16 +2477,16 @@ function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtyp # wrong # of args return Bottom end - return tf[3](argtypes...) + return tf[3](𝕃ᵢ, argtypes...) end # Query whether the given intrinsic is nothrow -_iszero(x) = x === Intrinsics.xor_int(x, x) -_isneg1(x) = _iszero(Intrinsics.not_int(x)) -_istypemin(x) = !_iszero(x) && Intrinsics.neg_int(x) === x +_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::Array{Any, 1}) +function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Vector{Any}) # First check that we have the correct number of arguments iidx = Int(reinterpret(Int32, f::IntrinsicFunction)) + 1 if iidx < 1 || iidx > length(T_IFUNC) @@ -1875,16 +2506,17 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1}) f === Intrinsics.llvmcall && return false 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 - isa(argtypes[2], Const) || return false - if !isprimitivetype(widenconst(argtypes[1])) || - (widenconst(argtypes[1]) !== widenconst(argtypes[2])) - return false - end - den_val = argtypes[2].val + arg2 = argtypes[2] + isa(arg2, Const) || return false + arg1 = argtypes[1] + 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(argtypes[1], Const) && !_istypemin(argtypes[1].val)) + return !_isneg1(den_val) || (isa(arg1, Const) && !_istypemin(arg1.val)) end if f === Intrinsics.pointerref # Nothrow as long as the types are ok. N.B.: dereferencability is not @@ -1945,8 +2577,11 @@ function is_pure_intrinsic_infer(f::IntrinsicFunction) end # whether `f` is effect free if nothrow -intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || - f === Intrinsics.have_fma || is_pure_intrinsic_infer(f) +function intrinsic_effect_free_if_nothrow(@nospecialize f) + return f === Intrinsics.pointerref || + f === Intrinsics.have_fma || + is_pure_intrinsic_infer(f) +end function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) if f === Intrinsics.llvmcall @@ -1954,72 +2589,181 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) return Effects() end - ipo_consistent = !(f === Intrinsics.pointerref || # this one is volatile - 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 - - effect_free = !(f === Intrinsics.pointerset) - - nothrow = isvarargtype(argtypes[end]) ? false : - intrinsic_nothrow(f, argtypes[2:end]) - - return Effects( - ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, - effect_free ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - ALWAYS_TRUE) + 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 + return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) end # TODO: this function is a very buggy and poor model of the return_type function # 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}, sv::InferenceState) +function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) + UNKNOWN = CallMeta(Type, EFFECTS_THROWS, NoCallInfo()) + if !(2 <= length(argtypes) <= 3) + return UNKNOWN + end + + tt = widenslotwrapper(argtypes[end]) + if !isa(tt, Const) && !(isType(tt) && !has_free_typevars(tt)) + return UNKNOWN + end + + af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1] + if !isa(af_argtype, DataType) || !(af_argtype <: Tuple) + return UNKNOWN + end + if length(argtypes) == 3 - tt = argtypes[3] - if isa(tt, Const) || (isType(tt) && !has_free_typevars(tt)) - aft = argtypes[2] - if isa(aft, Const) || (isType(aft) && !has_free_typevars(aft)) || - (isconcretetype(aft) && !(aft <: Builtin)) - af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1] - if isa(af_argtype, DataType) && af_argtype <: Tuple - argtypes_vec = Any[aft, af_argtype.parameters...] - if contains_is(argtypes_vec, Union{}) - return CallMeta(Const(Union{}), false) - end - call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), sv, -1) - info = verbose_stmt_info(interp) ? ReturnTypeCallInfo(call.info) : false - rt = widenconditional(call.rt) - if isa(rt, Const) - # output was computed to be constant - return CallMeta(Const(typeof(rt.val)), 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), info) - elseif !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}, info) - elseif (isa(tt, Const) || isconstType(tt)) && - (isa(aft, Const) || isconstType(aft)) - # input arguments were known for certain - # XXX: this doesn't imply we know anything about rt - return CallMeta(Const(rt), info) - elseif isType(rt) - return CallMeta(Type{rt}, info) - else - return CallMeta(Type{<:rt}, info) - end - end + aft = widenslotwrapper(argtypes[2]) + if !isa(aft, Const) && !(isType(aft) && !has_free_typevars(aft)) && + !(isconcretetype(aft) && !(aft <: Builtin)) + return UNKNOWN + end + argtypes_vec = Any[aft, af_argtype.parameters...] + else + argtypes_vec = Any[af_argtype.parameters...] + end + + if contains_is(argtypes_vec, Union{}) + return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo()) + end + + # Run the abstract_call without restricting abstract call + # sites. Otherwise, our behavior model of abstract_call + # below will be wrong. + if isa(sv, InferenceState) + old_restrict = sv.restrict_abstract_call_sites + sv.restrict_abstract_call_sites = false + call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, -1) + sv.restrict_abstract_call_sites = old_restrict + else + call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, -1) + end + info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure() + rt = widenslotwrapper(call.rt) + if isa(rt, Const) + # output was computed to be constant + return CallMeta(Const(typeof(rt.val)), 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) + 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) + elseif (isa(tt, Const) || isconstType(tt)) && + (isa(aft, Const) || isconstType(aft)) + # input arguments were known for certain + # XXX: this doesn't imply we know anything about rt + return CallMeta(Const(rt), EFFECTS_TOTAL, info) + elseif isType(rt) + return CallMeta(Type{rt}, EFFECTS_TOTAL, info) + else + return CallMeta(Type{<:rt}, 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(Union{}, EFFECTS_UNKNOWN, NoCallInfo()) + isvarargtype(argtypes[2]) && return CallMeta(Bool, 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) + if isa(matches, FailedMethodMatch) + rt = Bool # too many matches to analyze + else + (; valid_worlds, applicable) = matches + update_valid_age!(sv, valid_worlds) + + # also need an edge to the method table in case something gets + # added that did not intersect with any existing method + if isa(matches, MethodMatches) + matches.fullmatch || add_mt_backedge!(sv, matches.mt, atype) + else + for (thisfullmatch, mt) in zip(matches.fullmatches, matches.mts) + thisfullmatch || add_mt_backedge!(sv, mt, atype) + end + end + + napplicable = length(applicable) + if napplicable == 0 + rt = Const(false) # never any matches + else + rt = Const(true) # has applicable matches + for i in 1:napplicable + match = applicable[i]::MethodMatch + edge = specialize_method(match)::MethodInstance + add_backedge!(sv, edge) + end + + if 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. + rt = Bool end end end - return CallMeta(Type, false) + return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) end +add_tfunc(applicable, 1, INT_INF, @nospecs((𝕃::AbstractLattice, f, args...)->Bool), 40) + +# a simplified model of abstract_invoke for Core._hasmethod +function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState) + if length(argtypes) == 3 && !isvarargtype(argtypes[3]) + ft′ = argtype_by_index(argtypes, 2) + ft = widenconst(ft′) + ft === Bottom && return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + typeidx = 3 + elseif length(argtypes) == 2 && !isvarargtype(argtypes[2]) + typeidx = 2 + else + return CallMeta(Any, Effects(), NoCallInfo()) + end + (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, typeidx)) + isexact || return CallMeta(Bool, Effects(), NoCallInfo()) + unwrapped = unwrap_unionall(types) + if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name + return CallMeta(Bool, 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 + 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()) + end + match, valid_worlds, overlayed = findsup(types, method_table(interp)) + update_valid_age!(sv, valid_worlds) + if match === nothing + rt = Const(false) + add_mt_backedge!(sv, mt, types) # this should actually be an invoke-type backedge + else + rt = Const(true) + edge = specialize_method(match)::MethodInstance + add_invoke_backedge!(sv, types, edge) + end + return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) +end + # N.B.: typename maps type equivalence classes to a single value function typename_static(@nospecialize(t)) @@ -2029,20 +2773,188 @@ function typename_static(@nospecialize(t)) return isType(t) ? _typename(t.parameters[1]) : Core.TypeName end -function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s)) - if M isa Const && widenconst(M) === Module && - s isa Const && widenconst(s) === Symbol - return ccall(:jl_binding_type, Any, (Any, Any), M.val, s.val) !== nothing +function global_order_nothrow(@nospecialize(o), loading::Bool, storing::Bool) + o isa Const || return false + sym = o.val + if sym isa Symbol + order = get_atomic_order(sym, loading, storing) + return order !== MEMORY_ORDER_INVALID && order !== MEMORY_ORDER_NOTATOMIC + end + return false +end +@nospecs function getglobal_nothrow(M, s, o) + global_order_nothrow(o, #=loading=#true, #=storing=#false) || return false + return getglobal_nothrow(M, s) +end +@nospecs function getglobal_nothrow(M, s) + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return isdefined(M, s) + end + end + return false +end +@nospecs function getglobal_tfunc(𝕃::AbstractLattice, M, s, order=Symbol) + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return abstract_eval_global(M, s) + end + return Bottom + elseif !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) + return Bottom + end + return Any +end +@nospecs function setglobal!_tfunc(𝕃::AbstractLattice, M, s, v, order=Symbol) + if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) + return Bottom + end + return v +end +add_tfunc(getglobal, 2, 3, getglobal_tfunc, 1) +add_tfunc(setglobal!, 3, 4, setglobal!_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) +end +@nospecs function setglobal!_nothrow(M, s, newty) + if M isa Const && s isa Const + M, s = M.val, s.val + if isa(M, Module) && isa(s, Symbol) + return global_assignment_nothrow(M, s, newty) + end + end + return false +end + +function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty)) + if isdefined(M, s) && !isconst(M, s) + ty = ccall(:jl_get_binding_type, Any, (Any, Any), M, s) + return ty === nothing || newty ⊑ ty + end + return false +end + +@nospecs function get_binding_type_effect_free(M, s) + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return ccall(:jl_get_binding_type, Any, (Any, Any), M, s) !== nothing + end end return false end -function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s)) +@nospecs function get_binding_type_tfunc(𝕃::AbstractLattice, M, s) if get_binding_type_effect_free(M, s) - @assert M isa Const && s isa Const - return Const(Core.get_binding_type(M.val, s.val)) + return Const(Core.get_binding_type((M::Const).val, (s::Const).val)) end return Type end add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0) -@specialize +@nospecs function get_binding_type_nothrow(𝕃::AbstractLattice, M, s) + ⊑ = Core.Compiler.:⊑(𝕃) + return M ⊑ Module && s ⊑ Symbol +end + +# foreigncall +# =========== + +# N.B. the `abstract_eval` callback below allows us to use these queries +# both during abstract interpret and optimization + +const FOREIGNCALL_ARG_START = 6 + +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() + 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 +end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index d600df1dbb0a1..7d983ec5420db 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -1,5 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Tracking of newly-inferred CodeInstances during precompilation +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) @@ -25,7 +29,7 @@ using Core.Compiler: -, +, :, Vector, length, first, empty!, push!, pop!, @inlin struct InferenceFrameInfo mi::Core.MethodInstance world::UInt64 - sptypes::Vector{Any} + sptypes::Vector{Core.Compiler.VarState} slottypes::Vector{Any} nargs::Int end @@ -36,11 +40,13 @@ function _typeinf_identifier(frame::Core.Compiler.InferenceState) frame.world, copy(frame.sptypes), copy(frame.slottypes), - frame.nargs, + length(frame.result.argtypes), ) return mi_info end +_typeinf_identifier(frame::InferenceFrameInfo) = frame + """ Core.Compiler.Timing(mi_info, start_time, ...) @@ -83,7 +89,7 @@ function reset_timings() empty!(_timings) push!(_timings, Timing( # The MethodInstance for ROOT(), and default empty values for other fields. - InferenceFrameInfo(ROOTmi, 0x0, Any[], Any[Core.Const(ROOT)], 1), + InferenceFrameInfo(ROOTmi, 0x0, Core.Compiler.VarState[], Any[Core.Const(ROOT)], 1), _time_ns())) return nothing end @@ -198,8 +204,9 @@ 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 invocation. -function typeinf(interp::AbstractInterpreter, frame::InferenceState) +# Wrapper around `_typeinf` that optionally records the exclusive time for +# each inference performed by `NativeInterpreter`. +function typeinf(interp::NativeInterpreter, frame::InferenceState) if __measure_typeinf__[] Timings.enter_new_timer(frame) v = _typeinf(interp, frame) @@ -209,20 +216,34 @@ function typeinf(interp::AbstractInterpreter, 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 # implies `may_optimize(interp) === true` + if opt isa OptimizationState{typeof(interp)} # implies `may_optimize(interp) === true` if opt.ir !== nothing - caller.src = ir_to_codeinf!(opt) + 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 end return caller.src 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 @@ -237,8 +258,6 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) for caller in frames caller.valid_worlds = valid_worlds finish(caller, interp) - # finalize and record the linfo result - caller.inferred = true end # collect results for the new expanded frame results = Tuple{InferenceResult, Vector{Any}, Bool}[ @@ -249,16 +268,8 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) empty!(frames) for (caller, _, _) in results opt = caller.src - if opt isa OptimizationState # implies `may_optimize(interp) === true` - analyzed = optimize(interp, opt, OptimizationParams(interp), caller) - if isa(analyzed, ConstAPI) - # XXX: The work in ir_to_codeinf! is essentially wasted. The only reason - # we're doing it is so that code_llvm can return the code - # for the `return ...::Const` (which never runs anyway). We should do this - # as a post processing step instead. - ir_to_codeinf!(opt) - caller.src = analyzed - end + if opt isa OptimizationState{typeof(interp)} # implies `may_optimize(interp) === true` + analyzed = optimize(interp, opt, caller) caller.valid_worlds = (opt.inlining.et::EdgeTracker).valid_worlds[] end end @@ -277,16 +288,19 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) return true end -function CodeInstance( - result::InferenceResult, @nospecialize(inferred_result), valid_worlds::WorldRange) +function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, + @nospecialize(inferred_result), valid_worlds::WorldRange) local const_flags::Int32 result_type = result.result - @assert !(result_type isa LimitedAccuracy) - if inferred_result isa ConstAPI + @assert !(result_type === nothing || result_type isa LimitedAccuracy) + + if isa(result_type, Const) && is_foldable_nothrow(result.ipo_effects) && is_inlineable_constant(result_type.val) # use constant calling convention - rettype_const = inferred_result.val + rettype_const = result_type.val const_flags = 0x3 - inferred_result = nothing + if may_discard_trees(interp) + inferred_result = nothing + end else if isa(result_type, Const) rettype_const = result_type.val @@ -303,29 +317,31 @@ function CodeInstance( elseif isa(result_type, InterConditional) rettype_const = result_type const_flags = 0x2 + elseif isa(result_type, InterMustAlias) + rettype_const = result_type + const_flags = 0x2 else rettype_const = nothing const_flags = 0x00 end end - relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0) + relocatability = 0x0 + if isa(inferred_result, String) + t = @_gc_preserve_begin inferred_result + relocatability = unsafe_load(unsafe_convert(Ptr{UInt8}, inferred_result), Core.sizeof(inferred_result)) + @_gc_preserve_end t + elseif inferred_result === nothing + relocatability = 0x1 + 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), # TODO: Actually do something with non-IPO effects - encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, + encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, relocatability) end -# For the NativeInterpreter, we don't need to do an actual cache query to know -# if something was already inferred. If we reach this point, but the inference -# flag has been turned off, then it's in the cache. This is purely a performance -# optimization. -already_inferred_quick_test(interp::NativeInterpreter, mi::MethodInstance) = - !mi.inInference -already_inferred_quick_test(interp::AbstractInterpreter, mi::MethodInstance) = - false - function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInstance, ci::CodeInfo) def = linfo.def toplevel = !isa(def, Method) @@ -333,7 +349,7 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta return ci end if may_discard_trees(interp) - cache_the_tree = ci.inferred && (ci.inlineable || isa_compileable_sig(linfo.specTypes, def)) + cache_the_tree = ci.inferred && (is_inlineable(ci) || isa_compileable_sig(linfo.specTypes, linfo.sparam_vals, def)) else cache_the_tree = true end @@ -342,7 +358,7 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta nslots = length(ci.slotflags) resize!(ci.slottypes::Vector{Any}, nslots) resize!(ci.slotnames, nslots) - return ccall(:jl_compress_ir, Vector{UInt8}, (Any, Any), def, ci) + return ccall(:jl_compress_ir, String, (Any, Any), def, ci) else return ci end @@ -351,11 +367,12 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta end end -function transform_result_for_cache(interp::AbstractInterpreter, linfo::MethodInstance, - valid_worlds::WorldRange, @nospecialize(inferred_result)) +function transform_result_for_cache(interp::AbstractInterpreter, + linfo::MethodInstance, valid_worlds::WorldRange, result::InferenceResult) + inferred_result = result.src # If we decided not to optimize, drop the OptimizationState now. # External interpreters can override as necessary to cache additional information - if inferred_result isa OptimizationState + if inferred_result isa OptimizationState{typeof(interp)} inferred_result = ir_to_codeinf!(inferred_result) end if inferred_result isa CodeInfo @@ -364,7 +381,7 @@ function transform_result_for_cache(interp::AbstractInterpreter, linfo::MethodIn inferred_result = maybe_compress_codeinfo(interp, linfo, inferred_result) end # The global cache can only handle objects that codegen understands - if !isa(inferred_result, Union{CodeInfo, Vector{UInt8}, ConstAPI}) + if !isa(inferred_result, MaybeCompressed) inferred_result = nothing end return inferred_result @@ -387,8 +404,14 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) # TODO: also don't store inferred code if we've previously decided to interpret this function if !already_inferred - inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src) - code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) + inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result) + code_cache(interp)[linfo] = ci = CodeInstance(interp, result, inferred_result, valid_worlds) + if track_newly_inferred[] + m = linfo.def + if isa(m, Method) && m.module != Core + ccall(:jl_push_newly_inferred, Cvoid, (Any,), ci) + end + end end unlock_mi_inference(interp, linfo) nothing @@ -416,13 +439,73 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) return typ end -function rt_adjust_effects(@nospecialize(rt), ipo_effects::Effects) - # Always throwing an error counts or never returning both count as consistent, - # but we don't currently model idempontency using dataflow, so we don't notice. - # Fix that up here to improve precision. - if !ipo_effects.inbounds_taints_consistency && rt === Union{} - return Effects(ipo_effects, consistent=ALWAYS_TRUE) +function adjust_effects(sv::InferenceState) + ipo_effects = sv.ipo_effects + + # refine :consistent-cy effect using the return type information + # TODO this adjustment tries to compromise imprecise :consistent-cy information, + # that is currently modeled in a flow-insensitive way: ideally we want to model it + # with a proper dataflow analysis instead + rt = sv.bestguess + if ipo_effects.noinbounds && rt === Bottom + # always throwing an error counts or never returning both count as consistent + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_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 + ipo_effects = Effects(ipo_effects; inaccessiblememonly=ALWAYS_TRUE) + end + if is_consistent_if_notreturned(ipo_effects) && is_identity_free_argtype(rt) + # in a case when the :consistent-cy here is only tainted by mutable allocations + # (indicated by `CONSISTENT_IF_NOTRETURNED`), we may be able to refine it if the return + # type guarantees that the allocations are never returned + consistent = ipo_effects.consistent & ~CONSISTENT_IF_NOTRETURNED + ipo_effects = Effects(ipo_effects; consistent) + end + if is_consistent_if_inaccessiblememonly(ipo_effects) + if is_inaccessiblememonly(ipo_effects) + consistent = ipo_effects.consistent & ~CONSISTENT_IF_INACCESSIBLEMEMONLY + ipo_effects = Effects(ipo_effects; consistent) + elseif is_inaccessiblemem_or_argmemonly(ipo_effects) + else # `:inaccessiblememonly` is already tainted, there will be no chance to refine this + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_FALSE) + end + end + if is_effect_free_if_inaccessiblememonly(ipo_effects) + if is_inaccessiblememonly(ipo_effects) + effect_free = ipo_effects.effect_free & ~EFFECT_FREE_IF_INACCESSIBLEMEMONLY + ipo_effects = Effects(ipo_effects; effect_free) + elseif is_inaccessiblemem_or_argmemonly(ipo_effects) + else # `:inaccessiblememonly` is already tainted, there will be no chance to refine this + ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_FALSE) + end + end + + # override the analyzed effects using manually annotated effect settings + def = sv.linfo.def + if isa(def, Method) + override = decode_effects_override(def.purity) + if is_effect_overridden(override, :consistent) + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) + end + if is_effect_overridden(override, :effect_free) + ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE) + end + if is_effect_overridden(override, :nothrow) + ipo_effects = Effects(ipo_effects; nothrow=true) + end + if is_effect_overridden(override, :terminates_globally) + ipo_effects = Effects(ipo_effects; terminates=true) + end + if is_effect_overridden(override, :notaskstate) + ipo_effects = Effects(ipo_effects; notaskstate=true) + end + if is_effect_overridden(override, :inaccessiblememonly) + ipo_effects = Effects(ipo_effects; inaccessiblememonly=ALWAYS_TRUE) + end + end + return ipo_effects end @@ -446,11 +529,11 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end # inspect whether our inference had a limited result accuracy, # else it may be suitable to cache - me.bestguess = cycle_fix_limited(me.bestguess, me) - limited_ret = me.bestguess isa LimitedAccuracy + bestguess = me.bestguess = cycle_fix_limited(me.bestguess, me) + limited_ret = bestguess isa LimitedAccuracy limited_src = false if !limited_ret - gt = me.src.ssavaluetypes::Vector{Any} + gt = me.ssavaluetypes for j = 1:length(gt) gt[j] = gtj = cycle_fix_limited(gt[j], me) if gtj isa LimitedAccuracy && me.parent !== nothing @@ -464,135 +547,66 @@ function finish(me::InferenceState, interp::AbstractInterpreter) # we can throw everything else away now me.result.src = nothing me.cached = false - me.src.inlineable = false + set_inlineable!(me.src, false) unlock_mi_inference(interp, me.linfo) elseif limited_src # a type result will be cached still, but not this intermediate work: # we can throw everything else away now me.result.src = nothing - me.src.inlineable = false + set_inlineable!(me.src, false) else # annotate fulltree with type information, # either because we are the outermost code, or we might use this later doopt = (me.cached || me.parent !== nothing) - type_annotate!(me, doopt) + recompute_cfg = type_annotate!(interp, me, doopt) if doopt && may_optimize(interp) - me.result.src = OptimizationState(me, OptimizationParams(interp), interp) + me.result.src = OptimizationState(me, interp, recompute_cfg) else me.result.src = me.src::CodeInfo # stash a convenience copy of the code (e.g. for reflection) end end me.result.valid_worlds = me.valid_worlds - me.result.result = me.bestguess - me.result.ipo_effects = rt_adjust_effects(me.bestguess, me.ipo_effects) + me.result.result = bestguess + me.ipo_effects = me.result.ipo_effects = adjust_effects(me) validate_code_in_debug_mode(me.linfo, me.src, "inferred") nothing end # record the backedges -function store_backedges(frame::InferenceResult, edges::Vector{Any}) - toplevel = !isa(frame.linfo.def, Method) - if !toplevel - store_backedges(frame.linfo, edges) - end - nothing +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 function store_backedges(caller::MethodInstance, edges::Vector{Any}) - i = 1 - while i <= length(edges) - to = edges[i] - if isa(to, MethodInstance) - ccall(:jl_method_instance_add_backedge, Cvoid, (Any, Any), to, caller) - i += 1 + for itr in BackedgeIterator(edges) + callee = itr.caller + if isa(callee, MethodInstance) + ccall(:jl_method_instance_add_backedge, Cvoid, (Any, Any, Any), callee, itr.sig, caller) else - typeassert(to, Core.MethodTable) - typ = edges[i + 1] - ccall(:jl_method_table_add_backedge, Cvoid, (Any, Any, Any), to, typ, caller) - i += 2 - end - end -end - -# widen all Const elements in type annotations -function widen_all_consts!(src::CodeInfo) - ssavaluetypes = src.ssavaluetypes::Vector{Any} - for i = 1:length(ssavaluetypes) - ssavaluetypes[i] = widenconst(ssavaluetypes[i]) - end - - for i = 1:length(src.code) - x = src.code[i] - if isa(x, PiNode) - src.code[i] = PiNode(x.val, widenconst(x.typ)) + typeassert(callee, MethodTable) + ccall(:jl_method_table_add_backedge, Cvoid, (Any, Any, Any), callee, itr.sig, caller) end end - - src.rettype = widenconst(src.rettype) - - return src -end - -function annotate_slot_load!(e::Expr, vtypes::VarTable, sv::InferenceState, undefs::Array{Bool,1}) - head = e.head - i0 = 1 - if is_meta_expr_head(head) || head === :const - return - end - if head === :(=) || head === :method - i0 = 2 - end - for i = i0:length(e.args) - subex = e.args[i] - if isa(subex, Expr) - annotate_slot_load!(subex, vtypes, sv, undefs) - elseif isa(subex, SlotNumber) - e.args[i] = visit_slot_load!(subex, vtypes, sv, undefs) - end - end -end - -function annotate_slot_load(@nospecialize(e), vtypes::VarTable, sv::InferenceState, undefs::Array{Bool,1}) - if isa(e, Expr) - annotate_slot_load!(e, vtypes, sv, undefs) - elseif isa(e, SlotNumber) - return visit_slot_load!(e, vtypes, sv, undefs) - end - return e -end - -function visit_slot_load!(sl::SlotNumber, vtypes::VarTable, sv::InferenceState, undefs::Array{Bool,1}) - id = slot_id(sl) - s = vtypes[id] - vt = widenconditional(ignorelimited(s.typ)) - if s.undef - # find used-undef variables - undefs[id] = true - end - # add type annotations where needed - if !(sv.slottypes[id] ⊑ vt) - return TypedSlot(id, vt) - end - return sl + return nothing end function record_slot_assign!(sv::InferenceState) # look at all assignments to slots # and union the set of types stored there # to compute a lower bound on the storage required - states = sv.stmt_types body = sv.src.code::Vector{Any} slottypes = sv.slottypes::Vector{Any} - ssavaluetypes = sv.src.ssavaluetypes::Vector{Any} + ssavaluetypes = sv.ssavaluetypes for i = 1:length(body) expr = body[i] - st_i = states[i] # find all reachable assignments to locals - if isa(st_i, VarTable) && isa(expr, Expr) && expr.head === :(=) + if was_reached(sv, i) && isexpr(expr, :(=)) lhs = expr.args[1] - rhs = expr.args[2] if isa(lhs, SlotNumber) - vt = widenconst(ssavaluetypes[i]) + typ = ssavaluetypes[i] + @assert typ !== NOT_FOUND "active slot in unreached region" + vt = widenconst(typ) if vt !== Bottom id = slot_id(lhs) otherTy = slottypes[id] @@ -607,98 +621,146 @@ function record_slot_assign!(sv::InferenceState) end end end + sv.src.slottypes = slottypes + return nothing end -# annotate types of all symbols in AST -function type_annotate!(sv::InferenceState, run_optimizer::Bool) - # as an optimization, we delete dead statements immediately if we're going to run the optimizer - # (otherwise, we'll perhaps run the optimization passes later, outside of inference) +function record_bestguess!(sv::InferenceState) + bestguess = sv.bestguess + @assert !(bestguess isa LimitedAccuracy) + sv.src.rettype = bestguess + return nothing +end - # remove all unused ssa values - src = sv.src - ssavaluetypes = src.ssavaluetypes::Vector{Any} - for j = 1:length(ssavaluetypes) - t = ssavaluetypes[j] - ssavaluetypes[j] = t === NOT_FOUND ? Union{} : widenconditional(t) +function annotate_slot_load!(interp::AbstractInterpreter, undefs::Vector{Bool}, idx::Int, sv::InferenceState, @nospecialize x) + if isa(x, SlotNumber) + id = slot_id(x) + pc = find_dominating_assignment(id, idx, sv) + if pc === nothing + block = block_for_inst(sv.cfg, idx) + state = sv.bb_vartables[block]::VarTable + vt = state[id] + undefs[id] |= vt.undef + typ = widenslotwrapper(ignorelimited(vt.typ)) + else + typ = sv.ssavaluetypes[pc] + @assert typ !== NOT_FOUND "active slot in unreached region" + end + # add type annotations where needed + if !⊑(typeinf_lattice(interp), sv.slottypes[id], typ) + return TypedSlot(id, typ) + end + return x + elseif isa(x, Expr) + head = x.head + i0 = 1 + if is_meta_expr_head(head) || head === :const + return x + end + if head === :(=) || head === :method + i0 = 2 + end + for i = i0:length(x.args) + x.args[i] = annotate_slot_load!(interp, undefs, idx, sv, x.args[i]) + end + return x + elseif isa(x, ReturnNode) && isdefined(x, :val) + return ReturnNode(annotate_slot_load!(interp, undefs, idx, sv, x.val)) + elseif isa(x, GotoIfNot) + return GotoIfNot(annotate_slot_load!(interp, undefs, idx, sv, x.cond), x.dest) + end + return x +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) + block = block_for_inst(sv.cfg, idx) + for pc in reverse(sv.cfg.blocks[block].stmts) # N.B. reverse since the last assignment is dominating this block + pc < idx || continue # N.B. needs pc ≠ idx as `id` can be assigned at `idx` + stmt = sv.src.code[pc] + isexpr(stmt, :(=)) || continue + lhs = stmt.args[1] + isa(lhs, SlotNumber) || continue + slot_id(lhs) == id || continue + return pc + end + return nothing +end + +# annotate types of all symbols in AST, preparing for optimization +function type_annotate!(interp::AbstractInterpreter, sv::InferenceState, run_optimizer::Bool) + # widen `Conditional`s from `slottypes` + slottypes = sv.slottypes + for i = 1:length(slottypes) + slottypes[i] = widenconditional(slottypes[i]) end # compute the required type for each slot # to hold all of the items assigned into it record_slot_assign!(sv) - sv.src.slottypes = sv.slottypes - @assert !(sv.bestguess isa LimitedAccuracy) - sv.src.rettype = sv.bestguess + + record_bestguess!(sv) # annotate variables load types # remove dead code optimization # and compute which variables may be used undef - states = sv.stmt_types - nargs = sv.nargs - nslots = length(states[1]::VarTable) - undefs = fill(false, nslots) - body = src.code::Array{Any,1} + stmt_info = sv.stmt_info + src = sv.src + body = src.code nexpr = length(body) - - # replace GotoIfNot with its condition if the branch target is unreachable + codelocs = src.codelocs + ssavaluetypes = sv.ssavaluetypes + ssaflags = src.ssaflags + slotflags = src.slotflags + nslots = length(slotflags) + undefs = fill(false, nslots) + any_unreachable = false + + # this statement traversal does five things: + # 1. introduce temporary `TypedSlot`s that are supposed to be replaced with π-nodes later + # 2. mark used-undef slots (required by the `slot2reg` conversion) + # 3. mark unreached statements for a bulk code deletion (see issue #7836) + # 4. widen slot wrappers (`Conditional` and `MustAlias`) and remove `NOT_FOUND` from `ssavaluetypes` + # NOTE because of this, `was_reached` will no longer be available after this point + # 5. eliminate GotoIfNot if either branch target is unreachable + changemap = nothing # initialized if there is any dead region for i = 1:nexpr expr = body[i] - if isa(expr, GotoIfNot) - if !isa(states[expr.dest], VarTable) - body[i] = Expr(:call, GlobalRef(Core, :typeassert), expr.cond, GlobalRef(Core, :Bool)) - end - end - end - - i = 1 - oldidx = 0 - changemap = fill(0, nexpr) - - while i <= nexpr - oldidx += 1 - st_i = states[i] - expr = body[i] - if isa(st_i, VarTable) - # st_i === nothing => unreached statement (see issue #7836) - if isa(expr, Expr) - annotate_slot_load!(expr, st_i, sv, undefs) - elseif isa(expr, ReturnNode) && isdefined(expr, :val) - body[i] = ReturnNode(annotate_slot_load(expr.val, st_i, sv, undefs)) - elseif isa(expr, GotoIfNot) - body[i] = GotoIfNot(annotate_slot_load(expr.cond, st_i, sv, undefs), expr.dest) - elseif isa(expr, SlotNumber) - body[i] = visit_slot_load!(expr, st_i, sv, undefs) + if was_reached(sv, i) + if run_optimizer + if isa(expr, GotoIfNot) && widenconst(argextype(expr.cond, src, sv.sptypes)) === Bool + # 5: replace this live GotoIfNot with: + # - GotoNode if the fallthrough target is unreachable + # - no-op if the branch target is unreachable + if !was_reached(sv, i+1) + expr = GotoNode(expr.dest) + elseif !was_reached(sv, expr.dest) + expr = nothing + end + end end - else - if isa(expr, Expr) && is_meta_expr_head(expr.head) - # keep any lexically scoped expressions - elseif run_optimizer - deleteat!(body, i) - deleteat!(states, i) - deleteat!(ssavaluetypes, i) - deleteat!(src.codelocs, i) - deleteat!(sv.stmt_info, i) - deleteat!(src.ssaflags, i) - nexpr -= 1 - changemap[oldidx] = -1 - continue + body[i] = annotate_slot_load!(interp, undefs, i, sv, expr) # 1&2 + ssavaluetypes[i] = widenslotwrapper(ssavaluetypes[i]) # 4 + else # i.e. any runtime execution will never reach this statement + any_unreachable = true + if is_meta_expr(expr) # keep any lexically scoped expressions + ssavaluetypes[i] = Any # 4 else + ssavaluetypes[i] = Bottom # 4 body[i] = Const(expr) # annotate that this statement actually is dead end end - i += 1 - end - - if run_optimizer - renumber_ir_elements!(body, changemap) end # finish marking used-undef variables for j = 1:nslots if undefs[j] - src.slotflags[j] |= SLOT_USEDUNDEF | SLOT_STATICUNDEF + slotflags[j] |= SLOT_USEDUNDEF | SLOT_STATICUNDEF end end - nothing + + return any_unreachable end # at the end, all items in b's cycle @@ -720,91 +782,102 @@ function union_caller_cycle!(a::InferenceState, b::InferenceState) return end -function merge_call_chain!(parent::InferenceState, ancestor::InferenceState, child::InferenceState) +function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, ancestor::InferenceState, child::InferenceState) # add backedge of parent <- child # then add all backedges of parent <- parent.parent # and merge all of the callers into ancestor.callers_in_cycle # and ensure that walking the parent list will get the same result (DAG) from everywhere - # Also taint the termination effect, because we can no longer guarantee the absence - # of recursion. - tristate_merge!(parent, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) while true - add_cycle_backedge!(child, parent, parent.currpc) + add_cycle_backedge!(parent, child, parent.currpc) union_caller_cycle!(ancestor, child) - tristate_merge!(child, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) child = parent child === ancestor && break - parent = child.parent::InferenceState + parent = frame_parent(child) + while !isa(parent, InferenceState) + # XXX we may miss some edges here? + parent = frame_parent(parent::IRInterpretationState) + end + parent = parent::InferenceState end end -function is_same_frame(interp::AbstractInterpreter, linfo::MethodInstance, frame::InferenceState) - return linfo === frame.linfo +function is_same_frame(interp::AbstractInterpreter, mi::MethodInstance, frame::InferenceState) + return mi === frame_instance(frame) end -function poison_callstack(infstate::InferenceState, topmost::InferenceState) +function poison_callstack!(infstate::InferenceState, topmost::InferenceState) push!(infstate.pclimitations, topmost) nothing end -# Walk through `linfo`'s upstream call chain, starting at `parent`. If a parent -# frame matching `linfo` is encountered, then there is a cycle in the call graph -# (i.e. `linfo` is a descendant callee of itself). Upon encountering this cycle, +# Walk through `mi`'s upstream call chain, starting at `parent`. If a parent +# frame matching `mi` is encountered, then there is a cycle in the call graph +# (i.e. `mi` is a descendant callee of itself). Upon encountering this cycle, # we "resolve" it by merging the call chain, which entails unioning each intermediary # frame's `callers_in_cycle` field and adding the appropriate backedges. Finally, -# we return `linfo`'s pre-existing frame. If no cycles are found, `nothing` is +# we return `mi`'s pre-existing frame. If no cycles are found, `nothing` is # returned instead. -function resolve_call_cycle!(interp::AbstractInterpreter, linfo::MethodInstance, parent::InferenceState) +function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, parent::AbsIntState) + # TODO (#48913) implement a proper recursion handling for irinterp: + # This works just because currently the `:terminate` condition guarantees that + # irinterp doesn't fail into unresolved cycles, but it's not a good solution. + # We should revisit this once we have a better story for handling cycles in irinterp. + isa(parent, InferenceState) || return false frame = parent uncached = false while isa(frame, InferenceState) - uncached |= !frame.cached # ensure we never add an uncached frame to a cycle - if is_same_frame(interp, linfo, frame) + uncached |= !is_cached(frame) # ensure we never add an uncached frame to a cycle + if is_same_frame(interp, mi, frame) if uncached # our attempt to speculate into a constant call lead to an undesired self-cycle # that cannot be converged: poison our call-stack (up to the discovered duplicate frame) # with the limited flag and abort (set return type to Any) now - poison_callstack(parent, frame) + poison_callstack!(parent, frame) return true end - merge_call_chain!(parent, frame, frame) + merge_call_chain!(interp, parent, frame, frame) return frame end - for caller in frame.callers_in_cycle - if is_same_frame(interp, linfo, caller) + for caller in callers_in_cycle(frame) + if is_same_frame(interp, mi, caller) if uncached - poison_callstack(parent, frame) + poison_callstack!(parent, frame) return true end - merge_call_chain!(parent, frame, caller) + merge_call_chain!(interp, parent, frame, caller) return caller end end - frame = frame.parent + frame = frame_parent(frame) end return false end generating_sysimg() = ccall(:jl_generating_output, Cint, ()) != 0 && JLOptions().incremental == 0 -function tristate_merge!(caller::InferenceState, callee::Effects) - caller.ipo_effects = tristate_merge(caller.ipo_effects, callee) -end +ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) -function tristate_merge!(caller::InferenceState, callee::InferenceState) - tristate_merge!(caller, Effects(callee)) +struct EdgeCallResult + rt #::Type + edge::Union{Nothing,MethodInstance} + effects::Effects + function EdgeCallResult(@nospecialize(rt), + edge::Union{Nothing,MethodInstance}, + effects::Effects) + return new(rt, edge, effects) + end end -ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) - # 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::InferenceState) +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 - if code.inferred === nothing && is_stmt_inline(get_curr_ssaflag(caller)) - # we already inferred this edge previously and decided to discarded the inferred code - # but the inlinear will request to use it, we re-infer it here and keep it around in the local cache + inferred = @atomic :monotonic code.inferred + if inferred === nothing && is_stmt_inline(get_curr_ssaflag(caller)) + # 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 else effects = ipo_effects(code) @@ -812,30 +885,32 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize rettype = code.rettype if isdefined(code, :rettype_const) rettype_const = code.rettype_const - # the second subtyping conditions are necessary to distinguish usual cases + # the second subtyping/egal conditions are necessary to distinguish usual cases # from rare cases when `Const` wrapped those extended lattice type objects if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype) - return PartialStruct(rettype, rettype_const), mi, effects + rettype = PartialStruct(rettype, rettype_const) elseif isa(rettype_const, PartialOpaque) && rettype <: Core.OpaqueClosure - return rettype_const, mi, effects - elseif isa(rettype_const, InterConditional) && !(InterConditional <: rettype) - return rettype_const, mi, effects + rettype = rettype_const + elseif isa(rettype_const, InterConditional) && rettype !== InterConditional + rettype = rettype_const + elseif isa(rettype_const, InterMustAlias) && rettype !== InterMustAlias + rettype = rettype_const else - return Const(rettype_const), mi, effects + rettype = Const(rettype_const) end - else - return rettype, mi, effects end + return EdgeCallResult(rettype, mi, effects) end else cache = :global # cache edge targets by default end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_sysimg() - return Any, nothing, Effects() + add_remark!(interp, caller, "Inference is disabled for the target module") + return EdgeCallResult(Any, nothing, Effects()) end - if !caller.cached && caller.parent === nothing + if !is_cached(caller) && frame_parent(caller) === nothing # this caller exists to return to the user - # (if we asked resolve_call_cyle, it might instead detect that there is a cycle that it can't merge) + # (if we asked resolve_call_cycle!, it might instead detect that there is a cycle that it can't merge) frame = false else frame = resolve_call_cycle!(interp, mi, caller) @@ -843,99 +918,140 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize if frame === false # completely new lock_mi_inference(interp, mi) - result = InferenceResult(mi) + result = InferenceResult(mi, typeinf_lattice(interp)) frame = InferenceState(result, cache, 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 Any, nothing, Effects() + return EdgeCallResult(Any, nothing, Effects()) end - if caller.cached || caller.parent !== nothing # don't involve uncached functions in cycle resolution + if is_cached(caller) || frame_parent(caller) !== nothing # don't involve uncached functions in cycle resolution frame.parent = caller end typeinf(interp, frame) - update_valid_age!(frame, caller) - return frame.bestguess, frame.inferred ? mi : nothing, rt_adjust_effects(frame.bestguess, Effects(frame)) + update_valid_age!(caller, frame.valid_worlds) + edge = is_inferred(frame) ? mi : nothing + return EdgeCallResult(frame.bestguess, edge, frame.ipo_effects) # effects are adjusted already within `finish` elseif frame === true # unresolvable cycle - return Any, nothing, Effects() + return EdgeCallResult(Any, nothing, Effects()) end # return the current knowledge about this cycle frame = frame::InferenceState - update_valid_age!(frame, caller) - return frame.bestguess, nothing, rt_adjust_effects(frame.bestguess, Effects(frame)) + update_valid_age!(caller, frame.valid_worlds) + return EdgeCallResult(frame.bestguess, nothing, adjust_effects(frame)) end #### entry points for inferring a MethodInstance given a type signature #### # compute an inferred AST and return type function typeinf_code(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, run_optimizer::Bool) + frame = typeinf_frame(interp, method, atype, sparams, run_optimizer) + frame === nothing && return nothing, Any + is_inferred(frame) || return nothing, Any + code = frame.src + rt = widenconst(ignorelimited(frame.result.result)) + return code, rt +end + +""" + typeinf_ircode( + interp::AbstractInterpreter, + method::Method, + atype, + sparams::SimpleVector, + optimize_until::Union{Integer,AbstractString,Nothing}, + ) -> (ir::Union{IRCode,Nothing}, returntype::Type) + +Infer a `method` and return an `IRCode` with inferred `returntype` on success. +""" +function typeinf_ircode( + interp::AbstractInterpreter, + method::Method, + @nospecialize(atype), + sparams::SimpleVector, + optimize_until::Union{Integer,AbstractString,Nothing}, +) + start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) + frame = typeinf_frame(interp, method, atype, sparams, false) + if frame === nothing + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + return nothing, Any + end + (; result) = frame + opt = OptimizationState(frame, interp) + ir = run_passes(opt.src, opt, result, optimize_until) + rt = widenconst(ignorelimited(result.result)) + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + return ir, rt +end + +# compute an inferred frame +function typeinf_frame(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, run_optimizer::Bool) mi = specialize_method(method, atype, sparams)::MethodInstance - ccall(:jl_typeinf_begin, Cvoid, ()) - result = InferenceResult(mi) + start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) + result = InferenceResult(mi, typeinf_lattice(interp)) frame = InferenceState(result, run_optimizer ? :global : :no, interp) - frame === nothing && return (nothing, Any) + frame === nothing && return nothing typeinf(interp, frame) - ccall(:jl_typeinf_end, Cvoid, ()) - frame.inferred || return (nothing, Any) - return (frame.src, widenconst(ignorelimited(result.result))) + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + return frame end # compute (and cache) an inferred AST and return type function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance) method = mi.def::Method - for i = 1:2 # test-and-lock-and-test - i == 2 && ccall(:jl_typeinf_begin, Cvoid, ()) - code = get(code_cache(interp), mi, nothing) - if code isa CodeInstance - # see if this code already exists in the cache - inf = code.inferred - if use_const_api(code) - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) - rettype_const = code.rettype_const - tree.code = Any[ ReturnNode(quoted(rettype_const)) ] - nargs = Int(method.nargs) - tree.slotnames = ccall(:jl_uncompress_argnames, Vector{Symbol}, (Any,), method.slot_syms) - tree.slotflags = fill(IR_FLAG_NULL, nargs) - tree.ssavaluetypes = 1 - tree.codelocs = Int32[1] - tree.linetable = [LineInfoNode(method.module, method.name, method.file, Int(method.line), 0)] - tree.inferred = true - tree.ssaflags = UInt8[0] - tree.pure = true - tree.inlineable = true - tree.parent = mi - tree.rettype = Core.Typeof(rettype_const) - tree.min_world = code.min_world - tree.max_world = code.max_world - return tree - elseif isa(inf, CodeInfo) - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - 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, Vector{UInt8}) - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - inf = _uncompressed_ir(code, inf) - return inf + 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) + tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) + rettype_const = code.rettype_const + tree.code = Any[ ReturnNode(quoted(rettype_const)) ] + nargs = Int(method.nargs) + tree.slotnames = ccall(:jl_uncompress_argnames, Vector{Symbol}, (Any,), method.slot_syms) + tree.slotflags = fill(IR_FLAG_NULL, nargs) + tree.ssavaluetypes = 1 + tree.codelocs = Int32[1] + tree.linetable = LineInfoNode[LineInfoNode(method.module, mi, method.file, method.line, Int32(0))] + tree.ssaflags = UInt8[0] + set_inlineable!(tree, true) + tree.parent = mi + tree.rettype = Core.Typeof(rettype_const) + tree.min_world = code.min_world + tree.max_world = code.max_world + tree.inferred = true + return tree + elseif isa(inf, CodeInfo) + 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) + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + inf = _uncompressed_ir(code, inf) + return inf end end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_sysimg() - return retrieve_code_info(mi) + return retrieve_code_info(mi, get_world_counter(interp)) end lock_mi_inference(interp, mi) - frame = InferenceState(InferenceResult(mi), #=cache=#:global, interp) + result = InferenceResult(mi, typeinf_lattice(interp)) + frame = InferenceState(result, #=cache=#:global, interp) frame === nothing && return nothing typeinf(interp, frame) - ccall(:jl_typeinf_end, Cvoid, ()) + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) frame.src.inferred || return nothing return frame.src end @@ -946,19 +1062,17 @@ function typeinf_type(interp::AbstractInterpreter, method::Method, @nospecialize return Union{} # don't ask: it does weird and unnecessary things, if it occurs during bootstrap end mi = specialize_method(method, atype, sparams)::MethodInstance - for i = 1:2 # test-and-lock-and-test - i == 2 && ccall(:jl_typeinf_begin, Cvoid, ()) - code = get(code_cache(interp), mi, nothing) - if code isa CodeInstance - # see if this rettype already exists in the cache - i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) - return code.rettype - end + start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) + code = get(code_cache(interp), mi, nothing) + if code isa CodeInstance + # see if this rettype already exists in the cache + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + return code.rettype end - result = InferenceResult(mi) + result = InferenceResult(mi, typeinf_lattice(interp)) typeinf(interp, result, :global) - ccall(:jl_typeinf_end, Cvoid, ()) - result.result isa InferenceState && return nothing + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) + is_inferred(result) || return nothing return widenconst(ignorelimited(result.result)) end @@ -972,15 +1086,15 @@ function typeinf_ext_toplevel(interp::AbstractInterpreter, linfo::MethodInstance src = linfo.uninferred::CodeInfo if !src.inferred # toplevel lambda - infer directly - ccall(:jl_typeinf_begin, Cvoid, ()) + start_time = ccall(:jl_typeinf_timing_begin, UInt64, ()) if !src.inferred - result = InferenceResult(linfo) + result = InferenceResult(linfo, typeinf_lattice(interp)) frame = InferenceState(result, src, #=cache=#:global, interp) typeinf(interp, frame) - @assert frame.inferred # TODO: deal with this better + @assert is_inferred(frame) # TODO: deal with this better src = frame.src end - ccall(:jl_typeinf_end, Cvoid, ()) + ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time) end end return src diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index 1f55ceb94a062..700a6d333cbc4 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -17,44 +17,135 @@ # fields::Vector{Any} # elements are other type lattice members # end import Core: Const, PartialStruct +function PartialStruct(@nospecialize(typ), fields::Vector{Any}) + for i = 1:length(fields) + assert_nested_slotwrapper(fields[i]) + end + return Core._PartialStruct(typ, fields) +end -# The type of this value might be Bool. -# However, to enable a limited amount of back-propagation, -# we also keep some information about how this Bool value was created. -# In particular, if you branch on this value, then may assume that in -# the true branch, the type of `var` will be limited by `vtype` and in -# the false branch, it will be limited by `elsetype`. Example: -# ``` -# cond = isa(x::Union{Int, Float}, Int)::Conditional(x, Int, Float) -# if cond -# # May assume x is `Int` now -# else -# # May assume x is `Float` now -# end -# ``` +""" + cnd::Conditional + +The type of this value might be `Bool`. +However, to enable a limited amount of back-propagation, +we also keep some information about how this `Bool` value was created. +In particular, if you branch on this value, then may assume that in the true branch, +the type of `SlotNumber(cnd.slot)` will be limited by `cnd.thentype` +and in the false branch, it will be limited by `cnd.elsetype`. +Example: +```julia +let cond = isa(x::Union{Int, Float}, Int)::Conditional(x, Int, Float) + if cond + # May assume x is `Int` now + else + # May assume x is `Float` now + end +end +``` +""" struct Conditional - var::SlotNumber - vtype + slot::Int + thentype elsetype - function Conditional( - var, - @nospecialize(vtype), - @nospecialize(nottype)) - return new(var, vtype, nottype) + function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) + assert_nested_slotwrapper(thentype) + assert_nested_slotwrapper(elsetype) + return new(slot, thentype, elsetype) end end +Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) = + Conditional(slot_id(var), thentype, elsetype) + +""" + cnd::InterConditional -# # Similar to `Conditional`, but conveys inter-procedural constraints imposed on call arguments. -# # This is separate from `Conditional` to catch logic errors: the lattice element name is InterConditional -# # while processing a call, then Conditional everywhere else. Thus InterConditional does not appear in -# # CompilerTypes—these type's usages are disjoint—though we define the lattice for InterConditional. +Similar to `Conditional`, but conveys inter-procedural constraints imposed on call arguments. +This is separate from `Conditional` to catch logic errors: the lattice element name is `InterConditional` +while processing a call, then `Conditional` everywhere else. Thus `InterConditional` does not appear in +`CompilerTypes`—these type's usages are disjoint—though we define the lattice for `InterConditional`. +""" +:(InterConditional) +import Core: InterConditional # struct InterConditional # slot::Int -# vtype +# thentype # elsetype +# InterConditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) = +# new(slot, thentype, elsetype) # end -import Core: InterConditional +InterConditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) = + InterConditional(slot_id(var), thentype, elsetype) + const AnyConditional = Union{Conditional,InterConditional} +Conditional(cnd::InterConditional) = Conditional(cnd.slot, cnd.thentype, cnd.elsetype) +InterConditional(cnd::Conditional) = InterConditional(cnd.slot, cnd.thentype, cnd.elsetype) + +""" + alias::MustAlias + +This lattice element wraps a reference to object field while recoding the identity of the +parent object. It allows certain constraints that can be imposed on the object field type +by built-in functions like `isa` and `===` to be propagated to another reference to the +same object field. +One important note is that this lattice element assumes the invariant that the field of +wrapped slot object never changes until the slot object is re-assigned. This means, the +wrapped object field should be constant as inference currently doesn't track any memory +effects on per-object basis. Particularly `maybe_const_fldidx` takes the lift to check if +a given lattice element is eligible to be wrapped by `MustAlias`. Example: +```juila +let alias = getfield(x::Some{Union{Nothing,String}}, :value)::MustAlias(x, Some{Union{Nothing,String}}, 1, Union{Nothing,String}) + if alias === nothing + # May assume `getfield(x, :value)` is `nothing` now + else + # May assume `getfield(x, :value)` is `::String` now + end +end +``` +N.B. currently this lattice element is only used in abstractinterpret, not in optimization +""" +struct MustAlias + slot::Int + vartyp::Any + fldidx::Int + fldtyp::Any + function MustAlias(slot::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) + assert_nested_slotwrapper(vartyp) + assert_nested_slotwrapper(fldtyp) + # @assert !isalreadyconst(vartyp) "vartyp is already const" + # @assert !isalreadyconst(fldtyp) "fldtyp is already const" + return new(slot, vartyp, fldidx, fldtyp) + end +end +MustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) = + MustAlias(slot_id(var), vartyp, fldidx, fldtyp) + +_uniontypes(x::MustAlias, ts) = _uniontypes(widenconst(x), ts) + +""" + alias::InterMustAlias + +This lattice element used in a very similar way as `InterConditional`, but corresponds to `MustAlias`. +""" +struct InterMustAlias + slot::Int + vartyp::Any + fldidx::Int + fldtyp::Any + function InterMustAlias(slot::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) + assert_nested_slotwrapper(vartyp) + assert_nested_slotwrapper(fldtyp) + # @assert !isalreadyconst(vartyp) "vartyp is already const" + # @assert !isalreadyconst(fldtyp) "fldtyp is already const" + return new(slot, vartyp, fldidx, fldtyp) + end +end +InterMustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) = + InterMustAlias(slot_id(var), vartyp, fldidx, fldtyp) + +const AnyMustAlias = Union{MustAlias,InterMustAlias} +MustAlias(alias::InterMustAlias) = MustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp) +InterMustAlias(alias::MustAlias) = InterMustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp) struct PartialTypeVar tv::TypeVar @@ -80,23 +171,59 @@ struct StateUpdate conditional::Bool end -# Represent that the type estimate has been approximated, due to "causes" -# (only used in abstract interpretion, doesn't appear in optimization) -# N.B. in the lattice, this is epsilon smaller than `typ` (except Union{}) +""" + struct LimitedAccuracy + +A `LimitedAccuracy` lattice element is used to indicate that the true inference +result was approximate due to heuristic termination of a recursion. For example, +consider two call stacks starting from `A` and `B` that look like: + + A -> C -> A -> D + B -> C -> A -> D + +In the first case, inference may have decided that `A->C->A` constitutes a cycle, +widening the result it obtained for `C`, even if it might otherwise have been +able to obtain a result. In this case, the result inferred for `C` will be +annotated with this lattice type to indicate that the obtained result is an +upper bound for the non-limited inference. In particular, this means that the +call stack originating at `B` will re-perform inference without being poisoned +by the potentially inaccurate result obtained during the inference of `A`. + +N.B.: We do *not* take any efforts to ensure the reverse. For example, if `B` +is inferred first, then we may cache a precise result for `C` and re-use this +result while inferring `A`, even if inference of `A` would have not been able +to obtain this result due to limiting. This is undesirable, because it makes +some inference results order dependent, but there it is unclear how this situation +could be avoided. + +A `LimitedAccuracy` element wraps another lattice element (let's call it `T`) +and additionally tracks the `causes` due to which limitation occurred. As a +lattice element, `LimitedAccuracy(T)` is considered ε smaller than the +corresponding lattice element `T`, but in particular, all lattice elements that +are `⊑ T` (but not equal `T`) are also `⊑ LimitedAccuracy(T)`. + +The `causes` list is used to determine whether a particular cause of limitation is +inevitable and if so, widening `LimitedAccuracy(T)` back to `T`. For example, +in the call stacks above, if any call to `A` always leads back to `A`, then +it does not matter whether we start at `A` or reach it via `B`: Any inference +that reaches `A` will always hit the same limitation and the result may thus +be cached. +""" struct LimitedAccuracy typ causes::IdSet{InferenceState} function LimitedAccuracy(@nospecialize(typ), causes::IdSet{InferenceState}) - @assert !isa(typ, LimitedAccuracy) "malformed LimitedAccuracy" + @assert !isa(typ, LimitedAccuracy) "found nested LimitedAccuracy" return new(typ, causes) end end +LimitedAccuracy(@nospecialize(T), ::Nothing) = T """ struct NotFound end const NOT_FOUND = NotFound() -A special sigleton that represents a variable has not been analyzed yet. +A special singleton that represents a variable has not been analyzed yet. Particularly, all SSA value types are initialized as `NOT_FOUND` when creating a new `InferenceState`. Note that this is only used for `smerge`, which updates abstract state `VarTable`, and thus we don't define the lattice for this. @@ -105,7 +232,7 @@ struct NotFound end const NOT_FOUND = NotFound() -const CompilerTypes = Union{MaybeUndef, Const, Conditional, NotFound, PartialStruct} +const CompilerTypes = Union{MaybeUndef, Const, Conditional, MustAlias, NotFound, PartialStruct} ==(x::CompilerTypes, y::CompilerTypes) = x === y ==(x::Type, y::CompilerTypes) = false ==(x::CompilerTypes, y::Type) = false @@ -114,12 +241,63 @@ const CompilerTypes = Union{MaybeUndef, Const, Conditional, NotFound, PartialStr # lattice logic # ################# +# slot wrappers +# ============= + +function assert_nested_slotwrapper(@nospecialize t) + @assert !(t isa Conditional) "found nested Conditional" + @assert !(t isa InterConditional) "found nested InterConditional" + @assert !(t isa MustAlias) "found nested MustAlias" + @assert !(t isa InterMustAlias) "found nested InterMustAlias" + return t +end + +function widenslotwrapper(@nospecialize typ) + if isa(typ, AnyConditional) + return widenconditional(typ) + elseif isa(typ, AnyMustAlias) + return widenmustalias(typ) + end + return typ +end + +function widenwrappedslotwrapper(@nospecialize typ) + if isa(typ, LimitedAccuracy) + return LimitedAccuracy(widenslotwrapper(typ.typ), typ.causes) + end + return widenslotwrapper(typ) +end + +# Conditional +# =========== + +function widenconditional(@nospecialize typ) + if isa(typ, AnyConditional) + if typ.thentype === Union{} + return Const(false) + elseif typ.elsetype === Union{} + return Const(true) + else + return Bool + end + elseif isa(typ, LimitedAccuracy) + error("unhandled LimitedAccuracy") + end + return typ +end +function widenwrappedconditional(@nospecialize typ) + if isa(typ, LimitedAccuracy) + return LimitedAccuracy(widenconditional(typ.typ), typ.causes) + end + return widenconditional(typ) +end + # `Conditional` and `InterConditional` are valid in opposite contexts # (i.e. local inference and inter-procedural call), as such they will never be compared -function issubconditional(a::C, b::C) where {C<:AnyConditional} +function issubconditional(lattice::AbstractLattice, a::C, b::C) where {C<:AnyConditional} if is_same_conditionals(a, b) - if a.vtype ⊑ b.vtype - if a.elsetype ⊑ b.elsetype + if ⊑(lattice, a.thentype, b.thentype) + if ⊑(lattice, a.elsetype, b.elsetype) return true end end @@ -127,81 +305,209 @@ function issubconditional(a::C, b::C) where {C<:AnyConditional} return false end -is_same_conditionals(a::Conditional, b::Conditional) = slot_id(a.var) === slot_id(b.var) -is_same_conditionals(a::InterConditional, b::InterConditional) = a.slot === b.slot +is_same_conditionals(a::C, b::C) where C<:AnyConditional = a.slot == b.slot -is_lattice_bool(@nospecialize(typ)) = typ !== Bottom && typ ⊑ Bool +is_lattice_bool(lattice::AbstractLattice, @nospecialize(typ)) = typ !== Bottom && ⊑(lattice, typ, Bool) maybe_extract_const_bool(c::Const) = (val = c.val; isa(val, Bool)) ? val : nothing function maybe_extract_const_bool(c::AnyConditional) - (c.vtype === Bottom && !(c.elsetype === Bottom)) && return false - (c.elsetype === Bottom && !(c.vtype === Bottom)) && return true + (c.thentype === Bottom && !(c.elsetype === Bottom)) && return false + (c.elsetype === Bottom && !(c.thentype === Bottom)) && return true nothing end maybe_extract_const_bool(@nospecialize c) = nothing -""" - a ⊑ b -> Bool +# MustAlias +# ========= -The non-strict partial order over the type inference lattice. -""" -@nospecialize(a) ⊑ @nospecialize(b) = begin - if isa(b, LimitedAccuracy) - if !isa(a, LimitedAccuracy) - return false +function widenmustalias(@nospecialize typ) + if isa(typ, AnyMustAlias) + return typ.fldtyp + elseif isa(typ, LimitedAccuracy) + error("unhandled LimitedAccuracy") + end + return typ +end + +function isalreadyconst(@nospecialize t) + isa(t, Const) && return true + isa(t, DataType) && isdefined(t, :instance) && return true + return isconstType(t) +end + +function maybe_const_fldidx(@nospecialize(objtyp), @nospecialize(fldval)) + t = widenconst(objtyp) + if isa(fldval, Int) + fldidx = fldval + elseif isa(fldval, Symbol) + isa(t, DataType) || isa(t, UnionAll) || return nothing + fldidx = fieldindex(t, fldval, false) + else + return nothing + end + fldidx == 0 && return nothing + isconst(t, fldidx) || return nothing + fldcnt = fieldcount_noerror(t) + (fldcnt === nothing || fldcnt == 0) && return nothing + return fldidx +end + +function form_mustalias_conditional(alias::MustAlias, @nospecialize(thentype), @nospecialize(elsetype)) + (; slot, vartyp, fldidx) = alias + if isa(vartyp, PartialStruct) + fields = vartyp.fields + thenfields = thentype === Bottom ? nothing : copy(fields) + elsefields = elsetype === Bottom ? nothing : copy(fields) + for i in 1:length(fields) + if i == fldidx + thenfields === nothing || (thenfields[i] = thentype) + elsefields === nothing || (elsefields[i] = elsetype) + end end - if b.causes ⊈ a.causes - return false + return Conditional(slot, + thenfields === nothing ? Bottom : PartialStruct(vartyp.typ, thenfields), + elsefields === nothing ? Bottom : PartialStruct(vartyp.typ, elsefields)) + else + vartyp_widened = widenconst(vartyp) + thenfields = thentype === Bottom ? nothing : Any[] + elsefields = elsetype === Bottom ? nothing : Any[] + for i in 1:fieldcount(vartyp_widened) + if i == fldidx + thenfields === nothing || push!(thenfields, thentype) + elsefields === nothing || push!(elsefields, elsetype) + else + t = fieldtype(vartyp_widened, i) + thenfields === nothing || push!(thenfields, t) + elsefields === nothing || push!(elsefields, t) + end end - b = b.typ + return Conditional(slot, + thenfields === nothing ? Bottom : PartialStruct(vartyp_widened, thenfields), + elsefields === nothing ? Bottom : PartialStruct(vartyp_widened, elsefields)) end - isa(a, LimitedAccuracy) && (a = a.typ) - if isa(a, MaybeUndef) && !isa(b, MaybeUndef) - return false +end + +function issubalias(a::AnyMustAlias, b::AnyMustAlias) + return a.slot == b.slot && a.fldidx == b.fldidx && + a.vartyp ⊑ b.vartyp && a.fldtyp ⊑ b.fldtyp +end + +# LimitedAccuracy +# =============== + +ignorelimited(@nospecialize typ) = typ +ignorelimited(typ::LimitedAccuracy) = typ.typ + +# lattice order +# ============= + +function ⊑(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) + r = ⊑(widenlattice(lattice), ignorelimited(a), ignorelimited(b)) + r || return false + isa(b, LimitedAccuracy) || return true + + # We've found that ignorelimited(a) ⊑ ignorelimited(b). + # Now perform the reverse query to check for equality. + ab_eq = ⊑(widenlattice(lattice), b.typ, ignorelimited(a)) + + if !ab_eq + # a's unlimited type is strictly smaller than b's + return true + end + + # a and b's unlimited types are equal. + isa(a, LimitedAccuracy) || return false # b is limited, so ε smaller + return b.causes ⊆ a.causes +end + +function ⊑(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) + if isa(a, MaybeUndef) + isa(b, MaybeUndef) || return false + a, b = a.typ, b.typ + elseif isa(b, MaybeUndef) + b = b.typ end - isa(a, MaybeUndef) && (a = a.typ) - isa(b, MaybeUndef) && (b = b.typ) + return ⊑(widenlattice(lattice), a, b) +end + +function ⊑(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b)) + # Fast paths for common cases b === Any && return true a === Any && return false a === Union{} && return true b === Union{} && return false - @assert !isa(a, TypeVar) "invalid lattice item" - @assert !isa(b, TypeVar) "invalid lattice item" - if isa(a, AnyConditional) - if isa(b, AnyConditional) - return issubconditional(a, b) + ConditionalT = isa(lattice, ConditionalsLattice) ? Conditional : InterConditional + if isa(a, ConditionalT) + if isa(b, ConditionalT) + return issubconditional(lattice, a, b) elseif isa(b, Const) && isa(b.val, Bool) return maybe_extract_const_bool(a) === b.val end a = Bool - elseif isa(b, AnyConditional) + elseif isa(b, ConditionalT) return false end + return ⊑(widenlattice(lattice), a, b) +end + +function ⊑(𝕃::AnyMustAliasesLattice, @nospecialize(a), @nospecialize(b)) + MustAliasT = isa(𝕃, MustAliasesLattice) ? MustAlias : InterMustAlias + if isa(a, MustAliasT) + if isa(b, MustAliasT) + return issubalias(a, b) + end + a = widenmustalias(a) + elseif isa(b, MustAliasT) + return ⊏(widenlattice(𝕃), a, widenmustalias(b)) + end + return ⊑(widenlattice(𝕃), a, b) +end + +function ⊑(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, PartialStruct) if isa(b, PartialStruct) if !(length(a.fields) == length(b.fields) && a.typ <: b.typ) return false end for i in 1:length(b.fields) - # XXX: let's handle varargs later - ⊑(a.fields[i], b.fields[i]) || return false + af = a.fields[i] + bf = b.fields[i] + if i == length(b.fields) + if isvarargtype(af) + # If `af` is vararg, so must bf by the <: above + @assert isvarargtype(bf) + continue + elseif isvarargtype(bf) + # If `bf` is vararg, it must match the information + # in the type, so there's nothing to check here. + continue + end + end + ⊑(lattice, af, bf) || return false end return true end return isa(b, Type) && a.typ <: b elseif isa(b, PartialStruct) if isa(a, Const) - nfields(a.val) == length(b.fields) || return false - widenconst(b).name === widenconst(a).name || return false + nf = nfields(a.val) + nf == length(b.fields) || return false + widea = widenconst(a)::DataType + wideb = widenconst(b) + wideb′ = unwrap_unionall(wideb)::DataType + widea.name === wideb′.name || return false # We can skip the subtype check if b is a Tuple, since in that # case, the ⊑ of the elements is sufficient. - if b.typ.name !== Tuple.name && !(widenconst(a) <: widenconst(b)) + if wideb′.name !== Tuple.name && !(widea <: wideb) return false end - for i in 1:nfields(a.val) - # XXX: let's handle varargs later - isdefined(a.val, i) || return false - ⊑(Const(getfield(a.val, i)), b.fields[i]) || return false + for i in 1:nf + isdefined(a.val, i) || continue # since ∀ T Union{} ⊑ T + bfᵢ = b.fields[i] + if i == nf + bfᵢ = unwrapva(bfᵢ) + end + ⊑(lattice, Const(getfield(a.val, i)), bfᵢ) || return false end return true end @@ -211,10 +517,16 @@ The non-strict partial order over the type inference lattice. if isa(b, PartialOpaque) (a.parent === b.parent && a.source === b.source) || return false return (widenconst(a) <: widenconst(b)) && - ⊑(a.env, b.env) + ⊑(lattice, a.env, b.env) end - return widenconst(a) ⊑ b + return ⊑(widenlattice(lattice), widenconst(a), b) + elseif isa(b, PartialOpaque) + return false end + return ⊑(widenlattice(lattice), a, b) +end + +function ⊑(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, Const) if isa(b, Const) return a.val === b.val @@ -224,137 +536,229 @@ The non-strict partial order over the type inference lattice. # most conservative option. return isa(b, Type) && isa(a.val, b) elseif isa(b, Const) - if isa(a, DataType) && isdefined(a, :instance) + if issingletontype(a) return a.instance === b.val end return false - elseif isa(a, PartialTypeVar) && b === TypeVar - return true - elseif isa(a, Type) && isa(b, Type) - return a <: b - else # handle this conservatively in the remaining cases - return a === b + elseif isa(a, PartialTypeVar) + return b === TypeVar || a === b + elseif isa(b, PartialTypeVar) + return false end + return ⊑(widenlattice(lattice), a, b) end -""" - a ⊏ b -> Bool - -The strict partial order over the type inference lattice. -This is defined as the irreflexive kernel of `⊑`. -""" -@nospecialize(a) ⊏ @nospecialize(b) = a ⊑ b && !⊑(b, a) +function is_lattice_equal(lattice::InferenceLattice, @nospecialize(a), @nospecialize(b)) + if isa(a, LimitedAccuracy) + isa(b, LimitedAccuracy) || return false + a.causes == b.causes || return false + a = a.typ + b = b.typ + elseif isa(b, LimitedAccuracy) + return false + end + return is_lattice_equal(widenlattice(lattice), a, b) +end -""" - a ⋤ b -> Bool +function is_lattice_equal(lattice::OptimizerLattice, @nospecialize(a), @nospecialize(b)) + if isa(a, MaybeUndef) || isa(b, MaybeUndef) + # TODO: Unwrap these and recurse to is_lattice_equal + return ⊑(lattice, a, b) && ⊑(lattice, b, a) + end + return is_lattice_equal(widenlattice(lattice), a, b) +end -This order could be used as a slightly more efficient version of the strict order `⊏`, -where we can safely assume `a ⊑ b` holds. -""" -@nospecialize(a) ⋤ @nospecialize(b) = !⊑(b, a) +function is_lattice_equal(lattice::AnyConditionalsLattice, @nospecialize(a), @nospecialize(b)) + ConditionalT = isa(lattice, ConditionalsLattice) ? Conditional : InterConditional + if isa(a, ConditionalT) || isa(b, ConditionalT) + # TODO: Unwrap these and recurse to is_lattice_equal + return ⊑(lattice, a, b) && ⊑(lattice, b, a) + end + return is_lattice_equal(widenlattice(lattice), a, b) +end -# Check if two lattice elements are partial order equivalent. This is basically -# `a ⊑ b && b ⊑ a` but with extra performance optimizations. -function is_lattice_equal(@nospecialize(a), @nospecialize(b)) - a === b && return true +function is_lattice_equal(lattice::PartialsLattice, @nospecialize(a), @nospecialize(b)) if isa(a, PartialStruct) isa(b, PartialStruct) || return false length(a.fields) == length(b.fields) || return false widenconst(a) == widenconst(b) || return false + a.fields === b.fields && return true # fast path for i in 1:length(a.fields) - is_lattice_equal(a.fields[i], b.fields[i]) || return false + is_lattice_equal(lattice, a.fields[i], b.fields[i]) || return false end return true end isa(b, PartialStruct) && return false + if isa(a, PartialOpaque) + isa(b, PartialOpaque) || return false + widenconst(a) == widenconst(b) || return false + a.source === b.source || return false + a.parent === b.parent || return false + return is_lattice_equal(lattice, a.env, b.env) + end + isa(b, PartialOpaque) && return false + return is_lattice_equal(widenlattice(lattice), a, b) +end + +function is_lattice_equal(lattice::ConstsLattice, @nospecialize(a), @nospecialize(b)) + a === b && return true if a isa Const if issingletontype(b) return a.val === b.instance end + # N.B. Assumes a === b checked above return false end if b isa Const if issingletontype(a) return a.instance === b.val end + # N.B. Assumes a === b checked above return false end - if isa(a, PartialOpaque) - isa(b, PartialOpaque) || return false - widenconst(a) == widenconst(b) || return false - a.source === b.source || return false - a.parent === b.parent || return false - return is_lattice_equal(a.env, b.env) + if isa(a, PartialTypeVar) || isa(b, PartialTypeVar) + return false + end + return is_lattice_equal(widenlattice(lattice), a, b) +end + +# lattice operations +# ================== + +function tmeet(lattice::PartialsLattice, @nospecialize(v), @nospecialize(t::Type)) + if isa(v, PartialStruct) + has_free_typevars(t) && return v + widev = widenconst(v) + ti = typeintersect(widev, t) + if ti === widev + return v + end + valid_as_lattice(ti) || return Bottom + if widev <: Tuple + new_fields = Vector{Any}(undef, length(v.fields)) + for i = 1:length(new_fields) + vfi = v.fields[i] + if isvarargtype(vfi) + new_fields[i] = vfi + else + nfi = new_fields[i] = tmeet(lattice, vfi, widenconst(getfield_tfunc(lattice, t, Const(i)))) + if nfi === Bottom + return Bottom + end + end + end + return tuple_tfunc(lattice, new_fields) + end + v = widev + elseif isa(v, PartialOpaque) + has_free_typevars(t) && return v + widev = widenconst(v) + if widev <: t + return v + end + ti = typeintersect(widev, t) + valid_as_lattice(ti) || return Bottom + return PartialOpaque(ti, v.env, v.parent, v.source) + end + return tmeet(widenlattice(lattice), v, t) +end + +function tmeet(lattice::ConstsLattice, @nospecialize(v), @nospecialize(t::Type)) + if isa(v, Const) + if !has_free_typevars(t) && !isa(v.val, t) + return Bottom + end + return v + end + tmeet(widenlattice(lattice), widenconst(v), t) +end + +function tmeet(lattice::ConditionalsLattice, @nospecialize(v), @nospecialize(t::Type)) + if isa(v, Conditional) + if !(Bool <: t) + return Bottom + end + return v end - return a ⊑ b && b ⊑ a + tmeet(widenlattice(lattice), v, t) end -widenconst(c::AnyConditional) = Bool -widenconst((; val)::Const) = isa(val, Type) ? Type{val} : typeof(val) +function tmeet(𝕃::MustAliasesLattice, @nospecialize(v), @nospecialize(t::Type)) + if isa(v, MustAlias) + v = widenmustalias(v) + end + return tmeet(widenlattice(𝕃), v, t) +end + +function tmeet(lattice::InferenceLattice, @nospecialize(v), @nospecialize(t::Type)) + # TODO: This can probably happen and should be handled + @assert !isa(v, LimitedAccuracy) + tmeet(widenlattice(lattice), v, t) +end + +function tmeet(lattice::InterConditionalsLattice, @nospecialize(v), @nospecialize(t::Type)) + # TODO: This can probably happen and should be handled + @assert !isa(v, AnyConditional) + tmeet(widenlattice(lattice), v, t) +end + +function tmeet(𝕃::InterMustAliasesLattice, @nospecialize(v), @nospecialize(t::Type)) + if isa(v, InterMustAlias) + v = widenmustalias(v) + end + return tmeet(widenlattice(𝕃), v, t) +end + +function tmeet(lattice::OptimizerLattice, @nospecialize(v), @nospecialize(t::Type)) + # TODO: This can probably happen and should be handled + @assert !isa(v, MaybeUndef) + tmeet(widenlattice(lattice), v, t) +end + +""" + widenconst(x) -> t::Type + +Widens extended lattice element `x` to native `Type` representation. +""" +widenconst(::AnyConditional) = Bool +widenconst(a::AnyMustAlias) = widenconst(widenmustalias(a)) +widenconst(c::Const) = (v = c.val; isa(v, Type) ? Type{v} : typeof(v)) widenconst(m::MaybeUndef) = widenconst(m.typ) -widenconst(c::PartialTypeVar) = TypeVar +widenconst(::PartialTypeVar) = TypeVar widenconst(t::PartialStruct) = t.typ widenconst(t::PartialOpaque) = t.typ widenconst(t::Type) = t -widenconst(t::TypeVar) = error("unhandled TypeVar") -widenconst(t::TypeofVararg) = error("unhandled Vararg") -widenconst(t::LimitedAccuracy) = error("unhandled LimitedAccuracy") +widenconst(::TypeVar) = error("unhandled TypeVar") +widenconst(::TypeofVararg) = error("unhandled Vararg") +widenconst(::LimitedAccuracy) = error("unhandled LimitedAccuracy") -issubstate(a::VarState, b::VarState) = (a.typ ⊑ b.typ && a.undef <= b.undef) +#################### +# state management # +#################### -function smerge(sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}) +function smerge(lattice::AbstractLattice, sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}) sa === sb && return sa sa === NOT_FOUND && return sb sb === NOT_FOUND && return sa - issubstate(sa, sb) && return sb - issubstate(sb, sa) && return sa - return VarState(tmerge(sa.typ, sb.typ), sa.undef | sb.undef) -end - -@inline tchanged(@nospecialize(n), @nospecialize(o)) = o === NOT_FOUND || (n !== NOT_FOUND && !(n ⊑ o)) -@inline schanged(@nospecialize(n), @nospecialize(o)) = (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !issubstate(n::VarState, o::VarState))) - -widenconditional(@nospecialize typ) = typ -function widenconditional(typ::AnyConditional) - if typ.vtype === Union{} - return Const(false) - elseif typ.elsetype === Union{} - return Const(true) - else - return Bool - end + return VarState(tmerge(lattice, sa.typ, sb.typ), sa.undef | sb.undef) end -widenconditional(t::LimitedAccuracy) = error("unhandled LimitedAccuracy") - -widenwrappedconditional(@nospecialize(typ)) = widenconditional(typ) -widenwrappedconditional(typ::LimitedAccuracy) = LimitedAccuracy(widenconditional(typ.typ), typ.causes) -ignorelimited(@nospecialize typ) = typ -ignorelimited(typ::LimitedAccuracy) = typ.typ +@inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o)) = + (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !(n.undef <= o.undef && ⊑(lattice, n.typ, o.typ)))) -function stupdate!(state::Nothing, changes::StateUpdate) - newst = copy(changes.state) - changeid = slot_id(changes.var) - newst[changeid] = changes.vtype - # remove any Conditional for this slot from the vtable - # (unless this change is came from the conditional) - if !changes.conditional - for i = 1:length(newst) - newtype = newst[i] - if isa(newtype, VarState) - newtypetyp = ignorelimited(newtype.typ) - if isa(newtypetyp, Conditional) && slot_id(newtypetyp.var) == changeid - newtypetyp = widenwrappedconditional(newtype.typ) - newst[i] = VarState(newtypetyp, newtype.undef) - end - end - end +# remove any lattice elements that wrap the reassigned slot object from the vartable +function invalidate_slotwrapper(vt::VarState, changeid::Int, ignore_conditional::Bool) + newtyp = ignorelimited(vt.typ) + if (!ignore_conditional && isa(newtyp, Conditional) && newtyp.slot == changeid) || + (isa(newtyp, MustAlias) && newtyp.slot == changeid) + newtyp = @noinline widenwrappedslotwrapper(vt.typ) + return VarState(newtyp, vt.undef) end - return newst + return nothing end -function stupdate!(state::VarTable, changes::StateUpdate) - newstate = nothing +function stupdate!(lattice::AbstractLattice, state::VarTable, changes::StateUpdate) + changed = false changeid = slot_id(changes.var) for i = 1:length(state) if i == changeid @@ -362,66 +766,67 @@ function stupdate!(state::VarTable, changes::StateUpdate) else newtype = changes.state[i] end - oldtype = state[i] - # remove any Conditional for this slot from the vtable - # (unless this change is came from the conditional) - if !changes.conditional && isa(newtype, VarState) - newtypetyp = ignorelimited(newtype.typ) - if isa(newtypetyp, Conditional) && slot_id(newtypetyp.var) == changeid - newtypetyp = widenwrappedconditional(newtype.typ) - newtype = VarState(newtypetyp, newtype.undef) - end + invalidated = invalidate_slotwrapper(newtype, changeid, changes.conditional) + if invalidated !== nothing + newtype = invalidated end - if schanged(newtype, oldtype) - newstate = state - state[i] = smerge(oldtype, newtype) + oldtype = state[i] + if schanged(lattice, newtype, oldtype) + state[i] = smerge(lattice, oldtype, newtype) + changed = true end end - return newstate + return changed end -function stupdate!(state::VarTable, changes::VarTable) - newstate = nothing +function stupdate!(lattice::AbstractLattice, state::VarTable, changes::VarTable) + changed = false for i = 1:length(state) newtype = changes[i] oldtype = state[i] - if schanged(newtype, oldtype) - newstate = state - state[i] = smerge(oldtype, newtype) + if schanged(lattice, newtype, oldtype) + state[i] = smerge(lattice, oldtype, newtype) + changed = true end end - return newstate + return changed end -stupdate!(state::Nothing, changes::VarTable) = copy(changes) - -stupdate!(state::Nothing, changes::Nothing) = nothing - -function stupdate1!(state::VarTable, change::StateUpdate) +function stupdate1!(lattice::AbstractLattice, state::VarTable, change::StateUpdate) changeid = slot_id(change.var) - # remove any Conditional for this slot from the catch block vtable - # (unless this change is came from the conditional) - if !change.conditional - for i = 1:length(state) - oldtype = state[i] - if isa(oldtype, VarState) - oldtypetyp = ignorelimited(oldtype.typ) - if isa(oldtypetyp, Conditional) && slot_id(oldtypetyp.var) == changeid - oldtypetyp = widenconditional(oldtypetyp) - if oldtype.typ isa LimitedAccuracy - oldtypetyp = LimitedAccuracy(oldtypetyp, (oldtype.typ::LimitedAccuracy).causes) - end - state[i] = VarState(oldtypetyp, oldtype.undef) - end - end + 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(newtype, oldtype) - state[changeid] = smerge(oldtype, newtype) + 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] + end + return state +end + +function stoverwrite1!(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 + state[changeid] = newtype + return state +end diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index a7989777317c3..191820951fae1 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -6,7 +6,6 @@ const MAX_TYPEUNION_COMPLEXITY = 3 const MAX_TYPEUNION_LENGTH = 3 -const MAX_INLINE_CONST_SIZE = 256 ######################### # limitation heuristics # @@ -36,6 +35,12 @@ end # try to find `type` somewhere in `comparison` type # at a minimum nesting depth of `mindepth` function is_derived_type(@nospecialize(t), @nospecialize(c), mindepth::Int) + if has_free_typevars(t) || has_free_typevars(c) + # Don't allow finding types with free typevars. These strongly depend + # on identity and we do not make any effort to make sure this returns + # sensible results in that case. + return false + end if t === c return mindepth <= 1 end @@ -88,10 +93,7 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec return t # fast path: unparameterized are always simple else ut = unwrap_unionall(t) - if isa(ut, DataType) && isa(c, Type) && c !== Union{} && c <: t - # TODO: need to check that the UnionAll bounds on t are limited enough too - return t # t is already wider than the comparison in the type lattice - elseif is_derived_type_from_any(ut, sources, depth) + if is_derived_type_from_any(ut, sources, depth) return t # t isn't something new end end @@ -136,7 +138,7 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec Q = Any[ tP[i] for i in 1:np ] if ltP > np # combine tp[np:end] into tP[np] using Vararg - Q[np] = tuple_tail_elem(Bottom, Any[ tP[i] for i in np:ltP ]) + Q[np] = tuple_tail_elem(fallback_lattice, Bottom, Any[ tP[i] for i in np:ltP ]) end for i = 1:np # now apply limit element-wise to Q @@ -209,9 +211,6 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe return false # Bottom is as simple as they come elseif isa(t, DataType) && isempty(t.parameters) return false # fastpath: unparameterized types are always finite - elseif tupledepth > 0 && isa(unwrap_unionall(t), DataType) && isa(c, Type) && c !== Union{} && c <: t - # TODO: need to check that the UnionAll bounds on t are limited enough too - return false # t is already wider than the comparison in the type lattice elseif tupledepth > 0 && is_derived_type_from_any(unwrap_unionall(t), sources, depth) return false # t isn't something new end @@ -228,7 +227,7 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe end # rules for various comparison types if isa(c, TypeVar) - tupledepth = 1 # allow replacing a TypeVar with a concrete value (since we know the UnionAll must be in covariant position) + tupledepth = 1 if isa(t, TypeVar) return !(t.lb === Union{} || t.lb === c.lb) || # simplify lb towards Union{} type_more_complex(t.ub, c.ub, sources, depth + 1, tupledepth, 0) @@ -262,7 +261,8 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe elseif isa(c, DataType) && t.name === c.name cP = c.parameters length(cP) < length(tP) && return true - length(cP) > length(tP) && !isvarargtype(tP[end]) && depth == 1 && return false + isempty(tP) && return false + length(cP) > length(tP) && !isvarargtype(tP[end]) && depth == 1 && return false # is this line necessary? ntail = length(cP) - length(tP) # assume parameters were dropped from the tuple head # allow creating variation within a nested tuple, but only so deep if t.name === Tuple.name && tupledepth > 0 @@ -298,69 +298,206 @@ union_count_abstract(x::Union) = union_count_abstract(x.a) + union_count_abstrac union_count_abstract(@nospecialize(x)) = !isdispatchelem(x) function issimpleenoughtype(@nospecialize t) - t = ignorelimited(t) return unionlen(t) + union_count_abstract(t) <= MAX_TYPEUNION_LENGTH && unioncomplexity(t) <= MAX_TYPEUNION_COMPLEXITY end -# pick a wider type that contains both typea and typeb, -# with some limits on how "large" it can get, -# but without losing too much precision in common cases -# and also trying to be mostly associative and commutative -function tmerge(@nospecialize(typea), @nospecialize(typeb)) +# A simplified type_more_complex query over the extended lattice +# (assumes typeb ⊑ typea) +function issimplertype(𝕃::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) + typea isa MaybeUndef && (typea = typea.typ) # n.b. does not appear in inference + typeb isa MaybeUndef && (typeb = typeb.typ) # n.b. does not appear in inference + @assert !isa(typea, LimitedAccuracy) && !isa(typeb, LimitedAccuracy) "LimitedAccuracy not supported by simplertype lattice" # n.b. the caller was supposed to handle these + typea === typeb && return true + if typea isa PartialStruct + aty = widenconst(typea) + for i = 1:length(typea.fields) + ai = unwrapva(typea.fields[i]) + bi = fieldtype(aty, i) + is_lattice_equal(𝕃, ai, bi) && continue + tni = _typename(widenconst(ai)) + if tni isa Const + bi = (tni.val::Core.TypeName).wrapper + is_lattice_equal(𝕃, ai, bi) && continue + end + bi = getfield_tfunc(𝕃, typeb, Const(i)) + is_lattice_equal(𝕃, ai, bi) && continue + # It is not enough for ai to be simpler than bi: it must exactly equal + # (for this, an invariant struct field, by contrast to + # type_more_complex above which handles covariant tuples). + return false + end + elseif typea isa Type + return issimpleenoughtype(typea) + # elseif typea isa Const # fall-through to true is good + elseif typea isa Conditional # follow issubconditional query + typeb isa Const && return true + typeb isa Conditional || return false + is_same_conditionals(typea, typeb) || return false + issimplertype(𝕃, typea.thentype, typeb.thentype) || return false + issimplertype(𝕃, typea.elsetype, typeb.elsetype) || return false + elseif typea isa InterConditional # ibid + typeb isa Const && return true + typeb isa InterConditional || return false + is_same_conditionals(typea, typeb) || return false + issimplertype(𝕃, typea.thentype, typeb.thentype) || return false + issimplertype(𝕃, typea.elsetype, typeb.elsetype) || return false + elseif typea isa MustAlias + typeb isa MustAlias || return false + issubalias(typeb, typea) || return false + issimplertype(𝕃, typea.vartyp, typeb.vartyp) || return false + issimplertype(𝕃, typea.fldtyp, typeb.fldtyp) || return false + elseif typea isa InterMustAlias + typeb isa InterMustAlias || return false + issubalias(typeb, typea) || return false + issimplertype(𝕃, typea.vartyp, typeb.vartyp) || return false + issimplertype(𝕃, typea.fldtyp, typeb.fldtyp) || return false + elseif typea isa PartialOpaque + # TODO + typeb isa PartialOpaque || return false + aty = widenconst(typea) + bty = widenconst(typeb) + if typea.source === typeb.source && typea.parent === typeb.parent && aty == bty && typea.env == typeb.env + return false + end + return false + end + return true +end + +@inline function tmerge_fast_path(lattice::AbstractLattice, @nospecialize(typea), @nospecialize(typeb)) + # Fast paths typea === Union{} && return typeb typeb === Union{} && return typea - suba = typea ⊑ typeb - suba && issimpleenoughtype(typeb) && return typeb - subb = typeb ⊑ typea + typea === typeb && return typea + + suba = ⊑(lattice, typea, typeb) + suba && issimplertype(lattice, typeb, typea) && return typeb + subb = ⊑(lattice, typeb, typea) suba && subb && return typea - subb && issimpleenoughtype(typea) && return typea + subb && issimplertype(lattice, typea, typeb) && return typea + return nothing +end + +function tmerge(lattice::OptimizerLattice, @nospecialize(typea), @nospecialize(typeb)) + r = tmerge_fast_path(lattice, typea, typeb) + r !== nothing && return r - # type-lattice for LimitedAccuracy wrapper - # the merge create a slightly narrower type than needed, but we can't - # represent the precise intersection of causes and don't attempt to - # enumerate some of these cases where we could - if isa(typea, LimitedAccuracy) && isa(typeb, LimitedAccuracy) - if typea.causes ⊆ typeb.causes - causes = typeb.causes - elseif typeb.causes ⊆ typea.causes - causes = typea.causes - else - causes = union!(copy(typea.causes), typeb.causes) - end - return LimitedAccuracy(tmerge(typea.typ, typeb.typ), causes) - elseif isa(typea, LimitedAccuracy) - return LimitedAccuracy(tmerge(typea.typ, typeb), typea.causes) - elseif isa(typeb, LimitedAccuracy) - return LimitedAccuracy(tmerge(typea, typeb.typ), typeb.causes) - end # type-lattice for MaybeUndef wrapper if isa(typea, MaybeUndef) || isa(typeb, MaybeUndef) return MaybeUndef(tmerge( isa(typea, MaybeUndef) ? typea.typ : typea, isa(typeb, MaybeUndef) ? typeb.typ : typeb)) end - # type-lattice for Conditional wrapper + return tmerge(widenlattice(lattice), typea, typeb) +end + +function union_causes(causesa::IdSet{InferenceState}, causesb::IdSet{InferenceState}) + if causesa ⊆ causesb + return causesb + elseif causesb ⊆ causesa + return causesa + else + return union!(copy(causesa), causesb) + end +end + +function merge_causes(causesa::IdSet{InferenceState}, causesb::IdSet{InferenceState}) + # TODO: When lattice elements are equal, we're allowed to discard one or the + # other set, but we'll need to come up with a consistent rule. For now, we + # just check the length, but other heuristics may be applicable. + if length(causesa) < length(causesb) + return causesa + elseif length(causesb) < length(causesa) + return causesb + else + return union!(copy(causesa), causesb) + end +end + +@noinline function tmerge_limited(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) + typea === Union{} && return typeb + typeb === Union{} && return typea + + # Like tmerge_fast_path, but tracking which causes need to be preserved at + # the same time. + if isa(typea, LimitedAccuracy) && isa(typeb, LimitedAccuracy) + causesa = typea.causes + causesb = typeb.causes + typea = typea.typ + typeb = typeb.typ + suba = ⊑(lattice, typea, typeb) + subb = ⊑(lattice, typeb, typea) + + # Approximated types are lattice equal. Merge causes. + if suba && subb + return LimitedAccuracy(typeb, merge_causes(causesa, causesb)) + elseif suba + issimplertype(lattice, typeb, typea) && return LimitedAccuracy(typeb, causesb) + causes = causesb + # `a`'s causes may be discarded + elseif subb + causes = causesa + else + causes = union_causes(causesa, causesb) + end + else + if isa(typeb, LimitedAccuracy) + (typea, typeb) = (typeb, typea) + end + typea = typea::LimitedAccuracy + + causes = typea.causes + typea = typea.typ + + suba = ⊑(lattice, typea, typeb) + if suba + issimplertype(lattice, typeb, typea) && return typeb + # `typea` was narrower than `typeb`. Whatever tmerge produces, + # we know it must be wider than `typeb`, so we may drop the + # causes. + causes = nothing + end + subb = ⊑(lattice, typeb, typea) + end + + suba && subb && return LimitedAccuracy(typea, causes) + subb && issimplertype(lattice, typea, typeb) && return LimitedAccuracy(typea, causes) + return LimitedAccuracy(tmerge(widenlattice(lattice), typea, typeb), causes) +end + +function tmerge(lattice::InferenceLattice, @nospecialize(typea), @nospecialize(typeb)) + if isa(typea, LimitedAccuracy) || isa(typeb, LimitedAccuracy) + return tmerge_limited(lattice, typea, typeb) + end + + r = tmerge_fast_path(widenlattice(lattice), typea, typeb) + r !== nothing && return r + return tmerge(widenlattice(lattice), typea, typeb) +end + +function tmerge(lattice::ConditionalsLattice, @nospecialize(typea), @nospecialize(typeb)) + # type-lattice for Conditional wrapper (NOTE never be merged with InterConditional) if isa(typea, Conditional) && isa(typeb, Const) if typeb.val === true - typeb = Conditional(typea.var, Any, Union{}) + typeb = Conditional(typea.slot, Any, Union{}) elseif typeb.val === false - typeb = Conditional(typea.var, Union{}, Any) + typeb = Conditional(typea.slot, Union{}, Any) end end if isa(typeb, Conditional) && isa(typea, Const) if typea.val === true - typea = Conditional(typeb.var, Any, Union{}) + typea = Conditional(typeb.slot, Any, Union{}) elseif typea.val === false - typea = Conditional(typeb.var, Union{}, Any) + typea = Conditional(typeb.slot, Union{}, Any) end end if isa(typea, Conditional) && isa(typeb, Conditional) if is_same_conditionals(typea, typeb) - vtype = tmerge(typea.vtype, typeb.vtype) - elsetype = tmerge(typea.elsetype, typeb.elsetype) - if vtype !== elsetype - return Conditional(typea.var, vtype, elsetype) + thentype = tmerge(widenlattice(lattice), typea.thentype, typeb.thentype) + elsetype = tmerge(widenlattice(lattice), typea.elsetype, typeb.elsetype) + if thentype !== elsetype + return Conditional(typea.slot, thentype, elsetype) end end val = maybe_extract_const_bool(typea) @@ -369,7 +506,13 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb)) end return Bool end - # type-lattice for InterConditional wrapper, InterConditional will never be merged with Conditional + typea = widenconditional(typea) + typeb = widenconditional(typeb) + return tmerge(widenlattice(lattice), typea, typeb) +end + +function tmerge(lattice::InterConditionalsLattice, @nospecialize(typea), @nospecialize(typeb)) + # type-lattice for InterConditional wrapper (NOTE never be merged with Conditional) if isa(typea, InterConditional) && isa(typeb, Const) if typeb.val === true typeb = InterConditional(typea.slot, Any, Union{}) @@ -386,10 +529,10 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb)) end if isa(typea, InterConditional) && isa(typeb, InterConditional) if is_same_conditionals(typea, typeb) - vtype = tmerge(typea.vtype, typeb.vtype) - elsetype = tmerge(typea.elsetype, typeb.elsetype) - if vtype !== elsetype - return InterConditional(typea.slot, vtype, elsetype) + thentype = tmerge(widenlattice(lattice), typea.thentype, typeb.thentype) + elsetype = tmerge(widenlattice(lattice), typea.elsetype, typeb.elsetype) + if thentype !== elsetype + return InterConditional(typea.slot, thentype, elsetype) end end val = maybe_extract_const_bool(typea) @@ -398,53 +541,150 @@ function tmerge(@nospecialize(typea), @nospecialize(typeb)) end return Bool end - # type-lattice for Const and PartialStruct wrappers - if ((isa(typea, PartialStruct) || isa(typea, Const)) && - (isa(typeb, PartialStruct) || isa(typeb, Const))) - aty = widenconst(typea) - bty = widenconst(typeb) - if aty === bty - typea_nfields = nfields_tfunc(typea) - typeb_nfields = nfields_tfunc(typeb) - isa(typea_nfields, Const) || return aty - isa(typeb_nfields, Const) || return aty - type_nfields = typea_nfields.val::Int - type_nfields === typeb_nfields.val::Int || return aty - type_nfields == 0 && return aty - fields = Vector{Any}(undef, type_nfields) - anyconst = false - for i = 1:type_nfields - ai = getfield_tfunc(typea, Const(i)) - bi = getfield_tfunc(typeb, Const(i)) - ity = tmerge(ai, bi) - if ai === Union{} || bi === Union{} - ity = widenconst(ity) + typea = widenconditional(typea) + typeb = widenconditional(typeb) + return tmerge(widenlattice(lattice), typea, typeb) +end + +function tmerge(𝕃::AnyMustAliasesLattice, @nospecialize(typea), @nospecialize(typeb)) + typea = widenmustalias(typea) + typeb = widenmustalias(typeb) + return tmerge(widenlattice(𝕃), typea, typeb) +end + +# N.B. This can also be called with both typea::Const and typeb::Const to +# to recover PartialStruct from `Const`s with overlapping fields. +function tmerge_partial_struct(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) + aty = widenconst(typea) + bty = widenconst(typeb) + if aty === bty + # must have egal here, since we do not create PartialStruct for non-concrete types + typea_nfields = nfields_tfunc(lattice, typea) + typeb_nfields = nfields_tfunc(lattice, typeb) + isa(typea_nfields, Const) || return nothing + isa(typeb_nfields, Const) || return nothing + type_nfields = typea_nfields.val::Int + type_nfields === typeb_nfields.val::Int || return nothing + type_nfields == 0 && return nothing + fields = Vector{Any}(undef, type_nfields) + anyrefine = false + for i = 1:type_nfields + ai = getfield_tfunc(lattice, typea, Const(i)) + bi = getfield_tfunc(lattice, typeb, Const(i)) + # N.B.: We're assuming here that !isType(aty), because that case + # only arises when typea === typeb, which should have been caught + # before calling this. + ft = fieldtype(aty, i) + if is_lattice_equal(lattice, ai, bi) || is_lattice_equal(lattice, ai, ft) + # Since ai===bi, the given type has no restrictions on complexity. + # and can be used to refine ft + tyi = ai + elseif is_lattice_equal(lattice, bi, ft) + tyi = bi + elseif (tyi′ = tmerge_field(lattice, ai, bi); tyi′ !== nothing) + # allow external lattice implementation to provide a custom field-merge strategy + tyi = tyi′ + else + # Otherwise use the default aggressive field-merge implementation, and + # choose between using the fieldtype or some other simple merged type. + # The wrapper type never has restrictions on complexity, + # so try to use that to refine the estimated type too. + tni = _typename(widenconst(ai)) + if tni isa Const && tni === _typename(widenconst(bi)) + # A tmeet call may cause tyi to become complex, but since the inputs were + # strictly limited to being egal, this has no restrictions on complexity. + # (Otherwise, we would need to use <: and take the narrower one without + # intersection. See the similar comment in abstract_call_method.) + tyi = typeintersect(ft, (tni.val::Core.TypeName).wrapper) + else + # Since aty===bty, the fieldtype has no restrictions on complexity. + tyi = ft end - fields[i] = ity - anyconst |= has_nontrivial_const_info(ity) end - return anyconst ? PartialStruct(aty, fields) : aty + fields[i] = tyi + if !anyrefine + anyrefine = has_nontrivial_extended_info(lattice, tyi) || # extended information + ⋤(lattice, tyi, ft) # just a type-level information, but more precise than the declared type + end end + anyrefine && return PartialStruct(aty, fields) end - if isa(typea, PartialOpaque) && isa(typeb, PartialOpaque) && widenconst(typea) == widenconst(typeb) - if !(typea.source === typeb.source && - typea.parent === typeb.parent) - return widenconst(typea) - end - return PartialOpaque(typea.typ, tmerge(typea.env, typeb.env), - typea.parent, typea.source) + return nothing +end + +function tmerge(lattice::PartialsLattice, @nospecialize(typea), @nospecialize(typeb)) + # type-lattice for Const and PartialStruct wrappers + aps = isa(typea, PartialStruct) + bps = isa(typeb, PartialStruct) + acp = aps || isa(typea, Const) + bcp = bps || isa(typeb, Const) + if acp && bcp + typea === typeb && return typea + psrt = tmerge_partial_struct(lattice, typea, typeb) + psrt !== nothing && return psrt end - # no special type-inference lattice, join the types - typea, typeb = widenconst(typea), widenconst(typeb) - if !isa(typea, Type) || !isa(typeb, Type) - # XXX: this should never happen - return Any + + # Don't widen const here - external AbstractInterpreter might insert lattice + # layers between us and `ConstsLattice`. + wl = widenlattice(lattice) + aps && (typea = widenlattice(wl, typea)) + bps && (typeb = widenlattice(wl, typeb)) + + # type-lattice for PartialOpaque wrapper + apo = isa(typea, PartialOpaque) + bpo = isa(typeb, PartialOpaque) + if apo && bpo + aty = widenconst(typea) + bty = widenconst(typeb) + if aty == bty + if !(typea.source === typeb.source && + typea.parent === typeb.parent) + return widenconst(typea) + end + return PartialOpaque(typea.typ, tmerge(lattice, typea.env, typeb.env), + typea.parent, typea.source) + end + typea = aty + typeb = bty + elseif apo + typea = widenlattice(wl, typea) + elseif bpo + typeb = widenlattice(wl, typeb) end - typea == typeb && return typea + + return tmerge(wl, typea, typeb) +end + + +function tmerge(lattice::ConstsLattice, @nospecialize(typea), @nospecialize(typeb)) + acp = isa(typea, Const) || isa(typea, PartialTypeVar) + bcp = isa(typeb, Const) || isa(typeb, PartialTypeVar) + if acp && bcp + typea === typeb && return typea + end + wl = widenlattice(lattice) + acp && (typea = widenlattice(wl, typea)) + bcp && (typeb = widenlattice(wl, typeb)) + return tmerge(wl, typea, typeb) +end + +function tmerge(::JLTypeLattice, @nospecialize(typea::Type), @nospecialize(typeb::Type)) # it's always ok to form a Union of two concrete types - if (isconcretetype(typea) || isType(typea)) && (isconcretetype(typeb) || isType(typeb)) + act = isconcretetype(typea) + bct = isconcretetype(typeb) + if act && bct + # Extra fast path for pointer-egal concrete types + (pointer_from_objref(typea) === pointer_from_objref(typeb)) && return typea + end + if (act || isType(typea)) && (bct || isType(typeb)) return Union{typea, typeb} end + typea <: typeb && return typeb + typeb <: typea && return typea + return tmerge_types_slow(typea, typeb) +end + +@noinline function tmerge_types_slow(@nospecialize(typea::Type), @nospecialize(typeb::Type)) # collect the list of types from past tmerge calls returning Union # and then reduce over that list types = Any[] @@ -610,44 +850,3 @@ function tuplemerge(a::DataType, b::DataType) end return Tuple{p...} end - -# compute typeintersect over the extended inference lattice -# where v is in the extended lattice, and t is a Type -function tmeet(@nospecialize(v), @nospecialize(t)) - if isa(v, Const) - if !has_free_typevars(t) && !isa(v.val, t) - return Bottom - end - return v - elseif isa(v, PartialStruct) - has_free_typevars(t) && return v - widev = widenconst(v) - if widev <: t - return v - end - ti = typeintersect(widev, t) - valid_as_lattice(ti) || return Bottom - @assert widev <: Tuple - new_fields = Vector{Any}(undef, length(v.fields)) - for i = 1:length(new_fields) - vfi = v.fields[i] - if isvarargtype(vfi) - new_fields[i] = vfi - else - new_fields[i] = tmeet(vfi, widenconst(getfield_tfunc(t, Const(i)))) - if new_fields[i] === Bottom - return Bottom - end - end - end - return tuple_tfunc(new_fields) - elseif isa(v, Conditional) - if !(Bool <: t) - return Bottom - end - return v - end - ti = typeintersect(widenconst(v), t) - valid_as_lattice(ti) || return Bottom - return ti -end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index 956fd7c747e80..4a4f27c9c27c2 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -4,295 +4,405 @@ AbstractInterpreter An abstract base class that allows multiple dispatch to determine the method of -executing Julia code. The native Julia LLVM pipeline is enabled by using the +executing Julia code. The native Julia-LLVM pipeline is enabled by using the `NativeInterpreter` concrete instantiation of this abstract class, others can be swapped in as long as they follow the `AbstractInterpreter` API. -If `interp` is an `AbstractInterpreter`, it is expected to provide at least the following methods: -- `InferenceParams(interp)` - return an `InferenceParams` instance -- `OptimizationParams(interp)` - return an `OptimizationParams` instance -- `get_world_counter(interp)` - return the world age for this interpreter -- `get_inference_cache(interp)` - return the runtime inference cache -- `code_cache(interp)` - return the global inference cache +If `interp::NewInterpreter` is an `AbstractInterpreter`, it is expected to provide at least +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_cache(interp::NewInterpreter)` - return the local inference cache +- `code_cache(interp::NewInterpreter)` - return the global inference cache """ abstract type AbstractInterpreter end +abstract type AbstractLattice end struct ArgInfo fargs::Union{Nothing,Vector{Any}} argtypes::Vector{Any} end -struct TriState; state::UInt8; end -const ALWAYS_FALSE = TriState(0x00) -const ALWAYS_TRUE = TriState(0x01) -const TRISTATE_UNKNOWN = TriState(0x02) - -function tristate_merge(old::TriState, new::TriState) - (old === ALWAYS_FALSE || new === ALWAYS_FALSE) && return ALWAYS_FALSE - old === TRISTATE_UNKNOWN && return old - return new +struct StmtInfo + """ + If `used` is false, we know that the return value is statically unused and + need thus not be computed. + """ + used::Bool end -struct Effects - consistent::TriState - effect_free::TriState - nothrow::TriState - terminates::TriState - # This effect is currently only tracked in inference and modified - # :consistent before caching. We may want to track it in the future. - inbounds_taints_consistency::Bool -end -Effects(consistent::TriState, effect_free::TriState, nothrow::TriState, terminates::TriState) = - Effects(consistent, effect_free, nothrow, terminates, false) -Effects() = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN) - -Effects(e::Effects; consistent::TriState=e.consistent, - effect_free::TriState = e.effect_free, nothrow::TriState=e.nothrow, terminates::TriState=e.terminates, - inbounds_taints_consistency::Bool = e.inbounds_taints_consistency) = - Effects(consistent, effect_free, nothrow, terminates, inbounds_taints_consistency) - -is_total_or_error(effects::Effects) = - effects.consistent === ALWAYS_TRUE && effects.effect_free === ALWAYS_TRUE && - effects.terminates === ALWAYS_TRUE - -is_total(effects::Effects) = - is_total_or_error(effects) && effects.nothrow === ALWAYS_TRUE - -is_removable_if_unused(effects::Effects) = - effects.effect_free === ALWAYS_TRUE && - effects.terminates === ALWAYS_TRUE && - effects.nothrow === ALWAYS_TRUE - -const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE) - -encode_effects(e::Effects) = e.consistent.state | (e.effect_free.state << 2) | (e.nothrow.state << 4) | (e.terminates.state << 6) -decode_effects(e::UInt8) = - Effects(TriState(e & 0x3), - TriState((e >> 2) & 0x3), - TriState((e >> 4) & 0x3), - TriState((e >> 6) & 0x3), false) - -function tristate_merge(old::Effects, new::Effects) - Effects(tristate_merge( - old.consistent, new.consistent), - tristate_merge( - old.effect_free, new.effect_free), - tristate_merge( - old.nothrow, new.nothrow), - tristate_merge( - old.terminates, new.terminates), - old.inbounds_taints_consistency || - new.inbounds_taints_consistency) +struct MethodInfo + propagate_inbounds::Bool + method_for_inference_limit_heuristics::Union{Nothing,Method} end +MethodInfo(src::CodeInfo) = MethodInfo( + src.propagate_inbounds, + src.method_for_inference_limit_heuristics::Union{Nothing,Method}) -struct EffectsOverride - consistent::Bool - effect_free::Bool - nothrow::Bool - terminates_globally::Bool - terminates_locally::Bool -end - -function encode_effects_override(eo::EffectsOverride) - e = 0x00 - eo.consistent && (e |= 0x01) - eo.effect_free && (e |= 0x02) - eo.nothrow && (e |= 0x04) - eo.terminates_globally && (e |= 0x08) - eo.terminates_locally && (e |= 0x10) - e +""" + v::VarState + +A special wrapper that represents a local variable of a method being analyzed. +This does not participate in the native type system nor the inference lattice, and it thus +should be always unwrapped to `v.typ` when performing any type or lattice operations on it. +`v.undef` represents undefined-ness of this static parameter. If `true`, it means that the +variable _may_ be undefined at runtime, otherwise it is guaranteed to be defined. +If `v.typ === Bottom` it means that the variable is strictly undefined. +""" +struct VarState + typ + undef::Bool + VarState(@nospecialize(typ), undef::Bool) = new(typ, undef) end -decode_effects_override(e::UInt8) = - EffectsOverride( - (e & 0x01) != 0x00, - (e & 0x02) != 0x00, - (e & 0x04) != 0x00, - (e & 0x08) != 0x00, - (e & 0x10) != 0x00) +abstract type ForwardableArgtypes end """ - InferenceResult + InferenceResult(linfo::MethodInstance, [argtypes::ForwardableArgtypes, 𝕃::AbstractLattice]) A type that represents the result of running type inference on a chunk of code. + +See also [`matching_cache_argtypes`](@ref). """ mutable struct InferenceResult - linfo::MethodInstance - argtypes::Vector{Any} - overridden_by_const::BitVector - result # ::Type, or InferenceState if WIP - src # ::Union{CodeInfo, OptimizationState} if inferred copy is available, nothing otherwise + const linfo::MethodInstance + const argtypes::Vector{Any} + const overridden_by_const::BitVector + result # extended lattice element if inferred, nothing otherwise + 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 - function InferenceResult(linfo::MethodInstance, - arginfo#=::Union{Nothing,Tuple{ArgInfo,InferenceState}}=# = nothing) - argtypes, overridden_by_const = matching_cache_argtypes(linfo, arginfo) - return new(linfo, argtypes, overridden_by_const, Any, nothing, - WorldRange(), Effects(), Effects(), nothing) + 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) end 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)...) """ - OptimizationParams + inf_params::InferenceParams + +Parameters that control abstract interpretation-based type inference operation. + +--- +- `inf_params.max_methods::Int = 3`\\ + Type inference gives up analysis on a call when there are more than `max_methods` matching + methods. This trades off between compiler latency and generated code performance. + Typically, considering many methods means spending _lots_ of time obtaining poor type + information, so this option should be kept low. [`Base.Experimental.@max_methods`](@ref) + can have a more fine-grained control on this configuration with per-module or per-method + annotation basis. +--- +- `inf_params.max_union_splitting::Int = 4`\\ + Specifies the maximum number of union-tuples to swap or expand before computing the set of + matching methods or conditional types. +--- +- `inf_params.max_apply_union_enum::Int = 8`\\ + Specifies the maximum number of union-tuples to swap or expand when inferring a call to + `Core._apply_iterate`. +--- +- `inf_params.max_tuple_splat::Int = 32`\\ + When attempting to infer a call to `Core._apply_iterate`, abort the analysis if the tuple + contains more than this many elements. +--- +- `inf_params.tuple_complexity_limit_depth::Int = 3`\\ + Specifies the maximum depth of large tuple type that can appear as specialized method + signature when inferring a recursive call graph. +--- +- `inf_params.ipo_constant_propagation::Bool = true`\\ + If `false`, disables analysis with extended lattice information, i.e. disables any of + the concrete evaluation, semi-concrete interpretation and constant propagation entirely. + [`Base.@constprop :none`](@ref Base.@constprop) can have a more fine-grained control on + this configuration with per-method annotation basis. +--- +- `inf_params.aggressive_constant_propagation::Bool = false`\\ + If `true`, forces constant propagation on any methods when any extended lattice + information available. [`Base.@constprop :aggressive`](@ref Base.@constprop) can have a + more fine-grained control on this configuration with per-method annotation basis. +--- +- `inf_params.unoptimize_throw_blocks::Bool = true`\\ + If `true`, skips inferring calls that are in a block that is known to `throw`. + It may improve the compiler latency without sacrificing the runtime performance + in common situations. +--- +- `inf_params.assume_bindings_static::Bool = false`\\ + If `true`, assumes that no new bindings will be added, i.e. a non-existing binding at + inference time can be assumed to always not exist at runtime (and thus e.g. any access to + it will `throw`). Defaults to `false` since this assumption does not hold in Julia's + semantics for native code execution. +--- +""" +struct InferenceParams + max_methods::Int + max_union_splitting::Int + max_apply_union_enum::Int + max_tuple_splat::Int + tuple_complexity_limit_depth::Int + ipo_constant_propagation::Bool + aggressive_constant_propagation::Bool + unoptimize_throw_blocks::Bool + assume_bindings_static::Bool + ignore_recursion_hardlimit::Bool + + function InferenceParams( + max_methods::Int, + max_union_splitting::Int, + max_apply_union_enum::Int, + max_tuple_splat::Int, + tuple_complexity_limit_depth::Int, + ipo_constant_propagation::Bool, + aggressive_constant_propagation::Bool, + unoptimize_throw_blocks::Bool, + assume_bindings_static::Bool, + ignore_recursion_hardlimit::Bool) + return new( + max_methods, + max_union_splitting, + max_apply_union_enum, + max_tuple_splat, + tuple_complexity_limit_depth, + ipo_constant_propagation, + aggressive_constant_propagation, + unoptimize_throw_blocks, + assume_bindings_static, + ignore_recursion_hardlimit) + end +end +function InferenceParams( + params::InferenceParams = InferenceParams( # default constructor + #=max_methods::Int=# 3, + #=max_union_splitting::Int=# 4, + #=max_apply_union_enum::Int=# 8, + #=max_tuple_splat::Int=# 32, + #=tuple_complexity_limit_depth::Int=# 3, + #=ipo_constant_propagation::Bool=# true, + #=aggressive_constant_propagation::Bool=# false, + #=unoptimize_throw_blocks::Bool=# true, + #=assume_bindings_static::Bool=# false, + #=ignore_recursion_hardlimit::Bool=# false); + max_methods::Int = params.max_methods, + max_union_splitting::Int = params.max_union_splitting, + max_apply_union_enum::Int = params.max_apply_union_enum, + max_tuple_splat::Int = params.max_tuple_splat, + tuple_complexity_limit_depth::Int = params.tuple_complexity_limit_depth, + ipo_constant_propagation::Bool = params.ipo_constant_propagation, + aggressive_constant_propagation::Bool = params.aggressive_constant_propagation, + unoptimize_throw_blocks::Bool = params.unoptimize_throw_blocks, + assume_bindings_static::Bool = params.assume_bindings_static, + ignore_recursion_hardlimit::Bool = params.ignore_recursion_hardlimit) + return InferenceParams( + max_methods, + max_union_splitting, + max_apply_union_enum, + max_tuple_splat, + tuple_complexity_limit_depth, + ipo_constant_propagation, + aggressive_constant_propagation, + unoptimize_throw_blocks, + assume_bindings_static, + ignore_recursion_hardlimit) +end + +""" + opt_params::OptimizationParams Parameters that control optimizer operation. + +--- +- `opt_params.inlining::Bool = inlining_enabled()`\\ + Controls whether or not inlining is enabled. +--- +- `opt_params.inline_cost_threshold::Int = 100`\\ + Specifies the number of CPU cycles beyond which it's not worth inlining. +--- +- `opt_params.inline_nonleaf_penalty::Int = 1000`\\ + Specifies the penalty cost for a dynamic dispatch. +--- +- `opt_params.inline_tupleret_bonus::Int = 250`\\ + Specifies the extra inlining willingness for a method specialization with non-concrete + tuple return types (in hopes of splitting it up). `opt_params.inline_tupleret_bonus` will + be added to `opt_params.inline_cost_threshold` when making inlining decision. +--- +- `opt_params.inline_error_path_cost::Int = 20`\\ + Specifies the penalty cost for an un-optimized dynamic call in a block that is known to + `throw`. See also [`(inf_params::InferenceParams).unoptimize_throw_blocks`](@ref InferenceParams). +--- +- `opt_params.max_tuple_splat::Int = 32`\\ + When attempting to inline `Core._apply_iterate`, abort the optimization if the tuple + contains more than this many elements. +--- +- `opt_params.compilesig_invokes::Bool = true`\\ + If `true`, gives the inliner license to change which `MethodInstance` to invoke when + generating `:invoke` expression based on the [`@nospecialize`](@ref) annotation, + in order to avoid over-specialization. +--- +- `opt_params.trust_inference::Bool = false`\\ + If `false`, the inliner will unconditionally generate a fallback block when union-splitting + a callsite, in case of existing subtyping bugs. This option may be removed in the future. +--- +- `opt_params.assume_fatal_throw::Bool = false`\\ + If `true`, gives the optimizer license to assume that any `throw` is fatal and thus the + state after a `throw` is not externally observable. In particular, this gives the + optimizer license to move side effects (that are proven not observed within a particular + code path) across a throwing call. Defaults to `false`. +--- """ struct OptimizationParams - inlining::Bool # whether inlining is enabled - inline_cost_threshold::Int # number of CPU cycles beyond which it's not worth inlining - inline_nonleaf_penalty::Int # penalty for dynamic dispatch - inline_tupleret_bonus::Int # extra inlining willingness for non-concrete tuple return types (in hopes of splitting it up) - inline_error_path_cost::Int # cost of (un-optimized) calls in blocks that throw - + inlining::Bool + inline_cost_threshold::Int + inline_nonleaf_penalty::Int + inline_tupleret_bonus::Int + inline_error_path_cost::Int + max_tuple_splat::Int + compilesig_invokes::Bool trust_inference::Bool - - # Duplicating for now because optimizer inlining requires it. - # Keno assures me this will be removed in the near future - MAX_METHODS::Int - MAX_TUPLE_SPLAT::Int - MAX_UNION_SPLITTING::Int - - function OptimizationParams(; - inlining::Bool = inlining_enabled(), - inline_cost_threshold::Int = 100, - inline_nonleaf_penalty::Int = 1000, - inline_tupleret_bonus::Int = 250, - inline_error_path_cost::Int = 20, - max_methods::Int = 3, - tuple_splat::Int = 32, - union_splitting::Int = 4, - trust_inference::Bool = false - ) + assume_fatal_throw::Bool + + function OptimizationParams( + inlining::Bool, + inline_cost_threshold::Int, + inline_nonleaf_penalty::Int, + inline_tupleret_bonus::Int, + inline_error_path_cost::Int, + max_tuple_splat::Int, + compilesig_invokes::Bool, + trust_inference::Bool, + assume_fatal_throw::Bool) return new( inlining, inline_cost_threshold, inline_nonleaf_penalty, inline_tupleret_bonus, inline_error_path_cost, + max_tuple_splat, + compilesig_invokes, trust_inference, - max_methods, - tuple_splat, - union_splitting - ) + assume_fatal_throw) end end - -""" - InferenceParams - -Parameters that control type inference operation. -""" -struct InferenceParams - ipo_constant_propagation::Bool - aggressive_constant_propagation::Bool - unoptimize_throw_blocks::Bool - - # don't consider more than N methods. this trades off between - # compiler performance and generated code performance. - # typically, considering many methods means spending lots of time - # obtaining poor type information. - # It is important for N to be >= the number of methods in the error() - # function, so we can still know that error() is always Bottom. - MAX_METHODS::Int - # the maximum number of union-tuples to swap / expand - # before computing the set of matching methods - MAX_UNION_SPLITTING::Int - # the maximum number of union-tuples to swap / expand - # when inferring a call to _apply_iterate - MAX_APPLY_UNION_ENUM::Int - - # parameters limiting large (tuple) types - TUPLE_COMPLEXITY_LIMIT_DEPTH::Int - - # when attempting to inline _apply_iterate, abort the optimization if the - # tuple contains more than this many elements - MAX_TUPLE_SPLAT::Int - - function InferenceParams(; - ipo_constant_propagation::Bool = true, - aggressive_constant_propagation::Bool = false, - unoptimize_throw_blocks::Bool = true, - max_methods::Int = 3, - union_splitting::Int = 4, - apply_union_enum::Int = 8, - tupletype_depth::Int = 3, - tuple_splat::Int = 32, - ) - return new( - ipo_constant_propagation, - aggressive_constant_propagation, - unoptimize_throw_blocks, - max_methods, - union_splitting, - apply_union_enum, - tupletype_depth, - tuple_splat, - ) - end +function OptimizationParams( + params::OptimizationParams = OptimizationParams( + #=inlining::Bool=# inlining_enabled(), + #=inline_cost_threshold::Int=# 100, + #=inline_nonleaf_penalty::Int=# 1000, + #=inline_tupleret_bonus::Int=# 250, + #=inline_error_path_cost::Int=# 20, + #=max_tuple_splat::Int=# 32, + #=compilesig_invokes::Bool=# true, + #=trust_inference::Bool=# false, + #=assume_fatal_throw::Bool=# false); + inlining::Bool = params.inlining, + inline_cost_threshold::Int = params.inline_cost_threshold, + inline_nonleaf_penalty::Int = params.inline_nonleaf_penalty, + inline_tupleret_bonus::Int = params.inline_tupleret_bonus, + inline_error_path_cost::Int = params.inline_error_path_cost, + max_tuple_splat::Int = params.max_tuple_splat, + compilesig_invokes::Bool = params.compilesig_invokes, + trust_inference::Bool = params.trust_inference, + assume_fatal_throw::Bool = params.assume_fatal_throw) + return OptimizationParams( + inlining, + inline_cost_threshold, + inline_nonleaf_penalty, + inline_tupleret_bonus, + inline_error_path_cost, + max_tuple_splat, + compilesig_invokes, + trust_inference, + assume_fatal_throw) end """ - NativeInterpreter + NativeInterpreter <: AbstractInterpreter -This represents Julia's native type inference algorithm and codegen backend. -It contains many parameters used by the compilation pipeline. +This represents Julia's native type inference algorithm and the Julia-LLVM codegen backend. """ struct NativeInterpreter <: AbstractInterpreter - # Cache of inference results for this particular interpreter - cache::Vector{InferenceResult} # The world age we're working inside of world::UInt + # method table to lookup for during inference on this world age + method_table::CachedMethodTable{InternalMethodTable} + + # Cache of inference results for this particular interpreter + inf_cache::Vector{InferenceResult} + # Parameters for inference and optimization inf_params::InferenceParams opt_params::OptimizationParams - function NativeInterpreter(world::UInt = get_world_counter(); - inf_params = InferenceParams(), - opt_params = OptimizationParams(), - ) - # Sometimes the caller is lazy and passes typemax(UInt). - # we cap it to the current world age - if world == typemax(UInt) - world = get_world_counter() - end + # a boolean flag to indicate if this interpreter is performing semi concrete interpretation + irinterp::Bool +end - # If they didn't pass typemax(UInt) but passed something more subtly - # incorrect, fail out loudly. - @assert world <= get_world_counter() +function NativeInterpreter(world::UInt = get_world_counter(); + inf_params::InferenceParams = InferenceParams(), + opt_params::OptimizationParams = OptimizationParams()) + # 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() + end + # If they didn't pass typemax(UInt) but passed something more subtly + # incorrect, fail out loudly. + @assert world <= get_world_counter() - return new( - # Initially empty cache - Vector{InferenceResult}(), + method_table = CachedMethodTable(InternalMethodTable(world)) - # world age counter - world, + inf_cache = Vector{InferenceResult}() # Initially empty cache - # parameters for inference and optimization - inf_params, - opt_params, - ) - end + return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params, #=irinterp=#false) +end + +function NativeInterpreter(interp::NativeInterpreter; + world::UInt = interp.world, + 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) end # Quickly and easily satisfy the AbstractInterpreter API contract -InferenceParams(ni::NativeInterpreter) = ni.inf_params -OptimizationParams(ni::NativeInterpreter) = ni.opt_params -get_world_counter(ni::NativeInterpreter) = ni.world -get_inference_cache(ni::NativeInterpreter) = ni.cache -code_cache(ni::NativeInterpreter) = WorldView(GLOBAL_CI_CACHE, get_world_counter(ni)) +InferenceParams(interp::NativeInterpreter) = interp.inf_params +OptimizationParams(interp::NativeInterpreter) = interp.opt_params +get_world_counter(interp::NativeInterpreter) = interp.world +get_inference_cache(interp::NativeInterpreter) = interp.inf_cache +code_cache(interp::NativeInterpreter) = WorldView(GLOBAL_CI_CACHE, get_world_counter(interp)) + +""" + already_inferred_quick_test(::AbstractInterpreter, ::MethodInstance) + +For the `NativeInterpreter`, we don't need to do an actual cache query to know if something +was already inferred. If we reach this point, but the inference flag has been turned off, +then it's in the cache. This is purely for a performance optimization. +""" +already_inferred_quick_test(interp::NativeInterpreter, mi::MethodInstance) = !mi.inInference +already_inferred_quick_test(interp::AbstractInterpreter, mi::MethodInstance) = false """ - lock_mi_inference(ni::NativeInterpreter, mi::MethodInstance) + lock_mi_inference(::AbstractInterpreter, mi::MethodInstance) Hint that `mi` is in inference to help accelerate bootstrapping. -This helps us limit the amount of wasted work we might do when inference is working on initially inferring itself -by letting us detect when inference is already in progress and not running a second copy on it. -This creates a data-race, but the entry point into this code from C (`jl_type_infer`) already includes detection and restriction on recursion, -so it is hopefully mostly a benign problem (since it should really only happen during the first phase of bootstrapping that we encounter this flag). +This is particularly used by `NativeInterpreter` and helps us limit the amount of wasted +work we might do when inference is working on initially inferring itself by letting us +detect when inference is already in progress and not running a second copy on it. +This creates a data-race, but the entry point into this code from C (`jl_type_infer`) +already includes detection and restriction on recursion, so it is hopefully mostly a +benign problem, since it should really only happen during the first phase of bootstrapping +that we encounter this flag. """ lock_mi_inference(::NativeInterpreter, mi::MethodInstance) = (mi.inInference = true; nothing) lock_mi_inference(::AbstractInterpreter, ::MethodInstance) = return @@ -304,34 +414,42 @@ unlock_mi_inference(::NativeInterpreter, mi::MethodInstance) = (mi.inInference = unlock_mi_inference(::AbstractInterpreter, ::MethodInstance) = return """ -Emit an analysis remark during inference for the current line (`sv.pc`). -These annotations are ignored by the native interpreter, but can be used by external tooling -to annotate inference results. + add_remark!(::AbstractInterpreter, sv::InferenceState, remark) + +Emit an analysis remark during inference for the current line (i.e. `sv.currpc`). +These annotations are ignored by default, but can be used by external tooling to annotate +inference results. """ -add_remark!(::AbstractInterpreter, sv#=::InferenceState=#, s) = return +function add_remark! end may_optimize(::AbstractInterpreter) = true may_compress(::AbstractInterpreter) = true may_discard_trees(::AbstractInterpreter) = true verbose_stmt_info(::AbstractInterpreter) = false +""" + method_table(interp::AbstractInterpreter) -> MethodTableView + +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::NativeInterpreter) = interp.method_table """ By default `AbstractInterpreter` implements the following inference bail out logic: -- `bail_out_toplevel_call(::AbstractInterpreter, sig, ::InferenceState)`: bail out from inter-procedural inference when inferring top-level and non-concrete call site `callsig` -- `bail_out_call(::AbstractInterpreter, rt, ::InferenceState)`: bail out from inter-procedural inference when return type `rt` grows up to `Any` -- `bail_out_apply(::AbstractInterpreter, rt, ::InferenceState)`: bail out from `_apply_iterate` inference when return type `rt` grows up to `Any` +- `bail_out_toplevel_call(::AbstractInterpreter, sig, ::InferenceState)`: bail out from + inter-procedural inference when inferring top-level and non-concrete call site `callsig` +- `bail_out_call(::AbstractInterpreter, rt, ::InferenceState)`: bail out from + inter-procedural inference when return type `rt` grows up to `Any` +- `bail_out_apply(::AbstractInterpreter, rt, ::InferenceState)`: bail out from + `_apply_iterate` inference when return type `rt` grows up to `Any` It also bails out from local statement/frame inference when any lattice element gets down to `Bottom`, but `AbstractInterpreter` doesn't provide a specific interface for configuring it. """ -bail_out_toplevel_call(::AbstractInterpreter, @nospecialize(callsig), sv#=::InferenceState=#) = - return isa(sv.linfo.def, Module) && !isdispatchtuple(callsig) -bail_out_call(::AbstractInterpreter, @nospecialize(rt), sv#=::InferenceState=#) = - return rt === Any -bail_out_apply(::AbstractInterpreter, @nospecialize(rt), sv#=::InferenceState=#) = - return rt === Any +function bail_out_toplevel_call end, function bail_out_call end, function bail_out_apply end """ infer_compilation_signature(::AbstractInterpreter)::Bool @@ -343,3 +461,49 @@ to the call site signature. """ infer_compilation_signature(::AbstractInterpreter) = false infer_compilation_signature(::NativeInterpreter) = true + +typeinf_lattice(::AbstractInterpreter) = InferenceLattice(BaseInferenceLattice.instance) +ipo_lattice(::AbstractInterpreter) = InferenceLattice(IPOResultLattice.instance) +optimizer_lattice(::AbstractInterpreter) = OptimizerLattice(SimpleInferenceLattice.instance) + +typeinf_lattice(interp::NativeInterpreter) = interp.irinterp ? + OptimizerLattice(InferenceLattice(SimpleInferenceLattice.instance)) : + InferenceLattice(BaseInferenceLattice.instance) +ipo_lattice(interp::NativeInterpreter) = interp.irinterp ? + InferenceLattice(SimpleInferenceLattice.instance) : + InferenceLattice(IPOResultLattice.instance) +optimizer_lattice(interp::NativeInterpreter) = OptimizerLattice(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 + +nsplit(info::CallInfo) = nsplit_impl(info)::Union{Nothing,Int} +getsplit(info::CallInfo, idx::Int) = getsplit_impl(info, idx)::MethodLookupResult +getresult(info::CallInfo, idx::Int) = getresult_impl(info, idx) + +nsplit_impl(::CallInfo) = nothing +getsplit_impl(::CallInfo, ::Int) = error("unexpected call into `getsplit`") +getresult_impl(::CallInfo, ::Int) = nothing + +@specialize diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index c6f98939e5e14..cff10b02ceafc 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -4,8 +4,6 @@ # lattice utilities # ##################### -isType(@nospecialize t) = isa(t, DataType) && t.name === _TYPE_NAME - # true if Type{T} is inlineable as constant T # requires that T is a singleton, s.t. T == S implies T === S isconstType(@nospecialize t) = isType(t) && hasuniquerep(t.parameters[1]) @@ -25,15 +23,30 @@ function hasuniquerep(@nospecialize t) return false end -function has_nontrivial_const_info(@nospecialize t) - isa(t, PartialStruct) && return true - isa(t, PartialOpaque) && return true - isa(t, Const) || return false - val = t.val - return !isdefined(typeof(val), :instance) && !(isa(val, Type) && hasuniquerep(val)) +""" + isTypeDataType(@nospecialize t) -> Bool + +For a type `t` test whether ∀S s.t. `isa(S, rewrap_unionall(Type{t}, ...))`, +we have `isa(S, DataType)`. In particular, if a statement is typed as `Type{t}` +(potentially wrapped in some `UnionAll`), then we are guaranteed that this statement +will be a `DataType` at runtime (and not e.g. a `Union` or `UnionAll` typeequal to it). +""" +function isTypeDataType(@nospecialize t) + isa(t, DataType) || return false + isType(t) && return false + # Could be Union{} at runtime + t === Core.TypeofBottom && return false + if t.name === Tuple.name + # If we have a Union parameter, could have been redistributed at runtime, + # e.g. `Tuple{Union{Int, Float64}, Int}` is a DataType, but + # `Union{Tuple{Int, Int}, Tuple{Float64, Int}}` is typeequal to it and + # is not. + return all(isTypeDataType, t.parameters) + end + return true end -has_const_info(@nospecialize x) = (!isa(x, Type) && !isvarargtype(x)) || isType(x) +has_extended_info(@nospecialize x) = (!isa(x, Type) && !isvarargtype(x)) || isType(x) # Subtyping currently intentionally answers certain queries incorrectly for kind types. For # some of these queries, this check can be used to somewhat protect against making incorrect @@ -53,7 +66,7 @@ end # (therefore also a lower bound on the number of fields) function datatype_min_ninitialized(t::DataType) isabstracttype(t) && return 0 - if t.name === NamedTuple_typename + if t.name === _NAMEDTUPLE_NAME names, types = t.parameters[1], t.parameters[2] if names isa Tuple return length(names) @@ -80,7 +93,7 @@ function datatype_min_ninitialized(t::DataType) return length(t.name.names) - t.name.n_uninitialized end -has_concrete_subtype(d::DataType) = d.flags & 0x20 == 0x20 # n.b. often computed only after setting the type and layout fields +has_concrete_subtype(d::DataType) = d.flags & 0x0020 == 0x0020 # n.b. often computed only after setting the type and layout fields # determine whether x is a valid lattice element tag # For example, Type{v} is not valid if v is a value @@ -104,17 +117,26 @@ function valid_as_lattice(@nospecialize(x)) return false end -# test if non-Type, non-TypeVar `x` can be used to parameterize a type -function valid_tparam(@nospecialize(x)) - if isa(x, Tuple) - for t in x - isa(t, Symbol) || isbits(t) || return false +function valid_typeof_tparam(@nospecialize(t)) + if t === Symbol || t === Module || isbitstype(t) + return true + end + isconcretetype(t) || return false + if t <: NamedTuple + t = t.parameters[2]::DataType + end + if t <: Tuple + for p in t.parameters + valid_typeof_tparam(p) || return false end return true end - return isa(x, Symbol) || isbits(x) + return false end +# test if non-Type, non-TypeVar `x` can be used to parameterize a type +valid_tparam(@nospecialize(x)) = valid_typeof_tparam(typeof(x)) + function compatible_vatuple(a::DataType, b::DataType) vaa = a.parameters[end] vab = a.parameters[end] @@ -128,14 +150,14 @@ end # return an upper-bound on type `a` with type `b` removed # such that `return <: a` && `Union{return, b} == Union{a, b}` -function typesubtract(@nospecialize(a), @nospecialize(b), MAX_UNION_SPLITTING::Int) +function typesubtract(@nospecialize(a), @nospecialize(b), max_union_splitting::Int) if a <: b && isnotbrokensubtype(a, b) return Bottom end ua = unwrap_unionall(a) if isa(ua, Union) - uua = typesubtract(rewrap_unionall(ua.a, a), b, MAX_UNION_SPLITTING) - uub = typesubtract(rewrap_unionall(ua.b, a), b, MAX_UNION_SPLITTING) + uua = typesubtract(rewrap_unionall(ua.a, a), b, max_union_splitting) + uub = typesubtract(rewrap_unionall(ua.b, a), b, max_union_splitting) return Union{valid_as_lattice(uua) ? uua : Union{}, valid_as_lattice(uub) ? uub : Union{}} elseif a isa DataType @@ -143,7 +165,7 @@ function typesubtract(@nospecialize(a), @nospecialize(b), MAX_UNION_SPLITTING::I if ub isa DataType if a.name === ub.name === Tuple.name && length(a.parameters) == length(ub.parameters) - if 1 < unionsplitcost(a.parameters) <= MAX_UNION_SPLITTING + if 1 < unionsplitcost(JLTypeLattice(), a.parameters) <= max_union_splitting ta = switchtupleunion(a) return typesubtract(Union{ta...}, b, 0) elseif b isa DataType @@ -164,7 +186,8 @@ function typesubtract(@nospecialize(a), @nospecialize(b), MAX_UNION_SPLITTING::I ap = a.parameters[i] bp = b.parameters[i] (isvarargtype(ap) || isvarargtype(bp)) && return a - ta[i] = typesubtract(ap, bp, min(2, MAX_UNION_SPLITTING)) + ta[i] = typesubtract(ap, bp, min(2, max_union_splitting)) + ta[i] === Union{} && return Union{} return Tuple{ta...} end end @@ -190,11 +213,11 @@ end _typename(union::UnionAll) = _typename(union.body) _typename(a::DataType) = Const(a.name) -function tuple_tail_elem(@nospecialize(init), ct::Vector{Any}) +function tuple_tail_elem(𝕃::AbstractLattice, @nospecialize(init), ct::Vector{Any}) t = init for x in ct # FIXME: this is broken: it violates subtyping relations and creates invalid types with free typevars - t = tmerge(t, unwraptv(unwrapva(x))) + t = tmerge(𝕃, t, unwraptv(unwrapva(x))) end return Vararg{widenconst(t)} end @@ -205,10 +228,13 @@ end # or outside of the Tuple/Union nesting, though somewhat more expensive to be # outside than inside because the representation is larger (because and it # informs the callee whether any splitting is possible). -function unionsplitcost(argtypes::Union{SimpleVector,Vector{Any}}) +function unionsplitcost(𝕃::AbstractLattice, argtypes::Union{SimpleVector,Vector{Any}}) nu = 1 max = 2 for ti in argtypes + if has_extended_unionsplit(𝕃) && !isvarargtype(ti) + ti = widenconst(ti) + end if isa(ti, Union) nti = unionlen(ti) if nti > max @@ -226,12 +252,12 @@ end # and `Union{return...} == ty` function switchtupleunion(@nospecialize(ty)) tparams = (unwrap_unionall(ty)::DataType).parameters - return _switchtupleunion(Any[tparams...], length(tparams), [], ty) + return _switchtupleunion(JLTypeLattice(), Any[tparams...], length(tparams), [], ty) end -switchtupleunion(argtypes::Vector{Any}) = _switchtupleunion(argtypes, length(argtypes), [], nothing) +switchtupleunion(𝕃::AbstractLattice, argtypes::Vector{Any}) = _switchtupleunion(𝕃, argtypes, length(argtypes), [], nothing) -function _switchtupleunion(t::Vector{Any}, i::Int, tunion::Vector{Any}, @nospecialize(origt)) +function _switchtupleunion(𝕃::AbstractLattice, t::Vector{Any}, i::Int, tunion::Vector{Any}, @nospecialize(origt)) if i == 0 if origt === nothing push!(tunion, copy(t)) @@ -240,15 +266,22 @@ function _switchtupleunion(t::Vector{Any}, i::Int, tunion::Vector{Any}, @nospeci push!(tunion, tpl) end else - ti = t[i] + origti = ti = t[i] + # TODO remove this to implement callsite refinement of MustAlias if isa(ti, Union) - for ty in uniontypes(ti::Union) + for ty in uniontypes(ti) t[i] = ty - _switchtupleunion(t, i - 1, tunion, origt) + _switchtupleunion(𝕃, t, i - 1, tunion, origt) end - t[i] = ti + t[i] = origti + elseif has_extended_unionsplit(𝕃) && !isa(ti, Const) && !isvarargtype(ti) && isa(widenconst(ti), Union) + for ty in uniontypes(ti) + t[i] = ty + _switchtupleunion(𝕃, t, i - 1, tunion, origt) + end + t[i] = origti else - _switchtupleunion(t, i - 1, tunion, origt) + _switchtupleunion(𝕃, t, i - 1, tunion, origt) end end return tunion @@ -276,6 +309,15 @@ function _unioncomplexity(@nospecialize x) end end +function unionall_depth(@nospecialize ua) # aka subtype_env_size + depth = 0 + while ua isa UnionAll + depth += 1 + ua = ua.body + end + return depth +end + # convert a Union of Tuple types to a Tuple of Unions function unswitchtupleunion(u::Union) ts = uniontypes(u) @@ -291,12 +333,64 @@ function unswitchtupleunion(u::Union) return u end end - Tuple{Any[ Union{Any[t.parameters[i] for t in ts]...} for i in 1:n ]...} + Tuple{Any[ Union{Any[(t::DataType).parameters[i] for t in ts]...} for i in 1:n ]...} end -function unwraptv(@nospecialize t) +function unwraptv_ub(@nospecialize t) while isa(t, TypeVar) t = t.ub end return t end +function unwraptv_lb(@nospecialize t) + while isa(t, TypeVar) + t = t.lb + end + return t +end +const unwraptv = unwraptv_ub + +""" + is_identity_free_argtype(argtype) -> Bool + +Return `true` if the `argtype` object is identity free in the sense that this type or any +reachable through its fields has non-content-based identity (see `Base.isidentityfree`). +This query is specifically designed for `adjust_effects`, enabling it to refine the +`:consistent` effect property tainted by mutable allocation(s) within the analyzed call +graph when the return value type is `is_identity_free_argtype`, ensuring that the allocated +mutable objects are never returned. +""" +is_identity_free_argtype(@nospecialize ty) = is_identity_free_type(widenconst(ignorelimited(ty))) +is_identity_free_type(@nospecialize ty) = isidentityfree(ty) + +""" + is_immutable_argtype(argtype) -> Bool + +Return `true` if the `argtype` object is known to be immutable. +This query is specifically designed for `getfield_effects` and `isdefined_effects`, allowing +them to prove `:consistent`-cy of `getfield` / `isdefined` calls when applied to immutable +objects. Otherwise, we need to additionally prove that the non-immutable object is not a +global object to prove the `:consistent`-cy. +""" +is_immutable_argtype(@nospecialize argtype) = is_immutable_type(widenconst(ignorelimited(argtype))) +is_immutable_type(@nospecialize ty) = _is_immutable_type(unwrap_unionall(ty)) +function _is_immutable_type(@nospecialize ty) + if isa(ty, Union) + return _is_immutable_type(ty.a) && _is_immutable_type(ty.b) + end + return !isabstracttype(ty) && !ismutabletype(ty) +end + +""" + is_mutation_free_argtype(argtype) -> Bool + +Return `true` if `argtype` object is mutation free in the sense that no mutable memory +is reachable from this type (either in the type itself) or through any fields +(see `Base.ismutationfree`). +This query is specifically written for analyzing the `:inaccessiblememonly` effect property +and is supposed to improve the analysis accuracy by not tainting the `:inaccessiblememonly` +property when there is access to mutation-free global object. +""" +is_mutation_free_argtype(@nospecialize(argtype)) = + is_mutation_free_type(widenconst(ignorelimited(argtype))) +is_mutation_free_type(@nospecialize ty) = ismutationfree(ty) diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 9b1106e964919..836c370b98bd4 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -52,7 +52,7 @@ function istopfunction(@nospecialize(f), name::Symbol) tn = typeof(f).name if tn.mt.name === name top = _topmod(tn.module) - return isdefined(top, name) && isconst(top, name) && f === getfield(top, name) + return isdefined(top, name) && isconst(top, name) && f === getglobal(top, name) end return false end @@ -64,6 +64,7 @@ end # Meta expression head, these generally can't be deleted even when they are # in a dead branch but can be ignored when analyzing uses/liveness. 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 @@ -76,8 +77,14 @@ function quoted(@nospecialize(x)) return is_self_quoting(x) ? x : QuoteNode(x) end +############ +# inlining # +############ + +const MAX_INLINE_CONST_SIZE = 256 + function count_const_size(@nospecialize(x), count_self::Bool = true) - (x isa Type || x isa Symbol) && return 0 + (x isa Type || x isa Core.TypeName || x isa Symbol) && return 0 ismutable(x) && return MAX_INLINE_CONST_SIZE + 1 isbits(x) && return Core.sizeof(x) dt = typeof(x) @@ -107,27 +114,30 @@ end invoke_api(li::CodeInstance) = ccall(:jl_invoke_api, Cint, (Any,), li) use_const_api(li::CodeInstance) = invoke_api(li) == 2 -function get_staged(mi::MethodInstance) +function get_staged(mi::MethodInstance, world::UInt) may_invoke_generator(mi) || return nothing try # user code might throw errors – ignore them - ci = ccall(:jl_code_for_staged, Any, (Any,), mi)::CodeInfo + ci = ccall(:jl_code_for_staged, Any, (Any, UInt), mi, world)::CodeInfo return ci catch return nothing end end -function retrieve_code_info(linfo::MethodInstance) +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) + c = get_staged(linfo, world) end if c === nothing && isdefined(m, :source) src = m.source - if isa(src, Array{UInt8,1}) + 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) else c = copy(src::CodeInfo) @@ -142,14 +152,14 @@ end function get_compileable_sig(method::Method, @nospecialize(atype), sparams::SimpleVector) isa(atype, DataType) || return nothing - mt = ccall(:jl_method_table_for, Any, (Any,), atype) + mt = ccall(:jl_method_get_table, Any, (Any,), method) mt === nothing && return nothing return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any), mt, atype, sparams, method) end -isa_compileable_sig(@nospecialize(atype), method::Method) = - !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any), atype, method)) +isa_compileable_sig(@nospecialize(atype), sparams::SimpleVector, method::Method) = + !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any, Any), atype, sparams, method)) # eliminate UnionAll vars that might be degenerate due to having identical bounds, # or a concrete upper bound and appearing covariantly. @@ -172,6 +182,8 @@ function subst_trivial_bounds(@nospecialize(atype)) return UnionAll(v, subst_trivial_bounds(atype.body)) end +has_typevar(@nospecialize(t), v::TypeVar) = ccall(:jl_has_typevar, Cint, (Any, Any), t, v) != 0 + # If removing trivial vars from atype results in an equivalent type, use that # instead. Otherwise we can get a case like issue #38888, where a signature like # f(x::S) where S<:Int @@ -183,19 +195,14 @@ function normalize_typevars(method::Method, @nospecialize(atype), sparams::Simpl sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), at2, method.sig)::SimpleVector sparams = sp_[2]::SimpleVector end - return atype, sparams + return Pair{Any,SimpleVector}(atype, sparams) end # get a handle to the unique specialization object representing a particular instantiation of a call -function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false, compilesig::Bool=false) +function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false) if isa(atype, UnionAll) atype, sparams = normalize_typevars(method, atype, sparams) end - if compilesig - new_atype = get_compileable_sig(method, atype, sparams) - new_atype === nothing && return nothing - atype = new_atype - end if preexisting # check cached specializations # for an existing result stored there @@ -208,21 +215,97 @@ function specialize_method(match::MethodMatch; kwargs...) return specialize_method(match.method, match.spec_types, match.sparams; kwargs...) end -# This function is used for computing alternate limit heuristics -function method_for_inference_heuristics(method::Method, @nospecialize(sig), sparams::SimpleVector) - if isdefined(method, :generator) && method.generator.expand_early && may_invoke_generator(method, sig, sparams) - method_instance = specialize_method(method, sig, sparams) - if isa(method_instance, MethodInstance) - cinfo = get_staged(method_instance) - if isa(cinfo, CodeInfo) - method2 = cinfo.method_for_inference_limit_heuristics - if method2 isa Method - return method2 - end - end - end - end - return nothing +""" + is_declared_inline(method::Method) -> Bool + +Check if `method` is declared as `@inline`. +""" +is_declared_inline(method::Method) = _is_declared_inline(method, true) + +""" + is_declared_noinline(method::Method) -> Bool + +Check if `method` is declared as `@noinline`. +""" +is_declared_noinline(method::Method) = _is_declared_inline(method, false) + +function _is_declared_inline(method::Method, inline::Bool) + isdefined(method, :source) || return false + src = method.source + isa(src, MaybeCompressed) || return false + return (inline ? is_declared_inline : is_declared_noinline)(src) +end + +""" + is_aggressive_constprop(method::Union{Method,CodeInfo}) -> Bool + +Check if `method` is declared as `Base.@constprop :aggressive`. +""" +is_aggressive_constprop(method::Union{Method,CodeInfo}) = method.constprop == 0x01 + +""" + is_no_constprop(method::Union{Method,CodeInfo}) -> Bool + +Check if `method` is declared as `Base.@constprop :none`. +""" +is_no_constprop(method::Union{Method,CodeInfo}) = method.constprop == 0x02 + +############# +# backedges # +############# + +""" + BackedgeIterator(backedges::Vector{Any}) + +Return an iterator over a list of backedges. Iteration returns `(sig, caller)` elements, +which will be one of the following: + +- `BackedgePair(nothing, caller::MethodInstance)`: a call made by ordinary inferable dispatch +- `BackedgePair(invokesig::Type, caller::MethodInstance)`: a call made by `invoke(f, invokesig, args...)` +- `BackedgePair(specsig::Type, mt::MethodTable)`: an abstract call + +# Examples + +```julia +julia> callme(x) = x+1 +callme (generic function with 1 method) + +julia> callyou(x) = callme(x) +callyou (generic function with 1 method) + +julia> callyou(2.0) +3.0 + +julia> mi = which(callme, (Any,)).specializations +MethodInstance for callme(::Float64) + +julia> @eval Core.Compiler for (; sig, caller) in BackedgeIterator(Main.mi.backedges) + println(sig) + println(caller) + end +nothing +callyou(Float64) from callyou(Any) +``` +""" +struct BackedgeIterator + backedges::Vector{Any} +end + +const empty_backedge_iter = BackedgeIterator(Any[]) + +struct BackedgePair + sig # ::Union{Nothing,Type} + caller::Union{MethodInstance,MethodTable} + BackedgePair(@nospecialize(sig), caller::Union{MethodInstance,MethodTable}) = new(sig, caller) +end + +function iterate(iter::BackedgeIterator, i::Int=1) + backedges = iter.backedges + i > length(backedges) && return nothing + item = backedges[i] + isa(item, MethodInstance) && return BackedgePair(nothing, item), i+1 # regular dispatch + isa(item, MethodTable) && return BackedgePair(backedges[i+1], item), i+2 # abstract dispatch + return BackedgePair(item, backedges[i+1]::MethodInstance), i+2 # `invoke` calls end ######### @@ -230,20 +313,63 @@ end ######### function singleton_type(@nospecialize(ft)) + ft = widenslotwrapper(ft) if isa(ft, Const) return ft.val elseif isconstType(ft) return ft.parameters[1] - elseif ft isa DataType && isdefined(ft, :instance) + elseif issingletontype(ft) return ft.instance end return nothing end +function maybe_singleton_const(@nospecialize(t)) + if isa(t, DataType) + if issingletontype(t) + return Const(t.instance) + elseif isconstType(t) + return Const(t.parameters[1]) + end + end + return t +end + ################### # SSAValues/Slots # ################### +function ssamap(f, @nospecialize(stmt)) + urs = userefs(stmt) + for op in urs + val = op[] + if isa(val, SSAValue) + op[] = f(val) + end + end + return urs[] +end + +function foreachssa(@specialize(f), @nospecialize(stmt)) + urs = userefs(stmt) + for op in urs + val = op[] + if isa(val, SSAValue) + f(val) + end + end +end + +function foreach_anyssa(@specialize(f), @nospecialize(stmt)) + urs = userefs(stmt) + for op in urs + val = op[] + if isa(val, AnySSAValue) + f(val) + end + end +end + function find_ssavalue_uses(body::Vector{Any}, nvals::Int) uses = BitSet[ BitSet() for i = 1:nvals ] for line in 1:length(body) @@ -291,7 +417,7 @@ function is_throw_call(e::Expr) if e.head === :call f = e.args[1] if isa(f, GlobalRef) - ff = abstract_eval_global(f.mod, f.name) + ff = abstract_eval_globalref(f) if isa(ff, Const) && ff.val === Core.throw return true end @@ -346,8 +472,11 @@ function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) end # using a function to ensure we can infer this -@inline slot_id(s) = isa(s, SlotNumber) ? (s::SlotNumber).id : - isa(s, Argument) ? (s::Argument).n : (s::TypedSlot).id +@inline function slot_id(s) + isa(s, SlotNumber) && return s.id + isa(s, Argument) && return s.n + return (s::TypedSlot).id +end ########### # options # @@ -359,12 +488,12 @@ inlining_enabled() = (JLOptions().can_inline == 1) function coverage_enabled(m::Module) ccall(:jl_generating_output, Cint, ()) == 0 || return false # don't alter caches cov = JLOptions().code_coverage - if cov == 1 + if cov == 1 # user m = moduleroot(m) m === Core && return false isdefined(Main, :Base) && m === Main.Base && return false return true - elseif cov == 2 + elseif cov == 2 # all return true end return false diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 77ee422b6ffcd..68eb2ab15c59d 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -53,6 +53,7 @@ const NON_TOP_LEVEL_METHOD = "encountered `Expr` head `:method` in non-top-level const NON_TOP_LEVEL_GLOBAL = "encountered `Expr` head `:global` in non-top-level code (i.e. `nargs` > 0)" const SIGNATURE_NARGS_MISMATCH = "method signature does not match number of method arguments" const SLOTNAMES_NARGS_MISMATCH = "CodeInfo for method contains fewer slotnames than the number of method arguments" +const INVALID_SIGNATURE_OPAQUE_CLOSURE = "invalid signature of method for opaque closure - `sig` field must always be set to `Tuple`" struct InvalidCodeError <: Exception kind::String @@ -102,11 +103,11 @@ function _validate_val!(@nospecialize(x), errors, ssavals::BitSet) end """ - validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo) + validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo) Validate `c`, logging any violation by pushing an `InvalidCodeError` into `errors`. """ -function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_level::Bool = false) +function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_level::Bool = false) ssavals = BitSet() lhs_slotnums = BitSet() @@ -198,16 +199,15 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ end """ - validate_code!(errors::Vector{>:InvalidCodeError}, mi::MethodInstance, - c::Union{Nothing,CodeInfo} = Core.Compiler.retrieve_code_info(mi)) + validate_code!(errors::Vector{InvalidCodeError}, mi::MethodInstance, + c::Union{Nothing,CodeInfo}) Validate `mi`, logging any violation by pushing an `InvalidCodeError` into `errors`. If `isa(c, CodeInfo)`, also call `validate_code!(errors, c)`. It is assumed that `c` is -the `CodeInfo` instance associated with `mi`. +a `CodeInfo` instance associated with `mi`. """ -function validate_code!(errors::Vector{>:InvalidCodeError}, mi::Core.MethodInstance, - c::Union{Nothing,CodeInfo} = Core.Compiler.retrieve_code_info(mi)) +function validate_code!(errors::Vector{InvalidCodeError}, mi::Core.MethodInstance, c::Union{Nothing,CodeInfo}) is_top_level = mi.def isa Module if is_top_level mnargs = 0 @@ -215,7 +215,9 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, mi::Core.MethodInsta m = mi.def::Method mnargs = m.nargs n_sig_params = length((unwrap_unionall(m.sig)::DataType).parameters) - if (m.isva ? (n_sig_params < (mnargs - 1)) : (n_sig_params != mnargs)) + if m.is_for_opaque_closure + m.sig === Tuple || push!(errors, InvalidCodeError(INVALID_SIGNATURE_OPAQUE_CLOSURE, (m.sig, m.isva))) + elseif (m.isva ? (n_sig_params < (mnargs - 1)) : (n_sig_params != mnargs)) push!(errors, InvalidCodeError(SIGNATURE_NARGS_MISMATCH, (m.isva, n_sig_params, mnargs))) end end @@ -228,13 +230,13 @@ end validate_code(args...) = validate_code!(Vector{InvalidCodeError}(), args...) -is_valid_lvalue(@nospecialize(x)) = isa(x, Slot) || isa(x, GlobalRef) +is_valid_lvalue(@nospecialize(x)) = isa(x, UnoptSlot) || isa(x, GlobalRef) function is_valid_argument(@nospecialize(x)) - if isa(x, Slot) || isa(x, Argument) || isa(x, SSAValue) || isa(x, GlobalRef) || isa(x, QuoteNode) || - (isa(x,Expr) && (x.head in (:static_parameter, :boundscheck))) || - isa(x, Number) || isa(x, AbstractString) || isa(x, AbstractChar) || isa(x, Tuple) || - isa(x, Type) || isa(x, Core.Box) || isa(x, Module) || x === nothing + if isa(x, UnoptSlot) || isa(x, Argument) || isa(x, SSAValue) || + isa(x, GlobalRef) || isa(x, QuoteNode) || isexpr(x, (:static_parameter, :boundscheck)) || + isa(x, Number) || isa(x, AbstractString) || isa(x, AbstractChar) || isa(x, Tuple) || + isa(x, Type) || isa(x, Core.Box) || isa(x, Module) || x === nothing return true end # TODO: consider being stricter about what needs to be wrapped with QuoteNode diff --git a/base/complex.jl b/base/complex.jl index f68e519386d93..a0473c90d5c17 100644 --- a/base/complex.jl +++ b/base/complex.jl @@ -120,6 +120,7 @@ Float64 real(T::Type) = typeof(real(zero(T))) real(::Type{T}) where {T<:Real} = T real(C::Type{<:Complex}) = fieldtype(C, 1) +real(::Type{Union{}}, slurp...) = Union{}(im) """ isreal(x) -> Bool @@ -133,6 +134,9 @@ is true. julia> isreal(5.) true +julia> isreal(1 - 3im) +false + julia> isreal(Inf + 0im) true @@ -192,7 +196,7 @@ flipsign(x::Complex, y::Real) = ifelse(signbit(y), -x, x) function show(io::IO, z::Complex) r, i = reim(z) - compact = get(io, :compact, false) + compact = get(io, :compact, false)::Bool show(io, r) if signbit(i) && !isnan(i) print(io, compact ? "-" : " - ") @@ -242,6 +246,8 @@ bswap(z::Complex) = Complex(bswap(real(z)), bswap(imag(z))) ==(x::Real, z::Complex) = isreal(z) && real(z) == x isequal(z::Complex, w::Complex) = isequal(real(z),real(w)) & isequal(imag(z),imag(w)) +isequal(z::Complex, w::Real) = isequal(real(z),w) & isequal(imag(z),zero(w)) +isequal(z::Real, w::Complex) = isequal(z,real(w)) & isequal(zero(z),imag(w)) in(x::Complex, r::AbstractRange{<:Real}) = isreal(x) && real(x) in r @@ -469,9 +475,13 @@ function inv(z::Complex{T}) where T<:Union{Float16,Float32} end function inv(w::ComplexF64) c, d = reim(w) - (isinf(c) | isinf(d)) && return complex(copysign(0.0, c), flipsign(-0.0, d)) absc, absd = abs(c), abs(d) - cd = ifelse(absc>absd, absc, absd) # cheap `max`: don't need sign- and nan-checks here + cd, dc = ifelse(absc>absd, (absc, absd), (absd, absc)) + # no overflow from abs2 + if sqrt(floatmin(Float64)/2) <= cd <= sqrt(floatmax(Float64)/2) + return conj(w) / muladd(cd, cd, dc*dc) + end + (isinf(c) | isinf(d)) && return complex(copysign(0.0, c), flipsign(-0.0, d)) ϵ = eps(Float64) bs = 2/(ϵ*ϵ) @@ -490,12 +500,13 @@ function inv(w::ComplexF64) else q, p = robust_cinv(-d, -c) end - return ComplexF64(p*s, q*s) # undo scaling + return ComplexF64(p*s, q*s) end function robust_cinv(c::Float64, d::Float64) r = d/c - p = inv(muladd(d, r, c)) - q = -r*p + z = muladd(d, r, c) + p = 1.0/z + q = -r/z return p, q end @@ -591,7 +602,7 @@ julia> cispi(10000) 1.0 + 0.0im julia> cispi(0.25 + 1im) -0.030556854645952924 + 0.030556854645952924im +0.030556854645954562 + 0.03055685464595456im ``` !!! compat "Julia 1.6" @@ -601,8 +612,9 @@ function cispi end cispi(theta::Real) = Complex(reverse(sincospi(theta))...) function cispi(z::Complex) - sipi, copi = sincospi(z) - return complex(real(copi) - imag(sipi), imag(copi) + real(sipi)) + v = exp(-(pi*imag(z))) + s, c = sincospi(real(z)) + Complex(v * c, v * s) end """ @@ -1063,18 +1075,32 @@ end #Requires two different RoundingModes for the real and imaginary components """ round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]) - round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]; digits=, base=10) - round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]; sigdigits=, base=10) + round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]; digits=0, base=10) + round(z::Complex[, RoundingModeReal, [RoundingModeImaginary]]; sigdigits, base=10) Return the nearest integral value of the same type as the complex-valued `z` to `z`, breaking ties using the specified [`RoundingMode`](@ref)s. The first [`RoundingMode`](@ref) is used for rounding the real components while the second is used for rounding the imaginary components. + +`RoundingModeReal` and `RoundingModeImaginary` default to [`RoundNearest`](@ref), +which rounds to the nearest integer, with ties (fractional values of 0.5) +being rounded to the nearest even integer. + # Example ```jldoctest julia> round(3.14 + 4.5im) 3.0 + 4.0im + +julia> round(3.14 + 4.5im, RoundUp, RoundNearestTiesUp) +4.0 + 5.0im + +julia> round(3.14159 + 4.512im; digits = 1) +3.1 + 4.5im + +julia> round(3.14159 + 4.512im; sigdigits = 3) +3.14 + 4.51im ``` """ function round(z::Complex, rr::RoundingMode=RoundNearest, ri::RoundingMode=rr; kwargs...) diff --git a/base/condition.jl b/base/condition.jl index c536eceec17a0..20481c98ee805 100644 --- a/base/condition.jl +++ b/base/condition.jl @@ -61,12 +61,12 @@ Abstract implementation of a condition object for synchronizing tasks objects with a given lock. """ struct GenericCondition{L<:AbstractLock} - waitq::InvasiveLinkedList{Task} + waitq::IntrusiveLinkedList{Task} lock::L - GenericCondition{L}() where {L<:AbstractLock} = new{L}(InvasiveLinkedList{Task}(), L()) - GenericCondition{L}(l::L) where {L<:AbstractLock} = new{L}(InvasiveLinkedList{Task}(), l) - GenericCondition(l::AbstractLock) = new{typeof(l)}(InvasiveLinkedList{Task}(), l) + GenericCondition{L}() where {L<:AbstractLock} = new{L}(IntrusiveLinkedList{Task}(), L()) + GenericCondition{L}(l::L) where {L<:AbstractLock} = new{L}(IntrusiveLinkedList{Task}(), l) + GenericCondition(l::AbstractLock) = new{typeof(l)}(IntrusiveLinkedList{Task}(), l) end assert_havelock(c::GenericCondition) = assert_havelock(c.lock) @@ -78,12 +78,16 @@ islocked(c::GenericCondition) = islocked(c.lock) lock(f, c::GenericCondition) = lock(f, c.lock) # have waiter wait for c -function _wait2(c::GenericCondition, waiter::Task) +function _wait2(c::GenericCondition, waiter::Task, first::Bool=false) ct = current_task() assert_havelock(c) - push!(c.waitq, waiter) + if first + pushfirst!(c.waitq, waiter) + else + push!(c.waitq, waiter) + end # since _wait2 is similar to schedule, we should observe the sticky bit now - if waiter.sticky && Threads.threadid(waiter) == 0 + if waiter.sticky && Threads.threadid(waiter) == 0 && !GC.in_finalizer() # Issue #41324 # t.sticky && tid == 0 is a task that needs to be co-scheduled with # the parent task. If the parent (current_task) is not sticky we must @@ -103,7 +107,9 @@ Block the current task until some event occurs, depending on the type of the arg * [`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`. + 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. * `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 @@ -116,14 +122,14 @@ 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(c::GenericCondition) +function wait(c::GenericCondition; first::Bool=false) ct = current_task() - _wait2(c, ct) + _wait2(c, ct, first) token = unlockall(c.lock) try return wait() catch - ct.queue === nothing || list_deletefirst!(ct.queue, ct) + ct.queue === nothing || list_deletefirst!(ct.queue::IntrusiveLinkedList{Task}, ct) rethrow() finally relockall(c.lock, token) @@ -154,8 +160,6 @@ end notify_error(c::GenericCondition, err) = notify(c, err, true, true) -n_waiters(c::GenericCondition) = length(c.waitq) - """ isempty(condition) diff --git a/base/cpuid.jl b/base/cpuid.jl index b1fb82cf86dae..48930d8064ba9 100644 --- a/base/cpuid.jl +++ b/base/cpuid.jl @@ -56,9 +56,10 @@ const ISAs_by_family = Dict( "aarch64" => [ # Implicit in all sets, because always required: fp, asimd "armv8.0-a" => ISA(Set{UInt32}()), - "armv8.1-a" => ISA(Set((JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm))), - "armv8.2-a+crypto" => ISA(Set((JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2))), - "armv8.4-a+crypto+sve" => ISA(Set((JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_fp16fml, JL_AArch64_aes, JL_AArch64_sha2, JL_AArch64_dotprod, JL_AArch64_sve))), + "armv8.1-a" => ISA(Set((JL_AArch64_v8_1a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm))), + "armv8.2-a+crypto" => ISA(Set((JL_AArch64_v8_2a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2))), + "a64fx" => ISA(Set((JL_AArch64_v8_2a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_sha2, JL_AArch64_ccpp, JL_AArch64_complxnum, JL_AArch64_fullfp16, JL_AArch64_sve))), + "apple_m1" => ISA(Set((JL_AArch64_v8_5a, JL_AArch64_lse, JL_AArch64_crc, JL_AArch64_rdm, JL_AArch64_aes, JL_AArch64_sha2, JL_AArch64_sha3, JL_AArch64_ccpp, JL_AArch64_complxnum, JL_AArch64_fp16fml, JL_AArch64_fullfp16, JL_AArch64_dotprod, JL_AArch64_rcpc, JL_AArch64_altnzcv))), ], "powerpc64le" => [ # We have no way to test powerpc64le features yet, so we're only going to declare the lowest ISA: @@ -88,14 +89,27 @@ function normalize_arch(arch::String) return arch end +let + # Collect all relevant features for the current architecture, if any. + FEATURES = UInt32[] + arch = normalize_arch(String(Sys.ARCH)) + if arch in keys(ISAs_by_family) + for isa in ISAs_by_family[arch] + unique!(append!(FEATURES, last(isa).features)) + end + end + + # Use `@eval` to inline the list of features. + @eval function cpu_isa() + return ISA(Set{UInt32}(feat for feat in $(FEATURES) if test_cpu_feature(feat))) + end +end + """ cpu_isa() Return the [`ISA`](@ref) (instruction set architecture) of the current CPU. """ -function cpu_isa() - all_features = last(last(get(ISAs_by_family, normalize_arch(String(Sys.ARCH)), "" => [ISA(Set{UInt32}())]))).features - return ISA(Set{UInt32}(feat for feat in all_features if test_cpu_feature(feat))) -end +cpu_isa end # module CPUID diff --git a/base/deepcopy.jl b/base/deepcopy.jl index 317d999004c42..eae8974326d06 100644 --- a/base/deepcopy.jl +++ b/base/deepcopy.jl @@ -21,7 +21,7 @@ so far within the recursion. Within the definition, `deepcopy_internal` should b in place of `deepcopy`, and the `dict` variable should be updated as appropriate before returning. """ -function deepcopy(x) +function deepcopy(@nospecialize x) isbitstype(typeof(x)) && return x return deepcopy_internal(x, IdDict())::typeof(x) end @@ -140,7 +140,7 @@ function deepcopy_internal(x::GenericCondition, stackdict::IdDict) if haskey(stackdict, x) return stackdict[x] end - y = typeof(x)(deepcopy_internal(x.lock)) + y = typeof(x)(deepcopy_internal(x.lock, stackdict)) stackdict[x] = y return y end diff --git a/base/deprecated.jl b/base/deprecated.jl index 6709023147283..1b661716cc2d9 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -17,9 +17,10 @@ """ @deprecate old new [export_old=true] -Deprecate method `old` and specify the replacement call `new`. Prevent `@deprecate` from -exporting `old` by setting `export_old` to `false`. `@deprecate` defines a new method with the same -signature as `old`. +Deprecate method `old` and specify the replacement call `new`, defining a new method `old` +with the specified signature in the process. + +To prevent `old` from being exported, set `export_old` to `false`. !!! compat "Julia 1.5" As of Julia 1.5, functions defined by `@deprecate` do not print warning when `julia` @@ -34,45 +35,86 @@ old (generic function with 1 method) julia> @deprecate old(x) new(x) false old (generic function with 1 method) ``` + +Calls to `@deprecate` without explicit type-annotations will define +deprecated methods accepting any number of positional and keyword +arguments of type `Any`. + +!!! compat "Julia 1.9" + Keyword arguments are forwarded when there is no explicit type + annotation as of Julia 1.9. For older versions, you can manually + forward positional and keyword arguments by doing `@deprecate + old(args...; kwargs...) new(args...; kwargs...)`. + +To restrict deprecation to a specific signature, annotate the +arguments of `old`. For example, +```jldoctest; filter = r"@ .*"a +julia> new(x::Int) = x; + +julia> new(x::Float64) = 2x; + +julia> @deprecate old(x::Int) new(x); + +julia> methods(old) +# 1 method for generic function "old" from Main: + [1] old(x::Int64) + @ deprecated.jl:94 +``` +will define and deprecate a method `old(x::Int)` that mirrors `new(x::Int)` but will not +define nor deprecate the method `old(x::Float64)`. """ macro deprecate(old, new, export_old=true) + function cannot_export_nonsymbol() + error( + "if the third `export_old` argument is not specified or `true`, the first", + " argument must be of form", + " (1) `f(...)` where `f` is a symbol,", + " (2) `T{...}(...)` where `T` is a symbol, or", + " (3) a symbol.", + ) + end meta = Expr(:meta, :noinline) - if isa(old, Symbol) - oldname = Expr(:quote, old) - newname = Expr(:quote, new) - Expr(:toplevel, - export_old ? Expr(:export, esc(old)) : nothing, - :(function $(esc(old))(args...) - $meta - depwarn($"`$old` is deprecated, use `$new` instead.", Core.Typeof($(esc(old))).name.mt.name) - $(esc(new))(args...) - end)) - elseif isa(old, Expr) && (old.head === :call || old.head === :where) + if isa(old, Expr) && (old.head === :call || old.head === :where) remove_linenums!(new) oldcall = sprint(show_unquoted, old) newcall = sprint(show_unquoted, new) # if old.head is a :where, step down one level to the :call to avoid code duplication below callexpr = old.head === :call ? old : old.args[1] if callexpr.head === :call - if isa(callexpr.args[1], Symbol) - oldsym = callexpr.args[1]::Symbol - elseif isa(callexpr.args[1], Expr) && callexpr.args[1].head === :curly - oldsym = callexpr.args[1].args[1]::Symbol + fnexpr = callexpr.args[1] + if fnexpr isa Expr && fnexpr.head === :curly + fnexpr = fnexpr.args[1] + end + if export_old + if fnexpr isa Symbol + maybe_export = Expr(:export, esc(fnexpr)) + else + cannot_export_nonsymbol() + end else - error("invalid usage of @deprecate") + maybe_export = nothing end else error("invalid usage of @deprecate") end Expr(:toplevel, - export_old ? Expr(:export, esc(oldsym)) : nothing, + maybe_export, :($(esc(old)) = begin $meta - depwarn($"`$oldcall` is deprecated, use `$newcall` instead.", Core.Typeof($(esc(oldsym))).name.mt.name) + depwarn($"`$oldcall` is deprecated, use `$newcall` instead.", Core.Typeof($(esc(fnexpr))).name.mt.name) $(esc(new)) end)) else - error("invalid usage of @deprecate") + if export_old && !(old isa Symbol) + cannot_export_nonsymbol() + end + Expr(:toplevel, + export_old ? Expr(:export, esc(old)) : nothing, + :(function $(esc(old))(args...; kwargs...) + $meta + depwarn($"`$old` is deprecated, use `$new` instead.", Core.Typeof($(esc(old))).name.mt.name) + $(esc(new))(args...; kwargs...) + end)) end end @@ -88,8 +130,13 @@ function depwarn(msg, funcsym; force::Bool=false) _module=begin bt = backtrace() frame, caller = firstcaller(bt, funcsym) - # TODO: Is it reasonable to attribute callers without linfo to Core? - caller.linfo isa Core.MethodInstance ? caller.linfo.def.module : Core + linfo = caller.linfo + if linfo isa Core.MethodInstance + def = linfo.def + def isa Module ? def : def.module + else + Core # TODO: Is it reasonable to attribute callers without linfo to Core? + end end, _file=String(caller.file), _line=caller.line, @@ -120,11 +167,8 @@ function firstcaller(bt::Vector, funcsyms) if !found li = lkup.linfo if li isa Core.MethodInstance - ft = ccall(:jl_first_argument_datatype, Any, (Any,), (li.def::Method).sig) - if isa(ft, DataType) && ft.name === Type.body.name - ft = unwrap_unionall(ft.parameters[1]) - found = (isa(ft, DataType) && ft.name.name in funcsyms) - end + def = li.def + found = def isa Method && def.name in funcsyms end end end @@ -228,11 +272,11 @@ getindex(match::Core.MethodMatch, field::Int) = 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 - @_pure_meta + @_foldable_meta Tuple{S, T.parameters...} end function parameter_upper_bound(t::UnionAll, idx) - @_pure_meta + @_foldable_meta return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t) end @@ -243,7 +287,7 @@ cat_shape(dims, shape::Tuple{}) = () # make sure `cat_shape(dims, ())` do not re @deprecate unsafe_indices(A) axes(A) false @deprecate unsafe_length(r) length(r) false -# these were internal type aliases, but some pacakges seem to be relying on them +# these were internal type aliases, but some packages seem to be relying on them const Any16{N} = Tuple{Any,Any,Any,Any,Any,Any,Any,Any, Any,Any,Any,Any,Any,Any,Any,Any,Vararg{Any,N}} const All16{T,N} = Tuple{T,T,T,T,T,T,T,T, @@ -266,8 +310,77 @@ end # BEGIN 1.8 deprecations -@deprecate var"@_inline_meta" var"@inline" false -@deprecate var"@_noinline_meta" var"@noinline" false +const var"@_inline_meta" = var"@inline" +const var"@_noinline_meta" = var"@noinline" @deprecate getindex(t::Tuple, i::Real) t[convert(Int, i)] # END 1.8 deprecations + +# BEGIN 1.9 deprecations + +# We'd generally like to avoid direct external access to internal fields +# Core.Compiler.is_inlineable and Core.Compiler.set_inlineable! move towards this direction, +# but we need to keep these around for compat +function getproperty(ci::CodeInfo, s::Symbol) + s === :inlineable && return Core.Compiler.is_inlineable(ci) + return getfield(ci, s) +end + +function setproperty!(ci::CodeInfo, s::Symbol, v) + s === :inlineable && return Core.Compiler.set_inlineable!(ci, v) + return setfield!(ci, s, convert(fieldtype(CodeInfo, s), v)) +end + +@eval Threads nthreads() = threadpoolsize() + +@eval Threads begin + """ + resize_nthreads!(A, copyvalue=A[1]) + + Resize the array `A` to length [`nthreads()`](@ref). Any new + elements that are allocated are initialized to `deepcopy(copyvalue)`, + where `copyvalue` defaults to `A[1]`. + + This is typically used to allocate per-thread variables, and + should be called in `__init__` if `A` is a global constant. + + !!! warning + + This function is deprecated, since as of Julia v1.9 the number of + threads can change at run time. Instead, per-thread state should be + created as needed based on the thread id of the caller. + """ + function resize_nthreads!(A::AbstractVector, copyvalue=A[1]) + nthr = nthreads() + nold = length(A) + resize!(A, nthr) + for i = nold+1:nthr + A[i] = deepcopy(copyvalue) + end + return A + end +end + +# END 1.9 deprecations + +# BEGIN 1.10 deprecations + +""" + @pure ex + +`@pure` gives the compiler a hint for the definition of a pure function, +helping for type inference. + +!!! warning + This macro is intended for internal compiler use and may be subject to changes. + +!!! warning + In Julia 1.8 and higher, it is favorable to use [`@assume_effects`](@ref) instead of `@pure`. + This is because `@assume_effects` allows a finer grained control over Julia's purity + modeling and the effect system enables a wider range of optimizations. +""" +macro pure(ex) + return esc(:(Base.@assume_effects :foldable $ex)) +end + +# END 1.10 deprecations diff --git a/base/dict.jl b/base/dict.jl index dabdfa5c34773..8a78c1fa8da45 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -1,26 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -function _truncate_at_width_or_chars(str, width, chars="", truncmark="…") - truncwidth = textwidth(truncmark) - (width <= 0 || width < truncwidth) && return "" - - wid = truncidx = lastidx = 0 - for (idx, c) in pairs(str) - lastidx = idx - wid += textwidth(c) - wid >= width - truncwidth && truncidx == 0 && (truncidx = lastidx) - (wid >= width || c in chars) && break - end - - lastidx != 0 && str[lastidx] in chars && (lastidx = prevind(str, lastidx)) - truncidx == 0 && (truncidx = lastidx) - if lastidx < lastindex(str) - return String(SubString(str, 1, truncidx) * truncmark) - else - return String(str) - end -end - function show(io::IO, t::AbstractDict{K,V}) where V where K recur_io = IOContext(io, :SHOWN_SET => t, :typeinfo => eltype(t)) @@ -76,7 +55,8 @@ Dict{String, Int64} with 2 entries: ``` """ mutable struct Dict{K,V} <: AbstractDict{K,V} - slots::Array{UInt8,1} + # Metadata: empty => 0x00, removed => 0x7f, full => 0b1[7 most significant hash bits] + slots::Vector{UInt8} keys::Array{K,1} vals::Array{V,1} ndel::Int @@ -87,7 +67,7 @@ mutable struct Dict{K,V} <: AbstractDict{K,V} 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, 1, 0) + new(zeros(UInt8,n), Vector{K}(undef, n), Vector{V}(undef, n), 0, 0, 0, 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, @@ -166,11 +146,21 @@ end empty(a::AbstractDict, ::Type{K}, ::Type{V}) where {K, V} = Dict{K, V}() -hashindex(key, sz) = (((hash(key)::UInt % Int) & (sz-1)) + 1)::Int +# Gets 7 most significant bits from the hash (hsh), first bit is 1 +_shorthash7(hsh::UInt) = (hsh >> (8sizeof(UInt)-7))%UInt8 | 0x80 -@propagate_inbounds isslotempty(h::Dict, i::Int) = h.slots[i] == 0x0 -@propagate_inbounds isslotfilled(h::Dict, i::Int) = h.slots[i] == 0x1 -@propagate_inbounds isslotmissing(h::Dict, i::Int) = h.slots[i] == 0x2 +# hashindex (key, sz) - computes optimal position and shorthash7 +# idx - optimal position in the hash table +# sh::UInt8 - short hash (7 highest hash bits) +function hashindex(key, sz) + hsh = hash(key)::UInt + idx = (((hsh % Int) & (sz-1)) + 1)::Int + return idx, _shorthash7(hsh) +end + +@propagate_inbounds isslotempty(h::Dict, i::Int) = h.slots[i] == 0x00 +@propagate_inbounds isslotfilled(h::Dict, i::Int) = (h.slots[i] & 0x80) != 0 +@propagate_inbounds isslotmissing(h::Dict, i::Int) = h.slots[i] == 0x7f @constprop :none function rehash!(h::Dict{K,V}, newsz = length(h.keys)) where V where K olds = h.slots @@ -182,7 +172,7 @@ hashindex(key, sz) = (((hash(key)::UInt % Int) & (sz-1)) + 1)::Int h.idxfloor = 1 if h.count == 0 resize!(h.slots, newsz) - fill!(h.slots, 0) + fill!(h.slots, 0x0) resize!(h.keys, newsz) resize!(h.vals, newsz) h.ndel = 0 @@ -197,51 +187,41 @@ hashindex(key, sz) = (((hash(key)::UInt % Int) & (sz-1)) + 1)::Int maxprobe = 0 for i = 1:sz - @inbounds if olds[i] == 0x1 + @inbounds if (olds[i] & 0x80) != 0 k = oldk[i] v = oldv[i] - index0 = index = hashindex(k, newsz) + index, sh = hashindex(k, newsz) + index0 = index while slots[index] != 0 index = (index & (newsz-1)) + 1 end probe = (index - index0) & (newsz-1) probe > maxprobe && (maxprobe = probe) - slots[index] = 0x1 + slots[index] = olds[i] keys[index] = k vals[index] = v count += 1 - - if h.age != age0 - # if `h` is changed by a finalizer, retry - return rehash!(h, newsz) - end end end + @assert h.age == age0 "Multiple concurrent writes to Dict detected!" + h.age += 1 h.slots = slots h.keys = keys h.vals = vals h.count = count h.ndel = 0 h.maxprobe = maxprobe - @assert h.age == age0 - return h end function sizehint!(d::Dict{T}, newsz) where T oldsz = length(d.slots) # limit new element count to max_values of the key type - newsz = min(newsz, max_values(T)::Int) + newsz = min(max(newsz, length(d)), max_values(T)::Int) # need at least 1.5n space to hold n elements - newsz = cld(3 * newsz, 2) - if newsz <= oldsz - # todo: shrink - # be careful: rehash!() assumes everything fits. it was only designed - # for growing. - return d - end - rehash!(d, newsz) + newsz = _tablesz(cld(3 * newsz, 2)) + return newsz == oldsz ? d : rehash!(d, newsz) end """ @@ -272,50 +252,51 @@ function empty!(h::Dict{K,V}) where V where K h.ndel = 0 h.count = 0 h.age += 1 - h.idxfloor = 1 + h.idxfloor = sz return h end # get the index where a key is stored, or -1 if not present -function ht_keyindex(h::Dict{K,V}, key) where V where K +@assume_effects :terminates_locally function ht_keyindex(h::Dict{K,V}, key) where V where K + isempty(h) && return -1 sz = length(h.keys) iter = 0 maxprobe = h.maxprobe - index = hashindex(key, sz) + maxprobe < sz || throw(AssertionError()) # This error will never trigger, but is needed for terminates_locally to be valid + index, sh = hashindex(key, sz) keys = h.keys @inbounds while true - if isslotempty(h,index) - break - end - if !isslotmissing(h,index) && (key === keys[index] || isequal(key,keys[index])) - return index + isslotempty(h,index) && return -1 + if h.slots[index] == sh + k = keys[index] + if (key === k || isequal(key, k)) + return index + end end index = (index & (sz-1)) + 1 - iter += 1 - iter > maxprobe && break + (iter += 1) > maxprobe && return -1 end - return -1 + # This line is unreachable end -# get the index where a key is stored, or -pos if not present -# and the key would be inserted at pos +# get (index, sh) for the key +# index - where a key is stored, or -pos if not present +# and the key would be inserted at pos +# sh::UInt8 - short hash (7 highest hash bits) # This version is for use by setindex! and get! -function ht_keyindex2!(h::Dict{K,V}, key) where V where K +function ht_keyindex2_shorthash!(h::Dict{K,V}, key) where V where K sz = length(h.keys) iter = 0 maxprobe = h.maxprobe - index = hashindex(key, sz) + index, sh = hashindex(key, sz) avail = 0 keys = h.keys @inbounds while true if isslotempty(h,index) - if avail < 0 - return avail - end - return -index + return (avail < 0 ? avail : -index), sh end if isslotmissing(h,index) @@ -324,8 +305,11 @@ function ht_keyindex2!(h::Dict{K,V}, key) where V where K # in case "key" already exists in a later collided slot. avail = -index end - elseif key === keys[index] || isequal(key, keys[index]) - return index + elseif h.slots[index] == sh + k = keys[index] + if key === k || isequal(key, k) + return index, sh + end end index = (index & (sz-1)) + 1 @@ -333,14 +317,14 @@ function ht_keyindex2!(h::Dict{K,V}, key) where V where K iter > maxprobe && break end - avail < 0 && return avail + avail < 0 && return avail, sh maxallowed = max(maxallowedprobe, sz>>maxprobeshift) # Check if key is not present, may need to keep searching to find slot @inbounds while iter < maxallowed if !isslotfilled(h,index) h.maxprobe = iter - return -index + return -index, sh end index = (index & (sz-1)) + 1 iter += 1 @@ -348,11 +332,15 @@ function ht_keyindex2!(h::Dict{K,V}, key) where V where K rehash!(h, h.count > 64000 ? sz*2 : sz*4) - return ht_keyindex2!(h, key) + return ht_keyindex2_shorthash!(h, key) end -@propagate_inbounds function _setindex!(h::Dict, v, key, index) - h.slots[index] = 0x1 +# Only for better backward compatibility. It can be removed in the future. +ht_keyindex2!(h::Dict, key) = ht_keyindex2_shorthash!(h, key)[1] + +@propagate_inbounds function _setindex!(h::Dict, v, key, index, sh = _shorthash7(hash(key))) + h.ndel -= isslotmissing(h, index) + h.slots[index] = sh h.keys[index] = key h.vals[index] = v h.count += 1 @@ -363,31 +351,35 @@ end sz = length(h.keys) # Rehash now if necessary - if h.ndel >= ((3*sz)>>2) || h.count*3 > sz*2 - # > 3/4 deleted or > 2/3 full + if (h.count + h.ndel)*3 > sz*2 + # > 2/3 full (including tombstones) rehash!(h, h.count > 64000 ? h.count*2 : h.count*4) end nothing end function setindex!(h::Dict{K,V}, v0, key0) where V where K - key = convert(K, key0) - if !isequal(key, key0) - throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) + if key0 isa K + key = key0 + else + key = convert(K, key0)::K + if !(isequal(key, key0)::Bool) + throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) + end end setindex!(h, v0, key) end function setindex!(h::Dict{K,V}, v0, key::K) where V where K - v = convert(V, v0) - index = ht_keyindex2!(h, key) + v = v0 isa V ? v0 : convert(V, v0)::V + index, sh = ht_keyindex2_shorthash!(h, key) if index > 0 h.age += 1 @inbounds h.keys[index] = key @inbounds h.vals[index] = v else - @inbounds _setindex!(h, v, key, -index) + @inbounds _setindex!(h, v, key, -index, sh) end return h @@ -395,14 +387,14 @@ end function setindex!(h::Dict{K,Any}, v, key::K) where K @nospecialize v - index = ht_keyindex2!(h, key) + index, sh = ht_keyindex2_shorthash!(h, key) if index > 0 h.age += 1 @inbounds h.keys[index] = key @inbounds h.vals[index] = v else - @inbounds _setindex!(h, v, key, -index) + @inbounds _setindex!(h, v, key, -index, sh) end return h @@ -436,7 +428,7 @@ Dict{String, Int64} with 4 entries: get!(collection, key, default) """ - get!(f::Function, collection, key) + get!(f::Union{Function, Type}, collection, key) Return the value stored for the given key, or if no mapping for the key is present, store `key => f()`, and return `f()`. @@ -462,37 +454,43 @@ Dict{Int64, Int64} with 1 entry: 2 => 4 ``` """ -get!(f::Function, collection, key) +get!(f::Callable, collection, key) function get!(default::Callable, h::Dict{K,V}, key0) where V where K - key = convert(K, key0) - if !isequal(key, key0) - throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) + if key0 isa K + key = key0 + else + key = convert(K, key0)::K + if !isequal(key, key0) + throw(ArgumentError("$(limitrepr(key0)) is not a valid key for type $K")) + end end return get!(default, h, key) end function get!(default::Callable, h::Dict{K,V}, key::K) where V where K - index = ht_keyindex2!(h, key) + index, sh = ht_keyindex2_shorthash!(h, key) index > 0 && return h.vals[index] age0 = h.age - v = convert(V, default()) + v = default() + if !isa(v, V) + v = convert(V, v)::V + end if h.age != age0 - index = ht_keyindex2!(h, key) + index, sh = ht_keyindex2_shorthash!(h, key) end if index > 0 h.age += 1 @inbounds h.keys[index] = key @inbounds h.vals[index] = v else - @inbounds _setindex!(h, v, key, -index) + @inbounds _setindex!(h, v, key, -index, sh) end return v 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 @@ -526,7 +524,7 @@ function get(h::Dict{K,V}, key, default) where V where K end """ - get(f::Function, collection, key) + get(f::Union{Function, Type}, collection, key) Return the value stored for the given key, or if no mapping for the key is present, return `f()`. Use [`get!`](@ref) to also store the default value in the dictionary. @@ -540,7 +538,7 @@ get(dict, key) do end ``` """ -get(::Function, collection, key) +get(::Callable, collection, key) function get(default::Callable, h::Dict{K,V}, key) where V where K index = ht_keyindex(h, key) @@ -643,13 +641,30 @@ function pop!(h::Dict) end function _delete!(h::Dict{K,V}, index) where {K,V} - @inbounds h.slots[index] = 0x2 - @inbounds _unsetindex!(h.keys, index) - @inbounds _unsetindex!(h.vals, index) - h.ndel += 1 + @inbounds begin + slots = h.slots + sz = length(slots) + _unsetindex!(h.keys, index) + _unsetindex!(h.vals, index) + # if the next slot is empty we don't need a tombstone + # and can remove all tombstones that were required by the element we just deleted + ndel = 1 + nextind = (index & (sz-1)) + 1 + if isslotempty(h, nextind) + while true + ndel -= 1 + slots[index] = 0x00 + index = ((index - 2) & (sz-1)) + 1 + isslotmissing(h, index) || break + end + else + slots[index] = 0x7f + end + h.ndel += ndel h.count -= 1 h.age += 1 return h + end end """ @@ -702,7 +717,7 @@ end @propagate_inbounds _iterate(t::Dict{K,V}, i) where {K,V} = i == 0 ? nothing : (Pair{K,V}(t.keys[i],t.vals[i]), i == typemax(Int) ? 0 : i+1) @propagate_inbounds function iterate(t::Dict) - _iterate(t, skip_deleted_floor!(t)) + _iterate(t, skip_deleted(t, t.idxfloor)) end @propagate_inbounds iterate(t::Dict, i) = _iterate(t, skip_deleted(t, i)) @@ -720,7 +735,7 @@ end function filter!(pred, h::Dict{K,V}) where {K,V} h.count == 0 && return h @inbounds for i=1:length(h.slots) - if h.slots[i] == 0x01 && !pred(Pair{K,V}(h.keys[i], h.vals[i])) + if ((h.slots[i] & 0x80) != 0) && !pred(Pair{K,V}(h.keys[i], h.vals[i])) _delete!(h, i) end end @@ -746,15 +761,23 @@ 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)) for (k, v) in d2 - i = ht_keyindex2!(d1, k) + i, sh = ht_keyindex2_shorthash!(d1, k) if i > 0 d1.vals[i] = combine(d1.vals[i], v) else - if !isequal(k, convert(K, k)) - throw(ArgumentError("$(limitrepr(k)) is not a valid key for type $K")) + 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")) + end + k = k1 + end + if !isa(v, V) + v = convert(V, v)::V end - @inbounds _setindex!(d1, convert(V, v), k, -i) + @inbounds _setindex!(d1, v, k, -i, sh) end end return d1 diff --git a/base/div.jl b/base/div.jl index a2f7a39eb7053..9c2187e662ee9 100644 --- a/base/div.jl +++ b/base/div.jl @@ -5,10 +5,10 @@ """ div(x, y, r::RoundingMode=RoundToZero) -The quotient from Euclidean (integer) division. Computes x/y, rounded to +The quotient from Euclidean (integer) division. Computes `x / y`, rounded to an integer according to the rounding mode `r`. In other words, the quantity - round(x/y,r) + round(x / y, r) without any intermediate rounding. @@ -17,6 +17,9 @@ without any intermediate rounding. See also [`fld`](@ref) and [`cld`](@ref), which are special cases of this function. +!!! compat "Julia 1.9" + `RoundFromZero` requires at least Julia 1.9. + # Examples: ```jldoctest julia> div(4, 3, RoundDown) # Matches fld(4, 3) @@ -33,6 +36,10 @@ julia> div(-5, 2, RoundNearestTiesAway) -3 julia> div(-5, 2, RoundNearestTiesUp) -2 +julia> div(4, 3, RoundFromZero) +2 +julia> div(-4, 3, RoundFromZero) +-2 ``` """ div(x, y, r::RoundingMode) @@ -45,12 +52,12 @@ div(a, b) = div(a, b, RoundToZero) Compute the remainder of `x` after integer division by `y`, with the quotient rounded according to the rounding mode `r`. In other words, the quantity - x - y*round(x/y,r) + x - y * round(x / y, r) without any intermediate rounding. - if `r == RoundNearest`, then the result is exact, and in the interval - ``[-|y|/2, |y|/2]``. See also [`RoundNearest`](@ref). + ``[-|y| / 2, |y| / 2]``. See also [`RoundNearest`](@ref). - if `r == RoundToZero` (default), then the result is exact, and in the interval ``[0, |y|)`` if `x` is positive, or ``(-|y|, 0]`` otherwise. See also [`RoundToZero`](@ref). @@ -59,10 +66,17 @@ without any intermediate rounding. ``(y, 0]`` otherwise. The result may not be exact if `x` and `y` have different signs, and `abs(x) < abs(y)`. See also [`RoundDown`](@ref). -- if `r == RoundUp`, then the result is in the interval `(-y,0]` if `y` is positive, or - `[0,-y)` otherwise. The result may not be exact if `x` and `y` have the same sign, and +- if `r == RoundUp`, then the result is in the interval ``(-y, 0]`` if `y` is positive, or + ``[0, -y)`` otherwise. The result may not be exact if `x` and `y` have the same sign, and `abs(x) < abs(y)`. See also [`RoundUp`](@ref). +- if `r == RoundFromZero`, then the result is in the interval ``(-y, 0]`` if `y` is positive, or + ``[0, -y)`` otherwise. The result may not be exact if `x` and `y` have the same sign, and + `abs(x) < abs(y)`. See also [`RoundFromZero`](@ref). + +!!! compat "Julia 1.9" + `RoundFromZero` requires at least Julia 1.9. + # Examples: ```jldoctest julia> x = 9; y = 4; @@ -83,19 +97,23 @@ rem(x, y, r::RoundingMode) rem(x, y, ::RoundingMode{:ToZero}) = rem(x, y) rem(x, y, ::RoundingMode{:Down}) = mod(x, y) rem(x, y, ::RoundingMode{:Up}) = mod(x, -y) -rem(x, y, r::RoundingMode{:Nearest}) = x - y*div(x, y, r) +rem(x, y, r::RoundingMode{:Nearest}) = x - y * div(x, y, r) rem(x::Integer, y::Integer, r::RoundingMode{:Nearest}) = divrem(x, y, r)[2] +function rem(x, y, ::typeof(RoundFromZero)) + signbit(x) == signbit(y) ? rem(x, y, RoundUp) : rem(x, y, RoundDown) +end + """ fld(x, y) -Largest integer less than or equal to `x/y`. Equivalent to `div(x, y, RoundDown)`. +Largest integer less than or equal to `x / y`. Equivalent to `div(x, y, RoundDown)`. See also [`div`](@ref), [`cld`](@ref), [`fld1`](@ref). # Examples ```jldoctest -julia> fld(7.3,5.5) +julia> fld(7.3, 5.5) 1.0 julia> fld.(-5:5, 3)' @@ -105,11 +123,11 @@ julia> fld.(-5:5, 3)' Because `fld(x, y)` implements strictly correct floored rounding based on the true value of floating-point numbers, unintuitive situations can arise. For example: ```jldoctest -julia> fld(6.0,0.1) +julia> fld(6.0, 0.1) 59.0 -julia> 6.0/0.1 +julia> 6.0 / 0.1 60.0 -julia> 6.0/big(0.1) +julia> 6.0 / big(0.1) 59.99999999999999666933092612453056361837965690217069245739573412231113406246995 ``` What is happening here is that the true value of the floating-point number written @@ -123,13 +141,13 @@ fld(a, b) = div(a, b, RoundDown) """ cld(x, y) -Smallest integer larger than or equal to `x/y`. Equivalent to `div(x, y, RoundUp)`. +Smallest integer larger than or equal to `x / y`. Equivalent to `div(x, y, RoundUp)`. See also [`div`](@ref), [`fld`](@ref). # Examples ```jldoctest -julia> cld(5.5,2.2) +julia> cld(5.5, 2.2) 3.0 julia> cld.(-5:5, 3)' @@ -144,17 +162,17 @@ cld(a, b) = div(a, b, RoundUp) divrem(x, y, r::RoundingMode=RoundToZero) The quotient and remainder from Euclidean division. -Equivalent to `(div(x,y,r), rem(x,y,r))`. Equivalently, with the default -value of `r`, this call is equivalent to `(x÷y, x%y)`. +Equivalent to `(div(x, y, r), rem(x, y, r))`. Equivalently, with the default +value of `r`, this call is equivalent to `(x ÷ y, x % y)`. See also: [`fldmod`](@ref), [`cld`](@ref). # Examples ```jldoctest -julia> divrem(3,7) +julia> divrem(3, 7) (0, 3) -julia> divrem(7,3) +julia> divrem(7, 3) (2, 1) ``` """ @@ -172,23 +190,24 @@ function divrem(a, b, r::RoundingMode) (div(a, b, r), rem(a, b, r)) end end -#avoids calling rem for Integers-Integers (all modes), -#a-d*b not precise for Floats - AbstractFloat, AbstractIrrational. Rationals are still slower +# avoids calling rem for Integers-Integers (all modes), +# a - d * b not precise for Floats - AbstractFloat, AbstractIrrational. +# Rationals are still slower function divrem(a::Integer, b::Integer, r::Union{typeof(RoundUp), typeof(RoundDown), typeof(RoundToZero)}) if r === RoundToZero # For compat. Remove in 2.0. d = div(a, b) - (d, a - d*b) + (d, a - d * b) elseif r === RoundDown # For compat. Remove in 2.0. d = fld(a, b) - (d, a - d*b) + (d, a - d * b) elseif r === RoundUp # For compat. Remove in 2.0. d = div(a, b, r) - (d, a - d*b) + (d, a - d * b) end end function divrem(x::Integer, y::Integer, rnd::typeof(RoundNearest)) @@ -240,15 +259,19 @@ function divrem(x::Integer, y::Integer, rnd::typeof(RoundNearestTiesUp)) end end +function divrem(x, y, ::typeof(RoundFromZero)) + signbit(x) == signbit(y) ? divrem(x, y, RoundUp) : divrem(x, y, RoundDown) +end + """ fldmod(x, y) The floored quotient and modulus after division. A convenience wrapper for -`divrem(x, y, RoundDown)`. Equivalent to `(fld(x,y), mod(x,y))`. +`divrem(x, y, RoundDown)`. Equivalent to `(fld(x, y), mod(x, y))`. See also: [`fld`](@ref), [`cld`](@ref), [`fldmod1`](@ref). """ -fldmod(x,y) = divrem(x, y, RoundDown) +fldmod(x, y) = divrem(x, y, RoundDown) # We definite generic rounding methods for other rounding modes in terms of # RoundToZero. @@ -276,12 +299,16 @@ function div(x::Integer, y::Integer, rnd::Union{typeof(RoundNearest), divrem(x, y, rnd)[1] end +function div(x::Integer, y::Integer, ::typeof(RoundFromZero)) + signbit(x) == signbit(y) ? div(x, y, RoundUp) : div(x, y, RoundDown) +end + # For bootstrapping purposes, we define div for integers directly. Provide the # generic signature also div(a::T, b::T, ::typeof(RoundToZero)) where {T<:Union{BitSigned, BitUnsigned64}} = div(a, b) div(a::Bool, b::Bool, r::RoundingMode) = div(a, b) # Prevent ambiguities -for rm in (RoundUp, RoundDown, RoundToZero) +for rm in (RoundUp, RoundDown, RoundToZero, RoundFromZero) @eval div(a::Bool, b::Bool, r::$(typeof(rm))) = div(a, b) end function div(x::Bool, y::Bool, rnd::Union{typeof(RoundNearest), @@ -296,11 +323,11 @@ div(a::UInt128, b::UInt128, ::typeof(RoundToZero)) = div(a, b) rem(a::Int128, b::Int128, ::typeof(RoundToZero)) = rem(a, b) rem(a::UInt128, b::UInt128, ::typeof(RoundToZero)) = rem(a, b) -# These are kept for compatibility with external packages overriding fld/cld. -# In 2.0, packages should extend div(a,b,r) instead, in which case, these can +# These are kept for compatibility with external packages overriding fld / cld. +# In 2.0, packages should extend div(a, b, r) instead, in which case, these can # be removed. -fld(x::Real, y::Real) = div(promote(x,y)..., RoundDown) -cld(x::Real, y::Real) = div(promote(x,y)..., RoundUp) +fld(x::Real, y::Real) = div(promote(x, y)..., RoundDown) +cld(x::Real, y::Real) = div(promote(x, y)..., RoundUp) fld(x::Signed, y::Unsigned) = div(x, y, RoundDown) fld(x::Unsigned, y::Signed) = div(x, y, RoundDown) cld(x::Signed, y::Unsigned) = div(x, y, RoundUp) @@ -320,14 +347,14 @@ function div(x::Real, y::Real, r::RoundingMode) end # Integers -# fld(x,y) == div(x,y) - ((x>=0) != (y>=0) && rem(x,y) != 0 ? 1 : 0) -div(x::T, y::T, ::typeof(RoundDown)) where {T<:Unsigned} = div(x,y) +# fld(x, y) == div(x, y) - ((x >= 0) != (y >= 0) && rem(x, y) != 0 ? 1 : 0) +div(x::T, y::T, ::typeof(RoundDown)) where {T<:Unsigned} = div(x, y) function div(x::T, y::T, ::typeof(RoundDown)) where T<:Integer d = div(x, y, RoundToZero) return d - (signbit(x ⊻ y) & (d * y != x)) end -# cld(x,y) = div(x,y) + ((x>0) == (y>0) && rem(x,y) != 0 ? 1 : 0) +# cld(x, y) = div(x, y) + ((x > 0) == (y > 0) && rem(x, y) != 0 ? 1 : 0) function div(x::T, y::T, ::typeof(RoundUp)) where T<:Unsigned d = div(x, y, RoundToZero) return d + (d * y != x) @@ -340,5 +367,4 @@ end # Real # NOTE: C89 fmod() and x87 FPREM implicitly provide truncating float division, # so it is used here as the basis of float div(). -div(x::T, y::T, r::RoundingMode) where {T<:AbstractFloat} = convert(T,round((x-rem(x,y,r))/y)) -rem(x::T, y::T, ::typeof(RoundUp)) where {T<:AbstractFloat} = convert(T,x-y*ceil(x/y)) +div(x::T, y::T, r::RoundingMode) where {T<:AbstractFloat} = convert(T, round((x - rem(x, y, r)) / y)) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index b84b3ee8d55f4..e0d21715c2147 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -39,7 +39,7 @@ You can document an object after its definition by @doc "foo" function_to_doc @doc "bar" TypeToDoc -For macros, the syntax is `@doc "macro doc" :(@Module.macro)` or `@doc "macro doc" +For macros, the syntax is `@doc "macro doc" :(Module.@macro)` or `@doc "macro doc" :(string_macro"")` for string macros. Without the quote `:()` the expansion of the macro will be documented. @@ -73,9 +73,9 @@ const modules = Module[] const META = gensym(:meta) const METAType = IdDict{Any,Any} -function meta(m::Module) +function meta(m::Module; autoinit::Bool=true) if !isdefined(m, META) || getfield(m, META) === nothing - initmeta(m) + autoinit ? initmeta(m) : return nothing end return getfield(m, META)::METAType end @@ -161,7 +161,8 @@ end function docstr(binding::Binding, typesig = Union{}) @nospecialize typesig for m in modules - dict = meta(m) + dict = meta(m; autoinit=false) + isnothing(dict) && continue if haskey(dict, binding) docs = dict[binding].docs if haskey(docs, typesig) @@ -236,8 +237,10 @@ function doc!(__module__::Module, b::Binding, str::DocStr, @nospecialize sig = U if haskey(m.docs, sig) # We allow for docstrings to be updated, but print a warning since it is possible # that over-writing a docstring *may* have been accidental. The warning - # is suppressed for symbols in Main, for interactive use (#23011). - __module__ === Main || @warn "Replacing docs for `$b :: $sig` in module `$(__module__)`" + # is suppressed for symbols in Main (or current active module), + # for interactive use (#23011). + __module__ === Base.active_module() || + @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. @@ -297,9 +300,8 @@ function astname(x::Expr, ismacro::Bool) head = x.head if head === :. ismacro ? macroname(x) : x - # Call overloading, e.g. `(a::A)(b) = b` or `function (a::A)(b) b end` should document `A(b)` - elseif (head === :function || head === :(=)) && isexpr(x.args[1], :call) && isexpr((x.args[1]::Expr).args[1], :(::)) - return astname(((x.args[1]::Expr).args[1]::Expr).args[end], ismacro) + elseif head === :call && isexpr(x.args[1], :(::)) + return astname((x.args[1]::Expr).args[end], ismacro) else n = isexpr(x, (:module, :struct)) ? 2 : 1 astname(x.args[n], ismacro) @@ -515,11 +517,12 @@ function docm(source::LineNumberNode, mod::Module, ex) @nospecialize ex if isexpr(ex, :->) && length(ex.args) > 1 return docm(source, mod, ex.args...) - else + elseif isassigned(Base.REPL_MODULE_REF) # TODO: this is a shim to continue to allow `@doc` for looking up docstrings REPL = Base.REPL_MODULE_REF[] return REPL.lookup_doc(ex) end + return nothing end # Drop incorrect line numbers produced by nested macro calls. docm(source::LineNumberNode, mod::Module, _, _, x...) = docm(source, mod, x...) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index f27bc19fe7c02..684ed8b48f734 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -59,6 +59,27 @@ See the [manual section about modules](@ref modules) for details. """ kw"export" +""" + as + +`as` is used as a keyword to rename an identifier brought into scope by +`import` or `using`, for the purpose of working around name conflicts as +well as for shortening names. (Outside of `import` or `using` statements, +`as` is not a keyword and can be used as an ordinary identifier.) + +`import LinearAlgebra as LA` brings the imported `LinearAlgebra` standard library +into scope as `LA`. + +`import LinearAlgebra: eigen as eig, cholesky as chol` brings the `eigen` and `cholesky` methods +from `LinearAlgebra` into scope as `eig` and `chol` respectively. + +`as` works with `using` only when individual identifiers are brought into scope. +For example, `using LinearAlgebra: eigen as eig` or `using LinearAlgebra: eigen as eig, cholesky as chol` works, +but `using LinearAlgebra as LA` is invalid syntax, since it is nonsensical to +rename *all* exported names from `LinearAlgebra` to `LA`. +""" +kw"as" + """ abstract type @@ -106,10 +127,10 @@ kw"module" """ __init__ -`__init__()` function in your module would executes immediately *after* the module is loaded at -runtime for the first time (i.e., it is only called once and only after all statements in the -module have been executed). Because it is called *after* fully importing the module, `__init__` -functions of submodules will be executed *first*. Two typical uses of `__init__` are calling +The `__init__()` function in a module executes immediately *after* the module is loaded at +runtime for the first time. It is called once, after all other statements in the module +have been executed. Because it is called after fully importing the module, `__init__` +functions of submodules will be executed first. Two typical uses of `__init__` are calling runtime initialization functions of external C libraries and initializing global constants that involve pointers returned by external libraries. See the [manual section about modules](@ref modules) for more details. @@ -190,6 +211,8 @@ Every macro also implicitly gets passed the arguments `__source__`, which contai and file name the macro is called from, and `__module__`, which is the module the macro is expanded in. +See the manual section on [Metaprogramming](@ref) for more information about how to write a macro. + # Examples ```jldoctest julia> macro sayhello(name) @@ -279,6 +302,53 @@ julia> z """ kw"global" +""" + for outer + +Reuse an existing local variable for iteration in a `for` loop. + +See the [manual section on variable scoping](@ref scope-of-variables) for more information. + +See also [`for`](@ref). + + +# Examples +```jldoctest +julia> function f() + i = 0 + for i = 1:3 + # empty + end + return i + end; + +julia> f() +0 +``` + +```jldoctest +julia> function f() + i = 0 + for outer i = 1:3 + # empty + end + return i + end; + +julia> f() +3 +``` + +```jldoctest +julia> i = 0 # global variable + for outer i = 1:3 + end +ERROR: syntax: no outer local variable declaration exists for "for outer" +[...] +``` +""" +kw"outer" + """ ' ' @@ -430,22 +500,99 @@ kw"." """ let -`let` statements create a new hard scope block and introduce new variable bindings -each time they run. Whereas assignments might reassign a new value to an existing value location, -`let` always creates a new location. -This difference is only detectable in the case of variables that outlive their scope via -closures. The `let` syntax accepts a comma-separated series of assignments and variable -names: +`let` blocks create a new hard scope and optionally introduce new local bindings. + +Just like the [other scope constructs](@ref man-scope-table), `let` blocks define +the block of code where newly introduced local variables are accessible. +Additionally, the syntax has a special meaning for comma-separated assignments +and variable names that may optionally appear on the same line as the `let`: ```julia let var1 = value1, var2, var3 = value3 code end ``` -The assignments are evaluated in order, with each right-hand side evaluated in the scope -before the new variable on the left-hand side has been introduced. Therefore it makes -sense to write something like `let x = x`, since the two `x` variables are distinct and -have separate storage. + +The variables introduced on this line are local to the `let` block and the assignments are +evaluated in order, with each right-hand side evaluated in the scope +without considering the name on the left-hand side. Therefore it makes +sense to write something like `let x = x`, since the two `x` variables are distinct with +the left-hand side locally shadowing the `x` from the outer scope. This can even +be a useful idiom as new local variables are freshly created each time local scopes +are entered, but this is only observable in the case of variables that outlive their +scope via closures. A `let` variable without an assignment, such as `var2` in the +example above, declares a new local variable that is not yet bound to a value. + +By contrast, [`begin`](@ref) blocks also group multiple expressions together but do +not introduce scope or have the special assignment syntax. + +### Examples + +In the function below, there is a single `x` that is iteratively updated three times by the `map`. +The closures returned all reference that one `x` at its final value: + +```jldoctest +julia> function test_outer_x() + x = 0 + map(1:3) do _ + x += 1 + return ()->x + end + end +test_outer_x (generic function with 1 method) + +julia> [f() for f in test_outer_x()] +3-element Vector{Int64}: + 3 + 3 + 3 +``` + +If, however, we add a `let` block that introduces a _new_ local variable we will end up +with three distinct variables being captured (one at each iteration) even though we +chose to use (shadow) the same name. + +```jldoctest +julia> function test_let_x() + x = 0 + map(1:3) do _ + x += 1 + let x = x + return ()->x + end + end + end +test_let_x (generic function with 1 method) + +julia> [f() for f in test_let_x()] +3-element Vector{Int64}: + 1 + 2 + 3 +``` + +All scope constructs that introduce new local variables behave this way +when repeatedly run; the distinctive feature of `let` is its ability +to succinctly declare new `local`s that may shadow outer variables of the same +name. For example, directly using the argument of the `do` function similarly +captures three distinct variables: + +```jldoctest +julia> function test_do_x() + map(1:3) do x + return ()->x + end + end +test_do_x (generic function with 1 method) + +julia> [f() for f in test_do_x()] +3-element Vector{Int64}: + 1 + 2 + 3 +``` + + """ kw"let" @@ -559,6 +706,32 @@ Expr """ Expr +""" + :expr + +Quote an expression `expr`, returning the abstract syntax tree (AST) of `expr`. +The AST may be of type `Expr`, `Symbol`, or a literal value. +The syntax `:identifier` evaluates to a `Symbol`. + +See also: [`Expr`](@ref), [`Symbol`](@ref), [`Meta.parse`](@ref) + +# Examples +```jldoctest +julia> expr = :(a = b + 2*x) +:(a = b + 2x) + +julia> sym = :some_identifier +:some_identifier + +julia> value = :0xff +0xff + +julia> typeof((expr, sym, value)) +Tuple{Expr, Symbol, UInt8} +``` +""" +(:) + """ \$ @@ -643,7 +816,7 @@ julia> f(2) 7 ``` -Anonymous functions can also be defined for multiple argumets. +Anonymous functions can also be defined for multiple arguments. ```jldoctest julia> g = (x,y) -> x^2 + y^2 #2 (generic function with 1 method) @@ -758,6 +931,10 @@ kw"?", kw"?:" `for` loops repeatedly evaluate a block of statements while iterating over a sequence of values. +The iteration variable is always a new variable, even if a variable of the same name +exists in the enclosing scope. +Use [`outer`](@ref) to reuse an existing local variable for iteration. + # Examples ```jldoctest julia> for i in [1, 4, 0] @@ -986,8 +1163,16 @@ Adding `;` at the end of a line in the REPL will suppress printing the result of In function declarations, and optionally in calls, `;` separates regular arguments from keywords. -While constructing arrays, if the arguments inside the square brackets are separated by `;` -then their contents are vertically concatenated together. +In array literals, arguments separated by semicolons have their contents +concatenated together. A separator made of a single `;` concatenates vertically +(i.e. along the first dimension), `;;` concatenates horizontally (second +dimension), `;;;` concatenates along the third dimension, etc. Such a separator +can also be used in last position in the square brackets to add trailing +dimensions of length 1. + +A `;` in first position inside of parentheses can be used to construct a named +tuple. The same `(; ...)` syntax on the left side of an assignment allows for +property destructuring. In the standard REPL, typing `;` on an empty line will switch to shell mode. @@ -1011,11 +1196,40 @@ julia> function plot(x, y; style="solid", width=1, color="black") ### end -julia> [1 2; 3 4] +julia> A = [1 2; 3 4] 2×2 Matrix{Int64}: 1 2 3 4 +julia> [1; 3;; 2; 4;;; 10*A] +2×2×2 Array{Int64, 3}: +[:, :, 1] = + 1 2 + 3 4 + +[:, :, 2] = + 10 20 + 30 40 + +julia> [2; 3;;;] +2×1×1 Array{Int64, 3}: +[:, :, 1] = + 2 + 3 + +julia> nt = (; x=1) # without the ; or a trailing comma this would assign to x +(x = 1,) + +julia> key = :a; c = 3; + +julia> nt2 = (; key => 1, b=2, c, nt.x) +(a = 1, b = 2, c = 3, x = 1) + +julia> (; b, x) = nt2; # set variables b and x using property destructuring + +julia> b, x +(2, 1) + julia> ; # upon typing ;, the prompt changes (in place) to: shell> shell> echo hello hello @@ -1094,7 +1308,7 @@ first argument: with arguments are available as consecutive unnamed SSA variables (%0, %1, etc.); - as a 2-element tuple, containing a string of module IR and a string representing the name of the entry-point function to call; -- as a 2-element tuple, but with the module provided as an `Vector{UINt8}` with bitcode. +- as a 2-element tuple, but with the module provided as an `Vector{UInt8}` with bitcode. Note that contrary to `ccall`, the argument types must be specified as a tuple type, and not a tuple of types. All types, as well as the LLVM code, should be specified as literals, and @@ -1256,13 +1470,6 @@ parser rather than being implemented as a normal string macro `@var_str`. """ kw"var\"name\"", kw"@var_str" -""" - ans - -A variable referring to the last computed value, automatically set at the interactive prompt. -""" -kw"ans" - """ devnull @@ -1523,7 +1730,7 @@ The argument `val` to a function or constructor is outside the valid domain. ```jldoctest julia> sqrt(-1) ERROR: DomainError with -1.0: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). Stacktrace: [...] ``` @@ -1594,7 +1801,7 @@ A symbol in the current scope is not defined. # Examples ```jldoctest julia> a -ERROR: UndefVarError: a not defined +ERROR: UndefVarError: `a` not defined julia> a = 1; @@ -1617,7 +1824,7 @@ julia> function my_func(;my_arg) my_func (generic function with 1 method) julia> my_func() -ERROR: UndefKeywordError: keyword argument my_arg not assigned +ERROR: UndefKeywordError: keyword argument `my_arg` not assigned Stacktrace: [1] my_func() at ./REPL[1]:2 [2] top-level scope at REPL[2]:1 @@ -1894,9 +2101,8 @@ julia> eval(:x) `Symbol`s can also be constructed from strings or other values by calling the constructor `Symbol(x...)`. -`Symbol`s are immutable and should be compared using `===`. -The implementation re-uses the same object for all `Symbol`s with the same name, -so comparison tends to be efficient (it can just compare pointers). +`Symbol`s are immutable and their implementation re-uses the same object for all `Symbol`s +with the same name. Unlike strings, `Symbol`s are "atomic" or "scalar" entities that do not support iteration over characters. @@ -1924,7 +2130,7 @@ Symbol(x...) Construct a tuple of the given objects. -See also [`Tuple`](@ref), [`NamedTuple`](@ref). +See also [`Tuple`](@ref), [`ntuple`](@ref), [`NamedTuple`](@ref). # Examples ```jldoctest @@ -2051,6 +2257,82 @@ instruction, otherwise it'll use a loop. """ replacefield! +""" + getglobal(module::Module, name::Symbol, [order::Symbol=:monotonic]) + +Retrieve the value of the binding `name` from the module `module`. Optionally, an +atomic ordering can be defined for the operation, otherwise it defaults to +monotonic. + +While accessing module bindings using [`getfield`](@ref) is still supported to +maintain compatibility, using `getglobal` should always be preferred since +`getglobal` allows for control over atomic ordering (`getfield` is always +monotonic) and better signifies the code's intent both to the user as well as the +compiler. + +Most users should not have to call this function directly -- The +[`getproperty`](@ref Base.getproperty) function or corresponding syntax (i.e. +`module.name`) should be preferred in all but few very specific use cases. + +!!! compat "Julia 1.9" + This function requires Julia 1.9 or later. + +See also [`getproperty`](@ref Base.getproperty) and [`setglobal!`](@ref). + +# Examples +```jldoctest +julia> a = 1 +1 + +julia> module M + a = 2 + end; + +julia> getglobal(@__MODULE__, :a) +1 + +julia> getglobal(M, :a) +2 +``` +""" +getglobal + +""" + setglobal!(module::Module, name::Symbol, x, [order::Symbol=:monotonic]) + +Set or change the value of the binding `name` in the module `module` to `x`. No +type conversion is performed, so if a type has already been declared for the +binding, `x` must be of appropriate type or an error is thrown. + +Additionally, an atomic ordering can be specified for this operation, otherwise it +defaults to monotonic. + +Users will typically access this functionality through the +[`setproperty!`](@ref Base.setproperty!) function or corresponding syntax +(i.e. `module.name = x`) instead, so this is intended only for very specific use +cases. + +!!! compat "Julia 1.9" + This function requires Julia 1.9 or later. + +See also [`setproperty!`](@ref Base.setproperty!) and [`getglobal`](@ref) + +# Examples +```jldoctest +julia> module M end; + +julia> M.a # same as `getglobal(M, :a)` +ERROR: UndefVarError: `a` not defined + +julia> setglobal!(M, :a, 1) +1 + +julia> M.a +1 +``` +""" +setglobal! + """ typeof(x) @@ -2523,14 +2805,14 @@ union [`Union{}`](@ref) is the bottom type of Julia. julia> IntOrString = Union{Int,AbstractString} Union{Int64, AbstractString} -julia> 1 :: IntOrString -1 +julia> 1 isa IntOrString +true -julia> "Hello!" :: IntOrString -"Hello!" +julia> "Hello!" isa IntOrString +true -julia> 1.0 :: IntOrString -ERROR: TypeError: in typeassert, expected Union{Int64, AbstractString}, got a value of type Float64 +julia> 1.0 isa IntOrString +false ``` """ Union @@ -2622,17 +2904,48 @@ Vararg """ Tuple{Types...} -Tuples are an abstraction of the arguments of a function – without the function itself. The salient aspects of -a function's arguments are their order and their types. Therefore a tuple type is similar to a parameterized -immutable type where each parameter is the type of one field. Tuple types may have any number of parameters. +A tuple is a fixed-length container that can hold any values of different +types, but cannot be modified (it is immutable). The values can be accessed via +indexing. Tuple literals are written with commas and parentheses: + +```jldoctest +julia> (1, 1+1) +(1, 2) + +julia> (1,) +(1,) + +julia> x = (0.0, "hello", 6*7) +(0.0, "hello", 42) + +julia> x[2] +"hello" + +julia> typeof(x) +Tuple{Float64, String, Int64} +``` + +A length-1 tuple must be written with a comma, `(1,)`, since `(1)` would just +be a parenthesized value. `()` represents the empty (length-0) tuple. + +A tuple can be constructed from an iterator by using a `Tuple` type as constructor: + +```jldoctest +julia> Tuple(["a", 1]) +("a", 1) + +julia> Tuple{String, Float64}(["a", 1]) +("a", 1.0) +``` Tuple types are covariant in their parameters: `Tuple{Int}` is a subtype of `Tuple{Any}`. Therefore `Tuple{Any}` is considered an abstract type, and tuple types are only concrete if their parameters are. Tuples do not have field names; fields are only accessed by index. +Tuple types may have any number of parameters. See the manual section on [Tuple Types](@ref). -See also [`Vararg`](@ref), [`NTuple`](@ref), [`tuple`](@ref), [`NamedTuple`](@ref). +See also [`Vararg`](@ref), [`NTuple`](@ref), [`ntuple`](@ref), [`tuple`](@ref), [`NamedTuple`](@ref). """ Tuple @@ -2696,8 +3009,8 @@ the syntax `@atomic a.b` calls `getproperty(a, :b, :sequentially_consistent)`. # Examples ```jldoctest -julia> struct MyType - x +julia> struct MyType{T <: Number} + x::T end julia> function Base.getproperty(obj::MyType, sym::Symbol) @@ -2717,6 +3030,11 @@ julia> obj.x 1 ``` +One should overload `getproperty` only when necessary, as it can be confusing if +the behavior of the syntax `obj.f` is unusual. +Also note that using methods is often preferable. See also this style guide documentation +for more information: [Prefer exported methods over direct field access](@ref). + See also [`getfield`](@ref Core.getfield), [`propertynames`](@ref Base.propertynames) and [`setproperty!`](@ref Base.setproperty!). @@ -2729,7 +3047,10 @@ Base.getproperty The syntax `a.b = c` calls `setproperty!(a, :b, c)`. The syntax `@atomic order a.b = c` calls `setproperty!(a, :b, c, :order)` -and the syntax `@atomic a.b = c` calls `getproperty(a, :b, :sequentially_consistent)`. +and the syntax `@atomic a.b = c` calls `setproperty!(a, :b, c, :sequentially_consistent)`. + +!!! compat "Julia 1.8" + `setproperty!` on modules requires at least Julia 1.8. See also [`setfield!`](@ref Core.setfield!), [`propertynames`](@ref Base.propertynames) and @@ -2741,7 +3062,7 @@ Base.setproperty! swapproperty!(x, f::Symbol, v, order::Symbol=:not_atomic) The syntax `@atomic a.b, _ = c, a.b` returns `(c, swapproperty!(a, :b, c, :sequentially_consistent))`, -where there must be one getfield expression common to both sides. +where there must be one `getproperty` expression common to both sides. See also [`swapfield!`](@ref Core.swapfield!) and [`setproperty!`](@ref Base.setproperty!). @@ -2751,9 +3072,14 @@ Base.swapproperty! """ modifyproperty!(x, f::Symbol, op, v, order::Symbol=:not_atomic) -The syntax `@atomic! max(a().b, c)` returns `modifyproperty!(a(), :b, -max, c, :sequentially_consistent))`, where the first argument must be a -`getfield` expression and is modified atomically. +The syntax `@atomic op(x.f, v)` (and its equivalent `@atomic x.f op v`) returns +`modifyproperty!(x, :f, op, v, :sequentially_consistent)`, where the first argument +must be a `getproperty` expression and is modified atomically. + +Invocation of `op(getproperty(x, f), v)` must return a value that can be stored in the field +`f` of the object `x` by default. In particular, unlike the default behavior of +[`setproperty!`](@ref Base.setproperty!), the `convert` function is not called +automatically. See also [`modifyfield!`](@ref Core.modifyfield!) and [`setproperty!`](@ref Base.setproperty!). @@ -2782,7 +3108,7 @@ with elements of type `T` and `N` dimensions. If `A` is a `StridedArray`, then its elements are stored in memory with offsets, which may vary between dimensions but are constant within a dimension. For example, `A` could have stride 2 in dimension 1, and stride 3 in dimension 2. Incrementing `A` along -dimension `d` jumps in memory by [`strides(A, d)`] slots. Strided arrays are +dimension `d` jumps in memory by [`stride(A, d)`] slots. Strided arrays are particularly important and useful because they can sometimes be passed directly as pointers to foreign language libraries like BLAS. """ @@ -2858,7 +3184,7 @@ QuoteNode """ " -`"` Is used to delimit string literals. +`"` Is used to delimit string literals. A trailing `\\` can be used to continue a string literal on the next line. # Examples @@ -2868,6 +3194,10 @@ julia> "Hello World!" julia> "Hello World!\\n" "Hello World!\\n" + +julia> "Hello \\ + World" +"Hello World" ``` See also [`\"""`](@ref \"\"\"). @@ -2898,7 +3228,7 @@ See also [`"`](@ref \") kw"\"\"\"" """ - donotdelete(args...) + Base.donotdelete(args...) This function prevents dead-code elimination (DCE) of itself and any arguments passed to it, but is otherwise the lightest barrier possible. In particular, @@ -2915,21 +3245,101 @@ This is intended for use in benchmarks that want to guarantee that `args` are actually computed. (Otherwise DCE may see that the result of the benchmark is unused and delete the entire benchmark code). -**Note**: `donotdelete` does not affect constant folding. For example, in - `donotdelete(1+1)`, no add instruction needs to be executed at runtime and - the code is semantically equivalent to `donotdelete(2).` +!!! note + `donotdelete` does not affect constant folding. For example, in + `donotdelete(1+1)`, no add instruction needs to be executed at runtime and + the code is semantically equivalent to `donotdelete(2).` # Examples +```julia function loop() for i = 1:1000 - # The complier must guarantee that there are 1000 program points (in the correct + # The compiler must guarantee that there are 1000 program points (in the correct # order) at which the value of `i` is in a register, but has otherwise # total control over the program. donotdelete(i) end end +``` """ Base.donotdelete +""" + Base.compilerbarrier(setting::Symbol, val) + +This function puts a barrier at a specified compilation phase. +It is supposed to only influence the compilation behavior according to `setting`, +and its runtime semantics is just to return the second argument `val` (except that +this function will perform additional checks on `setting` in a case when `setting` +isn't known precisely at compile-time.) + +Currently either of the following `setting`s is allowed: +- Barriers on abstract interpretation: + * `:type`: the return type of this function call will be inferred as `Any` always + (the strongest barrier on abstract interpretation) + * `:const`: the return type of this function call will be inferred with widening + constant information on `val` + * `:conditional`: the return type of this function call will be inferred with widening + conditional information on `val` (see the example below) +- Any barriers on optimization aren't implemented yet + +!!! note + This function is supposed to be used _with `setting` known precisely at compile-time_. + Note that in a case when the `setting` isn't known precisely at compile-time, the compiler + currently will put the most strongest barrier(s) rather than emitting a compile-time warning. + +# Examples + +```julia +julia> Base.return_types((Int,)) do a + x = compilerbarrier(:type, a) # `x` won't be inferred as `x::Int` + return x + end |> only +Any + +julia> Base.return_types() do + x = compilerbarrier(:const, 42) + if x == 42 # no constant information here, so inference also accounts for the else branch + return x # but `x` is still inferred as `x::Int` at least here + else + return nothing + end + end |> only +Union{Nothing, Int64} + +julia> Base.return_types((Union{Int,Nothing},)) do a + if compilerbarrier(:conditional, isa(a, Int)) + # the conditional information `a::Int` isn't available here (leading to less accurate return type inference) + return a + else + return nothing + end + end |> only +Union{Nothing, Int64} +``` +""" +Base.compilerbarrier + +""" + Core.finalizer(f, o) + +This builtin is an implementation detail of [`Base.finalizer`](@ref) and end-users +should use the latter instead. + +# Differences from `Base.finalizer` + +The interface of `Core.finalizer` is essentially the same as `Base.finalizer`, +but there are a number of small differences. They are documented here for +completeness only and (unlike `Base.finalizer`) have no stability guarantees. + +The current differences are: +- `Core.finalizer` does not check for mutability of `o`. Attempting to register + a finalizer for an immutable object is undefined behavior. +- The value `f` must be a Julia object. `Core.finalizer` does not support a + raw C function pointer. +- `Core.finalizer` returns `nothing` rather than `o`. +""" +Core.finalizer + end diff --git a/base/docs/bindings.jl b/base/docs/bindings.jl index d96154f05fcb1..6095d52a28e5a 100644 --- a/base/docs/bindings.jl +++ b/base/docs/bindings.jl @@ -33,7 +33,7 @@ macro var(x) end function Base.show(io::IO, b::Binding) - if b.mod === Main + if b.mod === Base.active_module() print(io, b.var) else print(io, b.mod, '.', Base.isoperator(b.var) ? ":" : "", b.var) diff --git a/base/env.jl b/base/env.jl index 4fdc02e582a4c..a4a55d9dad013 100644 --- a/base/env.jl +++ b/base/env.jl @@ -73,9 +73,68 @@ variables. all keys to uppercase for display, iteration, and copying. Portable code should not rely on the ability to distinguish variables by case, and should beware that setting an ostensibly lowercase variable may result in an uppercase `ENV` key.) + +!!! warning + Mutating the environment is not thread-safe. + +# Examples +```julia-repl +julia> ENV +Base.EnvDict with "50" entries: + "SECURITYSESSIONID" => "123" + "USER" => "username" + "MallocNanoZone" => "0" + ⋮ => ⋮ + +julia> ENV["JULIA_EDITOR"] = "vim" +"vim" + +julia> ENV["JULIA_EDITOR"] +"vim" +``` + +See also: [`withenv`](@ref), [`addenv`](@ref). """ const ENV = EnvDict() +const get_bool_env_truthy = ( + "t", "T", + "true", "True", "TRUE", + "y", "Y", + "yes", "Yes", "YES", + "1") +const get_bool_env_falsy = ( + "f", "F", + "false", "False", "FALSE", + "n", "N", + "no", "No", "NO", + "0") + +""" + Base.get_bool_env(name::String, default::Bool)::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`. + +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 + return true + elseif val in get_bool_env_falsy + return false + else + return nothing + end +end + getindex(::EnvDict, k::AbstractString) = access_env(k->throw(KeyError(k)), k) get(::EnvDict, k::AbstractString, def) = access_env(Returns(def), k) get(f::Callable, ::EnvDict, k::AbstractString) = access_env(k->f(), k) @@ -117,7 +176,7 @@ if Sys.iswindows() m = nothing end if m === nothing - @warn "malformed environment entry: $env" + @warn "malformed environment entry" env continue end return (Pair{String,String}(winuppercase(env[1:prevind(env, m)]), env[nextind(env, m):end]), (pos, blk)) @@ -131,8 +190,8 @@ else # !windows env = env::String m = findfirst('=', env) if m === nothing - @warn "malformed environment entry: $env" - nothing + @warn "malformed environment entry" env + continue end return (Pair{String,String}(env[1:prevind(env, m)], env[nextind(env, m):end]), i+1) end @@ -162,6 +221,10 @@ by zero or more `"var"=>val` arguments `kv`. `withenv` is generally used via the `withenv(kv...) do ... end` syntax. A value of `nothing` can be used to temporarily unset an environment variable (if it is set). When `withenv` returns, the original environment has been restored. + +!!! warning + Changing the environment is not thread-safe. For running external commands with a different + environment from the parent process, prefer using [`addenv`](@ref) over `withenv`. """ function withenv(f, keyvals::Pair{T}...) where T<:AbstractString old = Dict{T,Any}() diff --git a/base/error.jl b/base/error.jl index 9ffcac5d7820c..4e9be0e172d61 100644 --- a/base/error.jl +++ b/base/error.jl @@ -162,7 +162,7 @@ end ## keyword arg lowering generates calls to this ## function kwerr(kw, args::Vararg{Any,N}) where {N} @noinline - throw(MethodError(typeof(args[1]).name.mt.kwsorter, (kw,args...))) + throw(MethodError(Core.kwcall, (kw, args...))) end ## system error handling ## @@ -261,7 +261,7 @@ function iterate(ebo::ExponentialBackOff, state= (ebo.n, min(ebo.first_delay, eb state[1] < 1 && return nothing next_n = state[1]-1 curr_delay = state[2] - next_delay = min(ebo.max_delay, state[2] * ebo.factor * (1.0 - ebo.jitter + (rand(Float64) * 2.0 * ebo.jitter))) + next_delay = min(ebo.max_delay, state[2] * ebo.factor * (1.0 - ebo.jitter + (Libc.rand(Float64) * 2.0 * ebo.jitter))) (curr_delay, (next_n, next_delay)) end length(ebo::ExponentialBackOff) = ebo.n @@ -295,7 +295,6 @@ function retry(f; delays=ExponentialBackOff(), check=nothing) try return f(args...; kwargs...) catch e - y === nothing && rethrow() if check !== nothing result = check(state, e) state, retry_or_not = length(result) == 2 ? result : (state, result) diff --git a/base/errorshow.jl b/base/errorshow.jl index 9441292e2c6ee..03650920aae57 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -107,8 +107,8 @@ showerror(io::IO, ex::InitError) = showerror(io, ex, []) function showerror(io::IO, ex::DomainError) if isa(ex.val, AbstractArray) - compact = get(io, :compact, true) - limit = get(io, :limit, true) + compact = get(io, :compact, true)::Bool + limit = get(io, :limit, true)::Bool print(IOContext(io, :compact => compact, :limit => limit), "DomainError with ", ex.val) else @@ -157,10 +157,10 @@ showerror(io::IO, ex::AssertionError) = print(io, "AssertionError: ", ex.msg) showerror(io::IO, ex::OverflowError) = print(io, "OverflowError: ", ex.msg) showerror(io::IO, ex::UndefKeywordError) = - print(io, "UndefKeywordError: keyword argument $(ex.var) not assigned") + print(io, "UndefKeywordError: keyword argument `$(ex.var)` not assigned") function showerror(io::IO, ex::UndefVarError) - print(io, "UndefVarError: $(ex.var) not defined") + print(io, "UndefVarError: `$(ex.var)` not defined") Experimental.show_error_hints(io, ex) end @@ -181,6 +181,7 @@ function print_with_compare(io::IO, @nospecialize(a::DataType), @nospecialize(b: if a.name === b.name Base.show_type_name(io, a.name) n = length(a.parameters) + n > 0 || return print(io, '{') for i = 1:n if i > length(b.parameters) @@ -235,17 +236,16 @@ function showerror(io::IO, ex::MethodError) show_candidates = true print(io, "MethodError: ") ft = typeof(f) - name = ft.name.mt.name f_is_function = false kwargs = () - if endswith(string(ft.name.name), "##kw") - f = ex.args[2] + if f === Core.kwcall && !is_arg_types + f = (ex.args::Tuple)[2] ft = typeof(f) - name = ft.name.mt.name arg_types_param = arg_types_param[3:end] kwargs = pairs(ex.args[1]) ex = MethodError(f, ex.args[3:end::Int]) 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) @@ -257,9 +257,7 @@ function showerror(io::IO, ex::MethodError) elseif isempty(methods(f)) && !isa(f, Function) && !isa(f, Type) print(io, "objects of type ", ft, " are not callable") else - if ft <: Function && isempty(ft.parameters) && - isdefined(ft.name.module, name) && - ft == typeof(getfield(ft.name.module, name)) + if ft <: Function && isempty(ft.parameters) && _isself(ft) f_is_function = true end print(io, "no method matching ") @@ -272,8 +270,7 @@ function showerror(io::IO, ex::MethodError) if !isempty(kwargs) print(io, "; ") for (i, (k, v)) in enumerate(kwargs) - print(io, k, "=") - show(IOContext(io, :limit => true), v) + print(io, k, "::", typeof(v)) i == length(kwargs)::Int || print(io, ", ") end end @@ -334,7 +331,7 @@ end striptype(::Type{T}) where {T} = T striptype(::Any) = nothing -function showerror_ambiguous(io::IO, meth, f, args) +function showerror_ambiguous(io::IO, meths, f, args) print(io, "MethodError: ") show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f)) print(io, "(") @@ -343,23 +340,25 @@ function showerror_ambiguous(io::IO, meth, f, args) print(io, "::", a) i < length(p) && print(io, ", ") end - print(io, ") is ambiguous. Candidates:") + println(io, ") is ambiguous.\n\nCandidates:") sigfix = Any - for m in meth - print(io, "\n ", m) + for m in meths + print(io, " ") + show_method(io, m; digit_align_width=0) + println(io) sigfix = typeintersect(m.sig, sigfix) end if isa(unwrap_unionall(sigfix), DataType) && sigfix <: Tuple let sigfix=sigfix - if all(m->morespecific(sigfix, m.sig), meth) + if all(m->morespecific(sigfix, m.sig), meths) print(io, "\nPossible fix, define\n ") Base.show_tuple_as_call(io, :function, sigfix) else - println(io) 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.") end end + println(io) end nothing end @@ -374,12 +373,9 @@ function showerror_nostdio(err, msg::AbstractString) ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, "\n") end -stacktrace_expand_basepaths()::Bool = - tryparse(Bool, get(ENV, "JULIA_STACKTRACE_EXPAND_BASEPATHS", "false")) === true -stacktrace_contract_userdir()::Bool = - tryparse(Bool, get(ENV, "JULIA_STACKTRACE_CONTRACT_HOMEDIR", "true")) === true -stacktrace_linebreaks()::Bool = - tryparse(Bool, get(ENV, "JULIA_STACKTRACE_LINEBREAKS", "false")) === true +stacktrace_expand_basepaths()::Bool = Base.get_bool_env("JULIA_STACKTRACE_EXPAND_BASEPATHS", false) === true +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) @@ -399,7 +395,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() # pool MethodErrors for these two functions. if f === convert && !isempty(arg_types_param) at1 = arg_types_param[1] - if isa(at1,DataType) && (at1::DataType).name === Type.body.name && !Core.Compiler.has_free_typevars(at1) + if isType(at1) && !Core.Compiler.has_free_typevars(at1) push!(funcs, (at1.parameters[1], arg_types_param[2:end])) end end @@ -409,7 +405,11 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() buf = IOBuffer() iob0 = iob = IOContext(buf, io) tv = Any[] - sig0 = method.sig + if func isa Core.OpaqueClosure + sig0 = signature_type(func, typeof(func).parameters[1]) + else + sig0 = method.sig + end while isa(sig0, UnionAll) push!(tv, sig0.var) iob = IOContext(iob, :unionall_env => sig0.var) @@ -417,17 +417,17 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() end sig0 = sig0::DataType s1 = sig0.parameters[1] - sig = sig0.parameters[2:end] - print(iob, " ") - if !isa(func, rewrap_unionall(s1, method.sig)) - # function itself doesn't match + if sig0 === Tuple || !isa(func, rewrap_unionall(s1, method.sig)) + # function itself doesn't match or is a builtin continue else + print(iob, " ") show_signature_function(iob, s1) end print(iob, "(") t_i = copy(arg_types_param) right_matches = 0 + sig = sig0.parameters[2:end] for i = 1 : min(length(t_i), length(sig)) i > 1 && print(iob, ", ") # If isvarargtype then it checks whether the rest of the input arguments matches @@ -446,7 +446,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() # the type of the first argument is not matched. t_in === Union{} && special && i == 1 && break if t_in === Union{} - if get(io, :color, false) + if get(io, :color, false)::Bool let sigstr=sigstr Base.with_output_color(Base.error_color(), iob) do iob print(iob, "::", sigstr...) @@ -490,7 +490,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() if !((min(length(t_i), length(sig)) == 0) && k==1) print(iob, ", ") end - if get(io, :color, false) + if get(io, :color, false)::Bool let sigstr=sigstr Base.with_output_color(Base.error_color(), iob) do iob print(iob, "::", sigstr...) @@ -508,12 +508,12 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() end print(iob, ")") show_method_params(iob0, tv) - file, line = functionloc(method) + file, line = updated_methodloc(method) if file === nothing file = string(method.file) end stacktrace_contract_userdir() && (file = contractuser(file)) - print(iob, " at ", file, ":", line) + if !isempty(kwargs)::Bool unexpected = Symbol[] if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords)) @@ -535,6 +535,12 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() 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) + # TODO: indicate if it's in the wrong world push!(lines, (buf, right_matches)) end @@ -543,7 +549,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() if !isempty(lines) # Display up to three closest candidates Base.with_output_color(:normal, io) do io - print(io, "\nClosest candidates are:") + print(io, "\n\nClosest candidates are:") sort!(lines, by = x -> -x[2]) i = 0 for line in lines @@ -555,6 +561,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() i += 1 print(io, String(take!(line[1]))) end + println(io) # extra newline for spacing to stacktrace end end end @@ -570,20 +577,17 @@ end # replace `sf` as needed. const update_stackframes_callback = Ref{Function}(identity) -const STACKTRACE_MODULECOLORS = [:magenta, :cyan, :green, :yellow] +const STACKTRACE_MODULECOLORS = Iterators.Stateful(Iterators.cycle([:magenta, :cyan, :green, :yellow])) const STACKTRACE_FIXEDCOLORS = IdDict(Base => :light_black, Core => :light_black) function show_full_backtrace(io::IO, trace::Vector; print_linebreaks::Bool) num_frames = length(trace) ndigits_max = ndigits(num_frames) - modulecolordict = copy(STACKTRACE_FIXEDCOLORS) - modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS)) - println(io, "\nStacktrace:") for (i, (frame, n)) in enumerate(trace) - print_stackframe(io, i, frame, n, ndigits_max, modulecolordict, modulecolorcycler) + print_stackframe(io, i, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) if i < num_frames println(io) print_linebreaks && println(io) @@ -643,15 +647,12 @@ function show_reduced_backtrace(io::IO, t::Vector) ndigits_max = ndigits(length(t)) - modulecolordict = Dict{Module, Symbol}() - modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS)) - push!(repeated_cycle, (0,0,0)) # repeated_cycle is never empty frame_counter = 1 for i in 1:length(displayed_stackframes) (frame, n) = displayed_stackframes[i] - print_stackframe(io, frame_counter, frame, n, ndigits_max, modulecolordict, modulecolorcycler) + print_stackframe(io, frame_counter, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) if i < length(displayed_stackframes) println(io) @@ -679,27 +680,30 @@ end # Print a stack frame where the module color is determined by looking up the parent module in # `modulecolordict`. If the module does not have a color, yet, a new one can be drawn # from `modulecolorcycler`. -function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, modulecolordict, modulecolorcycler) +function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulecolordict, modulecolorcycler) m = Base.parentmodule(frame) - if m !== nothing - while parentmodule(m) !== m - pm = parentmodule(m) - pm == Main && break - m = pm - end - if !haskey(modulecolordict, m) - modulecolordict[m] = popfirst!(modulecolorcycler) - end - modulecolor = modulecolordict[m] + modulecolor = if m !== nothing + m = parentmodule_before_main(m) + get!(() -> popfirst!(modulecolorcycler), modulecolordict, m) else - modulecolor = :default + :default end - print_stackframe(io, i, frame, n, digit_align_width, modulecolor) + print_stackframe(io, i, frame, n, ndigits_max, modulecolor) end +# Gets the topmost parent module that isn't Main +function parentmodule_before_main(m::Module) + while parentmodule(m) !== m + pm = parentmodule(m) + pm == Main && break + m = pm + end + m +end +parentmodule_before_main(x) = parentmodule_before_main(parentmodule(x)) # Print a stack frame where the module color is set manually with `modulecolor`. -function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, modulecolor) +function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulecolor) file, line = string(frame.file), frame.line file = fixup_stdlib_path(file) stacktrace_expand_basepaths() && (file = something(find_source_file(file), file)) @@ -714,8 +718,10 @@ function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, m inlined = getfield(frame, :inlined) modul = parentmodule(frame) + digit_align_width = ndigits_max + 2 + # frame number - print(io, " ", lpad("[" * string(i) * "]", digit_align_width + 2)) + print(io, " ", lpad("[" * string(i) * "]", digit_align_width)) print(io, " ") StackTraces.show_spec_linfo(IOContext(io, :backtrace=>true), frame) @@ -724,32 +730,33 @@ function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, m end println(io) - # @ - printstyled(io, " " ^ (digit_align_width + 2) * "@ ", color = :light_black) + # @ Module path / file : line + print_module_path_file(io, modul, file, line; modulecolor, digit_align_width) + + # inlined + printstyled(io, inlined ? " [inlined]" : "", color = :light_black) +end + +function print_module_path_file(io, modul, file, line; modulecolor = :light_black, digit_align_width = 0) + printstyled(io, " " ^ digit_align_width * "@", color = :light_black) # module - if modul !== nothing - printstyled(io, modul, color = modulecolor) + if modul !== nothing && modulecolor !== nothing print(io, " ") + printstyled(io, modul, color = modulecolor) end # filepath - pathparts = splitpath(file) - folderparts = pathparts[1:end-1] - if !isempty(folderparts) - printstyled(io, joinpath(folderparts...) * (Sys.iswindows() ? "\\" : "/"), color = :light_black) - end + stacktrace_expand_basepaths() && (file = something(find_source_file(file), file)) + stacktrace_contract_userdir() && (file = contractuser(file)) + print(io, " ") + dir = dirname(file) + !isempty(dir) && printstyled(io, dir, Filesystem.path_separator, color = :light_black) # filename, separator, line - # use escape codes for formatting, printstyled can't do underlined and color - # codes are bright black (90) and underlined (4) - printstyled(io, pathparts[end], ":", line; color = :light_black, underline = true) - - # inlined - printstyled(io, inlined ? " [inlined]" : "", color = :light_black) + printstyled(io, basename(file), ":", line; color = :light_black, underline = true) end - function show_backtrace(io::IO, t::Vector) if haskey(io, :last_shown_line_infos) empty!(io[:last_shown_line_infos]) @@ -783,11 +790,6 @@ function show_backtrace(io::IO, t::Vector) end -function is_kw_sorter_name(name::Symbol) - sn = string(name) - return !startswith(sn, '#') && endswith(sn, "##kw") -end - # For improved user experience, filter out frames for include() implementation # - see #33065. See also #35371 for extended discussion of internal frames. function _simplify_include_frames(trace) @@ -822,6 +824,72 @@ function _simplify_include_frames(trace) return trace[kept_frames] end +# Collapse frames that have the same location (in some cases) +function _collapse_repeated_frames(trace) + kept_frames = trues(length(trace)) + last_frame = nothing + for i in 1:length(trace) + frame::StackFrame, _ = trace[i] + if last_frame !== nothing && frame.file == last_frame.file && frame.line == last_frame.line + #= + Handles this case: + + f(g, a; kw...) = error(); + @inline f(a; kw...) = f(identity, a; kw...); + f(1) + + which otherwise ends up as: + + [4] #f#4 <-- useless + @ ./REPL[2]:1 [inlined] + [5] f(a::Int64) + @ Main ./REPL[2]:1 + =# + if startswith(sprint(show, last_frame), "#") + kept_frames[i-1] = false + end + + #= Handles this case + g(x, y=1, z=2) = error(); + g(1) + + which otherwise ends up as: + + [2] g(x::Int64, y::Int64, z::Int64) + @ Main ./REPL[1]:1 + [3] g(x::Int64) <-- useless + @ Main ./REPL[1]:1 + =# + if frame.linfo isa MethodInstance && last_frame.linfo isa MethodInstance && + frame.linfo.def isa Method && last_frame.linfo.def isa Method + m, last_m = frame.linfo.def::Method, last_frame.linfo.def::Method + params, last_params = Base.unwrap_unionall(m.sig).parameters, Base.unwrap_unionall(last_m.sig).parameters + if last_m.nkw != 0 + pos_sig_params = last_params[(last_m.nkw+2):end] + issame = true + if pos_sig_params == params + kept_frames[i] = false + end + end + if length(last_params) > length(params) + issame = true + for i = 1:length(params) + issame &= params[i] == last_params[i] + end + if issame + kept_frames[i] = false + end + end + end + + # TODO: Detect more cases that can be collapsed + end + last_frame = frame + end + return trace[kept_frames] +end + + function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) n = 0 last_frame = StackTraces.UNKNOWN @@ -839,15 +907,27 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) continue end - if (lkup.from_c && skipC) || is_kw_sorter_name(lkup.func) + if (lkup.from_c && skipC) continue end + code = lkup.linfo + if code isa MethodInstance + def = code.def + if def isa Method && def.name !== :kwcall && def.sig <: Tuple{typeof(Core.kwcall),NamedTuple,Any,Vararg} + # hide kwcall() methods, which are probably internal keyword sorter methods + # (we print the internal method instead, after demangling + # the argument list, since it has the right line number info) + continue + end + elseif !lkup.from_c + lkup.func === :kwcall && continue + end count += 1 if count > limit break end - if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== lkup.linfo + if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== last_frame.linfo if n > 0 push!(ret, (last_frame, n)) end @@ -862,7 +942,9 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) if n > 0 push!(ret, (last_frame, n)) end - return _simplify_include_frames(ret) + trace = _simplify_include_frames(ret) + trace = _collapse_repeated_frames(trace) + return trace end function show_exception_stack(io::IO, stack) @@ -904,6 +986,19 @@ end Experimental.register_error_hint(noncallable_number_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) + @nospecialize + if (ex.f === +) && all(i -> i <: AbstractString, arg_types) + print(io, "\nString concatenation is performed with ") + printstyled(io, "*", color=:cyan) + print(io, " (See also: https://docs.julialang.org/en/v1/manual/strings/#man-concatenation).") + end +end + +Experimental.register_error_hint(string_concatenation_hint_handler, 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 04df906628e36..06e2c3ea2ec87 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -1,11 +1,20 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using Core: CodeInfo, SimpleVector, donotdelete +import Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, arrayref const Callable = Union{Function,Type} const Bottom = Union{} +# Define minimal array interface here to help code used in macros: +length(a::Array) = arraylen(a) + +# 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...)))) + +==(a::GlobalRef, b::GlobalRef) = a.mod === b.mod && a.name === b.name + """ AbstractSet{T} @@ -23,21 +32,6 @@ An `AbstractDict{K, V}` should be an iterator of `Pair{K, V}`. """ abstract type AbstractDict{K,V} end -""" - Iterators.Pairs(values, keys) <: AbstractDict{eltype(keys), eltype(values)} - -Transforms an indexable container into a Dictionary-view of the same data. -Modifying the key-space of the underlying data may invalidate this object. -""" -struct Pairs{K, V, I, A} <: AbstractDict{K, V} - data::A - itr::I -end -Pairs{K, V}(data::A, itr::I) where {K, V, I, A} = Pairs{K, V, I, A}(data, itr) -Pairs{K}(data::A, itr::I) where {K, I, A} = Pairs{K, eltype(A), I, A}(data, itr) -Pairs(data::A, itr::I) where {I, A} = Pairs{eltype(I), eltype(A), I, A}(data, itr) -pairs(::Type{NamedTuple}) = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} where {V, N, names, T<:NTuple{N, Any}} - ## optional pretty printer: #const NamedTuplePair{N, V, names, T<:NTuple{N, Any}} = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} #export NamedTuplePair @@ -54,12 +48,12 @@ end @nospecialize Applied to a function argument name, hints to the compiler that the method -should not be specialized for different types of that argument, -but instead to use precisely the declared type for each argument. -This is only a hint for avoiding excess code generation. -Can be applied to an argument within a formal argument list, +implementation should not be specialized for different types of that argument, +but instead use the declared type for that argument. +It can be applied to an argument within a formal argument list, or in the function body. -When applied to an argument, the macro must wrap the entire argument expression. +When applied to an argument, the macro must wrap the entire argument expression, e.g., +`@nospecialize(x::Real)` or `@nospecialize(i::Integer...)` rather than wrapping just the argument name. When used in a function body, the macro must occur in statement position and before any code. @@ -87,6 +81,38 @@ end f(y) = [x for x in y] @specialize ``` + +!!! note + `@nospecialize` affects code generation but not inference: it limits the diversity + of the resulting native code, but it does not impose any limitations (beyond the + standard ones) on type-inference. + +# Example + +```julia +julia> f(A::AbstractArray) = g(A) +f (generic function with 1 method) + +julia> @noinline g(@nospecialize(A::AbstractArray)) = A[1] +g (generic function with 1 method) + +julia> @code_typed f([1.0]) +CodeInfo( +1 ─ %1 = invoke Main.g(_2::AbstractArray)::Float64 +└── return %1 +) => Float64 +``` + +Here, the `@nospecialize` annotation results in the equivalent of + +```julia +f(A::AbstractArray) = invoke(g, Tuple{AbstractArray}, A) +``` + +ensuring that only one version of native code will be generated for `g`, +one that is generic for any `AbstractArray`. +However, the specific return type is still inferred for both `g` and `f`, +and this is still used in optimizing the callers of `f` and `g`. """ macro nospecialize(vars...) if nfields(vars) === 1 @@ -151,9 +177,83 @@ macro isdefined(s::Symbol) return Expr(:escape, Expr(:isdefined, s)) end -macro _pure_meta() - return Expr(:meta, :pure) +""" + nameof(m::Module) -> Symbol + +Get the name of a `Module` as a [`Symbol`](@ref). + +# Examples +```jldoctest +julia> nameof(Base.Broadcast) +:Broadcast +``` +""" +nameof(m::Module) = ccall(:jl_module_name, Ref{Symbol}, (Any,), m) + +function _is_internal(__module__) + if ccall(:jl_base_relative_to, Any, (Any,), __module__)::Module === Core.Compiler || + nameof(__module__) === :Base + return true + end + return false end + +# can be used in place of `@assume_effects :total` (supposed to be used for bootstrapping) +macro _total_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#true, + #=:effect_free=#true, + #=:nothrow=#true, + #=:terminates_globally=#true, + #=:terminates_locally=#false, + #=:notaskstate=#true, + #=:inaccessiblememonly=#true)) +end +# 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, + #=:consistent=#true, + #=:effect_free=#true, + #=:nothrow=#false, + #=:terminates_globally=#true, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#true)) +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)) +end +# 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=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#true, + #=:notaskstate=#false, + #=:inaccessiblememonly=#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() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#true, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#true, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false)) +end + # another version of inlining that propagates an inbounds context macro _propagate_inbounds_meta() return Expr(:meta, :inline, :propagate_inbounds) @@ -210,7 +310,9 @@ See also: [`round`](@ref), [`trunc`](@ref), [`oftype`](@ref), [`reinterpret`](@r """ function convert end -convert(::Type{Union{}}, @nospecialize x) = throw(MethodError(convert, (Union{}, x))) +# ensure this is never ambiguous, and therefore fast for lookup +convert(T::Type{Union{}}, x...) = throw(ArgumentError("cannot convert a value to Union{} for assignment")) + convert(::Type{Type}, x::Type) = x # the ssair optimizer is strongly dependent on this method existing to avoid over-specialization # in the absence of inlining-enabled # (due to fields typed as `Type`, which is generally a bad idea) @@ -230,6 +332,26 @@ macro eval(mod, ex) return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex))) end +# use `@eval` here to directly form `:new` expressions avoid implicit `convert`s +# in order to achieve better effects inference +@eval struct Pairs{K, V, I, A} <: AbstractDict{K, V} + data::A + itr::I + Pairs{K, V, I, A}(data, itr) where {K, V, I, A} = $(Expr(:new, :(Pairs{K, V, I, A}), :(data isa A ? data : convert(A, data)), :(itr isa I ? itr : convert(I, itr)))) + Pairs{K, V}(data::A, itr::I) where {K, V, I, A} = $(Expr(:new, :(Pairs{K, V, I, A}), :data, :itr)) + Pairs{K}(data::A, itr::I) where {K, I, A} = $(Expr(:new, :(Pairs{K, eltype(A), I, A}), :data, :itr)) + Pairs(data::A, itr::I) where {I, A} = $(Expr(:new, :(Pairs{eltype(I), eltype(A), I, A}), :data, :itr)) +end +pairs(::Type{NamedTuple}) = Pairs{Symbol, V, NTuple{N, Symbol}, NamedTuple{names, T}} where {V, N, names, T<:NTuple{N, Any}} + +""" + Iterators.Pairs(values, keys) <: AbstractDict{eltype(keys), eltype(values)} + +Transforms an indexable container into a Dictionary-view of the same data. +Modifying the key-space of the underlying data may invalidate this object. +""" +Pairs + argtail(x, rest...) = rest """ @@ -282,13 +404,8 @@ function rename_unionall(@nospecialize(u)) if !isa(u, UnionAll) return u end - body = rename_unionall(u.body) - if body === u.body - body = u - else - body = UnionAll(u.var, body) - end var = u.var::TypeVar + body = UnionAll(var, rename_unionall(u.body)) nv = TypeVar(var.name, var.lb, var.ub) return UnionAll(nv, body{nv}) end @@ -306,8 +423,10 @@ function isvatuple(@nospecialize(t)) return false end -unwrapva(t::Core.TypeofVararg) = isdefined(t, :T) ? t.T : Any -unwrapva(@nospecialize(t)) = t +function unwrapva(@nospecialize(t)) + isa(t, Core.TypeofVararg) || return t + return isdefined(t, :T) ? t.T : Any +end function unconstrain_vararg_length(va::Core.TypeofVararg) # construct a new Vararg type where its length is unconstrained, @@ -335,7 +454,13 @@ function convert(::Type{T}, x::NTuple{N,Any}) where {N, T<:Tuple} if typeintersect(NTuple{N,Any}, T) === Union{} _tuple_error(T, x) end - cvt1(n) = (@inline; convert(fieldtype(T, n), getfield(x, n, #=boundscheck=#false))) + function cvt1(n) + @inline + Tn = fieldtype(T, n) + xn = getfield(x, n, #=boundscheck=#false) + xn isa Tn && return xn + return convert(Tn, xn) + end return ntuple(cvt1, Val(N))::NTuple{N,Any} end @@ -373,7 +498,7 @@ end """ oftype(x, y) -Convert `y` to the type of `x` (`convert(typeof(x), y)`). +Convert `y` to the type of `x` i.e. `convert(typeof(x), y)`. # Examples ```jldoctest @@ -388,7 +513,7 @@ julia> oftype(y, x) 4.0 ``` """ -oftype(x, y) = convert(typeof(x), y) +oftype(x, y) = y isa typeof(x) ? y : convert(typeof(x), y)::typeof(x) unsigned(x::Int) = reinterpret(UInt, x) signed(x::UInt) = reinterpret(Int, x) @@ -409,30 +534,26 @@ Neither `convert` nor `cconvert` should take a Julia object and turn it into a ` """ function cconvert end -cconvert(T::Type, x) = convert(T, x) # do the conversion eagerly in most cases +cconvert(T::Type, x) = x isa T ? x : convert(T, x) # do the conversion eagerly in most cases +cconvert(::Type{Union{}}, x...) = convert(Union{}, x...) cconvert(::Type{<:Ptr}, x) = x # but defer the conversion to Ptr to unsafe_convert unsafe_convert(::Type{T}, x::T) where {T} = x # unsafe_convert (like convert) defaults to assuming the convert occurred unsafe_convert(::Type{T}, x::T) where {T<:Ptr} = x # to resolve ambiguity with the next method unsafe_convert(::Type{P}, x::Ptr) where {P<:Ptr} = convert(P, x) """ - reinterpret(type, A) + reinterpret(type, x) -Change the type-interpretation of a block of memory. -For arrays, this constructs a view of the array with the same binary data as the given -array, but with the specified element type. -For example, -`reinterpret(Float32, UInt32(7))` interprets the 4 bytes corresponding to `UInt32(7)` as a +Change the type-interpretation of the binary data in the primitive value `x` +to that of the primitive type `type`. +The size of `type` 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). # Examples ```jldoctest julia> reinterpret(Float32, UInt32(7)) 1.0f-44 - -julia> reinterpret(Float32, UInt32[1 2 3 4 5]) -1×5 reinterpret(Float32, ::Matrix{UInt32}): - 1.0f-45 3.0f-45 4.0f-45 6.0f-45 7.0f-45 ``` """ reinterpret(::Type{T}, x) where {T} = bitcast(T, x) @@ -444,7 +565,7 @@ reinterpret(::Type{T}, x) where {T} = bitcast(T, x) Size, in bytes, of the canonical binary representation of the given `DataType` `T`, if any. Or the size, in bytes, of object `obj` if it is not a `DataType`. -See also [`summarysize`](@ref). +See also [`Base.summarysize`](@ref). # Examples ```jldoctest @@ -459,6 +580,17 @@ julia> sizeof(1.0) julia> sizeof(collect(1.0:10.0)) 80 + +julia> struct StructWithPadding + x::Int64 + flag::Bool + end + +julia> sizeof(StructWithPadding) # not the sum of `sizeof` of fields due to padding +16 + +julia> sizeof(Int64) + sizeof(Bool) # different from above +9 ``` If `DataType` `T` does not have a specific size, an error is thrown. @@ -571,7 +703,10 @@ end Using `@inbounds` may return incorrect results/crashes/corruption for out-of-bounds indices. The user is responsible for checking it manually. Only use `@inbounds` when it is certain from the information locally available - that all accesses are in bounds. + that all accesses are in bounds. In particular, using `1:length(A)` instead of + `eachindex(A)` in a function like the one above is _not_ safely inbounds because + the first index of `A` may not be `1` for all user defined types that subtype + `AbstractArray`. """ macro inbounds(blk) return Expr(:block, @@ -605,15 +740,13 @@ end # SimpleVector -function getindex(v::SimpleVector, i::Int) - @boundscheck if !(1 <= i <= length(v)) - throw(BoundsError(v,i)) - end - return ccall(:jl_svec_ref, Any, (Any, Int), v, i - 1) -end - +@eval getindex(v::SimpleVector, i::Int) = (@_foldable_meta; Core._svec_ref($(Expr(:boundscheck)), v, i)) function length(v::SimpleVector) - return ccall(:jl_svec_len, Int, (Any,), v) + @_total_meta + t = @_gc_preserve_begin v + len = unsafe_load(Ptr{Int}(pointer_from_objref(v))) + @_gc_preserve_end t + return len end firstindex(v::SimpleVector) = 1 lastindex(v::SimpleVector) = length(v) @@ -668,7 +801,7 @@ function isassigned end function isassigned(v::SimpleVector, i::Int) @boundscheck 1 <= i <= length(v) || return false - return ccall(:jl_svec_isassigned, Bool, (Any, Int), v, i - 1) + return true end @@ -688,6 +821,7 @@ struct Colon <: Function end const (:) = Colon() + """ Val(c) @@ -728,7 +862,7 @@ function invokelatest(@nospecialize(f), @nospecialize args...; kwargs...) if isempty(kwargs) return Core._call_latest(f, args...) end - return Core._call_latest(Core.kwfunc(f), kwargs, f, args...) + return Core._call_latest(Core.kwcall, kwargs, f, args...) end """ @@ -762,17 +896,23 @@ function invoke_in_world(world::UInt, @nospecialize(f), @nospecialize args...; k if isempty(kwargs) return Core._call_in_world(world, f, args...) end - return Core._call_in_world(world, Core.kwfunc(f), kwargs, f, args...) + return Core._call_in_world(world, Core.kwcall, kwargs, f, args...) end -# TODO: possibly make this an intrinsic -inferencebarrier(@nospecialize(x)) = Ref{Any}(x)[] +inferencebarrier(@nospecialize(x)) = compilerbarrier(:type, x) """ isempty(collection) -> Bool Determine whether a collection is empty (has no elements). +!!! warning + + `isempty(itr)` may consume the next element of a stateful iterator `itr` + unless an appropriate `Base.isdone(itr)` or `isempty` method is defined. + Use of `isempty` should therefore be avoided when writing generic + code which should support any iterator type. + # Examples ```jldoctest julia> isempty([]) @@ -846,7 +986,7 @@ function popfirst! end peek(stream[, T=UInt8]) Read and return a value of type `T` from a stream without advancing the current position -in the stream. +in the stream. See also [`startswith(stream, char_or_string)`](@ref). # Examples diff --git a/base/experimental.jl b/base/experimental.jl index d5af876cbbb23..cc8d368023b49 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -129,6 +129,15 @@ Set the maximum number of potentially-matching methods considered when running i for methods defined in the current module. This setting affects inference of calls with incomplete knowledge of the argument types. +The benefit of this setting is to avoid excessive compilation and reduce invalidation risks +in poorly-inferred cases. For example, when `@max_methods 2` is set and there are two +potentially-matching methods returning different types inside a function body, then Julia +will compile subsequent calls for both types so that the compiled function body accounts +for both possibilities. Also the compiled code is vulnerable to invalidations that would +happen when either of the two methods gets invalidated. This speculative compilation and +these invalidations can be avoided by setting `@max_methods 1` and allowing the compiled +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) @@ -136,6 +145,20 @@ macro max_methods(n::Int) return Expr(:meta, :max_methods, n) end +""" + Experimental.@max_methods n::Int function fname end + +Set the maximum number of potentially-matching methods considered when running inference +for the generic function `fname`. Overrides any module-level or global inference settings +for max_methods. This setting is global for the entire generic function (or more precisely +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`.") + (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,...} diff --git a/base/exports.jl b/base/exports.jl index 2d790f16b7986..ec151df0bfde2 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -18,6 +18,7 @@ export AbstractMatrix, AbstractRange, AbstractSet, + AbstractSlices, AbstractUnitRange, AbstractVector, AbstractVecOrMat, @@ -41,6 +42,7 @@ export ComplexF32, ComplexF16, ComposedFunction, + ColumnSlices, DenseMatrix, DenseVecOrMat, DenseVector, @@ -80,8 +82,10 @@ export RoundNearestTiesUp, RoundToZero, RoundUp, + RowSlices, Set, Some, + Slices, StepRange, StepRangeLen, StridedArray, @@ -122,6 +126,7 @@ export Cwstring, # Exceptions + CanonicalIndexError, CapturedException, CompositeException, DimensionMismatch, @@ -233,6 +238,7 @@ export bitrotate, bswap, cbrt, + fourthroot, ceil, cis, cispi, @@ -347,6 +353,7 @@ export tan, tand, tanh, + tanpi, trailing_ones, trailing_zeros, trunc, @@ -358,6 +365,7 @@ export zero, √, ∛, + ∜, ≈, ≉, @@ -440,6 +448,7 @@ export sortperm!, sortslices, dropdims, + stack, step, stride, strides, @@ -655,7 +664,6 @@ export # iteration iterate, - enumerate, # re-exported from Iterators zip, only, @@ -767,6 +775,7 @@ export # syntax esc, gensym, + @kwdef, macroexpand, @macroexpand1, @macroexpand, @@ -785,9 +794,13 @@ export parentmodule, pathof, pkgdir, + pkgversion, names, which, @isdefined, + @invoke, + invokelatest, + @invokelatest, # loading source files __precompile__, @@ -806,6 +819,7 @@ export atreplinit, exit, ntuple, + splat, # I/O and events close, @@ -880,6 +894,7 @@ export basename, dirname, expanduser, + contractuser, homedir, isabspath, isdirpath, @@ -929,6 +944,7 @@ export pwd, readlink, rm, + samefile, stat, symlink, tempdir, @@ -1005,6 +1021,7 @@ export @timev, @elapsed, @allocated, + @allocations, # tasks @sync, diff --git a/base/expr.jl b/base/expr.jl index 38e89d284c989..e45684f95a34f 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1,11 +1,15 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +isexpr(@nospecialize(ex), heads) = isa(ex, Expr) && in(ex.head, heads) +isexpr(@nospecialize(ex), heads, n::Int) = isa(ex, Expr) && in(ex.head, heads) && length(ex.args) == n +const is_expr = isexpr + ## symbols ## """ gensym([tag]) -Generates a symbol which will not conflict with other variable names. +Generates a symbol which will not conflict with other variable names (in the same module). """ gensym() = ccall(:jl_gensym, Ref{Symbol}, ()) @@ -71,6 +75,9 @@ function copy(c::CodeInfo) cnew.code = copy_exprargs(cnew.code) cnew.slotnames = copy(cnew.slotnames) cnew.slotflags = copy(cnew.slotflags) + if cnew.slottypes !== nothing + cnew.slottypes = copy(cnew.slottypes) + end cnew.codelocs = copy(cnew.codelocs) cnew.linetable = copy(cnew.linetable::Union{Vector{Any},Vector{Core.LineInfoNode}}) cnew.ssaflags = copy(cnew.ssaflags) @@ -189,7 +196,7 @@ Small functions typically do not need the `@inline` annotation, as the compiler does it automatically. By using `@inline` on bigger functions, an extra nudge can be given to the compiler to inline it. -`@inline` can be applied immediately before the definition or in its function body. +`@inline` can be applied immediately before a function definition or within a function body. ```julia # annotate long-form definition @@ -267,7 +274,7 @@ Small functions are typically inlined automatically. By using `@noinline` on small functions, auto-inlining can be prevented. -`@noinline` can be applied immediately before the definition or in its function body. +`@noinline` can be applied immediately before a function definition or within a function body. ```julia # annotate long-form definition @@ -335,53 +342,125 @@ macro noinline(x) return annotate_meta_def_or_block(x, :noinline) end -""" - @pure ex - @pure(ex) - -`@pure` gives the compiler a hint for the definition of a pure function, -helping for type inference. -This macro is intended for internal compiler use and may be subject to changes. """ -macro pure(ex) - esc(isa(ex, Expr) ? pushmeta!(ex, :pure) : ex) -end + @constprop setting [ex] -""" - @constprop setting ex - @constprop(setting, ex) +Control the mode of interprocedural constant propagation for the annotated function. -`@constprop` controls the mode of interprocedural constant propagation for the -annotated function. Two `setting`s are supported: +Two `setting`s are supported: -- `@constprop :aggressive ex`: apply constant propagation aggressively. +- `@constprop :aggressive [ex]`: apply constant propagation aggressively. For a method where the return type depends on the value of the arguments, this can yield improved inference results at the cost of additional compile time. -- `@constprop :none ex`: disable constant propagation. This can reduce compile +- `@constprop :none [ex]`: disable constant propagation. This can reduce compile times for functions that Julia might otherwise deem worthy of constant-propagation. Common cases are for functions with `Bool`- or `Symbol`-valued arguments or keyword arguments. + +`@constprop` can be applied immediately before a function definition or within a function body. + +```julia +# annotate long-form definition +@constprop :aggressive function longdef(x) + ... +end + +# annotate short-form definition +@constprop :aggressive shortdef(x) = ... + +# annotate anonymous function that a `do` block creates +f() do + @constprop :aggressive + ... +end +``` + +!!! compat "Julia 1.10" + The usage within a function body requires at least Julia 1.10. """ macro constprop(setting, ex) - if isa(setting, QuoteNode) - setting = setting.value + sym = constprop_setting(setting) + isa(ex, Expr) && return esc(pushmeta!(ex, sym)) + throw(ArgumentError(LazyString("Bad expression `", ex, "` in `@constprop settings ex`"))) +end +macro constprop(setting) + sym = constprop_setting(setting) + return Expr(:meta, sym) +end + +function constprop_setting(@nospecialize setting) + isa(setting, QuoteNode) && (setting = setting.value) + if setting === :aggressive + return :aggressive_constprop + elseif setting === :none + return :no_constprop end - setting === :aggressive && return esc(isa(ex, Expr) ? pushmeta!(ex, :aggressive_constprop) : ex) - setting === :none && return esc(isa(ex, Expr) ? pushmeta!(ex, :no_constprop) : ex) - throw(ArgumentError("@constprop $setting not supported")) + throw(ArgumentError(LazyString("@constprop "), setting, "not supported")) end """ - @assume_effects setting... ex - @assume_effects(setting..., ex) + @assume_effects setting... [ex] -`@assume_effects` overrides the compiler's effect modeling for the given method. -`ex` must be a method definition or `@ccall` expression. +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. + +# Examples +```jldoctest +julia> Base.@assume_effects :terminates_locally function pow(x) + # this :terminates_locally allows `pow` to be constant-folded + res = 1 + 1 < x < 20 || error("bad pow") + while x > 1 + res *= x + x -= 1 + end + return res + end +pow (generic function with 1 method) + +julia> code_typed() do + pow(12) + end +1-element Vector{Any}: + CodeInfo( +1 ─ return 479001600 +) => Int64 + +julia> code_typed() do + map((2,3,4)) do x + # this :terminates_locally allows this anonymous function to be constant-folded + Base.@assume_effects :terminates_locally + res = 1 + 1 < x < 20 || error("bad pow") + while x > 1 + res *= x + x -= 1 + end + return res + end + end +1-element Vector{Any}: + 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}) +``` + +!!! compat "Julia 1.10" + The usage within a function body requires at least Julia 1.10. !!! warning Improper use of this macro causes undefined behavior (including crashes, - incorrect answers, or other hard to track bugs). Use with care and only if - absolutely required. + incorrect answers, or other hard to track bugs). Use with care and only as a + last resort if absolutely required. Even in such a case, you SHOULD take all + possible steps to minimize the strength of the effect assertion (e.g., + do not use `:total` if `:nothrow` would have been sufficient). In general, each `setting` value makes an assertion about the behavior of the function, without requiring the compiler to prove that this behavior is indeed @@ -395,18 +474,24 @@ The following `setting`s are supported. - `:nothrow` - `:terminates_globally` - `:terminates_locally` +- `:notaskstate` +- `:inaccessiblememonly` +- `:foldable` +- `:removable` - `:total` +# Extended help + --- -# `:consistent` +## `:consistent` -The `:consistent` setting asserts that for egal inputs: +The `:consistent` setting asserts that for egal (`===`) inputs: - The manner of termination (return value, exception, non-termination) will always be the same. - If the method returns, the results will always be egal. !!! note - This in particular implies that the return value of the method must be - immutable. Multiple allocations of mutable objects (even with identical + This in particular implies that the method must not return a freshly allocated + mutable object. Multiple allocations of mutable objects (even with identical contents) are not egal. !!! note @@ -428,12 +513,19 @@ The `:consistent` setting asserts that for egal inputs: even for the same world age (e.g. because one ran in the interpreter, while the other was optimized). +!!! note + The `:consistent`-cy assertion currrently includes the assertion that the function + will not execute any undefined behavior (for any input). Note that undefined behavior + may technically cause the function to violate other effect assertions (such as + `:nothrow` or `:effect_free`) as well, but we do not model this, and all effects + except `:consistent` assume the absence of undefined behavior. + !!! note If `:consistent` functions terminate by throwing an exception, that exception itself is not required to meet the egality requirement specified above. --- -# `:effect_free` +## `:effect_free` The `:effect_free` setting asserts that the method is free of externally semantically visible side effects. The following is an incomplete list of externally semantically @@ -463,7 +555,7 @@ were not executed. valid for all world ages and limit use of this assertion accordingly. --- -# `:nothrow` +## `:nothrow` The `:nothrow` settings asserts that this method does not terminate abnormally (i.e. will either always return a value or never return). @@ -477,7 +569,7 @@ The `:nothrow` settings asserts that this method does not terminate abnormally `MethodErrors` and similar exceptions count as abnormal termination. --- -# `:terminates_globally` +## `:terminates_globally` The `:terminates_globally` settings asserts that this method will eventually terminate (either normally or abnormally), i.e. does not loop indefinitely. @@ -492,7 +584,7 @@ The `:terminates_globally` settings asserts that this method will eventually ter on a method that *technically*, but not *practically*, terminates. --- -# `:terminates_locally` +## `:terminates_locally` The `:terminates_locally` setting is like `:terminates_globally`, except that it only applies to syntactic control flow *within* the annotated method. It is thus @@ -503,54 +595,172 @@ non-termination if the method calls some other method that does not terminate. `:terminates_globally` implies `:terminates_locally`. --- -# `:total` +## `:notaskstate` + +The `:notaskstate` setting asserts that the method does not use or modify the +local task state (task local storage, RNG state, etc.) and may thus be safely +moved between tasks without observable results. + +!!! note + The implementation of exception handling makes use of state stored in the + task object. However, this state is currently not considered to be within + the scope of `:notaskstate` and is tracked separately using the `:nothrow` + effect. + +!!! note + The `:notaskstate` assertion concerns the state of the *currently running task*. + If a reference to a `Task` object is obtained by some other means that + does not consider which task is *currently* running, the `:notaskstate` + effect need not be tainted. This is true, even if said task object happens + to be `===` to the currently running task. + +!!! note + Access to task state usually also results in the tainting of other effects, + such as `:effect_free` (if task state is modified) or `:consistent` (if + task state is used in the computation of the result). In particular, + code that is not `:notaskstate`, but is `:effect_free` and `:consistent` + may still be dead-code-eliminated and thus promoted to `:total`. + +--- +## `:inaccessiblememonly` + +The `:inaccessiblememonly` setting asserts that the method does not access or modify +externally accessible mutable memory. This means the method can access or modify mutable +memory for newly allocated objects that is not accessible by other methods or top-level +execution before return from the method, but it can not access or modify any mutable +global state or mutable memory pointed to by its arguments. + +!!! note + Below is an incomplete list of examples that invalidate this assumption: + - a global reference or `getglobal` call to access a mutable global variable + - a global assignment or `setglobal!` call to perform assignment to a non-constant global variable + - `setfield!` call that changes a field of a global mutable variable + +!!! note + This `:inaccessiblememonly` assertion covers any other methods called by the annotated method. + +--- +## `:foldable` -This `setting` combines the following other assertions: +This setting is a convenient shortcut for the set of effects that the compiler +requires to be guaranteed to constant fold a call at compile time. It is +currently equivalent to the following `setting`s: - `:consistent` - `:effect_free` -- `:nothrow` - `:terminates_globally` -and is a convenient shortcut. !!! note - `@assume_effects :total` is similar to `@Base.pure` with the primary - distinction that the `:consistent`-cy requirement applies world-age wise rather - than globally as described above. However, in particular, a method annotated - `@Base.pure` is always `:total`. + This list in particular does not include `:nothrow`. The compiler will still + attempt constant propagation and note any thrown error at compile time. Note + however, that by the `:consistent`-cy requirements, any such annotated call + must consistently throw given the same argument values. + +!!! note + An explicit `@inbounds` annotation inside the function will also disable + constant folding and not be overriden by `:foldable`. + +--- +## `:removable` + +This setting is a convenient shortcut for the set of effects that the compiler +requires to be guaranteed to delete a call whose result is unused at compile time. +It is currently equivalent to the following `setting`s: +- `:effect_free` +- `:nothrow` +- `:terminates_globally` + +--- +## `:total` + +This `setting` is the maximum possible set of effects. It currently implies +the following other `setting`s: +- `:consistent` +- `:effect_free` +- `:nothrow` +- `:terminates_globally` +- `:notaskstate` +- `:inaccessiblememonly` + +!!! warning + `:total` is a very strong assertion and will likely gain additional semantics + in future versions of Julia (e.g. if additional effects are added and included + in the definition of `:total`). As a result, it should be used with care. + Whenever possible, prefer to use the minimum possible set of specific effect + assertions required for a particular application. In cases where a large + number of effect overrides apply to a set of functions, a custom macro is + recommended over the use of `:total`. + +--- +## Negated effects + +Effect names may be prefixed by `!` to indicate that the effect should be removed +from an earlier meta effect. For example, `:total !:nothrow` indicates that while +the call is generally total, it may however throw. """ macro assume_effects(args...) - (consistent, effect_free, nothrow, terminates_globally, terminates_locally) = - (false, false, false, false, false, false) - for setting in args[1:end-1] - if isa(setting, QuoteNode) - setting = setting.value - end + lastex = args[end] + inner = unwrap_macrocalls(lastex) + if is_function_def(inner) + ex = lastex + idx = length(args)-1 + elseif isexpr(lastex, :macrocall) && lastex.args[1] === Symbol("@ccall") + ex = lastex + idx = length(args)-1 + else # anonymous function case + ex = nothing + idx = length(args) + end + (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly) = + (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 = true + consistent = val elseif setting === :effect_free - effect_free = true + effect_free = val elseif setting === :nothrow - nothrow = true + nothrow = val elseif setting === :terminates_globally - terminates_globally = true + terminates_globally = val elseif setting === :terminates_locally - terminates_locally = true + terminates_locally = val + elseif setting === :notaskstate + notaskstate = val + elseif setting === :inaccessiblememonly + inaccessiblememonly = val + elseif setting === :foldable + consistent = effect_free = terminates_globally = val + elseif setting === :removable + effect_free = nothrow = terminates_globally = val elseif setting === :total - consistent = effect_free = nothrow = terminates_globally = true + consistent = effect_free = nothrow = terminates_globally = notaskstate = inaccessiblememonly = val else - throw(ArgumentError("@assume_effects $setting not supported")) + throw(ArgumentError("@assume_effects $org_setting not supported")) end end - ex = args[end] - isa(ex, Expr) || throw(ArgumentError("Bad expression `$ex` in @constprop [settings] ex")) - if ex.head === :macrocall && ex.args[1] == Symbol("@ccall") + if is_function_def(inner) + return esc(pushmeta!(ex, :purity, + consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly)) + 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 + consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, ))) return esc(ex) + else # anonymous function case + return Expr(:meta, Expr(:purity, + consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly)) + end +end + +function compute_assumed_setting(@nospecialize(setting), val::Bool=true) + if isexpr(setting, :call) && setting.args[1] === :(!) + return compute_assumed_setting(setting.args[2], !val) + elseif isa(setting, QuoteNode) + return compute_assumed_setting(setting.value, val) + else + return (setting, val) end - return esc(pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally)) end """ @@ -719,13 +929,33 @@ function remove_linenums!(src::CodeInfo) return src end +replace_linenums!(ex, ln::LineNumberNode) = ex +function replace_linenums!(ex::Expr, ln::LineNumberNode) + if ex.head === :block || ex.head === :quote + # replace line number expressions from metadata (not argument literal or inert) position + map!(ex.args, ex.args) do @nospecialize(x) + isa(x, Expr) && x.head === :line && length(x.args) == 1 && return Expr(:line, ln.line) + isa(x, Expr) && x.head === :line && length(x.args) == 2 && return Expr(:line, ln.line, ln.file) + isa(x, LineNumberNode) && return ln + return x + end + end + # preserve any linenums inside `esc(...)` guards + if ex.head !== :escape + for subex in ex.args + subex isa Expr && replace_linenums!(subex, ln) + end + end + return ex +end + macro generated() return Expr(:generated) end """ @generated f - @generated(f) + `@generated` is used to annotate a function which will be generated. In the body of the generated function, only types of arguments can be read (not the values). The function returns a quoted expression evaluated when the @@ -734,7 +964,7 @@ the global scope or depending on mutable elements. See [Metaprogramming](@ref) for further details. -## Example: +# Examples ```jldoctest julia> @generated function bar(x) if x <: Integer @@ -762,10 +992,7 @@ macro generated(f) Expr(:block, lno, Expr(:if, Expr(:generated), - # https://github.com/JuliaLang/julia/issues/25678 - Expr(:block, - :(local $tmp = $body), - :(if $tmp isa $(GlobalRef(Core, :CodeInfo)); return $tmp; else $tmp; end)), + body, Expr(:block, Expr(:meta, :generated_only), Expr(:return, nothing)))))) @@ -808,6 +1035,7 @@ This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. +# Examples ```jldoctest julia> mutable struct Atomic{T}; @atomic x::T; end @@ -907,6 +1135,7 @@ This operation translates to a `swapproperty!(a.b, :x, new)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. +# Examples ```jldoctest julia> mutable struct Atomic{T}; @atomic x::T; end @@ -953,6 +1182,7 @@ This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` ca See [Per-field atomics](@ref man-atomics) section in the manual for more details. +# Examples ```jldoctest julia> mutable struct Atomic{T}; @atomic x::T; end @@ -968,7 +1198,7 @@ julia> @atomic a.x # fetch field x of a, with sequential consistency julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency (old = 2, success = false) -julia> xchg = 2 => 0; # replace field x of a with 0 if it was 1, with sequential consistency +julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency julia> @atomicreplace a.x xchg (old = 2, success = true) diff --git a/base/fastmath.jl b/base/fastmath.jl index c01a8a5b225f7..7865736f8a776 100644 --- a/base/fastmath.jl +++ b/base/fastmath.jl @@ -28,7 +28,7 @@ module FastMath export @fastmath import Core.Intrinsics: sqrt_llvm_fast, neg_float_fast, - add_float_fast, sub_float_fast, mul_float_fast, div_float_fast, rem_float_fast, + add_float_fast, sub_float_fast, mul_float_fast, div_float_fast, eq_float_fast, ne_float_fast, lt_float_fast, le_float_fast const fast_op = @@ -41,6 +41,8 @@ const fast_op = :!= => :ne_fast, :< => :lt_fast, :<= => :le_fast, + :> => :gt_fast, + :>= => :ge_fast, :abs => :abs_fast, :abs2 => :abs2_fast, :cmp => :cmp_fast, @@ -82,7 +84,12 @@ const fast_op = :sinh => :sinh_fast, :sqrt => :sqrt_fast, :tan => :tan_fast, - :tanh => :tanh_fast) + :tanh => :tanh_fast, + # reductions + :maximum => :maximum_fast, + :minimum => :minimum_fast, + :maximum! => :maximum!_fast, + :minimum! => :minimum!_fast) const rewrite_op = Dict(:+= => :+, @@ -166,7 +173,6 @@ add_fast(x::T, y::T) where {T<:FloatTypes} = add_float_fast(x, y) sub_fast(x::T, y::T) where {T<:FloatTypes} = sub_float_fast(x, y) mul_fast(x::T, y::T) where {T<:FloatTypes} = mul_float_fast(x, y) div_fast(x::T, y::T) where {T<:FloatTypes} = div_float_fast(x, y) -rem_fast(x::T, y::T) where {T<:FloatTypes} = rem_float_fast(x, y) add_fast(x::T, y::T, zs::T...) where {T<:FloatTypes} = add_fast(add_fast(x, y), zs...) @@ -182,6 +188,8 @@ eq_fast(x::T, y::T) where {T<:FloatTypes} = eq_float_fast(x, y) ne_fast(x::T, y::T) where {T<:FloatTypes} = ne_float_fast(x, y) lt_fast(x::T, y::T) where {T<:FloatTypes} = lt_float_fast(x, y) le_fast(x::T, y::T) where {T<:FloatTypes} = le_float_fast(x, y) +gt_fast(x, y) = lt_fast(y, x) +ge_fast(x, y) = le_fast(y, x) isinf_fast(x) = false isfinite_fast(x) = true @@ -279,8 +287,8 @@ exp10_fast(x::Union{Float32,Float64}) = Base.Math.exp10_fast(x) # builtins -pow_fast(x::Float32, y::Integer) = ccall("llvm.powi.f32", llvmcall, Float32, (Float32, Int32), x, y) -pow_fast(x::Float64, y::Integer) = ccall("llvm.powi.f64", llvmcall, Float64, (Float64, Int32), x, y) +pow_fast(x::Float32, y::Integer) = ccall("llvm.powi.f32.i32", llvmcall, Float32, (Float32, Int32), x, y) +pow_fast(x::Float64, y::Integer) = ccall("llvm.powi.f64.i32", llvmcall, Float64, (Float64, Int32), x, y) pow_fast(x::FloatTypes, ::Val{p}) where {p} = pow_fast(x, p) # inlines already via llvm.powi @inline pow_fast(x, v::Val) = Base.literal_pow(^, x, v) @@ -295,6 +303,11 @@ sincos_fast(v::AbstractFloat) = (sin_fast(v), cos_fast(v)) sincos_fast(v::Real) = sincos_fast(float(v)::AbstractFloat) sincos_fast(v) = (sin_fast(v), cos_fast(v)) + +function rem_fast(x::T, y::T) where {T<:FloatTypes} + return @fastmath copysign(Base.rem_internal(abs(x), abs(y)), x) +end + @fastmath begin hypot_fast(x::T, y::T) where {T<:FloatTypes} = sqrt(x*x + y*y) @@ -362,4 +375,27 @@ for f in (:^, :atan, :hypot, :log) end end +# Reductions + +maximum_fast(a; kw...) = Base.reduce(max_fast, a; kw...) +minimum_fast(a; kw...) = Base.reduce(min_fast, a; kw...) + +maximum_fast(f, a; kw...) = Base.mapreduce(f, max_fast, a; kw...) +minimum_fast(f, a; kw...) = Base.mapreduce(f, min_fast, a; kw...) + +Base.reducedim_init(f, ::typeof(max_fast), A::AbstractArray, region) = + Base.reducedim_init(f, max, A::AbstractArray, region) +Base.reducedim_init(f, ::typeof(min_fast), A::AbstractArray, region) = + Base.reducedim_init(f, min, A::AbstractArray, region) + +maximum!_fast(r::AbstractArray, A::AbstractArray; kw...) = + maximum!_fast(identity, r, A; kw...) +minimum!_fast(r::AbstractArray, A::AbstractArray; kw...) = + minimum!_fast(identity, r, A; kw...) + +maximum!_fast(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) = + Base.mapreducedim!(f, max_fast, Base.initarray!(r, f, max, init, A), A) +minimum!_fast(f::Function, r::AbstractArray, A::AbstractArray; init::Bool=true) = + Base.mapreducedim!(f, min_fast, Base.initarray!(r, f, min, init, A), A) + end diff --git a/base/file.jl b/base/file.jl index afab2177e061f..866e82b6e39c2 100644 --- a/base/file.jl +++ b/base/file.jl @@ -32,7 +32,7 @@ export # get and set current directory """ - pwd() -> AbstractString + pwd() -> String Get the current working directory. @@ -105,7 +105,7 @@ if Sys.iswindows() end else function cd(f::Function, dir::AbstractString) - fd = ccall(:open, Int32, (Cstring, Int32), :., 0) + fd = ccall(:open, Int32, (Cstring, Int32, UInt32...), :., 0) systemerror(:open, fd == -1) try cd(dir) @@ -294,7 +294,7 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) rm(joinpath(path, p), force=force, recursive=true) end catch err - if !(force && isa(err, IOError) && err.code==Base.UV_EACCES) + if !(isa(err, IOError) && err.code==Base.UV_EACCES) rethrow(err) end end @@ -429,6 +429,7 @@ end """ touch(path::AbstractString) + touch(fd::File) Update the last-modified timestamp on a file to the current time. @@ -454,19 +455,14 @@ We can see the [`mtime`](@ref) has been modified by `touch`. function touch(path::AbstractString) f = open(path, JL_O_WRONLY | JL_O_CREAT, 0o0666) try - if Sys.isunix() - ret = ccall(:futimes, Cint, (Cint, Ptr{Cvoid}), fd(f), C_NULL) - systemerror(:futimes, ret != 0, extrainfo=path) - else - t = time() - futime(f,t,t) - end + touch(f) finally close(f) end path end + """ tempdir() @@ -548,7 +544,10 @@ function temp_cleanup_purge(; force::Bool=false) end !ispath(path) && delete!(TEMP_CLEANUP, path) catch ex - @warn "temp cleanup" _group=:file exception=(ex, catch_backtrace()) + @warn """ + Failed to clean up temporary path $(repr(path)) + $ex + """ _group=:file end end end @@ -633,7 +632,7 @@ end # os-test 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 -the very remote posibility of two simultaneous calls to `tempname` generating +the very remote possibility of two simultaneous calls to `tempname` generating the same file name. The name is guaranteed to differ from all files already existing at the time of the call to `tempname`. @@ -679,8 +678,9 @@ mktemp(parent) mktempdir(parent=tempdir(); prefix=$(repr(temp_prefix)), cleanup=true) -> path Create a temporary directory in the `parent` directory with a name -constructed from the given prefix and a random suffix, and return its path. -Additionally, any trailing `X` characters may be replaced with random characters. +constructed from the given `prefix` and a random suffix, and return its path. +Additionally, on some platforms, any trailing `'X'` characters in `prefix` may be replaced +with random characters. If `parent` does not exist, throw an error. The `cleanup` option controls whether the temporary directory is automatically deleted when the process exits. @@ -797,6 +797,8 @@ By default, `readdir` sorts the list of names it returns. If you want to skip sorting the names and get them in the order that the file system lists them, you can use `readdir(dir, sort=false)` to opt out of sorting. +See also: [`walkdir`](@ref). + !!! compat "Julia 1.4" The `join` and `sort` keyword arguments require at least Julia 1.4. @@ -842,7 +844,7 @@ julia> readdir("base", join=true) ⋮ "base/version_git.sh" "base/views.jl" - "base/weakkeydict.jl"``` + "base/weakkeydict.jl" julia> readdir(abspath("base"), join=true) 145-element Array{String,1}: @@ -896,6 +898,8 @@ If `walkdir` or `stat` encounters a `IOError` it will rethrow the error by defau A custom error handling function can be provided through `onerror` keyword argument. `onerror` is called with a `IOError` as argument. +See also: [`readdir`](@ref). + # Examples ```julia for (root, dirs, files) in walkdir(".") @@ -1062,7 +1066,7 @@ See also: [`hardlink`](@ref). !!! compat "Julia 1.6" The `dir_target` keyword argument was added in Julia 1.6. Prior to this, - symlinks to nonexistant paths on windows would always be file symlinks, and + symlinks to nonexistent paths on windows would always be file symlinks, and relative symlinks to directories were not supported. """ function symlink(target::AbstractString, link::AbstractString; @@ -1109,7 +1113,7 @@ function symlink(target::AbstractString, link::AbstractString; end """ - readlink(path::AbstractString) -> AbstractString + readlink(path::AbstractString) -> String Return the target location a symbolic link `path` points to. """ diff --git a/base/filesystem.jl b/base/filesystem.jl index cb268de1d9b83..63fe4281f6e59 100644 --- a/base/filesystem.jl +++ b/base/filesystem.jl @@ -48,6 +48,22 @@ export File, JL_O_SEQUENTIAL, JL_O_RANDOM, JL_O_NOCTTY, + JL_O_NOCTTY, + JL_O_NONBLOCK, + JL_O_NDELAY, + JL_O_SYNC, + JL_O_FSYNC, + JL_O_ASYNC, + JL_O_LARGEFILE, + JL_O_DIRECTORY, + JL_O_NOFOLLOW, + JL_O_CLOEXEC, + JL_O_DIRECT, + JL_O_NOATIME, + JL_O_PATH, + JL_O_TMPFILE, + JL_O_DSYNC, + JL_O_RSYNC, S_IRUSR, S_IWUSR, S_IXUSR, S_IRWXU, S_IRGRP, S_IWGRP, S_IXGRP, S_IRWXG, S_IROTH, S_IWOTH, S_IXOTH, S_IRWXO @@ -263,4 +279,16 @@ end fd(f::File) = f.handle stat(f::File) = stat(f.handle) +function touch(f::File) + @static if Sys.isunix() + ret = ccall(:futimes, Cint, (Cint, Ptr{Cvoid}), fd(f), C_NULL) + systemerror(:futimes, ret != 0) + else + t = time() + futime(f, t, t) + end + f +end + + end diff --git a/base/float.jl b/base/float.jl index 8d7381ce3be4a..fad7146655ade 100644 --- a/base/float.jl +++ b/base/float.jl @@ -101,6 +101,8 @@ exponent_one(::Type{Float16}) = 0x3c00 exponent_half(::Type{Float16}) = 0x3800 significand_mask(::Type{Float16}) = 0x03ff +mantissa(x::T) where {T} = reinterpret(Unsigned, x) & significand_mask(T) + for T in (Float16, Float32, Float64) @eval significand_bits(::Type{$T}) = $(trailing_ones(significand_mask(T))) @eval exponent_bits(::Type{$T}) = $(sizeof(T)*8 - significand_bits(T) - 1) @@ -135,6 +137,17 @@ i.e. the maximum integer value representable by [`exponent_bits(T)`](@ref) bits. """ function exponent_raw_max end +""" + uabs(x::Integer) + +Return the absolute value of `x`, possibly returning a different type should the +operation be susceptible to overflow. This typically arises when `x` is a two's complement +signed integer, so that `abs(typemin(x)) == typemin(x) < 0`, in which case the result of +`uabs(x)` will be an unsigned integer of the same size. +""" +uabs(x::Integer) = abs(x) +uabs(x::BitSigned) = unsigned(abs(x)) + ## conversions to floating-point ## # TODO: deprecate in 2.0 @@ -165,38 +178,50 @@ promote_rule(::Type{Float16}, ::Type{UInt128}) = Float16 promote_rule(::Type{Float16}, ::Type{Int128}) = Float16 function Float64(x::UInt128) - x == 0 && return 0.0 - n = 128-leading_zeros(x) # ndigits0z(x,2) - if n <= 53 - y = ((x % UInt64) << (53-n)) & 0x000f_ffff_ffff_ffff - else - y = ((x >> (n-54)) % UInt64) & 0x001f_ffff_ffff_ffff # keep 1 extra bit - y = (y+1)>>1 # round, ties up (extra leading bit in case of next exponent) - y &= ~UInt64(trailing_zeros(x) == (n-54)) # fix last bit to round to even + if x < UInt128(1) << 104 # Can fit it in two 52 bits mantissas + low_exp = 0x1p52 + high_exp = 0x1p104 + low_bits = (x % UInt64) & Base.significand_mask(Float64) + low_value = reinterpret(Float64, reinterpret(UInt64, low_exp) | low_bits) - low_exp + high_bits = ((x >> 52) % UInt64) + high_value = reinterpret(Float64, reinterpret(UInt64, high_exp) | high_bits) - high_exp + low_value + high_value + else # Large enough that low bits only affect rounding, pack low bits + low_exp = 0x1p76 + high_exp = 0x1p128 + low_bits = ((x >> 12) % UInt64) >> 12 | (x % UInt64) & 0xFFFFFF + low_value = reinterpret(Float64, reinterpret(UInt64, low_exp) | low_bits) - low_exp + high_bits = ((x >> 76) % UInt64) + high_value = reinterpret(Float64, reinterpret(UInt64, high_exp) | high_bits) - high_exp + low_value + high_value end - d = ((n+1022) % UInt64) << 52 - reinterpret(Float64, d + y) end function Float64(x::Int128) - x == 0 && return 0.0 - s = ((x >>> 64) % UInt64) & 0x8000_0000_0000_0000 # sign bit - x = abs(x) % UInt128 - n = 128-leading_zeros(x) # ndigits0z(x,2) - if n <= 53 - y = ((x % UInt64) << (53-n)) & 0x000f_ffff_ffff_ffff - else - y = ((x >> (n-54)) % UInt64) & 0x001f_ffff_ffff_ffff # keep 1 extra bit - y = (y+1)>>1 # round, ties up (extra leading bit in case of next exponent) - y &= ~UInt64(trailing_zeros(x) == (n-54)) # fix last bit to round to even + sign_bit = ((x >> 127) % UInt64) << 63 + ux = uabs(x) + if ux < UInt128(1) << 104 # Can fit it in two 52 bits mantissas + low_exp = 0x1p52 + high_exp = 0x1p104 + low_bits = (ux % UInt64) & Base.significand_mask(Float64) + low_value = reinterpret(Float64, reinterpret(UInt64, low_exp) | low_bits) - low_exp + high_bits = ((ux >> 52) % UInt64) + high_value = reinterpret(Float64, reinterpret(UInt64, high_exp) | high_bits) - high_exp + reinterpret(Float64, sign_bit | reinterpret(UInt64, low_value + high_value)) + else # Large enough that low bits only affect rounding, pack low bits + low_exp = 0x1p76 + high_exp = 0x1p128 + low_bits = ((ux >> 12) % UInt64) >> 12 | (ux % UInt64) & 0xFFFFFF + low_value = reinterpret(Float64, reinterpret(UInt64, low_exp) | low_bits) - low_exp + high_bits = ((ux >> 76) % UInt64) + high_value = reinterpret(Float64, reinterpret(UInt64, high_exp) | high_bits) - high_exp + reinterpret(Float64, sign_bit | reinterpret(UInt64, low_value + high_value)) end - d = ((n+1022) % UInt64) << 52 - reinterpret(Float64, s | d + y) end function Float32(x::UInt128) x == 0 && return 0f0 - n = 128-leading_zeros(x) # ndigits0z(x,2) + n = top_set_bit(x) # ndigits0z(x,2) if n <= 24 y = ((x % UInt32) << (24-n)) & 0x007f_ffff else @@ -212,7 +237,7 @@ function Float32(x::Int128) x == 0 && return 0f0 s = ((x >>> 96) % UInt32) & 0x8000_0000 # sign bit x = abs(x) % UInt128 - n = 128-leading_zeros(x) # ndigits0z(x,2) + n = top_set_bit(x) # ndigits0z(x,2) if n <= 24 y = ((x % UInt32) << (24-n)) & 0x007f_ffff else @@ -225,8 +250,8 @@ function Float32(x::Int128) end # TODO: optimize -Float16(x::UInt128) = convert(Float16, Float32(x)) -Float16(x::Int128) = convert(Float16, Float32(x)) +Float16(x::UInt128) = convert(Float16, Float64(x)) +Float16(x::Int128) = convert(Float16, Float64(x)) Float16(x::Float32) = fptrunc(Float16, x) Float16(x::Float64) = fptrunc(Float16, x) @@ -285,6 +310,7 @@ Float64 """ float(::Type{T}) where {T<:Number} = typeof(float(zero(T))) float(::Type{T}) where {T<:AbstractFloat} = T +float(::Type{Union{}}, slurp...) = Union{}(0.0) """ unsafe_trunc(T, x) @@ -358,6 +384,12 @@ floor(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,round(x, RoundDo ceil(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,round(x, RoundUp)) round(::Type{T}, x::AbstractFloat) where {T<:Integer} = trunc(T,round(x, RoundNearest)) +# Bool +trunc(::Type{Bool}, x::AbstractFloat) = (-1 < x < 2) ? 1 <= x : throw(InexactError(:trunc, Bool, x)) +floor(::Type{Bool}, x::AbstractFloat) = (0 <= x < 2) ? 1 <= x : throw(InexactError(:floor, Bool, x)) +ceil(::Type{Bool}, x::AbstractFloat) = (-1 < x <= 1) ? 0 < x : throw(InexactError(:ceil, Bool, x)) +round(::Type{Bool}, x::AbstractFloat) = (-0.5 <= x < 1.5) ? 0.5 < x : throw(InexactError(:round, Bool, x)) + round(x::IEEEFloat, r::RoundingMode{:ToZero}) = trunc_llvm(x) round(x::IEEEFloat, r::RoundingMode{:Down}) = floor_llvm(x) round(x::IEEEFloat, r::RoundingMode{:Up}) = ceil_llvm(x) @@ -385,11 +417,109 @@ muladd(x::T, y::T, z::T) where {T<:IEEEFloat} = muladd_float(x, y, z) # TODO: faster floating point fld? # TODO: faster floating point mod? -rem(x::T, y::T) where {T<:IEEEFloat} = rem_float(x, y) +function unbiased_exponent(x::T) where {T<:IEEEFloat} + return (reinterpret(Unsigned, x) & exponent_mask(T)) >> significand_bits(T) +end + +function explicit_mantissa_noinfnan(x::T) where {T<:IEEEFloat} + m = mantissa(x) + issubnormal(x) || (m |= significand_mask(T) + uinttype(T)(1)) + return m +end + +function _to_float(number::U, ep) where {U<:Unsigned} + F = floattype(U) + S = signed(U) + epint = unsafe_trunc(S,ep) + lz::signed(U) = unsafe_trunc(S, Core.Intrinsics.ctlz_int(number) - U(exponent_bits(F))) + number <<= lz + epint -= lz + bits = U(0) + if epint >= 0 + bits = number & significand_mask(F) + bits |= ((epint + S(1)) << significand_bits(F)) & exponent_mask(F) + else + bits = (number >> -epint) & significand_mask(F) + end + return reinterpret(F, bits) +end + +@assume_effects :terminates_locally :nothrow function rem_internal(x::T, y::T) where {T<:IEEEFloat} + xuint = reinterpret(Unsigned, x) + yuint = reinterpret(Unsigned, y) + if xuint <= yuint + if xuint < yuint + return x + end + return zero(T) + end + + e_x = unbiased_exponent(x) + e_y = unbiased_exponent(y) + # Most common case where |y| is "very normal" and |x/y| < 2^EXPONENT_WIDTH + if e_y > (significand_bits(T)) && (e_x - e_y) <= (exponent_bits(T)) + m_x = explicit_mantissa_noinfnan(x) + m_y = explicit_mantissa_noinfnan(y) + d = urem_int((m_x << (e_x - e_y)), m_y) + iszero(d) && return zero(T) + return _to_float(d, e_y - uinttype(T)(1)) + end + # Both are subnormals + if e_x == 0 && e_y == 0 + return reinterpret(T, urem_int(xuint, yuint) & significand_mask(T)) + end + + m_x = explicit_mantissa_noinfnan(x) + e_x -= uinttype(T)(1) + m_y = explicit_mantissa_noinfnan(y) + lz_m_y = uinttype(T)(exponent_bits(T)) + if e_y > 0 + e_y -= uinttype(T)(1) + else + m_y = mantissa(y) + lz_m_y = Core.Intrinsics.ctlz_int(m_y) + end -cld(x::T, y::T) where {T<:AbstractFloat} = -fld(-x,y) + tz_m_y = Core.Intrinsics.cttz_int(m_y) + sides_zeroes_cnt = lz_m_y + tz_m_y + + # n>0 + exp_diff = e_x - e_y + # Shift hy right until the end or n = 0 + right_shift = min(exp_diff, tz_m_y) + m_y >>= right_shift + exp_diff -= right_shift + e_y += right_shift + # Shift hx left until the end or n = 0 + left_shift = min(exp_diff, uinttype(T)(exponent_bits(T))) + m_x <<= left_shift + exp_diff -= left_shift + + m_x = urem_int(m_x, m_y) + iszero(m_x) && return zero(T) + iszero(exp_diff) && return _to_float(m_x, e_y) + + while exp_diff > sides_zeroes_cnt + exp_diff -= sides_zeroes_cnt + m_x <<= sides_zeroes_cnt + m_x = urem_int(m_x, m_y) + end + m_x <<= exp_diff + m_x = urem_int(m_x, m_y) + return _to_float(m_x, e_y) +end -function mod(x::T, y::T) where T<:AbstractFloat +function rem(x::T, y::T) where {T<:IEEEFloat} + if isfinite(x) && !iszero(x) && isfinite(y) && !iszero(y) + return copysign(rem_internal(abs(x), abs(y)), x) + elseif isinf(x) || isnan(y) || iszero(y) # y can still be Inf + return T(NaN) + else + return x + end +end + +function mod(x::T, y::T) where {T<:AbstractFloat} r = rem(x,y) if r == 0 copysign(r,y) @@ -490,7 +620,7 @@ See also: [`iszero`](@ref), [`isone`](@ref), [`isinf`](@ref), [`ismissing`](@ref isnan(x::AbstractFloat) = (x != x)::Bool isnan(x::Number) = false -isfinite(x::AbstractFloat) = x - x == 0 +isfinite(x::AbstractFloat) = !isnan(x - x) isfinite(x::Real) = decompose(x)[3] != 0 isfinite(x::Integer) = true @@ -502,6 +632,7 @@ Test whether a number is infinite. See also: [`Inf`](@ref), [`iszero`](@ref), [`isfinite`](@ref), [`isnan`](@ref). """ isinf(x::Real) = !isnan(x) & !isfinite(x) +isinf(x::IEEEFloat) = abs(x) === oftype(x, Inf) const hx_NaN = hash_uint64(reinterpret(UInt64, NaN)) let Tf = Float64, Tu = UInt64, Ti = Int64 @@ -658,17 +789,6 @@ end precision(::Type{T}; base::Integer=2) where {T<:AbstractFloat} = _precision(T, base) precision(::T; base::Integer=2) where {T<:AbstractFloat} = precision(T; base) -""" - uabs(x::Integer) - -Return the absolute value of `x`, possibly returning a different type should the -operation be susceptible to overflow. This typically arises when `x` is a two's complement -signed integer, so that `abs(typemin(x)) == typemin(x) < 0`, in which case the result of -`uabs(x)` will be an unsigned integer of the same size. -""" -uabs(x::Integer) = abs(x) -uabs(x::BitSigned) = unsigned(abs(x)) - """ nextfloat(x::AbstractFloat, n::Integer) @@ -791,6 +911,21 @@ end issubnormal(f) -> Bool Test whether a floating point number is subnormal. + +An IEEE floating point number is [subnormal](https://en.wikipedia.org/wiki/Subnormal_number) +when its exponent bits are zero and its significand is not zero. + +# Examples +```jldoctest +julia> floatmin(Float32) +1.1754944f-38 + +julia> issubnormal(1.0f-37) +false + +julia> issubnormal(1.0f-38) +true +``` """ function issubnormal(x::T) where {T<:IEEEFloat} y = reinterpret(Unsigned, x) diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index d1164005d3e44..9b8ca4b04ee28 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -97,15 +97,14 @@ julia> round(357.913; sigdigits=4, base=2) Rounding to specified digits in bases other than 2 can be inexact when operating on binary floating point numbers. For example, the [`Float64`](@ref) value represented by `1.15` is actually *less* than 1.15, yet will be - rounded to 1.2. + rounded to 1.2. For example: - # Examples - ```jldoctest; setup = :(using Printf) + ```jldoctest julia> x = 1.15 1.15 - julia> @sprintf "%.20f" x - "1.14999999999999991118" + julia> big(1.15) + 1.149999999999999911182158029987476766109466552734375 julia> x < 115//100 true @@ -236,14 +235,18 @@ function round(x::T, ::RoundingMode{:NearestTiesUp}) where {T <: AbstractFloat} copysign(floor((x + (T(0.25) - eps(T(0.5)))) + (T(0.25) + eps(T(0.5)))), x) end +function Base.round(x::AbstractFloat, ::typeof(RoundFromZero)) + signbit(x) ? round(x, RoundDown) : round(x, RoundUp) +end + # isapprox: approximate equality of numbers """ isapprox(x, y; atol::Real=0, rtol::Real=atol>0 ? 0 : √eps, nans::Bool=false[, norm::Function]) Inexact equality comparison. Two numbers compare equal if their relative distance *or* their absolute distance is within tolerance bounds: `isapprox` returns `true` if -`norm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))`. The default `atol` is zero and the -default `rtol` depends on the types of `x` and `y`. The keyword argument `nans` determines +`norm(x-y) <= max(atol, rtol*max(norm(x), norm(y)))`. The default `atol` (absolute tolerance) is zero and the +default `rtol` (relative tolerance) depends on the types of `x` and `y`. The keyword argument `nans` determines whether or not NaN values are considered equal (defaults to false). For real or complex floating-point values, if an `atol > 0` is not specified, `rtol` defaults to diff --git a/base/gcutils.jl b/base/gcutils.jl index d17301a1be9b0..fed30befd7d5c 100644 --- a/base/gcutils.jl +++ b/base/gcutils.jl @@ -1,20 +1,61 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + WeakRef(x) + +`w = WeakRef(x)` constructs a [weak reference](https://en.wikipedia.org/wiki/Weak_reference) +to the Julia value `x`: although `w` contains a reference to `x`, it does not prevent `x` from being +garbage collected. `w.value` is either `x` (if `x` has not been garbage-collected yet) or `nothing` +(if `x` has been garbage-collected). + +```jldoctest +julia> x = "a string" +"a string" + +julia> w = WeakRef(x) +WeakRef("a string") + +julia> GC.gc() + +julia> w # a reference is maintained via `x` +WeakRef("a string") + +julia> x = nothing # clear reference + +julia> GC.gc() + +julia> w +WeakRef(nothing) +``` +""" +WeakRef + ==(w::WeakRef, v::WeakRef) = isequal(w.value, v.value) ==(w::WeakRef, v) = isequal(w.value, v) ==(w, v::WeakRef) = isequal(w, v.value) +# Used by `Base.finalizer` to validate mutability of an object being finalized. +function _check_mutable(@nospecialize(o)) @noinline + if !ismutable(o) + error("objects of type ", typeof(o), " cannot be finalized") + end +end + """ finalizer(f, x) Register a function `f(x)` to be called when there are no program-accessible references to -`x`, and return `x`. The type of `x` must be a `mutable struct`, otherwise the behavior of -this function is unpredictable. +`x`, and return `x`. The type of `x` must be a `mutable struct`, otherwise the function +will throw. `f` must not cause a task switch, which excludes most I/O operations such as `println`. Using the `@async` macro (to defer context switching to outside of the finalizer) or `ccall` to directly invoke IO functions in C may be helpful for debugging purposes. +Note that there is no guaranteed world age for the execution of `f`. It may be +called in the world age in which the finalizer was registered or any later world age. + # Examples ```julia finalizer(my_mutable_struct) do x @@ -42,18 +83,13 @@ end ``` """ function finalizer(@nospecialize(f), @nospecialize(o)) - if !ismutable(o) - error("objects of type ", typeof(o), " cannot be finalized") - end - ccall(:jl_gc_add_finalizer_th, Cvoid, (Ptr{Cvoid}, Any, Any), - Core.getptls(), o, f) + _check_mutable(o) + Core.finalizer(f, o) return o end function finalizer(f::Ptr{Cvoid}, o::T) where T @inline - if !ismutable(o) - error("objects of type ", typeof(o), " cannot be finalized") - end + _check_mutable(o) ccall(:jl_gc_add_ptr_finalizer, Cvoid, (Ptr{Cvoid}, Any, Ptr{Cvoid}), Core.getptls(), o, f) return o @@ -126,6 +162,23 @@ function disable_finalizers() @inline ccall(:jl_gc_disable_finalizers_internal, Cvoid, ()) end +""" + GC.in_finalizer()::Bool + +Returns `true` if the current task is running a finalizer, returns `false` +otherwise. Will also return `false` within a finalizer which was inlined by the +compiler's eager finalization optimization, or if `finalize` is called on the +finalizer directly. + +The result of this function may be useful, for example, when a finalizer must +wait on a resource to become available; instead of polling the resource in a +`yield` loop (which is not legal to execute within a task running finalizers), +busy polling or an `@async` continuation could be used instead. +""" +function in_finalizer() @inline + ccall(:jl_gc_is_in_finalizer, Int8, ()) > 0 +end + """ GC.@preserve x1 x2 ... xn expr diff --git a/base/generator.jl b/base/generator.jl index 9d94996be1d4f..aa4b7f67cba95 100644 --- a/base/generator.jl +++ b/base/generator.jl @@ -53,7 +53,8 @@ axes(g::Generator) = axes(g.iter) ndims(g::Generator) = ndims(g.iter) keys(g::Generator) = keys(g.iter) last(g::Generator) = g.f(last(g.iter)) - +isempty(g::Generator) = isempty(g.iter) +isdone(g::Generator, state...) = isdone(g.iter, state...) ## iterator traits @@ -91,13 +92,13 @@ Base.HasLength() """ IteratorSize(x) = IteratorSize(typeof(x)) IteratorSize(::Type) = HasLength() # HasLength is the default +IteratorSize(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) +IteratorSize(::Type{Any}) = SizeUnknown() IteratorSize(::Type{<:Tuple}) = HasLength() IteratorSize(::Type{<:AbstractArray{<:Any,N}}) where {N} = HasShape{N}() IteratorSize(::Type{Generator{I,F}}) where {I,F} = IteratorSize(I) -IteratorSize(::Type{Any}) = SizeUnknown() - haslength(iter) = IteratorSize(iter) isa Union{HasShape, HasLength} abstract type IteratorEltype end @@ -125,7 +126,7 @@ Base.HasEltype() """ IteratorEltype(x) = IteratorEltype(typeof(x)) IteratorEltype(::Type) = HasEltype() # HasEltype is the default +IteratorEltype(::Type{Union{}}, slurp...) = throw(ArgumentError("Union{} does not have elements")) +IteratorEltype(::Type{Any}) = EltypeUnknown() IteratorEltype(::Type{Generator{I,T}}) where {I,T} = EltypeUnknown() - -IteratorEltype(::Type{Any}) = EltypeUnknown() diff --git a/base/gmp.jl b/base/gmp.jl index 435a0a0954ce9..69926f4ad0d06 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -10,7 +10,7 @@ import .Base: *, +, -, /, <, <<, >>, >>>, <=, ==, >, >=, ^, (~), (&), (|), xor, trailing_zeros, trailing_ones, count_ones, count_zeros, tryparse_internal, bin, oct, dec, hex, isequal, invmod, _prevpow2, _nextpow2, ndigits0zpb, widen, signed, unsafe_trunc, trunc, iszero, isone, big, flipsign, signbit, - sign, hastypemax, isodd, iseven, digits!, hash, hash_integer + sign, hastypemax, isodd, iseven, digits!, hash, hash_integer, top_set_bit if Clong == Int32 const ClongMax = Union{Int8, Int16, Int32} @@ -21,8 +21,16 @@ else end const CdoubleMax = Union{Float16, Float32, Float64} -version() = VersionNumber(unsafe_string(unsafe_load(cglobal((:__gmp_version, :libgmp), Ptr{Cchar})))) -bits_per_limb() = Int(unsafe_load(cglobal((:__gmp_bits_per_limb, :libgmp), Cint))) +if Sys.iswindows() + const libgmp = "libgmp-10.dll" +elseif Sys.isapple() + const libgmp = "@rpath/libgmp.10.dylib" +else + const libgmp = "libgmp.so.10" +end + +version() = VersionNumber(unsafe_string(unsafe_load(cglobal((:__gmp_version, libgmp), Ptr{Cchar})))) +bits_per_limb() = Int(unsafe_load(cglobal((:__gmp_bits_per_limb, libgmp), Cint))) const VERSION = version() const BITS_PER_LIMB = bits_per_limb() @@ -54,7 +62,7 @@ mutable struct BigInt <: Signed function BigInt(; nbits::Integer=0) b = MPZ.init2!(new(), nbits) - finalizer(cglobal((:__gmpz_clear, :libgmp)), b) + finalizer(cglobal((:__gmpz_clear, libgmp)), b) return b end end @@ -100,7 +108,7 @@ function __init__() bits_per_limb() != BITS_PER_LIMB ? @error(msg) : @warn(msg) end - ccall((:__gmp_set_memory_functions, :libgmp), Cvoid, + ccall((:__gmp_set_memory_functions, libgmp), Cvoid, (Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid}), cglobal(:jl_gc_counted_malloc), cglobal(:jl_gc_counted_realloc_with_old_size), @@ -112,7 +120,7 @@ function __init__() end # This only works with a patched version of GMP, ignore otherwise try - ccall((:__gmp_set_alloc_overflow_function, :libgmp), Cvoid, + ccall((:__gmp_set_alloc_overflow_function, libgmp), Cvoid, (Ptr{Cvoid},), cglobal(:jl_throw_out_of_memory_error)) ALLOC_OVERFLOW_FUNCTION[] = true @@ -129,23 +137,23 @@ module MPZ # wrapping of libgmp functions # - "output parameters" are labeled x, y, z, and are returned when appropriate # - constant input parameters are labeled a, b, c -# - a method modifying its input has a "!" appendend to its name, according to Julia's conventions +# - a method modifying its input has a "!" appended to its name, according to Julia's conventions # - some convenient methods are added (in addition to the pure MPZ ones), e.g. `add(a, b) = add!(BigInt(), a, b)` # and `add!(x, a) = add!(x, x, a)`. -using ..GMP: BigInt, Limb, BITS_PER_LIMB +using ..GMP: BigInt, Limb, BITS_PER_LIMB, libgmp const mpz_t = Ref{BigInt} const bitcnt_t = Culong -gmpz(op::Symbol) = (Symbol(:__gmpz_, op), :libgmp) +gmpz(op::Symbol) = (Symbol(:__gmpz_, op), libgmp) -init!(x::BigInt) = (ccall((:__gmpz_init, :libgmp), Cvoid, (mpz_t,), x); x) -init2!(x::BigInt, a) = (ccall((:__gmpz_init2, :libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) +init!(x::BigInt) = (ccall((:__gmpz_init, libgmp), Cvoid, (mpz_t,), x); x) +init2!(x::BigInt, a) = (ccall((:__gmpz_init2, libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) -realloc2!(x, a) = (ccall((:__gmpz_realloc2, :libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) +realloc2!(x, a) = (ccall((:__gmpz_realloc2, libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) realloc2(a) = realloc2!(BigInt(), a) -sizeinbase(a::BigInt, b) = Int(ccall((:__gmpz_sizeinbase, :libgmp), Csize_t, (mpz_t, Cint), a, b)) +sizeinbase(a::BigInt, b) = Int(ccall((:__gmpz_sizeinbase, libgmp), Csize_t, (mpz_t, Cint), a, b)) for (op, nbits) in (:add => :(BITS_PER_LIMB*(1 + max(abs(a.size), abs(b.size)))), :sub => :(BITS_PER_LIMB*(1 + max(abs(a.size), abs(b.size)))), @@ -161,7 +169,7 @@ for (op, nbits) in (:add => :(BITS_PER_LIMB*(1 + max(abs(a.size), abs(b.size)))) end invert!(x::BigInt, a::BigInt, b::BigInt) = - ccall((:__gmpz_invert, :libgmp), Cint, (mpz_t, mpz_t, mpz_t), x, a, b) + ccall((:__gmpz_invert, libgmp), Cint, (mpz_t, mpz_t, mpz_t), x, a, b) invert(a::BigInt, b::BigInt) = invert!(BigInt(), a, b) invert!(x::BigInt, b::BigInt) = invert!(x, x, b) @@ -174,7 +182,7 @@ for op in (:add_ui, :sub_ui, :mul_ui, :mul_2exp, :fdiv_q_2exp, :pow_ui, :bin_ui) end end -ui_sub!(x::BigInt, a, b::BigInt) = (ccall((:__gmpz_ui_sub, :libgmp), Cvoid, (mpz_t, Culong, mpz_t), x, a, b); x) +ui_sub!(x::BigInt, a, b::BigInt) = (ccall((:__gmpz_ui_sub, libgmp), Cvoid, (mpz_t, Culong, mpz_t), x, a, b); x) ui_sub(a, b::BigInt) = ui_sub!(BigInt(), a, b) for op in (:scan1, :scan0) @@ -183,7 +191,7 @@ for op in (:scan1, :scan0) @eval $op(a::BigInt, b) = Int(signed(ccall($(gmpz(op)), Culong, (mpz_t, Culong), a, b))) end -mul_si!(x::BigInt, a::BigInt, b) = (ccall((:__gmpz_mul_si, :libgmp), Cvoid, (mpz_t, mpz_t, Clong), x, a, b); x) +mul_si!(x::BigInt, a::BigInt, b) = (ccall((:__gmpz_mul_si, libgmp), Cvoid, (mpz_t, mpz_t, Clong), x, a, b); x) mul_si(a::BigInt, b) = mul_si!(BigInt(), a, b) mul_si!(x::BigInt, b) = mul_si!(x, x, b) @@ -205,47 +213,58 @@ for (op, T) in ((:fac_ui, Culong), (:set_ui, Culong), (:set_si, Clong), (:set_d, end end -popcount(a::BigInt) = Int(signed(ccall((:__gmpz_popcount, :libgmp), Culong, (mpz_t,), a))) +popcount(a::BigInt) = Int(signed(ccall((:__gmpz_popcount, libgmp), Culong, (mpz_t,), a))) -mpn_popcount(d::Ptr{Limb}, s::Integer) = Int(ccall((:__gmpn_popcount, :libgmp), Culong, (Ptr{Limb}, Csize_t), d, s)) +mpn_popcount(d::Ptr{Limb}, s::Integer) = Int(ccall((:__gmpn_popcount, libgmp), Culong, (Ptr{Limb}, Csize_t), d, s)) mpn_popcount(a::BigInt) = mpn_popcount(a.d, abs(a.size)) function tdiv_qr!(x::BigInt, y::BigInt, a::BigInt, b::BigInt) - ccall((:__gmpz_tdiv_qr, :libgmp), Cvoid, (mpz_t, mpz_t, mpz_t, mpz_t), x, y, a, b) + ccall((:__gmpz_tdiv_qr, libgmp), Cvoid, (mpz_t, mpz_t, mpz_t, mpz_t), x, y, a, b) x, y end tdiv_qr(a::BigInt, b::BigInt) = tdiv_qr!(BigInt(), BigInt(), a, b) powm!(x::BigInt, a::BigInt, b::BigInt, c::BigInt) = - (ccall((:__gmpz_powm, :libgmp), Cvoid, (mpz_t, mpz_t, mpz_t, mpz_t), x, a, b, c); x) + (ccall((:__gmpz_powm, libgmp), Cvoid, (mpz_t, mpz_t, mpz_t, mpz_t), x, a, b, c); x) powm(a::BigInt, b::BigInt, c::BigInt) = powm!(BigInt(), a, b, c) powm!(x::BigInt, b::BigInt, c::BigInt) = powm!(x, x, b, c) function gcdext!(x::BigInt, y::BigInt, z::BigInt, a::BigInt, b::BigInt) - ccall((:__gmpz_gcdext, :libgmp), Cvoid, (mpz_t, mpz_t, mpz_t, mpz_t, mpz_t), x, y, z, a, b) + ccall((:__gmpz_gcdext, libgmp), Cvoid, (mpz_t, mpz_t, mpz_t, mpz_t, mpz_t), x, y, z, a, b) x, y, z end gcdext(a::BigInt, b::BigInt) = gcdext!(BigInt(), BigInt(), BigInt(), a, b) -cmp(a::BigInt, b::BigInt) = Int(ccall((:__gmpz_cmp, :libgmp), Cint, (mpz_t, mpz_t), a, b)) -cmp_si(a::BigInt, b) = Int(ccall((:__gmpz_cmp_si, :libgmp), Cint, (mpz_t, Clong), a, b)) -cmp_ui(a::BigInt, b) = Int(ccall((:__gmpz_cmp_ui, :libgmp), Cint, (mpz_t, Culong), a, b)) -cmp_d(a::BigInt, b) = Int(ccall((:__gmpz_cmp_d, :libgmp), Cint, (mpz_t, Cdouble), a, b)) +cmp(a::BigInt, b::BigInt) = Int(ccall((:__gmpz_cmp, libgmp), Cint, (mpz_t, mpz_t), a, b)) +cmp_si(a::BigInt, b) = Int(ccall((:__gmpz_cmp_si, libgmp), Cint, (mpz_t, Clong), a, b)) +cmp_ui(a::BigInt, b) = Int(ccall((:__gmpz_cmp_ui, libgmp), Cint, (mpz_t, Culong), a, b)) +cmp_d(a::BigInt, b) = Int(ccall((:__gmpz_cmp_d, libgmp), Cint, (mpz_t, Cdouble), a, b)) -mpn_cmp(a::Ptr{Limb}, b::Ptr{Limb}, c) = ccall((:__gmpn_cmp, :libgmp), Cint, (Ptr{Limb}, Ptr{Limb}, Clong), a, b, c) +mpn_cmp(a::Ptr{Limb}, b::Ptr{Limb}, c) = ccall((:__gmpn_cmp, libgmp), Cint, (Ptr{Limb}, Ptr{Limb}, Clong), a, b, c) mpn_cmp(a::BigInt, b::BigInt, c) = mpn_cmp(a.d, b.d, c) -get_str!(x, a, b::BigInt) = (ccall((:__gmpz_get_str,:libgmp), Ptr{Cchar}, (Ptr{Cchar}, Cint, mpz_t), x, a, b); x) -set_str!(x::BigInt, a, b) = Int(ccall((:__gmpz_set_str, :libgmp), Cint, (mpz_t, Ptr{UInt8}, Cint), x, a, b)) -get_d(a::BigInt) = ccall((:__gmpz_get_d, :libgmp), Cdouble, (mpz_t,), a) +get_str!(x, a, b::BigInt) = (ccall((:__gmpz_get_str,libgmp), Ptr{Cchar}, (Ptr{Cchar}, Cint, mpz_t), x, a, b); x) +set_str!(x::BigInt, a, b) = Int(ccall((:__gmpz_set_str, libgmp), Cint, (mpz_t, Ptr{UInt8}, Cint), x, a, b)) +get_d(a::BigInt) = ccall((:__gmpz_get_d, libgmp), Cdouble, (mpz_t,), a) + +function export!(a::AbstractVector{T}, n::BigInt; order::Integer=-1, nails::Integer=0, endian::Integer=0) where {T<:Base.BitInteger} + stride(a, 1) == 1 || throw(ArgumentError("a must have stride 1")) + ndigits = cld(sizeinbase(n, 2), 8*sizeof(T) - nails) + length(a) < ndigits && resize!(a, ndigits) + count = Ref{Csize_t}() + ccall((:__gmpz_export, libgmp), Ptr{T}, (Ptr{T}, Ref{Csize_t}, Cint, Csize_t, Cint, Csize_t, mpz_t), + a, count, order, sizeof(T), endian, nails, n) + @assert count[] ≤ length(a) + return a, Int(count[]) +end -limbs_write!(x::BigInt, a) = ccall((:__gmpz_limbs_write, :libgmp), Ptr{Limb}, (mpz_t, Clong), x, a) -limbs_finish!(x::BigInt, a) = ccall((:__gmpz_limbs_finish, :libgmp), Cvoid, (mpz_t, Clong), x, a) -import!(x::BigInt, a, b, c, d, e, f) = ccall((:__gmpz_import, :libgmp), Cvoid, +limbs_write!(x::BigInt, a) = ccall((:__gmpz_limbs_write, libgmp), Ptr{Limb}, (mpz_t, Clong), x, a) +limbs_finish!(x::BigInt, a) = ccall((:__gmpz_limbs_finish, libgmp), Cvoid, (mpz_t, Clong), x, a) +import!(x::BigInt, a, b, c, d, e, f) = ccall((:__gmpz_import, libgmp), Cvoid, (mpz_t, Csize_t, Cint, Csize_t, Cint, Csize_t, Ptr{Cvoid}), x, a, b, c, d, e, f) -setbit!(x, a) = (ccall((:__gmpz_setbit, :libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) -tstbit(a::BigInt, b) = ccall((:__gmpz_tstbit, :libgmp), Cint, (mpz_t, bitcnt_t), a, b) % Bool +setbit!(x, a) = (ccall((:__gmpz_setbit, libgmp), Cvoid, (mpz_t, bitcnt_t), x, a); x) +tstbit(a::BigInt, b) = ccall((:__gmpz_tstbit, libgmp), Cint, (mpz_t, bitcnt_t), a, b) % Bool end # module MPZ @@ -396,7 +415,7 @@ function Float64(x::BigInt, ::RoundingMode{:Nearest}) z = Float64((unsafe_load(x.d, 2) % UInt64) << BITS_PER_LIMB + unsafe_load(x.d)) else y1 = unsafe_load(x.d, xsize) % UInt64 - n = 64 - leading_zeros(y1) + n = top_set_bit(y1) # load first 54(1 + 52 bits of fraction + 1 for rounding) y = y1 >> (n - (precision(Float64)+1)) if Limb == UInt64 @@ -586,7 +605,14 @@ Number of ones in the binary representation of abs(x). """ count_ones_abs(x::BigInt) = iszero(x) ? 0 : MPZ.mpn_popcount(x) +function top_set_bit(x::BigInt) + x < 0 && throw(DomainError(x, "top_set_bit only supports negative arguments when they have type BitSigned.")) + x == 0 && return 0 + Int(ccall((:__gmpz_sizeinbase, :libgmp), Csize_t, (Base.GMP.MPZ.mpz_t, Cint), x, 2)) +end + divrem(x::BigInt, y::BigInt) = MPZ.tdiv_qr(x, y) +divrem(x::BigInt, y::Integer) = MPZ.tdiv_qr(x, big(y)) cmp(x::BigInt, y::BigInt) = sign(MPZ.cmp(x, y)) cmp(x::BigInt, y::ClongMax) = sign(MPZ.cmp_si(x, y)) @@ -667,8 +693,12 @@ function prod(arr::AbstractArray{BigInt}) # to account for the rounding to limbs in MPZ.mul! # (BITS_PER_LIMB-1 would typically be enough, to which we add # 1 for the initial multiplication by init=1 in foldl) - nbits = GC.@preserve arr sum(arr; init=BITS_PER_LIMB) do x - abs(x.size) * BITS_PER_LIMB - leading_zeros(unsafe_load(x.d)) + nbits = BITS_PER_LIMB + for x in arr + iszero(x) && return zero(BigInt) + xsize = abs(x.size) + lz = GC.@preserve x leading_zeros(unsafe_load(x.d, xsize)) + nbits += xsize * BITS_PER_LIMB - lz end init = BigInt(; nbits) MPZ.set_si!(init, 1) @@ -677,8 +707,16 @@ end factorial(x::BigInt) = isneg(x) ? BigInt(0) : MPZ.fac_ui(x) -binomial(n::BigInt, k::UInt) = MPZ.bin_ui(n, k) -binomial(n::BigInt, k::Integer) = k < 0 ? BigInt(0) : binomial(n, UInt(k)) +function binomial(n::BigInt, k::Integer) + k < 0 && return BigInt(0) + k <= typemax(Culong) && return binomial(n, Culong(k)) + n < 0 && return isodd(k) ? -binomial(k - n - 1, k) : binomial(k - n - 1, k) + κ = n - k + κ < 0 && return BigInt(0) + κ <= typemax(Culong) && return binomial(n, Culong(κ)) + throw(OverflowError("Computation would exceed memory")) +end +binomial(n::BigInt, k::Culong) = MPZ.bin_ui(n, k) ==(x::BigInt, y::BigInt) = cmp(x,y) == 0 ==(x::BigInt, i::Integer) = cmp(x,i) == 0 @@ -731,19 +769,29 @@ function string(n::BigInt; base::Integer = 10, pad::Integer = 1) end function digits!(a::AbstractVector{T}, n::BigInt; base::Integer = 10) where {T<:Integer} - if 2 ≤ base ≤ 62 - s = codeunits(string(n; base)) - i, j = firstindex(a)-1, length(s)+1 - lasti = min(lastindex(a), firstindex(a) + length(s)-1 - isneg(n)) - while i < lasti - # base ≤ 36: 0-9, plus a-z for 10-35 - # base > 36: 0-9, plus A-Z for 10-35 and a-z for 36..61 - x = s[j -= 1] - a[i += 1] = base ≤ 36 ? (x>0x39 ? x-0x57 : x-0x30) : (x>0x39 ? (x>0x60 ? x-0x3d : x-0x37) : x-0x30) + if base ≥ 2 + if base ≤ 62 + # fast path using mpz_get_str via string(n; base) + s = codeunits(string(n; base)) + i, j = firstindex(a)-1, length(s)+1 + lasti = min(lastindex(a), firstindex(a) + length(s)-1 - isneg(n)) + while i < lasti + # base ≤ 36: 0-9, plus a-z for 10-35 + # base > 36: 0-9, plus A-Z for 10-35 and a-z for 36..61 + x = s[j -= 1] + a[i += 1] = base ≤ 36 ? (x>0x39 ? x-0x57 : x-0x30) : (x>0x39 ? (x>0x60 ? x-0x3d : x-0x37) : x-0x30) + end + lasti = lastindex(a) + while i < lasti; a[i+=1] = zero(T); end + return isneg(n) ? map!(-,a,a) : a + elseif a isa StridedVector{<:Base.BitInteger} && stride(a,1) == 1 && ispow2(base) && base-1 ≤ typemax(T) + # fast path using mpz_export + origlen = length(a) + _, writelen = MPZ.export!(a, n; nails = 8sizeof(T) - trailing_zeros(base)) + length(a) != origlen && resize!(a, origlen) # truncate to least-significant digits + a[begin+writelen:end] .= zero(T) + return isneg(n) ? map!(-,a,a) : a end - lasti = lastindex(a) - while i < lasti; a[i+=1] = zero(T); end - return isneg(n) ? map!(-,a,a) : a end return invoke(digits!, Tuple{typeof(a), Integer}, a, n; base) # slow generic fallback end @@ -873,7 +921,9 @@ module MPQ # Rational{BigInt} import .Base: unsafe_rational, __throw_rational_argerror_zero -import ..GMP: BigInt, MPZ, Limb, isneg +import ..GMP: BigInt, MPZ, Limb, isneg, libgmp + +gmpq(op::Symbol) = (Symbol(:__gmpq_, op), libgmp) mutable struct _MPQ num_alloc::Cint @@ -907,70 +957,119 @@ end function Rational{BigInt}(num::BigInt, den::BigInt) if iszero(den) iszero(num) && __throw_rational_argerror_zero(BigInt) - num = isneg(num) ? -one(BigInt) : one(BigInt) - return unsafe_rational(BigInt, num, den) + return set_si(flipsign(1, num), 0) end xq = _MPQ(MPZ.set(num), MPZ.set(den)) - ccall((:__gmpq_canonicalize, :libgmp), Cvoid, (mpq_t,), xq) + ccall((:__gmpq_canonicalize, libgmp), Cvoid, (mpq_t,), xq) return sync_rational!(xq) end -function Base.:+(x::Rational{BigInt}, y::Rational{BigInt}) +# define set, set_ui, set_si, set_z, and their inplace versions +function set!(z::Rational{BigInt}, x::Rational{BigInt}) + zq = _MPQ(z) + ccall((:__gmpq_set, libgmp), Cvoid, (mpq_t, mpq_t), zq, _MPQ(x)) + return sync_rational!(zq) +end + +function set_z!(z::Rational{BigInt}, x::BigInt) + zq = _MPQ(z) + ccall((:__gmpq_set_z, libgmp), Cvoid, (mpq_t, MPZ.mpz_t), zq, x) + return sync_rational!(zq) +end + +for (op, T) in ((:set, Rational{BigInt}), (:set_z, BigInt)) + op! = Symbol(op, :!) + @eval $op(a::$T) = $op!(unsafe_rational(BigInt(), BigInt()), a) +end + +# note that rationals returned from set_ui and set_si are not checked, +# set_ui(0, 0) will return 0//0 without errors, just like unsafe_rational +for (op, T1, T2) in ((:set_ui, Culong, Culong), (:set_si, Clong, Culong)) + op! = Symbol(op, :!) + @eval begin + function $op!(z::Rational{BigInt}, a, b) + zq = _MPQ(z) + ccall($(gmpq(op)), Cvoid, (mpq_t, $T1, $T2), zq, a, b) + return sync_rational!(zq) + end + $op(a, b) = $op!(unsafe_rational(BigInt(), BigInt()), a, b) + end +end + +# define add, sub, mul, div, and their inplace versions +function add!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt}) if iszero(x.den) || iszero(y.den) if iszero(x.den) && iszero(y.den) && isneg(x.num) != isneg(y.num) throw(DivideError()) end - return iszero(x.den) ? x : y + return set!(z, iszero(x.den) ? x : y) end - zq = _MPQ() - ccall((:__gmpq_add, :libgmp), Cvoid, + zq = _MPQ(z) + ccall((:__gmpq_add, libgmp), Cvoid, (mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y)) return sync_rational!(zq) end -function Base.:-(x::Rational{BigInt}, y::Rational{BigInt}) + +function sub!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt}) if iszero(x.den) || iszero(y.den) if iszero(x.den) && iszero(y.den) && isneg(x.num) == isneg(y.num) throw(DivideError()) end - return iszero(x.den) ? x : -y + iszero(x.den) && return set!(z, x) + return set_si!(z, flipsign(-1, y.num), 0) end - zq = _MPQ() - ccall((:__gmpq_sub, :libgmp), Cvoid, + zq = _MPQ(z) + ccall((:__gmpq_sub, libgmp), Cvoid, (mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y)) return sync_rational!(zq) end -function Base.:*(x::Rational{BigInt}, y::Rational{BigInt}) + +function mul!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt}) if iszero(x.den) || iszero(y.den) if iszero(x.num) || iszero(y.num) throw(DivideError()) end - return xor(isneg(x.num),isneg(y.num)) ? -one(BigInt)//zero(BigInt) : one(BigInt)//zero(BigInt) + return set_si!(z, ifelse(xor(isneg(x.num), isneg(y.num)), -1, 1), 0) end - zq = _MPQ() - ccall((:__gmpq_mul, :libgmp), Cvoid, + zq = _MPQ(z) + ccall((:__gmpq_mul, libgmp), Cvoid, (mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y)) return sync_rational!(zq) end -function Base.://(x::Rational{BigInt}, y::Rational{BigInt}) + +function div!(z::Rational{BigInt}, x::Rational{BigInt}, y::Rational{BigInt}) if iszero(x.den) if iszero(y.den) throw(DivideError()) end - return isneg(y.num) ? -x : x + isneg(y.num) || return set!(z, x) + return set_si!(z, flipsign(-1, x.num), 0) elseif iszero(y.den) - return y.den // y.num + return set_si!(z, 0, 1) elseif iszero(y.num) if iszero(x.num) throw(DivideError()) end - return (isneg(x.num) ? -one(BigInt) : one(BigInt)) // y.num + return set_si!(z, flipsign(1, x.num), 0) end - zq = _MPQ() - ccall((:__gmpq_div, :libgmp), Cvoid, + zq = _MPQ(z) + ccall((:__gmpq_div, libgmp), Cvoid, (mpq_t,mpq_t,mpq_t), zq, _MPQ(x), _MPQ(y)) return sync_rational!(zq) end +for (fJ, fC) in ((:+, :add), (:-, :sub), (:*, :mul), (://, :div)) + fC! = Symbol(fC, :!) + @eval begin + ($fC!)(x::Rational{BigInt}, y::Rational{BigInt}) = $fC!(x, x, y) + (Base.$fJ)(x::Rational{BigInt}, y::Rational{BigInt}) = $fC!(unsafe_rational(BigInt(), BigInt()), x, y) + end +end + +function Base.cmp(x::Rational{BigInt}, y::Rational{BigInt}) + Int(ccall((:__gmpq_cmp, libgmp), Cint, (mpq_t, mpq_t), _MPQ(x), _MPQ(y))) +end + end # MPQ module end # module diff --git a/base/hashing.jl b/base/hashing.jl index 746017f978dcb..5dbae09123bd6 100644 --- a/base/hashing.jl +++ b/base/hashing.jl @@ -6,7 +6,7 @@ hash(x[, h::UInt]) -> UInt Compute an integer hash code such that `isequal(x,y)` implies `hash(x)==hash(y)`. The -optional second argument `h` is a hash code to be mixed with the result. +optional second argument `h` is another hash code to be mixed with the result. New types should implement the 2-argument form, typically by calling the 2-argument `hash` method recursively in order to mix hashes of the contents with each other (and with `h`). @@ -15,10 +15,21 @@ Typically, any type that implements `hash` should also implement its own [`==`]( (operator `-`) should also implement [`widen`](@ref), which is required to hash values inside heterogeneous arrays. +The hash value may change when a new Julia process is started. + +```jldoctest +julia> a = hash(10) +0x95ea2955abd45275 + +julia> hash(10, a) # only use the output of another hash function as the second argument +0xd42bad54a8575b16 +``` + 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)) ## hashing general objects ## @@ -102,7 +113,7 @@ end const memhash = UInt === UInt64 ? :memhash_seed : :memhash32_seed const memhash_seed = UInt === UInt64 ? 0x71e729fd56419c81 : 0x56419c81 -function hash(s::String, h::UInt) +@assume_effects :total function hash(s::String, h::UInt) h += memhash_seed ccall(memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), s, sizeof(s), h % UInt32) + h end diff --git a/base/iddict.jl b/base/iddict.jl index 7247a85c9afc8..99710fbb3491e 100644 --- a/base/iddict.jl +++ b/base/iddict.jl @@ -68,7 +68,7 @@ end empty(d::IdDict, ::Type{K}, ::Type{V}) where {K, V} = IdDict{K,V}() -function rehash!(d::IdDict, newsz = length(d.ht)) +function rehash!(d::IdDict, newsz = length(d.ht)%UInt) d.ht = ccall(:jl_idtable_rehash, Vector{Any}, (Any, Csize_t), d.ht, newsz) d end @@ -86,10 +86,10 @@ 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")) if !(val isa V) # avoid a dynamic call - val = convert(V, val) + val = convert(V, val)::V end if d.ndel >= ((3*length(d.ht))>>2) - rehash!(d, max(length(d.ht)>>1, 32)) + rehash!(d, max((length(d.ht)%UInt)>>1, 32)) d.ndel = 0 end inserted = RefValue{Cint}(0) @@ -143,7 +143,7 @@ end _oidd_nextind(a, i) = reinterpret(Int, ccall(:jl_eqtable_nextind, Csize_t, (Any, Csize_t), a, i)) function iterate(d::IdDict{K,V}, idx=0) where {K, V} - idx = _oidd_nextind(d.ht, idx) + idx = _oidd_nextind(d.ht, idx%UInt) idx == -1 && return nothing return (Pair{K, V}(d.ht[idx + 1]::K, d.ht[idx + 2]::V), idx + 2) end @@ -155,7 +155,7 @@ copy(d::IdDict) = typeof(d)(d) function get!(d::IdDict{K,V}, @nospecialize(key), @nospecialize(default)) where {K, V} val = ccall(:jl_eqtable_get, Any, (Any, Any, Any), d.ht, key, secret_table_token) if val === secret_table_token - val = isa(default, V) ? default : convert(V, default) + val = isa(default, V) ? default : convert(V, default)::V setindex!(d, val, key) return val else diff --git a/base/indices.jl b/base/indices.jl index 28028f23c72a3..a9189865048cd 100644 --- a/base/indices.jl +++ b/base/indices.jl @@ -23,14 +23,14 @@ A linear indexing style uses one integer index to describe the position in the a (even if it's a multidimensional array) and column-major ordering is used to efficiently access the elements. This means that requesting [`eachindex`](@ref) from an array that is `IndexLinear` will return -a simple one-dimensional range, even if it is multidimensional. +a simple one-dimensional range, even if it is multidimensional. A custom array that reports its `IndexStyle` as `IndexLinear` only needs to implement indexing (and indexed assignment) with a single `Int` index; -all other indexing expressions — including multidimensional accesses — will +all other indexing expressions — including multidimensional accesses — will be recomputed to the linear index. For example, if `A` were a `2×3` custom matrix with linear indexing, and we referenced `A[1, 3]`, this would be -recomputed to the equivalent linear index and call `A[5]` since `2*1 + 3 = 5`. +recomputed to the equivalent linear index and call `A[5]` since `1 + 2*(3 - 1) = 5`. See also [`IndexCartesian`](@ref). """ @@ -50,13 +50,13 @@ a range of [`CartesianIndices`](@ref). A `N`-dimensional custom array that reports its `IndexStyle` as `IndexCartesian` needs to implement indexing (and indexed assignment) with exactly `N` `Int` indices; -all other indexing expressions — including linear indexing — will +all other indexing expressions — including linear indexing — will be recomputed to the equivalent Cartesian location. For example, if `A` were a `2×3` custom matrix with cartesian indexing, and we referenced `A[5]`, this would be -recomputed to the equivalent Cartesian index and call `A[1, 3]` since `5 = 2*1 + 3`. +recomputed to the equivalent Cartesian index and call `A[1, 3]` since `5 = 1 + 2*(3 - 1)`. It is significantly more expensive to compute Cartesian indices from a linear index than it is -to go the other way. The former operation requires division — a very costly operation — whereas +to go the other way. The former operation requires division — a very costly operation — whereas the latter only uses multiplication and addition and is essentially free. This asymmetry means it is far more costly to use linear indexing with an `IndexCartesian` array than it is to use Cartesian indexing with an `IndexLinear` array. @@ -92,7 +92,7 @@ particular, [`eachindex`](@ref) creates an iterator whose type depends on the setting of this trait. """ IndexStyle(A::AbstractArray) = IndexStyle(typeof(A)) -IndexStyle(::Type{Union{}}) = IndexLinear() +IndexStyle(::Type{Union{}}, slurp...) = IndexLinear() IndexStyle(::Type{<:AbstractArray}) = IndexCartesian() IndexStyle(::Type{<:Array}) = IndexLinear() IndexStyle(::Type{<:AbstractRange}) = IndexLinear() @@ -320,6 +320,26 @@ which they index. To support those cases, `to_indices(A, I)` calls `to_indices(A, axes(A), I)`, which then recursively walks through both the given tuple of indices and the dimensional indices of `A` in tandem. As such, not all index types are guaranteed to propagate to `Base.to_index`. + +# Examples +```jldoctest +julia> A = zeros(1,2,3,4); + +julia> to_indices(A, (1,1,2,2)) +(1, 1, 2, 2) + +julia> to_indices(A, (1,1,2,20)) # no bounds checking +(1, 1, 2, 20) + +julia> to_indices(A, (CartesianIndex((1,)), 2, CartesianIndex((3,4)))) # exotic index +(1, 2, 3, 4) + +julia> to_indices(A, ([1,1], 1:2, 3, 4)) +([1, 1], 1:2, 3, 4) + +julia> to_indices(A, (1,2)) # no shape checking +(1, 2) +``` """ to_indices(A, I::Tuple) = (@inline; to_indices(A, axes(A), I)) to_indices(A, I::Tuple{Any}) = (@inline; to_indices(A, (eachindex(IndexLinear(), A),), I)) @@ -329,11 +349,15 @@ 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{}) = () -to_indices(A, inds, I::Tuple{Any, Vararg{Any}}) = - (@inline; (to_index(A, I[1]), to_indices(A, _maybetail(inds), tail(I))...)) +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 -_maybetail(::Tuple{}) = () -_maybetail(t::Tuple) = tail(t) +_to_indices1(A, inds, I1) = (to_index(A, I1),) +_cutdim(inds, I1) = safe_tail(inds) """ Slice(indices) @@ -351,6 +375,8 @@ struct Slice{T<:AbstractUnitRange} <: AbstractUnitRange{Int} indices::T end Slice(S::Slice) = S +Slice{T}(S::Slice) where {T<:AbstractUnitRange} = Slice{T}(T(S.indices)) + axes(S::Slice) = (IdentityUnitRange(S.indices),) axes1(S::Slice) = IdentityUnitRange(S.indices) axes(S::Slice{<:OneTo}) = (S.indices,) @@ -366,7 +392,6 @@ getindex(S::Slice, i::StepRange{<:Integer}) = (@inline; @boundscheck checkbounds show(io::IO, r::Slice) = print(io, "Base.Slice(", r.indices, ")") iterate(S::Slice, s...) = iterate(S.indices, s...) - """ IdentityUnitRange(range::AbstractUnitRange) @@ -378,6 +403,8 @@ struct IdentityUnitRange{T<:AbstractUnitRange} <: AbstractUnitRange{Int} indices::T end IdentityUnitRange(S::IdentityUnitRange) = S +IdentityUnitRange{T}(S::IdentityUnitRange) where {T<:AbstractUnitRange} = IdentityUnitRange{T}(T(S.indices)) + # IdentityUnitRanges are offset and thus have offset axes, so they are their own axes axes(S::IdentityUnitRange) = (S,) axes1(S::IdentityUnitRange) = S @@ -448,6 +475,8 @@ julia> linear[1,2] struct LinearIndices{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractArray{Int,N} indices::R end +convert(::Type{LinearIndices{N,R}}, inds::LinearIndices{N}) where {N,R<:NTuple{N,AbstractUnitRange{Int}}} = + LinearIndices{N,R}(convert(R, inds.indices))::LinearIndices{N,R} LinearIndices(::Tuple{}) = LinearIndices{0,typeof(())}(()) LinearIndices(inds::NTuple{N,AbstractUnitRange{<:Integer}}) where {N} = @@ -459,16 +488,17 @@ LinearIndices(A::Union{AbstractArray,SimpleVector}) = LinearIndices(axes(A)) _convert2ind(i::Integer) = Base.OneTo(i) _convert2ind(ind::AbstractUnitRange) = first(ind):last(ind) -promote_rule(::Type{LinearIndices{N,R1}}, ::Type{LinearIndices{N,R2}}) where {N,R1,R2} = - LinearIndices{N,indices_promote_type(R1,R2)} - function indices_promote_type(::Type{Tuple{R1,Vararg{R1,N}}}, ::Type{Tuple{R2,Vararg{R2,N}}}) where {R1,R2,N} R = promote_type(R1, R2) - Tuple{R,Vararg{R,N}} + return Tuple{R, Vararg{R, N}} end -convert(::Type{LinearIndices{N,R}}, inds::LinearIndices{N}) where {N,R} = - LinearIndices(convert(R, inds.indices)) +promote_rule(::Type{LinearIndices{N,R1}}, ::Type{LinearIndices{N,R2}}) where {N,R1,R2} = + LinearIndices{N,indices_promote_type(R1,R2)} +promote_rule(a::Type{Slice{T1}}, b::Type{Slice{T2}}) where {T1,T2} = + el_same(promote_type(T1, T2), a, b) +promote_rule(a::Type{IdentityUnitRange{T1}}, b::Type{IdentityUnitRange{T2}}) where {T1,T2} = + el_same(promote_type(T1, T2), a, b) # AbstractArray implementation IndexStyle(::Type{<:LinearIndices}) = IndexLinear() diff --git a/base/initdefs.jl b/base/initdefs.jl index 4106ef4eb7777..002984b83dd97 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -315,6 +315,9 @@ end set_active_project(projfile::Union{AbstractString,Nothing}) Set the active `Project.toml` file to `projfile`. See also [`Base.active_project`](@ref). + +!!! compat "Julia 1.8" + This function requires at least Julia 1.8. """ function set_active_project(projfile::Union{AbstractString,Nothing}) ACTIVE_PROJECT[] = projfile @@ -350,12 +353,21 @@ end const atexit_hooks = Callable[ () -> Filesystem.temp_cleanup_purge(force=true) ] +const _atexit_hooks_lock = ReentrantLock() """ atexit(f) -Register a zero-argument function `f()` to be called at process exit. `atexit()` hooks are -called in last in first out (LIFO) order and run before object finalizers. +Register a zero- or one-argument function `f()` to be called at process exit. +`atexit()` hooks are called in last in first out (LIFO) order and run before +object finalizers. + +If `f` has a method defined for one integer argument, it will be called as +`f(n::Int32)`, where `n` is the current exit code, otherwise it will be called +as `f()`. + +!!! compat "Julia 1.9" + The one-argument form requires Julia 1.9 Exit hooks are allowed to call `exit(n)`, in which case Julia will exit with exit code `n` (instead of the original exit code). If more than one exit hook @@ -363,11 +375,35 @@ calls `exit(n)`, then Julia will exit with the exit code corresponding to the last called exit hook that calls `exit(n)`. (Because exit hooks are called in LIFO order, "last called" is equivalent to "first registered".) """ -atexit(f::Function) = (pushfirst!(atexit_hooks, f); nothing) +atexit(f::Function) = Base.@lock _atexit_hooks_lock (pushfirst!(atexit_hooks, f); nothing) -function _atexit() +function _atexit(exitcode::Cint) while !isempty(atexit_hooks) f = popfirst!(atexit_hooks) + try + if hasmethod(f, (Cint,)) + f(exitcode) + else + f() + end + catch ex + showerror(stderr, ex) + Base.show_backtrace(stderr, catch_backtrace()) + println(stderr) + end + end +end + +## postoutput: register post output hooks ## +## like atexit but runs after any requested output. +## any hooks saved in the sysimage are cleared in Base._start +const postoutput_hooks = Callable[] + +postoutput(f::Function) = (pushfirst!(postoutput_hooks, f); nothing) + +function _postoutput() + while !isempty(postoutput_hooks) + f = popfirst!(postoutput_hooks) try f() catch ex diff --git a/base/int.jl b/base/int.jl index 4f0b2877c0591..4b2f542bba788 100644 --- a/base/int.jl +++ b/base/int.jl @@ -174,8 +174,12 @@ julia> abs(-3) julia> abs(1 + im) 1.4142135623730951 -julia> abs(typemin(Int64)) --9223372036854775808 +julia> abs.(Int8[-128 -127 -126 0 126 127]) # overflow at typemin(Int8) +1×6 Matrix{Int8}: + -128 127 126 0 126 127 + +julia> maximum(abs, [1, -2, 3, -4]) +4 ``` """ function abs end @@ -198,8 +202,11 @@ See also: [`signed`](@ref), [`sign`](@ref), [`signbit`](@ref). julia> unsigned(-2) 0xfffffffffffffffe -julia> unsigned(2) -0x0000000000000002 +julia> unsigned(Int8(2)) +0x02 + +julia> typeof(ans) +UInt8 julia> signed(unsigned(-2)) -2 @@ -387,7 +394,7 @@ julia> string(bswap(1), base = 2) "100000000000000000000000000000000000000000000000000000000" ``` """ -bswap(x::Union{Int8, UInt8}) = x +bswap(x::Union{Int8, UInt8, Bool}) = x bswap(x::Union{Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128}) = bswap_int(x) @@ -475,6 +482,32 @@ julia> trailing_ones(3) """ trailing_ones(x::Integer) = trailing_zeros(~x) +""" + top_set_bit(x::Integer) -> Integer + +The number of bits in `x`'s binary representation, excluding leading zeros. + +Equivalently, the position of the most significant set bit in `x`'s binary +representation, measured from the least significant side. + +Negative `x` are only supported when `x::BitSigned`. + +See also: [`ndigits0z`](@ref), [`ndigits`](@ref). + +# Examples +```jldoctest +julia> Base.top_set_bit(4) +3 + +julia> Base.top_set_bit(0) +0 + +julia> Base.top_set_bit(-1) +64 +``` +""" +top_set_bit(x::BitInteger) = 8sizeof(x) - leading_zeros(x) + ## integer comparisons ## (< )(x::T, y::T) where {T<:BitUnsigned} = ult_int(x, y) @@ -507,11 +540,11 @@ trailing_ones(x::Integer) = trailing_zeros(~x) for to in BitInteger_types, from in (BitInteger_types..., Bool) if !(to === from) - if to.size < from.size + if Core.sizeof(to) < Core.sizeof(from) @eval rem(x::($from), ::Type{$to}) = trunc_int($to, x) elseif from === Bool @eval rem(x::($from), ::Type{$to}) = convert($to, x) - elseif from.size < to.size + elseif Core.sizeof(from) < Core.sizeof(to) if from <: Signed @eval rem(x::($from), ::Type{$to}) = sext_int($to, x) else @@ -571,8 +604,17 @@ if nameof(@__MODULE__) === :Base # Examples ```jldoctest - julia> 129 % Int8 + julia> x = 129 % Int8 -127 + + julia> typeof(x) + Int8 + + julia> x = 129 % BigInt + 129 + + julia> typeof(x) + BigInt ``` """ $fname(x::Integer, T::Type{<:Integer}) end @@ -655,10 +697,19 @@ floor(::Type{T}, x::Integer) where {T<:Integer} = convert(T, x) """ @int128_str str - @int128_str(str) -`@int128_str` parses a string into a Int128. -Throws an `ArgumentError` if the string is not a valid integer. +Parse `str` as an [`Int128`](@ref). +Throw an `ArgumentError` if the string is not a valid integer. + +# Examples +```jldoctest +julia> int128"123456789123" +123456789123 + +julia> int128"123456789123.4" +ERROR: LoadError: ArgumentError: invalid base 10 digit '.' in "123456789123.4" +[...] +``` """ macro int128_str(s) return parse(Int128, s) @@ -666,10 +717,19 @@ end """ @uint128_str str - @uint128_str(str) -`@uint128_str` parses a string into a UInt128. -Throws an `ArgumentError` if the string is not a valid integer. +Parse `str` as an [`UInt128`](@ref). +Throw an `ArgumentError` if the string is not a valid integer. + +# Examples +``` +julia> uint128"123456789123" +0x00000000000000000000001cbe991a83 + +julia> uint128"-123456789123" +ERROR: LoadError: ArgumentError: invalid base 10 digit '-' in "-123456789123" +[...] +``` """ macro uint128_str(s) return parse(UInt128, s) @@ -677,7 +737,6 @@ end """ @big_str str - @big_str(str) Parse a string into a [`BigInt`](@ref) or [`BigFloat`](@ref), and throw an `ArgumentError` if the string is not a valid number. @@ -690,6 +749,10 @@ julia> big"123_456" julia> big"7891.5" 7891.5 + +julia> big"_" +ERROR: ArgumentError: invalid number format _ for BigInt or BigFloat +[...] ``` """ macro big_str(s) @@ -744,13 +807,24 @@ promote_rule(::Type{UInt128}, ::Type{Int128}) = UInt128 The lowest value representable by the given (real) numeric DataType `T`. +See also: [`floatmin`](@ref), [`typemax`](@ref), [`eps`](@ref). + # Examples ```jldoctest +julia> typemin(Int8) +-128 + +julia> typemin(UInt32) +0x00000000 + julia> typemin(Float16) -Inf16 julia> typemin(Float32) -Inf32 + +julia> nextfloat(-Inf32) # smallest finite Float32 floating point number +-3.4028235f38 ``` """ function typemin end @@ -773,7 +847,10 @@ julia> typemax(UInt32) julia> typemax(Float64) Inf -julia> floatmax(Float32) # largest finite floating point number +julia> typemax(Float32) +Inf32 + +julia> floatmax(Float32) # largest finite Float32 floating point number 3.4028235f38 ``` """ diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 3c2d9b4beec7b..1b007700f4331 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -48,34 +48,46 @@ function gcd(a::T, b::T) where T<:Integer end function gcd(a::T, b::T) where T<:BitInteger - a == 0 && return checked_abs(b) - b == 0 && return checked_abs(a) - r = _gcd(a, b) - signbit(r) && __throw_gcd_overflow(a, b) - return r + a == 0 && return Base.checked_abs(b) + b == 0 && return Base.checked_abs(a) + if a isa Signed && a == typemin(T) + if a == b + Base.__throw_gcd_overflow(a, b) + else + a, b = b, a + end + end + return _gcd(a, b) end -@noinline __throw_gcd_overflow(a, b) = throw(OverflowError("gcd($a, $b) overflows")) +@noinline __throw_gcd_overflow(a, b) = + throw(OverflowError(LazyString("gcd(", a, ", ", b, ") overflows"))) +function absdiff(x::T,y::T) where {T<:Unsigned} + d = max(x,y) - min(x,y) + d, d +end +function absdiff(x::T,y::T) where {T<:Signed} + d = x - y + abs(d), d +end # binary GCD (aka Stein's) algorithm # about 1.7x (2.1x) faster for random Int64s (Int128s) -# Unfortunately, we need to manually annotate this as `@pure` to work around #41694. Since -# this is used in the Rational constructor, constant prop is something we do care about here. -# This does call generic functions, so it might not be completely sound, but since `_gcd` is -# restricted to BitIntegers, it is probably fine in practice. -@pure function _gcd(a::T, b::T) where T<:BitInteger - za = trailing_zeros(a) - zb = trailing_zeros(b) +# Unfortunately, we need to manually annotate this as `@assume_effects :terminates_locally` to work around #41694. +# Since this is used in the Rational constructor, constant folding is something we do care about here. +@assume_effects :terminates_locally function _gcd(ain::T, bin::T) where T<:BitInteger + zb = trailing_zeros(bin) + za = trailing_zeros(ain) + a = abs(ain) + b = abs(bin >> zb) k = min(za, zb) - u = unsigned(abs(a >> za)) - v = unsigned(abs(b >> zb)) - while u != v - if u > v - u, v = v, u - end - v -= u - v >>= trailing_zeros(v) + while a != 0 + a >>= za + absd, diff = absdiff(a, b) + za = trailing_zeros(diff) + b = min(a, b) + a = absd end - r = u << k + r = b << k return r % T end @@ -178,23 +190,23 @@ julia> gcdx(240, 46) Bézout coefficients that are computed by the extended Euclidean algorithm. (Ref: D. Knuth, TAoCP, 2/e, p. 325, Algorithm X.) For signed integers, these coefficients `u` and `v` are minimal in - the sense that ``|u| < |y/d|`` and ``|v| < |x/d|``. Furthermore, + the sense that ``|u| < |b/d|`` and ``|v| < |a/d|``. Furthermore, the signs of `u` and `v` are chosen so that `d` is positive. For unsigned integers, the coefficients `u` and `v` might be near their `typemax`, and the identity then holds only via the unsigned integers' modulo arithmetic. """ -function gcdx(a::Integer, b::Integer) +Base.@assume_effects :terminates_locally function gcdx(a::Integer, b::Integer) T = promote_type(typeof(a), typeof(b)) # a0, b0 = a, b s0, s1 = oneunit(T), zero(T) t0, t1 = s1, s0 - # The loop invariant is: s0*a0 + t0*b0 == a + # The loop invariant is: s0*a0 + t0*b0 == a && s1*a0 + t1*b0 == b x = a % T y = b % T while y != 0 - q = div(x, y) - x, y = y, rem(x, y) + q, r = divrem(x, y) + x, y = y, r s0, s1 = s1, s0 - q*s1 t0, t1 = t1, t0 - q*t1 end @@ -234,7 +246,7 @@ function invmod(n::Integer, m::Integer) n == typeof(n)(-1) && m == typemin(typeof(n)) && return T(-1) end g, x, y = gcdx(n, m) - g != 1 && throw(DomainError((n, m), "Greatest common divisor is $g.")) + g != 1 && throw(DomainError((n, m), LazyString("Greatest common divisor is ", g, "."))) # Note that m might be negative here. if n isa Unsigned && hastypemax(typeof(n)) && x > typemax(n)>>1 # x might have wrapped if it would have been negative @@ -247,20 +259,20 @@ 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, - string("Cannot raise an integer x to a negative power ", p, '.', - "\nConvert input to float."))) -@noinline throw_domerr_powbysq(::Integer, p) = throw(DomainError(p, - string("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), ", - "or write 1/x^$(-p), float(x)^$p, x^float($p) or (x//1)^$p"))) -@noinline throw_domerr_powbysq(::AbstractMatrix, p) = throw(DomainError(p, - string("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), or write float(x)^$p or Rational.(x)^$p"))) -function power_by_squaring(x_, p::Integer) +@noinline throw_domerr_powbysq(::Any, p) = throw(DomainError(p, LazyString( + "Cannot raise an integer x to a negative power ", p, ".", + "\nConvert input to float."))) +@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, ")", + "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, ")", + "or write float(x)^", p, " or Rational.(x)^", p, "."))) +@assume_effects :terminates_locally function power_by_squaring(x_, p::Integer) x = to_power_type(x_) if p == 1 return copy(x) @@ -321,7 +333,6 @@ const HWNumber = Union{HWReal, Complex{<:HWReal}, Rational{<:HWReal}} @inline literal_pow(::typeof(^), x::HWNumber, ::Val{3}) = x*x*x @inline literal_pow(::typeof(^), x::HWNumber, ::Val{-1}) = inv(x) @inline literal_pow(::typeof(^), x::HWNumber, ::Val{-2}) = (i=inv(x); i*i) -@inline literal_pow(::typeof(^), x::HWNumber, ::Val{-3}) = (i=inv(x); i*i*i) # don't use the inv(x) transformation here since float^p is slightly more accurate @inline literal_pow(::typeof(^), x::AbstractFloat, ::Val{p}) where {p} = x^p @@ -371,13 +382,26 @@ julia> powermod(5, 3, 19) ``` """ function powermod(x::Integer, p::Integer, m::T) where T<:Integer - p < 0 && return powermod(invmod(x, m), -p, m) p == 0 && return mod(one(m),m) + # When the concrete type of p is signed and has the lowest value, + # `p != 0 && p == -p` is equivalent to `p == typemin(typeof(p))` for 2's complement representation. + # but will work for integer types like `BigInt` that don't have `typemin` defined + # It needs special handling otherwise will cause overflow problem. + if p == -p + imod = invmod(x, m) + rhalf = powermod(imod, -(p÷2), m) + r::T = mod(widemul(rhalf, rhalf), m) + isodd(p) && (r = mod(widemul(r, imod), m)) + #else odd + return r + elseif p < 0 + return powermod(invmod(x, m), -p, m) + end (m == 1 || m == -1) && return zero(m) b = oftype(m,mod(x,m)) # this also checks for divide by zero t = prevpow(2, p) - r::T = 1 + r = 1 while true if p >= t r = mod(widemul(r,b),m) @@ -393,9 +417,9 @@ end # optimization: promote the modulus m to BigInt only once (cf. widemul in generic powermod above) powermod(x::Integer, p::Integer, m::Union{Int128,UInt128}) = oftype(m, powermod(x, p, big(m))) -_nextpow2(x::Unsigned) = oneunit(x)<<((sizeof(x)<<3)-leading_zeros(x-oneunit(x))) +_nextpow2(x::Unsigned) = oneunit(x)<<(top_set_bit(x-oneunit(x))) _nextpow2(x::Integer) = reinterpret(typeof(x),x < 0 ? -_nextpow2(unsigned(-x)) : _nextpow2(unsigned(x))) -_prevpow2(x::Unsigned) = one(x) << unsigned((sizeof(x)<<3)-leading_zeros(x)-1) +_prevpow2(x::Unsigned) = one(x) << unsigned(top_set_bit(x)-1) _prevpow2(x::Integer) = reinterpret(typeof(x),x < 0 ? -_prevpow2(unsigned(-x)) : _prevpow2(unsigned(x))) """ @@ -462,9 +486,16 @@ function nextpow(a::Real, x::Real) a <= 1 && throw(DomainError(a, "`a` must be greater than 1.")) x <= 1 && return one(a) n = ceil(Integer,log(a, x)) + # round-off error of log can go either direction, so need some checks p = a^(n-1) - # guard against roundoff error, e.g., with a=5 and x=125 - p >= x ? p : a^n + x > typemax(p) && throw(DomainError(x,"argument is beyond the range of type of the base")) + p >= x && return p + wp = a^n + wp > p || throw(OverflowError("result is beyond the range of type of the base")) + wp >= x && return wp + wwp = a^(n+1) + wwp > wp || throw(OverflowError("result is beyond the range of type of the base")) + return wwp end """ @@ -496,13 +527,18 @@ function prevpow(a::T, x::Real) where T <: Real a == 2 && isa(x, Integer) && return _prevpow2(x) a <= 1 && throw(DomainError(a, "`a` must be greater than 1.")) n = floor(Integer,log(a, x)) + # round-off error of log can go either direction, so need some checks p = a^n + x > typemax(p) && throw(DomainError(x,"argument is beyond the range of type of the base")) if a isa Integer wp, overflow = mul_with_overflow(a, p) - return (wp <= x && !overflow) ? wp : p + wp <= x && !overflow && return wp + else + wp = a^(n+1) + wp <= x && return wp end - wp = p*a - return wp <= x ? wp : p + p <= x && return p + return a^(n-1) end ## ndigits (number of digits) in base 10 ## @@ -516,7 +552,7 @@ const powers_of_ten = [ 0x002386f26fc10000, 0x016345785d8a0000, 0x0de0b6b3a7640000, 0x8ac7230489e80000, ] function bit_ndigits0z(x::Base.BitUnsigned64) - lz = (sizeof(x)<<3)-leading_zeros(x) + lz = top_set_bit(x) nd = (1233*lz)>>12+1 nd -= x < powers_of_ten[nd] end @@ -561,12 +597,12 @@ function ndigits0zpb(x::Integer, b::Integer) x = abs(x) if x isa Base.BitInteger x = unsigned(x)::Unsigned - b == 2 && return sizeof(x)<<3 - leading_zeros(x) - b == 8 && return (sizeof(x)<<3 - leading_zeros(x) + 2) ÷ 3 + b == 2 && return top_set_bit(x) + b == 8 && return (top_set_bit(x) + 2) ÷ 3 b == 16 && return sizeof(x)<<1 - leading_zeros(x)>>2 b == 10 && return bit_ndigits0z(x) if ispow2(b) - dv, rm = divrem(sizeof(x)<<3 - leading_zeros(x), trailing_zeros(b)) + dv, rm = divrem(top_set_bit(x), trailing_zeros(b)) return iszero(rm) ? dv : dv + 1 end end @@ -628,6 +664,9 @@ function ndigits0z(x::Integer, b::Integer) end end +# Extends the definition in base/int.jl +top_set_bit(x::Integer) = ceil(Integer, log2(x + oneunit(x))) + """ ndigits(n::Integer; base::Integer=10, pad::Integer=1) @@ -639,6 +678,9 @@ See also [`digits`](@ref), [`count_ones`](@ref). # Examples ```jldoctest +julia> ndigits(0) +1 + julia> ndigits(12345) 5 @@ -660,7 +702,7 @@ ndigits(x::Integer; base::Integer=10, pad::Integer=1) = max(pad, ndigits0z(x, ba ## integer to string functions ## function bin(x::Unsigned, pad::Int, neg::Bool) - m = 8 * sizeof(x) - leading_zeros(x) + m = top_set_bit(x) n = neg + max(pad, m) a = StringVector(n) # for i in 0x0:UInt(n-1) # automatic vectorization produces redundant codes @@ -687,7 +729,7 @@ function bin(x::Unsigned, pad::Int, neg::Bool) end function oct(x::Unsigned, pad::Int, neg::Bool) - m = div(8 * sizeof(x) - leading_zeros(x) + 2, 3) + m = div(top_set_bit(x) + 2, 3) n = neg + max(pad, m) a = StringVector(n) i = n @@ -891,7 +933,7 @@ end """ hastypemax(T::Type) -> Bool -Return true if and only if the extrema `typemax(T)` and `typemin(T)` are defined. +Return `true` if and only if the extrema `typemax(T)` and `typemin(T)` are defined. """ hastypemax(::Base.BitIntegerType) = true hastypemax(::Type{Bool}) = true @@ -1047,7 +1089,7 @@ julia> binomial(-5, 3) # External links * [Binomial coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient) on Wikipedia. """ -function binomial(n::T, k::T) where T<:Integer +Base.@assume_effects :terminates_locally function binomial(n::T, k::T) where T<:Integer n0, k0 = n, k k < 0 && return zero(T) sgn = one(T) @@ -1069,9 +1111,40 @@ function binomial(n::T, k::T) where T<:Integer while rr <= k xt = div(widemul(x, nn), rr) x = xt % T - x == xt || throw(OverflowError("binomial($n0, $k0) overflows")) + x == xt || throw(OverflowError(LazyString("binomial(", n0, ", ", k0, ") overflows"))) rr += one(T) nn += one(T) end copysign(x, sgn) end + +""" + binomial(x::Number, k::Integer) + +The generalized binomial coefficient, defined for `k ≥ 0` by +the polynomial +```math +\\frac{1}{k!} \\prod_{j=0}^{k-1} (x - j) +``` +When `k < 0` it returns zero. + +For the case of integer `x`, this is equivalent to the ordinary +integer binomial coefficient +```math +\\binom{n}{k} = \\frac{n!}{k! (n-k)!} +``` + +Further generalizations to non-integer `k` are mathematically possible, but +involve the Gamma function and/or the beta function, which are +not provided by the Julia standard library but are available +in external packages such as [SpecialFunctions.jl](https://github.com/JuliaMath/SpecialFunctions.jl). + +# External links +* [Binomial coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient) on Wikipedia. +""" +function binomial(x::Number, k::Integer) + k < 0 && return zero(x)/one(k) + # we don't use prod(i -> (x-i+1), 1:k) / factorial(k), + # and instead divide each term by i, to avoid spurious overflow. + return prod(i -> (x-(i-1))/i, OneTo(k), init=oneunit(x)/one(k)) +end diff --git a/base/io.jl b/base/io.jl index 69972249be02f..9c00c57576bac 100644 --- a/base/io.jl +++ b/base/io.jl @@ -173,6 +173,19 @@ function will block to wait for more data if necessary, and then return `false`. it is always safe to read one byte after seeing `eof` return `false`. `eof` will return `false` as long as buffered data is still available, even if the remote end of a connection is closed. + +# Examples +```jldoctest +julia> b = IOBuffer("my buffer"); + +julia> eof(b) +false + +julia> seekend(b); + +julia> eof(b) +true +``` """ function eof end @@ -206,6 +219,8 @@ julia> read(io, String) ``` """ read(stream, t) +read(stream, ::Type{Union{}}, slurp...; kwargs...) = error("cannot read a value of type Union{}") + """ write(io::IO, x) @@ -368,11 +383,9 @@ descriptor upon completion. # Examples ```jldoctest -julia> open("myfile.txt", "w") do io - write(io, "Hello world!") - end; +julia> write("myfile.txt", "Hello world!"); -julia> open(f->read(f, String), "myfile.txt") +julia> open(io->read(io, String), "myfile.txt") "Hello world!" julia> rm("myfile.txt") @@ -445,7 +458,7 @@ wait_close(io::AbstractPipe) = (wait_close(pipe_writer(io)::IO); wait_close(pipe # Exception-safe wrappers (io = open(); try f(io) finally close(io)) -write(filename::AbstractString, a1, args...) = open(io->write(io, a1, args...), filename, "w") +write(filename::AbstractString, a1, args...) = open(io->write(io, a1, args...), convert(String, filename)::String, "w") """ read(filename::AbstractString, args...) @@ -457,9 +470,9 @@ Open a file and read its contents. `args` is passed to `read`: this is equivalen Read the entire contents of a file as a string. """ -read(filename::AbstractString, args...) = open(io->read(io, args...), filename) +read(filename::AbstractString, args...) = open(io->read(io, args...), convert(String, filename)::String) -read(filename::AbstractString, ::Type{T}) where {T} = open(io->read(io, T), filename) +read(filename::AbstractString, ::Type{T}) where {T} = open(io->read(io, T), convert(String, filename)::String) """ read!(stream::IO, array::AbstractArray) @@ -469,7 +482,7 @@ Read binary data from an I/O stream or file, filling in `array`. """ function read! end -read!(filename::AbstractString, a) = open(io->read!(io, a), filename) +read!(filename::AbstractString, a) = open(io->read!(io, a), convert(String, filename)::String) """ readuntil(stream::IO, delim; keep::Bool = false) @@ -482,10 +495,7 @@ The text is assumed to be encoded in UTF-8. # Examples ```jldoctest -julia> open("my_file.txt", "w") do io - write(io, "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); - end -57 +julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readuntil("my_file.txt", 'L') "Julia" @@ -496,7 +506,7 @@ julia> readuntil("my_file.txt", '.', keep = true) julia> rm("my_file.txt") ``` """ -readuntil(filename::AbstractString, args...; kw...) = open(io->readuntil(io, args...; kw...), filename) +readuntil(filename::AbstractString, args...; kw...) = open(io->readuntil(io, args...; kw...), convert(String, filename)::String) """ readline(io::IO=stdin; keep::Bool=false) @@ -511,10 +521,7 @@ line. # Examples ```jldoctest -julia> open("my_file.txt", "w") do io - write(io, "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); - end -57 +julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readline("my_file.txt") "JuliaLang is a GitHub organization." @@ -562,10 +569,7 @@ arguments and saving the resulting lines as a vector of strings. See also # Examples ```jldoctest -julia> open("my_file.txt", "w") do io - write(io, "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); - end -57 +julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readlines("my_file.txt") 2-element Vector{String}: @@ -948,9 +952,7 @@ if there is one. Equivalent to `chomp(read(x, String))`. # Examples ```jldoctest -julia> open("my_file.txt", "w") do io - write(io, "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); - end; +julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readchomp("my_file.txt") "JuliaLang is a GitHub organization.\\nIt has many members." @@ -1001,7 +1003,7 @@ function read(s::IO, nb::Integer = typemax(Int)) return resize!(b, nr) end -read(s::IO, ::Type{String}) = String(read(s)) +read(s::IO, ::Type{String}) = String(read(s)::Vector{UInt8}) read(s::IO, T::Type) = error("The IO stream does not support reading objects of type $T.") ## high-level iterator interfaces ## @@ -1034,9 +1036,7 @@ lines, respectively. # Examples ```jldoctest -julia> open("my_file.txt", "w") do io - write(io, "JuliaLang is a GitHub organization.\\n It has many members.\\n"); - end; +julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\n It has many members.\\n"); julia> for line in eachline("my_file.txt") print(line) @@ -1306,6 +1306,7 @@ end """ countlines(io::IO; eol::AbstractChar = '\\n') + countlines(filename::AbstractString; eol::AbstractChar = '\\n') Read `io` until the end of the stream/file and count the number of lines. To specify a file pass the filename as the first argument. EOL markers other than `'\\n'` are supported by @@ -1333,6 +1334,19 @@ julia> io = IOBuffer("JuliaLang is a GitHub organization."); julia> countlines(io, eol = '.') 1 +``` +```jldoctest +julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\n") +36 + +julia> countlines("my_file.txt") +1 + +julia> countlines("my_file.txt", eol = 'n') +4 + +julia> rm("my_file.txt") + ``` """ function countlines(io::IO; eol::AbstractChar='\n') diff --git a/base/iobuffer.jl b/base/iobuffer.jl index e08a019d84a2c..6c95285f232f2 100644 --- a/base/iobuffer.jl +++ b/base/iobuffer.jl @@ -5,6 +5,7 @@ # Stateful string mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO data::T # T should support: getindex, setindex!, length, copyto!, and 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 @@ -17,7 +18,7 @@ mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO 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,readable,writable,seekable,append,length(data),maxsize,1,-1) + new(data,false,readable,writable,seekable,append,length(data),maxsize,1,-1) end end const IOBuffer = GenericIOBuffer{Vector{UInt8}} @@ -137,8 +138,12 @@ 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) +_similar_data(b::GenericIOBuffer, len::Int) = similar(b.data, len) +_similar_data(b::IOBuffer, len::Int) = StringVector(len) + function copy(b::GenericIOBuffer) - ret = typeof(b)(b.writable ? copy(b.data) : b.data, + ret = typeof(b)(b.reinit ? _similar_data(b, 0) : b.writable ? + copyto!(_similar_data(b, length(b.data)), b.data) : b.data, b.readable, b.writable, b.seekable, b.append, b.maxsize) ret.size = b.size ret.ptr = b.ptr @@ -203,7 +208,7 @@ function read_sub(from::GenericIOBuffer, a::AbstractArray{T}, offs, nel) where T GC.@preserve a unsafe_read(from, pointer(a, offs), nb) else for i = offs:offs+nel-1 - a[i] = read(to, T) + a[i] = read(from, T) end end return a @@ -270,7 +275,10 @@ function truncate(io::GenericIOBuffer, n::Integer) 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)")) - if n > length(io.data) + if io.reinit + io.data = _similar_data(io, n) + io.reinit = false + elseif n > length(io.data) resize!(io.data, n) end io.data[io.size+1:n] .= 0 @@ -325,9 +333,14 @@ end ensureroom_slowpath(io, nshort) end n = min((nshort % Int) + (io.append ? io.size : io.ptr-1), io.maxsize) - l = length(io.data) - if n > l - _growend!(io.data, (n - l) % UInt) + 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 end return io end @@ -390,18 +403,26 @@ end function take!(io::IOBuffer) ismarked(io) && unmark(io) if io.seekable - data = io.data if io.writable - maxsize = (io.maxsize == typemax(Int) ? 0 : min(length(io.data),io.maxsize)) - io.data = StringVector(maxsize) + if io.reinit + data = StringVector(0) + else + data = resize!(io.data, io.size) + io.reinit = true + end else - data = copy(data) + data = copyto!(StringVector(io.size), 1, io.data, 1, io.size) end - resize!(data,io.size) else nbytes = bytesavailable(io) - a = StringVector(nbytes) - data = read!(io, a) + if io.writable + data = io.data + io.reinit = true + _deletebeg!(data, io.ptr-1) + resize!(data, nbytes) + else + data = read!(io, StringVector(nbytes)) + end end if io.writable io.ptr = 1 @@ -410,6 +431,19 @@ function take!(io::IOBuffer) return data end +""" + _unsafe_take!(io::IOBuffer) + +This simply returns the raw resized `io.data`, with no checks to be +sure that `io` is readable etcetera, and leaves `io` in an inconsistent +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. +""" +_unsafe_take!(io::IOBuffer) = resize!(io.data, io.size) + function write(to::IO, from::GenericIOBuffer) if to === from from.ptr = from.size + 1 diff --git a/base/iostream.jl b/base/iostream.jl index 0af0e244cf357..23dfb53256e82 100644 --- a/base/iostream.jl +++ b/base/iostream.jl @@ -272,7 +272,7 @@ safe multi-threaded access. !!! compat "Julia 1.5" The `lock` argument is available as of Julia 1.5. """ -function open(fname::AbstractString; lock = true, +function open(fname::String; lock = true, read :: Union{Bool,Nothing} = nothing, write :: Union{Bool,Nothing} = nothing, create :: Union{Bool,Nothing} = nothing, @@ -299,6 +299,7 @@ function open(fname::AbstractString; lock = true, end return s end +open(fname::AbstractString; kwargs...) = open(convert(String, fname)::String; kwargs...) """ open(filename::AbstractString, [mode::AbstractString]; lock = true) -> IOStream diff --git a/base/irrationals.jl b/base/irrationals.jl index f3a9817f1ee35..6513e3269a4d7 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -24,17 +24,17 @@ abstract type AbstractIrrational <: Real end Number type representing an exact irrational value denoted by the symbol `sym`, such as [`π`](@ref pi), [`ℯ`](@ref) and [`γ`](@ref Base.MathConstants.eulergamma). -See also [`@irrational`], [`AbstractIrrational`](@ref). +See also [`AbstractIrrational`](@ref). """ struct Irrational{sym} <: AbstractIrrational end show(io::IO, x::Irrational{sym}) where {sym} = print(io, sym) function show(io::IO, ::MIME"text/plain", x::Irrational{sym}) where {sym} - if get(io, :compact, false) + if get(io, :compact, false)::Bool print(io, sym) else - print(io, sym, " = ", string(float(x))[1:15], "...") + print(io, sym, " = ", string(float(x))[1:min(end,15)], "...") end end @@ -48,7 +48,8 @@ AbstractFloat(x::AbstractIrrational) = Float64(x)::Float64 Float16(x::AbstractIrrational) = Float16(Float32(x)::Float32) Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x)) -@pure function Rational{T}(x::AbstractIrrational) where T<:Integer +# XXX this may change `DEFAULT_PRECISION`, thus not effect free +@assume_effects :total function Rational{T}(x::AbstractIrrational) where T<:Integer o = precision(BigFloat) p = 256 while true @@ -64,7 +65,7 @@ Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x)) end Rational{BigInt}(x::AbstractIrrational) = throw(ArgumentError("Cannot convert an AbstractIrrational to a Rational{BigInt}: use rationalize(BigInt, x) instead")) -@pure function (t::Type{T})(x::AbstractIrrational, r::RoundingMode) where T<:Union{Float32,Float64} +@assume_effects :total function (t::Type{T})(x::AbstractIrrational, r::RoundingMode) where T<:Union{Float32,Float64} setprecision(BigFloat, 256) do T(BigFloat(x)::BigFloat, r) end @@ -106,11 +107,11 @@ end <=(x::AbstractFloat, y::AbstractIrrational) = x < y # Irrational vs Rational -@pure function rationalize(::Type{T}, x::AbstractIrrational; tol::Real=0) where T +@assume_effects :total function rationalize(::Type{T}, x::AbstractIrrational; tol::Real=0) where T return rationalize(T, big(x), tol=tol) end -@pure function lessrational(rx::Rational{<:Integer}, x::AbstractIrrational) - # an @pure version of `<` for determining if the rationalization of +@assume_effects :total function lessrational(rx::Rational{<:Integer}, x::AbstractIrrational) + # an @assume_effects :total version of `<` for determining if the rationalization of # an irrational number required rounding up or down return rx < big(x) end @@ -153,6 +154,8 @@ zero(::Type{<:AbstractIrrational}) = false one(::AbstractIrrational) = true one(::Type{<:AbstractIrrational}) = true +sign(x::AbstractIrrational) = ifelse(x < zero(x), -1.0, 1.0) + -(x::AbstractIrrational) = -Float64(x) for op in Symbol[:+, :-, :*, :/, :^] @eval $op(x::AbstractIrrational, y::AbstractIrrational) = $op(Float64(x),Float64(y)) @@ -162,13 +165,51 @@ end round(x::Irrational, r::RoundingMode) = round(float(x), r) """ - @irrational sym val def - @irrational(sym, val, def) + @irrational sym [val] def + +Define a new `Irrational` value, `sym`, with arbitrary-precision definition in terms +of `BigFloat`s given by the expression `def`. + +Optionally provide a pre-computed `Float64` value `val` which must equal `Float64(def)`. +`val` will be computed automatically if omitted. + +An `AssertionError` is thrown when either `big(def) isa BigFloat` or `Float64(val) == Float64(def)` +returns `false`. + +!!! warning + This macro should not be used outside of `Base` Julia. + + The macro creates a new type `Irrational{:sym}` regardless of where it's invoked. This can + lead to conflicting definitions if two packages define an irrational number with the same + name but different values. + + +# Examples +```jldoctest +julia> Base.@irrational twoπ 2*big(π) -Define a new `Irrational` value, `sym`, with pre-computed `Float64` value `val`, -and arbitrary-precision definition in terms of `BigFloat`s given by the expression `def`. +julia> twoπ +twoπ = 6.2831853071795... + +julia> Base.@irrational sqrt2 1.4142135623730950488 √big(2) + +julia> sqrt2 +sqrt2 = 1.4142135623730... + +julia> Base.@irrational sqrt2 1.4142135623730950488 big(2) +ERROR: AssertionError: big($(Expr(:escape, :sqrt2))) isa BigFloat + +julia> Base.@irrational sqrt2 1.41421356237309 √big(2) +ERROR: AssertionError: Float64($(Expr(:escape, :sqrt2))) == Float64(big($(Expr(:escape, :sqrt2)))) +``` """ macro irrational(sym, val, def) + irrational(sym, val, def) +end +macro irrational(sym, def) + irrational(sym, :(big($(esc(sym)))), def) +end +function irrational(sym, val, def) esym = esc(sym) qsym = esc(Expr(:quote, sym)) bigconvert = isa(def,Symbol) ? quote @@ -188,8 +229,10 @@ macro irrational(sym, val, def) quote const $esym = Irrational{$qsym}() $bigconvert - Base.Float64(::Irrational{$qsym}) = $val - Base.Float32(::Irrational{$qsym}) = $(Float32(val)) + let v = $val, v64 = Float64(v), v32 = Float32(v) + Base.Float64(::Irrational{$qsym}) = v64 + Base.Float32(::Irrational{$qsym}) = v32 + end @assert isa(big($esym), BigFloat) @assert Float64($esym) == Float64(big($esym)) @assert Float32($esym) == Float32(big($esym)) diff --git a/base/iterators.jl b/base/iterators.jl index 3e339c59bebcb..11e94d3384de8 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -3,16 +3,29 @@ """ Methods for working with Iterators. """ -module Iterators +baremodule Iterators # small dance to make this work from Base or Intrinsics import ..@__MODULE__, ..parentmodule const Base = parentmodule(@__MODULE__) using .Base: - @inline, Pair, Pairs, AbstractDict, IndexLinear, IndexCartesian, IndexStyle, AbstractVector, Vector, - tail, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype, OneTo, - @propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator, AbstractRange, - LinearIndices, (:), |, +, -, !==, !, <=, <, missing, any, _counttuple + @inline, Pair, Pairs, AbstractDict, IndexLinear, IndexStyle, AbstractVector, Vector, + SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype, OneTo, + @propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator, + AbstractRange, AbstractUnitRange, UnitRange, LinearIndices, TupleOrBottom, + (:), |, +, -, *, !==, !, ==, !=, <=, <, >, >=, missing, + any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex, + tail, fieldtypes, min, max, minimum, zero, oneunit, promote, promote_shape +using Core: @doc + +if Base !== Core.Compiler +using .Base: + cld, fld, SubArray, view, resize!, IndexCartesian +using .Base.Checked: checked_mul +else + # Checked.checked_mul is not available during bootstrapping: + const checked_mul = * +end import .Base: first, last, @@ -20,9 +33,13 @@ import .Base: eltype, IteratorSize, IteratorEltype, haskey, keys, values, pairs, getindex, setindex!, get, iterate, - popfirst!, isdone, peek + popfirst!, isdone, peek, intersect -export enumerate, zip, rest, countfrom, take, drop, takewhile, dropwhile, cycle, repeated, product, flatten, partition +export enumerate, zip, rest, countfrom, take, drop, takewhile, dropwhile, cycle, repeated, product, flatten, flatmap + +if Base !== Core.Compiler +export partition +end """ Iterators.map(f, iterators...) @@ -55,7 +72,10 @@ _min_length(a, b, A, B) = min(length(a),length(b)) _diff_length(a, b, A, ::IsInfinite) = 0 _diff_length(a, b, ::IsInfinite, ::IsInfinite) = 0 _diff_length(a, b, ::IsInfinite, B) = length(a) # inherit behaviour, error -_diff_length(a, b, A, B) = max(length(a)-length(b), 0) +function _diff_length(a, b, A, B) + m, n = length(a), length(b) + return m > n ? m - n : zero(n - m) +end and_iteratorsize(isz::T, ::T) where {T} = isz and_iteratorsize(::HasLength, ::HasShape) = HasLength() @@ -72,15 +92,20 @@ and_iteratoreltype(a, b) = EltypeUnknown() Given an iterator `itr`, then `reverse(itr)` is an iterator over the same collection but in the reverse order. - This iterator is "lazy" in that it does not make a copy of the collection in order to reverse it; see [`Base.reverse`](@ref) for an eager implementation. +(By default, this returns +an `Iterators.Reverse` object wrapping `itr`, which is iterable +if the corresponding [`iterate`](@ref) methods are defined, but some `itr` types +may implement more specialized `Iterators.reverse` behaviors.) + Not all iterator types `T` support reverse-order iteration. If `T` doesn't, then iterating over `Iterators.reverse(itr::T)` will throw a [`MethodError`](@ref) -because of the missing [`iterate`](@ref) methods for `Iterators.Reverse{T}`. +because of the missing `iterate` methods for `Iterators.Reverse{T}`. (To implement these methods, the original iterator -`itr::T` can be obtained from `r = Iterators.reverse(itr)` by `r.itr`.) +`itr::T` can be obtained from an `r::Iterators.Reverse{T}` object by `r.itr`; +more generally, one can use `Iterators.reverse(r)`.) # Examples ```jldoctest @@ -141,10 +166,12 @@ end An iterator that yields `(i, x)` where `i` is a counter starting at 1, and `x` is the `i`th value from the given iterator. It's useful when you need not only the values `x` over which you are iterating, but -also the number of iterations so far. Note that `i` may not be valid -for indexing `iter`; it's also possible that `x != iter[i]`, if `iter` -has indices that do not start at 1. See the `pairs(IndexLinear(), -iter)` method if you want to ensure that `i` is an index. +also the number of iterations so far. + +Note that `i` may not be valid for indexing `iter`, or may index a +different element. This will happen if `iter` has indices that do not +start at 1, and may happen for strings, dictionaries, etc. +See the `pairs(IndexLinear(), iter)` method if you want to ensure that `i` is an index. # Examples ```jldoctest @@ -156,6 +183,18 @@ julia> for (index, value) in enumerate(a) 1 a 2 b 3 c + +julia> str = "naïve"; + +julia> for (i, val) in enumerate(str) + print("i = ", i, ", val = ", val, ", ") + try @show(str[i]) catch e println(e) end + end +i = 1, val = n, str[i] = 'n' +i = 2, val = a, str[i] = 'a' +i = 3, val = ï, str[i] = 'ï' +i = 4, val = v, StringIndexError("naïve", 4) +i = 5, val = e, str[i] = 'v' ``` """ enumerate(iter) = Enumerate(iter) @@ -170,7 +209,7 @@ size(e::Enumerate) = size(e.itr) end last(e::Enumerate) = (length(e.itr), e.itr[end]) -eltype(::Type{Enumerate{I}}) where {I} = Tuple{Int, eltype(I)} +eltype(::Type{Enumerate{I}}) where {I} = TupleOrBottom(Int, eltype(I)) IteratorSize(::Type{Enumerate{I}}) where {I} = IteratorSize(I) IteratorEltype(::Type{Enumerate{I}}) where {I} = IteratorEltype(I) @@ -200,7 +239,7 @@ of `A`. Specifying [`IndexLinear()`](@ref) ensures that `i` will be an integer; specifying [`IndexCartesian()`](@ref) ensures that `i` will be a -[`CartesianIndex`](@ref); specifying `IndexStyle(A)` chooses whichever has +[`Base.CartesianIndex`](@ref); specifying `IndexStyle(A)` chooses whichever has been defined as the native indexing style for array `A`. Mutation of the bounds of the underlying array will invalidate this iterator. @@ -233,23 +272,26 @@ CartesianIndex(2, 2) e See also [`IndexStyle`](@ref), [`axes`](@ref). """ pairs(::IndexLinear, A::AbstractArray) = Pairs(A, LinearIndices(A)) -pairs(::IndexCartesian, A::AbstractArray) = Pairs(A, CartesianIndices(axes(A))) # preserve indexing capabilities for known indexable types # faster than zip(keys(a), values(a)) for arrays pairs(tuple::Tuple) = Pairs{Int}(tuple, keys(tuple)) pairs(nt::NamedTuple) = Pairs{Symbol}(nt, keys(nt)) pairs(v::Core.SimpleVector) = Pairs(v, LinearIndices(v)) -pairs(A::AbstractArray) = pairs(IndexCartesian(), A) pairs(A::AbstractVector) = pairs(IndexLinear(), A) # pairs(v::Pairs) = v # listed for reference, but already defined from being an AbstractDict +if Base !== Core.Compiler +pairs(::IndexCartesian, A::AbstractArray) = Pairs(A, Base.CartesianIndices(axes(A))) +pairs(A::AbstractArray) = pairs(IndexCartesian(), A) +end + length(v::Pairs) = length(getfield(v, :itr)) axes(v::Pairs) = axes(getfield(v, :itr)) size(v::Pairs) = size(getfield(v, :itr)) -@propagate_inbounds function _pairs_elt(p::Pairs{K, V}, idx) where {K, V} - return Pair{K, V}(idx, getfield(p, :data)[idx]) +Base.@eval @propagate_inbounds function _pairs_elt(p::Pairs{K, V}, idx) where {K, V} + return $(Expr(:new, :(Pair{K, V}), :idx, :(getfield(p, :data)[idx]))) end @propagate_inbounds function iterate(p::Pairs{K, V}, state...) where {K, V} @@ -269,7 +311,7 @@ end @inline isdone(v::Pairs, state...) = isdone(getfield(v, :itr), state...) IteratorSize(::Type{<:Pairs{<:Any, <:Any, I}}) where {I} = IteratorSize(I) -IteratorSize(::Type{<:Pairs{<:Any, <:Any, <:Base.AbstractUnitRange, <:Tuple}}) = HasLength() +IteratorSize(::Type{<:Pairs{<:Any, <:Any, <:AbstractUnitRange, <:Tuple}}) = HasLength() function last(v::Pairs{K, V}) where {K, V} idx = last(getfield(v, :itr)) @@ -300,7 +342,11 @@ the `zip` iterator is a tuple of values of its subiterators. `zip` orders the calls to its subiterators in such a way that stateful iterators will not advance when another iterator finishes in the current iteration. -See also: [`enumerate`](@ref), [`splat`](@ref Base.splat). +!!! note + + `zip()` with no arguments yields an infinite iterator of empty tuples. + +See also: [`enumerate`](@ref), [`Base.splat`](@ref). # Examples ```jldoctest @@ -348,7 +394,7 @@ _promote_tuple_shape((m,)::Tuple{Integer}, (n,)::Tuple{Integer}) = (min(m, n),) _promote_tuple_shape(a, b) = promote_shape(a, b) _promote_tuple_shape(a, b...) = _promote_tuple_shape(a, _promote_tuple_shape(b...)) _promote_tuple_shape(a) = a -eltype(::Type{Zip{Is}}) where {Is<:Tuple} = Tuple{ntuple(n -> eltype(fieldtype(Is, n)), _counttuple(Is)::Int)...} +eltype(::Type{Zip{Is}}) where {Is<:Tuple} = TupleOrBottom(map(eltype, fieldtypes(Is))...) #eltype(::Type{Zip{Tuple{}}}) = Tuple{} #eltype(::Type{Zip{Tuple{A}}}) where {A} = Tuple{eltype(A)} #eltype(::Type{Zip{Tuple{A, B}}}) where {A, B} = Tuple{eltype(A), eltype(B)} @@ -669,7 +715,7 @@ end An iterator that generates at most the first `n` elements of `iter`. -See also: [`drop`](@ref Iterators.drop), [`peel`](@ref Iterators.peel), [`first`](@ref), [`take!`](@ref). +See also: [`drop`](@ref Iterators.drop), [`peel`](@ref Iterators.peel), [`first`](@ref), [`Base.take!`](@ref). # Examples ```jldoctest @@ -882,7 +928,7 @@ end An iterator that cycles through `iter` forever. If `iter` is empty, so is `cycle(iter)`. -See also: [`Iterators.repeated`](@ref), [`repeat`](@ref). +See also: [`Iterators.repeated`](@ref), [`Base.repeat`](@ref). # Examples ```jldoctest @@ -924,7 +970,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), [`repeat`](@ref). +See also: [`Iterators.cycle`](@ref), [`Base.repeat`](@ref). # Examples ```jldoctest @@ -1013,7 +1059,7 @@ _prod_axes1(a, A) = throw(ArgumentError("Cannot compute indices for object of type $(typeof(a))")) ndims(p::ProductIterator) = length(axes(p)) -length(P::ProductIterator) = prod(size(P)) +length(P::ProductIterator) = reduce(checked_mul, size(P); init=1) IteratorEltype(::Type{ProductIterator{Tuple{}}}) = HasEltype() IteratorEltype(::Type{ProductIterator{Tuple{I}}}) where {I} = IteratorEltype(I) @@ -1026,8 +1072,7 @@ end eltype(::Type{ProductIterator{I}}) where {I} = _prod_eltype(I) _prod_eltype(::Type{Tuple{}}) = Tuple{} -_prod_eltype(::Type{I}) where {I<:Tuple} = - Tuple{ntuple(n -> eltype(fieldtype(I, n)), _counttuple(I)::Int)...} +_prod_eltype(::Type{I}) where {I<:Tuple} = TupleOrBottom(ntuple(n -> eltype(fieldtype(I, n)), _counttuple(I)::Int)...) iterate(::ProductIterator{Tuple{}}) = (), true iterate(::ProductIterator{Tuple{}}, state) = nothing @@ -1041,6 +1086,7 @@ iterate(::ProductIterator{Tuple{}}, state) = nothing done1 === true || return done1 # false or missing return _pisdone(tail(iters), tail(states)) # check tail end +@inline isdone(::ProductIterator{Tuple{}}, states) = true @inline isdone(P::ProductIterator, states) = _pisdone(P.iterators, states) @inline _piterate() = () @@ -1081,6 +1127,7 @@ end reverse(p::ProductIterator) = ProductIterator(Base.map(reverse, p.iterators)) last(p::ProductIterator) = Base.map(last, p.iterators) +intersect(a::ProductIterator, b::ProductIterator) = ProductIterator(intersect.(a.iterators, b.iterators)) # flatten an iterator of iterators @@ -1123,6 +1170,7 @@ IteratorEltype(::Type{Flatten{Tuple{}}}) = IteratorEltype(Tuple{}) _flatteneltype(I, ::HasEltype) = IteratorEltype(eltype(I)) _flatteneltype(I, et) = EltypeUnknown() +flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{Union{}}, slurp...) = HasLength() # length==0 flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{<:NTuple{N,Any}}) where {N} = HasLength() flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{<:Tuple}) = SizeUnknown() flatten_iteratorsize(::Union{HasShape, HasLength}, ::Type{<:Number}) = HasLength() @@ -1134,6 +1182,7 @@ _flatten_iteratorsize(sz, ::HasEltype, ::Type{Tuple{}}) = HasLength() IteratorSize(::Type{Flatten{I}}) where {I} = _flatten_iteratorsize(IteratorSize(I), IteratorEltype(I), I) +flatten_length(f, T::Type{Union{}}, slurp...) = 0 function flatten_length(f, T::Type{<:NTuple{N,Any}}) where {N} return N * length(f.it) end @@ -1163,6 +1212,48 @@ reverse(f::Flatten) = Flatten(reverse(itr) for itr in reverse(f.it)) last(f::Flatten) = last(last(f.it)) """ + Iterators.flatmap(f, iterators...) + +Equivalent to `flatten(map(f, iterators...))`. + +See also [`Iterators.flatten`](@ref), [`Iterators.map`](@ref). + +!!! compat "Julia 1.9" + This function was added in Julia 1.9. + +# Examples +```jldoctest +julia> Iterators.flatmap(n -> -n:2:n, 1:3) |> collect +9-element Vector{Int64}: + -1 + 1 + -2 + 0 + 2 + -3 + -1 + 1 + 3 + +julia> stack(n -> -n:2:n, 1:3) +ERROR: DimensionMismatch: stack expects uniform slices, got axes(x) == (1:3,) while first had (1:2,) +[...] + +julia> Iterators.flatmap(n -> (-n, 10n), 1:2) |> collect +4-element Vector{Int64}: + -1 + 10 + -2 + 20 + +julia> ans == vec(stack(n -> (-n, 10n), 1:2)) +true +``` +""" +flatmap(f, c...) = flatten(map(f, c...)) + +if Base !== Core.Compiler # views are not defined +@doc """ partition(collection, n) Iterate over a collection `n` elements at a time. @@ -1175,8 +1266,7 @@ julia> collect(Iterators.partition([1,2,3,4,5], 2)) [3, 4] [5] ``` -""" -function partition(c, n::Integer) +""" function partition(c, n::Integer) n < 1 && throw(ArgumentError("cannot create partitions of length $n")) return PartitionIterator(c, Int(n)) end @@ -1186,7 +1276,7 @@ struct PartitionIterator{T} n::Int end # Partitions are explicitly a linear indexing operation, so reshape to 1-d immediately -PartitionIterator(A::AbstractArray, n::Int) = PartitionIterator(vec(A), n) +PartitionIterator(A::AbstractArray, n::Int) = PartitionIterator(Base.vec(A), n) PartitionIterator(v::AbstractVector, n::Int) = PartitionIterator{typeof(v)}(v, n) eltype(::Type{PartitionIterator{T}}) where {T} = Vector{eltype(T)} @@ -1244,7 +1334,7 @@ function iterate(itr::PartitionIterator, state...) return resize!(v, i), y === nothing ? IterationCutShort() : y[2] end -""" +@doc """ Stateful(itr) There are several different ways to think about this iterator wrapper: @@ -1257,7 +1347,7 @@ There are several different ways to think about this iterator wrapper: whenever an item is produced. `Stateful` provides the regular iterator interface. Like other mutable iterators -(e.g. [`Channel`](@ref)), if iteration is stopped early (e.g. by a [`break`](@ref) in a [`for`](@ref) loop), +(e.g. [`Base.Channel`](@ref)), if iteration is stopped early (e.g. by a [`break`](@ref) in a [`for`](@ref) loop), iteration can be resumed from the same spot by continuing to iterate over the same iterator object (in contrast, an immutable iterator would restart from the beginning). @@ -1302,49 +1392,63 @@ julia> sum(a) # Sum the remaining elements 7 ``` """ -mutable struct Stateful{T, VS} +mutable struct Stateful{T, VS, N<:Integer} itr::T # A bit awkward right now, but adapted to the new iteration protocol nextvalstate::Union{VS, Nothing} - taken::Int + + # 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} - new{T, Any}(itr, iterate(itr), 0) + itl = iterlength(itr) + new{T, Any, typeof(itl)}(itr, iterate(itr), itl) end @inline function Stateful(itr::T) where {T} VS = approx_iter_type(T) - return new{T, VS}(itr, iterate(itr)::VS, 0) + itl = iterlength(itr) + return new{T, VS, typeof(itl)}(itr, iterate(itr)::VS, itl) + end +end + +function iterlength(it)::Signed + if IteratorSize(it) isa Union{HasShape, HasLength} + return length(it) + else + -1 end end function reset!(s::Stateful{T,VS}, itr::T=s.itr) where {T,VS} s.itr = itr + itl = iterlength(itr) setfield!(s, :nextvalstate, iterate(itr)) - s.taken = 0 + s.remaining = itl s end -if Base === Core.Compiler - approx_iter_type(a::Type) = Any -else - # Try to find an appropriate type for the (value, state tuple), - # by doing a recursive unrolling of the iteration protocol up to - # fixpoint. - approx_iter_type(itrT::Type) = _approx_iter_type(itrT, Base._return_type(iterate, Tuple{itrT})) - # Not actually called, just passed to return type to avoid - # having to typesplit on Nothing - function doiterate(itr, valstate::Union{Nothing, Tuple{Any, Any}}) - valstate === nothing && return nothing - val, st = valstate - return iterate(itr, st) - end - function _approx_iter_type(itrT::Type, vstate::Type) - vstate <: Union{Nothing, Tuple{Any, Any}} || return Any - vstate <: Union{} && return Union{} - nextvstate = Base._return_type(doiterate, Tuple{itrT, vstate}) - return (nextvstate <: vstate ? vstate : Any) - end +# Try to find an appropriate type for the (value, state tuple), +# by doing a recursive unrolling of the iteration protocol up to +# fixpoint. +approx_iter_type(itrT::Type) = _approx_iter_type(itrT, Base._return_type(iterate, Tuple{itrT})) +# Not actually called, just passed to return type to avoid +# having to typesplit on Nothing +function doiterate(itr, valstate::Union{Nothing, Tuple{Any, Any}}) + valstate === nothing && return nothing + val, st = valstate + return iterate(itr, st) +end +function _approx_iter_type(itrT::Type, vstate::Type) + vstate <: Union{Nothing, Tuple{Any, Any}} || return Any + vstate <: Union{} && return Union{} + itrT <: Union{} && return Union{} + nextvstate = Base._return_type(doiterate, Tuple{itrT, vstate}) + return (nextvstate <: vstate ? vstate : Any) end +Stateful(x::Stateful) = x convert(::Type{Stateful}, itr) = Stateful(itr) @inline isdone(s::Stateful, st=nothing) = s.nextvalstate === nothing @@ -1352,11 +1456,12 @@ convert(::Type{Stateful}, itr) = Stateful(itr) @inline function popfirst!(s::Stateful) vs = s.nextvalstate if vs === nothing - throw(EOFError()) + throw(Base.EOFError()) else val, state = vs Core.setfield!(s, :nextvalstate, iterate(s.itr, state)) - s.taken += 1 + rem = s.remaining + s.remaining = rem - typeof(rem)(1) return val end end @@ -1366,10 +1471,21 @@ 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,VS}}) where {T,VS} = IteratorSize(T) isa HasShape ? HasLength() : IteratorSize(T) -eltype(::Type{Stateful{T, VS}} where VS) where {T} = eltype(T) -IteratorEltype(::Type{Stateful{T,VS}}) where {T,VS} = IteratorEltype(T) -length(s::Stateful) = length(s.itr) - s.taken +IteratorSize(::Type{<:Stateful{T}}) where {T} = IteratorSize(T) isa HasShape ? HasLength() : IteratorSize(T) +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 """ only(x) diff --git a/base/libc.jl b/base/libc.jl index 38b62847eaeb4..82286fbf01af6 100644 --- a/base/libc.jl +++ b/base/libc.jl @@ -225,7 +225,7 @@ function strptime(fmt::AbstractString, timestr::AbstractString) @static if Sys.isapple() # if we didn't explicitly parse the weekday or year day, use mktime # to fill them in automatically. - if !occursin(r"([^%]|^)%(a|A|j|w|Ow)", fmt) + if !occursin(r"([^%]|^)%(a|A|j|w|Ow)"a, fmt) ccall(:mktime, Int, (Ref{TmStruct},), tm) end end @@ -235,14 +235,14 @@ end # system date in seconds """ - time(t::TmStruct) + time(t::TmStruct) -> Float64 Converts a `TmStruct` struct to a number of seconds since the epoch. """ time(tm::TmStruct) = Float64(ccall(:mktime, Int, (Ref{TmStruct},), tm)) """ - time() + time() -> Float64 Get the system time in seconds since the epoch, with fairly high (typically, microsecond) resolution. """ @@ -255,12 +255,12 @@ time() = ccall(:jl_clock_now, Float64, ()) Get Julia's process ID. """ -getpid() = ccall(:jl_getpid, Int32, ()) +getpid() = ccall(:uv_os_getpid, Int32, ()) ## network functions ## """ - gethostname() -> AbstractString + gethostname() -> String Get the local machine's host name. """ @@ -376,31 +376,35 @@ free(p::Cwstring) = free(convert(Ptr{Cwchar_t}, p)) ## Random numbers ## +# Access to very high quality (kernel) randomness +function getrandom!(A::Union{Array,Base.RefValue}) + ret = ccall(:uv_random, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}, Csize_t, Cuint, Ptr{Cvoid}), + C_NULL, C_NULL, A, sizeof(A), 0, C_NULL) + Base.uv_error("getrandom", ret) + return A +end +_make_uint64_seed() = getrandom!(Base.RefValue{UInt64}())[] + # To limit dependency on rand functionality implemented in the Random module, -# Libc.rand is used in file.jl, and could be used in error.jl (but it breaks a test) +# Libc.rand is used in Base (it also is independent from Random.seed, so is +# only affected by `Libc.srand(seed)` calls) """ - rand([T::Type]) + rand([T::Type]=UInt32) -Interface to the C `rand()` function. If `T` is provided, generate a value of type `T` -by composing two calls to `rand()`. `T` can be `UInt32` or `Float64`. +Generate a random number of type `T`. `T` can be `UInt32` or `Float64`. """ -rand() = ccall(:rand, Cint, ()) -@static if Sys.iswindows() - # Windows RAND_MAX is 2^15-1 - rand(::Type{UInt32}) = ((rand() % UInt32) << 17) ⊻ ((rand() % UInt32) << 8) ⊻ (rand() % UInt32) -else - # RAND_MAX is at least 2^15-1 in theory, but we assume 2^16-1 - # on non-Windows systems (in practice, it's 2^31-1) - rand(::Type{UInt32}) = ((rand() % UInt32) << 16) ⊻ (rand() % UInt32) -end -rand(::Type{Float64}) = rand(UInt32) * 2.0^-32 +rand() = ccall(:jl_rand, UInt64, ()) % UInt32 +rand(::Type{UInt32}) = rand() +rand(::Type{Float64}) = rand() * 2.0^-32 """ srand([seed]) -Interface to the C `srand(seed)` function. +Set a value for the current global `seed`. """ -srand(seed=Base._make_uint_seed()) = ccall(:srand, Cvoid, (Cuint,), seed) +function srand(seed::Integer=_make_uint64_seed()) + ccall(:jl_srand, Cvoid, (UInt64,), seed % UInt64) +end struct Cpasswd username::Cstring @@ -433,7 +437,7 @@ end function getpwuid(uid::Unsigned, throw_error::Bool=true) ref_pd = Ref(Cpasswd()) - ret = ccall(:jl_os_get_passwd, Cint, (Ref{Cpasswd}, Culong), ref_pd, uid) + ret = ccall(:uv_os_get_passwd2, Cint, (Ref{Cpasswd}, Culong), ref_pd, uid) if ret != 0 throw_error && Base.uv_error("getpwuid", ret) return @@ -452,7 +456,7 @@ function getpwuid(uid::Unsigned, throw_error::Bool=true) end function getgrgid(gid::Unsigned, throw_error::Bool=true) ref_gp = Ref(Cgroup()) - ret = ccall(:jl_os_get_group, Cint, (Ref{Cgroup}, Culong), ref_gp, gid) + ret = ccall(:uv_os_get_group, Cint, (Ref{Cgroup}, Culong), ref_gp, gid) if ret != 0 throw_error && Base.uv_error("getgrgid", ret) return @@ -471,7 +475,7 @@ function getgrgid(gid::Unsigned, throw_error::Bool=true) gp.gid, members, ) - ccall(:jl_os_free_group, Cvoid, (Ref{Cgroup},), ref_gp) + ccall(:uv_os_free_group, Cvoid, (Ref{Cgroup},), ref_gp) return gp end diff --git a/base/libdl.jl b/base/libdl.jl index 4f29260bb24f8..fdf6103d1800b 100644 --- a/base/libdl.jl +++ b/base/libdl.jl @@ -185,7 +185,7 @@ function dlclose(p::Nothing) end """ - find_library(names, locations) + find_library(names [, locations]) Searches for the first library in `names` in the paths in the `locations` list, `DL_LOAD_PATH`, or system library paths (in that order) which can successfully be dlopen'd. diff --git a/base/libuv.jl b/base/libuv.jl index 53870188e75d9..24a04f5bcad78 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -61,8 +61,11 @@ function preserve_handle(x) end function unpreserve_handle(x) lock(preserve_handle_lock) - v = uvhandles[x]::Int - if v == 1 + v = get(uvhandles, x, 0)::Int + if v == 0 + unlock(preserve_handle_lock) + error("unbalanced call to unpreserve_handle for $(typeof(x))") + elseif v == 1 pop!(uvhandles, x) else uvhandles[x] = v - 1 @@ -100,6 +103,9 @@ uv_error(prefix::AbstractString, c::Integer) = c < 0 ? throw(_UVError(prefix, c) eventloop() = ccall(:jl_global_event_loop, Ptr{Cvoid}, ()) +uv_unref(h::Ptr{Cvoid}) = ccall(:uv_unref, Cvoid, (Ptr{Cvoid},), h) +uv_ref(h::Ptr{Cvoid}) = ccall(:uv_ref, Cvoid, (Ptr{Cvoid},), h) + function process_events() return ccall(:jl_process_events, Int32, ()) end diff --git a/base/linked_list.jl b/base/linked_list.jl index 113607f63a2ff..c477dc56bdb2b 100644 --- a/base/linked_list.jl +++ b/base/linked_list.jl @@ -1,23 +1,23 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -mutable struct InvasiveLinkedList{T} +mutable struct IntrusiveLinkedList{T} # Invasive list requires that T have a field `.next >: U{T, Nothing}` and `.queue >: U{ILL{T}, Nothing}` head::Union{T, Nothing} tail::Union{T, Nothing} - InvasiveLinkedList{T}() where {T} = new{T}(nothing, nothing) + IntrusiveLinkedList{T}() where {T} = new{T}(nothing, nothing) end #const list_append!! = append! #const list_deletefirst! = delete! -eltype(::Type{<:InvasiveLinkedList{T}}) where {T} = @isdefined(T) ? T : Any +eltype(::Type{<:IntrusiveLinkedList{T}}) where {T} = @isdefined(T) ? T : Any -iterate(q::InvasiveLinkedList) = (h = q.head; h === nothing ? nothing : (h, h)) -iterate(q::InvasiveLinkedList{T}, v::T) where {T} = (h = v.next; h === nothing ? nothing : (h, h)) +iterate(q::IntrusiveLinkedList) = (h = q.head; h === nothing ? nothing : (h, h)) +iterate(q::IntrusiveLinkedList{T}, v::T) where {T} = (h = v.next; h === nothing ? nothing : (h, h)) -isempty(q::InvasiveLinkedList) = (q.head === nothing) +isempty(q::IntrusiveLinkedList) = (q.head === nothing) -function length(q::InvasiveLinkedList) +function length(q::IntrusiveLinkedList) i = 0 head = q.head while head !== nothing @@ -27,7 +27,7 @@ function length(q::InvasiveLinkedList) return i end -function list_append!!(q::InvasiveLinkedList{T}, q2::InvasiveLinkedList{T}) where T +function list_append!!(q::IntrusiveLinkedList{T}, q2::IntrusiveLinkedList{T}) where T q === q2 && error("can't append list to itself") head2 = q2.head if head2 !== nothing @@ -49,7 +49,7 @@ function list_append!!(q::InvasiveLinkedList{T}, q2::InvasiveLinkedList{T}) wher return q end -function push!(q::InvasiveLinkedList{T}, val::T) where T +function push!(q::IntrusiveLinkedList{T}, val::T) where T val.queue === nothing || error("val already in a list") val.queue = q tail = q.tail @@ -62,7 +62,7 @@ function push!(q::InvasiveLinkedList{T}, val::T) where T return q end -function pushfirst!(q::InvasiveLinkedList{T}, val::T) where T +function pushfirst!(q::IntrusiveLinkedList{T}, val::T) where T val.queue === nothing || error("val already in a list") val.queue = q head = q.head @@ -75,20 +75,20 @@ function pushfirst!(q::InvasiveLinkedList{T}, val::T) where T return q end -function pop!(q::InvasiveLinkedList{T}) where {T} +function pop!(q::IntrusiveLinkedList{T}) where {T} val = q.tail::T list_deletefirst!(q, val) # expensive! return val end -function popfirst!(q::InvasiveLinkedList{T}) where {T} +function popfirst!(q::IntrusiveLinkedList{T}) where {T} val = q.head::T list_deletefirst!(q, val) # cheap return val end # this function assumes `val` is found in `q` -function list_deletefirst!(q::InvasiveLinkedList{T}, val::T) where T +function list_deletefirst!(q::IntrusiveLinkedList{T}, val::T) where T val.queue === q || return head = q.head::T if head === val @@ -125,20 +125,20 @@ end mutable struct LinkedListItem{T} # Adapter class to use any `T` in a LinkedList next::Union{LinkedListItem{T}, Nothing} - queue::Union{InvasiveLinkedList{LinkedListItem{T}}, Nothing} + queue::Union{IntrusiveLinkedList{LinkedListItem{T}}, Nothing} value::T LinkedListItem{T}(value::T) where {T} = new{T}(nothing, nothing, value) end -const LinkedList{T} = InvasiveLinkedList{LinkedListItem{T}} +const LinkedList{T} = IntrusiveLinkedList{LinkedListItem{T}} # delegate methods, as needed eltype(::Type{<:LinkedList{T}}) where {T} = @isdefined(T) ? T : Any iterate(q::LinkedList) = (h = q.head; h === nothing ? nothing : (h.value, h)) -iterate(q::InvasiveLinkedList{LLT}, v::LLT) where {LLT<:LinkedListItem} = (h = v.next; h === nothing ? nothing : (h.value, h)) +iterate(q::IntrusiveLinkedList{LLT}, v::LLT) where {LLT<:LinkedListItem} = (h = v.next; h === nothing ? nothing : (h.value, h)) push!(q::LinkedList{T}, val::T) where {T} = push!(q, LinkedListItem{T}(val)) pushfirst!(q::LinkedList{T}, val::T) where {T} = pushfirst!(q, LinkedListItem{T}(val)) -pop!(q::LinkedList) = invoke(pop!, Tuple{InvasiveLinkedList,}, q).value -popfirst!(q::LinkedList) = invoke(popfirst!, Tuple{InvasiveLinkedList,}, q).value +pop!(q::LinkedList) = invoke(pop!, Tuple{IntrusiveLinkedList,}, q).value +popfirst!(q::LinkedList) = invoke(popfirst!, Tuple{IntrusiveLinkedList,}, q).value function list_deletefirst!(q::LinkedList{T}, val::T) where T h = q.head while h !== nothing diff --git a/base/linking.jl b/base/linking.jl new file mode 100644 index 0000000000000..fd21ce74c9268 --- /dev/null +++ b/base/linking.jl @@ -0,0 +1,169 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license +module Linking + +import Base.Libc: Libdl + +# inlined LLD_jll +# These get calculated in __init__() +const PATH = Ref("") +const LIBPATH = Ref("") +const PATH_list = String[] +const LIBPATH_list = String[] +const lld_path = Ref{String}() +const lld_exe = Sys.iswindows() ? "lld.exe" : "lld" +const dsymutil_path = Ref{String}() +const dsymutil_exe = Sys.iswindows() ? "dsymutil.exe" : "dsymutil" + +if Sys.iswindows() + const LIBPATH_env = "PATH" + const LIBPATH_default = "" + const pathsep = ';' +elseif Sys.isapple() + const LIBPATH_env = "DYLD_FALLBACK_LIBRARY_PATH" + const LIBPATH_default = "~/lib:/usr/local/lib:/lib:/usr/lib" + const pathsep = ':' +else + const LIBPATH_env = "LD_LIBRARY_PATH" + const LIBPATH_default = "" + const pathsep = ':' +end + +function adjust_ENV!(env::Dict, PATH::String, LIBPATH::String, adjust_PATH::Bool, adjust_LIBPATH::Bool) + if adjust_LIBPATH + LIBPATH_base = get(env, LIBPATH_env, expanduser(LIBPATH_default)) + if !isempty(LIBPATH_base) + env[LIBPATH_env] = string(LIBPATH, pathsep, LIBPATH_base) + else + env[LIBPATH_env] = LIBPATH + end + end + if adjust_PATH && (LIBPATH_env != "PATH" || !adjust_LIBPATH) + if !isempty(get(env, "PATH", "")) + env["PATH"] = string(PATH, pathsep, env["PATH"]) + else + env["PATH"] = PATH + end + end + return env +end + +function __init_lld_path() + # Prefer our own bundled lld, but if we don't have one, pick it up off of the PATH + # If this is an in-tree build, `lld` will live in `tools`. Otherwise, it'll be in `private_libexecdir` + for bundled_lld_path in (joinpath(Sys.BINDIR, Base.PRIVATE_LIBEXECDIR, lld_exe), + joinpath(Sys.BINDIR, "..", "tools", lld_exe), + joinpath(Sys.BINDIR, lld_exe)) + if isfile(bundled_lld_path) + lld_path[] = abspath(bundled_lld_path) + return + end + end + lld_path[] = something(Sys.which(lld_exe), lld_exe) + return +end + +function __init_dsymutil_path() + #Same as with lld but for dsymutil + for bundled_dsymutil_path in (joinpath(Sys.BINDIR, Base.PRIVATE_LIBEXECDIR, dsymutil_exe), + joinpath(Sys.BINDIR, "..", "tools", dsymutil_exe), + joinpath(Sys.BINDIR, dsymutil_exe)) + if isfile(bundled_dsymutil_path) + dsymutil_path[] = abspath(bundled_dsymutil_path) + return + end + end + dsymutil_path[] = something(Sys.which(dsymutil_exe), dsymutil_exe) + return +end + +const VERBOSE = Ref{Bool}(false) + +function __init__() + VERBOSE[] = Base.get_bool_env("JULIA_VERBOSE_LINKING", false) + + __init_lld_path() + __init_dsymutil_path() + PATH[] = dirname(lld_path[]) + if Sys.iswindows() + # On windows, the dynamic libraries (.dll) are in Sys.BINDIR ("usr\\bin") + append!(LIBPATH_list, [abspath(Sys.BINDIR, Base.LIBDIR, "julia"), Sys.BINDIR]) + else + append!(LIBPATH_list, [abspath(Sys.BINDIR, Base.LIBDIR, "julia"), abspath(Sys.BINDIR, Base.LIBDIR)]) + end + LIBPATH[] = join(LIBPATH_list, pathsep) + return +end + +function lld(; adjust_PATH::Bool = true, adjust_LIBPATH::Bool = true) + env = adjust_ENV!(copy(ENV), PATH[], LIBPATH[], adjust_PATH, adjust_LIBPATH) + return Cmd(Cmd([lld_path[]]); env) +end + +function dsymutil(; adjust_PATH::Bool = true, adjust_LIBPATH::Bool = true) + env = adjust_ENV!(copy(ENV), PATH[], LIBPATH[], adjust_PATH, adjust_LIBPATH) + return Cmd(Cmd([dsymutil_path[]]); env) +end + +function ld() + default_args = `` + @static if Sys.iswindows() + # LLD supports mingw style linking + flavor = "gnu" + m = Sys.ARCH == :x86_64 ? "i386pep" : "i386pe" + default_args = `-m $m -Bdynamic --enable-auto-image-base --allow-multiple-definition` + elseif Sys.isapple() + flavor = "darwin" + arch = Sys.ARCH == :aarch64 ? :arm64 : Sys.ARCH + default_args = `-arch $arch -undefined dynamic_lookup -platform_version macos $(Base.MACOS_PRODUCT_VERSION) $(Base.MACOS_PLATFORM_VERSION)` + else + flavor = "gnu" + end + + `$(lld()) -flavor $flavor $default_args` +end + +const WHOLE_ARCHIVE = if Sys.isapple() + "-all_load" +else + "--whole-archive" +end + +const NO_WHOLE_ARCHIVE = if Sys.isapple() + "" +else + "--no-whole-archive" +end + +const SHARED = if Sys.isapple() + "-dylib" +else + "-shared" +end + +is_debug() = ccall(:jl_is_debugbuild, Cint, ()) == 1 +libdir() = abspath(Sys.BINDIR, Base.LIBDIR) +private_libdir() = abspath(Sys.BINDIR, Base.PRIVATE_LIBDIR) +if Sys.iswindows() + shlibdir() = Sys.BINDIR +else + shlibdir() = libdir() +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") + @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` +end + +function link_image(path, out, internal_stderr::IO=stderr, internal_stdout::IO=stdout) + run(link_image_cmd(path, out), Base.DevNull(), internal_stderr, internal_stdout) +end + +end # module Linking diff --git a/base/loading.jl b/base/loading.jl index 7dce4532c1571..9cc2f13752dfb 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -167,7 +167,8 @@ function dummy_uuid(project_file::String) end project_path = try realpath(project_file) - catch + catch ex + ex isa IOError || rethrow() project_file end uuid = uuid5(ns_dummy_uuid, project_path) @@ -231,12 +232,10 @@ end function get_updated_dict(p::TOML.Parser, f::CachedTOMLDict) s = stat(f.path) - time_since_cached = time() - f.mtime - rough_mtime_granularity = 0.1 # seconds - # In case the file is being updated faster than the mtime granularity, - # and have the same size after the update we might miss that it changed. Therefore - # always check the hash in case we recently created the cache. - if time_since_cached < rough_mtime_granularity || s.inode != f.inode || s.mtime != f.mtime || f.size != s.size + # note, this might miss very rapid in-place updates, such that mtime is + # identical but that is solvable by not doing in-place updates, and not + # rapidly changing these files + if s.inode != f.inode || s.mtime != f.mtime || f.size != s.size content = read(f.path) new_hash = _crc32c(content) if new_hash != f.hash @@ -257,9 +256,12 @@ 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}} end const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing) -LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set()) +LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set(), Dict(), Dict(), Dict()) struct TOMLCache @@ -297,65 +299,172 @@ end # Used by Pkg but not used in loading itself function find_package(arg) - pkg = identify_package(arg) - pkg === nothing && return nothing - return locate_package(pkg) + pkgenv = identify_package_env(arg) + pkgenv === nothing && return nothing + pkg, env = pkgenv + return locate_package(pkg, env) end -## package identity: given a package name and a context, try to return its identity ## -identify_package(where::Module, name::String) = identify_package(PkgId(where), name) +""" + 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} -# identify_package computes the PkgId for `name` from the context of `where` -# or return `nothing` if no mapping exists for it -function identify_package(where::PkgId, name::String)::Union{Nothing,PkgId} - where.name === name && return where - where.uuid === nothing && return identify_package(name) # ignore `where` - for env in load_path() - uuid = manifest_deps_get(env, where, name) - uuid === nothing && continue # not found--keep looking - uuid.uuid === nothing || return uuid # found in explicit environment--use it - return nothing # found in implicit environment--return "not found" +Same as [`Base.identify_package`](@ref) except that the path to the environment where the package is identified +is also returned. +""" +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 + end + pkg_env = nothing + if where.name === name + pkg_env = where, nothing + elseif where.uuid === nothing + pkg_env = identify_package_env(name) # ignore `where` + else + for env in load_path() + pkgid = manifest_deps_get(env, where, name) + pkgid === nothing && continue # not found--keep looking + if pkgid.uuid !== nothing + pkg_env = pkgid, env # found in explicit environment--use it + end + break # found in implicit environment--return "not found" + end end - return nothing + if cache !== nothing + cache.identified_where[(where, name)] = pkg_env + end + return pkg_env end - -# identify_package computes the PkgId for `name` from toplevel context -# by looking through the Project.toml files and directories -function identify_package(name::String)::Union{Nothing,PkgId} +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 + end + pkg_env = nothing for env in load_path() - uuid = project_deps_get(env, name) - uuid === nothing || return uuid # found--return it + pkg = project_deps_get(env, name) + if pkg !== nothing + pkg_env = pkg, env # found--return it + break + end end - return nothing + if cache !== nothing + cache.identified[name] = pkg_env + end + return pkg_env end -## package location: given a package identity, find file to load ## -function locate_package(pkg::PkgId)::Union{Nothing,String} +_nothing_or_first(x) = x === nothing ? nothing : first(x) + +""" + Base.identify_package(name::String)::Union{PkgId, Nothing} + Base.identify_package(where::Union{Module,PkgId}, name::String)::Union{PkgId, Nothing} + +Identify the package by its name from the current environment stack, returning +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 +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 +identifies the dependency with the corresponding name. + +```julia-repl +julia> Base.identify_package("Pkg") # Pkg is a dependency of the default environment +Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f] + +julia> using LinearAlgebra + +julia> Base.identify_package(LinearAlgebra, "Pkg") # Pkg is not a dependency of LinearAlgebra +``` +""" +identify_package(where::Module, name::String) = _nothing_or_first(identify_package_env(where, name)) +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) + cache = LOADING_CACHE[] + if cache !== nothing + pathenv = get(cache.located, (pkg, stopenv), nothing) + pathenv === nothing || 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) - found === nothing && continue - if pkg == found - # pkg.name is present in this directory or project file, - # return the path the entry point for the code, if it could be found - # otherwise, signal failure - return implicit_manifest_uuid_path(env, pkg) + if found !== nothing + @assert found.name == pkg.name + if found.uuid === nothing + # pkg.name is present in this directory or project file, + # return the path the entry point for the code, if it could be found + # otherwise, signal failure + path = implicit_manifest_uuid_path(env, pkg) + @goto done + end + end + if !(loading_extension || precompiling_extension) + stopenv == env && @goto done end - @assert found.uuid !== nothing - return locate_package(found) # restart search now that we know the uuid for pkg end else for env in load_path() + env′ = env path = manifest_uuid_path(env, pkg) - path === nothing || return entry_path(path, pkg.name) + # missing is used as a sentinel to stop looking further down in envs + if path === missing + path = nothing + @goto done + end + if path !== nothing + path = entry_path(path, pkg.name) + @goto done + end + if !(loading_extension || precompiling_extension) + stopenv == env && break + end end # Allow loading of stdlibs if the name/uuid are given # e.g. if they have been explicitly added to the project/manifest - path = manifest_uuid_path(Sys.STDLIB, pkg) - path === nothing || return entry_path(path, pkg.name) + mbypath = manifest_uuid_path(Sys.STDLIB, pkg) + if mbypath isa String + path = entry_path(mbypath, pkg.name) + @goto done + end end - return nothing + @label done + if cache !== nothing + cache.located[(pkg, stopenv)] = path, env′ + end + return path, env′ +end + +""" + Base.locate_package(pkg::PkgId)::Union{String, Nothing} + +The path to the entry-point file for the package corresponding to the identifier +`pkg`, or `nothing` if not found. See also [`identify_package`](@ref). + +```julia-repl +julia> pkg = Base.identify_package("Pkg") +Pkg [44cfe95a-1eb2-52ea-b672-e2afdf69b78f] + +julia> Base.locate_package(pkg) +"/path/to/julia/stdlib/v$(VERSION.major).$(VERSION.minor)/Pkg/src/Pkg.jl" +``` +""" +function locate_package(pkg::PkgId, stopenv::Union{String, Nothing}=nothing)::Union{Nothing,String} + _nothing_or_first(locate_package_env(pkg, stopenv)) end """ @@ -387,6 +496,9 @@ or `nothing` if `m` was not imported from a package. Optionally further path component strings can be provided to construct a path within the package root. +To get the root directory of the package that imported the current module +the form `pkgdir(@__MODULE__)` can be used. + ```julia-repl julia> pkgdir(Foo) "/path/to/Foo.jl" @@ -405,14 +517,66 @@ function pkgdir(m::Module, paths::String...) return joinpath(dirname(dirname(path)), paths...) end +function get_pkgversion_from_path(path) + project_file = locate_project_file(path) + if project_file isa String + d = parsed_toml(project_file) + v = get(d, "version", nothing) + if v !== nothing + return VersionNumber(v::String) + end + end + return nothing +end + +""" + pkgversion(m::Module) + +Return the version of the package that imported module `m`, +or `nothing` if `m` was not imported from a package, or imported +from a package without a version field set. + +The version is read from the package's Project.toml during package +load. + +To get the version of the package that imported the current module +the form `pkgversion(@__MODULE__)` can be used. + +!!! compat "Julia 1.9" + This function was introduced in Julia 1.9. +""" +function pkgversion(m::Module) + path = pkgdir(m) + path === nothing && return nothing + @lock require_lock begin + v = get_pkgversion_from_path(path) + pkgorigin = get(pkgorigins, PkgId(moduleroot(m)), nothing) + # Cache the version + if pkgorigin !== nothing && pkgorigin.version === nothing + pkgorigin.version = v + end + return v + end +end + ## generic project & manifest API ## const project_names = ("JuliaProject.toml", "Project.toml") const manifest_names = ("JuliaManifest.toml", "Manifest.toml") const preferences_names = ("JuliaLocalPreferences.toml", "LocalPreferences.toml") +function locate_project_file(env::String) + for proj in project_names + project_file = joinpath(env, proj) + if isfile_casesensitive(project_file) + return project_file + end + end + return true +end + # classify the LOAD_PATH entry to be one of: -# - `false`: nonexistant / nothing to see here +# - `false`: nonexistent / nothing to see here # - `true`: `env` is an implicit environment # - `path`: the path of an explicit project file function env_project_file(env::String)::Union{Bool,String} @@ -423,14 +587,7 @@ function env_project_file(env::String)::Union{Bool,String} project_file === nothing || return project_file end if isdir(env) - for proj in project_names - maybe_project_file = joinpath(env, proj) - if isfile_casesensitive(maybe_project_file) - project_file = maybe_project_file - break - end - end - project_file =true + project_file = locate_project_file(env) elseif basename(env) in project_names && isfile_casesensitive(env) project_file = env else @@ -466,8 +623,26 @@ function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothi pkg_uuid = explicit_project_deps_get(project_file, name) return PkgId(pkg_uuid, name) end + d = parsed_toml(project_file) + exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing} + if exts !== nothing + # Check if `where` is an extension of the project + if where.name in keys(exts) && where.uuid == uuid5(proj.uuid, where.name) + # Extensions can load weak deps... + weakdeps = get(d, "weakdeps", nothing)::Union{Dict{String, Any}, Nothing} + if weakdeps !== nothing + wuuid = get(weakdeps, name, nothing)::Union{String, Nothing} + if wuuid !== nothing + return PkgId(UUID(wuuid), name) + end + end + # ... and they can load same deps as the project itself + mby_uuid = explicit_project_deps_get(project_file, name) + mby_uuid === nothing || return PkgId(mby_uuid, name) + end + end # look for manifest file and `where` stanza - return explicit_manifest_deps_get(project_file, uuid, name) + return explicit_manifest_deps_get(project_file, where, name) elseif project_file # if env names a directory, search it return implicit_manifest_deps_get(env, where, name) @@ -475,14 +650,16 @@ function manifest_deps_get(env::String, where::PkgId, name::String)::Union{Nothi return nothing end -function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String} +function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String,Missing} project_file = env_project_file(env) if project_file isa String 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, pkg.name) + return project_file_path(project_file) end + mby_ext = project_file_ext_path(project_file, pkg.name) + mby_ext === nothing || return mby_ext # look for manifest file and `where` stanza return explicit_manifest_uuid_path(project_file, pkg) elseif project_file @@ -492,6 +669,25 @@ function manifest_uuid_path(env::String, pkg::PkgId)::Union{Nothing,String} return nothing end + +function find_ext_path(project_path::String, extname::String) + extfiledir = joinpath(project_path, "ext", extname, extname * ".jl") + isfile(extfiledir) && return extfiledir + return joinpath(project_path, "ext", extname * ".jl") +end + +function project_file_ext_path(project_file::String, name::String) + d = parsed_toml(project_file) + p = project_file_path(project_file) + exts = get(d, "extensions", nothing)::Union{Dict{String, Any}, Nothing} + if exts !== nothing + if name in keys(exts) + return find_ext_path(p, name) + end + end + return nothing +end + # find project file's top-level UUID entry (or nothing) function project_file_name_uuid(project_file::String, name::String)::PkgId d = parsed_toml(project_file) @@ -501,7 +697,7 @@ function project_file_name_uuid(project_file::String, name::String)::PkgId return PkgId(uuid, name) end -function project_file_path(project_file::String, name::String) +function project_file_path(project_file::String) d = parsed_toml(project_file) joinpath(dirname(project_file), get(d, "path", "")::String) end @@ -594,9 +790,10 @@ function explicit_project_deps_get(project_file::String, name::String)::Union{No return nothing end -function is_v1_format_manifest(raw_manifest::Dict) +function is_v1_format_manifest(raw_manifest::Dict{String}) if haskey(raw_manifest, "manifest_format") - if raw_manifest["manifest_format"] isa Dict && haskey(raw_manifest["manifest_format"], "uuid") + mf = raw_manifest["manifest_format"] + if mf isa Dict{String} && haskey(mf, "uuid") # the off-chance where an old format manifest has a dep called "manifest_format" return true end @@ -612,13 +809,13 @@ function get_deps(raw_manifest::Dict) return raw_manifest else # if the manifest has no deps, there won't be a `deps` field - return get(Dict{String, Any}, raw_manifest, "deps") + return get(Dict{String, Any}, raw_manifest, "deps")::Dict{String, Any} end end # find `where` stanza and return the PkgId for `name` # return `nothing` if it did not find `where` (indicating caller should continue searching) -function explicit_manifest_deps_get(project_file::String, where::UUID, name::String)::Union{Nothing,PkgId} +function explicit_manifest_deps_get(project_file::String, where::PkgId, name::String)::Union{Nothing,PkgId} manifest_file = project_file_manifest_path(project_file) manifest_file === nothing && return nothing # manifest not found--keep searching LOAD_PATH d = get_deps(parsed_toml(manifest_file)) @@ -630,16 +827,15 @@ function explicit_manifest_deps_get(project_file::String, where::UUID, name::Str entry = entry::Dict{String, Any} uuid = get(entry, "uuid", nothing)::Union{String, Nothing} uuid === nothing && continue - if UUID(uuid) === where + if UUID(uuid) === where.uuid found_where = true # deps is either a list of names (deps = ["DepA", "DepB"]) or # a table of entries (deps = {"DepA" = "6ea...", "DepB" = "55d..."} deps = get(entry, "deps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing} - deps === nothing && continue if deps isa Vector{String} found_name = name in deps break - else + elseif deps isa Dict{String, Any} deps = deps::Dict{String, Any} for (dep, uuid) in deps uuid::String @@ -648,6 +844,36 @@ function explicit_manifest_deps_get(project_file::String, where::UUID, name::Str end end end + else # Check for extensions + extensions = get(entry, "extensions", nothing) + if extensions !== nothing + if haskey(extensions, where.name) && where.uuid == uuid5(UUID(uuid), where.name) + found_where = true + if name == dep_name + return PkgId(UUID(uuid), name) + end + exts = extensions[where.name]::Union{String, Vector{String}} + if (exts isa String && name == exts) || (exts isa Vector{String} && name in exts) + weakdeps = get(entry, "weakdeps", nothing)::Union{Vector{String}, Dict{String, Any}, Nothing} + if weakdeps !== nothing + if weakdeps isa Vector{String} + found_name = name in weakdeps + break + elseif weakdeps isa Dict{String, Any} + weakdeps = weakdeps::Dict{String, Any} + for (dep, uuid) in weakdeps + uuid::String + if dep === name + return PkgId(UUID(uuid), name) + end + end + end + end + end + # `name` is not an ext, do standard lookup as if this was the parent + return identify_package(PkgId(UUID(uuid), dep_name), name) + end + end end end end @@ -665,19 +891,36 @@ function explicit_manifest_deps_get(project_file::String, where::UUID, name::Str end # find `uuid` stanza, return the corresponding path -function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{Nothing,String} +function explicit_manifest_uuid_path(project_file::String, pkg::PkgId)::Union{Nothing,String,Missing} manifest_file = project_file_manifest_path(project_file) manifest_file === nothing && return nothing # no manifest, skip env d = get_deps(parsed_toml(manifest_file)) entries = get(d, pkg.name, nothing)::Union{Nothing, Vector{Any}} - entries === nothing && return nothing # TODO: allow name to mismatch? - for entry in entries - entry = entry::Dict{String, Any} - uuid = get(entry, "uuid", nothing)::Union{Nothing, String} - uuid === nothing && continue - if UUID(uuid) === pkg.uuid - return explicit_manifest_entry_path(manifest_file, pkg, entry) + if entries !== nothing + for entry in entries + entry = entry::Dict{String, Any} + uuid = get(entry, "uuid", nothing)::Union{Nothing, String} + uuid === nothing && continue + if UUID(uuid) === pkg.uuid + return explicit_manifest_entry_path(manifest_file, pkg, entry) + end + end + end + # Extensions + for (name, entries) in d + entries = entries::Vector{Any} + for entry in entries + uuid = get(entry, "uuid", nothing)::Union{Nothing, String} + extensions = get(entry, "extensions", nothing)::Union{Nothing, Dict{String, Any}} + if extensions !== nothing && haskey(extensions, pkg.name) && uuid !== nothing && uuid5(UUID(uuid), pkg.name) == pkg.uuid + parent_path = locate_package(PkgId(UUID(uuid), name)) + if parent_path === nothing + error("failed to find source of parent package: \"$name\"") + end + p = normpath(dirname(parent_path), "..") + return find_ext_path(p, pkg.name) + end end end return nothing @@ -700,7 +943,8 @@ function explicit_manifest_entry_path(manifest_file::String, pkg::PkgId, entry:: ispath(path) && return abspath(path) end end - return nothing + # no depot contains the package, return missing to stop looking + return missing end ## implicit project & manifest API ## @@ -765,7 +1009,8 @@ function find_all_in_cache_path(pkg::PkgId) isdir(path) || continue for file in readdir(path, sort = false) # no sort given we sort later if !((pkg.uuid === nothing && file == entryfile * ".ji") || - (pkg.uuid !== nothing && startswith(file, entryfile * "_"))) + (pkg.uuid !== nothing && startswith(file, entryfile * "_") && + endswith(file, ".ji"))) continue end filepath = joinpath(path, file) @@ -782,15 +1027,72 @@ function find_all_in_cache_path(pkg::PkgId) end end +ocachefile_from_cachefile(cachefile) = string(chopsuffix(cachefile, ".ji"), ".", Base.Libc.dlext) +cachefile_from_ocachefile(cachefile) = string(chopsuffix(cachefile, ".$(Base.Libc.dlext)"), ".ji") + + +# use an Int counter so that nested @time_imports calls all remain open +const TIMING_IMPORTS = Threads.Atomic{Int}(0) + # 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(path::String, depmods::Vector{Any}) - sv = ccall(:jl_restore_incremental, Any, (Cstring, Any), path, depmods) +function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}) + assert_havelock(require_lock) + timing_imports = TIMING_IMPORTS[] > 0 + try + if timing_imports + t_before = time_ns() + cumulative_compile_timing(true) + t_comp_before = cumulative_compile_time_ns() + 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) + else + @debug "Loading cache file $path for $pkg" + sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), path, depmods, false, pkg.name) + end if isa(sv, Exception) return sv end - sv = sv::SimpleVector + + restored = register_restored_modules(sv, pkg, path) + + for M in restored + M = M::Module + if parentmodule(M) === M && PkgId(M) == pkg + if timing_imports + elapsed = round((time_ns() - t_before) / 1e6, digits = 1) + comp_time, recomp_time = cumulative_compile_time_ns() .- t_comp_before + print(lpad(elapsed, 9), " ms ") + parentid = get(EXT_PRIMED, pkg, nothing) + if parentid !== nothing + print(parentid.name, " → ") + end + print(pkg.name) + if comp_time > 0 + printstyled(" ", Ryu.writefixed(Float64(100 * comp_time / (elapsed * 1e6)), 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()) + end + println() + end + return M + end + end + return ErrorException("Required dependency $pkg failed to load from a cache file.") + + finally + timing_imports && cumulative_compile_timing(false) + end +end + +function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String) + # This function is also used by PkgCacheInspector.jl restored = sv[1]::Vector{Any} for M in restored M = M::Module @@ -801,11 +1103,18 @@ function _include_from_serialized(path::String, depmods::Vector{Any}) register_root_module(M) end end + + # Register this cache path now - If Requires.jl is loaded, Revise may end + # up looking at the cache path during the init callback. + get!(PkgOrigin, pkgorigins, pkg).cachepath = path + inits = sv[2]::Vector{Any} if !isempty(inits) unlock(require_lock) # temporarily _unlock_ during these callbacks try - ccall(:jl_init_restored_modules, Cvoid, (Any,), inits) + for (i, mod) in pairs(inits) + run_module_init(mod, i) + end finally lock(require_lock) end @@ -813,7 +1122,41 @@ function _include_from_serialized(path::String, depmods::Vector{Any}) return restored end +function run_module_init(mod::Module, i::Int=1) + # `i` informs ordering for the `@time_imports` report formatting + if TIMING_IMPORTS[] == 0 + ccall(:jl_init_restored_module, Cvoid, (Any,), mod) + else + if isdefined(mod, :__init__) + connector = i > 1 ? "├" : "┌" + printstyled(" $connector ", color = :light_black) + + elapsedtime = time_ns() + cumulative_compile_timing(true) + compile_elapsedtimes = cumulative_compile_time_ns() + + ccall(:jl_init_restored_module, Cvoid, (Any,), mod) + + elapsedtime = (time_ns() - elapsedtime) / 1e6 + cumulative_compile_timing(false); + comp_time, recomp_time = (cumulative_compile_time_ns() .- compile_elapsedtimes) ./ 1e6 + + 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()) + end + println() + end + end +end + function run_package_callbacks(modkey::PkgId) + run_extension_callbacks(modkey) + assert_havelock(require_lock) unlock(require_lock) try for callback in package_callbacks @@ -829,40 +1172,279 @@ function run_package_callbacks(modkey::PkgId) nothing end -function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt64, modpath::Union{Nothing, String}, depth::Int = 0) + +############## +# Extensions # +############## + +mutable struct ExtensionId + const id::PkgId + const parentid::PkgId # just need the name, for printing + ntriggers::Int # how many more packages must be defined until this is loaded +end + +const EXT_PRIMED = Dict{PkgId, PkgId}() # Extension -> Parent +const EXT_DORMITORY = Dict{PkgId,Vector{ExtensionId}}() # Trigger -> Extensions that can be triggered by it +const EXT_DORMITORY_FAILED = ExtensionId[] + +function insert_extension_triggers(pkg::PkgId) + pkg.uuid === nothing && return + 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 + # Look in project for extensions to insert + proj_pkg = project_file_name_uuid(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}} + extensions = get(d_proj, "extensions", nothing)::Union{Nothing, Dict{String, Any}} + extensions === nothing && return + weakdeps === nothing && return + return _insert_extension_triggers(pkg, extensions, weakdeps) + end + + # Now look in manifest + manifest_file = project_file_manifest_path(project_file) + manifest_file === nothing && return + d = get_deps(parsed_toml(manifest_file)) + for (dep_name, entries) in d + entries::Vector{Any} + for entry in entries + entry = entry::Dict{String, Any} + 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) + end + + d_weakdeps = Dict{String, String}() + 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 + end + @assert length(d_weakdeps) == length(weakdeps) + return _insert_extension_triggers(pkg, extensions, d_weakdeps) + end + end + end + end + return nothing +end + +function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, <:Any}, weakdeps::Dict{String, <:Any}) + for (ext::String, triggers::Union{String, Vector{String}}) in extensions + triggers isa String && (triggers = [triggers]) + id = PkgId(uuid5(parent.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 + EXT_PRIMED[id] = parent + gid = ExtensionId(id, parent, 1 + length(triggers)) + trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, parent) + push!(trigger1, gid) + for trigger in triggers + # TODO: Better error message if this lookup fails? + uuid_trigger = UUID(weakdeps[trigger]::String) + trigger_id = PkgId(uuid_trigger, trigger) + if !haskey(Base.loaded_modules, trigger_id) || haskey(package_locks, trigger_id) + trigger1 = get!(Vector{ExtensionId}, EXT_DORMITORY, trigger_id) + push!(trigger1, gid) + else + gid.ntriggers -= 1 + end + end + end +end + +loading_extension::Bool = false +precompiling_extension::Bool = false +function run_extension_callbacks(extid::ExtensionId) + assert_havelock(require_lock) + succeeded = try + # Used by Distributed to now load extensions in the package callback + global loading_extension = true + _require_prelocked(extid.id) + @debug "Extension $(extid.id.name) of $(extid.parentid.name) loaded" + true + catch + # Try to continue loading if loading an extension errors + errs = current_exceptions() + @error "Error during loading of extension $(extid.id.name) of $(extid.parentid.name), \ + use `Base.retry_load_extensions()` to retry." exception=errs + false + finally + global loading_extension = false + end + return succeeded +end + +function run_extension_callbacks(pkgid::PkgId) + assert_havelock(require_lock) + # take ownership of extids that depend on this pkgid + extids = pop!(EXT_DORMITORY, pkgid, nothing) + extids === nothing && return + for extid in extids + if extid.ntriggers > 0 + # indicate pkgid is loaded + extid.ntriggers -= 1 + end + if extid.ntriggers < 0 + # indicate pkgid is loaded + extid.ntriggers += 1 + succeeded = false + else + succeeded = true + end + if extid.ntriggers == 0 + # actually load extid, now that all dependencies are met, + # and record the result + succeeded = succeeded && run_extension_callbacks(extid) + succeeded || push!(EXT_DORMITORY_FAILED, extid) + end + end + return +end + +""" + retry_load_extensions() + +Loads all the (not yet loaded) extensions that have their extension-dependencies loaded. +This is used in cases where the automatic loading of an extension failed +due to some problem with the extension. Instead of restarting the Julia session, +the extension can be fixed, and this function run. +""" +function retry_load_extensions() + @lock require_lock begin + # this copy is desired since run_extension_callbacks will release this lock + # so this can still mutate the list to drop successful ones + failed = copy(EXT_DORMITORY_FAILED) + empty!(EXT_DORMITORY_FAILED) + filter!(failed) do extid + return !run_extension_callbacks(extid) + end + prepend!(EXT_DORMITORY_FAILED, failed) + end + return +end + +""" + get_extension(parent::Module, extension::Symbol) + +Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded. +""" +get_extension(parent::Module, ext::Symbol) = get_extension(PkgId(parent), ext) +function get_extension(parentid::PkgId, ext::Symbol) + parentid.uuid === nothing && return nothing + extid = PkgId(uuid5(parentid.uuid, string(ext)), string(ext)) + return get(loaded_modules, extid, nothing) +end + +# End extensions + +# loads a precompile cache file, after checking stale_cachefile tests +function _tryrequire_from_serialized(modkey::PkgId, build_id::UInt128) + assert_havelock(require_lock) + loaded = nothing if root_module_exists(modkey) - M = root_module(modkey) - if PkgId(M) == modkey && module_build_id(M) === build_id - return M + loaded = root_module(modkey) + else + loaded = start_loading(modkey) + if loaded === nothing + try + 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) + 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 +# 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 - if modpath === nothing - modpath = locate_package(modkey) - modpath === nothing && return nothing - end - mod = _require_search_from_serialized(modkey, String(modpath), depth) - get!(PkgOrigin, pkgorigins, modkey).path = modpath - if !isa(mod, Bool) - run_package_callbacks(modkey) - for M in mod::Vector{Any} - M = M::Module - if PkgId(M) == modkey && module_build_id(M) === build_id - return M + 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 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 - return nothing + if !(loaded isa Module) || PkgId(loaded) != modkey + return ErrorException("Required dependency $modkey failed to load from a cache file.") + end + return loaded end -function _require_from_serialized(path::String) - # loads a precompile cache file, ignoring stale_cachfile tests - # load all of the dependent modules first +# loads a precompile cache file, ignoring stale_cachefile tests +# load the best available (non-stale) version of 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") try - isvalid_cache_header(io) || return ArgumentError("Invalid header in cache file $path.") - depmodnames = parse_cache_header(io)[3] + iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") + _, _, depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io) + pkgimage = !isempty(clone_targets) + if pkgimage + ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided") + isfile(ocachepath) || return ArgumentError("Ocachepath $ocachepath is not a file.") + ocachepath == ocachefile_from_cachefile(path) || return ArgumentError("$ocachepath is not the expected ocachefile") + # TODO: Check for valid clone_targets? + isvalid_pkgimage_crc(io, ocachepath) || return ArgumentError("Invalid checksum in cache file $ocachepath.") + else + @assert ocachepath === nothing + end isvalid_file_crc(io) || return ArgumentError("Invalid checksum in cache file $path.") finally close(io) @@ -871,67 +1453,132 @@ function _require_from_serialized(path::String) depmods = Vector{Any}(undef, ndeps) for i in 1:ndeps modkey, build_id = depmodnames[i] - dep = _tryrequire_from_serialized(modkey, build_id, nothing) - dep === nothing && return ErrorException("Required dependency $modkey failed to load from a cache file.") - depmods[i] = dep::Module + dep = _tryrequire_from_serialized(modkey, build_id) + if !isa(dep, Module) + return dep + end + depmods[i] = dep end # then load the file - return _include_from_serialized(path, depmods) + return _include_from_serialized(pkg, path, ocachepath, depmods) end -# use an Int counter so that nested @time_imports calls all remain open -const TIMING_IMPORTS = Threads.Atomic{Int}(0) - -# returns `true` if require found a precompile cache for this sourcepath, but couldn't load it -# returns `false` if the module isn't known to be precompilable +# 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, depth::Int = 0) - t_before = time_ns() +@constprop :none function _require_search_from_serialized(pkg::PkgId, sourcepath::String, build_id::UInt128) + assert_havelock(require_lock) paths = find_all_in_cache_path(pkg) for path_to_try in paths::Vector{String} - staledeps = stale_cachefile(sourcepath, path_to_try) + staledeps = stale_cachefile(pkg, build_id, sourcepath, path_to_try) 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 # file might be read-only and then we fail to update timestamp, which is fine + 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, build_id = dep::Tuple{String, PkgId, UInt64} - dep = _tryrequire_from_serialized(modkey, build_id, modpath, depth + 1) - if dep === nothing - @debug "Required dependency $modkey failed to load from cache file for $modpath." - staledeps = true - break + 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 + @goto check_next_path end - staledeps[i] = dep::Module + staledeps[i] = dep end - if staledeps === true - continue + 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 + +# to synchronize multiple tasks trying to import/using something +const package_locks = Dict{PkgId,Pair{Task,Threads.Condition}}() + +debug_loading_deadlocks::Bool = true # Enable a slightly more expensive, but more complete algorithm that can handle simultaneous tasks. + # This only triggers if you have multiple tasks trying to load the same package at the same time, + # so it is unlikely to make a difference normally. +function start_loading(modkey::PkgId) + # handle recursive calls to require + assert_havelock(require_lock) + loading = get(package_locks, modkey, nothing) + if loading !== nothing + # load already in progress for this module on the task + task, cond = loading + deps = String[modkey.name] + pkgid = modkey + assert_havelock(cond.lock) + if debug_loading_deadlocks && current_task() !== task + waiters = Dict{Task,Pair{Task,PkgId}}() # invert to track waiting tasks => loading tasks + for each in package_locks + cond2 = each[2][2] + assert_havelock(cond2.lock) + for waiting in cond2.waitq + push!(waiters, waiting => (each[2][1] => each[1])) + end + end + while true + running = get(waiters, task, nothing) + running === nothing && break + task, pkgid = running + push!(deps, pkgid.name) + task === current_task() && break + end end - restored = _include_from_serialized(path_to_try, staledeps) - if isa(restored, Exception) - @debug "Deserialization checks failed while attempting to load cache from $path_to_try" exception=restored - else - if TIMING_IMPORTS[] > 0 - elapsed = round((time_ns() - t_before) / 1e6, digits = 1) - tree_prefix = depth == 0 ? "" : " "^(depth-1)*"┌ " - print(lpad(elapsed, 9), " ms ") - printstyled(tree_prefix, color = :light_black) - println(pkg.name) + if current_task() === task + others = String[modkey.name] # repeat this to emphasize the cycle here + for each in package_locks # list the rest of the packages being loaded too + if each[2][1] === task + other = each[1].name + other == modkey.name || other == pkgid.name || push!(others, other) + end end - return restored + msg = sprint(deps, others) do io, deps, others + print(io, "deadlock detected in loading ") + join(io, deps, " -> ") + print(io, " -> ") + join(io, others, " && ") + end + throw(ConcurrencyViolationError(msg)) end + return wait(cond) end - return !isempty(paths) + package_locks[modkey] = current_task() => Threads.Condition(require_lock) + return end -# to synchronize multiple tasks trying to import/using something -const package_locks = Dict{PkgId,Threads.Condition}() +function end_loading(modkey::PkgId, @nospecialize loaded) + loading = pop!(package_locks, modkey) + notify(loading[2], loaded, all=true) + nothing +end # to notify downstream consumers that a module was successfully loaded # Callbacks take the form (mod::Base.PkgId) -> nothing. @@ -943,7 +1590,7 @@ const package_callbacks = Any[] const include_callbacks = Any[] # used to optionally track dependencies when requiring a module: -const _concrete_dependencies = Pair{PkgId,UInt64}[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them +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 _track_dependencies = Ref(false) # set this to true to track the list of file dependencies function _include_dependency(mod::Module, _path::AbstractString) @@ -964,11 +1611,11 @@ end """ include_dependency(path::AbstractString) -In a module, declare that the file specified by `path` (relative or absolute) is a -dependency for precompilation; that is, the module will need to be recompiled if this file -changes. +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. -This is only needed if your module depends on a file that is not used via [`include`](@ref). It has +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. """ function include_dependency(path::AbstractString) @@ -1028,9 +1675,9 @@ function require(into::Module, mod::Symbol) @lock require_lock begin LOADING_CACHE[] = LoadingCache() try - uuidkey = identify_package(into, String(mod)) - # Core.println("require($(PkgId(into)), $mod) -> $uuidkey") - if uuidkey === nothing + uuidkey_env = identify_package_env(into, String(mod)) + # Core.println("require($(PkgId(into)), $mod) -> $uuidkey_env") + if uuidkey_env === nothing where = PkgId(into) if where.uuid === nothing hint, dots = begin @@ -1058,45 +1705,51 @@ function require(into::Module, mod::Symbol) - Otherwise you may need to report an issue with $(where.name)""")) end end + uuidkey, env = uuidkey_env if _track_dependencies[] push!(_require_dependencies, (into, binpack(uuidkey), 0.0)) end - return _require_prelocked(uuidkey) + return _require_prelocked(uuidkey, env) finally LOADING_CACHE[] = nothing end end end -mutable struct PkgOrigin - # version::VersionNumber - path::Union{String,Nothing} - cachepath::Union{String,Nothing} -end -PkgOrigin() = PkgOrigin(nothing, nothing) -const pkgorigins = Dict{PkgId,PkgOrigin}() - require(uuidkey::PkgId) = @lock require_lock _require_prelocked(uuidkey) -function _require_prelocked(uuidkey::PkgId) - just_loaded_pkg = false +const REPL_PKGID = PkgId(UUID("3fa0cd96-eef1-5676-8a61-b3b8758bbffb"), "REPL") + +function _require_prelocked(uuidkey::PkgId, env=nothing) + assert_havelock(require_lock) if !root_module_exists(uuidkey) - cachefile = _require(uuidkey) - if cachefile !== nothing - get!(PkgOrigin, pkgorigins, uuidkey).cachepath = cachefile + newm = _require(uuidkey, env) + if newm === nothing + error("package `$(uuidkey.name)` did not define the expected \ + module `$(uuidkey.name)`, check for typos in package module name") end + insert_extension_triggers(uuidkey) # After successfully loading, notify downstream consumers run_package_callbacks(uuidkey) - just_loaded_pkg = true - end - if just_loaded_pkg && !root_module_exists(uuidkey) - error("package `$(uuidkey.name)` did not define the expected \ - module `$(uuidkey.name)`, check for typos in package module name") + if uuidkey == REPL_PKGID + REPL_MODULE_REF[] = newm + end + else + newm = root_module(uuidkey) end - return root_module(uuidkey) + return newm +end + +mutable struct PkgOrigin + path::Union{String,Nothing} + cachepath::Union{String,Nothing} + version::Union{VersionNumber,Nothing} end +PkgOrigin() = PkgOrigin(nothing, nothing, nothing) +const pkgorigins = Dict{PkgId,PkgOrigin}() const 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) @@ -1110,9 +1763,14 @@ root_module_key(m::Module) = @lock require_lock module_keys[m] if haskey(loaded_modules, key) oldm = loaded_modules[key] if oldm !== m - @warn "Replacing module `$(key.name)`" + if (0 != ccall(:jl_generating_output, Cint, ())) && (JLOptions().incremental != 0) + error("Replacing module `$(key.name)`") + else + @warn "Replacing module `$(key.name)`" + end end end + push!(loaded_modules_order, m) loaded_modules[key] = m module_keys[m] = key end @@ -1132,12 +1790,15 @@ end # get a top-level Module from the given key root_module(key::PkgId) = @lock require_lock loaded_modules[key] -root_module(where::Module, name::Symbol) = - root_module(identify_package(where, String(name))) +function root_module(where::Module, name::Symbol) + key = identify_package(where, String(name)) + key isa PkgId || throw(KeyError(name)) + return root_module(key) +end maybe_root_module(key::PkgId) = @lock require_lock get(loaded_modules, key, nothing) root_module_exists(key::PkgId) = @lock require_lock haskey(loaded_modules, key) -loaded_modules_array() = @lock require_lock collect(values(loaded_modules)) +loaded_modules_array() = @lock require_lock copy(loaded_modules_order) function unreference_module(key::PkgId) if haskey(loaded_modules, key) @@ -1147,35 +1808,50 @@ function unreference_module(key::PkgId) end end -# Returns `nothing` or the name of the newly-created cachefile -function _require(pkg::PkgId) - # handle recursive calls to require - loading = get(package_locks, pkg, false) - if loading !== false - # load already in progress for this module - wait(loading) - return +# whoever takes the package_locks[pkg] must call this function immediately +function set_pkgorigin_version_path(pkg::PkgId, path::Union{String,Nothing}) + assert_havelock(require_lock) + pkgorigin = get!(PkgOrigin, pkgorigins, pkg) + if path !== nothing + # Pkg needs access to the version of packages in the sysimage. + if Core.Compiler.generating_sysimg() + pkgorigin.version = get_pkgversion_from_path(joinpath(dirname(path), "..")) + end end - package_locks[pkg] = Threads.Condition(require_lock) + pkgorigin.path = path + nothing +end + +# A hook to allow code load to use Pkg.precompile +const PKG_PRECOMPILE_HOOK = Ref{Function}() + +# Returns `nothing` or the new(ish) module +function _require(pkg::PkgId, env=nothing) + assert_havelock(require_lock) + loaded = start_loading(pkg) + loaded === nothing || return loaded last = toplevel_load[] try toplevel_load[] = false # perform the search operation to select the module file require intends to load - path = locate_package(pkg) - get!(PkgOrigin, pkgorigins, pkg).path = path + path = locate_package(pkg, env) if path === nothing throw(ArgumentError(""" Package $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 # attempt to load the module file via the precompile cache locations if JLOptions().use_compiled_modules != 0 - m = _require_search_from_serialized(pkg, path) - if !isa(m, Bool) - return + @label load_from_cache + m = _require_search_from_serialized(pkg, path, UInt128(0)) + if m isa Module + return m end end @@ -1183,7 +1859,7 @@ function _require(pkg::PkgId) # 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 $concrete_build_id is missing from the cache. + @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.""" if JLOptions().incremental != 0 # during incremental precompilation, this should be fail-fast @@ -1194,8 +1870,17 @@ function _require(pkg::PkgId) if JLOptions().use_compiled_modules != 0 if (0 == ccall(:jl_generating_output, Cint, ())) || (JLOptions().incremental != 0) + if !pkg_precompile_attempted && isinteractive() && isassigned(PKG_PRECOMPILE_HOOK) + pkg_precompile_attempted = true + unlock(require_lock) + try + PKG_PRECOMPILE_HOOK[](pkg.name, _from_loading = true) + finally + lock(require_lock) + end + @goto load_from_cache + end # spawn off a new incremental pre-compile task for recursive `require` calls - # or if the require search declared it was pre-compiled before (and therefore is expected to still be pre-compilable) cachefile = compilecache(pkg, path) if isa(cachefile, Exception) if precompilableerror(cachefile) @@ -1206,11 +1891,12 @@ function _require(pkg::PkgId) end # fall-through to loading the file locally else - m = _require_from_serialized(cachefile) - if isa(m, Exception) + 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 else - return cachefile + return m end end end @@ -1227,7 +1913,7 @@ function _require(pkg::PkgId) unlock(require_lock) try include(__toplevel__, path) - return + loaded = get(loaded_modules, pkg, nothing) finally lock(require_lock) if uuid !== old_uuid @@ -1236,12 +1922,26 @@ function _require(pkg::PkgId) end finally toplevel_load[] = last - loading = pop!(package_locks, pkg) - notify(loading, all=true) + end_loading(pkg, loaded) + end + return loaded +end + +# Only used from test/precompile.jl +function _require_from_serialized(uuidkey::PkgId, path::String, ocachepath::Union{String, Nothing}) + @lock require_lock begin + set_pkgorigin_version_path(uuidkey, nothing) + newm = _tryrequire_from_serialized(uuidkey, path, ocachepath) + newm isa Module || throw(newm) + insert_extension_triggers(uuidkey) + # After successfully loading, notify downstream consumers + run_package_callbacks(uuidkey) + return newm end - nothing end + + # relative-path load """ @@ -1289,8 +1989,11 @@ include_string(m::Module, txt::AbstractString, fname::AbstractString="string") = function source_path(default::Union{AbstractString,Nothing}="") s = current_task().storage - if s !== nothing && haskey(s::IdDict{Any,Any}, :SOURCE_PATH) - return s[:SOURCE_PATH]::Union{Nothing,String} + if s !== nothing + s = s::IdDict{Any,Any} + if haskey(s, :SOURCE_PATH) + return s[:SOURCE_PATH]::Union{Nothing,String} + end end return default end @@ -1301,7 +2004,7 @@ function source_dir() end """ - Base.include([mapexpr::Function,] [m::Module,] path::AbstractString) + Base.include([mapexpr::Function,] m::Module, path::AbstractString) Evaluate the contents of the input source file in the global scope of module `m`. Every module (except those defined with [`baremodule`](@ref)) has its own @@ -1344,8 +2047,27 @@ end """ evalfile(path::AbstractString, args::Vector{String}=String[]) -Load the file using [`include`](@ref), evaluate all expressions, -and return the value of the last one. +Load the file into an anonymous module using [`include`](@ref), evaluate all expressions, +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 + +```jldoctest +julia> write("testfile.jl", \"\"\" + @show ARGS + 1 + 1 + \"\"\"); + +julia> x = evalfile("testfile.jl", ["ARG1", "ARG2"]); +ARGS = ["ARG1", "ARG2"] + +julia> x +2 + +julia> rm("testfile.jl") +``` """ function evalfile(path::AbstractString, args::Vector{String}=String[]) return Core.eval(Module(:__anon__), @@ -1395,19 +2117,25 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto task_local_storage()[:SOURCE_PATH] = source end + ccall(:jl_set_newly_inferred, Cvoid, (Any,), Core.Compiler.newly_inferred) + Core.Compiler.track_newly_inferred.x = true try Base.include(Base.__toplevel__, input) catch ex precompilableerror(ex) || rethrow() @debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace()) exit(125) # we define status = 125 means PrecompileableError + finally + Core.Compiler.track_newly_inferred.x = false end end const PRECOMPILE_TRACE_COMPILE = Ref{String}() -function create_expr_cache(pkg::PkgId, input::String, output::String, concrete_deps::typeof(_concrete_dependencies), internal_stderr::IO = stderr, internal_stdout::IO = stdout) +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) @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()) @@ -1426,18 +2154,32 @@ function create_expr_cache(pkg::PkgId, input::String, output::String, concrete_d for (pkg, build_id) in concrete_deps push!(deps_strs, "$(pkg_str(pkg)) => $(repr(build_id))") end + + if output_o !== nothing + 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` + else + 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(`$(julia_cmd()::Cmd) -O0 - --output-ji $output --output-incremental=yes - --startup-file=no --history-file=no --warn-overwrite=yes - --color=$(have_color === nothing ? "auto" : have_color ? "yes" : "no") - $trace - -`, stderr = internal_stderr, stdout = internal_stdout), + io = open(pipeline(addenv(`$(julia_cmd(;cpu_target)::Cmd) $(opts) + --startup-file=no --history-file=no --warn-overwrite=yes + --color=$(have_color === nothing ? "auto" : have_color ? "yes" : "no") + $trace + -`, + "OPENBLAS_NUM_THREADS" => 1, + "JULIA_NUM_THREADS" => 1), + stderr = internal_stderr, stdout = internal_stdout), "w", stdout) # 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.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)))) """) @@ -1460,6 +2202,14 @@ function compilecache_path(pkg::PkgId, prefs_hash::UInt64)::String crc = _crc32c(something(Base.active_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) + + cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing) + if cpu_target === nothing + cpu_target = unsafe_string(JLOptions().cpu_target) + end + crc = _crc32c(cpu_target, crc) + crc = _crc32c(prefs_hash, crc) project_precompile_slug = slug(crc, 5) abspath(cachepath, string(entryfile, "_", project_precompile_slug, ".ji")) @@ -1484,7 +2234,7 @@ end const MAX_NUM_PRECOMPILE_FILES = Ref(10) function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, internal_stdout::IO = stdout, - ignore_loaded_modules::Bool = true) + keep_loaded_modules::Bool = true) @nospecialize internal_stderr internal_stdout # decide where to put the resulting cache file @@ -1492,10 +2242,10 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # build up the list of modules that we want the precompile process to preserve concrete_deps = copy(_concrete_dependencies) - if ignore_loaded_modules - for (key, mod) in loaded_modules + if keep_loaded_modules + for mod in loaded_modules_array() if !(mod === Main || mod === Core || mod === Base) - push!(concrete_deps, key => module_build_id(mod)) + push!(concrete_deps, PkgId(mod) => module_build_id(mod)) end end end @@ -1506,53 +2256,151 @@ function compilecache(pkg::PkgId, path::String, internal_stderr::IO = stderr, in # 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 tmppath, tmpio = mktemp(cachepath) + + if cache_objects + tmppath_o, tmpio_o = mktemp(cachepath) + tmppath_so, tmpio_so = mktemp(cachepath) + else + tmppath_o = nothing + end local p try close(tmpio) - p = create_expr_cache(pkg, path, tmppath, concrete_deps, internal_stderr, internal_stdout) + if cache_objects + close(tmpio_o) + close(tmpio_so) + end + p = create_expr_cache(pkg, path, tmppath, tmppath_o, concrete_deps, internal_stderr, internal_stdout) + if success(p) - # append checksum to the end of the .ji file: - open(tmppath, "a+") do f - write(f, _crc32c(seekstart(f))) + if cache_objects + # Run linker over tmppath_o + Linking.link_image(tmppath_o, tmppath_so) end - # inherit permission from the source file (and make them writable) - chmod(tmppath, filemode(path) & 0o777 | 0o200) # 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) + ocachefile = cache_objects ? ocachefile_from_cachefile(cachefile) : nothing + + # append checksum for so to the end of the .ji file: + crc_so = UInt32(0) + if cache_objects + crc_so = open(_crc32c, tmppath_so, "r") + end + + # 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)).") + end + seekend(f) + write(f, crc_so) + seekstart(f) + write(f, _crc32c(f)) + end + + # 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 entrypath, entryfile = cache_file_entry(pkg) - cachefiles = filter!(x -> startswith(x, entryfile * "_"), readdir(cachepath)) + cachefiles = filter!(x -> startswith(x, entryfile * "_") && endswith(x, ".ji"), readdir(cachepath)) if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES[] idx = findmin(mtime.(joinpath.(cachepath, cachefiles)))[2] - rm(joinpath(cachepath, cachefiles[idx])) + evicted_cachefile = joinpath(cachepath, cachefiles[idx]) + @debug "Evicting file from cache" evicted_cachefile + rm(evicted_cachefile; force=true) + try + rm(ocachefile_from_cachefile(evicted_cachefile); force=true) + @static if Sys.isapple() + rm(ocachefile_from_cachefile(evicted_cachefile) * ".dSYM"; force=true, recursive=true) + end + catch e + e isa IOError || rethrow() + end end end - # this is atomic according to POSIX: + 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) + end + @static if Sys.isapple() + run(`$(Linking.dsymutil()) $ocachefile`, Base.DevNull(), Base.DevNull(), Base.DevNull()) + end + end + # this is atomic according to POSIX (not Win32): rename(tmppath, cachefile; force=true) - return cachefile + return cachefile, ocachefile end finally rm(tmppath, force=true) + if cache_objects + rm(tmppath_o::String, force=true) + rm(tmppath_so, force=true) + end end if p.exitcode == 125 return PrecompilableError() else - error("Failed to precompile $pkg to $tmppath.") + error("Failed to precompile $pkg to $(repr(tmppath)).") end end -module_build_id(m::Module) = ccall(:jl_module_build_id, UInt64, (Any,), m) +function module_build_id(m::Module) + hi, lo = ccall(:jl_module_build_id, NTuple{2,UInt64}, (Any,), m) + return (UInt128(hi) << 64) | lo +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 -isvalid_cache_header(f::IOStream) = (0 != ccall(:jl_read_verify_header, Cint, (Ptr{Cvoid},), f.ios)) + if !iszero(checksum) && pkgimage[] != 0 + @debug "Cache header was for pkgimage" + return UInt64(0) # We somehow read the header for a pkgimage and not a ji + end + return checksum +end isvalid_file_crc(f::IOStream) = (_crc32c(seekstart(f), filesize(f) - 4) == read(f, UInt32)) +function isvalid_pkgimage_crc(f::IOStream, ocachefile::String) + seekstart(f) # TODO necessary + seek(f, filesize(f) - 8) + expected_crc_so = read(f, UInt32) + crc_so = open(_crc32c, ocachefile, "r") + expected_crc_so == crc_so +end + struct CacheHeaderIncludes id::PkgId filename::String @@ -1561,6 +2409,7 @@ struct CacheHeaderIncludes end function parse_cache_header(f::IO) + flags = read(f, UInt8) modules = Vector{Pair{PkgId, UInt64}}() while true n = read(f, Int32) @@ -1624,44 +2473,46 @@ function parse_cache_header(f::IO) totbytes -= 8 @assert totbytes == 0 "header of cache file appears to be corrupt (totbytes == $(totbytes))" # read the list of modules that are required to be present during loading - required_modules = Vector{Pair{PkgId, UInt64}}() + required_modules = Vector{Pair{PkgId, UInt128}}() while true n = read(f, Int32) n == 0 && break sym = String(read(f, n)) # module name uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID - build_id = read(f, UInt64) # build id + build_id = UInt128(read(f, UInt64)) << 64 + build_id |= read(f, UInt64) push!(required_modules, PkgId(uuid, sym) => build_id) end - return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash + l = read(f, Int32) + clone_targets = read(f, l) + + return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags end function parse_cache_header(cachefile::String; srcfiles_only::Bool=false) io = open(cachefile, "r") try - !isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) ret = parse_cache_header(io) srcfiles_only || return ret - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash = 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) - return modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash + return ret finally close(io) end end - - -preferences_hash(f::IO) = parse_cache_header(f)[end] +preferences_hash(f::IO) = parse_cache_header(f)[6] function preferences_hash(cachefile::String) io = open(cachefile, "r") try - if !isvalid_cache_header(io) + if iszero(isvalid_cache_header(io)) throw(ArgumentError("Invalid header in cache file $cachefile.")) end return preferences_hash(io) @@ -1670,16 +2521,15 @@ function preferences_hash(cachefile::String) end end - function cache_dependencies(f::IO) - defs, (includes, requires), modules, srctextpos, prefs, prefs_hash = parse_cache_header(f) + _, (includes, _), modules, _... = parse_cache_header(f) return modules, map(chi -> (chi.filename, chi.mtime), includes) # return just filename and mtime end function cache_dependencies(cachefile::String) io = open(cachefile, "r") try - !isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) return cache_dependencies(io) finally close(io) @@ -1687,7 +2537,7 @@ function cache_dependencies(cachefile::String) end function read_dependency_src(io::IO, filename::AbstractString) - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash = parse_cache_header(io) + srctextpos = parse_cache_header(io)[4] srctextpos == 0 && error("no source-text stored in cache file") seek(io, srctextpos) return _read_dependency_src(io, filename) @@ -1710,7 +2560,7 @@ end function read_dependency_src(cachefile::String, filename::AbstractString) io = open(cachefile, "r") try - !isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile.")) + iszero(isvalid_cache_header(io)) && throw(ArgumentError("Invalid header in cache file $cachefile.")) return read_dependency_src(io, filename) finally close(io) @@ -1734,7 +2584,7 @@ end # Test to see if this UUID is mentioned in this `Project.toml`; either as # the top-level UUID (e.g. that of the project itself), as a dependency, -# or as an extra for Preferences. +# or as an extra/weakdep for Preferences. function get_uuid_name(project::Dict{String, Any}, uuid::UUID) uuid_p = get(project, "uuid", nothing)::Union{Nothing, String} name = get(project, "name", nothing)::Union{Nothing, String} @@ -1749,7 +2599,7 @@ function get_uuid_name(project::Dict{String, Any}, uuid::UUID) end end end - for subkey in ("deps", "extras") + for subkey in ("deps", "extras", "weakdeps") subsection = get(project, subkey, nothing)::Union{Nothing, Dict{String, Any}} if subsection !== nothing for (k, v) in subsection @@ -1873,7 +2723,7 @@ function get_preferences_hash(uuid::Union{UUID, Nothing}, prefs_list::Vector{Str for name in prefs_list prefs_value = get(prefs, name, nothing) if prefs_value !== nothing - h = hash(prefs_value, h) + h = hash(prefs_value, h)::UInt end end # We always return a `UInt64` so that our serialization format is stable @@ -1897,17 +2747,100 @@ get_compiletime_preferences(uuid::UUID) = collect(get(Vector{String}, COMPILETIM get_compiletime_preferences(m::Module) = get_compiletime_preferences(PkgId(m).uuid) get_compiletime_preferences(::Nothing) = String[] -# returns true if it "cachefile.ji" is stale relative to "modpath.jl" +function check_clone_targets(clone_targets) + try + ccall(:jl_check_pkgimage_clones, Cvoid, (Ptr{Cchar},), clone_targets) + return true + catch + return false + 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 + +# 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) +end +@constprop :none function stale_cachefile(modkey::PkgId, build_id::UInt128, modpath::String, cachefile::String; ignore_loaded::Bool = false) io = open(cachefile, "r") try - if !isvalid_cache_header(io) + checksum = isvalid_cache_header(io) + if iszero(checksum) @debug "Rejecting cache file $cachefile due to it containing an invalid cache header" return true # invalid cache file end - modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash = parse_cache_header(io) - id = isempty(modules) ? nothing : first(modules).first + modules, (includes, requires), required_modules, srctextpos, prefs, prefs_hash, clone_targets, flags = parse_cache_header(io) + if isempty(modules) + return true # ignore empty file + end + if ccall(:jl_match_cache_flags, UInt8, (UInt8,), flags) == 0 + @debug """ + Rejecting cache file $cachefile for $modkey since the flags are mismatched + current session: $(CacheFlags()) + cache file: $(CacheFlags(flags)) + """ + return true + end + pkgimage = !isempty(clone_targets) + if pkgimage + ocachefile = ocachefile_from_cachefile(cachefile) + 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" + return true + end + if !check_clone_targets(clone_targets) + @debug "Rejecting cache file $cachefile for $modkey since pkgimage can't be loaded on this target" + return true + end + if !isfile(ocachefile) + @debug "Rejecting cache file $cachefile for $modkey since pkgimage $ocachefile was not found" + return true + end + else + ocachefile = nothing + end + id = first(modules) + if id.first != modkey && modkey != PkgId("") + @debug "Rejecting cache file $cachefile for $modkey since it is for $id instead" + 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))))" + return true + end + end + id = id.first modules = Dict{PkgId, UInt64}(modules) # Check if transitive dependencies can be fulfilled @@ -1930,7 +2863,6 @@ get_compiletime_preferences(::Nothing) = String[] else @label locate_branch path = locate_package(req_key) - get!(PkgOrigin, pkgorigins, req_key).path = path if path === nothing @debug "Rejecting cache file $cachefile because dependency $req_key not found." return true # Won't be able to fulfill dependency @@ -1946,18 +2878,19 @@ get_compiletime_preferences(::Nothing) = String[] 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 break end - @debug "Rejecting cache file $cachefile because it provides the wrong uuid (got $build_id) for $req_key (want $req_build_id)" + @debug "Rejecting cache file $cachefile because it provides the wrong build_id (got $((UUID(build_id)))) for $req_key (want $(UUID(req_build_id)))" 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 - if !samefile(includes[1].filename, modpath) + 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" return true # cache file was compiled from a different path end @@ -1970,11 +2903,24 @@ get_compiletime_preferences(::Nothing) = String[] end for chi in includes f, ftime_req = chi.filename, chi.mtime - # Issue #13606: compensate for Docker images rounding mtimes - # Issue #20837: compensate for GlusterFS truncating mtimes to microseconds - # The `ftime != 1.0` condition below provides compatibility with Nix mtime. + if !ispath(f) + _f = fixup_stdlib_path(f) + if isfile(_f) && startswith(_f, Sys.STDLIB) + # mtime is changed by extraction + @debug "Skipping mtime check for file $f used by $cachefile, since it is a stdlib" + continue + end + @debug "Rejecting stale cache file $cachefile because file $f does not exist" + return true + end ftime = mtime(f) - if ftime != ftime_req && ftime != floor(ftime_req) && ftime != trunc(ftime_req, digits=6) && ftime != 1.0 + 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 end @@ -1986,24 +2932,27 @@ get_compiletime_preferences(::Nothing) = String[] return true end - if isa(id, PkgId) - 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))" + if pkgimage + if !isvalid_pkgimage_crc(io, ocachefile::String) + @debug "Rejecting cache file $cachefile because $ocachefile has an invalid checksum" return true end + end - get!(PkgOrigin, pkgorigins, id).cachepath = cachefile + 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))" + return true end - return depmods # fresh cachefile + return depmods, ocachefile # fresh cachefile finally close(io) end end """ - @__FILE__ -> AbstractString + @__FILE__ -> String Expand to a string with the path to the file containing the macrocall, or an empty string if evaluated by `julia -e `. @@ -2016,7 +2965,7 @@ macro __FILE__() end """ - @__DIR__ -> AbstractString + @__DIR__ -> String Expand to a string with the absolute path to the directory of the file containing the macrocall. @@ -2029,16 +2978,16 @@ macro __DIR__() end """ - precompile(f, args::Tuple{Vararg{Any}}) + precompile(f, argtypes::Tuple{Vararg{Any}}) -Compile the given function `f` for the argument tuple (of types) `args`, but do not execute it. +Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it. """ -function precompile(@nospecialize(f), args::Tuple) - precompile(Tuple{Core.Typeof(f), args...}) +function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple)) + precompile(Tuple{Core.Typeof(f), argtypes...}) end const ENABLE_PRECOMPILE_WARNINGS = Ref(false) -function precompile(argt::Type) +function precompile(@nospecialize(argt::Type)) ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0 if !ret && ENABLE_PRECOMPILE_WARNINGS[] @warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0 @@ -2046,6 +2995,28 @@ function precompile(argt::Type) return ret end +# Variants that work for `invoke`d calls for which the signature may not be sufficient +precompile(mi::Core.MethodInstance, world::UInt=get_world_counter()) = + (ccall(:jl_compile_method_instance, Cvoid, (Any, Any, UInt), mi, C_NULL, world); return true) + +""" + precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method) + +Precompile a specific method for the given argument types. This may be used to precompile +a different method than the one that would ordinarily be chosen by dispatch, thus +mimicking `invoke`. +""" +function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method) + precompile(Tuple{Core.Typeof(f), argtypes...}, m) +end + +function precompile(@nospecialize(argt::Type), m::Method) + atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector + mi = Core.Compiler.specialize_method(m, atype, sparams) + 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, typeof(_concrete_dependencies), IO, IO)) +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)) diff --git a/base/lock.jl b/base/lock.jl index 9057a39294229..1663a765111bb 100644 --- a/base/lock.jl +++ b/base/lock.jl @@ -7,8 +7,8 @@ const ThreadSynchronizer = GenericCondition{Threads.SpinLock} ReentrantLock() Creates a re-entrant lock for synchronizing [`Task`](@ref)s. The same task can -acquire the lock as many times as required. Each [`lock`](@ref) must be matched -with an [`unlock`](@ref). +acquire the lock as many times as required (this is what the "Reentrant" part +of the name means). Each [`lock`](@ref) must be matched with an [`unlock`](@ref). Calling 'lock' will also inhibit running of finalizers on that thread until the corresponding 'unlock'. Use of the standard lock pattern illustrated below @@ -26,6 +26,9 @@ finally unlock(l) end ``` + +If [`!islocked(lck::ReentrantLock)`](@ref islocked) holds, [`trylock(lck)`](@ref trylock) +succeeds unless there are other tasks attempting to hold the lock "at the same time." """ mutable struct ReentrantLock <: AbstractLock # offset = 16 @@ -52,10 +55,43 @@ assert_havelock(l::ReentrantLock) = assert_havelock(l, l.locked_by) islocked(lock) -> Status (Boolean) Check whether the `lock` is held by any task/thread. -This should not be used for synchronization (see instead [`trylock`](@ref)). +This function alone should not be used for synchronization. However, `islocked` combined +with [`trylock`](@ref) can be used for writing the test-and-test-and-set or exponential +backoff algorithms *if it is supported by the `typeof(lock)`* (read its documentation). + +# Extended help + +For example, an exponential backoff can be implemented as follows if the `lock` +implementation satisfied the properties documented below. + +```julia +nspins = 0 +while true + while islocked(lock) + GC.safepoint() + nspins += 1 + nspins > LIMIT && error("timeout") + end + trylock(lock) && break + backoff() +end +``` + +## Implementation + +A lock implementation is advised to define `islocked` with the following properties and note +it in its docstring. + +* `islocked(lock)` is data-race-free. +* If `islocked(lock)` returns `false`, an immediate invocation of `trylock(lock)` must + succeed (returns `true`) if there is no interference from other tasks. """ +function islocked end +# Above docstring is a documentation for the abstract interface and not the one specific to +# `ReentrantLock`. + function islocked(rl::ReentrantLock) - return rl.havelock != 0 + return (@atomic :monotonic rl.havelock) != 0 end """ @@ -67,7 +103,15 @@ If the lock is already locked by a different task/thread, return `false`. Each successful `trylock` must be matched by an [`unlock`](@ref). + +Function `trylock` combined with [`islocked`](@ref) can be used for writing the +test-and-test-and-set or exponential backoff algorithms *if it is supported by the +`typeof(lock)`* (read its documentation). """ +function trylock end +# Above docstring is a documentation for the abstract interface and not the one specific to +# `ReentrantLock`. + @inline function trylock(rl::ReentrantLock) ct = current_task() if rl.locked_by === ct @@ -437,8 +481,8 @@ end """ reset(::Event) -Reset an Event back into an un-set state. Then any future calls to `wait` will -block until `notify` is called again. +Reset an [`Event`](@ref) back into an un-set state. Then any future calls to `wait` will +block until [`notify`](@ref) is called again. """ function reset(e::Event) @atomic e.set = false # full barrier diff --git a/base/locks-mt.jl b/base/locks-mt.jl index 7ede8704ec498..5d355b9ed200c 100644 --- a/base/locks-mt.jl +++ b/base/locks-mt.jl @@ -21,6 +21,8 @@ to execute and does not block (e.g. perform I/O). In general, [`ReentrantLock`](@ref) should be used instead. Each [`lock`](@ref) must be matched with an [`unlock`](@ref). +If [`!islocked(lck::SpinLock)`](@ref islocked) holds, [`trylock(lck)`](@ref trylock) +succeeds unless there are other tasks attempting to hold the lock "at the same time." Test-and-test-and-set spin locks are quickest up to about 30ish contending threads. If you have more contention than that, different @@ -41,7 +43,7 @@ function lock(l::SpinLock) if @inline trylock(l) return end - ccall(:jl_cpu_pause, Cvoid, ()) + ccall(:jl_cpu_suspend, Cvoid, ()) # Temporary solution before we have gc transition support in codegen. ccall(:jl_gc_safepoint, Cvoid, ()) end @@ -69,5 +71,5 @@ function unlock(l::SpinLock) end function islocked(l::SpinLock) - return l.owned != 0 + return (@atomic :monotonic l.owned) != 0 end diff --git a/base/logging.jl b/base/logging.jl index 667b8ddead983..c42af08d8f4ae 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -42,7 +42,7 @@ function handle_message end """ shouldlog(logger, level, _module, group, id) -Return true when `logger` accepts a message at `level`, generated for +Return `true` when `logger` accepts a message at `level`, generated for `_module`, `group` and with unique log identifier `id`. """ function shouldlog end @@ -58,7 +58,7 @@ function min_enabled_level end """ catch_exceptions(logger) -Return true if the logger should catch exceptions which happen during log +Return `true` if the logger should catch exceptions which happen during log record construction. By default, messages are caught By default all exceptions are caught to prevent log message generation from @@ -349,7 +349,7 @@ function logmsg_code(_module, file, line, level, message, exs...) kwargs = (;$(log_data.kwargs...)) true else - logging_error(logger, level, _module, group, id, file, line, err, false) + @invokelatest logging_error(logger, level, _module, group, id, file, line, err, false) false end end @@ -361,7 +361,7 @@ function logmsg_code(_module, file, line, level, message, exs...) kwargs = (;$(log_data.kwargs...)) true catch err - logging_error(logger, level, _module, group, id, file, line, err, true) + @invokelatest logging_error(logger, level, _module, group, id, file, line, err, true) false end end @@ -369,7 +369,8 @@ function logmsg_code(_module, file, line, level, message, exs...) return quote let level = $level - std_level = convert(LogLevel, 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[] group = $(log_data._group) _module = $(log_data._module) @@ -378,11 +379,14 @@ function logmsg_code(_module, file, line, level, message, exs...) id = $(log_data._id) # Second chance at an early bail-out (before computing the message), # based on arbitrary logger-specific logic. - if _invoked_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) && handle_message( + $(logrecord) && invokelatest(handle_message, logger, level, msg, _module, group, id, file, line; kwargs...) end @@ -442,7 +446,7 @@ function default_group_code(file) QuoteNode(default_group(file)) # precompute if we can else ref = Ref{Symbol}() # memoized run-time execution - :(isassigned($ref) ? $ref[] : $ref[] = default_group(something($file, ""))) + :(isassigned($ref) ? $ref[] : $ref[] = default_group(something($file, ""))::Symbol) end end @@ -457,7 +461,7 @@ end msg = try "Exception while generating log record in module $_module at $filepath:$line" catch ex - "Exception handling log message: $ex" + LazyString("Exception handling log message: ", ex) end bt = real ? catch_backtrace() : backtrace() handle_message( @@ -668,13 +672,13 @@ function handle_message(logger::SimpleLogger, level::LogLevel, message, _module, remaining > 0 || return end buf = IOBuffer() - stream = logger.stream - if !isopen(stream) + stream::IO = logger.stream + if !(isopen(stream)::Bool) stream = stderr end iob = IOContext(buf, stream) levelstr = level == Warn ? "Warning" : string(level) - msglines = eachsplit(chomp(string(message)::String), '\n') + msglines = eachsplit(chomp(convert(String, string(message))::String), '\n') msg1, rest = Iterators.peel(msglines) println(iob, "┌ ", levelstr, ": ", msg1) for msg in rest diff --git a/base/math.jl b/base/math.jl index af86c11c01b26..71bd4949498b5 100644 --- a/base/math.jl +++ b/base/math.jl @@ -5,12 +5,12 @@ module Math export sin, cos, sincos, tan, sinh, cosh, tanh, asin, acos, atan, asinh, acosh, atanh, sec, csc, cot, asec, acsc, acot, sech, csch, coth, asech, acsch, acoth, - sinpi, cospi, sincospi, sinc, cosc, + sinpi, cospi, sincospi, tanpi, sinc, cosc, cosd, cotd, cscd, secd, sind, tand, sincosd, acosd, acotd, acscd, asecd, asind, atand, rad2deg, deg2rad, log, log2, log10, log1p, exponent, exp, exp2, exp10, expm1, - cbrt, sqrt, significand, + cbrt, sqrt, fourthroot, significand, hypot, max, min, minmax, ldexp, frexp, clamp, clamp!, modf, ^, mod2pi, rem2pi, @evalpoly, evalpoly @@ -31,7 +31,11 @@ using .Base: IEEEFloat @noinline function throw_complex_domainerror(f::Symbol, x) throw(DomainError(x, - LazyString(f," will only return a complex result if called with a complex argument. Try ", f,"(Complex(x))."))) + LazyString(f," was called with a negative real argument but will only return a complex result if called with a complex argument. Try ", f,"(Complex(x))."))) +end +@noinline function throw_complex_domainerror_neg1(f::Symbol, x) + throw(DomainError(x, + LazyString(f," was called with a real argument < -1 but will only return a complex result if called with a complex argument. Try ", f,"(Complex(x))."))) end @noinline function throw_exp_domainerror(x) throw(DomainError(x, LazyString( @@ -42,7 +46,12 @@ end # non-type specific math functions -@inline function two_mul(x::Float64, y::Float64) +function two_mul(x::T, y::T) where {T<:Number} + xy = x*y + xy, fma(x, y, -xy) +end + +@assume_effects :consistent @inline function two_mul(x::Float64, y::Float64) if Core.Intrinsics.have_fma(Float64) xy = x*y return xy, fma(x, y, -xy) @@ -50,7 +59,7 @@ end return Base.twomul(x,y) end -@inline function two_mul(x::T, y::T) where T<: Union{Float16, Float32} +@assume_effects :consistent @inline function two_mul(x::T, y::T) where T<: Union{Float16, Float32} if Core.Intrinsics.have_fma(T) xy = x*y return xy, fma(x, y, -xy) @@ -190,6 +199,7 @@ end evalpoly(x, p::AbstractVector) = _evalpoly(x, p) function _evalpoly(x, p) + Base.require_one_based_indexing(p) N = length(p) ex = p[end] for i in N-1:-1:1 @@ -229,6 +239,7 @@ evalpoly(z::Complex, p::Tuple{<:Any}) = p[1] evalpoly(z::Complex, p::AbstractVector) = _evalpoly(z, p) function _evalpoly(z::Complex, p) + Base.require_one_based_indexing(p) length(p) == 1 && return p[1] N = length(p) a = p[end] @@ -293,10 +304,10 @@ end # polynomial evaluation using compensated summation. # much more accurate, especially when lo can be combined with other rounding errors -@inline function exthorner(x, p::Tuple) +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 = p[i] + 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) @@ -309,6 +320,8 @@ end Convert `x` from radians to degrees. +See also [`deg2rad`](@ref). + # Examples ```jldoctest julia> rad2deg(pi) @@ -322,7 +335,7 @@ rad2deg(z::AbstractFloat) = z * (180 / oftype(z, pi)) Convert `x` from degrees to radians. -See also: [`rad2deg`](@ref), [`sind`](@ref). +See also [`rad2deg`](@ref), [`sind`](@ref), [`pi`](@ref). # Examples ```jldoctest @@ -354,14 +367,14 @@ julia> log(4,2) julia> log(-2, 3) ERROR: DomainError with -2.0: -log will only return a complex result if called with a complex argument. Try log(Complex(x)). +log was called with a negative real argument but will only return a complex result if called with a complex argument. Try log(Complex(x)). Stacktrace: [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31 [...] julia> log(2, -3) ERROR: DomainError with -3.0: -log will only return a complex result if called with a complex argument. Try log(Complex(x)). +log was called with a negative real argument but will only return a complex result if called with a complex argument. Try log(Complex(x)). Stacktrace: [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31 [...] @@ -404,6 +417,28 @@ cosh(x::Number) tanh(x) Compute hyperbolic tangent of `x`. + +See also [`tan`](@ref), [`atanh`](@ref). + +# Examples + +```jldoctest +julia> tanh.(-3:3f0) # Here 3f0 isa Float32 +7-element Vector{Float32}: + -0.9950548 + -0.9640276 + -0.7615942 + 0.0 + 0.7615942 + 0.9640276 + 0.9950548 + +julia> tan.(im .* (1:3)) +3-element Vector{ComplexF64}: + 0.0 + 0.7615941559557649im + 0.0 + 0.9640275800758169im + 0.0 + 0.9950547536867306im +``` """ tanh(x::Number) @@ -420,6 +455,21 @@ For two arguments, this is the angle in radians between the positive *x*-axis an point (*x*, *y*), returning a value in the interval ``[-\\pi, \\pi]``. This corresponds to a standard [`atan2`](https://en.wikipedia.org/wiki/Atan2) function. Note that by convention `atan(0.0,x)` is defined as ``\\pi`` and `atan(-0.0,x)` is defined as ``-\\pi`` when `x < 0`. + +See also [`atand`](@ref) for degrees. + +# Examples + +```jldoctest +julia> rad2deg(atan(-1/√3)) +-30.000000000000004 + +julia> rad2deg(atan(-1, √3)) +-30.000000000000004 + +julia> rad2deg(atan(1, -√3)) +150.0 +``` """ atan(x::Number) @@ -442,7 +492,29 @@ asinh(x::Number) Compute sine of `x`, where `x` is in radians. -See also [`sind`], [`sinpi`], [`sincos`], [`cis`]. +See also [`sind`](@ref), [`sinpi`](@ref), [`sincos`](@ref), [`cis`](@ref), [`asin`](@ref). + +# Examples +```jldoctest +julia> round.(sin.(range(0, 2pi, length=9)'), digits=3) +1×9 Matrix{Float64}: + 0.0 0.707 1.0 0.707 0.0 -0.707 -1.0 -0.707 -0.0 + +julia> sind(45) +0.7071067811865476 + +julia> sinpi(1/4) +0.7071067811865475 + +julia> round.(sincos(pi/6), digits=3) +(0.5, 0.866) + +julia> round(cis(pi/6), digits=3) +0.866 + 0.5im + +julia> round(exp(im*pi/6), digits=3) +0.866 + 0.5im +``` """ sin(x::Number) @@ -451,7 +523,7 @@ sin(x::Number) Compute cosine of `x`, where `x` is in radians. -See also [`cosd`], [`cospi`], [`sincos`], [`cis`]. +See also [`cosd`](@ref), [`cospi`](@ref), [`sincos`](@ref), [`cis`](@ref). """ cos(x::Number) @@ -466,6 +538,17 @@ tan(x::Number) asin(x) Compute the inverse sine of `x`, where the output is in radians. + +See also [`asind`](@ref) for output in degrees. + +# Examples +```jldoctest +julia> asin.((0, 1/2, 1)) +(0.0, 0.5235987755982989, 1.5707963267948966) + +julia> asind.((0, 1/2, 1)) +(0.0, 30.000000000000004, 90.0) +``` """ asin(x::Number) @@ -496,7 +579,7 @@ atanh(x::Number) Compute the natural logarithm of `x`. Throws [`DomainError`](@ref) for negative [`Real`](@ref) arguments. Use complex negative arguments to obtain complex results. -See also [`log1p`], [`log2`], [`log10`]. +See also [`ℯ`](@ref), [`log1p`](@ref), [`log2`](@ref), [`log10`](@ref). # Examples ```jldoctest; filter = r"Stacktrace:(\\n \\[[0-9]+\\].*)*" @@ -505,10 +588,16 @@ julia> log(2) julia> log(-3) ERROR: DomainError with -3.0: -log will only return a complex result if called with a complex argument. Try log(Complex(x)). +log was called with a negative real argument but will only return a complex result if called with a complex argument. Try log(Complex(x)). Stacktrace: [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31 [...] + +julia> log.(exp.(-1:1)) +3-element Vector{Float64}: + -1.0 + 0.0 + 1.0 ``` """ log(x::Number) @@ -531,10 +620,16 @@ julia> log2(10) julia> log2(-2) ERROR: DomainError with -2.0: -log2 will only return a complex result if called with a complex argument. Try log2(Complex(x)). +log2 was called with a negative real argument but will only return a complex result if called with a complex argument. Try log2(Complex(x)). Stacktrace: [1] throw_complex_domainerror(f::Symbol, x::Float64) at ./math.jl:31 [...] + +julia> log2.(2.0 .^ (-1:1)) +3-element Vector{Float64}: + -1.0 + 0.0 + 1.0 ``` """ log2(x) @@ -555,7 +650,7 @@ julia> log10(2) julia> log10(-2) ERROR: DomainError with -2.0: -log10 will only return a complex result if called with a complex argument. Try log10(Complex(x)). +log10 was called with a negative real argument but will only return a complex result if called with a complex argument. Try log10(Complex(x)). Stacktrace: [1] throw_complex_domainerror(f::Symbol, x::Float64) at ./math.jl:31 [...] @@ -579,7 +674,7 @@ julia> log1p(0) julia> log1p(-2) ERROR: DomainError with -2.0: -log1p will only return a complex result if called with a complex argument. Try log1p(Complex(x)). +log1p was called with a real argument < -1 but will only return a complex result if called with a complex argument. Try log1p(Complex(x)). Stacktrace: [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31 [...] @@ -625,6 +720,13 @@ julia> .√(1:4) """ sqrt(x) +""" + fourthroot(x) + +Return the fourth root of `x` by applying `sqrt` twice successively. +""" +fourthroot(x::Number) = sqrt(sqrt(x)) + """ hypot(x, y) @@ -633,7 +735,7 @@ Compute the hypotenuse ``\\sqrt{|x|^2+|y|^2}`` avoiding overflow and underflow. This code is an implementation of the algorithm described in: An Improved Algorithm for `hypot(a,b)` by Carlos F. Borges -The article is available online at ArXiv at the link +The article is available online at arXiv at the link https://arxiv.org/abs/1904.09481 hypot(x...) @@ -651,7 +753,7 @@ julia> hypot(a, a) julia> √(a^2 + a^2) # a^2 overflows ERROR: DomainError with -2.914184810805068e18: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). Stacktrace: [...] @@ -671,7 +773,8 @@ true ``` """ hypot(x::Number) = abs(float(x)) -hypot(x::Number, y::Number, xs::Number...) = _hypot(float.(promote(x, y, xs...))...) +hypot(x::Number, y::Number) = _hypot(promote(float(x), y)...) +hypot(x::Number, y::Number, xs::Number...) = _hypot(promote(float(x), y, xs...)) function _hypot(x, y) # preserves unit axu = abs(x) @@ -743,7 +846,7 @@ end end _hypot(x::ComplexF16, y::ComplexF16) = Float16(_hypot(ComplexF32(x), ComplexF32(y))) -function _hypot(x...) +function _hypot(x::NTuple{N,<:Number}) where {N} maxabs = maximum(abs, x) if isnan(maxabs) && any(isinf, x) return typeof(maxabs)(Inf) @@ -754,20 +857,70 @@ function _hypot(x...) end end +function _hypot(x::NTuple{N,<:IEEEFloat}) where {N} + T = eltype(x) + infT = convert(T, Inf) + x = abs.(x) # doesn't change result but enables computational shortcuts + # note: any() was causing this to not inline for N=3 but mapreduce() was not + mapreduce(==(infT), |, x) && return infT # return Inf even if an argument is NaN + maxabs = reinterpret(T, maximum(z -> reinterpret(Signed, z), x)) # for abs(::IEEEFloat) values, a ::BitInteger cast does not change the result + maxabs > zero(T) || return maxabs # catch NaN before the @fastmath below, but also shortcut 0 since we can (remove if no more @fastmath below) + scale,invscale = scaleinv(maxabs) + # @fastmath(+) to allow reassociation (see #48129) + add_fast(x, y) = Core.Intrinsics.add_float_fast(x, y) # @fastmath is not available during bootstrap + return scale * sqrt(mapreduce(y -> abs2(y * invscale), add_fast, x)) +end + atan(y::Real, x::Real) = atan(promote(float(y),float(x))...) atan(y::T, x::T) where {T<:AbstractFloat} = Base.no_op_err("atan", T) -max(x::T, y::T) where {T<:AbstractFloat} = ifelse((y > x) | (signbit(y) < signbit(x)), - ifelse(isnan(x), x, y), ifelse(isnan(y), y, x)) +_isless(x::T, y::T) where {T<:AbstractFloat} = (x < y) || (signbit(x) > signbit(y)) +min(x::T, y::T) where {T<:AbstractFloat} = isnan(x) || ~isnan(y) && _isless(x, y) ? x : y +max(x::T, y::T) where {T<:AbstractFloat} = isnan(x) || ~isnan(y) && _isless(y, x) ? x : y +minmax(x::T, y::T) where {T<:AbstractFloat} = min(x, y), max(x, y) +_isless(x::Float16, y::Float16) = signbit(widen(x) - widen(y)) -min(x::T, y::T) where {T<:AbstractFloat} = ifelse((y < x) | (signbit(y) > signbit(x)), - ifelse(isnan(x), x, y), ifelse(isnan(y), y, x)) +const has_native_fminmax = Sys.ARCH === :aarch64 +@static if has_native_fminmax + @eval begin + Base.@assume_effects :total @inline llvm_min(x::Float64, y::Float64) = ccall("llvm.minimum.f64", llvmcall, Float64, (Float64, Float64), x, y) + Base.@assume_effects :total @inline llvm_min(x::Float32, y::Float32) = ccall("llvm.minimum.f32", llvmcall, Float32, (Float32, Float32), x, y) + Base.@assume_effects :total @inline llvm_max(x::Float64, y::Float64) = ccall("llvm.maximum.f64", llvmcall, Float64, (Float64, Float64), x, y) + Base.@assume_effects :total @inline llvm_max(x::Float32, y::Float32) = ccall("llvm.maximum.f32", llvmcall, Float32, (Float32, Float32), x, y) + end +end -minmax(x::T, y::T) where {T<:AbstractFloat} = - ifelse(isnan(x) | isnan(y), ifelse(isnan(x), (x,x), (y,y)), - ifelse((y > x) | (signbit(x) > signbit(y)), (x,y), (y,x))) +function min(x::T, y::T) where {T<:Union{Float32,Float64}} + @static if has_native_fminmax + return llvm_min(x,y) + end + diff = x - y + argmin = ifelse(signbit(diff), x, y) + anynan = isnan(x)|isnan(y) + return ifelse(anynan, diff, argmin) +end +function max(x::T, y::T) where {T<:Union{Float32,Float64}} + @static if has_native_fminmax + return llvm_max(x,y) + end + diff = x - y + argmax = ifelse(signbit(diff), y, x) + anynan = isnan(x)|isnan(y) + return ifelse(anynan, diff, argmax) +end + +function minmax(x::T, y::T) where {T<:Union{Float32,Float64}} + @static if has_native_fminmax + return llvm_min(x, y), llvm_max(x, y) + end + diff = x - y + sdiff = signbit(diff) + min, max = ifelse(sdiff, x, y), ifelse(sdiff, y, x) + anynan = isnan(x)|isnan(y) + return ifelse(anynan, diff, min), ifelse(anynan, diff, max) +end """ ldexp(x, n) @@ -826,18 +979,27 @@ end ldexp(x::Float16, q::Integer) = Float16(ldexp(Float32(x), q)) """ - exponent(x::AbstractFloat) -> Int + exponent(x) -> Int -Get the exponent of a normalized floating-point number. Returns the largest integer `y` such that `2^y ≤ abs(x)`. +For a normalized floating-point number `x`, this corresponds to the exponent of `x`. # Examples ```jldoctest +julia> exponent(8) +3 + +julia> exponent(64//1) +6 + julia> exponent(6.5) 2 julia> exponent(16.0) 4 + +julia> exponent(3.142e-4) +-12 ``` """ function exponent(x::T) where T<:IEEEFloat @@ -934,6 +1096,40 @@ function frexp(x::T) where T<:IEEEFloat return reinterpret(T, xu), k end +""" + $(@__MODULE__).scaleinv(x) + +Compute `(scale, invscale)` where `scale` and `invscale` are non-subnormal +(https://en.wikipedia.org/wiki/Subnormal_number) finite powers of two such that +`scale * invscale == 1` and `scale` is roughly on the order of `abs(x)`. +Inf, NaN, and zero inputs also result in finite nonzero outputs. +These values are useful to scale computations to prevent overflow and underflow +without round-off errors or division. + +UNSTABLE DETAIL: For `x isa IEEEFLoat`, `scale` is chosen to be the +`prevpow(2,abs(x))` when possible, but is never less than floatmin(x) or greater +than inv(floatmin(x)). `Inf` and `NaN` resolve to `inv(floatmin(x))`. This +behavior is subject to change. + +# Examples +```jldoctest +julia> $(@__MODULE__).scaleinv(7.5) +(4.0, 0.25) +``` +""" +function scaleinv(x::T) where T<:IEEEFloat + # by removing the sign and significand and restricting values to a limited range, + # we can invert a number using bit-twiddling instead of division + U = uinttype(T) + umin = reinterpret(U, floatmin(T)) + umax = reinterpret(U, inv(floatmin(T))) + emask = exponent_mask(T) # used to strip sign and significand + u = clamp(reinterpret(U, x) & emask, umin, umax) + scale = reinterpret(T, u) + invscale = reinterpret(T, umin + umax - u) # inv(scale) + return scale, invscale +end + # NOTE: This `rem` method is adapted from the msun `remainder` and `remainderf` # functions, which are under the following license: # @@ -996,34 +1192,53 @@ 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) - yint = unsafe_trunc(Int, y) # Note, this is actually safe since julia freezes the result - y == yint && return x^yint - x<0 && y > -4e18 && throw_exp_domainerror(x) # |y| is small enough that y isn't an integer - x == 1 && return 1.0 - return pow_body(x, y) + xu = reinterpret(UInt64, x) + xu == reinterpret(UInt64, 1.0) && return 1.0 + # Exponents greater than this will always overflow or underflow. + # Note that NaN can pass through this, but that will end up fine. + if !(abs(y)<0x1.8p62) + isnan(y) && return y + 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 + 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(x::Float64, y::Float64) - !isfinite(x) && return x*(y>0 || isnan(x)) - x==0 && return abs(y)*Inf*(!(y>0)) - logxhi,logxlo = Base.Math._log_ext(x) - xyhi = logxhi*y - xylo = logxlo*y +@inline function pow_body(xu::UInt64, y::Float64) + logxhi,logxlo = Base.Math._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(:ℯ)) end @constprop :aggressive function ^(x::T, y::T) where T <: Union{Float16, Float32} - yint = unsafe_trunc(Int64, y) # Note, this is actually safe since julia freezes the result + x == 1 && return one(T) + # Exponents greater than this will always overflow or underflow. + # Note that NaN can pass through this, but that will end up fine. + max_exp = T == Float16 ? T(3<<14) : T(0x1.Ap30) + if !(abs(y) -4e18 && throw_exp_domainerror(x) # |y| is small enough that y isn't an integer + x < 0 && throw_exp_domainerror(x) + !isfinite(x) && return x*(y>0 || isnan(x)) + x==0 && return abs(y)*T(Inf)*(!(y>0)) return pow_body(x, y) end @inline function pow_body(x::T, y::T) where T <: Union{Float16, Float32} - x == 1 && return one(T) - !isfinite(x) && return x*(y>0 || isnan(x)) - x==0 && return abs(y)*T(Inf)*(!(y>0)) return T(exp2(log2(abs(widen(x))) * y)) end @@ -1036,14 +1251,14 @@ end @assume_effects :terminates_locally @noinline function pow_body(x::Float64, n::Integer) y = 1.0 xnlo = ynlo = 0.0 + n == 3 && return x*x*x # keep compatibility with literal_pow if n < 0 rx = inv(x) - n==-2 && return rx*rx #keep compatability with literal_pow + n==-2 && return rx*rx #keep compatibility with literal_pow isfinite(x) && (xnlo = -fma(x, rx, -1.) * rx) x = rx n = -n end - n == 3 && return x*x*x # keep compatibility with literal_pow while n > 1 if n&1 > 0 err = muladd(y, xnlo, x*ynlo) @@ -1055,13 +1270,14 @@ end xnlo += err n >>>= 1 end - !isfinite(x) && return x*y - return muladd(x, y, muladd(y, xnlo, x*ynlo)) + err = muladd(y, xnlo, x*ynlo) + return ifelse(isfinite(x) & isfinite(err), muladd(x, y, err), x*y) end function ^(x::Float32, n::Integer) - n < 0 && return inv(x)^(-n) + n == -2 && return (i=inv(x); i*i) n == 3 && return x*x*x #keep compatibility with literal_pow + n < 0 && return Float32(Base.power_by_squaring(inv(Float64(x)),-n)) Float32(Base.power_by_squaring(Float64(x),n)) end @inline ^(x::Float16, y::Integer) = Float16(Float32(x) ^ y) @@ -1126,6 +1342,9 @@ julia> rem2pi(7pi/4, RoundDown) """ function rem2pi end function rem2pi(x::Float64, ::RoundingMode{:Nearest}) + isnan(x) && return x + isinf(x) && return NaN + abs(x) < pi && return x n,y = rem_pio2_kernel(x) @@ -1149,6 +1368,9 @@ function rem2pi(x::Float64, ::RoundingMode{:Nearest}) end end function rem2pi(x::Float64, ::RoundingMode{:ToZero}) + isnan(x) && return x + isinf(x) && return NaN + ax = abs(x) ax <= 2*Float64(pi,RoundDown) && return x @@ -1174,6 +1396,9 @@ function rem2pi(x::Float64, ::RoundingMode{:ToZero}) copysign(z,x) end function rem2pi(x::Float64, ::RoundingMode{:Down}) + isnan(x) && return x + isinf(x) && return NaN + if x < pi4o2_h if x >= 0 return x @@ -1203,6 +1428,9 @@ function rem2pi(x::Float64, ::RoundingMode{:Down}) end end function rem2pi(x::Float64, ::RoundingMode{:Up}) + isnan(x) && return x + isinf(x) && return NaN + if x > -pi4o2_h if x <= 0 return x @@ -1325,7 +1553,7 @@ include("special/log.jl") # Float16 definitions for func in (:sin,:cos,:tan,:asin,:acos,:atan,:cosh,:tanh,:asinh,:acosh, - :atanh,:log,:log2,:log10,:sqrt,:lgamma,:log1p) + :atanh,:log,:log2,:log10,:sqrt,:fourthroot,:log1p) @eval begin $func(a::Float16) = Float16($func(Float32(a))) $func(a::ComplexF16) = ComplexF16($func(ComplexF32(a))) @@ -1361,5 +1589,6 @@ end exp2(x::AbstractFloat) = 2^x exp10(x::AbstractFloat) = 10^x clamp(::Missing, lo, hi) = missing +fourthroot(::Missing) = missing end # module diff --git a/base/mathconstants.jl b/base/mathconstants.jl index 3bb4bb52ad07f..4bb8c409acf00 100644 --- a/base/mathconstants.jl +++ b/base/mathconstants.jl @@ -10,11 +10,11 @@ module MathConstants export π, pi, ℯ, e, γ, eulergamma, catalan, φ, golden -Base.@irrational π 3.14159265358979323846 pi -Base.@irrational ℯ 2.71828182845904523536 exp(big(1)) -Base.@irrational γ 0.57721566490153286061 euler -Base.@irrational φ 1.61803398874989484820 (1+sqrt(big(5)))/2 -Base.@irrational catalan 0.91596559417721901505 catalan +Base.@irrational π pi +Base.@irrational ℯ exp(big(1)) +Base.@irrational γ euler +Base.@irrational φ (1+sqrt(big(5)))/2 +Base.@irrational catalan catalan # aliases """ diff --git a/base/meta.jl b/base/meta.jl index fcf66a7a787b2..b0e0dc371b26c 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -48,7 +48,7 @@ quot(ex) = Expr(:quote, ex) """ Meta.isexpr(ex, head[, n])::Bool -Return true if `ex` is an `Expr` with the given type `head` and optionally that +Return `true` if `ex` is an `Expr` with the given type `head` and optionally that the argument list is of length `n`. `head` may be a `Symbol` or collection of `Symbol`s. For example, to check that a macro was passed a function call expression, you might use `isexpr(ex, :call)`. @@ -74,8 +74,7 @@ julia> Meta.isexpr(ex, :call, 2) true ``` """ -isexpr(@nospecialize(ex), heads) = isa(ex, Expr) && in(ex.head, heads) -isexpr(@nospecialize(ex), heads, n::Int) = isa(ex, Expr) && in(ex.head, heads) && length(ex.args) == n +isexpr """ replace_sourceloc!(location, expr) @@ -97,7 +96,7 @@ rather than line 2 where `@test` is used as an implementation detail. """ function replace_sourceloc!(sourceloc, @nospecialize(ex)) if ex isa Expr - if ex.head == :macrocall + if ex.head === :macrocall ex.args[2] = sourceloc end map!(e -> replace_sourceloc!(sourceloc, e), ex.args, ex.args) diff --git a/base/methodshow.jl b/base/methodshow.jl index ba9911179fd19..0eb99dc88303f 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -7,11 +7,11 @@ function strip_gensym(sym) if sym === :var"#self#" || sym === :var"#unused#" return empty_sym end - return Symbol(replace(String(sym), r"^(.*)#(.*#)?\d+$" => s"\1")) + return Symbol(replace(String(sym), r"^(.*)#(.*#)?\d+$"sa => s"\1")) end function argtype_decl(env, n, @nospecialize(sig::DataType), i::Int, nargs, isva::Bool) # -> (argname, argtype) - t = sig.parameters[unwrapva(min(i, end))] + t = unwrapva(sig.parameters[min(i, end)]) if i == nargs && isva va = sig.parameters[end] if isvarargtype(va) && (!isdefined(va, :N) || !isa(va.N, Int)) @@ -59,8 +59,7 @@ function arg_decl_parts(m::Method, html=false) push!(tv, sig.var) sig = sig.body end - file = m.file - line = m.line + file, line = updated_methodloc(m) argnames = method_argnames(m) if length(argnames) >= m.nargs show_env = ImmutableDict{Symbol, Any}() @@ -79,10 +78,9 @@ end # NOTE: second argument is deprecated and is no longer used function kwarg_decl(m::Method, kwtype = nothing) - mt = get_methodtable(m) - if isdefined(mt, :kwsorter) - kwtype = typeof(mt.kwsorter) - sig = rewrap_unionall(Tuple{kwtype, Any, (unwrap_unionall(m.sig)::DataType).parameters...}, m.sig) + if m.sig !== Tuple # OpaqueClosure or Builtin + kwtype = typeof(Core.kwcall) + sig = rewrap_unionall(Tuple{kwtype, NamedTuple, (unwrap_unionall(m.sig)::DataType).parameters...}, m.sig) kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, get_world_counter()) if kwli !== nothing kwli = kwli::Method @@ -95,6 +93,7 @@ function kwarg_decl(m::Method, kwtype = nothing) push!(kws, kws[i]) deleteat!(kws, i) end + isempty(kws) && push!(kws, :var"...") return kws end end @@ -136,7 +135,9 @@ function fixup_stdlib_path(path::String) STDLIB = Sys.STDLIB::String if BUILD_STDLIB_PATH != STDLIB # BUILD_STDLIB_PATH gets defined in sysinfo.jl - path = replace(path, normpath(BUILD_STDLIB_PATH) => normpath(STDLIB)) + npath = normpath(path) + npath′ = replace(npath, normpath(BUILD_STDLIB_PATH) => normpath(STDLIB)) + return npath == npath′ ? path : npath′ end end return path @@ -160,7 +161,7 @@ functionloc(m::Core.MethodInstance) = functionloc(m.def) """ functionloc(m::Method) -Returns a tuple `(filename,line)` giving the location of a `Method` definition. +Return a tuple `(filename,line)` giving the location of a `Method` definition. """ function functionloc(m::Method) file, ln = updated_methodloc(m) @@ -173,7 +174,7 @@ end """ functionloc(f::Function, types) -Returns a tuple `(filename,line)` giving the location of a generic `Function` definition. +Return a tuple `(filename,line)` giving the location of a generic `Function` definition. """ functionloc(@nospecialize(f), @nospecialize(types)) = functionloc(which(f,types)) @@ -193,6 +194,9 @@ function functionloc(@nospecialize(f)) end function sym_to_string(sym) + if sym === :var"..." + return "..." + end s = String(sym) if endswith(s, "...") return string(sprint(show_sym, Symbol(s[1:end-3])), "...") @@ -201,34 +205,53 @@ function sym_to_string(sym) end end -function show(io::IO, m::Method) +# default compact view +show(io::IO, m::Method; kwargs...) = show_method(IOContext(io, :compact=>true), m; kwargs...) + +show(io::IO, ::MIME"text/plain", m::Method; kwargs...) = show_method(io, m; kwargs...) + +function show_method(io::IO, m::Method; modulecolor = :light_black, digit_align_width = 1) tv, decls, file, line = arg_decl_parts(m) sig = unwrap_unionall(m.sig) if sig === Tuple # Builtin - print(io, m.name, "(...) in ", m.module) - return - end - print(io, decls[1][2], "(") - join( - io, - String[isempty(d[2]) ? d[1] : string(d[1], "::", d[2]) for d in decls[2:end]], - ", ", - ", ", - ) - kwargs = kwarg_decl(m) - if !isempty(kwargs) - print(io, "; ") - join(io, map(sym_to_string, kwargs), ", ", ", ") + print(io, m.name, "(...)") + file = "none" + line = 0 + else + print(io, decls[1][2], "(") + + # arguments + for (i,d) in enumerate(decls[2:end]) + printstyled(io, d[1], color=:light_black) + if !isempty(d[2]) + print(io, "::") + print_type_bicolor(io, d[2], color=:bold, inner_color=:normal) + end + i < length(decls)-1 && print(io, ", ") + end + + kwargs = kwarg_decl(m) + if !isempty(kwargs) + print(io, "; ") + for kw in kwargs + skw = sym_to_string(kw) + print(io, skw) + if kw != last(kwargs) + print(io, ", ") + end + end + end + print(io, ")") + show_method_params(io, tv) end - print(io, ")") - show_method_params(io, tv) - print(io, " in ", m.module) - if line > 0 - file, line = updated_methodloc(m) - print(io, " at ", file, ":", line) + + if !(get(io, :compact, false)::Bool) # single-line mode + println(io) + digit_align_width += 4 end - nothing + # module & file, re-using function from errorshow.jl + print_module_path_file(io, parentmodule(m), string(file), line; modulecolor, digit_align_width) end function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) @@ -244,11 +267,15 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) if hasname what = (startswith(sname, '@') ? "macro" - : mt.module === Core && last(ms).sig === Tuple ? + : mt.module === Core && mt.defs isa Core.TypeMapEntry && (mt.defs.func::Method).sig === Tuple ? "builtin function" : # else "generic function") - print(io, " for ", what, " ", namedisplay) + print(io, " for ", what, " ", namedisplay, " from ") + + col = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, parentmodule_before_main(ms.mt.module)) + + printstyled(io, ms.mt.module, color=col) elseif '#' in sname print(io, " for anonymous function ", namedisplay) elseif mt === _TYPE_NAME.mt @@ -256,8 +283,7 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) else print(io, " for callable object") end - n > 0 && print(io, ":") - nothing + !iszero(n) && print(io, ":") end function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=true) @@ -274,12 +300,29 @@ 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 if max == -1 || n < max n += 1 println(io) - print(io, "[$n] ") - show(io, meth) + + 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) + file, line = updated_methodloc(meth) if last_shown_line_infos !== nothing push!(last_shown_line_infos, (string(file), line)) @@ -292,7 +335,7 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru if rest > 0 println(io) if rest == 1 - show(io, last) + show_method(io, last) else print(io, "... $rest methods not shown") if hasname @@ -318,11 +361,11 @@ end fileurl(file) = let f = find_source_file(file); f === nothing ? "" : "file://"*f; end function url(m::Method) - M = m.module + M = parentmodule(m) (m.file === :null || m.file === :string) && return "" file = string(m.file) line = m.line - line <= 0 || occursin(r"In\[[0-9]+\]", file) && return "" + line <= 0 || occursin(r"In\[[0-9]+\]"a, file) && return "" Sys.iswindows() && (file = replace(file, '\\' => '/')) libgit2_id = PkgId(UUID((0x76f85450_5226_5b5a,0x8eaa_529ad045b433)), "LibGit2") if inbase(M) @@ -339,7 +382,7 @@ function url(m::Method) return LibGit2.with(LibGit2.GitRepoExt(d)) do repo LibGit2.with(LibGit2.GitConfig(repo)) do cfg u = LibGit2.get(cfg, "remote.origin.url", "") - u = match(LibGit2.GITHUB_REGEX,u).captures[1] + u = (match(LibGit2.GITHUB_REGEX,u)::AbstractMatch).captures[1] commit = string(LibGit2.head_oid(repo)) root = LibGit2.path(repo) if startswith(file, root) || startswith(realpath(file), root) @@ -362,14 +405,14 @@ function show(io::IO, ::MIME"text/html", m::Method) sig = unwrap_unionall(m.sig) if sig === Tuple # Builtin - print(io, m.name, "(...) in ", m.module) + print(io, m.name, "(...) in ", parentmodule(m)) return end print(io, decls[1][2], "(") join( io, String[ - isempty(d[2]) ? d[1] : string(d[1], "::", d[2], "") for d in decls[2:end] + isempty(d[2]) ? string(d[1]) : string(d[1], "::", d[2] , "") for d in decls[2:end] ], ", ", ", ", @@ -386,7 +429,7 @@ function show(io::IO, ::MIME"text/html", m::Method) show_method_params(io, tv) print(io,"") end - print(io, " in ", m.module) + print(io, " in ", parentmodule(m)) if line > 0 file, line = updated_methodloc(m) u = url(m) @@ -428,6 +471,8 @@ function show(io::IO, mime::MIME"text/plain", mt::AbstractVector{Method}) push!(last_shown_line_infos, (string(file), line)) end end + first && summary(io, mt) + nothing end function show(io::IO, mime::MIME"text/html", mt::AbstractVector{Method}) @@ -440,4 +485,5 @@ function show(io::IO, mime::MIME"text/html", mt::AbstractVector{Method}) end print(io, "") end + nothing end diff --git a/base/missing.jl b/base/missing.jl index 3176c56772602..4544c2b38c460 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -41,6 +41,7 @@ nonmissingtype(::Type{T}) where {T} = typesplit(T, Missing) function nonmissingtype_checked(T::Type) R = nonmissingtype(T) R >: T && error("could not compute non-missing type") + R <: Union{} && error("cannot convert a value to missing for assignment") return R end @@ -69,7 +70,6 @@ convert(::Type{T}, x::T) where {T>:Union{Missing, Nothing}} = x convert(::Type{T}, x) where {T>:Missing} = convert(nonmissingtype_checked(T), x) convert(::Type{T}, x) where {T>:Union{Missing, Nothing}} = convert(nonmissingtype_checked(nonnothingtype_checked(T)), x) - # Comparison operators ==(::Missing, ::Missing) = missing ==(::Missing, ::Any) = missing @@ -463,4 +463,3 @@ macro coalesce(args...) end return esc(:(let val; $expr; end)) end - diff --git a/base/mpfr.jl b/base/mpfr.jl index e85f281619ac0..ff85fc6155df4 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -8,7 +8,7 @@ export import .Base: *, +, -, /, <, <=, ==, >, >=, ^, ceil, cmp, convert, copysign, div, - inv, exp, exp2, exponent, factorial, floor, fma, hypot, isinteger, + inv, exp, exp2, exponent, factorial, floor, fma, muladd, hypot, isinteger, isfinite, isinf, isnan, ldexp, log, log2, log10, max, min, mod, modf, nextfloat, prevfloat, promote_rule, rem, rem2pi, round, show, float, sum, sqrt, string, print, trunc, precision, _precision, exp10, expm1, log1p, @@ -16,16 +16,26 @@ 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 + isone, big, _string_n, decompose, minmax, + sinpi, cospi, sincospi, tanpi, sind, cosd, tand, asind, acosd, atand import ..Rounding: rounding_raw, setrounding_raw -import ..GMP: ClongMax, CulongMax, CdoubleMax, Limb +import ..GMP: ClongMax, CulongMax, CdoubleMax, Limb, libgmp import ..FastMath.sincos_fast -version() = VersionNumber(unsafe_string(ccall((:mpfr_get_version,:libmpfr), Ptr{Cchar}, ()))) -patches() = split(unsafe_string(ccall((:mpfr_get_patches,:libmpfr), Ptr{Cchar}, ())),' ') +if Sys.iswindows() + const libmpfr = "libmpfr-6.dll" +elseif Sys.isapple() + const libmpfr = "@rpath/libmpfr.6.dylib" +else + const libmpfr = "libmpfr.so.6" +end + + +version() = VersionNumber(unsafe_string(ccall((:mpfr_get_version,libmpfr), Ptr{Cchar}, ()))) +patches() = split(unsafe_string(ccall((:mpfr_get_patches,libmpfr), Ptr{Cchar}, ())),' ') function __init__() try @@ -100,16 +110,16 @@ mutable struct BigFloat <: AbstractFloat global function _BigFloat(prec::Clong, sign::Cint, exp::Clong, d::String) # ccall-based version, inlined below #z = new(zero(Clong), zero(Cint), zero(Clong), C_NULL, d) - #ccall((:mpfr_custom_init,:libmpfr), Cvoid, (Ptr{Limb}, Clong), d, prec) # currently seems to be a no-op in mpfr + #ccall((:mpfr_custom_init,libmpfr), Cvoid, (Ptr{Limb}, Clong), d, prec) # currently seems to be a no-op in mpfr #NAN_KIND = Cint(0) - #ccall((:mpfr_custom_init_set,:libmpfr), Cvoid, (Ref{BigFloat}, Cint, Clong, Ptr{Limb}), z, NAN_KIND, prec, d) + #ccall((:mpfr_custom_init_set,libmpfr), Cvoid, (Ref{BigFloat}, Cint, Clong, Ptr{Limb}), z, NAN_KIND, prec, d) #return z return new(prec, sign, exp, pointer(d), d) end function BigFloat(; precision::Integer=DEFAULT_PRECISION[]) precision < 1 && throw(DomainError(precision, "`precision` cannot be less than 1.")) - nb = ccall((:mpfr_custom_get_size,:libmpfr), Csize_t, (Clong,), precision) + 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 #d = Vector{Limb}(undef, nb) d = _string_n(nb * Core.sizeof(Limb)) @@ -185,7 +195,7 @@ function BigFloat(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::I return x else z = BigFloat(;precision=precision) - ccall((:mpfr_set, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), + ccall((:mpfr_set, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, r) return z end @@ -193,7 +203,7 @@ end function _duplicate(x::BigFloat) z = BigFloat(;precision=_precision(x)) - ccall((:mpfr_set, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, 0) + ccall((:mpfr_set, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, 0) return z end @@ -202,24 +212,58 @@ for (fJ, fC) in ((:si,:Clong), (:ui,:Culong)) @eval begin function BigFloat(x::($fC), r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) z = BigFloat(;precision=precision) - ccall(($(string(:mpfr_set_,fJ)), :libmpfr), Int32, (Ref{BigFloat}, $fC, MPFRRoundingMode), z, x, r) + ccall(($(string(:mpfr_set_,fJ)), libmpfr), Int32, (Ref{BigFloat}, $fC, MPFRRoundingMode), z, x, r) return z end end end function BigFloat(x::Float64, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) - z = BigFloat(;precision=precision) - ccall((:mpfr_set_d, :libmpfr), Int32, (Ref{BigFloat}, Float64, MPFRRoundingMode), z, x, r) - if isnan(x) && signbit(x) != signbit(z) - z.sign = -z.sign + 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. + if precision <= Base.significand_bits(Float64) + ccall((:mpfr_set_d, libmpfr), Int32, (Ref{BigFloat}, Float64, MPFRRoundingMode), z, x, r) + if isnan(x) && signbit(x) != signbit(z) + z.sign = -z.sign + end + return z end - return z + z.sign = 1-2*signbit(x) + if iszero(x) || !isfinite(x) + if isinf(x) + z.exp = Clong(2) - typemax(Clong) + elseif isnan(x) + z.exp = Clong(1) - typemax(Clong) + else + z.exp = - typemax(Clong) + end + return z + end + z.exp = 1 + exponent(x) + # BigFloat doesn't have an implicit bit + val = reinterpret(UInt64, significand(x))<<11 | typemin(Int64) + nlimbs = (precision + 8*Core.sizeof(Limb) - 1) ÷ (8*Core.sizeof(Limb)) + + # Limb is a CLong which is a UInt32 on windows (thank M$) which makes this more complicated and slower. + if Limb === UInt64 + for i in 1:nlimbs-1 + unsafe_store!(z.d, 0x0, i) + end + unsafe_store!(z.d, val, nlimbs) + else + for i in 1:nlimbs-2 + unsafe_store!(z.d, 0x0, i) + end + unsafe_store!(z.d, val % UInt32, nlimbs-1) + unsafe_store!(z.d, (val >> 32) % UInt32, nlimbs) + end + z end function BigFloat(x::BigInt, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) z = BigFloat(;precision=precision) - ccall((:mpfr_set_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, r) + ccall((:mpfr_set_z, libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, r) return z end @@ -247,7 +291,7 @@ end function tryparse(::Type{BigFloat}, s::AbstractString; base::Integer=0, precision::Integer=DEFAULT_PRECISION[], rounding::MPFRRoundingMode=ROUNDING_MODE[]) !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 = ccall((:mpfr_set_str, libmpfr), Int32, (Ref{BigFloat}, Cstring, Int32, MPFRRoundingMode), z, s, base, rounding) err == 0 ? z : nothing end @@ -268,16 +312,16 @@ BigFloat(x::AbstractString, r::RoundingMode; precision::Integer=DEFAULT_PRECISIO _unchecked_cast(T, x::BigFloat, r::RoundingMode) = _unchecked_cast(T, x, convert(MPFRRoundingMode, r)) function _unchecked_cast(::Type{Int64}, x::BigFloat, r::MPFRRoundingMode) - ccall((:__gmpfr_mpfr_get_sj,:libmpfr), Cintmax_t, (Ref{BigFloat}, MPFRRoundingMode), x, r) + ccall((:__gmpfr_mpfr_get_sj,libmpfr), Cintmax_t, (Ref{BigFloat}, MPFRRoundingMode), x, r) end function _unchecked_cast(::Type{UInt64}, x::BigFloat, r::MPFRRoundingMode) - ccall((:__gmpfr_mpfr_get_uj,:libmpfr), Cuintmax_t, (Ref{BigFloat}, MPFRRoundingMode), x, r) + ccall((:__gmpfr_mpfr_get_uj,libmpfr), Cuintmax_t, (Ref{BigFloat}, MPFRRoundingMode), x, r) end function _unchecked_cast(::Type{BigInt}, x::BigFloat, r::MPFRRoundingMode) z = BigInt() - ccall((:mpfr_get_z, :libmpfr), Int32, (Ref{BigInt}, Ref{BigFloat}, MPFRRoundingMode), z, x, r) + ccall((:mpfr_get_z, libmpfr), Int32, (Ref{BigInt}, Ref{BigFloat}, MPFRRoundingMode), z, x, r) return z end @@ -294,7 +338,14 @@ function round(::Type{T}, x::BigFloat, r::Union{RoundingMode, MPFRRoundingMode}) end return unsafe_trunc(T, res) end -round(::Type{BigInt}, x::BigFloat, r::Union{RoundingMode, MPFRRoundingMode}) = _unchecked_cast(BigInt, x, r) + +function round(::Type{BigInt}, x::BigFloat, r::Union{RoundingMode, MPFRRoundingMode}) + clear_flags() + res = _unchecked_cast(BigInt, x, r) + had_range_exception() && throw(InexactError(:round, BigInt, x)) + return res +end + round(::Type{T}, x::BigFloat, r::RoundingMode) where T<:Union{Signed, Unsigned} = invoke(round, Tuple{Type{<:Union{Signed, Unsigned}}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, T, x, r) round(::Type{BigInt}, x::BigFloat, r::RoundingMode) = @@ -331,11 +382,11 @@ end _cpynansgn(x::AbstractFloat, y::BigFloat) = isnan(x) && signbit(x) != signbit(y) ? -x : x Float64(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) = - _cpynansgn(ccall((:mpfr_get_d,:libmpfr), Float64, (Ref{BigFloat}, MPFRRoundingMode), x, r), x) + _cpynansgn(ccall((:mpfr_get_d,libmpfr), Float64, (Ref{BigFloat}, MPFRRoundingMode), x, r), x) Float64(x::BigFloat, r::RoundingMode) = Float64(x, convert(MPFRRoundingMode, r)) Float32(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) = - _cpynansgn(ccall((:mpfr_get_flt,:libmpfr), Float32, (Ref{BigFloat}, MPFRRoundingMode), x, r), x) + _cpynansgn(ccall((:mpfr_get_flt,libmpfr), Float32, (Ref{BigFloat}, MPFRRoundingMode), x, r), x) Float32(x::BigFloat, r::RoundingMode) = Float32(x, convert(MPFRRoundingMode, r)) function Float16(x::BigFloat) :: Float16 @@ -378,14 +429,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_MODE[]) 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_MODE[]) return z end ($fJ)(c::CulongMax, x::BigFloat) = ($fJ)(x,c) @@ -393,7 +444,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_MODE[]) return z end ($fJ)(c::ClongMax, x::BigFloat) = ($fJ)(x,c) @@ -401,7 +452,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_MODE[]) return z end ($fJ)(c::CdoubleMax, x::BigFloat) = ($fJ)(x,c) @@ -409,7 +460,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_MODE[]) return z end ($fJ)(c::BigInt, x::BigFloat) = ($fJ)(x,c) @@ -421,50 +472,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_MODE[]) 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_MODE[]) 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_MODE[]) 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_MODE[]) 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_MODE[]) 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_MODE[]) 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_MODE[]) 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_MODE[]) return z end # no :mpfr_z_div function @@ -473,7 +524,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_MODE[]) return z end @@ -481,66 +532,68 @@ 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_MODE[]) return r end +muladd(x::BigFloat, y::BigFloat, z::BigFloat) = fma(x, y, z) + # div # BigFloat function div(x::BigFloat, y::BigFloat) z = BigFloat() - ccall((:mpfr_div,:libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_div,libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end # Unsigned Int function div(x::BigFloat, c::CulongMax) z = BigFloat() - ccall((:mpfr_div_ui, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_div_ui, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end function div(c::CulongMax, x::BigFloat) z = BigFloat() - ccall((:mpfr_ui_div, :libmpfr), Int32, (Ref{BigFloat}, Culong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_ui_div, libmpfr), Int32, (Ref{BigFloat}, Culong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end # Signed Integer function div(x::BigFloat, c::ClongMax) z = BigFloat() - ccall((:mpfr_div_si, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_div_si, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end function div(c::ClongMax, x::BigFloat) z = BigFloat() - ccall((:mpfr_si_div, :libmpfr), Int32, (Ref{BigFloat}, Clong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_si_div, libmpfr), Int32, (Ref{BigFloat}, Clong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end # Float32/Float64 function div(x::BigFloat, c::CdoubleMax) z = BigFloat() - ccall((:mpfr_div_d, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_div_d, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end function div(c::CdoubleMax, x::BigFloat) z = BigFloat() - ccall((:mpfr_d_div, :libmpfr), Int32, (Ref{BigFloat}, Cdouble, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_d_div, libmpfr), Int32, (Ref{BigFloat}, Cdouble, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end # BigInt function div(x::BigFloat, c::BigInt) z = BigFloat() - ccall((:mpfr_div_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, RoundToZero) - ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) + ccall((:mpfr_div_z, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, RoundToZero) + ccall((:mpfr_trunc, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z) return z end @@ -550,23 +603,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_MODE[]) + ccall(($(string(:mpfr_,fC)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[]) 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_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[]) 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_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[]) return z end end @@ -574,14 +627,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_MODE[]) 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_MODE[]) isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) return z end @@ -590,25 +643,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_MODE[]) 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_MODE[]) 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_MODE[]) 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_MODE[]) return z end @@ -618,7 +671,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_MODE[]) return z end end @@ -626,7 +679,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_MODE[]) return (s, c) end sincos(v::BigFloat) = sincos_fast(v) @@ -634,18 +687,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_MODE[]) 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_MODE[]) 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_MODE[]) return z end ldexp(x::BigFloat, n::ClongMax) = ldexp(x, convert(Clong, n)) @@ -658,70 +711,73 @@ 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_MODE[]) 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_MODE[]) return z end for f in (:log, :log2, :log10) @eval function $f(x::BigFloat) if x < 0 - throw(DomainError(x, string($f, " will only return a complex result if called ", + throw(DomainError(x, string($f, " was called with a negative real argument but ", + "will only return a complex result if called ", "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_MODE[]) return z end end function log1p(x::BigFloat) if x < -1 - throw(DomainError(x, string("log1p will only return a complex result if called ", + throw(DomainError(x, string("log1p was called with a real argument < -1 but ", + "will only return a complex result if called ", "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_MODE[]) return z end -function max(x::BigFloat, y::BigFloat) - isnan(x) && return x - isnan(y) && return y - z = BigFloat() - ccall((:mpfr_max, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) - return z +# For `min`/`max`, general fallback for `AbstractFloat` is good enough. +# Only implement `minmax` and `_extrema_rf` to avoid repeated calls. +function minmax(x::BigFloat, y::BigFloat) + isnan(x) && return x, x + isnan(y) && return y, y + Base.Math._isless(x, y) ? (x, y) : (y, x) end -function min(x::BigFloat, y::BigFloat) - isnan(x) && return x - isnan(y) && return y - z = BigFloat() - ccall((:mpfr_min, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[]) - return z +function Base._extrema_rf(x::NTuple{2,BigFloat}, y::NTuple{2,BigFloat}) + (x1, x2), (y1, y2) = x, y + isnan(x1) && return x + isnan(y1) && return y + z1 = Base.Math._isless(x1, y1) ? x1 : y1 + z2 = Base.Math._isless(x2, y2) ? y2 : x2 + z1, z2 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_MODE[]) 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_MODE[]) 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_MODE[]) return z end @@ -731,49 +787,76 @@ rem2pi(x::BigFloat, r::RoundingMode) = rem(x, 2*BigFloat(pi), r) function sum(arr::AbstractArray{BigFloat}) z = BigFloat(0) for i in arr - ccall((:mpfr_add, :libmpfr), Int32, + ccall((:mpfr_add, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, i, ROUNDING_MODE[]) end return z end # Functions for which NaN results are converted to DomainError, following Base -for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh) +for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh, :sinpi, :cospi, :tanpi) @eval begin 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_MODE[]) isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) return z end end end +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_MODE[]) return z end +# degree functions +for f in (:sin, :cos, :tan) + @eval begin + 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[]) + 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[]) + isnan(z) && throw(DomainError(x, "NaN result for non-NaN input.")) + return z + end + end +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[]) + return z +end + + # Utility functions -==(x::BigFloat, y::BigFloat) = ccall((:mpfr_equal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 -<=(x::BigFloat, y::BigFloat) = ccall((:mpfr_lessequal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 ->=(x::BigFloat, y::BigFloat) = ccall((:mpfr_greaterequal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 -<(x::BigFloat, y::BigFloat) = ccall((:mpfr_less_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 ->(x::BigFloat, y::BigFloat) = ccall((:mpfr_greater_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 +==(x::BigFloat, y::BigFloat) = ccall((:mpfr_equal_p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 +<=(x::BigFloat, y::BigFloat) = ccall((:mpfr_lessequal_p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 +>=(x::BigFloat, y::BigFloat) = ccall((:mpfr_greaterequal_p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 +<(x::BigFloat, y::BigFloat) = ccall((:mpfr_less_p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 +>(x::BigFloat, y::BigFloat) = ccall((:mpfr_greater_p, libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0 function cmp(x::BigFloat, y::BigInt) isnan(x) && return 1 - ccall((:mpfr_cmp_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}), x, y) + ccall((:mpfr_cmp_z, libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}), x, y) end function cmp(x::BigFloat, y::ClongMax) isnan(x) && return 1 - ccall((:mpfr_cmp_si, :libmpfr), Int32, (Ref{BigFloat}, Clong), x, y) + ccall((:mpfr_cmp_si, libmpfr), Int32, (Ref{BigFloat}, Clong), x, y) end function cmp(x::BigFloat, y::CulongMax) isnan(x) && return 1 - ccall((:mpfr_cmp_ui, :libmpfr), Int32, (Ref{BigFloat}, Culong), x, y) + ccall((:mpfr_cmp_ui, libmpfr), Int32, (Ref{BigFloat}, Culong), x, y) end cmp(x::BigFloat, y::Integer) = cmp(x,big(y)) cmp(x::Integer, y::BigFloat) = -cmp(y,x) @@ -781,7 +864,7 @@ cmp(x::Integer, y::BigFloat) = -cmp(y,x) function cmp(x::BigFloat, y::CdoubleMax) isnan(x) && return isnan(y) ? 0 : 1 isnan(y) && return -1 - ccall((:mpfr_cmp_d, :libmpfr), Int32, (Ref{BigFloat}, Cdouble), x, y) + ccall((:mpfr_cmp_d, libmpfr), Int32, (Ref{BigFloat}, Cdouble), x, y) end cmp(x::CdoubleMax, y::BigFloat) = -cmp(y,x) @@ -800,7 +883,7 @@ cmp(x::CdoubleMax, y::BigFloat) = -cmp(y,x) <=(x::BigFloat, y::CdoubleMax) = !isnan(x) && !isnan(y) && cmp(x,y) <= 0 <=(x::CdoubleMax, y::BigFloat) = !isnan(x) && !isnan(y) && cmp(y,x) >= 0 -signbit(x::BigFloat) = ccall((:mpfr_signbit, :libmpfr), Int32, (Ref{BigFloat},), x) != 0 +signbit(x::BigFloat) = ccall((:mpfr_signbit, libmpfr), Int32, (Ref{BigFloat},), x) != 0 function sign(x::BigFloat) c = cmp(x, 0) (c == 0 || isnan(x)) && return x @@ -808,7 +891,7 @@ function sign(x::BigFloat) end function _precision(x::BigFloat) # precision of an object of type BigFloat - return ccall((:mpfr_get_prec, :libmpfr), Clong, (Ref{BigFloat},), x) + return ccall((:mpfr_get_prec, libmpfr), Clong, (Ref{BigFloat},), x) end precision(x::BigFloat; base::Integer=2) = _precision(x, base) @@ -844,7 +927,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_MODE[]) return z end @@ -853,27 +936,27 @@ function exponent(x::BigFloat) throw(DomainError(x, "`x` must be non-zero and finite.")) end # The '- 1' is to make it work as Base.exponent - return ccall((:mpfr_get_exp, :libmpfr), Clong, (Ref{BigFloat},), x) - 1 + return ccall((:mpfr_get_exp, libmpfr), Clong, (Ref{BigFloat},), x) - 1 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_MODE[]) 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_MODE[]) # 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_MODE[]) return z end function isinteger(x::BigFloat) - return ccall((:mpfr_integer_p, :libmpfr), Int32, (Ref{BigFloat},), x) != 0 + return ccall((:mpfr_integer_p, libmpfr), Int32, (Ref{BigFloat},), x) != 0 end for (f,R) in ((:roundeven, :Nearest), @@ -884,18 +967,18 @@ for (f,R) in ((:roundeven, :Nearest), @eval begin function round(x::BigFloat, ::RoundingMode{$(QuoteNode(R))}) z = BigFloat() - ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, x) + ccall(($(string(:mpfr_,f)), libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, x) return z end end end function isinf(x::BigFloat) - return ccall((:mpfr_inf_p, :libmpfr), Int32, (Ref{BigFloat},), x) != 0 + return ccall((:mpfr_inf_p, libmpfr), Int32, (Ref{BigFloat},), x) != 0 end function isnan(x::BigFloat) - return ccall((:mpfr_nan_p, :libmpfr), Int32, (Ref{BigFloat},), x) != 0 + return ccall((:mpfr_nan_p, libmpfr), Int32, (Ref{BigFloat},), x) != 0 end isfinite(x::BigFloat) = !isinf(x) && !isnan(x) @@ -909,7 +992,7 @@ isone(x::BigFloat) = x == Clong(1) function nextfloat!(x::BigFloat, n::Integer=1) signbit(n) && return prevfloat!(x, abs(n)) for i = 1:n - ccall((:mpfr_nextabove, :libmpfr), Int32, (Ref{BigFloat},), x) + ccall((:mpfr_nextabove, libmpfr), Int32, (Ref{BigFloat},), x) end return x end @@ -917,7 +1000,7 @@ end function prevfloat!(x::BigFloat, n::Integer=1) signbit(n) && return nextfloat!(x, abs(n)) for i = 1:n - ccall((:mpfr_nextbelow, :libmpfr), Int32, (Ref{BigFloat},), x) + ccall((:mpfr_nextbelow, libmpfr), Int32, (Ref{BigFloat},), x) end return x end @@ -963,7 +1046,7 @@ setprecision(f::Function, prec::Integer; base::Integer=2) = setprecision(f, BigF function string_mpfr(x::BigFloat, fmt::String) pc = Ref{Ptr{UInt8}}() - n = ccall((:mpfr_asprintf,:libmpfr), Cint, + n = ccall((:mpfr_asprintf,libmpfr), Cint, (Ptr{Ptr{UInt8}}, Ptr{UInt8}, Ref{BigFloat}...), pc, fmt, x) p = pc[] @@ -975,7 +1058,7 @@ function string_mpfr(x::BigFloat, fmt::String) end end str = unsafe_string(p) - ccall((:mpfr_free_str, :libmpfr), Cvoid, (Ptr{UInt8},), p) + ccall((:mpfr_free_str, libmpfr), Cvoid, (Ptr{UInt8},), p) return str end @@ -1011,14 +1094,14 @@ function _string(x::BigFloat, fmt::String)::String isfinite(x) || return string(Float64(x)) _prettify_bigfloat(string_mpfr(x, fmt)) end -_string(x::BigFloat) = _string(x, "%.Re") +_string(x::BigFloat) = _string(x, "%Re") _string(x::BigFloat, k::Integer) = _string(x, "%.$(k)Re") string(b::BigFloat) = _string(b) print(io::IO, b::BigFloat) = print(io, string(b)) function show(io::IO, b::BigFloat) - if get(io, :compact, false) + if get(io, :compact, false)::Bool print(io, _string(b, 5)) else print(io, _string(b)) @@ -1026,17 +1109,17 @@ function show(io::IO, b::BigFloat) end # get/set exponent min/max -get_emax() = ccall((:mpfr_get_emax, :libmpfr), Clong, ()) -get_emax_min() = ccall((:mpfr_get_emax_min, :libmpfr), Clong, ()) -get_emax_max() = ccall((:mpfr_get_emax_max, :libmpfr), Clong, ()) +get_emax() = ccall((:mpfr_get_emax, libmpfr), Clong, ()) +get_emax_min() = ccall((:mpfr_get_emax_min, libmpfr), Clong, ()) +get_emax_max() = ccall((:mpfr_get_emax_max, libmpfr), Clong, ()) -get_emin() = ccall((:mpfr_get_emin, :libmpfr), Clong, ()) -get_emin_min() = ccall((:mpfr_get_emin_min, :libmpfr), Clong, ()) -get_emin_max() = ccall((:mpfr_get_emin_max, :libmpfr), Clong, ()) +get_emin() = ccall((:mpfr_get_emin, libmpfr), Clong, ()) +get_emin_min() = ccall((:mpfr_get_emin_min, libmpfr), Clong, ()) +get_emin_max() = ccall((:mpfr_get_emin_max, libmpfr), Clong, ()) check_exponent_err(ret) = ret == 0 || throw(ArgumentError("Invalid MPFR exponent range")) -set_emax!(x) = check_exponent_err(ccall((:mpfr_set_emax, :libmpfr), Cint, (Clong,), x)) -set_emin!(x) = check_exponent_err(ccall((:mpfr_set_emin, :libmpfr), Cint, (Clong,), x)) +set_emax!(x) = check_exponent_err(ccall((:mpfr_set_emax, libmpfr), Cint, (Clong,), x)) +set_emin!(x) = check_exponent_err(ccall((:mpfr_set_emin, libmpfr), Cint, (Clong,), x)) function Base.deepcopy_internal(x::BigFloat, stackdict::IdDict) get!(stackdict, x) do @@ -1044,7 +1127,7 @@ function Base.deepcopy_internal(x::BigFloat, stackdict::IdDict) d = x._d d′ = GC.@preserve d unsafe_string(pointer(d), sizeof(d)) # creates a definitely-new String y = _BigFloat(x.prec, x.sign, x.exp, d′) - #ccall((:mpfr_custom_move,:libmpfr), Cvoid, (Ref{BigFloat}, Ptr{Limb}), y, d) # unnecessary + #ccall((:mpfr_custom_move,libmpfr), Cvoid, (Ref{BigFloat}, Ptr{Limb}), y, d) # unnecessary return y end end @@ -1056,7 +1139,7 @@ function decompose(x::BigFloat)::Tuple{BigInt, Int, Int} s = BigInt() s.size = cld(x.prec, 8*sizeof(Limb)) # limbs b = s.size * sizeof(Limb) # bytes - ccall((:__gmpz_realloc2, :libgmp), Cvoid, (Ref{BigInt}, Culong), s, 8b) # bits + ccall((:__gmpz_realloc2, libgmp), Cvoid, (Ref{BigInt}, Culong), s, 8b) # bits ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Csize_t), s.d, x.d, b) # bytes s, x.exp - 8b, x.sign end @@ -1067,11 +1150,11 @@ function lerpi(j::Integer, d::Integer, a::BigFloat, b::BigFloat) end # flags -clear_flags() = ccall((:mpfr_clear_flags, :libmpfr), Cvoid, ()) -had_underflow() = ccall((:mpfr_underflow_p, :libmpfr), Cint, ()) != 0 -had_overflow() = ccall((:mpfr_underflow_p, :libmpfr), Cint, ()) != 0 -had_nan() = ccall((:mpfr_nanflag_p, :libmpfr), Cint, ()) != 0 -had_inexact_exception() = ccall((:mpfr_inexflag_p, :libmpfr), Cint, ()) != 0 -had_range_exception() = ccall((:mpfr_erangeflag_p, :libmpfr), Cint, ()) != 0 +clear_flags() = ccall((:mpfr_clear_flags, libmpfr), Cvoid, ()) +had_underflow() = ccall((:mpfr_underflow_p, libmpfr), Cint, ()) != 0 +had_overflow() = ccall((:mpfr_underflow_p, libmpfr), Cint, ()) != 0 +had_nan() = ccall((:mpfr_nanflag_p, libmpfr), Cint, ()) != 0 +had_inexact_exception() = ccall((:mpfr_inexflag_p, libmpfr), Cint, ()) != 0 +had_range_exception() = ccall((:mpfr_erangeflag_p, libmpfr), Cint, ()) != 0 end #module diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 7eb3cd915c3eb..ce1b6c39adb43 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -4,12 +4,13 @@ module IteratorsMD import .Base: eltype, length, size, first, last, in, getindex, setindex!, IndexStyle, min, max, zero, oneunit, isless, eachindex, ndims, IteratorSize, - convert, show, iterate, promote_rule, to_indices, to_index + convert, show, iterate, promote_rule 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, - ReshapedArray, ReshapedArrayLF, OneTo + ReshapedArray, ReshapedArrayLF, OneTo, Fix1 using .Base.Iterators: Reverse, PartitionIterator using .Base: @propagate_inbounds @@ -31,6 +32,10 @@ module IteratorsMD A `CartesianIndex` is sometimes produced by [`eachindex`](@ref), and always when iterating with an explicit [`CartesianIndices`](@ref). + An `I::CartesianIndex` is treated as a "scalar" (not a container) + for `broadcast`. In order to iterate over the components of a + `CartesianIndex`, convert it to a tuple with `Tuple(I)`. + # Examples ```jldoctest julia> A = reshape(Vector(1:16), (2, 2, 2, 2)) @@ -60,6 +65,10 @@ module IteratorsMD julia> A[CartesianIndex((1, 1, 2, 1))] 5 ``` + + !!! compat "Julia 1.10" + Using a `CartesianIndex` as a "scalar" for `broadcast` requires + Julia 1.10; in previous releases, use `Ref(I)`. """ struct CartesianIndex{N} <: AbstractCartesianIndex{N} I::NTuple{N,Int} @@ -75,13 +84,9 @@ module IteratorsMD CartesianIndex{N}() where {N} = CartesianIndex{N}(()) # Un-nest passed CartesianIndexes CartesianIndex(index::Union{Integer, CartesianIndex}...) = CartesianIndex(flatten(index)) - flatten(I::Tuple{}) = I - flatten(I::Tuple{Any}) = I - flatten(I::Tuple{<:CartesianIndex}) = I[1].I - @inline flatten(I) = _flatten(I...) - @inline _flatten() = () - @inline _flatten(i, I...) = (i, _flatten(I...)...) - @inline _flatten(i::CartesianIndex, I...) = (i.I..., _flatten(I...)...) + flatten(::Tuple{}) = () + flatten(I::Tuple{Any}) = Tuple(I[1]) + @inline flatten(I::Tuple) = (Tuple(I[1])..., flatten(tail(I))...) CartesianIndex(index::Tuple{Vararg{Union{Integer, CartesianIndex}}}) = CartesianIndex(index...) show(io::IO, i::CartesianIndex) = (print(io, "CartesianIndex"); show(io, i.I)) @@ -328,7 +333,7 @@ module IteratorsMD convert(Tuple{Vararg{UnitRange{Int}}}, R) convert(::Type{CartesianIndices{N,R}}, inds::CartesianIndices{N}) where {N,R} = - CartesianIndices(convert(R, inds.indices)) + CartesianIndices(convert(R, inds.indices))::CartesianIndices{N,R} # equality Base.:(==)(a::CartesianIndices{N}, b::CartesianIndices{N}) where N = @@ -338,6 +343,7 @@ module IteratorsMD # AbstractArray implementation Base.axes(iter::CartesianIndices{N,R}) where {N,R} = map(Base.axes1, iter.indices) Base.IndexStyle(::Type{CartesianIndices{N,R}}) where {N,R} = IndexCartesian() + Base.has_offset_axes(iter::CartesianIndices) = Base.has_offset_axes(iter.indices...) # getindex for a 0D CartesianIndices is necessary for disambiguation @propagate_inbounds function Base.getindex(iter::CartesianIndices{0,R}) where {R} CartesianIndex() @@ -457,13 +463,11 @@ module IteratorsMD last(iter::CartesianIndices) = CartesianIndex(map(last, iter.indices)) # When used as indices themselves, CartesianIndices can simply become its tuple of ranges - @inline function to_indices(A, inds, I::Tuple{CartesianIndices{N}, Vararg{Any}}) where N - _, indstail = split(inds, Val(N)) - (map(i -> to_index(A, i), I[1].indices)..., to_indices(A, indstail, tail(I))...) - end + _to_indices1(A, inds, I1::CartesianIndices) = map(Fix1(to_index, A), I1.indices) + _cutdim(inds::Tuple, I1::CartesianIndices) = split(inds, Val(ndims(I1)))[2] + # but preserve CartesianIndices{0} as they consume a dimension. - @inline to_indices(A, inds, I::Tuple{CartesianIndices{0},Vararg{Any}}) = - (first(I), to_indices(A, inds, tail(I))...) + _to_indices1(A, inds, I1::CartesianIndices{0}) = (I1,) @inline in(i::CartesianIndex, r::CartesianIndices) = false @inline in(i::CartesianIndex{N}, r::CartesianIndices{N}) where {N} = all(map(in, i.I, r.indices)) @@ -477,9 +481,8 @@ module IteratorsMD simd_inner_length(iter::CartesianIndices, I::CartesianIndex) = Base.length(iter.indices[1]) simd_index(iter::CartesianIndices{0}, ::CartesianIndex, I1::Int) = first(iter) - @propagate_inbounds function simd_index(iter::CartesianIndices, Ilast::CartesianIndex, I1::Int) - CartesianIndex(getindex(iter.indices[1], I1+first(Base.axes1(iter.indices[1]))), Ilast.I...) - end + @propagate_inbounds simd_index(iter::CartesianIndices, Ilast::CartesianIndex, I1::Int) = + CartesianIndex(iter.indices[1][I1+firstindex(iter.indices[1])], Ilast) # Split out the first N elements of a tuple @inline function split(t, V::Val) @@ -506,8 +509,30 @@ module IteratorsMD end # reversed CartesianIndices iteration + @inline function Base._reverse(iter::CartesianIndices, ::Colon) + CartesianIndices(reverse.(iter.indices)) + end + + Base.@constprop :aggressive function Base._reverse(iter::CartesianIndices, dim::Integer) + 1 <= dim <= ndims(iter) || throw(ArgumentError(Base.LazyString("invalid dimension ", dim, " in reverse"))) + ndims(iter) == 1 && return Base._reverse(iter, :) + indices = iter.indices + return CartesianIndices(Base.setindex(indices, reverse(indices[dim]), dim)) + end - Base.reverse(iter::CartesianIndices) = CartesianIndices(reverse.(iter.indices)) + Base.@constprop :aggressive function Base._reverse(iter::CartesianIndices, dims::Tuple{Vararg{Integer}}) + indices = iter.indices + # use `sum` to force const fold + dimrev = ntuple(i -> sum(==(i), dims; init = 0) == 1, Val(length(indices))) + length(dims) == sum(dimrev) || throw(ArgumentError(Base.LazyString("invalid dimensions ", dims, " in reverse"))) + length(dims) == length(indices) && return Base._reverse(iter, :) + indices′ = map((i, f) -> f ? (@noinline reverse(i)) : i, indices, dimrev) + return CartesianIndices(indices′) + end + + # fix ambiguity with array.jl: + Base._reverse(iter::CartesianIndices{1}, dims::Tuple{Integer}) = + Base._reverse(iter, first(dims)) @inline function iterate(r::Reverse{<:CartesianIndices}) iterfirst = last(r.itr) @@ -585,7 +610,7 @@ module IteratorsMD CartesianIndices(intersect.(a.indices, b.indices)) # Views of reshaped CartesianIndices are used for partitions — ensure these are fast - const CartesianPartition{T<:CartesianIndex, P<:CartesianIndices, R<:ReshapedArray{T,1,P}} = SubArray{T,1,R,Tuple{UnitRange{Int}},false} + const CartesianPartition{T<:CartesianIndex, P<:CartesianIndices, R<:ReshapedArray{T,1,P}} = SubArray{T,1,R,<:Tuple{AbstractUnitRange{Int}},false} eltype(::Type{PartitionIterator{T}}) where {T<:ReshapedArrayLF} = SubArray{eltype(T), 1, T, Tuple{UnitRange{Int}}, true} eltype(::Type{PartitionIterator{T}}) where {T<:ReshapedArray} = SubArray{eltype(T), 1, T, Tuple{UnitRange{Int}}, false} Iterators.IteratorEltype(::Type{<:PartitionIterator{T}}) where {T<:ReshapedArray} = Iterators.IteratorEltype(T) @@ -594,7 +619,6 @@ module IteratorsMD eltype(::Type{PartitionIterator{T}}) where {T<:Union{UnitRange, StepRange, StepRangeLen, LinRange}} = T Iterators.IteratorEltype(::Type{<:PartitionIterator{T}}) where {T<:Union{OneTo, UnitRange, StepRange, StepRangeLen, LinRange}} = Iterators.IteratorEltype(T) - @inline function iterate(iter::CartesianPartition) isempty(iter) && return nothing f = first(iter) @@ -610,33 +634,45 @@ module IteratorsMD # In general, the Cartesian Partition might start and stop in the middle of the outer # dimensions — thus the outer range of a CartesianPartition is itself a # CartesianPartition. - t = tail(iter.parent.parent.indices) - ci = CartesianIndices(t) - li = LinearIndices(t) - return @inbounds view(ci, li[tail(iter[1].I)...]:li[tail(iter[end].I)...]) + mi = iter.parent.mi + ci = iter.parent.parent + ax, ax1 = axes(ci), Base.axes1(ci) + subs = Base.ind2sub_rs(ax, mi, first(iter.indices[1])) + vl, fl = Base._sub2ind(tail(ax), tail(subs)...), subs[1] + vr, fr = divrem(last(iter.indices[1]) - 1, mi[end]) .+ (1, first(ax1)) + oci = CartesianIndices(tail(ci.indices)) + # A fake CartesianPartition to reuse the outer iterate fallback + outer = @inbounds view(ReshapedArray(oci, (length(oci),), mi), vl:vr) + init = @inbounds dec(oci[tail(subs)...].I, oci.indices) # real init state + # Use Generator to make inner loop branchless + @inline function skip_len_I(i::Int, I::CartesianIndex) + l = i == 1 ? fl : first(ax1) + r = i == length(outer) ? fr : last(ax1) + l - first(ax1), r - l + 1, I + end + (skip_len_I(i, I) for (i, I) in Iterators.enumerate(Iterators.rest(outer, (init, 0)))) end - function simd_outer_range(iter::CartesianPartition{CartesianIndex{2}}) + @inline function simd_outer_range(iter::CartesianPartition{CartesianIndex{2}}) # But for two-dimensional Partitions the above is just a simple one-dimensional range # over the second dimension; we don't need to worry about non-rectangular staggers in # higher dimensions. - return @inbounds CartesianIndices((iter[1][2]:iter[end][2],)) - end - @inline function simd_inner_length(iter::CartesianPartition, I::CartesianIndex) - inner = iter.parent.parent.indices[1] - @inbounds fi = iter[1].I - @inbounds li = iter[end].I - inner_start = I.I == tail(fi) ? fi[1] : first(inner) - inner_end = I.I == tail(li) ? li[1] : last(inner) - return inner_end - inner_start + 1 - end - @inline function simd_index(iter::CartesianPartition, Ilast::CartesianIndex, I1::Int) - # I1 is the 0-based distance from the first dimension's offest - offset = first(iter.parent.parent.indices[1]) # (this is 1 for 1-based arrays) - # In the first column we need to also add in the iter's starting point (branchlessly) - f = @inbounds iter[1] - startoffset = (Ilast.I == tail(f.I))*(f[1] - 1) - CartesianIndex((I1 + offset + startoffset, Ilast.I...)) + mi = iter.parent.mi + ci = iter.parent.parent + ax, ax1 = axes(ci), Base.axes1(ci) + fl, vl = Base.ind2sub_rs(ax, mi, first(iter.indices[1])) + fr, vr = Base.ind2sub_rs(ax, mi, last(iter.indices[1])) + outer = @inbounds CartesianIndices((ci.indices[2][vl:vr],)) + # Use Generator to make inner loop branchless + @inline function skip_len_I(I::CartesianIndex{1}) + l = I == first(outer) ? fl : first(ax1) + r = I == last(outer) ? fr : last(ax1) + l - first(ax1), r - l + 1, I + end + (skip_len_I(I) for I in outer) end + @inline simd_inner_length(iter::CartesianPartition, (_, len, _)::Tuple{Int,Int,CartesianIndex}) = len + @propagate_inbounds simd_index(iter::CartesianPartition, (skip, _, I)::Tuple{Int,Int,CartesianIndex}, n::Int) = + simd_index(iter.parent.parent, I, n + skip) end # IteratorsMD @@ -706,7 +742,7 @@ checkindex(::Type{Bool}, inds::Tuple, I::CartesianIndices) = all(checkindex.(Boo # combined count of all indices, including CartesianIndex and # AbstractArray{CartesianIndex} -# rather than returning N, it returns an NTuple{N,Bool} so the result is inferrable +# rather than returning N, it returns an NTuple{N,Bool} so the result is inferable @inline index_ndims(i1, I...) = (true, index_ndims(I...)...) @inline function index_ndims(i1::CartesianIndex, I...) (map(Returns(true), i1.I)..., index_ndims(I...)...) @@ -717,7 +753,7 @@ end index_ndims() = () # combined dimensionality of all indices -# rather than returning N, it returns an NTuple{N,Bool} so the result is inferrable +# rather than returning N, it returns an NTuple{N,Bool} so the result is inferable @inline index_dimsum(i1, I...) = (index_dimsum(I...)...,) @inline index_dimsum(::Colon, I...) = (true, index_dimsum(I...)...) @inline index_dimsum(::AbstractArray{Bool}, I...) = (true, index_dimsum(I...)...) @@ -825,20 +861,12 @@ 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 -@inline function to_indices(A, inds, I::Tuple{CartesianIndex{N}, Vararg{Any}}) where N - _, indstail = IteratorsMD.split(inds, Val(N)) - (map(i -> to_index(A, i), I[1].I)..., to_indices(A, indstail, tail(I))...) -end +_to_indices1(A, inds, I1::CartesianIndex) = map(Fix1(to_index, A), I1.I) +_cutdim(inds, I1::CartesianIndex) = IteratorsMD.split(inds, Val(length(I1)))[2] # For arrays of CartesianIndex, we just skip the appropriate number of inds -@inline function to_indices(A, inds, I::Tuple{AbstractArray{CartesianIndex{N}}, Vararg{Any}}) where N - _, indstail = IteratorsMD.split(inds, Val(N)) - (to_index(A, I[1]), to_indices(A, indstail, tail(I))...) -end +_cutdim(inds, I1::AbstractArray{CartesianIndex{N}}) where {N} = IteratorsMD.split(inds, Val(N))[2] # And boolean arrays behave similarly; they also skip their number of dimensions -@inline function to_indices(A, inds, I::Tuple{AbstractArray{Bool, N}, Vararg{Any}}) where N - _, indstail = IteratorsMD.split(inds, Val(N)) - (to_index(A, I[1]), to_indices(A, indstail, tail(I))...) -end +_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]),) @@ -846,12 +874,10 @@ _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` -@inline to_indices(A, inds, I::Tuple{Colon, Vararg{Any}}) = - (uncolon(inds, I), to_indices(A, _maybetail(inds), tail(I))...) +_to_indices1(A, inds, I1::Colon) = (uncolon(inds),) -const CI0 = Union{CartesianIndex{0}, AbstractArray{CartesianIndex{0}}} -uncolon(inds::Tuple{}, I::Tuple{Colon, Vararg{Any}}) = Slice(OneTo(1)) -uncolon(inds::Tuple, I::Tuple{Colon, Vararg{Any}}) = Slice(inds[1]) +uncolon(::Tuple{}) = Slice(OneTo(1)) +uncolon(inds::Tuple) = Slice(inds[1]) ### From abstractarray.jl: Internal multidimensional indexing definitions ### getindex(x::Union{Number,AbstractChar}, ::CartesianIndex{0}) = x @@ -1103,16 +1129,18 @@ function copyto!(dest::AbstractArray{T1,N}, Rdest::CartesianIndices{N}, checkbounds(src, first(Rsrc)) checkbounds(src, last(Rsrc)) src′ = unalias(dest, src) - ΔI = first(Rdest) - first(Rsrc) + CRdest = CartesianIndices(Rdest) + CRsrc = CartesianIndices(Rsrc) + ΔI = first(CRdest) - first(CRsrc) if @generated quote - @nloops $N i (n->Rsrc.indices[n]) begin - @inbounds @nref($N,dest,n->i_n+ΔI[n]) = @nref($N,src′,i) + @nloops $N i (n->CRsrc.indices[n]) begin + @inbounds @nref($N,dest,n->Rdest.indices[n][i_n+ΔI[n]]) = @nref($N,src′,n->Rsrc.indices[n][i_n]) end end else - for I in Rsrc - @inbounds dest[I + ΔI] = src′[I] + for I in CRsrc + @inbounds dest[Rdest[I + ΔI]] = src′[Rsrc[I]] end end dest @@ -1350,7 +1378,7 @@ end # Note: the next two functions rely on the following definition of the conversion to Bool: # convert(::Type{Bool}, x::Real) = x==0 ? false : x==1 ? true : throw(InexactError(...)) -# they're used to pre-emptively check in bulk when possible, which is much faster. +# they're used to preemptively check in bulk when possible, which is much faster. # Also, the functions can be overloaded for custom types T<:Real : # a) in the unlikely eventuality that they use a different logic for Bool conversion # b) to skip the check if not necessary @@ -1536,6 +1564,9 @@ end end end +isassigned(a::AbstractArray, i::CartesianIndex) = isassigned(a, Tuple(i)...) +isassigned(a::AbstractArray, i::Union{Integer, CartesianIndex}...) = isassigned(a, CartesianIndex(i)) + ## permutedims ## Permute array dims ## diff --git a/base/multimedia.jl b/base/multimedia.jl index d15768affd012..e634a19b7d6aa 100644 --- a/base/multimedia.jl +++ b/base/multimedia.jl @@ -57,7 +57,7 @@ print(io::IO, ::MIME{mime}) where {mime} = print(io, mime) """ showable(mime, x) -Returns a boolean value indicating whether or not the object `x` can be written +Return a boolean value indicating whether or not the object `x` can be written as the given `mime` type. (By default, this is determined automatically by the existence of the @@ -104,6 +104,18 @@ for that case. If a type benefits from custom human-readable output though, `show(::IO, ::MIME"text/plain", ::T)` should be defined. For example, the `Day` type uses `1 day` as the output for the `text/plain` MIME type, and `Day(1)` as the output of 2-argument `show`. +# Examples +```jldoctest +julia> struct Day + n::Int + end + +julia> Base.show(io::IO, ::MIME"text/plain", d::Day) = print(io, d.n, " day") + +julia> Day(1) +1 day +``` + Container types generally implement 3-argument `show` by calling `show(io, MIME"text/plain"(), x)` for elements `x`, with `:compact => true` set in an [`IOContext`](@ref) passed as the first argument. """ @@ -113,7 +125,7 @@ show(io::IO, m::AbstractString, x) = show(io, MIME(m), x) """ repr(mime, x; context=nothing) -Returns an `AbstractString` or `Vector{UInt8}` containing the representation of +Return an `AbstractString` or `Vector{UInt8}` containing the representation of `x` in the requested `mime` type, as written by [`show(io, mime, x)`](@ref) (throwing a [`MethodError`](@ref) if no appropriate `show` is available). An `AbstractString` is returned for MIME types with textual representations (such as `"text/html"` or @@ -220,7 +232,7 @@ display(mime::AbstractString, @nospecialize x) = display(MIME(mime), x) displayable(mime) -> Bool displayable(d::AbstractDisplay, mime) -> Bool -Returns a boolean value indicating whether the given `mime` type (string) is displayable by +Return a boolean value indicating whether the given `mime` type (string) is displayable by any of the displays in the current display stack, or specifically by the display `d` in the second variant. """ @@ -232,7 +244,7 @@ displayable(mime::AbstractString) = displayable(MIME(mime)) """ TextDisplay(io::IO) -Returns a `TextDisplay <: AbstractDisplay`, which displays any object as the text/plain MIME type +Return a `TextDisplay <: AbstractDisplay`, which displays any object as the text/plain MIME type (by default), writing the text representation to the given I/O stream. (This is how objects are printed in the Julia REPL.) """ diff --git a/base/multinverses.jl b/base/multinverses.jl index 4342a9a5f5cf7..21d8e53d2ff83 100644 --- a/base/multinverses.jl +++ b/base/multinverses.jl @@ -14,7 +14,7 @@ unsigned(::Type{Int64}) = UInt64 unsigned(::Type{Int128}) = UInt128 unsigned(::Type{T}) where {T<:Unsigned} = T -abstract type MultiplicativeInverse{T} end +abstract type MultiplicativeInverse{T} <: Number end # Computes integer division by a constant using multiply, add, and bitshift. @@ -97,7 +97,6 @@ struct UnsignedMultiplicativeInverse{T<:Unsigned} <: MultiplicativeInverse{T} function UnsignedMultiplicativeInverse{T}(d::T) where T<:Unsigned d == 0 && throw(ArgumentError("cannot compute magic for d == $d")) - u2 = convert(T, 2) add = false signedmin = one(d) << (sizeof(d)*8-1) signedmax = signedmin - one(T) @@ -135,13 +134,33 @@ struct UnsignedMultiplicativeInverse{T<:Unsigned} <: MultiplicativeInverse{T} end UnsignedMultiplicativeInverse(x::Unsigned) = UnsignedMultiplicativeInverse{typeof(x)}(x) +# Returns the higher half of the product a*b +function _mul_high(a::T, b::T) where {T<:Union{Signed, Unsigned}} + ((widen(a)*b) >>> (sizeof(a)*8)) % T +end + +function _mul_high(a::UInt128, b::UInt128) + shift = sizeof(a)*4 + mask = typemax(UInt128) >> shift + a1, a2 = a >>> shift, a & mask + b1, b2 = b >>> shift, b & mask + a1b1, a1b2, a2b1, a2b2 = a1*b1, a1*b2, a2*b1, a2*b2 + carry = ((a1b2 & mask) + (a2b1 & mask) + (a2b2 >>> shift)) >>> shift + a1b1 + (a1b2 >>> shift) + (a2b1 >>> shift) + carry +end +function _mul_high(a::Int128, b::Int128) + shift = sizeof(a)*8 - 1 + t1, t2 = (a >> shift) & b % UInt128, (b >> shift) & a % UInt128 + (_mul_high(a % UInt128, b % UInt128) - t1 - t2) % Int128 +end + function div(a::T, b::SignedMultiplicativeInverse{T}) where T - x = ((widen(a)*b.multiplier) >>> (sizeof(a)*8)) % T + x = _mul_high(a, b.multiplier) x += (a*b.addmul) % T ifelse(abs(b.divisor) == 1, a*b.divisor, (signbit(x) + (x >> b.shift)) % T) end function div(a::T, b::UnsignedMultiplicativeInverse{T}) where T - x = ((widen(a)*b.multiplier) >>> (sizeof(a)*8)) % T + x = _mul_high(a, b.multiplier) x = ifelse(b.add, convert(T, convert(T, (convert(T, a - x) >>> 1)) + x), x) ifelse(b.divisor == 1, a, x >>> b.shift) end diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 050d460f24724..320d068205a3d 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -8,6 +8,12 @@ tuple-like collection of values, where each entry has a unique name, represented [`Symbol`](@ref). Like `Tuple`s, `NamedTuple`s are immutable; neither the names nor the values can be modified in place after construction. +A named tuple can be created as a tuple literal with keys, e.g. `(a=1, b=2)`, +or as a tuple literal with semicolon after the opening parenthesis, e.g. `(; +a=1, b=2)` (this form also accepts programmatically generated names as +described below), or using a `NamedTuple` type as constructor, e.g. +`NamedTuple{(:a, :b)}((1,2))`. + Accessing the value associated with a name in a named tuple can be done using field access syntax, e.g. `x.a`, or using [`getindex`](@ref), e.g. `x[:a]` or `x[(:a, :b)]`. A tuple of the names can be obtained using [`keys`](@ref), and a tuple of the values @@ -51,16 +57,35 @@ julia> collect(pairs(x)) ``` In a similar fashion as to how one can define keyword arguments programmatically, -a named tuple can be created by giving a pair `name::Symbol => value` or splatting -an iterator yielding such pairs after a semicolon inside a tuple literal: +a named tuple can be created by giving pairs `name::Symbol => value` after a +semicolon inside a tuple literal. This and the `name=value` syntax can be mixed: ```jldoctest -julia> (; :a => 1) -(a = 1,) +julia> (; :a => 1, :b => 2, c=3) +(a = 1, b = 2, c = 3) +``` +The name-value pairs can also be provided by splatting a named tuple or any +iterator that yields two-value collections holding each a symbol as first +value: + +```jldoctest julia> keys = (:a, :b, :c); values = (1, 2, 3); -julia> (; zip(keys, values)...) +julia> NamedTuple{keys}(values) +(a = 1, b = 2, c = 3) + +julia> (; (keys .=> values)...) +(a = 1, b = 2, c = 3) + +julia> nt1 = (a=1, b=2); + +julia> nt2 = (c=3, d=4); + +julia> (; nt1..., nt2..., b=20) # the final b overwrites the value from nt1 +(a = 1, b = 20, c = 3, d = 4) + +julia> (; zip(keys, values)...) # zip yields tuples such as (:a, 1) (a = 1, b = 2, c = 3) ``` @@ -99,20 +124,30 @@ end function NamedTuple{names, T}(nt::NamedTuple) where {names, T <: Tuple} if @generated Expr(:new, :(NamedTuple{names, T}), - Any[ :(convert(fieldtype(T, $n), getfield(nt, $(QuoteNode(names[n]))))) for n in 1:length(names) ]...) + Any[ :(let Tn = fieldtype(T, $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)) end end +# Like NamedTuple{names, T} as a constructor, but omits the additional +# `convert` call, when the types are known to match the fields +@eval function _new_NamedTuple(T::Type{NamedTuple{NTN, NTT}} where {NTN, NTT}, args::Tuple) + $(Expr(:splatnew, :T, :args)) +end + function NamedTuple{names}(nt::NamedTuple) where {names} if @generated idx = Int[ fieldindex(nt, names[n]) for n in 1:length(names) ] types = Tuple{(fieldtype(nt, idx[n]) for n in 1:length(idx))...} Expr(:new, :(NamedTuple{names, $types}), Any[ :(getfield(nt, $(idx[n]))) for n in 1:length(idx) ]...) else - types = Tuple{(fieldtype(typeof(nt), names[n]) for n in 1:length(names))...} - NamedTuple{names, types}(map(Fix1(getfield, nt), names)) + length_names = length(names::Tuple) + types = Tuple{(fieldtype(typeof(nt), names[n]) for n in 1:length_names)...} + _new_NamedTuple(NamedTuple{names, types}, map(Fix1(getfield, nt), names)) end end @@ -134,6 +169,7 @@ firstindex(t::NamedTuple) = 1 lastindex(t::NamedTuple) = nfields(t) getindex(t::NamedTuple, i::Int) = getfield(t, i) getindex(t::NamedTuple, i::Symbol) = getfield(t, i) +getindex(t::NamedTuple, ::Colon) = t @inline getindex(t::NamedTuple, idxs::Tuple{Vararg{Symbol}}) = NamedTuple{idxs}(t) @inline getindex(t::NamedTuple, idxs::AbstractVector{Symbol}) = NamedTuple{Tuple(idxs)}(t) indexed_iterate(t::NamedTuple, i::Int, state=1) = (getfield(t, i), i+1) @@ -144,16 +180,25 @@ empty(::NamedTuple) = NamedTuple() prevind(@nospecialize(t::NamedTuple), i::Integer) = Int(i)-1 nextind(@nospecialize(t::NamedTuple), i::Integer) = Int(i)+1 -convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T<:Tuple} = nt -convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt +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 -function convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names}) where {names,T<:Tuple} - NamedTuple{names,T}(T(nt)) +function convert(::Type{NT}, nt::NamedTuple{names}) where {names, T<:Tuple, NT<:NamedTuple{names,T}} + if !@isdefined T + # converting abstract NT to an abstract Tuple type, to a concrete NT1, is not straightforward, so this could just be an error, but we define it anyways + # _tuple_error(NT, nt) + T1 = Tuple{ntuple(i -> fieldtype(NT, i), Val(length(names)))...} + NT1 = NamedTuple{names, T1} + else + T1 = T + NT1 = NT + end + return NT1(T1(nt))::NT1::NT end if nameof(@__MODULE__) === :Base Tuple(nt::NamedTuple) = (nt...,) - (::Type{T})(nt::NamedTuple) where {T <: Tuple} = convert(T, Tuple(nt)) + (::Type{T})(nt::NamedTuple) where {T <: Tuple} = (t = Tuple(nt); t isa T ? t : convert(T, t)::T) end function show(io::IO, t::NamedTuple) @@ -193,6 +238,12 @@ eltype(::Type{T}) where T<:NamedTuple = nteltype(T) nteltype(::Type) = Any nteltype(::Type{NamedTuple{names,T}} where names) where {T} = eltype(T) +keytype(@nospecialize nt::NamedTuple) = keytype(typeof(nt)) +keytype(@nospecialize T::Type{<:NamedTuple}) = Symbol + +valtype(@nospecialize nt::NamedTuple) = valtype(typeof(nt)) +valtype(@nospecialize T::Type{<:NamedTuple}) = eltype(T) + ==(a::NamedTuple{n}, b::NamedTuple{n}) where {n} = Tuple(a) == Tuple(b) ==(a::NamedTuple, b::NamedTuple) = false @@ -218,7 +269,7 @@ function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names NamedTuple{names}(map(f, map(Tuple, (nt, nts...))...)) end -@pure function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) +@assume_effects :total function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) @nospecialize an bn names = Symbol[an...] for n in bn @@ -229,12 +280,25 @@ end (names...,) end -@pure function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) +@assume_effects :total function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) @nospecialize names a b 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(@nospecialize(a::NamedTuple), @nospecialize(b::NamedTuple), + @nospecialize(an::Tuple{Vararg{Symbol}}), @nospecialize(bn::Tuple{Vararg{Symbol}})) + names = merge_names(an, bn) + types = merge_types(names, typeof(a), typeof(b)) + n = length(names) + A = Vector{Any}(undef, n) + for i=1:n + n = names[i] + A[i] = getfield(sym_in(n, bn) ? b : a, n) + end + _new_NamedTuple(NamedTuple{names, types}, (A...,)) +end + """ merge(a::NamedTuple, bs::NamedTuple...) @@ -265,11 +329,9 @@ function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} names = merge_names(an, bn) types = merge_types(names, a, b) vals = Any[ :(getfield($(sym_in(names[n], bn) ? :b : :a), $(QuoteNode(names[n])))) for n in 1:length(names) ] - :( NamedTuple{$names,$types}(($(vals...),)) ) + :( _new_NamedTuple(NamedTuple{$names,$types}, ($(vals...),)) ) else - names = merge_names(an, bn) - types = merge_types(names, typeof(a), typeof(b)) - NamedTuple{names,types}(map(n->getfield(sym_in(n, bn) ? b : a, n), names)) + merge_fallback(a, b, an, bn) end end @@ -313,15 +375,16 @@ function merge(a::NamedTuple, itr) merge(a, NamedTuple{(names...,)}((vals...,))) end -keys(nt::NamedTuple{names}) where {names} = names +keys(nt::NamedTuple{names}) where {names} = names::Tuple{Vararg{Symbol}} values(nt::NamedTuple) = Tuple(nt) haskey(nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) -get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = haskey(nt, key) ? getfield(nt, key) : default -get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = haskey(nt, key) ? getfield(nt, key) : f() -tail(t::NamedTuple{names}) where names = NamedTuple{tail(names)}(t) -front(t::NamedTuple{names}) where names = NamedTuple{front(names)}(t) +get(nt::NamedTuple, key::Union{Integer, Symbol}, default) = isdefined(nt, key) ? getfield(nt, key) : default +get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, key) ? getfield(nt, key) : f() +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))) -@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) +@assume_effects :total function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) @nospecialize an bn names = Symbol[] for n in an @@ -332,8 +395,25 @@ front(t::NamedTuple{names}) where names = NamedTuple{front(names)}(t) (names...,) end +@assume_effects :foldable function diff_types(@nospecialize(a::NamedTuple), @nospecialize(names::Tuple{Vararg{Symbol}})) + 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}})) + names = diff_names(an, bn) + isempty(names) && return (;) + types = diff_types(a, names) + n = length(names) + A = Vector{Any}(undef, n) + for i=1:n + n = names[i] + A[i] = getfield(a, n) + end + _new_NamedTuple(NamedTuple{names, types}, (A...,)) +end + """ - structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn},Type{NamedTuple{bn}}}) where {an,bn} + structdiff(a::NamedTuple, b::Union{NamedTuple,Type{NamedTuple}}) Construct a copy of named tuple `a`, except with fields that exist in `b` removed. `b` can be a named tuple, or a type of the form `NamedTuple{field_names}`. @@ -341,14 +421,13 @@ Construct a copy of named tuple `a`, except with fields that exist in `b` remove function structdiff(a::NamedTuple{an}, b::Union{NamedTuple{bn}, Type{NamedTuple{bn}}}) where {an, bn} if @generated names = diff_names(an, bn) + isempty(names) && return (;) # just a fast pass idx = Int[ fieldindex(a, names[n]) for n in 1:length(names) ] types = Tuple{Any[ fieldtype(a, idx[n]) for n in 1:length(idx) ]...} vals = Any[ :(getfield(a, $(idx[n]))) for n in 1:length(idx) ] - :( NamedTuple{$names,$types}(($(vals...),)) ) + return :( _new_NamedTuple(NamedTuple{$names,$types}, ($(vals...),)) ) else - names = diff_names(an, bn) - types = Tuple{Any[ fieldtype(typeof(a), names[n]) for n in 1:length(names) ]...} - NamedTuple{names,types}(map(Fix1(getfield, a), names)) + return diff_fallback(a, an, bn) end end @@ -386,20 +465,20 @@ This macro gives a more convenient syntax for declaring `NamedTuple` types. It r type with the given keys and types, equivalent to `NamedTuple{(:key1, :key2, ...), Tuple{Type1,Type2,...}}`. If the `::Type` declaration is omitted, it is taken to be `Any`. The `begin ... end` form allows the declarations to be split across multiple lines (similar to a `struct` declaration), but is otherwise -equivalent. +equivalent. The `NamedTuple` macro is used when printing `NamedTuple` types to e.g. the REPL. -For example, the tuple `(a=3.1, b="hello")` has a type `NamedTuple{(:a, :b),Tuple{Float64,String}}`, which +For example, the tuple `(a=3.1, b="hello")` has a type `NamedTuple{(:a, :b), Tuple{Float64, String}}`, which can also be declared via `@NamedTuple` as: ```jldoctest julia> @NamedTuple{a::Float64, b::String} -NamedTuple{(:a, :b), Tuple{Float64, String}} +@NamedTuple{a::Float64, b::String} julia> @NamedTuple begin a::Float64 b::String end -NamedTuple{(:a, :b), Tuple{Float64, String}} +@NamedTuple{a::Float64, b::String} ``` !!! compat "Julia 1.5" @@ -415,3 +494,9 @@ macro NamedTuple(ex) types = [esc(e isa Symbol ? :Any : e.args[2]) for e in decls] return :(NamedTuple{($(vars...),), Tuple{$(types...)}}) end + +@constprop :aggressive function split_rest(t::NamedTuple{names}, n::Int, st...) where {names} + _check_length_split_rest(length(t), n) + names_front, names_last_n = split_rest(names, n, st...) + return NamedTuple{names_front}(t), NamedTuple{names_last_n}(t) +end diff --git a/base/ntuple.jl b/base/ntuple.jl index 6f70b49481223..7391b86154ac4 100644 --- a/base/ntuple.jl +++ b/base/ntuple.jl @@ -33,17 +33,17 @@ end function _ntuple(f::F, n) where F @noinline - (n >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", n))) + (n >= 0) || throw(ArgumentError(LazyString("tuple length should be ≥ 0, got ", n))) ([f(i) for i = 1:n]...,) end function ntupleany(f, n) @noinline - (n >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", n))) + (n >= 0) || throw(ArgumentError(LazyString("tuple length should be ≥ 0, got ", n))) (Any[f(i) for i = 1:n]...,) end -# inferrable ntuple (enough for bootstrapping) +# inferable ntuple (enough for bootstrapping) ntuple(f, ::Val{0}) = () ntuple(f, ::Val{1}) = (@inline; (f(1),)) ntuple(f, ::Val{2}) = (@inline; (f(1), f(2))) @@ -68,12 +68,9 @@ julia> ntuple(i -> 2*i, Val(4)) """ @inline function ntuple(f::F, ::Val{N}) where {F,N} N::Int - (N >= 0) || throw(ArgumentError(string("tuple length should be ≥ 0, got ", N))) + (N >= 0) || throw(ArgumentError(LazyString("tuple length should be ≥ 0, got ", N))) if @generated - quote - @nexprs $N i -> t_i = f(i) - @ncall $N tuple t - end + :(@ntuple $N i -> f(i)) else Tuple(f(i) for i = 1:N) end @@ -82,7 +79,7 @@ end @inline function fill_to_length(t::Tuple, val, ::Val{_N}) where {_N} M = length(t) N = _N::Int - M > N && throw(ArgumentError("input tuple of length $M, requested $N")) + M > N && throw(ArgumentError(LazyString("input tuple of length ", M, ", requested ", N))) if @generated quote (t..., $(fill(:val, (_N::Int) - length(t.parameters))...)) diff --git a/base/number.jl b/base/number.jl index d3bf14d566250..923fc907d4038 100644 --- a/base/number.jl +++ b/base/number.jl @@ -4,7 +4,7 @@ # Numbers are convertible convert(::Type{T}, x::T) where {T<:Number} = x -convert(::Type{T}, x::Number) where {T<:Number} = T(x) +convert(::Type{T}, x::Number) where {T<:Number} = T(x)::T """ isinteger(x) -> Bool @@ -95,12 +95,12 @@ keys(::Number) = OneTo(1) getindex(x::Number) = x function getindex(x::Number, i::Integer) @inline - @boundscheck i == 1 || throw(BoundsError()) + @boundscheck i == 1 || throw(BoundsError(x, i)) x end function getindex(x::Number, I::Integer...) @inline - @boundscheck all(isone, I) || throw(BoundsError()) + @boundscheck all(isone, I) || throw(BoundsError(x, I)) x end get(x::Number, i::Integer, default) = isone(i) ? x : default @@ -115,7 +115,7 @@ copy(x::Number) = x # some code treats numbers as collection-like """ signbit(x) -Returns `true` if the value of the sign of `x` is negative, otherwise `false`. +Return `true` if the value of the sign of `x` is negative, otherwise `false`. See also [`sign`](@ref) and [`copysign`](@ref). @@ -168,12 +168,24 @@ abs(x::Real) = ifelse(signbit(x), -x, x) Squared absolute value of `x`. +This can be faster than `abs(x)^2`, especially for complex +numbers where `abs(x)` requires a square root via [`hypot`](@ref). + +See also [`abs`](@ref), [`conj`](@ref), [`real`](@ref). + # Examples ```jldoctest julia> abs2(-3) 9 + +julia> abs2(3.0 + 4.0im) +25.0 + +julia> sum(abs2, [1+2im, 3+4im]) # LinearAlgebra.norm(x)^2 +30 ``` """ +abs2(x::Number) = abs(x)^2 abs2(x::Real) = x*x """ @@ -295,6 +307,7 @@ julia> zero(rand(2,2)) """ zero(x::Number) = oftype(x,0) zero(::Type{T}) where {T<:Number} = convert(T,0) +zero(::Type{Union{}}, slurp...) = Union{}(0) """ one(x) @@ -333,6 +346,7 @@ julia> import Dates; one(Dates.Day(1)) """ one(::Type{T}) where {T<:Number} = convert(T,1) one(x::T) where {T<:Number} = one(T) +one(::Type{Union{}}, slurp...) = Union{}(1) # note that convert(T, 1) should throw an error if T is dimensionful, # so this fallback definition should be okay. @@ -340,7 +354,7 @@ one(x::T) where {T<:Number} = one(T) oneunit(x::T) oneunit(T::Type) -Returns `T(one(x))`, where `T` is either the type of the argument or +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) while `oneunit` is dimensionful (of the same type as `x`, or of type `T`). @@ -356,6 +370,7 @@ julia> import Dates; oneunit(Dates.Day) """ oneunit(x::T) where {T} = T(one(x)) oneunit(::Type{T}) where {T} = T(one(T)) +oneunit(::Type{Union{}}, slurp...) = Union{}(1) """ big(T::Type) @@ -376,3 +391,4 @@ Complex{BigInt} ``` """ big(::Type{T}) where {T<:Number} = typeof(big(zero(T))) +big(::Type{Union{}}, slurp...) = Union{}(0) diff --git a/base/opaque_closure.jl b/base/opaque_closure.jl index 71f1d0cb4eb6a..bb0ae8935b06c 100644 --- a/base/opaque_closure.jl +++ b/base/opaque_closure.jl @@ -24,3 +24,71 @@ end macro opaque(ty, ex) esc(Expr(:opaque_closure, ty, ex)) end + +# OpaqueClosure construction from pre-inferred CodeInfo/IRCode +using Core.Compiler: IRCode +using Core: CodeInfo + +function compute_ir_rettype(ir::IRCode) + rt = Union{} + for i = 1:length(ir.stmts) + stmt = ir.stmts[i][:inst] + if isa(stmt, Core.Compiler.ReturnNode) && isdefined(stmt, :val) + rt = Core.Compiler.tmerge(Core.Compiler.argextype(stmt.val, ir), rt) + end + end + return Core.Compiler.widenconst(rt) +end + +function compute_oc_signature(ir::IRCode, nargs::Int, isva::Bool) + argtypes = Vector{Any}(undef, nargs) + for i = 1:nargs + argtypes[i] = Core.Compiler.widenconst(ir.argtypes[i+1]) + end + if isva + lastarg = pop!(argtypes) + if lastarg <: Tuple + append!(argtypes, lastarg.parameters) + else + push!(argtypes, Vararg{Any}) + end + end + return Tuple{argtypes...} +end + +function Core.OpaqueClosure(ir::IRCode, @nospecialize env...; + isva::Bool = false, + do_compile::Bool = true) + # NOTE: we need ir.argtypes[1] == typeof(env) + ir = Core.Compiler.copy(ir) + nargs = length(ir.argtypes)-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)) + 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) +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...) +end + +function generate_opaque_closure(@nospecialize(sig), @nospecialize(rt_lb), @nospecialize(rt_ub), + src::CodeInfo, nargs::Int, isva::Bool, @nospecialize env...; + 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) +end diff --git a/base/operators.jl b/base/operators.jl index 9949f60bd597c..5893c5944a3a0 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -40,15 +40,8 @@ julia> supertype(Int32) Signed ``` """ -function supertype(T::DataType) - @_pure_meta - T.super -end - -function supertype(T::UnionAll) - @_pure_meta - UnionAll(T.var, supertype(T.body)) -end +supertype(T::DataType) = (@_total_meta; T.super) +supertype(T::UnionAll) = (@_total_meta; UnionAll(T.var, supertype(T.body))) ## generic comparison ## @@ -185,6 +178,13 @@ isless(x::AbstractFloat, y::AbstractFloat) = (!isnan(x) & (isnan(y) | signless(x isless(x::Real, y::AbstractFloat) = (!isnan(x) & (isnan(y) | signless(x, y))) | (x < y) isless(x::AbstractFloat, y::Real ) = (!isnan(x) & (isnan(y) | signless(x, y))) | (x < y) +# Performance optimization to reduce branching +# This is useful for sorting tuples of integers +# TODO: remove this when the compiler can optimize the generic version better +# See #48724 and #48753 +isless(a::Tuple{BitInteger, BitInteger}, b::Tuple{BitInteger, BitInteger}) = + isless(a[1], b[1]) | (isequal(a[1], b[1]) & isless(a[2], b[2])) + """ isgreater(x, y) @@ -192,7 +192,7 @@ Not the inverse of `isless`! Test whether `x` is greater than `y`, according to a fixed total order compatible with `min`. Defined with `isless`, this function is usually `isless(y, x)`, but `NaN` and -[`missing`](@ref) are ordered as smaller than any ordinary value with `missing` +[`missing`](@ref) are ordered as smaller than any regular value with `missing` smaller than `NaN`. So `isless` defines an ascending total order with `NaN` and `missing` as the @@ -247,14 +247,8 @@ isunordered(x) = false isunordered(x::AbstractFloat) = isnan(x) isunordered(x::Missing) = true -function ==(T::Type, S::Type) - @_pure_meta - return ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0 -end -function !=(T::Type, S::Type) - @_pure_meta - return !(T == S) -end +==(T::Type, S::Type) = (@_total_meta; ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0) +!=(T::Type, S::Type) = (@_total_meta; !(T == S)) ==(T::TypeVar, S::Type) = false ==(T::Type, S::TypeVar) = false @@ -337,7 +331,7 @@ Because of the behavior of floating-point NaN values, this operator implements a partial order. # Implementation -New numeric types with a canonical partial order should implement this function for +New types with a canonical partial order should implement this function for two arguments of the new type. Types with a canonical total order should implement [`isless`](@ref) instead. @@ -523,7 +517,7 @@ julia> identity("Well, what did you expect?") "Well, what did you expect?" ``` """ -identity(x) = x +identity(@nospecialize x) = x +(x::Number) = x *(x::Number) = x @@ -581,7 +575,7 @@ function afoldl(op, a, bs...) end return y end -typeof(afoldl).name.mt.max_args = 34 +setfield!(typeof(afoldl).name.mt, :max_args, 34, :monotonic) for op in (:+, :*, :&, :|, :xor, :min, :max, :kron) @eval begin @@ -671,9 +665,9 @@ end >>(x, n) Right bit shift operator, `x >> n`. For `n >= 0`, the result is `x` shifted -right by `n` bits, where `n >= 0`, filling with `0`s if `x >= 0`, `1`s if `x < -0`, preserving the sign of `x`. This is equivalent to `fld(x, 2^n)`. For `n < -0`, this is equivalent to `x << -n`. +right by `n` bits, filling with `0`s if `x >= 0`, `1`s if `x < 0`, preserving +the sign of `x`. This is equivalent to `fld(x, 2^n)`. For `n < 0`, this is +equivalent to `x << -n`. # Examples ```jldoctest @@ -712,8 +706,8 @@ end >>>(x, n) Unsigned right bit shift operator, `x >>> n`. For `n >= 0`, the result is `x` -shifted right by `n` bits, where `n >= 0`, filling with `0`s. For `n < 0`, this -is equivalent to `x << -n`. +shifted right by `n` bits, filling with `0`s. For `n < 0`, this is equivalent +to `x << -n`. For [`Unsigned`](@ref) integer types, this is equivalent to [`>>`](@ref). For [`Signed`](@ref) integer types, this is equivalent to `signed(unsigned(x) >> n)`. @@ -855,7 +849,7 @@ julia> x == (fld1(x, y) - 1) * y + mod1(x, y) true ``` """ -fld1(x::T, y::T) where {T<:Real} = (m = mod1(x, y); fld(x + y - m, y)) +fld1(x::T, y::T) where {T<:Real} = (m = mod1(x, y); fld((x - m) + y, y)) function fld1(x::T, y::T) where T<:Integer d = div(x, y) return d + (!signbit(x ⊻ y) & (d * y != x)) @@ -894,22 +888,35 @@ julia> widen(1.5f0) """ widen(x::T) where {T} = convert(widen(T), x) widen(x::Type{T}) where {T} = throw(MethodError(widen, (T,))) +widen(x::Type{Union{}}, slurp...) = throw(MethodError(widen, (Union{},))) # function pipelining """ |>(x, f) -Applies a function to the preceding argument. This allows for easy function chaining. +Infix operator which applies function `f` to the argument `x`. +This allows `f(g(x))` to be written `x |> g |> f`. +When used with anonymous functions, parentheses are typically required around +the definition to get the intended chain. # Examples ```jldoctest -julia> [1:5;] |> (x->x.^2) |> sum |> inv -0.01818181818181818 +julia> 4 |> inv +0.25 + +julia> [2, 3, 5] |> sum |> inv +0.1 + +julia> [0 1; 2 3] .|> (x -> x^2) |> sum +14 ``` """ |>(x, f) = f(x) +_stable_typeof(x) = typeof(x) +_stable_typeof(::Type{T}) where {T} = @isdefined(T) ? Type{T} : DataType + """ f = Returns(value) @@ -936,16 +943,11 @@ julia> f.value struct Returns{V} <: Function value::V Returns{V}(value) where {V} = new{V}(value) - Returns(value) = new{Core.Typeof(value)}(value) + Returns(value) = new{_stable_typeof(value)}(value) end -(obj::Returns)(args...; kw...) = obj.value -function show(io::IO, obj::Returns) - show(io, typeof(obj)) - print(io, "(") - show(io, obj.value) - print(io, ")") -end +(obj::Returns)(@nospecialize(args...); @nospecialize(kw...)) = obj.value + # function composition """ @@ -957,6 +959,7 @@ entered in the Julia REPL (and most editors, appropriately configured) by typing Function composition also works in prefix form: `∘(f, g)` is the same as `f ∘ g`. The prefix form supports composition of multiple functions: `∘(f, g, h) = f ∘ g ∘ h` and splatting `∘(fs...)` for composing an iterable collection of functions. +The last argument to `∘` execute first. !!! compat "Julia 1.4" Multiple function composition requires at least Julia 1.4. @@ -975,15 +978,21 @@ julia> map(uppercase∘first, ["apple", "banana", "carrot"]) 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase) 'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase) +julia> (==(6)∘length).(["apple", "banana", "carrot"]) +3-element BitVector: + 0 + 1 + 1 + julia> fs = [ x -> 2x - x -> x/2 x -> x-1 + x -> x/2 x -> x+1 ]; julia> ∘(fs...)(3) -3.0 +2.0 ``` See also [`ComposedFunction`](@ref), [`!f::Function`](@ref). """ @@ -996,7 +1005,7 @@ Represents the composition of two callable objects `outer::Outer` and `inner::In ```julia ComposedFunction(outer, inner)(args...; kw...) === outer(inner(args...; kw...)) ``` -The preferred way to construct instance of `ComposedFunction` is to use the composition operator [`∘`](@ref): +The preferred way to construct an instance of `ComposedFunction` is to use the composition operator [`∘`](@ref): ```jldoctest julia> sin ∘ cos === ComposedFunction(sin, cos) true @@ -1027,23 +1036,45 @@ struct ComposedFunction{O,I} <: Function ComposedFunction(outer, inner) = new{Core.Typeof(outer),Core.Typeof(inner)}(outer, inner) end -(c::ComposedFunction)(x...; kw...) = c.outer(c.inner(x...; kw...)) +(c::ComposedFunction)(x...; kw...) = call_composed(unwrap_composed(c), x, kw) +unwrap_composed(c::ComposedFunction) = (unwrap_composed(c.outer)..., unwrap_composed(c.inner)...) +unwrap_composed(c) = (maybeconstructor(c),) +call_composed(fs, x, kw) = (@inline; fs[1](call_composed(tail(fs), x, kw))) +call_composed(fs::Tuple{Any}, x, kw) = fs[1](x...; kw...) + +struct Constructor{F} <: Function end +(::Constructor{F})(args...; kw...) where {F} = (@inline; F(args...; kw...)) +maybeconstructor(::Type{F}) where {F} = Constructor{F}() +maybeconstructor(f) = f ∘(f) = f ∘(f, g) = ComposedFunction(f, g) ∘(f, g, h...) = ∘(f ∘ g, h...) function show(io::IO, c::ComposedFunction) - show(io, c.outer) + c.outer isa ComposedFunction ? show(io, c.outer) : _showcomposed(io, c.outer) print(io, " ∘ ") - show(io, c.inner) + _showcomposed(io, c.inner) +end + +#shows !f instead of (!) ∘ f when ! is the outermost function +function show(io::IO, c::ComposedFunction{typeof(!)}) + print(io, '!') + _showcomposed(io, c.inner) end +_showcomposed(io::IO, x) = show(io, x) +#display operators like + and - inside parens +_showcomposed(io::IO, f::Function) = isoperator(Symbol(f)) ? (print(io, '('); show(io, f); print(io, ')')) : show(io, f) +#nesting for chained composition +_showcomposed(io::IO, f::ComposedFunction) = (print(io, '('); show(io, f); print(io, ')')) +#no nesting when ! is the outer function in a composition chain +_showcomposed(io::IO, f::ComposedFunction{typeof(!)}) = show(io, f) + """ !f::Function -Predicate function negation: when the argument of `!` is a function, it returns a -function which computes the boolean negation of `f`. +Predicate function negation: when the argument of `!` is a function, it returns a composed function which computes the boolean negation of `f`. See also [`∘`](@ref). @@ -1058,8 +1089,12 @@ julia> filter(isletter, str) julia> filter(!isletter, str) "∀ > 0, ∃ > 0: |-| < ⇒ |()-()| < " ``` + +!!! compat "Julia 1.9" + Starting with Julia 1.9, `!f` returns a [`ComposedFunction`](@ref) instead of an anonymous function. """ -!(f::Function) = (x...)->!f(x...) +!(f::Function) = (!) ∘ f +!(f::ComposedFunction{typeof(!)}) = f.inner #allows !!f === f """ Fix1(f, x) @@ -1074,8 +1109,8 @@ struct Fix1{F,T} <: Function f::F x::T - Fix1(f::F, x::T) where {F,T} = new{F,T}(f, x) - Fix1(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x) + Fix1(f::F, x) where {F} = new{F,_stable_typeof(x)}(f, x) + Fix1(f::Type{F}, x) where {F} = new{Type{F},_stable_typeof(x)}(f, x) end (f::Fix1)(y) = f.f(f.x, y) @@ -1091,8 +1126,8 @@ struct Fix2{F,T} <: Function f::F x::T - Fix2(f::F, x::T) where {F,T} = new{F,T}(f, x) - Fix2(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x) + Fix2(f::F, x) where {F} = new{F,_stable_typeof(x)}(f, x) + Fix2(f::Type{F}, x) where {F} = new{Type{F},_stable_typeof(x)}(f, x) end (f::Fix2)(y) = f.f(y, f.x) @@ -1187,25 +1222,53 @@ used to implement specialized methods. """ splat(f) -Defined as +Equivalent to ```julia - splat(f) = args->f(args...) + my_splat(f) = args->f(args...) ``` i.e. given a function returns a new function that takes one argument and splats -its argument 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. +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: ```jldoctest -julia> map(Base.splat(+), zip(1:3,4:6)) +julia> map(splat(+), zip(1:3,4:6)) 3-element Vector{Int64}: 5 7 9 + +julia> my_add = splat(+) +splat(+) + +julia> my_add((1,2,3)) +6 +``` +""" +splat(f) = Splat(f) + +""" + Base.Splat{F} <: Function + +Represents a splatted function. That is +```julia +Base.Splat(f)(args) === f(args...) ``` +The preferred way to construct an instance of `Base.Splat` is to use the [`splat`](@ref) function. + +!!! compat "Julia 1.9" + Splat requires at least Julia 1.9. In earlier versions `splat` returns an anonymous function instead. + +See also [`splat`](@ref). """ -splat(f) = args->f(args...) +struct Splat{F} <: Function + f::F + Splat(f) = new{Core.Typeof(f)}(f) +end +(s::Splat)(args) = s.f(args...) +print(io::IO, s::Splat) = print(io, "splat(", s.f, ')') +show(io::IO, s::Splat) = print(io, s) ## in and related operators @@ -1267,20 +1330,19 @@ 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. -Returns a `Bool` value, except if `item` is [`missing`](@ref) or `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 -[`isequal`](@ref). To test for the presence of a key in a dictionary, -use [`haskey`](@ref) or `k in keys(dict)`. For these collections, the result -is always a `Bool` and never `missing`. +[`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 +[`isequal`](@ref). -To determine whether an item is not in a given collection, see [`:∉`](@ref). -You may also negate the `in` by doing `!(a in b)` which is logically similar to "not in". +To test for the presence of a key in a dictionary, use [`haskey`](@ref) +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. @@ -1290,6 +1352,8 @@ corresponding position in `collection`. To get a vector indicating whether each in `items` is in `collection`, wrap `collection` in a tuple or a `Ref` like this: `in.(items, Ref(collection))` or `items .∈ Ref(collection)`. +See also: [`∉`](@ref). + # Examples ```jldoctest julia> a = 1:3:20 @@ -1313,11 +1377,8 @@ true julia> missing in Set([1, 2]) false -julia> !(21 in a) -true - -julia> !(19 in a) -false +julia> (1=>missing) in Dict(1=>10, 2=>20) +missing julia> [1, 2] .∈ [2, 3] 2-element BitVector: diff --git a/base/options.jl b/base/options.jl index 2af8337673b93..23a3dbc802b5f 100644 --- a/base/options.jl +++ b/base/options.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# NOTE: This type needs to be kept in sync with jl_options in src/julia.h +# NOTE: This type needs to be kept in sync with jl_options in src/jloptions.h struct JLOptions quiet::Int8 banner::Int8 @@ -9,7 +9,10 @@ struct JLOptions commands::Ptr{Ptr{UInt8}} # (e)eval, (E)print, (L)load image_file::Ptr{UInt8} cpu_target::Ptr{UInt8} - nthreads::Int32 + nthreadpools::Int16 + nthreads::Int16 + ngcthreads::Int16 + nthreads_per_pool::Ptr{Int16} nprocs::Int32 machine_file::Ptr{UInt8} project::Ptr{UInt8} @@ -20,6 +23,7 @@ struct JLOptions compile_enabled::Int8 code_coverage::Int8 malloc_log::Int8 + tracked_path::Ptr{UInt8} opt_level::Int8 opt_level_min::Int8 debug_level::Int8 @@ -35,6 +39,7 @@ struct JLOptions handle_signals::Int8 use_sysimage_native_code::Int8 use_compiled_modules::Int8 + use_pkgimages::Int8 bindto::Ptr{UInt8} outputbc::Ptr{UInt8} outputunoptbc::Ptr{UInt8} @@ -49,6 +54,7 @@ struct JLOptions rr_detach::Int8 strip_metadata::Int8 strip_ir::Int8 + heap_size_hint::UInt64 end # This runs early in the sysimage != is not defined yet @@ -88,3 +94,7 @@ function unsafe_load_commands(v::Ptr{Ptr{UInt8}}) end return cmds end + +function is_file_tracked(file::Symbol) + return ccall(:jl_is_file_tracked, Cint, (Any,), file) == 1 +end diff --git a/base/ordering.jl b/base/ordering.jl index e49102159c962..d0c9cb99f9c72 100644 --- a/base/ordering.jl +++ b/base/ordering.jl @@ -122,7 +122,7 @@ lt(o::Lt, a, b) = o.lt(a,b) @propagate_inbounds function lt(p::Perm, a::Integer, b::Integer) da = p.data[a] db = p.data[b] - lt(p.order, da, db) | (!lt(p.order, db, da) & (a < b)) + (lt(p.order, da, db)::Bool) | (!(lt(p.order, db, da)::Bool) & (a < b)) end _ord(lt::typeof(isless), by::typeof(identity), order::Ordering) = order diff --git a/base/pair.jl b/base/pair.jl index b5dffbb4e7e86..f34ebb89c80da 100644 --- a/base/pair.jl +++ b/base/pair.jl @@ -28,6 +28,11 @@ julia> for x in p end foo 7 + +julia> replace.(["xops", "oxps"], "x" => "o") +2-element Vector{String}: + "oops" + "oops" ``` """ Pair, => @@ -55,7 +60,11 @@ last(p::Pair) = p.second convert(::Type{Pair{A,B}}, x::Pair{A,B}) where {A,B} = x function convert(::Type{Pair{A,B}}, x::Pair) where {A,B} - Pair{A,B}(convert(A, x[1]), convert(B, x[2])) + a = getfield(x, :first) + a isa A || (a = convert(A, a)) + b = getfield(x, :second) + b isa B || (b = convert(B, b)) + return Pair{A,B}(a, b)::Pair{A,B} end promote_rule(::Type{Pair{A1,B1}}, ::Type{Pair{A2,B2}}) where {A1,B1,A2,B2} = diff --git a/base/parse.jl b/base/parse.jl index 1c911c96e1479..d800e54258b0d 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -36,6 +36,7 @@ julia> parse(Complex{Float64}, "3.2e-1 + 4.5im") ``` """ parse(T::Type, str; base = Int) +parse(::Type{Union{}}, slurp...; kwargs...) = error("cannot parse a value as Union{}") function parse(::Type{T}, c::AbstractChar; base::Integer = 10) where T<:Integer a::Int = (base <= 36 ? 10 : 36) @@ -89,17 +90,22 @@ function parseint_preamble(signed::Bool, base::Int, s::AbstractString, startpos: return sgn, base, j end -@inline function __convert_digit(_c::UInt32, base) +# '0':'9' -> 0:9 +# 'A':'Z' -> 10:35 +# 'a':'z' -> 10:35 if base <= 36, 36:61 otherwise +# input outside of that is mapped to base +@inline function __convert_digit(_c::UInt32, base::UInt32) _0 = UInt32('0') _9 = UInt32('9') _A = UInt32('A') _a = UInt32('a') _Z = UInt32('Z') _z = UInt32('z') - a::UInt32 = base <= 36 ? 10 : 36 + a = base <= 36 ? UInt32(10) : UInt32(36) # converting here instead of via a type assertion prevents typeassert related errors d = _0 <= _c <= _9 ? _c-_0 : _A <= _c <= _Z ? _c-_A+ UInt32(10) : - _a <= _c <= _z ? _c-_a+a : UInt32(base) + _a <= _c <= _z ? _c-_a+a : + base end @@ -110,7 +116,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: return nothing end if !(2 <= base <= 62) - raise && throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base")) + raise && throw(ArgumentError(LazyString("invalid base: base must be 2 ≤ base ≤ 62, got ", base))) return nothing end if i == 0 @@ -132,7 +138,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: while n <= m # Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80 _c = reinterpret(UInt32, c) >> 24 - d::T = __convert_digit(_c, base) + d::T = __convert_digit(_c, base % UInt32) # we know 2 <= base <= 62, so prevent an incorrect InexactError here if d >= base raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))")) return nothing @@ -150,7 +156,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: while !isspace(c) # Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80 _c = reinterpret(UInt32, c) >> 24 - d::T = __convert_digit(_c, base) + d::T = __convert_digit(_c, base % UInt32) # we know 2 <= base <= 62 if d >= base raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))")) return nothing @@ -176,7 +182,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos:: return n end -function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString{String}}, +function tryparse_internal(::Type{Bool}, sbuff::AbstractString, startpos::Int, endpos::Int, base::Integer, raise::Bool) if isempty(sbuff) raise && throw(ArgumentError("input string is empty")) @@ -202,10 +208,15 @@ function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString{String}}, end len = endpos - startpos + 1 - p = pointer(sbuff) + startpos - 1 - GC.@preserve sbuff begin - (len == 4) && (0 == _memcmp(p, "true", 4)) && (return true) - (len == 5) && (0 == _memcmp(p, "false", 5)) && (return false) + if sbuff isa Union{String, SubString{String}} + p = pointer(sbuff) + startpos - 1 + GC.@preserve sbuff begin + (len == 4) && (0 == _memcmp(p, "true", 4)) && (return true) + (len == 5) && (0 == _memcmp(p, "false", 5)) && (return false) + end + else + (len == 4) && (SubString(sbuff, startpos:startpos+3) == "true") && (return true) + (len == 5) && (SubString(sbuff, startpos:startpos+4) == "false") && (return false) end if raise @@ -241,6 +252,7 @@ function parse(::Type{T}, s::AbstractString; base::Union{Nothing,Integer} = noth convert(T, tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), true)) end +tryparse(::Type{Union{}}, slurp...; kwargs...) = error("cannot parse a value as Union{}") ## string to float functions ## diff --git a/base/partr.jl b/base/partr.jl new file mode 100644 index 0000000000000..a02272ceab202 --- /dev/null +++ b/base/partr.jl @@ -0,0 +1,193 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module Partr + +using ..Threads: SpinLock, maxthreadid, threadid + +# a task minheap +mutable struct taskheap + const lock::SpinLock + const tasks::Vector{Task} + @atomic ntasks::Int32 + @atomic priority::UInt16 + taskheap() = new(SpinLock(), Vector{Task}(undef, 256), zero(Int32), typemax(UInt16)) +end + + +# multiqueue minheap state +const heap_d = UInt32(8) +const heaps = [Vector{taskheap}(undef, 0), Vector{taskheap}(undef, 0)] +const heaps_lock = [SpinLock(), SpinLock()] +const cong_unbias = [typemax(UInt32), typemax(UInt32)] + + +cong(max::UInt32, unbias::UInt32) = + ccall(:jl_rand_ptls, UInt32, (UInt32, UInt32), max, unbias) + UInt32(1) + +function unbias_cong(max::UInt32) + return typemax(UInt32) - ((typemax(UInt32) % max) + UInt32(1)) +end + + +function multiq_sift_up(heap::taskheap, idx::Int32) + while idx > Int32(1) + parent = (idx - Int32(2)) ÷ heap_d + Int32(1) + if heap.tasks[idx].priority < heap.tasks[parent].priority + t = heap.tasks[parent] + heap.tasks[parent] = heap.tasks[idx] + heap.tasks[idx] = t + idx = parent + else + break + end + end +end + + +function multiq_sift_down(heap::taskheap, idx::Int32) + if idx <= heap.ntasks + for child = (heap_d * idx - heap_d + 2):(heap_d * idx + 1) + child = Int(child) + child > length(heap.tasks) && break + if isassigned(heap.tasks, child) && + heap.tasks[child].priority < heap.tasks[idx].priority + t = heap.tasks[idx] + heap.tasks[idx] = heap.tasks[child] + heap.tasks[child] = t + multiq_sift_down(heap, Int32(child)) + end + end + end +end + + +function multiq_size(tpid::Int8) + nt = UInt32(Threads._nthreads_in_pool(tpid)) + tp = tpid + 1 + tpheaps = heaps[tp] + heap_c = UInt32(2) + heap_p = UInt32(length(tpheaps)) + + if heap_c * nt <= heap_p + return heap_p + end + + @lock heaps_lock[tp] begin + heap_p = UInt32(length(tpheaps)) + nt = UInt32(Threads._nthreads_in_pool(tpid)) + if heap_c * nt <= heap_p + return heap_p + end + + heap_p += heap_c * nt + newheaps = Vector{taskheap}(undef, heap_p) + copyto!(newheaps, tpheaps) + for i = (1 + length(tpheaps)):heap_p + newheaps[i] = taskheap() + end + heaps[tp] = newheaps + cong_unbias[tp] = unbias_cong(heap_p) + end + + return heap_p +end + + +function multiq_insert(task::Task, priority::UInt16) + tpid = ccall(:jl_get_task_threadpoolid, Int8, (Any,), task) + heap_p = multiq_size(tpid) + tp = tpid + 1 + + task.priority = priority + + rn = cong(heap_p, cong_unbias[tp]) + tpheaps = heaps[tp] + while !trylock(tpheaps[rn].lock) + rn = cong(heap_p, cong_unbias[tp]) + end + + heap = tpheaps[rn] + if heap.ntasks >= length(heap.tasks) + resize!(heap.tasks, length(heap.tasks) * 2) + end + + ntasks = heap.ntasks + Int32(1) + @atomic :monotonic heap.ntasks = ntasks + heap.tasks[ntasks] = task + multiq_sift_up(heap, ntasks) + priority = heap.priority + if task.priority < priority + @atomic :monotonic heap.priority = task.priority + end + unlock(heap.lock) + + return true +end + + +function multiq_deletemin() + local rn1, rn2 + local prio1, prio2 + + tid = Threads.threadid() + tp = ccall(:jl_threadpoolid, Int8, (Int16,), tid-1) + 1 + tpheaps = heaps[tp] + + @label retry + GC.safepoint() + heap_p = UInt32(length(tpheaps)) + for i = UInt32(0):heap_p + if i == heap_p + return nothing + end + rn1 = cong(heap_p, cong_unbias[tp]) + rn2 = cong(heap_p, cong_unbias[tp]) + prio1 = tpheaps[rn1].priority + prio2 = tpheaps[rn2].priority + if prio1 > prio2 + prio1 = prio2 + rn1 = rn2 + elseif prio1 == prio2 && prio1 == typemax(UInt16) + continue + end + if trylock(tpheaps[rn1].lock) + if prio1 == tpheaps[rn1].priority + break + end + unlock(tpheaps[rn1].lock) + end + end + + heap = tpheaps[rn1] + task = heap.tasks[1] + if ccall(:jl_set_task_tid, Cint, (Any, Cint), task, tid-1) == 0 + unlock(heap.lock) + @goto retry + end + ntasks = heap.ntasks + @atomic :monotonic heap.ntasks = ntasks - Int32(1) + heap.tasks[1] = heap.tasks[ntasks] + Base._unsetindex!(heap.tasks, Int(ntasks)) + prio1 = typemax(UInt16) + if ntasks > 1 + multiq_sift_down(heap, Int32(1)) + prio1 = heap.tasks[1].priority + end + @atomic :monotonic heap.priority = prio1 + unlock(heap.lock) + + return task +end + +function multiq_check_empty() + tid = Threads.threadid() + tp = ccall(:jl_threadpoolid, Int8, (Int16,), tid-1) + 1 + for i = UInt32(1):length(heaps[tp]) + if heaps[tp][i].ntasks != 0 + return false + end + end + return true +end + +end diff --git a/base/path.jl b/base/path.jl index 454fe5bd65d32..c439a2800acce 100644 --- a/base/path.jl +++ b/base/path.jl @@ -20,22 +20,22 @@ export if Sys.isunix() const path_separator = "/" - const path_separator_re = r"/+" - const path_directory_re = r"(?:^|/)\.{0,2}$" - const path_dir_splitter = r"^(.*?)(/+)([^/]*)$" - const path_ext_splitter = r"^((?:.*/)?(?:\.|[^/\.])[^/]*?)(\.[^/\.]*|)$" + const path_separator_re = r"/+"sa + const path_directory_re = r"(?:^|/)\.{0,2}$"sa + const path_dir_splitter = r"^(.*?)(/+)([^/]*)$"sa + const path_ext_splitter = r"^((?:.*/)?(?:\.|[^/\.])[^/]*?)(\.[^/\.]*|)$"sa splitdrive(path::String) = ("",path) elseif Sys.iswindows() const path_separator = "\\" - const path_separator_re = r"[/\\]+" - const path_absolute_re = r"^(?:[A-Za-z]+:)?[/\\]" - const path_directory_re = r"(?:^|[/\\])\.{0,2}$" - const path_dir_splitter = r"^(.*?)([/\\]+)([^/\\]*)$" - const path_ext_splitter = r"^((?:.*[/\\])?(?:\.|[^/\\\.])[^/\\]*?)(\.[^/\\\.]*|)$" + const path_separator_re = r"[/\\]+"sa + const path_absolute_re = r"^(?:[A-Za-z]+:)?[/\\]"sa + const path_directory_re = r"(?:^|[/\\])\.{0,2}$"sa + const path_dir_splitter = r"^(.*?)([/\\]+)([^/\\]*)$"sa + const path_ext_splitter = r"^((?:.*[/\\])?(?:\.|[^/\\\.])[^/\\]*?)(\.[^/\\\.]*|)$"sa function splitdrive(path::String) - m = match(r"^([^\\]+:|\\\\[^\\]+\\[^\\]+|\\\\\?\\UNC\\[^\\]+\\[^\\]+|\\\\\?\\[^\\]+:|)(.*)$"s, path) + m = match(r"^([^\\]+:|\\\\[^\\]+\\[^\\]+|\\\\\?\\UNC\\[^\\]+\\[^\\]+|\\\\\?\\[^\\]+:|)(.*)$"sa, path)::AbstractMatch String(something(m.captures[1])), String(something(m.captures[2])) end else @@ -145,7 +145,7 @@ function _splitdir_nodrive(a::String, b::String) end """ - dirname(path::AbstractString) -> AbstractString + dirname(path::AbstractString) -> String Get the directory part of a path. Trailing characters ('/' or '\\') in the path are counted as part of the path. @@ -161,10 +161,10 @@ julia> dirname("/home/myuser/") See also [`basename`](@ref). """ - dirname(path::AbstractString) = splitdir(path)[1] +dirname(path::AbstractString) = splitdir(path)[1] """ - basename(path::AbstractString) -> AbstractString + basename(path::AbstractString) -> String Get the file name part of a path. @@ -186,7 +186,7 @@ See also [`dirname`](@ref). basename(path::AbstractString) = splitdir(path)[2] """ - splitext(path::AbstractString) -> (AbstractString, AbstractString) + splitext(path::AbstractString) -> (String, String) If the last component of a path contains one or more dots, split the path into everything before the last dot and everything including and after the dot. Otherwise, return a tuple of the argument @@ -415,6 +415,16 @@ 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 + +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") + +Which gives a path like `"/home/JuliaUser/data/"`. + +See also [`joinpath`](@ref), [`pwd`](@ref), [`expanduser`](@ref). """ function abspath(a::String)::String if !isabspath(a) @@ -516,6 +526,8 @@ end expanduser(path::AbstractString) -> AbstractString On Unix systems, replace a tilde character at the start of a path with the current user's home directory. + +See also: [`contractuser`](@ref). """ expanduser(path::AbstractString) @@ -523,12 +535,14 @@ expanduser(path::AbstractString) contractuser(path::AbstractString) -> AbstractString On Unix systems, if the path starts with `homedir()`, replace it with a tilde character. + +See also: [`expanduser`](@ref). """ contractuser(path::AbstractString) """ - relpath(path::AbstractString, startpath::AbstractString = ".") -> AbstractString + relpath(path::AbstractString, startpath::AbstractString = ".") -> String Return a relative filepath to `path` either from the current directory or from an optional start directory. This is a path computation: the filesystem is not accessed to confirm the @@ -538,8 +552,8 @@ On Windows, case sensitivity is applied to every part of the path except drive l `path` and `startpath` refer to different drives, the absolute path of `path` is returned. """ function relpath(path::String, startpath::String = ".") - isempty(path) && throw(ArgumentError("`path` must be specified")) - isempty(startpath) && throw(ArgumentError("`startpath` must be specified")) + isempty(path) && throw(ArgumentError("`path` must be non-empty")) + isempty(startpath) && throw(ArgumentError("`startpath` must be non-empty")) curdir = "." pardir = ".." path == startpath && return curdir diff --git a/base/pcre.jl b/base/pcre.jl index a8edaaa089c31..7597c1217ca9e 100644 --- a/base/pcre.jl +++ b/base/pcre.jl @@ -24,28 +24,38 @@ function create_match_context() return ctx end -const THREAD_MATCH_CONTEXTS = Ptr{Cvoid}[C_NULL] +THREAD_MATCH_CONTEXTS::Vector{Ptr{Cvoid}} = [C_NULL] PCRE_COMPILE_LOCK = nothing -_tid() = Int(ccall(:jl_threadid, Int16, ())+1) -_nth() = Int(unsafe_load(cglobal(:jl_n_threads, Cint))) +_tid() = Int(ccall(:jl_threadid, Int16, ())) + 1 +_mth() = Int(Core.Intrinsics.atomic_pointerref(cglobal(:jl_n_threads, Cint), :acquire)) function get_local_match_context() tid = _tid() - ctx = @inbounds THREAD_MATCH_CONTEXTS[tid] + ctxs = THREAD_MATCH_CONTEXTS + if length(ctxs) < tid + # slow path to allocate it + l = PCRE_COMPILE_LOCK::Threads.SpinLock + lock(l) + try + ctxs = THREAD_MATCH_CONTEXTS + if length(ctxs) < tid + global THREAD_MATCH_CONTEXTS = ctxs = copyto!(fill(C_NULL, length(ctxs) + _mth()), ctxs) + end + finally + unlock(l) + end + end + ctx = @inbounds ctxs[tid] if ctx == C_NULL - @inbounds THREAD_MATCH_CONTEXTS[tid] = ctx = create_match_context() + # slow path to allocate it + ctx = create_match_context() + THREAD_MATCH_CONTEXTS[tid] = ctx end return ctx end -function __init__() - resize!(THREAD_MATCH_CONTEXTS, _nth()) - fill!(THREAD_MATCH_CONTEXTS, C_NULL) - global PCRE_COMPILE_LOCK = Threads.SpinLock() -end - # supported options for different use cases # arguments to pcre2_compile diff --git a/base/permuteddimsarray.jl b/base/permuteddimsarray.jl index ea966c44efc38..80685332a85dc 100644 --- a/base/permuteddimsarray.jl +++ b/base/permuteddimsarray.jl @@ -48,6 +48,7 @@ end 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) @@ -100,24 +101,36 @@ julia> A = reshape(Vector(1:8), (2,2,2)) 5 7 6 8 -julia> permutedims(A, (3, 2, 1)) +julia> perm = (3, 1, 2); # put the last dimension first + +julia> B = permutedims(A, perm) 2×2×2 Array{Int64, 3}: [:, :, 1] = - 1 3 - 5 7 + 1 2 + 5 6 [:, :, 2] = - 2 4 - 6 8 + 3 4 + 7 8 -julia> B = randn(5, 7, 11, 13); +julia> A == permutedims(B, invperm(perm)) # the inverse permutation +true +``` + +For each dimension `i` of `B = permutedims(A, perm)`, its corresponding dimension of `A` +will be `perm[i]`. This means the equality `size(B, i) == size(A, perm[i])` holds. + +```jldoctest +julia> A = randn(5, 7, 11, 13); -julia> perm = [4,1,3,2]; +julia> perm = [4, 1, 3, 2]; -julia> size(permutedims(B, perm)) +julia> B = permutedims(A, perm); + +julia> size(B) (13, 5, 11, 7) -julia> size(B)[perm] == ans +julia> size(A)[perm] == ans true ``` """ @@ -263,11 +276,21 @@ end P end -function Base._mapreduce_dim(f, op, init::Base._InitialValue, A::PermutedDimsArray, dims::Colon) +const CommutativeOps = Union{typeof(+),typeof(Base.add_sum),typeof(min),typeof(max),typeof(Base._extrema_rf),typeof(|),typeof(&)} + +function Base._mapreduce_dim(f, op::CommutativeOps, init::Base._InitialValue, A::PermutedDimsArray, dims::Colon) + Base._mapreduce_dim(f, op, init, parent(A), dims) +end +function Base._mapreduce_dim(f::typeof(identity), op::Union{typeof(Base.mul_prod),typeof(*)}, init::Base._InitialValue, A::PermutedDimsArray{<:Union{Real,Complex}}, dims::Colon) Base._mapreduce_dim(f, op, init, parent(A), dims) end -function Base.mapreducedim!(f, op, B::AbstractArray{T,N}, A::PermutedDimsArray{T,N,perm,iperm}) where {T,N,perm,iperm} +function Base.mapreducedim!(f, op::CommutativeOps, B::AbstractArray{T,N}, A::PermutedDimsArray{S,N,perm,iperm}) where {T,S,N,perm,iperm} + C = PermutedDimsArray{T,N,iperm,perm,typeof(B)}(B) # make the inverse permutation for the output + Base.mapreducedim!(f, op, C, parent(A)) + B +end +function Base.mapreducedim!(f::typeof(identity), op::Union{typeof(Base.mul_prod),typeof(*)}, B::AbstractArray{T,N}, A::PermutedDimsArray{<:Union{Real,Complex},N,perm,iperm}) where {T,N,perm,iperm} C = PermutedDimsArray{T,N,iperm,perm,typeof(B)}(B) # make the inverse permutation for the output Base.mapreducedim!(f, op, C, parent(A)) B diff --git a/base/pkgid.jl b/base/pkgid.jl index f3afb74bc3490..20d9de559b334 100644 --- a/base/pkgid.jl +++ b/base/pkgid.jl @@ -42,4 +42,3 @@ function binunpack(s::String) name = read(io, String) return PkgId(UUID(uuid), name) end - diff --git a/base/pointer.jl b/base/pointer.jl index b9475724f7637..62b34dd06d368 100644 --- a/base/pointer.jl +++ b/base/pointer.jl @@ -20,14 +20,14 @@ const C_NULL = bitcast(Ptr{Cvoid}, 0) # TODO: deprecate these conversions. C doesn't even allow them. # pointer to integer -convert(::Type{T}, x::Ptr) where {T<:Integer} = T(UInt(x)) +convert(::Type{T}, x::Ptr) where {T<:Integer} = T(UInt(x))::T # integer to pointer convert(::Type{Ptr{T}}, x::Union{Int,UInt}) where {T} = Ptr{T}(x) # pointer to pointer convert(::Type{Ptr{T}}, p::Ptr{T}) where {T} = p -convert(::Type{Ptr{T}}, p::Ptr) where {T} = bitcast(Ptr{T}, p) +convert(::Type{Ptr{T}}, p::Ptr) where {T} = bitcast(Ptr{T}, p)::Ptr{T} # object to pointer (when used with ccall) @@ -77,7 +77,10 @@ element type. `dims` is either an integer (for a 1d array) or a tuple of the arr calling `free` on the pointer when the array is no longer referenced. This function is labeled "unsafe" because it will crash if `pointer` is not -a valid memory address to data of the requested length. +a valid memory address to data of the requested length. Unlike [`unsafe_load`](@ref) +and [`unsafe_store!`](@ref), the programmer is responsible also for ensuring that the +underlying data is not accessed through two arrays of different element type, similar +to the strict aliasing rule in C. """ function unsafe_wrap(::Union{Type{Array},Type{Array{T}},Type{Array{T,N}}}, p::Ptr{T}, dims::NTuple{N,Int}; own::Bool = false) where {T,N} @@ -89,7 +92,8 @@ 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::Type, p::Ptr, dims::NTuple{N,<:Integer}; own::Bool = false) where {N} = +unsafe_wrap(Atype::Union{Type{Array},Type{Array{T}},Type{Array{T,N}}}, + p::Ptr{T}, dims::NTuple{N,<:Integer}; own::Bool = false) where {T,N} = unsafe_wrap(Atype, p, convert(Tuple{Vararg{Int}}, dims), own = own) """ @@ -99,8 +103,10 @@ Load a value of type `T` from the address of the `i`th element (1-indexed) start This is equivalent to the C expression `p[i-1]`. The `unsafe` prefix on this function indicates that no validation is performed on the -pointer `p` to ensure that it is valid. Incorrect usage may segfault your program or return -garbage answers, in the same manner as C. +pointer `p` to ensure that it is valid. Like C, the programmer is responsible for ensuring +that referenced memory is not freed or garbage collected while invoking this function. +Incorrect usage may segfault your program or return garbage answers. Unlike C, dereferencing +memory region allocated as different type may be valid provided that the types are compatible. """ unsafe_load(p::Ptr, i::Integer=1) = pointerref(p, Int(i), 1) @@ -111,8 +117,10 @@ Store a value of type `T` to the address of the `i`th element (1-indexed) starti This is equivalent to the C expression `p[i-1] = x`. The `unsafe` prefix on this function indicates that no validation is performed on the -pointer `p` to ensure that it is valid. Incorrect usage may corrupt or segfault your -program, in the same manner as C. +pointer `p` to ensure that it is valid. Like C, the programmer is responsible for ensuring +that referenced memory is not freed or garbage collected while invoking this function. +Incorrect usage may segfault your program. Unlike C, storing memory region allocated as +different type may be valid provided that that the types are compatible. """ unsafe_store!(p::Ptr{Any}, @nospecialize(x), i::Integer=1) = pointerset(p, x, Int(i), 1) unsafe_store!(p::Ptr{T}, x, i::Integer=1) where {T} = pointerset(p, convert(T,x), Int(i), 1) diff --git a/base/process.jl b/base/process.jl index 16e66b0bd9884..ed51a30ae3ced 100644 --- a/base/process.jl +++ b/base/process.jl @@ -38,9 +38,13 @@ pipe_writer(p::ProcessChain) = p.in # release ownership of the libuv handle function uvfinalize(proc::Process) if proc.handle != C_NULL - disassociate_julia_struct(proc.handle) - ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), proc.handle) - proc.handle = C_NULL + iolock_begin() + if proc.handle != C_NULL + disassociate_julia_struct(proc.handle) + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), proc.handle) + proc.handle = C_NULL + end + iolock_end() end nothing end @@ -52,6 +56,7 @@ function uv_return_spawn(p::Ptr{Cvoid}, exit_status::Int64, termsignal::Int32) proc = unsafe_pointer_to_objref(data)::Process proc.exitcode = exit_status proc.termsignal = termsignal + disassociate_julia_struct(proc.handle) # ensure that data field is set to C_NULL ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), proc.handle) proc.handle = C_NULL lock(proc.exitnotify) @@ -65,11 +70,12 @@ end # called when the libuv handle is destroyed function _uv_hook_close(proc::Process) - proc.handle = C_NULL + Libc.free(@atomicswap :not_atomic proc.handle = C_NULL) nothing end -const SpawnIOs = Vector{Any} # convenience name for readability +const SpawnIO = Union{IO, RawFD, OS_HANDLE} +const SpawnIOs = Vector{SpawnIO} # convenience name for readability function as_cpumask(cpus::Vector{UInt16}) n = max(Int(maximum(cpus)), Int(ccall(:uv_cpumask_size, Cint, ()))) @@ -84,7 +90,7 @@ end @noinline function _spawn_primitive(file, cmd::Cmd, stdio::SpawnIOs) loop = eventloop() cpumask = cmd.cpus - cpumask === nothing || (cpumask = as_cpumask(cmd.cpus)) + cpumask === nothing || (cpumask = as_cpumask(cpumask)) GC.@preserve stdio begin iohandles = Tuple{Cint, UInt}[ # assuming little-endian layout let h = rawhandle(io) @@ -95,8 +101,9 @@ end end for io in stdio] handle = Libc.malloc(_sizeof_uv_process) - disassociate_julia_struct(handle) # ensure that data field is set to C_NULL + disassociate_julia_struct(handle) (; exec, flags, env, dir) = cmd + iolock_begin() err = ccall(:jl_spawn, Int32, (Cstring, Ptr{Cstring}, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Tuple{Cint, UInt}}, Int, @@ -109,17 +116,21 @@ end cpumask === nothing ? C_NULL : cpumask, cpumask === nothing ? 0 : length(cpumask), @cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32))) + if err == 0 + pp = Process(cmd, handle) + associate_julia_struct(handle, pp) + else + ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), handle) # will call free on handle eventually + end + iolock_end() end if err != 0 - ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), handle) # will call free on handle eventually throw(_UVError("could not spawn " * repr(cmd), err)) end - pp = Process(cmd, handle) - associate_julia_struct(handle, pp) return pp end -_spawn(cmds::AbstractCmd) = _spawn(cmds, Any[]) +_spawn(cmds::AbstractCmd) = _spawn(cmds, SpawnIO[]) # optimization: we can spawn `Cmd` directly without allocating the ProcessChain function _spawn(cmd::Cmd, stdios::SpawnIOs) @@ -203,7 +214,7 @@ end # open the child end of each element of `stdios`, and initialize the parent end function setup_stdios(f, stdios::SpawnIOs) nstdio = length(stdios) - open_io = Vector{Any}(undef, nstdio) + open_io = SpawnIOs(undef, nstdio) close_io = falses(nstdio) try for i in 1:nstdio @@ -314,19 +325,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) = Any[stdios...] -spawn_opts_inherit(stdios::StdIOSet) = Any[stdios...] +spawn_opts_swallow(stdios::StdIOSet) = SpawnIO[stdios...] +spawn_opts_inherit(stdios::StdIOSet) = SpawnIO[stdios...] spawn_opts_swallow(in::Redirectable=devnull, out::Redirectable=devnull, err::Redirectable=devnull) = - Any[in, out, err] + SpawnIO[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)) = - Any[in, out, err] + SpawnIO[in, out, err] function eachline(cmd::AbstractCmd; keep::Bool=false) out = PipeEndpoint() - processes = _spawn(cmd, Any[devnull, out, stderr]) + processes = _spawn(cmd, SpawnIO[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 @@ -374,20 +385,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, Any[in, out, stderr]) + processes = _spawn(cmds, SpawnIO[in, out, stderr]) processes.in = in processes.out = out elseif read out = PipeEndpoint() - processes = _spawn(cmds, Any[stdio, out, stderr]) + processes = _spawn(cmds, SpawnIO[stdio, out, stderr]) processes.out = out elseif write in = PipeEndpoint() - processes = _spawn(cmds, Any[in, stdio, stderr]) + processes = _spawn(cmds, SpawnIO[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, Any[devnull, devnull, stderr]) + processes = _spawn(cmds, SpawnIO[devnull, devnull, stderr]) end return processes end @@ -402,16 +413,25 @@ process failed, or if the process attempts to print anything to stdout. """ function open(f::Function, cmds::AbstractCmd, args...; kwargs...) P = open(cmds, args...; kwargs...) + function waitkill(P::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 + process_running(P) && kill(P) + end + wait(P) + close(t) + end ret = try f(P) catch - kill(P) - close(P) + waitkill(P) rethrow() end close(P.in) - if !eof(P.out) - close(P.out) + if !(eof(P.out)::Bool) + waitkill(P) throw(_UVError("open(do)", UV_EPIPE)) end success(P) || pipeline_error(P) @@ -427,7 +447,7 @@ function read(cmd::AbstractCmd) procs = open(cmd, "r", devnull) bytes = read(procs.out) success(procs) || pipeline_error(procs) - return bytes + return bytes::Vector{UInt8} end """ @@ -444,6 +464,9 @@ Run a command object, constructed with backticks (see the [Running External Prog section in the manual). Throws an error if anything goes wrong, including the process exiting with a non-zero status (when `wait` is true). +The `args...` allow you to pass through file descriptors to the command, and are ordered +like regular unix file descriptors (eg `stdin, stdout, stderr, FD(3), FD(4)...`). + If `wait` is false, the process runs asynchronously. You can later wait for it and check its exit status by calling `success` on the returned process object. @@ -587,10 +610,10 @@ Get the child process ID, if it still exists. This function requires at least Julia 1.1. """ function Libc.getpid(p::Process) - # TODO: due to threading, this method is no longer synchronized with the user application + # TODO: due to threading, this method is only weakly synchronized with the user application iolock_begin() ppid = Int32(0) - if p.handle != C_NULL + if p.handle != C_NULL # e.g. process_running ppid = ccall(:jl_uv_process_pid, Int32, (Ptr{Cvoid},), p.handle) end iolock_end() diff --git a/base/promotion.jl b/base/promotion.jl index 845e16ca499d3..6e32bd7a42efa 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -3,16 +3,25 @@ ## type join (closest common ancestor, or least upper bound) ## """ - typejoin(T, S) + typejoin(T, S, ...) -Return the closest common ancestor of `T` and `S`, i.e. the narrowest type from which -they both inherit. +Return the closest common ancestor of types `T` and `S`, i.e. the narrowest type from which +they both inherit. Recurses on additional varargs. + +# Examples +```jldoctest +julia> typejoin(Int, Float64) +Real + +julia> typejoin(Int, Float64, ComplexF32) +Number +``` """ typejoin() = Bottom typejoin(@nospecialize(t)) = t -typejoin(@nospecialize(t), ts...) = (@_pure_meta; typejoin(t, typejoin(ts...))) +typejoin(@nospecialize(t), ts...) = (@_foldable_meta; typejoin(t, typejoin(ts...))) function typejoin(@nospecialize(a), @nospecialize(b)) - @_pure_meta + @_foldable_meta if isa(a, TypeVar) return typejoin(a.ub, b) elseif isa(b, TypeVar) @@ -29,11 +38,15 @@ function typejoin(@nospecialize(a), @nospecialize(b)) return typejoin(typejoin(a.a, a.b), b) elseif isa(b, Union) return typejoin(a, typejoin(b.a, b.b)) - elseif a <: Tuple + end + # a and b are DataTypes + # We have to hide Constant info from inference, see #44390 + a, b = inferencebarrier(a)::DataType, inferencebarrier(b)::DataType + if a <: Tuple if !(b <: Tuple) return Any end - ap, bp = a.parameters::Core.SimpleVector, b.parameters::Core.SimpleVector + ap, bp = a.parameters, b.parameters lar = length(ap) lbr = length(bp) if lar == 0 @@ -77,7 +90,6 @@ function typejoin(@nospecialize(a), @nospecialize(b)) elseif b <: Tuple return Any end - a, b = a::DataType, b::DataType while b !== Any if a <: b.name.wrapper while a.name !== b.name @@ -104,6 +116,7 @@ function typejoin(@nospecialize(a), @nospecialize(b)) if ai === bi || (isa(ai,Type) && isa(bi,Type) && ai <: bi && bi <: ai) aprimary = aprimary{ai} else + aprimary = aprimary::UnionAll # pushfirst!(vars, aprimary.var) _growbeg!(vars, 1) arrayset(false, vars, aprimary.var, 1) @@ -125,7 +138,7 @@ end # WARNING: this is wrong for some objects for which subtyping is broken # (Core.Compiler.isnotbrokensubtype), use only simple types for `b` function typesplit(@nospecialize(a), @nospecialize(b)) - @_pure_meta + @_foldable_meta if a <: b return Bottom end @@ -159,7 +172,12 @@ function promote_typejoin(@nospecialize(a), @nospecialize(b)) c = typejoin(_promote_typesubtract(a), _promote_typesubtract(b)) return Union{a, b, c}::Type end -_promote_typesubtract(@nospecialize(a)) = typesplit(a, Union{Nothing, Missing}) +_promote_typesubtract(@nospecialize(a)) = + a === Any ? a : + a >: Union{Nothing, Missing} ? typesplit(a, Union{Nothing, Missing}) : + a >: Nothing ? typesplit(a, Nothing) : + a >: Missing ? typesplit(a, Missing) : + a function promote_typejoin_union(::Type{T}) where T if T === Union{} @@ -177,7 +195,7 @@ function promote_typejoin_union(::Type{T}) where T end function typejoin_union_tuple(T::DataType) - @_pure_meta + @_foldable_meta u = Base.unwrap_unionall(T) p = (u::DataType).parameters lr = length(p)::Int @@ -207,16 +225,17 @@ function typejoin_union_tuple(T::DataType) end # Returns length, isfixed -function full_va_len(p) +function full_va_len(p::Core.SimpleVector) isempty(p) && return 0, true last = p[end] if isvarargtype(last) - if isdefined(last, :N) && isa(last.N, Int) - return length(p)::Int + last.N - 1, true + if isdefined(last, :N) + N = last.N + isa(N, Int) && return length(p) + N - 1, true end - return length(p)::Int, false + return length(p), false end - return length(p)::Int, true + return length(p), true end # reduce typejoin over A[i:end] @@ -234,7 +253,7 @@ end ## promotion mechanism ## """ - promote_type(type1, type2) + promote_type(type1, type2, ...) Promotion refers to converting values of mixed types to a single common type. `promote_type` represents the default promotion behavior in Julia when @@ -303,9 +322,15 @@ it for new types as appropriate. """ function promote_rule end -promote_rule(::Type{<:Any}, ::Type{<:Any}) = Bottom +promote_rule(::Type, ::Type) = Bottom +# Define some methods to avoid needing to enumerate unrelated possibilities when presented +# with Type{<:T}, and return a value in general accordance with the result given by promote_type +promote_rule(::Type{Bottom}, slurp...) = Bottom +promote_rule(::Type{Bottom}, ::Type{Bottom}, slurp...) = Bottom # not strictly necessary, since the next method would match unambiguously anyways +promote_rule(::Type{Bottom}, ::Type{T}, slurp...) where {T} = T +promote_rule(::Type{T}, ::Type{Bottom}, slurp...) where {T} = T -promote_result(::Type{<:Any},::Type{<:Any},::Type{T},::Type{S}) where {T,S} = (@inline; promote_type(T,S)) +promote_result(::Type,::Type,::Type{T},::Type{S}) where {T,S} = (@inline; promote_type(T,S)) # If no promote_rule is defined, both directions give Bottom. In that # case use typejoin on the original types instead. promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = (@inline; typejoin(T, S)) @@ -316,12 +341,25 @@ promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = Convert all arguments to a common type, and return them all (as a tuple). If no arguments can be converted, an error is raised. -See also: [`promote_type`], [`promote_rule`]. +See also: [`promote_type`](@ref), [`promote_rule`](@ref). # Examples ```jldoctest julia> promote(Int8(1), Float16(4.5), Float32(4.1)) (1.0f0, 4.5f0, 4.1f0) + +julia> promote_type(Int8, Float16, Float32) +Float32 + +julia> reduce(Base.promote_typejoin, (Int8, Float16, Float32)) +Real + +julia> promote(1, "x") +ERROR: promotion of types Int64 and String failed to change any arguments +[...] + +julia> promote_type(Int, String) +Any ``` """ function promote end @@ -440,6 +478,11 @@ else _return_type(@nospecialize(f), @nospecialize(t)) = Any end +function TupleOrBottom(tt...) + any(p -> p === Union{}, tt) && return Union{} + return Tuple{tt...} +end + """ promote_op(f, argtypes...) @@ -451,7 +494,12 @@ Guess what an appropriate container eltype would be for storing results of 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`. """ -promote_op(f, S::Type...) = _return_type(f, Tuple{S...}) +function promote_op(f, S::Type...) + argT = TupleOrBottom(S...) + argT === Union{} && return Union{} + return _return_type(f, argT) +end + ## catch-alls to prevent infinite recursion when definitions are missing ## @@ -472,7 +520,7 @@ xor(x::T, y::T) where {T<:Integer} = no_op_err("xor", T) (==)(x::T, y::T) where {T<:Number} = x === y (< )(x::T, y::T) where {T<:Real} = no_op_err("<" , T) -(<=)(x::T, y::T) where {T<:Real} = no_op_err("<=", T) +(<=)(x::T, y::T) where {T<:Real} = (x == y) | (x < y) rem(x::T, y::T) where {T<:Real} = no_op_err("rem", T) mod(x::T, y::T) where {T<:Real} = no_op_err("mod", T) diff --git a/base/randomdevice.jl b/base/randomdevice.jl deleted file mode 100644 index d63ff7edc1647..0000000000000 --- a/base/randomdevice.jl +++ /dev/null @@ -1,77 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -# This file contains the minimal support of RandomDevice for Base's own usage. -# The actual RandomDevice type that makes use of this infrastructure is defined -# in the Random stdlib. - -module DevRandomState - if !Sys.iswindows() - mutable struct FileRef - @atomic file::Union{IOStream, Nothing} - end - const DEV_RANDOM = FileRef(nothing) - const DEV_URANDOM = FileRef(nothing) - end - function __init__() - if !Sys.iswindows() - @atomic DEV_RANDOM.file = nothing - @atomic DEV_URANDOM.file = nothing - end - end -end - -if Sys.iswindows() - function RtlGenRandom!(A::Union{Array, Ref}) - Base.windowserror("SystemFunction036 (RtlGenRandom)", 0 == ccall( - (:SystemFunction036, :Advapi32), stdcall, UInt8, (Ptr{Cvoid}, UInt32), - A, sizeof(A))) - end - - # Manually implemented to work without the Random machinery - function _rand_uint() - r = Ref{Cuint}() - RtlGenRandom!(r) - return r[] - end -else # !windows - function _get_dev_random_fd(unlimited::Bool) - ref = unlimited ? DevRandomState.DEV_URANDOM : DevRandomState.DEV_RANDOM - fd = ref.file - if fd === nothing - fd = open(unlimited ? "/dev/urandom" : "/dev/random") - old, ok = @atomicreplace ref.file nothing => fd - if !ok - close(fd) - fd = old::IOStream - end - end - return fd - end - - # Manually implemented to work without the Random machinery - function _rand_uint() - return read(_get_dev_random_fd(true), Cuint) - end -end # os-test - -function _ad_hoc_entropy() - println(stderr, - "Entropy pool not available to seed RNG; using ad-hoc entropy sources.") - seed = reinterpret(UInt64, time()) - seed = hash(seed, getpid() % UInt) - try - seed = hash(seed, parse(UInt64, - read(pipeline(`ifconfig`, `sha1sum`), String)[1:40], - base = 16) % UInt) - catch - end - return seed -end - -function _make_uint_seed() - try - _rand_uint() - catch - return _ad_hoc_entropy() % Cuint - end -end \ No newline at end of file diff --git a/base/range.jl b/base/range.jl index d12a10518cd7f..f7dc35703a196 100644 --- a/base/range.jl +++ b/base/range.jl @@ -24,18 +24,27 @@ _colon(::Ordered, ::Any, start::T, step, stop::T) where {T} = StepRange(start, step, stop) # for T<:Union{Float16,Float32,Float64} see twiceprecision.jl _colon(::Ordered, ::ArithmeticRounds, start::T, step, stop::T) where {T} = - StepRangeLen(start, step, floor(Integer, (stop-start)/step)+1) + StepRangeLen(start, step, convert(Integer, fld(stop - start, step)) + 1) _colon(::Any, ::Any, start::T, step, stop::T) where {T} = - StepRangeLen(start, step, floor(Integer, (stop-start)/step)+1) + StepRangeLen(start, step, convert(Integer, fld(stop - start, step)) + 1) """ (:)(start, [step], stop) -Range operator. `a:b` constructs a range from `a` to `b` with a step size of 1 (a [`UnitRange`](@ref)) -, and `a:s:b` is similar but uses a step size of `s` (a [`StepRange`](@ref)). +Range operator. `a:b` constructs a range from `a` to `b` with a step size +equal to 1, which produces: -`:` is also used in indexing to select whole dimensions - and for [`Symbol`](@ref) literals, as in e.g. `:hello`. +* a [`UnitRange`](@ref) when `a` and `b` are integers, or +* a [`StepRange`](@ref) when `a` and `b` are characters, or +* a [`StepRangeLen`](@ref) when `a` and/or `b` are floating-point. + +`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. + +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`. +Since `:2 isa Int`, it does *not* create a range in indexing: `v[:2] == v[2] != v[begin:2]`. """ (:)(start::T, step, stop::T) where {T} = _colon(start, step, stop) (:)(start::T, step, stop::T) where {T<:Real} = _colon(start, step, stop) @@ -253,7 +262,7 @@ abstract type AbstractRange{T} <: AbstractArray{T,1} end RangeStepStyle(::Type{<:AbstractRange}) = RangeStepIrregular() RangeStepStyle(::Type{<:AbstractRange{<:Integer}}) = RangeStepRegular() -convert(::Type{T}, r::AbstractRange) where {T<:AbstractRange} = r isa T ? r : T(r) +convert(::Type{T}, r::AbstractRange) where {T<:AbstractRange} = r isa T ? r : T(r)::T ## ordinal ranges @@ -284,7 +293,7 @@ abstract type AbstractUnitRange{T} <: OrdinalRange{T,T} end Ranges with elements of type `T` with spacing of type `S`. The step between each element is constant, and the range is defined in terms of a `start` and `stop` of type `T` and a `step` of type `S`. Neither -`T` nor `S` should be floating point types. The syntax `a:b:c` with `b > 1` +`T` nor `S` should be floating point types. The syntax `a:b:c` with `b != 0` and `a`, `b`, and `c` all integers creates a `StepRange`. # Examples @@ -341,7 +350,8 @@ function steprange_last(start, step, stop)::typeof(stop) # Compute remainder as a nonnegative number: if absdiff isa Signed && absdiff < zero(absdiff) # unlikely, but handle the signed overflow case with unsigned rem - remain = convert(typeof(absdiff), unsigned(absdiff) % absstep) + overflow_case(absdiff, absstep) = (@noinline; convert(typeof(absdiff), unsigned(absdiff) % absstep)) + remain = overflow_case(absdiff, absstep) else remain = convert(typeof(absdiff), absdiff % absstep) end @@ -465,9 +475,12 @@ A range `r` where `r[i]` produces values of type `T` (in the first form, `T` is deduced automatically), parameterized by a `ref`erence value, a `step`, and the `len`gth. By default `ref` is the starting value `r[1]`, but alternatively you can supply it as the value of -`r[offset]` for some other index `1 <= offset <= len`. In conjunction -with `TwicePrecision` this can be used to implement ranges that are -free of roundoff error. +`r[offset]` for some other index `1 <= offset <= len`. The syntax `a:b` +or `a:b:c`, where any of `a`, `b`, or `c` are floating-point numbers, creates a +`StepRangeLen`. + +!!! compat "Julia 1.7" + The 4th type parameter `L` requires at least Julia 1.7. """ struct StepRangeLen{T,R,S,L<:Integer} <: AbstractRange{T} ref::R # reference value (might be smallest-magnitude value in the range) @@ -508,7 +521,7 @@ be an `Integer`. ```jldoctest julia> LinRange(1.5, 5.5, 9) 9-element LinRange{Float64, Int64}: - 1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5 + 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5 ``` Compared to using [`range`](@ref), directly constructing a `LinRange` should @@ -578,9 +591,10 @@ function show(io::IO, r::LinRange{T}) where {T} print(io, "LinRange{") show(io, T) print(io, "}(") - show(io, first(r)) + ioc = IOContext(io, :typeinto=>T) + show(ioc, first(r)) print(io, ", ") - show(io, last(r)) + show(ioc, last(r)) print(io, ", ") show(io, length(r)) print(io, ')') @@ -592,7 +606,7 @@ as if it were `collect(r)`, dependent on the size of the terminal, and taking into account whether compact numbers should be shown. It figures out the width in characters of each element, and if they end up too wide, it shows the first and last elements separated by a -horizontal ellipsis. Typical output will look like `1.0,2.0,3.0,…,4.0,5.0,6.0`. +horizontal ellipsis. Typical output will look like `1.0, 2.0, …, 5.0, 6.0`. `print_range(io, r, pre, sep, post, hdots)` uses optional parameters `pre` and `post` characters for each printed row, @@ -601,9 +615,9 @@ parameters `pre` and `post` characters for each printed row, """ function print_range(io::IO, r::AbstractRange, pre::AbstractString = " ", - sep::AbstractString = ",", + sep::AbstractString = ", ", post::AbstractString = "", - hdots::AbstractString = ",\u2026,") # horiz ellipsis + hdots::AbstractString = ", \u2026, ") # horiz ellipsis # This function borrows from print_matrix() in show.jl # and should be called by show and display sz = displaysize(io) @@ -687,10 +701,8 @@ step_hp(r::AbstractRange) = step(r) axes(r::AbstractRange) = (oneto(length(r)),) -# Needed to fold the `firstindex` call in SimdLoop.simd_index -firstindex(::UnitRange) = 1 -firstindex(::StepRange) = 1 -firstindex(::LinRange) = 1 +# Needed to ensure `has_offset_axes` can constant-fold. +has_offset_axes(::StepRange) = false # n.b. checked_length for these is defined iff checked_add and checked_sub are # defined between the relevant types @@ -753,64 +765,66 @@ length(r::OneTo) = Integer(r.stop - zero(r.stop)) length(r::StepRangeLen) = r.len length(r::LinRange) = r.len -let bigints = Union{Int, UInt, Int64, UInt64, Int128, UInt128} - global length, checked_length +let bigints = Union{Int, UInt, Int64, UInt64, Int128, UInt128}, + smallints = (Int === Int64 ? + Union{Int8, UInt8, Int16, UInt16, Int32, UInt32} : + Union{Int8, UInt8, Int16, UInt16}), + bitints = Union{bigints, smallints} + global length, checked_length, firstindex # compile optimization for which promote_type(T, Int) == T length(r::OneTo{T}) where {T<:bigints} = r.stop # slightly more accurate length and checked_length in extreme cases # (near typemax) for types with known `unsigned` functions function length(r::OrdinalRange{T}) where T<:bigints s = step(r) - isempty(r) && return zero(T) diff = last(r) - first(r) + isempty(r) && return zero(diff) # if |s| > 1, diff might have overflowed, but unsigned(diff)÷s should # therefore still be valid (if the result is representable at all) # n.b. !(s isa T) if s isa Unsigned || -1 <= s <= 1 || s == -s - a = div(diff, s) + a = div(diff, s) % typeof(diff) elseif s < 0 a = div(unsigned(-diff), -s) % typeof(diff) else a = div(unsigned(diff), s) % typeof(diff) end - return Integer(a) + oneunit(a) + return a + oneunit(a) end function checked_length(r::OrdinalRange{T}) where T<:bigints s = step(r) - isempty(r) && return zero(T) stop, start = last(r), first(r) + ET = promote_type(typeof(stop), typeof(start)) + isempty(r) && return zero(ET) # n.b. !(s isa T) if s > 1 diff = stop - start - a = convert(T, div(unsigned(diff), s)) + a = convert(ET, div(unsigned(diff), s)) elseif s < -1 diff = start - stop - a = convert(T, div(unsigned(diff), -s)) + a = convert(ET, div(unsigned(diff), -s)) elseif s > 0 - a = div(checked_sub(stop, start), s) + a = convert(ET, div(checked_sub(stop, start), s)) else - a = div(checked_sub(start, stop), -s) + a = convert(ET, div(checked_sub(start, stop), -s)) end return checked_add(a, oneunit(a)) end -end + firstindex(r::StepRange{<:bigints,<:bitints}) = one(last(r)-first(r)) -# some special cases to favor default Int type -let smallints = (Int === Int64 ? - Union{Int8, UInt8, Int16, UInt16, Int32, UInt32} : - Union{Int8, UInt8, Int16, UInt16}) - global length, checked_length - # n.b. !(step isa T) + # some special cases to favor default Int type function length(r::OrdinalRange{<:smallints}) s = step(r) isempty(r) && return 0 - return div(Int(last(r)) - Int(first(r)), s) + 1 + # n.b. !(step isa T) + return Int(div(Int(last(r)) - Int(first(r)), s)) + 1 end length(r::AbstractUnitRange{<:smallints}) = Int(last(r)) - Int(first(r)) + 1 length(r::OneTo{<:smallints}) = Int(r.stop) checked_length(r::OrdinalRange{<:smallints}) = length(r) checked_length(r::AbstractUnitRange{<:smallints}) = length(r) checked_length(r::OneTo{<:smallints}) = length(r) + firstindex(::StepRange{<:smallints,<:bitints}) = 1 end first(r::OrdinalRange{T}) where {T} = convert(T, r.start) @@ -1085,7 +1099,7 @@ show(io::IO, r::AbstractRange) = print(io, repr(first(r)), ':', repr(step(r)), ' show(io::IO, r::UnitRange) = print(io, repr(first(r)), ':', repr(last(r))) show(io::IO, r::OneTo) = print(io, "Base.OneTo(", r.stop, ")") function show(io::IO, r::StepRangeLen) - if step(r) != 0 + if !iszero(step(r)) print(io, repr(first(r)), ':', repr(step(r)), ':', repr(last(r))) else # ugly temporary printing, to avoid 0:0:0 etc. @@ -1228,19 +1242,17 @@ end # _findin (the index of intersection) function _findin(r::AbstractRange{<:Integer}, span::AbstractUnitRange{<:Integer}) - local ifirst - local ilast fspan = first(span) lspan = last(span) fr = first(r) lr = last(r) sr = step(r) if sr > 0 - ifirst = fr >= fspan ? 1 : ceil(Integer,(fspan-fr)/sr)+1 - ilast = lr <= lspan ? length(r) : length(r) - ceil(Integer,(lr-lspan)/sr) + ifirst = fr >= fspan ? 1 : cld(fspan-fr, sr)+1 + ilast = lr <= lspan ? length(r) : length(r) - cld(lr-lspan, sr) elseif sr < 0 - ifirst = fr <= lspan ? 1 : ceil(Integer,(lspan-fr)/sr)+1 - ilast = lr >= fspan ? length(r) : length(r) - ceil(Integer,(lr-fspan)/sr) + ifirst = fr <= lspan ? 1 : cld(lspan-fr, sr)+1 + ilast = lr >= fspan ? length(r) : length(r) - cld(lr-fspan, sr) else ifirst = fr >= fspan ? 1 : length(r)+1 ilast = fr <= lspan ? length(r) : 0 @@ -1263,9 +1275,8 @@ function -(r::LinRange) LinRange{typeof(start)}(start, -r.stop, length(r)) end - # promote eltype if at least one container wouldn't change, otherwise join container types. -el_same(::Type{T}, a::Type{<:AbstractArray{T,n}}, b::Type{<:AbstractArray{T,n}}) where {T,n} = a +el_same(::Type{T}, a::Type{<:AbstractArray{T,n}}, b::Type{<:AbstractArray{T,n}}) where {T,n} = a # we assume a === b el_same(::Type{T}, a::Type{<:AbstractArray{T,n}}, b::Type{<:AbstractArray{S,n}}) where {T,S,n} = a el_same(::Type{T}, a::Type{<:AbstractArray{S,n}}, b::Type{<:AbstractArray{T,n}}) where {T,S,n} = b el_same(::Type, a, b) = promote_typejoin(a, b) @@ -1289,10 +1300,8 @@ AbstractUnitRange{T}(r::AbstractUnitRange{T}) where {T} = r AbstractUnitRange{T}(r::UnitRange) where {T} = UnitRange{T}(r) AbstractUnitRange{T}(r::OneTo) where {T} = OneTo{T}(r) -OrdinalRange{T1, T2}(r::StepRange) where {T1, T2<: Integer} = StepRange{T1, T2}(r) -OrdinalRange{T1, T2}(r::AbstractUnitRange{T1}) where {T1, T2<:Integer} = r -OrdinalRange{T1, T2}(r::UnitRange) where {T1, T2<:Integer} = UnitRange{T1}(r) -OrdinalRange{T1, T2}(r::OneTo) where {T1, T2<:Integer} = OneTo{T1}(r) +OrdinalRange{T, S}(r::OrdinalRange) where {T, S} = StepRange{T, S}(r) +OrdinalRange{T, T}(r::AbstractUnitRange) where {T} = AbstractUnitRange{T}(r) function promote_rule(::Type{StepRange{T1a,T1b}}, ::Type{StepRange{T2a,T2b}}) where {T1a,T1b,T2a,T2b} Tb = promote_type(T1b, T2b) @@ -1392,14 +1401,13 @@ function sum(r::AbstractRange{<:Real}) end function _in_range(x, r::AbstractRange) - if !isfinite(x) - return false - elseif iszero(step(r)) - return !isempty(r) && first(r) == x - else - n = round(Integer, (x - first(r)) / step(r)) + 1 - return n >= 1 && n <= length(r) && r[n] == x - end + isempty(r) && return false + f, l = first(r), last(r) + # check for NaN, Inf, and large x that may overflow in the next calculation + f <= x <= l || l <= x <= f || return false + iszero(step(r)) && return true + n = round(Integer, (x - f) / step(r)) + 1 + n >= 1 && n <= length(r) && r[n] == x end in(x::Real, r::AbstractRange{<:Real}) = _in_range(x, r) # This method needs to be defined separately since -(::T, ::T) can be implemented diff --git a/base/rational.jl b/base/rational.jl index 9e887bdaefa91..6ab022736388e 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -83,6 +83,11 @@ end function show(io::IO, x::Rational) show(io, numerator(x)) + + if isone(denominator(x)) && get(io, :typeinfo, Any) <: Rational + return + end + print(io, "//") show(io, denominator(x)) end @@ -95,6 +100,20 @@ end function write(s::IO, z::Rational) write(s,numerator(z),denominator(z)) end +function parse(::Type{Rational{T}}, s::AbstractString) where T<:Integer + ss = split(s, '/'; limit = 2) + if isone(length(ss)) + return Rational{T}(parse(T, s)) + end + @inbounds ns, ds = ss[1], ss[2] + if startswith(ds, '/') + ds = chop(ds; head = 1, tail = 0) + end + n = parse(T, ns) + d = parse(T, ds) + return n//d +end + function Rational{T}(x::Rational) where T<:Integer unsafe_rational(T, convert(T, x.num), convert(T, x.den)) @@ -258,7 +277,7 @@ 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) = Rational(abs(x.num), x.den) +abs(x::Rational) = unsafe_rational(checked_abs(x.num), x.den) typemin(::Type{Rational{T}}) where {T<:Signed} = unsafe_rational(T, -one(T), zero(T)) typemin(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(T, zero(T), one(T)) @@ -526,7 +545,7 @@ function hash(x::Rational{<:BitInteger64}, h::UInt) pow = trailing_zeros(den) den >>= pow pow = -pow - if den == 1 && abs(num) < 9007199254740992 + if den == 1 && uabs(num) < UInt64(maxintfloat(Float64)) return hash(ldexp(Float64(num),pow),h) end end @@ -537,7 +556,7 @@ function hash(x::Rational{<:BitInteger64}, h::UInt) end # These methods are only needed for performance. Since `first(r)` and `last(r)` have the -# same denominator (because their difference is an integer), `length(r)` can be calulated +# same denominator (because their difference is an integer), `length(r)` can be calculated # without calling `gcd`. function length(r::AbstractUnitRange{T}) where T<:Rational @inline diff --git a/base/reduce.jl b/base/reduce.jl index 13e1b69c79ede..61a0f466b2902 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -64,6 +64,11 @@ function _foldl_impl(op::OP, init, itr) where {OP} return v end +function _foldl_impl(op, init, itr::Union{Tuple,NamedTuple}) + length(itr) <= 32 && return afoldl(op, init, itr...) + @invoke _foldl_impl(op, init, itr::Any) +end + struct _InitialValue end """ @@ -140,17 +145,25 @@ what is returned is `itr′` and op′ = (xfₙ ∘ ... ∘ xf₂ ∘ xf₁)(op) """ -_xfadjoint(op, itr) = (op, itr) -_xfadjoint(op, itr::Generator) = - if itr.f === identity - _xfadjoint(op, itr.iter) - else - _xfadjoint(MappingRF(itr.f, op), itr.iter) - end -_xfadjoint(op, itr::Filter) = - _xfadjoint(FilteringRF(itr.flt, op), itr.itr) -_xfadjoint(op, itr::Flatten) = - _xfadjoint(FlatteningRF(op), itr.it) +function _xfadjoint(op, itr) + itr′, wrap = _xfadjoint_unwrap(itr) + wrap(op), itr′ +end + +_xfadjoint_unwrap(itr) = itr, identity +function _xfadjoint_unwrap(itr::Generator) + itr′, wrap = _xfadjoint_unwrap(itr.iter) + itr.f === identity && return itr′, wrap + return itr′, wrap ∘ Fix1(MappingRF, itr.f) +end +function _xfadjoint_unwrap(itr::Filter) + itr′, wrap = _xfadjoint_unwrap(itr.itr) + return itr′, wrap ∘ Fix1(FilteringRF, itr.flt) +end +function _xfadjoint_unwrap(itr::Flatten) + itr′, wrap = _xfadjoint_unwrap(itr.it) + return itr′, wrap ∘ FlatteningRF +end """ mapfoldl(f, op, itr; [init]) @@ -188,11 +201,11 @@ foldl(op, itr; kw...) = mapfoldl(identity, op, itr; kw...) function mapfoldr_impl(f, op, nt, itr) op′, itr′ = _xfadjoint(BottomRF(FlipArgs(op)), Generator(f, itr)) - return foldl_impl(op′, nt, _reverse(itr′)) + return foldl_impl(op′, nt, _reverse_iter(itr′)) end -_reverse(itr) = Iterators.reverse(itr) -_reverse(itr::Tuple) = reverse(itr) #33235 +_reverse_iter(itr) = Iterators.reverse(itr) +_reverse_iter(itr::Union{Tuple,NamedTuple}) = length(itr) <= 32 ? reverse(itr) : Iterators.reverse(itr) #33235 struct FlipArgs{F} f::F @@ -380,7 +393,7 @@ reduce_empty_iter(op, itr, ::EltypeUnknown) = throw(ArgumentError(""" The value to be returned when calling [`reduce`](@ref), [`foldl`](@ref`) or [`foldr`](@ref) with reduction `op` over an iterator which contains a single element -`x`. This value may also used to initialise the recursion, so that `reduce(op, [x, y])` +`x`. This value may also be used to initialise the recursion, so that `reduce(op, [x, y])` may call `op(reduce_first(op, x), y)`. The default is `x` for most types. The main purpose is to ensure type stability, so @@ -403,8 +416,8 @@ reduce_first(::typeof(mul_prod), x::SmallUnsigned) = UInt(x) The value to be returned when calling [`mapreduce`](@ref), [`mapfoldl`](@ref`) or [`mapfoldr`](@ref) with map `f` and reduction `op` over an iterator which contains a -single element `x`. This value may also used to initialise the recursion, so that -`mapreduce(f, op, [x, y])` may call `op(reduce_first(op, f, x), f(y))`. +single element `x`. This value may also be used to initialise the recursion, so that +`mapreduce(f, op, [x, y])` may call `op(mapreduce_first(f, op, x), f(y))`. The default is `reduce_first(op, f(x))`. """ @@ -451,8 +464,10 @@ For empty collections, providing `init` will be necessary, except for some speci neutral element of `op`. Reductions for certain commonly-used operators may have special implementations, and -should be used instead: `maximum(itr)`, `minimum(itr)`, `sum(itr)`, `prod(itr)`, - `any(itr)`, `all(itr)`. +should be used instead: [`maximum`](@ref)`(itr)`, [`minimum`](@ref)`(itr)`, [`sum`](@ref)`(itr)`, +[`prod`](@ref)`(itr)`, [`any`](@ref)`(itr)`, [`all`](@ref)`(itr)`. +There are efficient methods for concatenating certain arrays of arrays +by calling `reduce(`[`vcat`](@ref)`, arr)` or `reduce(`[`hcat`](@ref)`, arr)`. The associativity of the reduction is implementation dependent. This means that you can't use non-associative operations like `-` because it is undefined whether `reduce(-,[1,2,3])` @@ -522,7 +537,7 @@ sum(f, a; kw...) = mapreduce(f, add_sum, a; kw...) """ sum(itr; [init]) -Returns the sum of all elements in a collection. +Return the sum of all elements in a collection. The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other @@ -554,7 +569,7 @@ sum(a::AbstractArray{Bool}; kw...) = """ prod(f, itr; [init]) -Returns the product of `f` applied to each element of `itr`. +Return the product of `f` applied to each element of `itr`. The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other @@ -578,7 +593,7 @@ prod(f, a; kw...) = mapreduce(f, mul_prod, a; kw...) """ prod(itr; [init]) -Returns the product of all elements of a collection. +Return the product of all elements of a collection. The return type is `Int` for signed integers of less than system word size, and `UInt` for unsigned integers of less than system word size. For all other @@ -665,7 +680,7 @@ end """ maximum(f, itr; [init]) -Returns the largest result of calling function `f` on each element of `itr`. +Return the largest result of calling function `f` on each element of `itr`. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `max` (i.e. which is less than or equal to any @@ -692,7 +707,7 @@ maximum(f, a; kw...) = mapreduce(f, max, a; kw...) """ minimum(f, itr; [init]) -Returns the smallest result of calling function `f` on each element of `itr`. +Return the smallest result of calling function `f` on each element of `itr`. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `min` (i.e. which is greater than or equal to any @@ -719,7 +734,7 @@ minimum(f, a; kw...) = mapreduce(f, min, a; kw...) """ maximum(itr; [init]) -Returns the largest element in a collection. +Return the largest element in a collection. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `max` (i.e. which is less than or equal to any @@ -751,7 +766,7 @@ maximum(a; kw...) = mapreduce(identity, max, a; kw...) """ minimum(itr; [init]) -Returns the smallest element in a collection. +Return the smallest element in a collection. The value returned for empty `itr` can be specified by `init`. It must be a neutral element for `min` (i.e. which is greater than or equal to any @@ -847,15 +862,22 @@ end ExtremaMap(::Type{T}) where {T} = ExtremaMap{Type{T}}(T) @inline (f::ExtremaMap)(x) = (y = f.f(x); (y, y)) -# TODO: optimize for inputs <: AbstractFloat @inline _extrema_rf((min1, max1), (min2, max2)) = (min(min1, min2), max(max1, max2)) +# optimization for IEEEFloat +function _extrema_rf(x::NTuple{2,T}, y::NTuple{2,T}) where {T<:IEEEFloat} + (x1, x2), (y1, y2) = x, y + anynan = isnan(x1)|isnan(y1) + z1 = ifelse(anynan, x1-y1, ifelse(signbit(x1-y1), x1, y1)) + z2 = ifelse(anynan, x1-y1, ifelse(signbit(x2-y2), y2, x2)) + z1, z2 +end ## findmax, findmin, argmax & argmin """ findmax(f, domain) -> (f(x), index) -Returns 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 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. @@ -882,7 +904,8 @@ julia> findmax(cos, 0:π/2:2π) (1.0, 1) ``` """ -findmax(f, domain) = mapfoldl( ((k, v),) -> (f(v), k), _rf_findmax, pairs(domain) ) +findmax(f, domain) = _findmax(f, domain, :) +_findmax(f, domain, ::Colon) = mapfoldl( ((k, v),) -> (f(v), k), _rf_findmax, pairs(domain) ) _rf_findmax((fm, im), (fx, ix)) = isless(fm, fx) ? (fx, ix) : (fm, im) """ @@ -913,7 +936,7 @@ _findmax(a, ::Colon) = findmax(identity, a) """ findmin(f, domain) -> (f(x), index) -Returns 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 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. @@ -941,7 +964,8 @@ julia> findmin(cos, 0:π/2:2π) ``` """ -findmin(f, domain) = mapfoldl( ((k, v),) -> (f(v), k), _rf_findmin, pairs(domain) ) +findmin(f, domain) = _findmin(f, domain, :) +_findmin(f, domain, ::Colon) = mapfoldl( ((k, v),) -> (f(v), k), _rf_findmin, pairs(domain) ) _rf_findmin((fm, im), (fx, ix)) = isgreater(fm, fx) ? (fx, ix) : (fm, im) """ @@ -972,7 +996,7 @@ _findmin(a, ::Colon) = findmin(identity, a) """ argmax(f, domain) -Return a value `x` in the domain of `f` for which `f(x)` is maximised. +Return a value `x` from `domain` for which `f(x)` is maximised. If there are multiple maximal values for `f(x)` then the first one will be found. `domain` must be a non-empty iterable. @@ -1024,7 +1048,7 @@ argmax(itr) = findmax(itr)[2] """ argmin(f, domain) -Return a value `x` in the domain of `f` for which `f(x)` is minimised. +Return a value `x` from `domain` for which `f(x)` is minimised. If there are multiple minimal values for `f(x)` then the first one will be found. `domain` must be a non-empty iterable. @@ -1203,6 +1227,27 @@ function _any(f, itr, ::Colon) return anymissing ? missing : false end +# Specialized versions of any(f, ::Tuple) +# We fall back to the for loop implementation all elements have the same type or +# if the tuple is too large. +function any(f, itr::Tuple) + if itr isa NTuple || length(itr) > 32 + return _any(f, itr, :) + end + _any_tuple(f, false, itr...) +end + +@inline function _any_tuple(f, anymissing, x, rest...) + v = f(x) + if ismissing(v) + anymissing = true + elseif v + return true + end + return _any_tuple(f, anymissing, rest...) +end +@inline _any_tuple(f, anymissing) = anymissing ? missing : false + """ all(p, itr) -> Bool @@ -1253,6 +1298,29 @@ function _all(f, itr, ::Colon) return anymissing ? missing : true end +# Specialized versions of all(f, ::Tuple), +# This is similar to any(f, ::Tuple) defined above. +function all(f, itr::Tuple) + if itr isa NTuple || length(itr) > 32 + return _all(f, itr, :) + end + _all_tuple(f, false, itr...) +end + +@inline function _all_tuple(f, anymissing, x, rest...) + v = f(x) + if ismissing(v) + anymissing = true + # this syntax allows throwing a TypeError for non-Bool, for consistency with any + elseif v + nothing + else + return false + end + return _all_tuple(f, anymissing, rest...) +end +@inline _all_tuple(f, anymissing) = anymissing ? missing : true + ## count _bool(f) = x->f(x)::Bool @@ -1286,15 +1354,7 @@ count(itr; init=0) = count(identity, itr; init) count(f, itr; init=0) = _simple_count(f, itr, init) -_simple_count(pred, itr, init) = _simple_count_helper(Generator(pred, itr), init) - -function _simple_count_helper(g, init::T) where {T} - n::T = init - for x in g - n += x::Bool - end - return n -end +_simple_count(pred, itr, init) = sum(_bool(pred), itr; init) function _simple_count(::typeof(identity), x::Array{Bool}, init::T=0) where {T} n::T = init diff --git a/base/reducedim.jl b/base/reducedim.jl index d55db2768e62b..c1c58ccdfefed 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -44,7 +44,7 @@ function reduced_indices0(inds::Indices{N}, d::Int) where N end function reduced_indices(inds::Indices{N}, region) where N - rinds = [inds...] + rinds = collect(inds) for i in region isa(i, Integer) || throw(ArgumentError("reduced dimension(s) must be integers")) d = Int(i) @@ -58,7 +58,7 @@ function reduced_indices(inds::Indices{N}, region) where N end function reduced_indices0(inds::Indices{N}, region) where N - rinds = [inds...] + rinds = collect(inds) for i in region isa(i, Integer) || throw(ArgumentError("reduced dimension(s) must be integers")) d = Int(i) @@ -211,8 +211,8 @@ reducedim_init(f, op::typeof(|), A::AbstractArrayOrBroadcasted, region) = reduce let BitIntFloat = Union{BitInteger, IEEEFloat} T = Union{ - [AbstractArray{t} for t in uniontypes(BitIntFloat)]..., - [AbstractArray{Complex{t}} for t in uniontypes(BitIntFloat)]...} + Any[AbstractArray{t} for t in uniontypes(BitIntFloat)]..., + Any[AbstractArray{Complex{t}} for t in uniontypes(BitIntFloat)]...} global function reducedim_init(f, op::Union{typeof(+),typeof(add_sum)}, A::T, region) z = zero(f(zero(eltype(A)))) @@ -372,7 +372,7 @@ _mapreduce_dim(f, op, ::_InitialValue, A::AbstractArrayOrBroadcasted, dims) = mapreducedim!(f, op, reducedim_init(f, op, A, dims), A) """ - reduce(f, A; dims=:, [init]) + reduce(f, A::AbstractArray; dims=:, [init]) Reduce 2-argument function `f` along dimensions of `A`. `dims` is a vector specifying the dimensions to reduce, and the keyword argument `init` is the initial value to use in the @@ -525,6 +525,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. # Examples ```jldoctest @@ -1027,7 +1029,7 @@ end ##### findmin & findmax ##### # The initial values of Rval are not used if the corresponding indices in Rind are 0. # -function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} +function findminmax!(f, op, Rval, Rind, A::AbstractArray{T,N}) where {T,N} (isempty(Rval) || isempty(A)) && return Rval, Rind lsiz = check_reducedims(Rval, A) for i = 1:N @@ -1048,8 +1050,8 @@ function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} tmpRi = Rind[i1,IR] for i in axes(A,1) k, kss = y::Tuple - tmpAv = A[i,IA] - if tmpRi == zi || f(tmpRv, tmpAv) + tmpAv = f(A[i,IA]) + if tmpRi == zi || op(tmpRv, tmpAv) tmpRv = tmpAv tmpRi = k end @@ -1063,10 +1065,10 @@ function findminmax!(f, Rval, Rind, A::AbstractArray{T,N}) where {T,N} IR = Broadcast.newindex(IA, keep, Idefault) for i in axes(A, 1) k, kss = y::Tuple - tmpAv = A[i,IA] + tmpAv = f(A[i,IA]) tmpRv = Rval[i,IR] tmpRi = Rind[i,IR] - if tmpRi == zi || f(tmpRv, tmpAv) + if tmpRi == zi || op(tmpRv, tmpAv) Rval[i,IR] = tmpAv Rind[i,IR] = k end @@ -1086,7 +1088,7 @@ dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. """ function findmin!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; init::Bool=true) - findminmax!(isgreater, init && !isempty(A) ? fill!(rval, first(A)) : rval, fill!(rind,zero(eltype(keys(A)))), A) + findminmax!(identity, isgreater, init && !isempty(A) ? fill!(rval, first(A)) : rval, fill!(rind,zero(eltype(keys(A)))), A) end """ @@ -1110,16 +1112,40 @@ julia> findmin(A, dims=2) ``` """ findmin(A::AbstractArray; dims=:) = _findmin(A, dims) +_findmin(A, dims) = _findmin(identity, A, dims) -function _findmin(A, region) +""" + findmin(f, A; dims) -> (f(x), index) + +For an array input, returns the value in the codomain and index of the corresponding value +which minimize `f` over the given dimensions. + +# Examples +```jldoctest +julia> A = [-1.0 1; -0.5 2] +2×2 Matrix{Float64}: + -1.0 1.0 + -0.5 2.0 + +julia> findmin(abs2, A, dims=1) +([0.25 1.0], CartesianIndex{2}[CartesianIndex(2, 1) CartesianIndex(1, 2)]) + +julia> findmin(abs2, A, dims=2) +([1.0; 0.25;;], CartesianIndex{2}[CartesianIndex(1, 1); CartesianIndex(2, 1);;]) +``` +""" +findmin(f, A::AbstractArray; dims=:) = _findmin(f, A, dims) + +function _findmin(f, A, region) ri = reduced_indices0(A, region) if isempty(A) if prod(map(length, reduced_indices(A, region))) != 0 throw(ArgumentError("collection slices must be non-empty")) end - (similar(A, ri), zeros(eltype(keys(A)), ri)) + similar(A, promote_op(f, eltype(A)), ri), zeros(eltype(keys(A)), ri) else - findminmax!(isgreater, fill!(similar(A, ri), first(A)), + fA = f(first(A)) + findminmax!(f, isgreater, fill!(similar(A, _findminmax_inittype(f, A), ri), fA), zeros(eltype(keys(A)), ri), A) end end @@ -1133,7 +1159,7 @@ dimensions of `rval` and `rind`, and store the results in `rval` and `rind`. """ function findmax!(rval::AbstractArray, rind::AbstractArray, A::AbstractArray; init::Bool=true) - findminmax!(isless, init && !isempty(A) ? fill!(rval, first(A)) : rval, fill!(rind,zero(eltype(keys(A)))), A) + findminmax!(identity, isless, init && !isempty(A) ? fill!(rval, first(A)) : rval, fill!(rind,zero(eltype(keys(A)))), A) end """ @@ -1157,20 +1183,54 @@ julia> findmax(A, dims=2) ``` """ findmax(A::AbstractArray; dims=:) = _findmax(A, dims) +_findmax(A, dims) = _findmax(identity, A, dims) + +""" + findmax(f, A; dims) -> (f(x), index) -function _findmax(A, region) +For an array input, returns the value in the codomain and index of the corresponding value +which maximize `f` over the given dimensions. + +# Examples +```jldoctest +julia> A = [-1.0 1; -0.5 2] +2×2 Matrix{Float64}: + -1.0 1.0 + -0.5 2.0 + +julia> findmax(abs2, A, dims=1) +([1.0 4.0], CartesianIndex{2}[CartesianIndex(1, 1) CartesianIndex(2, 2)]) + +julia> findmax(abs2, A, dims=2) +([1.0; 4.0;;], CartesianIndex{2}[CartesianIndex(1, 1); CartesianIndex(2, 2);;]) +``` +""" +findmax(f, A::AbstractArray; dims=:) = _findmax(f, A, dims) + +function _findmax(f, A, region) ri = reduced_indices0(A, region) if isempty(A) if prod(map(length, reduced_indices(A, region))) != 0 throw(ArgumentError("collection slices must be non-empty")) end - similar(A, ri), zeros(eltype(keys(A)), ri) + similar(A, promote_op(f, eltype(A)), ri), zeros(eltype(keys(A)), ri) else - findminmax!(isless, fill!(similar(A, ri), first(A)), + fA = f(first(A)) + findminmax!(f, isless, fill!(similar(A, _findminmax_inittype(f, A), ri), fA), zeros(eltype(keys(A)), ri), A) end end +function _findminmax_inittype(f, A::AbstractArray) + T = _realtype(f, promote_union(eltype(A))) + v0 = f(first(A)) + # First conditional: T is >: typeof(v0), so return it + # Second conditional: handle missing specifically, as most often, f(missing) = missing; + # certainly, some predicate functions return Bool, but not all. + # Else, return the type of the transformation. + Tr = v0 isa T ? T : Missing <: eltype(A) ? Union{Missing, typeof(v0)} : typeof(v0) +end + reducedim1(R, A) = length(axes1(R)) == 1 """ diff --git a/base/reflection.jl b/base/reflection.jl index bf8ceabe1f39b..97f1ed14c6729 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -2,19 +2,6 @@ # name and module reflection -""" - nameof(m::Module) -> Symbol - -Get the name of a `Module` as a [`Symbol`](@ref). - -# Examples -```jldoctest -julia> nameof(Base.Broadcast) -:Broadcast -``` -""" -nameof(m::Module) = ccall(:jl_module_name, Ref{Symbol}, (Any,), m) - """ parentmodule(m::Module) -> Module @@ -100,7 +87,9 @@ since it is not idiomatic to explicitly export names from `Main`. See also: [`@locals`](@ref Base.@locals), [`@__MODULE__`](@ref). """ names(m::Module; all::Bool = false, imported::Bool = false) = - sort!(ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported)) + sort!(unsorted_names(m; all, imported)) +unsorted_names(m::Module; all::Bool = false, imported::Bool = false) = + ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported) isexported(m::Module, s::Symbol) = ccall(:jl_module_exports_p, Cint, (Any, Any), m, s) != 0 isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0 @@ -112,17 +101,10 @@ function binding_module(m::Module, s::Symbol) return unsafe_pointer_to_objref(p)::Module end -function resolve(g::GlobalRef; force::Bool=false) - if force || isbindingresolved(g.mod, g.name) - return GlobalRef(binding_module(g.mod, g.name), g.name) - end - return g -end - -const NamedTuple_typename = NamedTuple.body.body.name +const _NAMEDTUPLE_NAME = NamedTuple.body.body.name function _fieldnames(@nospecialize t) - if t.name === NamedTuple_typename + if t.name === _NAMEDTUPLE_NAME if t.parameters[1] isa Tuple return t.parameters[1] else @@ -194,13 +176,25 @@ fieldnames(t::Type{<:Tuple}) = ntuple(identity, fieldcount(t)) Return a boolean indicating whether `T` has `name` as one of its own fields. +See also [`fieldnames`](@ref), [`fieldcount`](@ref), [`hasproperty`](@ref). + !!! compat "Julia 1.2" This function requires at least Julia 1.2. + +# Examples +```jldoctest +julia> struct Foo + bar::Int + end + +julia> hasfield(Foo, :bar) +true + +julia> hasfield(Foo, :x) +false +``` """ -function hasfield(T::Type, name::Symbol) - @_pure_meta - return fieldindex(T, name, false) > 0 -end +hasfield(T::Type, name::Symbol) = fieldindex(T, name, false) > 0 """ nameof(t::DataType) -> Symbol @@ -253,6 +247,10 @@ Determine whether a global is declared `const` in a given module `m`. isconst(m::Module, s::Symbol) = ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0 +function isconst(g::GlobalRef) + return ccall(:jl_globalref_is_const, Cint, (Any,), g) != 0 +end + """ isconst(t::DataType, s::Union{Int,Symbol}) -> Bool @@ -275,6 +273,27 @@ function isconst(@nospecialize(t::Type), s::Int) return unsafe_load(Ptr{UInt32}(constfields), 1 + s÷32) & (1 << (s%32)) != 0 end +""" + isfieldatomic(t::DataType, s::Union{Int,Symbol}) -> Bool + +Determine whether a field `s` is declared `@atomic` in a given type `t`. +""" +function isfieldatomic(@nospecialize(t::Type), s::Symbol) + t = unwrap_unionall(t) + isa(t, DataType) || return false + return isfieldatomic(t, fieldindex(t, s, false)) +end +function isfieldatomic(@nospecialize(t::Type), s::Int) + t = unwrap_unionall(t) + # TODO: what to do for `Union`? + isa(t, DataType) || return false # uncertain + ismutabletype(t) || return false # immutable structs are never atomic + 1 <= s <= length(t.name.names) || return false # OOB reads are not atomic (they always throw) + atomicfields = t.name.atomicfields + atomicfields === C_NULL && return false + s -= 1 + return unsafe_load(Ptr{UInt32}(atomicfields), 1 + s÷32) & (1 << (s%32)) != 0 +end """ @locals() @@ -315,20 +334,12 @@ macro locals() return Expr(:locals) end -""" - objectid(x) -> UInt - -Get a hash value for `x` based on object identity. `objectid(x)==objectid(y)` if `x === y`. - -See also [`hash`](@ref), [`IdDict`](@ref). -""" -objectid(@nospecialize(x)) = ccall(:jl_object_id, UInt, (Any,), x) - # concrete datatype predicates datatype_fieldtypes(x::DataType) = ccall(:jl_get_fieldtypes, Core.SimpleVector, (Any,), x) struct DataTypeLayout + size::UInt32 nfields::UInt32 npointers::UInt32 firstptr::Int32 @@ -345,7 +356,7 @@ Memory allocation minimum alignment for instances of this type. Can be called on any `isconcretetype`. """ function datatype_alignment(dt::DataType) - @_pure_meta + @_foldable_meta dt.layout == C_NULL && throw(UndefRefError()) alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment return Int(alignment) @@ -362,16 +373,19 @@ LLT_ALIGN(x, sz) = (x + sz - 1) & -sz # amount of total space taken by T when stored in a container function aligned_sizeof(@nospecialize T::Type) - @_pure_meta - if isbitsunion(T) - _, sz, al = uniontype_layout(T) - return LLT_ALIGN(sz, al) + @_foldable_meta + if isa(T, Union) + if allocatedinline(T) + # NOTE this check is equivalent to `isbitsunion(T)`, we can improve type + # inference in the second branch with the outer `isa(T, Union)` check + _, sz, al = uniontype_layout(T) + return LLT_ALIGN(sz, al) + end elseif allocatedinline(T) al = datatype_alignment(T) return LLT_ALIGN(Core.sizeof(T), al) - else - return Core.sizeof(Ptr{Cvoid}) end + return Core.sizeof(Ptr{Cvoid}) end gc_alignment(sz::Integer) = Int(ccall(:jl_alignment, Cint, (Csize_t,), sz)) @@ -385,7 +399,7 @@ with no intervening padding bytes. Can be called on any `isconcretetype`. """ function datatype_haspadding(dt::DataType) - @_pure_meta + @_foldable_meta dt.layout == C_NULL && throw(UndefRefError()) flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags return flags & 1 == 1 @@ -398,7 +412,7 @@ Return the number of fields known to this datatype's layout. Can be called on any `isconcretetype`. """ function datatype_nfields(dt::DataType) - @_pure_meta + @_foldable_meta dt.layout == C_NULL && throw(UndefRefError()) return unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).nfields end @@ -410,7 +424,7 @@ Return whether instances of this type can contain references to gc-managed memor Can be called on any `isconcretetype`. """ function datatype_pointerfree(dt::DataType) - @_pure_meta + @_foldable_meta dt.layout == C_NULL && throw(UndefRefError()) npointers = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).npointers return npointers == 0 @@ -426,7 +440,7 @@ Can be called on any `isconcretetype`. See also [`fieldoffset`](@ref). """ function datatype_fielddesc_type(dt::DataType) - @_pure_meta + @_foldable_meta dt.layout == C_NULL && throw(UndefRefError()) flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags return (flags >> 1) & 3 @@ -479,8 +493,12 @@ end ismutable(v) -> Bool Return `true` if and only if value `v` is mutable. See [Mutable Composite Types](@ref) -for a discussion of immutability. Note that this function works on values, so if you give it -a type, it will tell you that a value of `DataType` is mutable. +for a discussion of immutability. Note that this function works on values, so if you +give it a `DataType`, it will tell you that a value of the type is mutable. + +!!! note + For technical reasons, `ismutable` returns `true` for values of certain special types + (for example `String` and `Symbol`) even though they cannot be mutated in a permissible way. See also [`isbits`](@ref), [`isstructtype`](@ref). @@ -496,8 +514,7 @@ true !!! compat "Julia 1.5" This function requires at least Julia 1.5. """ -ismutable(@nospecialize(x)) = (@_pure_meta; typeof(x).name.flags & 0x2 == 0x2) - +ismutable(@nospecialize(x)) = (@_total_meta; typeof(x).name.flags & 0x2 == 0x2) """ ismutabletype(T) -> Bool @@ -509,12 +526,12 @@ Determine whether type `T` was declared as a mutable type This function requires at least Julia 1.7. """ function ismutabletype(@nospecialize t) + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? return isa(t, DataType) && t.name.flags & 0x2 == 0x2 end - """ isstructtype(T) -> Bool @@ -522,27 +539,25 @@ Determine whether type `T` was declared as a struct type (i.e. using the `struct` or `mutable struct` keyword). """ function isstructtype(@nospecialize t) - @_pure_meta + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? isa(t, DataType) || return false - hasfield = !isdefined(t, :types) || !isempty(t.types) - return hasfield || (t.size == 0 && !isabstracttype(t)) + return !isprimitivetype(t) && !isabstracttype(t) end """ isprimitivetype(T) -> Bool Determine whether type `T` was declared as a primitive type -(i.e. using the `primitive` keyword). +(i.e. using the `primitive type` syntax). """ function isprimitivetype(@nospecialize t) - @_pure_meta + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? isa(t, DataType) || return false - hasfield = !isdefined(t, :types) || !isempty(t.types) - return !hasfield && t.size != 0 && !isabstracttype(t) + return (t.flags & 0x0080) == 0x0080 end """ @@ -568,14 +583,36 @@ julia> isbitstype(Complex) false ``` """ -isbitstype(@nospecialize t) = (@_pure_meta; isa(t, DataType) && (t.flags & 0x8) == 0x8) +isbitstype(@nospecialize t) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0008) == 0x0008) """ isbits(x) Return `true` if `x` is an instance of an [`isbitstype`](@ref) type. """ -isbits(@nospecialize x) = (@_pure_meta; typeof(x).flags & 0x8 == 0x8) +isbits(@nospecialize x) = isbitstype(typeof(x)) + +""" + objectid(x) -> UInt + +Get a hash value for `x` based on object identity. + +If `x === y` then `objectid(x) == objectid(y)`, and usually when `x !== y`, `objectid(x) != objectid(y)`. + +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) +end +_objectid(@nospecialize(x)) = ccall(:jl_object_id, UInt, (Any,), x) """ isdispatchtuple(T) @@ -584,7 +621,47 @@ Determine whether type `T` is a tuple "leaf type", meaning it could appear as a type signature in dispatch and has no subtypes (or supertypes) which could appear in a call. """ -isdispatchtuple(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && (t.flags & 0x4) == 0x4) +isdispatchtuple(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0004) == 0x0004) + +datatype_ismutationfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0100) == 0x0100) + +""" + ismutationfree(T) + +Determine whether type `T` is mutation free in the sense that no mutable memory +is reachable from this type (either in the type itself) or through any fields. +Note that the type itself need not be immutable. For example, an empty mutable +type is `ismutabletype`, but also `ismutationfree`. +""" +function ismutationfree(@nospecialize(t)) + t = unwrap_unionall(t) + if isa(t, DataType) + return datatype_ismutationfree(t) + elseif isa(t, Union) + return ismutationfree(t.a) && ismutationfree(t.b) + end + # TypeVar, etc. + return false +end + +datatype_isidentityfree(dt::DataType) = (@_total_meta; (dt.flags & 0x0200) == 0x0200) + +""" + isidentityfree(T) + +Determine whether type `T` is identity free in the sense that this type or any +reachable through its fields has non-content-based identity. +""" +function isidentityfree(@nospecialize(t)) + t = unwrap_unionall(t) + if isa(t, DataType) + return datatype_isidentityfree(t) + elseif isa(t, Union) + return isidentityfree(t.a) && isidentityfree(t.b) + end + # TypeVar, etc. + return false +end iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) isconcretedispatch(@nospecialize t) = isconcretetype(t) && !iskindtype(t) @@ -592,12 +669,14 @@ has_free_typevars(@nospecialize(t)) = ccall(:jl_has_free_typevars, Cint, (Any,), # equivalent to isa(v, Type) && isdispatchtuple(Tuple{v}) || v === Union{} # and is thus perhaps most similar to the old (pre-1.0) `isleaftype` query -const _TYPE_NAME = Type.body.name function isdispatchelem(@nospecialize v) return (v === Bottom) || (v === typeof(Bottom)) || isconcretedispatch(v) || - (isa(v, DataType) && v.name === _TYPE_NAME && !has_free_typevars(v)) # isType(v) + (isType(v) && !has_free_typevars(v)) end +const _TYPE_NAME = Type.body.name +isType(@nospecialize t) = isa(t, DataType) && t.name === _TYPE_NAME + """ isconcretetype(T) @@ -627,13 +706,13 @@ julia> isconcretetype(Union{Int,String}) false ``` """ -isconcretetype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && (t.flags & 0x2) == 0x2) +isconcretetype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x0002) == 0x0002) """ isabstracttype(T) Determine whether type `T` was declared as an abstract type -(i.e. using the `abstract` keyword). +(i.e. using the `abstract type` syntax). # Examples ```jldoctest @@ -645,7 +724,7 @@ false ``` """ function isabstracttype(@nospecialize(t)) - @_pure_meta + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? return isa(t, DataType) && (t.name.flags & 0x1) == 0x1 @@ -657,17 +736,17 @@ end Determine whether type `T` has exactly one possible instance; for example, a struct type with no fields. """ -issingletontype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && isdefined(t, :instance)) +issingletontype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && isdefined(t, :instance)) """ - typeintersect(T, S) + 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. """ -typeintersect(@nospecialize(a), @nospecialize(b)) = (@_pure_meta; ccall(:jl_type_intersection, Any, (Any, Any), a, b)) +typeintersect(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_intersection, Any, (Any, Any), a::Type, b::Type)) -morespecific(@nospecialize(a), @nospecialize(b)) = ccall(:jl_type_morespecific, Cint, (Any, Any), a, b) != 0 +morespecific(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_morespecific, Cint, (Any, Any), a::Type, b::Type) != 0) """ fieldoffset(type, i) @@ -695,7 +774,7 @@ julia> structinfo(Base.Filesystem.StatStruct) (0x0000000000000060, :ctime, Float64) ``` """ -fieldoffset(x::DataType, idx::Integer) = (@_pure_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx)) +fieldoffset(x::DataType, idx::Integer) = (@_foldable_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx)) """ fieldtype(T, name::Symbol | index::Int) @@ -741,18 +820,44 @@ julia> Base.fieldindex(Foo, :z, false) ``` """ function fieldindex(T::DataType, name::Symbol, err::Bool=true) + @_foldable_meta + @noinline return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1) end function fieldindex(t::UnionAll, name::Symbol, err::Bool=true) t = argument_datatype(t) if t === nothing - throw(ArgumentError("type does not have definite fields")) + err && throw(ArgumentError("type does not have definite fields")) + return 0 end return fieldindex(t, name, err) end -argument_datatype(@nospecialize t) = ccall(:jl_argument_datatype, Any, (Any,), t) +function argument_datatype(@nospecialize t) + @_total_meta + @noinline + return ccall(:jl_argument_datatype, Any, (Any,), t)::Union{Nothing,DataType} +end + +function datatype_fieldcount(t::DataType) + if t.name === _NAMEDTUPLE_NAME + names, types = t.parameters[1], t.parameters[2] + if names isa Tuple + return length(names) + end + if types isa DataType && types <: Tuple + return fieldcount(types) + end + return nothing + elseif isabstracttype(t) || (t.name === Tuple.name && isvatuple(t)) + return nothing + end + if isdefined(t, :types) + return length(t.types) + end + return length(t.name.names) +end """ fieldcount(t::Type) @@ -761,37 +866,23 @@ Get the number of fields that an instance of the given type would have. An error is thrown if the type is too abstract to determine this. """ function fieldcount(@nospecialize t) + @_foldable_meta if t isa UnionAll || t isa Union t = argument_datatype(t) if t === nothing throw(ArgumentError("type does not have a definite number of fields")) end - t = t::DataType - elseif t == Union{} + elseif t === Union{} throw(ArgumentError("The empty type does not have a well-defined number of fields since it does not have instances.")) end if !(t isa DataType) throw(TypeError(:fieldcount, DataType, t)) end - if t.name === NamedTuple_typename - names, types = t.parameters[1], t.parameters[2] - if names isa Tuple - return length(names) - end - if types isa DataType && types <: Tuple - return fieldcount(types) - end - abstr = true - else - abstr = isabstracttype(t) || (t.name === Tuple.name && isvatuple(t)) - end - if abstr + fcount = datatype_fieldcount(t) + if fcount === nothing throw(ArgumentError("type does not have a definite number of fields")) end - if isdefined(t, :types) - return length(t.types) - end - return length(t.name.names) + return fcount end """ @@ -813,7 +904,7 @@ julia> fieldtypes(Foo) (Int64, String) ``` """ -fieldtypes(T::Type) = ntupleany(i -> fieldtype(T, i), fieldcount(T)) +fieldtypes(T::Type) = (@_foldable_meta; ntupleany(i -> fieldtype(T, i), fieldcount(T))) # return all instances, for types that can be enumerated @@ -838,9 +929,9 @@ function to_tuple_type(@nospecialize(t)) t = Tuple{t...} end if isa(t, Type) && t <: Tuple - for p in unwrap_unionall(t).parameters + for p in (unwrap_unionall(t)::DataType).parameters if isa(p, Core.TypeofVararg) - p = p.T + p = unwrapva(p) end if !(isa(p, Type) || isa(p, TypeVar)) error("argument tuple type must contain only types") @@ -852,14 +943,11 @@ function to_tuple_type(@nospecialize(t)) t end -function signature_type(@nospecialize(f), @nospecialize(args)) - f_type = isa(f, Type) ? Type{f} : typeof(f) - if isa(args, Type) - u = unwrap_unionall(args) - return rewrap_unionall(Tuple{f_type, u.parameters...}, args) - else - return Tuple{f_type, args...} - end +function signature_type(@nospecialize(f), @nospecialize(argtypes)) + argtypes = to_tuple_type(argtypes) + ft = Core.Typeof(f) + u = unwrap_unionall(argtypes)::DataType + return rewrap_unionall(Tuple{ft, u.parameters...}, argtypes) end """ @@ -887,10 +975,11 @@ function code_lowered(@nospecialize(f), @nospecialize(t=Tuple); generated::Bool= if debuginfo !== :source && debuginfo !== :none throw(ArgumentError("'debuginfo' must be either :source or :none")) end - return map(method_instances(f, t)) do m + world = get_world_counter() + return map(method_instances(f, t, world)) do m if generated && hasgenerator(m) if may_invoke_generator(m) - return ccall(:jl_code_for_staged, Any, (Any,), m)::CodeInfo + return 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 ", @@ -927,7 +1016,7 @@ function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing return _methods_by_ftype(t, mt, lim, world, false, RefValue{UInt}(typemin(UInt)), RefValue{UInt}(typemax(UInt)), Ptr{Int32}(C_NULL)) end function _methods_by_ftype(@nospecialize(t), mt::Union{Core.MethodTable, Nothing}, lim::Int, world::UInt, ambig::Bool, min::Ref{UInt}, max::Ref{UInt}, has_ambig::Ref{Int32}) - return ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, mt, lim, ambig, world, min, max, has_ambig)::Union{Array{Any,1}, Bool} + return ccall(:jl_matching_methods, Any, (Any, Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, mt, lim, ambig, world, min, max, has_ambig)::Union{Vector{Any},Nothing} end # high-level, more convenient method lookup functions @@ -965,13 +1054,12 @@ See also: [`which`](@ref) and `@which`. """ function methods(@nospecialize(f), @nospecialize(t), mod::Union{Tuple{Module},AbstractArray{Module},Nothing}=nothing) - t = to_tuple_type(t) world = get_world_counter() # Lack of specialization => a comprehension triggers too many invalidations via _collect, so collect the methods manually ms = Method[] for m in _methods(f, t, -1, world)::Vector m = m::Core.MethodMatch - (mod === nothing || m.method.module ∈ mod) && push!(ms, m.method) + (mod === nothing || parentmodule(m.method) ∈ mod) && push!(ms, m.method) end MethodList(ms, typeof(f).name.mt) end @@ -980,6 +1068,8 @@ methods(@nospecialize(f), @nospecialize(t), mod::Module) = methods(f, t, (mod,)) function methods_including_ambiguous(@nospecialize(f), @nospecialize(t)) tt = signature_type(f, t) world = get_world_counter() + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") min = RefValue{UInt}(typemin(UInt)) max = RefValue{UInt}(typemax(UInt)) ms = _methods_by_ftype(tt, nothing, -1, world, true, min, max, Ptr{Int32}(C_NULL))::Vector @@ -997,29 +1087,31 @@ function visit(f, mt::Core.MethodTable) nothing end function visit(f, mc::Core.TypeMapLevel) - if mc.targ !== nothing - e = mc.targ::Vector{Any} + function avisit(f, e::Array{Any,1}) for i in 2:2:length(e) - isassigned(e, i) && visit(f, e[i]) + isassigned(e, i) || continue + ei = e[i] + if ei isa Vector{Any} + for j in 2:2:length(ei) + isassigned(ei, j) || continue + visit(f, ei[j]) + end + else + visit(f, ei) + end end end + if mc.targ !== nothing + avisit(f, mc.targ::Vector{Any}) + end if mc.arg1 !== nothing - e = mc.arg1::Vector{Any} - for i in 2:2:length(e) - isassigned(e, i) && visit(f, e[i]) - end + avisit(f, mc.arg1::Vector{Any}) end if mc.tname !== nothing - e = mc.tname::Vector{Any} - for i in 2:2:length(e) - isassigned(e, i) && visit(f, e[i]) - end + avisit(f, mc.tname::Vector{Any}) end if mc.name1 !== nothing - e = mc.name1::Vector{Any} - for i in 2:2:length(e) - isassigned(e, i) && visit(f, e[i]) - end + avisit(f, mc.name1::Vector{Any}) end mc.list !== nothing && visit(f, mc.list) mc.any !== nothing && visit(f, mc.any) @@ -1032,6 +1124,34 @@ function visit(f, d::Core.TypeMapEntry) end nothing end +struct MethodSpecializations + specializations::Union{Nothing, Core.MethodInstance, Core.SimpleVector} +end +""" + specializations(m::Method) → itr + +Return an iterator `itr` of all compiler-generated specializations of `m`. +""" +specializations(m::Method) = MethodSpecializations(isdefined(m, :specializations) ? m.specializations : nothing) +function iterate(specs::MethodSpecializations) + s = specs.specializations + s === nothing && return nothing + isa(s, Core.MethodInstance) && return (s, nothing) + return iterate(specs, 0) +end +iterate(specs::MethodSpecializations, ::Nothing) = nothing +function iterate(specs::MethodSpecializations, i::Int) + s = specs.specializations::Core.SimpleVector + n = length(s) + i >= n && return nothing + item = nothing + while i < n && item === nothing + item = s[i+=1] + end + item === nothing && return nothing + return (item, i) +end +length(specs::MethodSpecializations) = count(Returns(true), specs) function length(mt::Core.MethodTable) n = 0 @@ -1046,15 +1166,17 @@ uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m, m.sourc 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::Array{UInt8,1}) = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), m, C_NULL, s)::CodeInfo -_uncompressed_ir(ci::Core.CodeInstance, s::Array{UInt8,1}) = ccall(:jl_uncompress_ir, Any, (Any, Any, Any), ci.def.def::Method, ci, s)::CodeInfo +_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 # for backwards compat const uncompressed_ast = uncompressed_ir const _uncompressed_ast = _uncompressed_ir -function method_instances(@nospecialize(f), @nospecialize(t), world::UInt=get_world_counter()) +function method_instances(@nospecialize(f), @nospecialize(t), world::UInt) tt = signature_type(f, t) results = Core.MethodInstance[] + # 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) push!(results, instance) @@ -1071,6 +1193,7 @@ struct CodegenParams prefer_specsig::Cint gnu_pubnames::Cint debug_info_kind::Cint + safepoint_on_entry::Cint lookup::Ptr{Cvoid} @@ -1079,12 +1202,14 @@ struct CodegenParams function CodegenParams(; track_allocations::Bool=true, code_coverage::Bool=true, prefer_specsig::Bool=false, gnu_pubnames=true, debug_info_kind::Cint = default_debug_info_kind(), - lookup::Ptr{Cvoid}=cglobal(:jl_rettype_inferred), + safepoint_on_entry::Bool=true, + lookup::Ptr{Cvoid}=unsafe_load(cglobal(:jl_rettype_inferred_addr, Ptr{Cvoid})), generic_context = nothing) return new( Cint(track_allocations), Cint(code_coverage), Cint(prefer_specsig), Cint(gnu_pubnames), debug_info_kind, + Cint(safepoint_on_entry), lookup, generic_context) end end @@ -1093,10 +1218,10 @@ const SLOT_USED = 0x8 ast_slotflag(@nospecialize(code), i) = ccall(:jl_ir_slotflag, UInt8, (Any, Csize_t), code, i - 1) """ - may_invoke_generator(method, atype, sparams) + may_invoke_generator(method, atype, sparams) -> Bool Computes whether or not we may invoke the generator for the given `method` on -the given atype and sparams. For correctness, all generated function are +the given `atype` and `sparams`. For correctness, all generated function are required to return monotonic answers. However, since we don't expect users to be able to successfully implement this criterion, we only call generated functions on concrete types. The one exception to this is that we allow calling @@ -1107,8 +1232,8 @@ computes whether we are in either of these cases. Unlike normal functions, the compilation heuristics still can't generate good dispatch in some cases, but this may still allow inference not to fall over in some limited cases. """ -function may_invoke_generator(method::MethodInstance) - return may_invoke_generator(method.def::Method, method.specTypes, method.sparam_vals) +function may_invoke_generator(mi::MethodInstance) + return may_invoke_generator(mi.def::Method, mi.specTypes, mi.sparam_vals) end function may_invoke_generator(method::Method, @nospecialize(atype), sparams::SimpleVector) # If we have complete information, we may always call the generator @@ -1122,31 +1247,46 @@ function may_invoke_generator(method::Method, @nospecialize(atype), sparams::Sim # generator only has one method generator = method.generator isa(generator, Core.GeneratedFunctionStub) || return false - gen_mthds = methods(generator.gen)::MethodList - length(gen_mthds) == 1 || return false + gen_mthds = _methods_by_ftype(Tuple{typeof(generator.gen), Vararg{Any}}, 1, method.primary_world) + (gen_mthds isa Vector && length(gen_mthds) == 1) || return false - generator_method = first(gen_mthds) + generator_method = first(gen_mthds).method nsparams = length(sparams) isdefined(generator_method, :source) || return false code = generator_method.source nslots = ccall(:jl_ir_nslots, Int, (Any,), code) - at = unwrap_unionall(atype)::DataType + at = unwrap_unionall(atype) + at isa DataType || return false (nslots >= 1 + length(sparams) + length(at.parameters)) || return false + firstarg = 1 for i = 1:nsparams if isa(sparams[i], TypeVar) - if (ast_slotflag(code, 1 + i) & SLOT_USED) != 0 + if (ast_slotflag(code, firstarg + i) & SLOT_USED) != 0 return false end end end - for i = 1:length(at.parameters) + nargs = Int(method.nargs) + non_va_args = method.isva ? nargs - 1 : nargs + for i = 1:non_va_args if !isdispatchelem(at.parameters[i]) - if (ast_slotflag(code, 1 + i + nsparams) & SLOT_USED) != 0 + if (ast_slotflag(code, firstarg + i + nsparams) & SLOT_USED) != 0 return false end end end + if method.isva + # If the va argument is used, we need to ensure that all arguments that + # contribute to the va tuple are dispatchelemes + if (ast_slotflag(code, firstarg + nargs + nsparams) & SLOT_USED) != 0 + for i = (non_va_args+1):length(at.parameters) + if !isdispatchelem(at.parameters[i]) + return false + end + end + end + end return true end @@ -1202,13 +1342,7 @@ function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f)); if isa(f, Core.OpaqueClosure) return code_typed_opaque_closure(f; optimize, debuginfo, interp) end - ft = Core.Typeof(f) - if isa(types, Type) - u = unwrap_unionall(types) - tt = rewrap_unionall(Tuple{ft, u.parameters...}, types) - else - tt = Tuple{ft, types...} - end + tt = signature_type(f, types) return code_typed_by_type(tt; optimize, debuginfo, world, interp) end @@ -1235,7 +1369,8 @@ function code_typed_by_type(@nospecialize(tt::Type); debuginfo::Symbol=:default, world = get_world_counter(), interp = Core.Compiler.NativeInterpreter(world)) - ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") if @isdefined(IRShow) debuginfo = IRShow.debuginfo(debuginfo) elseif debuginfo === :default @@ -1261,33 +1396,239 @@ function code_typed_by_type(@nospecialize(tt::Type); return asts end -function code_typed_opaque_closure(@nospecialize(closure::Core.OpaqueClosure); - optimize=true, - debuginfo::Symbol=:default, - interp = Core.Compiler.NativeInterpreter(closure.world)) +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") - m = closure.source + m = oc.source if isa(m, Method) code = _uncompressed_ir(m, m.source) debuginfo === :none && remove_linenums!(code) - return Any[(code => code.rettype)] + # intersect the declared return type and the inferred return type (if available) + rt = typeintersect(code.rettype, typeof(oc).parameters[2]) + return Any[code => rt] else error("encountered invalid Core.OpaqueClosure object") end end -function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)), interp=Core.Compiler.NativeInterpreter()) - ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") - types = to_tuple_type(types) - rt = [] - world = get_world_counter() +""" + code_ircode(f, [types]) + +Return an array of pairs of `IRCode` and inferred return type if type inference succeeds. +The `Method` is included instead of `IRCode` otherwise. + +See also: [`code_typed`](@ref) + +# Internal Keyword Arguments + +This section should be considered internal, and is only for who understands Julia compiler +internals. + +- `world=Base.get_world_counter()`: optional, controls the world age to use when looking up + methods, use current world age if not specified. +- `interp=Core.Compiler.NativeInterpreter(world)`: optional, controls the interpreter to + use, use the native interpreter Julia uses if not specified. +- `optimize_until`: optional, controls the optimization passes to run. If it is a string, + it specifies the name of the pass up to which the optimizer is run. If it is an integer, + it specifies the number of passes to run. If it is `nothing` (default), all passes are + run. + +# Example + +One can put the argument types in a tuple to get the corresponding `code_ircode`. + +```julia +julia> Base.code_ircode(+, (Float64, Int64)) +1-element Vector{Any}: + 388 1 ─ %1 = Base.sitofp(Float64, _3)::Float64 + │ %2 = Base.add_float(_2, %1)::Float64 + └── return %2 + => Float64 + +julia> Base.code_ircode(+, (Float64, Int64); optimize_until = "compact 1") +1-element Vector{Any}: + 388 1 ─ %1 = Base.promote(_2, _3)::Tuple{Float64, Float64} + │ %2 = Core._apply_iterate(Base.iterate, Base.:+, %1)::Float64 + └── return %2 + => Float64 +``` +""" +function code_ircode( + @nospecialize(f), + @nospecialize(types = default_tt(f)); + world = get_world_counter(), + interp = Core.Compiler.NativeInterpreter(world), + optimize_until::Union{Integer,AbstractString,Nothing} = nothing, +) + if isa(f, Core.OpaqueClosure) + error("OpaqueClosure not supported") + end + tt = signature_type(f, types) + return code_ircode_by_type(tt; world, interp, optimize_until) +end + +""" + code_ircode_by_type(types::Type{<:Tuple}; ...) + +Similar to [`code_ircode`](@ref), except the argument is a tuple type describing +a full signature to query. +""" +function code_ircode_by_type( + @nospecialize(tt::Type); + world = get_world_counter(), + interp = Core.Compiler.NativeInterpreter(world), + optimize_until::Union{Integer,AbstractString,Nothing} = nothing, +) + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + tt = to_tuple_type(tt) + matches = _methods_by_ftype(tt, -1, world)::Vector + asts = [] + for match in matches + match = match::Core.MethodMatch + meth = func_for_method_checked(match.method, tt, match.sparams) + (code, ty) = Core.Compiler.typeinf_ircode( + interp, + meth, + match.spec_types, + match.sparams, + optimize_until, + ) + if code === nothing + push!(asts, meth => Any) + else + push!(asts, code => ty) + end + end + return asts +end + + +""" + Base.return_types(f::Function, types::DataType=default_tt(f); + world::UInt=get_world_counter(), interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) + +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 + +```julia +julia> Base.return_types(sum, Tuple{Vector{Int}}) +1-element Vector{Any}: + Int64 + +julia> methods(sum, (Union{Vector{Int},UnitRange{Int}},)) +# 2 methods for generic function "sum" from Base: + [1] sum(r::AbstractRange{<:Real}) + @ range.jl:1396 + [2] sum(a::AbstractArray; dims, kw...) + @ reducedim.jl:996 + +julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) +2-element Vector{Any}: + Int64 # the result of inference on sum(r::AbstractRange{<:Real}) + Int64 # the result of inference on sum(a::AbstractArray; dims, kw...) +``` + +!!! warning + The `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 = get_world_counter(), + interp = Core.Compiler.NativeInterpreter(world)) + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + if isa(f, Core.OpaqueClosure) + _, rt = only(code_typed_opaque_closure(f)) + 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)] + end + rts = [] for match in _methods(f, types, -1, world)::Vector match = match::Core.MethodMatch meth = func_for_method_checked(match.method, types, match.sparams) ty = Core.Compiler.typeinf_type(interp, meth, match.spec_types, match.sparams) - push!(rt, something(ty, Any)) + push!(rts, something(ty, Any)) end - return rt + return rts +end + +""" + infer_effects(f, types=default_tt(f); world=get_world_counter(), interp=Core.Compiler.NativeInterpreter(world)) + +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. + +# 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`. + +# Returns +- `effects::Effects`: The computed effects of the function call. + +# Example + +```julia +julia> function foo(x) + y = x * 2 + return y + end; + +julia> effects = Base.infer_effects(foo, (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. + +!!! warning + The `infer_effects` function should not be used from generated functions; + doing so will result in an error. + +# See Also +- [`Core.Compiler.Effects`](@ref): A type representing the computational effects of a method call. +- [`Base.@assume_effects`](@ref): A macro for making assumptions about the effects of a method. +""" +function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); + world = get_world_counter(), + interp = Core.Compiler.NativeInterpreter(world)) + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") + if isa(f, Core.Builtin) + types = to_tuple_type(types) + argtypes = Any[Core.Compiler.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) + end + tt = signature_type(f, types) + result = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) + if result === missing + # unanalyzable call, return the unknown effects + return Core.Compiler.Effects() + end + (; matches) = result + effects = Core.Compiler.EFFECTS_TOTAL + if matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.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 + for match in matches.matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, + match.method, match.spec_types, match.sparams, #=run_optimizer=#false) + frame === nothing && return Core.Compiler.Effects() + effects = Core.Compiler.merge_effects(effects, frame.ipo_effects) + end + return effects end """ @@ -1317,7 +1658,8 @@ function print_statement_costs(io::IO, @nospecialize(tt::Type); else empty!(cst) resize!(cst, length(code.code)) - maxcost = Core.Compiler.statement_costs!(cst, code.code, code, Any[match.sparams...], false, params) + sptypes = Core.Compiler.VarState[Core.Compiler.VarState(sp, false) for sp in match.sparams] + maxcost = Core.Compiler.statement_costs!(cst, code.code, code, sptypes, false, params) nd = ndigits(maxcost) irshow_config = IRShow.IRShowConfig() do io, linestart, idx print(io, idx > 0 ? lpad(cst[idx], nd+1) : " "^(nd+1), " ") @@ -1331,16 +1673,24 @@ end print_statement_costs(args...; kwargs...) = print_statement_costs(stdout, args...; kwargs...) -function _which(@nospecialize(tt::Type), world=get_world_counter()) - min_valid = RefValue{UInt}(typemin(UInt)) - max_valid = RefValue{UInt}(typemax(UInt)) - match = ccall(:jl_gf_invoke_lookup_worlds, Any, - (Any, UInt, Ptr{Csize_t}, Ptr{Csize_t}), - tt, world, min_valid, max_valid) +function _which(@nospecialize(tt::Type); + method_table::Union{Nothing,Core.MethodTable,Core.Compiler.MethodTableView}=nothing, + world::UInt=get_world_counter(), + raise::Bool=true) + world == typemax(UInt) && error("code reflection cannot be used from generated functions") + if method_table === nothing + table = Core.Compiler.InternalMethodTable(world) + elseif method_table isa Core.MethodTable + table = Core.Compiler.OverlayMethodTable(world, method_table) + else + table = method_table + end + match, = Core.Compiler.findsup(tt, table) if match === nothing - error("no unique matching method found for the specified argument types") + raise && error("no unique matching method found for the specified argument types") + return nothing end - return match::Core.MethodMatch + return match end """ @@ -1353,7 +1703,6 @@ If `types` is an abstract type, then the method that would be called by `invoke` See also: [`parentmodule`](@ref), and `@which` and `@edit` in [`InteractiveUtils`](@ref man-interactive-utils). """ function which(@nospecialize(f), @nospecialize(t)) - t = to_tuple_type(t) tt = signature_type(f, t) return which(tt) end @@ -1414,16 +1763,27 @@ parentmodule(f::Function) = parentmodule(typeof(f)) """ parentmodule(f::Function, types) -> Module -Determine the module containing a given definition of a generic function. +Determine the module containing the first method of a generic function `f` matching +the specified `types`. """ function parentmodule(@nospecialize(f), @nospecialize(types)) m = methods(f, types) if isempty(m) error("no matching methods") end - return first(m).module + return parentmodule(first(m)) end +""" + parentmodule(m::Method) -> Module + +Return the module in which the given method `m` is defined. + +!!! compat "Julia 1.9" + Passing a `Method` as an argument requires Julia 1.9 or later. +""" +parentmodule(m::Method) = m.module + """ hasmethod(f, t::Type{<:Tuple}[, kwnames]; world=get_world_counter()) -> Bool @@ -1460,21 +1820,30 @@ julia> hasmethod(g, Tuple{}, (:a, :b, :c, :d)) # g accepts arbitrary kwargs true ``` """ -function hasmethod(@nospecialize(f), @nospecialize(t); world::UInt=get_world_counter()) - t = to_tuple_type(t) - t = signature_type(f, t) - return ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), t, world) !== nothing +function hasmethod(@nospecialize(f), @nospecialize(t)) + return Core._hasmethod(f, t isa Type ? t : to_tuple_type(t)) +end + +function Core.kwcall(kwargs::NamedTuple, ::typeof(hasmethod), @nospecialize(f), @nospecialize(t)) + world = kwargs.world::UInt # make sure this is the only local, to avoid confusing kwarg_decl() + return ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), signature_type(f, t), nothing, world) !== nothing end -function hasmethod(@nospecialize(f), @nospecialize(t), kwnames::Tuple{Vararg{Symbol}}; world::UInt=get_world_counter()) - # TODO: this appears to be doing the wrong queries - hasmethod(f, t, world=world) || return false - isempty(kwnames) && return true - m = which(f, t) - kws = kwarg_decl(m) +function hasmethod(f, t, kwnames::Tuple{Vararg{Symbol}}; world::UInt=get_world_counter()) + @nospecialize + isempty(kwnames) && return hasmethod(f, t; world) + t = to_tuple_type(t) + ft = Core.Typeof(f) + u = unwrap_unionall(t)::DataType + tt = rewrap_unionall(Tuple{typeof(Core.kwcall), NamedTuple, ft, u.parameters...}, t) + match = ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tt, nothing, world) + match === nothing && return false + kws = ccall(:jl_uncompress_argnames, Array{Symbol,1}, (Any,), (match::Method).slot_syms) + isempty(kws) && return true # some kwfuncs simply forward everything directly for kw in kws endswith(String(kw), "...") && return true end + kwnames = Symbol[kwnames[i] for i in 1:length(kwnames)] return issubset(kwnames, kws) end @@ -1486,41 +1855,41 @@ as written, called after all missing keyword-arguments have been assigned defaul `basemethod` is the method you obtain via [`which`](@ref) or [`methods`](@ref). """ function bodyfunction(basemethod::Method) - function getsym(arg) - isa(arg, Symbol) && return arg - isa(arg, GlobalRef) && return arg.name - return nothing - end - - fmod = basemethod.module + fmod = parentmodule(basemethod) # The lowered code for `basemethod` should look like # %1 = mkw(kwvalues..., #self#, args...) # return %1 # where `mkw` is the name of the "active" keyword body-function. ast = uncompressed_ast(basemethod) - f = nothing if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 callexpr = ast.code[end-1] - if isa(callexpr, Expr) && callexpr.head == :call + if isa(callexpr, Expr) && callexpr.head === :call fsym = callexpr.args[1] - if isa(fsym, Symbol) - f = getfield(fmod, fsym) - elseif isa(fsym, GlobalRef) - newsym = nothing - if fsym.mod === Core && fsym.name === :_apply - newsym = getsym(callexpr.args[2]) - elseif fsym.mod === Core && fsym.name === :_apply_iterate - newsym = getsym(callexpr.args[3]) - end - if isa(newsym, Symbol) - f = getfield(basemethod.module, newsym)::Function + while true + if isa(fsym, Symbol) + return getfield(fmod, fsym) + elseif isa(fsym, GlobalRef) + if fsym.mod === Core && fsym.name === :_apply + fsym = callexpr.args[2] + elseif fsym.mod === Core && fsym.name === :_apply_iterate + fsym = callexpr.args[3] + end + if isa(fsym, Symbol) + return getfield(fmod, fsym)::Function + elseif isa(fsym, GlobalRef) + return getfield(fsym.mod, fsym.name)::Function + elseif isa(fsym, Core.SSAValue) + fsym = ast.code[fsym.id] + else + return nothing + end else - f = getfield(fsym.mod, fsym.name)::Function + return nothing end end end end - return f + return nothing end """ @@ -1534,7 +1903,7 @@ Alternatively, in isolation `m1` and `m2` might be ordered, but if a third method cannot be sorted with them, they may cause an ambiguity together. For parametric types, the `ambiguous_bottom` keyword argument controls whether -`Union{}` counts as an ambiguous intersection of type parameters – when `true`, +`Union{}` counts as an ambiguous intersection of type parameters – when `true`, it is considered ambiguous, when `false` it is not. # Examples @@ -1578,7 +1947,9 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) end end # if ml-matches reported the existence of an ambiguity over their - # intersection, see if both m1 and m2 may be involved in it + # intersection, see if both m1 and m2 seem to be involved in it + # (if one was fully dominated by a different method, we want to will + # report the other ambiguous pair) have_m1 = have_m2 = false for match in ms match = match::Core.MethodMatch @@ -1603,18 +1974,14 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) minmax = m end end - if minmax === nothing + if minmax === nothing || minmax == m1 || minmax == m2 return true end for match in ms m = match.method m === minmax && continue - if match.fully_covers - if !morespecific(minmax.sig, m.sig) - return true - end - else - if morespecific(m.sig, minmax.sig) + if !morespecific(minmax.sig, m.sig) + if match.fully_covers || !morespecific(m.sig, minmax.sig) return true end end @@ -1631,12 +1998,12 @@ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) if ti2 <: m1.sig && ti2 <: m2.sig ti = ti2 elseif ti != ti2 - # TODO: this would be the correct way to handle this case, but + # TODO: this would be the more correct way to handle this case, but # people complained so we don't do it - # inner(ti2) || return false - return false # report that the type system failed to decide if it was ambiguous by saying they definitely aren't + #inner(ti2) || return false # report that the type system failed to decide if it was ambiguous by saying they definitely are + return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not else - return false # report that the type system failed to decide if it was ambiguous by saying they definitely aren't + return false # report that the type system failed to decide if it was ambiguous by saying they definitely are not end end inner(ti) || return false @@ -1654,7 +2021,7 @@ function delete_method(m::Method) end function get_methodtable(m::Method) - return ccall(:jl_method_table_for, Any, (Any,), m.sig)::Core.MethodTable + return ccall(:jl_method_get_table, Any, (Any,), m)::Core.MethodTable end """ @@ -1690,7 +2057,7 @@ as well to get the properties of an instance of the type. `propertynames(x)` may return only "public" property names that are part of the documented interface of `x`. If you want it to also return "private" -fieldnames intended for internal use, pass `true` for the optional second argument. +property names intended for internal use, pass `true` for the optional second argument. REPL tab completion on `x.` shows only the `private=false` properties. See also: [`hasproperty`](@ref), [`hasfield`](@ref). @@ -1714,21 +2081,67 @@ hasproperty(x, s::Symbol) = s in propertynames(x) """ @invoke f(arg::T, ...; kwargs...) -Provides a convenient way to call [`invoke`](@ref); -`@invoke f(arg1::T1, arg2::T2; kwargs...)` will be expanded into `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`. -When an argument's type annotation is omitted, it's specified as `Any` argument, e.g. -`@invoke f(arg1::T, arg2)` will be expanded into `invoke(f, Tuple{T,Any}, arg1, arg2)`. +Provides a convenient way to call [`invoke`](@ref) by expanding +`@invoke f(arg1::T1, arg2::T2; kwargs...)` to `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`. +When an argument's type annotation is omitted, it's replaced with `Core.Typeof` that argument. +To invoke a method where an argument is untyped or explicitly typed as `Any`, annotate the +argument with `::Any`. + +It also supports the following syntax: +- `@invoke (x::X).f` expands to `invoke(getproperty, Tuple{X,Symbol}, x, :f)` +- `@invoke (x::X).f = v::V` expands to `invoke(setproperty!, Tuple{X,Symbol,V}, x, :f, v)` +- `@invoke (xs::Xs)[i::I]` expands to `invoke(getindex, Tuple{Xs,I}, xs, i)` +- `@invoke (xs::Xs)[i::I] = v::V` expands to `invoke(setindex!, Tuple{Xs,V,I}, xs, v, i)` + +# Examples + +```jldoctest +julia> @macroexpand @invoke f(x::T, y) +:(Core.invoke(f, Tuple{T, Core.Typeof(y)}, x, y)) + +julia> @invoke 420::Integer % Unsigned +0x00000000000001a4 + +julia> @macroexpand @invoke (x::X).f +:(Core.invoke(Base.getproperty, Tuple{X, Core.Typeof(:f)}, x, :f)) + +julia> @macroexpand @invoke (x::X).f = v::V +:(Core.invoke(Base.setproperty!, Tuple{X, Core.Typeof(:f), V}, x, :f, v)) + +julia> @macroexpand @invoke (xs::Xs)[i::I] +:(Core.invoke(Base.getindex, Tuple{Xs, I}, xs, i)) + +julia> @macroexpand @invoke (xs::Xs)[i::I] = v::V +:(Core.invoke(Base.setindex!, Tuple{Xs, V, I}, xs, v, i)) +``` !!! compat "Julia 1.7" This macro requires Julia 1.7 or later. + +!!! compat "Julia 1.9" + This macro is exported as of Julia 1.9. + +!!! compat "Julia 1.10" + The additional syntax is supported as of Julia 1.10. """ macro invoke(ex) - f, args, kwargs = destructure_callex(ex) - arg2typs = map(args) do x - isexpr(x, :(::)) ? (x.args...,) : (x, GlobalRef(Core, :Any)) + topmod = Core.Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally + f, args, kwargs = destructure_callex(topmod, ex) + types = Expr(:curly, :Tuple) + out = Expr(:call, GlobalRef(Core, :invoke)) + isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) + push!(out.args, f) + push!(out.args, types) + for arg in args + if isexpr(arg, :(::)) + push!(out.args, arg.args[1]) + push!(types.args, arg.args[2]) + else + push!(out.args, arg) + push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), arg)) + end end - args, argtypes = first.(arg2typs), last.(arg2typs) - return esc(:($(GlobalRef(Core, :invoke))($(f), Tuple{$(argtypes...)}, $(args...); $(kwargs...)))) + return esc(out) end """ @@ -1738,29 +2151,90 @@ Provides a convenient way to call [`Base.invokelatest`](@ref). `@invokelatest f(args...; kwargs...)` will simply be expanded into `Base.invokelatest(f, args...; kwargs...)`. +It also supports the following syntax: +- `@invokelatest x.f` expands to `Base.invokelatest(getproperty, x, :f)` +- `@invokelatest x.f = v` expands to `Base.invokelatest(setproperty!, x, :f, v)` +- `@invokelatest xs[i]` expands to `invoke(getindex, xs, i)` +- `@invokelatest xs[i] = v` expands to `invoke(setindex!, xs, v, i)` + +```jldoctest +julia> @macroexpand @invokelatest f(x; kw=kwv) +:(Base.invokelatest(f, x; kw = kwv)) + +julia> @macroexpand @invokelatest x.f +:(Base.invokelatest(Base.getproperty, x, :f)) + +julia> @macroexpand @invokelatest x.f = v +:(Base.invokelatest(Base.setproperty!, x, :f, v)) + +julia> @macroexpand @invokelatest xs[i] +:(Base.invokelatest(Base.getindex, xs, i)) + +julia> @macroexpand @invokelatest xs[i] = v +:(Base.invokelatest(Base.setindex!, xs, v, i)) +``` + !!! compat "Julia 1.7" This macro requires Julia 1.7 or later. + +!!! compat "Julia 1.10" + The additional syntax is supported as of Julia 1.10. """ macro invokelatest(ex) - f, args, kwargs = destructure_callex(ex) - return esc(:($(GlobalRef(@__MODULE__, :invokelatest))($(f), $(args...); $(kwargs...)))) -end - -function destructure_callex(ex) - isexpr(ex, :call) || throw(ArgumentError("a call expression f(args...; kwargs...) should be given")) + topmod = Core.Compiler._topmod(__module__) # well, except, do not get it via CC but define it locally + f, args, kwargs = destructure_callex(topmod, ex) + out = Expr(:call, GlobalRef(Base, :invokelatest)) + isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) + push!(out.args, f) + append!(out.args, args) + return esc(out) +end + +function destructure_callex(topmod::Module, @nospecialize(ex)) + function flatten(xs) + out = Any[] + for x in xs + if isexpr(x, :tuple) + append!(out, x.args) + else + push!(out, x) + end + end + return out + end - f = first(ex.args) - args = [] - kwargs = [] - for x in ex.args[2:end] - if isexpr(x, :parameters) - append!(kwargs, x.args) - elseif isexpr(x, :kw) - push!(kwargs, x) + kwargs = Any[] + if isexpr(ex, :call) # `f(args...)` + f = first(ex.args) + args = Any[] + for x in ex.args[2:end] + if isexpr(x, :parameters) + append!(kwargs, x.args) + elseif isexpr(x, :kw) + push!(kwargs, x) + else + push!(args, x) + end + end + elseif isexpr(ex, :.) # `x.f` + f = GlobalRef(topmod, :getproperty) + args = flatten(ex.args) + elseif isexpr(ex, :ref) # `x[i]` + f = GlobalRef(topmod, :getindex) + args = flatten(ex.args) + elseif isexpr(ex, :(=)) # `x.f = v` or `x[i] = v` + lhs, rhs = ex.args + if isexpr(lhs, :.) + f = GlobalRef(topmod, :setproperty!) + args = flatten(Any[lhs.args..., rhs]) + elseif isexpr(lhs, :ref) + f = GlobalRef(topmod, :setindex!) + args = flatten(Any[lhs.args[1], rhs, lhs.args[2]]) else - push!(args, x) + throw(ArgumentError("expected a `setproperty!` expression `x.f = v` or `setindex!` expression `x[i] = v`")) end + else + throw(ArgumentError("expected a `:call` expression `f(args...; kwargs...)`")) end - return f, args, kwargs end diff --git a/base/refpointer.jl b/base/refpointer.jl index cd179c87b30d5..0cb2df6d24bce 100644 --- a/base/refpointer.jl +++ b/base/refpointer.jl @@ -101,7 +101,7 @@ IteratorSize(::Type{<:Ref}) = HasShape{0}() unsafe_convert(::Type{Ref{T}}, x::Ref{T}) where {T} = unsafe_convert(Ptr{T}, x) unsafe_convert(::Type{Ref{T}}, x) where {T} = unsafe_convert(Ptr{T}, x) -convert(::Type{Ref{T}}, x) where {T} = RefValue{T}(x) +convert(::Type{Ref{T}}, x) where {T} = RefValue{T}(x)::RefValue{T} ### Methods for a Ref object that is backed by an array at index i struct RefArray{T,A<:AbstractArray{T},R} <: Ref{T} @@ -112,6 +112,8 @@ struct RefArray{T,A<:AbstractArray{T},R} <: Ref{T} end RefArray(x::AbstractArray{T}, i::Int, roots::Any) where {T} = RefArray{T,typeof(x),Any}(x, i, roots) RefArray(x::AbstractArray{T}, i::Int=1, roots::Nothing=nothing) where {T} = RefArray{T,typeof(x),Nothing}(x, i, nothing) +RefArray(x::AbstractArray{T}, i::Integer, roots::Any) where {T} = RefArray{T,typeof(x),Any}(x, Int(i), roots) +RefArray(x::AbstractArray{T}, i::Integer, roots::Nothing=nothing) where {T} = RefArray{T,typeof(x),Nothing}(x, Int(i), nothing) convert(::Type{Ref{T}}, x::AbstractArray{T}) where {T} = RefArray(x, 1) function unsafe_convert(P::Union{Type{Ptr{T}},Type{Ptr{Cvoid}}}, b::RefArray{T})::P where T diff --git a/base/refvalue.jl b/base/refvalue.jl index 7cbb651d41aee..000088ff0ce76 100644 --- a/base/refvalue.jl +++ b/base/refvalue.jl @@ -45,7 +45,10 @@ function unsafe_convert(P::Union{Type{Ptr{T}},Type{Ptr{Cvoid}}}, b::RefValue{T}) # If it is actually an immutable, then we can't take it's pointer directly # Instead, explicitly load the pointer from the `RefValue`, # which also ensures this returns same pointer as the one rooted in the `RefValue` object. - p = pointerref(Ptr{Ptr{Cvoid}}(pointer_from_objref(b)), 1, Core.sizeof(Ptr{Cvoid})) + p = atomic_pointerref(Ptr{Ptr{Cvoid}}(pointer_from_objref(b)), :monotonic) + end + if p == C_NULL + throw(UndefRefError()) end return p end diff --git a/base/regex.jl b/base/regex.jl index ad26c18d4c581..400784e1b27d7 100644 --- a/base/regex.jl +++ b/base/regex.jl @@ -8,14 +8,7 @@ const DEFAULT_COMPILER_OPTS = PCRE.UTF | PCRE.MATCH_INVALID_UTF | PCRE.ALT_BSUX const DEFAULT_MATCH_OPTS = PCRE.NO_UTF_CHECK """ -An abstract type representing any sort of pattern matching expression -(typically a regular expression). `AbstractPattern` objects can be used to -match strings with [`match`](@ref). -""" -abstract type AbstractPattern end - -""" - Regex(pattern[, flags]) + Regex(pattern[, flags]) <: AbstractPattern A type representing a regular expression. `Regex` objects can be used to match strings with [`match`](@ref). @@ -53,19 +46,24 @@ mutable struct Regex <: AbstractPattern end function Regex(pattern::AbstractString, flags::AbstractString) - options = DEFAULT_COMPILER_OPTS + compile_options = DEFAULT_COMPILER_OPTS + match_options = DEFAULT_MATCH_OPTS for f in flags if f == 'a' - options &= ~PCRE.UCP + # instruct pcre2 to treat the strings as simple bytes (aka "ASCII"), not char encodings + compile_options &= ~PCRE.UCP # user can re-enable with (*UCP) + compile_options &= ~PCRE.UTF # user can re-enable with (*UTF) + compile_options &= ~PCRE.MATCH_INVALID_UTF # this would force on UTF + match_options &= ~PCRE.NO_UTF_CHECK # if the user did force on UTF, we should check it for safety else - options |= f=='i' ? PCRE.CASELESS : - f=='m' ? PCRE.MULTILINE : - f=='s' ? PCRE.DOTALL : - f=='x' ? PCRE.EXTENDED : - throw(ArgumentError("unknown regex flag: $f")) + compile_options |= f=='i' ? PCRE.CASELESS : + f=='m' ? PCRE.MULTILINE : + f=='s' ? PCRE.DOTALL : + f=='x' ? PCRE.EXTENDED : + throw(ArgumentError("unknown regex flag: $f")) end end - Regex(pattern, options, DEFAULT_MATCH_OPTS) + Regex(pattern, compile_options, match_options) end Regex(pattern::AbstractString) = Regex(pattern, DEFAULT_COMPILER_OPTS, DEFAULT_MATCH_OPTS) @@ -103,9 +101,15 @@ listed after the ending quote, to change its behaviour: - `s` allows the `.` modifier to match newlines. - `x` enables "comment mode": whitespace is enabled except when escaped with `\\`, and `#` is treated as starting a comment. -- `a` disables `UCP` mode (enables ASCII mode). By default `\\B`, `\\b`, `\\D`, `\\d`, `\\S`, - `\\s`, `\\W`, `\\w`, etc. match based on Unicode character properties. With this option, - these sequences only match ASCII characters. +- `a` enables ASCII mode (disables `UTF` and `UCP` modes). By default `\\B`, `\\b`, `\\D`, + `\\d`, `\\S`, `\\s`, `\\W`, `\\w`, etc. match based on Unicode character properties. With + this option, these sequences only match ASCII characters. This includes `\\u` also, which + will emit the specified character value directly as a single byte, and not attempt to + encode it into UTF-8. Importantly, this option allows matching against invalid UTF-8 + strings, by treating both matcher and target as simple bytes (as if they were ISO/IEC + 8859-1 / Latin-1 bytes) instead of as character encodings. In this case, this option is + often combined with `s`. This option can be further refined by starting the pattern with + (*UCP) or (*UTF). See [`Regex`](@ref) if interpolation is needed. @@ -119,23 +123,38 @@ This regex has the first three flags enabled. macro r_str(pattern, flags...) Regex(pattern, flags...) end function show(io::IO, re::Regex) - imsxa = PCRE.CASELESS|PCRE.MULTILINE|PCRE.DOTALL|PCRE.EXTENDED|PCRE.UCP + imsx = PCRE.CASELESS|PCRE.MULTILINE|PCRE.DOTALL|PCRE.EXTENDED + ac = PCRE.UTF|PCRE.MATCH_INVALID_UTF|PCRE.UCP + am = PCRE.NO_UTF_CHECK opts = re.compile_options - if (opts & ~imsxa) == (DEFAULT_COMPILER_OPTS & ~imsxa) + mopts = re.match_options + default = ((opts & ~imsx) | ac) == DEFAULT_COMPILER_OPTS + if default + if (opts & ac) == ac + default = mopts == DEFAULT_MATCH_OPTS + elseif (opts & ac) == 0 + default = mopts == (DEFAULT_MATCH_OPTS & ~am) + else + default = false + end + end + if default print(io, "r\"") escape_raw_string(io, re.pattern) print(io, "\"") - if (opts & PCRE.CASELESS ) != 0; print(io, 'i'); end - if (opts & PCRE.MULTILINE) != 0; print(io, 'm'); end - if (opts & PCRE.DOTALL ) != 0; print(io, 's'); end - if (opts & PCRE.EXTENDED ) != 0; print(io, 'x'); end - if (opts & PCRE.UCP ) == 0; print(io, 'a'); end + if (opts & PCRE.CASELESS ) != 0; print(io, "i"); end + if (opts & PCRE.MULTILINE) != 0; print(io, "m"); end + if (opts & PCRE.DOTALL ) != 0; print(io, "s"); end + if (opts & PCRE.EXTENDED ) != 0; print(io, "x"); end + if (opts & ac ) == 0; print(io, "a"); end else print(io, "Regex(") show(io, re.pattern) - print(io, ',') + print(io, ", ") show(io, opts) - print(io, ')') + print(io, ", ") + show(io, mopts) + print(io, ")") end end @@ -146,7 +165,7 @@ in a string using an `AbstractPattern`. abstract type AbstractMatch end """ - RegexMatch + RegexMatch <: AbstractMatch A type representing a single match to a `Regex` found in a string. Typically created from the [`match`](@ref) function. @@ -174,16 +193,23 @@ See [`keys`](@ref keys(::RegexMatch)) for more information. julia> m = match(r"(?\\d+):(?\\d+)(am|pm)?", "11:30 in the morning") RegexMatch("11:30", hour="11", minute="30", 3=nothing) -julia> hr, min, ampm = m; +julia> m.match +"11:30" + +julia> m.captures +3-element Vector{Union{Nothing, SubString{String}}}: + "11" + "30" + nothing -julia> hr -"11" julia> m["minute"] "30" -julia> m.match -"11:30" +julia> hr, min, ampm = m; # destructure capture groups by iteration + +julia> hr +"11" ``` """ struct RegexMatch <: AbstractMatch @@ -204,8 +230,8 @@ That is, `idx` will be in the return value even if `m[idx] == nothing`. Unnamed capture groups will have integer keys corresponding to their index. Named capture groups will have string keys. -!!! compat "Julia 1.6" - This method was added in Julia 1.6 +!!! compat "Julia 1.7" + This method was added in Julia 1.7 # Examples ```jldoctest @@ -243,19 +269,17 @@ end # Capture group extraction getindex(m::RegexMatch, idx::Integer) = m.captures[idx] -function getindex(m::RegexMatch, name::Symbol) +function getindex(m::RegexMatch, name::Union{AbstractString,Symbol}) idx = PCRE.substring_number_from_name(m.regex.regex, name) idx <= 0 && error("no capture group named $name found in regex") m[idx] end -getindex(m::RegexMatch, name::AbstractString) = m[Symbol(name)] haskey(m::RegexMatch, idx::Integer) = idx in eachindex(m.captures) -function haskey(m::RegexMatch, name::Symbol) +function haskey(m::RegexMatch, name::Union{AbstractString,Symbol}) idx = PCRE.substring_number_from_name(m.regex.regex, name) return idx > 0 end -haskey(m::RegexMatch, name::AbstractString) = haskey(m, Symbol(name)) iterate(m::RegexMatch, args...) = iterate(m.captures, args...) length(m::RegexMatch) = length(m.captures) @@ -266,7 +290,7 @@ function occursin(r::Regex, s::AbstractString; offset::Integer=0) return PCRE.exec_r(r.regex, String(s), offset, r.match_options) end -function occursin(r::Regex, s::SubString; offset::Integer=0) +function occursin(r::Regex, s::SubString{String}; offset::Integer=0) compile(r) return PCRE.exec_r(r.regex, s, offset, r.match_options) end @@ -298,7 +322,7 @@ function startswith(s::AbstractString, r::Regex) return PCRE.exec_r(r.regex, String(s), 0, r.match_options | PCRE.ANCHORED) end -function startswith(s::SubString, r::Regex) +function startswith(s::SubString{String}, r::Regex) compile(r) return PCRE.exec_r(r.regex, s, 0, r.match_options | PCRE.ANCHORED) end @@ -330,7 +354,7 @@ function endswith(s::AbstractString, r::Regex) return PCRE.exec_r(r.regex, String(s), 0, r.match_options | PCRE.ENDANCHORED) end -function endswith(s::SubString, r::Regex) +function endswith(s::SubString{String}, r::Regex) compile(r) return PCRE.exec_r(r.regex, s, 0, r.match_options | PCRE.ENDANCHORED) end @@ -435,54 +459,6 @@ findnext(r::Regex, s::AbstractString, idx::Integer) = throw(ArgumentError( findfirst(r::Regex, s::AbstractString) = findnext(r,s,firstindex(s)) -""" - findall( - pattern::Union{AbstractString,AbstractPattern}, - string::AbstractString; - overlap::Bool = false, - ) - -Return a `Vector{UnitRange{Int}}` of all the matches for `pattern` in `string`. -Each element of the returned vector is a range of indices where the -matching sequence is found, like the return value of [`findnext`](@ref). - -If `overlap=true`, the matching sequences are allowed to overlap indices in the -original string, otherwise they must be from disjoint character ranges. - -# Examples -```jldoctest -julia> findall("a", "apple") -1-element Vector{UnitRange{Int64}}: - 1:1 - -julia> findall("nana", "banana") -1-element Vector{UnitRange{Int64}}: - 3:6 - -julia> findall("a", "banana") -3-element Vector{UnitRange{Int64}}: - 2:2 - 4:4 - 6:6 -``` - -!!! compat "Julia 1.3" - This method requires at least Julia 1.3. -""" -function findall(t::Union{AbstractString,AbstractPattern}, s::AbstractString; overlap::Bool=false) - found = UnitRange{Int}[] - i, e = firstindex(s), lastindex(s) - while true - r = findnext(t, s, i) - isnothing(r) && break - push!(found, r) - j = overlap || isempty(r) ? first(r) : last(r) - j > e && break - @inbounds i = nextind(s, j) - end - return found -end - """ findall(c::AbstractChar, s::AbstractString) @@ -521,6 +497,18 @@ original string, otherwise they must be from disjoint character ranges. !!! compat "Julia 1.7" Using a character as the pattern requires at least Julia 1.7. + +# Examples +```jldoctest +julia> count('a', "JuliaLang") +2 + +julia> count(r"a(.)a", "cabacabac", overlap=true) +3 + +julia> count(r"a(.)a", "cabacabac") +2 +``` """ function count(t::Union{AbstractChar,AbstractString,AbstractPattern}, s::AbstractString; overlap::Bool=false) n = 0 @@ -537,7 +525,7 @@ function count(t::Union{AbstractChar,AbstractString,AbstractPattern}, s::Abstrac end """ - SubstitutionString(substr) + SubstitutionString(substr) <: AbstractString Stores the given string `substr` as a `SubstitutionString`, for use in regular expression substitutions. Most commonly constructed using the [`@s_str`](@ref) macro. @@ -552,9 +540,7 @@ s"Hello \\g, it's \\1" julia> typeof(subst) SubstitutionString{String} - ``` - """ struct SubstitutionString{T<:AbstractString} <: AbstractString string::T @@ -858,7 +844,7 @@ end """ - ^(s::Regex, n::Integer) + ^(s::Regex, n::Integer) -> Regex Repeat a regex `n` times. diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 3b54ed04089cd..2fc246f86fa96 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -19,12 +19,34 @@ struct ReinterpretArray{T,N,S,A<:AbstractArray{S},IsReshaped} <: AbstractArray{T @noinline throw(ArgumentError("cannot reinterpret a zero-dimensional `$(S)` array to `$(T)` which is of a $msg size")) end - function throwsingleton(S::Type, T::Type, kind) + function throwsingleton(S::Type, T::Type) @noinline - throw(ArgumentError("cannot reinterpret $kind `$(S)` array to `$(T)` which is a singleton type")) + throw(ArgumentError("cannot reinterpret a `$(S)` array to `$(T)` which is a singleton type")) end global reinterpret + + @doc """ + reinterpret(T::DataType, A::AbstractArray) + + Construct a view of the array with the same binary data as the given + array, but with `T` as element type. + + This function also works on "lazy" array whose elements are not computed until they are explicitly retrieved. + For instance, `reinterpret` on the range `1:6` works similarly as on the dense vector `collect(1:6)`: + + ```jldoctest + julia> reinterpret(Float32, UInt32[1 2 3 4 5]) + 1×5 reinterpret(Float32, ::Matrix{UInt32}): + 1.0f-45 3.0f-45 4.0f-45 6.0f-45 7.0f-45 + + julia> reinterpret(Complex{Int}, 1:6) + 3-element reinterpret(Complex{$Int}, ::UnitRange{$Int}): + 1 + 2im + 3 + 4im + 5 + 6im + ``` + """ function reinterpret(::Type{T}, a::A) where {T,N,S,A<:AbstractArray{S, N}} function thrownonint(S::Type, T::Type, dim) @noinline @@ -44,7 +66,7 @@ struct ReinterpretArray{T,N,S,A<:AbstractArray{S},IsReshaped} <: AbstractArray{T ax1 = axes(a)[1] dim = length(ax1) if issingletontype(T) - dim == 0 || throwsingleton(S, T, "a non-empty") + issingletontype(S) || throwsingleton(S, T) else rem(dim*sizeof(S),sizeof(T)) == 0 || thrownonint(S, T, dim) end @@ -75,7 +97,7 @@ struct ReinterpretArray{T,N,S,A<:AbstractArray{S},IsReshaped} <: AbstractArray{T if sizeof(S) == sizeof(T) N = ndims(a) elseif sizeof(S) > sizeof(T) - issingletontype(T) && throwsingleton(S, T, "with reshape a") + issingletontype(T) && throwsingleton(S, T) rem(sizeof(S), sizeof(T)) == 0 || throwintmult(S, T) N = ndims(a) + 1 else @@ -152,23 +174,15 @@ strides(a::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}) = siz stride(A::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, k::Integer) = k ≤ ndims(A) ? strides(A)[k] : length(A) -function strides(a::ReshapedReinterpretArray) - ap = parent(a) - els, elp = elsize(a), elsize(ap) - stp = strides(ap) - els == elp && return stp - els < elp && return (1, _checked_strides(stp, els, elp)...) +function strides(a::ReinterpretArray{T,<:Any,S,<:AbstractArray{S},IsReshaped}) where {T,S,IsReshaped} + _checkcontiguous(Bool, a) && return size_to_strides(1, size(a)...) + stp = strides(parent(a)) + els, elp = sizeof(T), sizeof(S) + els == elp && return stp # 0dim parent is also handled here. + IsReshaped && els < elp && return (1, _checked_strides(stp, els, elp)...) stp[1] == 1 || throw(ArgumentError("Parent must be contiguous in the 1st dimension!")) - return _checked_strides(tail(stp), els, elp) -end - -function strides(a::NonReshapedReinterpretArray) - ap = parent(a) - els, elp = elsize(a), elsize(ap) - stp = strides(ap) - els == elp && return stp - stp[1] == 1 || throw(ArgumentError("Parent must be contiguous in the 1st dimension!")) - return (1, _checked_strides(tail(stp), els, elp)...) + st′ = _checked_strides(tail(stp), els, elp) + return IsReshaped ? st′ : (1, st′...) end @inline function _checked_strides(stp::Tuple, els::Integer, elp::Integer) @@ -333,6 +347,8 @@ function axes(a::ReshapedReinterpretArray{T,N,S} where {N}) where {T,S} end 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)) @@ -646,7 +662,7 @@ function intersect(p1::Padding, p2::Padding) Padding(start, max(0, stop-start)) end -struct PaddingError +struct PaddingError <: Exception S::Type T::Type end @@ -706,25 +722,30 @@ function CyclePadding(T::DataType) CyclePadding(pad, as) end -using .Iterators: Stateful -@pure function array_subpadding(S, T) - checked_size = 0 +@assume_effects :total function array_subpadding(S, T) lcm_size = lcm(sizeof(S), sizeof(T)) - s, t = Stateful{<:Any, Any}(CyclePadding(S)), - Stateful{<:Any, Any}(CyclePadding(T)) - isempty(t) && return true - isempty(s) && return false + s, t = CyclePadding(S), CyclePadding(T) + checked_size = 0 + # use of Stateful harms inference and makes this vulnerable to invalidation + (pad, tstate) = let + it = iterate(t) + it === nothing && return true + it + end + (ps, sstate) = let + it = iterate(s) + it === nothing && return false + it + end while checked_size < lcm_size - # Take padding in T - pad = popfirst!(t) - # See if there's corresponding padding in S while true - ps = peek(s) + # See if there's corresponding padding in S ps.offset > pad.offset && return false intersect(ps, pad) == pad && break - popfirst!(s) + ps, sstate = iterate(s, sstate) end checked_size = pad.offset + pad.size + pad, tstate = iterate(t, tstate) end return true end diff --git a/base/reshapedarray.jl b/base/reshapedarray.jl index 82d293249afc6..4037aff246a81 100644 --- a/base/reshapedarray.jl +++ b/base/reshapedarray.jl @@ -205,6 +205,7 @@ function __reshape(p::Tuple{AbstractArray,IndexLinear}, dims::Dims) end size(A::ReshapedArray) = A.dims +length(A::ReshapedArray) = length(parent(A)) similar(A::ReshapedArray, eltype::Type, dims::Dims) = similar(parent(A), eltype, dims) IndexStyle(::Type{<:ReshapedArrayLF}) = IndexLinear() parent(A::ReshapedArray) = A.parent @@ -294,14 +295,51 @@ unsafe_convert(::Type{Ptr{T}}, V::SubArray{T,N,P,<:Tuple{Vararg{Union{RangeIndex unsafe_convert(Ptr{T}, V.parent) + (first_index(V)-1)*sizeof(T) -_checkcontiguous(::Type{Bool}, A::AbstractArray) = size_to_strides(1, size(A)...) == strides(A) -_checkcontiguous(::Type{Bool}, A::Array) = true +_checkcontiguous(::Type{Bool}, A::AbstractArray) = false +# `strides(A::DenseArray)` calls `size_to_strides` by default. +# Thus it's OK to assume all `DenseArray`s are contiguously stored. +_checkcontiguous(::Type{Bool}, A::DenseArray) = true _checkcontiguous(::Type{Bool}, A::ReshapedArray) = _checkcontiguous(Bool, parent(A)) _checkcontiguous(::Type{Bool}, A::FastContiguousSubArray) = _checkcontiguous(Bool, parent(A)) function strides(a::ReshapedArray) - # We can handle non-contiguous parent if it's a StridedVector - ndims(parent(a)) == 1 && return size_to_strides(only(strides(parent(a))), size(a)...) - _checkcontiguous(Bool, a) || throw(ArgumentError("Parent must be contiguous.")) - size_to_strides(1, size(a)...) + _checkcontiguous(Bool, a) && return size_to_strides(1, size(a)...) + apsz::Dims = size(a.parent) + apst::Dims = strides(a.parent) + msz, mst, n = merge_adjacent_dim(apsz, apst) # Try to perform "lazy" reshape + n == ndims(a.parent) && return size_to_strides(mst, size(a)...) # Parent is stridevector like + return _reshaped_strides(size(a), 1, msz, mst, n, apsz, apst) +end + +function _reshaped_strides(::Dims{0}, reshaped::Int, msz::Int, ::Int, ::Int, ::Dims, ::Dims) + reshaped == msz && return () + throw(ArgumentError("Input is not strided.")) +end +function _reshaped_strides(sz::Dims, reshaped::Int, msz::Int, mst::Int, n::Int, apsz::Dims, apst::Dims) + st = reshaped * mst + reshaped = reshaped * sz[1] + if length(sz) > 1 && reshaped == msz && sz[2] != 1 + msz, mst, n = merge_adjacent_dim(apsz, apst, n + 1) + reshaped = 1 + end + sts = _reshaped_strides(tail(sz), reshaped, msz, mst, n, apsz, apst) + return (st, sts...) +end + +merge_adjacent_dim(::Dims{0}, ::Dims{0}) = 1, 1, 0 +merge_adjacent_dim(apsz::Dims{1}, apst::Dims{1}) = apsz[1], apst[1], 1 +function merge_adjacent_dim(apsz::Dims{N}, apst::Dims{N}, n::Int = 1) where {N} + sz, st = apsz[n], apst[n] + while n < N + szₙ, stₙ = apsz[n+1], apst[n+1] + if sz == 1 + sz, st = szₙ, stₙ + elseif stₙ == st * sz || szₙ == 1 + sz *= szₙ + else + break + end + n += 1 + end + return sz, st, n end diff --git a/base/rounding.jl b/base/rounding.jl index bf29d8b54602e..25cfe2dc09829 100644 --- a/base/rounding.jl +++ b/base/rounding.jl @@ -37,9 +37,13 @@ Currently supported rounding modes are: - [`RoundNearestTiesAway`](@ref) - [`RoundNearestTiesUp`](@ref) - [`RoundToZero`](@ref) -- [`RoundFromZero`](@ref) ([`BigFloat`](@ref) only) +- [`RoundFromZero`](@ref) - [`RoundUp`](@ref) - [`RoundDown`](@ref) + +!!! compat "Julia 1.9" + `RoundFromZero` requires at least Julia 1.9. Prior versions support + `RoundFromZero` for `BigFloat`s only. """ struct RoundingMode{T} end @@ -76,7 +80,10 @@ const RoundDown = RoundingMode{:Down}() RoundFromZero Rounds away from zero. -This rounding mode may only be used with `T == BigFloat` inputs to [`round`](@ref). + +!!! compat "Julia 1.9" + `RoundFromZero` requires at least Julia 1.9. Prior versions support + `RoundFromZero` for `BigFloat`s only. # Examples ```jldoctest @@ -84,7 +91,7 @@ julia> BigFloat("1.0000000000000001", 5, RoundFromZero) 1.06 ``` """ -const RoundFromZero = RoundingMode{:FromZero}() # mpfr only +const RoundFromZero = RoundingMode{:FromZero}() """ RoundNearestTiesAway diff --git a/base/ryu/LICENSE.md b/base/ryu/LICENSE.md index 74c718646a08d..cab89eec22785 100644 --- a/base/ryu/LICENSE.md +++ b/base/ryu/LICENSE.md @@ -22,4 +22,4 @@ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file +DEALINGS IN THE SOFTWARE. diff --git a/base/ryu/utils.jl b/base/ryu/utils.jl index 352f8f19cb9be..4fe0b7d397d07 100644 --- a/base/ryu/utils.jl +++ b/base/ryu/utils.jl @@ -49,7 +49,7 @@ pow5bits(e) = ((e * 1217359) >> 19) + 1 Compute `(m * mul) >> j`, where `j >= 8*sizeof(U)`. The type of the results is the larger of `U` or `UInt32`. """ -@inline function mulshift(m::U, mul, j) where {U<:Unsigned} +function mulshift(m::U, mul, j) where {U<:Unsigned} W = widen(U) nbits = 8*sizeof(U) return ((((W(m) * (mul % U)) >> nbits) + W(m) * (mul >> nbits)) >> (j - nbits)) % promote_type(U,UInt32) @@ -64,16 +64,7 @@ lengthforindex(idx) = div(((Int64(16 * idx) * 1292913986) >> 32) + 1 + 16 + 8, 9 Return `true` if `5^p` is a divisor of `x`. """ -@inline function pow5(x, p) - count = 0 - while true - q = div(x, 5) - r = x - 5 * q - r != 0 && return count >= p - x = q - count += 1 - end -end +pow5(x, p) = x % (UInt64(5)^p) == 0 """ Ryu.pow2(x, p) @@ -87,7 +78,7 @@ pow2(x, p) = (x & ((Int64(1) << p) - 1)) == 0 The number of decimal digits of the integer `v`. """ -@inline function decimallength(v) +function decimallength(v) v >= 10000000000000000 && return 17 v >= 1000000000000000 && return 16 v >= 100000000000000 && return 15 @@ -106,7 +97,7 @@ The number of decimal digits of the integer `v`. v >= 10 && return 2 return 1 end -@inline function decimallength(v::UInt32) +function decimallength(v::UInt32) v >= 100000000 && return 9 v >= 10000000 && return 8 v >= 1000000 && return 7 @@ -117,7 +108,7 @@ end v >= 10 && return 2 return 1 end -@inline function decimallength(v::UInt16) +function decimallength(v::UInt16) v >= 10000 && return 5 v >= 1000 && return 4 v >= 100 && return 3 @@ -125,7 +116,7 @@ end return 1 end -@inline function mulshiftinvsplit(::Type{T}, mv, mp, mm, i, j) where {T} +function mulshiftinvsplit(::Type{T}, mv, mp, mm, i, j) where {T} mul = pow5invsplit_lookup(T, i) vr = mulshift(mv, mul, j) vp = mulshift(mp, mul, j) @@ -133,7 +124,7 @@ end return vr, vp, vm end -@inline function mulshiftsplit(::Type{T}, mv, mp, mm, i, j) where {T} +function mulshiftsplit(::Type{T}, mv, mp, mm, i, j) where {T} mul = pow5split_lookup(T, i) vr = mulshift(mv, mul, j) vp = mulshift(mp, mul, j) @@ -146,7 +137,7 @@ end Compute `p = a*b` where `b = bLo + bHi<<64`, returning the result as `pLo, pHi` where `p = pLo + pHi<<128`. """ -@inline function umul256(a, bHi, bLo) +function umul256(a, bHi, bLo) aLo = a % UInt64 aHi = (a >> 64) % UInt64 @@ -176,14 +167,14 @@ end Compute `pHi = (a*b)>>128` where `b = bLo + bHi<<64`. """ -@inline umul256_hi(a, bHi, bLo) = umul256(a, bHi, bLo)[2] +umul256_hi(a, bHi, bLo) = umul256(a, bHi, bLo)[2] """ Ryu.mulshiftmod1e9(m, mula, mulb, mulc, j)::UInt32 Compute `(m * mul) >> j % 10^9` where `mul = mula + mulb<<64 + mulc<<128`, and `j >= 128`. """ -@inline function mulshiftmod1e9(m, mula, mulb, mulc, j) +function mulshiftmod1e9(m, mula, mulb, mulc, j) b0 = UInt128(m) * mula b1 = UInt128(m) * mulb b2 = UInt128(m) * mulc @@ -195,7 +186,7 @@ Compute `(m * mul) >> j % 10^9` where `mul = mula + mulb<<64 + mulc<<128`, and ` return (v % UInt32) - UInt32(1000000000) * shifted end -@inline function append_sign(x, plus, space, buf, pos) +function append_sign(x, plus, space, buf, pos) if signbit(x) && !isnan(x) # suppress minus sign for signaling NaNs buf[pos] = UInt8('-') pos += 1 @@ -209,7 +200,7 @@ end return pos end -@inline function append_n_digits(olength, digits, buf, pos) +function append_n_digits(olength, digits, buf, pos) i = 0 while digits >= 10000 c = digits % 10000 @@ -237,7 +228,7 @@ end return pos + i end -@inline function append_d_digits(olength, digits, buf, pos, decchar) +function append_d_digits(olength, digits, buf, pos, decchar) i = 0 while digits >= 10000 c = digits % 10000 @@ -268,7 +259,7 @@ end return pos + i end -@inline function append_c_digits(count, digits, buf, pos) +function append_c_digits(count, digits, buf, pos) i = 0 while i < count - 1 c = (digits % 100) << 1 @@ -283,7 +274,7 @@ end return pos + i end -@inline function append_nine_digits(digits, buf, pos) +function append_nine_digits(digits, buf, pos) if digits == 0 for _ = 1:9 buf[pos] = UInt8('0') diff --git a/base/secretbuffer.jl b/base/secretbuffer.jl index 02a133be088f0..935c50fb80fd6 100644 --- a/base/secretbuffer.jl +++ b/base/secretbuffer.jl @@ -79,8 +79,17 @@ function SecretBuffer!(d::Vector{UInt8}) s end -unsafe_SecretBuffer!(s::Cstring) = unsafe_SecretBuffer!(convert(Ptr{UInt8}, s), Int(ccall(:strlen, Csize_t, (Cstring,), s))) +function unsafe_SecretBuffer!(s::Cstring) + if s == C_NULL + throw(ArgumentError("cannot convert NULL to SecretBuffer")) + end + len = Int(ccall(:strlen, Csize_t, (Cstring,), s)) + unsafe_SecretBuffer!(convert(Ptr{UInt8}, s), len) +end function unsafe_SecretBuffer!(p::Ptr{UInt8}, len=1) + if p == C_NULL + throw(ArgumentError("cannot convert NULL to SecretBuffer")) + end s = SecretBuffer(sizehint=len) for i in 1:len write(s, unsafe_load(p, i)) diff --git a/base/set.jl b/base/set.jl index 371799f2e3ff5..a91bf328bd911 100644 --- a/base/set.jl +++ b/base/set.jl @@ -1,5 +1,41 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" + Set{T} <: AbstractSet{T} + +`Set`s are mutable containers that provide fast membership testing. + +`Set`s have efficient implementations of set operations such as `in`, `union` and `intersect`. +Elements in a `Set` are unique, as determined by the elements' definition of `isequal`. +The order of elements in a `Set` is an implementation detail and cannot be relied on. + +See also: [`AbstractSet`](@ref), [`BitSet`](@ref), [`Dict`](@ref), +[`push!`](@ref), [`empty!`](@ref), [`union!`](@ref), [`in`](@ref), [`isequal`](@ref) + +# Examples +```jldoctest; filter = r"^ '.'"ma +julia> s = Set("aaBca") +Set{Char} with 3 elements: + 'a' + 'c' + 'B' + +julia> push!(s, 'b') +Set{Char} with 4 elements: + 'a' + 'b' + 'B' + 'c' + +julia> s = Set([NaN, 0.0, 1.0, 2.0]); + +julia> -0.0 in s # isequal(0.0, -0.0) is false +false + +julia> NaN in s # isequal(NaN, NaN) is true +true +``` +""" struct Set{T} <: AbstractSet{T} dict::Dict{T,Nothing} @@ -19,17 +55,7 @@ function Set{T}(s::KeySet{T, <:Dict{T}}) where {T} _Set(Dict{T,Nothing}(slots, keys, vals, d.ndel, d.count, d.age, d.idxfloor, d.maxprobe)) end -""" - Set([itr]) - -Construct a [`Set`](@ref) of the values generated by the given iterable object, or an -empty set. Should be used instead of [`BitSet`](@ref) for sparse integer sets, or -for sets of arbitrary objects. - -See also: [`push!`](@ref), [`empty!`](@ref), [`union!`](@ref), [`in`](@ref). -""" Set(itr) = _Set(itr, IteratorEltype(itr)) - _Set(itr, ::HasEltype) = Set{eltype(itr)}(itr) function _Set(itr, ::EltypeUnknown) @@ -64,6 +90,16 @@ end 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) +function in!(x, s::Set) + idx, sh = ht_keyindex2_shorthash!(s.dict, x) + idx > 0 && return true + _setindex!(s.dict, nothing, x, -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) @@ -137,10 +173,7 @@ function unique(itr) out = Vector{T}() seen = Set{T}() for x in itr - if !in(x, seen) - push!(seen, x) - push!(out, x) - end + !in!(x, seen) && push!(out, x) end return out end @@ -164,16 +197,10 @@ _unique_from(itr, out, seen, i) = unique_from(itr, out, seen, i) R = promote_typejoin(S, T) seenR = convert(Set{R}, seen) outR = convert(Vector{R}, out) - if !in(x, seenR) - push!(seenR, x) - push!(outR, x) - end + !in!(x, seenR) && push!(outR, x) return _unique_from(itr, outR, seenR, i) end - if !in(x, seen) - push!(seen, x) - push!(out, x) - end + !in!(x, seen) && push!(out, x) end return out end @@ -183,7 +210,7 @@ unique(r::AbstractRange) = allunique(r) ? r : oftype(r, r[begin:begin]) """ unique(f, itr) -Returns an array containing one value from `itr` for each unique value produced by `f` +Return an array containing one value from `itr` for each unique value produced by `f` applied to elements of `itr`. # Examples @@ -194,16 +221,34 @@ julia> unique(x -> x^2, [1, -1, 3, -3, 4]) 3 4 ``` +This functionality can also be used to extract the *indices* of the first +occurrences of unique elements in an array: +```jldoctest +julia> a = [3.1, 4.2, 5.3, 3.1, 3.1, 3.1, 4.2, 1.7]; + +julia> i = unique(i -> a[i], eachindex(a)) +4-element Vector{Int64}: + 1 + 2 + 3 + 8 + +julia> a[i] +4-element Vector{Float64}: + 3.1 + 4.2 + 5.3 + 1.7 + +julia> a[i] == unique(a) +true +``` """ function unique(f, C; seen::Union{Nothing,Set}=nothing) out = Vector{eltype(C)}() if seen !== nothing for x in C - y = f(x) - if y ∉ seen - push!(out, x) - push!(seen, y) - end + !in!(f(x), seen) && push!(out, x) end return out end @@ -387,37 +432,41 @@ See also: [`unique`](@ref), [`issorted`](@ref), [`allequal`](@ref). # Examples ```jldoctest -julia> a = [1; 2; 3] -3-element Vector{Int64}: - 1 - 2 - 3 - -julia> allunique(a) +julia> allunique([1, 2, 3]) true -julia> allunique([a, a]) +julia> allunique([1, 2, 1, 2]) +false + +julia> allunique(Real[1, 1.0, 2]) +false + +julia> allunique([NaN, 2.0, NaN, 4.0]) false ``` """ function allunique(C) - seen = Dict{eltype(C), Nothing}() + if haslength(C) + length(C) < 2 && return true + length(C) < 32 && return _indexed_allunique(collect(C)) + end + return _hashed_allunique(C) +end + +function _hashed_allunique(C) + seen = Set{eltype(C)}() x = iterate(C) if haslength(C) && length(C) > 1000 for i in OneTo(1000) - v, s = x - idx = ht_keyindex2!(seen, v) - idx > 0 && return false - _setindex!(seen, nothing, v, -idx) + v, s = something(x) + in!(v, seen) && return false x = iterate(C, s) end sizehint!(seen, length(C)) end while x !== nothing v, s = x - idx = ht_keyindex2!(seen, v) - idx > 0 && return false - _setindex!(seen, nothing, v, -idx) + in!(v, seen) && return false x = iterate(C, s) end return true @@ -427,6 +476,32 @@ 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 _indexed_allunique(A) + length(A) < 2 && return true + iter = eachindex(A) + I = iterate(iter) + while I !== nothing + i, s = I + a = A[i] + for j in Iterators.rest(iter, s) + isequal(a, @inbounds A[j]) && return false + end + I = iterate(iter, s) + end + return true +end + +function allunique(t::Tuple) + length(t) < 32 || return _hashed_allunique(t) + a = afoldl(true, tail(t)...) do b, x + b & !isequal(first(t), x) + end + return a && allunique(tail(t)) +end +allunique(t::Tuple{}) = true + """ allequal(itr) -> Bool @@ -473,7 +548,7 @@ function hash(s::AbstractSet, h::UInt) end convert(::Type{T}, s::T) where {T<:AbstractSet} = s -convert(::Type{T}, s::AbstractSet) where {T<:AbstractSet} = T(s) +convert(::Type{T}, s::AbstractSet) where {T<:AbstractSet} = T(s)::T ## replace/replace! ## @@ -542,7 +617,7 @@ function replace_pairs!(res, A, count::Int, old_new::Tuple{Vararg{Pair}}) end """ - replace!(new::Function, A; [count::Integer]) + replace!(new::Union{Function, Type}, A; [count::Integer]) Replace each element `x` in collection `A` by `new(x)`. If `count` is specified, then replace at most `count` values in total @@ -635,7 +710,7 @@ subtract_singletontype(::Type{T}, x::Pair{K}, y::Pair...) where {T, K} = subtract_singletontype(subtract_singletontype(T, y...), x) """ - replace(new::Function, A; [count::Integer]) + replace(new::Union{Function, Type}, A; [count::Integer]) Return a copy of `A` where each value `x` in `A` is replaced by `new(x)`. If `count` is specified, then replace at most `count` values in total diff --git a/base/shell.jl b/base/shell.jl index f443a1f9c094a..5bfd11fb46d29 100644 --- a/base/shell.jl +++ b/base/shell.jl @@ -292,9 +292,9 @@ function shell_escape_csh(io::IO, args::AbstractString...) first = false i = 1 while true - for (r,e) = (r"^[A-Za-z0-9/\._-]+\z" => "", - r"^[^']*\z" => "'", r"^[^\$\`\"]*\z" => "\"", - r"^[^']+" => "'", r"^[^\$\`\"]+" => "\"") + for (r,e) = (r"^[A-Za-z0-9/\._-]+\z"sa => "", + r"^[^']*\z"sa => "'", r"^[^\$\`\"]*\z"sa => "\"", + r"^[^']+"sa => "'", r"^[^\$\`\"]+"sa => "\"") if ((m = match(r, SubString(arg, i))) !== nothing) write(io, e) write(io, replace(m.match, '\n' => "\\\n")) @@ -361,12 +361,12 @@ cmdargs = Base.shell_escape_wincmd("Passing args with %cmdargs% works 100%!") run(setenv(`cmd /C echo %cmdargs%`, "cmdargs" => cmdargs)) ``` -!warning +!!! warning The argument parsing done by CMD when calling batch files (either inside `.bat` files or as arguments to them) is not fully compatible with the output of this function. In particular, the processing of `%` is different. -!important +!!! important Due to a peculiar behavior of the CMD parser/interpreter, each command after a literal `|` character (indicating a command pipeline) must have `shell_escape_wincmd` applied twice since it will be parsed twice by CMD. @@ -391,7 +391,7 @@ julia> Base.shell_escape_wincmd("a^\\"^o\\"^u\\"") """ function shell_escape_wincmd(io::IO, s::AbstractString) # https://stackoverflow.com/a/4095133/1990689 - occursin(r"[\r\n\0]", s) && + occursin(r"[\r\n\0]"sa, s) && throw(ArgumentError("control character unsupported by CMD.EXE")) i = 1 len = ncodeunits(s) @@ -446,7 +446,7 @@ function escape_microsoft_c_args(io::IO, args::AbstractString...) else write(io, ' ') # separator end - if isempty(arg) || occursin(r"[ \t\"]", arg) + if isempty(arg) || occursin(r"[ \t\"]"sa, arg) # Julia raw strings happen to use the same escaping convention # as the argv[] parser in Microsoft's C runtime library. write(io, '"') diff --git a/base/show.jl b/base/show.jl index 8359690034c23..36f7df54d0008 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1,8 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using Core.Compiler: has_typevar + function show(io::IO, ::MIME"text/plain", u::UndefInitializer) show(io, u) - get(io, :compact, false) && return + get(io, :compact, false)::Bool && return print(io, ": array initializer with undefined values") end @@ -21,24 +23,27 @@ function show(io::IO, ::MIME"text/plain", r::LinRange) print_range(io, r) end +function _isself(@nospecialize(ft)) + name = ft.name.mt.name + mod = parentmodule(ft) # NOTE: not necessarily the same as ft.name.mt.module + return isdefined(mod, name) && ft == typeof(getfield(mod, name)) +end + function show(io::IO, ::MIME"text/plain", f::Function) - get(io, :compact, false) && return show(io, f) + get(io, :compact, false)::Bool && return show(io, f) ft = typeof(f) - mt = ft.name.mt + name = ft.name.mt.name if isa(f, Core.IntrinsicFunction) print(io, f) id = Core.Intrinsics.bitcast(Int32, f) print(io, " (intrinsic function #$id)") elseif isa(f, Core.Builtin) - print(io, mt.name, " (built-in function)") + print(io, name, " (built-in function)") else - name = mt.name - isself = isdefined(ft.name.module, name) && - ft == typeof(getfield(ft.name.module, name)) n = length(methods(f)) m = n==1 ? "method" : "methods" sname = string(name) - ns = (isself || '#' in sname) ? sname : string("(::", ft, ")") + ns = (_isself(ft) || '#' in sname) ? sname : string("(::", ft, ")") what = startswith(ns, '@') ? "macro" : "generic function" print(io, ns, " (", what, " with $n $m)") end @@ -46,9 +51,68 @@ end show(io::IO, ::MIME"text/plain", c::ComposedFunction) = show(io, c) show(io::IO, ::MIME"text/plain", c::Returns) = show(io, c) +show(io::IO, ::MIME"text/plain", s::Splat) = show(io, s) + +const ansi_regex = r"(?s)(?:\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))|." + +# Pseudo-character representing an ANSI delimiter +struct ANSIDelimiter + del::SubString{String} +end +ncodeunits(c::ANSIDelimiter) = ncodeunits(c.del) +textwidth(::ANSIDelimiter) = 0 + +# An iterator similar to `pairs(::String)` but whose values are Char or ANSIDelimiter +struct ANSIIterator + captures::RegexMatchIterator +end +ANSIIterator(s::AbstractString) = ANSIIterator(eachmatch(ansi_regex, s)) + +IteratorSize(::Type{ANSIIterator}) = SizeUnknown() +eltype(::Type{ANSIIterator}) = Pair{Int, Union{Char,ANSIDelimiter}} +function iterate(I::ANSIIterator, (i, m_st)=(1, iterate(I.captures))) + m_st === nothing && return nothing + m, (j, new_m_st) = m_st + c = lastindex(m.match) == 1 ? only(m.match) : ANSIDelimiter(m.match) + return (i => c, (j, iterate(I.captures, (j, new_m_st)))) +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="…") + truncwidth = textwidth(truncmark) + (width <= 0 || width < truncwidth) && return "" + wid = truncidx = lastidx = 0 + # if str needs to be truncated, truncidx is the index of truncation. + stop = false # once set, only ANSI delimiters will be kept as new characters. + needANSIend = false # set if the last ANSI delimiter before truncidx is not "\033[0m". + I = ignore_ANSI ? ANSIIterator(str) : pairs(str) + for (i, c) in I + if c isa ANSIDelimiter + truncidx == 0 && (needANSIend = c != "\033[0m") + lastidx = i + ncodeunits(c) - 1 + else + stop && break + wid += textwidth(c) + truncidx == 0 && wid > (width - truncwidth) && (truncidx = lastidx) + lastidx = i + c in chars && break + stop = wid >= width + end + end + lastidx == 0 && return rpad ? ' '^width : "" + str[lastidx] in chars && (lastidx = prevind(str, lastidx)) + ANSIend = needANSIend ? "\033[0m" : "" + pad = rpad ? repeat(' ', max(0, width-wid)) : "" + truncidx == 0 && (truncidx = lastidx) + if lastidx < lastindex(str) + return string(SubString(str, 1, truncidx), ANSIend, truncmark, pad) + else + return string(str, ANSIend, pad) + end +end function show(io::IO, ::MIME"text/plain", iter::Union{KeySet,ValueIterator}) - isempty(iter) && get(io, :compact, false) && return show(io, iter) + isempty(iter) && get(io, :compact, false)::Bool && return show(io, iter) summary(io, iter) isempty(iter) && return print(io, ". ", isa(iter,KeySet) ? "Keys" : "Values", ":") @@ -70,7 +134,7 @@ function show(io::IO, ::MIME"text/plain", iter::Union{KeySet,ValueIterator}) if limit str = sprint(show, v, context=io, sizehint=0) - str = _truncate_at_width_or_chars(str, cols, "\r\n") + str = _truncate_at_width_or_chars(get(io, :color, false)::Bool, str, cols) print(io, str) else show(io, v) @@ -102,19 +166,20 @@ function show(io::IO, ::MIME"text/plain", t::AbstractDict{K,V}) where {K,V} rows -= 1 # Subtract the summary # determine max key width to align the output, caching the strings + hascolor = get(recur_io, :color, false) ks = Vector{String}(undef, min(rows, length(t))) vs = Vector{String}(undef, min(rows, length(t))) - keylen = 0 - vallen = 0 + keywidth = 0 + valwidth = 0 for (i, (k, v)) in enumerate(t) i > rows && break ks[i] = sprint(show, k, context=recur_io_k, sizehint=0) vs[i] = sprint(show, v, context=recur_io_v, sizehint=0) - keylen = clamp(length(ks[i]), keylen, cols) - vallen = clamp(length(vs[i]), vallen, cols) + keywidth = clamp(hascolor ? textwidth(ANSIIterator(ks[i])) : textwidth(ks[i]), keywidth, cols) + valwidth = clamp(hascolor ? textwidth(ANSIIterator(vs[i])) : textwidth(vs[i]), valwidth, cols) end - if keylen > max(div(cols, 2), cols - vallen) - keylen = max(cld(cols, 3), cols - vallen) + if keywidth > max(div(cols, 2), cols - valwidth) + keywidth = max(cld(cols, 3), cols - valwidth) end else rows = cols = typemax(Int) @@ -123,12 +188,12 @@ function show(io::IO, ::MIME"text/plain", t::AbstractDict{K,V}) where {K,V} for (i, (k, v)) in enumerate(t) print(io, "\n ") if i == rows < length(t) - print(io, rpad("⋮", keylen), " => ⋮") + print(io, rpad("⋮", keywidth), " => ⋮") break end if limit - key = rpad(_truncate_at_width_or_chars(ks[i], keylen, "\r\n"), keylen) + key = _truncate_at_width_or_chars(hascolor, ks[i], keywidth, true) else key = sprint(show, k, context=recur_io_k, sizehint=0) end @@ -136,7 +201,7 @@ function show(io::IO, ::MIME"text/plain", t::AbstractDict{K,V}) where {K,V} print(io, " => ") if limit - val = _truncate_at_width_or_chars(vs[i], cols - keylen, "\r\n") + val = _truncate_at_width_or_chars(hascolor, vs[i], cols - keywidth) print(io, val) else show(recur_io_v, v) @@ -180,7 +245,7 @@ function show(io::IO, ::MIME"text/plain", t::AbstractSet{T}) where T if limit str = sprint(show, v, context=recur_io, sizehint=0) - print(io, _truncate_at_width_or_chars(str, cols, "\r\n")) + print(io, _truncate_at_width_or_chars(get(io, :color, false)::Bool, str, cols)) else show(recur_io, v) end @@ -241,7 +306,7 @@ function IOContext(io::IO, dict::ImmutableDict) IOContext{typeof(io0)}(io0, dict) end -convert(::Type{IOContext}, io::IO) = IOContext(unwrapcontext(io)...) +convert(::Type{IOContext}, io::IO) = IOContext(unwrapcontext(io)...)::IOContext IOContext(io::IO) = convert(IOContext, io) @@ -372,9 +437,10 @@ Julia code when possible. [`repr`](@ref) returns the output of `show` as a string. -To customize human-readable text output for objects of type `T`, define -`show(io::IO, ::MIME"text/plain", ::T)` instead. Checking the `:compact` -[`IOContext`](@ref) property of `io` in such methods is recommended, +For a more verbose human-readable text output for objects of type `T`, define +`show(io::IO, ::MIME"text/plain", ::T)` in addition. Checking the `:compact` +[`IOContext`](@ref) key (often checked as `get(io, :compact, false)::Bool`) +of `io` in such methods is recommended, since some containers show their elements by calling this method with `:compact => true`. @@ -430,13 +496,20 @@ function _show_default(io::IO, @nospecialize(x)) print(io,')') end +function active_module() + isassigned(REPL_MODULE_REF) || return Main + REPL = REPL_MODULE_REF[] + return invokelatest(REPL.active_module)::Module +end + # Check if a particular symbol is exported from a standard library module function is_exported_from_stdlib(name::Symbol, mod::Module) !isdefined(mod, name) && return false orig = getfield(mod, name) while !(mod === Base || mod === Core) + activemod = active_module() parent = parentmodule(mod) - if mod === Main || mod === parent || parent === Main + if mod === activemod || mod === parent || parent === activemod return false end mod = parent @@ -444,29 +517,30 @@ function is_exported_from_stdlib(name::Symbol, mod::Module) return isexported(mod, name) && isdefined(mod, name) && !isdeprecated(mod, name) && getfield(mod, name) === orig end -function show_function(io::IO, f::Function, compact::Bool) +function show_function(io::IO, f::Function, compact::Bool, fallback::Function) ft = typeof(f) mt = ft.name.mt if mt === Symbol.name.mt # uses shared method table - show_default(io, f) + fallback(io, f) elseif compact print(io, mt.name) elseif isdefined(mt, :module) && isdefined(mt.module, mt.name) && getfield(mt.module, mt.name) === f - if is_exported_from_stdlib(mt.name, mt.module) || mt.module === Main + mod = active_module() + if is_exported_from_stdlib(mt.name, mt.module) || mt.module === mod show_sym(io, mt.name) else print(io, mt.module, ".") show_sym(io, mt.name) end else - show_default(io, f) + fallback(io, f) end end -show(io::IO, f::Function) = show_function(io, f, get(io, :compact, false)::Bool) -print(io::IO, f::Function) = show_function(io, f, true) +show(io::IO, f::Function) = show_function(io, f, get(io, :compact, false)::Bool, show_default) +print(io::IO, f::Function) = show_function(io, f, true, show) function show(io::IO, f::Core.IntrinsicFunction) if !(get(io, :compact, false)::Bool) @@ -485,8 +559,6 @@ function print_without_params(@nospecialize(x)) return isa(b, DataType) && b.name.wrapper === x end -has_typevar(@nospecialize(t), v::TypeVar) = ccall(:jl_has_typevar, Cint, (Any, Any), t, v)!=0 - function io_has_tvar_name(io::IOContext, name::Symbol, @nospecialize(x)) for (key, val) in io.dict if key === :unionall_env && val isa TypeVar && val.name === name && has_typevar(x, val) @@ -501,7 +573,7 @@ modulesof!(s::Set{Module}, x::TypeVar) = modulesof!(s, x.ub) function modulesof!(s::Set{Module}, x::Type) x = unwrap_unionall(x) if x isa DataType - push!(s, x.name.module) + push!(s, parentmodule(x)) elseif x isa Union modulesof!(s, x.a) modulesof!(s, x.b) @@ -537,7 +609,7 @@ function make_typealias(@nospecialize(x::Type)) end x isa UnionAll && push!(xenv, x) for mod in mods - for name in names(mod) + for name in unsorted_names(mod) if isdefined(mod, name) && !isdeprecated(mod, name) && isconst(mod, name) alias = getfield(mod, name) if alias isa Type && !has_free_typevars(alias) && !print_without_params(alias) && x <: alias @@ -547,7 +619,7 @@ function make_typealias(@nospecialize(x::Type)) env = env::SimpleVector # TODO: In some cases (such as the following), the `env` is over-approximated. # We'd like to disable `fix_inferred_var_bound` since we'll already do that fix-up here. - # (or detect and reverse the compution of it here). + # (or detect and reverse the computation of it here). # T = Array{Array{T,1}, 1} where T # (ti, env) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), T, Vector) # env[1].ub.var == T.var @@ -648,9 +720,9 @@ end function show_typealias(io::IO, name::GlobalRef, x::Type, env::SimpleVector, wheres::Vector) if !(get(io, :compact, false)::Bool) # Print module prefix unless alias is visible from module passed to - # IOContext. If :module is not set, default to Main. nothing can be used - # to force printing prefix. - from = get(io, :module, Main) + # 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 (from === nothing || !isvisible(name.name, name.mod, from)) show(io, name.mod) print(io, ".") @@ -727,11 +799,11 @@ function show_typealias(io::IO, @nospecialize(x::Type)) end function make_typealiases(@nospecialize(x::Type)) - Any === x && return Core.svec(), Union{} - x <: Tuple && return Core.svec(), Union{} + aliases = SimpleVector[] + Any === x && return aliases, Union{} + x <: Tuple && return aliases, Union{} mods = modulesof!(Set{Module}(), x) Core in mods && push!(mods, Base) - aliases = SimpleVector[] vars = Dict{Symbol,TypeVar}() xenv = UnionAll[] each = Any[] @@ -741,7 +813,7 @@ function make_typealiases(@nospecialize(x::Type)) end x isa UnionAll && push!(xenv, x) for mod in mods - for name in names(mod) + for name in unsorted_names(mod) if isdefined(mod, name) && !isdeprecated(mod, name) && isconst(mod, name) alias = getfield(mod, name) if alias isa Type && !has_free_typevars(alias) && !print_without_params(alias) && !(alias <: Tuple) @@ -783,23 +855,24 @@ function make_typealiases(@nospecialize(x::Type)) end end if isempty(aliases) - return Core.svec(), Union{} + return aliases, Union{} end - sort!(aliases, by = x -> x[4], rev = true) # heuristic sort by "best" environment + sort!(aliases, by = x -> x[4]::Tuple{Int,Int}, rev = true) # heuristic sort by "best" environment let applied = Union{} applied1 = Union{} keep = SimpleVector[] prev = (0, 0) for alias in aliases - if alias[4][1] < 2 + alias4 = alias[4]::Tuple{Int,Int} + if alias4[1] < 2 if !(alias[3] <: applied) applied1 = Union{applied1, alias[3]} push!(keep, alias) end - elseif alias[4] == prev || !(alias[3] <: applied) + elseif alias4 == prev || !(alias[3] <: applied) applied = applied1 = Union{applied1, alias[3]} push!(keep, alias) - prev = alias[4] + prev = alias4 end end return keep, applied1 @@ -825,16 +898,17 @@ function show_unionaliases(io::IO, x::Union) end if first && !tvar && length(aliases) == 1 alias = aliases[1] - wheres = make_wheres(io, alias[2], x) - show_typealias(io, alias[1], x, alias[2], wheres) + env = alias[2]::SimpleVector + wheres = make_wheres(io, env, x) + show_typealias(io, alias[1], x, env, wheres) show_wheres(io, wheres) else for alias in aliases print(io, first ? "Union{" : ", ") first = false - env = alias[2] - wheres = make_wheres(io, alias[2], x) - show_typealias(io, alias[1], x, alias[2], wheres) + env = alias[2]::SimpleVector + wheres = make_wheres(io, env, x) + show_typealias(io, alias[1], x, env, wheres) show_wheres(io, wheres) end if tvar @@ -879,15 +953,15 @@ end show(io::IO, @nospecialize(x::Type)) = _show_type(io, inferencebarrier(x)) function _show_type(io::IO, @nospecialize(x::Type)) if print_without_params(x) - show_type_name(io, unwrap_unionall(x).name) + show_type_name(io, (unwrap_unionall(x)::DataType).name) return - elseif get(io, :compact, true) && show_typealias(io, x) + elseif get(io, :compact, true)::Bool && show_typealias(io, x) return elseif x isa DataType show_datatype(io, x) return elseif x isa Union - if get(io, :compact, true) && show_unionaliases(io, x) + if get(io, :compact, true)::Bool && show_unionaliases(io, x) return end print(io, "Union") @@ -930,9 +1004,9 @@ end # If an object with this name exists in 'from', we need to check that it's the same binding # and that it's not deprecated. function isvisible(sym::Symbol, parent::Module, from::Module) - owner = ccall(:jl_binding_owner, Any, (Any, Any), parent, sym) - from_owner = ccall(:jl_binding_owner, Any, (Any, Any), from, sym) - return owner !== nothing && from_owner === owner && + owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), parent, sym) + from_owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), from, sym) + return owner !== C_NULL && from_owner === owner && !isdeprecated(parent, sym) && isdefined(from, sym) # if we're going to return true, force binding resolution end @@ -962,9 +1036,9 @@ function show_type_name(io::IO, tn::Core.TypeName) quo = false if !(get(io, :compact, false)::Bool) # Print module prefix unless type is visible from module passed to - # IOContext If :module is not set, default to Main. nothing can be used - # to force printing prefix - from = get(io, :module, Main) + # 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)) show(io, tn.module) print(io, ".") @@ -986,28 +1060,62 @@ end function show_datatype(io::IO, x::DataType, wheres::Vector{TypeVar}=TypeVar[]) parameters = x.parameters::SimpleVector istuple = x.name === Tuple.name + isnamedtuple = x.name === typename(NamedTuple) n = length(parameters) - # Print homogeneous tuples with more than 3 elements compactly as NTuple{N, T} + # Print tuple types with homogeneous tails longer than max_n compactly using `NTuple` or `Vararg` if istuple - if n > 3 && all(@nospecialize(i) -> (parameters[1] === i), parameters) + max_n = 3 + taillen = 1 + for i in (n-1):-1:1 + if parameters[i] === parameters[n] + taillen += 1 + else + break + end + end + if n == taillen > max_n print(io, "NTuple{", n, ", ") show(io, parameters[1]) print(io, "}") else print(io, "Tuple{") - # join(io, params, ", ") params but `show` it - first = true - for param in parameters - first ? (first = false) : print(io, ", ") - show(io, param) + for i = 1:(taillen > max_n ? n-taillen : n) + i > 1 && print(io, ", ") + show(io, parameters[i]) + end + if taillen > max_n + print(io, ", Vararg{") + show(io, parameters[n]) + print(io, ", ", taillen, "}") end print(io, "}") end - else - show_type_name(io, x.name) - show_typeparams(io, parameters, unwrap_unionall(x.name.wrapper).parameters, wheres) + return + elseif isnamedtuple + syms, types = parameters + first = true + if syms isa Tuple && types isa DataType + print(io, "@NamedTuple{") + for i in 1:length(syms) + if !first + print(io, ", ") + end + print(io, syms[i]) + typ = types.parameters[i] + if typ !== Any + print(io, "::") + show(io, typ) + end + first = false + end + print(io, "}") + return + end end + + show_type_name(io, x.name) + show_typeparams(io, parameters, (unwrap_unionall(x.name.wrapper)::DataType).parameters, wheres) end function show_supertypes(io::IO, typ::DataType) @@ -1088,7 +1196,7 @@ function show(io::IO, p::Pair) isdelimited(io_i, p[i]) || print(io, "(") show(io_i, p[i]) isdelimited(io_i, p[i]) || print(io, ")") - i == 1 && print(io, get(io, :compact, false) ? "=>" : " => ") + i == 1 && print(io, get(io, :compact, false)::Bool ? "=>" : " => ") end end @@ -1118,12 +1226,12 @@ function print_fullname(io::IO, m::Module) end end -function sourceinfo_slotnames(src::CodeInfo) - slotnames = src.slotnames +sourceinfo_slotnames(src::CodeInfo) = sourceinfo_slotnames(src.slotnames) +function sourceinfo_slotnames(slotnames::Vector{Symbol}) names = Dict{String,Int}() printnames = Vector{String}(undef, length(slotnames)) for i in eachindex(slotnames) - if slotnames[i] == :var"#unused#" + if slotnames[i] === :var"#unused#" printnames[i] = "_" continue end @@ -1283,12 +1391,14 @@ show(io::IO, s::Symbol) = show_unquoted_quote_expr(io, s, 0, 0, 0) # # This is consistent with many other show methods, i.e.: # show(Set([1,2,3])) # ==> "Set{Int64}([2,3,1])" -# eval(Meta.parse("Set{Int64}([2,3,1])”) # ==> An actual set +# eval(Meta.parse("Set{Int64}([2,3,1])")) # ==> An actual set # While this isn’t true of ALL show methods, it is of all ASTs. -const ExprNode = Union{Expr, QuoteNode, Slot, LineNumberNode, SSAValue, - GotoNode, GlobalRef, PhiNode, PhiCNode, UpsilonNode, - Core.Compiler.GotoIfNot, Core.Compiler.ReturnNode} +using Core.Compiler: TypedSlot, UnoptSlot + +const ExprNode = Union{Expr, QuoteNode, UnoptSlot, LineNumberNode, SSAValue, + GotoNode, GotoIfNot, GlobalRef, PhiNode, PhiCNode, UpsilonNode, + ReturnNode} # Operators have precedence levels from 1-N, and show_unquoted defaults to a # precedence level of 0 (the fourth argument). The top-level print and show # methods use a precedence of -1 to specially allow space-separated macro syntax. @@ -1476,8 +1586,6 @@ function operator_associativity(s::Symbol) return :left end -const is_expr = isexpr - is_quoted(ex) = false is_quoted(ex::QuoteNode) = true is_quoted(ex::Expr) = is_expr(ex, :quote, 1) || is_expr(ex, :inert, 1) @@ -1490,9 +1598,7 @@ unquoted(ex::Expr) = ex.args[1] function printstyled end function with_output_color end -is_expected_union(u::Union) = u.a == Nothing || u.b == Nothing || u.a == Missing || u.b == Missing - -emphasize(io, str::AbstractString, col = Base.error_color()) = get(io, :color, false) ? +emphasize(io, str::AbstractString, col = Base.error_color()) = get(io, :color, false)::Bool ? printstyled(io, str; color=col, bold=true) : print(io, uppercase(str)) @@ -1616,7 +1722,14 @@ end ## AST printing ## -show_unquoted(io::IO, val::SSAValue, ::Int, ::Int) = print(io, "%", val.id) +function show_unquoted(io::IO, val::SSAValue, ::Int, ::Int) + if get(io, :maxssaid, typemax(Int))::Int < val.id + # invalid SSAValue, print this in red for better recognition + printstyled(io, "%", val.id; color=:red) + else + print(io, "%", val.id) + end +end show_unquoted(io::IO, sym::Symbol, ::Int, ::Int) = show_sym(io, sym, allow_macroname=false) show_unquoted(io::IO, ex::LineNumberNode, ::Int, ::Int) = show_linenumber(io, ex.line, ex.file) show_unquoted(io::IO, ex::GotoNode, ::Int, ::Int) = print(io, "goto %", ex.label) @@ -1634,7 +1747,7 @@ function show_globalref(io::IO, ex::GlobalRef; allow_macroname=false) nothing end -function show_unquoted(io::IO, ex::Slot, ::Int, ::Int) +function show_unquoted(io::IO, ex::UnoptSlot, ::Int, ::Int) typ = isa(ex, TypedSlot) ? ex.typ : Any slotid = ex.id slotnames = get(io, :SOURCE_SLOTNAMES, false) @@ -1729,10 +1842,16 @@ function show_import_path(io::IO, ex, quote_level) end elseif ex.head === :(.) for i = 1:length(ex.args) - if ex.args[i] === :(.) + sym = ex.args[i]::Symbol + if sym === :(.) print(io, '.') else - show_sym(io, ex.args[i]::Symbol, allow_macroname=(i==length(ex.args))) + if sym === :(..) + # special case for https://github.com/JuliaLang/julia/issues/49168 + print(io, "(..)") + else + show_sym(io, sym, allow_macroname=(i==length(ex.args))) + end i < length(ex.args) && print(io, '.') end end @@ -1753,9 +1872,10 @@ function allow_macroname(ex) end end -function is_core_macro(arg, macro_name::AbstractString) - arg === GlobalRef(Core, Symbol(macro_name)) -end +is_core_macro(arg::GlobalRef, macro_name::AbstractString) = is_core_macro(arg, Symbol(macro_name)) +is_core_macro(arg::GlobalRef, macro_name::Symbol) = arg == GlobalRef(Core, macro_name) +is_core_macro(@nospecialize(arg), macro_name::AbstractString) = false +is_core_macro(@nospecialize(arg), macro_name::Symbol) = false # symbol for IOContext flag signaling whether "begin" is treated # as an ordinary symbol, which is true in indexing expressions. @@ -1791,8 +1911,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In # . print(io, '.') # item - parens = !(field isa Symbol) || (field::Symbol in quoted_syms) - quoted = parens || isoperator(field) + if isa(field, Symbol) + parens = field in quoted_syms + quoted = parens || isoperator(field) + else + parens = quoted = true + end quoted && print(io, ':') parens && print(io, '(') show_unquoted(io, field, indent, 0, quote_level) @@ -1916,8 +2040,9 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In # binary operator (i.e. "x + y") elseif func_prec > 0 # is a binary operator + func = func::Symbol # operator_precedence returns func_prec == 0 for non-Symbol na = length(func_args) - if (na == 2 || (na > 2 && isa(func, Symbol) && func in (:+, :++, :*)) || (na == 3 && func === :(:))) && + if (na == 2 || (na > 2 && func in (:+, :++, :*)) || (na == 3 && func === :(:))) && all(a -> !isa(a, Expr) || a.head !== :..., func_args) sep = func === :(:) ? "$func" : " $func " @@ -1949,7 +2074,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In # other call-like expressions ("A[1,2]", "T{X,Y}", "f.(X,Y)") elseif haskey(expr_calls, head) && nargs >= 1 # :ref/:curly/:calldecl/:(.) funcargslike = head === :(.) ? (args[2]::Expr).args : args[2:end] - show_call(head == :ref ? IOContext(io, beginsym=>true) : io, head, args[1], funcargslike, indent, quote_level, head !== :curly) + show_call(head === :ref ? IOContext(io, beginsym=>true) : io, head, args[1], funcargslike, indent, quote_level, head !== :curly) # comprehensions elseif head === :typed_comprehension && nargs == 2 @@ -2078,12 +2203,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In elseif head === :macrocall && nargs >= 2 # handle some special syntaxes # `a b c` - if is_core_macro(args[1], "@cmd") + if is_core_macro(args[1], :var"@cmd") print(io, "`", args[3], "`") # 11111111111111111111, 0xfffffffffffffffff, 1111...many digits... - elseif is_core_macro(args[1], "@int128_str") || - is_core_macro(args[1], "@uint128_str") || - is_core_macro(args[1], "@big_str") + elseif is_core_macro(args[1], :var"@int128_str") || + is_core_macro(args[1], :var"@uint128_str") || + is_core_macro(args[1], :var"@big_str") print(io, args[3]) # x"y" and x"y"z elseif isa(args[1], Symbol) && nargs >= 3 && isa(args[3], String) && @@ -2289,7 +2414,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In elseif head === :meta && nargs == 1 && args[1] === :pop_loc print(io, "# meta: pop location") elseif head === :meta && nargs == 2 && args[1] === :pop_loc - print(io, "# meta: pop locations ($(args[2]))") + print(io, "# meta: pop locations ($(args[2]::Int))") # print anything else as "Expr(head, args...)" else unhandled = true @@ -2314,16 +2439,14 @@ end # `io` should contain the UnionAll env of the signature function show_signature_function(io::IO, @nospecialize(ft), demangle=false, fargname="", html=false, qualified=false) uw = unwrap_unionall(ft) - if ft <: Function && isa(uw, DataType) && isempty(uw.parameters) && - isdefined(uw.name.module, uw.name.mt.name) && - ft == typeof(getfield(uw.name.module, uw.name.mt.name)) - if qualified && !is_exported_from_stdlib(uw.name.mt.name, uw.name.module) && uw.name.module !== Main - print_within_stacktrace(io, uw.name.module, '.', bold=true) + if ft <: Function && isa(uw, DataType) && isempty(uw.parameters) && _isself(uw) + uwmod = parentmodule(uw) + if qualified && !is_exported_from_stdlib(uw.name.mt.name, uwmod) && uwmod !== Main + print_within_stacktrace(io, uwmod, '.', bold=true) end s = sprint(show_sym, (demangle ? demangle_function_name : identity)(uw.name.mt.name), context=io) print_within_stacktrace(io, s, bold=true) - elseif isa(ft, DataType) && ft.name === Type.body.name && - (f = ft.parameters[1]; !isa(f, TypeVar)) + elseif isType(ft) && (f = ft.parameters[1]; !isa(f, TypeVar)) uwf = unwrap_unionall(f) parens = isa(f, UnionAll) && !(isa(uwf, DataType) && f === uwf.name.wrapper) parens && print(io, "(") @@ -2378,7 +2501,7 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type; print_within_stacktrace(io, argnames[i]; color=:light_black) end print(io, "::") - print_type_stacktrace(env_io, sig[i]) + print_type_bicolor(env_io, sig[i]; use_color = get(io, :backtrace, false)) end if kwargs !== nothing print(io, "; ") @@ -2388,7 +2511,7 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type; first = false print_within_stacktrace(io, k; color=:light_black) print(io, "::") - print_type_stacktrace(io, t) + print_type_bicolor(io, t; use_color = get(io, :backtrace, false)) end end print_within_stacktrace(io, ")", bold=true) @@ -2396,16 +2519,25 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type; nothing end -function print_type_stacktrace(io, type; color=:normal) +function print_type_bicolor(io, type; kwargs...) str = sprint(show, type, context=io) + print_type_bicolor(io, str; kwargs...) +end + +function print_type_bicolor(io, str::String; color=:normal, inner_color=:light_black, use_color::Bool=true) i = findfirst('{', str) - if !get(io, :backtrace, false)::Bool + if !use_color # fix #41928 print(io, str) elseif i === nothing printstyled(io, str; color=color) else printstyled(io, str[1:prevind(str,i)]; color=color) - printstyled(io, str[i:end]; color=:light_black) + if endswith(str, "...") + printstyled(io, str[i:prevind(str,end,3)]; color=inner_color) + printstyled(io, "..."; color=color) + else + printstyled(io, str[i:end]; color=inner_color) + end end end @@ -2486,9 +2618,9 @@ module IRShow const Compiler = Core.Compiler using Core.IR import ..Base - import .Compiler: IRCode, ReturnNode, GotoIfNot, CFG, scan_ssa_use!, Argument, - isexpr, compute_basic_blocks, block_for_inst, - TriState, Effects, ALWAYS_TRUE, ALWAYS_FALSE + import .Compiler: IRCode, TypedSlot, CFG, scan_ssa_use!, + isexpr, compute_basic_blocks, block_for_inst, IncrementalCompact, + Effects, ALWAYS_TRUE, ALWAYS_FALSE 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) @@ -2532,13 +2664,18 @@ function show(io::IO, src::CodeInfo; debuginfo::Symbol=:source) end function show(io::IO, inferred::Core.Compiler.InferenceResult) - tt = inferred.linfo.specTypes.parameters[2:end] + mi = inferred.linfo + tt = mi.specTypes.parameters[2:end] tts = join(["::$(t)" for t in tt], ", ") rettype = inferred.result if isa(rettype, Core.Compiler.InferenceState) rettype = rettype.bestguess end - print(io, "$(inferred.linfo.def.name)($(tts)) => $(rettype)") + if isa(mi.def, Method) + print(io, mi.def.name, "(", tts, " => ", rettype, ")") + else + print(io, "Toplevel MethodInstance thunk from ", mi.def, " => ", rettype) + end end function show(io::IO, ::Core.Compiler.NativeInterpreter) @@ -2627,7 +2764,7 @@ function dump(io::IOContext, x::Array, n::Int, indent) println(io) recur_io = IOContext(io, :SHOWN_SET => x) lx = length(x) - if get(io, :limit, false) + if get(io, :limit, false)::Bool dump_elts(recur_io, x, n, indent, 1, (lx <= 10 ? lx : 5)) if lx > 10 println(io) @@ -2657,7 +2794,7 @@ function dump(io::IOContext, x::DataType, n::Int, indent) tvar_io = IOContext(tvar_io, :unionall_env => tparam) end end - if x.name === NamedTuple_typename && !(x.parameters[1] isa Tuple) + if x.name === _NAMEDTUPLE_NAME && !(x.parameters[1] isa Tuple) # named tuple type with unknown field names return end @@ -2708,11 +2845,14 @@ MyStruct ``` """ function dump(arg; maxdepth=DUMP_DEFAULT_MAXDEPTH) - # this is typically used interactively, so default to being in Main - mod = get(stdout, :module, Main) - dump(IOContext(stdout, :limit => true, :module => mod), arg; maxdepth=maxdepth) + # this is typically used interactively, so default to being in Main (or current active module) + mod = get(stdout, :module, active_module()) + dump(IOContext(stdout::IO, :limit => true, :module => mod), arg; maxdepth=maxdepth) end +nocolor(io::IO) = IOContext(io, :color => false) +alignment_from_show(io::IO, x::Any) = + textwidth(sprint(show, x, context=nocolor(io), sizehint=0)) """ `alignment(io, X)` returns a tuple (left,right) showing how many characters are @@ -2730,35 +2870,38 @@ julia> Base.alignment(stdout, 1 + 10im) (3, 5) ``` """ -alignment(io::IO, x::Any) = (0, length(sprint(show, x, context=io, sizehint=0))) -alignment(io::IO, x::Number) = (length(sprint(show, x, context=io, sizehint=0)), 0) -alignment(io::IO, x::Integer) = (length(sprint(show, x, context=io, sizehint=0)), 0) +alignment(io::IO, x::Any) = (0, alignment_from_show(io, x)) +alignment(io::IO, x::Number) = (alignment_from_show(io, x), 0) +alignment(io::IO, x::Integer) = (alignment_from_show(io, x), 0) function alignment(io::IO, x::Real) - m = match(r"^(.*?)((?:[\.eEfF].*)?)$", sprint(show, x, context=io, sizehint=0)) - m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) : - (length(m.captures[1]), length(m.captures[2])) + s = sprint(show, x, context=nocolor(io), sizehint=0) + m = match(r"^(.*?)((?:[\.eEfF].*)?)$", s) + m === nothing ? (textwidth(s), 0) : + (textwidth(m.captures[1]), textwidth(m.captures[2])) end function alignment(io::IO, x::Complex) - m = match(r"^(.*[^ef][\+\-])(.*)$", sprint(show, x, context=io, sizehint=0)) - m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) : - (length(m.captures[1]), length(m.captures[2])) + s = sprint(show, x, context=nocolor(io), sizehint=0) + m = match(r"^(.*[^ef][\+\-])(.*)$", s) + m === nothing ? (textwidth(s), 0) : + (textwidth(m.captures[1]), textwidth(m.captures[2])) end function alignment(io::IO, x::Rational) - m = match(r"^(.*?/)(/.*)$", sprint(show, x, context=io, sizehint=0)) - m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) : - (length(m.captures[1]), length(m.captures[2])) + s = sprint(show, x, context=nocolor(io), sizehint=0) + m = match(r"^(.*?/)(/.*)$", s) + m === nothing ? (textwidth(s), 0) : + (textwidth(m.captures[1]), textwidth(m.captures[2])) end function alignment(io::IO, x::Pair) - s = sprint(show, x, context=io, sizehint=0) + fullwidth = alignment_from_show(io, x) if !isdelimited(io, x) # i.e. use "=>" for display ctx = IOContext(io, :typeinfo => gettypeinfos(io, x)[1]) - left = length(sprint(show, x.first, context=ctx, sizehint=0)) + left = alignment_from_show(ctx, x.first) left += 2 * !isdelimited(ctx, x.first) # for parens around p.first left += !(get(io, :compact, false)::Bool) # spaces are added around "=>" - (left+1, length(s)-left-1) # +1 for the "=" part of "=>" + (left+1, fullwidth-left-1) # +1 for the "=" part of "=>" else - (0, length(s)) # as for x::Any + (0, fullwidth) # as for x::Any end end diff --git a/base/slicearray.jl b/base/slicearray.jl new file mode 100644 index 0000000000000..e5a433cdb8d2a --- /dev/null +++ b/base/slicearray.jl @@ -0,0 +1,245 @@ +""" + AbstractSlices{S,N} <: AbstractArray{S,N} + +Supertype for arrays of slices into a parent array over some dimension(s), +returning views that select all the data from the other dimensions. + +`parent` will return the parent array. +""" +abstract type AbstractSlices{T,N} <: AbstractArray{T,N} end + +""" + Slices{P,SM,AX,S,N} <: AbstractSlices{S,N} + +An `AbstractArray` of slices into a parent array over specified dimension(s), +returning views that select all the data from the other dimension(s). + +These should typically be constructed by [`eachslice`](@ref), [`eachcol`](@ref) or +[`eachrow`](@ref). + +[`parent(s::Slices)`](@ref) will return the parent array. +""" +struct Slices{P,SM,AX,S,N} <: AbstractSlices{S,N} + """ + Parent array + """ + parent::P + """ + A tuple of length `ndims(parent)`, denoting how each dimension should be handled: + - an integer `i`: this is the `i`th dimension of the outer `Slices` object. + - `:`: an "inner" dimension + """ + slicemap::SM + """ + A tuple of length `N` containing the [`axes`](@ref) of the `Slices` object. + """ + axes::AX +end + +unitaxis(::AbstractArray) = Base.OneTo(1) + +function Slices(A::P, slicemap::SM, ax::AX) where {P,SM,AX} + N = length(ax) + argT = map((a,l) -> l === (:) ? Colon : eltype(a), axes(A), slicemap) + S = Base.promote_op(view, P, argT...) + Slices{P,SM,AX,S,N}(A, slicemap, ax) +end + +_slice_check_dims(N) = nothing +function _slice_check_dims(N, dim, dims...) + 1 <= dim <= N || throw(DimensionMismatch("Invalid dimension $dim")) + dim in dims && throw(DimensionMismatch("Dimensions $dims are not unique")) + _slice_check_dims(N,dims...) +end + +@constprop :aggressive function _eachslice(A::AbstractArray{T,N}, dims::NTuple{M,Integer}, drop::Bool) where {T,N,M} + _slice_check_dims(N,dims...) + if drop + # if N = 4, dims = (3,1) then + # axes = (axes(A,3), axes(A,1)) + # slicemap = (2, :, 1, :) + ax = map(dim -> axes(A,dim), dims) + slicemap = ntuple(dim -> something(findfirst(isequal(dim), dims), (:)), N) + return Slices(A, slicemap, ax) + else + # if N = 4, dims = (3,1) then + # axes = (axes(A,1), OneTo(1), axes(A,3), OneTo(1)) + # slicemap = (1, :, 3, :) + ax = ntuple(dim -> dim in dims ? axes(A,dim) : unitaxis(A), N) + slicemap = ntuple(dim -> dim in dims ? dim : (:), N) + return Slices(A, slicemap, ax) + end +end +@inline function _eachslice(A::AbstractArray, dim::Integer, drop::Bool) + _eachslice(A, (dim,), drop) +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 +integer or a tuple of integers. + +If `drop = true` (the default), the outer `Slices` will drop the inner dimensions, and +the ordering of the dimensions will match those in `dims`. If `drop = false`, then the +`Slices` will have the same dimensionality as the underlying array, with inner +dimensions having size 1. + +See [`stack`](@ref)`(slices; dims)` for the inverse of `eachslice(A; dims::Integer)`. + +See also [`eachrow`](@ref), [`eachcol`](@ref), [`mapslices`](@ref) and [`selectdim`](@ref). + +!!! compat "Julia 1.1" + This function requires at least Julia 1.1. + +!!! compat "Julia 1.9" + Prior to Julia 1.9, this returned an iterator, and only a single dimension `dims` was supported. + +# Example + +```jldoctest +julia> m = [1 2 3; 4 5 6; 7 8 9] +3×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + +julia> s = eachslice(m, dims=1) +3-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] + +julia> s[1] +3-element view(::Matrix{Int64}, 1, :) with eltype Int64: + 1 + 2 + 3 + +julia> eachslice(m, dims=1, drop=false) +3×1 Slices{Matrix{Int64}, Tuple{Int64, Colon}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}, 2}: + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] +``` +""" +@inline function eachslice(A; dims, drop=true) + _eachslice(A, dims, drop) +end + +""" + eachrow(A::AbstractVecOrMat) <: AbstractVector + +Create a [`RowSlices`](@ref) object that is a vector of rows of matrix or vector `A`. +Row slices are returned as `AbstractVector` views of `A`. + +For the inverse, see [`stack`](@ref)`(rows; dims=1)`. + +See also [`eachcol`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). + +!!! compat "Julia 1.1" + This function requires at least Julia 1.1. + +!!! compat "Julia 1.9" + Prior to Julia 1.9, this returned an iterator. + +# Example + +```jldoctest +julia> a = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> s = eachrow(a) +2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}: + [1, 2] + [3, 4] + +julia> s[1] +2-element view(::Matrix{Int64}, 1, :) with eltype Int64: + 1 + 2 +``` +""" +eachrow(A::AbstractMatrix) = _eachslice(A, (1,), true) +eachrow(A::AbstractVector) = eachrow(reshape(A, size(A,1), 1)) + +""" + eachcol(A::AbstractVecOrMat) <: AbstractVector + +Create a [`ColumnSlices`](@ref) object that is a vector of columns of matrix or vector `A`. +Column slices are returned as `AbstractVector` views of `A`. + +For the inverse, see [`stack`](@ref)`(cols)` or `reduce(`[`hcat`](@ref)`, cols)`. + +See also [`eachrow`](@ref), [`eachslice`](@ref) and [`mapslices`](@ref). + +!!! compat "Julia 1.1" + This function requires at least Julia 1.1. + +!!! compat "Julia 1.9" + Prior to Julia 1.9, this returned an iterator. + +# Example + +```jldoctest +julia> a = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> s = eachcol(a) +2-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}: + [1, 3] + [2, 4] + +julia> s[1] +2-element view(::Matrix{Int64}, :, 1) with eltype Int64: + 1 + 3 +``` +""" +eachcol(A::AbstractMatrix) = _eachslice(A, (2,), true) +eachcol(A::AbstractVector) = eachcol(reshape(A, size(A, 1), 1)) + +""" + RowSlices{M,AX,S} + +A special case of [`Slices`](@ref) that is a vector of row slices of a matrix, as +constructed by [`eachrow`](@ref). + +[`parent`](@ref) can be used to get the underlying matrix. +""" +const RowSlices{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Int,Colon},AX,S,1} + +""" + ColumnSlices{M,AX,S} + +A special case of [`Slices`](@ref) that is a vector of column slices of a matrix, as +constructed by [`eachcol`](@ref). + +[`parent`](@ref) can be used to get the underlying matrix. +""" +const ColumnSlices{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Colon,Int},AX,S,1} + + +IteratorSize(::Type{Slices{P,SM,AX,S,N}}) where {P,SM,AX,S,N} = HasShape{N}() +axes(s::Slices) = s.axes +size(s::Slices) = map(length, s.axes) + +@inline function _slice_index(s::Slices, c...) + return map(l -> l === (:) ? (:) : c[l], s.slicemap) +end + +@inline function getindex(s::Slices{P,SM,AX,S,N}, I::Vararg{Int,N}) where {P,SM,AX,S,N} + @boundscheck checkbounds(s, I...) + @inbounds view(s.parent, _slice_index(s, I...)...) +end +@inline function setindex!(s::Slices{P,SM,AX,S,N}, val, I::Vararg{Int,N}) where {P,SM,AX,S,N} + @boundscheck checkbounds(s, I...) + @inbounds s.parent[_slice_index(s, I...)...] = val +end + +parent(s::Slices) = s.parent diff --git a/base/some.jl b/base/some.jl index 8be58739a4df4..0d538cbed6c23 100644 --- a/base/some.jl +++ b/base/some.jl @@ -29,15 +29,14 @@ end function nonnothingtype_checked(T::Type) R = nonnothingtype(T) R >: T && error("could not compute non-nothing type") + R <: Union{} && error("cannot convert a value to nothing for assignment") return R end convert(::Type{T}, x::T) where {T>:Nothing} = x convert(::Type{T}, x) where {T>:Nothing} = convert(nonnothingtype_checked(T), x) -convert(::Type{Nothing}, x) = throw(MethodError(convert, (Nothing, x))) -convert(::Type{Nothing}, ::Nothing) = nothing convert(::Type{Some{T}}, x::Some{T}) where {T} = x -convert(::Type{Some{T}}, x::Some) where {T} = Some{T}(convert(T, x.value)) +convert(::Type{Some{T}}, x::Some) where {T} = Some{T}(convert(T, x.value))::Some{T} function show(io::IO, x::Some) if get(io, :typeinfo, Any) == typeof(x) @@ -65,7 +64,7 @@ Return `true` if `x === nothing`, and return `false` if not. !!! compat "Julia 1.1" This function requires at least Julia 1.1. -See also [`something`](@ref), [`notnothing`](@ref), [`ismissing`](@ref). +See also [`something`](@ref), [`Base.notnothing`](@ref), [`ismissing`](@ref). """ isnothing(x) = x === nothing @@ -87,6 +86,9 @@ julia> something(nothing, 1) julia> something(Some(1), nothing) 1 +julia> something(Some(nothing), 2) === nothing +true + julia> something(missing, nothing) missing diff --git a/base/sort.jl b/base/sort.jl index d26e9a4b09332..0e84657fc481e 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -2,20 +2,12 @@ module Sort -import ..@__MODULE__, ..parentmodule -const Base = parentmodule(@__MODULE__) -using .Base.Order -using .Base: copymutable, LinearIndices, length, (:), - eachindex, axes, first, last, similar, zip, OrdinalRange, - AbstractVector, @inbounds, AbstractRange, @eval, @inline, Vector, @noinline, - AbstractMatrix, AbstractUnitRange, isless, identity, eltype, >, <, <=, >=, |, +, -, *, !, - extrema, sub_with_overflow, add_with_overflow, oneunit, div, getindex, setindex!, - length, resize!, fill, Missing, require_one_based_indexing, keytype, - UnitRange, max, min - -using .Base: >>>, !== - -import .Base: +using Base.Order + +using Base: copymutable, midpoint, require_one_based_indexing, uinttype, + sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit + +import Base: sort, sort!, issorted, @@ -94,8 +86,7 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - inds = axes(v, 1) - sort!(v, first(inds), last(inds), PartialQuickSort(k), o) + _sort!(v, InitialOptimizations(ScratchQuickSort(k)), o, (;)) maybeview(v, k) end @@ -107,10 +98,9 @@ maybeview(v, k::Integer) = v[k] Partially sort the vector `v` in place, according to the order specified by `by`, `lt` and `rev` so that the value at index `k` (or range of adjacent values if `k` is a range) occurs -at the position where it would appear if the array were fully sorted via a non-stable -algorithm. If `k` is a single index, that value is returned; if `k` is a range, an array of -values at those indices is returned. Note that `partialsort!` does not fully sort the input -array. +at the position where it would appear if the array were fully sorted. If `k` is a single +index, that value is returned; if `k` is a range, an array of values at those indices is +returned. Note that `partialsort!` may not fully sort the input array. # Examples ```jldoctest @@ -166,33 +156,30 @@ same thing as `partialsort!` but leaving `v` unmodified. partialsort(v::AbstractVector, k::Union{Integer,OrdinalRange}; kws...) = partialsort!(copymutable(v), k; kws...) -# This implementation of `midpoint` is performance-optimized but safe -# only if `lo <= hi`. -midpoint(lo::T, hi::T) where T<:Integer = lo + ((hi - lo) >>> 0x01) -midpoint(lo::Integer, hi::Integer) = midpoint(promote(lo, hi)...) - # reference on sorted binary search: # http://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 length(v)+1 if x is greater than all values in v. +# returns lastindex(v)+1 if x is greater than all values in v. function searchsortedfirst(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer - u = T(1) - lo = lo - u - hi = hi + u - @inbounds while lo < hi - u - m = midpoint(lo, hi) + hi = hi + T(1) + len = hi - lo + @inbounds while len != 0 + half_len = len >>> 0x01 + m = lo + half_len if lt(o, v[m], x) - lo = m + lo = m + 1 + len -= half_len + 1 else hi = m + len = half_len end end - return hi + return lo end # index of the last value of vector a that is less than or equal to x; -# returns 0 if x is less than all values of v. +# returns firstindex(v)-1 if x is less than all values of v. function searchsortedlast(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer u = T(1) lo = lo - u @@ -293,7 +280,7 @@ searchsorted(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering) = for s in [:searchsortedfirst, :searchsortedlast, :searchsorted] @eval begin - $s(v::AbstractVector, x, o::Ordering) = (inds = axes(v, 1); $s(v,x,first(inds),last(inds),o)) + $s(v::AbstractVector, x, o::Ordering) = $s(v,x,firstindex(v),lastindex(v),o) $s(v::AbstractVector, x; lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) = $s(v,x,ord(lt,by,rev,order)) @@ -308,6 +295,8 @@ according to the order specified by the `by`, `lt` and `rev` keywords, assuming is already sorted in that order. Return an empty range located at the insertion point if `a` does not contain values equal to `x`. +See [`sort!`](@ref) for an explanation of the keyword arguments `by`, `lt` and `rev`. + See also: [`insorted`](@ref), [`searchsortedfirst`](@ref), [`sort`](@ref), [`findall`](@ref). # Examples @@ -326,6 +315,9 @@ julia> searchsorted([1, 2, 4, 5, 5, 7], 9) # no match, insert at end julia> searchsorted([1, 2, 4, 5, 5, 7], 0) # no match, insert at start 1:0 + +julia> searchsorted([1=>"one", 2=>"two", 2=>"two", 4=>"four"], 2=>"two", by=first) # compare the keys of the pairs +2:3 ``` """ searchsorted @@ -336,6 +328,10 @@ Return the index of the first value in `a` greater than or equal to `x`, accordi specified order. Return `lastindex(a) + 1` if `x` is greater than all values in `a`. `a` is assumed to be sorted. +`insert!`ing `x` at this index will maintain sorted order. + +See [`sort!`](@ref) for an explanation of the keyword arguments `by`, `lt` and `rev`. + See also: [`searchsortedlast`](@ref), [`searchsorted`](@ref), [`findfirst`](@ref). # Examples @@ -354,6 +350,9 @@ julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 9) # no match, insert at end julia> searchsortedfirst([1, 2, 4, 5, 5, 7], 0) # no match, insert at start 1 + +julia> searchsortedfirst([1=>"one", 2=>"two", 4=>"four"], 3=>"three", by=first) # Compare the keys of the pairs +3 ``` """ searchsortedfirst @@ -364,6 +363,8 @@ Return the index of the last value in `a` less than or equal to `x`, according t specified order. Return `firstindex(a) - 1` if `x` is less than all values in `a`. `a` is assumed to be sorted. +See [`sort!`](@ref) for an explanation of the keyword arguments `by`, `lt` and `rev`. + # Examples ```jldoctest julia> searchsortedlast([1, 2, 4, 5, 5, 7], 4) # single match @@ -380,13 +381,16 @@ julia> searchsortedlast([1, 2, 4, 5, 5, 7], 9) # no match, insert at end julia> searchsortedlast([1, 2, 4, 5, 5, 7], 0) # no match, insert at start 0 + +julia> searchsortedlast([1=>"one", 2=>"two", 4=>"four"], 3=>"three", by=first) # compare the keys of the pairs +2 ``` """ searchsortedlast """ - insorted(a, x; by=, lt=, rev=false) -> Bool + insorted(x, a; by=, lt=, rev=false) -> Bool -Determine whether an item is in the given sorted collection, in the sense that +Determine whether an item `x` is in the sorted collection `a`, in the sense that it is [`==`](@ref) to one of the values of the collection according to the order specified by the `by`, `lt` and `rev` keywords, assuming that `a` is already sorted in that order, see [`sort`](@ref) for the keywords. @@ -418,267 +422,925 @@ function insorted end insorted(x, v::AbstractVector; kw...) = !isempty(searchsorted(v, x; kw...)) insorted(x, r::AbstractRange) = in(x, r) -## sorting algorithms ## +## Alternative keyword management -abstract type Algorithm end +macro getkw(syms...) + getters = (getproperty(Sort, Symbol(:_, sym)) for sym in syms) + Expr(:block, (:($(esc(:((kw, $sym) = $getter(v, o, kw))))) for (sym, getter) in zip(syms, getters))...) +end -struct InsertionSortAlg <: Algorithm end -struct QuickSortAlg <: Algorithm end -struct MergeSortAlg <: Algorithm end +for (sym, exp, type) in [ + (:lo, :(firstindex(v)), Integer), + (:hi, :(lastindex(v)), Integer), + (:mn, :(throw(ArgumentError("mn is needed but has not been computed"))), :(eltype(v))), + (:mx, :(throw(ArgumentError("mx is needed but has not been computed"))), :(eltype(v))), + (:scratch, nothing, :(Union{Nothing, Vector})), # could have different eltype + (:allow_legacy_dispatch, true, Bool)] + usym = Symbol(:_, sym) + @eval function $usym(v, o, kw) + # using missing instead of nothing because scratch could === nothing. + res = get(kw, $(Expr(:quote, sym)), missing) + res !== missing && return kw, res::$type + $sym = $exp + (;kw..., $sym), $sym::$type + end +end + +## Scratch space management """ - PartialQuickSort{T <: Union{Integer,OrdinalRange}} + make_scratch(scratch::Union{Nothing, Vector}, T::Type, len::Integer) -Indicate that a sorting function should use the partial quick sort -algorithm. Partial quick sort returns the smallest `k` elements sorted from smallest -to largest, finding them and sorting them using [`QuickSort`](@ref). +Returns `(s, t)` where `t` is an `AbstractVector` of type `T` with length at least `len` +that is backed by the `Vector` `s`. If `scratch !== nothing`, then `s === scratch`. -Characteristics: - * *not stable*: does not preserve the ordering of elements which - compare equal (e.g. "a" and "A" in a sort of letters which - ignores case). - * *in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). +This function will allocate a new vector if `scratch === nothing`, `resize!` `scratch` if it +is too short, and `reinterpret` `scratch` if its eltype is not `T`. """ -struct PartialQuickSort{T <: Union{Integer,OrdinalRange}} <: Algorithm - k::T +function make_scratch(scratch::Nothing, T::Type, len::Integer) + s = Vector{T}(undef, len) + s, s +end +function make_scratch(scratch::Vector{T}, ::Type{T}, len::Integer) where T + len > length(scratch) && resize!(scratch, len) + scratch, scratch end +function make_scratch(scratch::Vector, T::Type, len::Integer) + len_bytes = len * sizeof(T) + len_scratch = div(len_bytes, sizeof(eltype(scratch))) + len_scratch > length(scratch) && resize!(scratch, len_scratch) + scratch, reinterpret(T, scratch) +end + +## sorting algorithm components ## """ - InsertionSort + _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw; t, offset) -Indicate that a sorting function should use the insertion sort -algorithm. Insertion sort traverses the collection one element -at a time, inserting each element into its correct, sorted position in -the output list. +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 +sorts `view(v, lo:hi)`) -Characteristics: - * *stable*: preserves the ordering of elements which - compare equal (e.g. "a" and "A" in a sort of letters - which ignores case). - * *in-place* in memory. - * *quadratic performance* in the number of elements to be sorted: - it is well-suited to small collections but should not be used for large ones. -""" -const InsertionSort = InsertionSortAlg() -""" - QuickSort +Returns a scratch space if provided or constructed during the sort, or `nothing` if +no scratch space is present. -Indicate that a sorting function should use the quick sort -algorithm, which is *not* stable. +!!! note + `_sort!` modifies but does not return `v`. -Characteristics: - * *not stable*: does not preserve the ordering of elements which - compare equal (e.g. "a" and "A" in a sort of letters which - ignores case). - * *in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). - * *good performance* for large collections. -""" -const QuickSort = QuickSortAlg() +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`. + +`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 +internal or recursive calls. """ - MergeSort +function _sort! end + +abstract type Algorithm end -Indicate that a sorting function should use the merge sort -algorithm. Merge sort divides the collection into -subcollections and repeatedly merges them, sorting each -subcollection at each step, until the entire -collection has been recombined in sorted form. -Characteristics: - * *stable*: preserves the ordering of elements which compare - equal (e.g. "a" and "A" in a sort of letters which ignores - case). - * *not in-place* in memory. - * *divide-and-conquer* sort strategy. """ -const MergeSort = MergeSortAlg() + MissingOptimization(next) <: Algorithm -const DEFAULT_UNSTABLE = QuickSort -const DEFAULT_STABLE = MergeSort -const SMALL_ALGORITHM = InsertionSort -const SMALL_THRESHOLD = 20 +Filter out missing values. -function sort!(v::AbstractVector, lo::Integer, hi::Integer, ::InsertionSortAlg, o::Ordering) - @inbounds for i = lo+1:hi - j = i - x = v[i] - while j > lo - if lt(o, x, v[j-1]) - v[j] = v[j-1] - j -= 1 - continue - end - break +Missing values are placed after other values according to `DirectOrdering`s. This pass puts +them there and passes on a view into the original vector that excludes the missing values. +This pass is triggered for both `sort([1, missing, 3])` and `sortperm([1, missing, 3])`. +""" +struct MissingOptimization{T <: Algorithm} <: Algorithm + next::T +end + +struct WithoutMissingVector{T, U} <: AbstractVector{T} + data::U + function WithoutMissingVector(data; unsafe=false) + if !unsafe && any(ismissing, data) + throw(ArgumentError("data must not contain missing values")) end - v[j] = x + new{nonmissingtype(eltype(data)), typeof(data)}(data) end - return v end +Base.@propagate_inbounds function Base.getindex(v::WithoutMissingVector, i) + out = v.data[i] + @assert !(out isa Missing) + out::eltype(v) +end +Base.@propagate_inbounds function Base.setindex!(v::WithoutMissingVector, x, i) + v.data[i] = x + v +end +Base.size(v::WithoutMissingVector) = size(v.data) +Base.axes(v::WithoutMissingVector) = axes(v.data) -# selectpivot! -# -# Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi]) and -# choose the middle value as a pivot -# -# Upon return, the pivot is in v[lo], and v[hi] is guaranteed to be -# greater than the pivot +""" + send_to_end!(f::Function, v::AbstractVector; [lo, hi]) -@inline function selectpivot!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) - @inbounds begin - mi = midpoint(lo, hi) +Send every element of `v` for which `f` returns `true` to the end of the vector and return +the index of the last element for which `f` returns `false`. - # sort v[mi] <= v[lo] <= v[hi] such that the pivot is immediately in place - if lt(o, v[lo], v[mi]) - v[mi], v[lo] = v[lo], v[mi] +`send_to_end!(f, v, lo, hi)` is equivalent to `send_to_end!(f, view(v, lo:hi))+lo-1` + +Preserves the order of the elements that are not sent to the end. +""" +function send_to_end!(f::F, v::AbstractVector; lo=firstindex(v), hi=lastindex(v)) where F <: Function + i = lo + @inbounds while i <= hi && !f(v[i]) + i += 1 + end + j = i + 1 + @inbounds while j <= hi + if !f(v[j]) + v[i], v[j] = v[j], v[i] + i += 1 end + j += 1 + end + i - 1 +end +""" + send_to_end!(f::Function, v::AbstractVector, o::DirectOrdering[, end_stable]; lo, hi) - if lt(o, v[hi], v[lo]) - if lt(o, v[hi], v[mi]) - v[hi], v[lo], v[mi] = v[lo], v[mi], v[hi] +Return `(a, b)` where `v[a:b]` are the elements that are not sent to the end. + +If `o isa ReverseOrdering` then the "end" of `v` is `v[lo]`. + +If `end_stable` is set, the elements that are sent to the end are stable instead of the +elements that are not +""" +@inline send_to_end!(f::F, v::AbstractVector, ::ForwardOrdering, end_stable=false; lo, hi) where F <: Function = + end_stable ? (lo, hi-send_to_end!(!f, view(v, hi:-1:lo))) : (lo, send_to_end!(f, v; lo, hi)) +@inline send_to_end!(f::F, v::AbstractVector, ::ReverseOrdering, end_stable=false; lo, hi) where F <: Function = + end_stable ? (send_to_end!(!f, v; lo, hi)+1, hi) : (hi-send_to_end!(f, view(v, hi:-1:lo))+1, hi) + + +function _sort!(v::AbstractVector, a::MissingOptimization, o::Ordering, kw) + @getkw lo hi + if o isa DirectOrdering && eltype(v) >: Missing && nonmissingtype(eltype(v)) != eltype(v) + lo, hi = send_to_end!(ismissing, v, o; lo, hi) + _sort!(WithoutMissingVector(v, unsafe=true), a.next, o, (;kw..., lo, hi)) + elseif o isa Perm && o.order isa DirectOrdering && eltype(v) <: Integer && + eltype(o.data) >: Missing && nonmissingtype(eltype(o.data)) != eltype(o.data) && + all(i === j for (i,j) in zip(v, eachindex(o.data))) + # TODO make this branch known at compile time + # This uses a custom function because we need to ensure stability of both sides and + # we can assume v is equal to eachindex(o.data) which allows a copying partition + # without allocations. + lo_i, hi_i = lo, hi + for i in eachindex(o.data) # equal to copy(v) + x = o.data[i] + if ismissing(x) == (o.order == Reverse) # should x go at the beginning/end? + v[lo_i] = i + lo_i += 1 else - v[hi], v[lo] = v[lo], v[hi] + v[hi_i] = i + hi_i -= 1 end end + reverse!(v, lo_i, hi) + if o.order == Reverse + lo = lo_i + else + hi = hi_i + end - # return the pivot - return v[lo] + _sort!(v, a.next, Perm(o.order, WithoutMissingVector(o.data, unsafe=true)), (;kw..., lo, hi)) + else + _sort!(v, a.next, o, kw) end end -# partition! -# -# select a pivot, and partition v according to the pivot -function partition!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) - pivot = selectpivot!(v, lo, hi, o) - # pivot == v[lo], v[hi] > pivot - i, j = lo, hi - @inbounds while true - i += 1; j -= 1 - while lt(o, v[i], pivot); i += 1; end; - while lt(o, pivot, v[j]); j -= 1; end; - i >= j && break - v[i], v[j] = v[j], v[i] - end - v[j], v[lo] = pivot, v[j] +""" + IEEEFloatOptimization(next) <: Algorithm - # v[j] == pivot - # v[k] >= pivot for k > j - # v[i] <= pivot for i < j - return j +Move NaN values to the end, partition by sign, and reinterpret the rest as unsigned integers. + +IEEE floating point numbers (`Float64`, `Float32`, and `Float16`) compare the same as +unsigned integers with the bits with a few exceptions. This pass + +This pass is triggered for both `sort([1.0, NaN, 3.0])` and `sortperm([1.0, NaN, 3.0])`. +""" +struct IEEEFloatOptimization{T <: Algorithm} <: Algorithm + next::T end -function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::QuickSortAlg, o::Ordering) - @inbounds while lo < hi - hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - j = partition!(v, lo, hi, o) - if j-lo < hi-j - # recurse on the smaller chunk - # this is necessary to preserve O(log(n)) - # stack space in the worst case (rather than O(n)) - lo < (j-1) && sort!(v, lo, j-1, a, o) - lo = j+1 +after_zero(::ForwardOrdering, x) = !signbit(x) +after_zero(::ReverseOrdering, x) = signbit(x) +is_concrete_IEEEFloat(T::Type) = T <: Base.IEEEFloat && isconcretetype(T) +function _sort!(v::AbstractVector, a::IEEEFloatOptimization, o::Ordering, kw) + @getkw lo hi + if is_concrete_IEEEFloat(eltype(v)) && o isa DirectOrdering + lo, hi = send_to_end!(isnan, v, o, true; lo, hi) + iv = reinterpret(uinttype(eltype(v)), v) + j = send_to_end!(x -> after_zero(o, x), v; lo, hi) + scratch = _sort!(iv, a.next, Reverse, (;kw..., lo, hi=j)) + if scratch === nothing # Union split + _sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, scratch)) else - j+1 < hi && sort!(v, j+1, hi, a, o) - hi = j-1 + _sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, scratch)) + end + elseif eltype(v) <: Integer && o isa Perm && o.order isa DirectOrdering && is_concrete_IEEEFloat(eltype(o.data)) + lo, hi = send_to_end!(i -> isnan(@inbounds o.data[i]), v, o.order, true; lo, hi) + ip = reinterpret(uinttype(eltype(o.data)), o.data) + j = send_to_end!(i -> after_zero(o.order, @inbounds o.data[i]), v; lo, hi) + scratch = _sort!(v, a.next, Perm(Reverse, ip), (;kw..., lo, hi=j)) + if scratch === nothing # Union split + _sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, scratch)) + else + _sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, scratch)) end + else + _sort!(v, a.next, o, kw) end - return v end -function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, t=similar(v,0)) - @inbounds if lo < hi - hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - m = midpoint(lo, hi) - (length(t) < m-lo+1) && resize!(t, m-lo+1) +""" + BoolOptimization(next) <: Algorithm - sort!(v, lo, m, a, o, t) - sort!(v, m+1, hi, a, o, t) +Sort `AbstractVector{Bool}`s using a specialized version of counting sort. - i, j = 1, lo - while j <= m - t[i] = v[j] - i += 1 - j += 1 +Accesses each element at most twice (one read and one write), and performs at most two +comparisons. +""" +struct BoolOptimization{T <: Algorithm} <: Algorithm + next::T +end +_sort!(v::AbstractVector, a::BoolOptimization, o::Ordering, kw) = _sort!(v, a.next, o, kw) +function _sort!(v::AbstractVector{Bool}, ::BoolOptimization, o::Ordering, kw) + first = lt(o, false, true) ? false : lt(o, true, false) ? true : return v + @getkw lo hi scratch + count = 0 + @inbounds for i in lo:hi + if v[i] == first + count += 1 end + end + @inbounds v[lo:lo+count-1] .= first + @inbounds v[lo+count:hi] .= !first + scratch +end - i, k = 1, lo - while k < j <= hi - if lt(o, v[j], t[i]) - v[k] = v[j] - j += 1 - else - v[k] = t[i] - i += 1 - end - k += 1 - end - while k < j - v[k] = t[i] - k += 1 - i += 1 - end + +""" + IsUIntMappable(yes, no) <: Algorithm + +Determines if the elements of a vector can be mapped to unsigned integers while preserving +their order under the specified ordering. + +If they can be, dispatch to the `yes` algorithm and record the unsigned integer type that +the elements may be mapped to. Otherwise dispatch to the `no` algorithm. +""" +struct IsUIntMappable{T <: Algorithm, U <: Algorithm} <: Algorithm + yes::T + no::U +end +function _sort!(v::AbstractVector, a::IsUIntMappable, o::Ordering, kw) + if UIntMappable(eltype(v), o) !== nothing + _sort!(v, a.yes, o, kw) + else + _sort!(v, a.no, o, kw) end +end - return v + +""" + Small{N}(small=SMALL_ALGORITHM, big) <: Algorithm + +Sort inputs with `length(lo:hi) <= N` using the `small` algorithm. Otherwise use the `big` +algorithm. +""" +struct Small{N, T <: Algorithm, U <: Algorithm} <: Algorithm + small::T + big::U +end +Small{N}(small, big) where N = Small{N, typeof(small), typeof(big)}(small, big) +Small{N}(big) where N = Small{N}(SMALL_ALGORITHM, big) +function _sort!(v::AbstractVector, a::Small{N}, o::Ordering, kw) where N + @getkw lo hi + if (hi-lo) < N + _sort!(v, a.small, o, kw) + else + _sort!(v, a.big, o, kw) + end end -function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::PartialQuickSort, - o::Ordering) - @inbounds while lo < hi - hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - j = partition!(v, lo, hi, o) - if j <= first(a.k) - lo = j+1 - elseif j >= last(a.k) - hi = j-1 - else - # recurse on the smaller chunk - # this is necessary to preserve O(log(n)) - # stack space in the worst case (rather than O(n)) - if j-lo < hi-j - lo < (j-1) && sort!(v, lo, j-1, a, o) - lo = j+1 - else - hi > (j+1) && sort!(v, j+1, hi, a, o) - hi = j-1 +struct InsertionSortAlg <: Algorithm end + +""" + InsertionSort + +Use the insertion sort algorithm. + +Insertion sort traverses the collection one element at a time, inserting +each element into its correct, sorted position in the output vector. + +Characteristics: +* *stable*: preserves the ordering of elements which compare equal +(e.g. "a" and "A" in a sort of letters which ignores case). +* *in-place* in memory. +* *quadratic performance* in the number of elements to be sorted: +it is well-suited to small collections but should not be used for large ones. +""" +const InsertionSort = InsertionSortAlg() +const SMALL_ALGORITHM = InsertionSortAlg() + +function _sort!(v::AbstractVector, ::InsertionSortAlg, o::Ordering, kw) + @getkw lo hi scratch + lo_plus_1 = (lo + 1)::Integer + @inbounds for i = lo_plus_1:hi + j = i + x = v[i] + while j > lo + y = v[j-1] + if !(lt(o, x, y)::Bool) + break end + v[j] = y + j -= 1 end + v[j] = x end - return v + scratch end -## generic sorting methods ## +""" + CheckSorted(next) <: Algorithm -defalg(v::AbstractArray) = DEFAULT_STABLE -defalg(v::AbstractArray{<:Union{Number, Missing}}) = DEFAULT_UNSTABLE -defalg(v::AbstractArray{Missing}) = DEFAULT_UNSTABLE -defalg(v::AbstractArray{Union{}}) = DEFAULT_UNSTABLE +Check if the input is already sorted and for large inputs, also check if it is +reverse-sorted. The reverse-sorted check is unstable. +""" +struct CheckSorted{T <: Algorithm} <: Algorithm + next::T +end +function _sort!(v::AbstractVector, a::CheckSorted, o::Ordering, kw) + @getkw lo hi scratch + + # For most arrays, a presorted check is cheap (overhead < 5%) and for most large + # arrays it is essentially free (<1%). + _issorted(v, lo, hi, o) && return scratch + + # For most large arrays, a reverse-sorted check is essentially free (overhead < 1%) + if hi-lo >= 500 && _issorted(v, lo, hi, ReverseOrdering(o)) + # If reversing is valid, do so. This violates stability. + reverse!(v, lo, hi) + return scratch + end -function sort!(v::AbstractVector, alg::Algorithm, order::Ordering) - inds = axes(v,1) - sort!(v,first(inds),last(inds),alg,order) + _sort!(v, a.next, o, kw) end + """ - sort!(v; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + ComputeExtrema(next) <: Algorithm -Sort the vector `v` in place. [`QuickSort`](@ref) is used by default for numeric arrays while -[`MergeSort`](@ref) is used for other arrays. You can specify an algorithm to use via the `alg` -keyword (see [Sorting Algorithms](@ref) for available algorithms). The `by` keyword lets you provide -a function that will be applied to each element before comparison; the `lt` keyword allows -providing a custom "less than" function (note that for every `x` and `y`, only one of `lt(x,y)` -and `lt(y,x)` can return `true`); use `rev=true` to reverse the sorting order. These -options are independent and can be used together in all possible combinations: if both `by` -and `lt` are specified, the `lt` function is applied to the result of the `by` function; -`rev=true` reverses whatever ordering specified via the `by` and `lt` keywords. +Compute the extrema of the input under the provided order. -# Examples +If the minimum is no less than the maximum, then the input is already sorted. Otherwise, +dispatch to the `next` algorithm. +""" +struct ComputeExtrema{T <: Algorithm} <: Algorithm + next::T +end +function _sort!(v::AbstractVector, a::ComputeExtrema, o::Ordering, kw) + @getkw lo hi scratch + mn = mx = v[lo] + @inbounds for i in (lo+1):hi + vi = v[i] + lt(o, vi, mn) && (mn = vi) + lt(o, mx, vi) && (mx = vi) + end + + lt(o, mn, mx) || return scratch # all same + + _sort!(v, a.next, o, (;kw..., mn, mx)) +end + + +""" + ConsiderCountingSort(counting=CountingSort(), next) <: Algorithm + +If the input's range is small enough, use the `counting` algorithm. Otherwise, dispatch to +the `next` algorithm. + +For most types, the threshold is if the range is shorter than half the length, but for types +larger than Int64, bitshifts are expensive and RadixSort is not viable, so the threshold is +much more generous. +""" +struct ConsiderCountingSort{T <: Algorithm, U <: Algorithm} <: Algorithm + counting::T + next::U +end +ConsiderCountingSort(next) = ConsiderCountingSort(CountingSort(), next) +function _sort!(v::AbstractVector{<:Integer}, a::ConsiderCountingSort, o::DirectOrdering, kw) + @getkw lo hi mn mx + range = maybe_unsigned(o === Reverse ? mn-mx : mx-mn) + + if range < (sizeof(eltype(v)) > 8 ? 5(hi-lo)-100 : div(hi-lo, 2)) + _sort!(v, a.counting, o, kw) + else + _sort!(v, a.next, o, kw) + end +end +_sort!(v::AbstractVector, a::ConsiderCountingSort, o::Ordering, kw) = _sort!(v, a.next, o, kw) + + +""" + CountingSort <: Algorithm + +Use the counting sort algorithm. + +`CountingSort` is an algorithm for sorting integers that runs in Θ(length + range) time and +space. It counts the number of occurrences of each value in the input and then iterates +through those counts repopulating the input with the values in sorted order. +""" +struct CountingSort <: Algorithm end +maybe_reverse(o::ForwardOrdering, x) = x +maybe_reverse(o::ReverseOrdering, x) = reverse(x) +function _sort!(v::AbstractVector{<:Integer}, ::CountingSort, o::DirectOrdering, kw) + @getkw lo hi mn mx scratch + range = maybe_unsigned(o === Reverse ? mn-mx : mx-mn) + offs = 1 - (o === Reverse ? mx : mn) + + counts = fill(0, range+1) # TODO use scratch (but be aware of type stability) + @inbounds for i = lo:hi + counts[v[i] + offs] += 1 + end + + idx = lo + @inbounds for i = maybe_reverse(o, 1:range+1) + lastidx = idx + counts[i] - 1 + val = i-offs + for j = idx:lastidx + v[j] = val isa Unsigned && eltype(v) <: Signed ? signed(val) : val + end + idx = lastidx + 1 + end + + scratch +end + + +""" + ConsiderRadixSort(radix=RadixSort(), next) <: 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. +""" +struct ConsiderRadixSort{T <: Algorithm, U <: Algorithm} <: Algorithm + radix::T + next::U +end +ConsiderRadixSort(next) = ConsiderRadixSort(RadixSort(), next) +function _sort!(v::AbstractVector, a::ConsiderRadixSort, o::DirectOrdering, kw) + @getkw lo hi mn mx + urange = uint_map(mx, o)-uint_map(mn, o) + bits = unsigned(top_set_bit(urange)) + if sizeof(eltype(v)) <= 8 && bits+70 < 22log(hi-lo) + _sort!(v, a.radix, o, kw) + else + _sort!(v, a.next, o, kw) + end +end + + +""" + RadixSort <: Algorithm + +Use the radix sort algorithm. + +`RadixSort` is a stable least significant bit first radix sort algorithm that runs in +`O(length * log(range))` time and linear space. + +It first sorts the entire vector by the last `chunk_size` bits, then by the second +to last `chunk_size` bits, and so on. Stability means that it will not reorder two elements +that compare equal. This is essential so that the order introduced by earlier, +less significant passes is preserved by later passes. + +Each pass divides the input into `2^chunk_size == mask+1` buckets. To do this, it + * counts the number of entries that fall into each bucket + * uses those counts to compute the indices to move elements of those buckets into + * moves elements into the computed indices in the swap array + * switches the swap and working array + +`chunk_size` is larger for larger inputs and determined by an empirical heuristic. +""" +struct RadixSort <: Algorithm end +function _sort!(v::AbstractVector, a::RadixSort, o::DirectOrdering, kw) + @getkw lo hi mn mx scratch + umn = uint_map(mn, o) + urange = uint_map(mx, o)-umn + bits = unsigned(top_set_bit(urange)) + + # At this point, we are committed to radix sort. + u = uint_map!(v, lo, hi, o) + + # we subtract umn to avoid radixing over unnecessary bits. For example, + # Int32[3, -1, 2] uint_maps to UInt32[0x80000003, 0x7fffffff, 0x80000002] + # which uses all 32 bits, but once we subtract umn = 0x7fffffff, we are left with + # UInt32[0x00000004, 0x00000000, 0x00000003] which uses only 3 bits, and + # Float32[2.012, 400.0, 12.345] uint_maps to UInt32[0x3fff3b63, 0x3c37ffff, 0x414570a4] + # which is reduced to UInt32[0x03c73b64, 0x00000000, 0x050d70a5] using only 26 bits. + # the overhead for this subtraction is small enough that it is worthwhile in many cases. + + # this is faster than u[lo:hi] .-= umn as of v1.9.0-DEV.100 + @inbounds for i in lo:hi + u[i] -= umn + end + + scratch, t = make_scratch(scratch, eltype(v), hi-lo+1) + tu = reinterpret(eltype(u), t) + if radix_sort!(u, lo, hi, bits, tu, 1-lo) + uint_unmap!(v, u, lo, hi, o, umn) + else + uint_unmap!(v, tu, lo, hi, o, umn, 1-lo) + end + scratch +end + + +""" + ScratchQuickSort(next::Algorithm=SMALL_ALGORITHM) <: Algorithm + ScratchQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}=lo, next::Algorithm=SMALL_ALGORITHM) <: Algorithm + +Use the `ScratchQuickSort` algorithm with the `next` algorithm as a base case. + +`ScratchQuickSort` is like `QuickSort`, but utilizes scratch space to operate faster and allow +for the possibility of maintaining stability. + +If `lo` and `hi` are provided, finds and sorts the elements in the range `lo:hi`, reordering +but not necessarily sorting other elements in the process. If `lo` or `hi` is `missing`, it +is treated as the first or last index of the input, respectively. + +`lo` and `hi` may be specified together as an `AbstractUnitRange`. + +Characteristics: + * *stable*: preserves the ordering of elements which compare equal + (e.g. "a" and "A" in a sort of letters which ignores case). + * *not in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`QuickSort`](@ref). + * *linear runtime* if `length(lo:hi)` is constant + * *quadratic worst case runtime* in pathological cases + (vanishingly rare for non-malicious input) +""" +struct ScratchQuickSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}, T<:Algorithm} <: Algorithm + lo::L + hi::H + next::T +end +ScratchQuickSort(next::Algorithm=SMALL_ALGORITHM) = ScratchQuickSort(missing, missing, next) +ScratchQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) = ScratchQuickSort(lo, hi, SMALL_ALGORITHM) +ScratchQuickSort(lo::Union{Integer, Missing}, next::Algorithm=SMALL_ALGORITHM) = ScratchQuickSort(lo, lo, next) +ScratchQuickSort(r::OrdinalRange, next::Algorithm=SMALL_ALGORITHM) = ScratchQuickSort(first(r), last(r), next) + +# select a pivot, partition v[lo:hi] according +# to the pivot, and store the result in t[lo:hi]. +# +# sets `pivot_dest[pivot_index+pivot_index_offset] = pivot` and returns that index. +function partition!(t::AbstractVector, lo::Integer, hi::Integer, offset::Integer, o::Ordering, + 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 + @inbounds begin + pivot = v[pivot_index] + while lo < pivot_index + x = v[lo] + fx = rev ? !lt(o, x, pivot) : lt(o, pivot, x) + t[(fx ? hi : lo) - offset] = x + offset += fx + lo += 1 + end + while lo < hi + x = v[lo+1] + fx = rev ? lt(o, pivot, x) : !lt(o, x, pivot) + t[(fx ? hi : lo) - offset] = x + offset += fx + lo += 1 + end + pivot_index = lo-offset + pivot_index_offset + pivot_dest[pivot_index] = pivot + end + + # t_pivot_index = lo-offset (i.e. without pivot_index_offset) + # t[t_pivot_index] is whatever it was before unless t is the pivot_dest + # t[t_pivot_index] >* pivot, reverse stable + + pivot_index +end + +function _sort!(v::AbstractVector, a::ScratchQuickSort, o::Ordering, kw; + t=nothing, offset=nothing, swap=false, rev=false) + @getkw lo hi scratch + + if t === nothing + scratch, t = make_scratch(scratch, eltype(v), hi-lo+1) + offset = 1-lo + kw = (;kw..., scratch) + end + + while lo < hi && hi - lo > SMALL_THRESHOLD + j = if swap + partition!(v, lo+offset, hi+offset, offset, o, t, rev, v, 0) + else + partition!(t, lo, hi, -offset, o, v, rev, v, -offset) + end + swap = !swap + + # For ScratchQuickSort(), a.lo === a.hi === missing, so the first two branches get skipped + if !ismissing(a.lo) && j <= a.lo # Skip sorting the lower part + swap && copyto!(v, lo, t, lo+offset, j-lo) + rev && reverse!(v, lo, j-1) + lo = j+1 + rev = !rev + elseif !ismissing(a.hi) && a.hi <= j # Skip sorting the upper part + swap && copyto!(v, j+1, t, j+1+offset, hi-j) + rev || reverse!(v, j+1, hi) + hi = j-1 + elseif j-lo < hi-j + # Sort the lower part recursively because it is smaller. Recursing on the + # smaller part guarantees O(log(n)) stack space even on pathological inputs. + _sort!(v, a, o, (;kw..., lo, hi=j-1); t, offset, swap, rev) + lo = j+1 + rev = !rev + else # Sort the higher part recursively + _sort!(v, a, o, (;kw..., lo=j+1, hi); t, offset, swap, rev=!rev) + hi = j-1 + end + end + hi < lo && return scratch + swap && copyto!(v, lo, t, lo+offset, hi-lo+1) + rev && reverse!(v, lo, hi) + _sort!(v, a.next, o, (;kw..., lo, hi)) +end + + +""" + StableCheckSorted(next) <: Algorithm + +Check if an input is sorted and/or reverse-sorted. + +The definition of reverse-sorted is that for every pair of adjacent elements, the latter is +less than the former. This is stricter than `issorted(v, Reverse(o))` to avoid swapping pairs +of elements that compare equal. +""" +struct StableCheckSorted{T<:Algorithm} <: Algorithm + next::T +end +function _sort!(v::AbstractVector, a::StableCheckSorted, o::Ordering, kw) + @getkw lo hi scratch + if _issorted(v, lo, hi, o) + return scratch + elseif _issorted(v, lo, hi, Lt((x, y) -> !lt(o, x, y))) + # Reverse only if necessary. Using issorted(..., Reverse(o)) would violate stability. + reverse!(v, lo, hi) + return scratch + end + + _sort!(v, a.next, o, kw) +end + + +# The return value indicates whether v is sorted (true) or t is sorted (false) +# This is one of the many reasons radix_sort! is not exported. +function radix_sort!(v::AbstractVector{U}, lo::Integer, hi::Integer, bits::Unsigned, + t::AbstractVector{U}, offset::Integer, + chunk_size=radix_chunk_size_heuristic(lo, hi, bits)) where U <: Unsigned + # bits is unsigned for performance reasons. + counts = Vector{Int}(undef, 1 << chunk_size + 1) # TODO use scratch for this + + shift = 0 + while true + @noinline radix_sort_pass!(t, lo, hi, offset, counts, v, shift, chunk_size) + # the latest data resides in t + shift += chunk_size + shift < bits || return false + @noinline radix_sort_pass!(v, lo+offset, hi+offset, -offset, counts, t, shift, chunk_size) + # the latest data resides in v + shift += chunk_size + shift < bits || return true + end +end +function radix_sort_pass!(t, lo, hi, offset, counts, v, shift, chunk_size) + mask = UInt(1) << chunk_size - 1 # mask is defined in pass so that the compiler + @inbounds begin # ↳ knows it's shape + # counts[2:mask+2] will store the number of elements that fall into each bucket. + # if chunk_size = 8, counts[2] is bucket 0x00 and counts[257] is bucket 0xff. + counts .= 0 + for k in lo:hi + x = v[k] # lookup the element + i = (x >> shift)&mask + 2 # compute its bucket's index for this pass + counts[i] += 1 # increment that bucket's count + end + + counts[1] = lo + offset # set target index for the first bucket + cumsum!(counts, counts) # set target indices for subsequent buckets + # counts[1:mask+1] now stores indices where the first member of each bucket + # belongs, not the number of elements in each bucket. We will put the first element + # of bucket 0x00 in t[counts[1]], the next element of bucket 0x00 in t[counts[1]+1], + # and the last element of bucket 0x00 in t[counts[2]-1]. + + for k in lo:hi + x = v[k] # lookup the element + i = (x >> shift)&mask + 1 # compute its bucket's index for this pass + j = counts[i] # lookup the target index + t[j] = x # put the element where it belongs + counts[i] = j + 1 # increment the target index for the next + end # ↳ element in this bucket + end +end +function radix_chunk_size_heuristic(lo::Integer, hi::Integer, bits::Unsigned) + # chunk_size is the number of bits to radix over at once. + # We need to allocate an array of size 2^chunk size, and on the other hand the higher + # the chunk size the fewer passes we need. Theoretically, chunk size should be based on + # the Lambert W function applied to length. Empirically, we use this heuristic: + guess = min(10, log(maybe_unsigned(hi-lo))*3/4+3) + # TODO the maximum chunk size should be based on architecture cache size. + + # We need iterations * chunk size ≥ bits, and these cld's + # make an effort to get iterations * chunk size ≈ bits + UInt8(cld(bits, cld(bits, guess))) +end + +maybe_unsigned(x::Integer) = x # this is necessary to avoid calling unsigned on BigInt +maybe_unsigned(x::BitSigned) = unsigned(x) +function _issorted(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) + @boundscheck checkbounds(v, lo:hi) + @inbounds for i in (lo+1):hi + lt(o, v[i], v[i-1]) && return false + end + true +end + + +## default sorting policy ## + +""" + InitialOptimizations(next) <: 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 +`alg=InsertionSort`, `alg=MergeSort`, or `alg=QuickSort` is passed as an argument. + +`InitialOptimizations` is an implementation detail and subject to change or removal in +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). +""" +InitialOptimizations(next) = MissingOptimization( + BoolOptimization( + Small{10}( + IEEEFloatOptimization( + next)))) +""" + DEFAULT_STABLE + +The default sorting algorithm. + +This algorithm is guaranteed to be stable (i.e. it will not reorder elements that compare +equal). It makes an effort to be fast for most inputs. + +The algorithms used by `DEFAULT_STABLE` are an implementation detail. See extended help +for the current dispatch system. + +# Extended Help + +`DEFAULT_STABLE` is composed of two parts: the [`InitialOptimizations`](@ref) and a hybrid +of Radix, Insertion, Counting, Quick sorts. + +We begin with MissingOptimization because it has no runtime cost when it is not +triggered and can enable other optimizations to be applied later. For example, +BoolOptimization cannot apply to an `AbstractVector{Union{Missing, Bool}}`, but after +[`MissingOptimization`](@ref) is applied, that input will be converted into am +`AbstractVector{Bool}`. + +We next apply [`BoolOptimization`](@ref) because it also has no runtime cost when it is not +triggered and when it is triggered, it is an incredibly efficient algorithm (sorting `Bool`s +is quite easy). + +Next, we dispatch to [`InsertionSort`](@ref) for inputs with `length <= 10`. This dispatch +occurs before the [`IEEEFloatOptimization`](@ref) pass because the +[`IEEEFloatOptimization`](@ref)s are not beneficial for very small inputs. + +To conclude the [`InitialOptimizations`](@ref), we apply [`IEEEFloatOptimization`](@ref). + +After these optimizations, we branch on whether radix sort and related algorithms can be +applied to the input vector and ordering. We conduct this branch by testing if +`UIntMappable(v, order) !== nothing`. That is, we see if we know of a reversible mapping +from `eltype(v)` to `UInt` that preserves the ordering `order`. We perform this check after +the initial optimizations because they can change the input vector's type and ordering to +make them `UIntMappable`. + +If the input is not [`UIntMappable`](@ref), then we perform a presorted check and dispatch +to [`ScratchQuickSort`](@ref). + +Otherwise, we dispatch to [`InsertionSort`](@ref) for inputs with `length <= 40` and then +perform a presorted check ([`CheckSorted`](@ref)). + +We check for short inputs before performing the presorted check to avoid the overhead of the +check for small inputs. Because the alternate dispatch is to [`InsertionSort`](@ref) which +has efficient `O(n)` runtime on presorted inputs, the check is not necessary for small +inputs. + +We check if the input is reverse-sorted for long vectors (more than 500 elements) because +the check is essentially free unless the input is almost entirely reverse sorted. + +Note that once the input is determined to be [`UIntMappable`](@ref), we know the order forms +a [total order](wikipedia.org/wiki/Total_order) over the inputs and so it is impossible to +perform an unstable sort because no two elements can compare equal unless they _are_ equal, +in which case switching them is undetectable. We utilize this fact to perform a more +aggressive reverse sorted check that will reverse the vector `[3, 2, 2, 1]`. + +After these potential fast-paths are tried and failed, we [`ComputeExtrema`](@ref) of the +input. This computation has a fairly fast `O(n)` runtime, but we still try to delay it until +it is necessary. + +Next, we [`ConsiderCountingSort`](@ref). If the range the input is small compared to its +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. +Consequently, we apply [`RadixSort`](@ref) for any reasonably long inputs that reach this +stage. + +Finally, if the input has length less than 80, we dispatch to [`InsertionSort`](@ref) and +otherwise we dispatch to [`ScratchQuickSort`](@ref). +""" +const DEFAULT_STABLE = InitialOptimizations( + IsUIntMappable( + Small{40}( + CheckSorted( + ComputeExtrema( + ConsiderCountingSort( + ConsiderRadixSort( + Small{80}( + ScratchQuickSort())))))), + StableCheckSorted( + ScratchQuickSort()))) +""" + DEFAULT_UNSTABLE + +An efficient sorting algorithm. + +The algorithms used by `DEFAULT_UNSTABLE` are an implementation detail. They are currently +the same as those used by [`DEFAULT_STABLE`](@ref), but this is subject to change in future. +""" +const DEFAULT_UNSTABLE = DEFAULT_STABLE +const SMALL_THRESHOLD = 20 + +function Base.show(io::IO, alg::Algorithm) + print_tree(io, alg, 0) +end +function print_tree(io::IO, alg::Algorithm, cols::Int) + print(io, " "^cols) + show_type(io, alg) + print(io, '(') + for (i, name) in enumerate(fieldnames(typeof(alg))) + arg = getproperty(alg, name) + i > 1 && print(io, ',') + if arg isa Algorithm + println(io) + print_tree(io, arg, cols+1) + else + i > 1 && print(io, ' ') + print(io, arg) + end + end + print(io, ')') +end +show_type(io::IO, alg::Algorithm) = Base.show_type_name(io, typeof(alg).name) +show_type(io::IO, alg::Small{N}) where N = print(io, "Base.Sort.Small{$N}") + +defalg(v::AbstractArray) = DEFAULT_STABLE +defalg(v::AbstractArray{<:Union{Number, Missing}}) = DEFAULT_UNSTABLE +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 the vector `v` in place. A stable algorithm is used by default. You can select a +specific algorithm to use via the `alg` keyword (see [Sorting Algorithms](@ref) for +available algorithms). The `by` keyword lets you provide a function that will be applied to +each element before comparison; the `lt` keyword allows providing a custom "less than" +function (note that for every `x` and `y`, only one of `lt(x,y)` and `lt(y,x)` can return +`true`); use `rev=true` to reverse the sorting order. `rev=true` preserves forward stability: +Elements that compare equal are not reversed. These options are independent and can +be used together in all possible combinations: if both `by` and `lt` are specified, the `lt` +function is applied to the result of the `by` function; `rev=true` reverses whatever +ordering specified via the `by` and `lt` keywords. + +# Examples ```jldoctest julia> v = [3, 1, 2]; sort!(v); v 3-element Vector{Int64}: @@ -705,47 +1367,15 @@ julia> v = [(1, "c"), (3, "a"), (2, "b")]; sort!(v, by = x -> x[2]); v (1, "c") ``` """ -function sort!(v::AbstractVector; +function sort!(v::AbstractVector{T}; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, - order::Ordering=Forward) - ordr = ord(lt,by,rev,order) - if (ordr === Forward || ordr === Reverse) && eltype(v)<:Integer - n = length(v) - if n > 1 - min, max = extrema(v) - (diff, o1) = sub_with_overflow(max, min) - (rangelen, o2) = add_with_overflow(diff, oneunit(diff)) - if !o1 && !o2 && rangelen < div(n,2) - return sort_int_range!(v, rangelen, min, ordr === Reverse ? reverse : identity) - end - end - end - sort!(v, alg, ordr) -end - -# sort! for vectors of few unique integers -function sort_int_range!(x::AbstractVector{<:Integer}, rangelen, minval, maybereverse) - offs = 1 - minval - - counts = fill(0, rangelen) - @inbounds for i = eachindex(x) - counts[x[i] + offs] += 1 - end - - idx = firstindex(x) - @inbounds for i = maybereverse(1:rangelen) - lastidx = idx + counts[i] - 1 - val = i-offs - for j = idx:lastidx - x[j] = val - end - idx = lastidx + 1 - end - - return x + order::Ordering=Forward, + scratch::Union{Vector{T}, Nothing}=nothing) where T + _sort!(v, maybe_apply_initial_optimizations(alg), ord(lt,by,rev,order), (;scratch)) + v end """ @@ -806,25 +1436,18 @@ julia> v[p] ``` """ partialsortperm(v::AbstractVector, k::Union{Integer,OrdinalRange}; kwargs...) = - partialsortperm!(similar(Vector{eltype(k)}, axes(v,1)), v, k; kwargs..., initialized=false) + partialsortperm!(similar(Vector{eltype(k)}, axes(v,1)), v, k; kwargs...) """ - partialsortperm!(ix, v, k; by=, lt=, rev=false, initialized=false) + partialsortperm!(ix, v, k; by=, lt=, rev=false) Like [`partialsortperm`](@ref), but accepts a preallocated index vector `ix` the same size as `v`, which is used to store (a permutation of) the indices of `v`. -If the index vector `ix` is initialized with the indices of `v` (or a permutation thereof), `initialized` should be set to -`true`. - -If `initialized` is `false` (the default), then `ix` is initialized to contain the indices of `v`. - -If `initialized` is `true`, but `ix` does not contain (a permutation of) the indices of `v`, the behavior of -`partialsortperm!` is undefined. +`ix` is initialized to contain the indices of `v`. (Typically, the indices of `v` will be `1:length(v)`, although if `v` has an alternative array type -with non-one-based indices, such as an `OffsetArray`, `ix` must also be an `OffsetArray` with the same -indices, and must contain as values (a permutation of) these same indices.) +with non-one-based indices, such as an `OffsetArray`, `ix` must share those same indices) Upon return, `ix` is guaranteed to have the indices `k` in their sorted positions, such that @@ -847,7 +1470,7 @@ julia> partialsortperm!(ix, v, 1) julia> ix = [1:4;]; -julia> partialsortperm!(ix, v, 2:3, initialized=true) +julia> partialsortperm!(ix, v, 2:3) 2-element view(::Vector{Int64}, 2:3) with eltype Int64: 4 3 @@ -861,17 +1484,15 @@ function partialsortperm!(ix::AbstractVector{<:Integer}, v::AbstractVector, order::Ordering=Forward, initialized::Bool=false) if axes(ix,1) != axes(v,1) - throw(ArgumentError("The index vector is used as a workspace and must have the " * + throw(ArgumentError("The index vector is used as scratch space and must have the " * "same length/indices as the source vector, $(axes(ix,1)) != $(axes(v,1))")) end - if !initialized - @inbounds for i = axes(ix,1) - ix[i] = i - end + @inbounds for i in eachindex(ix) + ix[i] = i end # do partial quicksort - sort!(ix, PartialQuickSort(k), Perm(ord(lt, by, rev, order), v)) + _sort!(ix, InitialOptimizations(ScratchQuickSort(k)), Perm(ord(lt, by, rev, order), v), (;)) maybeview(ix, k) end @@ -879,14 +1500,19 @@ end ## sortperm: the permutation to sort an array ## """ - sortperm(v; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + sortperm(A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, [dims::Integer]) -Return a permutation vector `I` that puts `v[I]` in sorted order. The order is specified +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 using the same keywords as [`sort!`](@ref). The permutation is guaranteed to be stable even if the sorting algorithm is unstable, meaning that indices of equal elements appear in ascending order. See also [`sortperm!`](@ref), [`partialsortperm`](@ref), [`invperm`](@ref), [`indexin`](@ref). +To sort slices of an array, refer to [`sortslices`](@ref). + +!!! compat "Julia 1.9" + The method accepting `dims` requires at least Julia 1.9. # Examples ```jldoctest @@ -903,40 +1529,62 @@ julia> v[p] 1 2 3 + +julia> A = [8 7; 5 6] +2×2 Matrix{Int64}: + 8 7 + 5 6 + +julia> sortperm(A, dims = 1) +2×2 Matrix{Int64}: + 2 4 + 1 3 + +julia> sortperm(A, dims = 2) +2×2 Matrix{Int64}: + 3 1 + 2 4 ``` """ -function sortperm(v::AbstractVector; +function sortperm(A::AbstractArray; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, - order::Ordering=Forward) - ordr = ord(lt,by,rev,order) - if ordr === Forward && isa(v,Vector) && eltype(v)<:Integer - n = length(v) + order::Ordering=Forward, + scratch::Union{Vector{<:Integer}, Nothing}=nothing, + dims...) #to optionally specify dims argument + if rev === true + _sortperm(A; alg, order=ord(lt, by, true, order), scratch, dims...) + else + _sortperm(A; alg, order=ord(lt, by, nothing, order), scratch, dims...) + end +end +function _sortperm(A::AbstractArray; alg, order, scratch, dims...) + if order === Forward && isa(A,Vector) && eltype(A)<:Integer + n = length(A) if n > 1 - min, max = extrema(v) + min, max = extrema(A) (diff, o1) = sub_with_overflow(max, min) (rangelen, o2) = add_with_overflow(diff, oneunit(diff)) - if !o1 && !o2 && rangelen < div(n,2) - return sortperm_int_range(v, rangelen, min) + if !(o1 || o2)::Bool && rangelen < div(n,2) + return sortperm_int_range(A, rangelen, min) end end end - ax = axes(v, 1) - p = similar(Vector{eltype(ax)}, ax) - for (i,ind) in zip(eachindex(p), ax) - p[i] = ind - end - sort!(p, alg, Perm(ordr,v)) + ix = copymutable(LinearIndices(A)) + sort!(ix; alg, order = Perm(order, vec(A)), scratch, dims...) end """ - sortperm!(ix, v; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, initialized::Bool=false) + sortperm!(ix, A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=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)`. -Like [`sortperm`](@ref), but accepts a preallocated index vector `ix`. If `initialized` is `false` -(the default), `ix` is initialized to contain the values `1:length(v)`. +!!! compat "Julia 1.9" + The method accepting `dims` requires at least Julia 1.9. # Examples ```jldoctest @@ -953,24 +1601,38 @@ julia> v[p] 1 2 3 + +julia> A = [8 7; 5 6]; p = zeros(Int,2, 2); + +julia> sortperm!(p, A; dims=1); p +2×2 Matrix{Int64}: + 2 4 + 1 3 + +julia> sortperm!(p, A; dims=2); p +2×2 Matrix{Int64}: + 3 1 + 2 4 ``` """ -function sortperm!(x::AbstractVector{<:Integer}, v::AbstractVector; +@inline function sortperm!(ix::AbstractArray{T}, A::AbstractArray; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward, - initialized::Bool=false) - if axes(x,1) != axes(v,1) - throw(ArgumentError("index vector must have the same length/indices as the source vector, $(axes(x,1)) != $(axes(v,1))")) - end - if !initialized - @inbounds for i = axes(v,1) - x[i] = i - end + initialized::Bool=false, + scratch::Union{Vector{T}, Nothing}=nothing, + dims...) where T <: Integer #to optionally specify dims argument + (typeof(A) <: AbstractVector) == (:dims in keys(dims)) && throw(ArgumentError("Dims argument incorrect for type $(typeof(A))")) + axes(ix) == axes(A) || throw(ArgumentError("index array must have the same size/axes as the source array, $(axes(ix)) != $(axes(A))")) + + ix .= LinearIndices(A) + if rev === true + sort!(ix; alg, order=Perm(ord(lt, by, true, order), vec(A)), scratch, dims...) + else + sort!(ix; alg, order=Perm(ord(lt, by, nothing, order), vec(A)), scratch, dims...) end - sort!(x, alg, Perm(ord(lt,by,rev,order),v)) end # sortperm for vectors of few unique integers @@ -1002,7 +1664,7 @@ end ## sorting multi-dimensional arrays ## """ - sort(A; dims::Integer, alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) + sort(A; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) Sort a multidimensional array `A` along the given dimension. See [`sort!`](@ref) for a description of possible @@ -1028,13 +1690,14 @@ julia> sort(A, dims = 2) 1 2 ``` """ -function sort(A::AbstractArray; +function sort(A::AbstractArray{T}; dims::Integer, - alg::Algorithm=DEFAULT_UNSTABLE, + alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, - order::Ordering=Forward) + order::Ordering=Forward, + scratch::Union{Vector{T}, Nothing}=nothing) where T dim = dims order = ord(lt,by,rev,order) n = length(axes(A, dim)) @@ -1042,23 +1705,36 @@ function sort(A::AbstractArray; pdims = (dim, setdiff(1:ndims(A), dim)...) # put the selected dimension first Ap = permutedims(A, pdims) Av = vec(Ap) - sort_chunks!(Av, n, alg, order) + sort_chunks!(Av, n, maybe_apply_initial_optimizations(alg), order, scratch) permutedims(Ap, invperm(pdims)) else Av = A[:] - sort_chunks!(Av, n, alg, order) + sort_chunks!(Av, n, maybe_apply_initial_optimizations(alg), order, scratch) reshape(Av, axes(A)) end end -@noinline function sort_chunks!(Av, n, alg, order) +@noinline function sort_chunks!(Av, n, alg, order, scratch) inds = LinearIndices(Av) - for s = first(inds):n:last(inds) - sort!(Av, s, s+n-1, alg, order) + sort_chunks!(Av, n, alg, order, scratch, first(inds), last(inds)) +end + +@noinline function sort_chunks!(Av, n, alg, order, scratch::Nothing, fst, lst) + for lo = fst:n:lst + s = _sort!(Av, alg, order, (; lo, hi=lo+n-1, scratch)) + s !== nothing && return sort_chunks!(Av, n, alg, order, s, lo+n, lst) end Av end +@noinline function sort_chunks!(Av, n, alg, order, scratch::AbstractVector, fst, lst) + for lo = fst:n:lst + _sort!(Av, alg, order, (; lo, hi=lo+n-1, scratch)) + end + Av +end + + """ sort!(A; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) @@ -1088,161 +1764,373 @@ julia> sort!(A, dims = 2); A 3 4 ``` """ -function sort!(A::AbstractArray; +function sort!(A::AbstractArray{T}; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, - order::Ordering=Forward) - ordr = ord(lt, by, rev, order) + order::Ordering=Forward, # TODO stop eagerly over-allocating. + scratch::Union{Vector{T}, Nothing}=similar(A, 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} nd = ndims(A) - k = dims - 1 <= k <= nd || throw(ArgumentError("dimension out of range")) + 1 <= K <= nd || throw(ArgumentError("dimension out of range")) - remdims = ntuple(i -> i == k ? 1 : axes(A, i), nd) + 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, ordr) + Av = view(A, ntuple(i -> i == K ? Colon() : idx[i], nd)...) + sort!(Av; alg, order, scratch) end A end -## fast clever sorting for floats ## -module Float -using ..Sort -using ...Order -using ..Base: @inbounds, AbstractVector, Vector, last, axes, Missing +## uint mapping to allow radix sorting primitives other than UInts ## -import Core.Intrinsics: slt_int -import ..Sort: sort! -import ...Order: lt, DirectOrdering +""" + UIntMappable(T::Type, order::Ordering) -const Floats = Union{Float32,Float64} -const FPSortable = Union{ # Mixed Float32 and Float64 are not allowed. - AbstractVector{Union{Float32, Missing}}, - AbstractVector{Union{Float64, Missing}}, - AbstractVector{Float32}, - AbstractVector{Float64}, - AbstractVector{Missing}} +Return `typeof(uint_map(x::T, order))` if [`uint_map`](@ref) and +[`uint_unmap`](@ref) are implemented. -struct Left <: Ordering end -struct Right <: Ordering end +If either is not implemented, return `nothing`. +""" +UIntMappable(T::Type, order::Ordering) = nothing -left(::DirectOrdering) = Left() -right(::DirectOrdering) = Right() +""" + uint_map(x, order::Ordering)::Unsigned -left(o::Perm) = Perm(left(o.order), o.data) -right(o::Perm) = Perm(right(o.order), o.data) +Map `x` to an un unsigned integer, maintaining sort order. -lt(::Left, x::T, y::T) where {T<:Floats} = slt_int(y, x) -lt(::Right, x::T, y::T) where {T<:Floats} = slt_int(x, y) +The map should be reversible with [`uint_unmap`](@ref), so `isless(order, a, b)` must be +a linear ordering for `a, b <: typeof(x)`. Satisfies +`isless(order, a, b) === (uint_map(a, order) < uint_map(b, order))` +and `x === uint_unmap(typeof(x), uint_map(x, order), order)` -isnan(o::DirectOrdering, x::Floats) = (x!=x) -isnan(o::DirectOrdering, x::Missing) = false -isnan(o::Perm, i::Integer) = isnan(o.order,o.data[i]) +See also: [`UIntMappable`](@ref) [`uint_unmap`](@ref) +""" +function uint_map end -ismissing(o::DirectOrdering, x::Floats) = false -ismissing(o::DirectOrdering, x::Missing) = true -ismissing(o::Perm, i::Integer) = ismissing(o.order,o.data[i]) +""" + uint_unmap(T::Type, u::Unsigned, order::Ordering) -allowsmissing(::AbstractVector{T}, ::DirectOrdering) where {T} = T >: Missing -allowsmissing(::AbstractVector{<:Integer}, - ::Perm{<:DirectOrdering,<:AbstractVector{T}}) where {T} = - T >: Missing +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`. -function specials2left!(testf::Function, v::AbstractVector, o::Ordering, - lo::Integer=first(axes(v,1)), hi::Integer=last(axes(v,1))) - i = lo - @inbounds while i <= hi && testf(o,v[i]) - i += 1 - end - j = i + 1 - @inbounds while j <= hi - if testf(o,v[j]) - v[i], v[j] = v[j], v[i] - i += 1 - end - j += 1 +See also: [`uint_map`](@ref) [`UIntMappable`](@ref) +""" +function uint_unmap end + + +### Primitive Types + +# Integers +uint_map(x::Unsigned, ::ForwardOrdering) = x +uint_unmap(::Type{T}, u::T, ::ForwardOrdering) where T <: Unsigned = u + +uint_map(x::Signed, ::ForwardOrdering) = + unsigned(xor(x, typemin(x))) +uint_unmap(::Type{T}, u::Unsigned, ::ForwardOrdering) where T <: Signed = + xor(signed(u), typemin(T)) + +UIntMappable(T::BitIntegerType, ::ForwardOrdering) = unsigned(T) + +# Floats are not UIntMappable under regular orderings because they fail on NaN edge cases. +# uint mappings for floats are defined in Float, where the Left and Right orderings +# guarantee that there are no NaN values + +# Chars +uint_map(x::Char, ::ForwardOrdering) = reinterpret(UInt32, x) +uint_unmap(::Type{Char}, u::UInt32, ::ForwardOrdering) = reinterpret(Char, u) +UIntMappable(::Type{Char}, ::ForwardOrdering) = UInt32 + +### Reverse orderings +uint_map(x, rev::ReverseOrdering) = ~uint_map(x, rev.fwd) +uint_unmap(T::Type, u::Unsigned, rev::ReverseOrdering) = uint_unmap(T, ~u, rev.fwd) +UIntMappable(T::Type, order::ReverseOrdering) = UIntMappable(T, order.fwd) + + +### Vectors + +# Convert v to unsigned integers in place, maintaining sort order. +function uint_map!(v::AbstractVector, lo::Integer, hi::Integer, order::Ordering) + u = reinterpret(UIntMappable(eltype(v), order), v) + @inbounds for i in lo:hi + u[i] = uint_map(v[i], order) end - return i, hi + u end -function specials2right!(testf::Function, v::AbstractVector, o::Ordering, - lo::Integer=first(axes(v,1)), hi::Integer=last(axes(v,1))) - i = hi - @inbounds while lo <= i && testf(o,v[i]) - i -= 1 + +function uint_unmap!(v::AbstractVector, u::AbstractVector{U}, lo::Integer, hi::Integer, + order::Ordering, offset::U=zero(U), + index_offset::Integer=0) where U <: Unsigned + @inbounds for i in lo:hi + v[i] = uint_unmap(eltype(v), u[i+index_offset]+offset, order) end - j = i - 1 - @inbounds while lo <= j - if testf(o,v[j]) - v[i], v[j] = v[j], v[i] - i -= 1 + v +end + + + +### Unused constructs for backward compatibility ### + +## Old algorithms ## + +struct QuickSortAlg <: Algorithm end +struct MergeSortAlg <: Algorithm end + +""" + PartialQuickSort{T <: Union{Integer,OrdinalRange}} + +Indicate that a sorting function should use the partial quick sort +algorithm. Partial quick sort returns the smallest `k` elements sorted from smallest +to largest, finding them and sorting them using [`QuickSort`](@ref). + +Characteristics: + * *not stable*: does not preserve the ordering of elements which + compare equal (e.g. "a" and "A" in a sort of letters which + ignores case). + * *in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). + + Note that `PartialQuickSort(k)` does not necessarily sort the whole array. For example, + +```jldoctest +julia> x = rand(100); + +julia> k = 50:100; + +julia> s1 = sort(x; alg=QuickSort); + +julia> s2 = sort(x; alg=PartialQuickSort(k)); + +julia> map(issorted, (s1, s2)) +(true, false) + +julia> map(x->issorted(x[k]), (s1, s2)) +(true, true) + +julia> s1[k] == s2[k] +true +``` +""" +struct PartialQuickSort{T <: Union{Integer,OrdinalRange}} <: Algorithm + k::T +end + +""" + QuickSort + +Indicate that a sorting function should use the quick sort +algorithm, which is *not* stable. + +Characteristics: + * *not stable*: does not preserve the ordering of elements which + compare equal (e.g. "a" and "A" in a sort of letters which + ignores case). + * *in-place* in memory. + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). + * *good performance* for large collections. +""" +const QuickSort = QuickSortAlg() + +""" + MergeSort + +Indicate that a sorting function should use the merge sort +algorithm. Merge sort divides the collection into +subcollections and repeatedly merges them, sorting each +subcollection at each step, until the entire +collection has been recombined in sorted form. + +Characteristics: + * *stable*: preserves the ordering of elements which compare + equal (e.g. "a" and "A" in a sort of letters which ignores + case). + * *not in-place* in memory. + * *divide-and-conquer* sort strategy. + * *good performance* for large collections but typically not quite as + fast as [`QuickSort`](@ref). +""" +const MergeSort = MergeSortAlg() + +maybe_apply_initial_optimizations(alg::Algorithm) = alg +maybe_apply_initial_optimizations(alg::QuickSortAlg) = InitialOptimizations(alg) +maybe_apply_initial_optimizations(alg::MergeSortAlg) = InitialOptimizations(alg) +maybe_apply_initial_optimizations(alg::InsertionSortAlg) = InitialOptimizations(alg) + +# selectpivot! +# +# Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi] and +# choose the middle value as a pivot +# +# Upon return, the pivot is in v[lo], and v[hi] is guaranteed to be +# greater than the pivot + +@inline function selectpivot!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) + @inbounds begin + mi = midpoint(lo, hi) + + # sort v[mi] <= v[lo] <= v[hi] such that the pivot is immediately in place + if lt(o, v[lo], v[mi]) + v[mi], v[lo] = v[lo], v[mi] end - j -= 1 + + if lt(o, v[hi], v[lo]) + if lt(o, v[hi], v[mi]) + v[hi], v[lo], v[mi] = v[lo], v[mi], v[hi] + else + v[hi], v[lo] = v[lo], v[hi] + end + end + + # return the pivot + return v[lo] end - return lo, i end -function specials2left!(v::AbstractVector, a::Algorithm, o::Ordering) - lo, hi = first(axes(v,1)), last(axes(v,1)) - if allowsmissing(v, o) - i, _ = specials2left!((v, o) -> ismissing(v, o) || isnan(v, o), v, o, lo, hi) - sort!(v, lo, i-1, a, o) - return i, hi - else - return specials2left!(isnan, v, o, lo, hi) +# partition! +# +# select a pivot, and partition v according to the pivot + +function partition!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) + pivot = selectpivot!(v, lo, hi, o) + # pivot == v[lo], v[hi] > pivot + i, j = lo, hi + @inbounds while true + i += 1; j -= 1 + while lt(o, v[i], pivot); i += 1; end; + while lt(o, pivot, v[j]); j -= 1; end; + i >= j && break + v[i], v[j] = v[j], v[i] end + v[j], v[lo] = pivot, v[j] + + # v[j] == pivot + # v[k] >= pivot for k > j + # v[i] <= pivot for i < j + return j end -function specials2right!(v::AbstractVector, a::Algorithm, o::Ordering) - lo, hi = first(axes(v,1)), last(axes(v,1)) - if allowsmissing(v, o) - _, i = specials2right!((v, o) -> ismissing(v, o) || isnan(v, o), v, o, lo, hi) - sort!(v, i+1, hi, a, o) - return lo, i - else - return specials2right!(isnan, v, o, lo, hi) + +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::QuickSortAlg, o::Ordering) + @inbounds while lo < hi + hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) + j = partition!(v, lo, hi, o) + if j-lo < hi-j + # recurse on the smaller chunk + # this is necessary to preserve O(log(n)) + # stack space in the worst case (rather than O(n)) + lo < (j-1) && sort!(v, lo, j-1, a, o) + lo = j+1 + else + j+1 < hi && sort!(v, j+1, hi, a, o) + hi = j-1 + end end + return v end -specials2end!(v::AbstractVector, a::Algorithm, o::ForwardOrdering) = - specials2right!(v, a, o) -specials2end!(v::AbstractVector, a::Algorithm, o::ReverseOrdering) = - specials2left!(v, a, o) -specials2end!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:ForwardOrdering}) = - specials2right!(v, a, o) -specials2end!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:ReverseOrdering}) = - specials2left!(v, a, o) +sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, t0::Vector{T}) where T = + invoke(sort!, Tuple{typeof.((v, lo, hi, a, o))..., AbstractVector{T}}, v, lo, hi, a, o, t0) # For disambiguation +function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, + t0::Union{AbstractVector{T}, Nothing}=nothing) where T + @inbounds if lo < hi + hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) -issignleft(o::ForwardOrdering, x::Floats) = lt(o, x, zero(x)) -issignleft(o::ReverseOrdering, x::Floats) = lt(o, x, -zero(x)) -issignleft(o::Perm, i::Integer) = issignleft(o.order, o.data[i]) + m = midpoint(lo, hi) -function fpsort!(v::AbstractVector, a::Algorithm, o::Ordering) - i, j = lo, hi = specials2end!(v,a,o) - @inbounds while true - while i <= j && issignleft(o,v[i]); i += 1; end - while i <= j && !issignleft(o,v[j]); j -= 1; end - i <= j || break - v[i], v[j] = v[j], v[i] - i += 1; j -= 1 + t = t0 === nothing ? similar(v, m-lo+1) : t0 + length(t) < m-lo+1 && resize!(t, m-lo+1) + Base.require_one_based_indexing(t) + + sort!(v, lo, m, a, o, t) + sort!(v, m+1, hi, a, o, t) + + i, j = 1, lo + while j <= m + t[i] = v[j] + i += 1 + j += 1 + end + + i, k = 1, lo + while k < j <= hi + if lt(o, v[j], t[i]) + v[k] = v[j] + j += 1 + else + v[k] = t[i] + i += 1 + end + k += 1 + end + while k < j + v[k] = t[i] + k += 1 + i += 1 + end end - sort!(v, lo, j, a, left(o)) - sort!(v, i, hi, a, right(o)) + return v end +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::PartialQuickSort, + o::Ordering) + @inbounds while lo < hi + hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) + j = partition!(v, lo, hi, o) -fpsort!(v::AbstractVector, a::Sort.PartialQuickSort, o::Ordering) = - sort!(v, first(axes(v,1)), last(axes(v,1)), a, o) + if j <= first(a.k) + lo = j+1 + elseif j >= last(a.k) + hi = j-1 + else + # recurse on the smaller chunk + # this is necessary to preserve O(log(n)) + # stack space in the worst case (rather than O(n)) + if j-lo < hi-j + lo < (j-1) && sort!(v, lo, j-1, a, o) + lo = j+1 + else + hi > (j+1) && sort!(v, j+1, hi, a, o) + hi = j-1 + end + end + end + return v +end -sort!(v::FPSortable, a::Algorithm, o::DirectOrdering) = - fpsort!(v, a, o) -sort!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:DirectOrdering,<:FPSortable}) = - fpsort!(v, a, o) +## Old extensibility mechanisms ## -end # module Sort.Float +# Support 3-, 5-, and 6-argument versions of sort! for calling into the internals in the old way +sort!(v::AbstractVector, a::Algorithm, o::Ordering) = sort!(v, firstindex(v), lastindex(v), a, o) +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering) + _sort!(v, a, o, (; lo, hi, allow_legacy_dispatch=false)) + v +end +sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering, _) = sort!(v, lo, hi, a, o) +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::Algorithm, o::Ordering, scratch::Vector) + _sort!(v, a, o, (; lo, hi, scratch, allow_legacy_dispatch=false)) + v +end + +# Support dispatch on custom algorithms in the old way +# sort!(::AbstractVector, ::Integer, ::Integer, ::MyCustomAlgorithm, ::Ordering) = ... +function _sort!(v::AbstractVector, a::Algorithm, o::Ordering, kw) + @getkw lo hi scratch allow_legacy_dispatch + if allow_legacy_dispatch + sort!(v, lo, hi, a, o) + scratch + else + # This error prevents infinite recursion for unknown algorithms + throw(ArgumentError("Base.Sort._sort!(::$(typeof(v)), ::$(typeof(a)), ::$(typeof(o))) is not defined")) + end +end end # module Sort diff --git a/base/special/exp.jl b/base/special/exp.jl index c2bbb47902360..9cca6f568305f 100644 --- a/base/special/exp.jl +++ b/base/special/exp.jl @@ -70,31 +70,30 @@ LogB(::Val{:ℯ}, ::Type{Float16}) = -0.6931472f0 LogB(::Val{10}, ::Type{Float16}) = -0.30103f0 # Range reduced kernels -@inline function expm1b_kernel(::Val{2}, x::Float64) +function expm1b_kernel(::Val{2}, x::Float64) return x * evalpoly(x, (0.6931471805599393, 0.24022650695910058, 0.05550411502333161, 0.009618129548366803)) end -@inline function expm1b_kernel(::Val{:ℯ}, x::Float64) +function expm1b_kernel(::Val{:ℯ}, x::Float64) return x * evalpoly(x, (0.9999999999999912, 0.4999999999999997, 0.1666666857598779, 0.04166666857598777)) end - -@inline function expm1b_kernel(::Val{10}, x::Float64) +function expm1b_kernel(::Val{10}, x::Float64) return x * evalpoly(x, (2.3025850929940255, 2.6509490552391974, 2.034678825384765, 1.1712552025835192)) end -@inline function expb_kernel(::Val{2}, x::Float32) +function expb_kernel(::Val{2}, x::Float32) return evalpoly(x, (1.0f0, 0.6931472f0, 0.2402265f0, 0.05550411f0, 0.009618025f0, 0.0013333423f0, 0.00015469732f0, 1.5316464f-5)) end -@inline function expb_kernel(::Val{:ℯ}, x::Float32) +function expb_kernel(::Val{:ℯ}, x::Float32) return evalpoly(x, (1.0f0, 1.0f0, 0.5f0, 0.16666667f0, 0.041666217f0, 0.008333249f0, 0.001394858f0, 0.00019924171f0)) end -@inline function expb_kernel(::Val{10}, x::Float32) +function expb_kernel(::Val{10}, x::Float32) return evalpoly(x, (1.0f0, 2.3025851f0, 2.650949f0, 2.0346787f0, 1.1712426f0, 0.53937745f0, 0.20788547f0, 0.06837386f0)) @@ -175,8 +174,10 @@ const J_TABLE = (0x0000000000000000, 0xaac00b1afa5abcbe, 0x9b60163da9fb3335, 0xa 0xa66f0f9c1cb64129, 0x93af252b376bba97, 0xacdf3ac948dd7273, 0x99df50765b6e4540, 0x9faf6632798844f8, 0xa12f7bfdad9cbe13, 0xaeef91d802243c88, 0x874fa7c1819e90d8, 0xacdfbdba3692d513, 0x62efd3c22b8f71f1, 0x74afe9d96b2a23d9) -@inline function table_unpack(ind) - j = @inbounds J_TABLE[ind] +# :nothrow needed since the compiler can't prove `ind` is inbounds. +Base.@assume_effects :nothrow function table_unpack(ind::Int32) + ind = ind & 255 + 1 # 255 == length(J_TABLE) - 1 + j = getfield(J_TABLE, ind) # use getfield so the compiler can prove consistent jU = reinterpret(Float64, JU_CONST | (j&JU_MASK)) jL = reinterpret(Float64, JL_CONST | (j>>8)) return jU, jL @@ -211,7 +212,7 @@ end r = muladd(N_float, LogBo256U(base, T), x) r = muladd(N_float, LogBo256L(base, T), r) k = N >> 8 - jU, jL = table_unpack(N&255 + 1) + jU, jL = table_unpack(N) small_part = muladd(jU, expm1b_kernel(base, r), jL) + jU if !(abs(x) <= SUBNORM_EXP(base, T)) @@ -220,7 +221,7 @@ end if k <= -53 # The UInt64 forces promotion. (Only matters for 32 bit systems.) twopk = (k + UInt64(53)) << 52 - return reinterpret(T, twopk + reinterpret(UInt64, small_part))*(2.0^-53) + return reinterpret(T, twopk + reinterpret(UInt64, small_part))*0x1p-53 end #k == 1024 && return (small_part * 2.0) * 2.0^1023 end @@ -236,16 +237,18 @@ end r = muladd(N_float, LogBo256U(base, T), x) r = muladd(N_float, LogBo256L(base, T), r) k = N >> 8 - jU, jL = table_unpack(N&255 + 1) - very_small = muladd(jU, expm1b_kernel(base, r), jL) - small_part = muladd(jU,xlo,very_small) + jU + jU, jL = table_unpack(N) + kern = expm1b_kernel(base, r) + very_small = muladd(kern, jU*xlo, jL) + hi, lo = Base.canonicalize2(1.0, kern) + small_part = fma(jU, hi, muladd(jU, (lo+xlo), very_small)) if !(abs(x) <= SUBNORM_EXP(base, T)) x >= MAX_EXP(base, T) && return Inf x <= MIN_EXP(base, T) && return 0.0 if k <= -53 # The UInt64 forces promotion. (Only matters for 32 bit systems.) twopk = (k + UInt64(53)) << 52 - return reinterpret(T, twopk + reinterpret(UInt64, small_part))*(2.0^-53) + return reinterpret(T, twopk + reinterpret(UInt64, small_part))*0x1p-53 end #k == 1024 && return (small_part * 2.0) * 2.0^1023 end @@ -275,17 +278,18 @@ end r = muladd(N_float, LogBU(base, T), x) r = muladd(N_float, LogBL(base, T), r) small_part = expb_kernel(base, r) - if !(abs(x) <= SUBNORM_EXP(base, T)) - x > MAX_EXP(base, T) && return Inf32 - x < MIN_EXP(base, T) && return 0.0f0 - if N <= Int32(-24) - twopk = reinterpret(T, (N+Int32(151)) << Int32(23)) - return (twopk*small_part)*(2f0^(-24)) - end - N == 128 && return small_part * T(2.0) * T(2.0)^127 + power = (N+Int32(127)) + x > MAX_EXP(base, T) && return Inf32 + x < MIN_EXP(base, T) && return 0.0f0 + if x <= -SUBNORM_EXP(base, T) + power += Int32(24) + small_part *= Float32(0x1p-24) end - twopk = reinterpret(T, (N+Int32(127)) << Int32(23)) - return twopk*small_part + if N == 128 + power -= Int32(1) + small_part *= 2f0 + end + return small_part * reinterpret(T, power << Int32(23)) end @inline function exp_impl_fast(x::Float32, base) @@ -320,8 +324,8 @@ for (func, fast_func, base) in ((:exp2, :exp2_fast, Val(2)), (:exp, :exp_fast, Val(:ℯ)), (:exp10, :exp10_fast, Val(10))) @eval begin - $func(x::Union{Float16,Float32,Float64}) = exp_impl(x, $base) - $fast_func(x::Union{Float32,Float64}) = exp_impl_fast(x, $base) + @noinline $func(x::Union{Float16,Float32,Float64}) = exp_impl(x, $base) + @noinline $fast_func(x::Union{Float32,Float64}) = exp_impl_fast(x, $base) end end @@ -438,7 +442,7 @@ function expm1(x::Float64) r = muladd(N_float, LogBo256U(Val(:ℯ), T), x) r = muladd(N_float, LogBo256L(Val(:ℯ), T), r) k = Int64(N >> 8) - jU, jL = table_unpack(N&255 +1) + jU, jL = table_unpack(N) p = expm1b_kernel(Val(:ℯ), r) twopk = reinterpret(Float64, (1023+k) << 52) twopnk = reinterpret(Float64, (1023-k) << 52) diff --git a/base/special/hyperbolic.jl b/base/special/hyperbolic.jl index 74f750064c7c2..333951b6f6024 100644 --- a/base/special/hyperbolic.jl +++ b/base/special/hyperbolic.jl @@ -175,7 +175,7 @@ function asinh(x::T) where T <: Union{Float32, Float64} # return sign(x)*log(2|x|+1/(|x|+sqrt(x*x+1))) # d) |x| >= 2^28 # return sign(x)*(log(x)+ln2)) - if isnan(x) || isinf(x) + if !isfinite(x) return x end absx = abs(x) diff --git a/base/special/log.jl b/base/special/log.jl index bca0d7143db48..5d7f1c8118724 100644 --- a/base/special/log.jl +++ b/base/special/log.jl @@ -92,7 +92,6 @@ const t_log_Float64 = ((0.0,0.0),(0.007782140442941454,-8.865052917267247e-13), (0.6853040030982811,6.383161517064652e-13),(0.6892332812385575,2.5144230728376075e-13), (0.6931471805601177,-1.7239444525614835e-13)) - # Float32 lookup table # to generate values: # N=16 @@ -156,7 +155,12 @@ logbU(::Type{Float64},::Val{10}) = 0.4342944819032518 logbL(::Type{Float64},::Val{10}) = 1.098319650216765e-17 # Procedure 1 -@inline function log_proc1(y::Float64,mf::Float64,F::Float64,f::Float64,jp::Int,base=Val(:ℯ)) +# XXX we want to mark :consistent-cy 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 @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] l_hi = mf* 0.6931471805601177 + hi @@ -211,8 +215,13 @@ end return fma(m_hi, u, fma(m_lo, u, m_hi*fma(fma(-u,f,2(f-u)), g, q))) end +# Procedure 1 +# XXX we want to mark :consistent-cy 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 @inline function log_proc1(y::Float32,mf::Float32,F::Float32,f::Float32,base=Val(:ℯ)) + jp = unsafe_trunc(Int,128.0f0*F)-127 -@inline function log_proc1(y::Float32,mf::Float32,F::Float32,f::Float32,jp::Int,base=Val(:ℯ)) ## Steps 1 and 2 @inbounds hi = t_log_Float32[jp] l = mf*0.6931471805599453 + hi @@ -232,6 +241,7 @@ end Float32(logb(Float32, base)*(l + (u + q))) end +# Procedure 2 @inline function log_proc2(f::Float32,base=Val(:ℯ)) ## Step 1 # compute in higher precision @@ -281,9 +291,8 @@ function _log(x::Float64, base, func) mf = Float64(m) F = (y + 3.5184372088832e13) - 3.5184372088832e13 # 0x1p-7*round(0x1p7*y) f = y-F - jp = unsafe_trunc(Int,128.0*F)-127 - return log_proc1(y,mf,F,f,jp,base) + return log_proc1(y,mf,F,f,base) elseif x == 0.0 -Inf elseif isnan(x) @@ -317,9 +326,8 @@ function _log(x::Float32, base, func) mf = Float32(m) F = (y + 65536.0f0) - 65536.0f0 # 0x1p-7*round(0x1p7*y) f = y-F - jp = unsafe_trunc(Int,128.0f0*F)-127 - log_proc1(y,mf,F,f,jp,base) + log_proc1(y,mf,F,f,base) elseif x == 0f0 -Inf32 elseif isnan(x) @@ -352,15 +360,14 @@ function log1p(x::Float64) mf = Float64(m) F = (y + 3.5184372088832e13) - 3.5184372088832e13 # 0x1p-7*round(0x1p7*y) f = (y - F) + c*s #2^m(F+f) = 1+x = z+c - jp = unsafe_trunc(Int,128.0*F)-127 - log_proc1(y,mf,F,f,jp) + log_proc1(y,mf,F,f) elseif x == -1.0 -Inf elseif isnan(x) NaN else - throw_complex_domainerror(:log1p, x) + throw_complex_domainerror_neg1(:log1p, x) end end @@ -385,63 +392,202 @@ function log1p(x::Float32) mf = Float32(m) F = (y + 65536.0f0) - 65536.0f0 # 0x1p-7*round(0x1p7*y) f = (y - F) + s*c #2^m(F+f) = 1+x = z+c - jp = unsafe_trunc(Int,128.0*F)-127 - log_proc1(y,mf,F,f,jp) + log_proc1(y,mf,F,f) elseif x == -1f0 -Inf32 elseif isnan(x) NaN32 else - throw_complex_domainerror(:log1p, x) + throw_complex_domainerror_neg1(:log1p, x) end end - -@inline function log_ext_kernel(x_hi::Float64, x_lo::Float64) - c1hi = 0.666666666666666629659233 - hi_order = evalpoly(x_hi, (0.400000000000000077715612, 0.285714285714249172087875, - 0.222222222230083560345903, 0.181818180850050775676507, - 0.153846227114512262845736, 0.13332981086846273921509, - 0.117754809412463995466069, 0.103239680901072952701192, - 0.116255524079935043668677)) - res_hi, res_lo = two_mul(hi_order, x_hi) - res_lo = fma(x_lo, hi_order, res_lo) - ans_hi = c1hi + res_hi - ans_lo = ((c1hi - ans_hi) + res_hi) + (res_lo + 3.80554962542412056336616e-17) - return ans_hi, ans_lo +#function make_compact_table(N) +# table = Tuple{UInt64,Float64}[] +# lo, hi = 0x1.69555p-1, 0x1.69555p0 +# for i in 0:N-1 +# # I am not fully sure why this is the right formula to use, but it apparently is +# center = i/(2*N) + lo < 1 ? (i+.5)/(2*N) + lo : (i+.5)/N + hi -1 +# invc = Float64(center < 1 ? round(N/center)/N : round(2*N/center)/(N*2)) +# c = inv(big(invc)) +# logc = Float64(round(0x1p43*log(c))/0x1p43) +# logctail = reinterpret(Float64, Float64(log(c) - logc)) +# p1 = (reinterpret(UInt64,invc) >> 45) % UInt8 +# push!(table, (p1|reinterpret(UInt64,logc),logctail)) +# end +# return Tuple(table) +#end +#const t_log_table_compact = make_compact_table(128) +const t_log_table_compact = ( + (0xbfd62c82f2b9c8b5, 5.929407345889625e-15), + (0xbfd5d1bdbf5808b4, -2.544157440035963e-14), + (0xbfd57677174558b3, -3.443525940775045e-14), + (0xbfd51aad872df8b2, -2.500123826022799e-15), + (0xbfd4be5f957778b1, -8.929337133850617e-15), + (0xbfd4618bc21c60b0, 1.7625431312172662e-14), + (0xbfd404308686a8af, 1.5688303180062087e-15), + (0xbfd3a64c556948ae, 2.9655274673691784e-14), + (0xbfd347dd9a9880ad, 3.7923164802093147e-14), + (0xbfd2e8e2bae120ac, 3.993416384387844e-14), + (0xbfd2895a13de88ab, 1.9352855826489123e-14), + (0xbfd2895a13de88ab, 1.9352855826489123e-14), + (0xbfd22941fbcf78aa, -1.9852665484979036e-14), + (0xbfd1c898c16998a9, -2.814323765595281e-14), + (0xbfd1675cababa8a8, 2.7643769993528702e-14), + (0xbfd1058bf9ae48a7, -4.025092402293806e-14), + (0xbfd0a324e27390a6, -1.2621729398885316e-14), + (0xbfd0402594b4d0a5, -3.600176732637335e-15), + (0xbfd0402594b4d0a5, -3.600176732637335e-15), + (0xbfcfb9186d5e40a4, 1.3029797173308663e-14), + (0xbfcef0adcbdc60a3, 4.8230289429940886e-14), + (0xbfce27076e2af0a2, -2.0592242769647135e-14), + (0xbfcd5c216b4fc0a1, 3.149265065191484e-14), + (0xbfcc8ff7c79aa0a0, 4.169796584527195e-14), + (0xbfcc8ff7c79aa0a0, 4.169796584527195e-14), + (0xbfcbc286742d909f, 2.2477465222466186e-14), + (0xbfcaf3c94e80c09e, 3.6507188831790577e-16), + (0xbfca23bc1fe2b09d, -3.827767260205414e-14), + (0xbfca23bc1fe2b09d, -3.827767260205414e-14), + (0xbfc9525a9cf4509c, -4.7641388950792196e-14), + (0xbfc87fa06520d09b, 4.9278276214647115e-14), + (0xbfc7ab890210e09a, 4.9485167661250996e-14), + (0xbfc7ab890210e09a, 4.9485167661250996e-14), + (0xbfc6d60fe719d099, -1.5003333854266542e-14), + (0xbfc5ff3070a79098, -2.7194441649495324e-14), + (0xbfc5ff3070a79098, -2.7194441649495324e-14), + (0xbfc526e5e3a1b097, -2.99659267292569e-14), + (0xbfc44d2b6ccb8096, 2.0472357800461955e-14), + (0xbfc44d2b6ccb8096, 2.0472357800461955e-14), + (0xbfc371fc201e9095, 3.879296723063646e-15), + (0xbfc29552f81ff094, -3.6506824353335045e-14), + (0xbfc1b72ad52f6093, -5.4183331379008994e-14), + (0xbfc1b72ad52f6093, -5.4183331379008994e-14), + (0xbfc0d77e7cd09092, 1.1729485484531301e-14), + (0xbfc0d77e7cd09092, 1.1729485484531301e-14), + (0xbfbfec9131dbe091, -3.811763084710266e-14), + (0xbfbe27076e2b0090, 4.654729747598445e-14), + (0xbfbe27076e2b0090, 4.654729747598445e-14), + (0xbfbc5e548f5bc08f, -2.5799991283069902e-14), + (0xbfba926d3a4ae08e, 3.7700471749674615e-14), + (0xbfba926d3a4ae08e, 3.7700471749674615e-14), + (0xbfb8c345d631a08d, 1.7306161136093256e-14), + (0xbfb8c345d631a08d, 1.7306161136093256e-14), + (0xbfb6f0d28ae5608c, -4.012913552726574e-14), + (0xbfb51b073f06208b, 2.7541708360737882e-14), + (0xbfb51b073f06208b, 2.7541708360737882e-14), + (0xbfb341d7961be08a, 5.0396178134370583e-14), + (0xbfb341d7961be08a, 5.0396178134370583e-14), + (0xbfb16536eea38089, 1.8195060030168815e-14), + (0xbfaf0a30c0118088, 5.213620639136504e-14), + (0xbfaf0a30c0118088, 5.213620639136504e-14), + (0xbfab42dd71198087, 2.532168943117445e-14), + (0xbfab42dd71198087, 2.532168943117445e-14), + (0xbfa77458f632c086, -5.148849572685811e-14), + (0xbfa77458f632c086, -5.148849572685811e-14), + (0xbfa39e87b9fec085, 4.6652946995830086e-15), + (0xbfa39e87b9fec085, 4.6652946995830086e-15), + (0xbf9f829b0e780084, -4.529814257790929e-14), + (0xbf9f829b0e780084, -4.529814257790929e-14), + (0xbf97b91b07d58083, -4.361324067851568e-14), + (0xbf8fc0a8b0fc0082, -1.7274567499706107e-15), + (0xbf8fc0a8b0fc0082, -1.7274567499706107e-15), + (0xbf7fe02a6b100081, -2.298941004620351e-14), + (0xbf7fe02a6b100081, -2.298941004620351e-14), + (0x0000000000000080, 0.0), + (0x0000000000000080, 0.0), + (0x3f8010157589007e, -1.4902732911301337e-14), + (0x3f9020565893807c, -3.527980389655325e-14), + (0x3f98492528c9007a, -4.730054772033249e-14), + (0x3fa0415d89e74078, 7.580310369375161e-15), + (0x3fa466aed42e0076, -4.9893776716773285e-14), + (0x3fa894aa149fc074, -2.262629393030674e-14), + (0x3faccb73cdddc072, -2.345674491018699e-14), + (0x3faeea31c006c071, -1.3352588834854848e-14), + (0x3fb1973bd146606f, -3.765296820388875e-14), + (0x3fb3bdf5a7d1e06d, 5.1128335719851986e-14), + (0x3fb5e95a4d97a06b, -5.046674438470119e-14), + (0x3fb700d30aeac06a, 3.1218748807418837e-15), + (0x3fb9335e5d594068, 3.3871241029241416e-14), + (0x3fbb6ac88dad6066, -1.7376727386423858e-14), + (0x3fbc885801bc4065, 3.957125899799804e-14), + (0x3fbec739830a2063, -5.2849453521890294e-14), + (0x3fbfe89139dbe062, -3.767012502308738e-14), + (0x3fc1178e8227e060, 3.1859736349078334e-14), + (0x3fc1aa2b7e23f05f, 5.0900642926060466e-14), + (0x3fc2d1610c86805d, 8.710783796122478e-15), + (0x3fc365fcb015905c, 6.157896229122976e-16), + (0x3fc4913d8333b05a, 3.821577743916796e-14), + (0x3fc527e5e4a1b059, 3.9440046718453496e-14), + (0x3fc6574ebe8c1057, 2.2924522154618074e-14), + (0x3fc6f0128b757056, -3.742530094732263e-14), + (0x3fc7898d85445055, -2.5223102140407338e-14), + (0x3fc8beafeb390053, -1.0320443688698849e-14), + (0x3fc95a5adcf70052, 1.0634128304268335e-14), + (0x3fca93ed3c8ae050, -4.3425422595242564e-14), + (0x3fcb31d8575bd04f, -1.2527395755711364e-14), + (0x3fcbd087383be04e, -5.204008743405884e-14), + (0x3fcc6ffbc6f0104d, -3.979844515951702e-15), + (0x3fcdb13db0d4904b, -4.7955860343296286e-14), + (0x3fce530effe7104a, 5.015686013791602e-16), + (0x3fcef5ade4dd0049, -7.252318953240293e-16), + (0x3fcf991c6cb3b048, 2.4688324156011588e-14), + (0x3fd07138604d5846, 5.465121253624792e-15), + (0x3fd0c42d67616045, 4.102651071698446e-14), + (0x3fd1178e8227e844, -4.996736502345936e-14), + (0x3fd16b5ccbacf843, 4.903580708156347e-14), + (0x3fd1bf99635a6842, 5.089628039500759e-14), + (0x3fd214456d0eb841, 1.1782016386565151e-14), + (0x3fd2bef07cdc903f, 4.727452940514406e-14), + (0x3fd314f1e1d3603e, -4.4204083338755686e-14), + (0x3fd36b6776be103d, 1.548345993498083e-14), + (0x3fd3c2527733303c, 2.1522127491642888e-14), + (0x3fd419b423d5e83b, 1.1054030169005386e-14), + (0x3fd4718dc271c83a, -5.534326352070679e-14), + (0x3fd4c9e09e173039, -5.351646604259541e-14), + (0x3fd522ae0738a038, 5.4612144489920215e-14), + (0x3fd57bf753c8d037, 2.8136969901227338e-14), + (0x3fd5d5bddf596036, -1.156568624616423e-14)) + + @inline function log_tab_unpack(t::UInt64) + invc = UInt64(t&UInt64(0xff)|0x1ff00)<<45 + logc = t&(~UInt64(0xff)) + return (reinterpret(Float64, invc), reinterpret(Float64, logc)) end # Log implementation that returns 2 numbers which sum to give true value with about 68 bits of precision -# Implimentation adapted from SLEEFPirates.jl +# Since `log` only makes sense for positive exponents, we speed up the implementation by stealing the sign bit +# of the input for an extra bit of the exponent which is used to normalize subnormal inputs. # Does not normalize results. -# Must be caused with positive finite arguments -function _log_ext(d::Float64) - m, e = significand(d), exponent(d) - if m > 1.5 - m *= 0.5 - e += 1.0 - end - # x = (m-1)/(m+1) - mp1hi = m + 1.0 - mp1lo = m + (1.0 - mp1hi) - invy = inv(mp1hi) - xhi = (m - 1.0) * invy - xlo = fma(-xhi, mp1lo, fma(-xhi, mp1hi, m - 1.0)) * invy - x2hi, x2lo = two_mul(xhi, xhi) - x2lo = muladd(xhi, xlo * 2.0, x2lo) - thi, tlo = log_ext_kernel(x2hi, x2lo) - - shi = 0.6931471805582987 * e - xhi2 = xhi * 2.0 - shinew = muladd(xhi, 2.0, shi) - slo = muladd(1.6465949582897082e-12, e, muladd(xlo, 2.0, (((shi - shinew) + xhi2)))) - shi = shinew - x3hi, x3lo = two_mul(x2hi, xhi) - x3lo = muladd(x2hi, xlo, muladd(xhi, x2lo,x3lo)) - x3thi, x3tlo = two_mul(x3hi, thi) - x3tlo = muladd(x3hi, tlo, muladd(x3lo, thi, x3tlo)) - anshi = x3thi + shi - anslo = slo + x3tlo - ((anshi - shi) - x3thi) - return anshi, anslo +# 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) + # 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)) + 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. + r = fma(z, invc, -1.0) + # k*Ln2 + log(c) + r. + t1 = muladd(k, 0.6931471805598903, logc) #ln(2) hi part + t2 = t1 + r + lo1 = muladd(k, 5.497923018708371e-14, logctail) #ln(2) lo part + lo2 = t1 - t2 + r + ar = -0.5 * r + ar2, lo3 = two_mul(r, ar) + # k*Ln2 + log(c) + r + .5*r*r. + hi = t2 + ar2 + lo4 = t2 - hi + ar2 + p = evalpoly(r, (-0x1.555555555556p-1, 0x1.0000000000006p-1, -0x1.999999959554ep-2, 0x1.555555529a47ap-2, -0x1.2495b9b4845e9p-2, 0x1.0002b8b263fc3p-2)) + lo = lo1 + lo2 + lo3 + muladd(r*ar2, p, lo4) + return hi, lo end diff --git a/base/special/rem_pio2.jl b/base/special/rem_pio2.jl index 4ec9945885e7e..de5c4151df2d0 100644 --- a/base/special/rem_pio2.jl +++ b/base/special/rem_pio2.jl @@ -23,6 +23,7 @@ # @printf "0x%016x,\n" k # I -= k # end + const INV_2PI = ( 0x28be_60db_9391_054a, 0x7f09_d5f4_7d4d_3770, @@ -93,9 +94,9 @@ end return unsafe_trunc(Int, fn), DoubleFloat64(y1, y2) end + """ fromfraction(f::Int128) - Compute a tuple of values `(z1,z2)` such that ``z1 + z2 == f / 2^128`` and the significand of `z1` has 27 trailing zeros. @@ -108,7 +109,7 @@ function fromfraction(f::Int128) # 1. get leading term truncated to 26 bits s = ((f < 0) % UInt64) << 63 # sign bit x = abs(f) % UInt128 # magnitude - n1 = 128-leading_zeros(x) # ndigits0z(x,2) + n1 = Base.top_set_bit(x) # ndigits0z(x,2) m1 = ((x >> (n1-26)) % UInt64) << 27 d1 = ((n1-128+1021) % UInt64) << 52 z1 = reinterpret(Float64, s | (d1 + m1)) @@ -118,14 +119,17 @@ function fromfraction(f::Int128) if x2 == 0 return (z1, 0.0) end - n2 = 128-leading_zeros(x2) + n2 = Base.top_set_bit(x2) m2 = (x2 >> (n2-53)) % UInt64 d2 = ((n2-128+1021) % UInt64) << 52 z2 = reinterpret(Float64, s | (d2 + m2)) return (z1,z2) end -function paynehanek(x::Float64) +# XXX we want to mark :consistent-cy 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 function paynehanek(x::Float64) # 1. Convert to form # # x = X * 2^k, @@ -210,14 +214,13 @@ end """ rem_pio2_kernel(x::Union{Float32, Float64}) - Calculate `x` divided by `π/2` accurately for arbitrarily large `x`. Returns a pair `(k, r)`, where `k` is the quadrant of the result (multiple of π/2) and `r` is the remainder, such that ``k * π/2 = x - r``. The remainder is given as a double-double pair. `k` is positive if `x > 0` and is negative if `x ≤ 0`. """ -@inline function rem_pio2_kernel(x::Float64) +@inline function rem_pio2_kernel(x::Float64) # accurate to 1e-22 xhp = poshighword(x) # xhp <= highword(5pi/4) implies |x| ~<= 5pi/4, if xhp <= 0x400f6a7a @@ -279,50 +282,15 @@ The remainder is given as a double-double pair. return paynehanek(x) end -## Float32 @inline function rem_pio2_kernel(x::Float32) - pio2_1 = 1.57079631090164184570e+00 - pio2_1t = 1.58932547735281966916e-08 - inv_pio2 = 6.36619772367581382433e-01 xd = convert(Float64, x) - absxd = abs(xd) - # it is assumed that NaN and Infs have been checked - if absxd <= pi*5/4 - if absxd <= pi*3/4 - if x > 0 - return 1, DoubleFloat32(xd - pi/2) - else - return -1, DoubleFloat32(xd + pi/2) - end - end - if x > 0 - return 2, DoubleFloat32(xd - pi) - else - return -2, DoubleFloat32(xd + pi) - end - elseif absxd <= pi*9/4 - if absxd <= pi*7/4 - if x > 0 - return 3, DoubleFloat32(xd - pi*3/2) - else - return -3, DoubleFloat32(xd + pi*3/2) - end - end - if x > 0 - return 4, DoubleFloat32(xd - pi*4/2) - else - return -4, DoubleFloat32(xd + pi*4/2) - end - end - #/* 33+53 bit pi is good enough for medium size */ - if absxd < Float32(pi)/2*2.0f0^28 # medium size */ - # use Cody Waite reduction with two coefficients - fn = round(xd*inv_pio2) - r = xd-fn*pio2_1 - w = fn*pio2_1t - y = r-w; + # use Cody Waite reduction with two coefficients + if abs(x) < Float32(pi*0x1p27) # x < 2^28 * pi/2 + fn = round(xd * (2/pi)) + r = fma(fn, -pi/2, xd) + y = fma(fn, -6.123233995736766e-17, r) # big(pi)/2 - pi/2 remainder return unsafe_trunc(Int, fn), DoubleFloat32(y) end - n, y = rem_pio2_kernel(xd) + n, y = @noinline paynehanek(xd) return n, DoubleFloat32(y.hi) end diff --git a/base/special/trig.jl b/base/special/trig.jl index e3033aab6c272..5b2a23688ca6b 100644 --- a/base/special/trig.jl +++ b/base/special/trig.jl @@ -34,7 +34,7 @@ function sin(x::T) where T<:Union{Float32, Float64} end return sin_kernel(x) elseif isnan(x) - return T(NaN) + return x elseif isinf(x) sin_domain_error(x) end @@ -103,7 +103,7 @@ function cos(x::T) where T<:Union{Float32, Float64} end return cos_kernel(x) elseif isnan(x) - return T(NaN) + return x elseif isinf(x) cos_domain_error(x) else @@ -127,6 +127,7 @@ const DC3 = 2.48015872894767294178e-05 const DC4 = -2.75573143513906633035e-07 const DC5 = 2.08757232129817482790e-09 const DC6 = -1.13596475577881948265e-11 + """ cos_kernel(y) @@ -178,7 +179,7 @@ function sincos(x::T) where T<:Union{Float32, Float64} end return sincos_kernel(x) elseif isnan(x) - return T(NaN), T(NaN) + return x, x elseif isinf(x) sincos_domain_error(x) end @@ -220,7 +221,7 @@ function tan(x::T) where T<:Union{Float32, Float64} end return tan_kernel(x) elseif isnan(x) - return T(NaN) + return x elseif isinf(x) tan_domain_error(x) end @@ -255,7 +256,7 @@ end # # Note: tan(y+z) = tan(y) + tan'(y)*z # ~ tan(y) + (1+y*y)*z - # Therefore, for better accuracz in computing tan(y+z), let + # Therefore, for better accuracy in computing tan(y+z), let # 3 2 2 2 2 # r = y *(T2+y *(T3+y *(...+y *(T12+y *T13)))) # then @@ -581,8 +582,8 @@ function atan(y::T, x::T) where T<:Union{Float32, Float64} # S8) ATAN2(+-INF,+INF ) is +-pi/4 ; # S9) ATAN2(+-INF,-INF ) is +-3pi/4; # S10) ATAN2(+-INF, (anything but,0,NaN, and INF)) is +-pi/2; - if isnan(x) || isnan(y) # S1 or S2 - return T(NaN) + if isnan(x) | isnan(y) # S1 or S2 + return isnan(x) ? x : y end if x == T(1.0) # then y/x = y and x > 0, see M2 @@ -722,23 +723,64 @@ function acos(x::T) where T <: Union{Float32, Float64} end end -# multiply in extended precision -function mulpi_ext(x::Float64) - m = 3.141592653589793 - m_hi = 3.1415926218032837 - m_lo = 3.178650954705639e-8 +# Uses minimax polynomial of sin(π * x) for π * x in [0, .25] +@inline function sinpi_kernel(x::Float64) + sinpi_kernel_wide(x) +end +@inline function sinpi_kernel_wide(x::Float64) + x² = x*x + x⁴ = x²*x² + r = evalpoly(x², (2.5501640398773415, -0.5992645293202981, 0.08214588658006512, + -7.370429884921779e-3, 4.662827319453555e-4, -2.1717412523382308e-5)) + return muladd(3.141592653589793, x, x*muladd(-5.16771278004997, + x², muladd(x⁴, r, 1.2245907532225998e-16))) +end +@inline function sinpi_kernel(x::Float32) + Float32(sinpi_kernel_wide(x)) +end +@inline function sinpi_kernel_wide(x::Float32) + x = Float64(x) + return x*evalpoly(x*x, (3.1415926535762266, -5.167712769188119, + 2.5501626483206374, -0.5992021090314925, 0.08100185277841528)) +end - x_hi = reinterpret(Float64, reinterpret(UInt64,x) & 0xffff_ffff_f800_0000) - x_lo = x-x_hi +@inline function sinpi_kernel(x::Float16) + Float16(sinpi_kernel_wide(x)) +end +@inline function sinpi_kernel_wide(x::Float16) + x = Float32(x) + return x*evalpoly(x*x, (3.1415927f0, -5.1677127f0, 2.5501626f0, -0.5992021f0, 0.081001855f0)) +end - y_hi = m*x - y_lo = x_hi * m_lo + (x_lo* m_hi + ((x_hi*m_hi-y_hi) + x_lo*m_lo)) +# Uses minimax polynomial of cos(π * x) for π * x in [0, .25] +@inline function cospi_kernel(x::Float64) + cospi_kernel_wide(x) +end +@inline function cospi_kernel_wide(x::Float64) + x² = x*x + r = x²*evalpoly(x², (4.058712126416765, -1.3352627688537357, 0.23533063027900392, + -0.025806887811869204, 1.9294917136379183e-3, -1.0368935675474665e-4)) + a_x² = 4.934802200544679 * x² + a_x²lo = muladd(3.109686485461973e-16, x², muladd(4.934802200544679, x², -a_x²)) - DoubleFloat64(y_hi,y_lo) + w = 1.0-a_x² + return w + muladd(x², r, ((1.0-w)-a_x²) - a_x²lo) +end +@inline function cospi_kernel(x::Float32) + Float32(cospi_kernel_wide(x)) +end +@inline function cospi_kernel_wide(x::Float32) + x = Float64(x) + return evalpoly(x*x, (1.0, -4.934802200541122, 4.058712123568637, + -1.3352624040152927, 0.23531426791507182, -0.02550710082498761)) +end +@inline function cospi_kernel(x::Float16) + Float16(cospi_kernel_wide(x)) +end +@inline function cospi_kernel_wide(x::Float16) + x = Float32(x) + return evalpoly(x*x, (1.0f0, -4.934802f0, 4.058712f0, -1.3352624f0, 0.23531426f0, -0.0255071f0)) end -mulpi_ext(x::Float32) = DoubleFloat32(pi*Float64(x)) -mulpi_ext(x::Rational) = mulpi_ext(float(x)) -mulpi_ext(x::Real) = pi*x # Fallback """ sinpi(x) @@ -747,118 +789,62 @@ Compute ``\\sin(\\pi x)`` more accurately than `sin(pi*x)`, especially for large See also [`sind`](@ref), [`cospi`](@ref), [`sincospi`](@ref). """ -function sinpi(x::T) where T<:AbstractFloat +function sinpi(_x::T) where T<:Union{IEEEFloat, Rational} + x = abs(_x) if !isfinite(x) isnan(x) && return x throw(DomainError(x, "`x` cannot be infinite.")) end - - ax = abs(x) - s = maxintfloat(T)/2 - ax >= s && return copysign(zero(T),x) # integer-valued - - # reduce to interval [-1,1] - # assumes RoundNearest rounding mode - t = 3*s - rx = x-((x+t)-t) # zeros may be incorrectly signed - arx = abs(rx) - - if (arx == 0) | (arx == 1) - copysign(zero(T),x) - elseif arx < 0.25 - sin_kernel(mulpi_ext(rx)) - elseif arx < 0.75 - y = mulpi_ext(T(0.5) - arx) - copysign(cos_kernel(y),rx) - else - y = mulpi_ext(copysign(one(T),rx) - rx) - sin_kernel(y) - end -end - -# Rationals -function sinpi(x::T) where T<:Rational - Tf = float(T) - if !isfinite(x) - throw(DomainError(x, "`x` must be finite.")) - end - - # until we get an IEEE remainder function (#9283) - rx = rem(x,2) - if rx > 1 - rx -= 2 - elseif rx < -1 - rx += 2 + # For large x, answers are all 1 or zero. + if T <: AbstractFloat + x >= maxintfloat(T) && return copysign(zero(T), _x) end - arx = abs(rx) - if (arx == 0) | (arx == 1) - copysign(zero(Tf),x) - elseif arx < 0.25 - sin_kernel(mulpi_ext(rx)) - elseif arx < 0.75 - y = mulpi_ext(T(0.5) - arx) - copysign(cos_kernel(y),rx) + # reduce to interval [0, 0.5] + n = round(2*x) + rx = float(muladd(T(-.5), n, x)) + n = Int64(n) & 3 + if n==0 + res = sinpi_kernel(rx) + elseif n==1 + res = cospi_kernel(rx) + elseif n==2 + res = zero(T)-sinpi_kernel(rx) else - y = mulpi_ext(copysign(one(T),rx) - rx) - sin_kernel(y) + res = zero(T)-cospi_kernel(rx) end + return ifelse(signbit(_x), -res, res) end - """ cospi(x) Compute ``\\cos(\\pi x)`` more accurately than `cos(pi*x)`, especially for large `x`. """ -function cospi(x::T) where T<:AbstractFloat +function cospi(x::T) where T<:Union{IEEEFloat, Rational} + x = abs(x) if !isfinite(x) isnan(x) && return x throw(DomainError(x, "`x` cannot be infinite.")) end - - ax = abs(x) - s = maxintfloat(T) - ax >= s && return one(T) # even integer-valued - - # reduce to interval [-1,1], then [0,1] - # assumes RoundNearest rounding mode - rx = abs(ax-((ax+s)-s)) - - if rx <= 0.25 - cos_kernel(mulpi_ext(rx)) - elseif rx < 0.75 - y = mulpi_ext(T(0.5) - rx) - sin_kernel(y) - else - y = mulpi_ext(one(T) - rx) - -cos_kernel(y) - end -end - -# Rationals -function cospi(x::T) where T<:Rational - if !isfinite(x) - throw(DomainError(x, "`x` must be finite.")) + # For large x, answers are all 1 or zero. + if T <: AbstractFloat + x >= maxintfloat(T) && return one(T) end - ax = abs(x) - # until we get an IEEE remainder function (#9283) - rx = rem(ax,2) - if rx > 1 - rx = 2-rx - end - - if rx <= 0.25 - cos_kernel(mulpi_ext(rx)) - elseif rx < 0.75 - y = mulpi_ext(T(0.5) - rx) - sin_kernel(y) + # reduce to interval [0, 0.5] + n = round(2*x) + rx = float(muladd(T(-.5), n, x)) + n = Int64(n) & 3 + if n==0 + return cospi_kernel(rx) + elseif n==1 + return zero(T)-sinpi_kernel(rx) + elseif n==2 + return zero(T)-cospi_kernel(rx) else - y = mulpi_ext(one(T) - rx) - -cos_kernel(y) + return sinpi_kernel(rx) end end - """ sincospi(x) @@ -870,74 +856,88 @@ where `x` is in radians), returning a tuple `(sine, cosine)`. See also: [`cispi`](@ref), [`sincosd`](@ref), [`sinpi`](@ref). """ -function sincospi(x::T) where T<:AbstractFloat +function sincospi(_x::T) where T<:Union{IEEEFloat, Rational} + x = abs(_x) if !isfinite(x) isnan(x) && return x, x throw(DomainError(x, "`x` cannot be infinite.")) end + # For large x, answers are all 1 or zero. + if T <: AbstractFloat + x >= maxintfloat(T) && return (copysign(zero(T), _x), one(T)) + end - ax = abs(x) - s = maxintfloat(T) - ax >= s && return (copysign(zero(T), x), one(T)) # even integer-valued - - # reduce to interval [-1,1] - # assumes RoundNearest rounding mode - t = 3*(s/2) - rx = x-((x+t)-t) # zeros may be incorrectly signed - arx = abs(rx) - - # same selection scheme as sinpi and cospi - if (arx == 0) | (arx == 1) - return copysign(zero(T), x), ifelse(ax % 2 == 0, one(T), -one(T)) - elseif arx < 0.25 - return sincos_kernel(mulpi_ext(rx)) - elseif arx < 0.75 - y = mulpi_ext(T(0.5) - arx) - return copysign(cos_kernel(y), rx), sin_kernel(y) + # reduce to interval [0, 0.5] + n = round(2*x) + rx = float(muladd(T(-.5), n, x)) + n = Int64(n) & 3 + si, co = sinpi_kernel(rx),cospi_kernel(rx) + if n==0 + si, co = si, co + elseif n==1 + si, co = co, zero(T)-si + elseif n==2 + si, co = zero(T)-si, zero(T)-co else - y_si = mulpi_ext(copysign(one(T), rx) - rx) - y_co = mulpi_ext(one(T) - arx) - return sin_kernel(y_si), -cos_kernel(y_co) + si, co = zero(T)-co, si end + si = ifelse(signbit(_x), -si, si) + return si, co end -# Rationals -function sincospi(x::T) where T<:Rational - Tf = float(T) +""" + tanpi(x) + +Compute ``\\tan(\\pi x)`` more accurately than `tan(pi*x)`, especially for large `x`. + +!!! compat "Julia 1.10" + This function requires at least Julia 1.10. + +See also [`tand`](@ref), [`sinpi`](@ref), [`cospi`](@ref), [`sincospi`](@ref). +""" + +function tanpi(_x::T) where T<:Union{IEEEFloat, Rational} + # This is modified from sincospi. + # Would it be faster or more accurate to make a tanpi_kernel? + x = abs(_x) if !isfinite(x) - throw(DomainError(x, "`x` must be finite.")) + isnan(x) && return x + throw(DomainError(x, "`x` cannot be infinite.")) end - - # until we get an IEEE remainder function (#9283) - rx = rem(x,2) - if rx > 1 - rx -= 2 - elseif rx < -1 - rx += 2 + # For large x, answers are all zero. + # All integer values for floats larger than maxintfloat are even. + if T <: AbstractFloat + x >= maxintfloat(T) && return copysign(zero(T), _x) end - arx = abs(rx) - # same selection scheme as sinpi and cospi - if (arx == 0) | (arx == 1) - return copysign(zero(Tf),x), ifelse(iseven(numerator(x)), one(Tf), -one(Tf)) - elseif arx < 0.25 - return sincos_kernel(mulpi_ext(rx)) - elseif arx < 0.75 - y = mulpi_ext(T(0.5) - arx) - return copysign(cos_kernel(y), rx), sin_kernel(y) + # reduce to interval [0, 0.5] + n = round(2*x) + rx = float(muladd(T(-.5), n, x)) + n = Int64(n) & 3 + si, co = sinpi_kernel_wide(rx), cospi_kernel_wide(rx) + if n==0 + si, co = si, co + elseif n==1 + si, co = co, zero(T)-si + elseif n==2 + si, co = zero(T)-si, zero(T)-co else - y_si = mulpi_ext(copysign(one(T), rx) - rx) - y_co = mulpi_ext(one(T) - arx) - return sin_kernel(y_si), -cos_kernel(y_co) + si, co = zero(T)-co, si end + si = ifelse(signbit(_x), -si, si) + return float(T)(si / co) end sinpi(x::Integer) = x >= 0 ? zero(float(x)) : -zero(float(x)) cospi(x::Integer) = isodd(x) ? -one(float(x)) : one(float(x)) +tanpi(x::Integer) = x >= 0 ? (isodd(x) ? -zero(float(x)) : zero(float(x))) : + (isodd(x) ? zero(float(x)) : -zero(float(x))) sincospi(x::Integer) = (sinpi(x), cospi(x)) -sinpi(x::Real) = sinpi(float(x)) -cospi(x::Real) = cospi(float(x)) -sincospi(x::Real) = sincospi(float(x)) +sinpi(x::Real) = sin(pi*x) +cospi(x::Real) = cos(pi*x) +sincospi(x::Real) = sincos(pi*x) +tanpi(x::Real) = tan(pi*x) +tanpi(x::Complex) = sinpi(x) / cospi(x) # Is there a better way to do this? function sinpi(z::Complex{T}) where T F = float(T) @@ -1191,7 +1191,7 @@ function sind(x::Real) if isinf(x) return throw(DomainError(x, "`x` cannot be infinite.")) elseif isnan(x) - return oftype(x,NaN) + return x end rx = copysign(float(rem(x,360)),x) @@ -1222,7 +1222,7 @@ function cosd(x::Real) if isinf(x) return throw(DomainError(x, "`x` cannot be infinite.")) elseif isnan(x) - return oftype(x,NaN) + return x end rx = abs(float(rem(x,360))) diff --git a/base/stacktraces.jl b/base/stacktraces.jl index 3cb81d82bd3f7..ee6a2762d7818 100644 --- a/base/stacktraces.jl +++ b/base/stacktraces.jl @@ -52,8 +52,9 @@ struct StackFrame # this type should be kept platform-agnostic so that profiles file::Symbol "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)" - linfo::Union{MethodInstance, CodeInfo, Nothing} + "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} "true if the code is from C" from_c::Bool "true if the code is from an inlined frame" @@ -95,6 +96,86 @@ 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} @@ -107,11 +188,26 @@ 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 1:length(infos) + for i in reverse(1:length(infos)) info = infos[i]::Core.SimpleVector @assert(length(info) == 6) - res[i] = StackFrame(info[1]::Symbol, info[2]::Symbol, info[3]::Int, info[4], info[5]::Bool, info[6]::Bool, pointer) + 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 + linfo = linfo === nothing ? parentmodule(res[i + 1]) : linfo # e.g. `macro expansion` + end + res[i] = StackFrame(func, file, linenum, linfo, info[5]::Bool, info[6]::Bool, pointer) end return res end @@ -153,7 +249,7 @@ end """ stacktrace([trace::Vector{Ptr{Cvoid}},] [c_funcs::Bool=false]) -> StackTrace -Returns a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace +Return a stack trace in the form of a vector of `StackFrame`s. (By default stacktrace doesn't return C functions, but this can be enabled.) When called without specifying a trace, `stacktrace` first calls `backtrace`. """ @@ -200,7 +296,7 @@ end """ remove_frames!(stack::StackTrace, m::Module) -Returns the `StackTrace` with all `StackFrame`s from the provided `Module` removed. +Return the `StackTrace` with all `StackFrame`s from the provided `Module` removed. """ function remove_frames!(stack::StackTrace, m::Module) filter!(f -> !from(f, m), stack) @@ -219,11 +315,19 @@ function show_spec_linfo(io::IO, frame::StackFrame) else Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true) end - elseif linfo isa MethodInstance - def = linfo.def - if isa(def, Method) - sig = linfo.specTypes + elseif linfo isa CodeInfo + print(io, "top-level scope") + elseif linfo isa Module + Base.print_within_stacktrace(io, Base.demangle_function_name(string(frame.func)), bold=true) + else + def, sig = if linfo isa MethodInstance + linfo.def, linfo.specTypes + else + linfo, linfo.sig + end + if def isa Method argnames = Base.method_argnames(def) + argnames = replace(argnames, :var"#unused#" => :var"") if def.nkw > 0 # rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...) kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+def.nkw) ] @@ -246,8 +350,6 @@ function show_spec_linfo(io::IO, frame::StackFrame) else Base.show_mi(io, linfo, true) end - elseif linfo isa CodeInfo - print(io, "top-level scope") end end @@ -277,9 +379,13 @@ function Base.parentmodule(frame::StackFrame) else return (def::Method).module end + elseif linfo isa Method + return linfo.module + elseif linfo isa Module + return linfo else - # The module is not always available (common reasons include inlined - # frames and frames arising from the interpreter) + # The module is not always available (common reasons include + # frames arising from the interpreter) nothing end end @@ -287,7 +393,7 @@ end """ from(frame::StackFrame, filter_mod::Module) -> Bool -Returns whether the `frame` is from the provided `Module` +Return whether the `frame` is from the provided `Module` """ function from(frame::StackFrame, m::Module) return parentmodule(frame) === m diff --git a/base/stat.jl b/base/stat.jl index f8d28cadf0c72..81f9dcfd20191 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -146,7 +146,7 @@ show(io::IO, ::MIME"text/plain", st::StatStruct) = show_statstruct(io, st, false macro stat_call(sym, arg1type, arg) return quote - stat_buf = zeros(UInt8, ccall(:jl_sizeof_stat, Int32, ())) + stat_buf = zeros(UInt8, Int(ccall(:jl_sizeof_stat, Int32, ()))) r = ccall($(Expr(:quote, sym)), Int32, ($(esc(arg1type)), Ptr{UInt8}), $(esc(arg)), stat_buf) if !(r in (0, Base.UV_ENOENT, Base.UV_ENOTDIR, Base.UV_EINVAL)) uv_error(string("stat(", repr($(esc(arg))), ")"), r) @@ -170,7 +170,7 @@ stat(fd::Integer) = stat(RawFD(fd)) """ stat(file) -Returns a structure whose fields contain information about the file. +Return a structure whose fields contain information about the file. The fields of the structure are: | Name | Description | @@ -353,12 +353,17 @@ Return `true` if `path` is a regular file, `false` otherwise. julia> isfile(homedir()) false -julia> f = open("test_file.txt", "w"); +julia> filename = "test_file.txt"; -julia> isfile(f) +julia> write(filename, "Hello world!"); + +julia> isfile(filename) true -julia> close(f); rm("test_file.txt") +julia> rm(filename); + +julia> isfile(filename) +false ``` See also [`isdir`](@ref) and [`ispath`](@ref). @@ -459,17 +464,17 @@ end islink(path...) = islink(lstat(path...)) # samefile can be used for files and directories: #11145#issuecomment-99511194 -samefile(a::StatStruct, b::StatStruct) = a.device==b.device && a.inode==b.inode -function samefile(a::AbstractString, b::AbstractString) - infoa = stat(a) - infob = stat(b) - if ispath(infoa) && ispath(infob) - samefile(infoa, infob) - else - return false - end +function samefile(a::StatStruct, b::StatStruct) + ispath(a) && ispath(b) && a.device == b.device && a.inode == b.inode end +""" + samefile(path_a::AbstractString, path_b::AbstractString) + +Check if the paths `path_a` and `path_b` refer to the same existing file or directory. +""" +samefile(a::AbstractString, b::AbstractString) = samefile(stat(a), stat(b)) + """ ismount(path) -> Bool diff --git a/base/stream.jl b/base/stream.jl index cee4894b28c3c..0b6c9a93777f6 100644 --- a/base/stream.jl +++ b/base/stream.jl @@ -43,7 +43,7 @@ end An abstract type for IO streams handled by libuv. -If`stream isa LibuvStream`, it must obey the following interface: +If `stream isa LibuvStream`, it must obey the following interface: - `stream.handle`, if present, must be a `Ptr{Cvoid}` - `stream.status`, if present, must be an `Int` @@ -105,7 +105,7 @@ function eof(s::LibuvStream) bytesavailable(s) > 0 && return false wait_readnb(s, 1) # This function is race-y if used from multiple threads, but we guarantee - # it to never return false until the stream is definitively exhausted + # it to never return true until the stream is definitively exhausted # and that we won't return true if there's a readerror pending (it'll instead get thrown). # This requires some careful ordering here (TODO: atomic loads) bytesavailable(s) > 0 && return false @@ -377,7 +377,7 @@ if OS_HANDLE != RawFD end function isopen(x::Union{LibuvStream, LibuvServer}) - if x.status == StatusUninit || x.status == StatusInit + if x.status == StatusUninit || x.status == StatusInit || x.handle === C_NULL throw(ArgumentError("$x is not initialized")) end return x.status != StatusClosed @@ -409,7 +409,7 @@ function wait_readnb(x::LibuvStream, nb::Int) while bytesavailable(x.buffer) < nb x.readerror === nothing || throw(x.readerror) isopen(x) || break - x.status != StatusEOF || break + x.status == StatusEOF && break x.throttle = max(nb, x.throttle) start_reading(x) # ensure we are reading iolock_end() @@ -457,7 +457,7 @@ function closewrite(s::LibuvStream) # try-finally unwinds the sigatomic level, so need to repeat sigatomic_end sigatomic_end() iolock_begin() - ct.queue === nothing || list_deletefirst!(ct.queue, ct) + ct.queue === nothing || list_deletefirst!(ct.queue::IntrusiveLinkedList{Task}, ct) if uv_req_data(req) != C_NULL # req is still alive, # so make sure we won't get spurious notifications later @@ -496,34 +496,37 @@ end function close(stream::Union{LibuvStream, LibuvServer}) iolock_begin() - should_wait = false if stream.status == StatusInit ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), stream.handle) stream.status = StatusClosing elseif isopen(stream) - should_wait = uv_handle_data(stream) != C_NULL if stream.status != StatusClosing ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle) stream.status = StatusClosing end end iolock_end() - should_wait && wait_close(stream) + wait_close(stream) nothing end function uvfinalize(uv::Union{LibuvStream, LibuvServer}) - uv.handle == C_NULL && return iolock_begin() if uv.handle != C_NULL - disassociate_julia_struct(uv.handle) # not going to call the usual close hooks - if uv.status != StatusUninit - close(uv) - else + disassociate_julia_struct(uv.handle) # not going to call the usual close hooks (so preserve_handle is not needed) + if uv.status == StatusUninit + Libc.free(uv.handle) + elseif uv.status == StatusInit + ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), uv.handle) + elseif isopen(uv) + if uv.status != StatusClosing + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), uv.handle) + end + elseif uv.status == StatusClosed Libc.free(uv.handle) end - uv.status = StatusClosed uv.handle = C_NULL + uv.status = StatusClosed end iolock_end() nothing @@ -662,9 +665,11 @@ function uv_readcb(handle::Ptr{Cvoid}, nread::Cssize_t, buf::Ptr{Cvoid}) elseif nread == UV_EOF # libuv called uv_stop_reading already if stream.status != StatusClosing stream.status = StatusEOF - if stream isa TTY # TODO: || ccall(:uv_is_writable, Cint, (Ptr{Cvoid},), stream.handle) != 0 - # stream can still be used either by reseteof # TODO: or write - notify(stream.cond) + notify(stream.cond) + if stream isa TTY + # stream can still be used by reseteof (or possibly write) + elseif !(stream isa PipeEndpoint) && ccall(:uv_is_writable, Cint, (Ptr{Cvoid},), stream.handle) != 0 + # stream can still be used by write else # underlying stream is no longer useful: begin finalization ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle) @@ -673,6 +678,7 @@ function uv_readcb(handle::Ptr{Cvoid}, nread::Cssize_t, buf::Ptr{Cvoid}) end else stream.readerror = _UVError("read", nread) + notify(stream.cond) # This is a fatal connection error ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle) stream.status = StatusClosing @@ -713,7 +719,6 @@ end function _uv_hook_close(uv::Union{LibuvStream, LibuvServer}) lock(uv.cond) try - uv.handle = C_NULL uv.status = StatusClosed # notify any listeners that exist on this libuv stream type notify(uv.cond) @@ -1045,7 +1050,7 @@ function uv_write(s::LibuvStream, p::Ptr{UInt8}, n::UInt) # try-finally unwinds the sigatomic level, so need to repeat sigatomic_end sigatomic_end() iolock_begin() - ct.queue === nothing || list_deletefirst!(ct.queue, ct) + ct.queue === nothing || list_deletefirst!(ct.queue::IntrusiveLinkedList{Task}, ct) if uv_req_data(uvw) != C_NULL # uvw is still alive, # so make sure we won't get spurious notifications later @@ -1356,7 +1361,7 @@ julia> io1 = open("same/path", "w") julia> io2 = open("same/path", "w") -julia> redirect_stdio(f, stdout=io1, stderr=io2) # not suppored +julia> redirect_stdio(f, stdout=io1, stderr=io2) # not supported ``` Also the `stdin` argument may not be the same descriptor as `stdout` or `stderr`. ```julia-repl @@ -1568,3 +1573,5 @@ function flush(s::BufferStream) nothing end end + +skip(s::BufferStream, n) = skip(s.buffer, n) diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 515b836311698..2609edeaaaa18 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -16,7 +16,7 @@ about strings: * Each `AbstractChar` in a string is encoded by one or more code units * Only the index of the first code unit of an `AbstractChar` is a valid index * The encoding of an `AbstractChar` is independent of what precedes or follows it - * String encodings are [self-synchronizing] – i.e. `isvalid(s, i)` is O(1) + * String encodings are [self-synchronizing] – i.e. `isvalid(s, i)` is O(1) [self-synchronizing]: https://en.wikipedia.org/wiki/Self-synchronizing_code @@ -46,8 +46,8 @@ AbstractString ncodeunits(s::AbstractString) -> Int Return the number of code units in a string. Indices that are in bounds to -access this string must satisfy `1 ≤ i ≤ ncodeunits(s)`. Not all such indices -are valid – they may not be the start of a character, but they will return a +access this string must satisfy `1 ≤ i ≤ ncodeunits(s)`. Not all such indices +are valid – they may not be the start of a character, but they will return a code unit value when calling `codeunit(s,i)`. # Examples @@ -104,7 +104,7 @@ UInt8 See also [`ncodeunits`](@ref), [`checkbounds`](@ref). """ -@propagate_inbounds codeunit(s::AbstractString, i::Integer) = typeof(i) === Int ? +@propagate_inbounds codeunit(s::AbstractString, i::Integer) = i isa Int ? throw(MethodError(codeunit, (s, i))) : codeunit(s, Int(i)) """ @@ -140,7 +140,7 @@ Stacktrace: [...] ``` """ -@propagate_inbounds isvalid(s::AbstractString, i::Integer) = typeof(i) === Int ? +@propagate_inbounds isvalid(s::AbstractString, i::Integer) = i isa Int ? throw(MethodError(isvalid, (s, i))) : isvalid(s, Int(i)) """ @@ -154,7 +154,7 @@ protocol may assume that `i` is the start of a character in `s`. See also [`getindex`](@ref), [`checkbounds`](@ref). """ -@propagate_inbounds iterate(s::AbstractString, i::Integer) = typeof(i) === Int ? +@propagate_inbounds iterate(s::AbstractString, i::Integer) = i isa Int ? throw(MethodError(iterate, (s, i))) : iterate(s, Int(i)) ## basic generic definitions ## @@ -229,7 +229,7 @@ Symbol(s::AbstractString) = Symbol(String(s)) Symbol(x...) = Symbol(string(x...)) convert(::Type{T}, s::T) where {T<:AbstractString} = s -convert(::Type{T}, s::AbstractString) where {T<:AbstractString} = T(s) +convert(::Type{T}, s::AbstractString) where {T<:AbstractString} = T(s)::T ## summary ## @@ -298,12 +298,13 @@ julia> cmp("b", "β") """ function cmp(a::AbstractString, b::AbstractString) a === b && return 0 - a, b = Iterators.Stateful(a), Iterators.Stateful(b) - for (c::AbstractChar, d::AbstractChar) in zip(a, b) + (iv1, iv2) = (iterate(a), iterate(b)) + while iv1 !== nothing && iv2 !== nothing + (c, d) = (first(iv1)::AbstractChar, first(iv2)::AbstractChar) c ≠ d && return ifelse(c < d, -1, 1) + (iv1, iv2) = (iterate(a, last(iv1)), iterate(b, last(iv2))) end - isempty(a) && return ifelse(isempty(b), 0, -1) - return 1 + return iv1 === nothing ? (iv2 === nothing ? 0 : -1) : 1 end """ @@ -345,7 +346,9 @@ isless(a::AbstractString, b::AbstractString) = cmp(a, b) < 0 # faster comparisons for symbols -cmp(a::Symbol, b::Symbol) = Int(sign(ccall(:strcmp, Int32, (Cstring, Cstring), a, b))) +@assume_effects :total function cmp(a::Symbol, b::Symbol) + Int(sign(ccall(:strcmp, Int32, (Cstring, Cstring), a, b))) +end isless(a::Symbol, b::Symbol) = cmp(a, b) < 0 @@ -389,7 +392,7 @@ length(s::AbstractString) = @inbounds return length(s, 1, ncodeunits(s)::Int) function length(s::AbstractString, i::Int, j::Int) @boundscheck begin 0 < i ≤ ncodeunits(s)::Int+1 || throw(BoundsError(s, i)) - 0 ≤ j < ncodeunits(s)::Int+1 || throw(BoundsError(s, j)) + 0 ≤ j < ncodeunits(s)::Int+1 || throw(BoundsError(s, j)) end n = 0 for k = i:j @@ -438,8 +441,8 @@ thisind(s::AbstractString, i::Integer) = thisind(s, Int(i)) function thisind(s::AbstractString, i::Int) z = ncodeunits(s)::Int + 1 i == z && return i - @boundscheck 0 ≤ i ≤ z || throw(BoundsError(s, i)) - @inbounds while 1 < i && !(isvalid(s, i)::Bool) + @boundscheck 0 ≤ i ≤ z || throw(BoundsError(s, i)) + @inbounds while 1 < i && !(isvalid(s, i)::Bool) i -= 1 end return i @@ -498,7 +501,7 @@ function prevind(s::AbstractString, i::Int, n::Int) z = ncodeunits(s) + 1 @boundscheck 0 < i ≤ z || throw(BoundsError(s, i)) n == 0 && return thisind(s, i) == i ? i : string_index_err(s, i) - while n > 0 && 1 < i + while n > 0 && 1 < i @inbounds n -= isvalid(s, i -= 1) end return i - n @@ -557,7 +560,7 @@ function nextind(s::AbstractString, i::Int, n::Int) z = ncodeunits(s) @boundscheck 0 ≤ i ≤ z || throw(BoundsError(s, i)) n == 0 && return thisind(s, i) == i ? i : string_index_err(s, i) - while n > 0 && i < z + while n > 0 && i < z @inbounds n -= isvalid(s, i += 1) end return i + n @@ -610,6 +613,38 @@ isascii(c::Char) = bswap(reinterpret(UInt32, c)) < 0x80 isascii(s::AbstractString) = all(isascii, s) isascii(c::AbstractChar) = UInt32(c) < 0x80 +@inline function _isascii(code_units::AbstractVector{CU}, first, last) where {CU} + r = zero(CU) + for n = first:last + @inbounds r |= code_units[n] + end + return 0 ≤ r < 0x80 +end + +#The chunking algorithm makes the last two chunks overlap inorder to keep the size fixed +@inline function _isascii_chunks(chunk_size,cu::AbstractVector{CU}, first,last) where {CU} + n=first + while n <= last - chunk_size + _isascii(cu,n,n+chunk_size-1) || return false + n += chunk_size + end + return _isascii(cu,last-chunk_size+1,last) +end +""" + isascii(cu::AbstractVector{CU}) where {CU <: Integer} -> Bool + +Test whether all values in the vector belong to the ASCII character set (0x00 to 0x7f). +This function is intended to be used by other string implementations that need a fast ASCII check. +""" +function isascii(cu::AbstractVector{CU}) where {CU <: Integer} + chunk_size = 1024 + chunk_threshold = chunk_size + (chunk_size ÷ 2) + first = firstindex(cu); last = lastindex(cu) + l = last - first + 1 + l < chunk_threshold && return _isascii(cu,first,last) + return _isascii_chunks(chunk_size,cu,first,last) +end + ## string map, filter ## function map(f, s::AbstractString) @@ -633,7 +668,7 @@ function filter(f, s::AbstractString) for c in s f(c) && write(out, c) end - String(take!(out)) + String(_unsafe_take!(out)) end ## string first and last ## @@ -715,7 +750,7 @@ julia> repeat("ha", 3) repeat(s::AbstractString, r::Integer) = repeat(String(s), r) """ - ^(s::Union{AbstractString,AbstractChar}, n::Integer) + ^(s::Union{AbstractString,AbstractChar}, n::Integer) -> AbstractString Repeat a string or character `n` times. This can also be written as `repeat(s, n)`. @@ -780,3 +815,16 @@ julia> codeunits("Juλia") ``` """ codeunits(s::AbstractString) = CodeUnits(s) + +function _split_rest(s::AbstractString, n::Int) + lastind = lastindex(s) + i = try + prevind(s, lastind, n) + catch e + e isa BoundsError || rethrow() + _check_length_split_rest(length(s), n) + end + last_n = SubString(s, nextind(s, i), lastind) + front = s[begin:i] + return front, last_n +end diff --git a/base/strings/io.jl b/base/strings/io.jl index fffe7904ebf92..5ae67fc8c841c 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -113,7 +113,7 @@ function sprint(f::Function, args...; context=nothing, sizehint::Integer=0) else f(s, args...) end - String(resize!(s.data, s.size)) + String(_unsafe_take!(s)) end function _str_sizehint(x) @@ -125,6 +125,10 @@ function _str_sizehint(x) return sizeof(x) elseif x isa Char return ncodeunits(x) + elseif x isa UInt64 || x isa UInt32 + return ndigits(x) + elseif x isa Int64 || x isa Int32 + return ndigits(x) + (x < zero(x)) else return 8 end @@ -143,7 +147,7 @@ function print_to_string(xs...) for x in xs print(s, x) end - String(resize!(s.data, s.size)) + String(_unsafe_take!(s)) end function string_with_env(env, xs...) @@ -160,7 +164,7 @@ function string_with_env(env, xs...) for x in xs print(env_io, x) end - String(resize!(s.data, s.size)) + String(_unsafe_take!(s)) end """ @@ -201,7 +205,7 @@ function show( ) # compute limit in default case if limit === nothing - get(io, :limit, false) || return show(io, str) + get(io, :limit, false)::Bool || return show(io, str) limit = max(20, displaysize(io)[2]) # one line in collection, seven otherwise get(io, :typeinfo, nothing) === nothing && (limit *= 7) @@ -209,7 +213,7 @@ function show( # early out for short strings len = ncodeunits(str) - len ≤ limit - 2 && # quote chars + len ≤ limit - 2 && # quote chars return show(io, str) # these don't depend on string data diff --git a/base/strings/lazy.jl b/base/strings/lazy.jl index b40fd9a5842b3..eaaa6397d37f2 100644 --- a/base/strings/lazy.jl +++ b/base/strings/lazy.jl @@ -9,14 +9,36 @@ of functions). This type is designed to be cheap to construct at runtime, trying to offload as much work as possible to either the macro or later printing operations. +# Examples + +```jldoctest +julia> n = 5; str = LazyString("n is ", n) +"n is 5" +``` + +See also [`@lazy_str`](@ref). + !!! compat "Julia 1.8" `LazyString` requires Julia 1.8 or later. + +# Extended help +## Safety properties for concurrent programs + +A lazy string itself does not introduce any concurrency problems even if it is printed in +multiple Julia tasks. However, if `print` methods on a captured value can have a +concurrency issue when invoked without synchronizations, printing the lazy string may cause +an issue. Furthermore, the `print` methods on the captured values may be invoked multiple +times, though only exactly one result will be returned. + +!!! compat "Julia 1.9" + `LazyString` is safe in the above sense in Julia 1.9 and later. """ mutable struct LazyString <: AbstractString - parts::Tuple + const parts::Tuple # Created on first access - str::String - LazyString(args...) = new(args) + @atomic str::Union{String,Nothing} + global _LazyString(parts, str) = new(parts, str) + LazyString(args...) = new(args, nothing) end """ @@ -26,6 +48,18 @@ Create a [`LazyString`](@ref) using regular string interpolation syntax. Note that interpolations are *evaluated* at LazyString construction time, but *printing* is delayed until the first access to the string. +See [`LazyString`](@ref) documentation for the safety properties for concurrent programs. + +# Examples + +``` +julia> n = 5; str = lazy"n is \$n" +"n is 5" + +julia> typeof(str) +LazyString +``` + !!! compat "Julia 1.8" `lazy"str"` requires Julia 1.8 or later. """ @@ -33,7 +67,7 @@ macro lazy_str(text) parts = Any[] lastidx = idx = 1 while (idx = findnext('$', text, idx)) !== nothing - lastidx < idx && push!(parts, text[lastidx:idx-1]) + lastidx < idx && push!(parts, text[lastidx:prevind(text, idx)]) idx += 1 expr, idx = Meta.parseatom(text, idx; filename=string(__source__.file)) push!(parts, esc(expr)) @@ -44,14 +78,15 @@ macro lazy_str(text) end function String(l::LazyString) - if !isdefined(l, :str) - l.str = sprint() do io - for p in l.parts - print(io, p) - end + old = @atomic :acquire l.str + old === nothing || return old + str = sprint() do io + for p in l.parts + print(io, p) end end - return l.str + old, ok = @atomicreplace :acquire_release :acquire l.str nothing => str + return ok ? str : (old::String) end hash(s::LazyString, h::UInt64) = hash(String(s), h) @@ -61,3 +96,6 @@ iterate(s::LazyString, i::Integer) = iterate(String(s), i) isequal(a::LazyString, b::LazyString) = isequal(String(a), String(b)) ==(a::LazyString, b::LazyString) = (String(a) == String(b)) ncodeunits(s::LazyString) = ncodeunits(String(s)) +codeunit(s::LazyString) = codeunit(String(s)) +codeunit(s::LazyString, i::Integer) = codeunit(String(s), i) +isvalid(s::LazyString, i::Integer) = isvalid(String(s), i) diff --git a/base/strings/search.jl b/base/strings/search.jl index 938ed8d527d99..1bb4936661c51 100644 --- a/base/strings/search.jl +++ b/base/strings/search.jl @@ -1,5 +1,15 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" +An abstract type representing any sort of pattern matching expression +(typically a regular expression). `AbstractPattern` objects can be used to +match strings with [`match`](@ref). + +!!! compat "Julia 1.6" + This type is available in Julia 1.6 and later. +""" +abstract type AbstractPattern end + nothing_sentinel(i) = i == 0 ? nothing : i function findnext(pred::Fix2{<:Union{typeof(isequal),typeof(==)},<:AbstractChar}, @@ -45,7 +55,7 @@ function _search(a::ByteArray, b::AbstractChar, i::Integer = 1) if isascii(b) _search(a,UInt8(b),i) else - _search(a,unsafe_wrap(Vector{UInt8},string(b)),i).start + _search(a,codeunits(string(b)),i).start end end @@ -88,7 +98,7 @@ function _rsearch(a::ByteArray, b::AbstractChar, i::Integer = length(a)) if isascii(b) _rsearch(a,UInt8(b),i) else - _rsearch(a,unsafe_wrap(Vector{UInt8},string(b)),i).start + _rsearch(a,codeunits(string(b)),i).start end end @@ -197,7 +207,7 @@ _nthbyte(t::AbstractVector, index) = t[index + (firstindex(t)-1)] function _searchindex(s::String, t::String, i::Integer) # Check for fast case of a single byte lastindex(t) == 1 && return something(findnext(isequal(t[1]), s, i), 0) - _searchindex(unsafe_wrap(Vector{UInt8},s), unsafe_wrap(Vector{UInt8},t), i) + _searchindex(codeunits(s), codeunits(t), i) end function _searchindex(s::AbstractVector{<:Union{Int8,UInt8}}, @@ -406,6 +416,67 @@ true """ findlast(ch::AbstractChar, string::AbstractString) = findlast(==(ch), string) +""" + findall( + pattern::Union{AbstractString,AbstractPattern}, + string::AbstractString; + overlap::Bool = false, + ) + findall( + pattern::Vector{UInt8} + A::Vector{UInt8}; + overlap::Bool = false, + ) + +Return a `Vector{UnitRange{Int}}` of all the matches for `pattern` in `string`. +Each element of the returned vector is a range of indices where the +matching sequence is found, like the return value of [`findnext`](@ref). + +If `overlap=true`, the matching sequences are allowed to overlap indices in the +original string, otherwise they must be from disjoint character ranges. + +# Examples +```jldoctest +julia> findall("a", "apple") +1-element Vector{UnitRange{Int64}}: + 1:1 + +julia> findall("nana", "banana") +1-element Vector{UnitRange{Int64}}: + 3:6 + +julia> findall("a", "banana") +3-element Vector{UnitRange{Int64}}: + 2:2 + 4:4 + 6:6 + +julia> findall(UInt8[1,2], UInt8[1,2,3,1,2]) +2-element Vector{UnitRange{Int64}}: + 1:2 + 4:5 +``` + +!!! compat "Julia 1.3" + This method requires at least Julia 1.3. +""" + +function findall(t::Union{AbstractString, AbstractPattern, AbstractVector{<:Union{Int8,UInt8}}}, + s::Union{AbstractString, AbstractPattern, AbstractVector{<:Union{Int8,UInt8}}}, + ; overlap::Bool=false) + found = UnitRange{Int}[] + i, e = firstindex(s), lastindex(s) + while true + r = findnext(t, s, i) + isnothing(r) && break + push!(found, r) + j = overlap || isempty(r) ? first(r) : last(r) + j > e && break + @inbounds i = nextind(s, j) + end + return found +end + # AbstractString implementation of the generic findprev interface function findprev(testf::Function, s::AbstractString, i::Integer) i = Int(i) @@ -450,7 +521,7 @@ function _rsearchindex(s::String, t::String, i::Integer) return something(findprev(isequal(t[1]), s, i), 0) elseif lastindex(t) != 0 j = i ≤ ncodeunits(s) ? nextind(s, i)-1 : i - return _rsearchindex(unsafe_wrap(Vector{UInt8}, s), unsafe_wrap(Vector{UInt8}, t), j) + return _rsearchindex(codeunits(s), codeunits(t), j) elseif i > sizeof(s) return 0 elseif i == 0 diff --git a/base/strings/string.jl b/base/strings/string.jl index c37e36594119e..9716d06deefdf 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -17,10 +17,12 @@ function Base.showerror(io::IO, exc::StringIndexError) if firstindex(s) <= exc.index <= ncodeunits(s) iprev = thisind(s, exc.index) inext = nextind(s, iprev) + escprev = escape_string(s[iprev:iprev]) if inext <= ncodeunits(s) - print(io, ", valid nearby indices [$iprev]=>'$(s[iprev])', [$inext]=>'$(s[inext])'") + escnext = escape_string(s[inext:inext]) + print(io, ", valid nearby indices [$iprev]=>'$escprev', [$inext]=>'$escnext'") else - print(io, ", valid nearby index [$iprev]=>'$(s[iprev])'") + print(io, ", valid nearby index [$iprev]=>'$escprev'") end end end @@ -29,6 +31,18 @@ const ByteArray = Union{CodeUnits{UInt8,String}, Vector{UInt8},Vector{Int8}, Fas @inline between(b::T, lo::T, hi::T) where {T<:Integer} = (lo ≤ b) & (b ≤ hi) +""" + String <: AbstractString + +The default string type in Julia, used by e.g. string literals. + +`String`s are immutable sequences of `Char`s. A `String` is stored internally as +a contiguous byte array, and while they are interpreted as being UTF-8 encoded, +they can be composed of any byte sequence. Use [`isvalid`](@ref) to validate +that the underlying byte sequence is valid as UTF-8. +""" +String + ## constructors and conversions ## # String constructor docstring from boot.jl, workaround for #16730 @@ -36,9 +50,9 @@ const ByteArray = Union{CodeUnits{UInt8,String}, Vector{UInt8},Vector{Int8}, Fas """ String(v::AbstractVector{UInt8}) -Create a new `String` object from a byte vector `v` containing UTF-8 encoded -characters. If `v` is `Vector{UInt8}` it will be truncated to zero length and -future modification of `v` cannot affect the contents of the resulting string. +Create a new `String` object using the data buffer from byte vector `v`. +If `v` is a `Vector{UInt8}` it will be truncated to zero length and future +modification of `v` cannot affect the contents of the resulting string. To avoid truncation of `Vector{UInt8}` data, use `String(copy(v))`; for other `AbstractVector` types, `String(v)` already makes a copy. @@ -78,13 +92,13 @@ end """ String(s::AbstractString) -Convert a string to a contiguous byte array representation encoded as UTF-8 bytes. -This representation is often appropriate for passing strings to C. +Create a new `String` from an existing `AbstractString`. """ String(s::AbstractString) = print_to_string(s) -@pure String(s::Symbol) = unsafe_string(unsafe_convert(Ptr{UInt8}, 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)) Vector{UInt8}(s::CodeUnits{UInt8,String}) = copyto!(Vector{UInt8}(undef, length(s)), s) Vector{UInt8}(s::String) = Vector{UInt8}(codeunits(s)) @@ -100,7 +114,8 @@ pointer(s::String, i::Integer) = pointer(s) + Int(i)::Int - 1 ncodeunits(s::String) = Core.sizeof(s) codeunit(s::String) = UInt8 -@inline function codeunit(s::String, i::Integer) +codeunit(s::String, i::Integer) = codeunit(s, Int(i)) +@assume_effects :foldable @inline function codeunit(s::String, i::Int) @boundscheck checkbounds(s, i) b = GC.@preserve s unsafe_load(pointer(s, i)) return b @@ -108,20 +123,20 @@ end ## comparison ## -_memcmp(a::Union{Ptr{UInt8},AbstractString}, b::Union{Ptr{UInt8},AbstractString}, len) = +@assume_effects :total _memcmp(a::String, b::String) = @invoke _memcmp(a::Union{Ptr{UInt8},AbstractString},b::Union{Ptr{UInt8},AbstractString}) + +_memcmp(a::Union{Ptr{UInt8},AbstractString}, b::Union{Ptr{UInt8},AbstractString}) = _memcmp(a, b, min(sizeof(a), sizeof(b))) +function _memcmp(a::Union{Ptr{UInt8},AbstractString}, b::Union{Ptr{UInt8},AbstractString}, len::Int) ccall(:memcmp, Cint, (Ptr{UInt8}, Ptr{UInt8}, Csize_t), a, b, len % Csize_t) % Int +end function cmp(a::String, b::String) al, bl = sizeof(a), sizeof(b) - c = _memcmp(a, b, min(al,bl)) + c = _memcmp(a, b) return c < 0 ? -1 : c > 0 ? +1 : cmp(al,bl) end -function ==(a::String, b::String) - pointer_from_objref(a) == pointer_from_objref(b) && return true - al = sizeof(a) - return al == sizeof(b) && 0 == _memcmp(a, b, al) -end +==(a::String, b::String) = a===b typemin(::Type{String}) = "" typemin(::String) = typemin(String) @@ -177,15 +192,201 @@ end end ## checking UTF-8 & ACSII validity ## +#= + The UTF-8 Validation is performed by a shift based DFA. + ┌───────────────────────────────────────────────────────────────────┐ + │ UTF-8 DFA State Diagram ┌──────────────2──────────────┐ │ + │ ├────────3────────┐ │ │ + │ ┌──────────┐ │ ┌─┐ ┌▼┐ │ │ + │ ASCII │ UTF-8 │ ├─5──►│9├───1────► │ │ │ + │ │ │ │ ├─┤ │ │ ┌▼┐ │ + │ │ ┌─0─┐ │ ├─6──►│8├─1,7,9──►4├──1,7,9──► │ │ + │ ┌─0─┐ │ │ │ │ │ ├─┤ │ │ │ │ │ + │ │ │ │ ┌▼───┴┐ │ ├─11─►│7├──7,9───► │ ┌───────►3├─┐ │ + │ ┌▼───┴┐ │ │ │ ▼ │ └─┘ └─┘ │ │ │ │ │ + │ │ 0 ├─────┘ │ 1 ├─► ──┤ │ ┌────► │ │ │ + │ └─────┘ │ │ │ ┌─┐ │ │ └─┘ │ │ + │ └──▲──┘ ├─10─►│5├─────7──────┘ │ │ │ + │ │ │ ├─┤ │ │ │ + │ │ └─4──►│6├─────1,9───────┘ │ │ + │ INVALID │ └─┘ │ │ + │ ┌─*─┐ └──────────────────1,7,9──────────────────┘ │ + │ ┌▼───┴┐ │ + │ │ 2 ◄─── All undefined transitions result in state 2 │ + │ └─────┘ │ + └───────────────────────────────────────────────────────────────────┘ + + Validation States + 0 -> _UTF8_DFA_ASCII is the start state and will only stay in this state if the string is only ASCII characters + If the DFA ends in this state the string is ASCII only + 1 -> _UTF8_DFA_ACCEPT is the valid complete character state of the DFA once it has encountered a UTF-8 Unicode character + 2 -> _UTF8_DFA_INVALID is only reached by invalid bytes and once in this state it will not change + as seen by all 1s in that column of table below + 3 -> One valid continuation byte needed to return to state 0 + 4,5,6 -> Two valid continuation bytes needed to return to state 0 + 7,8,9 -> Three valids continuation bytes needed to return to state 0 + + Current State + 0̲ 1̲ 2̲ 3̲ 4̲ 5̲ 6̲ 7̲ 8̲ 9̲ + 0 | 0 1 2 2 2 2 2 2 2 2 + 1 | 2 2 2 1 3 2 3 2 4 4 + 2 | 3 3 2 2 2 2 2 2 2 2 + 3 | 4 4 2 2 2 2 2 2 2 2 + 4 | 6 6 2 2 2 2 2 2 2 2 + Character 5 | 9 9 2 2 2 2 2 2 2 2 <- Next State + Class 6 | 8 8 2 2 2 2 2 2 2 2 + 7 | 2 2 2 1 3 3 2 4 4 2 + 8 | 2 2 2 2 2 2 2 2 2 2 + 9 | 2 2 2 1 3 2 3 4 4 2 + 10 | 5 5 2 2 2 2 2 2 2 2 + 11 | 7 7 2 2 2 2 2 2 2 2 + + 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 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 + the current state then masking the result with 0x11110 give the shift for the new state + + +=# + +#State type used by UTF-8 DFA +const _UTF8DFAState = UInt32 +# Fill the table with 256 UInt64 representing the DFA transitions for all bytes +const _UTF8_DFA_TABLE = let # let block rather than function doesn't pollute base + num_classes=12 + num_states=10 + bit_per_state = 6 + + # These shifts were derived using a SMT solver + state_shifts = [0, 4, 10, 14, 18, 24, 8, 20, 12, 26] + + character_classes = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, + 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ] + + # These are the rows discussed in comments above + state_arrays = [ 0 1 2 2 2 2 2 2 2 2; + 2 2 2 1 3 2 3 2 4 4; + 3 3 2 2 2 2 2 2 2 2; + 4 4 2 2 2 2 2 2 2 2; + 6 6 2 2 2 2 2 2 2 2; + 9 9 2 2 2 2 2 2 2 2; + 8 8 2 2 2 2 2 2 2 2; + 2 2 2 1 3 3 2 4 4 2; + 2 2 2 2 2 2 2 2 2 2; + 2 2 2 1 3 2 3 4 4 2; + 5 5 2 2 2 2 2 2 2 2; + 7 7 2 2 2 2 2 2 2 2] + + #This converts the state_arrays into the shift encoded _UTF8DFAState + class_row = zeros(_UTF8DFAState, num_classes) + + for i = 1:num_classes + row = _UTF8DFAState(0) + for j in 1:num_states + #Calculate the shift required for the next state + to_shift = UInt8((state_shifts[state_arrays[i,j]+1]) ) + #Shift the next state into the position of the current state + row = row | (_UTF8DFAState(to_shift) << state_shifts[j]) + end + class_row[i]=row + end + + map(c->class_row[c+1],character_classes) +end + + +const _UTF8_DFA_ASCII = _UTF8DFAState(0) #This state represents the start and end of any valid string +const _UTF8_DFA_ACCEPT = _UTF8DFAState(4) #This state represents the start and end of any valid string +const _UTF8_DFA_INVALID = _UTF8DFAState(10) # If the state machine is ever in this state just stop + +# The dfa step is broken out so that it may be used in other functions. The mask was calculated to work with state shifts above +@inline _utf_dfa_step(state::_UTF8DFAState, byte::UInt8) = @inbounds (_UTF8_DFA_TABLE[byte+1] >> state) & _UTF8DFAState(0x0000001E) + +@inline function _isvalid_utf8_dfa(state::_UTF8DFAState, bytes::AbstractVector{UInt8}, first::Int = firstindex(bytes), last::Int = lastindex(bytes)) + for i = first:last + @inbounds state = _utf_dfa_step(state, bytes[i]) + end + return (state) +end -byte_string_classify(s::Union{String,Vector{UInt8},FastContiguousSubArray{UInt8,1,Vector{UInt8}}}) = - ccall(:u8_isvalid, Int32, (Ptr{UInt8}, Int), s, sizeof(s)) +@inline function _find_nonascii_chunk(chunk_size,cu::AbstractVector{CU}, first,last) where {CU} + n=first + while n <= last - chunk_size + _isascii(cu,n,n+chunk_size-1) || return n + n += chunk_size + end + n= last-chunk_size+1 + _isascii(cu,n,last) || return n + return nothing +end + +## + +# Classifcations of string # 0: neither valid ASCII nor UTF-8 # 1: valid ASCII # 2: valid UTF-8 + byte_string_classify(s::AbstractString) = byte_string_classify(codeunits(s)) + + +function byte_string_classify(bytes::AbstractVector{UInt8}) + chunk_size = 1024 + chunk_threshold = chunk_size + (chunk_size ÷ 2) + n = length(bytes) + if n > chunk_threshold + start = _find_nonascii_chunk(chunk_size,bytes,1,n) + isnothing(start) && return 1 + else + _isascii(bytes,1,n) && return 1 + start = 1 + end + return _byte_string_classify_nonascii(bytes,start,n) +end + +function _byte_string_classify_nonascii(bytes::AbstractVector{UInt8}, first::Int, last::Int) + chunk_size = 256 + + start = first + stop = min(last,first + chunk_size - 1) + state = _UTF8_DFA_ACCEPT + while start <= last + # try to process ascii chunks + while state == _UTF8_DFA_ACCEPT + _isascii(bytes,start,stop) || break + (start = start + chunk_size) <= last || break + stop = min(last,stop + chunk_size) + end + # Process non ascii chunk + state = _isvalid_utf8_dfa(state,bytes,start,stop) + state == _UTF8_DFA_INVALID && return 0 + + start = start + chunk_size + stop = min(last,stop + chunk_size) + end + return ifelse(state == _UTF8_DFA_ACCEPT,2,0) +end + +isvalid(::Type{String}, bytes::AbstractVector{UInt8}) = (@inline byte_string_classify(bytes)) ≠ 0 +isvalid(::Type{String}, s::AbstractString) = (@inline byte_string_classify(s)) ≠ 0 -isvalid(::Type{String}, s::Union{Vector{UInt8},FastContiguousSubArray{UInt8,1,Vector{UInt8}},String}) = byte_string_classify(s) ≠ 0 -isvalid(s::String) = isvalid(String, s) +@inline isvalid(s::AbstractString) = @inline isvalid(String, codeunits(s)) is_valid_continuation(c) = c & 0xc0 == 0x80 @@ -236,7 +437,7 @@ function getindex_continued(s::String, i::Int, u::UInt32) end n = ncodeunits(s) - (i += 1) > n && @goto ret + (i += 1) > n && @goto ret @inbounds b = codeunit(s, i) # cont byte 1 b & 0xc0 == 0x80 || @goto ret u |= UInt32(b) << 16 @@ -271,26 +472,28 @@ getindex(s::String, r::AbstractUnitRange{<:Integer}) = s[Int(first(r)):Int(last( return ss end -length(s::String) = length_continued(s, 1, ncodeunits(s), ncodeunits(s)) +# nothrow because we know the start and end indices are valid +@assume_effects :nothrow length(s::String) = length_continued(s, 1, ncodeunits(s), ncodeunits(s)) -@inline function length(s::String, i::Int, j::Int) +# effects needed because @inbounds +@assume_effects :consistent :effect_free @inline function length(s::String, i::Int, j::Int) @boundscheck begin 0 < i ≤ ncodeunits(s)+1 || throw(BoundsError(s, i)) - 0 ≤ j < ncodeunits(s)+1 || throw(BoundsError(s, j)) + 0 ≤ j < ncodeunits(s)+1 || throw(BoundsError(s, j)) end j < i && return 0 @inbounds i, k = thisind(s, i), i c = j - i + (i == k) - length_continued(s, i, j, c) + @inbounds length_continued(s, i, j, c) end -@inline function length_continued(s::String, i::Int, n::Int, c::Int) +@assume_effects :terminates_locally @inline @propagate_inbounds function length_continued(s::String, i::Int, n::Int, c::Int) i < n || return c - @inbounds b = codeunit(s, i) - @inbounds while true + b = codeunit(s, i) + while true while true - (i += 1) ≤ n || return c - 0xc0 ≤ b ≤ 0xf7 && break + (i += 1) ≤ n || return c + 0xc0 ≤ b ≤ 0xf7 && break b = codeunit(s, i) end l = b @@ -298,12 +501,12 @@ end c -= (x = b & 0xc0 == 0x80) x & (l ≥ 0xe0) || continue - (i += 1) ≤ n || return c + (i += 1) ≤ n || return c b = codeunit(s, i) # cont byte 2 c -= (x = b & 0xc0 == 0x80) x & (l ≥ 0xf0) || continue - (i += 1) ≤ n || return c + (i += 1) ≤ n || return c b = codeunit(s, i) # cont byte 3 c -= (b & 0xc0 == 0x80) end @@ -313,12 +516,10 @@ end isvalid(s::String, i::Int) = checkbounds(Bool, s, i) && thisind(s, i) == i -function isascii(s::String) - @inbounds for i = 1:ncodeunits(s) - codeunit(s, i) >= 0x80 && return false - end - return true -end +isascii(s::String) = isascii(codeunits(s)) + +# don't assume effects for general integers since we cannot know their implementation +@assume_effects :foldable repeat(c::Char, r::BitInteger) = @invoke repeat(c::Char, r::Integer) """ repeat(c::AbstractChar, r::Integer) -> String @@ -332,8 +533,8 @@ julia> repeat('A', 3) "AAA" ``` """ -repeat(c::AbstractChar, r::Integer) = repeat(Char(c), r) # fallback -function repeat(c::Char, r::Integer) +function repeat(c::AbstractChar, r::Integer) + c = Char(c)::Char r == 0 && return "" r < 0 && throw(ArgumentError("can't repeat a character $r times")) u = bswap(reinterpret(UInt32, c)) diff --git a/base/strings/strings.jl b/base/strings/strings.jl index 07e43674fed97..d995d8535e24b 100644 --- a/base/strings/strings.jl +++ b/base/strings/strings.jl @@ -6,5 +6,7 @@ include("strings/unicode.jl") import .Unicode: textwidth, islowercase, isuppercase, isletter, isdigit, isnumeric, iscntrl, ispunct, isspace, isprint, isxdigit, lowercase, uppercase, titlecase, lowercasefirst, uppercasefirst +import .Iterators: PartitionIterator + include("strings/util.jl") include("strings/io.jl") diff --git a/base/strings/substring.jl b/base/strings/substring.jl index 5142cf65fe9c5..5ba08ac2f7fff 100644 --- a/base/strings/substring.jl +++ b/base/strings/substring.jl @@ -7,6 +7,9 @@ Like [`getindex`](@ref), but returns a view into the parent string `s` within range `i:j` or `r` respectively instead of making a copy. +The [`@views`](@ref) macro converts any string slices `s[i:j]` into +substrings `SubString(s, i, j)` in a block of code. + # Examples ```jldoctest julia> SubString("abc", 1, 2) @@ -25,7 +28,7 @@ struct SubString{T<:AbstractString} <: AbstractString ncodeunits::Int function SubString{T}(s::T, i::Int, j::Int) where T<:AbstractString - i ≤ j || return new(s, 0, 0) + i ≤ j || return new(s, 0, 0) @boundscheck begin checkbounds(s, i:j) @inbounds isvalid(s, i) || string_index_err(s, i) @@ -52,9 +55,14 @@ SubString{T}(s::T) where {T<:AbstractString} = SubString{T}(s, 1, lastindex(s):: @propagate_inbounds maybeview(s::AbstractString, args...) = getindex(s, args...) convert(::Type{SubString{S}}, s::AbstractString) where {S<:AbstractString} = - SubString(convert(S, s)) + SubString(convert(S, s))::SubString{S} convert(::Type{T}, s::T) where {T<:SubString} = s +# Regex match allows only Union{String, SubString{String}} so define conversion to this type +convert(::Type{Union{String, SubString{String}}}, s::String) = s +convert(::Type{Union{String, SubString{String}}}, s::SubString{String}) = s +convert(::Type{Union{String, SubString{String}}}, s::AbstractString) = convert(String, s)::String + function String(s::SubString{String}) parent = s.string copy = GC.@preserve parent unsafe_string(pointer(parent, s.offset+1), s.ncodeunits) @@ -84,31 +92,24 @@ function getindex(s::SubString, i::Integer) @inbounds return getindex(s.string, s.offset + i) end +isascii(ss::SubString{String}) = isascii(codeunits(ss)) + function isvalid(s::SubString, i::Integer) ib = true @boundscheck ib = checkbounds(Bool, s, i) @inbounds return ib && isvalid(s.string, s.offset + i)::Bool end -byte_string_classify(s::SubString{String}) = - ccall(:u8_isvalid, Int32, (Ptr{UInt8}, Int), s, sizeof(s)) - -isvalid(::Type{String}, s::SubString{String}) = byte_string_classify(s) ≠ 0 -isvalid(s::SubString{String}) = isvalid(String, s) - thisind(s::SubString{String}, i::Int) = _thisind_str(s, i) nextind(s::SubString{String}, i::Int) = _nextind_str(s, i) function ==(a::Union{String, SubString{String}}, b::Union{String, SubString{String}}) - s = sizeof(a) - s == sizeof(b) && 0 == _memcmp(a, b, s) + sizeof(a) == sizeof(b) && _memcmp(a, b) == 0 end function cmp(a::SubString{String}, b::SubString{String}) - na = sizeof(a) - nb = sizeof(b) - c = _memcmp(a, b, min(na, nb)) - return c < 0 ? -1 : c > 0 ? +1 : cmp(na, nb) + c = _memcmp(a, b) + return c < 0 ? -1 : c > 0 ? +1 : cmp(sizeof(a), sizeof(b)) end # don't make unnecessary copies when passing substrings to C functions @@ -199,19 +200,30 @@ end return n end -@inline function __unsafe_string!(out, s::Union{String, SubString{String}}, offs::Integer) +@assume_effects :nothrow @inline function __unsafe_string!(out, s::String, offs::Integer) + n = sizeof(s) + GC.@preserve s out unsafe_copyto!(pointer(out, offs), pointer(s), n) + return n +end + +@inline function __unsafe_string!(out, s::SubString{String}, offs::Integer) n = sizeof(s) GC.@preserve s out unsafe_copyto!(pointer(out, offs), pointer(s), n) return n end -@inline function __unsafe_string!(out, s::Symbol, offs::Integer) +@assume_effects :nothrow @inline function __unsafe_string!(out, s::Symbol, offs::Integer) n = sizeof(s) GC.@preserve s out unsafe_copyto!(pointer(out, offs), unsafe_convert(Ptr{UInt8},s), n) return n end -function string(a::Union{Char, String, SubString{String}, Symbol}...) +# nothrow needed here because for v in a can't prove the indexing is inbounds. +@assume_effects :foldable :nothrow string(a::Union{Char, String, Symbol}...) = _string(a...) + +string(a::Union{Char, String, SubString{String}, Symbol}...) = _string(a...) + +function _string(a::Union{Char, String, SubString{String}, Symbol}...) n = 0 for v in a # 4 types is too many for automatic Union-splitting, so we split manually @@ -229,11 +241,21 @@ function string(a::Union{Char, String, SubString{String}, Symbol}...) out = _string_n(n) offs = 1 for v in a - offs += __unsafe_string!(out, v, offs) + if v isa Char + offs += __unsafe_string!(out, v, offs) + elseif v isa String || v isa SubString{String} + offs += __unsafe_string!(out, v, offs) + else + offs += __unsafe_string!(out, v::Symbol, offs) + end end return out end +# don't assume effects for general integers since we cannot know their implementation +# not nothrow because r<0 throws +@assume_effects :foldable repeat(s::String, r::BitInteger) = @invoke repeat(s::String, r::Integer) + function repeat(s::Union{String, SubString{String}}, r::Integer) r < 0 && throw(ArgumentError("can't repeat a string $r times")) r == 0 && return "" diff --git a/base/strings/unicode.jl b/base/strings/unicode.jl index e687d94365c4a..17c5d66c160b6 100644 --- a/base/strings/unicode.jl +++ b/base/strings/unicode.jl @@ -11,7 +11,7 @@ import Base: show, ==, hash, string, Symbol, isless, length, eltype, """ isvalid(value) -> Bool -Returns `true` if the given value is valid for its type, which currently can be either +Return `true` if the given value is valid for its type, which currently can be either `AbstractChar` or `String` or `SubString{String}`. # Examples @@ -31,7 +31,7 @@ isvalid(value) """ isvalid(T, value) -> Bool -Returns `true` if the given value is valid for that type. Types currently can +Return `true` if the given value is valid for that type. Types currently can be either `AbstractChar` or `String`. Values for `AbstractChar` can be of type `AbstractChar` or [`UInt32`](@ref). Values for `String` can be of that type, `SubString{String}`, `Vector{UInt8}`, or a contiguous subarray thereof. @@ -270,10 +270,64 @@ julia> textwidth("March") """ textwidth(s::AbstractString) = mapreduce(textwidth, +, s; init=0) +""" + lowercase(c::AbstractChar) + +Convert `c` to lowercase. + +See also [`uppercase`](@ref), [`titlecase`](@ref). + +# Examples +```jldoctest +julia> lowercase('A') +'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase) + +julia> lowercase('Ö') +'ö': Unicode U+00F6 (category Ll: Letter, lowercase) +``` +""" lowercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('A' <= c <= 'Z' ? c + 0x20 : c) : T(ccall(:utf8proc_tolower, UInt32, (UInt32,), c)) + +""" + uppercase(c::AbstractChar) + +Convert `c` to uppercase. + +See also [`lowercase`](@ref), [`titlecase`](@ref). + +# Examples +```jldoctest +julia> uppercase('a') +'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase) + +julia> uppercase('ê') +'Ê': Unicode U+00CA (category Lu: Letter, uppercase) +``` +""" uppercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : T(ccall(:utf8proc_toupper, UInt32, (UInt32,), c)) + +""" + titlecase(c::AbstractChar) + +Convert `c` to titlecase. This may differ from uppercase for digraphs, +compare the example below. + +See also [`uppercase`](@ref), [`lowercase`](@ref). + +# Examples +```jldoctest +julia> titlecase('a') +'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase) + +julia> titlecase('dž') +'Dž': Unicode U+01C5 (category Lt: Letter, titlecase) + +julia> uppercase('dž') +'DŽ': Unicode U+01C4 (category Lu: Letter, uppercase) +``` +""" titlecase(c::T) where {T<:AbstractChar} = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : T(ccall(:utf8proc_totitle, UInt32, (UInt32,), c)) @@ -367,6 +421,8 @@ end Tests whether a character is a decimal digit (0-9). +See also: [`isletter`](@ref). + # Examples ```jldoctest julia> isdigit('❤') @@ -388,6 +444,8 @@ Test whether a character is a letter. A character is classified as a letter if it belongs to the Unicode general category Letter, i.e. a character whose category code begins with 'L'. +See also: [`isdigit`](@ref). + # Examples ```jldoctest julia> isletter('❤') @@ -410,7 +468,7 @@ A character is classified as numeric if it belongs to the Unicode general catego i.e. a character whose category code begins with 'N'. Note that this broad category includes characters such as ¾ and ௰. -Use [`isdigit`](@ref) to check whether a character a decimal digit between 0 and 9. +Use [`isdigit`](@ref) to check whether a character is a decimal digit between 0 and 9. # Examples ```jldoctest diff --git a/base/strings/util.jl b/base/strings/util.jl index 91686f330849b..7a42d7fecfc91 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -67,6 +67,25 @@ function startswith(a::Union{String, SubString{String}}, end end +""" + startswith(io::IO, prefix::Union{AbstractString,Base.Chars}) + +Check if an `IO` object starts with a prefix. See also [`peek`](@ref). +""" +function Base.startswith(io::IO, prefix::Base.Chars) + mark(io) + c = read(io, Char) + reset(io) + return c in prefix +end +function Base.startswith(io::IO, prefix::Union{String,SubString{String}}) + mark(io) + s = read(io, ncodeunits(prefix)) + reset(io) + return s == codeunits(prefix) +end +Base.startswith(io::IO, prefix::AbstractString) = startswith(io, String(prefix)) + function endswith(a::Union{String, SubString{String}}, b::Union{String, SubString{String}}) cub = ncodeunits(b) @@ -123,12 +142,10 @@ used to implement specialized methods. # Examples ```jldoctest -julia> endswith_julia = endswith("Julia"); - -julia> endswith_julia("Julia") +julia> endswith("Julia")("Ends with Julia") true -julia> endswith_julia("JuliaLang") +julia> endswith("Julia")("JuliaLang") false ``` """ @@ -148,12 +165,10 @@ used to implement specialized methods. # Examples ```jldoctest -julia> startswith_julia = startswith("Julia"); - -julia> startswith_julia("Julia") +julia> startswith("Julia")("JuliaLang") true -julia> startswith_julia("NotJulia") +julia> startswith("Julia")("Ends with Julia") false ``` """ @@ -504,8 +519,11 @@ See also [`split`](@ref). julia> a = "Ma.rch" "Ma.rch" -julia> collect(eachsplit(a, ".")) -2-element Vector{SubString}: +julia> b = eachsplit(a, ".") +Base.SplitIterator{String, String}("Ma.rch", ".", 0, true) + +julia> collect(b) +2-element Vector{SubString{String}}: "Ma" "rch" ``` @@ -521,7 +539,8 @@ struct SplitIterator{S<:AbstractString,F} keepempty::Bool end -eltype(::Type{<:SplitIterator}) = SubString +eltype(::Type{<:SplitIterator{T}}) where T = SubString{T} +eltype(::Type{<:SplitIterator{<:SubString{T}}}) where T = SubString{T} IteratorSize(::Type{<:SplitIterator}) = SizeUnknown() @@ -533,7 +552,7 @@ function iterate(iter::SplitIterator, (i, k, n)=(firstindex(iter.str), firstinde r = findnext(iter.splitter, iter.str, k)::Union{Nothing,Int,UnitRange{Int}} while r !== nothing && n != iter.limit - 1 && first(r) <= ncodeunits(iter.str) j, k = first(r), nextind(iter.str, last(r))::Int - k_ = k <= j ? nextind(iter.str, j) : k + k_ = k <= j ? nextind(iter.str, j)::Int : k if i < k substr = @inbounds SubString(iter.str, i, prevind(iter.str, j)::Int) (iter.keepempty || i < j) && return (substr, (k, k_, n + 1)) @@ -546,6 +565,15 @@ function iterate(iter::SplitIterator, (i, k, n)=(firstindex(iter.str), firstinde @inbounds SubString(iter.str, i), (ncodeunits(iter.str) + 2, k, n + 1) end +# Specialization for partition(s,n) to return a SubString +eltype(::Type{PartitionIterator{T}}) where {T<:AbstractString} = SubString{T} + +function iterate(itr::PartitionIterator{<:AbstractString}, state = firstindex(itr.c)) + state > ncodeunits(itr.c) && return nothing + r = min(nextind(itr.c, state, itr.n - 1), lastindex(itr.c)) + return SubString(itr.c, state, r), nextind(itr.c, r) +end + eachsplit(str::T, splitter; limit::Integer=0, keepempty::Bool=true) where {T<:AbstractString} = SplitIterator(str, splitter, limit, keepempty) @@ -591,8 +619,7 @@ julia> split(a, ".") """ function split(str::T, splitter; limit::Integer=0, keepempty::Bool=true) where {T<:AbstractString} - itr = eachsplit(str, splitter; limit, keepempty) - collect(T <: SubString ? T : SubString{T}, itr) + collect(eachsplit(str, splitter; limit, keepempty)) end # a bit oddball, but standard behavior in Perl, Ruby & Python: @@ -822,7 +849,7 @@ julia> hex2bytes(a) """ function hex2bytes end -hex2bytes(s) = hex2bytes!(Vector{UInt8}(undef, length(s) >> 1), s) +hex2bytes(s) = hex2bytes!(Vector{UInt8}(undef, length(s)::Int >> 1), s) # special case - valid bytes are checked in the generic implementation function hex2bytes!(dest::AbstractArray{UInt8}, s::String) diff --git a/base/subarray.jl b/base/subarray.jl index ff2408bb48534..214a2f98afe31 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -171,10 +171,14 @@ julia> view(2:5, 2:3) # returns a range as type is immutable 3:4 ``` """ -function view(A::AbstractArray, I::Vararg{Any,N}) where {N} +function view(A::AbstractArray{<:Any,N}, I::Vararg{Any,M}) where {N,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...) end @@ -455,3 +459,5 @@ function _indices_sub(i1::AbstractArray, I...) @inline (axes(i1)..., _indices_sub(I...)...) end + +has_offset_axes(S::SubArray) = has_offset_axes(S.indices...) diff --git a/base/summarysize.jl b/base/summarysize.jl index 4baa0e0c941b1..9bbae187cab12 100644 --- a/base/summarysize.jl +++ b/base/summarysize.jl @@ -77,7 +77,7 @@ end (ss::SummarySize)(@nospecialize obj) = _summarysize(ss, obj) # define the general case separately to make sure it is not specialized for every type @noinline function _summarysize(ss::SummarySize, @nospecialize obj) - isdefined(typeof(obj), :instance) && return 0 + issingletontype(typeof(obj)) && return 0 # NOTE: this attempts to discover multiple copies of the same immutable value, # and so is somewhat approximate. key = ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), obj) @@ -170,7 +170,7 @@ function (ss::SummarySize)(obj::Module) if isa(value, UnionAll) value = unwrap_unionall(value) end - if isa(value, DataType) && value.name.module === obj && value.name.name === binding + if isa(value, DataType) && parentmodule(value) === obj && nameof(value) === binding # charge a TypeName to its module (but not to the type) size += ss(value.name)::Int end @@ -192,3 +192,5 @@ function (ss::SummarySize)(obj::Task) # TODO: add stack size, and possibly traverse stack roots return size end + +(ss::SummarySize)(obj::BigInt) = _summarysize(ss, obj) + obj.alloc*sizeof(Base.GMP.Limb) diff --git a/base/sysimg.jl b/base/sysimg.jl index 36c40e4ae748a..b0eeffa5757ba 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -5,6 +5,7 @@ Core.include(Main, "Base.jl") using .Base # Set up Main module +using Base.MainInclude # ans, err, and sometimes Out import Base.MainInclude: eval, include # Ensure this file is also tracked @@ -19,6 +20,15 @@ Base.init_load_path() if Base.is_primary_base_module # load some stdlib packages but don't put their names in Main let + # Loading here does not call __init__(). This leads to uninitialized RNG + # state which causes rand(::UnitRange{Int}) to hang. This is a workaround: + task = current_task() + task.rngState0 = 0x5156087469e170ab + task.rngState1 = 0x7431eaead385992c + task.rngState2 = 0x503e1d32781c2608 + task.rngState3 = 0x3a77f7189200c20b + task.rngState4 = 0x5502376d099035ae + # Stdlibs sorted in dependency, then alphabetical, order by contrib/print_sorted_stdlibs.jl # Run with the `--exclude-jlls` option to filter out all JLL packages stdlibs = [ @@ -38,7 +48,6 @@ let :Unicode, # 1-depth packages - :DelimitedFiles, :LinearAlgebra, :Markdown, :Printf, @@ -47,21 +56,14 @@ let # 2-depth packages :Dates, - :Distributed, :Future, :InteractiveUtils, :LibGit2, - :Profile, - :SparseArrays, :UUIDs, # 3-depth packages :REPL, - :SharedArrays, - :Statistics, - :SuiteSparse, :TOML, - :Test, # 4-depth packages :LibCURL, @@ -71,11 +73,9 @@ let # 6-depth packages :Pkg, - - # 7-depth packages - :LazyArtifacts, ] - maxlen = reduce(max, textwidth.(string.(stdlibs)); init=0) + # PackageCompiler can filter out stdlibs so it can be empty + maxlen = maximum(textwidth.(string.(stdlibs)); init=0) tot_time_stdlib = 0.0 # use a temp module to avoid leaving the type of this closure in Main @@ -114,12 +114,12 @@ let tot_time = tot_time_base + tot_time_stdlib + tot_time_userimg println("Sysimage built. Summary:") - print("Total ─────── "); Base.time_print(tot_time * 10^9); print(" \n"); - print("Base: ─────── "); Base.time_print(tot_time_base * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_base / tot_time) * 100); println("%") - print("Stdlibs: ──── "); Base.time_print(tot_time_stdlib * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_stdlib / tot_time) * 100); println("%") + print("Base ──────── "); Base.time_print(tot_time_base * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_base / tot_time) * 100); println("%") + print("Stdlibs ───── "); Base.time_print(tot_time_stdlib * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_stdlib / tot_time) * 100); println("%") if isfile("userimg.jl") - print("Userimg: ──── "); Base.time_print(tot_time_userimg * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_userimg / tot_time) * 100); println("%") + print("Userimg ───── "); Base.time_print(tot_time_userimg * 10^9); print(" "); show(IOContext(stdout, :compact=>true), (tot_time_userimg / tot_time) * 100); println("%") end + print("Total ─────── "); Base.time_print(tot_time * 10^9); println(); empty!(LOAD_PATH) empty!(DEPOT_PATH) diff --git a/base/sysinfo.jl b/base/sysinfo.jl index f0852f32fc17d..2c962088484e7 100644 --- a/base/sysinfo.jl +++ b/base/sysinfo.jl @@ -20,6 +20,8 @@ export BINDIR, loadavg, free_memory, total_memory, + free_physical_memory, + total_physical_memory, isapple, isbsd, isdragonfly, @@ -246,19 +248,45 @@ function loadavg() return loadavg_ end +""" + Sys.free_physical_memory() + +Get the free memory of the system in bytes. The entire amount may not be available to the +current process; use `Sys.free_memory()` for the actually available amount. +""" +free_physical_memory() = ccall(:uv_get_free_memory, UInt64, ()) + +""" + Sys.total_physical_memory() + +Get the total memory in RAM (including that which is currently used) in bytes. The entire +amount may not be available to the current process; see `Sys.total_memory()`. +""" +total_physical_memory() = ccall(:uv_get_total_memory, UInt64, ()) + """ Sys.free_memory() Get the total free memory in RAM in bytes. """ -free_memory() = ccall(:uv_get_free_memory, UInt64, ()) +free_memory() = ccall(:uv_get_available_memory, UInt64, ()) """ Sys.total_memory() Get the total memory in RAM (including that which is currently used) in bytes. -""" -total_memory() = ccall(:uv_get_total_memory, UInt64, ()) +This amount may be constrained, e.g., by Linux control groups. For the unconstrained +amount, see `Sys.physical_memory()`. +""" +function total_memory() + constrained = ccall(:uv_get_constrained_memory, UInt64, ()) + physical = total_physical_memory() + if 0 < constrained <= physical + return constrained + else + return physical + end +end """ Sys.get_process_title() @@ -306,7 +334,7 @@ function isunix(os::Symbol) elseif os === :Emscripten # Emscripten implements the POSIX ABI and provides traditional # Unix-style operating system functions such as file system support. - # Therefor, we consider it a unix, even though this need not be + # Therefore, we consider it a unix, even though this need not be # generally true for a jsvm embedding. return true else @@ -515,9 +543,21 @@ function which(program_name::String) for path_dir in path_dirs for pname in program_names program_path = joinpath(path_dir, pname) - # If we find something that matches our name and we can execute - if isfile(program_path) && isexecutable(program_path) - return program_path + try + # If we find something that matches our name and we can execute + if isfile(program_path) && isexecutable(program_path) + return program_path + end + catch e + # If we encounter a permission error, we skip this directory + # and continue to the next directory in the PATH variable. + if isa(e, Base.IOError) && e.code == Base.UV_EACCES + # Permission denied, continue searching + continue + else + # Rethrow the exception if it's not a permission error + rethrow(e) + end end end end diff --git a/base/task.jl b/base/task.jl index 90dea508383b1..4fbb51fde3e8e 100644 --- a/base/task.jl +++ b/base/task.jl @@ -70,7 +70,7 @@ end """ TaskFailedException -This exception is thrown by a `wait(t)` call when task `t` fails. +This exception is thrown by a [`wait(t)`](@ref) call when task `t` fails. `TaskFailedException` wraps the failed task `t`. """ struct TaskFailedException <: Exception @@ -131,7 +131,8 @@ true ``` """ macro task(ex) - :(Task(()->$(esc(ex)))) + thunk = Base.replace_linenums!(:(()->$(esc(ex))), __source__) + :(Task($thunk)) end """ @@ -251,6 +252,10 @@ true istaskfailed(t::Task) = (load_state_acquire(t) === task_state_failed) Threads.threadid(t::Task) = Int(ccall(:jl_get_task_tid, Int16, (Any,), t)+1) +function Threads.threadpool(t::Task) + tpid = ccall(:jl_get_task_threadpoolid, Int8, (Any,), t) + return Threads._tpid_to_sym(tpid) +end task_result(t::Task) = t.result @@ -313,22 +318,22 @@ end # have `waiter` wait for `t` function _wait2(t::Task, waiter::Task) if !istaskdone(t) + # since _wait2 is similar to schedule, we should observe the sticky + # bit, even if we don't call `schedule` with early-return below + if waiter.sticky && Threads.threadid(waiter) == 0 && !GC.in_finalizer() + # Issue #41324 + # t.sticky && tid == 0 is a task that needs to be co-scheduled with + # the parent task. If the parent (current_task) is not sticky we must + # set it to be sticky. + # XXX: Ideally we would be able to unset this + current_task().sticky = true + tid = Threads.threadid() + ccall(:jl_set_task_tid, Cint, (Any, Cint), waiter, tid-1) + end lock(t.donenotify) if !istaskdone(t) push!(t.donenotify.waitq, waiter) unlock(t.donenotify) - # since _wait2 is similar to schedule, we should observe the sticky - # bit, even if we aren't calling `schedule` due to this early-return - if waiter.sticky && Threads.threadid(waiter) == 0 - # Issue #41324 - # t.sticky && tid == 0 is a task that needs to be co-scheduled with - # the parent task. If the parent (current_task) is not sticky we must - # set it to be sticky. - # XXX: Ideally we would be able to unset this - current_task().sticky = true - tid = Threads.threadid() - ccall(:jl_set_task_tid, Cint, (Any, Cint), waiter, tid-1) - end return nothing else unlock(t.donenotify) @@ -347,13 +352,18 @@ function wait(t::Task) nothing end +""" + fetch(x::Any) + +Return `x`. +""" fetch(@nospecialize x) = x """ fetch(t::Task) -Wait for a Task to finish, then return its result value. -If the task fails with an exception, a `TaskFailedException` (which wraps the failed task) +Wait for a [`Task`](@ref) to finish, then return its result value. +If the task fails with an exception, a [`TaskFailedException`](@ref) (which wraps the failed task) is thrown. """ function fetch(t::Task) @@ -415,19 +425,21 @@ function sync_end(c::Channel{Any}) # Capture all waitable objects scheduled after the end of `@sync` and # include them in the exception. This way, the user can check what was # scheduled by examining at the exception object. - local racy - for r in c - if !@isdefined(racy) - racy = [] + if isready(c) + local racy + for r in c + if !@isdefined(racy) + racy = [] + end + push!(racy, r) end - push!(racy, r) - end - if @isdefined(racy) - if !@isdefined(c_ex) - c_ex = CompositeException() + if @isdefined(racy) + if !@isdefined(c_ex) + c_ex = CompositeException() + end + # Since this is a clear programming error, show this exception first: + pushfirst!(c_ex, ScheduledAfterSyncException(racy)) end - # Since this is a clear programming error, show this exception first: - pushfirst!(c_ex, ScheduledAfterSyncException(racy)) end if @isdefined(c_ex) @@ -441,9 +453,22 @@ const sync_varname = gensym(:sync) """ @sync -Wait until all lexically-enclosed uses of `@async`, `@spawn`, `@spawnat` and `@distributed` +Wait until all lexically-enclosed uses of [`@async`](@ref), [`@spawn`](@ref Threads.@spawn), `@spawnat` and `@distributed` are complete. All exceptions thrown by enclosed async operations are collected and thrown as -a `CompositeException`. +a [`CompositeException`](@ref). + +# Examples +```julia-repl +julia> Threads.nthreads() +4 + +julia> @sync begin + Threads.@spawn println("Thread-id \$(Threads.threadid()), task 1") + Threads.@spawn println("Thread-id \$(Threads.threadid()), task 2") + end; +Thread-id 3, task 1 +Thread-id 1, task 2 +``` """ macro sync(block) var = esc(sync_varname) @@ -467,19 +492,33 @@ Values can be interpolated into `@async` via `\$`, which copies the value direct constructed underlying closure. This allows you to insert the _value_ of a variable, isolating the asynchronous code from changes to the variable's value in the current task. +!!! warning + It is strongly encouraged to favor `Threads.@spawn` over `@async` always **even when no + parallelism is required** especially in publicly distributed libraries. This is + because a use of `@async` disables the migration of the *parent* task across worker + threads in the current implementation of Julia. Thus, seemingly innocent use of + `@async` in a library function can have a large impact on the performance of very + different parts of user applications. + !!! compat "Julia 1.4" Interpolating values via `\$` is available as of Julia 1.4. """ macro async(expr) + do_async_macro(expr, __source__) +end + +# generate the code for @async, possibly wrapping the task in something before +# pushing it to the wait queue. +function do_async_macro(expr, linenums; wrap=identity) letargs = Base._lift_one_interp!(expr) - thunk = esc(:(()->($expr))) + thunk = Base.replace_linenums!(:(()->($(esc(expr)))), linenums) var = esc(sync_varname) quote let $(letargs...) local task = Task($thunk) if $(Expr(:islocal, var)) - put!($var, task) + put!($var, $(wrap(:task))) end schedule(task) task @@ -487,10 +526,47 @@ macro async(expr) end end +# task wrapper that doesn't create exceptions wrapped in TaskFailedException +struct UnwrapTaskFailedException <: Exception + task::Task +end + +# common code for wait&fetch for UnwrapTaskFailedException +function unwrap_task_failed(f::Function, t::UnwrapTaskFailedException) + try + f(t.task) + catch ex + if ex isa TaskFailedException + throw(ex.task.exception) + else + rethrow() + end + end +end + +# the unwrapping for above task wrapper (gets triggered in sync_end()) +wait(t::UnwrapTaskFailedException) = unwrap_task_failed(wait, t) + +# same for fetching the tasks, for convenience +fetch(t::UnwrapTaskFailedException) = unwrap_task_failed(fetch, t) + +# macro for running async code that doesn't throw wrapped exceptions +macro async_unwrap(expr) + do_async_macro(expr, __source__, wrap=task->:(Base.UnwrapTaskFailedException($task))) +end + """ errormonitor(t::Task) Print an error log to `stderr` if task `t` fails. + +# Examples +```julia-repl +julia> Base._wait(errormonitor(Threads.@spawn error("task failed"))) +Unhandled Task ERROR: task failed +Stacktrace: +[...] +``` """ function errormonitor(t::Task) t2 = Task() do @@ -613,14 +689,14 @@ end ## scheduler and work queue -struct InvasiveLinkedListSynchronized{T} - queue::InvasiveLinkedList{T} +struct IntrusiveLinkedListSynchronized{T} + queue::IntrusiveLinkedList{T} lock::Threads.SpinLock - InvasiveLinkedListSynchronized{T}() where {T} = new(InvasiveLinkedList{T}(), Threads.SpinLock()) + IntrusiveLinkedListSynchronized{T}() where {T} = new(IntrusiveLinkedList{T}(), Threads.SpinLock()) end -isempty(W::InvasiveLinkedListSynchronized) = isempty(W.queue) -length(W::InvasiveLinkedListSynchronized) = length(W.queue) -function push!(W::InvasiveLinkedListSynchronized{T}, t::T) where T +isempty(W::IntrusiveLinkedListSynchronized) = isempty(W.queue) +length(W::IntrusiveLinkedListSynchronized) = length(W.queue) +function push!(W::IntrusiveLinkedListSynchronized{T}, t::T) where T lock(W.lock) try push!(W.queue, t) @@ -629,7 +705,7 @@ function push!(W::InvasiveLinkedListSynchronized{T}, t::T) where T end return W end -function pushfirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T +function pushfirst!(W::IntrusiveLinkedListSynchronized{T}, t::T) where T lock(W.lock) try pushfirst!(W.queue, t) @@ -638,7 +714,7 @@ function pushfirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T end return W end -function pop!(W::InvasiveLinkedListSynchronized) +function pop!(W::IntrusiveLinkedListSynchronized) lock(W.lock) try return pop!(W.queue) @@ -646,7 +722,7 @@ function pop!(W::InvasiveLinkedListSynchronized) unlock(W.lock) end end -function popfirst!(W::InvasiveLinkedListSynchronized) +function popfirst!(W::IntrusiveLinkedListSynchronized) lock(W.lock) try return popfirst!(W.queue) @@ -654,7 +730,7 @@ function popfirst!(W::InvasiveLinkedListSynchronized) unlock(W.lock) end end -function list_deletefirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T +function list_deletefirst!(W::IntrusiveLinkedListSynchronized{T}, t::T) where T lock(W.lock) try list_deletefirst!(W.queue, t) @@ -664,48 +740,59 @@ function list_deletefirst!(W::InvasiveLinkedListSynchronized{T}, t::T) where T return W end -const StickyWorkqueue = InvasiveLinkedListSynchronized{Task} -global const Workqueues = [StickyWorkqueue()] -global const Workqueue = Workqueues[1] # default work queue is thread 1 -function __preinit_threads__() - if length(Workqueues) < Threads.nthreads() - resize!(Workqueues, Threads.nthreads()) - for i = 2:length(Workqueues) - Workqueues[i] = StickyWorkqueue() +const StickyWorkqueue = IntrusiveLinkedListSynchronized{Task} +global Workqueues::Vector{StickyWorkqueue} = [StickyWorkqueue()] +const Workqueues_lock = Threads.SpinLock() +const Workqueue = Workqueues[1] # default work queue is thread 1 // TODO: deprecate this variable + +function workqueue_for(tid::Int) + qs = Workqueues + if length(qs) >= tid && isassigned(qs, tid) + return @inbounds qs[tid] + end + # slow path to allocate it + l = Workqueues_lock + @lock l begin + qs = Workqueues + if length(qs) < tid + nt = Threads.maxthreadid() + @assert tid <= nt + global Workqueues = qs = copyto!(typeof(qs)(undef, length(qs) + nt - 1), qs) + end + if !isassigned(qs, tid) + @inbounds qs[tid] = StickyWorkqueue() end + return @inbounds qs[tid] end - nothing end function enq_work(t::Task) (t._state === task_state_runnable && t.queue === nothing) || error("schedule: Task not runnable") - tid = Threads.threadid(t) - # Note there are three reasons a Task might be put into a sticky queue - # even if t.sticky == false: - # 1. The Task's stack is currently being used by the scheduler for a certain thread. - # 2. There is only 1 thread. - # 3. The multiq is full (can be fixed by making it growable). - if t.sticky || Threads.nthreads() == 1 - if tid == 0 - # Issue #41324 - # t.sticky && tid == 0 is a task that needs to be co-scheduled with - # the parent task. If the parent (current_task) is not sticky we must - # set it to be sticky. - # XXX: Ideally we would be able to unset this - current_task().sticky = true + + # Sticky tasks go into their thread's work queue. + if t.sticky + tid = Threads.threadid(t) + if tid == 0 && !GC.in_finalizer() + # The task is not yet stuck to a thread. Stick it to the current + # thread and do the same to the parent task (the current task) so + # that the tasks are correctly co-scheduled (issue #41324). + # XXX: Ideally we would be able to unset this. tid = Threads.threadid() ccall(:jl_set_task_tid, Cint, (Any, Cint), t, tid-1) + current_task().sticky = true end - push!(Workqueues[tid], t) + push!(workqueue_for(tid), t) else - if ccall(:jl_enqueue_task, Cint, (Any,), t) != 0 - # if multiq is full, give to a random thread (TODO fix) - if tid == 0 - tid = mod(time_ns() % Int, Threads.nthreads()) + 1 - ccall(:jl_set_task_tid, Cint, (Any, Cint), t, tid-1) - end - push!(Workqueues[tid], t) + tp = Threads.threadpool(t) + if Threads.threadpoolsize(tp) == 1 + # There's only one thread in the task's assigned thread pool; + # use its work queue. + tid = (tp === :interactive) ? 1 : Threads.threadpoolsize(:interactive)+1 + ccall(:jl_set_task_tid, Cint, (Any, Cint), t, tid-1) + push!(workqueue_for(tid), t) else + # Otherwise, put the task in the multiqueue. + Partr.multiq_insert(t, t.priority) tid = 0 end end @@ -753,7 +840,7 @@ function schedule(t::Task, @nospecialize(arg); error=false) # schedule a task to be (re)started with the given value or exception t._state === task_state_runnable || Base.error("schedule: Task not runnable") if error - t.queue === nothing || Base.list_deletefirst!(t.queue, t) + t.queue === nothing || Base.list_deletefirst!(t.queue::IntrusiveLinkedList{Task}, t) setfield!(t, :result, arg) setfield!(t, :_isexception, true) else @@ -777,7 +864,7 @@ function yield() try wait() catch - ct.queue === nothing || list_deletefirst!(ct.queue, ct) + ct.queue === nothing || list_deletefirst!(ct.queue::IntrusiveLinkedList{Task}, ct) rethrow() end end @@ -848,12 +935,12 @@ end function ensure_rescheduled(othertask::Task) ct = current_task() - W = Workqueues[Threads.threadid()] + W = workqueue_for(Threads.threadid()) if ct !== othertask && othertask._state === task_state_runnable # we failed to yield to othertask # return it to the head of a queue to be retried later tid = Threads.threadid(othertask) - Wother = tid == 0 ? W : Workqueues[tid] + Wother = tid == 0 ? W : workqueue_for(tid) pushfirst!(Wother, othertask) end # if the current task was queued, @@ -864,24 +951,28 @@ function ensure_rescheduled(othertask::Task) end function trypoptask(W::StickyWorkqueue) - isempty(W) && return - t = popfirst!(W) - if t._state !== task_state_runnable - # assume this somehow got queued twice, - # probably broken now, but try discarding this switch and keep going - # can't throw here, because it's probably not the fault of the caller to wait - # and don't want to use print() here, because that may try to incur a task switch - ccall(:jl_safe_printf, Cvoid, (Ptr{UInt8}, Int32...), - "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state != :runnable\n") - return + while !isempty(W) + t = popfirst!(W) + if t._state !== task_state_runnable + # assume this somehow got queued twice, + # probably broken now, but try discarding this switch and keep going + # can't throw here, because it's probably not the fault of the caller to wait + # and don't want to use print() here, because that may try to incur a task switch + ccall(:jl_safe_printf, Cvoid, (Ptr{UInt8}, Int32...), + "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state !== :runnable\n") + continue + end + return t end - return t + return Partr.multiq_deletemin() end +checktaskempty = Partr.multiq_check_empty + @noinline function poptask(W::StickyWorkqueue) task = trypoptask(W) if !(task isa Task) - task = ccall(:jl_task_get_next, Ref{Task}, (Any, Any), trypoptask, W) + task = ccall(:jl_task_get_next, Ref{Task}, (Any, Any, Any), trypoptask, W, checktaskempty) end set_next_task(task) nothing @@ -889,7 +980,7 @@ end function wait() GC.safepoint() - W = Workqueues[Threads.threadid()] + W = workqueue_for(Threads.threadid()) poptask(W) result = try_yieldto(ensure_rescheduled) process_events() diff --git a/base/threadcall.jl b/base/threadcall.jl index f0e5f336ec0ca..45965fdbc6c65 100644 --- a/base/threadcall.jl +++ b/base/threadcall.jl @@ -9,7 +9,7 @@ const threadcall_restrictor = Semaphore(max_ccall_threads) The `@threadcall` macro is called in the same way as [`ccall`](@ref) but does the work in a different thread. This is useful when you want to call a blocking C -function without causing the main `julia` thread to become blocked. Concurrency +function without causing the current `julia` thread to become blocked. Concurrency is limited by size of the libuv thread pool, which defaults to 4 threads but can be increased by setting the `UV_THREADPOOL_SIZE` environment variable and restarting the `julia` process. diff --git a/base/threadingconstructs.jl b/base/threadingconstructs.jl index 9ed416caec2a6..d150fd3ea1af4 100644 --- a/base/threadingconstructs.jl +++ b/base/threadingconstructs.jl @@ -1,44 +1,130 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -export threadid, nthreads, @threads, @spawn +export threadid, nthreads, @threads, @spawn, + threadpool, nthreadpools """ - Threads.threadid() + Threads.threadid() -> Int -Get the ID number of the current thread of execution. The master thread has ID `1`. +Get the ID number of the current thread of execution. The master thread has +ID `1`. """ threadid() = Int(ccall(:jl_threadid, Int16, ())+1) -# Inclusive upper bound on threadid() +# lower bound on the largest threadid() """ - Threads.nthreads() + Threads.maxthreadid() -> Int -Get the number of threads available to the Julia process. This is the inclusive upper bound -on [`threadid()`](@ref). +Get a lower bound on the number of threads (across all thread pools) available +to the Julia process, with atomic-acquire semantics. The result will always be +greater than or equal to [`threadid()`](@ref) as well as `threadid(task)` for +any task you were able to observe before calling `maxthreadid`. +""" +maxthreadid() = Int(Core.Intrinsics.atomic_pointerref(cglobal(:jl_n_threads, Cint), :acquire)) + +""" + 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)`. + +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) +standard library and [`Threads.maxthreadid()`](@ref). +""" +nthreads(pool::Symbol) = threadpoolsize(pool) + +function _nthreads_in_pool(tpid::Int8) + p = unsafe_load(cglobal(:jl_n_threads_per_pool, Ptr{Cint})) + return Int(unsafe_load(p, tpid + 1)) +end + +function _tpid_to_sym(tpid::Int8) + return tpid == 0 ? :interactive : :default +end + +function _sym_to_tpid(tp::Symbol) + return tp === :interactive ? Int8(0) : Int8(1) +end + +""" + Threads.threadpool(tid = threadid()) -> Symbol + +Returns the specified thread's threadpool; either `:default` or `:interactive`. +""" +function threadpool(tid = threadid()) + tpid = ccall(:jl_threadpoolid, Int8, (Int16,), tid-1) + return _tpid_to_sym(tpid) +end + +""" + Threads.nthreadpools() -> Int + +Returns the number of threadpools currently configured. +""" +nthreadpools() = Int(unsafe_load(cglobal(:jl_n_threadpools, Cint))) + +""" + Threads.threadpoolsize(pool::Symbol = :default) -> Int + +Get the number of threads available to the default thread pool (or to the +specified thread pool). 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) standard library. """ -nthreads() = Int(unsafe_load(cglobal(:jl_n_threads, Cint))) +function threadpoolsize(pool::Symbol = :default) + if pool === :default || pool === :interactive + tpid = _sym_to_tpid(pool) + else + error("invalid threadpool specified") + end + return _nthreads_in_pool(tpid) +end + +""" + threadpooltids(pool::Symbol) + +Returns a vector of IDs of threads in the given pool. +""" +function threadpooltids(pool::Symbol) + ni = _nthreads_in_pool(Int8(0)) + if pool === :interactive + return collect(1:ni) + elseif pool === :default + return collect(ni+1:ni+_nthreads_in_pool(Int8(1))) + else + error("invalid threadpool specified") + end +end + +""" + Threads.ngcthreads() -> Int + +Returns the number of GC threads currently configured. +""" +ngcthreads() = Int(unsafe_load(cglobal(:jl_n_gcthreads, Cint))) + 1 function threading_run(fun, static) ccall(:jl_enter_threaded_region, Cvoid, ()) - n = nthreads() + n = threadpoolsize() + tid_offset = threadpoolsize(:interactive) tasks = Vector{Task}(undef, n) for i = 1:n t = Task(() -> fun(i)) # pass in tid t.sticky = static - static && ccall(:jl_set_task_tid, Cint, (Any, Cint), t, i-1) + static && ccall(:jl_set_task_tid, Cint, (Any, Cint), t, tid_offset + i-1) tasks[i] = t schedule(t) end - try - for i = 1:n - wait(tasks[i]) - end - finally - ccall(:jl_exit_threaded_region, Cvoid, ()) + for i = 1:n + Base._wait(tasks[i]) + end + ccall(:jl_exit_threaded_region, Cvoid, ()) + failed_tasks = filter(istaskfailed, tasks) + if !isempty(failed_tasks) + throw(CompositeException(map(TaskFailedException, failed_tasks))) end end @@ -48,7 +134,7 @@ function _threadsfor(iter, lbody, schedule) quote local threadsfor_fun let range = $(esc(range)) - function threadsfor_fun(tid=1; onethread=false) + function threadsfor_fun(tid = 1; onethread = false) r = range # Load into local variable lenr = length(r) # divide loop iterations among threads @@ -56,7 +142,7 @@ function _threadsfor(iter, lbody, schedule) tid = 1 len, rem = lenr, 0 else - len, rem = divrem(lenr, nthreads()) + len, rem = divrem(lenr, threadpoolsize()) end # not enough iterations for all the threads? if len == 0 @@ -99,46 +185,82 @@ end """ Threads.@threads [schedule] for ... end -A macro to parallelize a `for` loop to run with multiple threads. Splits the iteration -space among multiple tasks and runs those tasks on threads according to a scheduling -policy. -A barrier is placed at the end of the loop which waits for all tasks to finish -execution. +A macro to execute a `for` loop in parallel. The iteration space is distributed to +coarse-grained tasks. This policy can be specified by the `schedule` argument. The +execution of the loop waits for the evaluation of all iterations. + +See also: [`@spawn`](@ref Threads.@spawn) and +`pmap` in [`Distributed`](@ref man-distributed). -The `schedule` argument can be used to request a particular scheduling policy. +# Extended help -Except for `:static` scheduling, how the iterations are assigned to tasks, and how the tasks -are assigned to the worker threads is undefined. The exact assignments can be different -for each execution. The scheduling option is a hint. The loop body code (including any code -transitively called from it) must not make assumptions about the distribution of iterations -to tasks or the worker thread in which they are executed. The loop body for each iteration -must be able to make forward progress independent of other iterations and be free from data -races. As such, synchronizations across iterations may deadlock. +## Semantics + +Unless stronger guarantees are specified by the scheduling option, the loop executed by +`@threads` macro have the following semantics. + +The `@threads` macro executes the loop body in an unspecified order and potentially +concurrently. It does not specify the exact assignments of the tasks and the worker threads. +The assignments can be different for each execution. The loop body code (including any code +transitively called from it) must not make any assumptions about the distribution of +iterations to tasks or the worker thread in which they are executed. The loop body for each +iteration must be able to make forward progress independent of other iterations and be free +from data races. As such, invalid synchronizations across iterations may deadlock while +unsynchronized memory accesses may result in undefined behavior. For example, the above conditions imply that: -- The lock taken in an iteration *must* be released within the same iteration. +- A lock taken in an iteration *must* be released within the same iteration. - Communicating between iterations using blocking primitives like `Channel`s is incorrect. -- Write only to locations not shared across iterations (unless a lock or atomic operation is used). +- Write only to locations not shared across iterations (unless a lock or atomic operation is + used). +- The value of [`threadid()`](@ref Threads.threadid) may change even within a single + iteration. -Schedule options are: -- `:dynamic` (default) will schedule iterations dynamically to available worker threads, - assuming that the workload for each iteration is uniform. -- `:static` creates one task per thread and divides the iterations equally among - them, assigning each task specifically to each thread. - Specifying `:static` is an error if used from inside another `@threads` loop - or from a thread other than 1. +## Schedulers -Without the scheduler argument, the exact scheduling is unspecified and varies across Julia releases. +Without the scheduler argument, the exact scheduling is unspecified and varies across Julia +releases. Currently, `:dynamic` is used when the scheduler is not specified. !!! compat "Julia 1.5" The `schedule` argument is available as of Julia 1.5. +### `:dynamic` (default) + +`:dynamic` scheduler executes iterations dynamically to available worker threads. Current +implementation assumes that the workload for each iteration is uniform. However, this +assumption may be removed in the future. + +This scheduling option is merely a hint to the underlying execution mechanism. However, a +few properties can be expected. The number of `Task`s used by `:dynamic` scheduler is +bounded by a small constant multiple of the number of available worker threads +([`Threads.threadpoolsize()`](@ref)). Each task processes contiguous regions of the +iteration space. Thus, `@threads :dynamic for x in xs; f(x); end` is typically more +efficient than `@sync for x in xs; @spawn f(x); end` if `length(xs)` is significantly +larger than the number of the worker threads and the run-time of `f(x)` is relatively +smaller than the cost of spawning and synchronizing a task (typically less than 10 +microseconds). + !!! compat "Julia 1.8" The `:dynamic` option for the `schedule` argument is available and the default as of Julia 1.8. -For example, an illustration of the different scheduling strategies where `busywait` -is a non-yielding timed loop that runs for a number of seconds. +### `:static` + +`:static` scheduler creates one task per thread and divides the iterations equally among +them, assigning each task specifically to each thread. In particular, the value of +[`threadid()`](@ref Threads.threadid) is guaranteed to be constant within one iteration. +Specifying `:static` is an error if used from inside another `@threads` loop or from a +thread other than 1. + +!!! note + `:static` scheduling exists for supporting transition of code written before Julia 1.3. + In newly written library functions, `:static` scheduling is discouraged because the + functions using this option cannot be called from arbitrary worker threads. + +## Example + +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. ```julia-repl julia> function busywait(seconds) @@ -149,7 +271,7 @@ julia> function busywait(seconds) julia> @time begin Threads.@spawn busywait(5) - Threads.@threads :static for i in 1:Threads.nthreads() + Threads.@threads :static for i in 1:Threads.threadpoolsize() busywait(1) end end @@ -157,7 +279,7 @@ julia> @time begin julia> @time begin Threads.@spawn busywait(5) - Threads.@threads :dynamic for i in 1:Threads.nthreads() + Threads.@threads :dynamic for i in 1:Threads.threadpoolsize() busywait(1) end end @@ -166,10 +288,6 @@ julia> @time begin The `:dynamic` example takes 2 seconds since one of the non-occupied threads is able to run two of the 1-second iterations to complete the for loop. - -See also: [`@spawn`](@ref Threads.@spawn), [`nthreads()`](@ref Threads.nthreads), -[`threadid()`](@ref Threads.threadid), `pmap` in [`Distributed`](@ref man-distributed), -`BLAS.set_num_threads` in [`LinearAlgebra`](@ref man-linalg). """ macro threads(args...) na = length(args) @@ -199,36 +317,73 @@ macro threads(args...) return _threadsfor(ex.args[1], ex.args[2], sched) end +function _spawn_set_thrpool(t::Task, tp::Symbol) + tpid = _sym_to_tpid(tp) + if _nthreads_in_pool(tpid) == 0 + tpid = _sym_to_tpid(:default) + end + ccall(:jl_set_task_threadpoolid, Cint, (Any, Int8), t, tpid) + nothing +end + """ - Threads.@spawn expr + Threads.@spawn [:default|:interactive] expr -Create a [`Task`](@ref) and [`schedule`](@ref) it to run on any available thread. -The task is allocated to a thread after it becomes available. To wait for the task -to finish, call [`wait`](@ref) on the result of this macro, or call [`fetch`](@ref) to -wait and then obtain its return value. +Create a [`Task`](@ref) and [`schedule`](@ref) it to run on any available +thread in the specified threadpool (`:default` if unspecified). The task is +allocated to a thread once one becomes available. To wait for the task to +finish, call [`wait`](@ref) on the result of this macro, or call +[`fetch`](@ref) to wait and then obtain its return value. -Values can be interpolated into `@spawn` via `\$`, which copies the value directly into the -constructed underlying closure. This allows you to insert the _value_ of a variable, -isolating the asynchronous code from changes to the variable's value in the current task. +Values can be interpolated into `@spawn` via `\$`, which copies the value +directly into the constructed underlying closure. This allows you to insert +the _value_ of a variable, isolating the asynchronous code from changes to +the variable's value in the current task. !!! note - See the manual chapter on threading for important caveats. + See the manual chapter on [multi-threading](@ref man-multithreading) + for important caveats. See also the chapter on [threadpools](@ref man-threadpools). !!! compat "Julia 1.3" This macro is available as of Julia 1.3. !!! compat "Julia 1.4" Interpolating values via `\$` is available as of Julia 1.4. + +!!! compat "Julia 1.9" + A threadpool may be specified as of Julia 1.9. """ -macro spawn(expr) - letargs = Base._lift_one_interp!(expr) +macro spawn(args...) + tp = :default + na = length(args) + if na == 2 + ttype, ex = args + if ttype isa QuoteNode + ttype = ttype.value + elseif ttype isa Symbol + # TODO: allow unquoted symbols + ttype = nothing + end + if ttype === :interactive || ttype === :default + tp = ttype + else + throw(ArgumentError("unsupported threadpool in @spawn: $ttype")) + end + elseif na == 1 + ex = args[1] + else + throw(ArgumentError("wrong number of arguments in @spawn")) + end + + letargs = Base._lift_one_interp!(ex) - thunk = esc(:(()->($expr))) + thunk = Base.replace_linenums!(:(()->($(esc(ex)))), __source__) var = esc(Base.sync_varname) quote let $(letargs...) local task = Task($thunk) task.sticky = false + _spawn_set_thrpool(task, $(QuoteNode(tp))) if $(Expr(:islocal, var)) put!($var, task) end diff --git a/base/threads.jl b/base/threads.jl index 2b68c7104ee5e..2d388cc4b9f77 100644 --- a/base/threads.jl +++ b/base/threads.jl @@ -11,25 +11,4 @@ include("threadingconstructs.jl") include("atomics.jl") include("locks-mt.jl") - -""" - resize_nthreads!(A, copyvalue=A[1]) - -Resize the array `A` to length [`nthreads()`](@ref). Any new -elements that are allocated are initialized to `deepcopy(copyvalue)`, -where `copyvalue` defaults to `A[1]`. - -This is typically used to allocate per-thread variables, and -should be called in `__init__` if `A` is a global constant. -""" -function resize_nthreads!(A::AbstractVector, copyvalue=A[1]) - nthr = nthreads() - nold = length(A) - resize!(A, nthr) - for i = nold+1:nthr - A[i] = deepcopy(copyvalue) - end - return A -end - end diff --git a/base/threads_overloads.jl b/base/threads_overloads.jl index a0d4bbeda2288..ccbc7e50d227b 100644 --- a/base/threads_overloads.jl +++ b/base/threads_overloads.jl @@ -3,7 +3,7 @@ """ Threads.foreach(f, channel::Channel; schedule::Threads.AbstractSchedule=Threads.FairSchedule(), - ntasks=Threads.nthreads()) + ntasks=Threads.threadpoolsize()) Similar to `foreach(f, channel)`, but iteration over `channel` and calls to `f` are split across `ntasks` tasks spawned by `Threads.@spawn`. This function @@ -20,12 +20,27 @@ to load-balancing. This approach thus may be more suitable for fine-grained, uniform workloads, but may perform worse than `FairSchedule` in concurrence with other multithreaded workloads. +# Examples +```julia-repl +julia> n = 20 + +julia> c = Channel{Int}(ch -> foreach(i -> put!(ch, i), 1:n), 1) + +julia> d = Channel{Int}(n) do ch + f = i -> put!(ch, i^2) + Threads.foreach(f, c) + end + +julia> collect(d) +collect(d) = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400] +``` + !!! compat "Julia 1.6" This function requires Julia 1.6 or later. """ function Threads.foreach(f, channel::Channel; schedule::Threads.AbstractSchedule=Threads.FairSchedule(), - ntasks=Threads.nthreads()) + ntasks=Threads.threadpoolsize()) apply = _apply_for_schedule(schedule) stop = Threads.Atomic{Bool}(false) @sync for _ in 1:ntasks diff --git a/base/timing.jl b/base/timing.jl index c7870ac491169..3e1f3a3451149 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -12,13 +12,22 @@ struct GC_Num freecall ::Int64 total_time ::Int64 total_allocd ::Int64 # GC internal - since_sweep ::Int64 # GC internal collect ::Csize_t # GC internal pause ::Cint full_sweep ::Cint + max_pause ::Int64 + max_memory ::Int64 + time_to_safepoint ::Int64 + max_time_to_safepoint ::Int64 + total_time_to_safepoint ::Int64 + sweep_time ::Int64 + mark_time ::Int64 + total_sweep_time ::Int64 + total_mark_time ::Int64 end gc_num() = ccall(:jl_gc_num, GC_Num, ()) +reset_gc_stats() = ccall(:jl_gc_reset_stats, Cvoid, ()) # This type is to represent differences in the counters, so fields may be negative struct GC_Diff @@ -55,9 +64,21 @@ function gc_alloc_count(diff::GC_Diff) diff.malloc + diff.realloc + diff.poolalloc + diff.bigalloc end -# cumulative total time spent on compilation, in nanoseconds -cumulative_compile_time_ns_before() = ccall(:jl_cumulative_compile_time_ns_before, UInt64, ()) -cumulative_compile_time_ns_after() = ccall(:jl_cumulative_compile_time_ns_after, UInt64, ()) +# cumulative total time spent on compilation and recompilation, in nanoseconds +function cumulative_compile_time_ns() + comp = ccall(:jl_cumulative_compile_time_ns, UInt64, ()) + recomp = ccall(:jl_cumulative_recompile_time_ns, UInt64, ()) + return comp, recomp +end + +function cumulative_compile_timing(b::Bool) + if b + ccall(:jl_cumulative_compile_timing_enable, Cvoid, ()) + else + ccall(:jl_cumulative_compile_timing_disable, Cvoid, ()) + end + return +end # total time spend in garbage collection, in nanoseconds gc_time_ns() = ccall(:jl_gc_total_hrtime, UInt64, ()) @@ -82,7 +103,7 @@ Return the total amount (in bytes) allocated by the just-in-time compiler for e.g. native code and data. """ function jit_total_bytes() - return Int(ccall(:jl_jit_total_bytes, Csize_t, ())) + return ccall(:jl_jit_total_bytes, Csize_t, ()) end # print elapsed time, return expression value @@ -114,7 +135,7 @@ function format_bytes(bytes) # also used by InteractiveUtils end end -function time_print(elapsedtime, bytes=0, gctime=0, allocs=0, compile_time=0, newline=false, _lpad=true) +function time_print(elapsedtime, bytes=0, gctime=0, allocs=0, compile_time=0, recompile_time=0, newline=false, _lpad=true) timestr = Ryu.writefixed(Float64(elapsedtime/1e9), 6) str = sprint() do io _lpad && print(io, length(timestr) < 10 ? (" "^(10 - length(timestr))) : "") @@ -142,15 +163,22 @@ function time_print(elapsedtime, bytes=0, gctime=0, allocs=0, compile_time=0, ne end print(io, Ryu.writefixed(Float64(100*compile_time/elapsedtime), 2), "% compilation time") end + if recompile_time > 0 + perc = Float64(100 * recompile_time / compile_time) + # use "<1" to avoid the confusing UX of reporting 0% when it's >0% + print(io, ": ", perc < 1 ? "<1" : Ryu.writefixed(perc, 0), "% of which was recompilation") + end parens && print(io, ")") end newline ? println(str) : print(str) nothing end -function timev_print(elapsedtime, diff::GC_Diff, compile_time, _lpad) +function timev_print(elapsedtime, diff::GC_Diff, compile_times, _lpad) allocs = gc_alloc_count(diff) - time_print(elapsedtime, diff.allocd, diff.total_time, allocs, compile_time, true, _lpad) + compile_time = first(compile_times) + recompile_time = last(compile_times) + time_print(elapsedtime, diff.allocd, diff.total_time, allocs, compile_time, recompile_time, true, _lpad) padded_nonzero_print(elapsedtime, "elapsed time (ns)") padded_nonzero_print(diff.total_time, "gc time (ns)") padded_nonzero_print(diff.allocd, "bytes allocated") @@ -181,8 +209,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) or -compiling is shown as a percentage. +returning the value of the expression. Any time spent garbage collecting (gc), compiling +new code, or recompiling invalidated code is shown as a percentage. Optionally provide a description string to print before the time report. @@ -190,8 +218,8 @@ In some cases the system will look inside the `@time` expression and compile som 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 `@time @eval ...`. -See also [`@showtime`](@ref), [`@timev`](@ref), [`@timed`](@ref), [`@elapsed`](@ref), and -[`@allocated`](@ref). +See also [`@showtime`](@ref), [`@timev`](@ref), [`@timed`](@ref), [`@elapsed`](@ref), +[`@allocated`](@ref), and [`@allocations`](@ref). !!! note For more serious benchmarking, consider the `@btime` macro from the BenchmarkTools.jl @@ -201,6 +229,8 @@ See also [`@showtime`](@ref), [`@timev`](@ref), [`@timed`](@ref), [`@elapsed`](@ !!! compat "Julia 1.8" The option to add a description was introduced in Julia 1.8. + Recompilation time being shown separately from compilation time was introduced in Julia 1.8 + ```julia-repl julia> x = rand(10,10); @@ -238,16 +268,18 @@ macro time(msg, ex) Experimental.@force_compile local stats = gc_num() local elapsedtime = time_ns() - local compile_elapsedtime = cumulative_compile_time_ns_before() + cumulative_compile_timing(true) + local compile_elapsedtimes = cumulative_compile_time_ns() local val = @__tryfinally($(esc(ex)), (elapsedtime = time_ns() - elapsedtime; - compile_elapsedtime = cumulative_compile_time_ns_after() - compile_elapsedtime) + cumulative_compile_timing(false); + compile_elapsedtimes = cumulative_compile_time_ns() .- compile_elapsedtimes) ) local diff = GC_Diff(gc_num(), stats) local _msg = $(esc(msg)) local has_msg = !isnothing(_msg) has_msg && print(_msg, ": ") - time_print(elapsedtime, diff.allocd, diff.total_time, gc_alloc_count(diff), compile_elapsedtime, true, !has_msg) + time_print(elapsedtime, diff.allocd, diff.total_time, gc_alloc_count(diff), first(compile_elapsedtimes), last(compile_elapsedtimes), true, !has_msg) val end end @@ -286,8 +318,8 @@ Optionally provide a description string to print before the time report. !!! compat "Julia 1.8" The option to add a description was introduced in Julia 1.8. -See also [`@time`](@ref), [`@timed`](@ref), [`@elapsed`](@ref), and -[`@allocated`](@ref). +See also [`@time`](@ref), [`@timed`](@ref), [`@elapsed`](@ref), +[`@allocated`](@ref), and [`@allocations`](@ref). ```julia-repl julia> x = rand(10,10); @@ -320,16 +352,18 @@ macro timev(msg, ex) Experimental.@force_compile local stats = gc_num() local elapsedtime = time_ns() - local compile_elapsedtime = cumulative_compile_time_ns_before() + cumulative_compile_timing(true) + local compile_elapsedtimes = cumulative_compile_time_ns() local val = @__tryfinally($(esc(ex)), (elapsedtime = time_ns() - elapsedtime; - compile_elapsedtime = cumulative_compile_time_ns_after() - compile_elapsedtime) + cumulative_compile_timing(false); + compile_elapsedtimes = cumulative_compile_time_ns() .- compile_elapsedtimes) ) local diff = GC_Diff(gc_num(), stats) local _msg = $(esc(msg)) local has_msg = !isnothing(_msg) has_msg && print(_msg, ": ") - timev_print(elapsedtime, diff, compile_elapsedtime, !has_msg) + timev_print(elapsedtime, diff, compile_elapsedtimes, !has_msg) val end end @@ -345,7 +379,7 @@ called code before execution of the top-level expression begins. When that happe compilation time will not be counted. To include this time you can run `@elapsed @eval ...`. See also [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref), -and [`@allocated`](@ref). +[`@allocated`](@ref), and [`@allocations`](@ref). ```julia-repl julia> @elapsed sleep(0.3) @@ -376,7 +410,7 @@ end A macro to evaluate an expression, discarding the resulting value, instead returning the total number of bytes allocated during evaluation of the expression. -See also [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref), +See also [`@allocations`](@ref), [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref), and [`@elapsed`](@ref). ```julia-repl @@ -396,6 +430,33 @@ macro allocated(ex) end end +""" + @allocations + +A macro to evaluate an expression, discard the resulting value, and instead return the +total number of allocations during evaluation of the expression. + +See also [`@allocated`](@ref), [`@time`](@ref), [`@timev`](@ref), [`@timed`](@ref), +and [`@elapsed`](@ref). + +```julia-repl +julia> @allocations rand(10^6) +2 +``` + +!!! compat "Julia 1.9" + This macro was added in Julia 1.9. +""" +macro allocations(ex) + quote + Experimental.@force_compile + local stats = Base.gc_num() + $(esc(ex)) + local diff = Base.GC_Diff(Base.gc_num(), stats) + Base.gc_alloc_count(diff) + end +end + """ @timed @@ -407,8 +468,8 @@ In some cases the system will look inside the `@timed` expression and compile so 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), and -[`@allocated`](@ref). +See also [`@time`](@ref), [`@timev`](@ref), [`@elapsed`](@ref), +[`@allocated`](@ref), and [`@allocations`](@ref). ```julia-repl julia> stats = @timed rand(10^6); diff --git a/base/toml_parser.jl b/base/toml_parser.jl index 66db0e5695551..6c4ff6e2a52c0 100644 --- a/base/toml_parser.jl +++ b/base/toml_parser.jl @@ -194,6 +194,7 @@ end # Inline tables ErrExpectedCommaBetweenItemsInlineTable ErrTrailingCommaInlineTable + ErrInlineTableRedefine # Numbers ErrUnderscoreNotSurroundedByDigits @@ -202,6 +203,7 @@ end ErrLeadingDot ErrNoTrailingDigitAfterDot ErrTrailingUnderscoreNumber + ErrSignInNonBase10Number # DateTime ErrParsingDateTime @@ -226,9 +228,10 @@ const err_message = Dict( ErrEmptyBareKey => "bare key cannot be empty", ErrExpectedNewLineKeyValue => "expected newline after key value pair", ErrNewLineInString => "newline character in single quoted string", - ErrUnexpectedEndString => "string literal ened unexpectedly", + ErrUnexpectedEndString => "string literal ended unexpectedly", ErrExpectedEndOfTable => "expected end of table ']'", ErrAddKeyToInlineTable => "tried to add a new key to an inline table", + ErrInlineTableRedefine => "inline table overwrote key from other table", ErrArrayTreatedAsDictionary => "tried to add a key to an array", ErrAddArrayToStaticArray => "tried to append to a statically defined array", ErrGenericValueError => "failed to parse value", @@ -244,7 +247,8 @@ const err_message = Dict( ErrOverflowError => "overflowed when parsing integer", ErrInvalidUnicodeScalar => "invalid unicode scalar", ErrInvalidEscapeCharacter => "invalid escape character", - ErrUnexpectedEofExpectedValue => "unexpected end of file, expected a value" + ErrUnexpectedEofExpectedValue => "unexpected end of file, expected a value", + ErrSignInNonBase10Number => "number not in base 10 is not allowed to have a sign", ) for err in instances(ErrorType) @@ -326,7 +330,7 @@ function Base.showerror(io::IO, err::ParserError) str1, err1 = point_to_line(err.str::String, pos, pos, io) @static if VERSION <= v"1.6.0-DEV.121" # See https://github.com/JuliaLang/julia/issues/36015 - format_fixer = get(io, :color, false) == true ? "\e[0m" : "" + format_fixer = get(io, :color, false)::Bool == true ? "\e[0m" : "" println(io, "$format_fixer ", str1) print(io, "$format_fixer ", err1) else @@ -467,7 +471,7 @@ function parse_toplevel(l::Parser)::Err{Nothing} l.active_table = l.root @try parse_table(l) skip_ws_comment(l) - if !(peek(l) == '\n' || peek(l) == '\r' || peek(l) == EOF_CHAR) + if !(peek(l) == '\n' || peek(l) == '\r' || peek(l) == '#' || peek(l) == EOF_CHAR) eat_char(l) return ParserError(ErrExpectedNewLineKeyValue) end @@ -475,7 +479,7 @@ function parse_toplevel(l::Parser)::Err{Nothing} @try parse_entry(l, l.active_table) skip_ws_comment(l) # SPEC: "There must be a newline (or EOF) after a key/value pair." - if !(peek(l) == '\n' || peek(l) == '\r' || peek(l) == EOF_CHAR) + if !(peek(l) == '\n' || peek(l) == '\r' || peek(l) == '#' || peek(l) == EOF_CHAR) c = eat_char(l) return ParserError(ErrExpectedNewLineKeyValue) end @@ -563,6 +567,10 @@ function parse_entry(l::Parser, d)::Union{Nothing, ParserError} skip_ws(l) value = @try parse_value(l) + # Not allowed to overwrite a value with an inline dict + if value isa Dict && haskey(d, last_key_part) + return ParserError(ErrInlineTableRedefine) + end # TODO: Performance, hashing `last_key_part` again here d[last_key_part] = value return @@ -672,7 +680,7 @@ function push!!(v::Vector, el) out[1] = el return out else - if typeof(T) === Union + if T isa Union newT = Any else newT = Union{T, typeof(el)} @@ -789,9 +797,11 @@ function parse_number_or_date_start(l::Parser) set_marker!(l) sgn = 1 + parsed_sign = false if accept(l, '+') - # do nothing + parsed_sign = true elseif accept(l, '-') + parsed_sign = true sgn = -1 end if accept(l, 'i') @@ -811,14 +821,17 @@ function parse_number_or_date_start(l::Parser) if ok_end_value(peek(l)) return Int64(0) elseif accept(l, 'x') + parsed_sign && return ParserError(ErrSignInNonBase10Number) ate, contains_underscore = @try accept_batch_underscore(l, isvalid_hex) - ate && return parse_int(l, contains_underscore) + ate && return parse_hex(l, contains_underscore) elseif accept(l, 'o') + parsed_sign && return ParserError(ErrSignInNonBase10Number) ate, contains_underscore = @try accept_batch_underscore(l, isvalid_oct) - ate && return parse_int(l, contains_underscore) + ate && return parse_oct(l, contains_underscore) elseif accept(l, 'b') + parsed_sign && return ParserError(ErrSignInNonBase10Number) ate, contains_underscore = @try accept_batch_underscore(l, isvalid_binary) - ate && return parse_int(l, contains_underscore) + ate && return parse_bin(l, contains_underscore) elseif accept(l, isdigit) return parse_local_time(l) end @@ -886,15 +899,28 @@ function parse_float(l::Parser, contains_underscore)::Err{Float64} return v end -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") +for (name, T1, T2, n1, n2) in (("int", Int64, Int128, 17, 33), + ("hex", UInt64, UInt128, 18, 34), + ("oct", UInt64, UInt128, 24, 45), + ("bin", UInt64, UInt128, 66, 130), + ) + @eval function $(Symbol("parse_", name))(l::Parser, contains_underscore, base=nothing)::Err{Union{$(T1), $(T2), BigInt}} + s = take_string_or_substring(l, contains_underscore) + len = length(s) + v = try + if len ≤ $(n1) + Base.parse($(T1), s; base) + elseif $(n1) < len ≤ $(n2) + Base.parse($(T2), s; base) + else + Base.parse(BigInt, s; base) + end + 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 - return v end @@ -1165,7 +1191,7 @@ function parse_string_continue(l::Parser, multiline::Bool, quoted::Bool)::Err{St end function take_chunks(l::Parser, unescape::Bool)::String - nbytes = sum(length, l.chunks) + nbytes = sum(length, l.chunks; init=0) str = Base._string_n(nbytes) offset = 1 for chunk in l.chunks diff --git a/base/traits.jl b/base/traits.jl index 53ae14b12c61e..47ab8ddc0c7ac 100644 --- a/base/traits.jl +++ b/base/traits.jl @@ -11,7 +11,7 @@ OrderStyle(::Type{<:Real}) = Ordered() OrderStyle(::Type{<:AbstractString}) = Ordered() OrderStyle(::Type{Symbol}) = Ordered() OrderStyle(::Type{<:Any}) = Unordered() -OrderStyle(::Type{Union{}}) = Ordered() +OrderStyle(::Type{Union{}}, slurp...) = Ordered() # trait for objects that support arithmetic abstract type ArithmeticStyle end @@ -23,6 +23,7 @@ ArithmeticStyle(instance) = ArithmeticStyle(typeof(instance)) ArithmeticStyle(::Type{<:AbstractFloat}) = ArithmeticRounds() ArithmeticStyle(::Type{<:Integer}) = ArithmeticWraps() ArithmeticStyle(::Type{<:Any}) = ArithmeticUnknown() +ArithmeticStyle(::Type{Union{}}, slurp...) = ArithmeticUnknown() # trait for objects that support ranges with regular step """ @@ -58,5 +59,6 @@ ranges with an element type which is a subtype of `Integer`. abstract type RangeStepStyle end struct RangeStepRegular <: RangeStepStyle end # range with regular step struct RangeStepIrregular <: RangeStepStyle end # range with rounding error +RangeStepStyle(::Type{Union{}}, slurp...) = RangeStepIrregular() RangeStepStyle(instance) = RangeStepStyle(typeof(instance)) diff --git a/base/ttyhascolor.jl b/base/ttyhascolor.jl index 800dcc380394b..5984dba6d592e 100644 --- a/base/ttyhascolor.jl +++ b/base/ttyhascolor.jl @@ -24,4 +24,4 @@ end in(key_value::Pair{Symbol,Bool}, ::TTY) = key_value.first === :color && key_value.second === get_have_color() haskey(::TTY, key::Symbol) = key === :color getindex(::TTY, key::Symbol) = key === :color ? get_have_color() : throw(KeyError(key)) -get(::TTY, key::Symbol, default) = key === :color ? get_have_color() : default \ No newline at end of file +get(::TTY, key::Symbol, default) = key === :color ? get_have_color() : default diff --git a/base/tuple.jl b/base/tuple.jl index 3b5142d03039d..59fe2c1e531e1 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -11,6 +11,8 @@ A compact way of representing the type for a tuple of length `N` where all eleme julia> isa((1, 2, 3, 4, 5, 6), NTuple{6, Int}) true ``` + +See also [`ntuple`](@ref). """ NTuple @@ -28,6 +30,8 @@ size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(Argumen 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(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 @@ -35,7 +39,7 @@ getindex(t::Tuple, c::Colon) = t get(t::Tuple, i::Integer, default) = i in 1:length(t) ? getindex(t, i) : default get(f::Callable, t::Tuple, i::Integer) = i in 1:length(t) ? getindex(t, i) : f() -# returns new tuple; N.B.: becomes no-op if i is out-of-bounds +# returns new tuple; N.B.: becomes no-op if `i` is out-of-bounds """ setindex(c::Tuple, v, i::Integer) @@ -55,9 +59,9 @@ function setindex(x::Tuple, v, i::Integer) _setindex(v, i, x...) end -function _setindex(v, i::Integer, args...) +function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} @inline - return ntuple(j -> ifelse(j == i, v, args[j]), length(args)) + return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}()) end @@ -65,7 +69,7 @@ end function iterate(@nospecialize(t::Tuple), i::Int=1) @inline - return (1 <= i <= length(t)) ? (@inbounds t[i], i + 1) : nothing + return (1 <= i <= length(t)) ? (t[i], i + 1) : nothing end keys(@nospecialize t::Tuple) = OneTo(length(t)) @@ -108,12 +112,12 @@ if `collection` is an `AbstractString`, and an arbitrary iterator, falling back `Iterators.rest(collection[, itr_state])`, otherwise. Can be overloaded for user-defined collection types to customize the behavior of [slurping -in assignments](@ref destructuring-assignment), like `a, b... = collection`. +in assignments](@ref destructuring-assignment) in final position, like `a, b... = collection`. !!! compat "Julia 1.6" `Base.rest` requires at least Julia 1.6. -See also: [`first`](@ref first), [`Iterators.rest`](@ref). +See also: [`first`](@ref first), [`Iterators.rest`](@ref), [`Base.split_rest`](@ref). # Examples ```jldoctest @@ -136,6 +140,58 @@ rest(a::Array, i::Int=1) = a[i:end] rest(a::Core.SimpleVector, i::Int=1) = a[i:end] rest(itr, state...) = Iterators.rest(itr, state...) +""" + Base.split_rest(collection, n::Int[, itr_state]) -> (rest_but_n, last_n) + +Generic function for splitting the tail of `collection`, starting from a specific iteration +state `itr_state`. Returns a tuple of two new collections. The first one contains all +elements of the tail but the `n` last ones, which make up the second collection. + +The type of the first collection generally follows that of [`Base.rest`](@ref), except that +the fallback case is not lazy, but is collected eagerly into a vector. + +Can be overloaded for user-defined collection types to customize the behavior of [slurping +in assignments](@ref destructuring-assignment) in non-final position, like `a, b..., c = collection`. + +!!! compat "Julia 1.9" + `Base.split_rest` requires at least Julia 1.9. + +See also: [`Base.rest`](@ref). + +# Examples +```jldoctest +julia> a = [1 2; 3 4] +2×2 Matrix{Int64}: + 1 2 + 3 4 + +julia> first, state = iterate(a) +(1, 2) + +julia> first, Base.split_rest(a, 1, state) +(1, ([3, 2], [4])) +``` +""" +function split_rest end +function split_rest(itr, n::Int, state...) + if IteratorSize(itr) == IsInfinite() + throw(ArgumentError("Cannot split an infinite iterator in the middle.")) + end + return _split_rest(rest(itr, state...), n) +end +_split_rest(itr, n::Int) = _split_rest(collect(itr), n) +function _check_length_split_rest(len, n) + len < n && throw(ArgumentError( + "The iterator only contains $len elements, but at least $n were requested." + )) +end +function _split_rest(a::Union{AbstractArray, Core.SimpleVector}, n::Int) + _check_length_split_rest(length(a), n) + return a[begin:end-n], a[end-n+1:end] +end + +@eval split_rest(t::Tuple, n::Int, i=1) = ($(Expr(:meta, :aggressive_constprop)); (t[i:end-n], t[end-n+1:end])) + # Use dispatch to avoid a branch in first first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty")) first(t::Tuple) = t[1] @@ -154,24 +210,23 @@ function eltype(t::Type{<:Tuple{Vararg{E}}}) where {E} end eltype(t::Type{<:Tuple}) = _compute_eltype(t) function _tuple_unique_fieldtypes(@nospecialize t) - @_pure_meta + @_total_meta types = IdSet() t´ = unwrap_unionall(t) # Given t = Tuple{Vararg{S}} where S<:Real, the various # unwrapping/wrapping/va-handling here will return Real - if t isa Union + if t´ isa Union union!(types, _tuple_unique_fieldtypes(rewrap_unionall(t´.a, t))) union!(types, _tuple_unique_fieldtypes(rewrap_unionall(t´.b, t))) else - r = Union{} for ti in (t´::DataType).parameters - r = push!(types, rewrap_unionall(unwrapva(ti), t)) + push!(types, rewrap_unionall(unwrapva(ti), t)) end end return Core.svec(types...) end function _compute_eltype(@nospecialize t) - @_pure_meta # TODO: the compiler shouldn't need this + @_total_meta # TODO: the compiler shouldn't need this types = _tuple_unique_fieldtypes(t) return afoldl(types...) do a, b # if we've already reached Any, it can't widen any more @@ -181,6 +236,21 @@ function _compute_eltype(@nospecialize t) end end +# We'd like to be able to infer eltype(::Tuple), which needs to be able to +# look at these four methods: +# +# julia> methods(Base.eltype, Tuple{Type{<:Tuple}}) +# 4 methods for generic function "eltype" from Base: +# [1] eltype(::Type{Union{}}) +# @ abstractarray.jl:234 +# [2] eltype(::Type{Tuple{}}) +# @ tuple.jl:199 +# [3] eltype(t::Type{<:Tuple{Vararg{E}}}) where E +# @ tuple.jl:200 +# [4] eltype(t::Type{<:Tuple}) +# @ tuple.jl:209 +typeof(function eltype end).name.max_methods = UInt8(4) + # version of tail that doesn't throw on empty tuples (used in array indexing) safe_tail(t::Tuple) = tail(t) safe_tail(t::Tuple{}) = () @@ -243,6 +313,8 @@ function map(f, t::Any32) end # 2 argument function map(f, t::Tuple{}, s::Tuple{}) = () +map(f, t::Tuple, s::Tuple{}) = () +map(f, t::Tuple{}, s::Tuple) = () map(f, t::Tuple{Any,}, s::Tuple{Any,}) = (@inline; (f(t[1],s[1]),)) map(f, t::Tuple{Any,Any}, s::Tuple{Any,Any}) = (@inline; (f(t[1],s[1]), f(t[2],s[2]))) function map(f, t::Tuple, s::Tuple) @@ -250,7 +322,7 @@ function map(f, t::Tuple, s::Tuple) (f(t[1],s[1]), map(f, tail(t), tail(s))...) end function map(f, t::Any32, s::Any32) - n = length(t) + n = min(length(t), length(s)) A = Vector{Any}(undef, n) for i = 1:n A[i] = f(t[i], s[i]) @@ -261,12 +333,16 @@ end heads(ts::Tuple...) = map(t -> t[1], ts) tails(ts::Tuple...) = map(tail, ts) map(f, ::Tuple{}...) = () +anyempty(x::Tuple{}, xs...) = true +anyempty(x::Tuple, xs...) = anyempty(xs...) +anyempty() = false function map(f, t1::Tuple, t2::Tuple, ts::Tuple...) @inline + anyempty(t1, t2, ts...) && return () (f(heads(t1, t2, ts...)...), map(f, tails(t1, t2, ts...)...)...) end function map(f, t1::Any32, t2::Any32, ts::Any32...) - n = length(t1) + n = min(length(t1), length(t2), minimum(length, ts)) A = Vector{Any}(undef, n) for i = 1:n A[i] = f(t1[i], t2[i], map(t -> t[i], ts)...) @@ -274,8 +350,6 @@ function map(f, t1::Any32, t2::Any32, ts::Any32...) (A...,) end -_foldl_impl(op, init, itr::Tuple) = afoldl(op, init, itr...) - # type-stable padding fill_to_length(t::NTuple{N,Any}, val, ::Val{N}) where {N} = t fill_to_length(t::Tuple{}, val, ::Val{1}) = (val,) @@ -293,7 +367,7 @@ fill_to_length(t::Tuple{}, val, ::Val{2}) = (val, val) if nameof(@__MODULE__) === :Base function tuple_type_tail(T::Type) - @_pure_meta # TODO: this method is wrong (and not @pure) + @_foldable_meta # TODO: this method is wrong (and not :foldable) if isa(T, UnionAll) return UnionAll(T.var, tuple_type_tail(T.body)) elseif isa(T, Union) @@ -309,7 +383,7 @@ function tuple_type_tail(T::Type) end end -(::Type{T})(x::Tuple) where {T<:Tuple} = convert(T, x) # still use `convert` for tuples +(::Type{T})(x::Tuple) where {T<:Tuple} = x isa T ? x : convert(T, x) # still use `convert` for tuples Tuple(x::Ref) = tuple(getindex(x)) # faster than iterator for one element Tuple(x::Array{T,0}) where {T} = tuple(getindex(x)) @@ -327,11 +401,13 @@ function _totuple(::Type{T}, itr, s::Vararg{Any,N}) where {T,N} @inline y = iterate(itr, s...) y === nothing && _totuple_err(T) - t1 = convert(fieldtype(T, 1), y[1]) + T1 = fieldtype(T, 1) + y1 = y[1] + t1 = y1 isa T1 ? y1 : convert(T1, y1)::T1 # inference may give up in recursive calls, so annotate here to force accurate return type to be propagated rT = tuple_type_tail(T) ts = _totuple(rT, itr, y[2])::rT - return (t1, ts...) + return (t1, ts...)::T end # use iterative algorithm for long tuples @@ -352,6 +428,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}, x::Number) = (x,) # to make Tuple(x) inferable end @@ -367,10 +444,17 @@ function _findfirst_loop(f::Function, t) end findfirst(f::Function, t::Tuple) = length(t) < 32 ? _findfirst_rec(f, 1, t) : _findfirst_loop(f, t) -function findlast(f::Function, x::Tuple) +findlast(f::Function, t::Tuple) = length(t) < 32 ? _findlast_rec(f, t) : _findlast_loop(f, t) +function _findlast_rec(f::Function, x::Tuple) r = findfirst(f, reverse(x)) return isnothing(r) ? r : length(x) - r + 1 end +function _findlast_loop(f::Function, t) + for i in reverse(1:length(t)) + f(t[i]) && return i + end + return nothing +end ## filter ## @@ -474,7 +558,7 @@ isless(::Tuple, ::Tuple{}) = false """ isless(t1::Tuple, t2::Tuple) -Returns true when t1 is less than t2 in lexicographic order. +Return `true` when `t1` is less than `t2` in lexicographic order. """ function isless(t1::Tuple, t2::Tuple) a, b = t1[1], t2[1] @@ -521,34 +605,22 @@ any(x::Tuple{Bool}) = x[1] any(x::Tuple{Bool, Bool}) = x[1]|x[2] any(x::Tuple{Bool, Bool, Bool}) = x[1]|x[2]|x[3] -# equivalent to any(f, t), to be used only in bootstrap -_tuple_any(f::Function, t::Tuple) = _tuple_any(f, false, t...) -function _tuple_any(f::Function, tf::Bool, a, b...) - @inline - _tuple_any(f, tf | f(a), b...) -end -_tuple_any(f::Function, tf::Bool) = tf - - # a version of `in` esp. for NamedTuple, to make it pure, and not compiled for each tuple length -function sym_in(x::Symbol, itr::Tuple{Vararg{Symbol}}) - @nospecialize itr - @_pure_meta +function sym_in(x::Symbol, @nospecialize itr::Tuple{Vararg{Symbol}}) + @noinline + @_total_meta for y in itr y === x && return true end return false end -function in(x::Symbol, itr::Tuple{Vararg{Symbol}}) - @nospecialize itr - return sym_in(x, itr) -end +in(x::Symbol, @nospecialize itr::Tuple{Vararg{Symbol}}) = sym_in(x, itr) """ empty(x::Tuple) -Returns an empty tuple, `()`. +Return an empty tuple, `()`. """ empty(@nospecialize x::Tuple) = () diff --git a/base/twiceprecision.jl b/base/twiceprecision.jl index 8f80b2c8438a0..d91a04371230c 100644 --- a/base/twiceprecision.jl +++ b/base/twiceprecision.jl @@ -112,8 +112,8 @@ julia> Float64(hi) + Float64(lo) ``` """ function mul12(x::T, y::T) where {T<:AbstractFloat} - h = x * y - ifelse(iszero(h) | !isfinite(h), (h, h), canonicalize2(h, fma(x, y, -h))) + (h, l) = Math.two_mul(x, y) + ifelse(!isfinite(h), (h, h), (h, l)) end mul12(x::T, y::T) where {T} = (p = x * y; (p, zero(p))) mul12(x, y) = mul12(promote(x, y)...) @@ -141,6 +141,7 @@ julia> hi, lo = Base.div12(x, y) julia> Float64(hi) + Float64(lo) 1.0134170444063066 +``` """ function div12(x::T, y::T) where {T<:AbstractFloat} # We lose precision if any intermediate calculation results in a subnormal. @@ -164,7 +165,7 @@ div12(x, y) = div12(promote(x, y)...) A number with twice the precision of `T`, e.g., quad-precision if `T = Float64`. -!!! warn +!!! warning `TwicePrecision` is an internal type used to increase the precision of floating-point ranges, and not intended for external use. If you encounter them in real code, the most likely explanation is @@ -199,16 +200,14 @@ end TwicePrecision{T}(x::T) where {T} = TwicePrecision{T}(x, zero(T)) +TwicePrecision{T}(x::TwicePrecision{T}) where {T} = x + function TwicePrecision{T}(x) where {T} - xT = convert(T, x) + xT = T(x) Δx = x - xT TwicePrecision{T}(xT, T(Δx)) end -function TwicePrecision{T}(x::TwicePrecision) where {T} - TwicePrecision{T}(x.hi, x.lo) -end - TwicePrecision{T}(i::Integer) where {T<:AbstractFloat} = TwicePrecision{T}(canonicalize2(splitprec(T, i)...)...) @@ -254,7 +253,7 @@ nbitslen(::Type{T}, len, offset) where {T<:IEEEFloat} = min(cld(precision(T), 2), nbitslen(len, offset)) # The +1 here is for safety, because the precision of the significand # is 1 bit higher than the number that are explicitly stored. -nbitslen(len, offset) = len < 2 ? 0 : ceil(Int, log2(max(offset-1, len-offset))) + 1 +nbitslen(len, offset) = len < 2 ? 0 : top_set_bit(max(offset-1, len-offset) - 1) + 1 eltype(::Type{TwicePrecision{T}}) where {T} = T @@ -263,15 +262,14 @@ promote_rule(::Type{TwicePrecision{R}}, ::Type{TwicePrecision{S}}) where {R,S} = promote_rule(::Type{TwicePrecision{R}}, ::Type{S}) where {R,S<:Number} = TwicePrecision{promote_type(R,S)} -(::Type{T})(x::TwicePrecision) where {T<:Number} = T(x.hi + x.lo)::T -TwicePrecision{T}(x::Number) where {T} = TwicePrecision{T}(T(x), zero(T)) +(::Type{T})(x::TwicePrecision) where {T<:Number} = (T(x.hi) + T(x.lo))::T convert(::Type{TwicePrecision{T}}, x::TwicePrecision{T}) where {T} = x convert(::Type{TwicePrecision{T}}, x::TwicePrecision) where {T} = - TwicePrecision{T}(convert(T, x.hi), convert(T, x.lo)) + TwicePrecision{T}(convert(T, x.hi), convert(T, x.lo))::TwicePrecision{T} -convert(::Type{T}, x::TwicePrecision) where {T<:Number} = T(x) -convert(::Type{TwicePrecision{T}}, x::Number) where {T} = TwicePrecision{T}(x) +convert(::Type{T}, x::TwicePrecision) where {T<:Number} = T(x)::T +convert(::Type{TwicePrecision{T}}, x::Number) where {T} = TwicePrecision{T}(x)::TwicePrecision{T} float(x::TwicePrecision{<:AbstractFloat}) = x float(x::TwicePrecision) = TwicePrecision(float(x.hi), float(x.lo)) @@ -310,7 +308,7 @@ function *(x::TwicePrecision, v::Number) end function *(x::TwicePrecision{<:IEEEFloat}, v::Integer) v == 0 && return TwicePrecision(x.hi*v, x.lo*v) - nb = ceil(Int, log2(abs(v))) + nb = top_set_bit(abs(v)-1) u = truncbits(x.hi, nb) TwicePrecision(canonicalize2(u*v, ((x.hi-u) + x.lo)*v)...) end @@ -392,23 +390,7 @@ function floatrange(::Type{T}, start_n::Integer, step_n::Integer, len::Integer, steprangelen_hp(T, (ref_n, den), (step_n, den), nb, len, imin) end -function floatrange(a::AbstractFloat, st::AbstractFloat, len::Real, divisor::AbstractFloat) - len = len + 0 # promote with Int - T = promote_type(typeof(a), typeof(st), typeof(divisor)) - m = maxintfloat(T, Int) - if abs(a) <= m && abs(st) <= m && abs(divisor) <= m - ia, ist, idivisor = round(Int, a), round(Int, st), round(Int, divisor) - if ia == a && ist == st && idivisor == divisor - # We can return the high-precision range - return floatrange(T, ia, ist, len, idivisor) - end - end - # Fallback (misses the opportunity to set offset different from 1, - # but otherwise this is still high-precision) - steprangelen_hp(T, (a,divisor), (st,divisor), nbitslen(T, len, 1), len, oneunit(len)) -end - -function (:)(start::T, step::T, stop::T) where T<:Union{Float16,Float32,Float64} +function (:)(start::T, step::T, stop::T) where T<:IEEEFloat step == 0 && throw(ArgumentError("range step cannot be zero")) # see if the inputs have exact rational approximations (and if so, # perform all computations in terms of the rationals) @@ -453,7 +435,16 @@ end step(r::StepRangeLen{T,TwicePrecision{T},TwicePrecision{T}}) where {T<:AbstractFloat} = T(r.step) step(r::StepRangeLen{T,TwicePrecision{T},TwicePrecision{T}}) where {T} = T(r.step) -function range_start_step_length(a::T, st::T, len::Integer) where T<:Union{Float16,Float32,Float64} +range_start_step_length(a::Real, st::IEEEFloat, len::Integer) = + range_start_step_length(promote(a, st)..., len) + +range_start_step_length(a::IEEEFloat, st::Real, len::Integer) = + range_start_step_length(promote(a, st)..., len) + +range_start_step_length(a::IEEEFloat, st::IEEEFloat, len::Integer) = + range_start_step_length(promote(a, st)..., len) + +function range_start_step_length(a::T, st::T, len::Integer) where T<:IEEEFloat len = len + 0 # promote with Int start_n, start_d = rat(a) step_n, step_d = rat(st) @@ -471,6 +462,17 @@ function range_start_step_length(a::T, st::T, len::Integer) where T<:Union{Float steprangelen_hp(T, a, st, 0, len, 1) end +range_step_stop_length(step::Real, stop::IEEEFloat, len::Integer) = + range_step_stop_length(promote(step, stop)..., len) + +range_step_stop_length(step::IEEEFloat, stop::Real, len::Integer) = + range_step_stop_length(promote(step, stop)..., len) + +function range_step_stop_length(step::IEEEFloat, stop::IEEEFloat, len::Integer) + r = range_start_step_length(stop, negate(step), len) + reverse(r) +end + # This assumes that r.step has already been split so that (0:len-1)*r.step.hi is exact function unsafe_getindex(r::StepRangeLen{T,<:TwicePrecision,<:TwicePrecision}, i::Integer) where T # Very similar to _getindex_hiprec, but optimized to avoid a 2nd call to add12 diff --git a/base/util.jl b/base/util.jl index 935f357367a8e..6f424f80d13b6 100644 --- a/base/util.jl +++ b/base/util.jl @@ -22,6 +22,7 @@ const text_colors = Dict{Union{Symbol,Int},String}( :normal => "\033[0m", :default => "\033[39m", :bold => "\033[1m", + :italic => "\033[3m", :underline => "\033[4m", :blink => "\033[5m", :reverse => "\033[7m", @@ -35,6 +36,7 @@ end const disable_text_style = Dict{Symbol,String}( :bold => "\033[22m", + :italic => "\033[23m", :underline => "\033[24m", :blink => "\033[25m", :reverse => "\033[27m", @@ -47,7 +49,7 @@ const disable_text_style = Dict{Symbol,String}( # Create a docstring with an automatically generated list # of colors. let color_syms = collect(Iterators.filter(x -> !isa(x, Integer), keys(text_colors))), - formatting_syms = [:normal, :bold, :default] + formatting_syms = [:normal, :bold, :italic, :default] global const available_text_colors = cat( sort!(intersect(color_syms, formatting_syms), rev=true), sort!(setdiff( color_syms, formatting_syms)); @@ -69,7 +71,7 @@ Printing with the color `:nothing` will print the string without modifications. text_colors function with_output_color(@nospecialize(f::Function), color::Union{Int, Symbol}, io::IO, args...; - bold::Bool = false, underline::Bool = false, blink::Bool = false, + bold::Bool = false, italic::Bool = false, underline::Bool = false, blink::Bool = false, reverse::Bool = false, hidden::Bool = false) buf = IOBuffer() iscolor = get(io, :color, false)::Bool @@ -80,12 +82,14 @@ function with_output_color(@nospecialize(f::Function), color::Union{Int, Symbol} print(io, str) else bold && color === :bold && (color = :nothing) + italic && color === :italic && (color = :nothing) underline && color === :underline && (color = :nothing) blink && color === :blink && (color = :nothing) reverse && color === :reverse && (color = :nothing) hidden && color === :hidden && (color = :nothing) enable_ansi = get(text_colors, color, text_colors[:default]) * (bold ? text_colors[:bold] : "") * + (italic ? text_colors[:italic] : "") * (underline ? text_colors[:underline] : "") * (blink ? text_colors[:blink] : "") * (reverse ? text_colors[:reverse] : "") * @@ -96,6 +100,7 @@ function with_output_color(@nospecialize(f::Function), color::Union{Int, Symbol} (blink ? disable_text_style[:blink] : "") * (underline ? disable_text_style[:underline] : "") * (bold ? disable_text_style[:bold] : "") * + (italic ? disable_text_style[:italic] : "") * get(disable_text_style, color, text_colors[:default]) first = true for line in eachsplit(str, '\n') @@ -110,48 +115,63 @@ function with_output_color(@nospecialize(f::Function), color::Union{Int, Symbol} end """ - printstyled([io], xs...; bold::Bool=false, underline::Bool=false, blink::Bool=false, reverse::Bool=false, hidden::Bool=false, color::Union{Symbol,Int}=:normal) + printstyled([io], xs...; bold::Bool=false, italic::Bool=false, underline::Bool=false, blink::Bool=false, reverse::Bool=false, hidden::Bool=false, color::Union{Symbol,Int}=:normal) Print `xs` in a color specified as a symbol or integer, optionally in bold. Keyword `color` may take any of the values $(Base.available_text_colors_docstring) or an integer between 0 and 255 inclusive. Note that not all terminals support 256 colors. -Keywords `bold=true`, `underline=true`, `blink=true` are self-explanatory. +Keywords `bold=true`, `italic=true`, `underline=true`, `blink=true` are self-explanatory. Keyword `reverse=true` prints with foreground and background colors exchanged, -and `hidden=true` should be invisibe in the terminal but can still be copied. +and `hidden=true` should be invisible in the terminal but can still be copied. These properties can be used in any combination. See also [`print`](@ref), [`println`](@ref), [`show`](@ref). +!!! note + Not all terminals support italic output. Some terminals interpret italic as reverse or + blink. + !!! compat "Julia 1.7" Keywords except `color` and `bold` were added in Julia 1.7. +!!! compat "Julia 1.9" + Support for italic output was added in Julia 1.9. """ -@constprop :none printstyled(io::IO, msg...; bold::Bool=false, underline::Bool=false, blink::Bool=false, reverse::Bool=false, hidden::Bool=false, color::Union{Int,Symbol}=:normal) = - with_output_color(print, color, io, msg...; bold=bold, underline=underline, blink=blink, reverse=reverse, hidden=hidden) -@constprop :none printstyled(msg...; bold::Bool=false, underline::Bool=false, blink::Bool=false, reverse::Bool=false, hidden::Bool=false, color::Union{Int,Symbol}=:normal) = - printstyled(stdout, msg...; bold=bold, underline=underline, blink=blink, reverse=reverse, hidden=hidden, color=color) +@constprop :none printstyled(io::IO, msg...; bold::Bool=false, italic::Bool=false, underline::Bool=false, blink::Bool=false, reverse::Bool=false, hidden::Bool=false, color::Union{Int,Symbol}=:normal) = + with_output_color(print, color, io, msg...; bold=bold, italic=italic, underline=underline, blink=blink, reverse=reverse, hidden=hidden) +@constprop :none printstyled(msg...; bold::Bool=false, italic::Bool=false, underline::Bool=false, blink::Bool=false, reverse::Bool=false, hidden::Bool=false, color::Union{Int,Symbol}=:normal) = + 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())) + Base.julia_cmd(juliapath=joinpath(Sys.BINDIR, julia_exename()); cpu_target) Return a julia command similar to the one of the running process. Propagates any of the `--cpu-target`, `--sysimage`, `--compile`, `--sysimage-native-code`, -`--compiled-modules`, `--inline`, `--check-bounds`, `--optimize`, `-g`, +`--compiled-modules`, `--pkgimages`, `--inline`, `--check-bounds`, `--optimize`, `--min-optlevel`, `-g`, `--code-coverage`, `--track-allocation`, `--color`, `--startup-file`, and `--depwarn` command line arguments that are not at their default values. Among others, `--math-mode`, `--warn-overwrite`, and `--trace-compile` are notably not propagated currently. +To get the julia command without propagated command line arguments, `julia_cmd()[1]` can be used. + !!! compat "Julia 1.1" Only the `--cpu-target`, `--sysimage`, `--depwarn`, `--compile` and `--check-bounds` flags were propagated before Julia 1.1. !!! compat "Julia 1.5" The flags `--color` and `--startup-file` were added in Julia 1.5. + +!!! compat "Julia 1.9" + The keyword argument `cpu_target` was added. + + The flag `--pkgimages` was added in Julia 1.9. """ -function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename())) +function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Union{Nothing,String} = nothing) opts = JLOptions() - cpu_target = unsafe_string(opts.cpu_target) + if cpu_target === nothing + cpu_target = unsafe_string(opts.cpu_target) + end image_file = unsafe_string(opts.image_file) addflags = String[] let compile = if opts.compile_enabled == 0 @@ -196,6 +216,8 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename())) push!(addflags, "--code-coverage=user") elseif opts.code_coverage == 2 push!(addflags, "--code-coverage=all") + elseif opts.code_coverage == 3 + push!(addflags, "--code-coverage=@$(unsafe_string(opts.tracked_path))") end isempty(coverage_file) || push!(addflags, "--code-coverage=$coverage_file") end @@ -204,6 +226,8 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename())) push!(addflags, "--track-allocation=user") elseif opts.malloc_log == 2 push!(addflags, "--track-allocation=all") + elseif opts.malloc_log == 3 + push!(addflags, "--track-allocation=@$(unsafe_string(opts.tracked_path))") end if opts.color == 1 push!(addflags, "--color=yes") @@ -216,11 +240,17 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename())) 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` end function julia_exename() - if ccall(:jl_is_debugbuild, Cint, ()) == 0 + if !Base.isdebugbuild() return @static Sys.iswindows() ? "julia.exe" : "julia" else return @static Sys.iswindows() ? "julia-debug.exe" : "julia-debug" @@ -247,39 +277,90 @@ unsafe_securezero!(p::Ptr{Cvoid}, len::Integer=1) = Ptr{Cvoid}(unsafe_securezero Display a message and wait for the user to input a secret, returning an `IO` object containing the secret. -Note that on Windows, the secret might be displayed as it is typed; see -`Base.winprompt` for securely retrieving username/password pairs from a -graphical interface. +!!! info "Windows" + Note that on Windows, the secret might be displayed as it is typed; see + `Base.winprompt` for securely retrieving username/password pairs from a + graphical interface. """ function getpass end -if Sys.iswindows() +# Note, this helper only works within `with_raw_tty()` on POSIX platforms! +function _getch() + @static if Sys.iswindows() + return UInt8(ccall(:_getch, Cint, ())) + else + return read(stdin, UInt8) + end +end + +const termios_size = Int(ccall(:jl_termios_size, Cint, ())) +make_termios() = zeros(UInt8, termios_size) + +# These values seem to hold on all OSes we care about: +# glibc Linux, musl Linux, macOS, FreeBSD +@enum TCSETATTR_FLAGS TCSANOW=0 TCSADRAIN=1 TCSAFLUSH=2 + +function tcgetattr(fd::RawFD, termios) + ret = ccall(:tcgetattr, Cint, (Cint, Ptr{Cvoid}), fd, termios) + if ret != 0 + throw(IOError("tcgetattr failed", ret)) + end +end +function tcsetattr(fd::RawFD, termios, mode::TCSETATTR_FLAGS = TCSADRAIN) + ret = ccall(:tcsetattr, Cint, (Cint, Cint, Ptr{Cvoid}), fd, Cint(mode), termios) + if ret != 0 + throw(IOError("tcsetattr failed", ret)) + end +end +cfmakeraw(termios) = ccall(:cfmakeraw, Cvoid, (Ptr{Cvoid},), termios) + +function with_raw_tty(f::Function, input::TTY) + input === stdin || throw(ArgumentError("with_raw_tty only works for stdin")) + fd = RawFD(0) + + # If we're on windows, we do nothing, as we have access to `_getch()` quite easily + @static if Sys.iswindows() + return f() + end + + # Get the current terminal mode + old_termios = make_termios() + tcgetattr(fd, old_termios) + try + # Set a new, raw, terminal mode + new_termios = copy(old_termios) + cfmakeraw(new_termios) + tcsetattr(fd, new_termios) + + # Call the user-supplied callback + f() + finally + # Always restore the terminal mode + tcsetattr(fd, old_termios) + end +end + function getpass(input::TTY, output::IO, prompt::AbstractString) input === stdin || throw(ArgumentError("getpass only works for stdin")) - print(output, prompt, ": ") - flush(output) - s = SecretBuffer() - plen = 0 - while true - c = UInt8(ccall(:_getch, Cint, ())) - if c == 0xff || c == UInt8('\n') || c == UInt8('\r') - break # EOF or return - elseif c == 0x00 || c == 0xe0 - ccall(:_getch, Cint, ()) # ignore function/arrow keys - elseif c == UInt8('\b') && plen > 0 - plen -= 1 # delete last character on backspace - elseif !iscntrl(Char(c)) && plen < 128 - write(s, c) + with_raw_tty(stdin) do + print(output, prompt, ": ") + flush(output) + s = SecretBuffer() + plen = 0 + while true + c = _getch() + if c == 0xff || c == UInt8('\n') || c == UInt8('\r') || c == 0x04 + break # EOF or return + elseif c == 0x00 || c == 0xe0 + _getch() # ignore function/arrow keys + elseif c == UInt8('\b') && plen > 0 + plen -= 1 # delete last character on backspace + elseif !iscntrl(Char(c)) && plen < 128 + write(s, c) + end end + return seekstart(s) end - return seekstart(s) -end -else -function getpass(input::TTY, output::IO, prompt::AbstractString) - (input === stdin && output === stdout) || throw(ArgumentError("getpass only works for stdin")) - msg = string(prompt, ": ") - unsafe_SecretBuffer!(ccall(:getpass, Cstring, (Cstring,), msg)) -end end # allow new getpass methods to be defined if stdin has been @@ -293,7 +374,7 @@ Displays the `message` then waits for user input. Input is terminated when a new is encountered or EOF (^D) character is entered on a blank line. If a `default` is provided then the user can enter just a newline character to select the `default`. -See also `Base.getpass` and `Base.winprompt` for secure entry of passwords. +See also `Base.winprompt` (for Windows) and `Base.getpass` for secure entry of passwords. # Example @@ -414,7 +495,9 @@ _crc32c(a::NTuple{<:Any, UInt8}, crc::UInt32=0x00000000) = _crc32c(a::Union{Array{UInt8},FastContiguousSubArray{UInt8,N,<:Array{UInt8}} where N}, crc::UInt32=0x00000000) = unsafe_crc32c(a, length(a) % Csize_t, crc) -_crc32c(s::String, crc::UInt32=0x00000000) = unsafe_crc32c(s, sizeof(s) % Csize_t, crc) +function _crc32c(s::Union{String, SubString{String}}, crc::UInt32=0x00000000) + unsafe_crc32c(s, sizeof(s) % Csize_t, crc) +end function _crc32c(io::IO, nb::Integer, crc::UInt32=0x00000000) nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0, got $nb")) @@ -430,10 +513,17 @@ function _crc32c(io::IO, nb::Integer, crc::UInt32=0x00000000) end _crc32c(io::IO, crc::UInt32=0x00000000) = _crc32c(io, typemax(Int64), crc) _crc32c(io::IOStream, crc::UInt32=0x00000000) = _crc32c(io, filesize(io)-position(io), crc) -_crc32c(uuid::UUID, crc::UInt32=0x00000000) = - ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt128}, Csize_t), crc, uuid.value, 16) +_crc32c(uuid::UUID, crc::UInt32=0x00000000) = _crc32c(uuid.value, crc) +_crc32c(x::UInt128, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt128}, Csize_t), crc, x, 16) _crc32c(x::UInt64, crc::UInt32=0x00000000) = ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt64}, Csize_t), crc, x, 8) +_crc32c(x::UInt32, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt32}, Csize_t), crc, x, 4) +_crc32c(x::UInt16, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt16}, Csize_t), crc, x, 2) +_crc32c(x::UInt8, crc::UInt32=0x00000000) = + ccall(:jl_crc32c, UInt32, (UInt32, Ref{UInt8}, Csize_t), crc, x, 1) """ @kwdef typedef @@ -452,9 +542,12 @@ order to function correctly with the keyword outer constructor. `Base.@kwdef` for parametric structs, and structs with supertypes requires at least Julia 1.1. +!!! compat "Julia 1.9" + This macro is exported as of Julia 1.9. + # Examples ```jldoctest -julia> Base.@kwdef struct Foo +julia> @kwdef struct Foo a::Int = 1 # specified default b::String # required keyword end @@ -464,15 +557,14 @@ julia> Foo(b="hi") Foo(1, "hi") julia> Foo() -ERROR: UndefKeywordError: keyword argument b not assigned +ERROR: UndefKeywordError: keyword argument `b` not assigned Stacktrace: [...] ``` """ macro kwdef(expr) expr = macroexpand(__module__, expr) # to expand @static - expr isa Expr && expr.head === :struct || error("Invalid usage of @kwdef") - expr = expr::Expr + isexpr(expr, :struct) || error("Invalid usage of @kwdef") T = expr.args[2] if T isa Expr && T.head === :<: T = T.args[1] @@ -486,29 +578,33 @@ macro kwdef(expr) # overflow on construction if !isempty(params_ex.args) if T isa Symbol - kwdefs = :(($(esc(T)))($params_ex) = ($(esc(T)))($(call_args...))) - elseif T isa Expr && T.head === :curly - T = T::Expr + sig = :(($(esc(T)))($params_ex)) + call = :(($(esc(T)))($(call_args...))) + body = Expr(:block, __source__, call) + kwdefs = Expr(:function, sig, body) + elseif isexpr(T, :curly) # if T == S{A<:AA,B<:BB}, define two methods # S(...) = ... # S{A,B}(...) where {A<:AA,B<:BB} = ... S = T.args[1] P = T.args[2:end] - Q = Any[U isa Expr && U.head === :<: ? U.args[1] : U for U in P] + Q = Any[isexpr(U, :<:) ? U.args[1] : U for U in P] SQ = :($S{$(Q...)}) - kwdefs = quote - ($(esc(S)))($params_ex) =($(esc(S)))($(call_args...)) - ($(esc(SQ)))($params_ex) where {$(esc.(P)...)} = - ($(esc(SQ)))($(call_args...)) - end + body1 = Expr(:block, __source__, :(($(esc(S)))($(call_args...)))) + sig1 = :(($(esc(S)))($params_ex)) + def1 = Expr(:function, sig1, body1) + body2 = Expr(:block, __source__, :(($(esc(SQ)))($(call_args...)))) + sig2 = :(($(esc(SQ)))($params_ex) where {$(esc.(P)...)}) + def2 = Expr(:function, sig2, body2) + kwdefs = Expr(:block, def1, def2) else error("Invalid usage of @kwdef") end else kwdefs = nothing end - quote - Base.@__doc__($(esc(expr))) + return quote + Base.@__doc__ $(esc(expr)) $kwdefs end end @@ -523,7 +619,16 @@ function _kwdef!(blk, params_args, call_args) push!(params_args, ei) push!(call_args, ei) elseif ei isa Expr - if ei.head === :(=) + 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 @@ -539,7 +644,9 @@ function _kwdef!(blk, params_args, call_args) defexpr = ei.args[2] # defexpr push!(params_args, Expr(:kw, var, esc(defexpr))) push!(call_args, var) - blk.args[i] = lhs + 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] @@ -581,7 +688,8 @@ function runtests(tests = ["all"]; ncores::Int = ceil(Int, Sys.CPU_THREADS / 2), seed !== nothing && push!(tests, "--seed=0x$(string(seed % UInt128, base=16))") # cast to UInt128 to avoid a minus sign ENV2 = copy(ENV) ENV2["JULIA_CPU_THREADS"] = "$ncores" - ENV2["JULIA_DEPOT_PATH"] = mktempdir(; cleanup = true) + 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") delete!(ENV2, "JULIA_PROJECT") try @@ -597,3 +705,12 @@ function runtests(tests = ["all"]; ncores::Int = ceil(Int, Sys.CPU_THREADS / 2), "including error messages above and the output of versioninfo():\n$(read(buf, String))") end end + +""" + isdebugbuild() + +Return `true` if julia is a debug version. +""" +function isdebugbuild() + return ccall(:jl_is_debugbuild, Cint, ()) != 0 +end diff --git a/base/version.jl b/base/version.jl index 30bfb55791b36..67377c86a8493 100644 --- a/base/version.jl +++ b/base/version.jl @@ -80,6 +80,7 @@ VersionNumber(major::Integer, minor::Integer = 0, patch::Integer = 0, map(x->x isa Integer ? UInt64(x) : String(x), bld)) VersionNumber(v::Tuple) = VersionNumber(v...) +VersionNumber(v::VersionNumber) = v function print(io::IO, v::VersionNumber) v == typemax(VersionNumber) && return print(io, "∞") @@ -288,7 +289,7 @@ function banner(io::IO = stdout) commit_date = isempty(Base.GIT_VERSION_INFO.date_string) ? "" : " ($(split(Base.GIT_VERSION_INFO.date_string)[1]))" - if get(io, :color, false) + if get(io, :color, false)::Bool c = text_colors tx = c[:normal] # text jl = c[:normal] # julia diff --git a/base/version_git.sh b/base/version_git.sh index 2a3352d1066ef..76092e9800594 100644 --- a/base/version_git.sh +++ b/base/version_git.sh @@ -3,7 +3,7 @@ # This file collects git info and create a julia file with the GIT_VERSION_INFO struct -echo "# This file was autogenerated in base/version_git.sh" +echo "# This file was autogenerated by base/version_git.sh" echo "struct GitVersionInfo" echo " commit::String" echo " commit_short::String" @@ -41,7 +41,15 @@ if [ -n "$(git status --porcelain)" ]; then # append dirty mark '*' if the repository has uncommitted changes commit_short="$commit_short"* fi -branch=$(git rev-parse --abbrev-ref HEAD) + +# Our CI system checks commits out as a detached head, and so we must +# use the provided branch name, as we cannot autodetect this commit as +# the tip of any such branch. +if [ -n "${BUILDKITE_BRANCH}" ]; then + branch="${BUILDKITE_BRANCH}" +else + branch=$(git rev-parse --abbrev-ref HEAD) +fi topdir=$(git rev-parse --show-toplevel) verchanged=$(git blame -L ,1 -sl -- "$topdir/VERSION" | cut -f 1 -d " ") @@ -52,17 +60,21 @@ else build_number=$(git rev-list --count HEAD "^$verchanged") fi -date_string=$git_time case $(uname) in Darwin | FreeBSD) - date_string="$(/bin/date -jr $git_time -u '+%Y-%m-%d %H:%M %Z')" + if (date --version 2>/dev/null | grep -q 'GNU coreutils') + then # GNU date installed and earlier on PATH than BSD date + date_string="$(date --date="@$git_time" -u '+%Y-%m-%d %H:%M %Z')" + else # otherwise assume BSD date + date_string="$(date -jr $git_time -u '+%Y-%m-%d %H:%M %Z')" + fi ;; MINGW*) git_time=$(git log -1 --pretty=format:%ci) - date_string="$(/bin/date --date="$git_time" -u '+%Y-%m-%d %H:%M %Z')" + date_string="$(date --date="$git_time" -u '+%Y-%m-%d %H:%M %Z')" ;; *) - date_string="$(/bin/date --date="@$git_time" -u '+%Y-%m-%d %H:%M %Z')" + date_string="$(date --date="@$git_time" -u '+%Y-%m-%d %H:%M %Z')" ;; esac if [ $(git describe --tags --exact-match 2> /dev/null) ]; then diff --git a/base/views.jl b/base/views.jl index e26359a5c9fd7..70d4c1d9110ee 100644 --- a/base/views.jl +++ b/base/views.jl @@ -128,7 +128,9 @@ macro view(ex) if Meta.isexpr(ex, :ref) ex = Expr(:call, view, ex.args...) else # ex replaced by let ...; foo[...]; end - @assert Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[2], :ref) + 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)) @@ -214,6 +216,8 @@ to return a view. Scalar indices, non-array types, and explicit [`getindex`](@ref) calls (as opposed to `array[...]`) are unaffected. +Similarly, `@views` converts string slices into [`SubString`](@ref) views. + !!! note The `@views` macro only affects `array[...]` expressions that appear explicitly in the given `expression`, not array slicing that diff --git a/base/weakkeydict.jl b/base/weakkeydict.jl index 0a9987671ea9b..328f368c80b71 100644 --- a/base/weakkeydict.jl +++ b/base/weakkeydict.jl @@ -12,6 +12,8 @@ referenced in a hash table. See [`Dict`](@ref) for further help. Note, unlike [`Dict`](@ref), `WeakKeyDict` does not convert keys on insertion, as this would imply the key object was unreferenced anywhere before insertion. + +See also [`WeakRef`](@ref). """ mutable struct WeakKeyDict{K,V} <: AbstractDict{K,V} ht::Dict{WeakRef,V} @@ -21,7 +23,7 @@ mutable struct WeakKeyDict{K,V} <: AbstractDict{K,V} # Constructors mirror Dict's function WeakKeyDict{K,V}() where V where K - t = new(Dict{Any,V}(), ReentrantLock(), identity, 0) + t = new(Dict{WeakRef,V}(), ReentrantLock(), identity, 0) t.finalizer = k -> t.dirty = true return t end diff --git a/cli/Makefile b/cli/Makefile index 11855ee6244dc..c2e2bcd568a07 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -1,7 +1,6 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) JULIAHOME := $(abspath $(SRCDIR)/..) BUILDDIR ?= . -include $(JULIAHOME)/deps/Versions.make include $(JULIAHOME)/Make.inc include $(JULIAHOME)/deps/llvm-ver.make @@ -13,6 +12,8 @@ LOADER_LDFLAGS = $(JLDFLAGS) -ffreestanding -L$(build_shlibdir) -L$(build_libdir ifeq ($(OS),WINNT) LOADER_CFLAGS += -municode -mconsole -nostdlib -fno-stack-check -fno-stack-protector -mno-stack-arg-probe +else ifeq ($(OS),Linux) +LOADER_CFLAGS += -DGLIBCXX_LEAST_VERSION_SYMBOL=\"$(shell echo "$(CSL_NEXT_GLIBCXX_VERSION)" | cut -d'|' -f1 | sed 's/\\//g')\" endif ifeq ($(OS),WINNT) @@ -29,6 +30,14 @@ endif # Build list of dependent libraries that must be opened SHIPFLAGS += -DDEP_LIBS="\"$(LOADER_BUILD_DEP_LIBS)\"" DEBUGFLAGS += -DDEP_LIBS="\"$(LOADER_DEBUG_BUILD_DEP_LIBS)\"" +ifneq (,$(findstring MINGW,$(shell uname))) +# In MSYS2, do not perform path conversion for `DEP_LIBS`. +# https://www.msys2.org/wiki/Porting/#filesystem-namespaces +# We define this environment variable for only these two object files, +# as they're the only ones that require it at the time of writing. +$(BUILDDIR)/loader_lib.o: export MSYS2_ARG_CONV_EXCL = -DDEP_LIBS= +$(BUILDDIR)/loader_lib.dbg.obj: export MSYS2_ARG_CONV_EXCL = -DDEP_LIBS= +endif # MSYS2 EXE_OBJS := $(BUILDDIR)/loader_exe.o EXE_DOBJS := $(BUILDDIR)/loader_exe.dbg.obj @@ -46,9 +55,9 @@ all: release debug release debug : % : julia-% libjulia-% $(BUILDDIR)/loader_lib.o : $(SRCDIR)/loader_lib.c $(HEADERS) $(JULIAHOME)/VERSION - @$(call PRINT_CC, $(CC) -DLIBRARY_EXPORTS $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@) + @$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@) $(BUILDDIR)/loader_lib.dbg.obj : $(SRCDIR)/loader_lib.c $(HEADERS) $(JULIAHOME)/VERSION - @$(call PRINT_CC, $(CC) -DLIBRARY_EXPORTS $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@) + @$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@) $(BUILDDIR)/loader_exe.o : $(SRCDIR)/loader_exe.c $(HEADERS) $(JULIAHOME)/VERSION @$(call PRINT_CC, $(CC) $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@) $(BUILDDIR)/loader_exe.dbg.obj : $(SRCDIR)/loader_exe.c $(HEADERS) $(JULIAHOME)/VERSION @@ -61,8 +70,7 @@ dump-trampolines: $(SRCDIR)/trampolines/trampolines_$(ARCH).S $(CC) $(SHIPFLAGS) $(LOADER_CFLAGS) $< -S | sed -E 's/ ((%%)|;) /\n/g' | sed -E 's/.global/\n.global/g' DIRS = $(build_bindir) $(build_libdir) -$(DIRS): - @mkdir -p $@ +$(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) ifeq ($(OS),WINNT) $(BUILDDIR)/julia_res.o: $(JULIAHOME)/contrib/windows/julia.rc $(JULIAHOME)/VERSION @@ -102,9 +110,10 @@ 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 | $(build_shlibdir) $(build_libdir) - @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \ - $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT))) - @$(INSTALL_NAME_CMD)libjulia.$(SHLIB_EXT) $@ + @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \ + $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT))) + @$(INSTALL_NAME_CMD)libjulia.$(JL_MAJOR_SHLIB_EXT) $@ + @$(DSYMUTIL) $@ ifeq ($(OS), WINNT) @# Note that if the objcopy command starts getting too long, we can use `@file` to read @# command-line options from `file` instead. @@ -112,9 +121,10 @@ ifeq ($(OS), WINNT) endif $(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir) - @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \ - $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT))) - @$(INSTALL_NAME_CMD)libjulia-debug.$(SHLIB_EXT) $@ + @$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \ + $(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT))) + @$(INSTALL_NAME_CMD)libjulia-debug.$(JL_MAJOR_SHLIB_EXT) $@ + @$(DSYMUTIL) $@ ifeq ($(OS), WINNT) @$(call PRINT_ANALYZE, $(OBJCOPY) $(build_libdir)/$(notdir $@).tmp.a $(STRIP_EXPORTED_FUNCS) $(build_libdir)/$(notdir $@).a && rm $(build_libdir)/$(notdir $@).tmp.a) endif diff --git a/cli/jl_exports.h b/cli/jl_exports.h index e9be7c6f2f819..d28958c097edb 100644 --- a/cli/jl_exports.h +++ b/cli/jl_exports.h @@ -16,6 +16,10 @@ JL_EXPORTED_DATA_POINTERS(XX) JL_EXPORTED_DATA_SYMBOLS(XX) #undef XX +// define a copy of exported data +#define jl_max_tags 64 +JL_DLLEXPORT void *small_typeof[(jl_max_tags << 4) / sizeof(void*)]; // 16-bit aligned, like the GC + // Declare list of exported functions (sans type) #define XX(name) JL_DLLEXPORT void name(void); typedef void (anonfunc)(void); diff --git a/cli/loader.h b/cli/loader.h index 2d0b977f7142f..b778976cee495 100644 --- a/cli/loader.h +++ b/cli/loader.h @@ -20,10 +20,9 @@ #define strchr loader_strchr #define malloc loader_malloc #define realloc loader_realloc +#define free loader_free #endif -#include - #ifdef _OS_WINDOWS_ #define WIN32_LEAN_AND_MEAN @@ -46,25 +45,26 @@ #include #include #include +#include #endif +#include + // Borrow definition from `support/dtypes.h` #ifdef _OS_WINDOWS_ -# ifdef LIBRARY_EXPORTS +# ifdef JL_LIBRARY_EXPORTS # define JL_DLLEXPORT __declspec(dllexport) -# else -# define JL_DLLEXPORT __declspec(dllimport) # endif +# define JL_DLLIMPORT __declspec(dllimport) #define JL_HIDDEN #else -# if defined(LIBRARY_EXPORTS) && defined(_OS_LINUX_) -# define JL_DLLEXPORT __attribute__ ((visibility("protected"))) -# else -# define JL_DLLEXPORT __attribute__ ((visibility("default"))) -# endif +# define JL_DLLIMPORT __attribute__ ((visibility("default"))) #define JL_HIDDEN __attribute__ ((visibility("hidden"))) #endif +#ifndef JL_DLLEXPORT +# define JL_DLLEXPORT JL_DLLIMPORT +#endif /* * DEP_LIBS is our list of dependent libraries that must be loaded before `libjulia`. * Note that order matters, as each entry will be opened in-order. We define here a @@ -92,8 +92,8 @@ static void * lookup_symbol(const void * lib_handle, const char * symbol_name); #ifdef _OS_WINDOWS_ LPWSTR *CommandLineToArgv(LPWSTR lpCmdLine, int *pNumArgs); -int wchar_to_utf8(const wchar_t * wstr, char *str, size_t maxlen); -int utf8_to_wchar(const char * str, wchar_t *wstr, size_t maxlen); +char *wchar_to_utf8(const wchar_t * wstr); +wchar_t *utf8_to_wchar(const char * str); void setup_stdio(void); #endif diff --git a/cli/loader_exe.c b/cli/loader_exe.c index 07a0bddcd4b87..5fc8e73189ac6 100644 --- a/cli/loader_exe.c +++ b/cli/loader_exe.c @@ -15,7 +15,7 @@ extern "C" { JULIA_DEFINE_FAST_TLS #ifdef _COMPILER_ASAN_ENABLED_ -JL_DLLEXPORT const char* __asan_default_options() +JL_DLLEXPORT const char* __asan_default_options(void) { return "allow_user_segv_handler=1:detect_leaks=0"; // FIXME: enable LSAN after fixing leaks & defining __lsan_default_suppressions(), @@ -36,7 +36,7 @@ int main(int argc, char * argv[]) { #endif -#if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_) +#if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_) || defined(_COMPILER_MSAN_ENABLED_) // ASAN/TSAN do not support RTLD_DEEPBIND // https://github.com/google/sanitizers/issues/611 putenv("LBT_USE_RTLD_DEEPBIND=0"); @@ -45,9 +45,8 @@ int main(int argc, char * argv[]) // Convert Windows wchar_t values to UTF8 #ifdef _OS_WINDOWS_ for (int i = 0; i < argc; i++) { - size_t max_arg_len = 4*wcslen(wargv[i]); - argv[i] = (char *)malloc(max_arg_len); - if (!wchar_to_utf8(wargv[i], argv[i], max_arg_len)) { + argv[i] = wchar_to_utf8(wargv[i]); + if (!argv[i]) { jl_loader_print_stderr("Unable to convert all arguments to UTF-8!\n"); return 1; } diff --git a/cli/loader_lib.c b/cli/loader_lib.c index 0301b6eedde62..e2f615c684637 100644 --- a/cli/loader_lib.c +++ b/cli/loader_lib.c @@ -13,10 +13,15 @@ extern "C" { /* Bring in helper functions for windows without libgcc. */ #ifdef _OS_WINDOWS_ #include "loader_win_utils.c" + +#include +static int win_file_exists(wchar_t* wpath) { + return GetFileAttributesW(wpath) == INVALID_FILE_ATTRIBUTES ? 0 : 1; +} #endif // Save DEP_LIBS to a variable that is explicitly sized for expansion -static char dep_libs[1024] = DEP_LIBS; +static char dep_libs[1024] = "\0" DEP_LIBS; JL_DLLEXPORT void jl_loader_print_stderr(const char * msg) { @@ -31,9 +36,15 @@ void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char * } /* Wrapper around dlopen(), with extra relative pathing thrown in*/ +/* If err, then loads the library successfully or panics. + * If !err, then loads the library or returns null if the file does not exist, + * or panics if opening failed for any other reason. */ +/* Currently the only use of this function with !err is in opening libjulia-codegen, + * which the user can delete to save space if generating new code is not necessary. + * However, if it exists and cannot be loaded, that's a problem. So, we alert the user + * and abort the process. */ static void * load_library(const char * rel_path, const char * src_dir, int err) { void * handle = NULL; - // See if a handle is already open to the basename const char *basename = rel_path + strlen(rel_path); while (basename-- > rel_path) @@ -55,19 +66,33 @@ static void * load_library(const char * rel_path, const char * src_dir, int err) strncat(path, rel_path, sizeof(path) - 1); #if defined(_OS_WINDOWS_) - wchar_t wpath[2*JL_PATH_MAX + 1] = {0}; - if (!utf8_to_wchar(path, wpath, 2*JL_PATH_MAX)) { +#define PATH_EXISTS() win_file_exists(wpath) + wchar_t *wpath = utf8_to_wchar(path); + if (!wpath) { jl_loader_print_stderr3("ERROR: Unable to convert path ", path, " to wide string!\n"); exit(1); } handle = (void *)LoadLibraryExW(wpath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); #else +#define PATH_EXISTS() !access(path, F_OK) handle = dlopen(path, RTLD_NOW | (err ? RTLD_GLOBAL : RTLD_LOCAL)); #endif - - if (handle == NULL) { - if (!err) + if (handle != NULL) { +#if defined(_OS_WINDOWS_) + free(wpath); +#endif + } + else { + if (!err && !PATH_EXISTS()) { +#if defined(_OS_WINDOWS_) + free(wpath); +#endif return NULL; + } +#if defined(_OS_WINDOWS_) + free(wpath); +#endif +#undef PATH_EXISTS jl_loader_print_stderr3("ERROR: Unable to load dependent library ", path, "\n"); #if defined(_OS_WINDOWS_) LPWSTR wmsg = TEXT(""); @@ -78,11 +103,14 @@ static void * load_library(const char * rel_path, const char * src_dir, int err) NULL, GetLastError(), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPWSTR)&wmsg, 0, NULL); - char err[256] = {0}; - wchar_to_utf8(wmsg, err, 255); - jl_loader_print_stderr3("Message:", err, "\n"); + char *errmsg = wchar_to_utf8(wmsg); + jl_loader_print_stderr3("Message:", errmsg, "\n"); + free(errmsg); #else - jl_loader_print_stderr3("Message:", dlerror(), "\n"); + char *dlerr = dlerror(); + if (dlerr != NULL) { + jl_loader_print_stderr3("Message:", dlerr, "\n"); + } #endif exit(1); } @@ -98,20 +126,20 @@ static void * lookup_symbol(const void * lib_handle, const char * symbol_name) { } // Find the location of libjulia. -char lib_dir[JL_PATH_MAX]; +char *lib_dir = NULL; JL_DLLEXPORT const char * jl_get_libdir() { // Reuse the path if this is not the first call. - if (lib_dir[0] != 0) { + if (lib_dir) { return lib_dir; } #if defined(_OS_WINDOWS_) // On Windows, we use GetModuleFileNameW - wchar_t libjulia_path[JL_PATH_MAX]; + wchar_t *libjulia_path = utf8_to_wchar(LIBJULIA_NAME); HMODULE libjulia = NULL; // Get a handle to libjulia. - if (!utf8_to_wchar(LIBJULIA_NAME, libjulia_path, JL_PATH_MAX)) { + if (!libjulia_path) { jl_loader_print_stderr3("ERROR: Unable to convert path ", LIBJULIA_NAME, " to wide string!\n"); exit(1); } @@ -120,23 +148,30 @@ JL_DLLEXPORT const char * jl_get_libdir() jl_loader_print_stderr3("ERROR: Unable to load ", LIBJULIA_NAME, "!\n"); exit(1); } - if (!GetModuleFileNameW(libjulia, libjulia_path, JL_PATH_MAX)) { + free(libjulia_path); + libjulia_path = (wchar_t*)malloc(32768 * sizeof(wchar_t)); // max long path length + if (!GetModuleFileNameW(libjulia, libjulia_path, 32768)) { jl_loader_print_stderr("ERROR: GetModuleFileName() failed\n"); exit(1); } - if (!wchar_to_utf8(libjulia_path, lib_dir, JL_PATH_MAX)) { + lib_dir = wchar_to_utf8(libjulia_path); + if (!lib_dir) { jl_loader_print_stderr("ERROR: Unable to convert julia path to UTF-8\n"); exit(1); } + free(libjulia_path); #else // On all other platforms, use dladdr() Dl_info info; if (!dladdr(&jl_get_libdir, &info)) { jl_loader_print_stderr("ERROR: Unable to dladdr(&jl_get_libdir)!\n"); - jl_loader_print_stderr3("Message:", dlerror(), "\n"); + char *dlerr = dlerror(); + if (dlerr != NULL) { + jl_loader_print_stderr3("Message:", dlerr, "\n"); + } exit(1); } - strcpy(lib_dir, info.dli_fname); + lib_dir = strdup(info.dli_fname); #endif // Finally, convert to dirname const char * new_dir = dirname(lib_dir); @@ -147,58 +182,306 @@ JL_DLLEXPORT const char * jl_get_libdir() return lib_dir; } -void * libjulia_internal = NULL; +// On Linux, it can happen that the system has a newer libstdc++ than the one we ship, +// which can break loading of some system libraries: . +// As a fix, on linux we probe the system libstdc++ to see if it is newer, and then load it if it is. +// Otherwise, we load the bundled one. This improves compatibility with third party dynamic libs that +// may depend on symbols exported by the system libstdxc++. +#ifdef _OS_LINUX_ +#ifndef GLIBCXX_LEAST_VERSION_SYMBOL +#warning GLIBCXX_LEAST_VERSION_SYMBOL should always be defined in the makefile. +#define GLIBCXX_LEAST_VERSION_SYMBOL "GLIBCXX_a.b.c" /* Appease the linter */ +#endif + +#include +#include + +// write(), but handle errors and avoid EINTR +static void write_wrapper(int fd, const char *str, size_t len) +{ + size_t written_sofar = 0; + while (len) { + ssize_t bytes_written = write(fd, str + written_sofar, len); + if (bytes_written == -1 && errno == EINTR) continue; + if (bytes_written == -1 && errno != EINTR) { + perror("(julia) child libstdcxxprobe write"); + _exit(1); + } + len -= bytes_written; + written_sofar += bytes_written; + } +} + +// read(), but handle errors and avoid EINTR +static void read_wrapper(int fd, char **ret, size_t *ret_len) +{ + // Allocate an initial buffer + size_t len = JL_PATH_MAX; + char *buf = (char *)malloc(len + 1); + if (!buf) { + perror("(julia) malloc"); + exit(1); + } + + // Read into it, reallocating as necessary + size_t have_read = 0; + while (1) { + ssize_t n = read(fd, buf + have_read, len - have_read); + if (n == 0) break; + if (n == -1 && errno != EINTR) { + perror("(julia) libstdcxxprobe read"); + exit(1); + } + if (n == -1 && errno == EINTR) continue; + have_read += n; + if (have_read == len) { + buf = (char *)realloc(buf, 1 + (len *= 2)); + if (!buf) { + perror("(julia) realloc"); + exit(1); + } + } + } + + *ret = buf; + *ret_len = have_read; +} + +// Return the path to the libstdcxx to load. +// If the path is found, return it. +// Otherwise, print the error and exit. +// The path returned must be freed. +static char *libstdcxxprobe(void) +{ + // Create the pipe and child process. + int fork_pipe[2]; + int ret = pipe(fork_pipe); + if (ret == -1) { + perror("(julia) Error during libstdcxxprobe: pipe"); + exit(1); + } + pid_t pid = fork(); + if (pid == -1) { + perror("Error during libstdcxxprobe:\nfork"); + exit(1); + } + if (pid == (pid_t) 0) { // Child process. + close(fork_pipe[0]); + + // Open the first available libstdc++.so. + // If it can't be found, report so by exiting zero. + // The star is there to prevent the compiler from merging constants + // with "\0*libstdc++.so.6", which we string replace inside the .so during + // make install. + void *handle = dlopen("libstdc++.so.6\0*", RTLD_LAZY); + if (!handle) { + _exit(0); + } + + // See if the version is compatible + char *dlerr = dlerror(); // clear out dlerror + void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL); + dlerr = dlerror(); + if (dlerr) { + // We can't use the library that was found, so don't write anything. + // The main process will see that nothing was written, + // then exit the function and return null. + _exit(0); + } + + // No error means the symbol was found, we can use this library. + // Get the path to it, and write it to the parent process. + struct link_map *lm; + ret = dlinfo(handle, RTLD_DI_LINKMAP, &lm); + if (ret == -1) { + char *errbuf = dlerror(); + char *errdesc = (char*)"Error during libstdcxxprobe in child process:\ndlinfo: "; + write_wrapper(STDERR_FILENO, errdesc, strlen(errdesc)); + write_wrapper(STDERR_FILENO, errbuf, strlen(errbuf)); + write_wrapper(STDERR_FILENO, "\n", 1); + _exit(1); + } + char *libpath = lm->l_name; + write_wrapper(fork_pipe[1], libpath, strlen(libpath)); + _exit(0); + } + else { // Parent process. + close(fork_pipe[1]); + + // Read the absolute path to the lib from the child process. + char *path; + size_t pathlen; + read_wrapper(fork_pipe[0], &path, &pathlen); + + // Close the read end of the pipe + close(fork_pipe[0]); + + // Wait for the child to complete. + while (1) { + int wstatus; + pid_t npid = waitpid(pid, &wstatus, 0); + if (npid == -1) { + if (errno == EINTR) continue; + if (errno != EINTR) { + perror("Error during libstdcxxprobe in parent process:\nwaitpid"); + exit(1); + } + } + else if (!WIFEXITED(wstatus)) { + const char *err_str = "Error during libstdcxxprobe in parent process:\n" + "The child process did not exit normally.\n"; + size_t err_strlen = strlen(err_str); + write_wrapper(STDERR_FILENO, err_str, err_strlen); + exit(1); + } + else if (WEXITSTATUS(wstatus)) { + // The child has printed an error and exited, so the parent should exit too. + exit(1); + } + break; + } + + if (!pathlen) { + free(path); + return NULL; + } + return path; + } +} +#endif + +void *libjulia_internal = NULL; +void *libjulia_codegen = NULL; __attribute__((constructor)) void jl_load_libjulia_internal(void) { +#if defined(_OS_LINUX_) + // Julia uses `sigwait()` to handle signals, and all threads are required + // to mask the corresponding handlers so that the signals can be waited on. + // Here, we setup that masking early, so that it is inherited by any threads + // spawned (e.g. by constructors) when loading deps of libjulia-internal. + + sigset_t all_signals, prev_mask; + sigfillset(&all_signals); + pthread_sigmask(SIG_BLOCK, &all_signals, &prev_mask); +#endif + // Only initialize this once if (libjulia_internal != NULL) { return; } // Introspect to find our own path - const char * lib_dir = jl_get_libdir(); + const char *lib_dir = jl_get_libdir(); // Pre-load libraries that libjulia-internal needs. - int deps_len = strlen(dep_libs); - char * curr_dep = &dep_libs[0]; + 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 `@`) - // which are libraries that we want to load in some special, custom way, such as - // `libjulia-internal` or `libjulia-codegen`. + // which are libraries that we want to load in some special, custom way. + // The current list is: + // libstdc++ + // libjulia-internal + // libjulia-codegen + const int NUM_SPECIAL_LIBRARIES = 3; int special_idx = 0; - char * special_library_names[2] = {NULL}; while (1) { // try to find next colon character; if we can't, break out char * colon = strchr(curr_dep, ':'); if (colon == NULL) break; - // Chop the string at the colon so it's a valid-ending-string - *colon = '\0'; - // If this library name starts with `@`, don't open it here (but mark it as special) if (curr_dep[0] == '@') { - if (special_idx > sizeof(special_library_names)/sizeof(char *)) { + special_idx += 1; + if (special_idx > NUM_SPECIAL_LIBRARIES) { jl_loader_print_stderr("ERROR: Too many special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); exit(1); } - special_library_names[special_idx] = curr_dep + 1; - special_idx += 1; - } else { - load_library(curr_dep, lib_dir, 1); } - // Skip ahead to next dependency + // Skip to next dep curr_dep = colon + 1; } - if (special_idx != sizeof(special_library_names)/sizeof(char *)) { + // Assert that we have exactly the right number of special library names + if (special_idx != NUM_SPECIAL_LIBRARIES) { jl_loader_print_stderr("ERROR: Too few special library names specified, check LOADER_BUILD_DEP_LIBS and friends!\n"); exit(1); } - // Unpack our special library names. This is why ordering of library names matters. - libjulia_internal = load_library(special_library_names[0], lib_dir, 1); - void *libjulia_codegen = load_library(special_library_names[1], lib_dir, 0); + // Now that we've asserted that we have the right number of special + // libraries, actually run a loop over the deps loading them in-order. + // If it's a special library, we do slightly different things, especially + // for libstdc++, where we actually probe for a system libstdc++ and + // load that if it's newer. + special_idx = 0; + curr_dep = &dep_libs[1]; + while (1) { + // try to find next colon character; if we can't, break out + char * colon = strchr(curr_dep, ':'); + if (colon == NULL) + break; + + // Chop the string at the colon so it's a valid-ending-string + *colon = '\0'; + + // If this library name starts with `@`, it's a special library + // and requires special handling: + if (curr_dep[0] == '@') { + // Skip the `@` for future function calls. + curr_dep += 1; + + // First special library to be loaded is `libstdc++`; perform probing here. + if (special_idx == 0) { +#if defined(_OS_LINUX_) + int do_probe = 1; + int probe_successful = 0; + + // Check to see if the user has disabled libstdc++ probing + char *probevar = getenv("JULIA_PROBE_LIBSTDCXX"); + if (probevar) { + if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes") == 0) + do_probe = 1; + else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no") == 0) + do_probe = 0; + } + if (do_probe) { + char *cxxpath = libstdcxxprobe(); + if (cxxpath) { + void *cxx_handle = dlopen(cxxpath, RTLD_LAZY); + const char *dlr = dlerror(); + if (dlr) { + jl_loader_print_stderr("ERROR: Unable to dlopen(cxxpath) in parent!\n"); + jl_loader_print_stderr3("Message: ", dlr, "\n"); + exit(1); + } + free(cxxpath); + probe_successful = 1; + } + } + // If the probe rejected the system libstdc++ (or didn't find one!) + // just load our bundled libstdc++ as identified by curr_dep; + if (!probe_successful) { + load_library(curr_dep, lib_dir, 1); + } +#endif + } else if (special_idx == 1) { + // This special library is `libjulia-internal` + libjulia_internal = load_library(curr_dep, lib_dir, 1); + } else if (special_idx == 2) { + // This special library is `libjulia-codegen` + libjulia_codegen = load_library(curr_dep, lib_dir, 0); + } + special_idx++; + } else { + // Otherwise, just load it as "normal" + load_library(curr_dep, lib_dir, 1); + } + + // Skip ahead to next dependency + curr_dep = colon + 1; + } + const char * const * codegen_func_names; const char *codegen_liberr; if (libjulia_codegen == NULL) { @@ -249,6 +532,13 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) { // jl_options must be initialized very early, in case an embedder sets some // values there before calling jl_init ((void (*)(void))jl_init_options_addr)(); + +#if defined(_OS_LINUX_) + // Restore the original signal mask. `jl_init()` will later setup blocking + // for the specific set of signals we `sigwait()` on, and any threads spawned + // during loading above will still retain their inherited signal mask. + pthread_sigmask(SIG_SETMASK, &prev_mask, NULL); +#endif } // Load libjulia and run the REPL with the given arguments (in UTF-8 format) @@ -272,7 +562,7 @@ JL_DLLEXPORT int jl_load_repl(int argc, char * argv[]) { } #ifdef _OS_WINDOWS_ -int __stdcall DllMainCRTStartup(void* instance, unsigned reason, void* reserved) { +int __stdcall DllMainCRTStartup(void *instance, unsigned reason, void *reserved) { setup_stdio(); // Because we override DllMainCRTStartup, we have to manually call our constructor methods diff --git a/cli/loader_win_utils.c b/cli/loader_win_utils.c index 621834a030c52..2c3c826b08369 100644 --- a/cli/loader_win_utils.c +++ b/cli/loader_win_utils.c @@ -12,22 +12,28 @@ static FILE _stderr = { INVALID_HANDLE_VALUE }; FILE *stdout = &_stdout; FILE *stderr = &_stderr; -int loader_fwrite(const WCHAR *str, size_t nchars, FILE *out) { +int loader_fwrite(const char *str, size_t nchars, FILE *out) { DWORD written; if (out->isconsole) { - if (WriteConsole(out->fd, str, nchars, &written, NULL)) + // Windows consoles do not support UTF-8 (for reading input, though new Windows Terminal does for writing), only UTF-16. + wchar_t* wstr = utf8_to_wchar(str); + if (!wstr) + return -1; + if (WriteConsoleW(out->fd, wstr, wcslen(wstr), &written, NULL)) { + loader_free(wstr); return written; + } + loader_free(wstr); } else { - if (WriteFile(out->fd, str, sizeof(WCHAR) * nchars, &written, NULL)) + // However, we want to print UTF-8 if the output is a file. + if (WriteFile(out->fd, str, nchars, &written, NULL)) return written; } return -1; } int loader_fputs(const char *str, FILE *out) { - wchar_t wstr[1024]; - utf8_to_wchar(str, wstr, 1024); - return fwrite(wstr, wcslen(wstr), out); + return loader_fwrite(str, loader_strlen(str), out); } void * loader_malloc(const size_t size) { @@ -38,6 +44,10 @@ void * loader_realloc(void * mem, const size_t size) { return HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, mem, size); } +void loader_free(void* mem) { + HeapFree(GetProcessHeap(), 0, mem); +} + LPWSTR *CommandLineToArgv(LPWSTR lpCmdLine, int *pNumArgs) { LPWSTR out = lpCmdLine; LPWSTR cmd = out; @@ -106,36 +116,36 @@ void loader_exit(int code) { /* Utilities to convert from Windows' wchar_t stuff to UTF-8 */ -int wchar_to_utf8(const wchar_t * wstr, char *str, size_t maxlen) { +char *wchar_to_utf8(const wchar_t * wstr) { /* Fast-path empty strings, as WideCharToMultiByte() returns zero for them. */ if (wstr[0] == L'\0') { + char *str = malloc(1); str[0] = '\0'; - return 1; + return str; } size_t len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); if (!len) - return 0; - if (len > maxlen) - return 0; + return NULL; + char *str = (char *)malloc(len); if (!WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL)) - return 0; - return 1; + return NULL; + return str; } -int utf8_to_wchar(const char * str, wchar_t * wstr, size_t maxlen) { - /* Fast-path empty strings, as WideCharToMultiByte() returns zero for them. */ +wchar_t *utf8_to_wchar(const char * str) { + /* Fast-path empty strings, as MultiByteToWideChar() returns zero for them. */ if (str[0] == '\0') { + wchar_t *wstr = malloc(sizeof(wchar_t)); wstr[0] = L'\0'; - return 1; + return wstr; } size_t len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); if (!len) - return 0; - if (len > maxlen) - return 0; + return NULL; + wchar_t *wstr = (wchar_t *)malloc(len * sizeof(wchar_t)); if (!MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len)) - return 0; - return 1; + return NULL; + return wstr; } size_t loader_strlen(const char * x) { diff --git a/contrib/README.md b/contrib/README.md index 62eca671dc38e..46058bbf46642 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -6,17 +6,29 @@ Installation |[ mac/ ](https://github.com/JuliaLang/julia/blob/master/contrib/mac/) | Mac install files | |[ windows/ ](https://github.com/JuliaLang/julia/blob/master/contrib/windows/) | Windows install files | |[ add_license_to_files.jl ](https://github.com/JuliaLang/julia/blob/master/contrib/add_license_to_files.jl ) | Add the Julia license to files in the Julia Project | -|[ check-whitespace.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/check-whitespace.sh) | Check for trailing white space | +|[ check-whitespace.jl ](https://github.com/JuliaLang/julia/blob/master/contrib/check-whitespace.jl) | Check for white space issues | |[ commit-name.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/commit-name.sh) | Computes a version name for a commit | |[ fixup-libgfortran.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/fixup-libgfortran.sh) | Include libgfortran and libquadmath for installations | |[ fixup-libstdc++.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/fixup-libstdc++.sh) | Include libstdc++ for installations | |[ install.sh ](https://github.com/JuliaLang/julia/blob/master/contrib/install.sh) | Installation script with different permissions | |[ julia.appdata.xml ](https://github.com/JuliaLang/julia/blob/master/contrib/julia.appdata.xml) | Appdata config file | |[ julia-config.jl ](https://github.com/JuliaLang/julia/blob/master/contrib/julia-config.jl) | Determines build parameters required by an embedded Julia | -|[ julia.desktop ](https://github.com/JuliaLang/julia/blob/master/contrib/julia.desktop) | GNOME desktop config file | +|[ julia.desktop ](https://github.com/JuliaLang/julia/blob/master/contrib/julia.desktop) | Desktop entry file | +|[ julia.png ](https://github.com/JuliaLang/julia/blob/master/contrib/julia.png) | Julia png image file | +|[ julia.svg ](https://github.com/JuliaLang/julia/blob/master/contrib/julia.svg) | Julia svg image file | |[ relative_path.py ](https://github.com/JuliaLang/julia/blob/master/contrib/relative_path.py) | Convert absolute paths into relative paths | |[ stringreplace.c ](https://github.com/JuliaLang/julia/blob/master/contrib/stringreplace.c) | Replace strings to hardcoded paths in binaries during `make install` | +Packagers may want to run this command via a script after package installation. + +``` +if [ -e /usr/share/icons/hicolor/icon-theme.cache ]; then + if [ -x /usr/bin/gtk-update-icon-cache ]; then + /usr/bin/gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 + fi +fi +``` + Debugging ========= diff --git a/contrib/add_license_to_files.jl b/contrib/add_license_to_files.jl index 9650422ee07ca..1d301a5455394 100644 --- a/contrib/add_license_to_files.jl +++ b/contrib/add_license_to_files.jl @@ -59,6 +59,7 @@ const skipfiles = [ "../src/support/tzfile.h", "../src/support/utf8.c", "../src/crc32c.c", + "../src/mach_excUser.c", ] const ext_prefix = Dict([ diff --git a/contrib/asan/Make.user.asan b/contrib/asan/Make.user.asan index aa6293fd81cd7..96ed13b54e0f9 100644 --- a/contrib/asan/Make.user.asan +++ b/contrib/asan/Make.user.asan @@ -18,3 +18,10 @@ override WITH_GC_DEBUG_ENV=1 # default to a debug build for better line number reporting override JULIA_BUILD_MODE=debug + +# Enable Julia assertions and LLVM assertions +FORCE_ASSERTIONS=1 +LLVM_ASSERTIONS=1 + +# Build a minimal system image +JULIA_PRECOMPILE=0 diff --git a/contrib/check-whitespace.jl b/contrib/check-whitespace.jl new file mode 100755 index 0000000000000..d5473ab4c7c62 --- /dev/null +++ b/contrib/check-whitespace.jl @@ -0,0 +1,68 @@ +#!/usr/bin/env julia + +const patterns = split(""" + *.1 + *.c + *.cpp + *.h + *.inc + *.jl + *.lsp + *.make + *.md + *.mk + *.rst + *.scm + *.sh + *.yml + *Makefile +""") + +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")) + +const errors = Set{Tuple{String,Int,String}}() + +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)) + + 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 + +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 diff --git a/contrib/check-whitespace.sh b/contrib/check-whitespace.sh deleted file mode 100755 index ff5bd24ab2cbe..0000000000000 --- a/contrib/check-whitespace.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -# This file is a part of Julia. License is MIT: https://julialang.org/license - -# Check for trailing white space in source files; -# report an error if so - -# Files to check: -set -f # disable glob expansion in this script -file_patterns=' -*.1 -*.c -*.cpp -*.h -*.jl -*.lsp -*.scm -*.inc -*.make -*.mk -*.md -*.rst -*.sh -*.yml -*Makefile -' - -# TODO: Look also for trailing empty lines, and missing '\n' after the last line -if git --no-pager grep --color -n --full-name -e ' $' -- $file_patterns; then - echo "Error: trailing whitespace found in source file(s)" - echo "" - echo "This can often be fixed with:" - echo " git rebase --whitespace=fix HEAD~1" - echo "or" - echo " git rebase --whitespace=fix master" - echo "and then a forced push of the correct branch" - exit 1 -fi - -echo "Whitespace check found no issues" diff --git a/contrib/download_cmake.sh b/contrib/download_cmake.sh index e8aea4d9d0553..1deeb08ddded2 100755 --- a/contrib/download_cmake.sh +++ b/contrib/download_cmake.sh @@ -9,30 +9,37 @@ cd "$(dirname "$0")"/../deps/scratch CMAKE_VERSION_MAJOR=3 CMAKE_VERSION_MINOR=19 -CMAKE_VERSION_PATCH=2 +CMAKE_VERSION_PATCH=3 CMAKE_VERSION_MAJMIN=$CMAKE_VERSION_MAJOR.$CMAKE_VERSION_MINOR CMAKE_VERSION=$CMAKE_VERSION_MAJMIN.$CMAKE_VERSION_PATCH # listed at https://cmake.org/files/v$CMAKE_VERSION_MAJMIN/cmake-$CMAKE_VERSION-SHA-256.txt -# for the files cmake-$CMAKE_VERSION-Darwin-x86_64.tar.gz -# and cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz -CMAKE_SHA256_DARWIN=50afa2cb66bea6a0314ef28034f3ff1647325e30cf5940f97906a56fd9640bd8 -CMAKE_SHA256_LINUX=4d8a6d852c530f263b22479aad196416bb4406447e918bd9759c6593b7f5f3f9 +# for the files cmake-$CMAKE_VERSION-macos-universal.tar.gz +# cmake-$CMAKE_VERSION-Linux-x86_64.tar.gz and cmake-$CMAKE_VERSION-Linux-aarch64.tar.gz +CMAKE_SHA256_DARWIN=a6b79ad05f89241a05797510e650354d74ff72cc988981cdd1eb2b3b2bda66ac +CMAKE_SHA256_LINUX_X86_64=c18b65697e9679e5c88dccede08c323cd3d3730648e59048047bba82097e0ffc +CMAKE_SHA256_LINUX_AARCH64=66e507c97ffb586d7ca6567890808b792c8eb004b645706df6fbf27826a395a2 PLATFORM="$(uname)-$(uname -m)" -FULLNAME=cmake-$CMAKE_VERSION-$PLATFORM case $PLATFORM in - Darwin-x86_64) + Darwin-*) + FULLNAME=cmake-$CMAKE_VERSION-macos-universal ../tools/jldownload https://cmake.org/files/v$CMAKE_VERSION_MAJMIN/$FULLNAME.tar.gz echo "$CMAKE_SHA256_DARWIN $FULLNAME.tar.gz" | shasum -a 256 -c - CMAKE_EXTRACTED_PATH=$FULLNAME/CMake.app/Contents/bin/cmake;; Linux-x86_64) + FULLNAME=cmake-$CMAKE_VERSION-$PLATFORM ../tools/jldownload https://cmake.org/files/v$CMAKE_VERSION_MAJMIN/$FULLNAME.tar.gz - echo "$CMAKE_SHA256_LINUX $FULLNAME.tar.gz" | sha256sum -c - + echo "$CMAKE_SHA256_LINUX_X86_64 $FULLNAME.tar.gz" | sha256sum -c - + CMAKE_EXTRACTED_PATH=$FULLNAME/bin/cmake;; + Linux-aarch64) + FULLNAME=cmake-$CMAKE_VERSION-$PLATFORM + ../tools/jldownload https://cmake.org/files/v$CMAKE_VERSION_MAJMIN/$FULLNAME.tar.gz + echo "$CMAKE_SHA256_LINUX_AARCH64 $FULLNAME.tar.gz" | sha256sum -c - CMAKE_EXTRACTED_PATH=$FULLNAME/bin/cmake;; *) - echo "This script only supports x86_64 Mac and Linux. For other platforms," >&2 - echo "get cmake from your package manager or compile it from source." >&2 + echo "This script only supports Mac and Linux, both for x86_64 and aarch64." >&2 + echo "For other platforms, get cmake from your package manager or compile it from source." >&2 exit 1;; esac diff --git a/contrib/fixup-libgfortran.sh b/contrib/fixup-libgfortran.sh index 7f44ffb29d522..6121665fb5a86 100755 --- a/contrib/fixup-libgfortran.sh +++ b/contrib/fixup-libgfortran.sh @@ -160,4 +160,3 @@ for lib in libopenblas libcholmod liblapack $SONAMES; do done done done - diff --git a/contrib/fixup-libstdc++.sh b/contrib/fixup-libstdc++.sh index 1c19d98a54b1e..7442d995448a1 100755 --- a/contrib/fixup-libstdc++.sh +++ b/contrib/fixup-libstdc++.sh @@ -11,7 +11,8 @@ fi libdir="$1" private_libdir="$2" -if [ ! -f "$private_libdir/libjulia-internal.so" ]; then +if [ ! -f "$private_libdir/libjulia-internal.so" ] && \ + [ ! -f "$private_libdir/libjulia-internal-debug.so" ]; then echo "ERROR: Could not open $private_libdir/libjulia-internal.so" >&2 exit 2 fi @@ -24,7 +25,11 @@ find_shlib () } # Discover libstdc++ location and name -LIBSTD=$(find_shlib "$private_libdir/libjulia-internal.so" "libstdc++.so") +if [ -f "$private_libdir/libjulia-internal.so" ]; then + LIBSTD=$(find_shlib "$private_libdir/libjulia-internal.so" "libstdc++.so") +elif [ -f "$private_libdir/libjulia-internal-debug.so" ]; then + LIBSTD=$(find_shlib "$private_libdir/libjulia-internal-debug.so" "libstdc++.so") +fi LIBSTD_NAME=$(basename $LIBSTD) LIBSTD_DIR=$(dirname $LIBSTD) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index e6cf280812685..e8901a7b462ea 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -1,5 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +if Threads.maxthreadid() != 1 + @warn "Running this file with multiple Julia threads may lead to a build error" Threads.maxthreadid() +end + if Base.isempty(Base.ARGS) || Base.ARGS[1] !== "0" Sys.__init_build() # Prevent this from being put into the Main namespace @@ -11,7 +15,19 @@ Base.include(@__MODULE__, joinpath(Sys.BINDIR, "..", "share", "julia", "test", " import .FakePTYs: open_fake_pty using Base.Meta +## Debugging options +# Disable parallel precompiles generation by setting `false` +const PARALLEL_PRECOMPILATION = true + +# View the code sent to the repl by setting this to `stdout` +const debug_output = devnull # or stdout + +# Disable fancy printing +const fancyprint = (stdout isa Base.TTY) && Base.get_bool_env("CI", false) !== true +## + CTRL_C = '\x03' +CTRL_R = '\x12' UP_ARROW = "\e[A" DOWN_ARROW = "\e[B" @@ -29,6 +45,9 @@ precompile(Tuple{typeof(push!), Vector{Function}, Function}) # miscellaneous 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(isassigned), Core.SimpleVector, Int}) precompile(Tuple{typeof(getindex), Core.SimpleVector, Int}) precompile(Tuple{typeof(Base.Experimental.register_error_hint), Any, Type}) @@ -38,6 +57,7 @@ precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, Symbol, Module)) precompile(Base.CoreLogging.env_override_minlevel, (Symbol, Module)) precompile(Base.StackTraces.lookup, (Ptr{Nothing},)) +precompile(Tuple{typeof(Base.run_module_init), Module, Int}) """ for T in (Float16, Float32, Float64), IO in (IOBuffer, IOContext{IOBuffer}, Base.TTY, IOContext{Base.TTY}) @@ -51,9 +71,11 @@ print("") printstyled("a", "b") display([1]) display([1 2; 3 4]) -@time 1+1 +foo(x) = 1 +@time @eval foo(1) ; pwd $CTRL_C +$CTRL_R$CTRL_C ? reinterpret using Ra\t$CTRL_C \\alpha\t$CTRL_C @@ -112,28 +134,6 @@ if have_repl """ end -Distributed = get(Base.loaded_modules, - Base.PkgId(Base.UUID("8ba89e20-285c-5b6f-9357-94700520ee1b"), "Distributed"), - nothing) -if Distributed !== nothing - hardcoded_precompile_statements *= """ - precompile(Tuple{typeof(Distributed.remotecall),Function,Int,Module,Vararg{Any, 100}}) - precompile(Tuple{typeof(Distributed.procs)}) - precompile(Tuple{typeof(Distributed.finalize_ref), Distributed.Future}) - """ -# This is disabled because it doesn't give much benefit -# and the code in Distributed is poorly typed causing many invalidations -#= - precompile_script *= """ - using Distributed - addprocs(2) - pmap(x->iseven(x) ? 1 : 0, 1:4) - @distributed (+) for i = 1:100 Int(rand(Bool)) end - """ -=# -end - - Artifacts = get(Base.loaded_modules, Base.PkgId(Base.UUID("56f22d72-fd6d-98f1-02f0-08ddc0907c33"), "Artifacts"), nothing) @@ -148,7 +148,7 @@ if Artifacts !== nothing artifacts = Artifacts.load_artifacts_toml(artifacts_toml) platforms = [Artifacts.unpack_platform(e, "HelloWorldC", artifacts_toml) for e in artifacts["HelloWorldC"]] best_platform = select_platform(Dict(p => triplet(p) for p in platforms)) - dlopen("libjulia$(ccall(:jl_is_debugbuild, Cint, ()) != 0 ? "-debug" : "")", RTLD_LAZY | RTLD_DEEPBIND) + dlopen("libjulia$(Base.isdebugbuild() ? "-debug" : "")", RTLD_LAZY | RTLD_DEEPBIND) """ end @@ -159,7 +159,7 @@ Pkg = get(Base.loaded_modules, if Pkg !== nothing # TODO: Split Pkg precompile script into REPL and script part - repl_script *= Pkg.precompile_script + repl_script = Pkg.precompile_script * repl_script # do larger workloads first for better parallelization end FileWatching = get(Base.loaded_modules, @@ -182,52 +182,12 @@ if Libdl !== nothing """ end -Test = get(Base.loaded_modules, - Base.PkgId(Base.UUID("8dfed614-e22c-5e08-85e1-65c5234f0b40"), "Test"), - nothing) -if Test !== nothing - hardcoded_precompile_statements *= """ - precompile(Tuple{typeof(Test.do_test), Test.ExecutionResult, Any}) - precompile(Tuple{typeof(Test.testset_beginend_call), Tuple{String, Expr}, Expr, LineNumberNode}) - precompile(Tuple{Type{Test.DefaultTestSet}, String}) - precompile(Tuple{Type{Test.DefaultTestSet}, AbstractString}) - precompile(Tuple{Core.kwftype(Type{Test.DefaultTestSet}), Any, Type{Test.DefaultTestSet}, AbstractString}) - precompile(Tuple{typeof(Test.finish), Test.DefaultTestSet}) - precompile(Tuple{typeof(Test.eval_test), Expr, Expr, LineNumberNode, Bool}) - precompile(Tuple{typeof(Test._inferred), Expr, Module}) - precompile(Tuple{typeof(Test.push_testset), Test.DefaultTestSet}) - precompile(Tuple{typeof(Test.get_alignment), Test.DefaultTestSet, Int}) - precompile(Tuple{typeof(Test.get_test_result), Any, Any}) - precompile(Tuple{typeof(Test.do_test_throws), Test.ExecutionResult, Any, Any}) - precompile(Tuple{typeof(Test.print_counts), Test.DefaultTestSet, Int, Int, Int, Int, Int, Int, Int}) - precompile(Tuple{typeof(Test._check_testset), Type, Expr}) - precompile(Tuple{typeof(Test.test_expr!), Any, Any}) - precompile(Tuple{typeof(Test.test_expr!), Any, Any, Vararg{Any, 100}}) - precompile(Tuple{typeof(Test.pop_testset)}) - precompile(Tuple{typeof(Test.match_logs), Function, Tuple{Symbol, Regex}}) - precompile(Tuple{typeof(Test.match_logs), Function, Tuple{String, Regex}}) - precompile(Tuple{typeof(Base.CoreLogging.shouldlog), Test.TestLogger, Base.CoreLogging.LogLevel, Module, Symbol, Symbol}) - precompile(Tuple{typeof(Base.CoreLogging.handle_message), Test.TestLogger, Base.CoreLogging.LogLevel, String, Module, Symbol, Symbol, String, Int}) - precompile(Tuple{typeof(Core.kwfunc(Base.CoreLogging.handle_message)), typeof((exception=nothing,)), typeof(Base.CoreLogging.handle_message), Test.TestLogger, Base.CoreLogging.LogLevel, String, Module, Symbol, Symbol, String, Int}) - precompile(Tuple{typeof(Test.detect_ambiguities), Any}) - precompile(Tuple{typeof(Test.collect_test_logs), Function}) - precompile(Tuple{typeof(Test.do_broken_test), Test.ExecutionResult, Any}) - precompile(Tuple{typeof(Test.record), Test.DefaultTestSet, Union{Test.Error, Test.Fail}}) - precompile(Tuple{typeof(Test.filter_errors), Test.DefaultTestSet}) - """ -end - -Profile = get(Base.loaded_modules, - Base.PkgId(Base.UUID("9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"), "Profile"), +InteractiveUtils = get(Base.loaded_modules, + Base.PkgId(Base.UUID("b77e0a4c-d291-57a0-90e8-8db25a27a240"), "InteractiveUtils"), nothing) -if Profile !== nothing - repl_script *= Profile.precompile_script - hardcoded_precompile_statements *= """ - precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Int, UInt}) - precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Int, UnitRange{UInt}}) - precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, UnitRange{Int}, UInt}) - precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, UnitRange{Int}, UnitRange{UInt}}) - precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Vector{Int}, Vector{UInt}}) +if InteractiveUtils !== nothing + repl_script *= """ + @time_imports using Random """ end @@ -236,21 +196,78 @@ const PKG_PROMPT = "pkg> " const SHELL_PROMPT = "shell> " const HELP_PROMPT = "help?> " -function generate_precompile_statements() +# Printing the current state +let + global print_state + print_lk = ReentrantLock() + status = Dict{String, String}( + "step1" => "W", + "step2" => "W", + "repl" => "0/0", + "step3" => "W", + "clock" => "◐", + ) + function print_status(key::String) + txt = status[key] + if startswith(txt, "W") # Waiting + printstyled("? ", color=Base.warn_color()); print(txt[2:end]) + elseif startswith(txt, "R") # Running + print(status["clock"], " ", txt[2:end]) + elseif startswith(txt, "F") # Finished + printstyled("✓ ", color=:green); print(txt[2:end]) + else + print(txt) + end + end + function print_state(args::Pair{String,String}...) + lock(print_lk) do + isempty(args) || push!(status, args...) + print("\r└ Collect (Basic: ") + print_status("step1") + print(", REPL ", status["repl"], ": ") + print_status("step2") + print(") => Execute ") + print_status("step3") + end + end +end + +ansi_enablecursor = "\e[?25h" +ansi_disablecursor = "\e[?25l" + +generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printed start_time = time_ns() - debug_output = devnull # or stdout sysimg = Base.unsafe_string(Base.JLOptions().image_file) # Extract the precompile statements from the precompile file - statements = Set{String}() + statements_step1 = Channel{String}(Inf) + statements_step2 = Channel{String}(Inf) # From hardcoded statements for statement in split(hardcoded_precompile_statements::String, '\n') - push!(statements, statement) + push!(statements_step1, statement) + end + + println("Collecting and executing precompile statements") + fancyprint && print(ansi_disablecursor) + print_state() + clock = @async begin + t = Timer(0; interval=1/10) + anim_chars = ["◐","◓","◑","◒"] + current = 1 + if fancyprint + while isopen(statements_step2) || !isempty(statements_step2) + print_state("clock" => anim_chars[current]) + wait(t) + current = current == 4 ? 1 : current + 1 + end + end + close(t) end # Collect statements from running the script - mktempdir() do prec_path + step1 = @async mktempdir() do prec_path + print_state("step1" => "R") # Also precompile a package here pkgname = "__PackagePrecompilationStatementModule" mkpath(joinpath(prec_path, pkgname, "src")) @@ -269,16 +286,24 @@ function generate_precompile_statements() $precompile_script """ run(`$(julia_exepath()) -O0 --sysimage $sysimg --trace-compile=$tmp_proc --startup-file=no -Cnative -e $s`) + n_step1 = 0 for f in (tmp_prec, tmp_proc) + isfile(f) || continue for statement in split(read(f, String), '\n') - occursin("Main.", statement) && continue - push!(statements, statement) + push!(statements_step1, statement) + n_step1 += 1 end end + close(statements_step1) + print_state("step1" => "F$n_step1") + return :ok end + !PARALLEL_PRECOMPILATION && wait(step1) - mktemp() do precompile_file, precompile_file_h + step2 = @async mktemp() do precompile_file, precompile_file_h + print_state("step2" => "R") # Collect statements from running a REPL process and replaying our REPL script + touch(precompile_file) pts, ptm = open_fake_pty() blackhole = Sys.isunix() ? "/dev/null" : "nul" if have_repl @@ -315,50 +340,71 @@ function generate_precompile_statements() close(output_copy) close(ptm) end - # wait for the definitive prompt before start writing to the TTY - readuntil(output_copy, JULIA_PROMPT) - sleep(0.1) - readavailable(output_copy) - # Input our script - if have_repl - precompile_lines = split(repl_script::String, '\n'; keepempty=false) - curr = 0 - for l in precompile_lines - sleep(0.1) - curr += 1 - print("\rGenerating REPL precompile statements... $curr/$(length(precompile_lines))") - # consume any other output - 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") - readuntil(output_copy, "\n") - # wait for the next prompt-like to appear - readuntil(output_copy, "\n") - strbuf = "" - while true - strbuf *= String(readavailable(output_copy)) - occursin(JULIA_PROMPT, strbuf) && break - occursin(PKG_PROMPT, strbuf) && break - occursin(SHELL_PROMPT, strbuf) && break - occursin(HELP_PROMPT, strbuf) && break + repl_inputter = @async begin + # wait for the definitive prompt before start writing to the TTY + readuntil(output_copy, JULIA_PROMPT) + sleep(0.1) + readavailable(output_copy) + # Input our script + if have_repl + precompile_lines = split(repl_script::String, '\n'; keepempty=false) + curr = 0 + for l in precompile_lines sleep(0.1) + curr += 1 + print_state("repl" => "$curr/$(length(precompile_lines))") + # consume any other output + 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") + readuntil(output_copy, "\n") + # wait for the next prompt-like to appear + readuntil(output_copy, "\n") + strbuf = "" + while !eof(output_copy) + strbuf *= String(readavailable(output_copy)) + occursin(JULIA_PROMPT, strbuf) && break + occursin(PKG_PROMPT, strbuf) && break + occursin(SHELL_PROMPT, strbuf) && break + occursin(HELP_PROMPT, strbuf) && break + sleep(0.1) + end end end - println() + write(ptm, "exit()\n") + wait(tee) + success(p) || Base.pipeline_error(p) + close(ptm) + write(debug_output, "\n#### FINISHED ####\n") + end + + n_step2 = 0 + precompile_copy = Base.BufferStream() + buffer_reader = @async for statement in eachline(precompile_copy) + print_state("step2" => "R$n_step2") + push!(statements_step2, statement) + n_step2 += 1 end - write(ptm, "exit()\n") - wait(tee) - success(p) || Base.pipeline_error(p) - close(ptm) - write(debug_output, "\n#### FINISHED ####\n") - - for statement in split(read(precompile_file, String), '\n') - # Main should be completely clean - occursin("Main.", statement) && continue - push!(statements, statement) + + open(precompile_file, "r") do io + while true + # We need to allways call eof(io) for bytesavailable(io) to work + eof(io) && istaskdone(repl_inputter) && eof(io) && break + if bytesavailable(io) == 0 + sleep(0.1) + continue + end + write(precompile_copy, readavailable(io)) + end end + close(precompile_copy) + wait(buffer_reader) + close(statements_step2) + print_state("step2" => "F$n_step2") + return :ok end + !PARALLEL_PRECOMPILATION && wait(step2) # Create a staging area where all the loaded packages are available PrecompileStagingArea = Module() @@ -368,63 +414,58 @@ function generate_precompile_statements() end end - # Execute the collected precompile statements n_succeeded = 0 - include_time = @elapsed for statement in sort!(collect(statements)) + # Make statements unique + statements = Set{String}() + # Execute the precompile statements + for sts in [statements_step1, statements_step2], statement in sts + # Main should be completely clean + occursin("Main.", statement) && continue + Base.in!(statement, statements) && continue # println(statement) - # XXX: skip some that are broken. these are caused by issue #39902 - occursin("Tuple{Artifacts.var\"#@artifact_str\", LineNumberNode, Module, Any, Any}", statement) && continue - occursin("Tuple{Base.Cartesian.var\"#@ncall\", LineNumberNode, Module, Int64, Any, Vararg{Any}}", statement) && continue - occursin("Tuple{Base.Cartesian.var\"#@ncall\", LineNumberNode, Module, Int32, Any, Vararg{Any}}", statement) && continue - occursin("Tuple{Base.Cartesian.var\"#@nloops\", LineNumberNode, Module, Any, Any, Any, Vararg{Any}}", statement) && continue - occursin("Tuple{Core.var\"#@doc\", LineNumberNode, Module, Vararg{Any}}", statement) && continue - # XXX: this is strange, as this isn't the correct representation of this - occursin("typeof(Core.IntrinsicFunction)", statement) && continue - # XXX: this is strange, as this method should not be getting compiled - occursin(", Core.Compiler.AbstractInterpreter, ", statement) && continue try ps = Meta.parse(statement) - isexpr(ps, :call) || continue + 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 popfirst!(ps.args) # precompile(...) ps.head = :tuple - l = ps.args[end] - if (isexpr(l, :tuple) || isexpr(l, :curly)) && length(l.args) > 0 # Tuple{...} or (...) - # XXX: precompile doesn't currently handle overloaded Vararg arguments very well. - # Replacing N with a large number works around it. - l = l.args[end] - if isexpr(l, :curly) && length(l.args) == 2 && l.args[1] === :Vararg # Vararg{T} - push!(l.args, 100) # form Vararg{T, 100} instead - end - end # println(ps) ps = Core.eval(PrecompileStagingArea, ps) - # XXX: precompile doesn't currently handle overloaded nospecialize arguments very well. - # Skipping them avoids the warning. - ms = length(ps) == 1 ? Base._methods_by_ftype(ps[1], 1, Base.get_world_counter()) : Base.methods(ps...) - ms isa Vector || continue - precompile(ps...) - n_succeeded += 1 - print("\rExecuting precompile statements... $n_succeeded/$(length(statements))") + if precompile(ps...) + n_succeeded += 1 + else + @warn "Failed to precompile expression" form=statement _module=nothing _file=nothing _line=0 + end + failed = length(statements) - n_succeeded + yield() # Make clock spinning + 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 end end + wait(clock) # Stop asynchronous printing + failed = length(statements) - n_succeeded + print_state("step3" => string("F$n_succeeded", failed > 0 ? " ($failed failed)" : "")) println() if have_repl # Seems like a reasonable number right now, adjust as needed # comment out if debugging script - n_succeeded > 1200 || @warn "Only $n_succeeded precompile statements" + n_succeeded > 1500 || @warn "Only $n_succeeded precompile statements" end + fetch(step1) == :ok || throw("Step 1 of collecting precompiles failed.") + fetch(step2) == :ok || throw("Step 2 of collecting precompiles failed.") + tot_time = time_ns() - start_time - include_time *= 1e9 - gen_time = tot_time - include_time println("Precompilation complete. Summary:") - print("Total ─────── "); Base.time_print(tot_time); println() - print("Generation ── "); Base.time_print(gen_time); print(" "); show(IOContext(stdout, :compact=>true), gen_time / tot_time * 100); println("%") - print("Execution ─── "); Base.time_print(include_time); print(" "); show(IOContext(stdout, :compact=>true), include_time / tot_time * 100); println("%") - + print("Total ─────── "); Base.time_print(tot_time); println() +finally + fancyprint && print(ansi_enablecursor) return end @@ -442,4 +483,13 @@ empty!(Base.ARGS) empty!(Core.ARGS) end # @eval +end # if + +println("Outputting sysimage file...") +let pre_output_time = time_ns() + # Print report after sysimage has been saved so all time spent can be captured + Base.postoutput() do + output_time = time_ns() - pre_output_time + print("Output ────── "); Base.time_print(output_time); println() + end end diff --git a/contrib/julia-config.jl b/contrib/julia-config.jl index ad275c078c49c..df17b967c1ed7 100755 --- a/contrib/julia-config.jl +++ b/contrib/julia-config.jl @@ -17,7 +17,7 @@ function shell_escape(str) end function libDir() - return if ccall(:jl_is_debugbuild, Cint, ()) != 0 + return if Base.isdebugbuild() if Base.DARWIN_FRAMEWORK joinpath(dirname(abspath(Libdl.dlpath(Base.DARWIN_FRAMEWORK_NAME * "_debug"))),"lib") else @@ -33,7 +33,7 @@ function libDir() end function frameworkDir() - libjulia = ccall(:jl_is_debugbuild, Cint, ()) != 0 ? + libjulia = Base.isdebugbuild() ? Libdl.dlpath(Base.DARWIN_FRAMEWORK_NAME * "_debug") : Libdl.dlpath(Base.DARWIN_FRAMEWORK_NAME) normpath(joinpath(dirname(abspath(libjulia)),"..","..","..")) @@ -61,7 +61,7 @@ function ldlibs(doframework) # If the user wants the debug framework, DYLD_IMAGE_SUFFIX=_debug # should be used (refer to man 1 dyld). doframework && return "-framework $(Base.DARWIN_FRAMEWORK_NAME)" - libname = if ccall(:jl_is_debugbuild, Cint, ()) != 0 + libname = if Base.isdebugbuild() "julia-debug" else "julia" @@ -77,7 +77,7 @@ end function cflags(doframework) flags = IOBuffer() - print(flags, "-std=gnu99") + print(flags, "-std=gnu11") if doframework include = shell_escape(frameworkDir()) print(flags, " -F", include) diff --git a/contrib/julia.appdata.xml b/contrib/julia.appdata.xml index 3d451197098b2..f53a653af78d0 100644 --- a/contrib/julia.appdata.xml +++ b/contrib/julia.appdata.xml @@ -28,7 +28,7 @@ - https://julialang.org/images/julia-gnome.png + https://julialang.org/assets/images/julia-gnome.png https://julialang.org/ diff --git a/contrib/julia.desktop b/contrib/julia.desktop index 6b41981354769..037f6d865a9e4 100644 --- a/contrib/julia.desktop +++ b/contrib/julia.desktop @@ -1,8 +1,17 @@ +# To use uxterm, change to these values. +# +# Exec=uxterm -e julia +# Terminal=false +# +# To use a .png icon specify the full path and file extension. +# +# Icon=/usr/share/icons/hicolor/48x48/apps/julia.png +# [Desktop Entry] Name=Julia -Comment=High-level, high-performance dynamic language for technical computing +Comment=High-performance language for technical computing Exec=julia Icon=julia Terminal=true Type=Application -Categories=Development;ComputerScience;Building;Science;Math;NumericalAnalysis;ParallelComputing;DataVisualization;ConsoleOnly; +Categories=Development; diff --git a/contrib/julia.png b/contrib/julia.png new file mode 100644 index 0000000000000..d05f2861b784d Binary files /dev/null and b/contrib/julia.png differ diff --git a/contrib/julia.svg b/contrib/julia.svg new file mode 100644 index 0000000000000..ed7f17bb32f18 --- /dev/null +++ b/contrib/julia.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/contrib/mac/app/Makefile b/contrib/mac/app/Makefile index edb6f868c9486..81b7e47cdf2cf 100644 --- a/contrib/mac/app/Makefile +++ b/contrib/mac/app/Makefile @@ -50,6 +50,9 @@ dmg/$(APP_NAME): startup.applescript julia.icns make -C $(JULIAHOME) binary-dist tar zxf $(JULIAHOME)/$(JULIA_BINARYDIST_FILENAME).tar.gz -C $@/Contents/Resources/julia --strip-components 1 find $@/Contents/Resources/julia -type f -exec chmod -w {} \; + # Even though the tarball may already be signed, we re-sign here to make it easier to add + # unsigned executables (like the app launcher) and whatnot, without needing to maintain lists + # of what is or is not signed. Codesigning is cheap, so might as well do it early and often. if [ -n "$$MACOS_CODESIGN_IDENTITY" ]; then \ echo "Codesigning with identity $$MACOS_CODESIGN_IDENTITY"; \ MACHO_FILES=$$(find "$@" -type f -perm -0111 | cut -d: -f1); \ diff --git a/contrib/mac/app/renotarize_dmg.sh b/contrib/mac/app/renotarize_dmg.sh index f0d6d0a197e5f..c532ddb778e24 100755 --- a/contrib/mac/app/renotarize_dmg.sh +++ b/contrib/mac/app/renotarize_dmg.sh @@ -39,7 +39,7 @@ APP_NAME=$(basename dmg/*.app) VOL_NAME=$(basename /Volumes/Julia-*) if [[ ! -d dmg/${APP_NAME} ]]; then - echo "ERORR: Unable to auto-detect APP_NAME, check dmg folder!" >&2 + echo "ERROR: Unable to auto-detect APP_NAME, check dmg folder!" >&2 exit 1 fi # Unmount everything again diff --git a/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json b/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json index 2fe2dbc16b987..5071eb935ab9b 100644 --- a/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -65,4 +65,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/Contents.json b/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/Contents.json index da4a164c91865..2d92bd53fdb22 100644 --- a/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/Contents.json +++ b/contrib/mac/frameworkapp/JuliaLauncher/Assets.xcassets/Contents.json @@ -3,4 +3,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/contrib/mac/frameworkapp/Makefile b/contrib/mac/frameworkapp/Makefile index fbca4577df1bc..c94a5be145db9 100644 --- a/contrib/mac/frameworkapp/Makefile +++ b/contrib/mac/frameworkapp/Makefile @@ -116,8 +116,8 @@ signedproductarchive: $(PRODUCTARCHIVE) mv $<.signed $< clean: - -rm -rf $(XCARCHIVE) $(XCDERIVEDDATA) $(XCEXPORT) - -rm -rf $(FRAMEWORK_DESTDIR) + rm -rf $(XCARCHIVE) $(XCDERIVEDDATA) $(XCEXPORT) + rm -rf $(FRAMEWORK_DESTDIR) -rm -f $(PRODUCTARCHIVE) .PHONY: appexport clean productarchive signedproductarchive diff --git a/contrib/mac/frameworkapp/installresources/conclusion.rtf b/contrib/mac/frameworkapp/installresources/conclusion.rtf index 8d794ae31c04b..1f3e60f5f5277 100644 --- a/contrib/mac/frameworkapp/installresources/conclusion.rtf +++ b/contrib/mac/frameworkapp/installresources/conclusion.rtf @@ -77,4 +77,4 @@ Conclusion\ \f1 \cb1 \ \pard\pardeftab720\partightenfactor0 -\f2 \cf0 \cb2 ln -s INSTALL_LOCATION/Julia.framework/Helpers/julia DIR_IN_PATH/julia} \ No newline at end of file +\f2 \cf0 \cb2 ln -s INSTALL_LOCATION/Julia.framework/Helpers/julia DIR_IN_PATH/julia} diff --git a/contrib/mac/frameworkapp/installresources/readme.rtf b/contrib/mac/frameworkapp/installresources/readme.rtf index d555047dd5c1c..935d9a5f6a576 100644 --- a/contrib/mac/frameworkapp/installresources/readme.rtf +++ b/contrib/mac/frameworkapp/installresources/readme.rtf @@ -28,4 +28,4 @@ Readme\ \f2 \cb2 $HOME \f1 \cb1 usually expands to \f2 \cb2 /Users/username -\f1 \cb1 ).} \ No newline at end of file +\f1 \cb1 ).} diff --git a/contrib/normalize_triplet.py b/contrib/normalize_triplet.py index 43c9d492a4b2e..77c047b360b76 100755 --- a/contrib/normalize_triplet.py +++ b/contrib/normalize_triplet.py @@ -2,8 +2,8 @@ import re, sys -# This script designed to mimick `src/PlatformNames.jl` in `BinaryProvider.jl`, which has -# a method `platform_key_abi()` to parse uname-like output into something standarized. +# This script designed to mimic `src/PlatformNames.jl` in `BinaryProvider.jl`, which has +# a method `platform_key_abi()` to parse uname-like output into something standardized. if len(sys.argv) < 2: print("Usage: {} [] []".format(sys.argv[0])) @@ -113,9 +113,16 @@ def p(x): if not sys.argv[2]: libgfortran_version = "libgfortran5" else: - # Take the last thing that looks like a version number, and extract its major component - version_numbers = list(filter(lambda x: re.match("\d+\.\d+(\.\d+)?", x), sys.argv[2].split())) - major_ver = int(version_numbers[-1].split('.')[0]) + # Grab the first number in the last word with a number + # This will be the major version number. + major_ver = -1 + words = sys.argv[2].split() + for word in words[::-1]: + major_ver = re.search("[0-9]+", word) + if major_ver: + major_ver = int(major_ver.group()) + break + if major_ver <= 6: libgfortran_version = "libgfortran3" elif major_ver <= 7: diff --git a/contrib/prepare_release.sh b/contrib/prepare_release.sh index 7d4e55e3a402e..2772e44a858f1 100755 --- a/contrib/prepare_release.sh +++ b/contrib/prepare_release.sh @@ -56,12 +56,6 @@ curl -L -o julia-$version-win32.exe \ $julianightlies/winnt/x86/$majmin/julia-$majminpatch-$shashort-win32.exe cp julia-$version-win32.exe julia-$majmin-latest-win32.exe -if [ -e codesign.sh ]; then - # code signing needs to run on windows, script is not checked in since it - # hard-codes a few things. TODO: see if signtool.exe can run in wine - ./codesign.sh -fi - shasum -a 256 julia-$version* | grep -v -e sha256 -e md5 -e asc > julia-$version.sha256 md5sum julia-$version* | grep -v -e sha256 -e md5 -e asc > julia-$version.md5 diff --git a/contrib/print_sorted_stdlibs.jl b/contrib/print_sorted_stdlibs.jl index bbf890328cb4e..6bc2023c4f1cc 100644 --- a/contrib/print_sorted_stdlibs.jl +++ b/contrib/print_sorted_stdlibs.jl @@ -12,11 +12,12 @@ function check_flag(flag) end if check_flag("--help") || check_flag("-h") - println("Usage: julia print_sorted_stdlibs.jl [stdlib_dir] [--exclude-jlls]") + println("Usage: julia print_sorted_stdlibs.jl [stdlib_dir] [--exclude-jlls] [--exclude-sysimage]") end # Allow users to ask for JLL or no JLLs exclude_jlls = check_flag("--exclude-jlls") +exclude_sysimage = check_flag("--exclude-sysimage") # Default to the `stdlib/vX.Y` directory STDLIB_DIR = get(ARGS, 1, joinpath(@__DIR__, "..", "usr", "share", "julia", "stdlib")) @@ -27,9 +28,9 @@ end project_deps = Dict{String,Set{String}}() for project_dir in readdir(STDLIB_DIR, join=true) - files = readdir(project_dir) - if "Project.toml" in files - project = TOML.parsefile(joinpath(project_dir, "Project.toml")) + project_file = joinpath(project_dir, "Project.toml") + if isfile(project_file) + project = TOML.parsefile(project_file) if !haskey(project, "name") continue @@ -80,12 +81,20 @@ if exclude_jlls filter!(p -> !endswith(p, "_jll"), sorted_projects) end +if exclude_sysimage + loaded_modules = Set(map(k->k.name, collect(keys(Base.loaded_modules)))) + filter!(p->!in(p, loaded_modules), sorted_projects) +end + # Print out sorted projects, ready to be pasted into `sysimg.jl` last_depth = 0 println(" # Stdlibs sorted in dependency, then alphabetical, order by contrib/print_sorted_stdlibs.jl") if exclude_jlls println(" # Run with the `--exclude-jlls` option to filter out all JLL packages") end +if exclude_sysimage + println(" # Run with the `--exclude-sysimage` option to filter out all packages included in the system image") +end println(" stdlibs = [") println(" # No dependencies") for p in sorted_projects diff --git a/contrib/refresh_checksums.mk b/contrib/refresh_checksums.mk index 898bd5841ee82..f67088141ccd4 100644 --- a/contrib/refresh_checksums.mk +++ b/contrib/refresh_checksums.mk @@ -24,11 +24,11 @@ CLANG_TRIPLETS=$(filter %-darwin %-freebsd,$(TRIPLETS)) NON_CLANG_TRIPLETS=$(filter-out %-darwin %-freebsd,$(TRIPLETS)) # These are the projects currently using BinaryBuilder; both GCC-expanded and non-GCC-expanded: -BB_PROJECTS=mbedtls libssh2 nghttp2 mpfr curl libgit2 pcre libuv unwind llvmunwind dsfmt objconv p7zip zlib libsuitesparse openlibm blastrampoline +BB_PROJECTS=mbedtls libssh2 nghttp2 mpfr curl libgit2 pcre libuv unwind llvmunwind dsfmt objconv p7zip zlib libsuitesparse openlibm blastrampoline libtracyclient BB_GCC_EXPANDED_PROJECTS=openblas csl -BB_CXX_EXPANDED_PROJECTS=gmp llvm clang llvm-tools +BB_CXX_EXPANDED_PROJECTS=gmp llvm clang llvm-tools lld # These are non-BB source-only deps -NON_BB_PROJECTS=patchelf mozillacert lapack libwhich utf8proc +NON_BB_PROJECTS=patchelf mozillacert lapack libwhich utf8proc ittapi ifneq ($(VERBOSE),1) QUIET_MAKE := -s @@ -80,8 +80,12 @@ $(foreach project,$(BB_CXX_EXPANDED_PROJECTS),$(foreach triplet,$(CLANG_TRIPLETS # Special libLLVM_asserts_jll/LLVM_assert_jll targets $(foreach triplet,$(NON_CLANG_TRIPLETS),$(foreach cxxstring_abi,cxx11 cxx03,$(eval $(call checksum_dep,llvm,$(triplet)-$(cxxstring_abi),assert)))) +$(foreach triplet,$(NON_CLANG_TRIPLETS),$(foreach cxxstring_abi,cxx11 cxx03,$(eval $(call checksum_dep,clang,$(triplet)-$(cxxstring_abi),assert)))) +$(foreach triplet,$(NON_CLANG_TRIPLETS),$(foreach cxxstring_abi,cxx11 cxx03,$(eval $(call checksum_dep,lld,$(triplet)-$(cxxstring_abi),assert)))) $(foreach triplet,$(NON_CLANG_TRIPLETS),$(foreach cxxstring_abi,cxx11 cxx03,$(eval $(call checksum_dep,llvm-tools,$(triplet)-$(cxxstring_abi),assert)))) $(foreach triplet,$(CLANG_TRIPLETS),$(eval $(call checksum_dep,llvm,$(triplet),assert))) +$(foreach triplet,$(CLANG_TRIPLETS),$(eval $(call checksum_dep,clang,$(triplet),assert))) +$(foreach triplet,$(CLANG_TRIPLETS),$(eval $(call checksum_dep,lld,$(triplet),assert))) $(foreach triplet,$(CLANG_TRIPLETS),$(eval $(call checksum_dep,llvm-tools,$(triplet),assert))) # External stdlibs diff --git a/contrib/relative_path.py b/contrib/relative_path.py index b9d3d1e5bca7e..9a60607d64d9b 100755 --- a/contrib/relative_path.py +++ b/contrib/relative_path.py @@ -7,4 +7,4 @@ # shells and whatnot during the build are all POSIX shells/cygwin. We rely on the build # system itself to canonicalize to `\` when it needs to, and deal with the shell escaping # and whatnot at the latest possible moment. -sys.stdout.write(os.path.relpath(sys.argv[2], sys.argv[1]).replace(os.path.sep, '/')) \ No newline at end of file +sys.stdout.write(os.path.relpath(sys.argv[2], sys.argv[1]).replace(os.path.sep, '/')) diff --git a/contrib/tsan/Make.user.tsan b/contrib/tsan/Make.user.tsan index 01c9874a85182..b192c36e4cfee 100644 --- a/contrib/tsan/Make.user.tsan +++ b/contrib/tsan/Make.user.tsan @@ -11,6 +11,3 @@ USE_BINARYBUILDER_LLVM=1 override SANITIZE=1 override SANITIZE_THREAD=1 - -# default to a debug build for better line number reporting -override JULIA_BUILD_MODE=debug diff --git a/contrib/windows/build-installer.iss b/contrib/windows/build-installer.iss index 4f5f0259d2f2c..a63cf853d4373 100644 --- a/contrib/windows/build-installer.iss +++ b/contrib/windows/build-installer.iss @@ -150,6 +150,9 @@ begin case CurPageID of wpWelcome: WizardForm.Color := WizardForm.WelcomePage.Color; wpFinished: WizardForm.Color := WizardForm.FinishedPage.Color; + + //change button text from "next" to "install" when ReadyPage is disabled. + wpSelectTasks: WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall); else WizardForm.Color := WizardForm.InnerPage.Color; end; diff --git a/deps/Makefile b/deps/Makefile index ac0dbe7afcb1a..62bb85e72c492 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -8,7 +8,6 @@ BUILDDIR := scratch else BUILDDIR := . endif -include $(SRCDIR)/Versions.make include $(JULIAHOME)/Make.inc include $(SRCDIR)/tools/common.mk include $(SRCDIR)/tools/git-external.mk @@ -25,9 +24,9 @@ BUILDDIR := $(BUILDDIR)$(MAYBE_HOST) # # autoconf configure-driven scripts: pcre unwind gmp mpfr patchelf libuv curl # custom Makefile rules: openlibm dsfmt libsuitesparse lapack blastrampoline openblas utf8proc objconv libwhich -# CMake libs: llvm llvmunwind libgit2 libssh2 mbedtls +# CMake libs: llvm llvmunwind libgit2 libssh2 mbedtls libtracyclient # -# downloadable via git: llvm-svn, libuv, libopenlibm, utf8proc, libgit2, libssh2 +# downloadable via git: llvm-svn, libuv, libopenlibm, utf8proc, libgit2, libssh2, libtracyclient # # to debug 'define' rules, replace eval at the usage site with info or error @@ -51,6 +50,10 @@ ifeq ($(USE_SYSTEM_LIBUV), 0) DEP_LIBS += libuv endif +ifeq ($(WITH_TRACY), 1) +DEP_LIBS += libtracyclient +endif + ifeq ($(DISABLE_LIBUNWIND), 0) ifeq ($(USE_SYSTEM_LIBUNWIND), 0) ifeq ($(OS), Linux) @@ -89,6 +92,10 @@ ifeq ($(USE_SYSTEM_LLVM), 0) DEP_LIBS += llvm endif +ifeq ($(USE_SYSTEM_LLD), 0) +DEP_LIBS += lld +endif + ifeq ($(USE_SYSTEM_PCRE), 0) DEP_LIBS += pcre endif @@ -148,6 +155,16 @@ ifeq ($(USE_SYSTEM_P7ZIP), 0) DEP_LIBS += p7zip endif +ifeq ($(USE_INTEL_JITEVENTS), 1) +ifeq ($(USE_BINARYBUILDER_LLVM), 0) +DEP_LIBS += ittapi +endif +endif + +ifeq ($(WITH_ITTAPI),1) +DEP_LIBS += ittapi +endif + # Only compile standalone LAPACK if we are not using OpenBLAS. # OpenBLAS otherwise compiles LAPACK as part of its build. @@ -165,11 +182,13 @@ DEP_LIBS += libwhich endif endif +DEP_LIBS_STAGED := $(DEP_LIBS) + # list all targets DEP_LIBS_STAGED_ALL := llvm llvm-tools clang llvmunwind unwind libuv pcre \ openlibm dsfmt blastrampoline openblas lapack gmp mpfr patchelf utf8proc \ objconv mbedtls libssh2 nghttp2 curl libgit2 libwhich zlib p7zip csl \ - libsuitesparse + libsuitesparse lld libtracyclient ittapi DEP_LIBS_ALL := $(DEP_LIBS_STAGED_ALL) ifneq ($(USE_BINARYBUILDER_OPENBLAS),0) @@ -204,6 +223,7 @@ distcleanall: $(addprefix distclean-, $(DEP_LIBS_ALL)) getall: $(addprefix get-, $(DEP_LIBS_ALL)) include $(SRCDIR)/csl.mk +include $(SRCDIR)/ittapi.mk include $(SRCDIR)/llvm.mk include $(SRCDIR)/libuv.mk include $(SRCDIR)/pcre.mk @@ -226,5 +246,6 @@ include $(SRCDIR)/curl.mk include $(SRCDIR)/libgit2.mk include $(SRCDIR)/libwhich.mk include $(SRCDIR)/p7zip.mk +include $(SRCDIR)/libtracyclient.mk include $(SRCDIR)/tools/uninstallers.mk diff --git a/deps/Versions.make b/deps/Versions.make deleted file mode 100644 index 1d510ee4911b6..0000000000000 --- a/deps/Versions.make +++ /dev/null @@ -1,117 +0,0 @@ -## Dependencies and where to find them, listed in alphabetical order - -# To define a new dependency, you need to know the following pieces of information: -# -# * The Makefile variable stem; for LibCURL this is just "CURL". -# * The JLL name; for GMP this is "GMP", while for LLVM it could be "LLVM_full" or "LLVM_full_assert" -# * The upstream source version; for dSFMT this is currently "2.2.3" -# -# Everything else will be auto-generated. In particular, the version listed here -# represents the upstream source version; the JLL binary version that gets downloaded is -# controlled by the `Project.toml` files in `stdlib/XXX_jll/`. - -# Compiler Support Libraries -CSL_JLL_NAME := CompilerSupportLibraries - -# Clang (paired with LLVM, only here as a JLL download) -CLANG_JLL_NAME := Clang -CLANG_JLL_VER := 13.0.1+0 - -# DSFMT -DSFMT_VER := 2.2.4 -DSFMT_JLL_NAME := dSFMT - -# GMP -GMP_VER := 6.2.1 -GMP_JLL_NAME := GMP - -# LibCURL -CURL_VER := 7.81.0 -CURL_JLL_NAME := LibCURL - -# LAPACK, source-only -LAPACK_VER := 3.9.0 - -# LibGit2 -LIBGIT2_JLL_NAME := LibGit2 - -# LibSSH2 -LIBSSH2_VER := 1.10.2 -LIBSSH2_JLL_NAME := LibSSH2 - -# LibUV -LIBUV_VER := 2 -LIBUV_JLL_NAME := LibUV - -# LLVM -LLVM_VER := 13.0.1 -LLVM_ASSERT_JLL_VER := 13.0.1+0 -LLVM_JLL_NAME := libLLVM - -# LLVM_tools (downloads LLVM_jll to get things like `lit` and `opt`) -LLVM_TOOLS_JLL_NAME := LLVM -LLVM_TOOLS_JLL_VER := 13.0.1+0 -LLVM_TOOLS_ASSERT_JLL_VER := 13.0.1+0 - -# LLVM libunwind -LLVMUNWIND_VER := 12.0.1 -LLVMUNWIND_JLL_NAME := LLVMLibUnwind - -# MbedTLS -MBEDTLS_VER := 2.28.0 -MBEDTLS_JLL_NAME := MbedTLS - -# MPFR -MPFR_VER := 4.1.0 -MPFR_JLL_NAME := MPFR - -# nghttp2 -NGHTTP2_VER := 1.41.0 -NGHTTP2_JLL_NAME := nghttp2 - -# Objconv (we don't ship this, so no need for a fake JLL; therefore we specify the JLL_VER here) -OBJCONV_VER := 2.49.1 -OBJCONV_JLL_NAME := Objconv -OBJCONV_JLL_VER := 2.49.1+0 - -# blastrampoline -BLASTRAMPOLINE_VER := 5.0.1 -BLASTRAMPOLINE_JLL_NAME := libblastrampoline - -# OpenBLAS -OPENBLAS_VER := 0.3.17 -OPENBLAS_JLL_NAME := OpenBLAS - -# OpenLibm -OPENLIBM_VER := 0.8.1 -OPENLIBM_JLL_NAME := OpenLibm - -# Patchelf (we don't ship this or even use a JLL, we just always build it) -PATCHELF_VER := 0.13 - -# p7zip -P7ZIP_VER := 16.2.0 -P7ZIP_JLL_NAME := p7zip - -# PCRE -PCRE_VER := 10.36 -PCRE_JLL_NAME := PCRE2 - -# SuiteSparse -LIBSUITESPARSE_VER := 5.10.1 -LIBSUITESPARSE_JLL_NAME := SuiteSparse - -# unwind -UNWIND_VER := 1.5.0 -UNWIND_VER_TAG := 1.5 -UNWIND_JLL_NAME := LibUnwind -UNWIND_JLL_VER := 1.5.0+1 - -# zlib -ZLIB_VER := 1.2.11 -ZLIB_JLL_NAME := Zlib - -# 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 := 2022-02-01 diff --git a/deps/blastrampoline.mk b/deps/blastrampoline.mk index a29b9b19e0eaa..bd1cb65c6ae2d 100644 --- a/deps/blastrampoline.mk +++ b/deps/blastrampoline.mk @@ -6,16 +6,25 @@ BLASTRAMPOLINE_GIT_URL := https://github.com/JuliaLinearAlgebra/libblastrampolin BLASTRAMPOLINE_TAR_URL = https://api.github.com/repos/JuliaLinearAlgebra/libblastrampoline/tarball/$1 $(eval $(call git-external,blastrampoline,BLASTRAMPOLINE,,,$(BUILDDIR))) +BLASTRAMPOLINE_BUILD_OPTS := $(MAKE_COMMON) CC="$(CC) $(SANITIZE_OPTS)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" +BLASTRAMPOLINE_BUILD_OPTS += ARCH="$(ARCH)" OS="$(OS)" + $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/build-configured: $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/source-extracted mkdir -p $(dir $@) echo 1 > $@ +BLASTRAMPOLINE_BUILD_ROOT := $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/src $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/build-compiled: $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/build-configured - cd $(dir $@)/src && $(MAKE) $(MAKE_COMMON) + cd $(dir $@)/src && $(MAKE) $(BLASTRAMPOLINE_BUILD_OPTS) +ifeq ($(OS), WINNT) + # Windows doesn't like soft link, use hard link + cd $(BLASTRAMPOLINE_BUILD_ROOT)/build/ && \ + cp -f --dereference --link libblastrampoline.dll libblastrampoline.dll +endif echo 1 > $@ define BLASTRAMPOLINE_INSTALL - $(MAKE) -C $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/src $(MAKE_COMMON) install + $(MAKE) -C $(BLASTRAMPOLINE_BUILD_ROOT) install $(BLASTRAMPOLINE_BUILD_OPTS) DESTDIR="$2" endef $(eval $(call staged-install, \ blastrampoline,$(BLASTRAMPOLINE_SRC_DIR), \ @@ -23,6 +32,11 @@ $(eval $(call staged-install, \ $$(BLASTRAMPOLINE_OBJ_TARGET), \ $$(INSTALL_NAME_CMD)libblastrampoline.$$(SHLIB_EXT) $$(build_shlibdir)/libblastrampoline.$$(SHLIB_EXT))) +clean-blastrampoline: + -$(MAKE) -C $(BLASTRAMPOLINE_BUILD_ROOT) clean + -$(RM) $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/build-compiled \ + $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/build-configured + get-blastrampoline: $(BLASTRAMPOLINE_SRC_FILE) extract-blastrampoline: $(BUILDDIR)/$(BLASTRAMPOLINE_SRC_DIR)/source-extracted configure-blastrampoline: extract-blastrampoline diff --git a/deps/blastrampoline.version b/deps/blastrampoline.version index 86d77ab5bf293..616300377e3e6 100644 --- a/deps/blastrampoline.version +++ b/deps/blastrampoline.version @@ -1,2 +1,7 @@ -BLASTRAMPOLINE_BRANCH=v3.0.4 -BLASTRAMPOLINE_SHA1=23de7a09bf354fe6f655c457bab5bf47fdd2486d +## jll artifact +BLASTRAMPOLINE_JLL_NAME := libblastrampoline + +## source build +BLASTRAMPOLINE_VER := 5.8.0 +BLASTRAMPOLINE_BRANCH=v5.8.0 +BLASTRAMPOLINE_SHA1=81316155d4838392e8462a92bcac3eebe9acd0c7 diff --git a/deps/checksums/DelimitedFiles-db79c842f95f55b1f8d8037c0d3363ab21cd3b90.tar.gz/md5 b/deps/checksums/DelimitedFiles-db79c842f95f55b1f8d8037c0d3363ab21cd3b90.tar.gz/md5 new file mode 100644 index 0000000000000..9c6e4e44927fe --- /dev/null +++ b/deps/checksums/DelimitedFiles-db79c842f95f55b1f8d8037c0d3363ab21cd3b90.tar.gz/md5 @@ -0,0 +1 @@ +ee5afca99801e37fd3a42a9455ae986b diff --git a/deps/checksums/DelimitedFiles-db79c842f95f55b1f8d8037c0d3363ab21cd3b90.tar.gz/sha512 b/deps/checksums/DelimitedFiles-db79c842f95f55b1f8d8037c0d3363ab21cd3b90.tar.gz/sha512 new file mode 100644 index 0000000000000..69a50a7282781 --- /dev/null +++ b/deps/checksums/DelimitedFiles-db79c842f95f55b1f8d8037c0d3363ab21cd3b90.tar.gz/sha512 @@ -0,0 +1 @@ +2adec92de521df1668eb13f2903ffdb01efd6afa5f04ce6fbd1737caa4948f7b629cdda7f75a895853a0cd49dccf8b388860d5c19c29e4d4aad6c7f8fa6b7209 diff --git a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/md5 b/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/md5 deleted file mode 100644 index e9e9e90dc3db5..0000000000000 --- a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -cbf1ec373e14a1417e40bc6c672ff5ff diff --git a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/sha512 b/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/sha512 deleted file mode 100644 index 85e24b205834c..0000000000000 --- a/deps/checksums/Downloads-2a21b1536aec0219c6bdb78dbb6570fc31a40983.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -c14e843cfe11e4073f244c703573d6a3b9a8d3c8da0d6e0d23b3d63925c9d401c6c7f012ee96f010fa75eafa8a77efb714477b767d56dad50fbc71f8888afc8d diff --git a/deps/checksums/Downloads-f97c72fbd726e208a04c53791b35cc34c747569f.tar.gz/md5 b/deps/checksums/Downloads-f97c72fbd726e208a04c53791b35cc34c747569f.tar.gz/md5 new file mode 100644 index 0000000000000..4e70641a4a08b --- /dev/null +++ b/deps/checksums/Downloads-f97c72fbd726e208a04c53791b35cc34c747569f.tar.gz/md5 @@ -0,0 +1 @@ +fa2c90db0e7aa73186c491aa2f03bb2b diff --git a/deps/checksums/Downloads-f97c72fbd726e208a04c53791b35cc34c747569f.tar.gz/sha512 b/deps/checksums/Downloads-f97c72fbd726e208a04c53791b35cc34c747569f.tar.gz/sha512 new file mode 100644 index 0000000000000..3f54f39d35ac6 --- /dev/null +++ b/deps/checksums/Downloads-f97c72fbd726e208a04c53791b35cc34c747569f.tar.gz/sha512 @@ -0,0 +1 @@ +d36737b946af5e720402ce4f25e4c69c740bdbdc174385d6448c3660b26fffe34c14af7c4dd4d26ad864ad12771cabdf922c8b3cf4423167a46cdf3001ede125 diff --git a/deps/checksums/LibCURL-04c450c17024d5b49cb30013f1409306efd35203.tar.gz/md5 b/deps/checksums/LibCURL-04c450c17024d5b49cb30013f1409306efd35203.tar.gz/md5 deleted file mode 100644 index d10b369971129..0000000000000 --- a/deps/checksums/LibCURL-04c450c17024d5b49cb30013f1409306efd35203.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -6a545e3c5dc4a0d7fe73435ec4c45dea diff --git a/deps/checksums/LibCURL-04c450c17024d5b49cb30013f1409306efd35203.tar.gz/sha512 b/deps/checksums/LibCURL-04c450c17024d5b49cb30013f1409306efd35203.tar.gz/sha512 deleted file mode 100644 index 4f77a3abbb3c2..0000000000000 --- a/deps/checksums/LibCURL-04c450c17024d5b49cb30013f1409306efd35203.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -1308d4efde43eebd70646a77e4cf2d4a850a7c33d4a26018a1e84b4e7e1fb525ae193385fef7d47c405dbba0d685523d4b593702d93f441bcf8a495cc21fff0e diff --git a/deps/checksums/NetworkOptions-01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353.tar.gz/md5 b/deps/checksums/NetworkOptions-01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353.tar.gz/md5 deleted file mode 100644 index 385b70d612e3d..0000000000000 --- a/deps/checksums/NetworkOptions-01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -38005620dd59d364a3705127a2548b27 diff --git a/deps/checksums/NetworkOptions-01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353.tar.gz/sha512 b/deps/checksums/NetworkOptions-01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353.tar.gz/sha512 deleted file mode 100644 index 756079a989580..0000000000000 --- a/deps/checksums/NetworkOptions-01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -d7247ad3285d768514683693edea3ca47ad22fc3ad6c1fcd5fbc42a38ee31e44df0c33cae9ba8448f7c4cff59c52fb8f401163716f46fb0c53b2a7959ae4d885 diff --git a/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 new file mode 100644 index 0000000000000..9e91b76f9a3c8 --- /dev/null +++ b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/md5 @@ -0,0 +1 @@ +16bc9f2eefa3021e19a09ffefc84159b diff --git a/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 new file mode 100644 index 0000000000000..551f7c8da347c --- /dev/null +++ b/deps/checksums/NetworkOptions-f7bbeb66f05fc651adb12758b650e8630a998fbd.tar.gz/sha512 @@ -0,0 +1 @@ +5b53c09343e25b5bde7ea12c2119da656040ca5f62ce934f00f57945ce73dfaf26522da6a9a007ba06ac6fd75a285cbcbdf5edaf9113faa7bba0398294fbd684 diff --git a/deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/md5 b/deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/md5 new file mode 100644 index 0000000000000..08f5ccda57979 --- /dev/null +++ b/deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/md5 @@ -0,0 +1 @@ +c135dc6ed97656fe956d9ee5cf3cbc55 diff --git a/deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/sha512 b/deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/sha512 new file mode 100644 index 0000000000000..957075f0f281a --- /dev/null +++ b/deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/sha512 @@ -0,0 +1 @@ +2ae67fd4c5e1bf83df5df836fcd69afc0fb8454723043d32de9c7bc29feedf390adb76efda52e79937ea801ff21b5f4ea875469136424e2889904130b247b52a diff --git a/deps/checksums/Pkg-e31a3dc77201e1c7c469f6d4572c521f93fefb20.tar.gz/md5 b/deps/checksums/Pkg-e31a3dc77201e1c7c469f6d4572c521f93fefb20.tar.gz/md5 deleted file mode 100644 index f4cc3e5cde47d..0000000000000 --- a/deps/checksums/Pkg-e31a3dc77201e1c7c469f6d4572c521f93fefb20.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -df5033e44bde58e85642eabe9a3a118b diff --git a/deps/checksums/Pkg-e31a3dc77201e1c7c469f6d4572c521f93fefb20.tar.gz/sha512 b/deps/checksums/Pkg-e31a3dc77201e1c7c469f6d4572c521f93fefb20.tar.gz/sha512 deleted file mode 100644 index de5c95167ce9b..0000000000000 --- a/deps/checksums/Pkg-e31a3dc77201e1c7c469f6d4572c521f93fefb20.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -d3630f9fab8b72c9a42d5bb43a7ad4e9e024510b189dd63c581e989960d6478bd6c6c6676f702a0fea8be67c58182a7febd2b63c5934bc86068c7cd4168cdf9b diff --git a/deps/checksums/SHA-2d1f84e6f8417a1a368de48318640d948b023e7a.tar.gz/md5 b/deps/checksums/SHA-2d1f84e6f8417a1a368de48318640d948b023e7a.tar.gz/md5 new file mode 100644 index 0000000000000..f682cf3518658 --- /dev/null +++ b/deps/checksums/SHA-2d1f84e6f8417a1a368de48318640d948b023e7a.tar.gz/md5 @@ -0,0 +1 @@ +de53629eb0b1ce98ac6b245bdbf14e9d diff --git a/deps/checksums/SHA-2d1f84e6f8417a1a368de48318640d948b023e7a.tar.gz/sha512 b/deps/checksums/SHA-2d1f84e6f8417a1a368de48318640d948b023e7a.tar.gz/sha512 new file mode 100644 index 0000000000000..870098ef7aada --- /dev/null +++ b/deps/checksums/SHA-2d1f84e6f8417a1a368de48318640d948b023e7a.tar.gz/sha512 @@ -0,0 +1 @@ +71cdc58b03cc4f42f8c4b9c2353d6f94d77b4ac5c9d374387d435c57ba85e966f3be4e8c8447b34e184cb8e665c42b3cd2c9d9742c86f7fb5c71a85df5087966 diff --git a/deps/checksums/SHA-57c3a8c8358021b7a58526364e6885768fd95de2.tar.gz/md5 b/deps/checksums/SHA-57c3a8c8358021b7a58526364e6885768fd95de2.tar.gz/md5 deleted file mode 100644 index 1bcc55fb297fa..0000000000000 --- a/deps/checksums/SHA-57c3a8c8358021b7a58526364e6885768fd95de2.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -96d57bc32f4f9bb8c66117c96e6243fc diff --git a/deps/checksums/SHA-57c3a8c8358021b7a58526364e6885768fd95de2.tar.gz/sha512 b/deps/checksums/SHA-57c3a8c8358021b7a58526364e6885768fd95de2.tar.gz/sha512 deleted file mode 100644 index 7f6c994b2fbb7..0000000000000 --- a/deps/checksums/SHA-57c3a8c8358021b7a58526364e6885768fd95de2.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -7243eddcccb634910f35252f30b29fe44c348955039bea56546765ab828bddb575a87603e91c89bee2619ea6e45b606c23fab2c8f4fc28c910571800732201a9 diff --git a/deps/checksums/SparseArrays-8affe9e499379616e33fc60a24bb31500e8423d7.tar.gz/md5 b/deps/checksums/SparseArrays-8affe9e499379616e33fc60a24bb31500e8423d7.tar.gz/md5 new file mode 100644 index 0000000000000..96861ba265b5f --- /dev/null +++ b/deps/checksums/SparseArrays-8affe9e499379616e33fc60a24bb31500e8423d7.tar.gz/md5 @@ -0,0 +1 @@ +e6dc511b49e07a167848adc4e12690d8 diff --git a/deps/checksums/SparseArrays-8affe9e499379616e33fc60a24bb31500e8423d7.tar.gz/sha512 b/deps/checksums/SparseArrays-8affe9e499379616e33fc60a24bb31500e8423d7.tar.gz/sha512 new file mode 100644 index 0000000000000..f503304f810e4 --- /dev/null +++ b/deps/checksums/SparseArrays-8affe9e499379616e33fc60a24bb31500e8423d7.tar.gz/sha512 @@ -0,0 +1 @@ +f40fd137ccd6651fc8b697f57cfcbd8e3feccb99f6a6b32fbaa69cc0160b78cefc662b914ff8f4e48478ca48f9583318a6030d922d43ed66f8db59fd5985f768 diff --git a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/md5 b/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/md5 deleted file mode 100644 index dc29e4d73a572..0000000000000 --- a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -c800768d427797e16c1dfb050e3615f6 diff --git a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/sha512 b/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/sha512 deleted file mode 100644 index 093da363d2c6a..0000000000000 --- a/deps/checksums/SparseArrays-aa51c9b82d952502139213715c9b077ec36c4623.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -fe7966ba74a473c99c591bcc18ac8827949b8a764bf4e70991567cb0eb7fc8adeb2674ee09467ef431684d4ad3dbfc81d5f3df0bea7c48ca3a212b8de446303a diff --git a/deps/checksums/Statistics-61a021bcb330e6c52f2435f2abaffc77875ab6f2.tar.gz/md5 b/deps/checksums/Statistics-61a021bcb330e6c52f2435f2abaffc77875ab6f2.tar.gz/md5 deleted file mode 100644 index ad05c56de3970..0000000000000 --- a/deps/checksums/Statistics-61a021bcb330e6c52f2435f2abaffc77875ab6f2.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -028a653f3b33540ca9d95f119bc62a06 diff --git a/deps/checksums/Statistics-61a021bcb330e6c52f2435f2abaffc77875ab6f2.tar.gz/sha512 b/deps/checksums/Statistics-61a021bcb330e6c52f2435f2abaffc77875ab6f2.tar.gz/sha512 deleted file mode 100644 index 62ba9972c2029..0000000000000 --- a/deps/checksums/Statistics-61a021bcb330e6c52f2435f2abaffc77875ab6f2.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -27e6f153f119638c4ed8e29127db10c1aff4fe5c14217a86a65d2bcb7ffbd3ed8e22613ed26fe0b9ffbb525ba00fc673be989d9da50e10fa12fd9a460ceeddcf diff --git a/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 new file mode 100644 index 0000000000000..7e7a889eecd29 --- /dev/null +++ b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/md5 @@ -0,0 +1 @@ +6564297a5f5971231809bf9940f68b98 diff --git a/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 new file mode 100644 index 0000000000000..bbe9b8bed6371 --- /dev/null +++ b/deps/checksums/Statistics-a3feba2bb63f06b7f40024185e9fa5f6385e2510.tar.gz/sha512 @@ -0,0 +1 @@ +22d14c82a30f3ec7af09028423cc823808abf86918d5707fd1fcf6ca20dea7871589da9b22e462d194e86fcee380f549aeb65f585048f00bf23281786b17e040 diff --git a/deps/checksums/Tar-0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595.tar.gz/md5 b/deps/checksums/Tar-0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595.tar.gz/md5 deleted file mode 100644 index 60ff3e45e5336..0000000000000 --- a/deps/checksums/Tar-0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -faf67b4fe8308fc6f9d7ed9bfbd855a9 diff --git a/deps/checksums/Tar-0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595.tar.gz/sha512 b/deps/checksums/Tar-0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595.tar.gz/sha512 deleted file mode 100644 index b2e786c204e70..0000000000000 --- a/deps/checksums/Tar-0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -d97bd68d6d651ec13f399f9124cc0abba7092532b467fdcbb9c886f5f3d121e79392bfce7c6e631178ff175131360415a98645661da0f9c83f27db50691ce133 diff --git a/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 new file mode 100644 index 0000000000000..40d52c2803746 --- /dev/null +++ b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/md5 @@ -0,0 +1 @@ +438818cad063d6808354a9b4aecd3001 diff --git a/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 new file mode 100644 index 0000000000000..27c57c5051212 --- /dev/null +++ b/deps/checksums/Tar-ff55460f4d329949661a33e6c8168ce6d890676c.tar.gz/sha512 @@ -0,0 +1 @@ +f9a6e7757bbcca09a84d92ab3a2690a51612c318bdfd98bbb4ffcef56305b019029838e5f1483c9febafa7ecb5e735e68855bc82d04b593af04a446e32436145 diff --git a/deps/checksums/blastrampoline b/deps/checksums/blastrampoline index c0afa49764b87..011b0f6e4704d 100644 --- a/deps/checksums/blastrampoline +++ b/deps/checksums/blastrampoline @@ -1,34 +1,34 @@ -blastrampoline-23de7a09bf354fe6f655c457bab5bf47fdd2486d.tar.gz/md5/b81efa951fd909591339189f5909ff6b -blastrampoline-23de7a09bf354fe6f655c457bab5bf47fdd2486d.tar.gz/sha512/1c2558bab0aeaa76e7094d8a6a9798c95f2cf4efe2960640b70f1fd752f3dfb73813d9de93b539426376571febaab22ac22c2f903ccdf3296c7b067af92fecdc -libblastrampoline.v5.0.1+0.aarch64-apple-darwin.tar.gz/md5/8b2b28517ef5db95a0b440f1a936422e -libblastrampoline.v5.0.1+0.aarch64-apple-darwin.tar.gz/sha512/3d479efc47b8c81fa85fd4d2a868a48304051432b92af90a2bcd2142673f2c422419731b8941f987aed429064532e8634ce3ea8f8d71222cf2d9b9e1e8ba2f7f -libblastrampoline.v5.0.1+0.aarch64-linux-gnu.tar.gz/md5/23e53049a0c30c8d24482a25954ee497 -libblastrampoline.v5.0.1+0.aarch64-linux-gnu.tar.gz/sha512/c5ba3609e5c47066d8a10acdd1c13e25a78bea6003a39a354000c832aeb1cf04a29392089600b10f0d6e5544aa910412bb50f238ac1f81d55ac15f70aaeb3161 -libblastrampoline.v5.0.1+0.aarch64-linux-musl.tar.gz/md5/5b6770a56cf3632473726a6da3da8ac4 -libblastrampoline.v5.0.1+0.aarch64-linux-musl.tar.gz/sha512/13f01e51b754a7bb4f78d0217380923e353499815872694718922a842fb1d41774e83ec07305b0ca9df2b054e26a626c20e685127e467b3bbb5adb3b9de3c7d3 -libblastrampoline.v5.0.1+0.armv6l-linux-gnueabihf.tar.gz/md5/32f33430008184705b37afcce7d09fdc -libblastrampoline.v5.0.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/2af9ce233b473f2c81c4ba2e82253a88a519e4cbfa2cd410b27b1f1d7d06559376dd3743951105dbaa784310cce378516978b0d56bd8a196e2eb6c5fb7e6e969 -libblastrampoline.v5.0.1+0.armv6l-linux-musleabihf.tar.gz/md5/52da6ab8b5a9e03aebb032441b668d65 -libblastrampoline.v5.0.1+0.armv6l-linux-musleabihf.tar.gz/sha512/a6f1a375b61642e0b2fd235e27b5bf7e0cd1ff308cdfef27b904f62dfb9ac2bc8fa4e9a7869851310da90af4797994d86d581354070810ffedf3deea5afcc388 -libblastrampoline.v5.0.1+0.armv7l-linux-gnueabihf.tar.gz/md5/08fe2bf27a14e6a6fc4f6b394051aac9 -libblastrampoline.v5.0.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/fdf8d054c67ca3e60dfc7739e02e28817d4510558341d9666b3ccc35818d1ea835a30676cfbe66bbb119c5574c683f1626088119dfc672bf730c87811835e48e -libblastrampoline.v5.0.1+0.armv7l-linux-musleabihf.tar.gz/md5/836fdbe9e759c71b3c7ae6be2ff6cd6a -libblastrampoline.v5.0.1+0.armv7l-linux-musleabihf.tar.gz/sha512/6333f8b9270f51c9e520ef8eee52c1796659bd7574c4e8cc04774d3b5e0574af63e5b252cc3340a62bf44771970331839083a528f402bc7929f32cffdbeba876 -libblastrampoline.v5.0.1+0.i686-linux-gnu.tar.gz/md5/11f127c422a4b51d6cd0abe370176c25 -libblastrampoline.v5.0.1+0.i686-linux-gnu.tar.gz/sha512/ad8510a804637ed144ee931a11629ee86e3c29e36be394c1f020a04e68b64a04a0eaa976961a993be0693b1f57b687f18dd25d3313aafa217a9140913dc9849d -libblastrampoline.v5.0.1+0.i686-linux-musl.tar.gz/md5/c865cd79d083de137714df55dfd015c9 -libblastrampoline.v5.0.1+0.i686-linux-musl.tar.gz/sha512/99f4938626f84e5636231f34842c6877be5ac0d528f7bcae6b15d51b82d0daa06eb7d086a28f2c516234a989dd384f932886303f13cbac33f972fbf64b16dfb9 -libblastrampoline.v5.0.1+0.i686-w64-mingw32.tar.gz/md5/e9e2cbb1c90b691fd06f4df81674d36a -libblastrampoline.v5.0.1+0.i686-w64-mingw32.tar.gz/sha512/c32a7449476f994f8d1bdb576959d6cc54018ac33be2d691b8627467ff5870deac7427e83f275db9b675c5d92bd13254979b06da33b782d6de3b49b1a6dda19c -libblastrampoline.v5.0.1+0.powerpc64le-linux-gnu.tar.gz/md5/5904dce9e258e4bdf71493e6cdc5fb20 -libblastrampoline.v5.0.1+0.powerpc64le-linux-gnu.tar.gz/sha512/e10761289aaf985e96e0c908f988218450b54b78a5ba0ca67b509d63c422849471b38e952b93e1de0ffa92d9b8e76b16cfd733a05940203213f7f10cdb953dc9 -libblastrampoline.v5.0.1+0.x86_64-apple-darwin.tar.gz/md5/2d15a24ce47dc67ef575ca514530734e -libblastrampoline.v5.0.1+0.x86_64-apple-darwin.tar.gz/sha512/5209953e6ed72c5840b926c2c50e67f3ef2e8612877e5c6c4962e687870a9c4f95ab83fab1db77419ffdd21e303e5a951a86d21979cbd2e2e8b9d65a2b86a693 -libblastrampoline.v5.0.1+0.x86_64-linux-gnu.tar.gz/md5/67092e794898efbe1d75bbaf19912538 -libblastrampoline.v5.0.1+0.x86_64-linux-gnu.tar.gz/sha512/cc117c4d6d7a34fc7abfff4d40584f63b3ed80a2aa8be887f22a65b25e9196a2173d624bda77e8a1f2c401792c090948ad0a9069af3e48ee886664e1b2dd771f -libblastrampoline.v5.0.1+0.x86_64-linux-musl.tar.gz/md5/32f65fa0681d81ab4f5a84d18ec0ef40 -libblastrampoline.v5.0.1+0.x86_64-linux-musl.tar.gz/sha512/177f25c3108af15653726871b110d77e0a5e94b06bd6996503f83b7dd7c0d9877beff5eeadbdff4952ac606fcec426c04a97566efc2d88d75ed380e566ffe0c0 -libblastrampoline.v5.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/12494ac279b869c740712b8f774edadf -libblastrampoline.v5.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/6b896996f20552bb05d22fb314b6b9ad8e4359aec31f90fe7029cd13d37e6db1c305a87d9622ff4b036b155a12a5b305a8fd56e4074149bad8c3e6a225c70c5d -libblastrampoline.v5.0.1+0.x86_64-w64-mingw32.tar.gz/md5/4fdbfc6384ba4dbc74eda97dff919511 -libblastrampoline.v5.0.1+0.x86_64-w64-mingw32.tar.gz/sha512/e752486b9e6f6ed293a42337f432c8dcb86246523864674be5ff35fcc49f8cc848f77c41b2af1903959938f620d68b1de6028afc662f9e893045308eef72d624 +blastrampoline-81316155d4838392e8462a92bcac3eebe9acd0c7.tar.gz/md5/0478361eac783b99002b1ad985182f05 +blastrampoline-81316155d4838392e8462a92bcac3eebe9acd0c7.tar.gz/sha512/2489ce5770a9861889a2d07e61440ba4f233a92efd4a3544747f83320e0e7a229a8fe01553d99f5f1d98713316f2506daf0adb7d024a46e32b3de1bb2966d637 +libblastrampoline.v5.8.0+0.aarch64-apple-darwin.tar.gz/md5/a28837b9838fef2b3831de3278ec7949 +libblastrampoline.v5.8.0+0.aarch64-apple-darwin.tar.gz/sha512/111ac2fe5f8f8102f2f7c9e9e6aa1d1a12d2db941238c949ff8e64b30335e8b2f6ecce0d5f577879c231eb839c06e259302b709f3d34e94a97047bfa984222f6 +libblastrampoline.v5.8.0+0.aarch64-linux-gnu.tar.gz/md5/9e781a026e03118df81347fb90f10d45 +libblastrampoline.v5.8.0+0.aarch64-linux-gnu.tar.gz/sha512/89469f32a666efd46437351a8fb16758c35e5aecc563d202b480c10ddf9fa5350a5a321076b79b0a1a07ec2cea0b73aa5c28979cc382a198fa96cca0b5899d25 +libblastrampoline.v5.8.0+0.aarch64-linux-musl.tar.gz/md5/b7acda2fdd157bbb183d0dd33643beef +libblastrampoline.v5.8.0+0.aarch64-linux-musl.tar.gz/sha512/cf4125a47334fe2ec0d5a4b11624b12e1366ec031500218f680ad5a53152b9d752c0c02a0b92d0e07f3eb21f2f8f58d0c587438a4869a72197bbd5e91531369d +libblastrampoline.v5.8.0+0.armv6l-linux-gnueabihf.tar.gz/md5/eafabd99fb1287d495acb8efb8091fde +libblastrampoline.v5.8.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/63ff4e6bc400fa8ee713a1c5ae4af0a8e152d49860c6f5e94a17e426ad9f780d41cc0f84d33c75ea5347af1a53f07fc012798d603b6a94ea39f37cfd651a0719 +libblastrampoline.v5.8.0+0.armv6l-linux-musleabihf.tar.gz/md5/9788f74b375ef6b84c16c080f2be5bdd +libblastrampoline.v5.8.0+0.armv6l-linux-musleabihf.tar.gz/sha512/f00ebf794927404e2294a2fbb759b1e3e57836c7f683525fac0b2ac570da2c75904e43f154cf76fce310a624f9b35fbd40e6c7757882bb6f30db790f4221a543 +libblastrampoline.v5.8.0+0.armv7l-linux-gnueabihf.tar.gz/md5/4492bace63d8274d68ecdaa735e47e99 +libblastrampoline.v5.8.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/8868283e6c5224b80145fdfd17f13f713053ba94e49c170f38f0cbf9f794185d7dec9c107ce65dc76121d3ac5b21d2f3857f619d8279bede86a906230ff59a71 +libblastrampoline.v5.8.0+0.armv7l-linux-musleabihf.tar.gz/md5/d66b6ed1d4e5f6a130f36791063e651d +libblastrampoline.v5.8.0+0.armv7l-linux-musleabihf.tar.gz/sha512/414ad07574a6e9aa670bbfea13eaea11da13129c9ccb4193cad708014c31493ff10ff427558b90cb16040fa64c8a325c2e375e3310c39fb37bb3e7fdb6a72a5f +libblastrampoline.v5.8.0+0.i686-linux-gnu.tar.gz/md5/595199a3a01174cfa4d9ce3407bf30dc +libblastrampoline.v5.8.0+0.i686-linux-gnu.tar.gz/sha512/02c3b0c3c0a411d5090a081f3bbbe38aaae40eaa5fe63d0690e0582e233cd9ce76483922557d4f65dc457e29a4e84d86ee5af20a60b082aec7bec4ca8607c1ca +libblastrampoline.v5.8.0+0.i686-linux-musl.tar.gz/md5/5832d0044842cb84f4e1e1b0a04b8205 +libblastrampoline.v5.8.0+0.i686-linux-musl.tar.gz/sha512/d28954d0feef6a33fa0bfeb59acb68821222d36a4e353eaf41936ee2c9aace719c2d0f0b0f080eafe2baecc67a29de4cacc0446aac776bbb615c4426d35c9c8f +libblastrampoline.v5.8.0+0.i686-w64-mingw32.tar.gz/md5/89c07640b6c7ed719199b0cd0a570961 +libblastrampoline.v5.8.0+0.i686-w64-mingw32.tar.gz/sha512/71241e83501ed473af0bf60a3223075e22a48788fdcf0ad5b2932861c89ec0741c61bf6a04c8a26e68b2f39d360b6009a79ea2502b5cccf28249738e7796be89 +libblastrampoline.v5.8.0+0.powerpc64le-linux-gnu.tar.gz/md5/5f76f5c6a88c0caaa6419ba212f8cb94 +libblastrampoline.v5.8.0+0.powerpc64le-linux-gnu.tar.gz/sha512/785071e682075b2cebd992394e66169f4ee2db3a8e23affb88dc05d9abf55f49d597b2a7400a13c83ad106ad825b5ee666b01f8625e51aec267132573273991e +libblastrampoline.v5.8.0+0.x86_64-apple-darwin.tar.gz/md5/21beb51d448bd22e4608a16b3f4fde05 +libblastrampoline.v5.8.0+0.x86_64-apple-darwin.tar.gz/sha512/620ba64d93ef416e483f813617aa313957282d8361f920b5444702fa911ff0051d1f8a8814b5fa0b082fd4dc77d96cb8b763937c786959bbc97cbb6131617152 +libblastrampoline.v5.8.0+0.x86_64-linux-gnu.tar.gz/md5/14c1045ba4d400f490ddea5343a46f04 +libblastrampoline.v5.8.0+0.x86_64-linux-gnu.tar.gz/sha512/0fdae83f4df93b28951521cf426736367f568c1e76fb68eea42b045cc9a288b6836abb3206a6d61e4f88adcf198553e911c45231aecb0f552e06de28eb3bec54 +libblastrampoline.v5.8.0+0.x86_64-linux-musl.tar.gz/md5/59b110676fcb2fcfdcf670a5d435d555 +libblastrampoline.v5.8.0+0.x86_64-linux-musl.tar.gz/sha512/57a5022e9fabc0637a29f3c32f6180cb4f6a90282191232e299df6cea5265b535e4a0af4fde15c8fe80e5a59edea0fae96dd3a510f5720ecd78e85a2a9ffbfe0 +libblastrampoline.v5.8.0+0.x86_64-unknown-freebsd.tar.gz/md5/cb1c14b4f8754561c5eaf8502582f09a +libblastrampoline.v5.8.0+0.x86_64-unknown-freebsd.tar.gz/sha512/d3b19a2a9b3dc674119590d920a2e99705de823e7d01a210485b31f8b1ce59253c4a70f2d8fb967f7fa05efb6ac376d94e79ffc6848607a366b2f0caa58b4208 +libblastrampoline.v5.8.0+0.x86_64-w64-mingw32.tar.gz/md5/34fdc53745245887f968f420b2f02ed9 +libblastrampoline.v5.8.0+0.x86_64-w64-mingw32.tar.gz/sha512/bbf478736b7bd57b340ccd5b6744d526a7a95fc524d30fdf9af6e9d79285641be26fae5f9e5302d71a5be76b05c379e969a829e259d8100ba9c6ce202b632b3d diff --git a/deps/checksums/cacert-2022-02-01.pem/md5 b/deps/checksums/cacert-2022-02-01.pem/md5 deleted file mode 100644 index e287f024b8e18..0000000000000 --- a/deps/checksums/cacert-2022-02-01.pem/md5 +++ /dev/null @@ -1 +0,0 @@ -3b89462e00eba6769fae30eebfb9997f diff --git a/deps/checksums/cacert-2022-02-01.pem/sha512 b/deps/checksums/cacert-2022-02-01.pem/sha512 deleted file mode 100644 index a5d8840598343..0000000000000 --- a/deps/checksums/cacert-2022-02-01.pem/sha512 +++ /dev/null @@ -1 +0,0 @@ -75f5222c23d14d194856d3fa58eb605a6400cbf0068e208e1bc75a4821f841c39a95dde161b904db54ce922efa384796ad5f2e2b6ef75327475f711e72652388 diff --git a/deps/checksums/cacert-2023-01-10.pem/md5 b/deps/checksums/cacert-2023-01-10.pem/md5 new file mode 100644 index 0000000000000..92063050b50f3 --- /dev/null +++ b/deps/checksums/cacert-2023-01-10.pem/md5 @@ -0,0 +1 @@ +e7cf471ba7c88f4e313f492a76e624b3 diff --git a/deps/checksums/cacert-2023-01-10.pem/sha512 b/deps/checksums/cacert-2023-01-10.pem/sha512 new file mode 100644 index 0000000000000..d3322e5890f81 --- /dev/null +++ b/deps/checksums/cacert-2023-01-10.pem/sha512 @@ -0,0 +1 @@ +08cd35277bf2260cb3232d7a7ca3cce6b2bd58af9221922d2c6e9838a19c2f96d1ca6d77f3cc2a3ab611692f9fec939e9b21f67442282e867a487b0203ee0279 diff --git a/deps/checksums/clang b/deps/checksums/clang index 68f28d9640b21..c16dd849e6fc5 100644 --- a/deps/checksums/clang +++ b/deps/checksums/clang @@ -1,58 +1,108 @@ -Clang.v13.0.1+0.aarch64-apple-darwin.tar.gz/md5/e94db5924ccf13ba54642df7c93c69a9 -Clang.v13.0.1+0.aarch64-apple-darwin.tar.gz/sha512/1f77b8ea9f67e46a6fc65f58ba5cf5c451d97e8f94c3842e228886fb7571a07e544de78872e5d7f201e03a6b43ab0d94b9bfd538a3f73d7b6b53f442871c61df -Clang.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/md5/ed984baafbcd36c4627a45dc0edf9a11 -Clang.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/13ca14c74e4544bbc069ac562f296a73bfa347cb5cd015638f1bffc047f9395aaf49947040a61ceab360a50cea928d002752b1b01210662c286981832844c584 -Clang.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/md5/1f1207b0522351e57a55f0e05c98d6ce -Clang.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/7fa39fe15b3aaeec37cba5563a46423990b48bfc8a1f185797050de0bce9293ef0893603aec578c3aadbebab53d07caf33198eda7507876a49be9ec15cdbb1fd -Clang.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/md5/37b49d0d02a5911b74523cb8f8a1abf1 -Clang.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/sha512/1a5307498c9a1eec6e80bc1641fbd5819847ce504ee0c53c07cd09a5b15976649750364755b3ff5f851ffa197eaf6d69a74c4a96cc3b3e6d44c6ca66afd3cff9 -Clang.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/md5/ea5974f42ceea627ba96fac88e0f0ed9 -Clang.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/sha512/15d2c0526accb8610e64f9a4bf9cd9d72c3c903727fa4af129fbdce0af350295546c8a5e58c3a59196d511e30e57d7b0c448a087fadb60806cc0ac2fc5dba2f9 -Clang.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/3db46a89eb9323734fc4a4f6dcdb558e -Clang.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/bdd974cdc6ce4974fd1a0e594535efc66ffd14d9cc4f6421046b836337e950d983d67f23e7af12b59c62d0254df05b5a8dd19a5503e67b00d5d9442d85a789ef -Clang.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/fa0f8ba9ed675da78f19b7212a3f8a89 -Clang.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/b96b4121bd327fe004dc335382e2aa5193acdee411ec5b5a5fc449c209bf94d2645d40f43f15e9ddd92d5848a1f87c792e2852dccba2d469de2e1a9ea95f5ef6 -Clang.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/33e2cc2bc2883ee2d34c19b89927f736 -Clang.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/a35f10aa8412b008ec181d71dd575284ecdc103cf41f0e1c52c1e856cc26e77f566cfc3a581394b52b87d4fcb11616b7824631c389ee711c5786d43dc5ff52de -Clang.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/8990c4b777810f1335bfd2d2ace2cf3e -Clang.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/e92999e8112316b7806756967cbb1424a68c9415e03c7f9c1203a0450485f4f1d48d6e8341439ce3d63a9e88c4b6db46ce4f886db353e31dbcf3111f8e5744fd -Clang.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/91a4810d844aea695f7114bf1ac80207 -Clang.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/310ce9579c637de268e18c4f5cc31f5023784be36f3073273927c9ade7299326fb801759f0f5828cdf04580104502651e9b532d4a6b2934aa8d39acbad118956 -Clang.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/73c0c2c6533af4964892dba587c8b5fe -Clang.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/b0b311acc95a731fc791d578b6b1fc65834c98e1b551d91f0a4ac03f79c27af16427f0397a1f6f380ad4b77c9aa38465a207cf472f39e0651b39e54695150481 -Clang.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/e6b6bb1aa23fbbf60ac52bad871e9dbf -Clang.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/66e90be4aed8a5cf9becb929915156b3c2fb0bb8b2ee8c3a8f06c3e7c24fa84b69b37493843d0609020b6a7263b0df7ab2793dd0f6ce01b79d7f5a350cde2ac1 -Clang.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/9dcd26df744a47a1cefea19f17935b29 -Clang.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/a72d97d581f99be56cf8a6853662c77cabb3001eec4fcb802ec3278ab84517e96726373414f67c87c0926e25ce170f22c930b2bf804b0067b1511d6cfc61b00f -Clang.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/md5/9c1094a09da852d4bb48f7a60e0c83cb -Clang.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/sha512/6f62fb75f64c8b8adbae1ca8db44c4a4795ad6eae0673982aa18122282fb784c796107cc3a9a54e435694b4a898c63c86797317d7e37a0d8f1110f4fcbe4ef58 -Clang.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/md5/5d22a3bedc62200471878a42001fc39d -Clang.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/sha512/7fb2041030245c2e997f51cb3406ed5307def6dd5c23b1a32fff19b3dc03b59de1a0f2d6d530abb89ab0a2514110dfdffb53bb0178337f29f28d3fcaf00f8ce1 -Clang.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/md5/fcc97104506c26f5161fd94b973dbb46 -Clang.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/sha512/99a42e5d583442432175357546811c7fede695f4d3d6026eb9d02585539d7c21ccf1adb449de47bb248d602a5297ae1923766fadd52487806729f95381ebcfd5 -Clang.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/md5/1a712b6fa8672da1db6528dd655a8bf7 -Clang.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/sha512/eafc025c261f79dc646766aced9962b1901c820a2691e230f2610f499687905b34feffe65a241b885187f79dd83688dc796cd5adcd3af304effe75190098d6d4 -Clang.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/md5/7d9f36bc0be2b02443adafb6e57a180f -Clang.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/sha512/0642c87e349ae10c7ea8f48388a600ff97a276b23b7936ca35ac6d9a1f686c70d1ec4cc7e4a893aca13f8109b5368d2ca52113021d18ba33912c375007ac1051 -Clang.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/md5/034d5fb31a4b749f7fcf13742d5d211c -Clang.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/sha512/9313dcf2a807d349be44b827d34f44f9780f14a93e7b432ff99346c7e352c42e3938fc6fee508f9b1896853823f524410ce7fb85a7b3e542e474df3c20d810d3 -Clang.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/7b7286c7ce9e383a6180442ada1b21c2 -Clang.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/c9a10e970a93c2d0fe7cd1952f4c152a51c51648376ab0ebf41a736d89a20121c2f9744104290ca4377a397ee612d6af85f117817aea0c49a2ac8d4a861664e8 -Clang.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/53f47082122cd88d411af8ad98adf344 -Clang.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/8672668843e4aed4fa0c8acfc28066a2acfaffa47f46c3a4f6bfeeec4824269fc063860c848c737b76e009b15e8c0132ed6b63b2904b96bb1d0df5cf7d835022 -Clang.v13.0.1+0.x86_64-apple-darwin.tar.gz/md5/deb4584aa670642d499454aafe32b809 -Clang.v13.0.1+0.x86_64-apple-darwin.tar.gz/sha512/e4de906392344ba21a7ebee11a8bbce0e422f8460d39de31980a9637a52e88d49db6ea22b094d3ea1c27283062d7abc6d45fc570aeddc067d1e28f573c00c8fd -Clang.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/md5/8c999db749701fd4a4df7486f740c89f -Clang.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/ea9661825f40a31ae238b5644693767106710a9e157e1f7d715dab5faf63ff8433117e2507eeb863f0a25deed669cc0bfee750af961f6d167db27d7cf8b75819 -Clang.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/md5/7f09aa135ce9ae07586d075414a44e87 -Clang.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/93f75720fd620ca46997c7fd6f401cb45063afc8f860eb3c361f285d85ab5c4e902a13ca3abefae48cfe1e8fb902adde4341f2aabf72c3b188573054b81c6b9e -Clang.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/md5/fd701653e03d835e67b5c0930c281034 -Clang.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/sha512/7cf9180caa5d4b333842a41f3f451cd389457aee9ea83fa2405f655804f3c74d9be2d9e887bd6a787fe817afbde36ad658d4ae49b63ec1ebce0ed77c62326442 -Clang.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/md5/15fb3d47ee056a15d8f14799ff5fe45a -Clang.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/sha512/3cc641ebe266d959e0c5699c59d655095a5b596340e991cc9d4462a5674fa50d89d7cc1937582011464c8568306babe21cef0c4bd1d99430687fd17f3a6f479e -Clang.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/b4f855841995f513a632905184e6271c -Clang.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/d3390ea1ee311b49d355f9a6c41669575fbd3b66ddbc9791cfcb47673e19796d3cdd210469fecf351a57060d7447d9678980f022bbae1b4cda5799e8ece6aecf -Clang.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/323038a69d2760ac4c4cb6f3f712231b -Clang.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/51073b2862447c184c54b47a02d27d20733024f1d11d4d2f15938c47bb47f94002b56dc60994165cf416079b74d1850445d521811356070bccec0e32f09071fc -Clang.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/a7e7405baa541ca5bcf44468274c179d -Clang.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/07590b6f3ea2456f5bbf7aa87248b8462e60b8ca0f8c4c4ea419bf093efec232057551aee9e93114bff2cd7ee9a76ccec9515be632b94f4e6c17af4aae3478d6 +Clang.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/5dce383804bd3d404b8a1936c12ba457 +Clang.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/5661a1cb2044ded03566c9316978595d692667fbc4e951feca658f9986a8557196557b05ccddf1b00b818aac0893696c3bbbf63a35dc9ed7df146b4488529f6a +Clang.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/549cbc6fa28ebee446e99701aded16e8 +Clang.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/55eea0b514aa2e43ad2f373ad25ea4fad5219ff1cd8d5b639914c218a0a454ae9b27b8d022ae73771d8ec89fa329f5bfde538817653cc59e569b600148d56842 +Clang.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/ac3cd40e47702f306bc42d6be5826029 +Clang.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/73b217caa53702bc6fbbb3286241b7a20c111358cb9436283e9f7f9fec90436d5b54cb4c332afb7e447867a40ba46c9e3b93464acefbca7c0bb6191001525cbf +Clang.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/b1a656501493c15b98442bde584a34d7 +Clang.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/f424254cc887301d4d5b04fa71e2c7da6e4d561725d5b06278925e05be1c62a74769f19c37b431c2e2d73e7e5129acff07ac54a0b7fd381821aece27f260c116 +Clang.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/969170b1a791e89a0094154f34023e86 +Clang.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/d6ae356c9b1b80cbc5cea4eb8632b77ab3ce0d060b103cec4a5f1c73feaaf60688c2253034b2a6e132273fe04c803de93f415cbe2ef40cf1d6f6a30dcfa03af3 +Clang.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/22d599b774af41dcaa54481cc6325b1c +Clang.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b0f257d45f1a920f46b18049b762b5a3cefdf8683c4dce46f48ce2993e6a622dbdfaaa6cc9a9cda8a7f047094a6f804091d1ba6c83e26cefc38fbd1ca5c0a536 +Clang.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/f2f5064217c14700f0f933b704fff233 +Clang.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/2284978d8cfe22aa49b1f3b161c75cb0c9d43f84674ba58a1335edf818b91c6ea1684a9c3580f2e1918fdc050a624c698a4e87dc163e9076b9d6c0023c989d7a +Clang.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/eafd72ec24ec81d42cb044e4e4d638dc +Clang.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/bbfc6c9179fc43a1db0ad82fc8c1fcc8ec8ce94d5c32b38cd1f88490dedc67953283995c0dd4db7262a9206431135cf2671c6ecc6580da65ba8ff4ec0323ab64 +Clang.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/0432eb21283647995e35bd0d486148ab +Clang.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/561beaf45770c06b35bc1626e93a0cd89874026a8afa22017b40eb1e6ba306b05305619d42a4a2145c576b1dcc77ade80cd0bf0e0237761f3517f4db402f9b74 +Clang.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/653b9b87f2573818d66992f969f7811e +Clang.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/517df570b40b51a4f4cbcecbdaacdf0b592fce66ec328139d95eaf8b63c89a1adb41a9cfe4982f5bc032fb29a6b967dc1b16b0eced98cd78756ced36ff2257d8 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/4b1a5cf46925575bbc6765f3336e1cc8 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/7afb23aa5ce823b1f2371e038faf311e8e21c3843cc50a0b1473038cd746fcdc77dede67130631bfaee778c3d42ac1eaa23ec664a82f43e2ad406962f3019479 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/5a6200aef0e6660bb156ecf3e53cc3c8 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/0dc564fe753fbccfa03ac94e19828ea5ba2b8b74e7adbe7f501ac8b11d1ed8fd85a65572dcdf957018bfa1be3a6babadb1ec3937966347fe49fb38596a4b1728 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/ad693e5cf8f2583c3311a39c095b0bf8 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/b4e1120c960bd69f2643f185607bb2139095fa7a2f943fffec65ccad9422f2bd801131185cbeea1b75298c64cbf109fe28bae54c1b9917fe1ce8b2248d623668 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/c04cd594e25324c42d97739d72e772e1 +Clang.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/5aeeedbc3f0f8327f7760abe3eb6fda368353a7b429e31ff47a7bf42d612d070cc86f0e97031ca0c2fa9f9f448757d59b2652d89bb05b27fd380f2116a5beb6b +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/d706ad9062539a37df1e5cedc084086a +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/4862bbe0c71fe0e8cfddade0f881637ae5f58263208e1154f2284884ddf4ad43d76d98bde57904829f2218db21e4fb6ac038e231b682455fa22deeabe65f1336 +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/6cc35754a4378902f9f126139bf299a5 +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/4256e9c3f58dfc896d56eeccd7495601ec585e208857de14f91e2d95295a4d03009149f49254be40b27affd5a2250323c6d0744e1ddfbd5fb924fdedc8a993d6 +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/128bb901686224fb6d32c9689c03cc21 +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/b7048ff3d8a3b3e3cddc49b2cd9fbda8ad308fe10e932e8d90000e76c12059547342a1132149254628077d0efc36b34479688b3e9f32e7364301e85a18304cf8 +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/d860412ac46bdeef203a578f0bfc5b05 +Clang.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/497fa51af138b3c645d5017165aea6d33410262d2ce69e322b259b34fbdcf52a131541dbac66fae8b9a9027b70771199f9a76869721bf18760065ca7cb3b5364 +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/6fb13f1cc2aec210298c3045f8a7fd94 +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/085c94f43fb46ecc8cadfed5c5d91978c9ddb9d647eea6e82ff0a548eec53dbddc77721faaa8c43ab5b0674f83fef7aa3b34ba0dc273feabdbb8cb95bf5534ee +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/63d765b268e792df2aa92f3689de23de +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/32b2397bb4b627f0ad9b00838e30c965feca902e417117d0884244a2be6a50e0d4d40e55a27a87616e33819967455f90ae0a4319c2eefefd49b82e9041835444 +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/c00e93211a1e470f1b00a53e776a9e3c +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/6621b3ab12302657ef2441482e8bc6335535964fda472ab8378221e4a9cc0813968589f457e1af66141821cdedbf8eff3080c20105eec810742e5539fc329fcf +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/254fdeddad203954ec0531875cecec8c +Clang.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/84a19469231a9204a553abc58073e423173ff828445634501a61837c0e249ed003f9051fcf1da4eb16201f80d755e7bb4b7513536c749eb1e7ea78c7ded59945 +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/0475a3e401b59e1a34dcbd9d9b980823 +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/e0c9e1e18cc4f7106effaeb04e0e6f41fe8ad872d67d3d0da928ce36d1bce6be3d5231d149b2d404b3a4b99900b50d280ac6f7dd8965d30c4dcd3913590144a6 +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/08c9e802640582af0b79bc04702c9771 +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/d4f413bbb5d5c3ae01cea2b87ef4e46816023fcf4373f00fca13f2edc6278eac651718feea3f8c7d04d3ef82360417dd93b6c7163d54ecd79a3811a0ed588054 +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/e7c253db924ea5cb5098be57029e009f +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/373884c492e5862aaff27f5782ba44e202e581e4faeb2cffe14bd696a590c0bc72459fccf3342aadbf189282af0c43efe3db113caa47c27c3ea556f0b3313e7e +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/9c1867e316ac258d9199b389ea053d2d +Clang.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/9537f285d2a06b8c86ff21aab9daad1ba7e71bcfac55d780c693da8cc250707011ee22ed021e387422543b1e2abbc34de1a7fe49175a27a9c11e43b00549f1be +Clang.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/f9a13a80efacf45f49d6d7591d2cc3ea +Clang.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/c7edc55c4f76ae086080ba639d83793738884b9385618c52b30f5c3fadb0ed2a31bbe95ab80c5eee8504ec6301d73fc7318a8c0f877ba8b5f51170de51179d9a +Clang.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/c9911680ea55b36c4b9f59cfda2a8e33 +Clang.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/9c3722bd402627a4f51b4c98c8712a85031aa79380fe38be0db9df13a5cfabe428fcc7d5d5cf804ac4387d738cad1796bb3f341ebdcf4726ea7f699c6de586e9 +Clang.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/db82d62c163f69038364189a60b18d09 +Clang.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/5dc415426bd99dc2d7b5fc4fe3f2bb1aabc8961fc2b03a2bc14562f330b273c4d1942d7ea5f05b38c76ee753b440cc4f92015a25f9de7980aa3b1d52f7d0f2bb +Clang.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/67b7194b31f68db8ffcf5ec250948740 +Clang.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/a032c2ae911b6318ab23950ac74dc95f2c8bf815196be62e410b20cd2e271c4154f916388d119ca91c77e07853ba2c56bd5e75a4ce6742d2a7bbd9d3e61853ea +Clang.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/50b4fa021c1c9b6bdb29eae63ea22103 +Clang.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/40b377df590521e5291c3f2f9daa8d60863c03253b07d0e537288324819a909ab3466b710b10b1a92ccd6f3566702c515d808f03e6d9fe9d01617b9a836bb63f +Clang.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/d2da27ebc23793c107cb03e176f02d6e +Clang.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/3ed297cfd3c1ec03cbff10d7b54f9f4a374a9cf8c699287f179ebd5fa000dd525fdbed3c31b59a8ae32ef1c56115c3a84640d776f01c8a92bfae979c332043f5 +Clang.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/aefacc80a5f704aa7498b35dfc2441e6 +Clang.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/76c7fd64fc4323ca442fb0aa30b236355b26328f897ea8cf3e3be029246574d150a9790ae1c45b289e4fc3050fdacc20b6d57b588a707f6d0750e6da91815edf +Clang.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/59048d333a8a261d079673828c174d96 +Clang.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/bcd0c3c5e04cea24383fc2472f6190e48f8738fb7fa625ad700d1997f8aa81c9b6909af0fc38a2287b80756fbfd01300f3388c19c8df791d78ed913d8d59dee1 +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/bb4007dc5b0c0d545f457bdf35e868ee +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/2f686bdd0bbcc62aaf9e20d3804c83291ad7c41a0a174516d7a83dee7f969f7d50f19f70c0f35901a3eaa8d54fe83204d832a901586feb9eb8e141631c411b3b +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/08f088ab3498a4f7645393f43098583d +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/faf62bba3a282f218ea569d3064d6c0cefde9232d055fc3a08c994fe424f2b60dd9bbf1655f6ca101da701e3d05bd813695d6a66628ec2b6b4d11b89f773f0e4 +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/bb8f05da1e35ab358a96265f68b37f57 +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/30e3789ccca1fdc5eecaeb25345c30bc4f752cd41b8725c5279654d9b3f500d6e8693c6d1dda8b3167fcce15443682994d66922a17986419eb48bb09970f02e0 +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/ea9fdfb7c8d1a9c973ea953d4e057f0d +Clang.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/5e5d9298a12e65a7e4d401a0e404eb172c96e70fa906096f549e7eda5dbfb294189e4f3526246f28f71ba3bcf35d1bf790f05522150c5877bf8f186d8c503795 +Clang.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/053334d0c5aabaccc81f22c1a371c9a6 +Clang.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/aa8daa99a4b52985d80e57d175b6fc4489058ed84f06fb2fd67710a873d5333ee77b64ed0620df099ed5617792fb3eab23d9cedf3ab3c79f4eb6f04ad1fd9588 +Clang.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/b80918f03dcdfc5b5f1e8afa90dd4e88 +Clang.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/c0397541e06878535b41ba7479b603699d78f1ea3345d9a1146a0e7d17f42078e8365dc71a117981b2d2b25f35a40aeb707ff9ee8a2145303f3cb6567e82bd54 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/78b9e190d5cb7e6fb172814eda2996f7 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/2c9a764ba2427faa8e67285205dd1b8c211665046c9a4a19aea02de46d02a6d4287467bacd1260b7996b2b85d3e571e750d92f02c21b180abe37709ee9da78c1 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/ba6dcd205dbd7c0301855f2a892c6467 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/9a98c10943a8abfbe92b151f184370d21a10ce72afb22f131bd0522672c65875868357f60650122e1a2cc91254adceaf8044de4533aea08c4df400ded8c01669 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/ce62f8e67b89c612eea35f4ba0e09d45 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/9c3afaf0dd338abed0631b81d5f6c197b5dff6aae637996f5bc2f85f2f7dbf64a7a4bdc07dee9ab72abada5be576bb0466550280a9ee9093946a469a2b6af648 +Clang.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/543ebeb138123ce190e74cf0ad17d43f +Clang.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/aff131b5d0ed372557e3195e15701543ec32db05d5fc18117c4aee789a5cb967706d28b2dc53588bc7566f3a4498fd9e2293518ff28387466464ee07c10e9fff +Clang.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/58617f16466bcb1b56b204dde697cd89 +Clang.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/bdc0c52168beabc0552ee941246b1d4506fec50913030965b374f4cedd67d6fd2b5746f04505aa5bbd4e6d61c5f684dd22c3b207e364578fd8538aef8efe0b14 +Clang.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/aa6f0d9a455f5f0109433b9cfaa8f009 +Clang.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/b267bd6291fc5830ffee075af00fed9a37177141b0cdcaa8ffd602e6a8bfc58e191408c3a6a12c0fb3ea7a5d825adf1ef99122399e8246e0312b4cd056d49a2f +Clang.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/ee2d7c4dc5c95e46c6d46c4fff112e9a +Clang.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/cd11acb2dccd2ac45a53fc48ee6a58299b5e54e80a5b9747c680e9b068381bf87cd388ee75cb0a51ccb1162ee8af03acd4c3f730a5f5a3ed5f443dd24ee91cde +Clang.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/a5c16a8832f5c28346912f610932ecb4 +Clang.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/91b244ccd569597fe42ec45e5a62f6de0ab2c4da048b8b3ed191bbdde0a8ba5a710054d9f40c31a405a6c494a25c7546748870d1170d76e2d3b22dbb0c618e87 +Clang.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/2d789f91744aebb0deed9b91202c1abf +Clang.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/fb991942325fcbfa1ad4903db43e81fcfeda5d007ee664d96a0e0d2ee5f04b5767d6ad5d37e0273f5af626efbf1c6fde84d54536b74cb17433d29b6772bcf7bc +Clang.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/ab8fae829b5822e9123fc3d763d327e1 +Clang.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/1b24b03f6a81fba7400bdaa57899e9cdffd6da7e476832870460a12ab6188662c15a3cadd80ccd7dc0790834aa76ba0df098b400c87fd067eaa9f9fec0b053be +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/d5638f87a6ac840d571a3973e89316cf +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/0f07e9e8dd75691ee73ab0e78a29047596a543c5886a137a7503c916ee6792cf7d6a7f279dbd864a2ad36d36aac422555d408381e3781ec004bcde5525abeb68 +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/e777625c3c7efe2dcb029e74ac7d1ba7 +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/25e0a48a4d8a2ad7f5f5adb7c30429655ff496e6b5a224fc5707f092233239d4c3f4cc17432de12815e546bb595caf2a70b18ff208a53b9f0236accbd83acda3 +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/22e03dc887f6e425f98cd66e0859ab2f +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/ef20886b841ba8b749ffb0c5780a9dc25d5f563ef726b1026ee77607e0572c45b8eb3470e252f882e2c4c23a2159d88ee83d31aae5081c6e4f4c37a61a7875c1 +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/5d8f1390ff66b6b357768b1994a43d1c +Clang.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/5fd2fc0cf888d95c38531d236564109b284f20faed222d1feeab2beae68662073c9c59baee310e2bd67908f267416cded7b75f73e28969e2a16d2fcea0b03854 diff --git a/deps/checksums/compiler-rt-11.0.1.src.tar.xz/md5 b/deps/checksums/compiler-rt-11.0.1.src.tar.xz/md5 deleted file mode 100644 index 0ad8aad90f820..0000000000000 --- a/deps/checksums/compiler-rt-11.0.1.src.tar.xz/md5 +++ /dev/null @@ -1 +0,0 @@ -29d6186e048936008512b8bbdb3a1b71 diff --git a/deps/checksums/compiler-rt-11.0.1.src.tar.xz/sha512 b/deps/checksums/compiler-rt-11.0.1.src.tar.xz/sha512 deleted file mode 100644 index 59f76a7d34acd..0000000000000 --- a/deps/checksums/compiler-rt-11.0.1.src.tar.xz/sha512 +++ /dev/null @@ -1 +0,0 @@ -869208f0d2c5f0828a317a6006d4ce47a946b03db2692c8557485caddc56fbeb0335a87b4c9663aa0d1397de94337e56ae10f802c4aca443072962f728e2bdf4 diff --git a/deps/checksums/compilersupportlibraries b/deps/checksums/compilersupportlibraries index e351d100cb481..098c181ca5c87 100644 --- a/deps/checksums/compilersupportlibraries +++ b/deps/checksums/compilersupportlibraries @@ -1,92 +1,92 @@ -CompilerSupportLibraries.v0.5.0+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/307711def378e337a999c182aa7e07d8 -CompilerSupportLibraries.v0.5.0+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/0dcad5e315e045397320f667b27fc378da898ebfea9b55a2837e68b29434fe2c2ddc9652cc75a4551062ce70a2bfaffa8223c77398aa41fe1a73ccb44952cd8f -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/177f2665038919c3f8ed968226ff3b56 -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/ea67c3b9986106aee12e5f22ab3d3c5d71a58759a7d20a7724bbb198e5c71f42fa2034e46f3147006a2d2277b3881f0546030d1040cb9393e58eeae87eb82c4d -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/f16db35be9018a5c61eaafaaf7226d10 -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/051b5a0dd2235eaa90557e487c83499b3d7e0b9e921f7b2f14e77c81152c338acd5bac8040bdf6679db656cd8039093db43565f843dede253717425e464e61b0 -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/e6082f3e46b627fdaef09f1ef81c1d7b -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/13d0ab1c0e84a65db729ea6bd45a868d9d65e1a0ec95412448846d1044e2bbf11b11d96cfa576dccf3d7eccc4bed4eb9ae4bac0989e9b1b97adad5e404dfe4a4 -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/00703177897f8c46a577c2b0518432bc -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/af14ad1303f3918dd691e0b509ea0fd52ac7c9f0c285e8dbb741bd34ce0b1927f89f219fcf8d260315c503b18bf98b3df117810328066a9964917cc34968ce98 -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/f823b692319cd370ca59189ad2ba4a3d -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/b0c4131bf4d15c482bbed83fcc570da2f7bb8ef99d507e0e13eb0c8f5519ec73ff234c58d505294be3f8d39b6dd1c7022578db02005ae111c7873243e8ddc8ef -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/a9ef1a68518058fe6c945e8b00f8400f -CompilerSupportLibraries.v0.5.0+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/6aa53edf48a17ec8515cad5c79a15ab0e40cc44c9ffb188fd57fc560dde7a99d6487ead6e4caafaa9912c6590c6a391f914016fd4342589da09d56c657ad2c07 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/d3aaf50955ad671917e941e0dcf3803f -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/72983b2272300c2332cfe6864b5dd5249bbbb181bd65b10bf6bfb3a37e5e582bb9c159db0b63a077066a325899a2864717f28c60c85027be3b637bb80f994e52 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/e221d51df9b18b2562a0f3e8dc8012cd -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/758b07b4a559dda747574649926333a70355e2d80acb2ea37bb39777c0b1cecf8f308a5f8062110c378db2230ec8baf23385ae313d1c58de8bfc651573c64c1f -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/96f7feef9b1dd7944130de2e9cda68b8 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/8b4aaff1388cd506bef7f3a9edd42ed8ee1db468a18d34cd5d58d7da305853dbf48d4665e99c06c6fb0115e421d19dba5c36e947cb06defe7f479a05b547f112 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/a1e3642a7ce2b7834aa2f1b695a9977c -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/9d22b1fa8fa8eaaa5316cb494eb223e0fe73660aa5ca7518180e40d296d6d07a9863938501e5d5350bf79e79d975d7d66dca12768a0a69527d2c17baf7aaf345 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/d897098fd98928c2d644ed5ee26c3faa -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/4aad051f4f1e3d744825c650363a49f39e04cbd44dad25197ddee1890339e9441aa872f893478a2d8ff556c9a70a89c2885cd779ba3efd3c0f7193c386b820b7 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/c36bfd4c5b90d55c55bc18feaf51b134 -CompilerSupportLibraries.v0.5.0+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/ab16c638780a0118b930ac587df81fa74d2731bf1af402266106e1ecb791df353c1f368a8e7fc9147d390825ff8624e600aae45f1f6ccfc0015ce131368452d7 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/feb76551e6f7407de3006a3d363cee7a -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/976f8e34e72231b013ea0418feff9c3c9efa7b9c34688aca115a03f2bade8760ca9f259f8f502ef5012fbb389f4bf365fd7639b066daca16fb7ec1d32b5cd789 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/560ca43fa6dbd3f2e9052401477df165 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/333c7f4fbc172e7fd3d99e2673dbed1d9c699a5bb29a20095a255fadc89ded05abda755fc167aa8a16a4e93f524390c9c817df7b67fccdca88754d0301259977 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/d3ac5f871599ab225a1128c302486345 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/adb706882e923978b6e18c7134578bc86ed4e031a7a0120222018cd1b8efcf530854e426b6442dbd80b8c77c3677f1906aedb12c0ddeb33efcdd3bcd2c4a109a -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/58774aa398a63479af3f4c69678d0191 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/fe9307e6fb0b54522495fc9cc48756a60fc79af27d9e73bfb3ee49cbb366dddec1beedca03614f15761b308bc28014205f174f673fa258e76d5947446b87b039 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/af1a8ce693ba307e61184f4023d73d67 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/2ea581bb44408fc789ac306734736f6eb6cf0a15b234f43a6f50ae8f10014b5689f5aa8356112c2b54a86b9a7734ace3479c4e4aba1e5df636dda3dcd09b7e28 -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/20d62064f495877f12b7e87e684ad43a -CompilerSupportLibraries.v0.5.0+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/31b1c7c9fe3378e8bb788c897bbac0505a5ae70f500f3b1457325dbbb149c14224a88d17fbcf453465d8a572f33157766bb0e815cce7c8a2aa8a44422d34a365 -CompilerSupportLibraries.v0.5.0+0.i686-linux-gnu-libgfortran3.tar.gz/md5/fd4035aef1c83be0b865d70aa35e770b -CompilerSupportLibraries.v0.5.0+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/a72047e7071838899d75896b4dcbdc102bca884507f4758b4e0dd62f50c9ce584f2b2b86d8b67dfc4fce9864faf9723056820e464bbab1a6173be47ad941d6da -CompilerSupportLibraries.v0.5.0+0.i686-linux-gnu-libgfortran4.tar.gz/md5/89715bfa0e69528d4d294ed449ef0e09 -CompilerSupportLibraries.v0.5.0+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/6eb7947c72ec32d189221de42d5a76423a1fb5745db0812d88afe7f961d8f42669c7cf487235c1dcc81fbe73106b785c906bd6741e98f60e9931f4083be0e9ce -CompilerSupportLibraries.v0.5.0+0.i686-linux-gnu-libgfortran5.tar.gz/md5/5c1c73dc72029781847f74bcb1189c4b -CompilerSupportLibraries.v0.5.0+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/642d35ed41a65c7a2d7f4f127f936d3cb1665c207aa5feef25cce09cc11e733d7ec129673fea873403567c35cf16122ed1635c303ba13bb3349be44585f3ca82 -CompilerSupportLibraries.v0.5.0+0.i686-linux-musl-libgfortran3.tar.gz/md5/f91c962e7bc3ffb825c7e5fb1e099ba6 -CompilerSupportLibraries.v0.5.0+0.i686-linux-musl-libgfortran3.tar.gz/sha512/f89df221ff80bcbb1e6edc2f9cc28dc138d7d6ae99ac018a3cdc9a09ba637f1a9938b1f0876086f4f822fb911853286dd4f1776d603a403190bee052431ae572 -CompilerSupportLibraries.v0.5.0+0.i686-linux-musl-libgfortran4.tar.gz/md5/d2a81da3371a638f76087629ae0a6507 -CompilerSupportLibraries.v0.5.0+0.i686-linux-musl-libgfortran4.tar.gz/sha512/67941af15a0f032a853cdea180e4f87249bed2dfd09ade6fca9760f5a44b26fc94a0d6932803edbd27b75aa8d26e64c377af2d64ddcba3206562be1427a64c80 -CompilerSupportLibraries.v0.5.0+0.i686-linux-musl-libgfortran5.tar.gz/md5/cec9f3b9d4924a49a34c632efd167752 -CompilerSupportLibraries.v0.5.0+0.i686-linux-musl-libgfortran5.tar.gz/sha512/9320eee2b6dbadd4e0ed3f8763d58854eb179b1d1661c8f1dba75c22af2330812040507944b0ab20b7a7cb233c9953a1d3a4b27937e7b7a858aed2255ad0fbbc -CompilerSupportLibraries.v0.5.0+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/c36411b24c8bec4805230bd4fe0f2391 -CompilerSupportLibraries.v0.5.0+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/839b447efa46caffa699258ec8ae5e0a55d7f98a7fc037b48e6a6c29193e3d8bf48397575cc518716f41e2e9344daa670693df605a1b9d4a23d3f454ec5ab399 -CompilerSupportLibraries.v0.5.0+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/d2e392edff3525afff6734fdf47c9ab1 -CompilerSupportLibraries.v0.5.0+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/1816c7ed409acc1435c7fcfd550b7664a08b31ecf433a906d8903a60ed458dab0fa712bd0d1590a0dc8506763a617446ba402efc78a2c010562c45e8eca66a88 -CompilerSupportLibraries.v0.5.0+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/2cfeb5cd0a7e2400c9be3e846a1875d2 -CompilerSupportLibraries.v0.5.0+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/ca620dd8542ffe9a177b0f95712e77e59b0fc1044e0186dd7468a86aba4d2b92931a1d6f980e75cceb26c6c5f9dab427f4ce32e0f77998b9a827b3ce9151041c -CompilerSupportLibraries.v0.5.0+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/8ba0e4070358839909934d8a1bc9e0bf -CompilerSupportLibraries.v0.5.0+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/8750769ca321f863fbb354f6e4e76b1241f7e24e5f4ea14ea511486dc5bc4fe8274740f1500149c5ac85a8214a0193c9a09332f35eb47e6222bef9070eecc6c8 -CompilerSupportLibraries.v0.5.0+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/50554a092af3a4a651b53e3ce3cf8a2d -CompilerSupportLibraries.v0.5.0+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/53ec765d4de3b0bae9727b3b2a27437b184f2072aecda5d0b22d648a95fbba777bb89da823bc851d7242cd3f8c212e3fdaea8e5af11db21c578c2e12db51991d -CompilerSupportLibraries.v0.5.0+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/b09a5913b537b26aa7f8996b1877c748 -CompilerSupportLibraries.v0.5.0+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/b68020c1b1acf4a1c51822bccc1eb67574ceffae3c133e7efe22ec0cc3a674a7c056c01be02c1c681f469fe1443d76baf4b0e305bec8181e57c3ce5a446a5c22 -CompilerSupportLibraries.v0.5.0+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/1e4c5d2084f76eacb4419214668c6594 -CompilerSupportLibraries.v0.5.0+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/696155b560bfaf592bf7024ba0e6f084382dd269cdd25416fa8840387c101132901e94709c8d0534f038666a6f6849c3d55e8bed4223b5be499e099b49610e77 -CompilerSupportLibraries.v0.5.0+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/63b386e59f3732d03459c59000fc1382 -CompilerSupportLibraries.v0.5.0+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/f6c7e0611df7fd86cc9ca63b380e112561d10b489bc8fbfe911c441ef5e87776761d3c161ff5f6aade479f7e96456084c6939d7eff175ced4f42b3b9ee29426a -CompilerSupportLibraries.v0.5.0+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/07e22a4b58aaaf145e52b36602c5b08d -CompilerSupportLibraries.v0.5.0+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/8a047b0098e8504e2dde0113170416686bc70f9d685fcb19bf3eb76afe30dc16a3b0d2023eb704c25025bbef87e99603dbd2a2708b1a3df908747b06cbfc92ee -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/23048b3be33f184ffc9be42ca914aa3a -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/4573b21e34f4d8127a86c18f95065039da92eeb9ade4058bd8459034bb4a003ceefe29e865089126fdc36cffd95a9c12bcb72ed74bff5987a9d1f4b300ecfe45 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/3314ec0668abf069c900558de0690b65 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/d012c4674401773000f0de831cb8b4b6c454d0ab68d51fbbe970504e76c693211086a24a7df34de2390eaeb438ab23f63c68b480a408ab2136f442aba5094bd7 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/e7768c00909613b8f29f6a5860ff4247 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/43c29456a0fc74c4fda42d088903651c6bbac6b842f2aa600e3019b391b04158ee97f884e6962bd9e7a9cf337dbb1cdb2151d103e1dee5214ba798b167b1ed32 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/b2a30e92ba8e40ef070e3ec7c16b97f0 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/64a4029dd1e84922728b2c93a455d7d6b262c979dddf59301ff96e9c28980fbd9c1db57e81afaece96ccb51b9751e5a0180b84e412427430487280c56d8da266 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/b0610d32a80b3f87baebf0250b0f92d6 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/3b7098fbb82e4a7a903b82f942303b248e0e35be13a47e4839a036085c4a33925f1f78fe941b852331cc52de80f32bcdb9a64ccff0386e1070a6ca4600c08eb8 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/3f905dd4e8b3cfd2cc3f8efcaa50a407 -CompilerSupportLibraries.v0.5.0+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/22af14d245e3c062131dd274afa6d9c7cde9a11ee2455e27ae2f7725a025fc2cd6cdb3a1a3c899988c6c3412a714c1f0763f4e08924726212405938c3cf66da5 -CompilerSupportLibraries.v0.5.0+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/2c56a22c935dda76831f36c713cca099 -CompilerSupportLibraries.v0.5.0+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/6bd9bd6ec8b6b18013b3c6de344de134835c9281d39bc5e6e31928970c60b584fa625df18efbce3ea571dee53011dec73e9aae9159e812f219692fbb4dd86a2d -CompilerSupportLibraries.v0.5.0+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/e483c3e85b4d4b2685ee4e8f09951ac1 -CompilerSupportLibraries.v0.5.0+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/47c2f305237ccd55ed2ba445cbcd599c23f9c1392388017506f9d61a4dc8fec4ba4136be81a0e82de4f161f6788c4a62acc9d71efe6cf90b766e5339950ed337 -CompilerSupportLibraries.v0.5.0+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/41c25d9cf7545721b8d4dd2386e95ead -CompilerSupportLibraries.v0.5.0+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/173570bbf4eb60d678472058ec2c18732cd27ad2911457c83f47a1d97c1c0028d91005cf56539e51d4a04178544ac0bba47ea27e74b6b4e8d3310551ad3167fe -CompilerSupportLibraries.v0.5.0+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/f124c93580a038ce806f479568b46597 -CompilerSupportLibraries.v0.5.0+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/c313390dbcffaea6cb5202645b5304134a1ce6aac5a3835696f45316c8170b237c04f13166694eee0f31903ac1e5c3cd73ad8974ba19b44289da3504d3436f8c -CompilerSupportLibraries.v0.5.0+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/050fe7a6bdf980c198f4c201629d15e0 -CompilerSupportLibraries.v0.5.0+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/211e435f5e2b7209aedaf4a81b5e0d5e615b9144de248c06e43dc61b31890dbde80d718e74454b489bd1f77476d34bd01d3f9a25355bc50fca0dc07df0264cad -CompilerSupportLibraries.v0.5.0+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/3566d0f714c1503b92160b486a4eaa4a -CompilerSupportLibraries.v0.5.0+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/b2f29c1c6dc35e1002021f8f15a20a72a57c346b33a6d045ff7a261e88767738a4da1dd88aa71a20514bdf6376099979c9d938173fa3ae28641c40372c94db60 +CompilerSupportLibraries.v1.0.2+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/20ebaad57850393b6ac9fa924e511fe4 +CompilerSupportLibraries.v1.0.2+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/020de4d8b0ff6bedbadaa305ff8445e6849f12053762ea4aa68412d1ec763dbd86f479587a2fbb862487f1feb04d976c38099ddf3887817a3d32b3f029cf85b1 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/3908fa1a2f739b330e787468c9bfb5c8 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/1741e3403ac7aa99e7cfd9a01222c4153ed300f47cc1b347e1af1a6cd07a82caaa54b9cfbebae8751440420551621cc6524504413446d104f9493dff2c081853 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/2444dbb7637b32cf543675cc12330878 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/8537f0b243df8544350c884021b21c585fd302e8dd462a30a6ee84c7a36a049133262e5d1bc362f972066b8e8d6a091c32c3b746bab1feb9fccf2e7cca65756c +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/d79c1434594c0c5e7d6be798bf52c99e +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/7e71accc401a45b51b298702fb4c79a2fc856c7b28f0935f6ad3a0db5381c55fe5432daff371842930d718024b7c6c1d80e2bd09d397145203673bebbe3496ae +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/f212059053d99558a9b0bf54b20180e1 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/5c104b1282cec8a944e5d008f44a4d60f4394fd5d797fec7d1f487d13e7328cd9c88ec4916dabf18596d87160756bda914e4f8c5a356b5577f9349d0d9e976d6 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/3e3b3795ee93ef317223050e803a9875 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/85d3c955e15f66bfe8bfec2f28c9160bc03d4d531ea4ffe6bc6b51e0d69ccea3ab67a16ca752dabc870861c407381c4519d75c6be3832e8dccd6122ec8c6ed75 +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/cf2d1315f6a348af2e6c065e2a286e7a +CompilerSupportLibraries.v1.0.2+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/58420377bc77aa7678034ee5f708eb6be7db359faef2c2638869765453633da9bf455512bd88e95b38ae0428ecc4053561517b176b2371129bdaef9d8d5dadfd +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 +CompilerSupportLibraries.v1.0.2+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/f5c09ed7e0eeb8d345d328f950582f26 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/9c657f55c8fcdeb404be168a3a63a5e84304730fe34f25673d92cdae4b0a1fcc6a877ee1433f060e1be854c7811d66632e32510a2ed591d88330f1340b9c20de +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/c685518aca4721cd8621d510e2039683 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/b760468c6377dcd2b8dd50200daaabe604006afc070984d78152b2becd0680b59036c9a6e91dea490121bd85b58d285bfc1e1cf696d29af236528400101de36c +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/8faf5c8ad62ab10f71dd2ec9683053e2 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/921239f241a5c89710cf07272d7f6c3f10201a7533068ed1e9643f9fb2f439e1bb765a4966d913829866ee0ce4f1589d30d06e4b5c1361e3c016a9473f087177 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/b38fcb70691ac2621379d298eef8c79e +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/06c7f64257ce721f5941f6e50a0d2717cdc9394fc532ded19ce3eaacd5e92a416969534227562e4fee04d2b6340c650d8bc9779e14519b90038bc41e8d1f5ce3 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/cdfab2c7bc41765caf4441c3caeed761 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/7109d4a7b32c00309c42685f54a86fc2cc63c0c00f65584ad296b6e44ad3320eed1aaf49684a8831841cdffa5555d72f89272fb722a780596e27ef020528026b +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/441980ebd23d72772cbe603f1c275336 +CompilerSupportLibraries.v1.0.2+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/e273d9f1af259a3080df8f173e1808a1ade976a943aba97216bf59a96178e7c052e7a048b0ceee53ab486ed577a2ecb92579857be2f7b29e76322ee1f13c9d76 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran3.tar.gz/md5/6decf8fd5afb50451771c761e63a8917 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/4984724bcc847724b1bc005b6f760a18b68147f7d5402d0faf4e28fc0d14fa10975368a951f9caf2a8856500046dec8343043274557d58269e77492b929a9e4b +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran4.tar.gz/md5/39d1e8a3baa144c018d3eaf7f3806482 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/fc4d429279c5a93b6c28b6e911b1e7cfd1c1cfe46f11f2e901b3832ce90d45f49d3d29f0ef18518a94af6cc8651f67c4ed81672680f9281ada390440b172a2af +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran5.tar.gz/md5/37dabd9cd224c9fed9633dedccb6c565 +CompilerSupportLibraries.v1.0.2+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/b253149e72eef9486888fbaace66e9b6945f4477f6b818f64f3047331165b0e2bc17aa6e3fc8c88686a72e478eb62c8f53883415d5419db448d8016fa3a1da5e +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran3.tar.gz/md5/afdd32bfadd465848e6be458817a44ae +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran3.tar.gz/sha512/eebd679c499143014514c7c9d1875dedbbab9e3af51526c4dd445a9e3dbade95d24522da8bbad0a50ab400755e47b018828b324c4ad7705e212ccd990e34439a +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran4.tar.gz/md5/bc4a0f0b7cea328f7e8850583774496b +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran4.tar.gz/sha512/82285b67946212b49cddf6259f2c60ff5469f8c5263ccefe44f1d93ace98ab68e2c152e1b54434b2f075fd8d192c06d5451bc8cca26d951ad15f3453102f02b5 +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran5.tar.gz/md5/177f0232abce8d523882530ed7a93092 +CompilerSupportLibraries.v1.0.2+0.i686-linux-musl-libgfortran5.tar.gz/sha512/db80acf0f2434f28ee7680e1beb34f564940071815d1ad89fb5913cbd9ac24da528e826d0d54be6265a7340ebd661b6d308ed79d96b67fa5d8c98dc3f1bee8d6 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/756718e5eaa4547b874a71a8e3545492 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/c21c1be10ca8810f56e435b3629e2ab0678926ea9c4f4c3dd003f9e292c075493b83df04401d3bcf7738f1a44098f674f9b01bba9db4b9a9e45ad7af3497444e +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/65ce0024bf8fe3276addbf185ed03e48 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/5e8105a12ab04e2949e41eda50a060dea04ccd98660c7528cfc86e120fe61cca8bab878fd2c92a3858f02ac3f3c55d0e48789907e5fbd2392a8e84b183ed4636 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/b7727324d550f637209db795238c46a4 +CompilerSupportLibraries.v1.0.2+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/864b1db2642e68665b9d3322563c7ce964835d0e720325ea00b193e2cbf6791760e0014710e2a79876165ab0daffa6d53d61b87a5034f956ba6e255b0144652c +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/4e5e4b23dc87450738da33926a07511d +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/fc09879d94b750e75775d8b64a41ab9924d675fb53c5700467604412928fe7f5cb21911da0f64898d2463fa77ffbaf4c96c397b9060f4746eec152747930cddc +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/9a92138ed69aa317a932a615c6e62d69 +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/0b7785379936a2a209b074177b1424dd7e00b29b5165f564e799b0aa4e06a582e9d616525d97274ba2507cb88192028f1ac485d3f99bdc7ee53fc63c1a7e85de +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/8ffee3d6de5197c7a1f354d72c8238fa +CompilerSupportLibraries.v1.0.2+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/deadc4d7224c84f9b82dc956b69e815c44ae036802838365d870ab9f58c8bcf8ce0645f2f387c8ff344ac2108fc8e7e1ee907fa55e93c91aa5d9fd921bf3fdcb +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/87449e72e3f33dbb69b7053cdc2649d4 +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/5ce02ad10c6f4686a476eb2a5de2988cd8b482f5e693db2880c84ad1c82f468ef03fe01b9d0feefe5d4ee741d1d16643d36b144e6261ed32311b3b6f312fac2f +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/0407cde92cfa42fa89ac83217ca0ec16 +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/032c831f1166a336551138939ac40eb2c68a048ce786c0c1403b879a20c1b706caac16d22560b2c7f2b3d6373986c347188675674116005ca251336ee048d09f +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/23418763b808371ee94772a90d501f4d +CompilerSupportLibraries.v1.0.2+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/7867b843551457b11bda7821dd384c1c1cf23b80a308b2058a693de7b7da099f0b37eb0a6de2b84c04b625a68c60eea55138e200d5d6ec6f6af09bd7ce406a96 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/e3d33ae03c18affea74699bdc1fabb68 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/42013f4921de5a69ad857195ce5c19ad1bca3c920d79699e5501f1f4534ab132fabd422362b2b5056f5d182215d6c069db5df460bafa700903faf962cc00f77b +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/d40c1e8c0393213c6057c53a12f44175 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/fe7baa4de7490065ab7b953cc12f41462a24bcb49d0a4a64b23249e98e7569b19bb1cb455af2f76090e34066a7d3cdd7a48cae6515ce6c7a5c8486b0cacc5106 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/48541b90f715c4c86ee4da0570275947 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/7f2683fb98e80f12629f4ed3bea9fd59d32b7e7a9ed1699e782d8e238ff0915ecc61bf00adaf4597cfe41caf82cdca0f9be250f595f5f0bea6d8f77dba99eaf4 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/4547059eb905995667be48bf85d49911 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/7400fdabc924434ab4a4949248c3603887ac06ffd2f205ae33e14495d86cd4f816bbd1999eeafa0257f518df1e7f7c522f596e847a71dbfbfccff4859f50acc7 +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/46267543cad6584d7b7b9fcc8f18f21d +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/0353d7d724be48d4185d3c181692970b7996f53f6a01723072aa5c94b53a8c5055faeed30df51659c252a46f4b941dec0cb24569323e3c85c166f14c5b7c8e9e +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/14dba2897a6e9d370fa9091c045375fc +CompilerSupportLibraries.v1.0.2+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/10b79f9c059839f5b57fa8d2a381a034c4067262c4088bd354d14ea56bec097878069383aa9cfadaa09d73bd20fc348fb61662d863a8d62cb25d7af6b8e29858 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/eed836d1addeb10d0901f836724aff1e +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/e33eca424d1529a1fb23ba9cf7fac345ed1cfc8073c975b6b31ca44d2e8c3f5083af65433df009b22483dceb2e43149f3c1e8433681fec5fb812e1d5b4243ce4 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/d5ae9f9519341fdaabf62267c89461d2 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/6421aa5d1bd6f08ad43f59ed4dc1bef8b9b598ebbbd3e48149730f3bec3471f8e2c02ffb338427326924290b8f52ef9e626e3313448bc931a61d866c5dc544ae +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/fc1df521395362a5aaa2e2aeef707207 +CompilerSupportLibraries.v1.0.2+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/f2e5a08e3cae171242ae6a20d2d4838c1529ce042745dc466148b7bbc06896d94476fd05c7787e6e8641bea752dfc0e6b09e95b160bede600d20d2ad68e7705f +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/2338f8aa2696935f7460454e708ce308 +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/5a4b0e97928c26eee16bbec4c3e69e55fa9c768101257c3e2f161118809c778aa0feaf21307198822c3172a58ed12ca0a49285b2941ed0b8f2b367e64ca1c51a +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/b393d2bf0d181d218130ac572c17d369 +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/76e0f7caa24bb734c6f7542be9f834d5b912f082cb3c4c3c52a63e37d4b8c33dd94e576c43f4bee6c04bfb44af2f2b67ba70773fa52ad0de6c8c0059b3e51b83 +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/23db836e6e4142f621862971017fe61e +CompilerSupportLibraries.v1.0.2+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/c0b04f7fe5aabfe6af509c77a1f68e0bcfd14714758042fe502b968c4cc272156fc84c8b4c1ee574754bb2fddaa810f6a4215cbd164ddc11b697b3adaef09a81 diff --git a/deps/checksums/curl b/deps/checksums/curl index 4b6e8da990d69..85974ba0bc8a0 100644 --- a/deps/checksums/curl +++ b/deps/checksums/curl @@ -1,36 +1,36 @@ -LibCURL-fd8af649b38ae20c3ff7f5dca53753512ca00376.tar.gz/md5/f082283e6a35fcba5b63c9a6219d8003 -LibCURL-fd8af649b38ae20c3ff7f5dca53753512ca00376.tar.gz/sha512/3bea5fa3fb6d29651daa923ae6bcb8eeb356ab9f2a1f3e005a6b746b617b0cf609aed4cadda4181783959840873c04b18e34e45ab973549169d19775a05ea01e -LibCURL.v7.81.0+0.aarch64-apple-darwin.tar.gz/md5/16d584cdac9f1756de1935c844f2095c -LibCURL.v7.81.0+0.aarch64-apple-darwin.tar.gz/sha512/38f800e309fddb2cd103ef5c65ad1ef2f7ec0dd7711e9afdb716b96b802c7fe089b04ea8d2bd2e675d62adc3b8aca3c7a243780f097b3466a496dbb25d2f7807 -LibCURL.v7.81.0+0.aarch64-linux-gnu.tar.gz/md5/6f70f7df6325bf6b62531d52ad313ae6 -LibCURL.v7.81.0+0.aarch64-linux-gnu.tar.gz/sha512/303fb30e2859c9d11fe64e964405ec2d4bcff4bafaaa5815a5548fdb0b42fca91fdfdf85473737b03399817f0ca6e23d870f56c354b0e53dd6ec142f2c69b182 -LibCURL.v7.81.0+0.aarch64-linux-musl.tar.gz/md5/b7aedf4bcbadf952c600d30643a2e284 -LibCURL.v7.81.0+0.aarch64-linux-musl.tar.gz/sha512/8bedf575e4eb2d4844b97b13b00f3d2c1fffccf10c1adbe11392053f7f956bd7592ac32a1eada474c57cc8d77999e214945ad6cf5242e577fa9ada29b35eaebd -LibCURL.v7.81.0+0.armv6l-linux-gnueabihf.tar.gz/md5/ed25c1478101dce0e37c18c68bfc2287 -LibCURL.v7.81.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/6bc00770fea95aa01e8144738833990fb9080807efc3bed31b8ebaa45c04fe2817d8bcb0179f0322d60b77e4dd59514032978a680320fcc20287a0ba549e9972 -LibCURL.v7.81.0+0.armv6l-linux-musleabihf.tar.gz/md5/ce3591ab3e9b5c1da0b7f44ac3c03ff5 -LibCURL.v7.81.0+0.armv6l-linux-musleabihf.tar.gz/sha512/355c9f5d278d49329dbc56219df64f5d2b37581e1ee6cf2100deb52102f90ae7c9fdc047b9a341489985062d2461c058c1c8feb557776e7cf1563d4f49cb0a08 -LibCURL.v7.81.0+0.armv7l-linux-gnueabihf.tar.gz/md5/1e86f1abdc9ba03f26155f46db952150 -LibCURL.v7.81.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/cc305e36e7427cbfeed7d5ddb10d34eb6f7475e1e9695f829fcb6400498ed5307051ebd31a28193b99cf11e87f79cb4f8a66e589f10b76b9ed6898a11e917b09 -LibCURL.v7.81.0+0.armv7l-linux-musleabihf.tar.gz/md5/dfaf544cdcf189cd09951aaaa26fbdc2 -LibCURL.v7.81.0+0.armv7l-linux-musleabihf.tar.gz/sha512/a412fef9e80f956f10092996b29c86f3fd673421339a0c502b2230bbca97065877ef379b18380197d071234abcd818edea797c739410c78170244c7eeaa141f4 -LibCURL.v7.81.0+0.i686-linux-gnu.tar.gz/md5/b8561fde02ddfcb64f724cd037cb59e9 -LibCURL.v7.81.0+0.i686-linux-gnu.tar.gz/sha512/904c043db84bef78f1bbb7b7ae1ba177942ad316ec39cdd7f28f9b2d3c578b8a835eb86d8ee91b604ed14e10b9200ae60ed8312e8a1ab7684e20d75536242e60 -LibCURL.v7.81.0+0.i686-linux-musl.tar.gz/md5/5fc2e3fbe3ccc362488e79fbd5eab20b -LibCURL.v7.81.0+0.i686-linux-musl.tar.gz/sha512/495be4a6ae0526c5ac6983e96b342226cfb2fa5c203135f0a402bbf3e8486d820454b8964c1a9fac4695df1619e5555a61a8cb4a3174c99cf0e8a3546a7f8749 -LibCURL.v7.81.0+0.i686-w64-mingw32.tar.gz/md5/24aa660ea3f5c019fb81f609bda7c44c -LibCURL.v7.81.0+0.i686-w64-mingw32.tar.gz/sha512/64f75cde988dedc0abbabb912b90850b07c54b24f8544125d6ceac5989337266cf3ea78b0758b58e3a490c7335090b8ac45d1282a2fe15dfb4fa93f55d4a46ab -LibCURL.v7.81.0+0.powerpc64le-linux-gnu.tar.gz/md5/26568c1b5e75fe00189cb6ebe6fa9ec2 -LibCURL.v7.81.0+0.powerpc64le-linux-gnu.tar.gz/sha512/ca7b2bba5190500275236966b7014935285b22ff551698a532681b970e461feb507fbe682ea95833ef453bdb5bf0516948fd9ca8971e10349252d286593a4792 -LibCURL.v7.81.0+0.x86_64-apple-darwin.tar.gz/md5/07850295b3ab6bb6cd63fcd9d4a35e6d -LibCURL.v7.81.0+0.x86_64-apple-darwin.tar.gz/sha512/cfc9fdf3f0891ce26d077696a4059a9fe0d95793dd391fc530b94367d074ce96bbb9f8a3af4cb5dcbbcc8c4ae160fe17146011bf805263ae4fefc36f320402e2 -LibCURL.v7.81.0+0.x86_64-linux-gnu.tar.gz/md5/39dc13a4ed2492a9ce9675737e8b5b10 -LibCURL.v7.81.0+0.x86_64-linux-gnu.tar.gz/sha512/f6e1c439620717be028a28fc9878d1618329aefe92561a2d4d95026bbe88c91526bf98a3b2e4643f47ad3ac047986c4461c5ace67412386f2ed53084826e5523 -LibCURL.v7.81.0+0.x86_64-linux-musl.tar.gz/md5/c7dfa116097f19421bba42728567a543 -LibCURL.v7.81.0+0.x86_64-linux-musl.tar.gz/sha512/91d3d99d67243bf6eac3aca09bb59d6b41bb5dbc4d7ecd6e81f84a9f7bb9a619ba5317ba06bdbc59ba372b0a9c5ef26d6d9654e8661ec6c890ef8bb189fb44ff -LibCURL.v7.81.0+0.x86_64-unknown-freebsd.tar.gz/md5/a19342f14c554d1a4a8355c17ee9e662 -LibCURL.v7.81.0+0.x86_64-unknown-freebsd.tar.gz/sha512/45ef0edb6a850ed0a45e7094fb5766b59ad325c29612a269a3e3a89cbc5fe62b06f9967bee5bae1239d4884e12af751e8c5054eb124a4ecdd06993b04aa6ea05 -LibCURL.v7.81.0+0.x86_64-w64-mingw32.tar.gz/md5/cffc213693c62d651f9cee6ed726eb81 -LibCURL.v7.81.0+0.x86_64-w64-mingw32.tar.gz/sha512/4b15a3240152aec816e16a25778aa5f5c26e8d3fc6e1db326ff20bafe1dc1e84f665dbedbca3a12a9486768d6128c2d1f18d07f812c5b74878bfe3173f130229 -curl-7.81.0.tar.bz2/md5/f42ab772edb85374fc985ae65810439e -curl-7.81.0.tar.bz2/sha512/4889e94998cb9da3f05a70e61e7a0599a0fd3529455f5b3664ede255a834276f1d7898bd370e9b0fb21b0c0ffe4ce50c0757bb8bf896943726c538f8ead0cc41 +LibCURL-a65b64f6eabc932f63c2c0a4a5fb5d75f3e688d0.tar.gz/md5/e8c53aa3fb963c80921787d5d565eb2c +LibCURL-a65b64f6eabc932f63c2c0a4a5fb5d75f3e688d0.tar.gz/sha512/8e442ea834299df9c02acb87226c121395ad8e550025ac5ee1103df09c6ff43817e9e48dd1bcbc92c80331ef3ddff531962430269115179acbec2bab2de5b011 +LibCURL.v8.0.1+0.aarch64-apple-darwin.tar.gz/md5/f697b4391608c2916ef159187e0d0b29 +LibCURL.v8.0.1+0.aarch64-apple-darwin.tar.gz/sha512/41da87eed77ffac391a60a4af7fdc707f117affebe54960eaf43e3077440ce17d95fbe0f47de41bb1456e222e7a126d687fa0beb26cf98713b3472e9b3ba9e57 +LibCURL.v8.0.1+0.aarch64-linux-gnu.tar.gz/md5/9d3e7e7601ac21a587bbb4289e149225 +LibCURL.v8.0.1+0.aarch64-linux-gnu.tar.gz/sha512/67ac7bc108cc274ee5e088411dd9d652a969952892236d6c37a6dcd710a1887f9ff83df2c01ca0f5b16b2086852077d6c62ae7a13f7b9ac4b9e257cd1aacb0ea +LibCURL.v8.0.1+0.aarch64-linux-musl.tar.gz/md5/bd2b62cd40b9e87fe149d842d4ff55ca +LibCURL.v8.0.1+0.aarch64-linux-musl.tar.gz/sha512/7c6bff3dbe341e2a271b61e02767a25768b74631894c789fffdef580605d821518274a04d9441c9b5d3255b9a9297d0d35f22310dccaab367aa92d928f25c062 +LibCURL.v8.0.1+0.armv6l-linux-gnueabihf.tar.gz/md5/9effcc21c5074ef88ad54c8b6b7a3f8f +LibCURL.v8.0.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/9327fc8e0db9edcf941548b0291e0bafe9b956e92f6edf47795ca961303a24ed305b30b09f29478a70149056411c4ca4652facbeca89c2bb3db41a6c97df14a9 +LibCURL.v8.0.1+0.armv6l-linux-musleabihf.tar.gz/md5/9cb716973ec75e2a2fa7379201aad59f +LibCURL.v8.0.1+0.armv6l-linux-musleabihf.tar.gz/sha512/3e4d22be628af7b478862593653a5d34c2d69623b70f128d9f15641ab3366282aadee96bc46ffacafa0dcbc539fbbda4e92f6ff5c7a4e65f59040948233eabce +LibCURL.v8.0.1+0.armv7l-linux-gnueabihf.tar.gz/md5/95bd98a64034f8dfc5e1dda8fb7ac94e +LibCURL.v8.0.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/6a7898670e71efd7f06e614cdf535cf390eb6def9e93409d4ce2d9811a8e1f892959c0f6ca8e370f49e215df495ee8f95e1b7d9f92e2708ca548344b6ef9cc22 +LibCURL.v8.0.1+0.armv7l-linux-musleabihf.tar.gz/md5/42aeb569e80865377c65bba6cc84b262 +LibCURL.v8.0.1+0.armv7l-linux-musleabihf.tar.gz/sha512/fa46e52d8abd49e22636e48fb43f11be95bfdabbc13142e0cdaf4bb892ff982eb09abd9f3bf1c33ad374efc18ce21ab9968ed22c084411a55afddec0c459ab3d +LibCURL.v8.0.1+0.i686-linux-gnu.tar.gz/md5/ded5d6d6580b979c372992c0fcf0aad6 +LibCURL.v8.0.1+0.i686-linux-gnu.tar.gz/sha512/f8a40285a25d61878e87d525bebcfe6e8c30cc5a40f38297de774c8e3191490c38716b3938cf81582afb23714a38405c20ed0241bcd3d41c68a5594822498b70 +LibCURL.v8.0.1+0.i686-linux-musl.tar.gz/md5/cd2bcf96545c783f5012611824169a93 +LibCURL.v8.0.1+0.i686-linux-musl.tar.gz/sha512/318dd3adcbf36c7979df9f394e78b7fb876dc60c9ec87d6b0edf47676c69df4dc3e73c07b2434b15c6e7497b385dc0fbf3fe7e3235b291a369f6f1d883c99645 +LibCURL.v8.0.1+0.i686-w64-mingw32.tar.gz/md5/276cc56eaf744ac0a5cec6c8c396ede7 +LibCURL.v8.0.1+0.i686-w64-mingw32.tar.gz/sha512/55cd7882ad976aeed1acaab7b1d59279ff3a0d2456d0bffa6240957ac6f152e903485f0ca05baafa5e97e0d1474cb204987eb9c94b1b2ddd657b52864a44c646 +LibCURL.v8.0.1+0.powerpc64le-linux-gnu.tar.gz/md5/cfdc41294b2f4aa85bb8b27beced17ca +LibCURL.v8.0.1+0.powerpc64le-linux-gnu.tar.gz/sha512/24f92091ab44a3be40228a9d9a57febc026f49b12c538c98e46a06dbcd679086332b773662126c68dbe4a60dd90a77c970c8a398237afbcf06c660fdbea16a76 +LibCURL.v8.0.1+0.x86_64-apple-darwin.tar.gz/md5/10a19a4f428951adbca7cfee91406498 +LibCURL.v8.0.1+0.x86_64-apple-darwin.tar.gz/sha512/28ddbad4310ed886c65edf28ccf01a5aba77fe11784740600aaec2aaa5c10c5e5915e297a4d72dd85bbc5304bb2027f5d18b95f13868b4bb1353fafed7bce4e0 +LibCURL.v8.0.1+0.x86_64-linux-gnu.tar.gz/md5/a68df850605cc9ec24268887e4b4ea77 +LibCURL.v8.0.1+0.x86_64-linux-gnu.tar.gz/sha512/f532dfcc84dbb4b92229a79b5629b16198061158e1f12d2dd37948cd0ceccc095221b5fc9a8e2de30de19727c727ee500c8ea4508722c677c7938ddef1c40350 +LibCURL.v8.0.1+0.x86_64-linux-musl.tar.gz/md5/023a2d8271173de0a02bdca8d1d55bbe +LibCURL.v8.0.1+0.x86_64-linux-musl.tar.gz/sha512/e3195f917c250f31ce9669c304918b33664c5b03583f328929e73377f4feff525cedac42dc74adc9ba98a704630294a5697f07eb95ca520c6db4a67f0f83383f +LibCURL.v8.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/ecd39a1cc45ee76751e1e3c5edf469d7 +LibCURL.v8.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/fa06afb1173bc23474f8f7992268ae9a0df52bc3c1af86d2b60da2cfff43371bb029b51debe638d81d8a1dd334a95dcd3c53dc12923220ad9b1336fcdad1ff8a +LibCURL.v8.0.1+0.x86_64-w64-mingw32.tar.gz/md5/d9a735335e3603635a56eb3b86e6ea87 +LibCURL.v8.0.1+0.x86_64-w64-mingw32.tar.gz/sha512/8fc6677b1be27a900d2a984cf9f9f4b3aa1555bfd732da2bd6553c28da98048c4c86216b57744d7156de94c522b013768e57f42e662845002e5bd9f730c818a8 +curl-8.0.1.tar.bz2/md5/b2e694208b4891d7396d118712148ff3 +curl-8.0.1.tar.bz2/sha512/24e84e922612ebf19341525c5f12f36e730cd21a5279cbea6421742d1ba61e5fa404f2add2e71d64e5692a1feabfa92c5a5d56501f161d1e157718fee467e0a5 diff --git a/deps/checksums/dsfmt b/deps/checksums/dsfmt index 12d071b1fab2b..edadf5c01b1d7 100644 --- a/deps/checksums/dsfmt +++ b/deps/checksums/dsfmt @@ -1,5 +1,3 @@ -dsfmt-2.2.4.tar.gz/md5/ed30e63552d62df48d709dde4f755660 -dsfmt-2.2.4.tar.gz/sha512/fe84e986cbf198172340adfac0436b08f087643eca3f1ceccacde146cbfd8c41e3eb0dfbb062f7ca5f462db13c386abd7c269bc0cbefc9a0ecf97a8a8870a2e4 dSFMT.v2.2.4+1.aarch64-apple-darwin.tar.gz/md5/0299af20dae6bed519635900687f4aeb dSFMT.v2.2.4+1.aarch64-apple-darwin.tar.gz/sha512/5f20bd7602f09dcb23299d979372453db9a0e76a66129d69cc93c4b45a65ad377486f3cecb7093ff65307f515358420dc318b19eaf5945ff2fbfbe6886e95efa dSFMT.v2.2.4+1.aarch64-linux-gnu.tar.gz/md5/78a0fa53ad3db17f2849c744246a6bc6 @@ -32,3 +30,5 @@ dSFMT.v2.2.4+1.x86_64-unknown-freebsd.tar.gz/md5/e27869ac4f1ea6774ade7d3b53cd301 dSFMT.v2.2.4+1.x86_64-unknown-freebsd.tar.gz/sha512/762571a5d5773c2d9780586603859272f48ed67d6c8b09cd95c92fd62dc9bb03c274b12c2c04e05f426c9a42edbbc8e33beba3c79865f2c49459eca2d588b14c dSFMT.v2.2.4+1.x86_64-w64-mingw32.tar.gz/md5/74e5c27ba9eb654b4e998ce73719e724 dSFMT.v2.2.4+1.x86_64-w64-mingw32.tar.gz/sha512/59badcef14b06f14f8f5bce1c72de6750c8310ae18581e24b5d663edefe1bed3d120b4cebb87b53dc664411b62d9802f75aefde4e5236ada1dec740e6ef2445d +dsfmt-2.2.4.tar.gz/md5/ed30e63552d62df48d709dde4f755660 +dsfmt-2.2.4.tar.gz/sha512/fe84e986cbf198172340adfac0436b08f087643eca3f1ceccacde146cbfd8c41e3eb0dfbb062f7ca5f462db13c386abd7c269bc0cbefc9a0ecf97a8a8870a2e4 diff --git a/deps/checksums/gmp b/deps/checksums/gmp index 6b95ca883ddf8..0c45aa6a00ca9 100644 --- a/deps/checksums/gmp +++ b/deps/checksums/gmp @@ -1,118 +1,60 @@ -GMP.v6.2.1+0.aarch64-apple-darwin.tar.gz/md5/e805c580078e4d6bcaeb6781cb6d56fa -GMP.v6.2.1+0.aarch64-apple-darwin.tar.gz/sha512/62435e80f5fa0b67e2788c8bfc3681426add7a9b2853131bbebe890d1a2d9b54cebaea0860f6ddd0e93e1ae302baba39851d5f58a65acf0b2a9ea1226bb4eea4 -GMP.v6.2.1+0.aarch64-linux-gnu-cxx03.tar.gz/md5/5384d6ba6fd408bc71c2781b643cd59a -GMP.v6.2.1+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/99bdf165b44b53605bd3121a741ca4576c4dd37861f4e2e2f3b508c1a797855ba8647c98f24ded875a1dc55ec577d4f617620c05606b4d2cf04361c143b818e7 -GMP.v6.2.1+0.aarch64-linux-gnu-cxx11.tar.gz/md5/67641c0755a252965fc842d0f55ea24d -GMP.v6.2.1+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/a7db5855898adad99b2d36fce5d8db5b1aaccf47d5fc419c6c52c838a68ae374e49f76424807c351213ee5339acf2bdd31b38de9b75bb826b6c6a37c48c6f12c -GMP.v6.2.1+0.aarch64-linux-musl-cxx03.tar.gz/md5/49377ccee261d425e9fb6d7716776742 -GMP.v6.2.1+0.aarch64-linux-musl-cxx03.tar.gz/sha512/9e8c92c86dd98e339691796362d8a70d51e07d7ba78cc48455ee15243f9df84030ba5cc3080a200a6e94e0c002a7517e702570cc6e6d38301e90f991556cb090 -GMP.v6.2.1+0.aarch64-linux-musl-cxx11.tar.gz/md5/cd4cb308ae7736ea91ec5b97b7c80923 -GMP.v6.2.1+0.aarch64-linux-musl-cxx11.tar.gz/sha512/d84f399b8e66d5d86abcbfb30ba23a1676f5a469e8cde6b316322889ca0a242533bb537bb3cb5c57407dc9d91ce9f3cf879f71bddaf2fdef08b103e30674ec39 -GMP.v6.2.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/c115f3f1e1575649e8c898336cfb6300 -GMP.v6.2.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/3443d0994b625adb2c4bb013618372aff82de1c54022c45c7bf1bd043fbda6f50a4202018066569572a3ef8bb90725055628e1295b454dd56951b408d67c2f56 -GMP.v6.2.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/c80eb461a5427bc83ed38a2b15a6a354 -GMP.v6.2.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/3bcecb52ad64e587d50a6bdbc19560cd961360b265403639662b9314806e394848e64bd9d2be6881a3467f4c42ab43872035f71d5a0421f3284e5e08ad4cccb3 -GMP.v6.2.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/14f0a09730eda4706b64042c4eb204b0 -GMP.v6.2.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/c405065dba56aaafeee907baaec3e23337ccdc67f2cdfc460b6d5c338e85b0ec211f39df67a885fa5048d444ad28a07207ecc31872888576fbaec0c2881a2240 -GMP.v6.2.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/14b8bd2136680d00261264876da76f34 -GMP.v6.2.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/f6b5f56af796bd0e9411eabc53864c773002ba5fee44398770260cd1a0114ce9a1e0d3531f424f968e16436fb9f51e31b896fa3f43277405fe759141093d556d -GMP.v6.2.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/cfa020ab9b1f0d9f761034ce28acd851 -GMP.v6.2.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/2678b9b45a065569c58ed840bb5b0f5fb1a1e59897fa7054738760aba737b11f1d180f632f3f04a80a5530463b005367e41f4c599526abc688e86cd9a032888e -GMP.v6.2.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/8b238ae87be9b3ee908044b5dbd295da -GMP.v6.2.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/4e91356b3a294f1eec4309e6db0d9160edd5afe3d3cac6b8211853ff179642504669c403e8e6053accf187d86d55a7700db64e870c6321484247cc0648e4e793 -GMP.v6.2.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/fe1c989a71b4a292f96c410b8b467834 -GMP.v6.2.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/b87d0e869cafa27d5b229025b23b081051b25a38291128290fb15f18f5445129ff7b9a74c3076da011e503463521128c5940677e8290e6e15c7a57e92916be2e -GMP.v6.2.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/47323b44eb6f0b1342500cb38943923a -GMP.v6.2.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/11bf94c7df9aadbbb01b83628967c110bf9bbfee68ae9f7792ba2aeb004e555c50ed54974f0a56525eb43555a9de955003ff66283979e02724bd577c73b1e855 -GMP.v6.2.1+0.i686-linux-gnu-cxx03.tar.gz/md5/0b8ad9c99dec6097ecad2f7e8946014d -GMP.v6.2.1+0.i686-linux-gnu-cxx03.tar.gz/sha512/29dc39445cc89e156866c7607bace3e61a18348d3772b5f354cdef2b57e6c1c8be1682b1705f5bfa9314c973a7592db9dfc9027f0cf2ad12c935e876c3845052 -GMP.v6.2.1+0.i686-linux-gnu-cxx11.tar.gz/md5/f8ce89edd1412fd54b98c1c85870ecee -GMP.v6.2.1+0.i686-linux-gnu-cxx11.tar.gz/sha512/add5da0d9b5b1cd66192fad3028203114fcf4908d3bd24e6d13fb59d85c4413f82cbde917d6bbc9f80186da54627531680ccf53146b9c0262523c29192a1ac85 -GMP.v6.2.1+0.i686-linux-musl-cxx03.tar.gz/md5/84c1f2487a10daab9b715d9cc490253b -GMP.v6.2.1+0.i686-linux-musl-cxx03.tar.gz/sha512/fb4a2f27a932273e1f2dabfe31c02d96892d3767c9cc488ade254dc05fe0bb1d9894b4e7afc72a7b9928f05b26bbb4eec05df71aaa15c8d99bc050e64f725663 -GMP.v6.2.1+0.i686-linux-musl-cxx11.tar.gz/md5/5fb602b9d2a80aa041fdd9a7f01b0827 -GMP.v6.2.1+0.i686-linux-musl-cxx11.tar.gz/sha512/f51dd2c914c4957052502ff32bb185115f9950a42d20e3443040574ab84ae929ad04f2e53ca5296803f726182c71438156b7f8691c50d3450824693579fd0e4a -GMP.v6.2.1+0.i686-w64-mingw32-cxx03.tar.gz/md5/c03aad92ce6d66b1a795f7213b0ff9f0 -GMP.v6.2.1+0.i686-w64-mingw32-cxx03.tar.gz/sha512/97789b371cb66699fc26e32f658020f0dd9fb4242bc2253be5ccee91e0cda8bfe393b8c57c4bf3ee2ae5c585fd79ad42a48c058c0c02491358680b9acb197137 -GMP.v6.2.1+0.i686-w64-mingw32-cxx11.tar.gz/md5/23a32a01e4fa9e34ff72773c913f4cb3 -GMP.v6.2.1+0.i686-w64-mingw32-cxx11.tar.gz/sha512/1a1569eb095d9cb1c697a488cd57dce9d4a96447d19a94dfa95f03f47b91c2cee676f0fbdf313062019eabe4b6ce28d6c5978f53c9fb537ee6e9c39530bb9529 -GMP.v6.2.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/98e4b7257714395e6a64848381187fb1 -GMP.v6.2.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/a8775f0f7d2c0f0511fee0fdacbc60e68dbb4dc92a787849516fd9ae01289dfcf2c5b60e2999568e3374e386f6708adc6d3d17b07bc0345f8ed4e3eb00a1e072 -GMP.v6.2.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/dff490a43ca1d6974c43f2d9356c6c54 -GMP.v6.2.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/557f706464ae636e2720e4483ee4e20d83267f5824ee5c963cf75270d8b76ce8f5c3a1ddc8fbca6ade8d044bfb2e125e19ca43452277a24e6991a6aaf5b4d1f5 -GMP.v6.2.1+0.x86_64-apple-darwin.tar.gz/md5/80a9cf52964de5e9ecca4d4e86606e72 -GMP.v6.2.1+0.x86_64-apple-darwin.tar.gz/sha512/06d4ce5343ab9809908295cc322e0186b5b4cd94b67fbb17d5c648a7f5ed219eb15b8a4bbac2035c078e66eda80b412bb630fff1a9bf60722ba23526e0dfbb9c -GMP.v6.2.1+0.x86_64-linux-gnu-cxx03.tar.gz/md5/a0e34e7eb8dc0a45fa7a746d5077d8f7 -GMP.v6.2.1+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/ae6f052766ffe8e9595ce78d8ad8589459456801910b78321fbd174b095a6822ec046ca69ec9496f0ee676312e7008ca7e9b179890c586672eeab817c8da67b3 -GMP.v6.2.1+0.x86_64-linux-gnu-cxx11.tar.gz/md5/a444271a5a3fb646a1bf3071b4b58109 -GMP.v6.2.1+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/a756425aa4d67cd8822f2fb23d055b455787ed1339f1995f9bbf905021d041dd663ddafd1c681a35bc5e124ce6acbb69789ae483ed7168c9fb8a6bf1bc9c144a -GMP.v6.2.1+0.x86_64-linux-musl-cxx03.tar.gz/md5/1aa292bffef8ebe685e4486513c0cbef -GMP.v6.2.1+0.x86_64-linux-musl-cxx03.tar.gz/sha512/03968755c53457296b79752ca9808d4660aad8474f876836cec9e9d6c1c38267614a134bd222a50eddac5dddbe923df3a4d11298bd1e019876b717033ffd3eb3 -GMP.v6.2.1+0.x86_64-linux-musl-cxx11.tar.gz/md5/ef269f501c1a331ef6c3e7905047c3eb -GMP.v6.2.1+0.x86_64-linux-musl-cxx11.tar.gz/sha512/9c58c2fc09ec6f5e8e21602fdc22ca49c4b54ed1bbf544717c376a1d67b378cd63b9f25c1a8e3e12c358783eba17662b1a6b661ca5f588655e8b6ecbf490e199 -GMP.v6.2.1+0.x86_64-unknown-freebsd.tar.gz/md5/54b35608d79a2bc3f9d81be8cd8fe7a3 -GMP.v6.2.1+0.x86_64-unknown-freebsd.tar.gz/sha512/79aa5e7705aad4b4d5d248d0bef3ab1d17185ce710058a8f3e74e5eab86190a9150d316eb3a33ae41a497f3a94da03f90768978f2e154c5db57f5345bf0ba4c9 -GMP.v6.2.1+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/1499a265b438cf5169286c1830eb5734 -GMP.v6.2.1+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/d2e6fe76abe0a0cb1a7445ea93cd5bd0bf9f729aec8df9c76d06a1f6f5e67cce442be69b66950eb33aa22cfda2e5a308f2bade64018a27bebfcb4b7a97e1d047 -GMP.v6.2.1+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/fdb4187f617511d8eb19f67f8499a8d0 -GMP.v6.2.1+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/bb6d8ead1c20cffebc2271461d3787cfad794fee2b32e23583af6521c0667ed9107805268a996d23d6edcab9fe653e542a210cab07252f7713af0c23feb76fb3 -GMP.v6.2.1+1.aarch64-apple-darwin.tar.gz/md5/03cb14ac16daabb4a77fe1c78e8e48a9 -GMP.v6.2.1+1.aarch64-apple-darwin.tar.gz/sha512/5b8f974a07f579272981f5ebe44191385a4ce95f58d434a3565ffa827a6d65824cbe4173736b7328630bbccfe6af4242195aec24de3f0aa687e2e32a18a97a5c -GMP.v6.2.1+1.aarch64-linux-gnu-cxx03.tar.gz/md5/0ce7d419a49f2f90033618bdda2588e7 -GMP.v6.2.1+1.aarch64-linux-gnu-cxx03.tar.gz/sha512/16363dedaae116fa0d493182aeadb2ffa7f990f1813e4b47cae3cd61ca71f23b65267ea4e2c698d52bd78d61e12feaa73179d7b86ab6d6df01eeb7b6a9b27958 -GMP.v6.2.1+1.aarch64-linux-gnu-cxx11.tar.gz/md5/011f1cdc39b9e529b4b6ea80f4c33108 -GMP.v6.2.1+1.aarch64-linux-gnu-cxx11.tar.gz/sha512/1ed2139580c5c78578f350ee83dbf9cd0120d9d36e1951438d757f5734cda7931600b3f83bfe0d0d806926636d6aea8048c6b64aa42a22e59310282c2428f417 -GMP.v6.2.1+1.aarch64-linux-musl-cxx03.tar.gz/md5/34f17083a1f142c284b707cc82407b00 -GMP.v6.2.1+1.aarch64-linux-musl-cxx03.tar.gz/sha512/dd32912c31a8422734c2e5d5a37001ac18f0e9de151982583d9dc185e5cc3e45076d737729345cca8e8eaf42993d4102353261a2de245e26a8a9cd86960a2fbf -GMP.v6.2.1+1.aarch64-linux-musl-cxx11.tar.gz/md5/9ba1b822f20f88a1e4c6e81dc8c4fdc1 -GMP.v6.2.1+1.aarch64-linux-musl-cxx11.tar.gz/sha512/d8a4ecd5c35022b9c912c3b4fabe3a4c31258d6a1bd38e4fea13a3da53206a29bfd90f4d602f6e3ee3ee271d84289d1ecdf45534adfabf7e657daef5b5cb0b21 -GMP.v6.2.1+1.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/23e28efa2579d636cb4c80036da5d4ea -GMP.v6.2.1+1.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/02c8023958fa616c1f944898e686510d449b743d053cfd42f526e9c4fe3ff3dd9de7309694b8537b4bb6dc978085339eb787983ec4ba32dc041448c912a8b982 -GMP.v6.2.1+1.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/bf2a2c4f81f6d04746cc528438f62639 -GMP.v6.2.1+1.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/1c152abeed24761c775e78a64835f8e61b28b16cbc29a6fde88fa4fdbf2a5782cd62697bd03a552d873995bda58b7bdc081c11ecd5e4badde2dea426e5218116 -GMP.v6.2.1+1.armv6l-linux-musleabihf-cxx03.tar.gz/md5/25cbceed2cf1bb12601fe285c342d6b0 -GMP.v6.2.1+1.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/37d8b21bf59c0c555f2b59d6dca4f486bf1725ae18a7fea9a2f31533c54ebb818b5ddb88ec8aa2b618e0ecad78973659abd1a9f095f64ef65067ab8ed08d7801 -GMP.v6.2.1+1.armv6l-linux-musleabihf-cxx11.tar.gz/md5/8ec72c769625a218c6951abed32b3684 -GMP.v6.2.1+1.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/4cb9ccb97859b0918002b649e1b5e74e1fc89a2daeec6f32d5a06ce0d84217f54d1ee788f472cebeefc73ef52284a3d5607efbed47058b438d2dcbcf9f384ed0 -GMP.v6.2.1+1.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/6f799d6516cc46af28eacf8409634825 -GMP.v6.2.1+1.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/541c1e03726584ddb672a83becdc9a99c68f5da9a7415750d582753b47774910bf25cee7fe21f5b5c2a80ff8ce87fc327abd45bf54d6cfe821cb202c81b67e43 -GMP.v6.2.1+1.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/17dba9ebcc1bf4637095a98a876375a8 -GMP.v6.2.1+1.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/648220e632618d23e8611e10fa4bb2e581ed4432e3fff77d0d7349a7585bffa65ae57bf1ce64c550bf6d2acc016f499c0bbbfed8088281445b9d4ecbbf9a64bc -GMP.v6.2.1+1.armv7l-linux-musleabihf-cxx03.tar.gz/md5/79c77b81cc16fd22ad4cef75af7aa220 -GMP.v6.2.1+1.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/0059ba54806ef0ca6621ddcd309a18922c4c7d9d9e214bc6870b6338a9449a472934cc27569402741d41a18dd53a896aae2f68b788f853fd4ea3db63035c8153 -GMP.v6.2.1+1.armv7l-linux-musleabihf-cxx11.tar.gz/md5/87b79bfc5c780e214863d0f0c1944da9 -GMP.v6.2.1+1.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/88dcabcf96d8f2dcc7968333a94adcb8e8a91615b67ca23edf75c3368a89ef60a8deff8e8532d0cd4d5dd5356343b753b0ae0bf88ce7e190639468bf8170939a -GMP.v6.2.1+1.i686-linux-gnu-cxx03.tar.gz/md5/61d39e42ab6fd5844e938605e357b336 -GMP.v6.2.1+1.i686-linux-gnu-cxx03.tar.gz/sha512/8e0d382adf6b45cbf613092cee524551a04096b0bc6fb8893701edae9c1928bda67b5522cae3ef954a882ff73b735190881ade37495d9d1a6db88ed6fbcdc6b1 -GMP.v6.2.1+1.i686-linux-gnu-cxx11.tar.gz/md5/b66b49054426adf3e1d3454a80010d97 -GMP.v6.2.1+1.i686-linux-gnu-cxx11.tar.gz/sha512/b28f22bbfbf796c4e959b1fa3433d46b4cf0dbd402c0497a6d4893c8030aa12fd038da4846d8bce02199f1da9b0158d78f2b4ff2636799ba139602775725ff6d -GMP.v6.2.1+1.i686-linux-musl-cxx03.tar.gz/md5/69ea3b3348813777a1682e41a117d7c3 -GMP.v6.2.1+1.i686-linux-musl-cxx03.tar.gz/sha512/048dd08b5891864e69504baf6328ef5423e0f8e31c5c6cfac552eb51b3ef943af83b7ac654c33e1a0cf061c5832e08eebb9c03dbda6532fbc24e160e99c2aae6 -GMP.v6.2.1+1.i686-linux-musl-cxx11.tar.gz/md5/e7c82091d29a3e5958442c9ec631ad78 -GMP.v6.2.1+1.i686-linux-musl-cxx11.tar.gz/sha512/8574f2e42e181a7bd1cf8aa8056a14d13efe555ee74b14e14aef1bdce7f26ce2afe41b4f85ee20de6823045d5ff38e4dbcebcc7042fff4288af1b7d296202d43 -GMP.v6.2.1+1.i686-w64-mingw32-cxx03.tar.gz/md5/dcef59aa056dcd56e6e36ad49174389f -GMP.v6.2.1+1.i686-w64-mingw32-cxx03.tar.gz/sha512/3cf3096c325ae2baea8b3c3aed4a26d649dc2bb3cf0d979809d9962521422ada3fdcdddbcfc52b27d43b473a1d3ed4a40368cdeb16cac4d32718c604dbc9f388 -GMP.v6.2.1+1.i686-w64-mingw32-cxx11.tar.gz/md5/b772a602b016e73dfc9a93908f51622b -GMP.v6.2.1+1.i686-w64-mingw32-cxx11.tar.gz/sha512/00e06591e2cc44100dca1a8897c72933bf4bd8c3c732daea99a9efa4d0a67f6a8820bf3e5d27583dfddc50d4cda656fa7462a2c453035d03657948f0051dc2fe -GMP.v6.2.1+1.powerpc64le-linux-gnu-cxx03.tar.gz/md5/b31c423855c4c5633b41301e3b424312 -GMP.v6.2.1+1.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/2565176e2bbcb9deab25a91736e8b6de01e7dca619ed1fcc98cebcaaa144eb03f89f4f6d5989aa8454b0d1c7266d1ace690e6deef67c0cf5c3fc1c2ab4d41b43 -GMP.v6.2.1+1.powerpc64le-linux-gnu-cxx11.tar.gz/md5/1ed2494342b5713308f6ffed5fe3863d -GMP.v6.2.1+1.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/c600802c81c77247a24a50ec0695f742177c8c9f090b4c345f9b0cd065b35183f49592a764cdb7b1b6d5ee3722e7dd26672d85db963d1e490731545a36d1e581 -GMP.v6.2.1+1.x86_64-apple-darwin.tar.gz/md5/51e00a2b55e9f81eb62abe23bb5f6fd9 -GMP.v6.2.1+1.x86_64-apple-darwin.tar.gz/sha512/91731427afd8df54b54d87b93006190a8b959438dc591eb5fa44724056911b8bd5588b2b1e70e9da3d8d6e9ce5aaa6fea66b0706f636cb56b3c860e8f3c0550a -GMP.v6.2.1+1.x86_64-linux-gnu-cxx03.tar.gz/md5/3f3a6f15e4e8499470bbe69a9ea885c1 -GMP.v6.2.1+1.x86_64-linux-gnu-cxx03.tar.gz/sha512/2659344ab097cd9542a5946c127a43af6fad05aa1445d69a4978d1a6d9a9f0e0502a5a60c6ca88acccb86d038dd10f2a72a7c2d4dd7ad5383c7d687e9720cc88 -GMP.v6.2.1+1.x86_64-linux-gnu-cxx11.tar.gz/md5/15ee858d8e1f07f18df8a893634d859e -GMP.v6.2.1+1.x86_64-linux-gnu-cxx11.tar.gz/sha512/9d8ffa570eb22a5a908679e06af4dd0ce8c06cf97ff9fd766baeca352a99bcc54b4b71b9c52829ba80043a688f2ed6a33b0302072518f2b16416235d5295ea00 -GMP.v6.2.1+1.x86_64-linux-musl-cxx03.tar.gz/md5/79078a236575994696e7328e34326243 -GMP.v6.2.1+1.x86_64-linux-musl-cxx03.tar.gz/sha512/d4b77a4056a2b0dcb6f789381fff720ab7481cc7edb4672756cb2057ed6475abeb6ea414e6cec3e2450ef7302b647d7d2fc2d9f7de52feddd7767548392e84bb -GMP.v6.2.1+1.x86_64-linux-musl-cxx11.tar.gz/md5/94f822c7521f83652d87fd5f1ad8bb19 -GMP.v6.2.1+1.x86_64-linux-musl-cxx11.tar.gz/sha512/fa4f70f81524d47b65d5cf3ff5abe38a691f09e3297c62f0db2512483702b9af33bc4a3c15f6f1465d6dce4eeb19f665f29872e6dd7caea0806f4c7fd32c2c5a -GMP.v6.2.1+1.x86_64-unknown-freebsd.tar.gz/md5/cdb93a733763e8a4fc29652fda8c8b13 -GMP.v6.2.1+1.x86_64-unknown-freebsd.tar.gz/sha512/ec529f57eb167bfcb367310b375a3cded007cbc386cab9b09faa9fe8f37a443302c674814ada6c82125ad0ce4aebecb75bb61633a21e7a3a00fc928fbe05cb4f -GMP.v6.2.1+1.x86_64-w64-mingw32-cxx03.tar.gz/md5/8b5be9da6a0a293e14ab1d589a622b98 -GMP.v6.2.1+1.x86_64-w64-mingw32-cxx03.tar.gz/sha512/73287b8390cac2ce8afc4565c5218ac739ed8a23c56754f4667570039f022b777284aee25d7857a94ff46fd502ac0fabe46f509a5f870b1aa074f6ed1278dcf1 -GMP.v6.2.1+1.x86_64-w64-mingw32-cxx11.tar.gz/md5/11bcbfc3b65b19d73c3abf92ec46cb6a -GMP.v6.2.1+1.x86_64-w64-mingw32-cxx11.tar.gz/sha512/1dd9a6fe5c4991483a2d46420cd892271d37d9d23c409ed782b7736ab1942cd6c42360efbc308b5684bd5f991c7a96e8d375f3e855dc537bb3089e3402eed110 +GMP.v6.2.1+2.aarch64-apple-darwin.tar.gz/md5/37a4c537149a1d6d7424833294e61dac +GMP.v6.2.1+2.aarch64-apple-darwin.tar.gz/sha512/33dd86279b5b3b08496180c92971c2e7ef84715e9ed3a80071a178ee94de6231ea3cf7b4dd4fa7e0dbd0b386a1a04c4f6b28446e86cb92c100ebb295b2f5ee3a +GMP.v6.2.1+2.aarch64-linux-gnu-cxx03.tar.gz/md5/44ef76b228cdc4cf54e5d4b40a29034d +GMP.v6.2.1+2.aarch64-linux-gnu-cxx03.tar.gz/sha512/255a680c75d3e8ca542dffc47050adfce038e25a12a4131c18dc719d36b364c1a6488ee5743d1c5de445b4bc5ccbb932399f7071083d86fe5bd2befc521cfbfd +GMP.v6.2.1+2.aarch64-linux-gnu-cxx11.tar.gz/md5/0289ffc3621b5d62dc2f9e1b36c41f9f +GMP.v6.2.1+2.aarch64-linux-gnu-cxx11.tar.gz/sha512/f27b82efb5aa1d7eaaed7574d3312969664eac38f45cf40c6de13ca20b256d45481546fc1a402e6c04bee416c842a092a4e57b8df702bbcdc52f742555d07aa7 +GMP.v6.2.1+2.aarch64-linux-musl-cxx03.tar.gz/md5/9ff4c76804f59056b49a9bf5b6a02099 +GMP.v6.2.1+2.aarch64-linux-musl-cxx03.tar.gz/sha512/d86afa10bdc4e20fa259a17ce7d0a5dca2524b42752bc7d5c33e4323973587d234d4c420900deef34670bfce8ab8c6725e7edb45bfd3896b2644a42ec187dfd7 +GMP.v6.2.1+2.aarch64-linux-musl-cxx11.tar.gz/md5/cc9857a965afcdcbc2b378a368360690 +GMP.v6.2.1+2.aarch64-linux-musl-cxx11.tar.gz/sha512/c46bff9fdcbecc71c12914dadb31ee9fd5b4293cb45bda782200daa18d7f7e8b588e0c0f68a39c2fec7cc3d026bcef3620dae35ae2dd3acf2505dcfc084d11bd +GMP.v6.2.1+2.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/5b3343367896e31b29571fe0d2b90390 +GMP.v6.2.1+2.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/65a501db63c386727aa336d6dbecdff0417628bc9ff7ac1b2161922246d94f8caa71b63fc3789ec6bb10aff03b96d5d0c22c37c82bd95d74e557df8de7e8a09c +GMP.v6.2.1+2.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/cc04dda18412fa11f228e66eb5a03aad +GMP.v6.2.1+2.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/49fdd452fe8f0129ee06795e04a0cc0238132f9d6f60a124dd2c7395fabbb71f005c16d95fdc00d87f8bf82b048cc54e07f162fbc38223c644854cc72c4d26b0 +GMP.v6.2.1+2.armv6l-linux-musleabihf-cxx03.tar.gz/md5/675599595f3dedb8ca11151168da7110 +GMP.v6.2.1+2.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/eedcdc2230fd81d613d54be356679a97b59491f5f9a17c518239b5504c3dd5da15721d553f57ae21f1c55d253e808e7afd1d1651b8c666379c55c7b48f71217e +GMP.v6.2.1+2.armv6l-linux-musleabihf-cxx11.tar.gz/md5/9a74abbc46439ae8268ca926f0045691 +GMP.v6.2.1+2.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/6329506f7a886d0dd907b051d6cbab1bd0cd21b2d5715f55402bf9ad6cb1ae33e058931bdf6cba17658b0e455f9e4fb7f9aad274755a159106cfe1c4d1ea328a +GMP.v6.2.1+2.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/8c20e0def927a202f2d23aed78aadb4a +GMP.v6.2.1+2.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/b7f42efae6fce864c9e07714056444ba74befb9cc9a766ffe14e676240f23f83d3241b1bf3a8f4a282acbdc197287fffb27dadedf3055505ad63bb0b9df573c6 +GMP.v6.2.1+2.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/423a625816b3c52efa6021e76f6009b7 +GMP.v6.2.1+2.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/21cbbfd647d4a7c884344dc66e0fd83d654d22c3338669539e8eab515bdc6bbd772b47f949d28280789e4343e9a8d6319a73dc9e11c23da381b8a452ef7fb098 +GMP.v6.2.1+2.armv7l-linux-musleabihf-cxx03.tar.gz/md5/7d67f981538d7a69ab1e458a54bf56f4 +GMP.v6.2.1+2.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/8aefbcddc326d4ef289dcdba8d3bd56a5f9656a7be30c83b4dbd9a0b8ee26a963c6a2f4294c94b8a8f2f712f1e1c9e17b8b9dcc9967d64294ca466e51656f7c7 +GMP.v6.2.1+2.armv7l-linux-musleabihf-cxx11.tar.gz/md5/ed8713b71636ea75fcc0c9fbc4a8618d +GMP.v6.2.1+2.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/d7f50d06a256fd9176d5fbf682ff599a5ffba62bb35fb37321ab41e88970921a9d9fa4531bd74e73e471c7e15fcae568d0536d3e32a2b2d7f81dc9cd1f0c039f +GMP.v6.2.1+2.i686-linux-gnu-cxx03.tar.gz/md5/875f0bc57172788cb80ca2b80ff3065f +GMP.v6.2.1+2.i686-linux-gnu-cxx03.tar.gz/sha512/808a3c2422b5168260dbf7a3875d5c8151e10b20a8ec87a66bf08f71ad7cf5de20fb7a4f3457c3ab2b4ffc9627764c743baa96f409629c70f2233ea7a5b628b9 +GMP.v6.2.1+2.i686-linux-gnu-cxx11.tar.gz/md5/09ae13f2a6a0dc317d2bca5700d2bf59 +GMP.v6.2.1+2.i686-linux-gnu-cxx11.tar.gz/sha512/9c986e2904247de937e30c05b29e0179986d7747b217468c59bc56af6d4c48d4575f24dace521dc8d66d84230eebd695fe0538972bfd744182ca940a23a9239c +GMP.v6.2.1+2.i686-linux-musl-cxx03.tar.gz/md5/45f53fd95dd69a6ee6b43463976b5aa6 +GMP.v6.2.1+2.i686-linux-musl-cxx03.tar.gz/sha512/4df57d6c88f0ff86e0ee78da8f6ad02decf7a38884ae8c785c114e0e38e791b733e0d046c90712327c08645dd40b7f0391fcb3258cb3bfb8b6a62c59c27d6e83 +GMP.v6.2.1+2.i686-linux-musl-cxx11.tar.gz/md5/8b15988bfb1ba0543eefab73b3ac3439 +GMP.v6.2.1+2.i686-linux-musl-cxx11.tar.gz/sha512/e32dec7ded9bf6fc26033df83521481dde851c68d7cc45efaabeded7603417cdc5016de45f78a956b69aaed00a55a91aa8b1cd5bbe5431b01074dafce2c47751 +GMP.v6.2.1+2.i686-w64-mingw32-cxx03.tar.gz/md5/4138d0b5185f722aef4e1f215f381275 +GMP.v6.2.1+2.i686-w64-mingw32-cxx03.tar.gz/sha512/255d4ecf178b9440b667c56e542baa4422d731f83a67accd41b76268274c2344fbbf94979fddbbd1f6b5751bac2d228a8ef49a93365de78c1772146edd1b4845 +GMP.v6.2.1+2.i686-w64-mingw32-cxx11.tar.gz/md5/606b4b453af25ded1323aee9e085c132 +GMP.v6.2.1+2.i686-w64-mingw32-cxx11.tar.gz/sha512/8605b764ff6e5d81767432fd8e70c25c5ad76f2cac7c2b3d6ed0596df692300973803487c970a896a0a316d46de3e3cae31b21d4e11fe2961e228cd389da13da +GMP.v6.2.1+2.powerpc64le-linux-gnu-cxx03.tar.gz/md5/3fbd157df4ae738da6820b26fb75e75e +GMP.v6.2.1+2.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/6e64c5c4e393c0001bd7085e627126134b5999c2d8df2fa9b72c9f9835d6b0f0ad440a2f58fe6537ec446a517f8df2667881871fce9b4d61c356d2b52080d641 +GMP.v6.2.1+2.powerpc64le-linux-gnu-cxx11.tar.gz/md5/35608e3166278d52a482d7e19313eca6 +GMP.v6.2.1+2.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/a9550fe2b94e0e111a487159c0cd8fb6f1a21b8941ada7bb281572079dbbece921f80b0275bcc8f88117ecc72e7f8e93219350f5444b67295620db1aa9ae947d +GMP.v6.2.1+2.x86_64-apple-darwin.tar.gz/md5/b5004a436660a2533b94b41c592b686c +GMP.v6.2.1+2.x86_64-apple-darwin.tar.gz/sha512/b7b4dc8025ce304c5b899084f42c8f5aad5bbe03509bada17dbe6be952f98306729180a22b5d0a095692f349406db0b98f99f5e3f2be5f2165825e6f7f7d1813 +GMP.v6.2.1+2.x86_64-linux-gnu-cxx03.tar.gz/md5/47ba899c9ac714a4594f999d845f45cf +GMP.v6.2.1+2.x86_64-linux-gnu-cxx03.tar.gz/sha512/99624ec71865d6285ab409ef54f4cf12ba246de6233de56a2fb9f70806574891539efed32e711202003570c157918fde8d53534c695fd5b8476e0d4e0ecd1bd4 +GMP.v6.2.1+2.x86_64-linux-gnu-cxx11.tar.gz/md5/3b0c1258ecafcaf96e549f9b979420ee +GMP.v6.2.1+2.x86_64-linux-gnu-cxx11.tar.gz/sha512/b94d8f25d23597f96cc0cf0aebd1708755a8714ec4a481108add852b77addc737d3d8feba566ec410db019698ca2de826583b1a6105f0d2188679e7f72331df0 +GMP.v6.2.1+2.x86_64-linux-musl-cxx03.tar.gz/md5/061cfe5f416c1365e98d6b1ed89abd63 +GMP.v6.2.1+2.x86_64-linux-musl-cxx03.tar.gz/sha512/b6847f7ff599fa811851788a6ec6ce69ba02dbb3672d0a64b03b7056b35215536b059287709b3d207bc977094e994a7d744061b7ecf95886510285489bb89578 +GMP.v6.2.1+2.x86_64-linux-musl-cxx11.tar.gz/md5/81911acbc0c3607338c6455b1798cab8 +GMP.v6.2.1+2.x86_64-linux-musl-cxx11.tar.gz/sha512/e007441194abc5c80d9521a17e2ab9e6fb54f319571f4045fec2f7464ffaa99652d3252416c15d110dbf9deaad2c1dc94f81c638e28ce620cf543f554eb7d1e0 +GMP.v6.2.1+2.x86_64-unknown-freebsd.tar.gz/md5/ef7173194848e8d00d73ef05fc520f0e +GMP.v6.2.1+2.x86_64-unknown-freebsd.tar.gz/sha512/512c3cf8fb951fe0ef7b1715b78202d0bdf5844fe33e16c4674a19e6335440fb5352d7bde71fce83e8e373efe43281d05b160b11657a582a9d3a0201ce97a189 +GMP.v6.2.1+2.x86_64-w64-mingw32-cxx03.tar.gz/md5/882c6749f217f5a691b744ef728ad089 +GMP.v6.2.1+2.x86_64-w64-mingw32-cxx03.tar.gz/sha512/53424ad8a9dcfb8e0e738d4521b2ab1c75aaf54668a54a76b8bcab2404308e69b531dc25b3dc18bc8eaa7ebd9e2914d6624c5d371e6c0ecb9e8d24aa575e99ab +GMP.v6.2.1+2.x86_64-w64-mingw32-cxx11.tar.gz/md5/bcdd7bcbc69161744397d249a9c82e45 +GMP.v6.2.1+2.x86_64-w64-mingw32-cxx11.tar.gz/sha512/b7f8fb4f5aaf5034d4d2f60e29cc7b5e06c13d4b677af30f30831e1fc95925a575275ebffda36efcc09e29ccd78ba56475c1be3ad0627e28862057764f1ef74e gmp-6.2.1.tar.bz2/md5/28971fc21cf028042d4897f02fd355ea gmp-6.2.1.tar.bz2/sha512/8904334a3bcc5c896ececabc75cda9dec642e401fb5397c4992c4fabea5e962c9ce8bd44e8e4233c34e55c8010cc28db0545f5f750cbdbb5f00af538dc763be9 diff --git a/deps/checksums/ittapi b/deps/checksums/ittapi new file mode 100644 index 0000000000000..896e44d8f2907 --- /dev/null +++ b/deps/checksums/ittapi @@ -0,0 +1,2 @@ +ittapi-0014aec56fea2f30c1374f40861e1bccdd53d0cb.tar.gz/md5/932501cdb0e1c7841e23c12da7740419 +ittapi-0014aec56fea2f30c1374f40861e1bccdd53d0cb.tar.gz/sha512/4dd3343837398ada0cdcdaaff630d8d91738d166897d86b77770facde30da99dbb90931b58a4a887399e6bc9a7a1c245057d0a0f63762230d577d71da871701f diff --git a/deps/checksums/libcxx-11.0.1.src.tar.xz/md5 b/deps/checksums/libcxx-11.0.1.src.tar.xz/md5 deleted file mode 100644 index 5b905de3304cc..0000000000000 --- a/deps/checksums/libcxx-11.0.1.src.tar.xz/md5 +++ /dev/null @@ -1 +0,0 @@ -4b2467eb023c9b4c84335808f811d5fa diff --git a/deps/checksums/libcxx-11.0.1.src.tar.xz/sha512 b/deps/checksums/libcxx-11.0.1.src.tar.xz/sha512 deleted file mode 100644 index 251c002b1e83d..0000000000000 --- a/deps/checksums/libcxx-11.0.1.src.tar.xz/sha512 +++ /dev/null @@ -1 +0,0 @@ -adda227d412bc28a47612cc6580bf85353838792b4816633d8401efb92cd65f6801278941f779d301bd6162b75ef8d54825f9cdfb0f61c6f5f621eca7fb7c004 diff --git a/deps/checksums/libgit2 b/deps/checksums/libgit2 index 9b360b711ceb5..a70a404ae6843 100644 --- a/deps/checksums/libgit2 +++ b/deps/checksums/libgit2 @@ -1,34 +1,34 @@ -LibGit2.v1.3.0+0.aarch64-apple-darwin.tar.gz/md5/af98f6fafe5678873b7f974c481c4238 -LibGit2.v1.3.0+0.aarch64-apple-darwin.tar.gz/sha512/1c3509d6b2e00ddfb282e4081a3994302b86cef2ff54c74e63000ec5319cf9f37b7685a14cad85f48a90e37afa507efa97881c27a4f4926fae1b74e96a4aed5a -LibGit2.v1.3.0+0.aarch64-linux-gnu.tar.gz/md5/4884296753929a70f6f01b36bfec1f61 -LibGit2.v1.3.0+0.aarch64-linux-gnu.tar.gz/sha512/42babda48f23b672ac382780b450e314ee16929c523125246f7d66e11fd27208354fd4d4c7e663e2a6091de78612be0e825f8d1cd4eba595a056838df12fd213 -LibGit2.v1.3.0+0.aarch64-linux-musl.tar.gz/md5/c0b53bfdfa9d4b9e653a5470eccb40c6 -LibGit2.v1.3.0+0.aarch64-linux-musl.tar.gz/sha512/b3d79bbaeb26869066d6b7e228bc2712b67c5dc45badd317c3023eda86d82ac2b712a2126d4049f1074d0ed86ec9f80f9a2e7d6458d47c1d3c953a37a4b3ac0e -LibGit2.v1.3.0+0.armv6l-linux-gnueabihf.tar.gz/md5/ac91abf4ce2ef1f25729d352c2bd3630 -LibGit2.v1.3.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/c8297da990ead579e285e4347a99a824a81c147965a8394e034690b63e3e84f55f21d37f2754725dacc7836812698a06fb6101fe05b222cbe11d558742986e91 -LibGit2.v1.3.0+0.armv6l-linux-musleabihf.tar.gz/md5/eaf893aabde1ec021bbeda5150df6212 -LibGit2.v1.3.0+0.armv6l-linux-musleabihf.tar.gz/sha512/3bca29dd9bb724db74eae72a9bbeae777e315a616938d76144c2183c9bb9b1656c83de3e7c743c3cba8f2492f74a1cf4254c8942859211f1d6beb514e3532b18 -LibGit2.v1.3.0+0.armv7l-linux-gnueabihf.tar.gz/md5/fd6b13ae9129c6f082ab194782e33c01 -LibGit2.v1.3.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/e236667bc0e3a929b625a410c55fffa0be3f71aef090c3c18c9806ff3c866a0f2dfc1afdcba34d7f0b81b0fd38fa3441b56f4343fedf66c5eead64f059c095d3 -LibGit2.v1.3.0+0.armv7l-linux-musleabihf.tar.gz/md5/9370e574abf25984dda0521b9b3d2df9 -LibGit2.v1.3.0+0.armv7l-linux-musleabihf.tar.gz/sha512/281c7f04d8560f5ef4c9902ace66f41b255e6868d83bbe37e61923371b0752a076f93f249abbd64e6d3849460c2c36dee9207303f0e0eb0ef5d37990aa7337df -LibGit2.v1.3.0+0.i686-linux-gnu.tar.gz/md5/e0ecd37c7dd9709ddedf3eab8a4f2d47 -LibGit2.v1.3.0+0.i686-linux-gnu.tar.gz/sha512/04ff40e26df0f66413564f4189a031abf538dbea2cd41192164ab5e5361527b18d2a448ca7dacd9754a419d198dd816dd25bfecde4b2cfb1f497aa437a1784a3 -LibGit2.v1.3.0+0.i686-linux-musl.tar.gz/md5/4d98934e46f16eaa3afb597474639de6 -LibGit2.v1.3.0+0.i686-linux-musl.tar.gz/sha512/6d2fea7fe42d12642004f41d31d8ecf5213d0ce7cef3489c96583c9096d8b044b70dd3466bfc2ad901a7302fb8d320713a2ee4bb03702dd7487b0eb0b34966dc -LibGit2.v1.3.0+0.i686-w64-mingw32.tar.gz/md5/29fc6f7d785aebaed7a916106efaf690 -LibGit2.v1.3.0+0.i686-w64-mingw32.tar.gz/sha512/ac29ef9b8d2d2b44a8d37bba4a40103fb174a55c3210b2b5c6d02baf7cf7f0bbea5acefc5c465ce931fc21275045ad0b39ada32b96795a74e4a46fb405c76398 -LibGit2.v1.3.0+0.powerpc64le-linux-gnu.tar.gz/md5/8b412e41808ff41418b35feab033c445 -LibGit2.v1.3.0+0.powerpc64le-linux-gnu.tar.gz/sha512/fecbcbe322e53a311aa28a6734ca6331438f14e030342efcab7d3f9f2b582c1c23b9d40ce63e1decb92d4d8620685d2e9ae7fadc55bf5db9169590f83327e2de -LibGit2.v1.3.0+0.x86_64-apple-darwin.tar.gz/md5/033cb0d768a322dc6fc8f1fc58963f62 -LibGit2.v1.3.0+0.x86_64-apple-darwin.tar.gz/sha512/69d6c9e4b38257c89311cdf0a219a8497482c85a7a39c2c6d5819c5d9443d93f4978fbe08291313492f673e02ff4ae709fa4047c3ce015a806b4f22284c1c2cf -LibGit2.v1.3.0+0.x86_64-linux-gnu.tar.gz/md5/bd7a36813ec371d2a39a98eced238294 -LibGit2.v1.3.0+0.x86_64-linux-gnu.tar.gz/sha512/b3a605a9cb7057f4c9652cd551922b4e11f65d74383a9c5b7a0234b7cf599e0a23cf79695e1db6c760dea194f76b59a347e38e14729b0bcc9b120812b63bb2c3 -LibGit2.v1.3.0+0.x86_64-linux-musl.tar.gz/md5/856ac3655c979881c6c0537f49907d18 -LibGit2.v1.3.0+0.x86_64-linux-musl.tar.gz/sha512/8cae2271bfa4a9dc6168fdd1d6133997756f56c60cd94525357f907109b41cb7d6594f7916557de03f1450b4bc694705f2f21d8426cb909c5678cee7f1477b88 -LibGit2.v1.3.0+0.x86_64-unknown-freebsd.tar.gz/md5/5c864ad058dd9c30340bb1c196d97b66 -LibGit2.v1.3.0+0.x86_64-unknown-freebsd.tar.gz/sha512/e2d3953ebe99743b0f6e62901b953e7c57030cfd5b1cc40d2bda85ed26573212c2e4748f2fdf46d88b75f584471a8b0b287c0a65ef83f502164e92624aaa091e -LibGit2.v1.3.0+0.x86_64-w64-mingw32.tar.gz/md5/45d92f092eb1a319396dd7ebb36a2273 -LibGit2.v1.3.0+0.x86_64-w64-mingw32.tar.gz/sha512/0f219513972259f131fbc509eb035103a986bf1483f7cf9d0bfbca2802f5bbf23296a36a9f9d72e4d89a70f86acf781aec3d49df6aaf408da4f025f65d559cfa -libgit2-b7bad55e4bb0a285b073ba5e02b01d3f522fc95d.tar.gz/md5/02582c680d006890def088ffaccea7d8 -libgit2-b7bad55e4bb0a285b073ba5e02b01d3f522fc95d.tar.gz/sha512/ee51c06c012503d66ba28d9c2fc9ad42af69f22fd1ae1be54642820ccd80c74e24d78eeec7fe5222daf2432930bcce163800502db1224571da852238c1970e36 +LibGit2.v1.6.1+0.aarch64-apple-darwin.tar.gz/md5/62bb842de0ede8a7c2b119cfa7402a61 +LibGit2.v1.6.1+0.aarch64-apple-darwin.tar.gz/sha512/e5117912419fd73138779322d5cb84454c641aad87d0df7d44b5074c96576fe1ee3822dba18c8207dacc9bae2b74cef87353d5c519fb7fba8ea89c858415f993 +LibGit2.v1.6.1+0.aarch64-linux-gnu.tar.gz/md5/3f42f283a9f550841b285216d681f3d0 +LibGit2.v1.6.1+0.aarch64-linux-gnu.tar.gz/sha512/0a793bb239976946941af5794cb45cfd7d1d99b9aa125800aee9337bf9d9c5152bcad258f75d987a7af9b547ea906ee2beebe7b8d2c8cea111e6878df0eb3ea9 +LibGit2.v1.6.1+0.aarch64-linux-musl.tar.gz/md5/0f20cee604380bfa789334b5544b1cab +LibGit2.v1.6.1+0.aarch64-linux-musl.tar.gz/sha512/86d7e6a64bf24f3e69dfa4383ed896c5d8a915e19f6f0351e8cf38361352347c827f79032fd8576ca9bfb94dc8db4704d35540ae67b46d671f44ab549c6ceb49 +LibGit2.v1.6.1+0.armv6l-linux-gnueabihf.tar.gz/md5/5c025b4c9065c0b481c7b0f6dd7666a0 +LibGit2.v1.6.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/5b1d3472df47462b3e38c5a5b3400d90038b1637a7f479e9fe04ef046849c14d12301328498429a9f290ff82b6343ccd9ae7616c5ff1d5fd83f35559bedf8747 +LibGit2.v1.6.1+0.armv6l-linux-musleabihf.tar.gz/md5/8015b63706e6d5826779f870681ff865 +LibGit2.v1.6.1+0.armv6l-linux-musleabihf.tar.gz/sha512/e3c8c46d8da8df409b2dc7c476da638da2c79974270390b84473ebefb66f26cf60647445c2b141f7b6cf45655de12404deea30731b812952fd9156acbd7344a1 +LibGit2.v1.6.1+0.armv7l-linux-gnueabihf.tar.gz/md5/74672b31da80507609e59b19448ec415 +LibGit2.v1.6.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/6c6365501abeffc7e796f3b67a139e93262dab1550ba5fe6ead179c0a9d32c62bab7b422b81524d7a367ca1032c7bfd2b3385155e364fc267f660dffa8eee39a +LibGit2.v1.6.1+0.armv7l-linux-musleabihf.tar.gz/md5/057c22b3fc988a98551fc319eb080c39 +LibGit2.v1.6.1+0.armv7l-linux-musleabihf.tar.gz/sha512/edfb8c57aad5499fae88f09a17e905b4c009e2a8781727566321a858f3ed8a4bcb75b990ae5ad4ac57bcb2b01bd2dfbe0375b01a41405c161106881c8859aa78 +LibGit2.v1.6.1+0.i686-linux-gnu.tar.gz/md5/ecde35f4ca6b4a03f8491d90480f33b3 +LibGit2.v1.6.1+0.i686-linux-gnu.tar.gz/sha512/ca77a1b3c381be2286be9134d7adfde51fb38c4bc9dcb3f56cf1840809c40c484c843cf4ed8d77c538889e06cbef2e5d1b4468739bf761cc91c676a0dc5a34ee +LibGit2.v1.6.1+0.i686-linux-musl.tar.gz/md5/1a56e7832761479fe911b8efd66b5b73 +LibGit2.v1.6.1+0.i686-linux-musl.tar.gz/sha512/e929261ba9564762d2b3c3191dde216caede5c436b84a00d08706a708436023430a9a762cbd94bf96e903a230c690ea28787ee08208d5b50e51d98e56587b30f +LibGit2.v1.6.1+0.i686-w64-mingw32.tar.gz/md5/671a1c045725877e1a4f55b42fbb15b9 +LibGit2.v1.6.1+0.i686-w64-mingw32.tar.gz/sha512/5b0e78b5f5f24b7ee8c88d704bf58043626174d9e8e28226b72873f62d0ff6a6f87d6200adfd613e35c27f6d127d967f49a1f7ef26ded8d1b08c89589b59ce85 +LibGit2.v1.6.1+0.powerpc64le-linux-gnu.tar.gz/md5/4ffc17733025ac94e525f8d9416713a4 +LibGit2.v1.6.1+0.powerpc64le-linux-gnu.tar.gz/sha512/a382f7f15484426d6e913c9cd54facd63573650449f1a2d7b180f1905b79dc75280fdb48ff9e47ffc1ef70c9941d43a6ca35e21bc9746172689886fbbc9d65a4 +LibGit2.v1.6.1+0.x86_64-apple-darwin.tar.gz/md5/af4192c866787ce226fb7a6d5229bfa2 +LibGit2.v1.6.1+0.x86_64-apple-darwin.tar.gz/sha512/18bac55bd7bcd9ea66002c98717ef358710aa689c9bff63be77de1cce4db2082f023ee577060f6ed11e3830c2e751bf2adae1a9b232570a090031c5246f29edf +LibGit2.v1.6.1+0.x86_64-linux-gnu.tar.gz/md5/d26008f39b244ab0caa804ae0365d69b +LibGit2.v1.6.1+0.x86_64-linux-gnu.tar.gz/sha512/3d6068d2165c012ce66317cc0993c374df43cdb2dcd584ec7966f602062428d4f5e18d157c7aa19572affa1e9dcb0346105a01c64f8e5ac01546aaf7b5d99439 +LibGit2.v1.6.1+0.x86_64-linux-musl.tar.gz/md5/fcbfc9f15ffe3c4b2ea055e198795e96 +LibGit2.v1.6.1+0.x86_64-linux-musl.tar.gz/sha512/16bb30defa9d23e6025e3729e313766940105e02f00168e61bff81ae38beae9ae050a5fbf2307083b3cd89d364aa70a7042b94062160fda2174aaf5018f3e2f3 +LibGit2.v1.6.1+0.x86_64-unknown-freebsd.tar.gz/md5/a4fe2ed51c1ac1aaaa4f46a00714d85a +LibGit2.v1.6.1+0.x86_64-unknown-freebsd.tar.gz/sha512/bba31901fcd8b2e69f43e9645c028be4c840b3d9afb4e92e64c9ea46c7fb44dfecf14f99cde586380ae0508fdb8402d3bbe93ec7b38219fe7806299b70576949 +LibGit2.v1.6.1+0.x86_64-w64-mingw32.tar.gz/md5/11ed8da2cb4c7ef924b50768cbb54678 +LibGit2.v1.6.1+0.x86_64-w64-mingw32.tar.gz/sha512/b39f12931d638809af27e446d7ac25b17bfd5c003cac89bcf83dc4c5331d14ec12b07ae410cfdc636546a3b1edf0f7d360bd194aa58c835261642b51edb4afd1 +libgit2-8a871d13b7f4e186b8ad943ae5a7fcf30be52e67.tar.gz/md5/831f4d09a6a22662dc0043063d0305cb +libgit2-8a871d13b7f4e186b8ad943ae5a7fcf30be52e67.tar.gz/sha512/17ad43e6f80e87e8115cef89919475a9d9ea11d679e107221e6d82623577fc8e4002876a33c7eb2a52a47e3d8142976777bc79f81e4c4cf2da6adb1553d17b00 diff --git a/deps/checksums/libtracyclient b/deps/checksums/libtracyclient new file mode 100644 index 0000000000000..19b7b26c5461e --- /dev/null +++ b/deps/checksums/libtracyclient @@ -0,0 +1,34 @@ +LibTracyClient.v0.9.1+2.aarch64-apple-darwin.tar.gz/md5/08881ffc565e099903e2e972a7f7c002 +LibTracyClient.v0.9.1+2.aarch64-apple-darwin.tar.gz/sha512/a9dcc7f9ed7565a769dd1080513eec7439cd7b03d68d48f570ac3f396769ef0a7f9b07446045ce6536b7e67860096eb150670256c311c0a77ac1a271dc4b4422 +LibTracyClient.v0.9.1+2.aarch64-linux-gnu.tar.gz/md5/d6a8dbc7cf871f772f848a5e515e6502 +LibTracyClient.v0.9.1+2.aarch64-linux-gnu.tar.gz/sha512/cb9b3065f581a956d318d71a94216ca0e57599262a12a25bc2e6fa0234505fed5a9cad9c2eb7ad30d7ffe9c4ee3d26d9f645887d3f7180d69d3bf1d0745b4f22 +LibTracyClient.v0.9.1+2.aarch64-linux-musl.tar.gz/md5/0d74193e3571fbd80eb7d9e884b47e53 +LibTracyClient.v0.9.1+2.aarch64-linux-musl.tar.gz/sha512/18821911a96129486cb12726018b33fde1da345228623b7f326b92ccfcbbbb2349d79a35e6fa7cb4b6cf9283a860e8ac44c40d6b54a4dc1ea4373b869491b6d6 +LibTracyClient.v0.9.1+2.armv6l-linux-gnueabihf.tar.gz/md5/6111f3b3c696d9d07139e137c2ec1d08 +LibTracyClient.v0.9.1+2.armv6l-linux-gnueabihf.tar.gz/sha512/135139c221cb2d4d6000bd1a3771bd095e93487c7c649ebdf760ff5cb03f6ae003c33c2a36a52bbdf70e4c349195f78a97bc963336a36f33fcdeee33e4fc1eb7 +LibTracyClient.v0.9.1+2.armv6l-linux-musleabihf.tar.gz/md5/5b3154cc849b04bb3523f04fa4481b83 +LibTracyClient.v0.9.1+2.armv6l-linux-musleabihf.tar.gz/sha512/7f62a546c7cdbe3bb6a0a446980371ff340d5f530907a2434eba2a14bbfede8c740a763b0c68a252d7a3e357d9d933bcc6313919cd9bfa385715bc833be56cce +LibTracyClient.v0.9.1+2.armv7l-linux-gnueabihf.tar.gz/md5/f6952d495c5b699226260e065cf2703c +LibTracyClient.v0.9.1+2.armv7l-linux-gnueabihf.tar.gz/sha512/5fdad7f8ce3a03ce05adb3deb6bc8347aefcc8a7fe0a30e0f7684fe233eb8520aca138e0b8a6cc5555a1f2316a6e36bca32cb5de37f2aac5c5deddfaeb0f8570 +LibTracyClient.v0.9.1+2.armv7l-linux-musleabihf.tar.gz/md5/84924c2e32b39ed580b553a968e97360 +LibTracyClient.v0.9.1+2.armv7l-linux-musleabihf.tar.gz/sha512/2b81834b91472eb9897abefbe77e931782e8c14eaf7193f22fce82024610906b6e96122610edfab29a9c844581cc4ee9124e330af9eacd97fb8759c1de421472 +LibTracyClient.v0.9.1+2.i686-linux-gnu.tar.gz/md5/9f243a9d10cd928d45436f634d020c27 +LibTracyClient.v0.9.1+2.i686-linux-gnu.tar.gz/sha512/c9512030d83f32942c7fefd598bfa597ce758f39d11bc9551fbf565a418a3000d23f899f1e9411cddebb3642efef8cccfa3cf3f629bcc11fcf50585e1a80549e +LibTracyClient.v0.9.1+2.i686-linux-musl.tar.gz/md5/4aebc58f4c8101640d9e450338a4e12a +LibTracyClient.v0.9.1+2.i686-linux-musl.tar.gz/sha512/2085b7c0658bb39dce9a9b511c209a348916ed8e50ed0d51eb22f7eac167b890a87d357e433e12eaf7034c15842c8d2893a0c128443c4f25fa90fd5ca83e256d +LibTracyClient.v0.9.1+2.i686-w64-mingw32.tar.gz/md5/dc6f911f5cdd2789ef9f13a1a9882243 +LibTracyClient.v0.9.1+2.i686-w64-mingw32.tar.gz/sha512/57894c759db949dc669e23b7d5e015942630328a3dc754185a0f6bae95a66f0c3e65e365317bae95f3a216f4dcab681203e64dc8c9a0b5478cc9e27c9dab2e56 +LibTracyClient.v0.9.1+2.powerpc64le-linux-gnu.tar.gz/md5/a7429f900f7f0a14fa355186d99a24e1 +LibTracyClient.v0.9.1+2.powerpc64le-linux-gnu.tar.gz/sha512/e37ff8e8de9b74367b9f0d6fe49d983900529caf9c2c55d5ace305d5896c2de6589380247dc85017d959901864d4a163fe110e6d860340d949c6ea4dec50f47c +LibTracyClient.v0.9.1+2.x86_64-apple-darwin.tar.gz/md5/b037ea1027e6466d5dd9c0fb41f65ded +LibTracyClient.v0.9.1+2.x86_64-apple-darwin.tar.gz/sha512/81e2d00bd8eaa1cbcbd5c0ee4552028ccedffcc072beea3dc08ac3181677da93406e8dfc581a78434175fa5bb861df06848dd3012f8adbbb6dc72efcbb5094a0 +LibTracyClient.v0.9.1+2.x86_64-linux-gnu.tar.gz/md5/cfbe122083aeeea6bd7ddc4591b1cb53 +LibTracyClient.v0.9.1+2.x86_64-linux-gnu.tar.gz/sha512/e0418a0b50d64990d6f1b80dfe65e2360817211e1225c4d8d9fc9c871a95bbb62c2601c617adf1d55305518f5ba1dd05baee82f6934d0011269fab21b89336b9 +LibTracyClient.v0.9.1+2.x86_64-linux-musl.tar.gz/md5/f152ba78f2461fec711144ae66380c34 +LibTracyClient.v0.9.1+2.x86_64-linux-musl.tar.gz/sha512/f59f837d2beb4df4d3d65352a8c46261bb5a92ae88a62e2d1bfb7293184e02be982fbefe20736456719055e718a26003984224d0d74a0a6244dcc59e0d350556 +LibTracyClient.v0.9.1+2.x86_64-unknown-freebsd.tar.gz/md5/83c7b3d9438dd04d25573a386bc5c3df +LibTracyClient.v0.9.1+2.x86_64-unknown-freebsd.tar.gz/sha512/f22d0d4f4171067bd1f56bb63dba801e262d0ed4809538dae907296d1a12817954ad759cdc9e61f710fff5802fb7371d8283d6df52c9e8faf6b43c713c23e371 +LibTracyClient.v0.9.1+2.x86_64-w64-mingw32.tar.gz/md5/83f3db14b65b8e9942c754bcdb430060 +LibTracyClient.v0.9.1+2.x86_64-w64-mingw32.tar.gz/sha512/8acdd1d407ae927925f33eb75891684d6687e3577d5f8ac77e738daedc8145462b1f044e31edd9e2db4507673a0abebcea19e171833042cbbe5a135b0c0435cb +libtracyclient-897aec5b062664d2485f4f9a213715d2e527e0ca.tar.gz/md5/51986311723ba88ac305ad2c1e3e86c6 +libtracyclient-897aec5b062664d2485f4f9a213715d2e527e0ca.tar.gz/sha512/f92c5bd71fd3e933f03e3535c0668a9afddc7ea19531aaee11b22bde09c57cc8a555f7f17f489d4221645fb6d73ecf9299d5bb11949d7529987beec3e7d91763 diff --git a/deps/checksums/libuv b/deps/checksums/libuv index 6c90c1b2115c7..709fba71f159b 100644 --- a/deps/checksums/libuv +++ b/deps/checksums/libuv @@ -1,34 +1,34 @@ -LibUV.v2.0.1+5.aarch64-apple-darwin.tar.gz/md5/54a94c839c561f5b74601d6d2bd5bf1e -LibUV.v2.0.1+5.aarch64-apple-darwin.tar.gz/sha512/bba06826461a4f35abbe54ba5266d9bf354d22e1f33d75f4273a917ce92437432d8b2cc9d4b4670164c14542e896ee97396a1c34ce0f653d6a2787ab4b6160bb -LibUV.v2.0.1+5.aarch64-linux-gnu.tar.gz/md5/b2680a3cebeb850bfec0df820e27072c -LibUV.v2.0.1+5.aarch64-linux-gnu.tar.gz/sha512/9c5611ae653642ef0060c46235fa2d2e0e4094804fb52629456ae4e5deed7e5fcc88640537799d11d824b6c0c00e75fa2bbddc0206e69c587ae3a77b68e11366 -LibUV.v2.0.1+5.aarch64-linux-musl.tar.gz/md5/a50cea6c75ea4093851cd7420168a59e -LibUV.v2.0.1+5.aarch64-linux-musl.tar.gz/sha512/51ed9be7dec0546cba4822eb116188c15c464ef155df03f0d5d8e9431ba8fe4c23dffde33c3331ef6e7ef3f8135b025fe26b01f036ab193aa340020f9d3bcb6e -LibUV.v2.0.1+5.armv6l-linux-gnueabihf.tar.gz/md5/1b6750b5c85c5f456a448325a77bee06 -LibUV.v2.0.1+5.armv6l-linux-gnueabihf.tar.gz/sha512/06decd104aad78de07101576fab5c0200867c332d12f1cb0cbe8c558c0c2c84c918e5772fbfc62f6ce80437ad68ae97e3d180c97dd40383c80d5e81fee96ecd7 -LibUV.v2.0.1+5.armv6l-linux-musleabihf.tar.gz/md5/54e9820e027e97af7f324d7b5c12fee1 -LibUV.v2.0.1+5.armv6l-linux-musleabihf.tar.gz/sha512/a30353cbf74bf698e38fd357e57fec03345a4ce71e971d9eb034aa211b536dc83b994da533df914a65ba3f5babc7ab66423ed12da665b67c050a8e799cdeada6 -LibUV.v2.0.1+5.armv7l-linux-gnueabihf.tar.gz/md5/252f5fc6d094edea5faef71630f4ba83 -LibUV.v2.0.1+5.armv7l-linux-gnueabihf.tar.gz/sha512/79ebe1e57cefa243219525fdebad35765736534a4b036f2487d6dfa0376a685c8e9f16259bbce83155baebe5ceeeff2592933b597ceafa724060ffd4dd63b0c4 -LibUV.v2.0.1+5.armv7l-linux-musleabihf.tar.gz/md5/39bc81ad36519ee9261a662d444c13b4 -LibUV.v2.0.1+5.armv7l-linux-musleabihf.tar.gz/sha512/97a312f2a42a2377458ff5d5356905fb469c9c30f9ae3fa7d091c7e2cdab3a7ea813e1142fb7d08f2e0000a3d8388fb5fe0d82d3ff646310924439ba99f02903 -LibUV.v2.0.1+5.i686-linux-gnu.tar.gz/md5/ca4b4a317b62cd48f4277bba5ebb9b80 -LibUV.v2.0.1+5.i686-linux-gnu.tar.gz/sha512/2cf17359c976b10a2e0e08d92b43ef2d113a0071748209ad6b2896d9578cb3e96b55f7c72a7c7243ded244b95945c67ea3aa248c1513b5fd37ea714154e04c2d -LibUV.v2.0.1+5.i686-linux-musl.tar.gz/md5/7f088f43c6ae4029e9d90c2881cf2509 -LibUV.v2.0.1+5.i686-linux-musl.tar.gz/sha512/b3653bd4cd95b2d4247b4b83215bfb756e211a3cc02e7e7ca1887e820cb1a7d461397d7259057b63e51825dc344e2f20e904d17defeba59584ddc54df94f1ccc -LibUV.v2.0.1+5.i686-w64-mingw32.tar.gz/md5/8ec8f225a708ebb95fd6dbe6039c386d -LibUV.v2.0.1+5.i686-w64-mingw32.tar.gz/sha512/fd9575300a65af9b7c3a59451646a5f617fd9df0fcae21db02f0f1e9c689605b1e75d12f0ee46654cb8d2b44ac044d2b44b34f9c6d008c19d41b001a69e40c6e -LibUV.v2.0.1+5.powerpc64le-linux-gnu.tar.gz/md5/54c51f81a0b69687f0cbfce63b530991 -LibUV.v2.0.1+5.powerpc64le-linux-gnu.tar.gz/sha512/79a9daa826432da8f389bbb6788720f0bdf0e6a09a16b8296f0ead8e0eae175a72a0690e4ffa5e5d8169e22f596a8ad41607eb836d3f55b217bcf74885e707e0 -LibUV.v2.0.1+5.x86_64-apple-darwin.tar.gz/md5/9ea7e5bf6107f0773e7cdb875d831939 -LibUV.v2.0.1+5.x86_64-apple-darwin.tar.gz/sha512/07b5137c94adaf1c024373b27c2a2a0e77b20cc87f536551e6080b59bd47f65d6ccaaf40ec14068e9e24140c07ad518ef749c09d93fcc36b0507c4ed6acc7032 -LibUV.v2.0.1+5.x86_64-linux-gnu.tar.gz/md5/c4feae1cb61b43ab38b8adb80f8cb46f -LibUV.v2.0.1+5.x86_64-linux-gnu.tar.gz/sha512/cef015385abca586215796c7d2420a4b2496b8a50a62bd9c483d76bb00adb4e3decefe17ba8398353166818bb23b758d3bdb311965849ea68f8b68377c1b08bc -LibUV.v2.0.1+5.x86_64-linux-musl.tar.gz/md5/47f23d12e6c2094604f168c6c40ca131 -LibUV.v2.0.1+5.x86_64-linux-musl.tar.gz/sha512/abe0d74ceabc2d7efc80c1e8d0a6938205bea883257c43a637fc739c82a7085d4f0109c22d0f67e332aa14bed60433dd739676e0237fd28aba6a15c82d3e41f4 -LibUV.v2.0.1+5.x86_64-unknown-freebsd.tar.gz/md5/6a6eeb9108db8a30f776685d4f98a853 -LibUV.v2.0.1+5.x86_64-unknown-freebsd.tar.gz/sha512/e08961cfeb904145b67c2833e6ea3f91b90bc9c8948cfd61399c7d10b1a9cffe17728a6c906a9d791b71da406d8012014b7dcde70ed445084d21e99563cdd377 -LibUV.v2.0.1+5.x86_64-w64-mingw32.tar.gz/md5/7d592fefa8b295e09b4640bd999aa358 -LibUV.v2.0.1+5.x86_64-w64-mingw32.tar.gz/sha512/b4e738c5d86ad27171289f284e35124c6bcf94fc55512622563c6be75027de5033672100008e283aced530c71a6bb1da038872719e1073566d5979278ea76e0b -libuv-3a63bf71de62c64097989254e4f03212e3bf5fc8.tar.gz/md5/a385b594c170085018bc954e50cb42cc -libuv-3a63bf71de62c64097989254e4f03212e3bf5fc8.tar.gz/sha512/5415e992a20498ae29c09bfdb4819857d15be83367488e9fbd8c5f6a460da4cd2d0dff7eaa6087a4bcf6dee6d1c873acbe5751f5594851c978456665d6a21cf9 +LibUV.v2.0.1+13.aarch64-apple-darwin.tar.gz/md5/1a58ce9dc88984c3b5f7df97af6cbf83 +LibUV.v2.0.1+13.aarch64-apple-darwin.tar.gz/sha512/2bfd482ac759ac88d885371854affa8e358a10fea6c7756e0d1b366bc82ecbea56bdf24ca634525fb2a6fc2b3a5c77b07a4c6dec2923d8bffe2bc962bd3e7f84 +LibUV.v2.0.1+13.aarch64-linux-gnu.tar.gz/md5/7f270dd1e3046c8db432e350dd5cf114 +LibUV.v2.0.1+13.aarch64-linux-gnu.tar.gz/sha512/c0debcf17b54ba9f1588d4b267d610751f739d8ff96936c9d5fb6d8742039f8736c63fa70037322705569e221d73fb83c03b6ba9fb4454442fffd3a9f1a1a2da +LibUV.v2.0.1+13.aarch64-linux-musl.tar.gz/md5/07f56c32d5a2c12e6c351cf9f705631c +LibUV.v2.0.1+13.aarch64-linux-musl.tar.gz/sha512/8037d7aa0cb06850f055fd19cebdcfcf3146dde0d12768a9669bf05dcab91fdf3708798203258cb3f452158bdec7faae41e6afbb0e60b21403e683db3e23a1c9 +LibUV.v2.0.1+13.armv6l-linux-gnueabihf.tar.gz/md5/5558a7f68c7c375f40bc64da59fef0ad +LibUV.v2.0.1+13.armv6l-linux-gnueabihf.tar.gz/sha512/92ed6601cb5aa9a3ea2478a1485849543c9e847c8e85542e72f372a2d37c4c8b90f5ecb1bee1e462db31e1e8dba460f584b3cca9c833989c2b9ee404e355654e +LibUV.v2.0.1+13.armv6l-linux-musleabihf.tar.gz/md5/de6bfb7f0c0468b79e8895f166fb6340 +LibUV.v2.0.1+13.armv6l-linux-musleabihf.tar.gz/sha512/7948d007171bf57b827b489f3627ac74df447f4d696e8226e54e95ef0c8eed5a5ddbf758fbad841bc367f78cd61e6a5899eb478003dca3a79cb494b38cab830b +LibUV.v2.0.1+13.armv7l-linux-gnueabihf.tar.gz/md5/5be35de1d881f80981647c369b9b4ec8 +LibUV.v2.0.1+13.armv7l-linux-gnueabihf.tar.gz/sha512/458e5058ea4e794e0dc790da4c98569676056bac336df69762e8ccfec8f2955dcc55e8d090daa1b191c0ffa41392a04530c9bc28aa27cf411c1df2f1ba14bb97 +LibUV.v2.0.1+13.armv7l-linux-musleabihf.tar.gz/md5/8d034490da1ec2ef3dd3c69336177654 +LibUV.v2.0.1+13.armv7l-linux-musleabihf.tar.gz/sha512/7f595a8ab8b664d229cf6144e9ed1b5936ba8aaa70b92611ddb85bbe9046bb1b94d8417355a5abf058fb00023d4d56be0b2ddfd5dba896cd7b64e84e32dbfc5a +LibUV.v2.0.1+13.i686-linux-gnu.tar.gz/md5/ccb9aba78456c99b8473e8ddd328f90e +LibUV.v2.0.1+13.i686-linux-gnu.tar.gz/sha512/d382d90137db308933257a75e51d90988d6d07663b3b2915478547127d32f73ae6cdb4575d5ee20758f8850c7e85908fe4710c053cb361826621f22bc5b6502d +LibUV.v2.0.1+13.i686-linux-musl.tar.gz/md5/5ade48f16aa26bb68dc046d285c73043 +LibUV.v2.0.1+13.i686-linux-musl.tar.gz/sha512/f5728a5dc567268e59aa2697deb793ae427e11dcb6796c577e3da3ac24225ece5d4a6c4f903d4a7b184d3c3a3c8c1586c34b97e4a75de0a4e23ace720020fa8c +LibUV.v2.0.1+13.i686-w64-mingw32.tar.gz/md5/399d6fbe54dcfb2f997f276cd38fd185 +LibUV.v2.0.1+13.i686-w64-mingw32.tar.gz/sha512/55707e02a4b5bdf9c94683dbaaea1cac58f7735d5ae22009c219ea61ddfab1fe19b9bc6e830fc32207efc588c27f92770d2441b972f351a1bb3fdbbf5671a58b +LibUV.v2.0.1+13.powerpc64le-linux-gnu.tar.gz/md5/26656d4eaae8739099c55054bad54f57 +LibUV.v2.0.1+13.powerpc64le-linux-gnu.tar.gz/sha512/f85f8cfd91e7b1b02b073931ef9a3bb05620641d18ada039744a92b8c40e5a3de8d7c5efa7189b88baf1eb11fbcf9e6d16031b86e40f99f1b7cfebb0f5c5adf1 +LibUV.v2.0.1+13.x86_64-apple-darwin.tar.gz/md5/c7da6b91394a20c43acdf6f680cb62e2 +LibUV.v2.0.1+13.x86_64-apple-darwin.tar.gz/sha512/238d22bd299ae3b0dfd24a5b38d6d0d07b751fb301487a2d1d2f5313ae3596f33492388ea9fbff549293787505fc527e174ebcd4068f1bda43b40bc19e016d89 +LibUV.v2.0.1+13.x86_64-linux-gnu.tar.gz/md5/8c8913068263257cce5042b725918e0e +LibUV.v2.0.1+13.x86_64-linux-gnu.tar.gz/sha512/a848381012d5a20a0c881f5835e479cfff811928ce508cc57041d69668782f2135c14c7e5388e7dbf693ae57aa1825d911f6f450b9e909cce45487b03a581a23 +LibUV.v2.0.1+13.x86_64-linux-musl.tar.gz/md5/16747c066b6d7fe56850c77f66ea7478 +LibUV.v2.0.1+13.x86_64-linux-musl.tar.gz/sha512/833a02f9191edf3b56f1e02f5671f22de6cb27ec3c9f770530ec95d8da7ba0b9c05bcdf6b094224ea8e43ba70918e1599f3237bd98900763daef80c327d3d2de +LibUV.v2.0.1+13.x86_64-unknown-freebsd.tar.gz/md5/71f7d9d9234a0623c4b2ee3a44089b62 +LibUV.v2.0.1+13.x86_64-unknown-freebsd.tar.gz/sha512/e73911c3ec35a2201d42c035ecc86e8bd860604b950cb1b7784ff49e27ef5ac9b1da09b59d359ff25b093b87593a8305105bc43711c12eb9654972e280c26d3c +LibUV.v2.0.1+13.x86_64-w64-mingw32.tar.gz/md5/471d20fa2eac6bfd5d7cdb1b7f58c602 +LibUV.v2.0.1+13.x86_64-w64-mingw32.tar.gz/sha512/3f5ad55268184227378ddcfed0146bf0386c8cf468bc53a348d21195d818db4db768be61fd23e1ee2ecbb52f073815884a04a923d815b9b5992825d144c0633a +libuv-2723e256e952be0b015b3c0086f717c3d365d97e.tar.gz/md5/d2284d7f6fa75d6a35673d22e1be058b +libuv-2723e256e952be0b015b3c0086f717c3d365d97e.tar.gz/sha512/68d6ab740945b9ce3475118ce3d186fb67d7e8125784cc0c827df23d63f50c40c0261ef37365d8c11ab9462a8dd4e2e6b19e91e3c84b64d8fb84fd3894afc4ac diff --git a/deps/checksums/lld b/deps/checksums/lld new file mode 100644 index 0000000000000..1b238fdbd1a96 --- /dev/null +++ b/deps/checksums/lld @@ -0,0 +1,108 @@ +LLD.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/0edc0983135da9e37b18fa3fe6d56237 +LLD.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/2adbb4eb76e72be28951c96140070b6d16c5144f689631d51b56365549a5d38535c1dbb5e351a6bdac4648ba52da02297591874193b1c16e7078060c99d23f04 +LLD.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/59b06fca083f1a5e9bf9517ae4f6a4d6 +LLD.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/7f1dc641df9288dfcd887239b86e7fe2871220b9d7f877b24b3197ab73d2176c4533decbea427b09e8f70ddc6c7570d31f5682eaed7215193e95f323769276a8 +LLD.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/c97e607a661b9ff571eba4238ec649dd +LLD.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/7c7add8a0fac379b580a19a02966adca4932bd4573ba0111262544c0d935fc121c5aadaeadc97f9564331202b08c7366ceb170bb2b318db3425c157772d283ea +LLD.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/d55ebbd25b97a4e4628fad1e04782056 +LLD.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/681729b4d10d8f66b0cdb89ca4500ee8a417561cc886608d06af0809d946bdf7cf5c6bda2b6d5d577bae3a15dc347568a3d7d7428568f86ca61327041026fbd2 +LLD.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/78b06e5a351e6eab372ae29d393ffdcf +LLD.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/37a8b5fa3491ec8ae74da88e81a0c229d38166acbb46ff3f5a819034c40fa59ca2ebf4c0ed58e615baf7bf7da789ba86114738252501cfbd842be95cc2104dd4 +LLD.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/7ba5b76c83d746a3c62354bf753db697 +LLD.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/1fa403c8923487e2d6a8e8c1d86c2ea955ed32bcde2328cb1167a315cdcf704af896505e9c44b750ffca9e3ae66e805f60831136eb79fe1c6d58eaf81a78b1a4 +LLD.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/f052208026a0fd5120ea838843b244ac +LLD.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/fd9ff2d5836300bcf76e4aeefb1e57860b3203fab0c32e668dce3e636dc362876d0fba1f2c23bf55a342ac17294c73e839a8eaf065d64d4397582dc212b8b9f4 +LLD.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/4d1077835df0f592a168c140ffe6299e +LLD.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/8dfd44113b817f607bc38ac1b4ffb192be340c826b9bc8f9d41e92e0f0333d8fc4227f93aaed16a4b9e94a5ec8b79628f2d3a73fb644684a595921f36ccfbeb8 +LLD.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/0f31939f4ff00c572eb392b6e70aab38 +LLD.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/581441087ad4869cfdba13808b2d6adaf929ea1b38ce96c357f276d77c3e63439f8edbb822c8f41770cb61fc08837d7eed2466d187683bc44f2cb3c553e2e60e +LLD.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/ca767173044b5a19a86c6a890dda3b05 +LLD.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/0577785079039b534fd736ea7a51d9b5176693d81e0bcda4fccd760d7c1218042999b6a38b973a903c0ef68e57dfb3b86e9e2f9e307dbaf603997a853f34eed3 +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/89bb950f17a5b792a6e60ef98450a6b4 +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/54bb68159743cd14ac0fce7f218a66ff6bf29e626df8dbdbd6e8581699d9b1d357a3c10d86c6822bde7299c14728bc55480f91cefd041d1de61cc179ed347b9a +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/735e4dda5f8cc06934f6bda59eab21d6 +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/a9b91beed959804b9e121fee786f28808a7670fc5d2728688cca1c7e0fe56e82e47d95712e38fdfc42e02030896843c4b3df9928eb34c2aca9ac02262427c76c +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/30a95179bef252aaca41984daa54c680 +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/0302db3c04396a30d1f6ab8d8d585bbe3a9e70342f068747ddb875b024c173bb9bb34518da7e76a10d3a325dfd741118f36f67fb83251bdb8a9901c4799ad79f +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/4386c746c5d9b1408dbe7df04bc6a08d +LLD.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/d71c6ebf5d3eb42368ab336cf8520afcd05470308ea117fe95797171e5c573948412ce777f62cbd45ee99ffa59cc769c276a60393a22fecffbeaf8b77b50ea35 +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/49287977de61b100979355e458c8970c +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/85ed3b2c7d2478a307a393a2003e694fc3097cc6812143abb3cbdd73a7d36bcb6f06a7d341ea639b9849f714c2d8f418a8b96035ed1c19a3957b42d005c0427a +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/80a97341c9537b8a58c7df23f86d5cf4 +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/5774b246ae820de4230a1f4f65bd683145dad5cbc4d326fd75649e06e773c74c2cffd48108a79ee0cc93175786450b6d50f7ac532e6f68961c18fe6119ef94f5 +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/6f84d6858aecdfd95726a37c9b6a0e0f +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/2cdac9a810c777ec6d85093926292c75e4287f83b7224246f6fa248e3874a2078c46377cd5ccb0f36a5e25b139691f1111d705079e89ea4215c9bc8659414094 +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/d40f0956cc36aa7846630755a672a91c +LLD.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/01368311a0ecfbe3f23514115f0bce7ce816c878815d937f3fa067b9daab07da0c02f520a96ad793212e5056bfb6294dd0129dae75f274dfeb48191e504c5322 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/689120b8091b9da8cc9528c96f5c5df2 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/ab78810af7d77116a4973b5825d5090133218cf08d5d77be14f83e028821e83493a112adf71094cc208f74cf4deabda63d7fff98866cc0304793aec9b27b7222 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/5627ccf1677c48b7ef8ac9e5faac1d20 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/454d2636cd72974c79c2d907e56e3c69c30c3fff78b199591c9ebe4f14d04c40c4bd7331f8dc2c957c37e214da8d28ef3a47ed8d3dd4ca9d480d52bab3429b39 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/8f50e5f684c41845308c123f8e45a0d5 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/21baf8a00fa65473ff6cf7ef2974ef88cd5b0eadd06ff85598de10d09425074297bcff3472ef001047a5440065a2de2fc6b1eefe3a32c7c1b3e3261165dc063c +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/c2e0a5f58e38a9acf2c3914177ceb827 +LLD.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/2a1653d171a2ff08bde55c53973e62955fe9d9629388ae014a645d3199d8f4bcf0fb923d06812ccd62e224032b261c8ebed56ebebed750acbc87671203d7aee5 +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/fa3959aa413a2b707d8831edd2bd7867 +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/8b74fef916a72c2f4933c21d3344410c7e03e64265a44dd62cf2ef2ac0feeafeb2b443eafa5dad3d3d0028be96b9424ff67b16391f1b3a2185826de68921adab +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/b0751bf7eba4f7f7a28dc22993eac9cc +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/7510f7349b06365e9cd260229e7b8c84da26bac072c5fe9a4e59484d82a0753d4ecf1066ffe41343f881a682590dc9ee4ef4a49cd83dba45c21b8d76dfb80f67 +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/5abfe9e960bab4c8a44f41aaccaf936b +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/efda0e0a35e2774af2f2df53f89d61f146a5730086d40865d448b009c833934b23ea4b296c3dc3f2039527b72ef40493fdee6f7c630484f64cec2d1aebf4a4c1 +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/bfe87378e965050b1b20e993c8b13a53 +LLD.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/ef2fd5e81f349673417bffd68c4122a87c09caed3f6f8f0235bc70b75deca7363cad68276aa708fb9ad8f7edd249d49f78d9f5fe7b226b62e8604c7bd3d4b9cc +LLD.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/4ee16f57d7dc060007250e17ffd55817 +LLD.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/27fd3a21bac676feb2c2c2363c027cf12988c70d889174e52c6bc1fcb4a93241f4bae85d5750ceba5fa971611700a9d15e3e02803cc14382cf6a1ab2918b719c +LLD.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/06699da5617371442b0539203152405d +LLD.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/83ba6300d5669b52c1913440598a2577106ea73e0b83549a5b3b0f081a94b6b8ca9fc05687d2be4b60c2d6a524bafd43b839082f0eee58b4685758061b229fde +LLD.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/a051688aa3a6383b4be4faa4f4aee985 +LLD.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/2059c6ac6579c4720e7167cd547b679a9c1a27a2c68174ed543be935ee23122234b3f2a4555de0abab3a982aba73d1751db336f3e28005ce8e4659d61f9269aa +LLD.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/600baa66310cf348ef3b4351ada014f4 +LLD.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/52b4718993d8abdca8ab701e86022367655d7927dabb8f3a8e41e43dbc90a9af78caf8abd37907a79b0f05017b6f0ef72314a187dab5bdac8ef7996e74c96e2d +LLD.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/4bc599fc07e9c7c717355802c1538a6b +LLD.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/4521e40cf6cca31cc9ec8ad974c6eb922632d8ad0d5008c951e23b7ec193a71dba5f3bc2dadcfe47e2ca29395646293c6559bd88ac286c5d31d5c4521756177d +LLD.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/462b9c453405768c2d93535fc83308b8 +LLD.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/39dee4d4a0073a8dc4ea63d43bc9a357bcf8e26e3c5c17f1441fa72145f5a4ff6a53e0aae6de687b8fcbace40207ba06e61cb8452c9bfff7882ab48e9f9f5ff0 +LLD.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/8b12a4f5db80b925785f42a97e6489f0 +LLD.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/797d12888668712658fce85ff842d812a255fa4633bf4e78b21488867518a1fc2de746885e2fca1055595ae476670790239a714797f2322ca04027afbf27330f +LLD.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/acb8716cf94f654078c7dce4a140f71c +LLD.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/cf64ae04ae3e55575d5781ad30212b1c0ec734f81b42e3c26da8766bde7c47b6a9512515997afd15f9eeef2ee326c7aa589ee1b557c45b4ef955a8afc72fd759 +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/331d844c447f564171345009764321a1 +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/978349a74fc5498408a5318c87ec6d25c01268b9d21fb85e6bb601243ad0d33be8501b181d1f9ab7663433a740912f5bcb7160caf1011b1a2c84fdd51e0fce78 +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/8595a49c49e851973fffae7c4062911d +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/f707e514843a206b53f380c7bd8d4d8203cc62219344c1234416462dc1cb3d3f8a7452ddfd0f07178d43dfb193b4402a018cc465dc76b43b687fd20fa1ea5222 +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/5b4463e81c156dabe3d182c42eb647e1 +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/995db577d4a78d62cfcfca3f1fafb333ff26548b41d8aa8d763e4705dcdfe8005e2f68873faba4040599a6d15821a523261d0451d75fdf6e1c5224e8e777a71e +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/d2f9f08cc952c0639f7ef1073c8630d6 +LLD.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b1cab7b813fe0f7c26c55261e8561295cbdf1e812db3844b87605fb527d09855f2bef4a40ddb0a7cd354c7cbb626293d4d4012f33acc242f9af4abe1dbbbeeb7 +LLD.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/e82e3b67a073cfa6b019bf5604eabf2a +LLD.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/9bb18adf78afa9dfa0054e6511f5750a9e2fa9138aeb1bd83f7a51d37d031e2f3c151463ea8f682dc7130cb98fafae0b84c60d3befe27f9d0d3dc3334ef82420 +LLD.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/56da3cbe81ddff089ccf6b6392a9396c +LLD.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/2af483a1761022dcad414fa7cec7fb5c6fd54be28185e49539f4824cb0b6acdc1cfa5c78de31268dbdc444201936c5a6d2e04f39ef6f0b9fb184985ba4e3daa2 +LLD.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/15cbf5eaf89c7b834ee19629387515a5 +LLD.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/75ce7c398bdfd57af2c09dfc946b024d5a72e90575ed92f28e015e620ca89e421dfc9a391f4a78277c3e06c38dd696d572c5601a2b1866e521dbc2fc5a60da56 +LLD.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/b895da29b6082cdff6f0324179352fdf +LLD.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/e89a97dfd6c345158e3e12cdf97d33c22f849e5438401cf5a3670c0d1cf0252ca03e4c52475a42c3e6c2b2d689c2f53fc5cb7c925a23167ac51fa1a5e01e3d7f +LLD.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/7edda2d8c2eaadec2d262ded2456934a +LLD.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/0b1d60840d638c0b0269b901a3f5198e18e244da338aef2fb49b474b3601d44a2b4dec13e258909985e363ef8a8749838b01dd195e05a266ca36e6d9f274ef17 +LLD.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/e26138e3491a053ea9a998dd00ad728b +LLD.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/1215861fa52b1ee21196bbce0e99912b25f887f5734e0c2628ac78c1af5fdf57c4d7cf099cddcd7031a26c60cf141aeea66a0147428008cb485c207e90801835 +LLD.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/a1e786ac775517b8b483bbe3f6571d37 +LLD.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/3937f156fc2fb8eecb13444c71f380753c16b08f29124228808c91ea4258ee2195219c4a9b601d4468cc24bd584403c16175518a620bd94a7dadff868b3771d7 +LLD.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/976d840de14ef6ee2c0a538197fe8f10 +LLD.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/7f58f975dc3d69f502537aca79509bbc3c4f5da2ff8ddb1c7e27180a6bb2123713eb42da61cfabd7a48a31fc464fd74554b34935dfdb3ec095d14ff443f514f3 +LLD.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/ab0295ba327cfa6b9a252b0e7a4b50a5 +LLD.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/7c750916d4157ba0a37cd1277a0f8faf32123dfc626ea76f848a7c567fd889a7801f8402a307c190ab34fc21b156f2a23967abc9972fc103e5847a200ffc7305 +LLD.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/6827f38ed653f33953ff7ae510a517d5 +LLD.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/f01c655f6433ec6808b62872b8fb4c5a2d8e187643c11f0b4f5c06e2302e462353b516f431c1e26ee60b579c0f8c8c6385f018db3011c619745a39f9ef263436 +LLD.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/385cd2715d29de3e85a3ac10bcbc88d8 +LLD.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/5c90e8e583176ed9dd563f794073bb344283284a10e303834b6c5a9b71369f50dfbcbac61400ff70f34f3065279c848dc29086309ad38774e50eca3fdd5f9799 +LLD.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/241978345735e3b57a88918693c0c0db +LLD.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/916c6a4540ce9a2b2574d92c3aed42171f9e49f776ab97d3e5be84df832d463b7e542529c3ae81e4d6a31d5789d55b96f9559f48c0e4c8be36d70e3ff6f4292f +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/a4f16e809240c1837b90d28930e3f711 +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/983201793e0f9e6416bcea23b4a70a5a1a36fbdd72bed2cc60ec267eee441aa3d9c850b4aa3da6a232f3de451089754138ecd5411e5431f632e48c1993513ef9 +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/70f47c2be55741f754ffe89e4749dafa +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/f2dcf4f6ce888801e8a14875909f78b46d8ed853a7063a185356c7f21e42e15323d847d9a9d4b020481a7fcec9539d979e4c7f2b083ac1c1bf75a275a200562b +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/becf7c6cc39a98cb722899c94b32ca34 +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/84818621307779e27cc149afbf958653049e47a62ca44ff78552878114c2fb0f7c40cc83722394ee8d880a6ddfdec79012235a6ed20bbfd1e5d9e83ed0a0199b +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/0117c05f8dabf41c4628532d59cccd3b +LLD.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/b276dff2c54fdb6403a461ecf5435978e2cf9c9273934edcf3a31e7f640ecccf37de672f6b0b3f296ddb6a7059b0d95ca6c5bf62d62ca545cc62a69ebb84b8ce diff --git a/deps/checksums/lldb-11.0.1.src.tar.xz/md5 b/deps/checksums/lldb-11.0.1.src.tar.xz/md5 deleted file mode 100644 index 901bdea38188d..0000000000000 --- a/deps/checksums/lldb-11.0.1.src.tar.xz/md5 +++ /dev/null @@ -1 +0,0 @@ -e49cde09adb5ed43a651e6d5bcb2aded diff --git a/deps/checksums/lldb-11.0.1.src.tar.xz/sha512 b/deps/checksums/lldb-11.0.1.src.tar.xz/sha512 deleted file mode 100644 index 16f939fb1007e..0000000000000 --- a/deps/checksums/lldb-11.0.1.src.tar.xz/sha512 +++ /dev/null @@ -1 +0,0 @@ -05de84a0606becdabacb46fbc5cd67607ca47c22469da13470b76a96b96e6f34b3045fd1f5bed9c82228c2ce529562ee71667788a5048f079fef450d63a1557c diff --git a/deps/checksums/llvm b/deps/checksums/llvm index cff2c009bd0ce..6380397ffb84f 100644 --- a/deps/checksums/llvm +++ b/deps/checksums/llvm @@ -1,61 +1,111 @@ -LLVM.v13.0.1+0.aarch64-apple-darwin.tar.gz/md5/de198200e72a0176aeb383bdc916b472 -LLVM.v13.0.1+0.aarch64-apple-darwin.tar.gz/sha512/84e5472df5a89821baa7c7f5f787d576a4fb312738da194af3d79dda916c5f69bcff05e693d76f15e00af6c6832a26e01933fb0c33b57225dca5a048869c9ea8 -LLVM.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/md5/ad3571e776e2fdc16d7ea54b236929b4 -LLVM.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/f9ceb4c1389301fd8d85bebf966f9482fcea31a5767fd2dc89c460f4404549ae9df68ac1d52e0948c75910665b857090d62ca53e84a09cc191ca265f460f2975 -LLVM.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/md5/27ce9c71e0c41e1f72e54b7a4c6f4826 -LLVM.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/941de4e99e24ea33944a3e93fc4c486b9adb9c721a641656803996785138eff9dff929ee4b3261dd57916086da3ee2dc7489a255c44ed8d2f0a1d2a915bf875c -LLVM.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/md5/e4a26e2ffd866a29d276f20565a0e76d -LLVM.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/sha512/0c5c7b8641a02c53ce24d40183638986651e644e423fe43b58f3657a6dd21f294c43dcca588dd04c044d65745f8d493f1353cfd168be0cb4f5b68f63df921468 -LLVM.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/md5/ff6fe3eb7392178db4fe8fa65a61dd7b -LLVM.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/sha512/1e69c89cb616d9ea9b2f6a863f44d0fa83e2e181f8de66dc478faf3881a06d8b6a81a032607064a952b37b1ee5d25df06105ba4d2758e2da3698e7394ab69a7d -LLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/a0498659a1f2e896762421cb4f6d2a9f -LLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/8811f7ad799f0a31191eb7d8dc3e13fae3b47b1372aef99e02b3477e3e75de87da6d7dc3a8f7972ffa5ebbef4c58846d57981021b944ef8a7b303083322559d9 -LLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/2f5ecc129ff7d58eaf224c703973c157 -LLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/562d16c8b9de1489d655c1a3faf58b44a69b195b5d97fbbb3b60baf886a357ffff232c0ed1daded6b5aa1b635615aa3d9de497c7e87b081ba83d2c408507acf9 -LLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/9308ce36b9b3f9f23719b8ec4c7eed0d -LLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/01330f93f15fa56b5485c0444e2c6aad82df61170579499b0a1b586871ab05a783651cd903043c39bdd955c8036e8511fd33fd541358210bd3d801b21d31750a -LLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/9e60c460dcc29228d137f13d3c04798f -LLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/0bbac99fcd2b7e6fb958c1966ecd135898666b313938b8fec13154fb16069ec2dac06f19626a6cbad973a967ea99bcfe7c21930486715af0a666cb850ccc7ec4 -LLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/b4aacd37b274cd86f0d74150a6481e80 -LLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/fd7cc8368fdf27805155e25c94f099b65e01d0b3edddfc3934e81da84e480801967960bdef4ef68e5cfa325f5445cda6f3e1ab9d60729e86f4aaa39c20729af8 -LLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/ed180a5375b1198dfd58bb1de07db4fa -LLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/09077792ea1eb299bc5215ecc7904098467dec48f1f3cab532ec673bfcd9711120e77744440d5a28a1496b50490d3f551b4d8e14958396964d40991adaf8252c -LLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/53503aca7737a92abff745a3ad23f270 -LLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/12d388a6b5dfd45f8c0fe29453f49cc17bd1ea54ba281b92cf84d8698b03c9204feefab79245e7d9e8063a311b96679f849456366064b021f86c284417c43d71 -LLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/f9f002f64d325fade65076f5912377ab -LLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/c87ce1742babd909ed4faa66aef71301d9da48c01fe772e8775af7b5b41f49ba3f24b0f8e26694ba93a8c2f14fdda698a157bdb3d95bd114e2bc90dd85acb340 -LLVM.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/md5/39e654c42cf3b5a4a752e46566b8b9fa -LLVM.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/sha512/4fc6e48cae0e33843b875dcc39fc2b860380cd6ad6f9214367827049b29e2db85593544866107bc8950ea844be09671092ef133aa764dab48119105332b932bd -LLVM.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/md5/a5928523eff8a9fd2ef66012eb3ab556 -LLVM.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/sha512/2595eb66b23fb9013f866578a829e07c4144996ae660a7448c196255aec43e6959caef2bd074db0690d91e0a39275b09c935d634855eb69613ae834426844f7c -LLVM.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/md5/47d3b87788b3269da6aea81069ea13dc -LLVM.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/sha512/0721c1440daaeecc95beec69e7493dca098d619ad27125df51429704f3d463fa8ab86685f9f486378a028a99b445705dd052d9cfa9c1e729ff80fc2e1b46d508 -LLVM.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/md5/0604eae4ea2d2dc715924976d006b026 -LLVM.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/sha512/6ba0acc9f08d1308c07ceb587e9bcc3de3d167a133d053326eb24d0660d18b52c789a8dd86612b85c894c9faa5d4fe6b9dc65bba1c8ffe649999b8458348dd19 -LLVM.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/md5/7879e8a03f4db12585ad2f8545fe5e06 -LLVM.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/sha512/e0d23395b0962870df1c13edf4aa67bb2ac9372ede4160e7347fb94a47d90e76e738a2224b82a604926a8fd4a3f685935be0d9c0e4697b4c5ed53183ae5e9bf6 -LLVM.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/md5/fac7f70937406d1c06d84cee96f61a61 -LLVM.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/sha512/5b987b3a3b4ae677dfc11f9dad75a5db0f4affd6447061f0996fe81d978760f9553c9f7a89a1a229ecacb6a159b9e7728da2c7bcdb49c8a2fdd4b1498d117e6a -LLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/8852de922ee08484018d8b8f4a4459f7 -LLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/17412ebd9e63f370eee499e883fa0da0fa05a3ccb6ee3149648b4e55241166d2f5b34d759b23d654ff58b0167ace2cbe10329bcf984cc84b7c7690b6528063b9 -LLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/c172ee499e60fe6e22dcb135854d9f39 -LLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/79773c87795f5251095473d5797a0fbc7a4a4e7eeea45eadccbe01f62eacbba0b6159370675088907297b91e020be2bf1339c211682f7525c03c1033b91178c9 -LLVM.v13.0.1+0.x86_64-apple-darwin.tar.gz/md5/730d568f05aad99f6eb596d623c18763 -LLVM.v13.0.1+0.x86_64-apple-darwin.tar.gz/sha512/fecde3420de9051f32a1753c30d83436f9ebe2e5805d2dcbddbcb10eed6d84f0b5af81d33ff05d1c34996fa3d1198f20db56d8fec302e64d85e1322893acce2a -LLVM.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/md5/2dcc7db78138d81c6548c59e9ad2625f -LLVM.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/48e18a31f149c0101f80d34e8e293078c5332194821a33c290aebd0701249a8130876752938b6af4346b1985f8c16dea575248f4e862d019c3290dd1c2570e6a -LLVM.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/md5/f101a354d0b9b777f4754505a0d7f677 -LLVM.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/f77a338d4e0c379e5958457ce5b3d1cf323c3869616a4ab6f40be3753493966a893699de9c09946f4712c6684cdf08e235cb2d33b724e87dc8a2970f677ca952 -LLVM.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/md5/155c5015da0e2ffd94fcdf9496e855df -LLVM.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/sha512/a1b2e1f5f8aaba0d74efb0819e39ad5ddb1740ad7955ad41c44b0a3483ee5d17db2b32f5d548200493c390cadd08dfae3f277833dd774c95c90ff989c6bf5969 -LLVM.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/md5/d3f804be18541fa1102af46da18a743d -LLVM.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/sha512/bb0ab78b3c03081f352bca252f2ebab3e5a47a83ee4c2dd0504543457c6f32dbe1449de97a2b5d8f970980497a77f78bebae3dcdb7d0c1c346e9df46721eb32f -LLVM.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/7f7de7e59d22411068a35977a6fef75d -LLVM.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/29c9531e6ed6d0b5d85d58bb5122531212c39ecd10f4a78ea1eb42311f3328813fcc4d2ad2311eb5cc3030778492a6b8bc5c9b12653f1ba36f16e0a50c4e0272 -LLVM.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/1823541a9a6c9e9134ac7645501399f5 -LLVM.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/2dbee2c1f01e5cc4f0b70c0147352ad95f0b91f5cb1efcde7ed61b54b2baa1b0bcea0b97e0c0ff6c55526e6b037f25808cf995f861ce46da56195bfe0b0e48e3 -LLVM.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/454453a2afb04e3c4d6cdffb37591a3d -LLVM.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/21bda5f9ceb9d4030121eb9c563233bcdab5b9d1d5b0b9b0fd22cfba3d507ec59ab4c98211d0d5c2cc5ac0b0695d1fbe4707a0264fde423833cd7a461193b556 +LLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/f18fa63ec97c79f3773af2bba51f69c6 +LLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/4ee1c3e746177296fbe976976c58b6ca09dec22943ac1e63008aeed94f46619e4e60d8278566e74f4912fa9d3aa21c8b03ae2bee360db54c7dcdfa2381469148 +LLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/f482e543971546cd59d946cc33d79d5a +LLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/d026b746f419e9bcc04daea60b1e66e26d4132e7a551b0f14c95ea95dc9a0f4e645110d8cd5b91b92bce7775ababb715747a2e4a09c0920787e2f25ef1bfbf19 +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/5d12f50225285b180274cc89c21e7c44 +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/3947f0d909444716a29c26a0645288e0f02ab19e6fa6ac0104c5ffc9659f01337198a5914beca2ccea7c98c9aeb12fc537891d440766054c0b9d3bbc40e24165 +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/e555476d3324996897cb0845ca22312b +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/a809d8c455d6f72c2bfc2517ab375d6ce329880ae33c5c1bf575dfd599d6132e38df35fac4300a0e72726ca33ae1db69ae67f5fb03d5c617eb34f7ad20f09b8d +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/6432ac27166a0ebb550c7b000c27e2da +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/be6440412d46252292e6d907f04193ed3f438b06419d0fb8b067a7cd89e5cd2dd9143af4605de9a2a697ec2745efbdaf6021d065346041fec3b86051de42a26b +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/0bfd05e6bd23c92b73751a86826b288e +LLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/68c08b2624bd0d38c7cfaa8b61b7e1ed70c7a106dda814f146a3f5796cbd42f476ef19f726d3ce368d89e624c7a3fa7f07829c171d79581f3cf565dba28c27de +LLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/53a9db6445352b44717f7e0f81d896b2 +LLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/ae34208c128f1d4468d8a25b060bd1904f36a73dd0029606394061843f90aa26f9c3071e8281e76dbc10fcfd103f04602fde370a0cb04d435fe2f7a230989cb2 +LLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/f7320272ec2f3cc86a742a8ce3b4cec2 +LLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/612f03f49b04fce2a21e3e0242c3ae591ccdf6398e31aaa63956c40fb805d4a060da8acd6e5ca1d1c0a7b1f994105ad74b1acf78490e31a149368c8a9c96c026 +LLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/db7b7a03c047a6aa7b599cafbf6023c0 +LLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/05474495e73c56a8bf8a2459e705198a6c6e32df5b83ab153f1080a763d2f7d79dbe014592e12f0f3063b30bb0641dcfbf4f161ed988c777c8955ce9bdb89cbe +LLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/88255189a80045bb410da1eee3c277e2 +LLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/b944ed004867d6bcf48dbc089d6ee5904318d6a2ab3a7dac3c802cb7646d4df21950a6e4bcd5bc57bbea872f99f39ef9e174dde8dfa4f5518f23a1fa0e8cf959 +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/a25160098b55d2ec00cde15d088343f9 +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/2e84a0b52a4852a69155aa4cdf33366b863caba7ced42db573e401a64c0fd2acd1d27446a3ad0ff94740a5fc4c579e745802bc32f925bb505177afdc64fb85eb +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/10b225be9d25681a36fbffdb5f3e315f +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/6c38d87c8aa321fa08ff9880bb27cedda1806bf6aece891f08f757e6276dd37e450a899c4fca587bb693f683f9ad0d85f388e7c4ec4a76c96e73f0f26ff6766a +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/320b77cc43b91549ae0e6b538ff53f7b +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/6b297c643530c06be5ef1d8dc2fd47abbfaa3a7862ba42ee9e4cff1361e54aa7ce77d4d9d7f5d2db38a3c780cd38a472eba1308e1f50aba74d3de3bf188fe91a +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/c3e0fe843bfcbe0c03a563bd40a16f0d +LLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/b62c3d8867594e34b1eb0c16f1db609c4b43146deceeabc23d4ee9af2046d8b2ae1a8566e2613a69691646d1991017f0a7d37ba8636a395d471f8f385a478479 +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/be03ae93d0825f335411a4039905052a +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/9e0159681e8ecfe477d3099314ccf2986eb2a8325cee274b6ab35e04ee9e89ea61356e5082d9adab6c41b8be98d0171e41642afca283ec59ed91267e66223c6e +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/9e244718d094dd6b2cdc50be77a284af +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/705668d6b44bc754fff8f28246d8359773f29888c1f6ead6a5f1e10386c88572de27d4d47b8a1bb160211c07fcde2667833615c31ae445d1929229d981e36e3c +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/12162558c4c89913f0486f3a4c969c8f +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/dc6a48cdc9a04b3f0938784d5d40d0b453bf438881895c78a0cad9ebd83090cd9f1d12fc00df6538d053b2943a590a3217a8309aa0912fb3615d728280979276 +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/e5012844af1fd76d6cf92ff0921a9f24 +LLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/436ace73907097593bd060ff5674db2e36f7a6e4081033b078554b76244ba0d2caea30dd94a49fb62c96f2a1c3e1f190de440bd2bb9242c1206f4794b65b30a8 +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/9ee929acc7c52d18a7c42808761ae233 +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/12f07258d295245f2b53414d0df0144c547c60b090354b5548f50bb704a82e1623e55ad353eec233407f1840a50d423d1404fc3e7b87f2386863189e7f886813 +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/c94a2e1f4bc031a7c663111babb0f8fd +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/4c82406d8df72312798d95ac0d038b38eb332b4f4f8a586bca7103bdbf7759365daccb6f3bdef9a9c74a06d04a12e96c01ac9fd03aa38f3c586a7ef3c7ec7e8c +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/e038b8feabb2e60b866756a8dc7a5947 +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/c3e03bff11db87c7f131dbf7163b414cac91556795e4c5c340bec52409c39f7e91c26cb34a6339c10610d0906f57a209d36f6cfd458b26d24ffca9a43d259f5a +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/b3bf4ff216946ad38ac6be230e0865e6 +LLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/0daba831dda378b2add9607fdc0d32046c0390a0a63758a6cdd9c0b90f660559cad0e71c5ee0b1c4264f3427e523a8c615bb87ebdfb63a65b983acfcb8df43e1 +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/33a3c56ab597e6f2c2863842f0103e53 +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/fb15d07a66b1f56b73625ead591f90b57a843cd9cb140e5158159a5f7c9249437678c61d0e19a11a65a536776dad37abd6be34ee0ec5dec7c0736079a0fcc7e6 +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/7488ef289e45e6c44753a42dc51aad7c +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/6ecd62f0756a1941c8df92605a7edf9fc2e70957f39ae407e5b1b49977301ac6e82d55bcb856668135c27f1a75d156f3dfe7a27c47c6a3594c2c9f032af8ef19 +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/5a286dd05b936c0a3ab61722531ef5ee +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/80016717959246708eec8588fd6bb5cb4894bf05c2d78cd1641e31cb43f38c0fda866283dabf1d999c77d030b70b89363e2346bd9b9310a2999623e47b2e4e7f +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/b62420d31c65fd8720427900b72f9aa4 +LLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/f63f62a667f6f2c6ea76db2b142d58cad3165a426fd420348f0831d447a9eacfda5ec9c006e05f60c1f2804e8b25e87369e754a0bace28257035a63a1ea23a76 +LLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/ea922c8edae65c855e40f6ff924c35d7 +LLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/d83a3737058da3c2427c061cac83ad910c43368e47bd1f9ff86c21ef0b40669946b128bd1063a8fcb081563ecf606d70a783a0747ec951c3443077b3ec8e93f8 +LLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/7a20fc23311317b85127fa033cb69059 +LLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/36d51f84dcb3c76556b6ee677a4f0fde1610df30a7030d1799fe9681c27e04faf1ecb4b5731db9a58060879076c037e3e5bab65faecc527296b439743bdd7d86 +LLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/bf6859a7e73fb51bf91e2c7ce5b879e9 +LLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/39aa6b1e2923aa572458cba58a328bf6ac0efd5f11974e04343d65cbb56fc5804066f7cedb1e9c58252313f94ee0487d6855a1714adebb3b71fd6c783a01018b +LLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/10c32deaee824ed7a19dca9055a138ae +LLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b9b14c9ddc2b0b07c07a53bbd3b711737d1a7d71626d3c34812bc3862145865205e5da07b052e119aeaf54fb97968b27e86450d768312623a7a87c6b8179d872 +LLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/caa574701f180bf4dc323ecb441fa53d +LLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/1c97d2311111f4411c3eedc6f1338a8c899932e7fc3490a03c0c9b2bc4c9a52d5797c50339ec7105d60edca951fc57c6f11bc7198c8e1c96334147d2b2dc670c +LLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/f46c39e2f848fb5fbc9f1eed7fa695af +LLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/ed5bfd8057b2d6d543c4a11f0c1c6502dc7aafd07d0c5a96ca2b1d0c5194093f20f995ee38a4a25cc0291b31c682c6dcee460f9fb657b90be5afd43258ce4c43 +LLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/06533f3ac22a8a9be2501b6708821806 +LLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/5284308b46ab1d8ed896e0425fae4288f87a640707c8cd5f298520cb19cea8d6311b0e6d21d5ed016f6d87f47b93d92d371abfe9bf1810b357972b7c9b437811 +LLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/f75c2acc329a9ee041ff2c81aa93b4ed +LLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/6ec83776bac9e2cf2cbf3f890412a940c9507ba06eb50b6a05148c9c336775168cd5b6ec4aa1aa148703e6724c414830e54c3ae075e4d3649280ada705ce9816 +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/7e2ea1a3e9c61976b446cbceadf33193 +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/b21830528362115476cec7f32b11f3c1541a5779027c824882fdc00b248ea0f0ca8d08ebd86b938c10fc80a7f7930d86e2cd4482fdce33822613128eb250884c +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/d77b1c5ec7cb8bd02ccd24347e2e620a +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/84ddacf1c222bb5d895939ba4aab80dc6b5c5c596a36fcc2869a87d639d006a156750f04d268b6c10b47d286cf3bb5e8c20804174fc93881383f2512833ad7cc +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/26f634aff16b5c8cff48b0183f3f8ddd +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/cc3619c9c8adf322bb334a6b2c9de1ad088a17f117bcb9aae5b51a4f7613a50391c3478b7f892e9dcdb802067de69b098ba7d61edc9979b8f960028af0fa172b +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/891a1f113e7f3f8dfa56f5f28e1c8176 +LLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/9b6a4a26c8f83764f892f7caf5f09a5453ab6e89c742ae4cb1e831a0711104d131d8fe0d9a8cbdd384b2d881edb3d9026af804f47f5f79d62da1d51dad4ec0e0 +LLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/7dbc009fb3ef6ba400baaafa733afb54 +LLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/c279c4be6a5e131b567625173b33e1f51a56c53eb0740895c1afc8b6824a00d4331df76bae9960c2143f7bfc2a9758dcbc7898fb49ef4aab56df6bba7030d636 +LLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/007fdc357a995d68a01fb45d52a92da9 +LLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/2bf2752f654db140822f4ed74494bcdddb85f4040ae24a753ed9c77efa29d2f50397719fa20de031325823004a66ddc1c00c9624887289c8020d6627ffd21f5a +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/fb17aeedc48fb6a24f0aa2d078ceb2f3 +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/bd622d2472f85ac5b0cb255a929413ae3b30ee06ec7204148072dc1f9da7bf451b07960f4905a66d2673db9926797e4bc33b262cff656e7bf4cbcfd132b49868 +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/eceea244f8fdaf61c6addac8b8f57319 +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/44ab4a30ff65685a121dc54c2de55de441fad95b02f54cb359ad44fb298adbf48fd7651ce871fecb40b08d95e1ca701ad4c857f975a37a5e5a42280dab6fc670 +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/b09f19c4940f6fa12ea8b5076501e297 +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/a52da2ace1f0f2ce0090a582a267fcba526c86a88be3d8e55020ea07e00a1cbb0323f8b8b0205c9417982774fcc05d667b8330f7676dd40c869f374130dcc50c +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/a365e7fd610b6f6ad2dda2d94a141b4b +LLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/5242fa37a93dfd99720f9c4966b4f9ac164987cb8de136c01b3474860c6229538e73db7727a6c7c832f651ce7ccb97dba0082cd66da2fe812dbc8ecd44fe2cf8 +LLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/6645a6254d82bf854e50e47d671b192e +LLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/d330eb15c34e13cad0eeb046e2e27f10eaefcf1d6cb68bc4d55668b55e3c00cfa07bccfb4292647a737ffc69bdf4070cf5a8bb1cb7f6319a1caf0faddde7aafe +LLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/4073ae0cc33b7f803644a272cd0730d2 +LLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/2ea897e3ed3688e2ae45918db51c5a1273afabf46d01a6a27739ac951803645861c549fd438b48dcda05294a4d87b6c39778d42e916301277a0bfc1d9ce53979 +LLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/e223954ddf9e11830cbab24e4ed435c9 +LLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/fb88bfc543ccae5cc9ef737e81757a8f7f61d1a2816501d569456fa62bd8ce30ae57b837ed32dd6d2a7c55fdc26c2c1b1a9965968f784eb3c01680f25ee5bd8e +LLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/356d2f3008be6e04843a278d182834ff +LLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/ae5b30925cce41593a34cf2e76b606e978c352f2bc915d8869b01600c8a81547ad392fc900766db2ade06355c2d95aa473bd51dd3d45f6bf20289d9cdfbb126a +LLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/c31804464c51d1967e73f491948e2763 +LLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/84ab795067bbe71390f15b2d700ff9e0c4fc124c3d111bdd141643242cf6dd7d3317a92d9c97ef5129ef089cfa3d703abc2b12c6a9d2287c90a9ad58a4de8478 +LLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/9f205efa80dbc9d43560830c668659b9 +LLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/54548970bc7b3988142c1a5c2be36f877c4d2cbdb3a58dba71acd7bb32b20cab2ab12c82619abeb6b3bde9a95fb66942e08104df0fb0f59d2ead7eda957b783d +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/ab175b04b9c8dc73f2c06c06bd9d6915 +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/c28bb2033ce2fe182f6a5a29e34a6ce4cdd22e994245f7122c4efb39cedd491c9d4343d8ba2aa8062eac156ad36d9f54605e6832feadce3c6e9f66e9ed7c760f +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/7e4dedc77bdcd6853d613d8b0e3e9af0 +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/e09c451cf018548bb388f9a0b419496a6c6540cdf1e204be391391b3a5645c2198562c2f995c3ae30f775c786e9e59e8b93c0fbb5d00fc9ebf1529dbca9c568a +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/0835b50b6cd53b4d1fd894f27b3e072a +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/8d228561f66feaaa96cf0af71421032f6c241e8a8ce3b8352771072d7bdd972e1b6270e15b0a4f5f4b76764cbd65ec371626cabe8607294041679fe9b6bac5f4 +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/bb61fbd156bb0a70184f6f425ba770a5 +LLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/ec310cab20f39facaa6c0b3a8badded0e4ffbd7bbc1fea6b3e67717046bfe6932a94cf562d3e35dba5052d5cfe62c540c6a38477452e535da52e650fe5dd4d6c 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 @@ -88,181 +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 -LLVM_assert.v13.0.1+0.aarch64-apple-darwin.tar.gz/md5/edbc793469fb7c14af3c33f8584d22df -LLVM_assert.v13.0.1+0.aarch64-apple-darwin.tar.gz/sha512/a3137f2d2d4847e6db1acfc834e686379cdd80712feb3d36d616f73af473599356ade48c98a865d3c233a59d395d40114083fbd78617001b95ebe363fe12cde5 -LLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/md5/00176b5cd73dea5f9265155574c08dd5 -LLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/a911c597ebfdd66bc5e20af38e2456cd1e2be051642abf939d6290017ea4426ad6c68dd17b8f59b9e5e942dff62bc2627a7d66df0c628c100d4bc948251afc58 -LLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/md5/b494be6cdca661a43cb07e55a185cdd9 -LLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/3338abf24c2dd710d0d356e785f30d72c6a83eff5ff91a7e0113f66a213bc39f241e9886f09d41b3e5ccd56f19cc431565d391a4ae88d590a47fc5ce35b57bcb -LLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/md5/8bdd207d78547f38d599010272b7beca -LLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/sha512/f349ef36df2dfa76f915353f3e3e1f0a336614c89e33fd9516a604e6d72b541fd83e0862576c3d0864b518e6fa038749a9c510788f1c07148fa5924fda357e25 -LLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/md5/b7659747556ff940eb0093153ad01dd6 -LLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/sha512/6e0f04738beb2533cb83891c45d9f3bfc701ec1f83ed1c1e06e885c5b5bb4b51c1b6cffbc0a2cae648df1c65b01a8af378d35cd743e72ae3fdb8047774e8d54a -LLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/79d6bca4a7660422a43185066350f9d2 -LLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/094a750a1e4f98a39e0e8a30a3a3e55e55317cab5084115ff33714db82c6645d9fa3ce0599f773930e47ef9261805a7e1bde51c1d067d07e2e844147ce180c4b -LLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/7790a193f05283eb60f2668ddd6e4a47 -LLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/a41689262509178100866588964d5da99736c87e47f23fccaedc53128484e8f24e693858bd82ca63eecdd5af2ef627e3a37ca83df27d103affb015c93c3d2372 -LLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/67a56a20625adfec51210d86cca998eb -LLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/00a950e6fc1b9447dc63fa0905088d6b8f441fd48e4a234018aa0b9fabdc3c173174fa3a22a6707bafd1f4476b3da436bf6f3a5d388095502e07ae9df4de2373 -LLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/77377f6eed3c5393ed2af8205eef67d1 -LLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/edf79f368c23501883ae850fc5a293dbed4fa4b22da322af43233e55799a34887fc090f7ed3a865c73692be60484c770f754af54edffad800da35e17a9a4bf39 -LLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/f3df2582d0c31fa17ec40a20aab9b684 -LLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/99905914383be921e9279a8f304daec4f3155bd88cf33c716f4a7967441f8ad4c544ded404c946b1f8270172a797cf17598bb8a05118da455e1ee5c24b7d7bda -LLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/4ff964f982c57cfd279ff101e923fdbb -LLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/d13eb4378e014d6370b5dc9929c0247ce73dadcac17be446f6aa3db227c466193fa3034252f26ebe06069a6da87120ea6d41ed2087ad3f8a9d64d4c54c8c28d8 -LLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/1324fd002337d2b69abd203bda0d9b6a -LLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/d0f69d9ff0f997f9c72f70060040825a11b377a3518f2060bcd4a85253b46ed2e8eec773732547dab436f1cffde5883b24e52f75d295cbf3f7096dd0d9c90173 -LLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/313006aa96874279764a7b7c4666ea23 -LLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/59c4a59a7e0184643077a45b5da6c5693123d3678e010fd3ccce88761a4434c1321082e056bf16beb88131bc6a98f40515338e2faa8bf5353e448926d80213b9 -LLVM_assert.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/md5/3333f1d17d5a8fd8ad07b1ef42c50f12 -LLVM_assert.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/sha512/cc244bc19588ce041159f6b251141565b31190fd8da44bccb2bc8fb7dab4cdfb6c3aaad166e4e2ffb1796cf28296bb53f94715eeeb110f4dda0852f328fd8db5 -LLVM_assert.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/md5/8aaf25616a93aa95819b2d95de9a11b7 -LLVM_assert.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/sha512/cd0c65cf2cac76cb813eee1e87dcdfea0735a01a296a9d9483c75dd1268b1b48d8ecbbb2bb7321954503686754b78c0c0cd07c428a5722e5e3781d6323046fab -LLVM_assert.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/md5/c13905bd6d398ac5369161a177687508 -LLVM_assert.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/sha512/40719ed2c074a3b18b8811c0c0d204bb4c38e007daf3eb09844fd2fe59737fe850e448f4c650412ff611370f767b04b44fd02c4550ec2d120828c5577451ed7d -LLVM_assert.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/md5/95944a48b2360c17e0a40cef17fee9ab -LLVM_assert.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/sha512/5554935d3932744fb15feb0cba3e86aa98059e037d8c71d3413f2c986e88ec1a58b454d884ac0e0583fa612c546009a27a7287dd240058e79bdbc41f445cfb7d -LLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/md5/391138eb01ed8be350669e6e22ae9fb9 -LLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/sha512/5e25e8b941e60950c5889e1f51c05bc70ea3ca75ab7bc950b674cd1f93a44a7621d1dee89b6f6be6fd0d5982b6618c36e0b4b4ec443d19856fbc8f4832fee6c6 -LLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/md5/22dd78fd71f93c062f090afb96529912 -LLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/sha512/21f3008287015ef9d3bbbb76f6b7a320a6a4ec96ba49a126cee97648e6ce48f4dbd4df46f05c551187f3f681ed622aa2392b7c08ac060deea27f7f74ddb2d0cf -LLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/ee9b9db47c5745d12620c6e52e7fcc6a -LLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/e15d831588352e6404ea766852d9479dc0d5b78f88eb4108694c4fed8b123a17cd9f4211cef31ff45f4f18274622b43f54c5928c17eddfb2f195ecd59646f5bf -LLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/c9e22ebe1f7c7e046d142b699b0649d8 -LLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/72e59f38647daafa323f55f6259c9091b39df90b6736f09244e48f2cef8230b03eae689aa8a83c2f0031a9225bafa33bccb5f1badf8fb71d5a4d22efd6de9410 -LLVM_assert.v13.0.1+0.x86_64-apple-darwin.tar.gz/md5/9c5db337206c28fb567e96a0b2f18533 -LLVM_assert.v13.0.1+0.x86_64-apple-darwin.tar.gz/sha512/cc67489ec1c086640c9969eca1d8a0868840cff375775d1c60fdcfbbb84714d960549a5ec314077dec9409eb5fab5bdaecd9e6f4605c7c654a0b52f7b791ffeb -LLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/md5/a188fad8f09c3080618b6861476b9252 -LLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/2c5f95a1386b5a7f122e2af6d754173512eef72b637c9e3d1250b1bd1b1ad993a9cdadc9e71947c15e09cea308b1f30a84a2ff937fad3693b8b3c84145deeec9 -LLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/md5/d27c6edc49622f79d61face403301f13 -LLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/9b778434293bc2da965ecfa314dca1190677372a61553dc25bc6146ae1dcf553b3b71f473df9c1ff661f17fd56e75ff6715233859a5de1a91e2d1663abaaa71a -LLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/md5/5c6f3e570a3c3d6af0ebcaed3139c27d -LLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/sha512/1754a7dcf4a4fb9f88e9d5e451b5185ca5d72cf51a6675abe87cd059df1cd8b10388a3f90335e2a5f12864aa3baa7504299b90924439609e66eed24dc60c0965 -LLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/md5/8fc7c0e358d2c98bce2dfce7f3c2f507 -LLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/sha512/81f7032f5e7ed45e3d84619c18b4f588a570a3cb36f8ce9792fd41a9442ac73cccb64b4243128a07445f6b412b20048aef98a6501efdd9b526ea0e6a1c803f57 -LLVM_assert.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/f8c750975059dfed1633735f9dbecdf6 -LLVM_assert.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/d01efc6da3de4172aa4c085a6c90d8410ca33d1dc470f1b908b5836a7873c68963fa2fcfbbe24a4a7c6ad016f869084d430e113e71e6c94a8078c46a860b3f80 -LLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/70e2d1e2e84e7f8b19be1f518949d753 -LLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/df5caf19b914f194266dd27d05218bbf11c5d0bfc2cdc589391bb40ebacf7384f9dc691a9d882dec873c8db594c1b8c158e80c1cec60965daacbf42b6486add2 -LLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/f5c5d3f2a55d6c5bf89fd9bfe1166969 -LLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/f97aa158391b35f4f62ba7bc2398382f16f33161384478ddb10c5d64d24ee4d64c6ce9439fa05a997521f2f1d391f8a13f4d5a8b29d14eb22c7bca121d4a10c8 -libLLVM.v13.0.1+0.aarch64-apple-darwin.tar.gz/md5/90c59343fc5a9ad5ffd6258467e6603c -libLLVM.v13.0.1+0.aarch64-apple-darwin.tar.gz/sha512/97a49af9f0e68f76a10e13813900c2ad0d4575ed31ee703ce86bc19490f6dcc282d47b5b641499fff0b949f5330e1e0e58559f84987e9230b1c5f3f33a4caf7b -libLLVM.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/md5/ab3c2b357634a2660820012df34414f5 -libLLVM.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/6038edbe7aa305dd35660592dd37fe0ad207e074126766623573be8d7b3b8a06056a626b6da210957264e74192e40bdfc0f396dc9961757dfe6dc8d85a0ad0bc -libLLVM.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/md5/3f1572194c43db046610d4043b7eadaf -libLLVM.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/d8be84d5627aa37d65bd81c2c3e0248eb053cc88ce13c38189f53e785d1df7858669045271cea40f1ea6b0516a99b8d4e01d747fe23384c4b39e69c8e509b32e -libLLVM.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/md5/bb96b1a1ade79e3970759b137d83f350 -libLLVM.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/sha512/80f586b763a32ed2efeec2b30c931477fea6f707388180dddbf9147129ab8e3a765ae921642fcc0b75319a5de5af80b358926604d16ab5b162453faa73521db2 -libLLVM.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/md5/7bbc79416781ae9de6983879ba7b6566 -libLLVM.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/sha512/db1f5ac2d3e0a44f69a19284fe91b4d06ec438f295db7564160257e10c0de010ba7d2f346277060ec93126ccf9cd2194a87a73a7ddc4141f9dfc0a6a16fd1ae0 -libLLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/cd2cedf55992338a3a72d65fd317a6f2 -libLLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/979069f43f8215adc0c4d527e7341e3cb42faa287b697d4fae781bb9f321c513fcada965796033d01ffd2b8169d8e4936bff6c953a860f758f5eceaad46c8162 -libLLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/5ca3a104123a63acbc05aa5c9a372db9 -libLLVM.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/8fd77092ea76499efd78898f1179e6c37a08c6c161558986459491863344edf6a7baac7c4c8cca45c8d82269ba073b8fecc259e5bfde99f2abd5c56e87344502 -libLLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/4e56e434d66a5bdb3e5a34a99972270c -libLLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/41f32d057c2be5f771be3ae96c4642401285a1024ce4aabf8ae3255b4557635adec1485c4afa5d57f672c1b5de57cb723f488361e54eedf65a8a43161552d5c9 -libLLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/037399603a44f4ffd2ff98e6b9456236 -libLLVM.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/0e01a8b286f99b98382b35905653c573776c9858465cf21d70e0d5842871aac27fd1b3da759644894e0bdc29351891edff159246cbc523e7ff0a8bcec67e852e -libLLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/60e8fbacfa5c23f90ddfc4b13917c9f9 -libLLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/7125b3dbeeadb0513ea12bf8bc04f44de98da11a60dd1a1886fd5210416408cc6484ef814f5176e19338e7ba7c8a4a8aef085ebd00f2853056e549d2c6bff55a -libLLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/3decd9bef6de6b3e5a306fee9f6af2a9 -libLLVM.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/622a60f4f256a802aa9413aed830f57546f28ef7c5a4ff09c3c66736ed958a1b8fa0169de002de26ddef3ce1151fc1352235668f4da51640615339e6d7bb271a -libLLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/5c8370e3462987d15d0edc21c6e8af9c -libLLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/eb961730e622074e0f2c05b7729a33d088cf084d2162e8a428d3f763d39b782bc5d341a60823d1b3f4fee9a03a995c0ff8251e2cfcd0fe13f8e09b60c3fe231d -libLLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/6e659916b90b66cec5fb1f1d424eb177 -libLLVM.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/2489c0d76d46a10479eb2197324dae1556f330848f8efbcd545e155d871652ea0692fae2063665f3bfe02ab165567ae5d7dbeabf287fd38e180141ed9714f29f -libLLVM.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/md5/569dbeb437cb438636244ffa0248f2f9 -libLLVM.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/sha512/6dc44b2458dcbd59d695f20d4786a39a92d7affd2cfd8e25536f0fcf46489930c7315887e2f611d0b9f27ac04ea1bfc1ffc9b770dcb8328cfcccc8f419705466 -libLLVM.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/md5/2e9e078ca524ecf96a801f3361e47798 -libLLVM.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/sha512/5833103547bea7614447ad27e7bfae7f7fa4e3bf6bfe49301d57974f50de26c8c43747aff60504cf923958b53189030b4016b8d381244f92be8a3cde82147a42 -libLLVM.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/md5/babec2df18c459f4bd068c711e4f3fcf -libLLVM.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/sha512/c3660a02a8215a0becb17d6e2ec2317e65d3c312172048ab6d867de11b3c618f4d31e8f215b349a049130fcfbe7b59f018e12c89138a1965704a84a403b3995c -libLLVM.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/md5/3aa2b9f877a34a8ba83fd03f9aff59ea -libLLVM.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/sha512/1e02a817fef96628ee4ab2ed62bcd49156d7df5a61463420e0e8d9c208d242994d09d6999d6ff223b46de516b8b3bc3448d2807dee422128d729f44594dbaf91 -libLLVM.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/md5/767865e3ed6fdc200ac9b6ae569d7fc4 -libLLVM.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/sha512/034904561e6715b8ee1b5d9f5d3669f3765cec05357e21de0e1b875346b8dfc199e545d87747f1676cf16329f4122b4e574eaf4bb91573b9893ff72dc7a0b33b -libLLVM.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/md5/be8fcb1eceeb0b0b1064bfd1459c440c -libLLVM.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/sha512/1b8011e432fd570a34a90bb449082ca086a311159b3b699a9a176e9f7dfa916bfb58e06f82a4f1e40c7896d1781acfed40eff77d447070186f193f2605a2521a -libLLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/bd14e02f94880856d9cbdc531bbc2d9a -libLLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/4fd86b2562e96ccf8327c4791be34a1c03be7f96382626201076104e3cf04226e76fcb628f36e977487f8c4a717f4e25626713f8e2967b42a335a4cfa8836909 -libLLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/2da035de66d4e2af430b21c5ff04c8f9 -libLLVM.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/d86ed30cc3e3a42058436059f8aaa74b910ebe8ed8df65add637214e21118173f7863e834c7fc87f71b9d7014643fc129363f97e5e8e4e9694da6b31e9e21970 -libLLVM.v13.0.1+0.x86_64-apple-darwin.tar.gz/md5/513383b4044ac84dcde32afee478c1a7 -libLLVM.v13.0.1+0.x86_64-apple-darwin.tar.gz/sha512/552b09934c77bc5d44057c6a47fc5af413a5ce636a6f79308a8a304a4f5ef6d9714147d7babb9c0fe207d7526086834583cd77cb2ed3cdbce07978d4e1f2be3a -libLLVM.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/md5/732f0349aa40bb2b81ea78bfe0c41f96 -libLLVM.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/8ae7d1c7b38dee47a9e8758a11c27da897cac1ba0766a300018b72dd5263299bce61fd93ed58f95b6d3afcb70be091503d78613a346e6e1bfda2261af35da895 -libLLVM.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/md5/07ef28642d4d8e1fb0557937f55e2106 -libLLVM.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/aeae745dccdc86d3af6c2332d26f152683f2b9bcca4942de880096e6d4e55457bb5bf75d51095db57dbf44e222876bd88292d9aeb06f5037c4d2752593a30c79 -libLLVM.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/md5/db6f67a674141e999fc113a3a016fcac -libLLVM.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/sha512/f64558e48b04f36386c1a908ed08d8975f385e4449a98b3fad3068fab760956a15c77af0f1bfe9443781779b3856c87aa537062abe608b2b33eea8a26f8a0d72 -libLLVM.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/md5/d0ab18c49c5bac39ba7e42f034d73ed7 -libLLVM.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/sha512/8b012d61d7040a14feffc81346fae3034905f45f04ecf67ad63f589097a2f66f15bce573627145a4c20e9b96fb742773c31ae628c5ff9ac0b80b212d4180973d -libLLVM.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/ea4034d5e3168a88b2ec93ce19ef4368 -libLLVM.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/c88d998522b35159589dd153fbdd4d0fe318af5b7bd21ccb76993315e7cb88237b86c0b1d3926112b82de6c1a01a568db3e4e7ab782b377169a9b4ce16362859 -libLLVM.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/3abb0ab78813dde21bdac01c2abe0f56 -libLLVM.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/f0e9f8f5b51bd88a3bc44a31cfd17ee5fee5693e58335e15e75a02edb633eccb20b4b550272f62fb94accf0601c0ffeda90b651386d5f4533f53efcaa737b62a -libLLVM.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/6cd7c931f078cd4e7fdaa7100f849fdc -libLLVM.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/5d1627125bc08887a6115d90e9fc82b489e1181508b949dae5d4bae556cae6de21d2db7a70f72f28af79db9b3e24e410f36edf7e1b8e6bbeb58f88c579739f12 -libLLVM_assert.v13.0.1+0.aarch64-apple-darwin.tar.gz/md5/106b3e9243647066dea672db53433830 -libLLVM_assert.v13.0.1+0.aarch64-apple-darwin.tar.gz/sha512/443fcf037bf415e8fc80ba54549d7562cdcff4a8b9f3904f7f9340dbca2c2f696812205d65dcd243a0272858e33ff5990eac25b67dfafd4bb43432cbe7894c8e -libLLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/md5/96a08126d18c388cbf465823180e50d0 -libLLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx03.tar.gz/sha512/764cd65e04e3366eaa8b37464e446494d7da51fefbdb036ce1694d8e2ac690464a12c4f02e8e0001f513fd96df3387bf947d786309faa3c2ca105f2a962cc703 -libLLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/md5/f0cd12f061e008b0fffc8f5a0e59f694 -libLLVM_assert.v13.0.1+0.aarch64-linux-gnu-cxx11.tar.gz/sha512/e16a9ed2da79448297f89a0e1d85f9c482aa9f181b5b1e10b00f8f8411f46fde85b0ff6c1b5fded0c1ca05f22d578b9f1fc3b57d2f2e51adbfbabf0bc36eeca2 -libLLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/md5/2cb2998d7da32b8b0ca5086c1b1c65fb -libLLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx03.tar.gz/sha512/cec31970c67541ff979bd94780f5369c72a63576eeaa2803598ad453e72c273f238eff492410b38c372a616e992ab02b229232e5e23eba0d15a0a61a23f179ff -libLLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/md5/3541fd14098d5d673a75b39d1171842a -libLLVM_assert.v13.0.1+0.aarch64-linux-musl-cxx11.tar.gz/sha512/6320d5e3b8b3b4839e90ae66c0d5639816de9bb74e463125ad05566ca394733bc83fea9a4bc49366a0ee6e31c83acbd5408d388cfd957b6918b4986d639f104c -libLLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/md5/11b71aa8a64a8580dd297a72c6b44303 -libLLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx03.tar.gz/sha512/4468015d50d8cae071b7abcae525e2e2c05eb6cbaa138ab59c9c2092b4cd3c9616a0b22a222accb0c9d0564e975587e638afa892d1cd480a2f5db7295bf510ea -libLLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/md5/106a99c91928b5dcf7f214bf9f0a0b9f -libLLVM_assert.v13.0.1+0.armv6l-linux-gnueabihf-cxx11.tar.gz/sha512/94da8219ad4cf7c1155bea4878d6b6306487e9bcd7e3cd4a5f88f0106dd60fe8a5b89edf62f6db6fafdaca728b0195bc0032c3a404119930c7b5e0c7443d20c9 -libLLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/md5/f9a037108728810c78636e9ca5bdfd7f -libLLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx03.tar.gz/sha512/2d04f17e72f505ad908433d3ee9781480bb90ea78a405c892c02f4af899a0bcaec9b8c6e8e1554aaf4241912532db59cb1719edd328edf6a75f65393a1db32b6 -libLLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/md5/6e0d147ccab5f63b61b330d6e4e261f2 -libLLVM_assert.v13.0.1+0.armv6l-linux-musleabihf-cxx11.tar.gz/sha512/43aece34e5be174628e7e645d163a442e379f10bca6988f768d3f45e2f449b0262e3a789cb71dde5431c7fea4305bffed591009c467a902bd5e079c9e0797035 -libLLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/md5/ffff6ccd75cb9e9cc59e0fef9133efd7 -libLLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx03.tar.gz/sha512/8d7201223badb90ac932e31f63b46af7bf004af32f1316e2552d7646ebd65fc69bf3d267ede2502f743f0d41e567d1448a1550c942d223e218678bbaba3d39da -libLLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/md5/ec045bb81ffd9d9a4fa34990018e4c8d -libLLVM_assert.v13.0.1+0.armv7l-linux-gnueabihf-cxx11.tar.gz/sha512/bcdfb4bca9088bb6d02755fb50e6531a4c7414123810e75d13ed1f71a85aef709a8164110e5d21769578ff6a43b659476bcf274d3df721f9c49183f7e3683169 -libLLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/md5/92d538e671e3bce0619181499198d6bf -libLLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx03.tar.gz/sha512/8ef2004e7cf30327ea6ab91cf89e5bde22a378439870811969b79199ca9ddfa5825b92241cfc8e606b893c17da2a6dda665ed6dc09c34ccb95e8e3a843bcf059 -libLLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/md5/988828fe05b1564f43218978438b6395 -libLLVM_assert.v13.0.1+0.armv7l-linux-musleabihf-cxx11.tar.gz/sha512/97aa19516ada176a689118f27c6be1423316bc4f047812e1b8c0a4037b227fa20b0398e63ff764de0b75174d6fc41c656854de201121845ea66917551003526f -libLLVM_assert.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/md5/38434f9d60b437c3ca3216696f194e8f -libLLVM_assert.v13.0.1+0.i686-linux-gnu-cxx03.tar.gz/sha512/dcc7f39f46268883a6890d70bcab0efb5c9b752ed724d0a1ec0379da0d090599db47d82d0ddd9e8acae0a351df4caee2cd0f7283e84439b702788e2d4f3a9588 -libLLVM_assert.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/md5/7fbe5817d732c50a59736d4c048effd5 -libLLVM_assert.v13.0.1+0.i686-linux-gnu-cxx11.tar.gz/sha512/aeb7090365053c653273e0d592485c7bfba1e63f758ecf57545261540ee045df9fb2b58b91658cd087e78d15f3fb8ecfd280b64ab8af8f04dd7589085d8e1ddb -libLLVM_assert.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/md5/7cbb0d59fec17b98b633f47b7eeb80e6 -libLLVM_assert.v13.0.1+0.i686-linux-musl-cxx03.tar.gz/sha512/2579ebd9b9b50fdbf9f3b38c0c2ca22312bdf6712a0d3c6c51058691107cb05dba9e5f4d5b27acd165f74258eb493d1680a320ed4c821943efcd2f600f68e44f -libLLVM_assert.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/md5/354dc055ea15b8e4c866fbe439b3ec83 -libLLVM_assert.v13.0.1+0.i686-linux-musl-cxx11.tar.gz/sha512/2ef407435ad00d605c28b255eafc0b748d26a868e58a4508431a427b4aedb5c4182268d95dafda000f3ee190ce0b2d32a488641a627834b6b3ce22c171b039bc -libLLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/md5/27f88f260b1175132be84d00834ec825 -libLLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx03.tar.gz/sha512/b904c91bca86286db662b4889dd4815a87482aeb20c49ac0e59f6adda4524a8f6385277f9aee24197aa1539096baa7445ff3caa6110432b0861966872234868c -libLLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/md5/0e1e5267c63088088065a69846fac5f3 -libLLVM_assert.v13.0.1+0.i686-w64-mingw32-cxx11.tar.gz/sha512/ecce393ce899991f7eec3ca07887306bb002bd54270f0ccf3f8e93318024b9ea8024c8151e639c71d719c956bfbd3ed5c38c0b52f1cec40ea893d2da7b6172d3 -libLLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/md5/090a448043257587a7b9001162b0d982 -libLLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx03.tar.gz/sha512/67e5bdaa89ad657f98bbe9012b06e89a6ee30306afcd09ab46e518d7b552bcef47fc37cf166259bffdf98cfa4d7b1cd7e04851de1fe3a16507f7b354067c1393 -libLLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/md5/5eaa7afa170aa19b9f31183c47d82354 -libLLVM_assert.v13.0.1+0.powerpc64le-linux-gnu-cxx11.tar.gz/sha512/147f5a6ab233b42277e42ebab197616a6a0b0a265128fbd619b20bdf1b2af6e0ad524c990e31a5836dcdb2c0c500657021f974d91de7e8b02a761ffd29bec624 -libLLVM_assert.v13.0.1+0.x86_64-apple-darwin.tar.gz/md5/90f43cb235a3525ade4e250be1a0a7f6 -libLLVM_assert.v13.0.1+0.x86_64-apple-darwin.tar.gz/sha512/9ea0b79a16b4697276915c7dac9dc4a426213f48e4c1e1db2705c5810aa3b17ecbd9dde2ca562b472be65f7063d85e239d4948b9743407c095c910e97ae24bf6 -libLLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/md5/12d3dde26ccf6aa21fc13a2dd9aa3768 -libLLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx03.tar.gz/sha512/b8b362345fb550b8af61d851d9918413ff23f1f7b78b7817f103384af110dca3383d4c8067a56748cb97fca7d1f75957b0dd2ce323d61a56cb9a266a378361b9 -libLLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/md5/d1673dae2652f131c6ebee2ba257f629 -libLLVM_assert.v13.0.1+0.x86_64-linux-gnu-cxx11.tar.gz/sha512/47a7f634256a3df1f7ff56875ce969a550b217cfc897e9796b60fc4c45d7c4b1a22ba56a33cb7932ec40c0e987d407678234716447ef51123c5060c713a61948 -libLLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/md5/6454e1cf23e77ced847cd623995a234c -libLLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx03.tar.gz/sha512/30ce182636afcdccf265ffec468c9954434d3f0a135878cb55698799cb829c138e828a28b0493d8226d80a36d00250be0c0dae083efcd63b0e939f5fb75b1f6e -libLLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/md5/cd24ac0e5a37b5db288b265a90f5fe9f -libLLVM_assert.v13.0.1+0.x86_64-linux-musl-cxx11.tar.gz/sha512/d90aa1a0e4edb57e2a940d63ae28e198c1e515e7892008f1b04289828be466662aa38596c02884dd787798c04d00ff6314f884be5a859287f840d18f79ac8c3c -libLLVM_assert.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/md5/7164700b24a94828b17abf8aa2e44477 -libLLVM_assert.v13.0.1+0.x86_64-unknown-freebsd.tar.gz/sha512/5ba54ec75cde0df60253efe694963b7a2eadff5f23028b2cb8ba612530acfc148cfe738d2d2e65bf9dcc419aa9998bd8544e7852167300ffdcebecfd0ac6821e -libLLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/md5/a17f42d502120079943a1695128ae7f8 -libLLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx03.tar.gz/sha512/e4f6a370c96c29ba6bc5e979fd3660becdcb95d5c26299e4f7f31d1ca089d4acf6915371e1452dc538551aed2db4beaa2903dddb35e72a131f4a5262cd266334 -libLLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/md5/a458b0572d77d3d79b66a53e94a6436c -libLLVM_assert.v13.0.1+0.x86_64-w64-mingw32-cxx11.tar.gz/sha512/43b6ab2becd9b3179f91f2f856854d4795e53c4078dda26607e5b6a8dfde37cdc28f9fec6c0ca9e0d0d8de5f2304d5775d5c6b7a03c0f6feb2b93e43053997c4 -llvm-julia-13.0.1-0.tar.gz/md5/34edc9f707d86fe8c5758b0ae8c35206 -llvm-julia-13.0.1-0.tar.gz/sha512/0d55c1bf3c581551faa077aab7046d1f020e8775ed16f1fbd8ccee65bc8f43173504f5ce1215227fa5e565f2804f8772e2cda039bc333bb23677067a4a3f9f87 +libLLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/c1bfb47e9a53cc612fe98505788e1838 +libLLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/f16c9f1faa3e959d486fbb109add976c2a2018597a0b053ac3168abad074ff9c2b23874f8969f0a71c6551c8092082938bcc35ad846913a0a9965dd27d6dc876 +libLLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/md5/cbe0859ffa50e2de82b8fe86c2540f6f +libLLVM.v15.0.7+5.aarch64-apple-darwin-llvm_version+15.tar.gz/sha512/e864e7d62eb3b62066fe14210c43b79dfab704f04381ba29fcfc2a2e2b839e8db2ad3f61bb257b64cb6a546cc45e95195089e8b734425d9d4afa3168211f6762 +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/352f8869f53096a566b387b885a74918 +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/67dc69f8b327791ab77d4082208653ca74ce2cc750d9cba833cadf4d0f311dba73dbc951d0ce088a66b06321f7addda34bd5705a6c38d4d901b5813b2d1bd37b +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/220916b081fea2190e372df195daf13f +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/28bc05009335d61bfec33f24c89e67412f13760de72ea9acf7a12b2abf6d89cc3f3067fddb4ce598031b054b33efcf6773b4057d5adad830ab15c88fdbe56955 +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/2774e9f2922e087d06e0976076d3ecf3 +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/2aacbce77120fa9d24fd4026220e610b70c08b36175dee70f718f4d023b0ced9f8ae9dd2d58e35b61db7ca77ae337ed6f2da6a0de70296b4160a3f8e99ecdf67 +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/63801b5fa51c2e75abd4b46f4ab1046c +libLLVM.v15.0.7+5.aarch64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/eec9642a9c000d1aa3d298382a5b7c66caa81714665c7a405b416818f2e7a0cf1bedb81bc2a650452424391fe57061c33c2559abfc55bbac9b58e19d82131d5d +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/b3b3975a9a00b0292b9ba4b7fdf5e757 +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/c886fff05f76053682a906dd94c6674f072206f37781b1025ec8a977eb952e0aeefcf20d76a3411e54782a6425667ee3a373f0a48d5a486fd4f37c02b0ecef78 +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/88cf748f1a8086f949cb6217fcdd40b7 +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/4e3d3cef71062b002406afb923f3d16508206662c3835242bf522cc7c881ea236695cee6add1b1f85a0b2708510dab2b59eafe004e67ee1d87a5970602a9d942 +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/dae6e06bf26505fff786d0187cc5f90c +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/ed76e52223f84dd8c1ad7190341b167928493c2c617968aa17266c274527d18348865d9289cb82dd1c0d12240220750ac31e6c1354ddd9bc5ec2e226f360ba87 +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/1bdae6507ca26b09a81c3b5b89f17908 +libLLVM.v15.0.7+5.aarch64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/b2704c0ef478467eb0fa21c7b436d6efc9602e8723bcf194dfcf6b3ac33d316b79de66c0c1c291e92f45f5bb09b6ab579a45782fa1ba3c03192177aaad6c29e1 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/8906c5b197baec7fc795256b92ca0a75 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/b79ec5ef4e59b0016784d31e51a94c9b292d19c36d66eadcfb3be6579244048b2650206836b4e017a63d84d8a0c72cd487f22ea08fd92f5b5ab4cb46d218e1a0 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/bd81f6f43b54364bef1e6486c17e3dea +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/892e4478e672fed55d63bfbf20a959b488e1cfafa332e2f1743cb519594526b5e0f2167b67636714dec6f43c76dcc0eb0bb2775eb43e4d898e63a0d1e78e9c94 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/6437ac1aa63c9b83c72238f4b0eaca00 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/f5e2bdb0af587e5cd55a5a2f16bf551c0e0fbadd2d9232fd5d3b2b38cdfaa80920d25903af5d79cb52a45a703a5bc07e550ca07163628cd1a79d3b3dda0d05d1 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/5616fc6e683ab133ed751d60164ca894 +libLLVM.v15.0.7+5.armv6l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/40944ea809c3f4000038b7b26e6297a5ce9d2710995c57b4e0751e74dcbeed9c00b1d89d0c75bf0f0d9094fd4811f5c5ca0cc5b83f54cbe20c1b2db85de44d72 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/dcdb815f425a6ec2aca7f29f601a73b5 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/3619419dbc81807db63e5c7bd7b237a6355ec60d2aada9bf26c1d38f10b4cb87a3cb3fc9a81e7f695ed7a195d2c3c214cd9bf96d3ccca68422898be323797fb1 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/ab2250406d3a69d68755b77b79b61f53 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/f5eaf02c7d19689a9cff2410269daccc00a075abde9287b025de3aff1d5b539b43001d1f2120f88c4c149af27eaf0caedb2942ae029550cc822e6af103b32960 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/77576af5b13b2916dae4e7e24760afec +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/1b3708202ccebd47aecca5a7c6396799ef14c4235b0904d23d6b6b4fdd91fb6b13a1627f65211ee0283a15d96b8a68cfc962d7aa2ddf75c08f2670a767c6cfa8 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/81277b7fde4cf08293f8ca956417fe05 +libLLVM.v15.0.7+5.armv6l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/72caccf9933e1790bdb0b6f6dc1ec5da6a84a5fc06336e29f2928142f3182261afd39477be913427d65655c40ddbda5ec5042c360bc49383e88c871db19b018b +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/d326fe9ccfbbf179571fdcd684bb7b80 +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/a34550dcbb416f79648a5c4306775f1aca041c4e8e3b269e94f960ec0925d5b7cca0ed1934b2b63b9f4437d304d658adc6c0d3e0169c629d50d7c0b5051dbb04 +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/md5/5ced197907e87c470e5cc1ab08a7eedf +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx03-llvm_version+15.tar.gz/sha512/b57810b718bbfb1ec48415ec3e727388bb647fa3768ee191d81fbb16248edbde9332086d445ff57ad53e9d62fb9c8fb1f8be176649350f5eb57c744404c63cb9 +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/4d5133f794e0b53d563ccfc10ca42e98 +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/9fc7bbb8dee022304c4aedb930318db04345987bb7ec9b78c3d488a5616680738ca2b9a9087f60b7d6cc68650234295d18c6cee4a45d1956d2240791993fe45a +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/md5/e5c8eae08bd2defe76e0985687d6f057 +libLLVM.v15.0.7+5.armv7l-linux-gnueabihf-cxx11-llvm_version+15.tar.gz/sha512/d632971cd93131b90d5a26fdcd8a262f2042a2dd59a09c82a8523558f2b292f9a3f285b0a6276f0e6b255f34d855736c0bfb9f426488c5929f2abf6c0b921b73 +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/md5/f0fb4b9b0257e0ead2e5aeafebb64214 +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.asserts.tar.gz/sha512/1993c7d6ceb7efd93f2eb21379c046073b7d9d2460d6eab5aca26cae94bcbe07658780a2f6382a052e4d64813614078b5e582a933a0bc9a5d64d8388df98ce69 +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/md5/e236983a6c801d33ead6f60140cf1ddd +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx03-llvm_version+15.tar.gz/sha512/c6b44cd0d9139e0b1d47c8b17e9035099a6b360f873a2fc5c6e84c1c97dd455510f4f4262c746b47910703158fe0ceba0d19b8e6a61117d9723346f4c3e89004 +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/md5/c3ad2f3774b9b7651078fa3b2dfbe7ff +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.asserts.tar.gz/sha512/009561d4fecd65e35960843670048b79e70495c2cfc80a7c80614f253bea7ca46d8278d338bdf7719229fa7eb9f02299bf8bc39ace683b862ad005cfebcca0e7 +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/md5/6f8d226436a2822eb7e0f25d1073925c +libLLVM.v15.0.7+5.armv7l-linux-musleabihf-cxx11-llvm_version+15.tar.gz/sha512/b63a32b1eb4a8af210f6a9511bcc4c90ad39091a6b2c50431253f4fe5e1ab304b68f79e71fe55e173449ebc96a3395dd1ee55a9a8cdd289950b609a5bec8e722 +libLLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/a618c88b200fa25434e969a168b93a15 +libLLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/940d6b61162bdd2d9ab5c264c9ba71789638fec646e62b9204e9304c8244d10c8a5ea3603c84bff76c5471e7f3184a21e4d1760bfe05deec80c8126a7207db04 +libLLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/8a4e4c69ff51c941244d0765947dfaba +libLLVM.v15.0.7+5.i686-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/287e59ff6e8e81e1650796da7a01be568b9ef024eef0505eaa34cdaf4cfd8d798596e9396e48fafa39acab5e70c3a41129917e8ec7d625f9acb896bc4e9e7b5e +libLLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/3f131f5c0e11db8de1e0268708ff17c4 +libLLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/04d1371a970694c234880ccd826f6a75615793867a3ba1fdce683a844cac3c9d33a58d34163bf2141624dde71f3af0e3582effbfce679ad2134894f86ac3ce98 +libLLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/8636def624785ea4b99d12c0d65bc0c3 +libLLVM.v15.0.7+5.i686-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/b8ae5cc249664d32a8dbc26a2bf180a782f51ba69126d099bb239ee94afdca7b8492a7458971cc91aef0ca55a1ca38d3bf3c8716234ded81319a2ad5ac082732 +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/bedb9f6540966fc382de1a4544ce8c9c +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/527ad792c220e491fcfb46de81b9b15cf4f6a1d50cfe4435296e0f94ae4d8e53165b6f634f85e95a8c7369a1e7b3788d1683fa77b843f56dfb1264313f80dae1 +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/62051888207139e47c9a0694cf4de5c6 +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/034e4e272d09ae8f573d3a7e591f93dc551651c7a32e2b8923fcd7fcf36be5bb491530f4673cb9bf39a54c1527cc3e3ecab64c79e3fd7075209fd81f32f7f4f9 +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/8543a076a97e6c72e7c514021ca5f121 +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/fc11ac25945adee135ebc523fe3908bcd5c5a7aa4c2a405e3dba61e0fb59502e5aef3cf4982502da7f7ee1974bcee8354ac675e0e0182f9319ea20c299813a1f +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/6247a9f59f87a2b923aacdc0a7c128ca +libLLVM.v15.0.7+5.i686-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/f13adadc653d2f8442c8ee4ecca9563d6cad5f958abf2893d8a3eda331d9ed8c33cd4a52bb721be811dec66b3b5566f038bfebbcfea620bf0094c305cd3aef0f +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/873155e60b133d597cf8c40169c5745e +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/a000e1fe4698d5c19bf85b048bcf76cdffea191ee281b44ffbd83230de5dd93c9efb564a51da082df070f2358d6dce423bf0d6023836217c5b34d563844d977e +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/17467f361317ad56762b7e455d869724 +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/62a8d601c8db178cbdaa57a23a26cd65a8f3855be40ba2966b445afc9ee223db2ed6c2fc344ea98ff129d8917c14f34ed93158633521780d52763fc4a4f2a799 +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/2c094ecef656dc6c9317038b0c5a47cc +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/af5972750be3de00df275a0f03c9c8a3b487a040f9bd29356457bc18661ffba9b3aa909849b24ae1c518fd2975a9b687c33353ba927f8713796a6c8eefa6e509 +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/c10497e3f473e80e309d4c6102fc194d +libLLVM.v15.0.7+5.powerpc64le-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/2349230301cbebe8c7a7d7054bb4e60d991e1798dbb8bc6b8cf73350738e7058a9eb3c1067ce7d3ece1780e360080d00dd4777359742aff924d2db5c463f2a8b +libLLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/md5/15c99e56a9e8ed664deb2d6aedeb7ea0 +libLLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.asserts.tar.gz/sha512/c7d3d6d33f0fc0cad0394c02662bed2dd7d5389a6aa21027d7ebee124c3c9f5910316c44bd4485f1d45c6bb9fe12775c697a176602809bb52c8d3cfadf4f2737 +libLLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/md5/b8d748a34a381d085c962549612a212f +libLLVM.v15.0.7+5.x86_64-apple-darwin-llvm_version+15.tar.gz/sha512/02afa1db42ff68a3ea0443ab539a7c613e5acb6170f7849cce1d36969ddad36e7546427bc55cd289df46a5fd8e83477b70941b8fd9aba0717dd861c84473da49 +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/md5/12f825c1c1586a8f7c9ce56e243b6bbf +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.asserts.tar.gz/sha512/f6c9cb33f129f1ff95800c0c88152d27e6de3fd78e01b29d75a80df9fdd8d95de70003dee0df3868215009cf434006223b488c64d6eb240f1e18799f529e980d +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/md5/19d05d46cd97714abd23b668693afe4e +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx03-llvm_version+15.tar.gz/sha512/deb786016835fb34967e474235b1ca9c2e9f0258c88394979c41654fc4487ef83ac622f1e049aed5d83da8738b8f1079b3dbc67ca788f6c68b432d7007b850e7 +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/md5/0fee1aea27ac30304228af1f398dbf14 +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.asserts.tar.gz/sha512/e14eb6fad8ef734efd5dae610cc1906901b389c7557853e7fad27c4cbf6c06614996bdd5840ee3640b9fcd8a870ea058c212bc978b6b869f4594cd8b06b42ca7 +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/md5/dc14c7faeadb0c42f4e9cffcae8c7684 +libLLVM.v15.0.7+5.x86_64-linux-gnu-cxx11-llvm_version+15.tar.gz/sha512/10ef07d1e1fe3bcf8bc52da169156ad10de7b3bd54f16bf1d694bd243bc4c86b4244643f1a71fec94b024ffa2e605141eec9b10e6e65dce5d96aee2b454fdb6a +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/md5/ee90487acb75a33b77f24fdb075402f9 +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.asserts.tar.gz/sha512/6bc8605021dbb23aa71636318468a1f81f8dbf7308d637f551132700634fea208d24608c4afb28a9609a7a866302597f684d204f718fd8cae10a616abc1b7b0a +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/md5/2c96c511ef55496a1044f63d4fdb096e +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx03-llvm_version+15.tar.gz/sha512/564202d6cd321b8b058124c4623bfa7d7310a5020140f194bfecd44a25490ff9590e661bbb838b1af4f7e40fc15f88363a1510d8f7a2138f8ccc52ad76700506 +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/md5/555ea3150d5eeeec54b1d463380447cf +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.asserts.tar.gz/sha512/9da05a39e8d4d9cffffe85bc2717e105a47137682ede9cbbd2f216065ebdbb6624b68a2e120a1b87247838276cd8a501c83aec63c91673229bde8d207f651f4c +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/md5/a1f6daa0703ddcbc87b8f9d17c9ad54a +libLLVM.v15.0.7+5.x86_64-linux-musl-cxx11-llvm_version+15.tar.gz/sha512/e803ba34861b600b350bc99484adb619bd75a82162633e8d80f1456a908d42d95842f194a6752fa43e683c26356592fb94b64f7823b64edc922aca154d970288 +libLLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/md5/364b73f29c1df14d8b942183cb113dd2 +libLLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.asserts.tar.gz/sha512/c4966e3607314acbace4b31dc095b81770ac3414ac1bddb43084443191b92b2b96f6702177dec76b70be12f7a3af4797c9692cf872ea7eaf60569dc7fdd92ee4 +libLLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/md5/d4aea085c08951e0facaa553b3c22a91 +libLLVM.v15.0.7+5.x86_64-unknown-freebsd-llvm_version+15.tar.gz/sha512/cc5cc36d50a342b5692144905ae52676fe9ff19054245152e3fff02276250604009881325cb5ef063f274b51cb2b45dcc88db0a929f6244d81cad1f241bd0c64 +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/md5/5cdf36e1300bbc9b032bebe5cba7bd6a +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.asserts.tar.gz/sha512/c732ba652aaf7a5f6aa8cd2f39088d83b78d2fe3121c4e2415bdc935b0a3ccdff7f028d3ef50f0b5f7bccff54f1fb5acbf970fc28301510d09b3f3847556c613 +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/md5/c5b335f634ec9e663a7c5d54dfeb1967 +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx03-llvm_version+15.tar.gz/sha512/51c7b1ceb0e235d9d7db9727eb7744cbd8b2e51e189c58bfa6d3b65bc4b6e7a8224e8b7b57eeeefce01c7f65a4df48da97a975dec61fb000d83d23d15737728d +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/md5/822be345af871cd1d5e595b2a83bedf3 +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.asserts.tar.gz/sha512/fda0ff71c7a26e783436da214acc22842fe73df1f9d1d526955f4acd0794c3afa8722df8e4c9671b11948fd96e4c079fe525c9bf3e38b5119a79793a22baf16c +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/md5/1201b56c0dea9d1fd2a5ceb4d62f78a9 +libLLVM.v15.0.7+5.x86_64-w64-mingw32-cxx11-llvm_version+15.tar.gz/sha512/550c041f495a2d2439e6c4abcd4db6da06702d32046f6574f6a595fceea467ebf896635bc70d9c3e41c99b42404c87d98e3cd76a34b0f959d21284e3e4f15941 +llvm-julia-15.0.7-5.tar.gz/md5/1ffb5b00586262196d24dcc7baa4a4c0 +llvm-julia-15.0.7-5.tar.gz/sha512/5b5c93b4359cee649974bbdb5c3c191cff5ce5c3862e7cce00e2e35dd0627bf50e0aee454e67ea0fadd21c36065b7c1cae6e77abdd512fab70b71899d369cfac 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 723b9012bfe00..d0b43ad80ea70 100644 --- a/deps/checksums/mbedtls +++ b/deps/checksums/mbedtls @@ -1,34 +1,34 @@ -MbedTLS.v2.28.0+0.aarch64-apple-darwin.tar.gz/md5/ba33f960c7bcc3fda818c84f5e716df7 -MbedTLS.v2.28.0+0.aarch64-apple-darwin.tar.gz/sha512/3878531424317954417d09090b0a7618c6c0a6907bb04db34aef37d55a033972371455fcffca548ac03be41c0b0d1f8e51a9fe6e8f8fb4d8ef4fcbf91f15b3ea -MbedTLS.v2.28.0+0.aarch64-linux-gnu.tar.gz/md5/9e7c78fc7c39fd19dcb170d57c8c0ec6 -MbedTLS.v2.28.0+0.aarch64-linux-gnu.tar.gz/sha512/59eaeec1a772265e62fa4049e0bc8c96cd7403d954213ac6098921acf6e128b624d6bc1ba5c6062c88ecb92aa8bf9d0a06e365eee241b6516ef0bfe2b4c47188 -MbedTLS.v2.28.0+0.aarch64-linux-musl.tar.gz/md5/44f939956834d5d8130ccb3bd5962b0c -MbedTLS.v2.28.0+0.aarch64-linux-musl.tar.gz/sha512/f9797a44851222c005fd4068df6e0bcee68133c9a48e19e16d188b8a6927be56c620fec83264398d682eb5c89b7f01683e5898d3cbcb7aecf53e5ce678464db6 -MbedTLS.v2.28.0+0.armv6l-linux-gnueabihf.tar.gz/md5/fc07035dddd51e9c57e62edfc3fc5691 -MbedTLS.v2.28.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/ffb707ba7439050862654316b4388f52e8bd09bbeb7076cf6cdc924cb60c61f871c01ccfe14e1ae1e62a5733490487324ba60e8545d60902f3317039264db83b -MbedTLS.v2.28.0+0.armv6l-linux-musleabihf.tar.gz/md5/fc54575519130bd468ee4dbe23da0ea9 -MbedTLS.v2.28.0+0.armv6l-linux-musleabihf.tar.gz/sha512/d4b9e1bd8877f7d93d1b4e0d1c4c3d4e5d2af6920e39222667e689ec84cf9817988c91a826755a734a60ce05fed913e5421b8aa9980f257450da7f51c5e9342a -MbedTLS.v2.28.0+0.armv7l-linux-gnueabihf.tar.gz/md5/0753a99f4645ba7e1ceb27a03c65a107 -MbedTLS.v2.28.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/a7a65338ee6f93117d44975651d77c351f0c919a3ae2eea6e220719dd084f71617946adf04a08a82d55c22af0275d21fce3c692becf87ccf2d932c8aa32af7af -MbedTLS.v2.28.0+0.armv7l-linux-musleabihf.tar.gz/md5/ff335caa1cec22366cfa2c2bf87f61f7 -MbedTLS.v2.28.0+0.armv7l-linux-musleabihf.tar.gz/sha512/a3ff7d53b45134165347dec209bc27f48be984b4fb58ddd54286a146b837d038ab21e22033f1e0713d359c72adc0b97e979532ebaa734495eb88bfceaf3c2155 -MbedTLS.v2.28.0+0.i686-linux-gnu.tar.gz/md5/c4c9728ee9d875685765eb4c9c3bf731 -MbedTLS.v2.28.0+0.i686-linux-gnu.tar.gz/sha512/214142ee7ca3a5b447a97928ffcbe0389fbb8c1fa68de387656e5c0e4406f02411e4183fb051b2107600b222bd5279b9fd3a5aec43a9d97a9556b08c5338cb7b -MbedTLS.v2.28.0+0.i686-linux-musl.tar.gz/md5/2684f2bc8a04234ae67603150e6d0917 -MbedTLS.v2.28.0+0.i686-linux-musl.tar.gz/sha512/a533afd26893464bee62dbfa9babf6e4e1119a4be31ecb242e2ff28f5f6e3a3969057e2ce653c98c1b8d2a19e340df7a17dac8693fce270399df92cfbf3a32ca -MbedTLS.v2.28.0+0.i686-w64-mingw32.tar.gz/md5/f205fd351e94f42cd38d34d3eff6e69a -MbedTLS.v2.28.0+0.i686-w64-mingw32.tar.gz/sha512/cfdb819d3e6fa9ce3985e29ac733c2af6c988230ae49bbdc13f0fc234e82444d17ce5da4d3b6d8cc6ac45ea4a999f0ce03ac42533223c87bea066a371487ef1e -MbedTLS.v2.28.0+0.powerpc64le-linux-gnu.tar.gz/md5/41b1f61ebda30a8e8f02dcd955ae0d40 -MbedTLS.v2.28.0+0.powerpc64le-linux-gnu.tar.gz/sha512/25b62106404cb3b9be3e0f778ed953bdcf9d18cb289be823f97f7a1759012c84cfe7240fc936f2e6e858273ce2022d75ecc2554d5696cea110eda6d059362416 -MbedTLS.v2.28.0+0.x86_64-apple-darwin.tar.gz/md5/e7b286dac94bef06915930180b2d3bac -MbedTLS.v2.28.0+0.x86_64-apple-darwin.tar.gz/sha512/a2acaacb77ca6e2704144d8d99e51df49b1fc69c8751e43973e0c41219d023676d35ae05bd4ff7a3680dc0edf5438e51b67baa76f5b78947560dcc420623a3da -MbedTLS.v2.28.0+0.x86_64-linux-gnu.tar.gz/md5/39662265088efadb142fdc7255a0b7a3 -MbedTLS.v2.28.0+0.x86_64-linux-gnu.tar.gz/sha512/a3648c78bebf4c024ddf491965cb7707df887ce10dec6f9e42eb6493bc7d1220e5b23c53f5e4e73dfe94e8d8dcf35ffc6860d1992deb9b63a0c4691d4167e59f -MbedTLS.v2.28.0+0.x86_64-linux-musl.tar.gz/md5/1fbe9f2593bc11af031075b58a108bc8 -MbedTLS.v2.28.0+0.x86_64-linux-musl.tar.gz/sha512/d185ced64d471fba9ae1aa495b2eba0e60738e8e5ef918670b1c40cc8981389ecd48e4f17506229bafab4a11f7a257d3d544cfe87ad198482778931c2a7a8aa9 -MbedTLS.v2.28.0+0.x86_64-unknown-freebsd.tar.gz/md5/26beed62ee2abe8c6e52c1dbddbe0b1a -MbedTLS.v2.28.0+0.x86_64-unknown-freebsd.tar.gz/sha512/f04a417d99e3b908383d3c14cf8512b2f13e4b226d07235e2334090aadb6aecce40a23ae8f8df9c0ed9618707e839aaac6de64d5fee6d7e3955b290bc564d3a2 -MbedTLS.v2.28.0+0.x86_64-w64-mingw32.tar.gz/md5/cc55fe5537719aa8bf3bbee981c01413 -MbedTLS.v2.28.0+0.x86_64-w64-mingw32.tar.gz/sha512/3436647e81fdb9db138063229f20f47e2c8405e6379ca3e7cf38fb9fde84d2b6618a5f29b8df19cbffe75af7f99e00e9583d67be7b53dcce27bff453b96dcf13 -mbedtls-2.28.0.tar.gz/md5/d64054513df877458493dbb28e2935fa -mbedtls-2.28.0.tar.gz/sha512/907867edf532ba3b099f4fb7ce31f5773ceceb072a8d067b1d830e879d541f92f401d64f13bbe6b4eb0845e58bb765d7d28896be414bb0fc7ac5b3876066be5f +MbedTLS.v2.28.2+0.aarch64-apple-darwin.tar.gz/md5/ef83fb4706100ee678cd8af3f7a5c762 +MbedTLS.v2.28.2+0.aarch64-apple-darwin.tar.gz/sha512/03dda8cc9afa3d79c3c733e45c77891e75d939dc2bcca5ba8eb7aa3bd01fb52011ea9323df9cf7294fe6dcf87eb86c1b1c4b2f3b8af6116929b3371698559fe4 +MbedTLS.v2.28.2+0.aarch64-linux-gnu.tar.gz/md5/ac46c3840d2d0cc7c573f31c2f3d0d61 +MbedTLS.v2.28.2+0.aarch64-linux-gnu.tar.gz/sha512/bb458f1dc9b8684a38f603136ee4ba1c51b47f5047c5a5cfe2c552be266e79dfcd8243b216b0831abf24390eeb6f4524bc7e43b2642eb2ad0227399222cd0d8a +MbedTLS.v2.28.2+0.aarch64-linux-musl.tar.gz/md5/d74732e0bbcd03666243605e60bb345a +MbedTLS.v2.28.2+0.aarch64-linux-musl.tar.gz/sha512/90b0699477b697b94c0ab1ba0607fb3e1cd40d66a80a51cb1e0f3b927de03ba201e7e280d453db672e6265db5b07d0145846e53ddbcb4b550afcabef1716470b +MbedTLS.v2.28.2+0.armv6l-linux-gnueabihf.tar.gz/md5/65ce7c51884b50dcb8343a945644b862 +MbedTLS.v2.28.2+0.armv6l-linux-gnueabihf.tar.gz/sha512/e9df753e9f3a08fd645b15422be7cc0ec3aeac3f8d5f76e0c4c5ec24c54e1b653db320ed0c6799411802a05801241a5363bb449a8765fda7856413c7e3297721 +MbedTLS.v2.28.2+0.armv6l-linux-musleabihf.tar.gz/md5/7b7fc8eafc95416d75e3f1bfb2640e09 +MbedTLS.v2.28.2+0.armv6l-linux-musleabihf.tar.gz/sha512/68362114808fb4f986dea673ef1c7f104caad8233bed1c7f6a365d5d69bb7f7c92b234d6b1bfa5b014e7096411841c115a5cfe9932ae9ce642293cab962f8d38 +MbedTLS.v2.28.2+0.armv7l-linux-gnueabihf.tar.gz/md5/4a477379b15fafbf0c05435f5ab370ac +MbedTLS.v2.28.2+0.armv7l-linux-gnueabihf.tar.gz/sha512/fd34b475bf94b411e3155f5a5166d1ad081fef3622d7b99f4915b592d4235f63a0b910e0559ba2a0c3d596df9ccc2d7ecb61984091debb20bd4b995942857132 +MbedTLS.v2.28.2+0.armv7l-linux-musleabihf.tar.gz/md5/fc6551ef5f189010a84230dd48f6bdfe +MbedTLS.v2.28.2+0.armv7l-linux-musleabihf.tar.gz/sha512/d3a7199f3e1ffb1c289c5f0a4384f3b5d1af6e868eb1081d66d6cbfc60e6415e68a7e22afb497f2e7c7900678a19bf1ba2a4c888efa1019c03bce376af62154c +MbedTLS.v2.28.2+0.i686-linux-gnu.tar.gz/md5/335c3ac146bbe8cd862e4737bc362037 +MbedTLS.v2.28.2+0.i686-linux-gnu.tar.gz/sha512/f12ef67a92af27f4021f73171cdf2ef5558f734fcb185e4417fd7e16752dafe3f75be4291854b5ce346abda674252d58064d9186122eb4f9b15ff89156d221ce +MbedTLS.v2.28.2+0.i686-linux-musl.tar.gz/md5/435b864b02d1d2c96e5d8dc32b433ae1 +MbedTLS.v2.28.2+0.i686-linux-musl.tar.gz/sha512/52e3a79a70b3ff4617c93cafdeb702105c13b34687fc0fa31eebc91aa5cacea356d5b6a6bdbbfd81417d77debe256ea8f0f2a43c8d140154099bde097740dce7 +MbedTLS.v2.28.2+0.i686-w64-mingw32.tar.gz/md5/a238801f7e0d14f4b693aa4b74645263 +MbedTLS.v2.28.2+0.i686-w64-mingw32.tar.gz/sha512/431db4c388d3c52b08795d6fee6e6696cf383506a603816d6a63dc3571dbdc2b673837a1df1d9003c5009f8f8dc6eaaef3f80aaea396dc2fdf54b7e6a3c6aad6 +MbedTLS.v2.28.2+0.powerpc64le-linux-gnu.tar.gz/md5/26c8f09aa65e5b70be528311519d4376 +MbedTLS.v2.28.2+0.powerpc64le-linux-gnu.tar.gz/sha512/2d47567388b8554ce7714f4ded013fcbffbf94726dbc6a1b7287dc17b27d1fa35baba55cf7dac17c555892a5f4c74119afdf552b42b0e8f80f26621adaa4dbca +MbedTLS.v2.28.2+0.x86_64-apple-darwin.tar.gz/md5/dfc263208b1a8d4c29b4ec3b6f10e5ce +MbedTLS.v2.28.2+0.x86_64-apple-darwin.tar.gz/sha512/3b2941c4b151206a56a9a795f0f30519676ea4bc0c93f66b419b15568edc91bb976954f584116accb7f9bd067580712e61b3c580a249332640e27e6346ca51ff +MbedTLS.v2.28.2+0.x86_64-linux-gnu.tar.gz/md5/94b908036eecbe59372722b41f0b1985 +MbedTLS.v2.28.2+0.x86_64-linux-gnu.tar.gz/sha512/c37a4c34eb450bd716c076c4105bd6022892731c470d64a854ac0fca6653dcf5a70b23982050e7d82cdfd67d02902d9efe4c94d2cf5e0d29d497c3c5ac03f8e8 +MbedTLS.v2.28.2+0.x86_64-linux-musl.tar.gz/md5/217866be499144eeb2e0944b0b60cc09 +MbedTLS.v2.28.2+0.x86_64-linux-musl.tar.gz/sha512/144180e1968da627c92173277a130283aea711157a04a2655786658234232e397985f63d5407166377fc5f38a7447c19797c51b66a9c4b1773601d9e7e01d0e0 +MbedTLS.v2.28.2+0.x86_64-unknown-freebsd.tar.gz/md5/74316c624c8106faf7c04e05149b5c38 +MbedTLS.v2.28.2+0.x86_64-unknown-freebsd.tar.gz/sha512/9eca254c9b663b2f5799705c2e0aebb5529a7ff7759b0f3b67516e622dd4561169fface1d08340666453e779133498eacb8ef2dae1ef6332ceb4d8052d3614d3 +MbedTLS.v2.28.2+0.x86_64-w64-mingw32.tar.gz/md5/cdd28912607781f5e6ea6cad73c7dba2 +MbedTLS.v2.28.2+0.x86_64-w64-mingw32.tar.gz/sha512/e5793778d57b725a0cab48dd7e8f45022699b654bb8e890620efa73628140e453c80601e43647a700d6090a4b66d3c30b11634c4224c016c11c7bfde6b8a1b2a +mbedtls-2.28.2.tar.gz/md5/421c47c18ef46095e3ad38ffc0543e11 +mbedtls-2.28.2.tar.gz/sha512/93cdb44f764b200131b8dbefb9363e5fa38760eaf01473a512f93673cc55db3515830e16b813e03b39cb819323ad78cee4cb7f3fa85861ec5e72e0f89541c7fc diff --git a/deps/checksums/mpfr b/deps/checksums/mpfr index 8353d49aa190b..2b4281659b13a 100644 --- a/deps/checksums/mpfr +++ b/deps/checksums/mpfr @@ -1,34 +1,34 @@ -mpfr-4.1.0.tar.bz2/md5/44b892bc5a45bafb4294d134e13aad1d -mpfr-4.1.0.tar.bz2/sha512/410208ee0d48474c1c10d3d4a59decd2dfa187064183b09358ec4c4666e34d74383128436b404123b831e585d81a9176b24c7ced9d913967c5fce35d4040a0b4 -MPFR.v4.1.1+1.aarch64-apple-darwin.tar.gz/md5/157265257536980394e0a025b9d28de1 -MPFR.v4.1.1+1.aarch64-apple-darwin.tar.gz/sha512/44064eb67f087c2c38857273b069eacec9ebc199dd908f975895ab28bcdeb761adaec1a20cb5c3a98788090eb9ec31678ab1c5802896b22738d120e379f1f6ad -MPFR.v4.1.1+1.aarch64-linux-gnu.tar.gz/md5/ed45c58b6f9ee6993f34012570ffa6bd -MPFR.v4.1.1+1.aarch64-linux-gnu.tar.gz/sha512/d90cc0826df50f359c49a5ad7a48639137d7f58649d480a50f1a8cd9b77ca09a2678b320aef29dbe0f07f65e40c1994f46ec6adec6047d345d7ed1cf100d0724 -MPFR.v4.1.1+1.aarch64-linux-musl.tar.gz/md5/9634a53796d208acb1353ed500685644 -MPFR.v4.1.1+1.aarch64-linux-musl.tar.gz/sha512/9fa2af227851bc9db79b8c4c381c07be12ce526a7e72e01bef76353b3488fe92cca17978d8df7ae38cbe610e1406b5a8d825b18b43932ced36809dca5ba81f46 -MPFR.v4.1.1+1.armv6l-linux-gnueabihf.tar.gz/md5/865fb6701c5b42b959c104387f8aaf08 -MPFR.v4.1.1+1.armv6l-linux-gnueabihf.tar.gz/sha512/726b07c8dc7b0f67416df2b86edbec8577187b1e6285e53b54c55c613493e3e2987037e29b83f861ff9f64b5700d8815985cc564813f55399d91c1e33e8fac6e -MPFR.v4.1.1+1.armv6l-linux-musleabihf.tar.gz/md5/29e12f8ee50b1060fe9ebfa0ee4e18fe -MPFR.v4.1.1+1.armv6l-linux-musleabihf.tar.gz/sha512/871f834e1336782e51aa42fbf3a06165de91e5d469d69bd3acffe743bdb63ca55d7fef9f6e064ed91512d733bd82dfd7b68a2351f9b9f38f1d853e74f6713b31 -MPFR.v4.1.1+1.armv7l-linux-gnueabihf.tar.gz/md5/23d59ed4fd3e8923b1db11bde9c77e5e -MPFR.v4.1.1+1.armv7l-linux-gnueabihf.tar.gz/sha512/0093a048c0f56036c6a27830c7999a6da396acf58da93bc11c31b638d10e0fa2dd8518e6eac02f9f041b8b83b5c74bfbcc532f43e05c7662b2e6ad5b16943415 -MPFR.v4.1.1+1.armv7l-linux-musleabihf.tar.gz/md5/e7be267d931c33e1a5a97af9ee3d13f0 -MPFR.v4.1.1+1.armv7l-linux-musleabihf.tar.gz/sha512/da6d7ed8fbf01dfb8624f4aef27f095cd4ea88002f9587a51f877b05b9308ab2af277bb452ec9442cb71a82e322ec03fc30a90d17f43f3b9fabbcd5eca64c68c -MPFR.v4.1.1+1.i686-linux-gnu.tar.gz/md5/26db9d697d5e40b3364cf3a52893b64c -MPFR.v4.1.1+1.i686-linux-gnu.tar.gz/sha512/0ac65c66e669cd7bd9d951f61b06249c19579d280cc6146b8b2fb324482f1191c9fe1bba6187f5f67ba219506db2bfe2c71a00e6179b5a8995d4c91cc903b8fe -MPFR.v4.1.1+1.i686-linux-musl.tar.gz/md5/8a012b83532aff4022784a4b85f5974b -MPFR.v4.1.1+1.i686-linux-musl.tar.gz/sha512/182eb18ee7f4766d5f45adaa1eab703acd99e801a02812e8772a50fd59e7fcff3dedd9a008c85ae816c257ef106ca8d809315f95f38b34548307a9ea0e4fe692 -MPFR.v4.1.1+1.i686-w64-mingw32.tar.gz/md5/d59ad915170aa5dbb230a64a44e1ace8 -MPFR.v4.1.1+1.i686-w64-mingw32.tar.gz/sha512/96adfca120ae691e28741f8a2dadbba0df605fcae622fe4c83c17985ee66b3092761104e0cefb68315237900f413fa3790b60306c8aa82a0e4d7bf32311c684d -MPFR.v4.1.1+1.powerpc64le-linux-gnu.tar.gz/md5/4796379b5d91ee63f5c37687b6962ac5 -MPFR.v4.1.1+1.powerpc64le-linux-gnu.tar.gz/sha512/b3567a40c1b105a24305d48ecf65aaba70ab2f44d1c7d9e7ac37a53393fedd56e6aa7f5c4395226eb7dd3c02f8aa9403485dd85e327f5d7c61e8fee5caf85d00 -MPFR.v4.1.1+1.x86_64-apple-darwin.tar.gz/md5/54e27d8dd1807dac1c0e77699c3e6180 -MPFR.v4.1.1+1.x86_64-apple-darwin.tar.gz/sha512/8e54dc8b24031ba66c53b45e537b7709dafa2736c2811ead8ca9062f03c22e78095579091dc8a4e7f69b666399c48906dfd22986657ce5f81a1f20043a80f504 -MPFR.v4.1.1+1.x86_64-linux-gnu.tar.gz/md5/7060b44302ca6544d372ec71b3b76aa8 -MPFR.v4.1.1+1.x86_64-linux-gnu.tar.gz/sha512/4f4e4f762106becf8c17790addada3a0a5f33444fde858359e4634041d877ee65a45b6d90f91f3126dc08e7bdad4506bcfdf3bcbda5994ed592267566393582a -MPFR.v4.1.1+1.x86_64-linux-musl.tar.gz/md5/0c8110f6699a2ea27f2eeeb3949ce781 -MPFR.v4.1.1+1.x86_64-linux-musl.tar.gz/sha512/40c91daf959a9b78af513b054e4e8d0cd1c121a5f3e0e6cdf22446e97d28d3f056f79978092907ba08645c3f6e29b5134ef344ccc79a9c2bbaaeb2233140cc25 -MPFR.v4.1.1+1.x86_64-unknown-freebsd.tar.gz/md5/9dc9d9bb0662700510b89e6da4f44f2d -MPFR.v4.1.1+1.x86_64-unknown-freebsd.tar.gz/sha512/14208fb683233d44eb2263e7674b9c5cf4f7f7151f025b2b00fb482e6609b78b2189eb25edd7c45b8634bca07e1aca746a6094af50d1449248847529ff58bcaa -MPFR.v4.1.1+1.x86_64-w64-mingw32.tar.gz/md5/6159f631081b32b7df88e090af417f4c -MPFR.v4.1.1+1.x86_64-w64-mingw32.tar.gz/sha512/5086da1de24b1f9431ea7dbe6407ae9c81df7a10b04845e8fe4a476a6a5dcb78d3e4b06ca81c85d1a8cf2d081948d20bb77672a4c9f6d20e194f384a323a1f71 +MPFR.v4.2.0+0.aarch64-apple-darwin.tar.gz/md5/f9393a636497b19c846343b456b2dd7e +MPFR.v4.2.0+0.aarch64-apple-darwin.tar.gz/sha512/a77a0387e84f572ef5558977096e70da8eb7b3674a8198cc6ae35462971f76d684145ffae7c2ddca32e2bd1c8b2ccb33e4447eb8606d5d5cd5958298472b3ea9 +MPFR.v4.2.0+0.aarch64-linux-gnu.tar.gz/md5/ade253017d195de694780c32f9161dcf +MPFR.v4.2.0+0.aarch64-linux-gnu.tar.gz/sha512/1b68de5f8e557b7434c8c1bc016227b58683b56c0977b763422ea85a673bec446fcfee3a4f69e1d4689abb9bb6bf47f2a50fbb56ecac6a9d40096e66bd0f2080 +MPFR.v4.2.0+0.aarch64-linux-musl.tar.gz/md5/7dbd121c7192ccaf7191de5ab8d91afb +MPFR.v4.2.0+0.aarch64-linux-musl.tar.gz/sha512/8614e3cb28491b24a0ec5060b44abaf264b61c91ddd29d70105ff583bd3112cff1b9bd5ed45e39f186265333982d5eeb8bf35fedc3b51b2a009cc7a51046b50b +MPFR.v4.2.0+0.armv6l-linux-gnueabihf.tar.gz/md5/adb2b7fdf111c8b19df1516cfb278bb1 +MPFR.v4.2.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/0c47aeffd05a194802f6c4e0e2779d56fb46007e6c3e145ee6992854a21a317a9d51512c59a0ce4ddcd314c387945225c6557d6c2ab6961ae4848875e8983de8 +MPFR.v4.2.0+0.armv6l-linux-musleabihf.tar.gz/md5/c30358bdeffcff65ba9be906cd35889b +MPFR.v4.2.0+0.armv6l-linux-musleabihf.tar.gz/sha512/2857ec27ae2d53a451d62dd241ce9b43f7ee182bee180ecd9ad92c907c66d0b0ab2d1ea3b20fe61cc176ae44ecbe6041305cc8a9343b396c9cb54dd77a1e2868 +MPFR.v4.2.0+0.armv7l-linux-gnueabihf.tar.gz/md5/a1e30436bade2150c9dc924177f0c321 +MPFR.v4.2.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/d2f4662c494fefda66847e7a085edda3ce396383aafb4e17fc2e176191b0f530541726c261cac3467f13136e8ec728c8a7cf0e352f3e9ebf960d153cbfe766b8 +MPFR.v4.2.0+0.armv7l-linux-musleabihf.tar.gz/md5/857e3c82804e7c853d21603f18caa715 +MPFR.v4.2.0+0.armv7l-linux-musleabihf.tar.gz/sha512/86cf3e940fd66820b5269e9aa2a49c3fc3077857bec037a08e0d301b0bf3cc5c79ac331cc6370d852e20f4acf8f601c49d5dbe24e96652e4411b3f33a11e3f45 +MPFR.v4.2.0+0.i686-linux-gnu.tar.gz/md5/5a432be79a112e67e970980f4bde13a0 +MPFR.v4.2.0+0.i686-linux-gnu.tar.gz/sha512/94198b23ac94dcb9dca95938a46b9899c3ef329bafbb13b32076cd3415b89f11908632c7c07e90549c01bd9ed7fc9a002dae07a645f85b8509234c49be729621 +MPFR.v4.2.0+0.i686-linux-musl.tar.gz/md5/4ce71dc250c2469f844a02c6ee6571a1 +MPFR.v4.2.0+0.i686-linux-musl.tar.gz/sha512/134b67b23de75ab172594cd0fac55b5c265730bfea195978698e3e6fbc47d65617652bd72d90ba092ed1bac4c29d5b2c109df5d8dc60b5d8f91159fd58575b67 +MPFR.v4.2.0+0.i686-w64-mingw32.tar.gz/md5/df41bde61d33b56fd48bdb0f9ec0c624 +MPFR.v4.2.0+0.i686-w64-mingw32.tar.gz/sha512/145bc14f22eb077992cd993a20d3205eeeee1d2bb99ff4f48277173b0b39c848e2cd3044d2141003607aa4ea3665546a87b9ffea87bf570ab1b152117ef4045c +MPFR.v4.2.0+0.powerpc64le-linux-gnu.tar.gz/md5/d818894054b38232ba02ee0e129f6fe0 +MPFR.v4.2.0+0.powerpc64le-linux-gnu.tar.gz/sha512/0e73ca926f3e06466d1899f0b3e9ae4abe15102804dce6716ce23154344a571773c40d276f0038a0ae4e626799867ee715428e1d961334a01ad3091745367e8e +MPFR.v4.2.0+0.x86_64-apple-darwin.tar.gz/md5/9652148df4e771be39713c4f43d3ff61 +MPFR.v4.2.0+0.x86_64-apple-darwin.tar.gz/sha512/91a0219fd1880dfa90d196fa403f4e1df0347ced58a4772492196b94476f346d80696885a4f3520424494bc09679cca0c0ccf2f6e9247d60b52ebdf564485e72 +MPFR.v4.2.0+0.x86_64-linux-gnu.tar.gz/md5/4de39327a792be708119ac7b43957628 +MPFR.v4.2.0+0.x86_64-linux-gnu.tar.gz/sha512/447b59d5589a8517061627668e8baed4366408cacc9d8e063528b9b795de6d27e4005844578310185f03f568f4948bc4a794624235875fb61b6187264b6f483b +MPFR.v4.2.0+0.x86_64-linux-musl.tar.gz/md5/f9b8c3c094b339341b19828cc5e1d47c +MPFR.v4.2.0+0.x86_64-linux-musl.tar.gz/sha512/c661e7c5bded3bdf11b2bd5e5ef4ad8e446934d9b82dfe26f0be1b83cea98d7e56e0903bfc1075f91c8d23401cc6b3b722f2d60f46d73cab884e81fe518aba27 +MPFR.v4.2.0+0.x86_64-unknown-freebsd.tar.gz/md5/83700aaebc7344d84d70f0bd0f9c7890 +MPFR.v4.2.0+0.x86_64-unknown-freebsd.tar.gz/sha512/039cb18a142a90fadc7951f05324fe9c033da9502a61da77fdcd5d9557075ad1ca8500b9b9b39ce57a44b9cb28d41dfc6cbde10cfdbdb40077ebada24a2bab9a +MPFR.v4.2.0+0.x86_64-w64-mingw32.tar.gz/md5/9cdaa3fc0d13a8835d165c745937c385 +MPFR.v4.2.0+0.x86_64-w64-mingw32.tar.gz/sha512/21464bf836362ecc50da82859a4ba2de3d32d76ff57de9719ac850e73918814e1002130e0d6797fbb914b822f13bea383be3a29b2a1c9c8415cb2e3c5d321669 +mpfr-4.2.0.tar.bz2/md5/f8c66d737283fd35f9fe433fb419b05f +mpfr-4.2.0.tar.bz2/sha512/cb2a9314b94e34a4ea49ce2619802e9420c982e55258a4bc423f802740632646a3d420e7fcf373b19618385b8b2b412abfa127e8f473053863424cac233893c0 diff --git a/deps/checksums/nghttp2 b/deps/checksums/nghttp2 index 27f120546f34b..6113b23d68c14 100644 --- a/deps/checksums/nghttp2 +++ b/deps/checksums/nghttp2 @@ -1,34 +1,34 @@ -nghttp2-1.41.0.tar.bz2/md5/523d330f62560a2fe4268beb84920890 -nghttp2-1.41.0.tar.bz2/sha512/61de1bbbe91230ebe9f7a3ef4d3874391f8180d93c8ff1e94a58035e4061d2f9057e5ba2b90f6fe86f6aefc7244795385d176a862019c47a3aad974b60caa143 -nghttp2.v1.41.0+1.aarch64-apple-darwin.tar.gz/md5/727cf8227b41aff95c91b9db31fbe303 -nghttp2.v1.41.0+1.aarch64-apple-darwin.tar.gz/sha512/86067f3d04bc1b1cbaafd044ee6eb6c306ab27ba1828290412e62bbd2637d1be90cba437c8c39b853df0b11776d90a6525c9d0d9750abd9462003cb319e38cb6 -nghttp2.v1.41.0+1.aarch64-linux-gnu.tar.gz/md5/9a1c71ba214a363072002ea4856c9f93 -nghttp2.v1.41.0+1.aarch64-linux-gnu.tar.gz/sha512/16a18a1d2e3486243cc419362f195e29e0bd64344a2b7167da6b10fe6efff1a6c7bd07806915358aa3ec5dd2590e9bd02024df1e3723432ba3dc833ee52f292f -nghttp2.v1.41.0+1.aarch64-linux-musl.tar.gz/md5/1ed0166911be5703d0d6377b666e63c6 -nghttp2.v1.41.0+1.aarch64-linux-musl.tar.gz/sha512/632b6ea76e89b60d2c4f0379a88b0a33468dafcaa3605b69b3a5f57546d5036e7003341ea14060ecc417e659b74130597278c71cc34052ff7188472e23bf7092 -nghttp2.v1.41.0+1.armv6l-linux-gnueabihf.tar.gz/md5/288b56ea29c570c01cd52683085d1ff4 -nghttp2.v1.41.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/91bd764317b72df51289e67616771d5299ee32ad6222993555abc9bf4af5ce1920bed6a9bb2f03145c41bec557460a357d79f7716774f6b730629b225ec5b2df -nghttp2.v1.41.0+1.armv6l-linux-musleabihf.tar.gz/md5/acba1517897a8e43aa3707d9d02c03a2 -nghttp2.v1.41.0+1.armv6l-linux-musleabihf.tar.gz/sha512/908db4e29550c277acffe2043b97a0e7946b45f9324b93b2c63fbe83bbd9edd5e899a6665925d1bb85c5662f301de7612829cc36e8edccc3de57ac54911d7357 -nghttp2.v1.41.0+1.armv7l-linux-gnueabihf.tar.gz/md5/2b0fb368ffecdd5f527eebca88925092 -nghttp2.v1.41.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/8ddd95df9896856f77be0af35fa9cb3c276a6cab2528553c08a2ba68014a5320d6ba7bd370bde362ba48a2fe097f6d5c348f5d8b20e1762010a9bb63c7bec502 -nghttp2.v1.41.0+1.armv7l-linux-musleabihf.tar.gz/md5/30baa03195b0560adf729f1aefd0d961 -nghttp2.v1.41.0+1.armv7l-linux-musleabihf.tar.gz/sha512/c93d9db188ccacb3da80a51c84799c91d686563cb055e2528094600be2565f608ceb57db9570f36a1933754a45e8f007e3c2a8de13de332effe0e5879814e5ee -nghttp2.v1.41.0+1.i686-linux-gnu.tar.gz/md5/5bc7fbde702b477d28c1843c6ff053cc -nghttp2.v1.41.0+1.i686-linux-gnu.tar.gz/sha512/2f9317172eb8489fab668cdef156462e1f0b71d365731b358c0f246c873e0ad7dc94b2b27d165f54c57ce6caba2c3fe89b9ce2555e235aaab7b05adbcf6a33ea -nghttp2.v1.41.0+1.i686-linux-musl.tar.gz/md5/75a8870627e4db65706df7af92163492 -nghttp2.v1.41.0+1.i686-linux-musl.tar.gz/sha512/f1ea9b7e66d01de30a92da34fcc72cbd9c988ed768f8d8099d68f031ccc33d9f965460289a183ae1f9f507580d25020366a312b61fbbcd3b2f06ee697f8cd133 -nghttp2.v1.41.0+1.i686-w64-mingw32.tar.gz/md5/258224cfa14b53e7af79caa1dea2eb2c -nghttp2.v1.41.0+1.i686-w64-mingw32.tar.gz/sha512/69a783d09e9258df81ad6121906f8102ad2959c623efca9fff7adf062c947d6e9a44fdab27cdd49bb08519b4a4d636bc529715771d276a69c6e3604237712056 -nghttp2.v1.41.0+1.powerpc64le-linux-gnu.tar.gz/md5/a358200b27f3229fc888d3e1763cca0a -nghttp2.v1.41.0+1.powerpc64le-linux-gnu.tar.gz/sha512/cc02237e70e1cafea3e59e9929a4bd86a9b4dbd2928c812b3e654dcc91b87077d91284e7006799186b324ca5f8822d09e3bce1d4a69cea18f42d4e08f6aa5f3b -nghttp2.v1.41.0+1.x86_64-apple-darwin.tar.gz/md5/fe76513593627368edcb95783b830ed1 -nghttp2.v1.41.0+1.x86_64-apple-darwin.tar.gz/sha512/fbabf532cc32277394b66cbd1c011c4d7a65380c2759b1cf57e57599c3c8752fbd2d86b42677159005d6430025b4fde5f623d814629215449fd934d1328589dc -nghttp2.v1.41.0+1.x86_64-linux-gnu.tar.gz/md5/ca57b30aa01b0a8f69babed6398bad9a -nghttp2.v1.41.0+1.x86_64-linux-gnu.tar.gz/sha512/3fa5a6806bf6eeb4305038b2c6c381e815988b1a37bcacb4510c229f9186e280aa98fd023495b3fd77c4f16314f81b7233778cd81cc3e3d64f503dac3e1ad70f -nghttp2.v1.41.0+1.x86_64-linux-musl.tar.gz/md5/043dbfb6df2778a1b0ea57c70b982974 -nghttp2.v1.41.0+1.x86_64-linux-musl.tar.gz/sha512/70d7ad982fe3b9de49cc37758588c02e78e306ab3ac84682e130e2ab4541814790ec3e201daa96cdd900ebc8fc0a7c1ff6e02e796a65f092c6c24b56c1214b3b -nghttp2.v1.41.0+1.x86_64-unknown-freebsd.tar.gz/md5/13a4e74ffd31852b885e44f2f0fed516 -nghttp2.v1.41.0+1.x86_64-unknown-freebsd.tar.gz/sha512/c81b76e9cb2920a0cc554fb9ff23f0cf20f3f9601276fcc07d1ffe0df608cbcc2513b31273f839eed3798cd2183fe9ad0580c833707526a4b8cfddbbc7b53006 -nghttp2.v1.41.0+1.x86_64-w64-mingw32.tar.gz/md5/635cc7c17cbe5de2d7e320c0d920e61c -nghttp2.v1.41.0+1.x86_64-w64-mingw32.tar.gz/sha512/e6e0543b2835eab2f4774e027e921acfd1d2a9229876d6acf5c64dc61f7dc73c078d6489910b3179aee4ccb95aa7281b1502170aa6256e41ab1516982d0da230 +nghttp2-1.52.0.tar.bz2/md5/bde5874bd8e7e8be3512a621de27b9d5 +nghttp2-1.52.0.tar.bz2/sha512/019ec7a904d1baf8755ffcea0b38acf45ea9c6829d989a530ab35807338ba78d3328b86eebb3106b8372b7a8c51b466974d423e0cd786b6d6d020f0840c160bf +nghttp2.v1.52.0+0.aarch64-apple-darwin.tar.gz/md5/e3d9e07029e184cc55b7e0c4d2e27c7f +nghttp2.v1.52.0+0.aarch64-apple-darwin.tar.gz/sha512/cd098db984f751b00d2cc99d7f7eba0fa830ba178dd85a9dfa679a591e62d57364dcfd74e6a55ef513a0436a8e520b1a5474d4bfa9a8bdcd70e398482b7c9985 +nghttp2.v1.52.0+0.aarch64-linux-gnu.tar.gz/md5/73fe75f3cfa2bd3e804ea39a4eb884a9 +nghttp2.v1.52.0+0.aarch64-linux-gnu.tar.gz/sha512/71f4b2a23ba148b66432797b0db954dbd98fc900045d4572f488b43779aae125f71929e5bba6bbadd30c7998a133c5e5beb70888968bf3b01bb5fe9c9ea0e451 +nghttp2.v1.52.0+0.aarch64-linux-musl.tar.gz/md5/736a24a7eee567851a965558e31489fb +nghttp2.v1.52.0+0.aarch64-linux-musl.tar.gz/sha512/ab36182b04a590b092fae9e3a912a87467e8b01ad40a628a1d2e52910ee513ab327d5d2836df598d5aa8203f60a605d19d0b9636eb35d12a84a1c9d87124604b +nghttp2.v1.52.0+0.armv6l-linux-gnueabihf.tar.gz/md5/56fd32e8d77d4c9d9e2355565f4db19b +nghttp2.v1.52.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/85718e0e5cee35d91a8684ea33d8f965bb30d62dbd6b74a574a2fbc4c1027b1ef23ef68f1dec3f037fa6c5739287329567df9591a69f8f23b23fab2516a0b644 +nghttp2.v1.52.0+0.armv6l-linux-musleabihf.tar.gz/md5/283273d3bf4d53b56d12ef6af2e72f20 +nghttp2.v1.52.0+0.armv6l-linux-musleabihf.tar.gz/sha512/5c1d92cbf5f2f4e1ceb4ee13634c0bceb6ca28abaf9d87cc673f264d274bb96aa095648295e9aa76f86eb0890a426f47c0b942e72610daf722ed8e86b5f0df69 +nghttp2.v1.52.0+0.armv7l-linux-gnueabihf.tar.gz/md5/d7ae84e5365759a42d0fe0360f679b61 +nghttp2.v1.52.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/63212e3ad94d2bc54ca9ebd452d8de8e67aa53c03a3b3033d36da765303e714d8d5c24156ea4fb985acc72fe52e2977e8e8a658cdd9409bd41ecf401c08c1aee +nghttp2.v1.52.0+0.armv7l-linux-musleabihf.tar.gz/md5/a6ad0f25f43b7f1832faeaaadf683ed4 +nghttp2.v1.52.0+0.armv7l-linux-musleabihf.tar.gz/sha512/64b9075c0d819288345d53c5ce88b360d2ca4d24c3d2e81fb53c55f86054b1a3e95d7831b363a4100965cdbf479268a5993d66ef59089a219a97b4151d8fef60 +nghttp2.v1.52.0+0.i686-linux-gnu.tar.gz/md5/9781f6eeb4d24a291d6737e59e74edc1 +nghttp2.v1.52.0+0.i686-linux-gnu.tar.gz/sha512/2b542cb67e78993ef881694dc50c980b57db3761c5f4e11c381afb1b31d1fb8ab0a8b20e1279303a602c07912f21e8ef9d732366b76ab3f356a74b444a5dc78c +nghttp2.v1.52.0+0.i686-linux-musl.tar.gz/md5/08603b9364179ab4cbe0637b9b1b63b5 +nghttp2.v1.52.0+0.i686-linux-musl.tar.gz/sha512/0a5b79709482548c6a713843b670695b4b13d2b219b592d029719da0b4187fe884798fb44e2c511c300f02bab03f2b0b289d49d6256e3ce0b9602a66ea2382bd +nghttp2.v1.52.0+0.i686-w64-mingw32.tar.gz/md5/1abdf0cad466ed0ca0da137809999d8e +nghttp2.v1.52.0+0.i686-w64-mingw32.tar.gz/sha512/04680895ead989fda56b284d8963e7ca31680492c8f77f4c6bd7ca03b9a66ee7529b78cf35e07b2e106f43c9aa543dffd4081b034339803ba95021293d3df997 +nghttp2.v1.52.0+0.powerpc64le-linux-gnu.tar.gz/md5/ae411e40e24cb3f3b07fe8de211b58c6 +nghttp2.v1.52.0+0.powerpc64le-linux-gnu.tar.gz/sha512/7433502d76646e5761ea2707fa65ea5a412c513c70908a4d9ceb504f08121b1f39bcff984543370c221814785b7064f85dedc777a22df5e30a64a64e510e0978 +nghttp2.v1.52.0+0.x86_64-apple-darwin.tar.gz/md5/59f0de0affaa17898e837b5074de68fc +nghttp2.v1.52.0+0.x86_64-apple-darwin.tar.gz/sha512/e639c813373b17d95220640ec2a568e9731cfc32df826610357ec9ff8e9d7e7abe10291140eaeb9342ae69215798bf3f999db7647c23efb4f815b54f4da9cfe4 +nghttp2.v1.52.0+0.x86_64-linux-gnu.tar.gz/md5/6bc8501392d47b349c7463e984dc5909 +nghttp2.v1.52.0+0.x86_64-linux-gnu.tar.gz/sha512/522cc2a8464ee5770c01b83a6b4ecbbcce322efffbd738f7c907643fe85342e785bbc805028d41c2b7404d6241168d1ab37a9db15018623c265b53905bcf060f +nghttp2.v1.52.0+0.x86_64-linux-musl.tar.gz/md5/725a6adc23880b28303017597b974535 +nghttp2.v1.52.0+0.x86_64-linux-musl.tar.gz/sha512/ede5a34b7f71310e4c3cd99b9b61b2453db5dc8117675de12adb1e68c9283cdf821614f49f4d04bdd3b0f17d51a52972ec1e226d0dbdc5462b1a4a1fcc9f39e7 +nghttp2.v1.52.0+0.x86_64-unknown-freebsd.tar.gz/md5/a2b89913c1057ff67e7be6086619a65f +nghttp2.v1.52.0+0.x86_64-unknown-freebsd.tar.gz/sha512/6b4efd2a0807f19cecf1f1e97b23ade11ed39f651e29586bb21185e17d0c50dcb63e26233ff994bfa934b383468e29f680b1ebe0cc2a2dd09768b14dead399a4 +nghttp2.v1.52.0+0.x86_64-w64-mingw32.tar.gz/md5/e1c8ec6ec2d69b2ac64b114ebf09f8b4 +nghttp2.v1.52.0+0.x86_64-w64-mingw32.tar.gz/sha512/cb43cb138f14717501e852ed388a44d41012e2bb70b6887584b37b4e0f42827d74f17ea85ba4aa0bc09d623dedeef73eee80815c1db2b6858b31251feb0b5580 diff --git a/deps/checksums/openblas b/deps/checksums/openblas index 31e6e27d61d20..5cd8d27baf25e 100644 --- a/deps/checksums/openblas +++ b/deps/checksums/openblas @@ -1,94 +1,94 @@ -OpenBLAS.v0.3.17+2.aarch64-apple-darwin-libgfortran5.tar.gz/md5/9020e93ed6349bab95c2ca7cf21b2ebf -OpenBLAS.v0.3.17+2.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/3058c47b1fecc9d9d63dee30d277fbe665b3641850e72349415c18dc8372971c3f1c36c9cf62ceec672604e70f5b5a0c118e484f63aaf1aba37075324537908b -OpenBLAS.v0.3.17+2.aarch64-linux-gnu-libgfortran3.tar.gz/md5/02f560828fab7c2df6ce7d81927045ed -OpenBLAS.v0.3.17+2.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/54f9acd7842ad8305073dde0e0e689a35e79cdee8f843560091fa3277957b9ca298d1516d027c6f0870d48743a70285714fec4f09e0eb43bd6954e8d6bea3843 -OpenBLAS.v0.3.17+2.aarch64-linux-gnu-libgfortran4.tar.gz/md5/24f4d8eea07a992735fc4433d24cdd74 -OpenBLAS.v0.3.17+2.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/8e1fb731cb9e3e0a9214c01538b2974eb6ed1a69857327e29dd166719491015d9a0695a75100ec804a5f9beaec121cc095f1ddf8c7a417f18a046035f1969c06 -OpenBLAS.v0.3.17+2.aarch64-linux-gnu-libgfortran5.tar.gz/md5/de3d9d1bd4b8d148084499f97ef9eff3 -OpenBLAS.v0.3.17+2.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/d7d31bc345389c5240a5dc7341741264ea328adc3604c8fea3e21914c13c3a1720270427465daccdfce080d2df6723384d2d9e9907db2a24c8fde32e492ccae4 -OpenBLAS.v0.3.17+2.aarch64-linux-musl-libgfortran3.tar.gz/md5/665a8dd827b32769fd307f65f18ce09f -OpenBLAS.v0.3.17+2.aarch64-linux-musl-libgfortran3.tar.gz/sha512/070d015f72d0030838985e949f1855e40997fcf31e1c51a1cc5666d681cb47fb02a289435cebd8ef15346bcb85140b0e164874dcf9e269e8799253fb538ea3f7 -OpenBLAS.v0.3.17+2.aarch64-linux-musl-libgfortran4.tar.gz/md5/fe47ac70b33442c9c7d882ea87e86901 -OpenBLAS.v0.3.17+2.aarch64-linux-musl-libgfortran4.tar.gz/sha512/d97588cb9511225e160fd6fc828a13e8f99ca6e16ecdbf57bc8e7a95296c004ca11316854f90421cf0ac7935a7ec09045324af2de6084b11c62dcdc3e96d1249 -OpenBLAS.v0.3.17+2.aarch64-linux-musl-libgfortran5.tar.gz/md5/fd550b91aec55ed97c86c876f2339edd -OpenBLAS.v0.3.17+2.aarch64-linux-musl-libgfortran5.tar.gz/sha512/53c258962bff09e8a4642c6bd02949792e36b7681bad45b3d21b711428025262cac3b8171530fe97fcf09b31e1e2029c6e32300ee1facb9c7de497beb8a99edb -OpenBLAS.v0.3.17+2.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/eb8996220a8d2ab0ff3fccf791c19d2d -OpenBLAS.v0.3.17+2.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/f2a91bb7523ed50607936774c6d31bba81584046e0bfffb2cccb84ac3319fd1700003991edf54d1c0af4b0558637275309d826fac76a908e46f5f58f006baba9 -OpenBLAS.v0.3.17+2.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/02b7b39750d7f4dd4b37c0260dd5ecea -OpenBLAS.v0.3.17+2.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/1017388c9141381e37625ade63ad58ee16c0da6ec775e0c8f20e13912e155e9e868024595accc388708c22341e36b5b9cd8f9343c904ea8e7d30ec1bf6c05310 -OpenBLAS.v0.3.17+2.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/56cc6e5f74809a81319ed36ca783bb81 -OpenBLAS.v0.3.17+2.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/fc416c3842ffd49a1a201138559f4271d92d6840847b8b224046c6a6310f30044c598aee453ac4f5ea52e5aafe1b3ebe1dd55486883d5197f15bc4dfe0262af6 -OpenBLAS.v0.3.17+2.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/78d82e6b98ce18f3a0ea92f2e18eb1bb -OpenBLAS.v0.3.17+2.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/bc7476532fed7efa0726937cc6ae8e4a693929cff2dc49fe28dc16ad4d3b18265b907ec0c14e12822d00a018d49dfa487fc3d7867da5c428ced381ccfdf346c0 -OpenBLAS.v0.3.17+2.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/e55e149728e4e2c18957f6db4dc38c4f -OpenBLAS.v0.3.17+2.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/e0403a40a91b2f7db4b23ba46b221b39996f7e6c8a417a4b0346c728e1e8520651e0a3a9ef6bcc0214251f34a968a42bfc124ddf4ea6b4fa2d1122a1e7540365 -OpenBLAS.v0.3.17+2.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/71f7071a2702ccb32cb9eb296c921210 -OpenBLAS.v0.3.17+2.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/29861c10bc8fbdb9163c21e133ac972898ce01eadfc38af089cab680d1d059cbd40ed16304ea3b256844c68070233dfce4197d690080cc9ec12961b8d56b5a94 -OpenBLAS.v0.3.17+2.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/b6c52ebccedf4d31ad03e4e883c9cb85 -OpenBLAS.v0.3.17+2.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/f9c04600842629b4ad4dea8afcfa54bc2e06bc4f204714d725e1e87044b155261870ec74bebd05ed21739c6e81e2876226732cf65367e12cb3e52c4fac1db332 -OpenBLAS.v0.3.17+2.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/3c154804cea0f5b83a5bb278d8a2bac0 -OpenBLAS.v0.3.17+2.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/5ccf2cab5a473619cfca7f381aa4c5da1f2057d10235224aad76d40c9349880d4e0f84dfe173f1f47653c82ff523fffd01bb6360179d2b1e4687029f64fc2d81 -OpenBLAS.v0.3.17+2.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/f5cecf92901773f2aebb13cf23e9603b -OpenBLAS.v0.3.17+2.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/855763d0071009c4d799942e86808c90e06c00a78db4350f8b798a414fad333e5b3fca7397cfcdfc06c5718497d1f19a4c19bc79f8d23685d064947585e98a4f -OpenBLAS.v0.3.17+2.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/16376d821f9b6b16d7b0ee1890ae79af -OpenBLAS.v0.3.17+2.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/68319193bfc960d7879cf2370fe17415d15086587958dfc85bb781c26332399b75cf3928ac0e6d727f6d54ecb41425f1bd724eba4bdba2648c73cc860ff7eba6 -OpenBLAS.v0.3.17+2.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/7d8099352db1e40a02bf80172979b2f3 -OpenBLAS.v0.3.17+2.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/5e73b0b13fe6db964332d663475590d750c3a21c85dd9d2bf181acc7834d22ae94eca7cd69f0dfe58fc4b195dfcdb28bdf526d3603e5706350153a71223f377e -OpenBLAS.v0.3.17+2.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/efd2b34c3931fe3354ab49f8d6fb330c -OpenBLAS.v0.3.17+2.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/ce5f743e261e2801beb075b48d87ff756c8fe157042beb2ffc3d7b506cdf182da11d07bd24dd543103d549f20b83212a0d390eb36c3d9ad715d9ca2cabdeca50 -OpenBLAS.v0.3.17+2.i686-linux-gnu-libgfortran3.tar.gz/md5/f52216036e4f1be71257bc876c67d95b -OpenBLAS.v0.3.17+2.i686-linux-gnu-libgfortran3.tar.gz/sha512/f83db9977940844b220a1ba0e2c2f3c63dfd355301e5d14b01ad85599fb931f5b797bc2ace5563ee5df47a243cac1800514cbe4884ca2a33db78cb1f9937185d -OpenBLAS.v0.3.17+2.i686-linux-gnu-libgfortran4.tar.gz/md5/381088794504a68c826d62cc27d14b9c -OpenBLAS.v0.3.17+2.i686-linux-gnu-libgfortran4.tar.gz/sha512/60b8fa109d32764ad9306e386aabb1ee6809aa03e04253a23a6ea97626d520bafa2ae09ea2f6762fa6bc9d88295bf7dd59fd2978e510c3c63925e7a6560947c2 -OpenBLAS.v0.3.17+2.i686-linux-gnu-libgfortran5.tar.gz/md5/f560fcacad77bf87d8d5945c921938e2 -OpenBLAS.v0.3.17+2.i686-linux-gnu-libgfortran5.tar.gz/sha512/9741eea135584ca23b74827ae02c8f2a91dc8a54b83401e0b2e119aca8c48736ba9816fc224a57f853cfe18fd10467b7f9934f3a10a50073af333270622b4796 -OpenBLAS.v0.3.17+2.i686-linux-musl-libgfortran3.tar.gz/md5/2c52064ddbd658e158347b62ffaa1cb2 -OpenBLAS.v0.3.17+2.i686-linux-musl-libgfortran3.tar.gz/sha512/4fba023c3caefe5fdddf27bac7915d075073c6ed0589348c26864686680710b7e84518072c8e94bdf444e25b5063ee6655afefcb1bf72e64ee5e3247e16fb39a -OpenBLAS.v0.3.17+2.i686-linux-musl-libgfortran4.tar.gz/md5/66da3df20820d2ee0de93e8a512aa5dc -OpenBLAS.v0.3.17+2.i686-linux-musl-libgfortran4.tar.gz/sha512/dca0075ba332ce1e68543f77b4ef666265d8e0bb443171d8cd53775800a3b8e13a755a9de067bcf4503835949bd1bc123f241a32fb74ec0014ef642151f36f1c -OpenBLAS.v0.3.17+2.i686-linux-musl-libgfortran5.tar.gz/md5/2df728b678feae582515a048abc6a3d0 -OpenBLAS.v0.3.17+2.i686-linux-musl-libgfortran5.tar.gz/sha512/755480899352f501fd2bc98adf5cd38a0869b7afbb8d3eb4de173d51ab355f31f03937d6fc2a8f560ca840f3adc04084090a11e495b00b04b465ffb1e0d003e5 -OpenBLAS.v0.3.17+2.i686-w64-mingw32-libgfortran3.tar.gz/md5/52b682596ac8a728bef3baa4e3bcc156 -OpenBLAS.v0.3.17+2.i686-w64-mingw32-libgfortran3.tar.gz/sha512/a6b59fef2d03da5a6246bf1832f0dfa654ab99d0275f69f280bdc54d9a8ab19d2ecce4f53d0f2406114ebdac43b09131c7c3982311f627810cd1de3001bd06b9 -OpenBLAS.v0.3.17+2.i686-w64-mingw32-libgfortran4.tar.gz/md5/0b63ad0bbada8158a000b2f1f64579df -OpenBLAS.v0.3.17+2.i686-w64-mingw32-libgfortran4.tar.gz/sha512/ace0c217299296662ed2e2a479096f26e0bf3a14166429b089ca856214c3d46442ad1b71ae94e2b14fe654fc5acdd940e3ad3970f956e75377601fd99f82b270 -OpenBLAS.v0.3.17+2.i686-w64-mingw32-libgfortran5.tar.gz/md5/a03556c3a4ee2d02f956aa011e5a53ad -OpenBLAS.v0.3.17+2.i686-w64-mingw32-libgfortran5.tar.gz/sha512/dde7ea92fdd47ec05edbeeb71fd3d75cb8b5ba5893e18419e47fd1f06032177a9453fc5920c6bd08aec4e2381c5f2c606ce9df7cbbecdda67d2e67aec8be3265 -OpenBLAS.v0.3.17+2.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/8c8b0dbb3e0c81d9430460c421dd76ab -OpenBLAS.v0.3.17+2.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/8639a186f74c9bf4bf5f9e2f69becf700a3ebec4e119519bdbad53fef559fd525e5f532bf7ea5a63bd29059d9c0564eec89a1cf7802cc7f6a3aeb4be9af3cbec -OpenBLAS.v0.3.17+2.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/e67d9c5a54b6a5dda63e0fe5ef5b24ad -OpenBLAS.v0.3.17+2.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/960cd0bf59fed7c70115358a673cc049cb539aa1b015cb473697309327e3b9afb9447b62239d58d8c56a9e8b1955b2b097b31c14b0013cafe77fbb4b967679be -OpenBLAS.v0.3.17+2.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/028c1ed0a8b84c83ec64b2970b1739fc -OpenBLAS.v0.3.17+2.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/2427b8f4de817ffbbd697f8b7caf710c3a3d9c02045a9650e8fde26c891c7cdc70482bda14f067b0cfa29d436a53f4484a00da8caba6188cba9fe25e7b57dc4c -OpenBLAS.v0.3.17+2.x86_64-apple-darwin-libgfortran3.tar.gz/md5/0277b078caf9b0f0a33bf1da351fcac0 -OpenBLAS.v0.3.17+2.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/52c11d822859209f989462aa38cb8c3a7886cd881da40699a06998498d59bfe40276196218c122b8c0c314384a27e7e4b1b6181c818ad1e543cd2af896be521c -OpenBLAS.v0.3.17+2.x86_64-apple-darwin-libgfortran4.tar.gz/md5/d43dd98167a2c99bd4bbd3f52271595b -OpenBLAS.v0.3.17+2.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/5eef221ed4e30090feec8dfa32a732a1987c692dbd2cf943aafb733ad4e5bd669ec55919ca5c89562e2500b4b1fbaffd6b1bbc8de3f71c9dc0037104412bb234 -OpenBLAS.v0.3.17+2.x86_64-apple-darwin-libgfortran5.tar.gz/md5/e93a6128adb949c43ea946ceca159d38 -OpenBLAS.v0.3.17+2.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/3fa4829b0c18085e935b1c3b7b5062a06ab4ebff60948ae6196ada22476798ee68b4e7b637cf3e5df9dc4dc8a5dbf7c924960b89d58de5c45dc8c8ca4834532a -OpenBLAS.v0.3.17+2.x86_64-linux-gnu-libgfortran3.tar.gz/md5/eddb496fe2c7915d61a4ead82c2622ff -OpenBLAS.v0.3.17+2.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/071d471c973bab1986fe32cd76f4f93eba49fbdf0f72561b90d09b846ce8990e20f328ef1ddfa5e0aa1483f4d95ede80d66fde197bdfec47ea9642a2f16b85d0 -OpenBLAS.v0.3.17+2.x86_64-linux-gnu-libgfortran4.tar.gz/md5/91050bb45fc71c6532d9b3a204903cab -OpenBLAS.v0.3.17+2.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/b02a226dab088e289b4bdcbf6f3ad2319ba26fa880ade277383b482c1e65bc056b834056d7eec0c75b425615d4167bfca581252eb31b87bd2b53d597fb8a47f0 -OpenBLAS.v0.3.17+2.x86_64-linux-gnu-libgfortran5.tar.gz/md5/87a0516c856af6128e2ecd2631c19d34 -OpenBLAS.v0.3.17+2.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/73012b9e99c57fc812e0f64fda6233ce204f2cdfc255ebbea221f614fd1d7ccdf5b2e1f017f55864a5dae8febbd1ed2fafb1fb3a79a53b8c1f1c7d6455ab7fed -OpenBLAS.v0.3.17+2.x86_64-linux-musl-libgfortran3.tar.gz/md5/6446a0328a83c504740b81e0a93087c5 -OpenBLAS.v0.3.17+2.x86_64-linux-musl-libgfortran3.tar.gz/sha512/8f77e02f32e69bf24205f10a3524d96d8bf79050d73f51a522db4228744ad9745a02c1bae1fdd3236a195481b93bec06e92a266fcdc36ea1bcedde33362c51d5 -OpenBLAS.v0.3.17+2.x86_64-linux-musl-libgfortran4.tar.gz/md5/6de9e28283dc703e8597cfe81cb036be -OpenBLAS.v0.3.17+2.x86_64-linux-musl-libgfortran4.tar.gz/sha512/9d99cc42bf17ef982c4884774a43beeb2a160db950a31a5b1970dcdac38ffad316bc21830878aae818cfb4235fe486d757c5d67816ffd556b161acbe66c686fd -OpenBLAS.v0.3.17+2.x86_64-linux-musl-libgfortran5.tar.gz/md5/f1ebb2a6447a2a44c52dafe94499b2f3 -OpenBLAS.v0.3.17+2.x86_64-linux-musl-libgfortran5.tar.gz/sha512/9d1b57a4fff907e7f730de7090e285c5158bcda0867730c23e32cfde4e1b4e5d9be27d19df26178d35fc6f578290e43e120ddcd76854df3c9155b6144ab85dcc -OpenBLAS.v0.3.17+2.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/e12409bcb87b4889aef1ee6055193777 -OpenBLAS.v0.3.17+2.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/f93f703bc74ab355b7fd09f057d7cc0de0bc3a21193e7515bdc4601612ae8d2cfdb4afa61c9450db28058c0cf311e93a2c12a0f921633003df7fca0f4a2e47c4 -OpenBLAS.v0.3.17+2.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/80e9374a5c694c62085099d16e12b0c5 -OpenBLAS.v0.3.17+2.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/cb235f5415fbf7b96c5013e9931b5790e15262f2bb65512064af31e1ec31af86f9a64f4b9874ec97c861ed001ebd0602bff860dda0703bf174db80332e77dd02 -OpenBLAS.v0.3.17+2.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/42a455ed7d2f102617f7344684c6b532 -OpenBLAS.v0.3.17+2.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/8e254f1eca11673c859255f257f2015a1fa285554c0697f4602e64770dfa6f7738149d4aadb5f6451cfa2a21c963f61233535ca98af9f0e1b71137eedef99c22 -OpenBLAS.v0.3.17+2.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/d648f4a82c849bb7d6d6a5290868403c -OpenBLAS.v0.3.17+2.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/a80c9d4af3f4bff2803a1adf1439e1894197a4a86660e5c4bb25741be590e81785711022928910267c862c4368e5aea2f645bb159e23c403135019c6be31780b -OpenBLAS.v0.3.17+2.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/3e1be20b44219134e47e816682b0b8eb -OpenBLAS.v0.3.17+2.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/03c64778515e007574c9d14b2dc3dc53dddbb01f6af4872858f5006da446be2ed91b0e07d119651d40d8018968cdf2d3fcc8eebd4834d07b25c2201bb6c3183a -OpenBLAS.v0.3.17+2.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/bc04ffe4100d89fc5eced47d1ac894c4 -OpenBLAS.v0.3.17+2.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/ab8aea7d065a560305821d199d216e3dfe556e3ec1ebfc98507914fab355e2a0231f628fc7fe4c48dffd80d5d4c4a5a90fd540c8ba90236702ef660af635c09e -openblas-d909f9f3d4fc4ccff36d69f178558df154ba1002.tar.gz/md5/4acd59865ca8b50c823bef1354148930 -openblas-d909f9f3d4fc4ccff36d69f178558df154ba1002.tar.gz/sha512/227ee7decccf9bdd2e5754757f590e32ada95b576db9eddc2c74ef06d35aba1db9438acaf57750184baacac741917f7f5ad9f15991d31314480db371fe59cc17 +OpenBLAS.v0.3.23+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/f4ab1aa718db6ab731179199b48506ad +OpenBLAS.v0.3.23+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/5cd6326eab751d087b6638acc256a7c5dfc3a8a4be8949f4e2b5b8079aedc05cd8569774da19912fcbcd2dc1eac6a09d72d19bdbeded1198317992a85ccd605b +OpenBLAS.v0.3.23+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/57b8903e05998d293d28e70ee6cbc4d8 +OpenBLAS.v0.3.23+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/03325728191f88dcfc2bea16d818c0325b4f42019ed9c2e0533233e8e2a4da09a2c70503632fef2ab55ed12b7da39fdab470b801d34a9b6f576bda509f8a8a8d +OpenBLAS.v0.3.23+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/fe529647382de5693557363f658c71b6 +OpenBLAS.v0.3.23+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/77ac56f683a481477fa898d208e67c0c04c1ab8ca9dacb1e4e4ea3795fadb2604faffd1f3fd35d53eecb223c7f92de40cc8b2bdeb9c8a6a1b6a9949965cb9380 +OpenBLAS.v0.3.23+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/5aea8a00a946273a154110ca7b468214 +OpenBLAS.v0.3.23+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/a606933bed17e563d15ac206a4a37d38d75e9bb0bef46ef62485dcd32aa5a0e8501dab01f6887a1e60736c59177c6fbf0ec541fa521a9a8de854f44703f337c3 +OpenBLAS.v0.3.23+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/d81dc2a42a8c0d87f4ee9bad98579f2a +OpenBLAS.v0.3.23+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/f2bda57546f1b9aa1f8dfe9a07b2243cadc002a9ffefbcfdde344ccc96efb07608a55bf8dbb6de34925af03f01ac5487f9fe293befa84edd9a84c01a9b7409e1 +OpenBLAS.v0.3.23+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/400ba512f73a60420aa0d316bc24db48 +OpenBLAS.v0.3.23+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/927c711c3950f24e6b4c22c6dd92cd2b212e3df9241c637ff42f5b9135e7bee8f3864868aea594c6e8ba5b40f0563d63a5f8634ea3c3276bec35d480601e76e5 +OpenBLAS.v0.3.23+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/6a91ea53f3aff17b602b324d025309c5 +OpenBLAS.v0.3.23+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/8ee85883fcc605c16031bafdd0f1a4f4d4a5957a4f85c2022466232f902a4cf64c284537dd2f237221f7d0c154e2b46200501891d3990e94dcf49a74a66c36de +OpenBLAS.v0.3.23+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/c653ff340dc25b19ca36309060dd6b1a +OpenBLAS.v0.3.23+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/cc77c84538bb0301eaa98ca1a32f024da6242e40e847e71f4a36ab69233590422aea41a32ee67031d8055c929f741617053416e5b9d446affa36e7233e5af48b +OpenBLAS.v0.3.23+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/18a914a1df2be07ff6b419617cb6347f +OpenBLAS.v0.3.23+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/eafab27655b0c179ad8b9b1dc818e8394d365f19cf75a0d77402951a38e204aa2fbe580037116a28e8e1254b66d15a543ccd0f438f3ae388e8bcad39f5953c64 +OpenBLAS.v0.3.23+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/4b8d18500b4bdc6f1081da6f0837340f +OpenBLAS.v0.3.23+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/6512bd03d58b7669dba7f9830d3f8654b2747ee66c7bfc05acdbca6c3d2c3750c9d1163768a3f91d56c5a87cb30705ad6f10395652fee4c9cd06cd2920db3027 +OpenBLAS.v0.3.23+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/27fd022a3b84c3a92da9d6062d8dafaf +OpenBLAS.v0.3.23+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/c0e73f2012df2453cc6231a9e7a644609ba1280c9aea63d2cbbf9594539fb26c8f9ab6976de8ec9870cab483b1fe7e3a1fc81246fa99bbd7526051e74a4733e1 +OpenBLAS.v0.3.23+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/e2b0503bf1144f4b6a65ae9f09b25828 +OpenBLAS.v0.3.23+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/204678995b9f337e4ddae793762c3a00968faa3da3433ea17578944fd56f33c381150521b6a561d6ff2022693f8d46b9d0f32f330e500036b4bfc08a7dbd8a62 +OpenBLAS.v0.3.23+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/3e733c1c668a3efaccfde643092595e5 +OpenBLAS.v0.3.23+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/4a37e5de66920f20a648118f62555755b51e6e089e7ee43d2b7b8ec0dc47e68c7705b878158ad83d152cfebf77118f789d1bf7b2ee0702334d4317f0c6a926a1 +OpenBLAS.v0.3.23+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/c653ff340dc25b19ca36309060dd6b1a +OpenBLAS.v0.3.23+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/cc77c84538bb0301eaa98ca1a32f024da6242e40e847e71f4a36ab69233590422aea41a32ee67031d8055c929f741617053416e5b9d446affa36e7233e5af48b +OpenBLAS.v0.3.23+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/18a914a1df2be07ff6b419617cb6347f +OpenBLAS.v0.3.23+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/eafab27655b0c179ad8b9b1dc818e8394d365f19cf75a0d77402951a38e204aa2fbe580037116a28e8e1254b66d15a543ccd0f438f3ae388e8bcad39f5953c64 +OpenBLAS.v0.3.23+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/4b8d18500b4bdc6f1081da6f0837340f +OpenBLAS.v0.3.23+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/6512bd03d58b7669dba7f9830d3f8654b2747ee66c7bfc05acdbca6c3d2c3750c9d1163768a3f91d56c5a87cb30705ad6f10395652fee4c9cd06cd2920db3027 +OpenBLAS.v0.3.23+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/27fd022a3b84c3a92da9d6062d8dafaf +OpenBLAS.v0.3.23+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/c0e73f2012df2453cc6231a9e7a644609ba1280c9aea63d2cbbf9594539fb26c8f9ab6976de8ec9870cab483b1fe7e3a1fc81246fa99bbd7526051e74a4733e1 +OpenBLAS.v0.3.23+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/e2b0503bf1144f4b6a65ae9f09b25828 +OpenBLAS.v0.3.23+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/204678995b9f337e4ddae793762c3a00968faa3da3433ea17578944fd56f33c381150521b6a561d6ff2022693f8d46b9d0f32f330e500036b4bfc08a7dbd8a62 +OpenBLAS.v0.3.23+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/3e733c1c668a3efaccfde643092595e5 +OpenBLAS.v0.3.23+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/4a37e5de66920f20a648118f62555755b51e6e089e7ee43d2b7b8ec0dc47e68c7705b878158ad83d152cfebf77118f789d1bf7b2ee0702334d4317f0c6a926a1 +OpenBLAS.v0.3.23+0.i686-linux-gnu-libgfortran3.tar.gz/md5/639643a12f8018e4be7bb1f9f29e57f6 +OpenBLAS.v0.3.23+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/0993e1967964874a3f90610745d82369ee70fa4313445391fdcb26c4218c6badb18577c67648d2f77f359b163dafde31a3723998e0b006622effeace506b669f +OpenBLAS.v0.3.23+0.i686-linux-gnu-libgfortran4.tar.gz/md5/13ec86d62840258c425b0a5a6824a609 +OpenBLAS.v0.3.23+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/0bc74dac87b8ab5ea244fa5bcd05baf2968b7041c4eb392ff808d0aae897cec4b3082ef7fecda28aea2662b6cd956a5254212740b1802a947dd3f1e5a3dfe2d2 +OpenBLAS.v0.3.23+0.i686-linux-gnu-libgfortran5.tar.gz/md5/413d4eae7b9c409204ab5fb7867dc30f +OpenBLAS.v0.3.23+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/4a484d2aa239d8c1e2733cd9d16bd17549f5048d9958899a4e20039a7efcfd280bba901f3fe63b3b079fd7fae88911f7201a7649a472d47d0148ba8520f350cb +OpenBLAS.v0.3.23+0.i686-linux-musl-libgfortran3.tar.gz/md5/7f342d27a9b193b5d37e2ae4de6e4640 +OpenBLAS.v0.3.23+0.i686-linux-musl-libgfortran3.tar.gz/sha512/2927b18e176e07fe8a05d2eba24f6160680131832094bde9634f0890c1bc3b877c3293163fc65067cea402f3e75871c41b47e4a9999f273e667ac400878aa2b2 +OpenBLAS.v0.3.23+0.i686-linux-musl-libgfortran4.tar.gz/md5/523c007c319adbdde6e8cd7d3d89a9a1 +OpenBLAS.v0.3.23+0.i686-linux-musl-libgfortran4.tar.gz/sha512/ddb7a8d67c9430976ad967e21a6b8717c8a5501e8808fabf6e7b2e7298a0ca56049dcfc12214a5a19dbf7bd52d625b0b2b1bcc6b4c1d921c3ee62fd2766da891 +OpenBLAS.v0.3.23+0.i686-linux-musl-libgfortran5.tar.gz/md5/7dd91db180e59da5f866f73eaccc4d1d +OpenBLAS.v0.3.23+0.i686-linux-musl-libgfortran5.tar.gz/sha512/ff0ee65e536eae5ece7fbc00a0735349d560a142e025084d64f28891bdd3da5914e976640be354d8ad34fd3d89bfb90461eb95f2426d5e292906ed4ead1cfafc +OpenBLAS.v0.3.23+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/fef43c3fed5ed7e9fdd9c7757be6b95e +OpenBLAS.v0.3.23+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/b580c1da073ed94d1a259183c5b2a6896a746c5e88c83e2df57fea801f259cb49f99b3468bbc5c1d7dc6bb84f597843bc3c383c9cab7608dbfbbb15352fb1012 +OpenBLAS.v0.3.23+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/88db137baca7ce99e58ff3b13ee73644 +OpenBLAS.v0.3.23+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/1608f3ee3964df833db9a1277fb9f69e3bb1d328a27482ac419e08520a51b2cb25501cf8986b2ff617bc04881984ce73ecd2b55b0c99afb5cb28f32d24d89052 +OpenBLAS.v0.3.23+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/32c1ca252dcae7d02bcd54d2b00a4409 +OpenBLAS.v0.3.23+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/401126557d3072d965327aa1440eeaf22fdfb1e5265c28dca779d81b94ababd1d487603d55e384f2bac305125c9ed3826f0bb7be99af20b0d18a674a8069ce5b +OpenBLAS.v0.3.23+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/3059083c8293106486a0f28a3564e499 +OpenBLAS.v0.3.23+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/019bb4bc71d7be14f040b36d1b44f653ee89aac680749a6a3b8b72446dffae185dd3d8172ca7ac9aac45cfe564c0fc6cf3221a6f8496b9ba10d04ab44d897b65 +OpenBLAS.v0.3.23+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/648167f83536f32921f1208d09cc8f47 +OpenBLAS.v0.3.23+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/084346b93a99671967433f4ac6548d7b828aa65c402bac7e68aee78bbf75e5cb06b22f42a7d4876fdea3e838162278ee3fcf011fa18530c8d8b0e853a4c6440c +OpenBLAS.v0.3.23+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/9796916fb0acbea2e93747dafa96d496 +OpenBLAS.v0.3.23+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/7c3643c3669fea262907bb5c0f27b492adfec910716498a0bd992d705a544b21023d77801f27c967c07be9d5b30bbd936137c8f59f61632fb16cc0e1f2efebd1 +OpenBLAS.v0.3.23+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/cbf9ad429547ebd1a473f735b6c65442 +OpenBLAS.v0.3.23+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/5e98ec17ee35624bf0a286a2dbe01f5ae4fa879274af70b218080c537a325a92fe76331b746e98b3ce3a0d127df2c03f522f554cb43c169a2b7b1890a9a8a81f +OpenBLAS.v0.3.23+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/28792164b6c34bc627966e338221ff34 +OpenBLAS.v0.3.23+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/433dcec661ff2459740c4d1e72d766549135f6f41a7ffb488502d76751fcb00c3d75aaa0e3db182441ef6b5e3b487a9df3e1b8b979da3681496f4ac6c6ce819b +OpenBLAS.v0.3.23+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/7013b806bfcd2c65582df5f224bd7d86 +OpenBLAS.v0.3.23+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/1078cf5583d158af5d38690acf913db378195b79b4743d977e7654c246fecb0ded4ebee96d89f54c5ec5f04af1b9858bcc0700251ccce1bf7c87926ede069b91 +OpenBLAS.v0.3.23+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/f959117d5c3fd001412c790bd478f7f6 +OpenBLAS.v0.3.23+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/e6fbe9fe1b7a92e10760d2b945bcc2c1c5e8399d729fbbb771764e7b72856707629123bc2d2fed2549f551776f8f0a737b0f414ffddc820a655172d933c10af9 +OpenBLAS.v0.3.23+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/af04d6bd91df5c9bcc63fe06c88a4b79 +OpenBLAS.v0.3.23+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/0cd4972d0a44505f9d8d3958bd20e491c986f55f5f84000ab534020dc8d39d788402355fa51bbd521c8c1bf6884d9d35c1db156bd106a98fbde80c104e8dd5a1 +OpenBLAS.v0.3.23+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/c5e6138630c5b616df1d045e1c388710 +OpenBLAS.v0.3.23+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/a54db7cb7e28dd792bd2c4f33945e7d99db1ee9a620bbe77a21cd7fa7f4cddc5c7744d27116951582f00223df09e7dc2258754032cebd57f61a723762743d3fb +OpenBLAS.v0.3.23+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/7d407633f4f59c305896f9132c098cd2 +OpenBLAS.v0.3.23+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/8a04d46b6dc2eef87d6c4ac43bcdacf5da2b1669bb829c42f07f7f73bc0dba35a6e48f303d1e9cb951062fa2c3a4cce894406c5551c2bac7f57f02d2f92122a3 +OpenBLAS.v0.3.23+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/1d6c7e0b6f3eeedb41ecfea9881d0bac +OpenBLAS.v0.3.23+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/9152b7f584ecc3f06caf0eaf0a496d9e9c16afe41a4750a9bcce0477cd3cabcdcec5c97c24fa3fba03d603148c8a3dcf7199c171abe10121aaee2f8a68b93c91 +OpenBLAS.v0.3.23+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/fdd5c9e5f746403f7ba4789d8d8c47e1 +OpenBLAS.v0.3.23+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/2bd980e1e2021b32f3455fb3fdbae407fb672074ca798664c77e063ea6a7503b625eac7655c8cf25307afbfd9abaa64af52fbb3ed811ff8eb6515e3edcf26b1d +OpenBLAS.v0.3.23+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/8c69d9b7b6fbd0896f839c8979c35a81 +OpenBLAS.v0.3.23+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/d8859f485fa35b33be167dd45f1fe87696be0b12f27dd041087cfbb9df0da94bb726fb9c5f89162405de473969013e3a6a11b0520236db7f5603b25466ebf0d9 +OpenBLAS.v0.3.23+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/40724e1d694288f930a15860650f37bd +OpenBLAS.v0.3.23+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/b7bd75b57803da93d19016f5fe63bd88357aa4e728fdde026a55ab2382957f5a82254b12e701ffb19085a6d1ecc0c0b0c685efb6fa9654e7537f146087cce00a +OpenBLAS.v0.3.23+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/d78352f4e9baf1225aa135b03da9315b +OpenBLAS.v0.3.23+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/aa4d3b0972429af7376e80eab93375ea0368f2f3a31cdbacdb782ff32f7b1c708c5e2d7f1c30ba5b8a7c604a3a7c27a7601fc7f09c8dad2b6dbc54ff099fc0e2 +OpenBLAS.v0.3.23+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/dbf8b0592102b01de80df0767f681227 +OpenBLAS.v0.3.23+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/9bdf9ab9c3ff36281fa501771c4ed932e8a481ffc4cef08725b4877999bd320c99f9c756beba7143050705323bdc0bea150ab3a11e47f3f7c60f206595c37b73 +OpenBLAS.v0.3.23+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/feba9f9647e82992ba310650e3b8ff71 +OpenBLAS.v0.3.23+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/b6c98a5a57764eef4940d81461f9706f905d376d165abdbd0fafbdd5802e34523ad15e6ee75a4550555b7c969630c43438d6cce3d6e37ac95e57b58bcc9d542c +OpenBLAS.v0.3.23+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/732544eb61201b6dd7c27d5be376d50d +OpenBLAS.v0.3.23+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/7b68cceb0bdb892ae74e2744f2a9139602a03e01d937188ca9c875d606d79f555594a5ff022b64d955613b6eb0026a26003011dc17382f019882d9c4c612e8e2 +openblas-394a9fbafe9010b76a2615c562204277a956eb52.tar.gz/md5/7ccaaaafc8176b87dc59d4e527ca4d9f +openblas-394a9fbafe9010b76a2615c562204277a956eb52.tar.gz/sha512/12235f0459469b483a393844c228be5ad4bc60575bbe4b3238198f2480b7b457e4b0609730ce6d99530bb82e1d16fdd2338ceed6d28c952e6fff0da7f571f863 diff --git a/deps/checksums/p7zip b/deps/checksums/p7zip index 0df5ed96067b1..b3c24a811a043 100644 --- a/deps/checksums/p7zip +++ b/deps/checksums/p7zip @@ -1,34 +1,34 @@ -p7zip-16.2.0.tar.bz2/md5/a0128d661cfe7cc8c121e73519c54fbf -p7zip-16.2.0.tar.bz2/sha512/d2c4d53817f96bb4c7683f42045198d4cd509cfc9c3e2cb85c8d9dc4ab6dfa7496449edeac4e300ecf986a9cbbc90bd8f8feef8156895d94617c04e507add55f -p7zip.v16.2.1+1.aarch64-apple-darwin.tar.gz/md5/12485086522a08b62dfef503b61af007 -p7zip.v16.2.1+1.aarch64-apple-darwin.tar.gz/sha512/dc9d92b294a65c55d8742b33df0d905a8cd1e80500647b33537fd404167aaa43a01280bb19035a9e4da94dd49c6ee712a0fbf455b9661af487e1c35a9a09eda7 -p7zip.v16.2.1+1.aarch64-linux-gnu.tar.gz/md5/35a760ced992c9cd4c6085e40394299b -p7zip.v16.2.1+1.aarch64-linux-gnu.tar.gz/sha512/da3123601db48cead255240e048e33de401de52cbddddbc1e109dd7b3b36645251008108c7545abaf09e0b2803198ac4067b00a3f0ff7fe31f65a5de4ce49710 -p7zip.v16.2.1+1.aarch64-linux-musl.tar.gz/md5/f795313bc73c9f635a63861126c838eb -p7zip.v16.2.1+1.aarch64-linux-musl.tar.gz/sha512/886b0e9e2476915be2c7106a8fb4547e9326d50fad93d8002ca97f4e35a856fee43a2350b48404f112938da6fc19255cb2dfb31e3112c74551d4a3ccb44a7fbf -p7zip.v16.2.1+1.armv6l-linux-gnueabihf.tar.gz/md5/644ed1b6a5d7bb16407cea5264ef45ce -p7zip.v16.2.1+1.armv6l-linux-gnueabihf.tar.gz/sha512/3cbdb56faca44ac2a3ea4cba35b8913811a2d3602a689496228968fb17c23b191ab3e01b43f619526cd8ea0f33c5a4453d2b5cca7437026e54b2c164acb1e8ee -p7zip.v16.2.1+1.armv6l-linux-musleabihf.tar.gz/md5/219fdda71c08848844b4630e613bf35d -p7zip.v16.2.1+1.armv6l-linux-musleabihf.tar.gz/sha512/419297b14aa820f8f49d6add367fe3a7153be18546e41e9f8bf6bbddada7535301dd3ea524089981046fc739b8094cff9113fb2aeca2947e796a8e6b74414245 -p7zip.v16.2.1+1.armv7l-linux-gnueabihf.tar.gz/md5/919e6508e4b2adb82fa2493a805875e9 -p7zip.v16.2.1+1.armv7l-linux-gnueabihf.tar.gz/sha512/cf8f58ee590e23aa6fe348b639f2b052fbc0ed52ecf7ce1e370f7dc3255e47727ef65a109b14cd045d59201ef8a5b426eb05b167967ce95581a35df7a6b67400 -p7zip.v16.2.1+1.armv7l-linux-musleabihf.tar.gz/md5/8bfb81a9a4d31ac9f05b59c19490461e -p7zip.v16.2.1+1.armv7l-linux-musleabihf.tar.gz/sha512/6b13c1971e7049613aefd4a2bad64d534ffc7293efb037b2da92e23754462fc3872169399f3a9fe34bc337b900ecc4fccc878e3e54067238b3f890c09f8e05f0 -p7zip.v16.2.1+1.i686-linux-gnu.tar.gz/md5/f62eefb6fb2724082933e95d706b232f -p7zip.v16.2.1+1.i686-linux-gnu.tar.gz/sha512/43a669bb64e0318c16feade75ade6e4ac73e056fb33479268e217310fa469a8f535ace13b8ade45495d96d8a540e1c247dcdb8fd7044c8096693f3766f00224f -p7zip.v16.2.1+1.i686-linux-musl.tar.gz/md5/8a80bbfcb8c4a05d6c56539640a7bfaf -p7zip.v16.2.1+1.i686-linux-musl.tar.gz/sha512/38ce14788fbfd964fa446c98c89ecd3854c732f5529406d6d650d8f0ac4a657caeea8ae2985370f5cee129d974a4bafa8cd164fd1c11ae0cad5191e9640534f0 -p7zip.v16.2.1+1.i686-w64-mingw32.tar.gz/md5/d55077826cdfe69747efd4fd53b81e18 -p7zip.v16.2.1+1.i686-w64-mingw32.tar.gz/sha512/71ee03bbb9916eff2e7807ff25d1c1992c209506c4602f570095ee0cd12355ed4590d77dfd090085a109604c4cbad221154bfd55d5fd79bf35c76b3b43c67a25 -p7zip.v16.2.1+1.powerpc64le-linux-gnu.tar.gz/md5/16682edc596bc1f7d6311339644070fb -p7zip.v16.2.1+1.powerpc64le-linux-gnu.tar.gz/sha512/09c3bfbae7c4ab2757fdee0dac4baf71f6fa7b99aab48c5260ed9481c5e7b05317f7a6d466c543ffe46318281011b61c5652fef33466c02a5b24b3c39d92137d -p7zip.v16.2.1+1.x86_64-apple-darwin.tar.gz/md5/6d7873510fca444740ab2f4ae701ae3a -p7zip.v16.2.1+1.x86_64-apple-darwin.tar.gz/sha512/e6fc0c669b62eb2e6f11d07e840ce44beb6c8981750ac4fb5d7401cf00916465f97f8b3a49c73777d893752a7df9bed8bf40068fe7339df88942a21aff4e9d2a -p7zip.v16.2.1+1.x86_64-linux-gnu.tar.gz/md5/2cd2efe4d51967ac8acf24a6f2c80893 -p7zip.v16.2.1+1.x86_64-linux-gnu.tar.gz/sha512/a0fdf061b5d7da97134eee7fc9afb468d8bee01108843814432d318d2b5c6217772e62700a015d5be41010ecf7b613218ed9e8ea6e2da2a24d1e5c15a1734a59 -p7zip.v16.2.1+1.x86_64-linux-musl.tar.gz/md5/f5a312e21abd7f24100e91eefa875c7f -p7zip.v16.2.1+1.x86_64-linux-musl.tar.gz/sha512/034b00d0685da5456b91f45c0b4196e0aa21436e67ecd7a09318a578a814491774ca5c2ce2c49f6b17e1665d9c8a896a0f2f6fca6d3260208ad8be44c1dce656 -p7zip.v16.2.1+1.x86_64-unknown-freebsd.tar.gz/md5/1e647ff7fd8bf2dfdcdd569c743e9c8c -p7zip.v16.2.1+1.x86_64-unknown-freebsd.tar.gz/sha512/e868eb1bab65ff383177ed0e929ff0db084df1f4b144430098f25cb8df788696113fe466ecf756c4ca61439fa8eed8c8a3fc396aec2972bea6ec7b3b0be51baa -p7zip.v16.2.1+1.x86_64-w64-mingw32.tar.gz/md5/70d58fe372550313b18437f58cd249e1 -p7zip.v16.2.1+1.x86_64-w64-mingw32.tar.gz/sha512/1908d3dfd218e33c8e85366e02d920e237111b5fdb8bf028d8f7a2029ec7292c465d4d0ee50f58ef186fa8c83bfe33ea98d0bacdbcbb9c345b71eeb038cbda89 +p7zip.v17.4.0+0.aarch64-apple-darwin.tar.gz/md5/af8134ed9c24b99d69e4edb4d5226ca5 +p7zip.v17.4.0+0.aarch64-apple-darwin.tar.gz/sha512/b8bb6aee60a54cca37568af8b2d9baedd892ba0d4918b93bcb29d74189524af7115901f4fabafb1ca58ed17e97c59846fcdfbd460abc81059806802b0a7be840 +p7zip.v17.4.0+0.aarch64-linux-gnu.tar.gz/md5/20abac5ebb99f31742878013c02f96a3 +p7zip.v17.4.0+0.aarch64-linux-gnu.tar.gz/sha512/6d8ebf895b969b1f707d0c23a19db4cd0dee47957d076e6e389395e09404d55bfcb78bb14bb67bb35b93b6a0072f2b4f097d839503d1ccab62b4ce28939dc71d +p7zip.v17.4.0+0.aarch64-linux-musl.tar.gz/md5/185c979c7419b7ded3832c0f5cfd3b77 +p7zip.v17.4.0+0.aarch64-linux-musl.tar.gz/sha512/722e880c9f111738cb4cde84bf62c36892dbefdba625ae2b9e0fae76a7b1eabfa481a9838fbf9667223f19f62b6f09fcfd42b50c2bff7a65af0fae3616250fc7 +p7zip.v17.4.0+0.armv6l-linux-gnueabihf.tar.gz/md5/dceb37181763f86bf12f8ca473cf3403 +p7zip.v17.4.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/51e409bbcd3c54838cb3219b2476c8b45c8340e0a2fd26cced0d8484ae7f51711723e06e9023fce9ae9a1b51b5fb94aba536428ce2a5c5902b38498a0b3c2b50 +p7zip.v17.4.0+0.armv6l-linux-musleabihf.tar.gz/md5/193ecd888787ea03a500d102a7e33afa +p7zip.v17.4.0+0.armv6l-linux-musleabihf.tar.gz/sha512/d525aad33f5ed27dc993f31c6db2996b830716bfac9bc7c49cb462ea3f0b412d0d3267765b9952c85e9c9be31d36d095d55ba89c0fa2c92823d9490372389c95 +p7zip.v17.4.0+0.armv7l-linux-gnueabihf.tar.gz/md5/096f11a7f1af5ff730bb8cfef22e335e +p7zip.v17.4.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/1866ffd0169e0795594aaa70f1af8102ebbd79b3cafaadfb9c6a537dac0cdbb6eb7c31ad5165a975508c1b850744f94b60d9c530d658cdcc5536a474203cff21 +p7zip.v17.4.0+0.armv7l-linux-musleabihf.tar.gz/md5/fef1576982f45d1922582f6f7a7d6665 +p7zip.v17.4.0+0.armv7l-linux-musleabihf.tar.gz/sha512/71061585b32fa1a8e0a403a60c07e9f90586291a9799d7e2d6f7e6ec9f7b0ebf4b45ed080efd87cad82c45f71ec9a14cbcf9134a73bad4f5e3329f23bc6df01a +p7zip.v17.4.0+0.i686-linux-gnu.tar.gz/md5/8818389b3bf00f10c6a39fe0c4a331b4 +p7zip.v17.4.0+0.i686-linux-gnu.tar.gz/sha512/bec2051a258f7e8a762b7cd4324e7b8f00fe5d99d48f05fb3557c41604e8b08af9ab66ab830f4a48086656be41aaf011b2aae0fb530e0ffefec38689f85a3bb5 +p7zip.v17.4.0+0.i686-linux-musl.tar.gz/md5/4ed9c16a65ed1d656aa214013e46eb28 +p7zip.v17.4.0+0.i686-linux-musl.tar.gz/sha512/7a5b3e15d0038bea0de7fc28ce058d7f93b8e04f271e30953a6b52d2b5d71f59d10177033e888a50cf8dfeb4f44bcf3271c9b9d1b28d0122ab2b239decdad446 +p7zip.v17.4.0+0.i686-w64-mingw32.tar.gz/md5/d06cff2ec0b7c8415700587f931ce1ac +p7zip.v17.4.0+0.i686-w64-mingw32.tar.gz/sha512/ed72440f5306a57465a70b00bff33185a83c3e223844a79aa0b0d1fbe30dbd35da75e6188725aa621f5c4574a09527daf1e4893c7c6979ab91b2c09b4979dbcb +p7zip.v17.4.0+0.powerpc64le-linux-gnu.tar.gz/md5/949ca7d111e497b82c9c762e5ac63a6b +p7zip.v17.4.0+0.powerpc64le-linux-gnu.tar.gz/sha512/4842e0d44bf6380100723209596f526181fefe8a81d59c28658d03ea16600e71d010d5c7898b4c943efdd9caaa2301c3fdb0dccb343d631d1734acda1c559f65 +p7zip.v17.4.0+0.x86_64-apple-darwin.tar.gz/md5/2322c7a08f62592ca394a716949008bc +p7zip.v17.4.0+0.x86_64-apple-darwin.tar.gz/sha512/9549f3e1052730ce13414636b32f0d1a9a1ac944a2b622380eac0da144b11fd65d437afe877ba6797d651da9c4ec77f0ebd3e515146caceaa2524829419eda48 +p7zip.v17.4.0+0.x86_64-linux-gnu.tar.gz/md5/a21b12946a62ef3688d5fc965974e8f7 +p7zip.v17.4.0+0.x86_64-linux-gnu.tar.gz/sha512/d32faeac23acf8a023f65350ba1d62bb3d9f904e32570ae03b8fb0a5375758784dd95be8caeecd007cbde40e103854a077e2c817f62afa72491f3b8966deb738 +p7zip.v17.4.0+0.x86_64-linux-musl.tar.gz/md5/c448e872d4ad66beb2d46d9134952f2f +p7zip.v17.4.0+0.x86_64-linux-musl.tar.gz/sha512/92588f4817e145ef655c718dec049e7f43dd93644f43f19cd320643fac5f5b2312837c7a6c3e782e97fd08747311c58ed4657484f8bc778942fc5206ff8ea4e5 +p7zip.v17.4.0+0.x86_64-unknown-freebsd.tar.gz/md5/2cca6259a2eb1b0fea777d566267bf05 +p7zip.v17.4.0+0.x86_64-unknown-freebsd.tar.gz/sha512/92f90e2be4a8b8fcd80a4ceacac8bbab750913526b85f9279f8ee9ed91b77248b5de2d35d0c6241d0ad51fda185f4cb1ead1dcc9d23e2bef35e0b61efe3c3170 +p7zip.v17.4.0+0.x86_64-w64-mingw32.tar.gz/md5/5d272c78d7ffb40da0f333463f3cc098 +p7zip.v17.4.0+0.x86_64-w64-mingw32.tar.gz/sha512/2d999c6df4786cec1bba396b3a651a63740f4b799e9fc11754afd24438076e898daae74b4d3c7072450428e89881991e8884711cd4c349879a00c7aeeb4e1d3e +p7zip-17.04.tar.gz/md5/00acfd6be87848231722d2d53f89e4a5 +p7zip-17.04.tar.gz/sha512/ad176db5b657b1c39584f6792c47978d94f2f1ccb1cf5bdb0f52ab31a7356b3822f4a922152c4253f4aa7e79166ba052b6592530b7a38f548cd555fe9c008be3 diff --git a/deps/checksums/pcre b/deps/checksums/pcre index f7e1fa0c1a3ba..cab79abe745bf 100644 --- a/deps/checksums/pcre +++ b/deps/checksums/pcre @@ -1,34 +1,34 @@ -pcre2-10.36.tar.bz2/md5/bd7e7421ff3fa2e2d5429229ecfad095 -pcre2-10.36.tar.bz2/sha512/fc2a920562c80c3d31cedd94028fab55314ae0fb168cac7178f286c344a11fc514939edc3b83b8e0b57c872db4e595fd5530fd1d4b8c779be629553e9ec965a3 -PCRE2.v10.36.0+2.aarch64-apple-darwin.tar.gz/md5/12ac3bee39df3a79f868f6463964953b -PCRE2.v10.36.0+2.aarch64-apple-darwin.tar.gz/sha512/a1a1312931deb7f742f80886188babcf9c179ed3f156626fb23d92633fde896d1ee9b2d72cd99ae4a1f8048971b6d939e9b0b10c455d4eeec24b265968593486 -PCRE2.v10.36.0+2.aarch64-linux-gnu.tar.gz/md5/32240ccddee3040aeedcbe69ea52fcad -PCRE2.v10.36.0+2.aarch64-linux-gnu.tar.gz/sha512/86fb9febd186fcaeec83d2ed336fb060d7e49c7b7efe1bd8a6d2d74023ddbcce04eed5cf0e5d15348313eb2b51cd6b27763c08f7b9cf4eaf9df22d88f9405ef8 -PCRE2.v10.36.0+2.aarch64-linux-musl.tar.gz/md5/06abf8210e597a8669fb371da73865ce -PCRE2.v10.36.0+2.aarch64-linux-musl.tar.gz/sha512/063edaa92e36468a8cf70ca9e25d9004586400a5304c0e91b067788825cbf5354e0190cad951f163e318b65d0f3f915f1944d03de61a5627ead2ead2674d3279 -PCRE2.v10.36.0+2.armv6l-linux-gnueabihf.tar.gz/md5/70ca2acdd5b1524141f15d02d26c3b1c -PCRE2.v10.36.0+2.armv6l-linux-gnueabihf.tar.gz/sha512/377fdc5fd8b771027ffe8c0871e1688f8d991caf930b26b397eae01504af2fad5bdfbe2b3af33f25cf4b5c7bfd73dc77b16b65882a7846803a00edc0968ccef2 -PCRE2.v10.36.0+2.armv6l-linux-musleabihf.tar.gz/md5/860180f0a15ad38fac20590fab177718 -PCRE2.v10.36.0+2.armv6l-linux-musleabihf.tar.gz/sha512/412e7b0355a7bcdecca4ff5f85a1c6af1eeb094a9f07c2e90de105a0e0e6acedcbca146b5c136509ef8b38666f645b0c06fc68676dd8b1b70e2c7af4b070eb3d -PCRE2.v10.36.0+2.armv7l-linux-gnueabihf.tar.gz/md5/12fd561c00fc7fca14e577ed54525740 -PCRE2.v10.36.0+2.armv7l-linux-gnueabihf.tar.gz/sha512/e5655e5c3f96a3a95699be534acbd399bc29873fa1064f50c2d78c43ad8e85a1fbf9039bcb674a88ecdb9bf5b468f9ecdf9a79f0dce5d95996f99d6c700da79a -PCRE2.v10.36.0+2.armv7l-linux-musleabihf.tar.gz/md5/97d5eab8806a1920e6fd30f82db1b754 -PCRE2.v10.36.0+2.armv7l-linux-musleabihf.tar.gz/sha512/827fc45049a4b3adb6de2ab0569e45dd5e8749c09c42e57c579d3d6350f0953f6ad4fae1ba71af7347c9271ffff805a0200b5c9418e7f1894a6bc17a4fe0071c -PCRE2.v10.36.0+2.i686-linux-gnu.tar.gz/md5/d7c9fdbcf3055c4745ea93a9274e16d1 -PCRE2.v10.36.0+2.i686-linux-gnu.tar.gz/sha512/ac0edd5d5910e7948a65c2a5c9fb05d2a6beb3f9bd875ea87433b910444bcba617ac5bc215fa0f101cbd7c5556966de7593080674cfaf28fdc8784e2485cf71b -PCRE2.v10.36.0+2.i686-linux-musl.tar.gz/md5/05ef7559eba68cecbad0f2c75c017640 -PCRE2.v10.36.0+2.i686-linux-musl.tar.gz/sha512/91603d596a1b70bc4a933f9151fc791e09a167e4ad2de442a7ff9c355a329353cc9fb3148cf75639eaef0de3cf4f71212525f1040b0eff63c5d884892814b7af -PCRE2.v10.36.0+2.i686-w64-mingw32.tar.gz/md5/8015e6633bf0f4c359f85445d4a98a9a -PCRE2.v10.36.0+2.i686-w64-mingw32.tar.gz/sha512/527183fcc473c8e3f04622701cf73a55c5df132713e8230cd0bfd484023da594a9e29f5745d384f1e1015b8efac96e88bd985b06af5901b0d3052f90af8d89d6 -PCRE2.v10.36.0+2.powerpc64le-linux-gnu.tar.gz/md5/2ece20fa11fdbae393fb85a41ee1e17d -PCRE2.v10.36.0+2.powerpc64le-linux-gnu.tar.gz/sha512/e6fbc03efed53da43b3b15b31cc0fbd85aaf5cc65564392b8c7bc02695d3a32fe832880d547c37b3a508197a4d4023be0aef910cd36da69a54ee184880cc0438 -PCRE2.v10.36.0+2.x86_64-apple-darwin.tar.gz/md5/26c560dd16b460a1ac7c81807edbacc6 -PCRE2.v10.36.0+2.x86_64-apple-darwin.tar.gz/sha512/ce56bc399e204e4b437d3f398b4e68c33d9c55ec990126523f3be0b14571603eea3b3104e1909deb22eab3f5302da72fcc690d1a279cb85ef598c42a5ef9a8a9 -PCRE2.v10.36.0+2.x86_64-linux-gnu.tar.gz/md5/474dec882abefcb56febddc309ed4682 -PCRE2.v10.36.0+2.x86_64-linux-gnu.tar.gz/sha512/882898c2d6cab8cd5ecf1027388bd08ddd1fec2339b45388786f98c53518bf7ca56f9e2cccb4a5ede953cc85e6c1cc54a5a00f80ece4cbfdc17e5f6116a9976a -PCRE2.v10.36.0+2.x86_64-linux-musl.tar.gz/md5/af6d90c071437c5529306a5bafe6f6aa -PCRE2.v10.36.0+2.x86_64-linux-musl.tar.gz/sha512/92a16960d7514c829a5f372a40472c87c717d49e9694030ae0cb39106d6530f5bb169155a74a416bf340139f9dea231ddc2b7ae6e54fcb935f6a9bf672b5e0c1 -PCRE2.v10.36.0+2.x86_64-unknown-freebsd.tar.gz/md5/97410029c0b6ed5f7fb0d14e1f1215ea -PCRE2.v10.36.0+2.x86_64-unknown-freebsd.tar.gz/sha512/229e910759da2959ddef83ca89e05a050c266b8e755c85dfce6a786658be541911c3b78a0fca7dfdee1b41fbbdccf57da75cf9fe45fd2821dba8d2aaeabfd538 -PCRE2.v10.36.0+2.x86_64-w64-mingw32.tar.gz/md5/39827564bca329768e0380bd79b869fe -PCRE2.v10.36.0+2.x86_64-w64-mingw32.tar.gz/sha512/4579049b99fca3334d726b0ca1f07524d1643a758e375b5b02b8f294ba7d9c2a4130da1a1523de29033233a8848105b3cb660e15bb4a759593405d805ee99883 +PCRE2.v10.42.0+0.aarch64-apple-darwin.tar.gz/md5/667a570d341396c3213749ee1e5b5fda +PCRE2.v10.42.0+0.aarch64-apple-darwin.tar.gz/sha512/c1bb99e8928efded9b0ea3f294ceb41daea7254204ca30c0ff88686110ccd58138d8ea8b20b9a9d6d16a6d8d3f34e27e74e7b57d3c8fe6b051c9d8fa6f86431a +PCRE2.v10.42.0+0.aarch64-linux-gnu.tar.gz/md5/1a758f275ff3306fbad7698df7b9b7be +PCRE2.v10.42.0+0.aarch64-linux-gnu.tar.gz/sha512/d09508c0b255366d01f1b4d1ae6748a8e47f18c451498d30715f5f968784990949dab7540cd086396abd912f61b5f7c44c8c72a27efaba0a7fc08b71a167c057 +PCRE2.v10.42.0+0.aarch64-linux-musl.tar.gz/md5/e61147579fdc9b57a61b814bdf9c84bb +PCRE2.v10.42.0+0.aarch64-linux-musl.tar.gz/sha512/eecaf4c1937fc04210b910ac65318524c02d690e8c4894c38e74eaba36d26c87a1fd9e1cc36f4307a11ff3552a79f081fa8f05085435eb34872dc2fdecce2d18 +PCRE2.v10.42.0+0.armv6l-linux-gnueabihf.tar.gz/md5/b4c484a3b87923c0e2e4d9cc5f140eb7 +PCRE2.v10.42.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/5931cf13d340971356a9b87f62c9efdb3656ba649e7b25f1722127a3fd70973d94c815a37b43cecab8eb0ed8d1ae02ef1a0c0a12051852c1b9242c3eaa01c496 +PCRE2.v10.42.0+0.armv6l-linux-musleabihf.tar.gz/md5/bc7b5bb1c5b0b99c121bad5a89299ca7 +PCRE2.v10.42.0+0.armv6l-linux-musleabihf.tar.gz/sha512/86b5ad4fa6f4b5bd1a76ad68ddff4b39916d0ed0acc03a3fee8eab5256aaed53abc0ff4ce9d9d9f8b9203c087211684da92fe6aa06ff5bc331ba1b3da2cba57e +PCRE2.v10.42.0+0.armv7l-linux-gnueabihf.tar.gz/md5/3541eb26fa5a4d13e2c7d063dbd900d8 +PCRE2.v10.42.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/872181f931662edaf653351486c5e2a700e94cfa0966ca90eca893fdc75dd46eb40d9d45737c198aa4b9ad8ebab33fd78697ef35906985e4e1c9748ddf58d363 +PCRE2.v10.42.0+0.armv7l-linux-musleabihf.tar.gz/md5/fe059feb18fcc9312f1033362070fe34 +PCRE2.v10.42.0+0.armv7l-linux-musleabihf.tar.gz/sha512/5a96acf3908c964ccb4f296c449499388ed447d9a094c2760c979e02ef656fa710ede3926b9626e89fb5b0545c111e6eedff21e48416e923c17fc9ff129d0519 +PCRE2.v10.42.0+0.i686-linux-gnu.tar.gz/md5/67f49cb139017109c422c51c0120823a +PCRE2.v10.42.0+0.i686-linux-gnu.tar.gz/sha512/8873d9995bdf5701fc5a24163f93eada12af76d09781a679a4ed61b66f117cf322505d291931d1c58b3b3eb560f6487a1100b0735c14abe6cb38677750b481c7 +PCRE2.v10.42.0+0.i686-linux-musl.tar.gz/md5/092af10d8182cb4240cdd975efce4d7c +PCRE2.v10.42.0+0.i686-linux-musl.tar.gz/sha512/79a48f4fd50ffdf49c8d57581e01ace38c1b3d7edd86d44db44b8efd93074d16faf035131a0d60c6631b8bf22f0fd8296acedba45908da56e8096c296122f047 +PCRE2.v10.42.0+0.i686-w64-mingw32.tar.gz/md5/2bb13db8b5d6d1a5632de3db874c2614 +PCRE2.v10.42.0+0.i686-w64-mingw32.tar.gz/sha512/7d1324696087c32d1bbbb64f5e4b8c8a220ef216d025886b3c3e6d685c3f701428c6696d7ae0bcc771d3295381ba2bdd5db040f788f8a9a58f80ad4d790dd141 +PCRE2.v10.42.0+0.powerpc64le-linux-gnu.tar.gz/md5/0de1215b2a1e9c0efd131355e9fbf2c1 +PCRE2.v10.42.0+0.powerpc64le-linux-gnu.tar.gz/sha512/69dae12627685ae665db8c91264a79aba7c60ae97eccdc79ef889f2a5f69b465fa333aba298fc90bbb95710cfc324e3630bc427a97577855e8fb6c8fe227cfec +PCRE2.v10.42.0+0.x86_64-apple-darwin.tar.gz/md5/c5c52b399921c5ab81a5f598b350d2ca +PCRE2.v10.42.0+0.x86_64-apple-darwin.tar.gz/sha512/e6c8ba3aa3fbf54b37079301ab317104c6852812b23835f52ca40f31f0831678172d32e077fbaa712a8a2cb16d62bb97d475827004353e7807922a2d6e049b28 +PCRE2.v10.42.0+0.x86_64-linux-gnu.tar.gz/md5/b074dd1f85e24e723349e566350e2c78 +PCRE2.v10.42.0+0.x86_64-linux-gnu.tar.gz/sha512/236017e02c9f32b913b772dbf22897c8460e5791f196c86f8a073e329ad8925f6859afe48f3bf18ca057c265f08fedbde255360d8f859e2303c6569ab1b0e1bb +PCRE2.v10.42.0+0.x86_64-linux-musl.tar.gz/md5/9f32ca77e79843fc9c4b5fc8ed336d11 +PCRE2.v10.42.0+0.x86_64-linux-musl.tar.gz/sha512/334a31724e9d69c6517568d922717ce76d85cf87dbc863b7262b25ab43c79734b457833cd42674eb6a004864e5c74da3ae1d0a45794b4cd459eea24d9669fac5 +PCRE2.v10.42.0+0.x86_64-unknown-freebsd.tar.gz/md5/037bf13e9a53eb90846b6643610a17df +PCRE2.v10.42.0+0.x86_64-unknown-freebsd.tar.gz/sha512/64bc9acda3d158621f442aa2e766730cc425df3795965f461b530d8152934ffaf93d75b86ebc483345b78b203b0502857683c183ec65a01da1834b55405c7f77 +PCRE2.v10.42.0+0.x86_64-w64-mingw32.tar.gz/md5/6b04c3778bf02947cb1b7e70a41f3292 +PCRE2.v10.42.0+0.x86_64-w64-mingw32.tar.gz/sha512/9b808832cc48703ed525eca06d1dd0162dae3f94a9ad72d044876edcb86a90e8443c8b169e60ccf3507d5960156c447d8f3f30e586ac2a22b6d43dbe807009d0 +pcre2-10.42.tar.bz2/md5/a8e9ab2935d428a4807461f183034abe +pcre2-10.42.tar.bz2/sha512/72fbde87fecec3aa4b47225dd919ea1d55e97f2cbcf02aba26e5a0d3b1ffb58c25a80a9ef069eb99f9cf4e41ba9604ad06a7ec159870e1e875d86820e12256d3 diff --git a/deps/checksums/suitesparse b/deps/checksums/suitesparse index c4d7a7bd7b70c..65db184c5cbca 100644 --- a/deps/checksums/suitesparse +++ b/deps/checksums/suitesparse @@ -1,36 +1,36 @@ SuiteSparse-5.10.1.tar.gz/md5/68bb912f3cf3d2b01f30ebafef690302 SuiteSparse-5.10.1.tar.gz/sha512/8f85c6d63b76cba95707dfa732c51200df7794cb4c2599dbd92100475747b8d02b05089a47096e85c60b89bc852a8e768e0670f24902a82d29494a80ccf2bb5f -SuiteSparse-f63732c1c6adecb277d8f2981cc8c1883c321bcc.tar.gz/md5/baeb73b8ac38dd04174ed04fa1ea8cef -SuiteSparse-f63732c1c6adecb277d8f2981cc8c1883c321bcc.tar.gz/sha512/a95e6ebafe948f419a65a9630b01cda380f3ce19499afe57e212a75dd43aa7a09ddd038e90d1215ae55566a676e392e696565d2d7a96853ec4fca7f73762b268 -SuiteSparse.v5.10.1+0.aarch64-apple-darwin.tar.gz/md5/b9392f8e71c0c40d37489e7b2071c5ad -SuiteSparse.v5.10.1+0.aarch64-apple-darwin.tar.gz/sha512/109d67cb009e3b2931b94d63cbdaaee29d60dc190b731ebe3737181cd48d913b8a1333043c67be8179c73e4d3ae32ed1361ab4e34312c0f42e4b29f8a7afda3e -SuiteSparse.v5.10.1+0.aarch64-linux-gnu.tar.gz/md5/1b2651ede4a74cd57f65505a65093314 -SuiteSparse.v5.10.1+0.aarch64-linux-gnu.tar.gz/sha512/753f986a749d139f9a6baedac059d8ed8efdd716ed28eacdbf00e6ebe863b4e17467f01a9693dcb39571d38b4b5c4c1375dbb790b88a7e704116e3fe83f7ff3e -SuiteSparse.v5.10.1+0.aarch64-linux-musl.tar.gz/md5/051ff9bbbc95c57d58563df8a2c8eedd -SuiteSparse.v5.10.1+0.aarch64-linux-musl.tar.gz/sha512/855979ed8d6290c529d9c9e82944fb15c88f9d9d8da7db1fa2fc34efb0ed985fc6554312882107f26956f2a18ae985918909cd834e068b874906c21a0f53b6c9 -SuiteSparse.v5.10.1+0.armv6l-linux-gnueabihf.tar.gz/md5/dbc5fb4844077084663612af26e180ce -SuiteSparse.v5.10.1+0.armv6l-linux-gnueabihf.tar.gz/sha512/b906f7275ab58006acd52927e7e04c79eec59b5f28e9d7e5d5b8556c0eedd54cfff87e494373702c205afa2384ee6b0f2bb5e811fd440b1b50d5c9eee1b47b99 -SuiteSparse.v5.10.1+0.armv6l-linux-musleabihf.tar.gz/md5/7770d256e76d5ce1484c3781508cc3ed -SuiteSparse.v5.10.1+0.armv6l-linux-musleabihf.tar.gz/sha512/4f1d46cc8da5a7eff665b4bb96f9e21319f39231f98a6164d8c3d654d5b6f93c3e4477f55a39a80b7f8125a78d690cc5a1cc58f29143ba4c109a4182d7fa2110 -SuiteSparse.v5.10.1+0.armv7l-linux-gnueabihf.tar.gz/md5/ee1fa978bcfb264842749f915bbefd77 -SuiteSparse.v5.10.1+0.armv7l-linux-gnueabihf.tar.gz/sha512/9592a42f6474fd89eea1144f62ecc2a23796ad251173a9c36ccbc9bc18dd88687ce49f51528974f56b5652e2ab15f0aa41634513f2cc0b3c54259de3b68350bd -SuiteSparse.v5.10.1+0.armv7l-linux-musleabihf.tar.gz/md5/30f708421b92158c7741c82576e9047b -SuiteSparse.v5.10.1+0.armv7l-linux-musleabihf.tar.gz/sha512/d8793d48757dbb62aa7a21c215b6d6e63a26ce4ba740f1f7f42a3e485ad3d9628744f021ad9cc96e29c8c88bfb2f02ea92865c26b971ca739d3c05c7f28875d9 -SuiteSparse.v5.10.1+0.i686-linux-gnu.tar.gz/md5/9018b6168b9a687bab0c9a9cbf45afba -SuiteSparse.v5.10.1+0.i686-linux-gnu.tar.gz/sha512/308a92f441af6855517c40c6871b4935251677c05cc082c21fd1249e0137b635fa524f60cad61c7524026301a6de7ffea0ad1f4b9a4d9d6e3ced3f332a6719d4 -SuiteSparse.v5.10.1+0.i686-linux-musl.tar.gz/md5/99143f8d6de4f071ffa19942252b6dec -SuiteSparse.v5.10.1+0.i686-linux-musl.tar.gz/sha512/9fb719fffea03296dfac8bc221bafc3ed8f7791749eca6c4b00265994de1be5d242e7e5184693603c745b39c4538feb11ab283204e0e33df2745f904cf0c7252 -SuiteSparse.v5.10.1+0.i686-w64-mingw32.tar.gz/md5/d049c943fbda2c8380dea33e16569275 -SuiteSparse.v5.10.1+0.i686-w64-mingw32.tar.gz/sha512/174768464432b991ecff88d5e5126caca83672fb5173115de59bc2387ef8aa75a56d3e84957fce625fabaf50ba462549f2ea828aea7258be7513835b7fea2e31 -SuiteSparse.v5.10.1+0.powerpc64le-linux-gnu.tar.gz/md5/f01f7e134f8ee77849f3a46e773c1ff2 -SuiteSparse.v5.10.1+0.powerpc64le-linux-gnu.tar.gz/sha512/dc0339f2b35f05d49fbd1dcf1822c774a07af122fabc8e00eb7435dc53fcf82b3c1ec24e2bb41b1a58d3f8ab8903830eb7ece19dc6fce3f5e73d90a3dc3c4194 -SuiteSparse.v5.10.1+0.x86_64-apple-darwin.tar.gz/md5/02975a8670660c5e79eab0a70b051a0b -SuiteSparse.v5.10.1+0.x86_64-apple-darwin.tar.gz/sha512/e55685ed7a63318c5baa326795503f13f031e0a617c045c972d5c89252ab51e7325e2b0425ca10dfbd59e79c5b4200545f5a4944fddd376e7610b6ebf74ded14 -SuiteSparse.v5.10.1+0.x86_64-linux-gnu.tar.gz/md5/6c111d315fb25c529710722bd5ae6af0 -SuiteSparse.v5.10.1+0.x86_64-linux-gnu.tar.gz/sha512/c971aed91bd695a0f7f735f58ddcb075d32b9522a8a50a30ad383ba5ce2c8e572fec97644e6cb85745206f4e5da72d7865d9a9724eb63ce3c04e90a4eedc90c9 -SuiteSparse.v5.10.1+0.x86_64-linux-musl.tar.gz/md5/7c98daf0edfad31764c3078e6351b521 -SuiteSparse.v5.10.1+0.x86_64-linux-musl.tar.gz/sha512/2c4b3cae1bd8d1ce62dae6aeca3ffbf90c26a1b01c0da4fb7761d6fe4293b8fad0b6fbfd5f930cefe6ccaef7546a482022ff2f50dc59ecf17c5c0dfc6a5961f5 -SuiteSparse.v5.10.1+0.x86_64-unknown-freebsd.tar.gz/md5/aeca88a7bc3f9d239c61084996ce9182 -SuiteSparse.v5.10.1+0.x86_64-unknown-freebsd.tar.gz/sha512/0bee1ee07c3883fe28dd322c40195be9adb757d6dab3eb1730d7b0ff65dd4517520047696ccdda4ca618e671d898cdb45b787094594e142cb4b176549a74200b -SuiteSparse.v5.10.1+0.x86_64-w64-mingw32.tar.gz/md5/63e449554eee134757e3d50ca8b5f47d -SuiteSparse.v5.10.1+0.x86_64-w64-mingw32.tar.gz/sha512/95b58df4fe7520e2b526f9e3b199253909992789cd24ecca814ddb9a0c0bb37ff93c1de40239e5295a8503613cdb2431a87f0a70a3d657d94d4661f1778797f2 +SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/md5/46541001073d1c3c85e18d910f8308f3 +SuiteSparse-e8285dd13a6d5b5cf52d8124793fc4d622d07554.tar.gz/sha512/f7470a447b934ca9315e216a07b97e363f11bc93186f9aa057b20b2d05092c58ae4f1b733de362de4a0730861c00be4ca5588d0b3ba65f018c1798b9122b9672 +SuiteSparse.v5.10.1+6.aarch64-apple-darwin.tar.gz/md5/14cc0d3c7b5271246eb45c495c7a4e79 +SuiteSparse.v5.10.1+6.aarch64-apple-darwin.tar.gz/sha512/a56da81a5165bcdf49d1913799bffcaea84efd6f8740dd002f700eb4070313cac64be5359ba88d1f39fe976944e34ee6ed6575ceade2ae2d97b850e6a1aee0ae +SuiteSparse.v5.10.1+6.aarch64-linux-gnu.tar.gz/md5/b93b047040e2db5e0277e52b9bd3feb7 +SuiteSparse.v5.10.1+6.aarch64-linux-gnu.tar.gz/sha512/e03a9ecafce9dcc6791dd202efac2f864bdf3a0a4524567801c092304c17ab15dae949abfb1fe2bc71b367a0e398260ccfdd91dad611860090df471b44e75ee3 +SuiteSparse.v5.10.1+6.aarch64-linux-musl.tar.gz/md5/22c44d9d82608724e1aa62d126fdf030 +SuiteSparse.v5.10.1+6.aarch64-linux-musl.tar.gz/sha512/39a3c11429cd3e6afa2f615dc4b0c8d16d7b94a423d76e598b3b48db2c47fe64d644233e2a672bd6654f8bd57da91dd7a787a3e4978f0f803237ab4ec6f97905 +SuiteSparse.v5.10.1+6.armv6l-linux-gnueabihf.tar.gz/md5/505ee3c0750a720ed1e4de670f81e220 +SuiteSparse.v5.10.1+6.armv6l-linux-gnueabihf.tar.gz/sha512/20fafbdd2df96427b95b730901663c255dafc415f3a8154e3364ec46ca2b205fa45a081f92272b81d7aed22b9f8373d2d4eee70ff8ab5ed8d1d80b6a340c8aad +SuiteSparse.v5.10.1+6.armv6l-linux-musleabihf.tar.gz/md5/8e1821668cbca9c2d3c5cee5ad1746c8 +SuiteSparse.v5.10.1+6.armv6l-linux-musleabihf.tar.gz/sha512/58fb4ec10a537d101e0be8417648a4d0127444b3fe8a32498320aaaefc747f5cac3c7503b70775c1d708b077034060fe5ed8609e73bf9be22f9a8729abc4c73d +SuiteSparse.v5.10.1+6.armv7l-linux-gnueabihf.tar.gz/md5/43d133a916e548ecae50671b92f64c6f +SuiteSparse.v5.10.1+6.armv7l-linux-gnueabihf.tar.gz/sha512/f7f767c0e7eb45afe10941513695bfcc9e0628195cb9245a9c24700967f9cfa7cd0030cdcfaf47a76400d5dd3eb908c1f9ea5e44efd3054ed7bba47e664279a2 +SuiteSparse.v5.10.1+6.armv7l-linux-musleabihf.tar.gz/md5/7c3b2e19d3296002b1aa72b951421eec +SuiteSparse.v5.10.1+6.armv7l-linux-musleabihf.tar.gz/sha512/7546ce844b03d0414168ab6d0925f848b14b35ed27cb545b41f2512bad44b7da4f39004e75657c7c572557ccb015177d3e0d346e2c3182b27a6ee602876ee0df +SuiteSparse.v5.10.1+6.i686-linux-gnu.tar.gz/md5/e00a73f0fad92a266dd8d3774707f9b1 +SuiteSparse.v5.10.1+6.i686-linux-gnu.tar.gz/sha512/9cc2332a78d0490170d722d2f062d6f660fb3bd9042dd177c3683675d0f44306b93bf882cb79c0707ab79318280d08582431eb1c92334f2bb50946e942be0b16 +SuiteSparse.v5.10.1+6.i686-linux-musl.tar.gz/md5/71fb647a76ecc9e547df903535011b5b +SuiteSparse.v5.10.1+6.i686-linux-musl.tar.gz/sha512/7806cd9179e46fa61b63a3f711b37289da72a48430912e564c88e3dcb4caaad8a9bd232d6f572f8270806d286e4a4eb9edfdcda29fe8d91dadb1b03d57eda76d +SuiteSparse.v5.10.1+6.i686-w64-mingw32.tar.gz/md5/d4e6c9aba53b2107469cda6de9ca2724 +SuiteSparse.v5.10.1+6.i686-w64-mingw32.tar.gz/sha512/c0c49641c6e7f3f0333e3fa44ce62dcd4ad5942c74b2429aaeb49fd0d7b8c13c872150ae4d54cc5cfaae07a65a24a7d4ea731adc78db3d9341a54e5edb5c80f0 +SuiteSparse.v5.10.1+6.powerpc64le-linux-gnu.tar.gz/md5/5432dca00f7e0f42b7dbd16083537318 +SuiteSparse.v5.10.1+6.powerpc64le-linux-gnu.tar.gz/sha512/61946a7faa2a49613ea2c08a01f064b619c9ec134f0d9509eb42a96bebf2a63f5fb57b14702f25618def410658da8334bb6aa5200280956e573aa944476efef2 +SuiteSparse.v5.10.1+6.x86_64-apple-darwin.tar.gz/md5/ca175d433a02f91407e2921872c2b67c +SuiteSparse.v5.10.1+6.x86_64-apple-darwin.tar.gz/sha512/14d9b01e2db8c04f9a1076bcbac022c6573728f708f31344825805fed53971e922aecebeb4b2f567a6b5f44ad27c0d66e142887ff4684c8679ab65b902538abf +SuiteSparse.v5.10.1+6.x86_64-linux-gnu.tar.gz/md5/6c271ced91dbb1bf748efbaace1dac10 +SuiteSparse.v5.10.1+6.x86_64-linux-gnu.tar.gz/sha512/5984db9c101ef80d63024bc3b51821268349450deedd5aaea5fade0fc5932992379a0133c4f91711af134014835afea1bde518ae1e7efd482d556a97e54b0238 +SuiteSparse.v5.10.1+6.x86_64-linux-musl.tar.gz/md5/c7d55069969dbb98997687c847ab643d +SuiteSparse.v5.10.1+6.x86_64-linux-musl.tar.gz/sha512/b54012765f7c7329125b41c3fb678e23888a858d3fd5a139c52bd980e383a308282238020754e795de6457fb312b61c39e6ab2d665ca5af95c65f52f0c354067 +SuiteSparse.v5.10.1+6.x86_64-unknown-freebsd.tar.gz/md5/e641be38c8205e362a7299c736aedad5 +SuiteSparse.v5.10.1+6.x86_64-unknown-freebsd.tar.gz/sha512/d55e85335bccb59210014c35233ad9e42f5d086f01a43fe0ee13f21cbb8555ea05f1d91c95a6d3f883477086851e123c4b0cde7cd2dcd8e08835fe9f685d5b25 +SuiteSparse.v5.10.1+6.x86_64-w64-mingw32.tar.gz/md5/45cad947fa962e1f192cb7b52a1f7b3c +SuiteSparse.v5.10.1+6.x86_64-w64-mingw32.tar.gz/sha512/e6545c681ba7d2346baf8fafabdf25f2faf6ea54763d999b14499f30d235e90f34fd4f83430ea7f17c01adea0699dff6c4d7ae3cb938c749d6a15f8bf4f1519f diff --git a/deps/checksums/unwind b/deps/checksums/unwind index 6703b0c2648fc..7a3141d79368c 100644 --- a/deps/checksums/unwind +++ b/deps/checksums/unwind @@ -1,26 +1,26 @@ -LibUnwind.v1.5.0+1.aarch64-linux-gnu.tar.gz/md5/4f27fbe5a0e0897d75e3690e2f24c10b -LibUnwind.v1.5.0+1.aarch64-linux-gnu.tar.gz/sha512/fad4ac07121823859bf6af322c0d3b52b353b1e7b2a12611dc14bfc18663fc4278a4eab61653b306bf07abfff8dc92ef07d55b24117d8ccc5a8662139b5301a1 -LibUnwind.v1.5.0+1.aarch64-linux-musl.tar.gz/md5/730455d3e334b61e9232f978a5ba9841 -LibUnwind.v1.5.0+1.aarch64-linux-musl.tar.gz/sha512/64a04b6d362774c5dc9534a49b58ea676cb514fc10ce0747cd79d5319841e9b0695701c631a2598499d2fa888d36a89f0560f7910d01fa4c7e2fc223a2143a8d -LibUnwind.v1.5.0+1.armv6l-linux-gnueabihf.tar.gz/md5/e335c0eb5fd97e870400f472ed163722 -LibUnwind.v1.5.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/4211fa7a4a08631a335a327cda511272527ff590507819dc9ee89ec1db7a7603c1ce9dcfcb22950acb4246fef297493eccd5260fb76d9929c84cc9c755381849 -LibUnwind.v1.5.0+1.armv6l-linux-musleabihf.tar.gz/md5/d8b094c08d496b45cf5e4f6f964caa43 -LibUnwind.v1.5.0+1.armv6l-linux-musleabihf.tar.gz/sha512/0fe159785b8f35ae5b8301754ed244f687156a9565fe2d6d6c72e8b4e0c04c26183079b69093ab4fec9de4bd8b69d248a1569d8fc539ef04787ed09d36e41bdd -LibUnwind.v1.5.0+1.armv7l-linux-gnueabihf.tar.gz/md5/55289aa21e11f8fa3867dd650f863b2d -LibUnwind.v1.5.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/e669616bc1be3b34552b9927279e0b43df1e5ab0e72659a9e128d894df6b46d10f7d72b30eb5ad3355f01776a5f5250195633b846747fdbaefd2ac69915157fc -LibUnwind.v1.5.0+1.armv7l-linux-musleabihf.tar.gz/md5/5a99cca618b56961b7108c618f8704c4 -LibUnwind.v1.5.0+1.armv7l-linux-musleabihf.tar.gz/sha512/7465262f611ff347fa57a910019e4a243451d801edf1fd9bb19a5f2ab9e9b84885da26781af18c1405347918e74b21b4f0a308d938cd3198b6260e0df8b5bc6b -LibUnwind.v1.5.0+1.i686-linux-gnu.tar.gz/md5/bd52e05f513c8b86d8b71a9f833bde57 -LibUnwind.v1.5.0+1.i686-linux-gnu.tar.gz/sha512/750a082730c35b11cc1745a05e140bd83fd7511560ac90e15b472383b60b4641285b959a1a0897e2c6cb6bbf3857497ef7164642bfc79660c9c681cda189f530 -LibUnwind.v1.5.0+1.i686-linux-musl.tar.gz/md5/41482181efe7218a6aae785b76ad4d62 -LibUnwind.v1.5.0+1.i686-linux-musl.tar.gz/sha512/e1212ecc9efe16fc7401b1abafd3ab55c435a868e9a5408a4d9ca0039c4c422fb635314ae8d69913d4699a52ae070dc12db9cbb95d18f6e4fa41dd5047b22049 -LibUnwind.v1.5.0+1.powerpc64le-linux-gnu.tar.gz/md5/fa9dafea4ad00266188a310294b9342e -LibUnwind.v1.5.0+1.powerpc64le-linux-gnu.tar.gz/sha512/d90370601804b477f588fbeba549a197150fc080ccee112b0e93a00393d03b7908a5f8ceed1a33c6ea8860c0f88c0d1e05a676336c948897f518d6130c480f5e -LibUnwind.v1.5.0+1.x86_64-linux-gnu.tar.gz/md5/57b35a4b10407daf0e06c32acb942c0f -LibUnwind.v1.5.0+1.x86_64-linux-gnu.tar.gz/sha512/0ab66f46a0b947c29d9ac76b1b10591435a9098e1a93b99eb64444c9fabd97439764a0fd1483ee5e26c2f617ca97d3929184bcbb9f1f2778ad109a9fb07d2daa -LibUnwind.v1.5.0+1.x86_64-linux-musl.tar.gz/md5/7116a5228632a6f96bfc979d72ad530d -LibUnwind.v1.5.0+1.x86_64-linux-musl.tar.gz/sha512/20c18da77864985ba8bb0a5857c23b807730de354d2fd0504b627e53a2d8763a72c2ebb5ac8bb7615f4a66a27937c12e89619489893b4ee17c3fea8acd12c704 -LibUnwind.v1.5.0+1.x86_64-unknown-freebsd.tar.gz/md5/79bb8128f52068faf1cb8e82f39272dc -LibUnwind.v1.5.0+1.x86_64-unknown-freebsd.tar.gz/sha512/c60a09be8d945d7309e219e830f7cebcb11b7924b9d98ea4b0a93730ed4c2eda846bd0f043cdb225c86cc576d5708fd4f4fbe1324c7b71e47a02ae1d42c7e47f +LibUnwind.v1.5.0+4.aarch64-linux-gnu.tar.gz/md5/b40fee1e2995d3fa2c823c45b231d9f0 +LibUnwind.v1.5.0+4.aarch64-linux-gnu.tar.gz/sha512/d5865dabb541c3e1a5b6bc20547adc0788dde0f74731006e44e2cd128742c1ce61638a31340f8f4bfcd8b052706c3d57c24a202d048cb8d0496a909ff51fe9f7 +LibUnwind.v1.5.0+4.aarch64-linux-musl.tar.gz/md5/580b46908f43309c3f88c9ec4177d296 +LibUnwind.v1.5.0+4.aarch64-linux-musl.tar.gz/sha512/c12caa005586bea53932054d2742d6b55c40fd1a284daeb73924f3b761115929e022f3cf377b590d818e2c69726d42f12d4c87be2daf6d43caeaef54e226afdb +LibUnwind.v1.5.0+4.armv6l-linux-gnueabihf.tar.gz/md5/5af8f16e7eb32718cde68ee840c373c2 +LibUnwind.v1.5.0+4.armv6l-linux-gnueabihf.tar.gz/sha512/71e6f64477bc356c42bf1604e61a2596dfdb90f5fc3005e6656f2aa5ba0576867e6b482501d3d3c68da623cf4d6c572e4fb9708a71988671b1bbe76d6c2e4754 +LibUnwind.v1.5.0+4.armv6l-linux-musleabihf.tar.gz/md5/446f9021d1903410ed9b2e400e2533af +LibUnwind.v1.5.0+4.armv6l-linux-musleabihf.tar.gz/sha512/bf39ac9faea323c394e627647aaafacccdcd9545ac970b771dc4736376c56f0e1cfe58fead45625b7c491d91ae4f1dd41c3303d04536ef514c3a3657c06fd261 +LibUnwind.v1.5.0+4.armv7l-linux-gnueabihf.tar.gz/md5/ab594ba2df5cdc08dcf74ee2d0af9742 +LibUnwind.v1.5.0+4.armv7l-linux-gnueabihf.tar.gz/sha512/80f3b0c922b27d98fec1ba58f227af3c9d3e9691f34ed088152619289fa09b03a5b891162cd8ba497432867d60c2cd97a3466178c0891d848ded167e64f720ef +LibUnwind.v1.5.0+4.armv7l-linux-musleabihf.tar.gz/md5/84cdf938ab0880447f242d86ad9e6d1d +LibUnwind.v1.5.0+4.armv7l-linux-musleabihf.tar.gz/sha512/a985e9fc4e75cb292e7cb80ae0446110221a7f785818f53ac26c03dc2e142c959a6f380ffbceb43039dc95659e0da608b436d5faa5133f7d49308dd6198652f3 +LibUnwind.v1.5.0+4.i686-linux-gnu.tar.gz/md5/29a8d300b5edc3b25fc0c38d415ec4a7 +LibUnwind.v1.5.0+4.i686-linux-gnu.tar.gz/sha512/c96b954ee5736ad69a47e1214aac483ed2697a013749a696de823e2064bd5869590ae17c19268bf06227c9065b10bb36b197fb73987a74706fd37e0eefc17254 +LibUnwind.v1.5.0+4.i686-linux-musl.tar.gz/md5/fe8822d87cbad1abc4173a0c5c3f082f +LibUnwind.v1.5.0+4.i686-linux-musl.tar.gz/sha512/ff09cdbb4046413c260df0058a2fb3c2daa56e656a038c1ff4c47b251254e08066ae3b8b144a02483e1ca7d92192d8e3c1b005adcf2dad26343219eab4c26d95 +LibUnwind.v1.5.0+4.powerpc64le-linux-gnu.tar.gz/md5/15eea5ef1f4ad04cc8fb8f701571233f +LibUnwind.v1.5.0+4.powerpc64le-linux-gnu.tar.gz/sha512/875d50cea141397783c4d3062a08a1951fb14c96e9c99489ddeb91f94f403c48e8d358c181b6649198318586463efedd1b5f991acc792d8412a6ad2c810c568e +LibUnwind.v1.5.0+4.x86_64-linux-gnu.tar.gz/md5/2b7b2264763d10f39c548b3f23ea1a95 +LibUnwind.v1.5.0+4.x86_64-linux-gnu.tar.gz/sha512/7e76ae26ce7f6f60020af0908c7197e28204a8b290022af7dd92b17d64b01d68338d347e3f78a5946fef2faec3cd3f1c274bc55de1472a6245867b8e5219dd0a +LibUnwind.v1.5.0+4.x86_64-linux-musl.tar.gz/md5/84789e4ee681fbe4697e02431ab1004b +LibUnwind.v1.5.0+4.x86_64-linux-musl.tar.gz/sha512/e8166e2efbb70a3b492551556c72181c505b8cdb2e5d528caa69b32727c59f3e065e4455fdd9749878bb6d1ab5962ca7dfe2ebc9efa6dbdb0bebd210bd16c6a7 +LibUnwind.v1.5.0+4.x86_64-unknown-freebsd.tar.gz/md5/f35f256dd24183f72a932946c07073b0 +LibUnwind.v1.5.0+4.x86_64-unknown-freebsd.tar.gz/sha512/de80153025ba3e4192c8faf3f7c5f5a0044d4580f8cb56f4c0206f7030cbeeb406cdd064f87b4568392c06e96b9e32fc07c55b68b92e8cc5d596fb79040ecb78 libunwind-1.5.0.tar.gz/md5/c6923dda0675f6a4ef21426164dc8b6a libunwind-1.5.0.tar.gz/sha512/1df20ca7a8cee2f2e61294fa9b677e88fec52e9d5a329f88d05c2671c69fa462f6c18808c97ca9ff664ef57292537a844f00b18d142b1938c9da701ca95a4bab diff --git a/deps/checksums/utf8proc b/deps/checksums/utf8proc index 6c2b22983ec3d..c1b2a6779e555 100644 --- a/deps/checksums/utf8proc +++ b/deps/checksums/utf8proc @@ -1,2 +1,2 @@ -utf8proc-8ca6144c85c165987cb1c5d8395c7314e13d4cd7.tar.gz/md5/af7d2e685f46ff6317fc4ab276bfade7 -utf8proc-8ca6144c85c165987cb1c5d8395c7314e13d4cd7.tar.gz/sha512/0b1c839457755db6679057c99a7872e72e3f17d8535e1e173749e139050bcf10f2e9a9b9fadccabde644ffcc865cfb9396429fc31e5a5a383f95856a01ea98a2 +utf8proc-1cb28a66ca79a0845e99433fd1056257456cef8b.tar.gz/md5/aff37aadd1b02cad3259683e8a5f4543 +utf8proc-1cb28a66ca79a0845e99433fd1056257456cef8b.tar.gz/sha512/3ee433e5577e01f334aa4224275dfb7ee6ae7c785013df3eee6fc0488218d3bc895649811589edf57461c6520ad70437fbf6a376959a6a6f70bd920eb01c5001 diff --git a/deps/checksums/zlib b/deps/checksums/zlib index ba31ecdbae00b..15e2cffa5b485 100644 --- a/deps/checksums/zlib +++ b/deps/checksums/zlib @@ -1,34 +1,34 @@ -zlib-cacf7f1d4e3d44d871b605da3b647f07d718623f.tar.gz/md5/93d10d4dd040f14ae63417070d1346e8 -zlib-cacf7f1d4e3d44d871b605da3b647f07d718623f.tar.gz/sha512/a1e9c5a2963266a582192d0fe88c179f5239245f11c4df4427dda755ad77d31e1fcf045d7d3fe49141090f4ff8da13d9a2e8d8d317fe6460a5f3e9bdea29b883 -Zlib.v1.2.12+1.aarch64-apple-darwin.tar.gz/md5/6e255e13279855a99dae7d4ccf206069 -Zlib.v1.2.12+1.aarch64-apple-darwin.tar.gz/sha512/d160928dc6cad6bbc9fce36ea0d807c1f432aae375e6a032b0fd58d18640d02fc50c25233b32f8b73f3fc3488a091cf57418ad04498160441e3d7e4aa79302fe -Zlib.v1.2.12+1.aarch64-linux-gnu.tar.gz/md5/ff0ce9d6dec1c1b07114ed48f2bcfc88 -Zlib.v1.2.12+1.aarch64-linux-gnu.tar.gz/sha512/fdcea5e1fccc93641d0c372b6ba041c33c006e84ca6ba532bd9d6bb5ac449379daf27c5e1b95df3a6a57d3c24a363f12e55d5fb92184f1130606000e045a0d9b -Zlib.v1.2.12+1.aarch64-linux-musl.tar.gz/md5/900884b5eb02307665c1e6244f9d4be8 -Zlib.v1.2.12+1.aarch64-linux-musl.tar.gz/sha512/c3cbf7b41566af260a6e4ff2a2206b7f88439f0925609c72f822876eff384e3656e6bcd12131eac47d4177e5a1359ea6ebedbae949682c1d307607588ebfd80c -Zlib.v1.2.12+1.armv6l-linux-gnueabihf.tar.gz/md5/2766764794ae29ff4dc97c42faebbd91 -Zlib.v1.2.12+1.armv6l-linux-gnueabihf.tar.gz/sha512/341262c50ba5117ea93afb4acf3a036ee40a83d9b46b13a8360f36d74561c152d9ffa807887f4c452c65e91cae98df44fed861014ce26c4293ee0f45bafcb87e -Zlib.v1.2.12+1.armv6l-linux-musleabihf.tar.gz/md5/9037801d9524b3912acb5a5d3abfaa87 -Zlib.v1.2.12+1.armv6l-linux-musleabihf.tar.gz/sha512/6984076b0262e7ef19f08e6e83aa855eb6b60ae478dcad985d360b38f52ea6cc0fbf4e5c7723c007b722b01dc70ae378f6d487ddbe934e84ab4376de2688ce86 -Zlib.v1.2.12+1.armv7l-linux-gnueabihf.tar.gz/md5/627bcdf4216e9fb7020dcc50f71402e2 -Zlib.v1.2.12+1.armv7l-linux-gnueabihf.tar.gz/sha512/575000bed533f223ef2551ebdb7b431a743f83bf248edaf0a05ba00d33cf7848481952b325d7e18fdce3b91d2f0ec6fd02b24fb8cfa812f8a511f924a192fd1c -Zlib.v1.2.12+1.armv7l-linux-musleabihf.tar.gz/md5/11c79b0221d07986eeaf016650667059 -Zlib.v1.2.12+1.armv7l-linux-musleabihf.tar.gz/sha512/7f0415e8ebad6690621906885f72d3660962279e4ef57893334406a92f3eb9f6dac177d7430da0f4ae1ab0cabf185b33dbb347e054c35498e94e45771dd4b05a -Zlib.v1.2.12+1.i686-linux-gnu.tar.gz/md5/fc024f3aa4fffb298b6059adc7db6911 -Zlib.v1.2.12+1.i686-linux-gnu.tar.gz/sha512/cb219ecd89adda98f84914a4bc9355ba363bd942c7cd16adba70aa3f8ac37d1f7f812df942294a8eb3fa5ed474ee59126a567dea1f536467087fa27eb66c41b1 -Zlib.v1.2.12+1.i686-linux-musl.tar.gz/md5/5473f0c5ae14d4c34bc51c6ad583f21e -Zlib.v1.2.12+1.i686-linux-musl.tar.gz/sha512/c6380f1b22866dbfb8baaf724bcc33f2db3602741d3ffcdd61a6831740f1e4e4344b4ac4ac020054df06ebefac235f56a034a1d7cbc40e6c19d2e953945725c2 -Zlib.v1.2.12+1.i686-w64-mingw32.tar.gz/md5/1119dbaf451c691028522e43e2ca7f20 -Zlib.v1.2.12+1.i686-w64-mingw32.tar.gz/sha512/366d3ef55e3b448176388f8d92c6ffe00e68f7ae62b67ad1ceedb73984ba30b16c8a086807f61e87caa8262e8ea1cb7799b49d22b0269dcee7735d3ea36df6aa -Zlib.v1.2.12+1.powerpc64le-linux-gnu.tar.gz/md5/127bf2fbb739f52d1d455d9b8dd0b08e -Zlib.v1.2.12+1.powerpc64le-linux-gnu.tar.gz/sha512/cd647435a5ca819180f662f288106ce49521ad75501b7c95ad912f008caa264531f8b62ccc042c0f8f2cb1a728d89d84fef395c9f3797b0f9f111c1f8b8ce1b9 -Zlib.v1.2.12+1.x86_64-apple-darwin.tar.gz/md5/5740e0da15acce6234d54b56bc462529 -Zlib.v1.2.12+1.x86_64-apple-darwin.tar.gz/sha512/1b973091f381cd2d1403685fcc7ca69f31019e2bab6a031cc934bffdf339775bbd529fb375996bdade090ff4cfcf6f2aec6cb9891b91a5b21c3f847f159748a0 -Zlib.v1.2.12+1.x86_64-linux-gnu.tar.gz/md5/750e79f7ad235ee94088ad297c407e36 -Zlib.v1.2.12+1.x86_64-linux-gnu.tar.gz/sha512/ae995d9069eda2ac602eb53cd6d86c22d0d5e353504d1a6525a33efb99628fa4abd40d0dcc16f0927c409d5c57b6f7d63208d2aae01474665f9f93114bd1388a -Zlib.v1.2.12+1.x86_64-linux-musl.tar.gz/md5/bb62d2d9f6800c36183d2f2e6e094f42 -Zlib.v1.2.12+1.x86_64-linux-musl.tar.gz/sha512/d2ba384a1d31cf0f3cb6bc843d43005c39a72007954bc58bfa24c5d6d65af10ae2969670baecd854c8074f94424288f3fb29f735c9226f7f8a2df49eb62e6033 -Zlib.v1.2.12+1.x86_64-unknown-freebsd.tar.gz/md5/21dfda8d26dbe76c914216e79d7847d6 -Zlib.v1.2.12+1.x86_64-unknown-freebsd.tar.gz/sha512/2cd7be4070dbf20ab1c46553a9e3f84c98bf8e8fc72bf2eb4678630e648cb9ad02cae5e004f3c2a69216e2782d9bba43eac6a45a480f6fe58d1091a9fbba93ff -Zlib.v1.2.12+1.x86_64-w64-mingw32.tar.gz/md5/140ddbeeaf27867aeeeec118682e879d -Zlib.v1.2.12+1.x86_64-w64-mingw32.tar.gz/sha512/f61f3d1eb7e7960b2fdbc1d68f22526a06ba598cd821261e7ba3819e00daee4c5b5427f9c03277b57b7226860142f0071410c0583535ca4e4b9acbe5ee4b5ade +Zlib.v1.2.13+0.aarch64-apple-darwin.tar.gz/md5/64403a5962d70d7e4b6bf7c225526144 +Zlib.v1.2.13+0.aarch64-apple-darwin.tar.gz/sha512/a7e6bb32c324943e5df3fa8501ee9d744d132db6f27033fe8ce789c1f19f26c15dc456ee8d6fc8095b427054e750ffe268500f5f69edecaa1af230b4b23535c4 +Zlib.v1.2.13+0.aarch64-linux-gnu.tar.gz/md5/a2d3265543017db03bc47b9d9778d99d +Zlib.v1.2.13+0.aarch64-linux-gnu.tar.gz/sha512/c8143445222e151d7f522a98ee8f2742571542f4e71d515e88086c9d7f27b952662ced93f40c795e0de42e3a07c0cb5e1d9d8e792347f3c068cb07ccc144a640 +Zlib.v1.2.13+0.aarch64-linux-musl.tar.gz/md5/c1f2a1c562f72c7aa4b228f57c2346d4 +Zlib.v1.2.13+0.aarch64-linux-musl.tar.gz/sha512/7ed89bc7696690c03617c7413f5456ff5a1caa0dd600880ae67132f6c9190672ae451a06d23956a1969be00bf5c8f29bfa4f5bc4ab646b3b375c350f67c993e5 +Zlib.v1.2.13+0.armv6l-linux-gnueabihf.tar.gz/md5/7dff966f7bc5dd2902fa9ce20444235b +Zlib.v1.2.13+0.armv6l-linux-gnueabihf.tar.gz/sha512/49e7b4a7c84996b697cf944b11ce06ce6064983a6a911c4539587385afa1e0119e3b1dbf816703a2c132acc90f7f114ec10631647638b59b14954382c1a82014 +Zlib.v1.2.13+0.armv6l-linux-musleabihf.tar.gz/md5/6982f19d2446559c0fd369afe84ebe4a +Zlib.v1.2.13+0.armv6l-linux-musleabihf.tar.gz/sha512/8f69dfb7fb91cd6f7c934e1acddd83f77c2ebcc1732553f41ae1adcb7805a3304d16062133ce5094a8aea18ff5eca5f7a2df5724ae5a5cb9137caee732c1bf36 +Zlib.v1.2.13+0.armv7l-linux-gnueabihf.tar.gz/md5/30579a91f8f1c96752fe9a82bc053523 +Zlib.v1.2.13+0.armv7l-linux-gnueabihf.tar.gz/sha512/64f6a0e66ee13b086609e0d070c8742de20052e1ef43da201be0007e478c65b2f0a28a3c19ca5be6537b7c8bbeb6a4b2886c15a1e47bb2bd1cfe9d5e1590a620 +Zlib.v1.2.13+0.armv7l-linux-musleabihf.tar.gz/md5/b052ad151dbc3bad78762bc06164d667 +Zlib.v1.2.13+0.armv7l-linux-musleabihf.tar.gz/sha512/b5d2de09a4d65d898cf9ba0db34327c712f42a78cd1fd0f1d77fd8798910502049be63ccfed23de5fe3b499d9e0fe3d4cbb07c72765fd54db275e92f8f1e4dc4 +Zlib.v1.2.13+0.i686-linux-gnu.tar.gz/md5/3074702010889f586b43aa3dbbda4ceb +Zlib.v1.2.13+0.i686-linux-gnu.tar.gz/sha512/92aa87c5aa3831155305276c2f0da091b5be4e8a396772e1a28650c2837ceb116dd2207329732b653a97c011abd7dd6ac1fc9574ac64cb3049ccd36fa6700748 +Zlib.v1.2.13+0.i686-linux-musl.tar.gz/md5/eff02476825ea7a53ab26b346d58f96e +Zlib.v1.2.13+0.i686-linux-musl.tar.gz/sha512/14b72607d524948198e999e3919ee01046c049b3ec441bc581c77642cf37c3d28cc3c5500a3c073d62e9b8dc1efc9661b23bb925ed9c80b5e69abaddbcb59115 +Zlib.v1.2.13+0.i686-w64-mingw32.tar.gz/md5/279d2699458b1dfec80da17dd6f32f02 +Zlib.v1.2.13+0.i686-w64-mingw32.tar.gz/sha512/fb14d27b4f4ed5eb75bf4d4377074a206610558301be89ed692cf61d1266e425edb0489511fbbec100dafc71cff2cac863a4ea4ec70cfaa94e8175b9b7add25c +Zlib.v1.2.13+0.powerpc64le-linux-gnu.tar.gz/md5/bc69de101d9159b22b7a334e2700faa6 +Zlib.v1.2.13+0.powerpc64le-linux-gnu.tar.gz/sha512/174eb4f154594d268d970d23eb6144dd2f6be41ddcfb9bc756b2ff48f0781ad0ed6571e2ead64dab0967da91517a02cd8db2b0e33a0bde9400103b5204f78e85 +Zlib.v1.2.13+0.x86_64-apple-darwin.tar.gz/md5/9a53075fc5595e638bacd25341f7ff42 +Zlib.v1.2.13+0.x86_64-apple-darwin.tar.gz/sha512/8124f677c036a288575712e201a809f44532b300fa56f8c12be9a1d7094fd644cb198c47b63d9f9f16d5509e27e7b3c59f080d4748ae489a4977fdfeae79e762 +Zlib.v1.2.13+0.x86_64-linux-gnu.tar.gz/md5/b192d547d56124262e2ae744f385efd6 +Zlib.v1.2.13+0.x86_64-linux-gnu.tar.gz/sha512/c6dca3c0a713ef2e2296bc9e9afa75e103a4cc4f00b5c905ebc5cff688904d6a454f83ab5ef3b6c66bdf425daa2fcd25825e50a3534c0ff109b13affbb686179 +Zlib.v1.2.13+0.x86_64-linux-musl.tar.gz/md5/f2a466b38b2ff1c895f630982147a950 +Zlib.v1.2.13+0.x86_64-linux-musl.tar.gz/sha512/191261d37fc501591005bf680d76bf518da261252456c4fef1c12bc572f9200a855fbd1b125bb8ad10d803eedbc53d4c9d7a2861e9a35d629fb40f87e5306f5f +Zlib.v1.2.13+0.x86_64-unknown-freebsd.tar.gz/md5/00cb91c5edede46f72fae113b3115799 +Zlib.v1.2.13+0.x86_64-unknown-freebsd.tar.gz/sha512/8894e4a89dbf10e60ed020993484dcad91a52a8d310f3dfcc53808643c8401b1e445db46a815c19d55c0e5fd1a386945d1253c16af94b00ff27ccda44941f69b +Zlib.v1.2.13+0.x86_64-w64-mingw32.tar.gz/md5/f98c68e19d9cfd24c7cec0b79d374e05 +Zlib.v1.2.13+0.x86_64-w64-mingw32.tar.gz/sha512/8e68edbdfe4e2ec6de70a724e30bc2df439901291639eca9e5aace75e31c7c6d3f47021213b8b7473b1f6ad4986f6b8695da4e24e2ea3025681e5d07dcfc067d +zlib-04f42ceca40f73e2978b50e93806c2a18c1281fc.tar.gz/md5/60a49c89b9409dd91c1b039266f7bd0c +zlib-04f42ceca40f73e2978b50e93806c2a18c1281fc.tar.gz/sha512/83122539da9399ce5f51c2ecbc38a627405334a9a6d53a024341353c1263a1e3aef7498f30ee281a49b3022be70e992eae475691e33da7a9c6a59b83207bd688 diff --git a/deps/clang.version b/deps/clang.version new file mode 100644 index 0000000000000..d291dc8e8f8d8 --- /dev/null +++ b/deps/clang.version @@ -0,0 +1,4 @@ +## jll artifact +# Clang (paired with LLVM, only here as a JLL download) +CLANG_JLL_NAME := Clang +CLANG_JLL_VER := 15.0.7+5 diff --git a/deps/csl.mk b/deps/csl.mk index 9f95c00f3cfe7..457e276c66709 100644 --- a/deps/csl.mk +++ b/deps/csl.mk @@ -1,6 +1,6 @@ # 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 | grep '^programs: =' | sed -e "s/^programs: =//") -STD_LIB_PATH += :$(shell LANG=C $(FC) -print-search-dirs | grep '^libraries: =' | sed -e "s/^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 := $(shell echo '$(STD_LIB_PATH)' | sed -e "s!/lib/!/bin/!g") endif @@ -12,17 +12,16 @@ endef # CSL bundles lots of system compiler libraries, and while it is quite bleeding-edge # as compared to what most distros ship, if someone tries to build an older branch, -# the version of CSL that ships with that branch may become relatively old. This is -# not a problem for code that is built in BB, but when we build Julia with the system +# the version of CSL that ships with that branch may be relatively old. This is not +# a problem for code that is built in BB, but when we build Julia with the system # compiler, that compiler uses the version of `libstdc++` that it is bundled with, -# and we can get linker errors when trying to run that `julia` executable with the +# and we can get linker errors when trying to run that `julia` executable with the # `libstdc++` that comes from the (now old) BB-built CSL. # # To fix this, we take note when the system `libstdc++.so` is newer than whatever we # would get from CSL (by searching for a `GLIBCXX_3.4.X` symbol that does not exist # in our CSL, but would in a newer one), and default to `USE_BINARYBUILDER_CSL=0` in # this case. -CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.30|GLIBCXX_3\.5\.|GLIBCXX_4\. # First, check to see if BB is disabled on a global setting ifeq ($(USE_BINARYBUILDER),0) @@ -69,15 +68,19 @@ $(eval $(call copy_csl,$(call versioned_libname,libatomic,1))) $(eval $(call copy_csl,$(call versioned_libname,libgomp,1))) ifeq ($(OS),WINNT) -# Windwos has special gcc_s names +# Windows has special gcc_s names ifeq ($(ARCH),i686) $(eval $(call copy_csl,$(call versioned_libname,libgcc_s_sjlj,1))) else $(eval $(call copy_csl,$(call versioned_libname,libgcc_s_seh,1))) endif else +ifeq ($(APPLE_ARCH),arm64) +$(eval $(call copy_csl,$(call versioned_libname,libgcc_s,1.1))) +else $(eval $(call copy_csl,$(call versioned_libname,libgcc_s,1))) endif +endif # winpthread is only Windows, pthread is only others ifeq ($(OS),WINNT) $(eval $(call copy_csl,$(call versioned_libname,libwinpthread,1))) diff --git a/deps/csl.version b/deps/csl.version new file mode 100644 index 0000000000000..51af26c566c92 --- /dev/null +++ b/deps/csl.version @@ -0,0 +1,2 @@ +## jll artifact +CSL_JLL_NAME := CompilerSupportLibraries diff --git a/deps/curl.mk b/deps/curl.mk index f2cf21d19a354..a063dfe07fba0 100644 --- a/deps/curl.mk +++ b/deps/curl.mk @@ -1,4 +1,5 @@ ## CURL ## +include $(SRCDIR)/curl.version ifeq ($(USE_SYSTEM_LIBSSH2), 0) $(BUILDDIR)/curl-$(CURL_VER)/build-configured: | $(build_prefix)/manifest/libssh2 @@ -22,18 +23,27 @@ CURL_LDFLAGS += -lpthread endif $(SRCCACHE)/curl-$(CURL_VER).tar.bz2: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://curl.haxx.se/download/curl-$(CURL_VER).tar.bz2 + $(JLDOWNLOAD) $@ https://curl.se/download/curl-$(CURL_VER).tar.bz2 $(SRCCACHE)/curl-$(CURL_VER)/source-extracted: $(SRCCACHE)/curl-$(CURL_VER).tar.bz2 $(JLCHECKSUM) $< cd $(dir $<) && $(TAR) jxf $(notdir $<) - cp $(SRCDIR)/patches/config.sub $(SRCCACHE)/curl-$(CURL_VER)/config.sub - touch -c $(SRCCACHE)/curl-$(CURL_VER)/configure # old target echo 1 > $@ checksum-curl: $(SRCCACHE)/curl-$(CURL_VER).tar.bz2 $(JLCHECKSUM) $< +## xref: https://github.com/JuliaPackaging/Yggdrasil/blob/master/L/LibCURL/common.jl +# Disable....almost everything +CURL_CONFIGURE_FLAGS := $(CONFIGURE_COMMON) \ + --without-gnutls --without-libidn2 --without-librtmp \ + --without-nss --without-libpsl --without-libgsasl --without-fish-functions-dir \ + --disable-ares --disable-manual --disable-ldap --disable-ldaps --disable-static \ + --without-gssapi --without-brotli +# A few things we actually enable +CURL_CONFIGURE_FLAGS += --enable-versioned-symbols \ + --with-libssh2=${build_prefix} --with-zlib=${build_prefix} --with-nghttp2=${build_prefix} + # We use different TLS libraries on different platforms. # On Windows, we use schannel # On MacOS, we use SecureTransport @@ -45,22 +55,17 @@ CURL_TLS_CONFIGURE_FLAGS := --with-secure-transport else CURL_TLS_CONFIGURE_FLAGS := --with-mbedtls=$(build_prefix) endif +CURL_CONFIGURE_FLAGS += $(CURL_TLS_CONFIGURE_FLAGS) $(BUILDDIR)/curl-$(CURL_VER)/build-configured: $(SRCCACHE)/curl-$(CURL_VER)/source-extracted mkdir -p $(dir $@) cd $(dir $@) && \ - $(dir $<)/configure $(CONFIGURE_COMMON) --includedir=$(build_includedir) \ - --without-ssl --without-gnutls --without-gssapi --disable-ares \ - --without-libidn --without-libidn2 --without-librtmp \ - --without-nss --without-polarssl --without-spnego --without-libpsl \ - --disable-ldap --disable-ldaps --without-zsh-functions-dir --disable-static \ - --with-libssh2=$(build_prefix) --with-zlib=$(build_prefix) --with-nghttp2=$(build_prefix) \ - $(CURL_TLS_CONFIGURE_FLAGS) \ + $(dir $<)/configure $(CURL_CONFIGURE_FLAGS) \ CFLAGS="$(CFLAGS) $(CURL_CFLAGS)" LDFLAGS="$(LDFLAGS) $(CURL_LDFLAGS)" echo 1 > $@ $(BUILDDIR)/curl-$(CURL_VER)/build-compiled: $(BUILDDIR)/curl-$(CURL_VER)/build-configured - $(MAKE) -C $(dir $<) $(LIBTOOL_CCLD) + $(MAKE) -C $(dir $<) $(MAKE_COMMON) echo 1 > $@ $(BUILDDIR)/curl-$(CURL_VER)/build-checked: $(BUILDDIR)/curl-$(CURL_VER)/build-compiled @@ -71,15 +76,15 @@ endif $(eval $(call staged-install, \ curl,curl-$$(CURL_VER), \ - MAKE_INSTALL,$$(LIBTOOL_CCLD),, \ + MAKE_INSTALL,,, \ $$(INSTALL_NAME_CMD)libcurl.$$(SHLIB_EXT) $$(build_shlibdir)/libcurl.$$(SHLIB_EXT))) clean-curl: - -rm $(BUILDDIR)/curl-$(CURL_VER)/build-configured $(BUILDDIR)/curl-$(CURL_VER)/build-compiled + -rm -f $(BUILDDIR)/curl-$(CURL_VER)/build-configured $(BUILDDIR)/curl-$(CURL_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/curl-$(CURL_VER) clean distclean-curl: - -rm -rf $(SRCCACHE)/curl-$(CURL_VER).tar.bz2 $(SRCCACHE)/curl-$(CURL_VER) $(BUILDDIR)/curl-$(CURL_VER) + rm -rf $(SRCCACHE)/curl-$(CURL_VER).tar.bz2 $(SRCCACHE)/curl-$(CURL_VER) $(BUILDDIR)/curl-$(CURL_VER) get-curl: $(SRCCACHE)/curl-$(CURL_VER).tar.bz2 extract-curl: $(SRCCACHE)/curl-$(CURL_VER)/source-extracted diff --git a/deps/curl.version b/deps/curl.version new file mode 100644 index 0000000000000..f704bc2bebc61 --- /dev/null +++ b/deps/curl.version @@ -0,0 +1,6 @@ +# -*- makefile -*- +## jll artifact +CURL_JLL_NAME := LibCURL + +## source build +CURL_VER := 8.0.1 diff --git a/deps/dsfmt.mk b/deps/dsfmt.mk index e22f1b51fe8f7..da57799053933 100644 --- a/deps/dsfmt.mk +++ b/deps/dsfmt.mk @@ -1,13 +1,17 @@ ## DSFMT ## +include $(SRCDIR)/dsfmt.version ifneq ($(USE_BINARYBUILDER_DSFMT),1) -DSFMT_CFLAGS := $(CFLAGS) -DNDEBUG -DDSFMT_MEXP=19937 $(fPIC) -DDSFMT_DO_NOT_USE_OLD_NAMES -DDSFMT_SHLIB +DSFMT_CFLAGS := $(CFLAGS) -DNDEBUG -DDSFMT_MEXP=19937 $(fPIC) -DDSFMT_DO_NOT_USE_OLD_NAMES -DDSFMT_SHLIB $(SANITIZE_OPTS) DSFMT_CFLAGS += -O3 -finline-functions -fomit-frame-pointer -fno-strict-aliasing \ - --param max-inline-insns-single=1800 -Wall -std=c99 -shared + -Wall -std=c99 -shared ifeq ($(ARCH), x86_64) DSFMT_CFLAGS += -msse2 -DHAVE_SSE2 endif +ifneq ($(OS), emscripten) +DSFMT_CFLAGS += --param max-inline-insns-single=1800 +endif $(SRCCACHE)/dsfmt-$(DSFMT_VER).tar.gz: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://github.com/MersenneTwister-Lab/dSFMT/archive/v$(DSFMT_VER).tar.gz @@ -15,7 +19,7 @@ $(SRCCACHE)/dsfmt-$(DSFMT_VER).tar.gz: | $(SRCCACHE) $(BUILDDIR)/dsfmt-$(DSFMT_VER)/source-extracted: $(SRCCACHE)/dsfmt-$(DSFMT_VER).tar.gz $(JLCHECKSUM) $< - -rm -r $(dir $@) + rm -rf $(dir $@) mkdir -p $(dir $@) $(TAR) -C $(dir $@) --strip-components 1 -xf $< echo 1 > $@ @@ -47,11 +51,11 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)libdSFMT.$$(SHLIB_EXT) $$(build_shlibdir)/libdSFMT.$$(SHLIB_EXT))) clean-dsfmt: - -rm $(BUILDDIR)/dsfmt-$(DSFMT_VER)/build-compiled - -rm $(BUILDDIR)/dsfmt-$(DSFMT_VER)/libdSFMT.$(SHLIB_EXT) + -rm -f $(BUILDDIR)/dsfmt-$(DSFMT_VER)/build-compiled + -rm -f $(BUILDDIR)/dsfmt-$(DSFMT_VER)/libdSFMT.$(SHLIB_EXT) distclean-dsfmt: - -rm -rf $(SRCCACHE)/dsfmt*.tar.gz $(SRCCACHE)/dsfmt-$(DSFMT_VER) $(BUILDDIR)/dsfmt-$(DSFMT_VER) + rm -rf $(SRCCACHE)/dsfmt*.tar.gz $(SRCCACHE)/dsfmt-$(DSFMT_VER) $(BUILDDIR)/dsfmt-$(DSFMT_VER) get-dsfmt: $(SRCCACHE)/dsfmt-$(DSFMT_VER).tar.gz extract-dsfmt: $(BUILDDIR)/dsfmt-$(DSFMT_VER)/source-extracted diff --git a/deps/dsfmt.version b/deps/dsfmt.version new file mode 100644 index 0000000000000..bbb63417f46cd --- /dev/null +++ b/deps/dsfmt.version @@ -0,0 +1,5 @@ +## jll artifact +DSFMT_JLL_NAME := dSFMT + +## source build +DSFMT_VER := 2.2.4 diff --git a/deps/gfortblas.c b/deps/gfortblas.c index 4133a97537399..321fe124d7e87 100644 --- a/deps/gfortblas.c +++ b/deps/gfortblas.c @@ -119,4 +119,3 @@ __attribute__((destructor)) static void fini(void) { SetBLASParamErrorProc(NULL); /* restore default handler */ } - diff --git a/deps/gmp.mk b/deps/gmp.mk index a37327d82101e..12ba15f8aa0f6 100644 --- a/deps/gmp.mk +++ b/deps/gmp.mk @@ -1,4 +1,15 @@ ## GMP ## +include $(SRCDIR)/gmp.version + +ifneq ($(USE_BINARYBUILDER_GMP),1) + +GMP_CONFIGURE_OPTS := $(CONFIGURE_COMMON) +GMP_CONFIGURE_OPTS += --enable-cxx --enable-shared --disable-static +GMP_CONFIGURE_OPTS += CC_FOR_BUILD="$(HOSTCC)" + +ifeq ($(BUILD_ARCH),x86_64) +GMP_CONFIGURE_OPTS += --enable-fat +endif ifeq ($(SANITIZE),1) GMP_CONFIGURE_OPTS += --disable-assembly @@ -8,7 +19,9 @@ ifeq ($(BUILD_OS),WINNT) GMP_CONFIGURE_OPTS += --srcdir="$(subst \,/,$(call mingw_to_dos,$(SRCCACHE)/gmp-$(GMP_VER)))" endif -ifneq ($(USE_BINARYBUILDER_GMP),1) +ifeq ($(OS),emscripten) +GMP_CONFIGURE_OPTS += CFLAGS="-fPIC" +endif $(SRCCACHE)/gmp-$(GMP_VER).tar.bz2: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://gmplib.org/download/gmp/$(notdir $@) @@ -39,44 +52,42 @@ $(SRCCACHE)/gmp-$(GMP_VER)/gmp_alloc_overflow_func.patch-applied: $(SRCCACHE)/gm patch -p1 < $(SRCDIR)/patches/gmp_alloc_overflow_func.patch echo 1 > $@ -$(SRCCACHE)/gmp-$(GMP_VER)/source-patched: \ - $(SRCCACHE)/gmp-$(GMP_VER)/gmp-HG-changeset.patch-applied \ - $(SRCCACHE)/gmp-$(GMP_VER)/gmp-exception.patch-applied \ - $(SRCCACHE)/gmp-$(GMP_VER)/gmp_alloc_overflow_func.patch-applied +$(SRCCACHE)/gmp-$(GMP_VER)/gmp-CVE-2021-43618.patch-applied: $(SRCCACHE)/gmp-$(GMP_VER)/gmp_alloc_overflow_func.patch-applied + cd $(dir $@) && \ + patch -p1 < $(SRCDIR)/patches/gmp-CVE-2021-43618.patch echo 1 > $@ -$(BUILDDIR)/gmp-$(GMP_VER)/build-configured: $(SRCCACHE)/gmp-$(GMP_VER)/source-extracted $(SRCCACHE)/gmp-$(GMP_VER)/source-patched +$(SRCCACHE)/gmp-$(GMP_VER)/source-patched: $(SRCCACHE)/gmp-$(GMP_VER)/gmp-CVE-2021-43618.patch-applied + echo 1 > $@ + +$(BUILDDIR)/gmp-$(GMP_VER)/build-configured: $(SRCCACHE)/gmp-$(GMP_VER)/source-patched mkdir -p $(dir $@) cd $(dir $@) && \ - $(dir $<)/configure $(CONFIGURE_COMMON) F77= --enable-cxx --enable-shared --disable-static $(GMP_CONFIGURE_OPTS) + $(dir $<)/configure $(GMP_CONFIGURE_OPTS) echo 1 > $@ $(BUILDDIR)/gmp-$(GMP_VER)/build-compiled: $(BUILDDIR)/gmp-$(GMP_VER)/build-configured - $(MAKE) -C $(dir $<) $(LIBTOOL_CCLD) + $(MAKE) -C $(dir $<) echo 1 > $@ $(BUILDDIR)/gmp-$(GMP_VER)/build-checked: $(BUILDDIR)/gmp-$(GMP_VER)/build-compiled ifeq ($(OS),$(BUILD_OS)) - $(MAKE) -C $(dir $@) $(LIBTOOL_CCLD) check + $(MAKE) -C $(dir $@) check endif echo 1 > $@ -define GMP_INSTALL - mkdir -p $2/$(build_shlibdir) $2/$(build_includedir) - $(INSTALL_M) $1/.libs/libgmp*$(SHLIB_EXT)* $2/$(build_shlibdir) - $(INSTALL_F) $1/gmp.h $2/$(build_includedir) -endef $(eval $(call staged-install, \ gmp,gmp-$(GMP_VER), \ - GMP_INSTALL,,, \ - $$(INSTALL_NAME_CMD)libgmp.$$(SHLIB_EXT) $$(build_shlibdir)/libgmp.$$(SHLIB_EXT))) + MAKE_INSTALL,,, \ + $$(WIN_MAKE_HARD_LINK) $(build_bindir)/libgmp-*.dll $(build_bindir)/libgmp.dll && \ + $$(INSTALL_NAME_CMD)libgmp.$$(SHLIB_EXT) $$(build_shlibdir)/libgmp.$$(SHLIB_EXT))) clean-gmp: - -rm $(BUILDDIR)/gmp-$(GMP_VER)/build-configured $(BUILDDIR)/gmp-$(GMP_VER)/build-compiled + -rm -f $(BUILDDIR)/gmp-$(GMP_VER)/build-configured $(BUILDDIR)/gmp-$(GMP_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/gmp-$(GMP_VER) clean distclean-gmp: - -rm -rf $(SRCCACHE)/gmp-$(GMP_VER).tar.bz2 \ + rm -rf $(SRCCACHE)/gmp-$(GMP_VER).tar.bz2 \ $(SRCCACHE)/gmp-$(GMP_VER) \ $(BUILDDIR)/gmp-$(GMP_VER) @@ -90,4 +101,5 @@ check-gmp: $(BUILDDIR)/gmp-$(GMP_VER)/build-checked else # USE_BINARYBUILDER_GMP $(eval $(call bb-install,gmp,GMP,false,true)) + endif diff --git a/deps/gmp.version b/deps/gmp.version new file mode 100644 index 0000000000000..f77cac5906cea --- /dev/null +++ b/deps/gmp.version @@ -0,0 +1,5 @@ +## jll artifact +GMP_JLL_NAME := GMP + +## source build +GMP_VER := 6.2.1 diff --git a/deps/ittapi.mk b/deps/ittapi.mk new file mode 100644 index 0000000000000..1a47c3ae89390 --- /dev/null +++ b/deps/ittapi.mk @@ -0,0 +1,43 @@ +## ittapi ## +include $(SRCDIR)/ittapi.version + +ITTAPI_GIT_URL := https://github.com/intel/ittapi.git +ITTAPI_TAR_URL = https://api.github.com/repos/intel/ittapi/tarball/$1 +$(eval $(call git-external,ittapi,ITTAPI,CMakeLists.txt,,$(SRCCACHE))) + +ITTAPI_OPTS := $(CMAKE_COMMON) -DCMAKE_BUILD_TYPE=Release -DITT_API_IPT_SUPPORT= -DITT_API_FORTRAN_SUPPORT=0 + +$(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-configured: $(SRCCACHE)/$(ITTAPI_SRC_DIR)/source-extracted + mkdir -p $(dir $@) + cd $(dir $@) && \ + $(CMAKE) $(dir $<) $(ITTAPI_OPTS) + echo 1 > $@ + +$(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-compiled: $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-configured + $(MAKE) -C $(dir $<) + echo 1 > $@ + +define ITTAPI_INSTALL + mkdir -p $2/$$(build_libdir) + mkdir -p $2/$$(build_includedir)/ittapi + cp -a $1/bin/libittnotify.a $2/$$(build_libdir) + cp -a $1/bin/libjitprofiling.a $2/$$(build_libdir) + # cp -a $1/bin/libadvisor.a $2/$$(build_libdir) + cp -a $(SRCCACHE)/$(ITTAPI_SRC_DIR)/include/ittnotify.h $2/$$(build_includedir)/ittapi/ + cp -a $(SRCCACHE)/$(ITTAPI_SRC_DIR)/include/ittnotify-zca.h $2/$$(build_includedir)/ittapi/ + cp -a $(SRCCACHE)/$(ITTAPI_SRC_DIR)/include/jitprofiling.h $2/$$(build_includedir)/ittapi/ +endef + +$(eval $(call staged-install, \ + ittapi,$(ITTAPI_SRC_DIR), \ + ITTAPI_INSTALL,,,)) + +get-ittapi: $(ITTAPI_SRC_FILE) +extract-ittapi: $(SRCCACHE)/$(ITTAPI_SRC_DIR)/source-extracted +configure-ittapi: $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-configured +compile-ittapi: $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-compiled +fastcheck-ittapi: #none +check-ittapi: #none + +clean-ittapi: + -rm -f $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-compiled $(build_libdir)/libopenlibm.a diff --git a/deps/ittapi.version b/deps/ittapi.version new file mode 100644 index 0000000000000..81afb6de2add2 --- /dev/null +++ b/deps/ittapi.version @@ -0,0 +1,3 @@ +## source build +ITTAPI_BRANCH=v3.24.0 +ITTAPI_SHA1=0014aec56fea2f30c1374f40861e1bccdd53d0cb diff --git a/deps/libdSFMT.def b/deps/libdSFMT.def deleted file mode 100644 index 7388fc8d39366..0000000000000 --- a/deps/libdSFMT.def +++ /dev/null @@ -1,30 +0,0 @@ -LIBRARY "libdSFMT.dll" -EXPORTS -dsfmt_chk_init_by_array -dsfmt_chk_init_gen_rand -dsfmt_fill_array_close1_open2 -dsfmt_fill_array_close_open -dsfmt_fill_array_open_close -dsfmt_fill_array_open_open -dsfmt_gen_rand_all -dsfmt_genrand_close1_open2 -dsfmt_genrand_close_open -dsfmt_genrand_open_close -dsfmt_genrand_open_open -dsfmt_genrand_uint32 -dsfmt_get_idstring -dsfmt_get_min_array_size -dsfmt_global_data DATA -dsfmt_gv_fill_array_close1_open2 -dsfmt_gv_fill_array_close_open -dsfmt_gv_fill_array_open_close -dsfmt_gv_fill_array_open_open -dsfmt_gv_genrand_close1_open2 -dsfmt_gv_genrand_close_open -dsfmt_gv_genrand_open_close -dsfmt_gv_genrand_open_open -dsfmt_gv_genrand_uint32 -dsfmt_gv_init_by_array -dsfmt_gv_init_gen_rand -dsfmt_init_by_array -dsfmt_init_gen_rand diff --git a/deps/libgit2.mk b/deps/libgit2.mk index 5902cc68960ae..9bd7bd555d89d 100644 --- a/deps/libgit2.mk +++ b/deps/libgit2.mk @@ -13,7 +13,7 @@ ifeq ($(USE_SYSTEM_MBEDTLS), 0) $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured: | $(build_prefix)/manifest/mbedtls endif -LIBGIT2_OPTS := $(CMAKE_COMMON) -DCMAKE_BUILD_TYPE=Release -DTHREADSAFE=ON -DUSE_BUNDLED_ZLIB=ON +LIBGIT2_OPTS := $(CMAKE_COMMON) -DCMAKE_BUILD_TYPE=Release -DUSE_THREADS=ON -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON -DBUILD_CLI=OFF ifeq ($(OS),WINNT) LIBGIT2_OPTS += -DWIN32=ON -DMINGW=ON ifneq ($(ARCH),x86_64) @@ -30,25 +30,11 @@ endif endif ifneq (,$(findstring $(OS),Linux FreeBSD)) -LIBGIT2_OPTS += -DUSE_HTTPS="mbedTLS" -DSHA1_BACKEND="CollisionDetection" -DCMAKE_INSTALL_RPATH="\$$ORIGIN" +LIBGIT2_OPTS += -DUSE_HTTPS="mbedTLS" -DUSE_SHA1="CollisionDetection" -DCMAKE_INSTALL_RPATH="\$$ORIGIN" endif LIBGIT2_SRC_PATH := $(SRCCACHE)/$(LIBGIT2_SRC_DIR) -$(LIBGIT2_SRC_PATH)/libgit2-agent-nonfatal.patch-applied: $(LIBGIT2_SRC_PATH)/source-extracted - cd $(LIBGIT2_SRC_PATH) && \ - patch -p1 -f < $(SRCDIR)/patches/libgit2-agent-nonfatal.patch - echo 1 > $@ - -$(LIBGIT2_SRC_PATH)/libgit2-hostkey.patch-applied: $(LIBGIT2_SRC_PATH)/libgit2-agent-nonfatal.patch-applied - cd $(LIBGIT2_SRC_PATH) && \ - patch -p1 -f < $(SRCDIR)/patches/libgit2-hostkey.patch - echo 1 > $@ - -$(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured: \ - $(LIBGIT2_SRC_PATH)/libgit2-agent-nonfatal.patch-applied \ - $(LIBGIT2_SRC_PATH)/libgit2-hostkey.patch-applied - $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured: $(LIBGIT2_SRC_PATH)/source-extracted mkdir -p $(dir $@) cd $(dir $@) && \ @@ -80,8 +66,8 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)libgit2.$$(SHLIB_EXT) $$(build_shlibdir)/libgit2.$$(SHLIB_EXT))) clean-libgit2: - -rm $(build_datarootdir)/julia/cert.pem - -rm $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-compiled + -rm -f $(build_datarootdir)/julia/cert.pem + -rm -f $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-compiled -$(MAKE) -C $(BUILDDIR)/$(LIBGIT2_SRC_DIR) clean get-libgit2: $(LIBGIT2_SRC_FILE) diff --git a/deps/libgit2.version b/deps/libgit2.version index 042f76bba673e..b8cefc3c5c6f3 100644 --- a/deps/libgit2.version +++ b/deps/libgit2.version @@ -1,2 +1,13 @@ -LIBGIT2_BRANCH=v1.3.0 -LIBGIT2_SHA1=b7bad55e4bb0a285b073ba5e02b01d3f522fc95d +# -*- makefile -*- +## jll artifact +LIBGIT2_JLL_NAME := LibGit2 + +## source build +LIBGIT2_BRANCH=v1.6.1 +LIBGIT2_SHA1=8a871d13b7f4e186b8ad943ae5a7fcf30be52e67 + +## 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 diff --git a/deps/libssh2.mk b/deps/libssh2.mk index e852d59fa996d..d0174c0c090e2 100644 --- a/deps/libssh2.mk +++ b/deps/libssh2.mk @@ -36,8 +36,15 @@ $(LIBSSH2_SRC_PATH)/libssh2-userauth-check.patch-applied: $(LIBSSH2_SRC_PATH)/so patch -p1 -f < $(SRCDIR)/patches/libssh2-userauth-check.patch echo 1 > $@ +# issue: https://github.com/JuliaLang/julia/issues/45645#issuecomment-1153214379 +# fix pr: https://github.com/libssh2/libssh2/pull/711 +$(LIBSSH2_SRC_PATH)/libssh2-fix-import-lib-name.patch-applied: $(LIBSSH2_SRC_PATH)/libssh2-userauth-check.patch-applied + cd $(LIBSSH2_SRC_PATH) && \ + patch -p1 -f < $(SRCDIR)/patches/libssh2-fix-import-lib-name.patch + echo 1 > $@ + $(BUILDDIR)/$(LIBSSH2_SRC_DIR)/build-configured: \ - $(LIBSSH2_SRC_PATH)/libssh2-userauth-check.patch-applied + $(LIBSSH2_SRC_PATH)/libssh2-fix-import-lib-name.patch-applied $(BUILDDIR)/$(LIBSSH2_SRC_DIR)/build-configured: $(LIBSSH2_SRC_PATH)/source-extracted mkdir -p $(dir $@) @@ -61,7 +68,7 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)libssh2.$$(SHLIB_EXT) $$(build_shlibdir)/libssh2.$$(SHLIB_EXT))) clean-libssh2: - -rm $(BUILDDIR)/$(LIBSSH2_SRC_DIR)/build-configured $(BUILDDIR)/$(LIBSSH2_SRC_DIR)/build-compiled + -rm -f $(BUILDDIR)/$(LIBSSH2_SRC_DIR)/build-configured $(BUILDDIR)/$(LIBSSH2_SRC_DIR)/build-compiled -$(MAKE) -C $(BUILDDIR)/$(LIBSSH2_SRC_DIR) clean diff --git a/deps/libssh2.version b/deps/libssh2.version index 1c4d5412c0c09..3d5b2bb98d7eb 100644 --- a/deps/libssh2.version +++ b/deps/libssh2.version @@ -1,2 +1,7 @@ +## jll artifact +LIBSSH2_JLL_NAME := LibSSH2 + +## source build +LIBSSH2_VER := 1.10.2 LIBSSH2_BRANCH=libssh2-1.10.0 LIBSSH2_SHA1=635caa90787220ac3773c1d5ba11f1236c22eae8 diff --git a/deps/libsuitesparse.mk b/deps/libsuitesparse.mk index 42ae7807bb4ec..7d79e03ee8d0e 100644 --- a/deps/libsuitesparse.mk +++ b/deps/libsuitesparse.mk @@ -1,4 +1,5 @@ ## LIBSUITESPARSE ## +include $(SRCDIR)/libsuitesparse.version ifeq ($(USE_BLAS64), 1) UMFPACK_CONFIG := -DLONGBLAS='long long' @@ -17,13 +18,18 @@ 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) -SUITE_SPARSE_LIB := $(LDFLAGS) -L"$(abspath $(BUILDDIR))/SuiteSparse-$(LIBSUITESPARSE_VER)/lib" +SUITESPARSE_LIB := $(LDFLAGS) -L"$(abspath $(BUILDDIR))/SuiteSparse-$(LIBSUITESPARSE_VER)/lib" ifeq ($(OS), Darwin) -SUITE_SPARSE_LIB += $(RPATH_ESCAPED_ORIGIN) +SUITESPARSE_LIB += $(RPATH_ESCAPED_ORIGIN) endif -LIBSUITESPARSE_MFLAGS := CC="$(CC)" CXX="$(CXX)" F77="$(FC)" AR="$(AR)" RANLIB="$(RANLIB)" BLAS="-L$(build_shlibdir) -lblastrampoline" LAPACK="-L$(build_shlibdir) -lblastrampoline" \ - LDFLAGS="$(SUITE_SPARSE_LIB)" CFOPENMP="" CUDA=no CUDA_PATH="" \ - UMFPACK_CONFIG="$(UMFPACK_CONFIG)" CHOLMOD_CONFIG="$(CHOLMOD_CONFIG)" SPQR_CONFIG="$(SPQR_CONFIG)" +LIBSUITESPARSE_MFLAGS := CC="$(CC) $(SANITIZE_OPTS)" CXX="$(CXX) $(SANITIZE_OPTS)" F77="$(FC)" \ + AR="$(AR)" RANLIB="$(RANLIB)" \ + BLAS="-L$(build_shlibdir) -lblastrampoline" \ + LAPACK="-L$(build_shlibdir) -lblastrampoline" \ + LDFLAGS="$(SUITESPARSE_LIB) $(SANITIZE_LDFLAGS)" CFOPENMP="" CUDA=no CUDA_PATH="" \ + UMFPACK_CONFIG="$(UMFPACK_CONFIG)" \ + CHOLMOD_CONFIG="$(CHOLMOD_CONFIG)" \ + SPQR_CONFIG="$(SPQR_CONFIG)" ifeq ($(OS),WINNT) LIBSUITESPARSE_MFLAGS += UNAME=Windows else @@ -80,13 +86,13 @@ $(build_prefix)/manifest/libsuitesparse: $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARS echo $(UNINSTALL_libsuitesparse) > $@ clean-libsuitesparse: uninstall-libsuitesparse - -rm $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-compiled + -rm -f $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/build-compiled -rm -fr $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/lib -rm -fr $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER)/include -$(MAKE) -C $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER) clean distclean-libsuitesparse: - -rm -rf $(SRCCACHE)/SuiteSparse-$(LIBSUITESPARSE_VER).tar.gz \ + rm -rf $(SRCCACHE)/SuiteSparse-$(LIBSUITESPARSE_VER).tar.gz \ $(BUILDDIR)/SuiteSparse-$(LIBSUITESPARSE_VER) get-libsuitesparse: $(SRCCACHE)/SuiteSparse-$(LIBSUITESPARSE_VER).tar.gz @@ -107,6 +113,6 @@ endif define manual_libsuitesparse uninstall-libsuitesparse: - -rm $(build_prefix)/manifest/libsuitesparse - -rm $(addprefix $(build_shlibdir)/lib,$3) + -rm -f $(build_prefix)/manifest/libsuitesparse + -rm -f $(addprefix $(build_shlibdir)/lib,$3) endef diff --git a/deps/libsuitesparse.version b/deps/libsuitesparse.version new file mode 100644 index 0000000000000..2237db6f2d116 --- /dev/null +++ b/deps/libsuitesparse.version @@ -0,0 +1,5 @@ +## jll artifact +LIBSUITESPARSE_JLL_NAME := SuiteSparse + +## source build +LIBSUITESPARSE_VER := 5.10.1 diff --git a/deps/libtracyclient.mk b/deps/libtracyclient.mk new file mode 100644 index 0000000000000..92d6bee4caea6 --- /dev/null +++ b/deps/libtracyclient.mk @@ -0,0 +1,83 @@ +## LIBTRACYCLIENT ## +ifneq ($(USE_BINARYBUILDER_LIBTRACYCLIENT),1) +LIBTRACYCLIENT_GIT_URL:=https://github.com/wolfpld/tracy.git +LIBTRACYCLIENT_TAR_URL=https://api.github.com/repos/wolfpld/tracy/tarball/$1 +$(eval $(call git-external,libtracyclient,LIBTRACYCLIENT,,,$(BUILDDIR))) + +LIBTRACYCLIENT_BUILDDIR := $(BUILDDIR)/$(LIBTRACYCLIENT_SRC_DIR) +LIBTRACYCLIENT_SRCCACHE := $(SRCCACHE)/$(LIBTRACYCLIENT_SRC_DIR) + +LIBTRACYCLIENT_CMAKE := +LIBTRACYCLIENT_CMAKE += -DBUILD_SHARED_LIBS=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_FIBERS=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_ONLY_LOCALHOST=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_NO_CODE_TRANSFER=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_NO_FRAME_IMAGE=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_NO_CRASH_HANDLER=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_ON_DEMAND=ON +LIBTRACYCLIENT_CMAKE += -DTRACY_TIMER_FALLBACK=ON + +ifeq ($(WITH_TRACY_CALLSTACKS),1) +LIBTRACYCLIENT_CMAKE += -DTRACY_CALLSTACK=32 +else +LIBTRACYCLIENT_CMAKE += -DTRACY_NO_SAMPLING=ON +endif + +$(LIBTRACYCLIENT_BUILDDIR)/cmake-patch-applied: $(LIBTRACYCLIENT_BUILDDIR)/source-extracted +ifneq ($(OS),WINNT) + echo "target_compile_definitions(TracyClient PUBLIC __STDC_FORMAT_MACROS)" >> $(LIBTRACYCLIENT_BUILDDIR)/CMakeLists.txt +else + echo "target_compile_definitions(TracyClient PUBLIC WINVER=0x0602 _WIN32_WINNT=0x0602)" >> $(LIBTRACYCLIENT_BUILDDIR)/CMakeLists.txt +endif + echo 1 > $@ + +$(LIBTRACYCLIENT_BUILDDIR)/libTracyClient-freebsd-elfw.patch-applied: $(LIBTRACYCLIENT_BUILDDIR)/cmake-patch-applied + cd $(LIBTRACYCLIENT_BUILDDIR) && \ + patch -p1 -f < $(SRCDIR)/patches/libTracyClient-freebsd-elfw.patch + echo 1 > $@ + +$(LIBTRACYCLIENT_BUILDDIR)/libTracyClient-no-sampling.patch-applied: $(LIBTRACYCLIENT_BUILDDIR)/libTracyClient-freebsd-elfw.patch-applied + cd $(LIBTRACYCLIENT_BUILDDIR) && \ + patch -p1 -f < $(SRCDIR)/patches/libTracyClient-no-sampling.patch + echo 1 > $@ + +$(LIBTRACYCLIENT_BUILDDIR)/libTracyClient-plot-config.patch-applied: $(LIBTRACYCLIENT_BUILDDIR)/libTracyClient-no-sampling.patch-applied + cd $(LIBTRACYCLIENT_BUILDDIR) && \ + patch -p1 -f < $(SRCDIR)/patches/libTracyClient-plot-config.patch + echo 1 > $@ + +$(LIBTRACYCLIENT_BUILDDIR)/build-configured: $(LIBTRACYCLIENT_BUILDDIR)/libTracyClient-plot-config.patch-applied + mkdir -p $(dir $@) + cd $(dir $@) && \ + $(CMAKE) . $(CMAKE_GENERATOR_COMMAND) $(CMAKE_COMMON) $(LIBTRACYCLIENT_CMAKE) \ + || { echo '*** To install a newer version of cmake, run contrib/download_cmake.sh ***' && false; } + echo 1 > $@ + +$(LIBTRACYCLIENT_BUILDDIR)/build-compiled: $(LIBTRACYCLIENT_BUILDDIR)/build-configured + cd $(LIBTRACYCLIENT_BUILDDIR) && \ + $(if $(filter $(CMAKE_GENERATOR),make), \ + $(MAKE), \ + $(CMAKE) --build .) + echo 1 > $@ + +$(eval $(call staged-install, \ + libtracyclient,$$(LIBTRACYCLIENT_SRC_DIR), \ + MAKE_INSTALL,,, \ + $$(INSTALL_NAME_CMD)libtracyclient.$$(SHLIB_EXT) $$(build_shlibdir)/libtracyclient.$$(SHLIB_EXT))) + +clean-libtracyclient: + rm -rf $(LIBTRACYCLIENT_BUILDDIR)/build-configured $(LIBTRACYCLIENT_BUILDDIR)/build-compiled + -$(MAKE) -C $(LIBTRACYCLIENT_BUILDDIR) clean + +get-libtracyclient: $(LIBTRACYCLIENT_SRC_FILE) +extract-libtracyclient: $(LIBTRACYCLIENT_BUILDDIR)/source-extracted +configure-libtracyclient: $(LIBTRACYCLIENT_BUILDDIR)/build-configured +compile-libtracyclient: $(LIBTRACYCLIENT_BUILDDIR)/build-compiled +fastcheck-libtracyclient: check-libtracyclient +check-libtracyclient: compile-libtracyclient + +else # USE_BINARYBUILDER_LIBTRACYCLIENT + +$(eval $(call bb-install,libtracyclient,LIBTRACYCLIENT,false)) + +endif diff --git a/deps/libtracyclient.version b/deps/libtracyclient.version new file mode 100644 index 0000000000000..0baf8504261f1 --- /dev/null +++ b/deps/libtracyclient.version @@ -0,0 +1,8 @@ +## jll artifact +LIBTRACYCLIENT_JLL_NAME := LibTracyClient +LIBTRACYCLIENT_JLL_VER := 0.9.1+2 + +## source build +LIBTRACYCLIENT_VER := 0.9.1 +LIBTRACYCLIENT_BRANCH=v0.9.1 +LIBTRACYCLIENT_SHA1=897aec5b062664d2485f4f9a213715d2e527e0ca diff --git a/deps/libuv.mk b/deps/libuv.mk index a51cc5a9f6bb7..eacabac55e34f 100644 --- a/deps/libuv.mk +++ b/deps/libuv.mk @@ -18,6 +18,21 @@ LIBUV_BUILDDIR := $(BUILDDIR)/$(LIBUV_SRC_DIR) ifneq ($(CLDFLAGS)$(SANITIZE_LDFLAGS),) $(LIBUV_BUILDDIR)/build-configured: LDFLAGS:=$(LDFLAGS) $(CLDFLAGS) $(SANITIZE_LDFLAGS) endif + +ifeq ($(OS), emscripten) +$(LIBUV_BUILDDIR)/build-configured: $(SRCCACHE)/$(LIBUV_SRC_DIR)/source-extracted + mkdir -p $(dir $@) + cd $(dir $@) && cmake -E env \ + CMAKE_C_FLAGS="-pthread" \ + CMAKE_SHARED_LINKER_FLAGS="-sTOTAL_MEMORY=65536000 -pthread" \ + CMAKE_EXE_LINKER_FLAGS="-sTOTAL_MEMORY=65536000 -pthread" \ + emcmake cmake $(dir $<) $(CMAKE_COMMON) -DBUILD_TESTING=OFF + echo 1 > $@ + +$(LIBUV_BUILDDIR)/build-compiled: $(LIBUV_BUILDDIR)/build-configured + emmake $(MAKE) -C $(dir $<) $(UV_MFLAGS) + echo 1 > $@ +else $(LIBUV_BUILDDIR)/build-configured: $(SRCCACHE)/$(LIBUV_SRC_DIR)/source-extracted touch -c $(SRCCACHE)/$(LIBUV_SRC_DIR)/aclocal.m4 # touch a few files to prevent autogen from getting called touch -c $(SRCCACHE)/$(LIBUV_SRC_DIR)/Makefile.in @@ -30,6 +45,7 @@ $(LIBUV_BUILDDIR)/build-configured: $(SRCCACHE)/$(LIBUV_SRC_DIR)/source-extracte $(LIBUV_BUILDDIR)/build-compiled: $(LIBUV_BUILDDIR)/build-configured $(MAKE) -C $(dir $<) $(UV_MFLAGS) echo 1 > $@ +endif $(LIBUV_BUILDDIR)/build-checked: $(LIBUV_BUILDDIR)/build-compiled ifeq ($(OS),$(BUILD_OS)) @@ -43,7 +59,7 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)libuv.$$(SHLIB_EXT) $$(build_shlibdir)/libuv.$$(SHLIB_EXT))) clean-libuv: - -rm -rf $(LIBUV_BUILDDIR)/build-configured $(LIBUV_BUILDDIR)/build-compiled + rm -rf $(LIBUV_BUILDDIR)/build-configured $(LIBUV_BUILDDIR)/build-compiled -$(MAKE) -C $(LIBUV_BUILDDIR) clean diff --git a/deps/libuv.version b/deps/libuv.version index 7339533223083..01bf4fecc6dc6 100644 --- a/deps/libuv.version +++ b/deps/libuv.version @@ -1,2 +1,7 @@ -LIBUV_BRANCH=julia-uv2-1.42.0 -LIBUV_SHA1=3a63bf71de62c64097989254e4f03212e3bf5fc8 +## jll artifact +LIBUV_JLL_NAME := LibUV + +## source build +LIBUV_VER := 2 +LIBUV_BRANCH=julia-uv2-1.44.2 +LIBUV_SHA1=2723e256e952be0b015b3c0086f717c3d365d97e diff --git a/deps/libwhich.mk b/deps/libwhich.mk index aae5dead6f9f4..79017838193d2 100644 --- a/deps/libwhich.mk +++ b/deps/libwhich.mk @@ -25,7 +25,7 @@ $(eval $(call staged-install, \ LIBWHICH_INSTALL,,,)) clean-libwhich: - -rm $(BUILDDIR)/$(LIBWHICH_SRC_DIR)/build-compiled + -rm -f $(BUILDDIR)/$(LIBWHICH_SRC_DIR)/build-compiled -$(MAKE) -C $(BUILDDIR)/$(LIBWHICH_SRC_DIR) clean get-libwhich: $(LIBWHICH_SRC_FILE) diff --git a/deps/lld.version b/deps/lld.version new file mode 100644 index 0000000000000..d4b2a664d980c --- /dev/null +++ b/deps/lld.version @@ -0,0 +1,3 @@ +## jll artifact +LLD_JLL_NAME := LLD +LLD_JLL_VER := 15.0.7+5 diff --git a/deps/llvm-tools.version b/deps/llvm-tools.version new file mode 100644 index 0000000000000..f2ecd0b33e989 --- /dev/null +++ b/deps/llvm-tools.version @@ -0,0 +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+5 +LLVM_TOOLS_ASSERT_JLL_VER := 15.0.7+5 diff --git a/deps/llvm-ver.make b/deps/llvm-ver.make index c2c7f2bc56da7..3c498be6c2363 100644 --- a/deps/llvm-ver.make +++ b/deps/llvm-ver.make @@ -1,3 +1,5 @@ +include $(JULIAHOME)/deps/llvm.version + LLVM_VER_MAJ:=$(word 1, $(subst ., ,$(LLVM_VER))) LLVM_VER_MIN:=$(word 2, $(subst ., ,$(LLVM_VER))) # define a "short" LLVM version for easy comparisons @@ -10,3 +12,8 @@ LLVM_VER_PATCH:=$(word 3, $(subst ., ,$(LLVM_VER))) ifeq ($(LLVM_VER_PATCH),) LLVM_VER_PATCH := 0 endif + +LLVM_SHARED_LIB_VER_SUFFIX := $(LLVM_VER_MAJ)jl +# e.g.: "libLLVM-14jl" +LLVM_SHARED_LIB_NAME := libLLVM-$(LLVM_SHARED_LIB_VER_SUFFIX) +LLVM_SHARED_LINK_FLAG := -lLLVM-$(LLVM_SHARED_LIB_VER_SUFFIX) diff --git a/deps/llvm.mk b/deps/llvm.mk index 5afef0b83ba3c..83b9a66ec608e 100644 --- a/deps/llvm.mk +++ b/deps/llvm.mk @@ -1,4 +1,5 @@ ## LLVM ## +include $(SRCDIR)/llvm.version include $(SRCDIR)/llvm-ver.make include $(SRCDIR)/llvm-options.mk @@ -55,12 +56,15 @@ endif ifeq ($(BUILD_LIBCXX), 1) LLVM_ENABLE_RUNTIMES := $(LLVM_ENABLE_RUNTIMES);libcxx;libcxxabi endif +ifeq ($(BUILD_LLD), 1) +LLVM_ENABLE_PROJECTS := $(LLVM_ENABLE_PROJECTS);lld +endif LLVM_LIB_FILE := libLLVMCodeGen.a # Figure out which targets to build -LLVM_TARGETS := host;NVPTX;AMDGPU;WebAssembly;BPF +LLVM_TARGETS := host;NVPTX;AMDGPU;WebAssembly;BPF;AVR LLVM_EXPERIMENTAL_TARGETS := LLVM_CFLAGS := @@ -78,6 +82,9 @@ LLVM_CMAKE += -DLLVM_EXTERNAL_RV_SOURCE_DIR=$(LLVM_MONOSRC_DIR)/rv LLVM_CMAKE += -DLLVM_CXX_STD=c++14 endif +# Otherwise LLVM will translate \\ to / on mingw +LLVM_CMAKE += -DLLVM_WINDOWS_PREFER_FORWARD_SLASH=False + # Allow adding LLVM specific flags LLVM_CFLAGS += $(CFLAGS) LLVM_CXXFLAGS += $(CXXFLAGS) @@ -94,7 +101,7 @@ endif LLVM_CMAKE += -DLLVM_TOOLS_INSTALL_DIR=$(call rel_path,$(build_prefix),$(build_depsbindir)) LLVM_CMAKE += -DLLVM_UTILS_INSTALL_DIR=$(call rel_path,$(build_prefix),$(build_depsbindir)) LLVM_CMAKE += -DLLVM_INCLUDE_UTILS=ON -DLLVM_INSTALL_UTILS=ON -LLVM_CMAKE += -DLLVM_BINDINGS_LIST="" -DLLVM_INCLUDE_DOCS=Off -DLLVM_ENABLE_TERMINFO=Off -DHAVE_HISTEDIT_H=Off -DHAVE_LIBEDIT=Off +LLVM_CMAKE += -DLLVM_BINDINGS_LIST="" -DLLVM_ENABLE_BINDINGS=OFF -DLLVM_INCLUDE_DOCS=Off -DLLVM_ENABLE_TERMINFO=Off -DHAVE_HISTEDIT_H=Off -DHAVE_LIBEDIT=Off ifeq ($(LLVM_ASSERTIONS), 1) LLVM_CMAKE += -DLLVM_ENABLE_ASSERTIONS:BOOL=ON endif # LLVM_ASSERTIONS @@ -113,7 +120,7 @@ ifeq ($(USE_LLVM_SHLIB),1) LLVM_CMAKE += -DLLVM_BUILD_LLVM_DYLIB:BOOL=ON -DLLVM_LINK_LLVM_DYLIB:BOOL=ON endif ifeq ($(USE_INTEL_JITEVENTS), 1) -LLVM_CMAKE += -DLLVM_USE_INTEL_JITEVENTS:BOOL=ON +LLVM_CMAKE += -DLLVM_USE_INTEL_JITEVENTS:BOOL=ON -DITTAPI_SOURCE_DIR=$(SRCCACHE)/$(ITTAPI_SRC_DIR) endif # USE_INTEL_JITEVENTS ifeq ($(USE_OPROFILE_JITEVENTS), 1) @@ -143,7 +150,7 @@ endif ifeq ($(LLVM_SANITIZE),1) ifeq ($(SANITIZE_MEMORY),1) LLVM_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins -LLVM_LDFLAGS += -fsanitize=memory -fsanitize-memory-track-origins +LLVM_LDFLAGS += -fsanitize=memory -fsanitize-memory-track-origins -rpath $(build_shlibdir) LLVM_CXXFLAGS += -fsanitize=memory -fsanitize-memory-track-origins LLVM_CMAKE += -DLLVM_USE_SANITIZER="MemoryWithOrigins" endif @@ -194,21 +201,11 @@ ifeq ($(BUILD_LLDB),0) LLVM_CMAKE += -DLLVM_TOOL_LLDB_BUILD=OFF endif -# LLDB still relies on plenty of python 2.x infrastructure, without checking -llvm_python_location=$(shell /usr/bin/env python2 -c 'import sys; print(sys.executable)') -llvm_python_workaround=$(SRCCACHE)/python2_path -$(llvm_python_workaround): - mkdir -p $@ - -python -c 'import sys; sys.exit(not sys.version_info > (3, 0))' && \ - /usr/bin/env python2 -c 'import sys; sys.exit(not sys.version_info < (3, 0))' && \ - ln -sf $(llvm_python_location) "$@/python" && \ - ln -sf $(llvm_python_location)-config "$@/python-config" - LLVM_CMAKE += -DCMAKE_EXE_LINKER_FLAGS="$(LLVM_LDFLAGS)" \ -DCMAKE_SHARED_LINKER_FLAGS="$(LLVM_LDFLAGS)" # change the SONAME of Julia's private LLVM -# i.e. libLLVM-6.0jl.so +# i.e. libLLVM-14jl.so # see #32462 LLVM_CMAKE += -DLLVM_VERSION_SUFFIX:STRING="jl" LLVM_CMAKE += -DLLVM_SHLIB_SYMBOL_VERSION:STRING="JL_LLVM_$(LLVM_VER_SHORT)" @@ -233,43 +230,42 @@ $$(LLVM_BUILDDIR_withtype)/build-compiled: $$(SRCCACHE)/$$(LLVM_SRC_DIR)/$1.patc LLVM_PATCH_PREV := $$(SRCCACHE)/$$(LLVM_SRC_DIR)/$1.patch-applied endef +ifeq ($(USE_SYSTEM_ZLIB), 0) +$(LLVM_BUILDDIR_withtype)/build-configured: | $(build_prefix)/manifest/zlib +endif + # NOTE: LLVM 12 and 13 have their patches applied to JuliaLang/llvm-project # declare that all patches must be applied before running ./configure -$(LLVM_BUILDDIR_withtype)/build-configured: | $(LLVM_PATCH_PREV) $(build_prefix)/manifest/zlib +$(LLVM_BUILDDIR_withtype)/build-configured: | $(LLVM_PATCH_PREV) -$(LLVM_BUILDDIR_withtype)/build-configured: $(SRCCACHE)/$(LLVM_SRC_DIR)/source-extracted | $(llvm_python_workaround) +$(LLVM_BUILDDIR_withtype)/build-configured: $(SRCCACHE)/$(LLVM_SRC_DIR)/source-extracted mkdir -p $(dir $@) cd $(dir $@) && \ - export PATH=$(llvm_python_workaround):"$$PATH" && \ $(CMAKE) $(SRCCACHE)/$(LLVM_SRC_DIR)/llvm $(CMAKE_GENERATOR_COMMAND) $(CMAKE_COMMON) $(LLVM_CMAKE) \ || { echo '*** To install a newer version of cmake, run contrib/download_cmake.sh ***' && false; } echo 1 > $@ -$(LLVM_BUILDDIR_withtype)/build-compiled: $(LLVM_BUILDDIR_withtype)/build-configured | $(llvm_python_workaround) +$(LLVM_BUILDDIR_withtype)/build-compiled: $(LLVM_BUILDDIR_withtype)/build-configured cd $(LLVM_BUILDDIR_withtype) && \ - export PATH=$(llvm_python_workaround):"$$PATH" && \ $(if $(filter $(CMAKE_GENERATOR),make), \ $(MAKE), \ $(CMAKE) --build .) echo 1 > $@ -$(LLVM_BUILDDIR_withtype)/build-checked: $(LLVM_BUILDDIR_withtype)/build-compiled | $(llvm_python_workaround) +$(LLVM_BUILDDIR_withtype)/build-checked: $(LLVM_BUILDDIR_withtype)/build-compiled ifeq ($(OS),$(BUILD_OS)) cd $(LLVM_BUILDDIR_withtype) && \ - export PATH=$(llvm_python_workaround):"$$PATH" && \ $(CMAKE) --build . --target check endif echo 1 > $@ -$(build_prefix)/manifest/llvm: | $(llvm_python_workaround) - LLVM_INSTALL = \ cd $1 && mkdir -p $2$$(build_depsbindir) && \ - cp -r $$(SRCCACHE)/$$(LLVM_SRC_DIR)/llvm/utils/lit $2$$(build_depsbindir)/ && \ - $$(CMAKE) -DCMAKE_INSTALL_PREFIX="$2$$(build_prefix)" -P cmake_install.cmake + cp -r $$(SRCCACHE)/$$(LLVM_SRC_DIR)/llvm/utils/lit $2$$(build_depsbindir)/ && \ + $$(CMAKE) -DCMAKE_INSTALL_PREFIX="$2$$(build_prefix)" -P cmake_install.cmake ifeq ($(OS), WINNT) -LLVM_INSTALL += && cp $2$$(build_shlibdir)/libLLVM.dll $2$$(build_depsbindir) +LLVM_INSTALL += && cp $2$$(build_shlibdir)/$(LLVM_SHARED_LIB_NAME).dll $2$$(build_depsbindir) endif ifeq ($(OS),Darwin) # https://github.com/JuliaLang/julia/issues/29981 @@ -281,7 +277,7 @@ $(eval $(call staged-install, \ LLVM_INSTALL,,,)) clean-llvm: - -rm $(LLVM_BUILDDIR_withtype)/build-configured $(LLVM_BUILDDIR_withtype)/build-compiled + -rm -f $(LLVM_BUILDDIR_withtype)/build-configured $(LLVM_BUILDDIR_withtype)/build-compiled -$(MAKE) -C $(LLVM_BUILDDIR_withtype) clean get-llvm: $(LLVM_SRC_FILE) @@ -290,22 +286,38 @@ configure-llvm: $(LLVM_BUILDDIR_withtype)/build-configured compile-llvm: $(LLVM_BUILDDIR_withtype)/build-compiled fastcheck-llvm: #none check-llvm: $(LLVM_BUILDDIR_withtype)/build-checked + +ifeq ($(USE_INTEL_JITEVENTS),1) +extract-llvm: $(SRCCACHE)/$(ITTAPI_SRC_DIR)/source-extracted +endif + #todo: LLVM make check target is broken on julia.mit.edu (and really slow elsewhere) else # USE_BINARYBUILDER_LLVM # We provide a way to subversively swap out which LLVM JLL we pull artifacts from ifeq ($(LLVM_ASSERTIONS), 1) -LLVM_JLL_DOWNLOAD_NAME := libLLVM_assert -LLVM_JLL_VER := $(LLVM_ASSERT_JLL_VER) -LLVM_TOOLS_JLL_DOWNLOAD_NAME := LLVM_assert -LLVM_TOOLS_JLL_VER := $(LLVM_TOOLS_ASSERT_JLL_VER) +# LLVM_JLL_DOWNLOAD_NAME := libLLVM_assert +# LLVM_JLL_VER := $(LLVM_ASSERT_JLL_VER) +# LLVM_TOOLS_JLL_DOWNLOAD_NAME := LLVM_assert +# LLVM_TOOLS_JLL_VER := $(LLVM_TOOLS_ASSERT_JLL_VER) +LLVM_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ).asserts +CLANG_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ).asserts +LLD_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ).asserts +LLVM_TOOLS_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ).asserts +else +LLVM_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ) +CLANG_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ) +LLD_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ) +LLVM_TOOLS_JLL_TAGS := -llvm_version+$(LLVM_VER_MAJ) endif $(eval $(call bb-install,llvm,LLVM,false,true)) +$(eval $(call bb-install,lld,LLD,false,true)) $(eval $(call bb-install,clang,CLANG,false,true)) $(eval $(call bb-install,llvm-tools,LLVM_TOOLS,false,true)) -install-clang install-llvm-tools: install-llvm - endif # USE_BINARYBUILDER_LLVM + +get-lld: get-llvm +install-lld install-clang install-llvm-tools: install-llvm diff --git a/deps/llvm.version b/deps/llvm.version index ed9cfbcfc7a25..e35db3bd6aed2 100644 --- a/deps/llvm.version +++ b/deps/llvm.version @@ -1,2 +1,7 @@ -LLVM_BRANCH=julia-13.0.1-0 -LLVM_SHA1=julia-13.0.1-0 +## jll artifact +LLVM_JLL_NAME := libLLVM +LLVM_ASSERT_JLL_VER := 15.0.7+5 +## source build +LLVM_VER := 15.0.7 +LLVM_BRANCH=julia-15.0.7-5 +LLVM_SHA1=julia-15.0.7-5 diff --git a/deps/llvmunwind.version b/deps/llvmunwind.version new file mode 100644 index 0000000000000..7d13af9a158f7 --- /dev/null +++ b/deps/llvmunwind.version @@ -0,0 +1,5 @@ +## jll artifact +LLVMUNWIND_JLL_NAME := LLVMLibUnwind + +## source build +LLVMUNWIND_VER := 12.0.1 diff --git a/deps/mbedtls.mk b/deps/mbedtls.mk index 07d830441a090..b4147c2c2684e 100644 --- a/deps/mbedtls.mk +++ b/deps/mbedtls.mk @@ -1,13 +1,14 @@ ## mbedtls +include $(SRCDIR)/mbedtls.version ifneq ($(USE_BINARYBUILDER_MBEDTLS), 1) MBEDTLS_SRC = mbedtls-$(MBEDTLS_VER) -MBEDTLS_URL = https://github.com/ARMmbed/mbedtls/archive/v$(MBEDTLS_VER).tar.gz +MBEDTLS_URL = https://github.com/Mbed-TLS/mbedtls/archive/v$(MBEDTLS_VER).tar.gz MBEDTLS_OPTS := $(CMAKE_COMMON) -DUSE_SHARED_MBEDTLS_LIBRARY=ON \ -DUSE_STATIC_MBEDTLS_LIBRARY=OFF -DENABLE_PROGRAMS=OFF -DCMAKE_BUILD_TYPE=Release -MBEDTLS_OPTS += -DENABLE_ZLIB_SUPPORT=OFF +MBEDTLS_OPTS += -DENABLE_ZLIB_SUPPORT=OFF -DMBEDTLS_FATAL_WARNINGS=OFF ifeq ($(BUILD_OS),WINNT) MBEDTLS_OPTS += -G"MSYS Makefiles" endif @@ -71,12 +72,12 @@ $(eval $(call staged-install, \ clean-mbedtls: - -rm $(BUILDDIR)/$(MBEDTLS_SRC)/build-configured \ + -rm -f $(BUILDDIR)/$(MBEDTLS_SRC)/build-configured \ $(BUILDDIR)/$(MBEDTLS_SRC)/build-compiled -$(MAKE) -C $(BUILDDIR)/$(MBEDTLS_SRC) clean distclean-mbedtls: - -rm -rf $(SRCCACHE)/$(MBEDTLS_SRC).tar.gz \ + rm -rf $(SRCCACHE)/$(MBEDTLS_SRC).tar.gz \ $(SRCCACHE)/$(MBEDTLS_SRC) \ $(BUILDDIR)/$(MBEDTLS_SRC) diff --git a/deps/mbedtls.version b/deps/mbedtls.version new file mode 100644 index 0000000000000..f262476af1684 --- /dev/null +++ b/deps/mbedtls.version @@ -0,0 +1,5 @@ +## jll artifact +MBEDTLS_JLL_NAME := MbedTLS + +## source build +MBEDTLS_VER := 2.28.2 diff --git a/deps/mpfr.mk b/deps/mpfr.mk index f908604ad8f36..5a0605ba6b601 100644 --- a/deps/mpfr.mk +++ b/deps/mpfr.mk @@ -1,4 +1,5 @@ ## MPFR ## +include $(SRCDIR)/mpfr.version ifeq ($(USE_SYSTEM_GMP), 0) $(BUILDDIR)/mpfr-$(MPFR_VER)/build-configured: | $(build_prefix)/manifest/gmp @@ -6,32 +7,29 @@ endif ifneq ($(USE_BINARYBUILDER_MPFR),1) -MPFR_OPTS := --enable-thread-safe --enable-shared-cache --disable-float128 --disable-decimal-float -ifeq ($(USE_SYSTEM_GMP), 0) -MPFR_OPTS += --with-gmp-include=$(abspath $(build_includedir)) --with-gmp-lib=$(abspath $(build_shlibdir)) -endif -ifeq ($(BUILD_OS),WINNT) -ifeq ($(OS),WINNT) -MPFR_OPTS += CFLAGS="$(CFLAGS) -DNPRINTF_L -DNPRINTF_T -DNPRINTF_J" -endif -endif - +MPFR_CONFIGURE_OPTS := $(CONFIGURE_COMMON) +MPFR_CONFIGURE_OPTS += --enable-thread-safe --enable-shared-cache --disable-float128 --disable-decimal-float +MPFR_CONFIGURE_OPTS += --enable-shared --disable-static -ifeq ($(OS),Darwin) -MPFR_CHECK_MFLAGS := LDFLAGS="$(LDFLAGS) -Wl,-rpath,'$(build_libdir)'" +ifeq ($(USE_SYSTEM_GMP), 0) +MPFR_CONFIGURE_OPTS += --with-gmp=$(abspath $(build_prefix)) endif ifeq ($(SANITIZE),1) # Force generic C build -MPFR_OPTS += --host=none-unknown-linux +MPFR_CONFIGURE_OPTS += --host=none-unknown-linux +endif + +ifeq ($(OS),emscripten) +MPFR_CONFIGURE_OPTS += CFLAGS="-fPIC" endif $(SRCCACHE)/mpfr-$(MPFR_VER).tar.bz2: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://www.mpfr.org/mpfr-$(MPFR_VER)/$(notdir $@) + $(SRCCACHE)/mpfr-$(MPFR_VER)/source-extracted: $(SRCCACHE)/mpfr-$(MPFR_VER).tar.bz2 $(JLCHECKSUM) $< cd $(dir $<) && $(TAR) -jxf $< - cp $(SRCDIR)/patches/config.sub $(SRCCACHE)/mpfr-$(MPFR_VER)/config.sub touch -c $(SRCCACHE)/mpfr-$(MPFR_VER)/configure # old target echo 1 > $@ @@ -41,30 +39,31 @@ checksum-mpfr: $(SRCCACHE)/mpfr-$(MPFR_VER).tar.bz2 $(BUILDDIR)/mpfr-$(MPFR_VER)/build-configured: $(SRCCACHE)/mpfr-$(MPFR_VER)/source-extracted mkdir -p $(dir $@) cd $(dir $@) && \ - $(dir $<)/configure $(CONFIGURE_COMMON) $(MPFR_OPTS) F77= --enable-shared --disable-static + $(dir $<)/configure $(MPFR_CONFIGURE_OPTS) echo 1 > $@ $(BUILDDIR)/mpfr-$(MPFR_VER)/build-compiled: $(BUILDDIR)/mpfr-$(MPFR_VER)/build-configured - $(MAKE) -C $(dir $<) $(LIBTOOL_CCLD) + $(MAKE) -C $(dir $<) echo 1 > $@ $(BUILDDIR)/mpfr-$(MPFR_VER)/build-checked: $(BUILDDIR)/mpfr-$(MPFR_VER)/build-compiled ifeq ($(OS),$(BUILD_OS)) - $(MAKE) -C $(dir $@) $(LIBTOOL_CCLD) check $(MPFR_CHECK_MFLAGS) + $(MAKE) -C $(dir $@) check endif echo 1 > $@ $(eval $(call staged-install, \ mpfr,mpfr-$(MPFR_VER), \ - MAKE_INSTALL,$$(LIBTOOL_CCLD),, \ - $$(INSTALL_NAME_CMD)libmpfr.$$(SHLIB_EXT) $$(build_shlibdir)/libmpfr.$$(SHLIB_EXT))) + MAKE_INSTALL,,, \ + $$(WIN_MAKE_HARD_LINK) $(build_bindir)/libmpfr-*.dll $(build_bindir)/libmpfr.dll && \ + $$(INSTALL_NAME_CMD)libmpfr.$$(SHLIB_EXT) $$(build_shlibdir)/libmpfr.$$(SHLIB_EXT))) clean-mpfr: - -rm $(BUILDDIR)/mpfr-$(MPFR_VER)/build-configured $(BUILDDIR)/mpfr-$(MPFR_VER)/build-compiled + -rm -f $(BUILDDIR)/mpfr-$(MPFR_VER)/build-configured $(BUILDDIR)/mpfr-$(MPFR_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/mpfr-$(MPFR_VER) clean distclean-mpfr: - -rm -rf $(SRCCACHE)/mpfr-$(MPFR_VER).tar.bz2 \ + rm -rf $(SRCCACHE)/mpfr-$(MPFR_VER).tar.bz2 \ $(SRCCACHE)/mpfr-$(MPFR_VER) \ $(BUILDDIR)/mpfr-$(MPFR_VER) diff --git a/deps/mpfr.version b/deps/mpfr.version new file mode 100644 index 0000000000000..e4f1c8a45aeb0 --- /dev/null +++ b/deps/mpfr.version @@ -0,0 +1,5 @@ +## jll artifact +MPFR_JLL_NAME := MPFR + +## source build +MPFR_VER := 4.2.0 diff --git a/deps/nghttp2.mk b/deps/nghttp2.mk index 2b8a18728b712..5c12a0155c017 100644 --- a/deps/nghttp2.mk +++ b/deps/nghttp2.mk @@ -1,4 +1,5 @@ ## nghttp2 +include $(SRCDIR)/nghttp2.version ifneq ($(USE_BINARYBUILDER_NGHTTP2), 1) @@ -36,11 +37,11 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)libnghttp2.$$(SHLIB_EXT) $$(build_shlibdir)/libnghttp2.$$(SHLIB_EXT))) clean-nghttp2: - -rm $(BUILDDIR)/nghttp2-$(NGHTTP2_VER)/build-configured $(BUILDDIR)/nghttp2-$(NGHTTP2_VER)/build-compiled + -rm -f $(BUILDDIR)/nghttp2-$(NGHTTP2_VER)/build-configured $(BUILDDIR)/nghttp2-$(NGHTTP2_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/nghttp2-$(NGHTTP2_VER) clean distclean-nghttp2: - -rm -rf $(SRCCACHE)/nghttp2-$(NGHTTP2_VER).tar.bz2 \ + rm -rf $(SRCCACHE)/nghttp2-$(NGHTTP2_VER).tar.bz2 \ $(SRCCACHE)/nghttp2-$(NGHTTP2_VER) \ $(BUILDDIR)/nghttp2-$(NGHTTP2_VER) diff --git a/deps/nghttp2.version b/deps/nghttp2.version new file mode 100644 index 0000000000000..200e08bf4bfd9 --- /dev/null +++ b/deps/nghttp2.version @@ -0,0 +1,6 @@ +# -*- makefile -*- +## jll artifact +NGHTTP2_JLL_NAME := nghttp2 + +## source build +NGHTTP2_VER := 1.52.0 diff --git a/deps/objconv.mk b/deps/objconv.mk index 7514004457ac7..70c7289b07bfa 100644 --- a/deps/objconv.mk +++ b/deps/objconv.mk @@ -1,4 +1,5 @@ ## objconv ## +include $(SRCDIR)/objconv.version ifneq ($(USE_BINARYBUILDER_OBJCONV),1) @@ -6,7 +7,7 @@ $(SRCCACHE)/objconv.zip: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://www.agner.org/optimize/objconv.zip $(BUILDDIR)/objconv/source-extracted: $(SRCCACHE)/objconv.zip - -rm -r $(dir $@) + rm -rf $(dir $@) mkdir -p $(BUILDDIR) unzip -d $(dir $@) $< cd $(dir $@) && unzip source.zip @@ -21,10 +22,10 @@ $(eval $(call staged-install, \ BINFILE_INSTALL,$$(BUILDDIR)/objconv/objconv,,)) clean-objconv: - -rm $(BUILDDIR)/objconv/build-compiled $(build_depsbindir)/objconv + -rm -f $(BUILDDIR)/objconv/build-compiled $(build_depsbindir)/objconv distclean-objconv: - -rm -rf $(SRCCACHE)/objconv.zip $(BUILDDIR)/objconv + rm -rf $(SRCCACHE)/objconv.zip $(BUILDDIR)/objconv get-objconv: $(SRCCACHE)/objconv.zip diff --git a/deps/objconv.version b/deps/objconv.version new file mode 100644 index 0000000000000..322c8fa828a17 --- /dev/null +++ b/deps/objconv.version @@ -0,0 +1,7 @@ +## jll artifact +# Objconv (we don't ship this, so no need for a fake JLL; therefore we specify the JLL_VER here instead of in a `stdlib/Objconv_jll/Project.toml` file) +OBJCONV_JLL_NAME := Objconv +OBJCONV_JLL_VER := 2.49.1+0 + +## source build +OBJCONV_VER := 2.49.1 diff --git a/deps/openblas.mk b/deps/openblas.mk index 50873c9220f08..e2837bc47232a 100644 --- a/deps/openblas.mk +++ b/deps/openblas.mk @@ -5,7 +5,7 @@ OPENBLAS_GIT_URL := https://github.com/xianyi/OpenBLAS.git OPENBLAS_TAR_URL = https://api.github.com/repos/xianyi/OpenBLAS/tarball/$1 $(eval $(call git-external,openblas,OPENBLAS,,,$(BUILDDIR))) -OPENBLAS_BUILD_OPTS := CC="$(CC)" FC="$(FC)" LD="$(LD)" RANLIB="$(RANLIB)" TARGET=$(OPENBLAS_TARGET_ARCH) BINARY=$(BINARY) +OPENBLAS_BUILD_OPTS := CC="$(CC) $(SANITIZE_OPTS)" FC="$(FC) $(SANITIZE_OPTS)" LD="$(LD) $(SANITIZE_LDFLAGS)" RANLIB="$(RANLIB)" BINARY=$(BINARY) # Thread support ifeq ($(OPENBLAS_USE_THREAD), 1) @@ -21,15 +21,20 @@ endif OPENBLAS_BUILD_OPTS += NO_AFFINITY=1 # Build for all architectures - required for distribution +ifeq ($(SANITIZE_MEMORY),1) +OPENBLAS_BUILD_OPTS += TARGET=GENERIC +else +OPENBLAS_BUILD_OPTS += TARGET=$(OPENBLAS_TARGET_ARCH) ifeq ($(OPENBLAS_DYNAMIC_ARCH), 1) OPENBLAS_BUILD_OPTS += DYNAMIC_ARCH=1 endif +endif # 64-bit BLAS interface ifeq ($(USE_BLAS64), 1) OPENBLAS_BUILD_OPTS += INTERFACE64=1 SYMBOLSUFFIX="$(OPENBLAS_SYMBOLSUFFIX)" LIBPREFIX="libopenblas$(OPENBLAS_LIBNAMESUFFIX)" ifeq ($(OS), Darwin) -OPENBLAS_BUILD_OPTS += OBJCONV=$(abspath $(build_bindir)/objconv) +OPENBLAS_BUILD_OPTS += OBJCONV=$(abspath $(build_depsbindir)/objconv) $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-compiled: | $(build_prefix)/manifest/objconv endif endif @@ -90,12 +95,7 @@ $(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)/openblas-julia42415-lapack625-openblas3392.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-ofast-power.patch-applied - cd $(BUILDDIR)/$(OPENBLAS_SRC_DIR) && \ - patch -p1 -f < $(SRCDIR)/patches/openblas-julia42415-lapack625-openblas3392.patch - echo 1 > $@ - -$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/neoverse-generic-kernels.patch-applied: $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/openblas-julia42415-lapack625-openblas3392.patch-applied +$(BUILDDIR)/$(OPENBLAS_SRC_DIR)/neoverse-generic-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 echo 1 > $@ @@ -120,7 +120,7 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)libopenblas$$(OPENBLAS_LIBNAMESUFFIX).$$(SHLIB_EXT) $$(build_shlibdir)/libopenblas$$(OPENBLAS_LIBNAMESUFFIX).$$(SHLIB_EXT))) clean-openblas: - -rm $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-compiled + -rm -f $(BUILDDIR)/$(OPENBLAS_SRC_DIR)/build-compiled -$(MAKE) -C $(BUILDDIR)/$(OPENBLAS_SRC_DIR) clean @@ -191,11 +191,11 @@ $(eval $(call staged-install, \ $$(INSTALL_NAME_CMD)liblapack.$$(SHLIB_EXT) $$(build_shlibdir)/liblapack.$$(SHLIB_EXT))) clean-lapack: - -rm $(BUILDDIR)/lapack-$(LAPACK_VER)/build-compiled0 $(BUILDDIR)/lapack-$(LAPACK_VER)/build-compiled + -rm -f $(BUILDDIR)/lapack-$(LAPACK_VER)/build-compiled0 $(BUILDDIR)/lapack-$(LAPACK_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/lapack-$(LAPACK_VER) clean distclean-lapack: - -rm -rf $(SRCCACHE)/lapack-$(LAPACK_VER).tgz $(BUILDDIR)/lapack-$(LAPACK_VER) + rm -rf $(SRCCACHE)/lapack-$(LAPACK_VER).tgz $(BUILDDIR)/lapack-$(LAPACK_VER) get-lapack: $(SRCCACHE)/lapack-$(LAPACK_VER).tgz diff --git a/deps/openblas.version b/deps/openblas.version index 346e75dac614b..be0506fcd5137 100644 --- a/deps/openblas.version +++ b/deps/openblas.version @@ -1,2 +1,11 @@ -OPENBLAS_BRANCH=v0.3.17 -OPENBLAS_SHA1=d909f9f3d4fc4ccff36d69f178558df154ba1002 +# -*- makefile -*- +## jll artifact +OPENBLAS_JLL_NAME := OpenBLAS + +## source build +OPENBLAS_VER := 0.3.23 +OPENBLAS_BRANCH=v0.3.23 +OPENBLAS_SHA1=394a9fbafe9010b76a2615c562204277a956eb52 + +# LAPACK, source-only +LAPACK_VER := 3.9.0 diff --git a/deps/openlibm.mk b/deps/openlibm.mk index 544519e12f0d0..f99cdade47b91 100644 --- a/deps/openlibm.mk +++ b/deps/openlibm.mk @@ -16,7 +16,7 @@ $(eval $(call staged-install, \ $(INSTALL_NAME_CMD)libopenlibm.$(SHLIB_EXT) $(build_shlibdir)/libopenlibm.$(SHLIB_EXT))) clean-openlibm: - -rm $(BUILDDIR)/$(OPENLIBM_SRC_DIR)/build-compiled $(build_libdir)/libopenlibm.a + -rm -f $(BUILDDIR)/$(OPENLIBM_SRC_DIR)/build-compiled $(build_libdir)/libopenlibm.a -$(MAKE) -C $(BUILDDIR)/$(OPENLIBM_SRC_DIR) distclean $(OPENLIBM_FLAGS) diff --git a/deps/openlibm.version b/deps/openlibm.version index 9edba0c1f257b..f35b291260380 100644 --- a/deps/openlibm.version +++ b/deps/openlibm.version @@ -1,2 +1,7 @@ +## jll artifact +OPENLIBM_JLL_NAME := OpenLibm + +## source build +OPENLIBM_VER := 0.8.1 OPENLIBM_BRANCH=v0.8.1 OPENLIBM_SHA1=ae2d91698508701c83cab83714d42a1146dccf85 diff --git a/deps/p7zip.mk b/deps/p7zip.mk index 20c85602f767a..c7c2874d49a5e 100644 --- a/deps/p7zip.mk +++ b/deps/p7zip.mk @@ -1,36 +1,21 @@ ## p7zip ## +include $(SRCDIR)/p7zip.version ifneq ($(USE_BINARYBUILDER_P7ZIP),1) -# Force optimization for P7ZIP flags (Issue #11668) -$(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.bz2: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://downloads.sourceforge.net/project/p7zip/p7zip/16.02/p7zip_16.02_src_all.tar.bz2 -$(BUILDDIR)/p7zip-$(P7ZIP_VER)/source-extracted: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.bz2 +$(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz: | $(SRCCACHE) + $(JLDOWNLOAD) $@ https://github.com/p7zip-project/p7zip/archive/refs/tags/v$(P7ZIP_VER).tar.gz + +$(BUILDDIR)/p7zip-$(P7ZIP_VER)/source-extracted: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz $(JLCHECKSUM) $< mkdir -p $(dir $@) - cd $(dir $@) && $(TAR) --strip-components 1 -jxf $< + cd $(dir $@) && $(TAR) --strip-components 1 -zxf $< echo 1 > $@ -checksum-p7zip: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.bz2 +checksum-p7zip: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz $(JLCHECKSUM) $< -$(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-12-CVE-2016-9296.patch-applied: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/source-extracted - cd $(dir $@) && patch -p1 -f < $(SRCDIR)/patches/p7zip-12-CVE-2016-9296.patch - echo 1 > $@ - -$(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-13-CVE-2017-17969.patch-applied: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-12-CVE-2016-9296.patch-applied - cd $(dir $@) && patch -p1 -f < $(SRCDIR)/patches/p7zip-13-CVE-2017-17969.patch - echo 1 > $@ - -$(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-15-Enhanced-encryption-strength.patch-applied: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-13-CVE-2017-17969.patch-applied - cd $(dir $@) && patch -p4 -f < $(SRCDIR)/patches/p7zip-15-Enhanced-encryption-strength.patch - echo 1 > $@ - -$(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-Windows_ErrorMsg.patch-applied: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-15-Enhanced-encryption-strength.patch-applied - cd $(dir $@) && patch -p0 -f < $(SRCDIR)/patches/p7zip-Windows_ErrorMsg.patch - echo 1 > $@ - -$(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-configured: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/p7zip-Windows_ErrorMsg.patch-applied +$(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-configured: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/source-extracted $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-compiled: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-configured $(MAKE) -C $(dir $<) $(MAKE_COMMON) CC="$(CC)" CXX="$(CXX)" 7za echo 1 > $@ @@ -44,15 +29,15 @@ $(eval $(call staged-install, \ P7ZIP_INSTALL,,,)) clean-p7zip: - -rm $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-configured $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-compiled - -rm $(build_bindir)/7za + -rm -f $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-configured $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-compiled + -rm -f $(build_bindir)/7za -$(MAKE) -C $(BUILDDIR)/p7zip-$(P7ZIP_VER) clean distclean-p7zip: - -rm -rf $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.bz2 $(SRCCACHE)/p7zip-$(P7ZIP_VER) $(BUILDDIR)/p7zip-$(P7ZIP_VER) + rm -rf $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz $(SRCCACHE)/p7zip-$(P7ZIP_VER) $(BUILDDIR)/p7zip-$(P7ZIP_VER) -get-p7zip: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.bz2 +get-p7zip: $(SRCCACHE)/p7zip-$(P7ZIP_VER).tar.gz extract-p7zip: $(SRCCACHE)/p7zip-$(P7ZIP_VER)/source-extracted configure-p7zip: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-configured compile-p7zip: $(BUILDDIR)/p7zip-$(P7ZIP_VER)/build-compiled diff --git a/deps/p7zip.version b/deps/p7zip.version new file mode 100644 index 0000000000000..d4a13155d9162 --- /dev/null +++ b/deps/p7zip.version @@ -0,0 +1,5 @@ +## jll artifact +P7ZIP_JLL_NAME := p7zip + +## source build +P7ZIP_VER := 17.04 diff --git a/deps/patchelf.mk b/deps/patchelf.mk index e3a8c6fb9bf1a..9b4947f183117 100644 --- a/deps/patchelf.mk +++ b/deps/patchelf.mk @@ -1,4 +1,5 @@ ## patchelf ## +include $(SRCDIR)/patchelf.version $(SRCCACHE)/patchelf-$(PATCHELF_VER).tar.bz2: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://github.com/NixOS/patchelf/releases/download/$(PATCHELF_VER)/patchelf-$(PATCHELF_VER).tar.bz2 @@ -38,12 +39,12 @@ $(eval $(call staged-install, \ MAKE_INSTALL,$$(LIBTOOL_CCLD),,)) clean-patchelf: - -rm $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-configured \ + -rm -f $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-configured \ $(BUILDDIR)/patchelf-$(PATCHELF_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/patchelf-$(PATCHELF_VER) clean distclean-patchelf: - -rm -rf $(SRCCACHE)/patchelf-$(PATCHELF_VER).tar.bz2 \ + rm -rf $(SRCCACHE)/patchelf-$(PATCHELF_VER).tar.bz2 \ $(SRCCACHE)/patchelf-$(PATCHELF_VER) \ $(BUILDDIR)/patchelf-$(PATCHELF_VER) diff --git a/deps/patchelf.version b/deps/patchelf.version new file mode 100644 index 0000000000000..bbeaa87d25136 --- /dev/null +++ b/deps/patchelf.version @@ -0,0 +1,3 @@ +## source build +# Patchelf (we don't ship this or even use a JLL, we just always build it) +PATCHELF_VER := 0.13 diff --git a/deps/patches/config.sub b/deps/patches/config.sub deleted file mode 100755 index 3d9a8dc3d5a76..0000000000000 --- a/deps/patches/config.sub +++ /dev/null @@ -1,1851 +0,0 @@ -#! /bin/sh -# Configuration validation subroutine script. -# Copyright 1992-2020 Free Software Foundation, Inc. - -timestamp='2020-07-10' - -# This file is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, see . -# -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that -# program. This Exception is an additional permission under section 7 -# of the GNU General Public License, version 3 ("GPLv3"). - - -# Please send patches to . -# -# Configuration subroutine to validate and canonicalize a configuration type. -# Supply the specified configuration type as an argument. -# If it is invalid, we print an error message on stderr and exit with code 1. -# Otherwise, we print the canonical config type on stdout and succeed. - -# You can get the latest version of this script from: -# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub - -# This file is supposed to be the same for all GNU packages -# and recognize all the CPU types, system types and aliases -# that are meaningful with *any* GNU software. -# Each package is responsible for reporting which valid configurations -# it does not support. The user should be able to distinguish -# a failure to support a valid configuration from a meaningless -# configuration. - -# The goal of this file is to map all the various variations of a given -# machine specification into a single specification in the form: -# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM -# or in some cases, the newer four-part form: -# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM -# It is wrong to echo any other type of specification. - -me=`echo "$0" | sed -e 's,.*/,,'` - -usage="\ -Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS - -Canonicalize a configuration name. - -Options: - -h, --help print this help, then exit - -t, --time-stamp print date of last modification, then exit - -v, --version print version number, then exit - -Report bugs and patches to ." - -version="\ -GNU config.sub ($timestamp) - -Copyright 1992-2020 Free Software Foundation, Inc. - -This is free software; see the source for copying conditions. There is NO -warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." - -help=" -Try \`$me --help' for more information." - -# Parse command line -while test $# -gt 0 ; do - case $1 in - --time-stamp | --time* | -t ) - echo "$timestamp" ; exit ;; - --version | -v ) - echo "$version" ; exit ;; - --help | --h* | -h ) - echo "$usage"; exit ;; - -- ) # Stop option processing - shift; break ;; - - ) # Use stdin as input. - break ;; - -* ) - echo "$me: invalid option $1$help" >&2 - exit 1 ;; - - *local*) - # First pass through any local machine types. - echo "$1" - exit ;; - - * ) - break ;; - esac -done - -case $# in - 0) echo "$me: missing argument$help" >&2 - exit 1;; - 1) ;; - *) echo "$me: too many arguments$help" >&2 - exit 1;; -esac - -# Split fields of configuration type -# shellcheck disable=SC2162 -IFS="-" read field1 field2 field3 field4 <&2 - exit 1 - ;; - *-*-*-*) - basic_machine=$field1-$field2 - basic_os=$field3-$field4 - ;; - *-*-*) - # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two - # parts - maybe_os=$field2-$field3 - case $maybe_os in - nto-qnx* | linux-* | uclinux-uclibc* \ - | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ - | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ - | storm-chaos* | os2-emx* | rtmk-nova*) - basic_machine=$field1 - basic_os=$maybe_os - ;; - android-linux) - basic_machine=$field1-unknown - basic_os=linux-android - ;; - *) - basic_machine=$field1-$field2 - basic_os=$field3 - ;; - esac - ;; - *-*) - # A lone config we happen to match not fitting any pattern - case $field1-$field2 in - decstation-3100) - basic_machine=mips-dec - basic_os= - ;; - *-*) - # Second component is usually, but not always the OS - case $field2 in - # Prevent following clause from handling this valid os - sun*os*) - basic_machine=$field1 - basic_os=$field2 - ;; - # Manufacturers - dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \ - | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \ - | unicom* | ibm* | next | hp | isi* | apollo | altos* \ - | convergent* | ncr* | news | 32* | 3600* | 3100* \ - | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \ - | ultra | tti* | harris | dolphin | highlevel | gould \ - | cbm | ns | masscomp | apple | axis | knuth | cray \ - | microblaze* | sim | cisco \ - | oki | wec | wrs | winbond) - basic_machine=$field1-$field2 - basic_os= - ;; - *) - basic_machine=$field1 - basic_os=$field2 - ;; - esac - ;; - esac - ;; - *) - # Convert single-component short-hands not valid as part of - # multi-component configurations. - case $field1 in - 386bsd) - basic_machine=i386-pc - basic_os=bsd - ;; - a29khif) - basic_machine=a29k-amd - basic_os=udi - ;; - adobe68k) - basic_machine=m68010-adobe - basic_os=scout - ;; - alliant) - basic_machine=fx80-alliant - basic_os= - ;; - altos | altos3068) - basic_machine=m68k-altos - basic_os= - ;; - am29k) - basic_machine=a29k-none - basic_os=bsd - ;; - amdahl) - basic_machine=580-amdahl - basic_os=sysv - ;; - amiga) - basic_machine=m68k-unknown - basic_os= - ;; - amigaos | amigados) - basic_machine=m68k-unknown - basic_os=amigaos - ;; - amigaunix | amix) - basic_machine=m68k-unknown - basic_os=sysv4 - ;; - apollo68) - basic_machine=m68k-apollo - basic_os=sysv - ;; - apollo68bsd) - basic_machine=m68k-apollo - basic_os=bsd - ;; - aros) - basic_machine=i386-pc - basic_os=aros - ;; - aux) - basic_machine=m68k-apple - basic_os=aux - ;; - balance) - basic_machine=ns32k-sequent - basic_os=dynix - ;; - blackfin) - basic_machine=bfin-unknown - basic_os=linux - ;; - cegcc) - basic_machine=arm-unknown - basic_os=cegcc - ;; - convex-c1) - basic_machine=c1-convex - basic_os=bsd - ;; - convex-c2) - basic_machine=c2-convex - basic_os=bsd - ;; - convex-c32) - basic_machine=c32-convex - basic_os=bsd - ;; - convex-c34) - basic_machine=c34-convex - basic_os=bsd - ;; - convex-c38) - basic_machine=c38-convex - basic_os=bsd - ;; - cray) - basic_machine=j90-cray - basic_os=unicos - ;; - crds | unos) - basic_machine=m68k-crds - basic_os= - ;; - da30) - basic_machine=m68k-da30 - basic_os= - ;; - decstation | pmax | pmin | dec3100 | decstatn) - basic_machine=mips-dec - basic_os= - ;; - delta88) - basic_machine=m88k-motorola - basic_os=sysv3 - ;; - dicos) - basic_machine=i686-pc - basic_os=dicos - ;; - djgpp) - basic_machine=i586-pc - basic_os=msdosdjgpp - ;; - ebmon29k) - basic_machine=a29k-amd - basic_os=ebmon - ;; - es1800 | OSE68k | ose68k | ose | OSE) - basic_machine=m68k-ericsson - basic_os=ose - ;; - gmicro) - basic_machine=tron-gmicro - basic_os=sysv - ;; - go32) - basic_machine=i386-pc - basic_os=go32 - ;; - h8300hms) - basic_machine=h8300-hitachi - basic_os=hms - ;; - h8300xray) - basic_machine=h8300-hitachi - basic_os=xray - ;; - h8500hms) - basic_machine=h8500-hitachi - basic_os=hms - ;; - harris) - basic_machine=m88k-harris - basic_os=sysv3 - ;; - hp300 | hp300hpux) - basic_machine=m68k-hp - basic_os=hpux - ;; - hp300bsd) - basic_machine=m68k-hp - basic_os=bsd - ;; - hppaosf) - basic_machine=hppa1.1-hp - basic_os=osf - ;; - hppro) - basic_machine=hppa1.1-hp - basic_os=proelf - ;; - i386mach) - basic_machine=i386-mach - basic_os=mach - ;; - isi68 | isi) - basic_machine=m68k-isi - basic_os=sysv - ;; - m68knommu) - basic_machine=m68k-unknown - basic_os=linux - ;; - magnum | m3230) - basic_machine=mips-mips - basic_os=sysv - ;; - merlin) - basic_machine=ns32k-utek - basic_os=sysv - ;; - mingw64) - basic_machine=x86_64-pc - basic_os=mingw64 - ;; - mingw32) - basic_machine=i686-pc - basic_os=mingw32 - ;; - mingw32ce) - basic_machine=arm-unknown - basic_os=mingw32ce - ;; - monitor) - basic_machine=m68k-rom68k - basic_os=coff - ;; - morphos) - basic_machine=powerpc-unknown - basic_os=morphos - ;; - moxiebox) - basic_machine=moxie-unknown - basic_os=moxiebox - ;; - msdos) - basic_machine=i386-pc - basic_os=msdos - ;; - msys) - basic_machine=i686-pc - basic_os=msys - ;; - mvs) - basic_machine=i370-ibm - basic_os=mvs - ;; - nacl) - basic_machine=le32-unknown - basic_os=nacl - ;; - ncr3000) - basic_machine=i486-ncr - basic_os=sysv4 - ;; - netbsd386) - basic_machine=i386-pc - basic_os=netbsd - ;; - netwinder) - basic_machine=armv4l-rebel - basic_os=linux - ;; - news | news700 | news800 | news900) - basic_machine=m68k-sony - basic_os=newsos - ;; - news1000) - basic_machine=m68030-sony - basic_os=newsos - ;; - necv70) - basic_machine=v70-nec - basic_os=sysv - ;; - nh3000) - basic_machine=m68k-harris - basic_os=cxux - ;; - nh[45]000) - basic_machine=m88k-harris - basic_os=cxux - ;; - nindy960) - basic_machine=i960-intel - basic_os=nindy - ;; - mon960) - basic_machine=i960-intel - basic_os=mon960 - ;; - nonstopux) - basic_machine=mips-compaq - basic_os=nonstopux - ;; - os400) - basic_machine=powerpc-ibm - basic_os=os400 - ;; - OSE68000 | ose68000) - basic_machine=m68000-ericsson - basic_os=ose - ;; - os68k) - basic_machine=m68k-none - basic_os=os68k - ;; - paragon) - basic_machine=i860-intel - basic_os=osf - ;; - parisc) - basic_machine=hppa-unknown - basic_os=linux - ;; - psp) - basic_machine=mipsallegrexel-sony - basic_os=psp - ;; - pw32) - basic_machine=i586-unknown - basic_os=pw32 - ;; - rdos | rdos64) - basic_machine=x86_64-pc - basic_os=rdos - ;; - rdos32) - basic_machine=i386-pc - basic_os=rdos - ;; - rom68k) - basic_machine=m68k-rom68k - basic_os=coff - ;; - sa29200) - basic_machine=a29k-amd - basic_os=udi - ;; - sei) - basic_machine=mips-sei - basic_os=seiux - ;; - sequent) - basic_machine=i386-sequent - basic_os= - ;; - sps7) - basic_machine=m68k-bull - basic_os=sysv2 - ;; - st2000) - basic_machine=m68k-tandem - basic_os= - ;; - stratus) - basic_machine=i860-stratus - basic_os=sysv4 - ;; - sun2) - basic_machine=m68000-sun - basic_os= - ;; - sun2os3) - basic_machine=m68000-sun - basic_os=sunos3 - ;; - sun2os4) - basic_machine=m68000-sun - basic_os=sunos4 - ;; - sun3) - basic_machine=m68k-sun - basic_os= - ;; - sun3os3) - basic_machine=m68k-sun - basic_os=sunos3 - ;; - sun3os4) - basic_machine=m68k-sun - basic_os=sunos4 - ;; - sun4) - basic_machine=sparc-sun - basic_os= - ;; - sun4os3) - basic_machine=sparc-sun - basic_os=sunos3 - ;; - sun4os4) - basic_machine=sparc-sun - basic_os=sunos4 - ;; - sun4sol2) - basic_machine=sparc-sun - basic_os=solaris2 - ;; - sun386 | sun386i | roadrunner) - basic_machine=i386-sun - basic_os= - ;; - sv1) - basic_machine=sv1-cray - basic_os=unicos - ;; - symmetry) - basic_machine=i386-sequent - basic_os=dynix - ;; - t3e) - basic_machine=alphaev5-cray - basic_os=unicos - ;; - t90) - basic_machine=t90-cray - basic_os=unicos - ;; - toad1) - basic_machine=pdp10-xkl - basic_os=tops20 - ;; - tpf) - basic_machine=s390x-ibm - basic_os=tpf - ;; - udi29k) - basic_machine=a29k-amd - basic_os=udi - ;; - ultra3) - basic_machine=a29k-nyu - basic_os=sym1 - ;; - v810 | necv810) - basic_machine=v810-nec - basic_os=none - ;; - vaxv) - basic_machine=vax-dec - basic_os=sysv - ;; - vms) - basic_machine=vax-dec - basic_os=vms - ;; - vsta) - basic_machine=i386-pc - basic_os=vsta - ;; - vxworks960) - basic_machine=i960-wrs - basic_os=vxworks - ;; - vxworks68) - basic_machine=m68k-wrs - basic_os=vxworks - ;; - vxworks29k) - basic_machine=a29k-wrs - basic_os=vxworks - ;; - xbox) - basic_machine=i686-pc - basic_os=mingw32 - ;; - ymp) - basic_machine=ymp-cray - basic_os=unicos - ;; - *) - basic_machine=$1 - basic_os= - ;; - esac - ;; -esac - -# Decode 1-component or ad-hoc basic machines -case $basic_machine in - # Here we handle the default manufacturer of certain CPU types. It is in - # some cases the only manufacturer, in others, it is the most popular. - w89k) - cpu=hppa1.1 - vendor=winbond - ;; - op50n) - cpu=hppa1.1 - vendor=oki - ;; - op60c) - cpu=hppa1.1 - vendor=oki - ;; - ibm*) - cpu=i370 - vendor=ibm - ;; - orion105) - cpu=clipper - vendor=highlevel - ;; - mac | mpw | mac-mpw) - cpu=m68k - vendor=apple - ;; - pmac | pmac-mpw) - cpu=powerpc - vendor=apple - ;; - - # Recognize the various machine names and aliases which stand - # for a CPU type and a company and sometimes even an OS. - 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) - cpu=m68000 - vendor=att - ;; - 3b*) - cpu=we32k - vendor=att - ;; - bluegene*) - cpu=powerpc - vendor=ibm - basic_os=cnk - ;; - decsystem10* | dec10*) - cpu=pdp10 - vendor=dec - basic_os=tops10 - ;; - decsystem20* | dec20*) - cpu=pdp10 - vendor=dec - basic_os=tops20 - ;; - delta | 3300 | motorola-3300 | motorola-delta \ - | 3300-motorola | delta-motorola) - cpu=m68k - vendor=motorola - ;; - dpx2*) - cpu=m68k - vendor=bull - basic_os=sysv3 - ;; - encore | umax | mmax) - cpu=ns32k - vendor=encore - ;; - elxsi) - cpu=elxsi - vendor=elxsi - basic_os=${basic_os:-bsd} - ;; - fx2800) - cpu=i860 - vendor=alliant - ;; - genix) - cpu=ns32k - vendor=ns - ;; - h3050r* | hiux*) - cpu=hppa1.1 - vendor=hitachi - basic_os=hiuxwe2 - ;; - hp3k9[0-9][0-9] | hp9[0-9][0-9]) - cpu=hppa1.0 - vendor=hp - ;; - hp9k2[0-9][0-9] | hp9k31[0-9]) - cpu=m68000 - vendor=hp - ;; - hp9k3[2-9][0-9]) - cpu=m68k - vendor=hp - ;; - hp9k6[0-9][0-9] | hp6[0-9][0-9]) - cpu=hppa1.0 - vendor=hp - ;; - hp9k7[0-79][0-9] | hp7[0-79][0-9]) - cpu=hppa1.1 - vendor=hp - ;; - hp9k78[0-9] | hp78[0-9]) - # FIXME: really hppa2.0-hp - cpu=hppa1.1 - vendor=hp - ;; - hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) - # FIXME: really hppa2.0-hp - cpu=hppa1.1 - vendor=hp - ;; - hp9k8[0-9][13679] | hp8[0-9][13679]) - cpu=hppa1.1 - vendor=hp - ;; - hp9k8[0-9][0-9] | hp8[0-9][0-9]) - cpu=hppa1.0 - vendor=hp - ;; - i*86v32) - cpu=`echo "$1" | sed -e 's/86.*/86/'` - vendor=pc - basic_os=sysv32 - ;; - i*86v4*) - cpu=`echo "$1" | sed -e 's/86.*/86/'` - vendor=pc - basic_os=sysv4 - ;; - i*86v) - cpu=`echo "$1" | sed -e 's/86.*/86/'` - vendor=pc - basic_os=sysv - ;; - i*86sol2) - cpu=`echo "$1" | sed -e 's/86.*/86/'` - vendor=pc - basic_os=solaris2 - ;; - j90 | j90-cray) - cpu=j90 - vendor=cray - basic_os=${basic_os:-unicos} - ;; - iris | iris4d) - cpu=mips - vendor=sgi - case $basic_os in - irix*) - ;; - *) - basic_os=irix4 - ;; - esac - ;; - miniframe) - cpu=m68000 - vendor=convergent - ;; - *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) - cpu=m68k - vendor=atari - basic_os=mint - ;; - news-3600 | risc-news) - cpu=mips - vendor=sony - basic_os=newsos - ;; - next | m*-next) - cpu=m68k - vendor=next - case $basic_os in - openstep*) - ;; - nextstep*) - ;; - ns2*) - basic_os=nextstep2 - ;; - *) - basic_os=nextstep3 - ;; - esac - ;; - np1) - cpu=np1 - vendor=gould - ;; - op50n-* | op60c-*) - cpu=hppa1.1 - vendor=oki - basic_os=proelf - ;; - pa-hitachi) - cpu=hppa1.1 - vendor=hitachi - basic_os=hiuxwe2 - ;; - pbd) - cpu=sparc - vendor=tti - ;; - pbb) - cpu=m68k - vendor=tti - ;; - pc532) - cpu=ns32k - vendor=pc532 - ;; - pn) - cpu=pn - vendor=gould - ;; - power) - cpu=power - vendor=ibm - ;; - ps2) - cpu=i386 - vendor=ibm - ;; - rm[46]00) - cpu=mips - vendor=siemens - ;; - rtpc | rtpc-*) - cpu=romp - vendor=ibm - ;; - sde) - cpu=mipsisa32 - vendor=sde - basic_os=${basic_os:-elf} - ;; - simso-wrs) - cpu=sparclite - vendor=wrs - basic_os=vxworks - ;; - tower | tower-32) - cpu=m68k - vendor=ncr - ;; - vpp*|vx|vx-*) - cpu=f301 - vendor=fujitsu - ;; - w65) - cpu=w65 - vendor=wdc - ;; - w89k-*) - cpu=hppa1.1 - vendor=winbond - basic_os=proelf - ;; - none) - cpu=none - vendor=none - ;; - leon|leon[3-9]) - cpu=sparc - vendor=$basic_machine - ;; - leon-*|leon[3-9]-*) - cpu=sparc - vendor=`echo "$basic_machine" | sed 's/-.*//'` - ;; - - *-*) - # shellcheck disable=SC2162 - IFS="-" read cpu vendor <&2 - exit 1 - ;; - esac - ;; -esac - -# Here we canonicalize certain aliases for manufacturers. -case $vendor in - digital*) - vendor=dec - ;; - commodore*) - vendor=cbm - ;; - *) - ;; -esac - -# Decode manufacturer-specific aliases for certain operating systems. - -if [ x$basic_os != x ] -then - -# First recognize some ad-hoc caes, or perhaps split kernel-os, or else just -# set os. -case $basic_os in - gnu/linux*) - kernel=linux - os=`echo $basic_os | sed -e 's|gnu/linux|gnu|'` - ;; - nto-qnx*) - kernel=nto - os=`echo $basic_os | sed -e 's|nto-qnx|qnx|'` - ;; - *-*) - # shellcheck disable=SC2162 - IFS="-" read kernel os <&2 - exit 1 - ;; -esac - -# As a final step for OS-related things, validate the OS-kernel combination -# (given a valid OS), if there is a kernel. -case $kernel-$os in - linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* | linux-musl* | linux-uclibc* ) - ;; - -dietlibc* | -newlib* | -musl* | -uclibc* ) - # These are just libc implementations, not actual OSes, and thus - # require a kernel. - echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 - exit 1 - ;; - kfreebsd*-gnu* | kopensolaris*-gnu*) - ;; - nto-qnx*) - ;; - *-eabi* | *-gnueabi*) - ;; - -*) - # Blank kernel with real OS is always fine. - ;; - *-*) - echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 - exit 1 - ;; -esac - -# Here we handle the case where we know the os, and the CPU type, but not the -# manufacturer. We pick the logical manufacturer. -case $vendor in - unknown) - case $cpu-$os in - *-riscix*) - vendor=acorn - ;; - *-sunos*) - vendor=sun - ;; - *-cnk* | *-aix*) - vendor=ibm - ;; - *-beos*) - vendor=be - ;; - *-hpux*) - vendor=hp - ;; - *-mpeix*) - vendor=hp - ;; - *-hiux*) - vendor=hitachi - ;; - *-unos*) - vendor=crds - ;; - *-dgux*) - vendor=dg - ;; - *-luna*) - vendor=omron - ;; - *-genix*) - vendor=ns - ;; - *-clix*) - vendor=intergraph - ;; - *-mvs* | *-opened*) - vendor=ibm - ;; - *-os400*) - vendor=ibm - ;; - s390-* | s390x-*) - vendor=ibm - ;; - *-ptx*) - vendor=sequent - ;; - *-tpf*) - vendor=ibm - ;; - *-vxsim* | *-vxworks* | *-windiss*) - vendor=wrs - ;; - *-aux*) - vendor=apple - ;; - *-hms*) - vendor=hitachi - ;; - *-mpw* | *-macos*) - vendor=apple - ;; - *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) - vendor=atari - ;; - *-vos*) - vendor=stratus - ;; - esac - ;; -esac - -echo "$cpu-$vendor-${kernel:+$kernel-}$os" -exit - -# Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) -# time-stamp-start: "timestamp='" -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "'" -# End: diff --git a/deps/patches/gmp-CVE-2021-43618.patch b/deps/patches/gmp-CVE-2021-43618.patch new file mode 100644 index 0000000000000..a4e420e9219da --- /dev/null +++ b/deps/patches/gmp-CVE-2021-43618.patch @@ -0,0 +1,24 @@ +# 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/libTracyClient-freebsd-elfw.patch b/deps/patches/libTracyClient-freebsd-elfw.patch new file mode 100644 index 0000000000000..8feb738714e11 --- /dev/null +++ b/deps/patches/libTracyClient-freebsd-elfw.patch @@ -0,0 +1,33 @@ +diff --git a/public/TracyClient.cpp b/public/TracyClient.cpp +index 77f81a4a..ebeb65c9 100644 +--- a/public/TracyClient.cpp ++++ b/public/TracyClient.cpp +@@ -19,6 +19,28 @@ + # pragma warning(push, 0) + #endif + ++#ifndef ElfW ++# if defined(FREEBSD) ++# if __ELF_WORD_SIZE == 32 ++# define ElfW(type) Elf32_##type ++# else ++# define ElfW(type) Elf64_##type ++# endif ++# elif defined(NETBSD) || defined(OPENBSD) ++# if ELFSIZE == 32 ++# define ElfW(type) Elf32_##type ++# else ++# define ElfW(type) Elf64_##type ++# endif ++# else ++# if !defined(ELF_CLASS) || ELF_CLASS == ELFCLASS32 ++# define ElfW(type) Elf32_##type ++# else ++# define ElfW(type) Elf64_##type ++# endif ++# endif ++#endif ++ + #include "common/tracy_lz4.cpp" + #include "client/TracyProfiler.cpp" + #include "client/TracyCallstack.cpp" diff --git a/deps/patches/libTracyClient-no-sampling.patch b/deps/patches/libTracyClient-no-sampling.patch new file mode 100644 index 0000000000000..c4c8576099348 --- /dev/null +++ b/deps/patches/libTracyClient-no-sampling.patch @@ -0,0 +1,79 @@ +commit 6249999153a9497b32bc84e9dc95a1537a0af714 +Author: Cody Tapscott +Date: Tue Apr 4 15:20:46 2023 -0400 + + linux: respect `TRACY_NO_SAMPLING` for sys-tracing + + This compile-time flag was being ignored on Linux. This change adds + gating for software-sampled stack trace sampling following the same + pattern as other `TRACY_NO_SAMPLE_*` options. + + If `TRACY_NO_SAMPLING=1` is provided as an environment variable, + software stack sampling is also disabled. + +diff --git a/public/client/TracySysTrace.cpp b/public/client/TracySysTrace.cpp +index 4a562eaa..af0641fe 100644 +--- a/public/client/TracySysTrace.cpp ++++ b/public/client/TracySysTrace.cpp +@@ -770,6 +770,13 @@ bool SysTraceStart( int64_t& samplingPeriod ) + TracyDebug( "sched_wakeup id: %i\n", wakeupId ); + TracyDebug( "drm_vblank_event id: %i\n", vsyncId ); + ++#ifdef TRACY_NO_SAMPLING ++ const bool noSoftwareSampling = true; ++#else ++ const char* noSoftwareSamplingEnv = GetEnvVar( "TRACY_NO_SAMPLING" ); ++ const bool noSoftwareSampling = noSoftwareSamplingEnv && noSoftwareSamplingEnv[0] == '1'; ++#endif ++ + #ifdef TRACY_NO_SAMPLE_RETIREMENT + const bool noRetirement = true; + #else +@@ -839,28 +846,31 @@ bool SysTraceStart( int64_t& samplingPeriod ) + pe.clockid = CLOCK_MONOTONIC_RAW; + #endif + +- TracyDebug( "Setup software sampling\n" ); +- ProbePreciseIp( pe, currentPid ); +- for( int i=0; i +Date: Wed Mar 8 23:18:36 2023 +0100 + + Add support for configuring plots to C API. + +diff --git a/public/client/TracyProfiler.cpp b/public/client/TracyProfiler.cpp +index 6104a7ed..38b5ea13 100644 +--- a/public/client/TracyProfiler.cpp ++++ b/public/client/TracyProfiler.cpp +@@ -4149,6 +4149,7 @@ TRACY_API void ___tracy_emit_frame_image( const void* image, uint16_t w, uint16_ + TRACY_API void ___tracy_emit_plot( const char* name, double val ) { tracy::Profiler::PlotData( name, val ); } + TRACY_API void ___tracy_emit_plot_float( const char* name, float val ) { tracy::Profiler::PlotData( name, val ); } + TRACY_API void ___tracy_emit_plot_int( const char* name, int64_t val ) { tracy::Profiler::PlotData( name, val ); } ++TRACY_API void ___tracy_emit_plot_config( const char* name, int type, int step, int fill, uint32_t color ) { tracy::Profiler::ConfigurePlot( name, tracy::PlotFormatType(type), step, fill, color ); } + TRACY_API void ___tracy_emit_message( const char* txt, size_t size, int callstack ) { tracy::Profiler::Message( txt, size, callstack ); } + TRACY_API void ___tracy_emit_messageL( const char* txt, int callstack ) { tracy::Profiler::Message( txt, callstack ); } + TRACY_API void ___tracy_emit_messageC( const char* txt, size_t size, uint32_t color, int callstack ) { tracy::Profiler::MessageColor( txt, size, color, callstack ); } +diff --git a/public/tracy/TracyC.h b/public/tracy/TracyC.h +index bedf5e16..736b51ed 100644 +--- a/public/tracy/TracyC.h ++++ b/public/tracy/TracyC.h +@@ -11,6 +11,13 @@ + extern "C" { + #endif + ++enum TracyPlotFormatEnum ++{ ++ TracyPlotFormatNumber, ++ TracyPlotFormatMemory, ++ TracyPlotFormatPercentage, ++}; ++ + TRACY_API void ___tracy_set_thread_name( const char* name ); + + #define TracyCSetThreadName( name ) ___tracy_set_thread_name( name ); +@@ -60,6 +67,8 @@ typedef const void* TracyCZoneCtx; + #define TracyCPlot(x,y) + #define TracyCPlotF(x,y) + #define TracyCPlotI(x,y) ++#define TracyCPlotConfig(x,y,z,w,a) ++ + #define TracyCMessage(x,y) + #define TracyCMessageL(x) + #define TracyCMessageC(x,y,z) +@@ -289,11 +298,13 @@ TRACY_API void ___tracy_emit_frame_image( const void* image, uint16_t w, uint16_ + TRACY_API void ___tracy_emit_plot( const char* name, double val ); + TRACY_API void ___tracy_emit_plot_float( const char* name, float val ); + TRACY_API void ___tracy_emit_plot_int( const char* name, int64_t val ); ++TRACY_API void ___tracy_emit_plot_config( const char* name, int type, int step, int fill, uint32_t color ); + TRACY_API void ___tracy_emit_message_appinfo( const char* txt, size_t size ); + + #define TracyCPlot( name, val ) ___tracy_emit_plot( name, val ); + #define TracyCPlotF( name, val ) ___tracy_emit_plot_float( name, val ); + #define TracyCPlotI( name, val ) ___tracy_emit_plot_int( name, val ); ++#define TracyCPlotConfig( name, type, step, fill, color ) ___tracy_emit_plot_config( name, type, step, fill, color ); + #define TracyCAppInfo( txt, size ) ___tracy_emit_message_appinfo( txt, size ); diff --git a/deps/patches/libgit2-agent-nonfatal.patch b/deps/patches/libgit2-agent-nonfatal.patch deleted file mode 100644 index 3ada9ecaed93f..0000000000000 --- a/deps/patches/libgit2-agent-nonfatal.patch +++ /dev/null @@ -1,25 +0,0 @@ -commit 70020247d1903c7a1262d967cf205a44dc6f6ebe -Author: Keno Fischer -Date: Wed Jul 20 19:59:00 2016 -0400 - - Make failure to connect to ssh-agent non-fatal - - Julia issue: https://github.com/JuliaLang/julia/pull/17459 - Upstream: https://github.com/libgit2/libgit2/issues/3866 - -diff --git a/src/transports/ssh.c b/src/transports/ssh.c -index cfd5736..82d2c63 100644 ---- a/src/transports/ssh.c -+++ b/src/transports/ssh.c -@@ -296,8 +296,10 @@ static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) { - - rc = libssh2_agent_connect(agent); - -- if (rc != LIBSSH2_ERROR_NONE) -+ if (rc != LIBSSH2_ERROR_NONE) { -+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED; - goto shutdown; -+ } - - rc = libssh2_agent_list_identities(agent); - diff --git a/deps/patches/libgit2-hostkey.patch b/deps/patches/libgit2-hostkey.patch deleted file mode 100644 index 8be5e5cc92e5e..0000000000000 --- a/deps/patches/libgit2-hostkey.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/src/transports/ssh.c b/src/transports/ssh.c -index 471c3273ed..32189d0979 100644 ---- a/src/transports/ssh.c -+++ b/src/transports/ssh.c -@@ -525,6 +525,7 @@ static int _git_ssh_setup_conn( - git_credential *cred = NULL; - LIBSSH2_SESSION *session=NULL; - LIBSSH2_CHANNEL *channel=NULL; -+ char *host_and_port; - - t->current_stream = NULL; - -@@ -636,7 +637,15 @@ post_extract: - - cert_ptr = &cert; - -- error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload); -+ if (atoi(urldata.port) == SSH_DEFAULT_PORT) { -+ host_and_port = urldata.host; -+ } else { -+ size_t n = strlen(urldata.host) + strlen(urldata.port) + 2; -+ host_and_port = alloca(n); -+ sprintf(host_and_port, "%s:%s", urldata.host, urldata.port); -+ } -+ -+ error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host_and_port, t->owner->message_cb_payload); - - if (error < 0 && error != GIT_PASSTHROUGH) { - if (!git_error_last()) diff --git a/deps/patches/libssh2-fix-import-lib-name.patch b/deps/patches/libssh2-fix-import-lib-name.patch new file mode 100644 index 0000000000000..15aafb58d2736 --- /dev/null +++ b/deps/patches/libssh2-fix-import-lib-name.patch @@ -0,0 +1,26 @@ +From 3732420725efbf410df5863b91a09ca214ee18ba Mon Sep 17 00:00:00 2001 +From: "Y. Yang" +Date: Thu, 16 Jun 2022 19:16:37 +0800 +Subject: [PATCH] Fix DLL import library name + +https://aur.archlinux.org/packages/mingw-w64-libssh2 +https://cmake.org/cmake/help/latest/prop_tgt/IMPORT_PREFIX.html +--- + src/CMakeLists.txt | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index cb8fee1..17ecefd 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -220,6 +220,7 @@ endif() + add_library(libssh2 ${SOURCES}) + # we want it to be called libssh2 on all platforms + set_target_properties(libssh2 PROPERTIES PREFIX "") ++set_target_properties(libssh2 PROPERTIES IMPORT_PREFIX "") + + target_compile_definitions(libssh2 PRIVATE ${PRIVATE_COMPILE_DEFINITIONS}) + target_include_directories(libssh2 +-- +2.36.1 + diff --git a/deps/patches/libunwind-non-empty-structs.patch b/deps/patches/libunwind-non-empty-structs.patch new file mode 100644 index 0000000000000..0c04709a13184 --- /dev/null +++ b/deps/patches/libunwind-non-empty-structs.patch @@ -0,0 +1,108 @@ +From 1f35cd8f2bdcc1876af7352cc3e87bb7277e8162 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mos=C3=A8=20Giordano?= +Date: Sat, 18 Jun 2022 10:35:36 +0100 +Subject: [PATCH 1/1] Make some structs non-empty + +Backport of . +--- + include/libunwind-aarch64.h | 6 ++++++ + include/libunwind-arm.h | 6 ++++++ + include/libunwind-x86.h | 6 ++++++ + 3 files changed, 18 insertions(+) + +diff --git a/include/libunwind-aarch64.h b/include/libunwind-aarch64.h +index aeaef630..b7066c51 100644 +--- a/include/libunwind-aarch64.h ++++ b/include/libunwind-aarch64.h +@@ -35,6 +35,10 @@ extern "C" { + #include + #include + ++#ifndef UNW_EMPTY_STRUCT ++# define UNW_EMPTY_STRUCT uint8_t unused; ++#endif ++ + #define UNW_TARGET aarch64 + #define UNW_TARGET_AARCH64 1 + +@@ -60,6 +64,7 @@ typedef long double unw_tdep_fpreg_t; + typedef struct + { + /* no aarch64-specific auxiliary proc-info */ ++ UNW_EMPTY_STRUCT + } + unw_tdep_proc_info_t; + +@@ -169,6 +174,7 @@ aarch64_regnum_t; + typedef struct unw_tdep_save_loc + { + /* Additional target-dependent info on a save location. */ ++ UNW_EMPTY_STRUCT + } + unw_tdep_save_loc_t; + +diff --git a/include/libunwind-arm.h b/include/libunwind-arm.h +index 6709b7ab..7c7005d1 100644 +--- a/include/libunwind-arm.h ++++ b/include/libunwind-arm.h +@@ -32,6 +32,10 @@ extern "C" { + #include + #include + ++#ifndef UNW_EMPTY_STRUCT ++# define UNW_EMPTY_STRUCT uint8_t unused; ++#endif ++ + #define UNW_TARGET arm + #define UNW_TARGET_ARM 1 + +@@ -247,6 +251,7 @@ arm_regnum_t; + typedef struct unw_tdep_save_loc + { + /* Additional target-dependent info on a save location. */ ++ UNW_EMPTY_STRUCT + } + unw_tdep_save_loc_t; + +@@ -288,6 +293,7 @@ unw_tdep_context_t; + typedef struct + { + /* no arm-specific auxiliary proc-info */ ++ UNW_EMPTY_STRUCT + } + unw_tdep_proc_info_t; + +diff --git a/include/libunwind-x86.h b/include/libunwind-x86.h +index 40fe0464..d3b741d3 100644 +--- a/include/libunwind-x86.h ++++ b/include/libunwind-x86.h +@@ -34,6 +34,10 @@ extern "C" { + #include + #include + ++#ifndef UNW_EMPTY_STRUCT ++# define UNW_EMPTY_STRUCT uint8_t unused; ++#endif ++ + #define UNW_TARGET x86 + #define UNW_TARGET_X86 1 + +@@ -158,6 +162,7 @@ x86_regnum_t; + typedef struct unw_tdep_save_loc + { + /* Additional target-dependent info on a save location. */ ++ UNW_EMPTY_STRUCT + } + unw_tdep_save_loc_t; + +@@ -169,6 +174,7 @@ typedef ucontext_t unw_tdep_context_t; + typedef struct + { + /* no x86-specific auxiliary proc-info */ ++ UNW_EMPTY_STRUCT + } + unw_tdep_proc_info_t; + +-- +2.36.1 + diff --git a/deps/patches/llvm-libunwind-force-dwarf.patch b/deps/patches/llvm-libunwind-force-dwarf.patch new file mode 100644 index 0000000000000..2f4d31acb8a4a --- /dev/null +++ b/deps/patches/llvm-libunwind-force-dwarf.patch @@ -0,0 +1,179 @@ +An updated version of this libosxunwind commit: + +Author: Keno Fischer +Date: Tue Aug 27 15:01:22 2013 -0400 + + Add option to step with DWARF + +--- +diff -pur a/libunwind/include/libunwind.h b/libunwind/include/libunwind.h +--- a/libunwind/include/libunwind.h 2021-06-28 18:23:38.000000000 +0200 ++++ b/libunwind/include/libunwind.h 2022-05-04 18:44:24.000000000 +0200 +@@ -108,6 +108,7 @@ extern "C" { + + extern int unw_getcontext(unw_context_t *) LIBUNWIND_AVAIL; + extern int unw_init_local(unw_cursor_t *, unw_context_t *) LIBUNWIND_AVAIL; ++extern int unw_init_local_dwarf(unw_cursor_t *, unw_context_t *) LIBUNWIND_AVAIL; + extern int unw_step(unw_cursor_t *) LIBUNWIND_AVAIL; + extern int unw_get_reg(unw_cursor_t *, unw_regnum_t, unw_word_t *) LIBUNWIND_AVAIL; + extern int unw_get_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t *) LIBUNWIND_AVAIL; +Only in b/libunwind/include: libunwind.h.orig +diff -pur a/libunwind/src/UnwindCursor.hpp b/libunwind/src/UnwindCursor.hpp +--- a/libunwind/src/UnwindCursor.hpp 2021-06-28 18:23:38.000000000 +0200 ++++ b/libunwind/src/UnwindCursor.hpp 2022-05-04 18:45:11.000000000 +0200 +@@ -437,6 +437,9 @@ public: + virtual bool isSignalFrame() { + _LIBUNWIND_ABORT("isSignalFrame not implemented"); + } ++ virtual void setForceDWARF(bool) { ++ _LIBUNWIND_ABORT("setForceDWARF not implemented"); ++ } + virtual bool getFunctionName(char *, size_t, unw_word_t *) { + _LIBUNWIND_ABORT("getFunctionName not implemented"); + } +@@ -894,6 +897,7 @@ public: + virtual void getInfo(unw_proc_info_t *); + virtual void jumpto(); + virtual bool isSignalFrame(); ++ virtual void setForceDWARF(bool force); + virtual bool getFunctionName(char *buf, size_t len, unw_word_t *off); + virtual void setInfoBasedOnIPRegister(bool isReturnAddress = false); + virtual const char *getRegisterName(int num); +@@ -963,7 +967,7 @@ private: + const UnwindInfoSections §s); + int stepWithCompactEncoding() { + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) +- if ( compactSaysUseDwarf() ) ++ if ( _forceDwarf || compactSaysUseDwarf() ) + return stepWithDwarfFDE(); + #endif + R dummy; +@@ -1198,6 +1202,7 @@ private: + unw_proc_info_t _info; + bool _unwindInfoMissing; + bool _isSignalFrame; ++ bool _forceDwarf; + #if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) + bool _isSigReturn = false; + #endif +@@ -1207,7 +1212,7 @@ private: + template + UnwindCursor::UnwindCursor(unw_context_t *context, A &as) + : _addressSpace(as), _registers(context), _unwindInfoMissing(false), +- _isSignalFrame(false) { ++ _isSignalFrame(false), _forceDwarf(false) { + static_assert((check_fit, unw_cursor_t>::does_fit), + "UnwindCursor<> does not fit in unw_cursor_t"); + static_assert((alignof(UnwindCursor) <= alignof(unw_cursor_t)), +@@ -1217,7 +1222,8 @@ UnwindCursor::UnwindCursor(unw_con + + template + UnwindCursor::UnwindCursor(A &as, void *) +- : _addressSpace(as), _unwindInfoMissing(false), _isSignalFrame(false) { ++ : _addressSpace(as), _unwindInfoMissing(false), _isSignalFrame(false), ++ _forceDwarf(false) { + memset(&_info, 0, sizeof(_info)); + // FIXME + // fill in _registers from thread arg +@@ -1273,6 +1279,10 @@ template bool U + return _isSignalFrame; + } + ++template void UnwindCursor::setForceDWARF(bool force) { ++ _forceDwarf = force; ++} ++ + #endif // defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) + + #if defined(_LIBUNWIND_ARM_EHABI) +@@ -1941,7 +1951,13 @@ void UnwindCursor::setInfoBasedOnI + // record that we have no unwind info. + if (_info.format == 0) + _unwindInfoMissing = true; ++ #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) ++ if (!(_forceDwarf || compactSaysUseDwarf(&dwarfOffset))) ++ return; ++ #else + return; ++ #endif ++ + } + } + #endif // defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) +diff -pur a/libunwind/src/libunwind.cpp b/libunwind/src/libunwind.cpp +--- a/libunwind/src/libunwind.cpp 2021-06-28 18:23:38.000000000 +0200 ++++ b/libunwind/src/libunwind.cpp 2022-05-04 18:44:24.000000000 +0200 +@@ -71,6 +71,7 @@ _LIBUNWIND_HIDDEN int __unw_init_local(u + new (reinterpret_cast *>(cursor)) + UnwindCursor( + context, LocalAddressSpace::sThisAddressSpace); ++ static_assert(sizeof(unw_cursor_t) >= sizeof(UnwindCursor), "libunwind header outdated"); + #undef REGISTER_KIND + AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; + co->setInfoBasedOnIPRegister(); +@@ -79,6 +80,54 @@ _LIBUNWIND_HIDDEN int __unw_init_local(u + } + _LIBUNWIND_WEAK_ALIAS(__unw_init_local, unw_init_local) + ++_LIBUNWIND_HIDDEN int __unw_init_local_dwarf(unw_cursor_t *cursor, ++ unw_context_t *context) { ++ _LIBUNWIND_TRACE_API("__unw_init_local_dwarf(cursor=%p, context=%p)", ++ static_cast(cursor), ++ static_cast(context)); ++#if defined(__i386__) ++# define REGISTER_KIND Registers_x86 ++#elif defined(__x86_64__) ++# define REGISTER_KIND Registers_x86_64 ++#elif defined(__powerpc64__) ++# define REGISTER_KIND Registers_ppc64 ++#elif defined(__ppc__) ++# define REGISTER_KIND Registers_ppc ++#elif defined(__aarch64__) ++# define REGISTER_KIND Registers_arm64 ++#elif defined(__arm__) ++# define REGISTER_KIND Registers_arm ++#elif defined(__or1k__) ++# define REGISTER_KIND Registers_or1k ++#elif defined(__hexagon__) ++# define REGISTER_KIND Registers_hexagon ++#elif defined(__mips__) && defined(_ABIO32) && _MIPS_SIM == _ABIO32 ++# define REGISTER_KIND Registers_mips_o32 ++#elif defined(__mips64) ++# define REGISTER_KIND Registers_mips_newabi ++#elif defined(__mips__) ++# warning The MIPS architecture is not supported with this ABI and environment! ++#elif defined(__sparc__) ++# define REGISTER_KIND Registers_sparc ++#elif defined(__riscv) && __riscv_xlen == 64 ++# define REGISTER_KIND Registers_riscv ++#else ++# error Architecture not supported ++#endif ++ // Use "placement new" to allocate UnwindCursor in the cursor buffer. ++ new (reinterpret_cast *>(cursor)) ++ UnwindCursor( ++ context, LocalAddressSpace::sThisAddressSpace); ++ static_assert(sizeof(unw_cursor_t) >= sizeof(UnwindCursor), "libunwind header outdated"); ++#undef REGISTER_KIND ++ AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; ++ co->setForceDWARF(true); ++ co->setInfoBasedOnIPRegister(); ++ ++ return UNW_ESUCCESS; ++} ++_LIBUNWIND_WEAK_ALIAS(__unw_init_local_dwarf, unw_init_local_dwarf) ++ + /// Get value of specified register at cursor position in stack frame. + _LIBUNWIND_HIDDEN int __unw_get_reg(unw_cursor_t *cursor, unw_regnum_t regNum, + unw_word_t *value) { +diff -pur a/libunwind/src/libunwind_ext.h b/libunwind/src/libunwind_ext.h +--- a/libunwind/src/libunwind_ext.h 2021-06-28 18:23:38.000000000 +0200 ++++ b/libunwind/src/libunwind_ext.h 2022-05-04 18:44:24.000000000 +0200 +@@ -25,6 +25,7 @@ extern "C" { + + extern int __unw_getcontext(unw_context_t *); + extern int __unw_init_local(unw_cursor_t *, unw_context_t *); ++extern int __unw_init_local_dwarf(unw_cursor_t *, unw_context_t *); + extern int __unw_step(unw_cursor_t *); + extern int __unw_get_reg(unw_cursor_t *, unw_regnum_t, unw_word_t *); + extern int __unw_get_fpreg(unw_cursor_t *, unw_regnum_t, unw_fpreg_t *); diff --git a/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch b/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch new file mode 100644 index 0000000000000..afb4b941d5b92 --- /dev/null +++ b/deps/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch @@ -0,0 +1,107 @@ +Modification of the following patch in the FreeBSD source tree, which +includes LLVM libunwind in contrib/llvm-project/libunwind. + +From 9f287522cec9feac040d7cb845a440a8f6b7b90e Mon Sep 17 00:00:00 2001 +From: Dimitry Andric +Date: Sun, 2 Aug 2020 18:12:14 +0000 +Subject: [PATCH] Reapply r310365 (by emaste): + +libunwind: make __{de,}register_frame compatible with libgcc API + +The libgcc __register_frame and __deregister_frame functions take a +pointer to a set of FDE/CIEs, terminated by an entry where length is 0. + +In Apple's libunwind implementation the pointer is taken to be to a +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 +for more detail. + +This change is based on the LLVM RTDyldMemoryManager.cpp. It should +later be changed to be alignment-safe. + +Reported by: dim +Reviewed by: dim +Sponsored by: The FreeBSD Foundation +Differential Revision: https://reviews.freebsd.org/D8869 + +Reapply r351610: + +Update libunwind custom frame register and deregister functions for +FreeBSD: use the new doubly underscored names for unw_add_dynamic_fde +and unw_remove_dynamic_fde. + +NOTE: this should be upstreamed... +--- + .../libunwind/src/UnwindLevel1-gcc-ext.c | 42 ++++++++++++++++++- + 1 file changed, 41 insertions(+), 1 deletion(-) + +diff --git a/libunwind/src/UnwindLevel1-gcc-ext.c b/libunwind/src/UnwindLevel1-gcc-ext.c +index 310b836d129e5..30f9cabf241f2 100644 +--- a/libunwind/src/UnwindLevel1-gcc-ext.c ++++ b/libunwind/src/UnwindLevel1-gcc-ext.c +@@ -234,6 +234,46 @@ _LIBUNWIND_EXPORT uintptr_t _Unwind_GetIPInfo(struct _Unwind_Context *context, + + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) + ++#if defined(__FreeBSD__) ++ ++// Based on LLVM's lib/ExecutionEngine/RuntimeDyld/RTDyldMemoryManager.cpp ++// and XXX should be fixed to be alignment-safe. ++static void processFDE(const char *addr, bool isDeregister) { ++ uint64_t length; ++ while ((length = *((const uint32_t *)addr)) != 0) { ++ const char *p = addr + 4; ++ if (length == 0xffffffff) { ++ length = *((const uint64_t *)p); ++ p += 8; ++ } ++ uint32_t offset = *((const uint32_t *)p); ++ if (offset != 0) { ++ if (isDeregister) ++ __unw_remove_dynamic_fde((unw_word_t)(uintptr_t)addr); ++ else ++ __unw_add_dynamic_fde((unw_word_t)(uintptr_t)addr); ++ } ++ addr = p + length; ++ } ++} ++ ++/// Called by programs with dynamic code generators that want to register ++/// dynamically generated FDEs, with a libgcc-compatible API. ++ ++_LIBUNWIND_EXPORT void __register_frame(const void *addr) { ++ _LIBUNWIND_TRACE_API("__register_frame(%p)", addr); ++ processFDE(addr, false); ++} ++ ++/// Called by programs with dynamic code generators that want to unregister ++/// dynamically generated FDEs, with a libgcc-compatible API. ++_LIBUNWIND_EXPORT void __deregister_frame(const void *addr) { ++ _LIBUNWIND_TRACE_API("__deregister_frame(%p)", addr); ++ processFDE(addr, true); ++} ++ ++#else // defined(__FreeBSD__) ++ + /// Called by programs with dynamic code generators that want + /// to register a dynamically generated FDE. + /// This function has existed on Mac OS X since 10.4, but +@@ -243,7 +283,6 @@ _LIBUNWIND_EXPORT void __register_frame(const void *fde) { + __unw_add_dynamic_fde((unw_word_t)(uintptr_t)fde); + } + +- + /// Called by programs with dynamic code generators that want + /// to unregister a dynamically generated FDE. + /// This function has existed on Mac OS X since 10.4, but +@@ -253,6 +292,7 @@ _LIBUNWIND_EXPORT void __deregister_frame(const void *fde) { + __unw_remove_dynamic_fde((unw_word_t)(uintptr_t)fde); + } + ++#endif // defined(__FreeBSD__) + + // The following register/deregister functions are gcc extensions. + // They have existed on Mac OS X, but have never worked because Mac OS X diff --git a/deps/patches/llvm-libunwind-prologue-epilogue.patch b/deps/patches/llvm-libunwind-prologue-epilogue.patch new file mode 100644 index 0000000000000..7dadca728f9cf --- /dev/null +++ b/deps/patches/llvm-libunwind-prologue-epilogue.patch @@ -0,0 +1,183 @@ +An updated version of this libosxunwind commit: + +commit ca57a5b60de4cd1daa42ed2e5d1d4aa3e96a09d1 +Author: Keno Fischer +Date: Mon Aug 26 15:28:08 2013 -0400 + + Add support for unwinding during prologue/epilogue + +--- +diff --git a/libunwind/src/CompactUnwinder.hpp b/libunwind/src/CompactUnwinder.hpp +index 1c3175dff50a..78a658ccbc27 100644 +--- a/libunwind/src/CompactUnwinder.hpp ++++ b/libunwind/src/CompactUnwinder.hpp +@@ -310,6 +310,50 @@ int CompactUnwinder_x86_64::stepWithCompactEncodingRBPFrame( + uint32_t savedRegistersLocations = + EXTRACT_BITS(compactEncoding, UNWIND_X86_64_RBP_FRAME_REGISTERS); + ++ // If we have not stored EBP yet ++ if (functionStart == registers.getIP()) { ++ uint64_t rsp = registers.getSP(); ++ // old esp is ebp less return address ++ registers.setSP(rsp+8); ++ // pop return address into eip ++ registers.setIP(addressSpace.get64(rsp)); ++ ++ return UNW_STEP_SUCCESS; ++ } else if (functionStart + 1 == registers.getIP()) { ++ uint64_t rsp = registers.getSP(); ++ // old esp is ebp less return address ++ registers.setSP(rsp + 16); ++ // pop return address into eip ++ registers.setIP(addressSpace.get64(rsp + 8)); ++ ++ return UNW_STEP_SUCCESS; ++ } ++ ++ // If we're about to return, we've already popped the base pointer ++ uint8_t b = addressSpace.get8(registers.getIP()); ++ ++ // This is a hack to detect VZEROUPPER but in between popq rbp and ret ++ // It's not pretty but it works ++ if (b == 0xC5) { ++ if ((b = addressSpace.get8(registers.getIP() + 1)) == 0xF8 && ++ (b = addressSpace.get8(registers.getIP() + 2)) == 0x77) ++ b = addressSpace.get8(registers.getIP() + 3); ++ else ++ goto skip_ret; ++ } ++ ++ if (b == 0xC3 || b == 0xCB || b == 0xC2 || b == 0xCA) { ++ uint64_t rbp = registers.getSP(); ++ // old esp is ebp less return address ++ registers.setSP(rbp + 16); ++ // pop return address into eip ++ registers.setIP(addressSpace.get64(rbp + 8)); ++ ++ return UNW_STEP_SUCCESS; ++ } ++ ++ skip_ret: ++ + uint64_t savedRegisters = registers.getRBP() - 8 * savedRegistersOffset; + for (int i = 0; i < 5; ++i) { + switch (savedRegistersLocations & 0x7) { +@@ -430,6 +474,118 @@ int CompactUnwinder_x86_64::stepWithCompactEncodingFrameless( + } + } + } ++ ++ // Note that the order of these registers is so that ++ // registersSaved[0] is the one that will be pushed onto the stack last. ++ // Thus, if we want to walk this from the top, we need to go in reverse. ++ assert(regCount <= 6); ++ ++ // check whether we are still in the prologue ++ uint64_t curAddr = functionStart; ++ if (regCount > 0) { ++ for (int8_t i = (int8_t)(regCount) - 1; i >= 0; --i) { ++ if (registers.getIP() == curAddr) { ++ // None of the registers have been modified yet, so we don't need to reload them ++ framelessUnwind(addressSpace, registers.getSP() + 8 * (regCount - (uint64_t)(i + 1)), registers); ++ return UNW_STEP_SUCCESS; ++ } else { ++ assert(curAddr < registers.getIP()); ++ } ++ ++ ++ // pushq %rbp and pushq %rbx is 1 byte. Everything else 2 ++ if ((UNWIND_X86_64_REG_RBP == registersSaved[i]) || ++ (UNWIND_X86_64_REG_RBX == registersSaved[i])) ++ curAddr += 1; ++ else ++ curAddr += 2; ++ } ++ } ++ if (registers.getIP() == curAddr) { ++ // None of the registers have been modified yet, so we don't need to reload them ++ framelessUnwind(addressSpace, registers.getSP() + 8*regCount, registers); ++ return UNW_STEP_SUCCESS; ++ } else { ++ assert(curAddr < registers.getIP()); ++ } ++ ++ ++ // And now for the epilogue ++ { ++ uint8_t i = 0; ++ uint64_t p = registers.getIP(); ++ uint8_t b = 0; ++ ++ while (true) { ++ b = addressSpace.get8(p++); ++ // This is a hack to detect VZEROUPPER but in between the popq's and ret ++ // It's not pretty but it works ++ if (b == 0xC5) { ++ if ((b = addressSpace.get8(p++)) == 0xF8 && (b = addressSpace.get8(p++)) == 0x77) ++ b = addressSpace.get8(p++); ++ else ++ break; ++ } ++ // popq %rbx popq %rbp ++ if (b == 0x5B || b == 0x5D) { ++ i++; ++ } else if (b == 0x41) { ++ b = addressSpace.get8(p++); ++ if (b == 0x5C || b == 0x5D || b == 0x5E || b == 0x5F) ++ i++; ++ else ++ break; ++ } else if (b == 0xC3 || b == 0xCB || b == 0xC2 || b == 0xCA) { ++ // i pop's haven't happened yet ++ uint64_t savedRegisters = registers.getSP() + 8 * i; ++ if (regCount > 0) { ++ for (int8_t j = (int8_t)(regCount) - 1; j >= (int8_t)(regCount) - i; --j) { ++ uint64_t addr = savedRegisters - 8 * (regCount - (uint64_t)(j)); ++ switch (registersSaved[j]) { ++ case UNWIND_X86_64_REG_RBX: ++ registers.setRBX(addressSpace.get64(addr)); ++ break; ++ case UNWIND_X86_64_REG_R12: ++ registers.setR12(addressSpace.get64(addr)); ++ break; ++ case UNWIND_X86_64_REG_R13: ++ registers.setR13(addressSpace.get64(addr)); ++ break; ++ case UNWIND_X86_64_REG_R14: ++ registers.setR14(addressSpace.get64(addr)); ++ break; ++ case UNWIND_X86_64_REG_R15: ++ registers.setR15(addressSpace.get64(addr)); ++ break; ++ case UNWIND_X86_64_REG_RBP: ++ registers.setRBP(addressSpace.get64(addr)); ++ break; ++ default: ++ _LIBUNWIND_DEBUG_LOG("bad register for frameless, encoding=%08X for " ++ "function starting at 0x%llX", ++ encoding, functionStart); ++ _LIBUNWIND_ABORT("invalid compact unwind encoding"); ++ } ++ } ++ } ++ framelessUnwind(addressSpace, savedRegisters, registers); ++ return UNW_STEP_SUCCESS; ++ } else { ++ break; ++ } ++ } ++ } ++ ++ /* ++ 0x10fe2733a: 5b popq %rbx ++ 0x10fe2733b: 41 5c popq %r12 ++ 0x10fe2733d: 41 5d popq %r13 ++ 0x10fe2733f: 41 5e popq %r14 ++ 0x10fe27341: 41 5f popq %r15 ++ 0x10fe27343: 5d popq %rbp ++ */ ++ ++ + uint64_t savedRegisters = registers.getSP() + stackSize - 8 - 8 * regCount; + for (uint32_t i = 0; i < regCount; ++i) { + switch (registersSaved[i]) { diff --git a/deps/patches/llvm-libunwind-revert-monorepo-requirement.patch b/deps/patches/llvm-libunwind-revert-monorepo-requirement.patch new file mode 100644 index 0000000000000..4e3897dfb9801 --- /dev/null +++ b/deps/patches/llvm-libunwind-revert-monorepo-requirement.patch @@ -0,0 +1,156 @@ +Upstream commit 8c03fdf34a659925a3f09c8f54016e47ea1c7519 changed the build such +that it requires living inside the monorepo with libcxx available, only so that +it can reuse a CMake file to simplify some build steps. This patch is a revert +of that commit applied only to libunwind. + +--- +diff --git a/libunwind/CMakeLists.txt b/libunwind/CMakeLists.txt +index 570b8db90653..a383d7d77d6f 100644 +--- a/libunwind/CMakeLists.txt ++++ b/libunwind/CMakeLists.txt +@@ -1,7 +1,3 @@ +-if (NOT IS_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../libcxx") +- message(FATAL_ERROR "libunwind requires being built in a monorepo layout with libcxx available") +-endif() +- + #=============================================================================== + # Setup Project + #=============================================================================== +@@ -15,31 +11,103 @@ set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + ) + +-set(LIBUNWIND_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +-set(LIBUNWIND_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +-set(LIBUNWIND_LIBCXX_PATH "${CMAKE_CURRENT_LIST_DIR}/../libcxx" CACHE PATH +- "Specify path to libc++ source.") +- + if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR OR LIBUNWIND_STANDALONE_BUILD) + project(libunwind LANGUAGES C CXX ASM) + ++ # Rely on llvm-config. ++ set(CONFIG_OUTPUT) ++ if(NOT LLVM_CONFIG_PATH) ++ find_program(LLVM_CONFIG_PATH "llvm-config") ++ endif() ++ if (DEFINED LLVM_PATH) ++ set(LLVM_INCLUDE_DIR ${LLVM_INCLUDE_DIR} CACHE PATH "Path to llvm/include") ++ set(LLVM_PATH ${LLVM_PATH} CACHE PATH "Path to LLVM source tree") ++ set(LLVM_MAIN_SRC_DIR ${LLVM_PATH}) ++ set(LLVM_CMAKE_PATH "${LLVM_PATH}/cmake/modules") ++ elseif(LLVM_CONFIG_PATH) ++ message(STATUS "Found LLVM_CONFIG_PATH as ${LLVM_CONFIG_PATH}") ++ set(CONFIG_COMMAND ${LLVM_CONFIG_PATH} "--includedir" "--prefix" "--src-root") ++ execute_process(COMMAND ${CONFIG_COMMAND} ++ RESULT_VARIABLE HAD_ERROR ++ OUTPUT_VARIABLE CONFIG_OUTPUT) ++ if (NOT HAD_ERROR) ++ string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" ++ CONFIG_OUTPUT ${CONFIG_OUTPUT}) ++ else() ++ string(REPLACE ";" " " CONFIG_COMMAND_STR "${CONFIG_COMMAND}") ++ message(STATUS "${CONFIG_COMMAND_STR}") ++ message(FATAL_ERROR "llvm-config failed with status ${HAD_ERROR}") ++ endif() ++ ++ list(GET CONFIG_OUTPUT 0 INCLUDE_DIR) ++ list(GET CONFIG_OUTPUT 1 LLVM_OBJ_ROOT) ++ list(GET CONFIG_OUTPUT 2 MAIN_SRC_DIR) ++ ++ set(LLVM_INCLUDE_DIR ${INCLUDE_DIR} CACHE PATH "Path to llvm/include") ++ set(LLVM_BINARY_DIR ${LLVM_OBJ_ROOT} CACHE PATH "Path to LLVM build tree") ++ set(LLVM_MAIN_SRC_DIR ${MAIN_SRC_DIR} CACHE PATH "Path to LLVM source tree") ++ set(LLVM_LIT_PATH "${LLVM_PATH}/utils/lit/lit.py") ++ ++ # --cmakedir is supported since llvm r291218 (4.0 release) ++ execute_process( ++ COMMAND ${LLVM_CONFIG_PATH} --cmakedir ++ RESULT_VARIABLE HAD_ERROR ++ OUTPUT_VARIABLE CONFIG_OUTPUT ++ ERROR_QUIET) ++ if(NOT HAD_ERROR) ++ string(STRIP "${CONFIG_OUTPUT}" LLVM_CMAKE_PATH_FROM_LLVM_CONFIG) ++ file(TO_CMAKE_PATH "${LLVM_CMAKE_PATH_FROM_LLVM_CONFIG}" LLVM_CMAKE_PATH) ++ else() ++ file(TO_CMAKE_PATH "${LLVM_BINARY_DIR}" LLVM_BINARY_DIR_CMAKE_STYLE) ++ set(LLVM_CMAKE_PATH "${LLVM_BINARY_DIR_CMAKE_STYLE}/lib${LLVM_LIBDIR_SUFFIX}/cmake/llvm") ++ endif() ++ else() ++ message(WARNING "UNSUPPORTED LIBUNWIND CONFIGURATION DETECTED: " ++ "llvm-config not found and LLVM_MAIN_SRC_DIR not defined. " ++ "Reconfigure with -DLLVM_CONFIG=path/to/llvm-config " ++ "or -DLLVM_PATH=path/to/llvm-source-root.") ++ endif() ++ ++ if (EXISTS ${LLVM_CMAKE_PATH}) ++ list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_PATH}") ++ include("${LLVM_CMAKE_PATH}/AddLLVM.cmake") ++ include("${LLVM_CMAKE_PATH}/HandleLLVMOptions.cmake") ++ else() ++ message(WARNING "Not found: ${LLVM_CMAKE_PATH}") ++ endif() ++ + set(PACKAGE_NAME libunwind) + set(PACKAGE_VERSION 12.0.1) + set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") + set(PACKAGE_BUGREPORT "llvm-bugs@lists.llvm.org") + +- # Add the CMake module path of libcxx so we can reuse HandleOutOfTreeLLVM.cmake +- set(LIBUNWIND_LIBCXX_CMAKE_PATH "${LIBUNWIND_LIBCXX_PATH}/cmake/Modules") +- list(APPEND CMAKE_MODULE_PATH "${LIBUNWIND_LIBCXX_CMAKE_PATH}") ++ if (EXISTS ${LLVM_MAIN_SRC_DIR}/utils/lit/lit.py) ++ set(LLVM_LIT ${LLVM_MAIN_SRC_DIR}/utils/lit/lit.py) ++ else() ++ # Seek installed Lit. ++ find_program(LLVM_LIT "lit.py" ${LLVM_MAIN_SRC_DIR}/utils/lit ++ DOC "Path to lit.py") ++ endif() + +- # In a standalone build, we don't have llvm to automatically generate the +- # llvm-lit script for us. So we need to provide an explicit directory that +- # the configurator should write the script into. +- set(LIBUNWIND_STANDALONE_BUILD 1) +- set(LLVM_LIT_OUTPUT_DIR "${LIBUNWIND_BINARY_DIR}/bin") ++ if (LLVM_LIT) ++ # Define the default arguments to use with 'lit', and an option for the user ++ # to override. ++ set(LIT_ARGS_DEFAULT "-sv") ++ if (MSVC OR XCODE) ++ set(LIT_ARGS_DEFAULT "${LIT_ARGS_DEFAULT} --no-progress-bar") ++ endif() ++ set(LLVM_LIT_ARGS "${LIT_ARGS_DEFAULT}" CACHE STRING "Default options for lit") ++ ++ # On Win32 hosts, provide an option to specify the path to the GnuWin32 tools. ++ if (WIN32 AND NOT CYGWIN) ++ set(LLVM_LIT_TOOLS_DIR "" CACHE PATH "Path to GnuWin32 tools") ++ endif() ++ else() ++ set(LLVM_INCLUDE_TESTS OFF) ++ endif() + +- # Find the LLVM sources and simulate LLVM CMake options. +- include(HandleOutOfTreeLLVM) ++ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX}) ++ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX}) + else() + set(LLVM_LIT "${CMAKE_SOURCE_DIR}/utils/lit/lit.py") + endif() +@@ -85,8 +153,6 @@ set(LIBUNWIND_TEST_COMPILER_FLAGS "" CACHE STRING + "Additional compiler flags for test programs.") + set(LIBUNWIND_TEST_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/test/lit.site.cfg.in" CACHE STRING + "The Lit testing configuration to use when running the tests.") +-set(LIBUNWIND_TEST_PARAMS "" CACHE STRING +- "A list of parameters to run the Lit test suite with.") + + if (NOT LIBUNWIND_ENABLE_SHARED AND NOT LIBUNWIND_ENABLE_STATIC) + message(FATAL_ERROR "libunwind must be built as either a shared or static library.") +@@ -113,6 +179,9 @@ set(CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/cmake" + ${CMAKE_MODULE_PATH}) + ++set(LIBUNWIND_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) ++set(LIBUNWIND_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) ++ + if(LLVM_ENABLE_PER_TARGET_RUNTIME_DIR AND NOT APPLE) + set(LIBUNWIND_LIBRARY_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR}/${LLVM_DEFAULT_TARGET_TRIPLE}/c++) + set(LIBUNWIND_INSTALL_LIBRARY_DIR lib${LLVM_LIBDIR_SUFFIX}/${LLVM_DEFAULT_TARGET_TRIPLE}/c++) diff --git a/deps/patches/openblas-julia42415-lapack625-openblas3392.patch b/deps/patches/openblas-julia42415-lapack625-openblas3392.patch deleted file mode 100644 index e7b874b961cca..0000000000000 --- a/deps/patches/openblas-julia42415-lapack625-openblas3392.patch +++ /dev/null @@ -1,95 +0,0 @@ -From 2be5ee3cca97a597f2ee2118808a2d5eacea050c Mon Sep 17 00:00:00 2001 -From: Martin Kroeker -Date: Fri, 1 Oct 2021 11:17:21 +0200 -Subject: [PATCH 1/4] Fix out of bounds read in ?llarv (Reference-LAPACK PR - 625) - ---- - lapack-netlib/SRC/clarrv.f | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/lapack-netlib/SRC/clarrv.f b/lapack-netlib/SRC/clarrv.f -index a45f55ac3b..26a9febc87 100644 ---- a/lapack-netlib/SRC/clarrv.f -+++ b/lapack-netlib/SRC/clarrv.f -@@ -351,7 +351,7 @@ SUBROUTINE CLARRV( N, VL, VU, D, L, PIVMIN, - * - * Quick return if possible - * -- IF( N.LE.0 ) THEN -+ IF( (N.LE.0) .OR. (M.LE.0) ) THEN - RETURN - END IF - * - -From fe497efa0510466fd93578aaf9da1ad8ed4edbe7 Mon Sep 17 00:00:00 2001 -From: Martin Kroeker -Date: Fri, 1 Oct 2021 11:18:20 +0200 -Subject: [PATCH 2/4] Fix out of bounds read in ?llarv (Reference-LAPACK PR - 625) - ---- - lapack-netlib/SRC/dlarrv.f | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/lapack-netlib/SRC/dlarrv.f b/lapack-netlib/SRC/dlarrv.f -index 4a59a2bbf9..a1c6e9c9d7 100644 ---- a/lapack-netlib/SRC/dlarrv.f -+++ b/lapack-netlib/SRC/dlarrv.f -@@ -353,7 +353,7 @@ SUBROUTINE DLARRV( N, VL, VU, D, L, PIVMIN, - * - * Quick return if possible - * -- IF( N.LE.0 ) THEN -+ IF( (N.LE.0).OR.(M.LE.0) ) THEN - RETURN - END IF - * - -From ddb0ff5353637bb5f5ad060c9620e334c143e3d7 Mon Sep 17 00:00:00 2001 -From: Martin Kroeker -Date: Fri, 1 Oct 2021 11:19:07 +0200 -Subject: [PATCH 3/4] Fix out of bounds read in ?llarv (Reference-LAPACK PR - 625) - ---- - lapack-netlib/SRC/slarrv.f | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/lapack-netlib/SRC/slarrv.f b/lapack-netlib/SRC/slarrv.f -index 04519fde8c..9448b2fd92 100644 ---- a/lapack-netlib/SRC/slarrv.f -+++ b/lapack-netlib/SRC/slarrv.f -@@ -353,7 +353,7 @@ SUBROUTINE SLARRV( N, VL, VU, D, L, PIVMIN, - * - * Quick return if possible - * -- IF( N.LE.0 ) THEN -+ IF( (N.LE.0).OR.(M.LE.0) ) THEN - RETURN - END IF - * - -From 337b65133df174796794871b3988cd03426e6d41 Mon Sep 17 00:00:00 2001 -From: Martin Kroeker -Date: Fri, 1 Oct 2021 11:19:53 +0200 -Subject: [PATCH 4/4] Fix out of bounds read in ?llarv (Reference-LAPACK PR - 625) - ---- - lapack-netlib/SRC/zlarrv.f | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/lapack-netlib/SRC/zlarrv.f b/lapack-netlib/SRC/zlarrv.f -index 23976dbefe..8d10e3c2e3 100644 ---- a/lapack-netlib/SRC/zlarrv.f -+++ b/lapack-netlib/SRC/zlarrv.f -@@ -351,7 +351,7 @@ SUBROUTINE ZLARRV( N, VL, VU, D, L, PIVMIN, - * - * Quick return if possible - * -- IF( N.LE.0 ) THEN -+ IF( (N.LE.0).OR.(M.LE.0) ) THEN - RETURN - END IF - * diff --git a/deps/patches/openblas-ofast-power.patch b/deps/patches/openblas-ofast-power.patch index c741496cae757..405e3f7581331 100644 --- a/deps/patches/openblas-ofast-power.patch +++ b/deps/patches/openblas-ofast-power.patch @@ -1,17 +1,18 @@ diff --git a/Makefile.power b/Makefile.power -index 946f5523..19593050 100644 +index 28a0bae0..b4869fbd 100644 --- a/Makefile.power +++ b/Makefile.power -@@ -11,14 +11,14 @@ endif - +@@ -11,7 +11,7 @@ endif + ifeq ($(CORE), POWER10) ifneq ($(C_COMPILER), PGI) -CCOMMON_OPT += -Ofast -mcpu=power10 -mtune=power10 -mvsx -fno-fast-math +CCOMMON_OPT += -mcpu=power10 -mtune=power10 -mvsx -fno-fast-math - FCOMMON_OPT += -O2 -frecursive -mcpu=power10 -mtune=power10 -fno-fast-math - endif - endif - + ifeq ($(F_COMPILER), IBM) + FCOMMON_OPT += -O2 -qrecur -qnosave + else +@@ -22,7 +22,7 @@ endif + ifeq ($(CORE), POWER9) ifneq ($(C_COMPILER), PGI) -CCOMMON_OPT += -Ofast -mvsx -fno-fast-math @@ -19,8 +20,8 @@ index 946f5523..19593050 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) -@@ -51,7 +51,7 @@ endif - +@@ -59,7 +59,7 @@ endif + ifeq ($(CORE), POWER8) ifneq ($(C_COMPILER), PGI) -CCOMMON_OPT += -Ofast -mcpu=power8 -mtune=power8 -mvsx -fno-fast-math diff --git a/deps/patches/p7zip-12-CVE-2016-9296.patch b/deps/patches/p7zip-12-CVE-2016-9296.patch deleted file mode 100644 index 42245c92c0aae..0000000000000 --- a/deps/patches/p7zip-12-CVE-2016-9296.patch +++ /dev/null @@ -1,23 +0,0 @@ -From: Robert Luberda -Date: Sat, 19 Nov 2016 08:48:08 +0100 -Subject: Fix nullptr dereference (CVE-2016-9296) - -Patch taken from https://sourceforge.net/p/p7zip/bugs/185/ ---- - CPP/7zip/Archive/7z/7zIn.cpp | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/CPP/7zip/Archive/7z/7zIn.cpp b/CPP/7zip/Archive/7z/7zIn.cpp -index b0c6b98..7c6dde2 100644 ---- a/CPP/7zip/Archive/7z/7zIn.cpp -+++ b/CPP/7zip/Archive/7z/7zIn.cpp -@@ -1097,7 +1097,8 @@ HRESULT CInArchive::ReadAndDecodePackedStreams( - if (CrcCalc(data, unpackSize) != folders.FolderCRCs.Vals[i]) - ThrowIncorrect(); - } -- HeadersSize += folders.PackPositions[folders.NumPackStreams]; -+ if (folders.PackPositions) -+ HeadersSize += folders.PackPositions[folders.NumPackStreams]; - return S_OK; - } - diff --git a/deps/patches/p7zip-13-CVE-2017-17969.patch b/deps/patches/p7zip-13-CVE-2017-17969.patch deleted file mode 100644 index a9787c4a90886..0000000000000 --- a/deps/patches/p7zip-13-CVE-2017-17969.patch +++ /dev/null @@ -1,35 +0,0 @@ -From: =?utf-8?q?Antoine_Beaupr=C3=A9?= -Date: Fri, 2 Feb 2018 11:11:41 +0100 -Subject: Heap-based buffer overflow in 7zip/Compress/ShrinkDecoder.cpp - -Origin: vendor, https://sourceforge.net/p/p7zip/bugs/_discuss/thread/0920f369/27d7/attachment/CVE-2017-17969.patch -Forwarded: https://sourceforge.net/p/p7zip/bugs/_discuss/thread/0920f369/#27d7 -Bug: https://sourceforge.net/p/p7zip/bugs/204/ -Bug-Debian: https://bugs.debian.org/888297 -Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-17969 -Reviewed-by: Salvatore Bonaccorso -Last-Update: 2018-02-01 -Applied-Upstream: 18.00-beta ---- - CPP/7zip/Compress/ShrinkDecoder.cpp | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/CPP/7zip/Compress/ShrinkDecoder.cpp b/CPP/7zip/Compress/ShrinkDecoder.cpp -index 80b7e67..ca37764 100644 ---- a/CPP/7zip/Compress/ShrinkDecoder.cpp -+++ b/CPP/7zip/Compress/ShrinkDecoder.cpp -@@ -121,8 +121,13 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * - { - _stack[i++] = _suffixes[cur]; - cur = _parents[cur]; -+ if (cur >= kNumItems || i >= kNumItems) -+ break; - } -- -+ -+ if (cur >= kNumItems || i >= kNumItems) -+ break; -+ - _stack[i++] = (Byte)cur; - lastChar2 = (Byte)cur; - diff --git a/deps/patches/p7zip-15-Enhanced-encryption-strength.patch b/deps/patches/p7zip-15-Enhanced-encryption-strength.patch deleted file mode 100644 index ab1cfb9c743fb..0000000000000 --- a/deps/patches/p7zip-15-Enhanced-encryption-strength.patch +++ /dev/null @@ -1,298 +0,0 @@ -From ea31bbe661abef761e49983b56923e6523b9463a Mon Sep 17 00:00:00 2001 -From: aone -Date: Thu, 7 Mar 2019 10:06:16 +0100 -Subject: [PATCH] Enhanced encryption strength from 7-Zip 19.00 - -https://github.com/aonez/Keka/issues/379 -https://sourceforge.net/p/sevenzip/bugs/2176 ---- - .../CPP/7zip/Archive/Wim/WimHandlerOut.cpp | 2 +- - Bin/p7zip/source/CPP/7zip/Crypto/7zAes.cpp | 4 +- - Bin/p7zip/source/CPP/7zip/Crypto/RandGen.cpp | 135 ++++++++++++++++-- - Bin/p7zip/source/CPP/7zip/Crypto/RandGen.h | 19 +++ - Bin/p7zip/source/CPP/7zip/Crypto/WzAes.cpp | 2 +- - .../source/CPP/7zip/Crypto/ZipCrypto.cpp | 2 +- - 6 files changed, 146 insertions(+), 18 deletions(-) - -diff --git a/Bin/p7zip/source/CPP/7zip/Archive/Wim/WimHandlerOut.cpp b/Bin/p7zip/source/CPP/7zip/Archive/Wim/WimHandlerOut.cpp -index 1d198df0..39679883 100644 ---- a/Bin/p7zip/source/CPP/7zip/Archive/Wim/WimHandlerOut.cpp -+++ b/Bin/p7zip/source/CPP/7zip/Archive/Wim/WimHandlerOut.cpp -@@ -671,7 +671,7 @@ void CHeader::SetDefaultFields(bool useLZX) - ChunkSize = kChunkSize; - ChunkSizeBits = kChunkSizeBits; - } -- g_RandomGenerator.Generate(Guid, 16); -+ MY_RAND_GEN(Guid, 16); - PartNumber = 1; - NumParts = 1; - NumImages = 1; -diff --git a/Bin/p7zip/source/CPP/7zip/Crypto/7zAes.cpp b/Bin/p7zip/source/CPP/7zip/Crypto/7zAes.cpp -index d33b562a..2ed69bad 100644 ---- a/Bin/p7zip/source/CPP/7zip/Crypto/7zAes.cpp -+++ b/Bin/p7zip/source/CPP/7zip/Crypto/7zAes.cpp -@@ -164,8 +164,8 @@ STDMETHODIMP CEncoder::ResetInitVector() - { - for (unsigned i = 0; i < sizeof(_iv); i++) - _iv[i] = 0; -- _ivSize = 8; -- g_RandomGenerator.Generate(_iv, _ivSize); -+ _ivSize = 16; -+ MY_RAND_GEN(_iv, _ivSize); - return S_OK; - } - -diff --git a/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.cpp b/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.cpp -index f5ea31f0..a70f4ec8 100644 ---- a/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.cpp -+++ b/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.cpp -@@ -2,14 +2,44 @@ - - #include "StdAfx.h" - -+#include "RandGen.h" -+ -+#ifndef USE_STATIC_SYSTEM_RAND -+ - #ifndef _7ZIP_ST - #include "../../Windows/Synchronization.h" - #endif - --#include "RandGen.h" - --#ifndef _WIN32 -+#ifdef _WIN32 -+ -+#ifdef _WIN64 -+#define USE_STATIC_RtlGenRandom -+#endif -+ -+#ifdef USE_STATIC_RtlGenRandom -+ -+#include -+ -+EXTERN_C_BEGIN -+#ifndef RtlGenRandom -+ #define RtlGenRandom SystemFunction036 -+ BOOLEAN WINAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength); -+#endif -+EXTERN_C_END -+ -+#else -+EXTERN_C_BEGIN -+typedef BOOLEAN (WINAPI * Func_RtlGenRandom)(PVOID RandomBuffer, ULONG RandomBufferLength); -+EXTERN_C_END -+#endif -+ -+ -+#else - #include -+#include -+#include -+#include - #define USE_POSIX_TIME - #define USE_POSIX_TIME2 - #endif -@@ -21,11 +51,9 @@ - #endif - #endif - --// This is not very good random number generator. --// Please use it only for salt. --// First generated data block depends from timer and processID. -+// The seed and first generated data block depend from processID, -+// theadID, timer and system random generator, if available. - // Other generated data blocks depend from previous state --// Maybe it's possible to restore original timer value from generated value. - - #define HASH_UPD(x) Sha256_Update(&hash, (const Byte *)&x, sizeof(x)); - -@@ -34,25 +62,102 @@ void CRandomGenerator::Init() - CSha256 hash; - Sha256_Init(&hash); - -+ unsigned numIterations = 1000; -+ -+ { -+ #ifndef UNDER_CE -+ const unsigned kNumIterations_Small = 100; -+ const unsigned kBufSize = 32; -+ Byte buf[kBufSize]; -+ #endif -+ - #ifdef _WIN32 -+ - DWORD w = ::GetCurrentProcessId(); - HASH_UPD(w); - w = ::GetCurrentThreadId(); - HASH_UPD(w); -+ -+ #ifdef UNDER_CE -+ /* -+ if (CeGenRandom(kBufSize, buf)) -+ { -+ numIterations = kNumIterations_Small; -+ Sha256_Update(&hash, buf, kBufSize); -+ } -+ */ -+ #elif defined(USE_STATIC_RtlGenRandom) -+ if (RtlGenRandom(buf, kBufSize)) -+ { -+ numIterations = kNumIterations_Small; -+ Sha256_Update(&hash, buf, kBufSize); -+ } - #else -+ { -+ HMODULE hModule = ::LoadLibrary(TEXT("Advapi32.dll")); -+ if (hModule) -+ { -+ // SystemFunction036() is real name of RtlGenRandom() function -+ Func_RtlGenRandom my_RtlGenRandom = (Func_RtlGenRandom)GetProcAddress(hModule, "SystemFunction036"); -+ if (my_RtlGenRandom) -+ { -+ if (my_RtlGenRandom(buf, kBufSize)) -+ { -+ numIterations = kNumIterations_Small; -+ Sha256_Update(&hash, buf, kBufSize); -+ } -+ } -+ ::FreeLibrary(hModule); -+ } -+ } -+ #endif -+ -+ #else -+ - pid_t pid = getpid(); - HASH_UPD(pid); - pid = getppid(); - HASH_UPD(pid); -+ -+ { -+ int f = open("/dev/urandom", O_RDONLY); -+ unsigned numBytes = kBufSize; -+ if (f >= 0) -+ { -+ do -+ { -+ int n = read(f, buf, numBytes); -+ if (n <= 0) -+ break; -+ Sha256_Update(&hash, buf, n); -+ numBytes -= n; -+ } -+ while (numBytes); -+ close(f); -+ if (numBytes == 0) -+ numIterations = kNumIterations_Small; -+ } -+ } -+ /* -+ { -+ int n = getrandom(buf, kBufSize, 0); -+ if (n > 0) -+ { -+ Sha256_Update(&hash, buf, n); -+ if (n == kBufSize) -+ numIterations = kNumIterations_Small; -+ } -+ } -+ */ -+ -+ #endif -+ } -+ -+ #ifdef _DEBUG -+ numIterations = 2; - #endif - -- for (unsigned i = 0; i < -- #ifdef _DEBUG -- 2; -- #else -- 1000; -- #endif -- i++) -+ do - { - #ifdef _WIN32 - LARGE_INTEGER v; -@@ -83,6 +188,8 @@ void CRandomGenerator::Init() - Sha256_Update(&hash, _buff, SHA256_DIGEST_SIZE); - } - } -+ while (--numIterations); -+ - Sha256_Final(&hash, _buff); - _needInit = false; - } -@@ -120,3 +227,5 @@ void CRandomGenerator::Generate(Byte *data, unsigned size) - } - - CRandomGenerator g_RandomGenerator; -+ -+#endif -diff --git a/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.h b/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.h -index cfdcd60d..5122ec4b 100644 ---- a/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.h -+++ b/Bin/p7zip/source/CPP/7zip/Crypto/RandGen.h -@@ -5,6 +5,21 @@ - - #include "../../../C/Sha256.h" - -+#ifdef _WIN64 -+// #define USE_STATIC_SYSTEM_RAND -+#endif -+ -+#ifdef USE_STATIC_SYSTEM_RAND -+ -+#ifdef _WIN32 -+#include -+#define MY_RAND_GEN(data, size) RtlGenRandom(data, size) -+#else -+#define MY_RAND_GEN(data, size) getrandom(data, size, 0) -+#endif -+ -+#else -+ - class CRandomGenerator - { - Byte _buff[SHA256_DIGEST_SIZE]; -@@ -18,4 +33,8 @@ public: - - extern CRandomGenerator g_RandomGenerator; - -+#define MY_RAND_GEN(data, size) g_RandomGenerator.Generate(data, size) -+ -+#endif -+ - #endif -diff --git a/Bin/p7zip/source/CPP/7zip/Crypto/WzAes.cpp b/Bin/p7zip/source/CPP/7zip/Crypto/WzAes.cpp -index 4572f06e..d415ab84 100644 ---- a/Bin/p7zip/source/CPP/7zip/Crypto/WzAes.cpp -+++ b/Bin/p7zip/source/CPP/7zip/Crypto/WzAes.cpp -@@ -96,7 +96,7 @@ STDMETHODIMP CBaseCoder::Init() - HRESULT CEncoder::WriteHeader(ISequentialOutStream *outStream) - { - unsigned saltSize = _key.GetSaltSize(); -- g_RandomGenerator.Generate(_key.Salt, saltSize); -+ MY_RAND_GEN(_key.Salt, saltSize); - Init2(); - RINOK(WriteStream(outStream, _key.Salt, saltSize)); - return WriteStream(outStream, _key.PwdVerifComputed, kPwdVerifSize); -diff --git a/Bin/p7zip/source/CPP/7zip/Crypto/ZipCrypto.cpp b/Bin/p7zip/source/CPP/7zip/Crypto/ZipCrypto.cpp -index ae715063..8610297a 100644 ---- a/Bin/p7zip/source/CPP/7zip/Crypto/ZipCrypto.cpp -+++ b/Bin/p7zip/source/CPP/7zip/Crypto/ZipCrypto.cpp -@@ -49,7 +49,7 @@ HRESULT CEncoder::WriteHeader_Check16(ISequentialOutStream *outStream, UInt16 cr - PKZIP 2.0+ used 1 byte CRC check. It's more secure. - We also use 1 byte CRC. */ - -- g_RandomGenerator.Generate(h, kHeaderSize - 1); -+ MY_RAND_GEN(h, kHeaderSize - 1); - // h[kHeaderSize - 2] = (Byte)(crc); - h[kHeaderSize - 1] = (Byte)(crc >> 8); - --- -2.17.1 - diff --git a/deps/patches/p7zip-Windows_ErrorMsg.patch b/deps/patches/p7zip-Windows_ErrorMsg.patch deleted file mode 100644 index 71de3e9f59c86..0000000000000 --- a/deps/patches/p7zip-Windows_ErrorMsg.patch +++ /dev/null @@ -1,33 +0,0 @@ -This fixes the build with Clang 6.0: - - ../../../../CPP/Windows/ErrorMsg.cpp:24:10: error: case value evaluates to -2147024809, which cannot be narrowed to type 'DWORD' (aka 'unsigned int') [-Wc++11-narrowing] - case E_INVALIDARG : txt = "E_INVALIDARG"; break ; - ^ - ../../../../CPP/Common/MyWindows.h:89:22: note: expanded from macro 'E_INVALIDARG' - #define E_INVALIDARG ((HRESULT)0x80070057L) - ^ - -The HRESULT cast in the macro causes the value to be read as signed int. ---- CPP/Windows/ErrorMsg.cpp.orig 2015-01-18 18:20:28 UTC -+++ CPP/Windows/ErrorMsg.cpp -@@ -15,13 +15,13 @@ UString MyFormatMessage(DWORD errorCode) - - switch(errorCode) { - case ERROR_NO_MORE_FILES : txt = "No more files"; break ; -- case E_NOTIMPL : txt = "E_NOTIMPL"; break ; -- case E_NOINTERFACE : txt = "E_NOINTERFACE"; break ; -- case E_ABORT : txt = "E_ABORT"; break ; -- case E_FAIL : txt = "E_FAIL"; break ; -- case STG_E_INVALIDFUNCTION : txt = "STG_E_INVALIDFUNCTION"; break ; -- case E_OUTOFMEMORY : txt = "E_OUTOFMEMORY"; break ; -- case E_INVALIDARG : txt = "E_INVALIDARG"; break ; -+ case (DWORD)(E_NOTIMPL) : txt = "E_NOTIMPL"; break ; -+ case (DWORD)(E_NOINTERFACE) : txt = "E_NOINTERFACE"; break ; -+ case (DWORD)(E_ABORT) : txt = "E_ABORT"; break ; -+ case (DWORD)(E_FAIL) : txt = "E_FAIL"; break ; -+ case (DWORD)(STG_E_INVALIDFUNCTION) : txt = "STG_E_INVALIDFUNCTION"; break ; -+ case (DWORD)(E_OUTOFMEMORY) : txt = "E_OUTOFMEMORY"; break ; -+ case (DWORD)(E_INVALIDARG) : txt = "E_INVALIDARG"; break ; - case ERROR_DIRECTORY : txt = "Error Directory"; break ; - default: - txt = strerror(errorCode); diff --git a/deps/patches/pcre2-sljit-apple-silicon-support.patch b/deps/patches/pcre2-sljit-apple-silicon-support.patch deleted file mode 100644 index 3aff832ca08fd..0000000000000 --- a/deps/patches/pcre2-sljit-apple-silicon-support.patch +++ /dev/null @@ -1,244 +0,0 @@ -From e87e1ccf93768238db3d6e28d0272980dba707fa Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Carlo=20Marcelo=20Arenas=20Bel=C3=B3n?= -Date: Mon, 30 Nov 2020 01:35:13 -0800 -Subject: [PATCH] macos: add BigSur support to execalloc (#90) - -Apple Silicon requires that pages that will hold JIT code are -marked with MAP_JIT (even if not using the hardened runtime) -and that a call be made to a pthread function before writing -to them, so a special exception could be made to the current -thread[1]; add support for both. - -since the allocator keeps the metadata about chunk/block in the -executable pages, all functions that modify that metadata will -also need to be updated. - -note that since there is no need for an accurate pointer range -with the apple implementation, NULL is passed for the pointers. - -historically, adding MAP_JIT was only recommended when the hardened -runtime was being used as it adds several undocumented restrictions -(like not being able to use JIT pages accross fork()) so the -new codepath won't be used if running in Intel. - -Tested-by: @Keno -Fixes: #51 - -[1] https://developer.apple.com/documentation/apple_silicon/porting_just-in-time_compilers_to_apple_silicon?language=objc ---- - sljit_src/sljitExecAllocator.c | 113 ++++++++++++++++++--------------- - 1 file changed, 63 insertions(+), 50 deletions(-) - -diff --git a/sljit_src/sljitExecAllocator.c b/sljit_src/sljitExecAllocator.c -index 61a32f2..2e1c138 100644 ---- a/sljit_src/sljitExecAllocator.c -+++ b/sljit_src/sljitExecAllocator.c -@@ -79,6 +79,7 @@ - */ - - #ifdef _WIN32 -+#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) - - static SLJIT_INLINE void* alloc_chunk(sljit_uw size) - { -@@ -91,65 +92,76 @@ static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size) - VirtualFree(chunk, 0, MEM_RELEASE); - } - --#else -- --#ifdef __APPLE__ --#ifdef MAP_ANON --/* Configures TARGET_OS_OSX when appropriate */ --#include -- --#if TARGET_OS_OSX && defined(MAP_JIT) --#include --#endif /* TARGET_OS_OSX && MAP_JIT */ -- --#ifdef MAP_JIT -+#else /* POSIX */ - -+#if defined(__APPLE__) && defined(MAP_JIT) - /* - On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a -- version where it's OK to have more than one JIT block. -+ version where it's OK to have more than one JIT block or where MAP_JIT is -+ required. - On non-macOS systems, returns MAP_JIT if it is defined. - */ -+#include -+#if TARGET_OS_OSX -+#if defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86 -+#ifdef MAP_ANON -+#include -+#include -+ -+#define SLJIT_MAP_JIT (get_map_jit_flag()) -+ - static SLJIT_INLINE int get_map_jit_flag() - { --#if TARGET_OS_OSX -- sljit_sw page_size = get_page_alignment() + 1; -+ sljit_sw page_size; - void *ptr; -+ struct utsname name; - static int map_jit_flag = -1; - -- /* -- The following code is thread safe because multiple initialization -- sets map_jit_flag to the same value and the code has no side-effects. -- Changing the kernel version witout system restart is (very) unlikely. -- */ -- if (map_jit_flag == -1) { -- struct utsname name; -- -+ if (map_jit_flag < 0) { - map_jit_flag = 0; - uname(&name); - -- /* Kernel version for 10.14.0 (Mojave) */ -+ /* Kernel version for 10.14.0 (Mojave) or later */ - if (atoi(name.release) >= 18) { -+ page_size = get_page_alignment() + 1; - /* Only use MAP_JIT if a hardened runtime is used */ -+ ptr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC, -+ MAP_PRIVATE | MAP_ANON, -1, 0); - -- ptr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); -- -- if (ptr == MAP_FAILED) { -- map_jit_flag = MAP_JIT; -- } else { -+ if (ptr != MAP_FAILED) - munmap(ptr, page_size); -- } -+ else -+ map_jit_flag = MAP_JIT; - } - } -- - return map_jit_flag; --#else /* !TARGET_OS_OSX */ -- return MAP_JIT; --#endif /* TARGET_OS_OSX */ - } -- --#endif /* MAP_JIT */ - #endif /* MAP_ANON */ --#endif /* __APPLE__ */ -+#else /* !SLJIT_CONFIG_X86 */ -+#if !(defined SLJIT_CONFIG_ARM && SLJIT_CONFIG_ARM) -+#error Unsupported architecture -+#endif /* SLJIT_CONFIG_ARM */ -+#include -+ -+#define SLJIT_MAP_JIT (MAP_JIT) -+#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) \ -+ apple_update_wx_flags(enable_exec) -+ -+static SLJIT_INLINE void apple_update_wx_flags(sljit_s32 enable_exec) -+{ -+ pthread_jit_write_protect_np(enable_exec); -+} -+#endif /* SLJIT_CONFIG_X86 */ -+#else /* !TARGET_OS_OSX */ -+#define SLJIT_MAP_JIT (MAP_JIT) -+#endif /* TARGET_OS_OSX */ -+#endif /* __APPLE__ && MAP_JIT */ -+#ifndef SLJIT_UPDATE_WX_FLAGS -+#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) -+#endif /* !SLJIT_UPDATE_WX_FLAGS */ -+#ifndef SLJIT_MAP_JIT -+#define SLJIT_MAP_JIT (0) -+#endif /* !SLJIT_MAP_JIT */ - - static SLJIT_INLINE void* alloc_chunk(sljit_uw size) - { -@@ -157,12 +169,7 @@ static SLJIT_INLINE void* alloc_chunk(sljit_uw size) - const int prot = PROT_READ | PROT_WRITE | PROT_EXEC; - - #ifdef MAP_ANON -- -- int flags = MAP_PRIVATE | MAP_ANON; -- --#ifdef MAP_JIT -- flags |= get_map_jit_flag(); --#endif -+ int flags = MAP_PRIVATE | MAP_ANON | SLJIT_MAP_JIT; - - retval = mmap(NULL, size, prot, flags, -1, 0); - #else /* !MAP_ANON */ -@@ -173,14 +180,15 @@ static SLJIT_INLINE void* alloc_chunk(sljit_uw size) - #endif /* MAP_ANON */ - - if (retval == MAP_FAILED) -- retval = NULL; -- else { -- if (mprotect(retval, size, prot) < 0) { -- munmap(retval, size); -- retval = NULL; -- } -+ return NULL; -+ -+ if (mprotect(retval, size, prot) < 0) { -+ munmap(retval, size); -+ return NULL; - } - -+ SLJIT_UPDATE_WX_FLAGS(retval, (uint8_t *)retval + size, 0); -+ - return retval; - } - -@@ -189,7 +197,7 @@ static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size) - munmap(chunk, size); - } - --#endif -+#endif /* windows */ - - /* --------------------------------------------------------------------- */ - /* Common functions */ -@@ -261,6 +269,7 @@ SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size) - while (free_block) { - if (free_block->size >= size) { - chunk_size = free_block->size; -+ SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0); - if (chunk_size > size + 64) { - /* We just cut a block from the end of the free block. */ - chunk_size -= size; -@@ -326,6 +335,7 @@ SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr) - allocated_size -= header->size; - - /* Connecting free blocks together if possible. */ -+ SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0); - - /* If header->prev_size == 0, free_block will equal to header. - In this case, free_block->header.size will be > 0. */ -@@ -358,6 +368,7 @@ SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr) - } - } - -+ SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1); - SLJIT_ALLOCATOR_UNLOCK(); - } - -@@ -367,6 +378,7 @@ SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void) - struct free_block* next_free_block; - - SLJIT_ALLOCATOR_LOCK(); -+ SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0); - - free_block = free_blocks; - while (free_block) { -@@ -381,5 +393,6 @@ SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void) - } - - SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks)); -+ SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1); - SLJIT_ALLOCATOR_UNLOCK(); - } --- -2.30.0 - diff --git a/deps/patches/pcre2-sljit-nomprotect.patch b/deps/patches/pcre2-sljit-nomprotect.patch deleted file mode 100644 index 3c2df1808630b..0000000000000 --- a/deps/patches/pcre2-sljit-nomprotect.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/sljit_src/sljitExecAllocator.c b/sljit_src/sljitExecAllocator.c -index 2e1c138..bae8cd6 100644 ---- a/sljit_src/sljitExecAllocator.c -+++ b/sljit_src/sljitExecAllocator.c -@@ -182,10 +182,12 @@ static SLJIT_INLINE void* alloc_chunk(sljit_uw size) - if (retval == MAP_FAILED) - return NULL; - -+#ifdef SLIJT_WX_OS_NEEDSCHEK - if (mprotect(retval, size, prot) < 0) { - munmap(retval, size); - return NULL; - } -+#endif - - SLJIT_UPDATE_WX_FLAGS(retval, (uint8_t *)retval + size, 0); - diff --git a/deps/pcre.mk b/deps/pcre.mk index 053a773e5609e..cd1180d992885 100644 --- a/deps/pcre.mk +++ b/deps/pcre.mk @@ -1,34 +1,33 @@ ## PCRE ## +include $(SRCDIR)/pcre.version ifneq ($(USE_BINARYBUILDER_PCRE),1) # Force optimization for PCRE flags (Issue #11668) PCRE_CFLAGS := -O3 PCRE_LDFLAGS := $(RPATH_ESCAPED_ORIGIN) +ifeq ($(OS),emscripten) +PCRE_CFLAGS += -fPIC +PCRE_JIT = --disable-jit +else +PCRE_JIT = --enable-jit +endif + $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2: | $(SRCCACHE) - $(JLDOWNLOAD) $@ https://github.com/PhilipHazel/pcre2/releases/download/pcre2-$(PCRE_VER)/pcre2-$(PCRE_VER).tar.bz2 + $(JLDOWNLOAD) $@ https://github.com/PCRE2Project/pcre2/releases/download/pcre2-$(PCRE_VER)/pcre2-$(PCRE_VER).tar.bz2 $(SRCCACHE)/pcre2-$(PCRE_VER)/source-extracted: $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2 $(JLCHECKSUM) $< cd $(dir $<) && $(TAR) jxf $(notdir $<) - cp $(SRCDIR)/patches/config.sub $(SRCCACHE)/pcre2-$(PCRE_VER)/config.sub echo 1 > $@ checksum-pcre: $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2 $(JLCHECKSUM) $< -$(SRCCACHE)/pcre2-$(PCRE_VER)/pcre2-sljit-apple-silicon-support.patch-applied: $(SRCCACHE)/pcre2-$(PCRE_VER)/source-extracted - cd $(SRCCACHE)/pcre2-$(PCRE_VER) && patch -d src/sljit -p2 -f < $(SRCDIR)/patches/pcre2-sljit-apple-silicon-support.patch - echo 1 > $@ - -$(SRCCACHE)/pcre2-$(PCRE_VER)/pcre2-sljit-nomprotect.patch-applied: $(SRCCACHE)/pcre2-$(PCRE_VER)/pcre2-sljit-apple-silicon-support.patch-applied - cd $(SRCCACHE)/pcre2-$(PCRE_VER) && patch -d src/sljit -p2 -f < $(SRCDIR)/patches/pcre2-sljit-nomprotect.patch - echo 1 > $@ - -$(BUILDDIR)/pcre2-$(PCRE_VER)/build-configured: $(SRCCACHE)/pcre2-$(PCRE_VER)/source-extracted $(SRCCACHE)/pcre2-$(PCRE_VER)/pcre2-sljit-apple-silicon-support.patch-applied $(SRCCACHE)/pcre2-$(PCRE_VER)/pcre2-sljit-nomprotect.patch-applied +$(BUILDDIR)/pcre2-$(PCRE_VER)/build-configured: $(SRCCACHE)/pcre2-$(PCRE_VER)/source-extracted mkdir -p $(dir $@) cd $(dir $@) && \ - $(dir $<)/configure $(CONFIGURE_COMMON) --enable-jit --includedir=$(build_includedir) CFLAGS="$(CFLAGS) $(PCRE_CFLAGS) -g -O0" LDFLAGS="$(LDFLAGS) $(PCRE_LDFLAGS)" + $(dir $<)/configure $(CONFIGURE_COMMON) $(PCRE_JIT) --includedir=$(build_includedir) CFLAGS="$(CFLAGS) $(PCRE_CFLAGS) -g -O0" LDFLAGS="$(LDFLAGS) $(PCRE_LDFLAGS)" echo 1 > $@ $(BUILDDIR)/pcre2-$(PCRE_VER)/build-compiled: $(BUILDDIR)/pcre2-$(PCRE_VER)/build-configured @@ -46,15 +45,15 @@ endif $(eval $(call staged-install, \ pcre,pcre2-$$(PCRE_VER), \ MAKE_INSTALL,$$(LIBTOOL_CCLD),, \ - rm $$(build_shlibdir)/libpcre2-posix.* && \ + rm -f $$(build_shlibdir)/libpcre2-posix.* && \ $$(INSTALL_NAME_CMD)libpcre2-8.$$(SHLIB_EXT) $$(build_shlibdir)/libpcre2-8.$$(SHLIB_EXT))) clean-pcre: - -rm $(BUILDDIR)/pcre2-$(PCRE_VER)/build-configured $(BUILDDIR)/pcre2-$(PCRE_VER)/build-compiled + -rm -f $(BUILDDIR)/pcre2-$(PCRE_VER)/build-configured $(BUILDDIR)/pcre2-$(PCRE_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/pcre2-$(PCRE_VER) clean distclean-pcre: - -rm -rf $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2 $(SRCCACHE)/pcre2-$(PCRE_VER) $(BUILDDIR)/pcre2-$(PCRE_VER) + rm -rf $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2 $(SRCCACHE)/pcre2-$(PCRE_VER) $(BUILDDIR)/pcre2-$(PCRE_VER) get-pcre: $(SRCCACHE)/pcre2-$(PCRE_VER).tar.bz2 diff --git a/deps/pcre.version b/deps/pcre.version new file mode 100644 index 0000000000000..ce27921435e1d --- /dev/null +++ b/deps/pcre.version @@ -0,0 +1,5 @@ +## jll artifact +PCRE_JLL_NAME := PCRE2 + +## source build +PCRE_VER := 10.42 diff --git a/deps/tools/bb-install.mk b/deps/tools/bb-install.mk index 781d66f1c5dda..ee7f833a8ac2b 100644 --- a/deps/tools/bb-install.mk +++ b/deps/tools/bb-install.mk @@ -5,6 +5,7 @@ # 4 cxx11) # signifies a cxx11 ABI dependency define bb-install +include $$(SRCDIR)/$1.version TRIPLET_VAR := BB_TRIPLET ifeq ($(3),true) TRIPLET_VAR := $$(TRIPLET_VAR)_LIBGFORTRAN @@ -26,10 +27,13 @@ $(2)_JLL_VER ?= $$(shell [ -f $$($(2)_STDLIB_PATH)/Project.toml ] && grep "^vers # Allow things to override which JLL we pull from, e.g. libLLVM_jll vs. libLLVM_assert_jll $(2)_JLL_DOWNLOAD_NAME ?= $$($(2)_JLL_NAME) +# Allow things to provide platform tags +$(2)_JLL_TAGS ?= + $(2)_BB_TRIPLET := $$($$(TRIPLET_VAR)) $(2)_JLL_VER_NOPLUS := $$(firstword $$(subst +,$(SPACE),$$($(2)_JLL_VER))) -$(2)_JLL_BASENAME := $$($(2)_JLL_DOWNLOAD_NAME).v$$($(2)_JLL_VER).$$($(2)_BB_TRIPLET).tar.gz -$(2)_BB_URL := https://github.com/JuliaBinaryWrappers/$$($(2)_JLL_DOWNLOAD_NAME)_jll.jl/releases/download/$$($(2)_JLL_DOWNLOAD_NAME)-v$$($(2)_JLL_VER)/$$($(2)_JLL_DOWNLOAD_NAME).v$$($(2)_JLL_VER_NOPLUS).$$($(2)_BB_TRIPLET).tar.gz +$(2)_JLL_BASENAME := $$($(2)_JLL_DOWNLOAD_NAME).v$$($(2)_JLL_VER).$$($(2)_BB_TRIPLET)$$($(2)_JLL_TAGS).tar.gz +$(2)_BB_URL := https://github.com/JuliaBinaryWrappers/$$($(2)_JLL_DOWNLOAD_NAME)_jll.jl/releases/download/$$($(2)_JLL_DOWNLOAD_NAME)-v$$($(2)_JLL_VER)/$$($(2)_JLL_DOWNLOAD_NAME).v$$($(2)_JLL_VER_NOPLUS).$$($(2)_BB_TRIPLET)$$($(2)_JLL_TAGS).tar.gz $$(SRCCACHE)/$$($(2)_JLL_BASENAME): | $$(SRCCACHE) $$(JLDOWNLOAD) $$@ $$($(2)_BB_URL) @@ -79,5 +83,5 @@ endef define bb-uninstaller uninstall-$(strip $1): -cd $$(build_prefix) && rm -fv -- $$$$($$(TAR) -tzf $$(SRCCACHE)/$2.tar.gz | grep -v '/$$$$') - -rm $$(build_prefix)/manifest/$(strip $1) + -rm -f $$(build_prefix)/manifest/$(strip $1) endef diff --git a/deps/tools/common.mk b/deps/tools/common.mk index 642528376d457..c19886114c14e 100644 --- a/deps/tools/common.mk +++ b/deps/tools/common.mk @@ -11,15 +11,14 @@ endif ifeq ($(OS),WINNT) CONFIGURE_COMMON += LDFLAGS="$(LDFLAGS) -Wl,--stack,8388608" else -CONFIGURE_COMMON += LDFLAGS="$(LDFLAGS) $(RPATH_ESCAPED_ORIGIN)" +CONFIGURE_COMMON += LDFLAGS="$(LDFLAGS) $(RPATH_ESCAPED_ORIGIN) $(SANITIZE_LDFLAGS)" endif -CONFIGURE_COMMON += F77="$(FC)" CC="$(CC)" CXX="$(CXX)" LD="$(LD)" +CONFIGURE_COMMON += F77="$(FC)" CC="$(CC) $(SANITIZE_OPTS)" CXX="$(CXX) $(SANITIZE_OPTS)" LD="$(LD)" CMAKE_CC_ARG := $(CC_ARG) CMAKE_CXX_ARG := $(CXX_ARG) CMAKE_COMMON := -DCMAKE_INSTALL_PREFIX:PATH=$(build_prefix) -DCMAKE_PREFIX_PATH=$(build_prefix) -CMAKE_COMMON += -DCMAKE_INSTALL_LIBDIR=$(build_libdir) -DCMAKE_INSTALL_BINDIR=$(build_bindir) CMAKE_COMMON += -DLIB_INSTALL_DIR=$(build_shlibdir) ifeq ($(OS), Darwin) CMAKE_COMMON += -DCMAKE_MACOSX_RPATH=1 @@ -31,11 +30,11 @@ endif # The call to which here is to work around https://cmake.org/Bug/view.php?id=14366 CMAKE_COMMON += -DCMAKE_C_COMPILER="$$(which $(CC_BASE))" ifneq ($(strip $(CMAKE_CC_ARG)),) -CMAKE_COMMON += -DCMAKE_C_COMPILER_ARG1="$(CMAKE_CC_ARG)" +CMAKE_COMMON += -DCMAKE_C_COMPILER_ARG1="$(CMAKE_CC_ARG) $(SANITIZE_OPTS)" endif CMAKE_COMMON += -DCMAKE_CXX_COMPILER="$(CXX_BASE)" ifneq ($(strip $(CMAKE_CXX_ARG)),) -CMAKE_COMMON += -DCMAKE_CXX_COMPILER_ARG1="$(CMAKE_CXX_ARG)" +CMAKE_COMMON += -DCMAKE_CXX_COMPILER_ARG1="$(CMAKE_CXX_ARG) $(SANITIZE_OPTS)" endif CMAKE_COMMON += -DCMAKE_LINKER="$$(which $(LD))" -DCMAKE_AR="$$(which $(AR))" -DCMAKE_RANLIB="$$(which $(RANLIB))" @@ -108,8 +107,8 @@ endif DIRS := $(sort $(build_bindir) $(build_depsbindir) $(build_libdir) $(build_includedir) $(build_sysconfdir) $(build_datarootdir) $(build_staging) $(build_prefix)/manifest) $(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) - $(build_prefix): | $(DIRS) + $(eval $(call dir_target,$(SRCCACHE))) @@ -142,45 +141,46 @@ define BINFILE_INSTALL endef define staged-install -stage-$(strip $1): $$(build_staging)/$2.tgz +stage-$(strip $1): $$(build_staging)/$2.tar install-$(strip $1): $$(build_prefix)/manifest/$(strip $1) -ifeq (exists, $$(shell [ -e $$(build_staging)/$2.tgz ] && echo exists )) +ifeq (exists, $$(shell [ -e $$(build_staging)/$2.tar ] && echo exists )) # clean depends on uninstall only if the staged file exists distclean-$(strip $1) clean-$(strip $1): uninstall-$(strip $1) else # uninstall depends on staging only if the staged file doesn't exist # otherwise, uninstall doesn't actually want the file to be updated first -uninstall-$(strip $1): | $$(build_staging)/$2.tgz +uninstall-$(strip $1): | $$(build_staging)/$2.tar endif reinstall-$(strip $1): +$$(MAKE) uninstall-$(strip $1) - -rm $$(build_staging)/$2.tgz + -rm -f $$(build_staging)/$2.tar +$$(MAKE) stage-$(strip $1) +$$(MAKE) install-$(strip $1) -$$(build_staging)/$2.tgz: $$(BUILDDIR)/$2/build-compiled +$$(build_staging)/$2.tar: $$(BUILDDIR)/$2/build-compiled rm -rf $$(build_staging)/$2 mkdir -p $$(build_staging)/$2$$(build_prefix) $(call $3,$$(BUILDDIR)/$2,$$(build_staging)/$2,$4) - cd $$(build_staging)/$2$$(build_prefix) && $$(TAR) -czf $$@.tmp . + cd $$(build_staging)/$2$$(build_prefix) && $$(TAR) -cf $$@.tmp . rm -rf $$(build_staging)/$2 mv $$@.tmp $$@ UNINSTALL_$(strip $1) := $2 staged-uninstaller -$$(build_prefix)/manifest/$(strip $1): $$(build_staging)/$2.tgz | $(build_prefix)/manifest +$$(build_prefix)/manifest/$(strip $1): $$(build_staging)/$2.tar | $(build_prefix)/manifest -+[ ! -e $$@ ] || $$(MAKE) uninstall-$(strip $1) $(UNTAR) $$< -C $$(build_prefix) $6 echo '$$(UNINSTALL_$(strip $1))' > $$@ +.PHONY: $(addsuffix -$(strip $1),stage install distclean uninstall reinstall) endef define staged-uninstaller uninstall-$(strip $1): - -cd $$(build_prefix) && rm -fv -- $$$$($$(TAR) -tzf $$(build_staging)/$2.tgz | grep -v '/$$$$') - -rm $$(build_prefix)/manifest/$(strip $1) + -cd $$(build_prefix) && rm -fv -- $$$$($$(TAR) -tf $$(build_staging)/$2.tar | grep -v '/$$$$') + -rm -f $$(build_prefix)/manifest/$(strip $1) endef @@ -193,14 +193,18 @@ endef define symlink_install # (target-name, rel-from, abs-to) clean-$1: uninstall-$1 install-$1: $$(build_prefix)/manifest/$1 -reinstall-$1: install-$1 +reinstall-$1: + +$$(MAKE) uninstall-$1 + +$$(MAKE) stage-$1 + +$$(MAKE) install-$1 +.PHONY: $(addsuffix -$1,clean install reinstall) UNINSTALL_$(strip $1) := $2 symlink-uninstaller $3 -$$(build_prefix)/manifest/$1: $$(BUILDDIR)/$2/build-compiled | $3 $$(build_prefix)/manifest +$$(build_prefix)/manifest/$1: $$(BUILDDIR)/$2/build-compiled | $$(abspath $$(dir $3/$1)) $$(abspath $$(dir $$(build_prefix)/manifest/$1)) -+[ ! \( -e $3/$1 -o -h $3/$1 \) ] || $$(MAKE) uninstall-$1 ifeq ($$(BUILD_OS), WINNT) - cmd //C mklink //J $$(call mingw_to_dos,$3/$1,cd $3 &&) $$(call mingw_to_dos,$$(BUILDDIR)/$2,) + cmd //C mklink //J $$(call mingw_to_dos,$3/$1,cd $3/$(dir $1) &&) $$(call mingw_to_dos,$$(BUILDDIR)/$2,) else ifneq (,$$(findstring CYGWIN,$$(BUILD_OS))) cmd /C mklink /J $$(call cygpath_w,$3/$1) $$(call cygpath_w,$$(BUILDDIR)/$2) else ifdef JULIA_VAGRANT_BUILD @@ -214,20 +218,20 @@ endef define symlink-uninstaller uninstall-$1: ifeq ($$(BUILD_OS), WINNT) - -cmd //C rmdir $$(call mingw_to_dos,$3/$1,cd $3 &&) + -cmd //C rmdir $$(call mingw_to_dos,$3/$1,cd $3/$(dir $1) &&) else - -rm -r $3/$1 + rm -rf $3/$1 endif - -rm $$(build_prefix)/manifest/$1 + -rm -f $$(build_prefix)/manifest/$1 endef ifneq (bsdtar,$(findstring bsdtar,$(TAR_TEST))) #gnu tar -UNTAR = $(TAR) -xmzf +UNTAR = $(TAR) -xmf else #bsd tar -UNTAR = $(TAR) -xmUzf +UNTAR = $(TAR) -xmUf endif diff --git a/deps/tools/git-external.mk b/deps/tools/git-external.mk index 5dc1259a0f378..cf1610ac1bf5d 100644 --- a/deps/tools/git-external.mk +++ b/deps/tools/git-external.mk @@ -63,16 +63,17 @@ $$($2_SRC_FILE): | $$(SRCCACHE) $$(JLDOWNLOAD) $$@ $$(call $2_TAR_URL,$$($2_SHA1)) $5/$$($2_SRC_DIR)/source-extracted: $$($2_SRC_FILE) $$(JLCHECKSUM) $$< - -[ ! \( -e $$(dir $$@) -o -h $$(dir $$@) \) ] || rm -r $$(dir $$@) + -[ ! \( -e $$(dir $$@) -o -h $$(dir $$@) \) ] || rm -rf $$(dir $$@) mkdir -p $$(dir $$@) $(TAR) -C $$(dir $$@) --strip-components 1 -xf $$< echo 1 > $$@ -checksum-$(1): $$($2_SRC_FILE) +checksum-$1: $$($2_SRC_FILE) $$(JLCHECKSUM) $$< endif # DEPS_GIT $$(build_prefix)/manifest/$1: $$(SRCDIR)/$1.version # make the manifest stale if the version file is touched (causing re-install for compliant targets) distclean-$1: - -rm -rf $5/$$($2_SRC_DIR) $$($2_SRC_FILE) $$(BUILDDIR)/$$($2_SRC_DIR) + rm -rf $5/$$($2_SRC_DIR) $$($2_SRC_FILE) $$(BUILDDIR)/$$($2_SRC_DIR) +.PHONY: $(addsuffix -$1,checksum distclean) endef diff --git a/deps/tools/stdlib-external.mk b/deps/tools/stdlib-external.mk index 043a53341193a..0a99111605a45 100644 --- a/deps/tools/stdlib-external.mk +++ b/deps/tools/stdlib-external.mk @@ -16,12 +16,17 @@ $$(eval $$(call git-external,$1,$2,,,$$(BUILDDIR))) $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled: $$(BUILDDIR)/$$($2_SRC_DIR)/source-extracted @# no build steps echo 1 > $$@ -$$(eval $$(call symlink_install,$1,$$$$($2_SRC_DIR),$$$$(build_datarootdir)/julia/stdlib/$$$$(VERSDIR))) +$$(eval $$(call symlink_install,$$$$(VERSDIR)/$1,$$$$($2_SRC_DIR),$$$$(build_datarootdir)/julia/stdlib)) clean-$1: - -rm $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled + -rm -f $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled get-$1: $$($2_SRC_FILE) extract-$1: $$(BUILDDIR)/$$($2_SRC_DIR)/source-extracted configure-$1: extract-$1 compile-$1: $$(BUILDDIR)/$$($2_SRC_DIR)/build-compiled - +install-$1: install-$$(VERSDIR)/$1 +uninstall-$1: uninstall-$$(VERSDIR)/$1 +reinstall-$1: reinstall-$$(VERSDIR)/$1 +version-check-$1: version-check-$$(VERSDIR)/$1 +clean-$1: clean-$$(VERSDIR)/$1 +.PHONY: $(addsuffix -$1,get extract configure compile install uninstall reinstall clean) endef diff --git a/deps/tools/uninstallers.mk b/deps/tools/uninstallers.mk index 48387914643db..0051786ed1d0a 100644 --- a/deps/tools/uninstallers.mk +++ b/deps/tools/uninstallers.mk @@ -17,6 +17,7 @@ else uninstall-$1: @echo "skipping uninstall: $1 not installed" endif +.PHONY: uninstall-$1 endef $(foreach dep,$(DEP_LIBS_STAGED_ALL),$(eval $(call define-uninstaller,$(dep)))) diff --git a/deps/unwind.mk b/deps/unwind.mk index c794b94d5e636..76593df1e5ef0 100644 --- a/deps/unwind.mk +++ b/deps/unwind.mk @@ -1,13 +1,19 @@ ## UNWIND ## +include $(SRCDIR)/unwind.version +include $(SRCDIR)/llvmunwind.version ifneq ($(USE_BINARYBUILDER_LIBUNWIND),1) -LIBUNWIND_CFLAGS := -U_FORTIFY_SOURCE $(fPIC) -lz +LIBUNWIND_CFLAGS := -U_FORTIFY_SOURCE $(fPIC) -lz $(SANITIZE_OPTS) LIBUNWIND_CPPFLAGS := ifeq ($(USE_SYSTEM_ZLIB),0) $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-configured: | $(build_prefix)/manifest/zlib endif +ifeq ($(USE_SYSTEM_LLVM),0) +$(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured: | $(build_prefix)/manifest/llvm +endif + $(SRCCACHE)/libunwind-$(UNWIND_VER).tar.gz: | $(SRCCACHE) $(JLDOWNLOAD) $@ https://github.com/libunwind/libunwind/releases/download/v$(UNWIND_VER_TAG)/libunwind-$(UNWIND_VER).tar.gz @@ -36,10 +42,17 @@ $(SRCCACHE)/libunwind-$(UNWIND_VER)/libunwind-dwarf-table.patch-applied: $(SRCCA cd $(SRCCACHE)/libunwind-$(UNWIND_VER) && patch -p1 -f -u -l < $(SRCDIR)/patches/libunwind-dwarf-table.patch echo 1 > $@ -$(BUILDDIR)/libunwind-$(UNWIND_VER)/build-configured: $(SRCCACHE)/libunwind-$(UNWIND_VER)/source-extracted $(SRCCACHE)/libunwind-$(UNWIND_VER)/libunwind-dwarf-table.patch-applied +$(SRCCACHE)/libunwind-$(UNWIND_VER)/libunwind-non-empty-structs.patch-applied: $(SRCCACHE)/libunwind-$(UNWIND_VER)/libunwind-dwarf-table.patch-applied + cd $(SRCCACHE)/libunwind-$(UNWIND_VER) && patch -p1 -f -u -l < $(SRCDIR)/patches/libunwind-non-empty-structs.patch + echo 1 > $@ + +# note minidebuginfo requires liblzma, which we do not have a source build for +# (it will be enabled in BinaryBuilder-based downloads however) +# since https://github.com/JuliaPackaging/Yggdrasil/commit/0149e021be9badcb331007c62442a4f554f3003c +$(BUILDDIR)/libunwind-$(UNWIND_VER)/build-configured: $(SRCCACHE)/libunwind-$(UNWIND_VER)/source-extracted $(SRCCACHE)/libunwind-$(UNWIND_VER)/libunwind-non-empty-structs.patch-applied mkdir -p $(dir $@) cd $(dir $@) && \ - $(dir $<)/configure $(CONFIGURE_COMMON) CPPFLAGS="$(CPPFLAGS) $(LIBUNWIND_CPPFLAGS)" CFLAGS="$(CFLAGS) $(LIBUNWIND_CFLAGS)" --enable-shared --disable-minidebuginfo --disable-tests --enable-zlibdebuginfo + $(dir $<)/configure $(CONFIGURE_COMMON) CPPFLAGS="$(CPPFLAGS) $(LIBUNWIND_CPPFLAGS)" CFLAGS="$(CFLAGS) $(LIBUNWIND_CFLAGS)" --enable-shared --disable-minidebuginfo --disable-tests --enable-zlibdebuginfo --disable-conservative-checks echo 1 > $@ $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-compiled: $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-configured @@ -57,11 +70,11 @@ $(eval $(call staged-install, \ MAKE_INSTALL,,,)) clean-unwind: - -rm $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-configured $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-compiled + -rm -f $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-configured $(BUILDDIR)/libunwind-$(UNWIND_VER)/build-compiled -$(MAKE) -C $(BUILDDIR)/libunwind-$(UNWIND_VER) clean distclean-unwind: - -rm -rf $(SRCCACHE)/libunwind-$(UNWIND_VER).tar.gz \ + rm -rf $(SRCCACHE)/libunwind-$(UNWIND_VER).tar.gz \ $(SRCCACHE)/libunwind-$(UNWIND_VER) \ $(BUILDDIR)/libunwind-$(UNWIND_VER) @@ -95,10 +108,18 @@ $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-force-dwarf.patch-applie cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-force-dwarf.patch echo 1 > $@ +$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-revert-monorepo-requirement.patch-applied: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-force-dwarf.patch-applied + cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-revert-monorepo-requirement.patch + echo 1 > $@ + +$(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-freebsd-libgcc-api-compat.patch-applied: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-revert-monorepo-requirement.patch-applied + cd $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) && patch -p2 -f < $(SRCDIR)/patches/llvm-libunwind-freebsd-libgcc-api-compat.patch + echo 1 > $@ + checksum-llvmunwind: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz $(JLCHECKSUM) $< -$(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/source-extracted $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-force-dwarf.patch-applied +$(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured: $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/source-extracted $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/llvm-libunwind-freebsd-libgcc-api-compat.patch-applied mkdir -p $(dir $@) cd $(dir $@) && \ $(CMAKE) $(dir $<) $(LLVMUNWIND_OPTS) @@ -114,12 +135,12 @@ $(eval $(call staged-install, \ cp -fR $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER)/include/* $(build_includedir))) clean-llvmunwind: - -rm $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-compiled - -rm -r $(build_includedir)/mach-o/ $(build_includedir)/unwind.h $(build_includedir)/libunwind.h + -rm -f $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-configured $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER)/build-compiled + rm -rf $(build_includedir)/mach-o/ $(build_includedir)/unwind.h $(build_includedir)/libunwind.h -$(MAKE) -C $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER) clean distclean-llvmunwind: - -rm -rf $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz \ + rm -rf $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER).tar.xz \ $(SRCCACHE)/llvmunwind-$(LLVMUNWIND_VER) \ $(BUILDDIR)/llvmunwind-$(LLVMUNWIND_VER) diff --git a/deps/unwind.version b/deps/unwind.version new file mode 100644 index 0000000000000..e17b2e91c2e51 --- /dev/null +++ b/deps/unwind.version @@ -0,0 +1,6 @@ +## jll artifact +UNWIND_JLL_NAME := LibUnwind + +## source build +UNWIND_VER_TAG := 1.5 +UNWIND_VER := 1.5.0 diff --git a/deps/utf8proc.mk b/deps/utf8proc.mk index 52775a2dff5e6..cc526d40250c5 100644 --- a/deps/utf8proc.mk +++ b/deps/utf8proc.mk @@ -5,7 +5,7 @@ $(eval $(call git-external,utf8proc,UTF8PROC,,,$(BUILDDIR))) UTF8PROC_OBJ_LIB := $(build_libdir)/libutf8proc.a UTF8PROC_OBJ_HEADER := $(build_includedir)/utf8proc.h -UTF8PROC_CFLAGS := -O2 +UTF8PROC_CFLAGS := -O2 $(SANITIZE_OPTS) UTF8PROC_MFLAGS := CC="$(CC)" CFLAGS="$(CFLAGS) $(UTF8PROC_CFLAGS)" PICFLAG="$(fPIC)" AR="$(AR)" UTF8PROC_BUILDDIR := $(BUILDDIR)/$(UTF8PROC_SRC_DIR) @@ -29,7 +29,7 @@ $(eval $(call staged-install, \ UTF8PROC_INSTALL,,,)) clean-utf8proc: - -rm $(BUILDDIR)/$(UTF8PROC_SRC_DIR)/build-compiled + -rm -f $(BUILDDIR)/$(UTF8PROC_SRC_DIR)/build-compiled -$(MAKE) -C $(BUILDDIR)/$(UTF8PROC_SRC_DIR) clean get-utf8proc: $(UTF8PROC_SRC_FILE) diff --git a/deps/utf8proc.version b/deps/utf8proc.version index 246a38de00bae..659b995e8abaf 100644 --- a/deps/utf8proc.version +++ b/deps/utf8proc.version @@ -1,2 +1,2 @@ -UTF8PROC_BRANCH=v2.7.0 -UTF8PROC_SHA1=8ca6144c85c165987cb1c5d8395c7314e13d4cd7 +UTF8PROC_BRANCH=v2.8.0 +UTF8PROC_SHA1=1cb28a66ca79a0845e99433fd1056257456cef8b diff --git a/deps/zlib.mk b/deps/zlib.mk index b31ab425ccfc3..5548a0791f4d2 100644 --- a/deps/zlib.mk +++ b/deps/zlib.mk @@ -4,9 +4,13 @@ ZLIB_GIT_URL := https://github.com/madler/zlib.git ZLIB_TAR_URL = https://api.github.com/repos/madler/zlib/tarball/$1 $(eval $(call git-external,zlib,ZLIB,,,$(SRCCACHE))) +# use `-DUNIX=true` to ensure that it is always named `libz` +ZLIB_BUILD_OPTS := $(CMAKE_COMMON) -DCMAKE_BUILD_TYPE=Release -DUNIX=true +ZLIB_BUILD_OPTS += -DCMAKE_POSITION_INDEPENDENT_CODE=ON + $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-configured: $(SRCCACHE)/$(ZLIB_SRC_DIR)/source-extracted mkdir -p $(dir $@) - cd $(dir $@) && $(CMAKE) -DCMAKE_INSTALL_PREFIX=$(abspath $(build_prefix)) -DCMAKE_BUILD_TYPE=Release -DUNIX=true $(dir $<) + cd $(dir $@) && $(CMAKE) $(ZLIB_BUILD_OPTS) $(dir $<) echo 1 > $@ $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-compiled: $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-configured @@ -19,12 +23,12 @@ $(eval $(call staged-install, \ $(INSTALL_NAME_CMD)libz.$(SHLIB_EXT) $(build_shlibdir)/libz.$(SHLIB_EXT))) clean-zlib: - -rm $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-compiled $(build_libdir)/libz.a* $(build_libdir)/libz.so* $(build_includedir)/zlib.h $(build_includedir)/zconf.h - -$(MAKE) -C $(BUILDDIR)/$(ZLIB_SRC_DIR) distclean $(ZLIB_FLAGS) + -rm -f $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-configured $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-compiled + -$(MAKE) -C $(BUILDDIR)/$(ZLIB_SRC_DIR) clean get-zlib: $(ZLIB_SRC_FILE) extract-zlib: $(BUILDDIR)/$(ZLIB_SRC_DIR)/source-extracted -configure-zlib: extract-zlib +configure-zlib: $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-configured compile-zlib: $(BUILDDIR)/$(ZLIB_SRC_DIR)/build-compiled fastcheck-zlib: check-zlib check-zlib: compile-zlib diff --git a/deps/zlib.version b/deps/zlib.version index e363169315051..89a304c49b6dc 100644 --- a/deps/zlib.version +++ b/deps/zlib.version @@ -1,2 +1,8 @@ -ZLIB_BRANCH=v1.2.11 -ZLIB_SHA1=cacf7f1d4e3d44d871b605da3b647f07d718623f +# -*- makefile -*- +## jll artifact +ZLIB_JLL_NAME := Zlib + +## source build +ZLIB_VER := 1.2.13 +ZLIB_BRANCH=v1.2.13 +ZLIB_SHA1=04f42ceca40f73e2978b50e93806c2a18c1281fc diff --git a/doc/Makefile b/doc/Makefile index 2f8b3f18495d8..4469a40f74248 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -28,7 +28,7 @@ DOCUMENTER_OPTIONS := linkcheck=$(linkcheck) doctest=$(doctest) buildroot=$(call UNICODE_DATA_VERSION=13.0.0 $(SRCCACHE)/UnicodeData-$(UNICODE_DATA_VERSION).txt: @mkdir -p "$(SRCCACHE)" - $(JLDOWNLOAD) "$@" http://www.unicode.org/Public/$(UNICODE_DATA_VERSION)/ucd/UnicodeData.txt + $(JLDOWNLOAD) "$@" https://www.unicode.org/Public/$(UNICODE_DATA_VERSION)/ucd/UnicodeData.txt deps: $(SRCCACHE)/UnicodeData-$(UNICODE_DATA_VERSION).txt $(JLCHECKSUM) "$<" @@ -38,7 +38,7 @@ checksum-unicodedata: $(SRCCACHE)/UnicodeData-$(UNICODE_DATA_VERSION).txt $(JLCHECKSUM) "$<" clean: - -rm -rf _build/* deps/* docbuild.log UnicodeData.txt + rm -rf _build/* deps/* docbuild.log UnicodeData.txt cleanall: clean diff --git a/doc/Manifest.toml b/doc/Manifest.toml index f38f11a7778bb..cf50a1d41ddbd 100644 --- a/doc/Manifest.toml +++ b/doc/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.8.0-DEV.1335" +julia_version = "1.9.0-DEV" manifest_format = "2.0" project_hash = "e0c77beb18dc1f6cce661ebd60658c0c1a77390f" @@ -18,15 +18,15 @@ uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" [[deps.DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" +git-tree-sha1 = "5158c2b41018c5f7eb1470d558127ac274eca0c9" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" +version = "0.9.1" [[deps.Documenter]] deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "75c6cf9d99e0efc79b724f5566726ad3ad010a01" +git-tree-sha1 = "6030186b00a38e9d0434518627426570aac2ef95" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.27.12" +version = "0.27.23" [[deps.IOCapture]] deps = ["Logging", "Random"] @@ -40,9 +40,9 @@ uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37" +git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.2" +version = "0.21.3" [[deps.LibGit2]] deps = ["Base64", "NetworkOptions", "Printf", "SHA"] @@ -64,9 +64,9 @@ version = "1.2.0" [[deps.Parsers]] deps = ["Dates"] -git-tree-sha1 = "92f91ba9e5941fc781fecf5494ac1da87bdac775" +git-tree-sha1 = "3d5bf43e3e8b412656404ed9466f1dcbf7c50269" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.2.0" +version = "2.4.0" [[deps.Printf]] deps = ["Unicode"] diff --git a/doc/make.jl b/doc/make.jl index bb7ef83048178..3c69f4e6c47b5 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -46,18 +46,20 @@ end const render_pdf = "pdf" in ARGS # Generate a suitable markdown file from NEWS.md and put it in src -str = read(joinpath(@__DIR__, "..", "NEWS.md"), String) -splitted = split(str, "") -@assert length(splitted) == 2 -replaced_links = replace(splitted[1], r"\[\#([0-9]*?)\]" => s"[#\g<1>](https://github.com/JuliaLang/julia/issues/\g<1>)") -write( - joinpath(@__DIR__, "src", "NEWS.md"), - """ - ```@meta - EditURL = "https://github.com/JuliaLang/julia/blob/master/NEWS.md" - ``` - - """ * replaced_links) +function generate_markdown(basename) + str = read(joinpath(@__DIR__, "..", "$basename.md"), String) + splitted = split(str, "") + @assert length(splitted) == 2 + replaced_links = replace(splitted[1], r"\[\#([0-9]*?)\]" => s"[#\g<1>](https://github.com/JuliaLang/julia/issues/\g<1>)") + write( + joinpath(@__DIR__, "src", "$basename.md"), + """ + ```@meta + EditURL = "https://github.com/JuliaLang/julia/blob/master/$basename.md" + ``` + """ * replaced_links) +end +generate_markdown("NEWS") Manual = [ "manual/getting-started.md", @@ -98,7 +100,7 @@ Manual = [ "manual/faq.md", "manual/noteworthy-differences.md", "manual/unicode-input.md", - "manual/command-line-options.md", + "manual/command-line-interface.md", ] BaseDocs = [ @@ -140,6 +142,7 @@ DevDocs = [ "devdocs/subarrays.md", "devdocs/isbitsunionarrays.md", "devdocs/sysimg.md", + "devdocs/pkgimg.md", "devdocs/llvm.md", "devdocs/stdio.md", "devdocs/boundscheck.md", @@ -150,11 +153,13 @@ DevDocs = [ "devdocs/ssair.md", "devdocs/EscapeAnalysis.md", "devdocs/gc-sa.md", + "devdocs/gc.md", ], "Developing/debugging Julia's C code" => [ "devdocs/backtraces.md", "devdocs/debuggingtips.md", "devdocs/valgrind.md", + "devdocs/external_profilers.md", "devdocs/sanitizers.md", "devdocs/probes.md", ], @@ -175,8 +180,8 @@ const PAGES = [ "Manual" => ["index.md", Manual...], "Base" => BaseDocs, "Standard Library" => StdlibDocs, - "Developer Documentation" => DevDocs, - hide("NEWS.md"), + # Add "Release Notes" to devdocs + "Developer Documentation" => [DevDocs..., hide("NEWS.md")], ] else const PAGES = [ @@ -236,12 +241,6 @@ DocMeta.setdocmeta!( maybe_revise(:(using SparseArrays, LinearAlgebra)); recursive=true, warn=false, ) -DocMeta.setdocmeta!( - SuiteSparse, - :DocTestSetup, - maybe_revise(:(using SparseArrays, LinearAlgebra, SuiteSparse)); - recursive=true, warn=false, -) DocMeta.setdocmeta!( UUIDs, :DocTestSetup, @@ -292,6 +291,7 @@ else analytics = "UA-28835595-6", collapselevel = 1, sidebar_sitename = false, + ansicolor = true, ) end @@ -339,8 +339,14 @@ end # Define our own DeployConfig struct BuildBotConfig <: Documenter.DeployConfig end +Documenter.authentication_method(::BuildBotConfig) = Documenter.HTTPS +Documenter.authenticated_repo_url(::BuildBotConfig) = "https://github.com/JuliaLang/docs.julialang.org.git" function Documenter.deploy_folder(::BuildBotConfig; devurl, repo, branch, kwargs...) - haskey(ENV, "DOCUMENTER_KEY") || return Documenter.DeployDecision(; all_ok=false) + if !haskey(ENV, "DOCUMENTER_KEY") + @info "Unable to deploy the documentation: DOCUMENTER_KEY missing" + return Documenter.DeployDecision(; all_ok=false) + end + release = match(r"^release-([0-9]+\.[0-9]+)$", Base.GIT_VERSION_INFO.branch) if Base.GIT_VERSION_INFO.tagged_commit # Strip extra pre-release info (1.5.0-rc2.0 -> 1.5.0-rc2) ver = VersionNumber(VERSION.major, VERSION.minor, VERSION.patch, @@ -349,7 +355,16 @@ function Documenter.deploy_folder(::BuildBotConfig; devurl, repo, branch, kwargs return Documenter.DeployDecision(; all_ok=true, repo, branch, subfolder) elseif Base.GIT_VERSION_INFO.branch == "master" return Documenter.DeployDecision(; all_ok=true, repo, branch, subfolder=devurl) + elseif !isnothing(release) + # If this is a non-tag build from a release-* branch, we deploy them as dev docs into the + # appropriate vX.Y-dev subdirectory. + return Documenter.DeployDecision(; all_ok=true, repo, branch, subfolder="v$(release[1])-dev") end + @info """ + Unable to deploy the documentation: invalid GIT_VERSION_INFO + GIT_VERSION_INFO.tagged_commit: $(Base.GIT_VERSION_INFO.tagged_commit) + GIT_VERSION_INFO.branch: $(Base.GIT_VERSION_INFO.branch) + """ return Documenter.DeployDecision(; all_ok=false) end @@ -377,11 +392,16 @@ function Documenter.Writers.HTMLWriter.expand_versions(dir::String, v::Versions) return Documenter.Writers.HTMLWriter.expand_versions(dir, v.versions) end -deploydocs( - repo = "github.com/JuliaLang/docs.julialang.org.git", - deploy_config = BuildBotConfig(), - target = joinpath(buildroot, "doc", "_build", "html", "en"), - dirname = "en", - devurl = devurl, - versions = Versions(["v#.#", devurl => devurl]), -) +if "deploy" in ARGS + deploydocs( + repo = "github.com/JuliaLang/docs.julialang.org.git", + deploy_config = BuildBotConfig(), + target = joinpath(buildroot, "doc", "_build", "html", "en"), + dirname = "en", + devurl = devurl, + versions = Versions(["v#.#", devurl => devurl]), + archive = get(ENV, "DOCUMENTER_ARCHIVE", nothing), + ) +else + @info "Skipping deployment ('deploy' not passed)" +end diff --git a/doc/man/julia.1 b/doc/man/julia.1 index 0b008619014e1..fa9f641b1e76f 100644 --- a/doc/man/julia.1 +++ b/doc/man/julia.1 @@ -21,16 +21,18 @@ .\" - diagnostics .\" - notes -.TH JULIA 1 2013-12-10 Julia "Julia Programmers' Reference Guide" +.TH JULIA 1 2022-02-17 JULIA .\" from the front page of https://julialang.org/ .SH NAME julia - a high-level, high-performance dynamic programming language for technical computing .SH SYNOPSIS -julia [option] [program] [args..] +\fBjulia\fR [OPTIONS...] \fB--\fR [PROGRAMMFILE] [ARGS...] + +If a Julia source file is given as a \fIPROGRAMFILE\fP (optionally followed by +arguments in \fIARGS\fP) Julia will execute the program and exit. -.\" Taken almost verbatim from the front page of https://julialang.org/ .SH DESCRIPTION Julia is a high-level, high-performance dynamic programming language for technical computing, with syntax that is familiar to users @@ -49,10 +51,6 @@ For a more in-depth discussion of the rationale and advantages of Julia over other systems, please see the online manual: https://docs.julialang.org -If a Julia source file is given as a \fIprogram\fP (optionally followed by - arguments in \fIargs\fP) Julia will execute the program and exit. - -.\" This section was taken nearly verbatim from the output of `julia --help` .SH "COMMAND-LINE OPTIONS" .TP @@ -63,6 +61,10 @@ Display version information -h, --help Print help message +.TP +--help-hidden +Print uncommon options not shown by `-h` + .TP --project[=/@.] Set as the home project/environment. The default @. option will search @@ -73,22 +75,27 @@ found. -J, --sysimage Start up with the given system image file -.TP ---sysimage-native-code={yes|no} -Use precompiled code from system image if available - .TP -H, --home Set location of julia executable .TP ---startup-file={yes|no} -Load ~/.julia/config/startup.jl +--startup-file={yes*|no} +Load `JULIA_DEPOT_PATH/config/startup.jl`; if `JULIA_DEPOT_PATH` +environment variable is unset, load `~/.julia/config/startup.jl` .TP ---handle-signals={yes|no} +--handle-signals={yes*|no} Enable or disable Julia's default signal handlers +.TP +--sysimage-native-code={yes*|no} +Use native code from system image if available + +.TP +--compiled-modules={yes*|no} +Enable or disable incremental precompilation of modules + .TP -e, --eval Evaluate @@ -103,11 +110,23 @@ Load immediately on all processors .TP -t, --threads -Enable n 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. .TP --p, --procs -Run n local processes +--gcthreads +Enable n GC threads; If unspecified is set to half of the +compute worker threads. + +.TP +-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) .TP --machine-file @@ -115,68 +134,101 @@ Run processes on hosts listed in .TP -i -Interactive mode; REPL runs and isinteractive() is true +Interactive mode; REPL runs and `isinteractive()` is true .TP ---banner={yes|no|auto} +-q, --quiet +Quiet startup: no banner, suppress REPL warnings + +.TP +--banner={yes|no|auto*} Enable or disable startup banner .TP ---color={yes|no|auto} +--color={yes|no|auto*} Enable or disable color text .TP ---history-file={yes|no} +--history-file={yes*|no} Load or save history .TP ---compile={yes|no|all|min} -Enable or disable compiler, or request exhaustive or minimal compilation +--depwarn={yes|no*|error} +Enable or disable syntax and method deprecation warnings (`error` turns warnings into errors) .TP --C, --cpu-target= -Limit usage of cpu features up to +--warn-overwrite={yes|no*} +Enable or disable method overwrite warnings .TP --O, --optimize -Run time-intensive code optimizations +--warn-scope={yes*|no} +Enable or disable warning for ambiguous top-level scope .TP --O , --optimize= -Set the optimization level to +-C, --cpu-target= +Limit usage of CPU features up to ; set to `help` to see the available options .TP ---min-optlevel= -Set the minimum optimization level to , overriding per-module settings +-O, --optimize={0,1,2*,3} +Set the optimization level (level 3 if `-O` is used without a level) .TP --g -Enable generation of full debug info +--min-optlevel={0*,1,2,3} +Set a lower bound on the optimization level .TP --g -Set the level of debug info generation to +-g {0,1*,2} +Set the level of debug info generation (level 2 if `-g` is used without a level) .TP ---inline={yes|no} -Control whether inlining is permitted (overrides functions declared as @inline) +--inline={yes*|no} +Control whether inlining is permitted, including overriding @inline declarations .TP ---check-bounds={yes|no|auto} +--check-bounds={yes|no|auto*} Emit bounds checks always, never, or respect @inbounds declarations .TP --math-mode={ieee|user} -Always use IEEE semantics for math (ignoring declarations), -or adhere to declarations in source code +Disallow or enable unsafe floating point optimizations (overrides @fastmath declaration) .TP ---depwarn={yes|no|error} -Enable or disable syntax and method deprecation warnings ('error' turns warnings into errors) +--code-coverage[={none*|user|all}] +Count executions of source lines (omitting setting is equivalent to `user`) .TP ---warn-overwrite={yes|no} -Enable or disable method overwrite warnings +--code-coverage=@ +Count executions of source lines 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. + +.TP + --code-coverage=tracefile.info + Append coverage information to the LCOV tracefile (filename supports format tokens) + +.TP +--track-allocation[={none*|user|all}] +Count bytes allocated by each source line (omitting setting is equivalent to `user`) + +.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. + +.TP +--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 +fallbacks to the latest compatible BugReporting.jl if not. For more information, see +--bug-report=help. + +.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) + +.TP +--compile={yes*|no|all|min} +Enable or disable JIT compiler, or request exhaustive or minimal compilation .TP --output-o @@ -186,36 +238,45 @@ Generate an object file (including system image data) --output-ji Generate a system image data file (.ji) +.TP +--strip-metadata +Remove docstrings and source location info from system image + +.TP +--strip-ir +Remove IR (intermediate representation) of compiled functions + +.TP +--output-unopt-bc +Generate unoptimized LLVM bitcode (.bc) + .TP --output-bc Generate LLVM bitcode (.bc) .TP ---output-incremental={yes|no} -Generate an incremental output file (rather than complete) +--output-asm +Generate an assembly file (.s) .TP ---code-coverage={none|user|all}, --code-coverage -Count executions of source lines (omitting setting is equivalent to 'user') +--output-incremental={yes|no*} +Generate an incremental output file (rather than complete) .TP ---track-allocation={none|user|all}, --track-allocation -Count bytes allocated by each source line +--trace-compile={stderr,name} +Print precompile statements for methods compiled during execution or save to a path -.SH FILES -.I ~/.julia/config/startup.jl -.RS -Per user startup file. -.RE +.TP +-image-codegen +Force generate code in imaging mode -.I /etc/julia/startup.jl -.RS -System-wide startup file. -.RE +.SH FILES AND ENVIRONMENT +See https://docs.julialang.org/en/v1/manual/environment-variables/ .SH BUGS Please report any bugs using the GitHub issue tracker: https://github.com/julialang/julia/issues?state=open + .SH AUTHORS Contributors: https://github.com/JuliaLang/julia/graphs/contributors diff --git a/doc/src/assets/cover-splash.tex b/doc/src/assets/cover-splash.tex new file mode 100644 index 0000000000000..10409a14d5742 --- /dev/null +++ b/doc/src/assets/cover-splash.tex @@ -0,0 +1,353 @@ +%% Direct translation of the backsplash image in "JuliaLang/www.julialang.org" +%% And cropping of the image +%% https://github.com/JuliaLang/www.julialang.org/blob/main/_assets/infra/backsplash-min-0.5.svg + + +\newcommand{\splashScaleFactor}{0.6} +\newcommand{\whiteMaskTransparency}{0.5} +\newcommand{\triangleTransparency}{0.6} +\begin{tikzpicture}[x=1,y=1,yscale=-\splashScaleFactor,xscale=\splashScaleFactor,draw=white] +% Clipping +\clip (510,15) rectangle (1570,350); +% Cropping +\useasboundingbox(510,0) rectangle (1570,350.0); +% gary background +% \draw[fill=splash_gary,opacity=0] (510.0,0.0)--++(1057.5,0.0)--++(0.0,350.0)--++(-1057.5,0.0)--cycle; + +%% Draw triangles +\draw[fill=julia_red,opacity=\triangleTransparency] + (991.9,11.4)--++(51.5,19.7)--++(-56.2,25.3) + ++(56.8,56.9)--++(-47.4,-27.5)--++(48.4,-52.2) + (990.9,9.0)--++(-40.6,1.4)--++(125.4,-21.0) + (969.5,111.5)--++(35.3,20.8)--++(-45.2,3.6) + (952.4,205.8)--++(14.0,55.9)--++(-44.7,-29.8) + ++(33.8,-160.3)--++(12.1,37.4)--++(-63.5,-1.9) + (946.8,335.7)--++(-6.5,-33.3)--++(58.2,17.6) + (920.0,235.8)--++(17.8,64.6)--++(-39.3,15.0) + ++(-0.7,3.0)--++(45.6,18.6)--++(-51.2,22.3) + (910.2,17.7)--++(43.6,51.2)--++(-72.1,-0.9) + (901.8,109.5)--++(53.8,27.4)--++(-62.3,24.5) + (885.4,204.6)--++(32.3,26.8)--++(-47.1,9.2) + (879.5,70.3)--++(19.3,35.8)--++(-54.1,-22.0) + (870.0,317.0)--++(0.0,-69.4)--++(25.6,68.5) + (868.7,319.7)--++(20.5,40.0)--++(-53.1,-8.4) + (867.4,190.9)--++(-12.9,-43.1)--++(35.8,15.6) + (866.0,193.8)--++(1.9,45.8)--++(-63.6,-57.1) + (815.2,79.7)--++(-16.2,-17.1)--++(65.6,-10.8) + ++(3.4,-40.0)--++(0.9,35.9)--++(-50.7,-41.5) + (801.8,362.7)--++(-7.3,-40.3)--++(37.5,29.3) + ++(-16.2,-267.4)--++(35.8,61.3)--++(-41.5,22.6) + (796.4,60.5)--++(-49.1,-26.4)--++(66.1,-27.4) + (765.0,352.3)--++(-0.9,-61.7)--++(27.6,29.5) + (758.0,131.6)--++(40.8,48.4)--++(-80.7,-0.9) + ++(66.2,78.4)--++(-38.6,-22.6)--++(53.7,-50.9) + (741.8,98.9)--++(14.7,30.4)--++(-45.1,7.4) + (720.7,321.9)--++(38.2,30.3)--++(-40.8,-13.9) + (716.2,182.1)--++(19.5,30.7)--++(-50.2,38.1) + (708.3,140.2)--++(6.5,39.0)--++(-52.0,9.3) + ++(4.7,130.8)--++(46.6,19.6)--++(-64.3,20.5) + ++(33.5,-102.7)--++(34.9,62.2)--++(-50.0,-1.9) + (697.1,1.3)--++(3.7,57.5)--++(-37.1,-47.3) + ++(20.3,86.2)--++(-30.6,-59.4)--++(47.4,24.1) + (660.1,193.2)--++(21.3,59.2)--++(-35.1,-12.0) + (650.6,34.4)--++(-49.2,-41.9)--++(59.2,19.1) + (641.9,103.0)--++(40.0,-1.9)--++(-45.6,59.5) + (640.4,99.6)--++(-46.4,-58.7)--++(55.9,-3.8) + (611.6,316.3)--++(34.5,43.8)--++(-43.8,2.8) + (600.4,137.3)--++(-7.1,-17.7)--++(43.4,-15.0) + (596.6,210.6)--++(44.0,28.8)--++(-49.4,-7.2) + (591.0,41.9)--++(0.0,71.1)--++(-19.8,-57.6) + (551.4,303.2)--++(-11.2,-49.5)--++(47.7,-18.7) + (541.9,104.3)--++(47.4,14.9)--++(-51.2,25.1) + (540.0,214.9)--++(-1.9,-66.3)--++(55.9,59.6) + ++(-2.7,-170.7)--++(-44.8,-53.2)--++(49.5,6.5) + (512.0,192.8)--++(-28.2,-58.3)--++(51.7,12.2) + ++(-23.0,50.0)--++(24.9,19.4)--++(-44.3,38.8) + (503.7,105.9)--++(-15.7,-57.3)--++(50.9,53.6) + (487.0,42.5)--(487.0,6.7)--++(53.2,-21.1) + (1602.3,36.6)--++(12.3,58.5)--++(-57.5,-32.1) + (1545.2,19.0)--++(-49.1,-29.1)--++(117.4,19.1) + (1538.8,89.3)--++(67.7,71.6)--++(-73.5,-3.8) + (1537.9,366.1)--++(-2.9,-80.5)--++(70.9,33.6) + (1532.0,159.8)--++(18.4,27.6)--++(-47.0,12.9) + ++(34.3,-114.6)--++(-48.3,-30.1)--++(63.8,8.2) + (1501.7,203.7)--++(63.0,41.4)--++(-71.5,0.9) + (1499.9,137.4)--++(-43.1,-47.7)--++(59.6,25.7) + (1493.0,315.9)--++(0.0,-66.7)--++(39.5,34.8) + (1491.7,319.6)--++(15.7,27.7)--++(-60.1,12.0) + (1469.9,265.9)--++(20.1,49.8)--++(-21.0,-12.2) + (1445.7,193.2)--++(21.8,66.4)--++(-57.8,-16.1) + ++(44.0,-158.6)--++(-7.4,-46.2)--++(37.0,15.7) + (1433.7,139.9)--++(-33.8,-45.1)--++(51.7,-6.6) + ++(-7.4,-52.1)--++(-30.1,1.8)--++(38.3,-51.9) + (1418.7,179.9)--++(-19.1,-27.3)--++(32.7,-9.1) + (1407.3,246.3)--++(7.5,63.2)--++(-49.1,-17.0) + (1384.6,-17.4)--++(24.9,53.5)--++(-49.9,-34.2) + (1367.2,191.1)--++(-28.2,-58.4)--++(57.4,19.8) + (1364.4,296.2)--++(23.2,49.3)--++(-35.3,17.7) + (1301.7,135.9)--++(-4.3,-16.4)--++(35.3,11.2) + (1297.0,116.5)--++(-1.9,-78.9)--++(52.3,57.0) + ++(-49.6,-80.8)--++(48.0,38.2)--++(-50.7,-17.8) + (1296.9,251.9)--++(-8.5,-57.6)--++(69.0,52.0) + (1295.1,254.1)--++(7.4,59.0)--++(-37.8,-54.3) + ++(23.2,74.8)--++(-32.1,-28.5)--++(46.3,12.5) + (1288.0,336.1)--++(55.2,26.3)--++(-77.0,-15.8) + (1258.6,34.5)--++(-14.0,-47.5)--++(50.3,25.2) + ++(-8.4,177.8)--++(-32.7,-39.3)--++(45.8,-12.2) + (1255.1,194.9)--++(6.6,60.1)--++(-60.1,-69.5) + ++(49.7,116.1)--++(-66.9,-32.0)--++(77.2,-10.4) + ++(-10.2,45.7)--++(10.3,41.3)--++(-66.6,13.1) + (1249.1,118.0)--++(1.8,31.1)--++(-43.9,1.8) + (1187.6,-8.5)--++(17.0,65.3)--++(-53.0,-5.7) + (1182.3,316.6)--++(8.3,42.4)--++(-40.6,-24.0) + (1180.3,267.6)--++(-31.7,-12.1)--++(48.5,-67.1) + (1180.1,271.2)--++(1.8,42.2)--++(-31.2,-14.7) + (1150.7,162.1)--++(-7.6,-81.0)--++(57.2,7.6) + (1146.0,256.9)--++(1.9,39.9)--++(-56.7,-44.6) + ++(55.8,81.3)--++(-43.2,-14.7)--++(44.1,-18.4) + (1141.6,77.9)--++(-32.5,-38.8)--++(39.7,13.5) + ++(-40.4,87.8)--++(39.0,24.2)--++(-60.4,13.9) + (1108.9,-14.3)--++(21.7,26.3)--++(-23.5,22.6) + (1108.3,136.4)--++(-14.1,-53.4)--++(45.9,-1.9) + (1066.9,169.8)--++(16.8,10.6)--++(-19.4,35.3) + ++(-15.7,97.5)--++(-10.4,-69.9)--++(48.2,8.5) + (1066.5,166.5)--++(-19.7,-49.7)--++(59.1,22.5) + (1058.4,4.5)--++(43.9,31.1)--++(-54.9,-5.5) + (1048.8,317.1)--++(48.4,2.7)--++(-55.7,27.4) + (1037.4,240.4)--++(-15.5,-33.7)--++(39.1,13.6) + (1007.4,135.4)--++(11.4,68.6)--++(-63.8,-0.9) + (1005.9,249.3)--++(40.4,65.8)--++(-43.3,3.8); + +\draw[fill=julia_purple,opacity=\triangleTransparency] + (995.4,84.1)--++(-8.2,-25.5)--++(54.7,-24.6) + (969.5,108.2)--++(-11.9,-36.5)--++(35.6,14.6) + (967.9,265.8)--++(31.9,52.5)--++(-59.1,-17.8) + ++(28.0,-37.9)--++(-14.0,-55.9)--++(48.5,39.1) + (952.1,202.2)--++(-58.0,-39.0)--++(62.7,-24.7) + (891.2,161.6)--++(-36.3,-15.8)--++(44.7,-35.4) + ++(-19.6,-43.5)--++(-8.8,-16.7)--++(35.1,-29.8) + (884.3,201.4)--++(-15.7,-8.7)--++(21.8,-26.2) + (841.1,84.9)--++(9.9,55.8)--++(-34.2,-58.4) + ++(-7.5,89.0)--++(47.9,19.0)--++(-54.5,-9.9) + (815.2,4.0)--++(-37.7,-10.6)--++(69.6,14.2) + ++(-45.4,175.3)--++(65.2,58.5)--++(-80.6,16.3) + (784.3,261.5)--++(7.3,55.5)--++(-27.3,-29.1) + ++(-18.4,-252.3)--++(49.1,26.4)--++(-51.9,33.1) + (762.0,290.0)--++(0.9,62.9)--++(-41.3,-32.9) + (758.3,128.4)--++(-14.9,-30.7)--++(67.9,-14.9) + (744.5,233.2)--++(-6.3,-19.7)--++(57.2,-28.6) + (744.2,32.2)--++(-43.9,-31.7)--++(49.5,-12.1) + (709.1,136.3)--++(-23.3,-35.4)--++(54.0,-2.8) + (635.0,165.5)--++(23.1,24.0)--++(-59.1,17.6) + (611.3,311.1)--++(-19.9,-76.8)--++(52.2,7.6) + (591.5,120.4)--++(7.1,17.8)--++(-56.0,6.2) + (551.3,307.9)--++(47.1,54.6)--++(-64.0,-12.2) + (540.0,251.5)--++(0.0,-34.1)--++(47.1,15.7) + ++(-16.6,-179.6)--++(-23.7,-65.5)--++(43.7,51.9) + (538.7,256.1)--++(11.1,49.2)--++(-38.0,8.4) + ++(29.1,-212.2)--++(-52.2,-55.0)--++(79.7,9.5) + (1608.2,159.7)--++(-67.6,-71.4)--++(74.2,9.5) + (1567.8,247.7)--++(37.8,69.1)--++(-70.0,-33.1) + (1555.7,61.5)--++(-9.3,-40.1)--++(54.0,14.0) + (1518.1,113.1)--++(-30.4,-56.3)--++(48.9,30.4) + (1517.6,117.1)--++(12.6,38.6)--++(-28.8,-17.1) + ++(7.4,207.0)--++(-15.5,-27.4)--++(38.4,-31.1) + (1484.1,52.5)--++(-37.4,-15.9)--++(43.0,-45.8) + ++(-20.2,268.7)--++(-20.9,-63.7)--++(41.9,51.0) + (1446.6,189.1)--++(-11.3,-46.1)--++(62.1,-2.8) + (1443.9,358.7)--++(-26.3,-46.9)--++(48.8,-6.6) + (1419.8,183.3)--++(24.6,8.2)--++(-34.6,48.3) + (1412.5,41.1)--++(38.5,45.1)--++(-51.7,6.6) + (1367.7,195.4)--++(38.3,47.7)--++(-44.9,2.8) + ++(-2.4,-242.2)--++(50.9,34.8)--++(-57.4,15.1) + (1359.1,248.1)--++(3.7,43.1)--++(-63.7,-37.4) + ++(37.6,-120.9)--++(28.6,59.1)--++(-76.3,-1.0) + (1351.9,57.0)--++(43.4,36.0)--++(-45.2,0.9) + (1336.4,129.8)--++(-37.5,-11.9)--++(48.5,-21.0) + (1304.0,318.5)--++(42.3,43.2)--++(-56.7,-27.0) + (1293.0,36.0)--++(1.9,77.2)--++(-35.3,-76.3) + (1256.9,192.9)--++(-3.6,-39.8)--++(31.7,38.0) + (1252.9,148.1)--++(-1.8,-31.1)--++(42.0,1.8) + (1204.9,153.0)--++(43.5,-1.8)--++(-48.0,30.8) + (1201.2,86.2)--++(-47.4,-32.8)--++(51.1,5.5) + ++(1.4,-3.7)--++(-17.0,-65.3)--++(52.0,-3.8) + (1201.1,91.1)--++(1.9,60.1)--++(-50.7,12.2) + (1184.0,313.8)--++(-1.9,-43.2)--++(66.7,31.9) + (1155.9,186.0)--++(41.1,-0.9)--++(-48.6,67.3) + (1149.3,166.2)--++(4.4,17.7)--++(-62.0,-4.4) + (1132.3,10.8)--++(-22.0,-26.6)--++(73.4,5.5) + (1131.7,13.7)--++(16.5,36.6)--++(-40.3,-13.7) + (1101.8,317.6)--++(-12.3,-64.2)--++(57.6,45.3) + (1092.5,80.0)--++(-44.0,-47.8)--++(56.2,5.6) + (1092.4,83.6)--++(14.1,53.7)--++(-59.4,-22.6) + (1062.9,221.4)--++(22.7,28.1)--++(-46.3,-8.2) + (1057.8,2.0)--++(-50.7,5.3)--++(88.5,-20.4) + (1036.2,243.1)--++(10.1,68.2)--++(-39.6,-64.5) + ++(32.7,100.5)--++(-34.7,-26.5)--++(42.0,-3.7) + (1020.7,203.2)--++(-11.4,-68.2)--++(54.9,33.1) + (1008.6,131.6)--++(-12.0,-43.5)--++(46.3,26.8) + (1001.4,321.2)--++(14.9,40.1)--++(-67.1,-24.2); + +\draw[fill=julia_green,opacity=\triangleTransparency] + (994.5,87.8)--++(11.9,43.0)--++(-35.7,-21.1) + ++(-11.7,28.1)--++(46.9,-3.8)--++(-51.6,66.6) + ++(-8.2,137.3)--++(66.0,23.8)--++(-116.4,-1.8) + (985.4,59.3)--++(8.0,24.9)--++(-34.7,-14.2) + (985.2,55.9)--++(-47.7,-43.0)--++(52.4,-1.9) + (957.5,135.6)--++(-52.1,-26.5)--++(62.1,1.8) + (935.0,11.0)--++(-170.3,-23.1)--++(330.5,-3.7) + (934.3,13.1)--++(18.8,52.0)--++(-41.3,-48.4) + (901.4,106.5)--++(-19.7,-36.5)--++(71.2,0.9) + (896.9,313.7)--++(-26.5,-71.0)--++(48.3,-9.5) + (892.7,164.7)--++(56.9,38.3)--++(-63.5,-0.9) + (890.4,357.7)--++(-19.8,-38.8)--++(25.2,-0.9) + (870.9,47.9)--++(-0.9,-35.8)--++(37.6,4.6) + (869.8,51.8)--++(8.8,16.7)--++(-33.4,13.2) + ++(24.6,155.7)--++(-1.7,-42.6)--++(15.7,8.7) + (868.0,243.2)--++(0.0,72.9)--++(-80.5,-56.6) + ++(28.0,-252.8)--++(52.1,42.7)--++(-69.2,11.4) + (852.4,147.5)--++(13.1,43.8)--++(-54.1,-21.5) + (833.9,350.7)--++(-38.1,-29.8)--++(70.7,-1.9) + (800.9,179.4)--++(-35.9,-42.6)--++(42.6,33.4) + ++(-55.8,-181.9)--++(60.1,16.9)--++(-65.7,27.2) + (796.8,63.3)--++(16.2,17.1)--++(-65.8,14.4) + (763.3,285.9)--++(-17.4,-48.7)--++(37.7,22.0) + (736.5,214.7)--++(6.3,19.7)--++(-54.6,17.0) + (720.3,318.5)--++(-35.0,-62.5)--++(75.7,32.2) + (717.0,340.1)--++(41.8,14.2)--++(-103.1,5.3) + (716.6,177.7)--++(-6.5,-38.9)--++(45.3,-7.4) + (702.9,60.2)--++(-3.8,-58.2)--++(44.1,31.9) + (702.3,63.6)--++(37.1,32.5)--++(-53.9,2.8) + (683.9,101.7)--++(23.5,35.8)--++(-69.6,24.5) + ++(45.3,89.4)--++(-21.8,-60.5)--++(53.0,-9.5) + (666.9,314.1)--++(-20.4,-71.6)--++(35.3,12.1) + (662.2,12.9)--++(36.2,46.1)--++(-46.1,-23.5) + (651.5,39.2)--++(30.9,59.9)--++(-40.2,1.9) + (647.8,359.1)--++(-34.6,-43.9)--++(52.3,3.7) + (644.4,239.4)--++(-46.1,-30.1)--++(60.2,-17.9) + (634.2,162.2)--++(-32.6,-23.3)--++(38.2,-34.4) + (595.1,206.6)--++(-56.0,-59.8)--++(59.8,-6.6) + (589.7,235.7)--++(20.0,77.1)--++(-57.1,-7.6) + (569.8,57.3)--++(20.7,60.1)--++(-47.9,-15.0) + (538.0,218.2)--++(0.0,33.9)--++(-44.0,4.6) + ++(42.2,-113.1)--++(-31.2,-35.8)--++(34.9,-3.7) + (532.8,348.8)--++(-21.2,-33.2)--++(37.8,-8.3) + (496.9,336.0)--++(30.8,13.7)--++(-86.5,-4.3) + (486.2,4.8)--++(-20.6,-17.9)--++(72.7,-2.7) + (1607.2,320.8)--++(4.7,48.2)--++(-71.8,-1.9) + ++(15.2,-302.8)--++(55.7,31.0)--++(-71.2,-9.1) + (1602.6,33.9)--++(-52.5,-13.6)--++(67.9,-10.0) + (1588.5,189.0)--++(-32.4,-1.8)--++(49.9,-22.8) + (1551.3,189.2)--++(15.0,54.5)--++(-62.9,-41.3) + (1544.3,21.5)--++(9.4,40.3)--++(-65.6,-8.4) + (1533.2,288.9)--++(2.8,77.1)--++(-25.7,-18.4) + (1531.4,153.0)--++(-12.3,-37.8)--++(17.6,-24.6) + (1502.0,198.9)--++(-0.9,-58.1)--++(29.5,17.5) + (1491.0,248.8)--++(0.0,64.1)--++(-20.8,-51.4) + (1484.8,55.7)--++(31.0,57.3)--++(-61.0,-26.3) + (1454.8,-16.9)--++(34.2,5.5)--++(-42.5,45.2) + (1453.4,88.9)--++(44.4,49.2)--++(-62.4,2.8) + ++(56.0,103.8)--++(-42.9,-52.2)--++(51.3,10.3) + (1444.2,38.1)--++(7.3,45.6)--++(-37.4,-43.8) + (1433.8,145.4)--++(10.8,44.1)--++(-24.3,-8.1) + (1416.8,308.9)--++(-7.6,-63.5)--++(57.9,16.1) + (1412.2,37.0)--++(-25.6,-55.0)--++(65.5,0.9) + (1399.0,150.7)--(1399,97.0)--++(33.3,44.4) + (1398.0,153.7)--++(19.3,27.6)--++(-47.9,10.1) + (1397.4,92.2)--++(-44.3,-36.7)--++(57.5,-15.1) + ++(-21.4,303.7)--++(-23.3,-49.4)--++(48.4,16.8) + (1389.105,347.066)--++(48.442,13.211)--++(-81.91,3.524) + (1364.8,290.6)--++(-3.7,-42.7)--++(44.5,-2.8) + (1359.2,245.2)--++(-69.2,-52.1)--++(75.8,0.9) + (1350.4,362.9)--++(-44.6,-45.5)--++(56.9,-22.8) + (1350.2,53.1)--++(-50.9,-40.5)--++(57.4,-9.4) + (1349.4,97.0)--++(45.4,52.9)--++(-56.6,-19.5) + (1301.8,137.9)--++(32.7,-5.5)--++(-45.5,55.5) + (1297.1,10.9)--++(-50.1,-25.0)--++(131.6,-3.7) + (1295.4,119.7)--++(4.4,16.6)--++(-42.9,11.4) + ++(6.5,112.7)--++(38.2,54.9)--++(-48.4,-13.0) + (1286.6,196.1)--++(8.3,56.1)--++(-30.4,4.6) + ++(28.6,-222.7)--++(-31.4,0.9)--++(34.0,-20.9) + (1263.0,348.0)--++(76.7,15.7)--++(-138.7,-3.5) + (1251.2,151.7)--++(3.7,41.1)--++(-53.2,-9.3) + (1249.3,113.4)--++(-41.7,-54.9)--++(49.3,-20.8) + ++(-57.3,148.6)--++(61.5,71.1)--++(-78.8,10.6) + (1204.9,150.0)--++(-1.9,-60.2)--++(45.2,26.3) + (1192.6,359.0)--++(-8.5,-43.2)--++(64.8,-11.3) + (1155.8,184.0)--++(-4.3,-17.4)--++(42.5,16.5) + ++(-43.4,-129.5)--++(47.4,32.8)--++(-54.7,-7.3) + (1150.1,49.9)--++(-16.8,-37.3)--++(52.2,-21.5) + ++(-37.5,345.1)--++(40.4,23.9)--++(-72.5,1.8) + (1149.9,296.8)--++(-1.8,-39.3)--++(31.1,11.9) + ++(-29.2,31.2)--++(30.9,14.5)--++(-31.8,18.2) + (1141.3,83.2)--++(7.5,79.9)--++(-39.5,-24.4) + ++(36.8,116.3)--++(-55.9,-4.7)--++(63.4,-62.5) + (1106.4,39.1)--++(33.5,40.0)--++(-45.6,1.9) + (1105.1,35.1)--++(-45.0,-31.9)--++(46.9,-18.7) + (1100.4,320.4)--++(11.3,41.3)--++(-68.5,-13.1) + (1087.5,253.7)--++(12.3,64.2)--++(-50.0,-2.8) + (1084.2,183.6)--++(2.7,64.4)--++(-22.7,-28.1) + ++(-17.2,-186.4)--++(44.5,48.3)--++(-45.5,31.3) + (1045.5,29.7)--++(-50.2,-19.2)--++(61.1,-6.4) + (1044.5,116.4)--++(19.5,49.2)--++(-53.9,-32.5) + (1020.1,207.6)--++(15.4,33.5)--++(-29.0,3.6) + ++(12.0,116.7)--++(-14.4,-38.6)--++(34.1,26.0) + (1001.1,316.6)--++(-31.7,-52.2)--++(34.5,-16.8); + +\draw[fill=julia_blue,opacity=\triangleTransparency] + (956.6,68.7)--++(-19.3,-53.3)--++(46.8,42.2) + (939.4,298.8)--++(-17.8,-64.5)--++(44.9,29.9) + (938.2,302.4)--++(6.4,33.0)--++(-44.9,-18.3) + (920.0,230.7)--++(-32.2,-26.7)--++(62.5,0.9) + (869.1,10.0)--++(-88.1,-17.9)--++(137.3,18.7) + (853.7,144.2)--++(-10.4,-58.6)--++(55.7,22.7) + (841.6,82.9)--++(-23.4,-2.6)--++(47.6,-26.8) + (808.1,168.1)--++(-48.6,-38.1)--++(54.3,-46.7) + (793.9,319.0)--++(-7.6,-57.9)--++(79.7,56.0) + (792.4,322.3)--++(7.3,40.4)--++(-34.0,-8.3) + (743.4,236.2)--++(17.9,49.9)--++(-75.4,-32.0) + (741.1,94.9)--++(-37.5,-32.8)--++(40.3,-26.2) + (737.4,211.7)--++(-19.5,-30.7)--++(79.0,0.9) + (716.2,337.6)--++(-43.8,-18.4)--++(46.4,1.8) + (662.0,10.0)--++(-55.8,-18.0)--++(86.8,8.6) + (660.0,188.6)--++(-23.2,-24.1)--++(68.7,-24.1) + (644.8,243.8)--++(20.9,73.0)--++(-53.1,-3.8) + (600.4,361.8)--++(-47.0,-54.5)--++(56.3,7.5) + ++(-8.9,-174.0)--++(32.6,23.3)--++(-36.4,42.0) + (597.8,-8.0)--++(50.7,43.2)--++(-55.4,3.8) + (593.0,117.6)--++(0.0,-74.7)--++(46.3,58.6) + (589.3,231.7)--++(-45.8,-15.3)--++(51.2,-6.3) + (543.6,-15.1)--++(24.9,68.9)--++(-80.4,-9.6) + (536.1,149.9)--++(1.8,64.0)--++(-24.7,-19.2) + ++(-3.1,117.6)--++(-17.8,-53.4)--++(45.0,-4.7) + (510.0,316.9)--++(20.4,31.9)--++(-31.9,-14.2) + ++(4.5,-226.0)--++(31.2,35.8)--++(-50.4,-11.9) + (1552.4,186.7)--++(-18.4,-27.6)--++(70.9,3.7) + (1533.9,282.6)--++(-39.3,-34.6)--++(71.1,-0.9) + ++(2.4,-4.3)--++(-14.8,-53.7)--++(34.3,1.9) + (1508.8,349.1)--++(24.7,17.7)--++(-82.2,-6.2) + (1499.0,141.3)--++(0.9,59.5)--++(-51.9,-10.4) + (1491.9,-10.3)--++(51.2,30.3)--++(-56.8,32.2) + (1468.5,305.4)--++(21.8,12.7)--++(-43.7,39.1) + (1467.0,303.1)--++(-48.1,6.5)--++(49.0,-46.2) + (1415.9,312.8)--++(26.1,46.6)--++(-51.3,-14.0) + (1407.4,241.7)--++(-38.6,-48.0)--++(49.0,-10.4) + (1397.0,95.0)--++(0.0,54.3)--++(-45.9,-53.3) + (1357.6,1.1)--++(-49.6,8.1)--++(71.5,-25.2) + (1348.1,92.6)--++(-50.7,-55.3)--++(52.5,18.4) + (1304.8,315.6)--++(-7.6,-60.7)--++(64.5,37.9) + (1263.5,253.6)--++(-6.4,-58.7)--++(28.4,-1.8) + ++(-21.9,152.5)--++(-9.9,-39.7)--++(32.5,28.9) + (1258.6,39.7)--++(35.8,77.2)--++(-43.3,-1.9) + (1242.8,-12.3)--++(14.0,47.7)--++(-48.7,20.6) + (1206.7,60.5)--++(39.5,52.1)--++(-43.1,-25.1) + (1198.2,182.6)--++(-44.9,-17.4)--++(49.4,-11.9) + (1113.6,361.3)--++(-11.1,-40.7)--++(43.5,14.8) + (1088.9,248.7)--++(-2.9,-67.6)--++(66.7,4.8) + (1084.6,178.6)--++(-16.8,-10.6)--++(37.2,-25.7) + (1062.1,218.6)--++(-40.0,-14.0)--++(42.8,-34.5) + (1004.5,244.3)--++(-48.7,-39.3)--++(62.7,0.9); + +% White Mask +\draw[fill=white,opacity=\whiteMaskTransparency] (500,0)--(1560,0)--(1560,360)--(500,360)--cycle; +\end{tikzpicture} diff --git a/doc/src/assets/cover.tex b/doc/src/assets/cover.tex new file mode 100644 index 0000000000000..67b77e520acd3 --- /dev/null +++ b/doc/src/assets/cover.tex @@ -0,0 +1,46 @@ +%% ============================================================================ +%% Custom tex styles, including this file, add a custom cover to the document. +%% +%% These custom styles include: +%% - `cover.tex`: This file, The main definition of the cover, +%% used to replace the default `\maketitle` command. +%% - `custom.sty`: Load the macro package required for the cover, +%% define the background image style, etc. +%% - `preamble.tex`: Replace the default preamble for inserting a custom cover. +%% - `logo.tex`: logo of julia. +%% - `cover-splash.tex`: Background image of the cover title, +%% from julia's homepage. +%% ============================================================================ + +%% ---- 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 +\begin{titlingpage} + % set background image + \BgThispage + \vspace*{2.2cm} + + %% Centering content + \begin{center} + %% Main Heading + \textcolor{black}{ \MainHeading \DocMainTitle } + \vfill + + %% logo + % logo scale factor + \newcommand{\scaleFactor}{0.5} + \input{./assets/logo} + \\[1.5cm] + % git tag or doc version + { \SecondaryHeading V\JuliaVersion\ } + \vfill + + { \HUGE \DocAuthors } + \\[0.5cm] + % build time + { \huge \today } + \end{center} +\end{titlingpage} +\restoregeometry +%% ---- restore geometry diff --git a/doc/src/assets/custom.sty b/doc/src/assets/custom.sty new file mode 100644 index 0000000000000..03e6ff805cd3f --- /dev/null +++ b/doc/src/assets/custom.sty @@ -0,0 +1,46 @@ +%% Load the macro package required for the cover. + + +%% pkg for make cover page BEGIN ---------------------------------------------- +% Load `geometry' to modify margins later +\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 +\usepackage[pages=some]{background} + +%% Color definitions for Julia +%% https://github.com/JuliaLang/julia-logo-graphics#color-definitions +\definecolor{julia_blue} {HTML}{4063D8} +\definecolor{julia_green} {HTML}{389826} +\definecolor{julia_purple}{HTML}{9558B2} +\definecolor{julia_red} {HTML}{CB3C33} +\definecolor{splash_gary} {HTML}{1A1A33} + +% ---- define heading background +% ref: background.pdf, #2.1 Options +\backgroundsetup{ +scale=1, % scaling factor +angle=0, % counterclockwise angle +opacity=1, % transparency +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 +\begin{tikzpicture}[remember picture,overlay,draw=white] + \draw [path picture={ + % ref: pgfmanual, 15.6, "Predefined node path picture bounding box" + \node at (path picture bounding box.center){ + \input{assets/cover-splash} + };}] (-0.5\paperwidth,4cm) rectangle (0.5\paperwidth,11cm); + % Put picture to right place + % ref: pgfmanual, #2.6 Rectangle Path Construction +\end{tikzpicture} +}}% + +% ---- Heading font style +\usepackage{anyfontsize} +\newcommand{\MainHeading}{\fontspec{DejaVu Sans}\fontsize{40}{40}\selectfont\bfseries} +\newcommand{\SecondaryHeading}{\fontspec{DejaVu Sans}\LARGE} +%% cover page END ------------------------------------------------------------- diff --git a/doc/src/assets/julialogoheaderimage_dark.svg b/doc/src/assets/julialogoheaderimage_dark.svg new file mode 100644 index 0000000000000..04e06d2665633 --- /dev/null +++ b/doc/src/assets/julialogoheaderimage_dark.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/assets/julialogoheaderimage_light.svg b/doc/src/assets/julialogoheaderimage_light.svg new file mode 100644 index 0000000000000..892ca1bd08701 --- /dev/null +++ b/doc/src/assets/julialogoheaderimage_light.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/src/assets/logo-dark.svg b/doc/src/assets/logo-dark.svg index 0c90d2f7713c2..e578fd9f9a035 100644 --- a/doc/src/assets/logo-dark.svg +++ b/doc/src/assets/logo-dark.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/doc/src/assets/logo.tex b/doc/src/assets/logo.tex new file mode 100644 index 0000000000000..a19022140d17f --- /dev/null +++ b/doc/src/assets/logo.tex @@ -0,0 +1,142 @@ + +%% Direct translation of the Julia logo definition code in Luxor.jl +%% https://github.com/JuliaGraphics/Luxor.jl/blob/master/src/juliagraphics.jl#L62 +\begin{tikzpicture}[x=1,y=1,yscale=-\scaleFactor,xscale=\scaleFactor] +% Blue circle in "j" +\path[fill=julia_blue] (77.953125, 68.08984375) .. + controls (77.953125, 77.7578125) and (70.1171875, 85.58984375) .. (60.453125, 85.58984375) .. + controls (50.7890625, 85.58984375) and (42.953125, 77.7578125) .. (42.953125, 68.08984375) .. + controls (42.953125, 58.42578125) and (50.7890625, 50.58984375) .. (60.453125, 50.58984375) .. + controls (70.1171875, 50.58984375) and (77.953125, 58.42578125) .. (77.953125, 68.08984375); + +% Letter "j" +\path[fill=black] (72.87109375, 177.3125) .. + controls (72.87109375, 184.84765625) and (72.0234375, 190.93359375) .. (70.328125, 195.56640625) .. + controls (68.6328125, 200.203125) and (66.22265625, 203.80078125) .. (63.09375, 206.36328125) .. + controls (59.96875, 208.92578125) and (56.21875, 210.640625) .. (51.84765625, 211.5078125) .. + controls (47.4765625, 212.37109375) and (42.61328125, 212.8046875) .. (37.265625, 212.8046875) .. + controls (30.02734375, 212.8046875) and (24.48828125, 211.67578125) .. (20.6484375, 209.4140625) .. + controls (16.8046875, 207.15234375) and (14.8828125, 204.44140625) .. (14.8828125, 201.2734375) .. + controls (14.8828125, 198.63671875) and (15.953125, 196.4140625) .. (18.1015625, 194.60546875) .. + controls (20.25, 192.796875) and (23.1328125, 191.89453125) .. (26.75, 191.89453125) .. + controls (29.46484375, 191.89453125) and (31.6328125, 192.62890625) .. (33.25, 194.09765625) .. + controls (34.87109375, 195.56640625) and (36.2109375, 197.01953125) .. (37.265625, 198.44921875) .. + controls (38.46875, 200.03125) and (39.48828125, 201.0859375) .. (40.31640625, 201.61328125) .. + controls (41.14453125, 202.140625) and (41.8984375, 202.40625) .. (42.578125, 202.40625) .. + controls (44.0078125, 202.40625) and (45.1015625, 201.55859375) .. (45.85546875, 199.86328125) .. + controls (46.609375, 198.16796875) and (46.984375, 194.87109375) .. (46.984375, 189.97265625) -- + (46.984375, 97.05078125) -- + (72.87109375, 89.9296875) -- + cycle; + +% Letter "u" +\path[fill=black] (109.73828125, 92.4140625) -- + (109.73828125, 152.21484375) .. + controls (109.73828125, 153.875) and (110.05859375, 155.4375) .. (110.69921875, 156.90625) .. + controls (111.33984375, 158.375) and (112.2265625, 159.640625) .. (113.35546875, 160.6953125) .. + controls (114.48828125, 161.75) and (115.8046875, 162.59765625) .. (117.3125, 163.23828125) .. + controls (118.8203125, 163.87890625) and (120.44140625, 164.19921875) .. (122.17578125, 164.19921875) .. + controls (124.1328125, 164.19921875) and (126.359375, 163.1015625) .. (129.0703125, 161.203125) .. + controls (133.36328125, 158.1953125) and (135.96484375, 156.12890625) .. (135.96484375, 153.68359375) .. + controls (135.96484375, 153.09765625) and (135.96484375, 92.4140625) .. (135.96484375, 92.4140625) -- + (161.73828125, 92.4140625) -- + (161.73828125, 177.3125) -- + (135.96484375, 177.3125) -- + (135.96484375, 169.3984375) .. + controls (132.57421875, 172.26171875) and (128.95703125, 174.55859375) .. (125.11328125, 176.29296875) .. + controls (121.26953125, 178.02734375) and (117.5390625, 178.89453125) .. (113.921875, 178.89453125) .. + controls (109.703125, 178.89453125) and (105.78125, 178.1953125) .. (102.1640625, 176.80078125) .. + controls (98.546875, 175.40625) and (95.3828125, 173.50390625) .. (92.671875, 171.09375) .. + controls (89.95703125, 168.68359375) and (87.828125, 165.85546875) .. (86.28125, 162.61328125) .. + controls (84.73828125, 159.375) and (83.96484375, 155.90625) .. (83.96484375, 152.21484375) -- + (83.96484375, 92.4140625) -- + cycle; + +% Letter "l" +\path[fill=black] (197.8828125, 177.3125) -- + (172.22265625, 177.3125) -- + (172.22265625, 58.27734375) -- + (197.8828125, 51.15625) -- + cycle; + +%% "i" + 3 circles +% Green circle +\path[fill=julia_green] (261.30078125, 31.671875) .. + controls (261.30078125, 41.3359375) and (253.46484375, 49.171875) .. (243.80078125, 49.171875) .. + controls (234.1328125, 49.171875) and (226.30078125, 41.3359375) .. (226.30078125, 31.671875) .. + controls (226.30078125, 22.0078125) and (234.1328125, 14.171875) .. (243.80078125, 14.171875) .. + controls (253.46484375, 14.171875) and (261.30078125, 22.0078125) .. (261.30078125, 31.671875); + +% Purple circle +\path[fill=julia_purple] (282.3203125, 68.08984375) .. + controls (282.3203125, 77.7578125) and (274.484375, 85.58984375) .. (264.8203125, 85.58984375) .. + controls (255.15625, 85.58984375) and (247.3203125, 77.7578125) .. (247.3203125, 68.08984375) .. + controls (247.3203125, 58.42578125) and (255.15625, 50.58984375) .. (264.8203125, 50.58984375) .. + controls (274.484375, 50.58984375) and (282.3203125, 58.42578125) .. (282.3203125, 68.08984375); + +% Red circle in "i" +\path[fill=julia_red] (240.2734375, 68.08984375) .. + controls (240.2734375, 77.7578125) and (232.4375, 85.58984375) .. (222.7734375, 85.58984375) .. + controls (213.10546875, 85.58984375) and (205.2734375, 77.7578125) .. (205.2734375, 68.08984375) .. + controls (205.2734375, 58.42578125) and (213.10546875, 50.58984375) .. (222.7734375, 50.58984375) .. + controls (232.4375, 50.58984375) and (240.2734375, 58.42578125) .. (240.2734375, 68.08984375); + +% Letter "i" +\path[fill=black] (208.6015625, 97.05078125) -- + (234.375, 89.9296875) -- + (234.375, 177.3125) -- + (208.6015625, 177.3125) -- + cycle; + +% Letter "a" +\path[fill=black,nonzero rule] (288.2265625, 133.44921875) .. + controls (285.73828125, 134.5078125) and (283.23046875, 135.73046875) .. (280.70703125, 137.125) .. + controls (278.18359375, 138.51953125) and (275.8828125, 140.046875) .. (273.8125, 141.703125) .. + controls (271.73828125, 143.359375) and (270.0625, 145.1328125) .. (268.78125, 147.015625) .. + controls (267.5, 148.8984375) and (266.859375, 150.859375) .. (266.859375, 152.89453125) .. + controls (266.859375, 154.4765625) and (267.06640625, 156.00390625) .. (267.48046875, 157.47265625) .. + controls (267.89453125, 158.94140625) and (268.48046875, 160.203125) .. (269.234375, 161.2578125) .. + controls (269.98828125, 162.3125) and (270.81640625, 163.16015625) .. (271.72265625, 163.80078125) .. + controls (272.625, 164.44140625) and (273.60546875, 164.76171875) .. (274.66015625, 164.76171875) .. + controls (276.76953125, 164.76171875) and (278.8984375, 164.12109375) .. (281.046875, 162.83984375) .. + controls (283.1953125, 161.55859375) and (285.5859375, 159.94140625) .. (288.2265625, 157.98046875) -- + cycle + (314.109375, 177.3125) -- + (288.2265625, 177.3125) -- + (288.2265625, 170.52734375) .. + controls (286.79296875, 171.734375) and (285.3984375, 172.84765625) .. (284.04296875, 173.86328125) .. + controls (282.6875, 174.87890625) and (281.16015625, 175.765625) .. (279.46484375, 176.51953125) .. + controls (277.76953125, 177.2734375) and (275.8671875, 177.85546875) .. (273.75390625, 178.2734375) .. + controls (271.64453125, 178.6875) and (269.15625, 178.89453125) .. (266.296875, 178.89453125) .. + controls (262.375, 178.89453125) and (258.8515625, 178.328125) .. (255.7265625, 177.19921875) .. + controls (252.59765625, 176.06640625) and (249.94140625, 174.5234375) .. (247.7578125, 172.5625) .. + controls (245.5703125, 170.60546875) and (243.89453125, 168.28515625) .. (242.7265625, 165.609375) .. + controls (241.55859375, 162.9375) and (240.97265625, 160.015625) .. (240.97265625, 156.8515625) .. + controls (240.97265625, 153.609375) and (241.59375, 150.671875) .. (242.83984375, 148.03125) .. + controls (244.08203125, 145.39453125) and (245.77734375, 143.0234375) .. (247.92578125, 140.91015625) .. + controls (250.07421875, 138.80078125) and (252.578125, 136.91796875) .. (255.44140625, 135.2578125) .. + controls (258.3046875, 133.6015625) and (261.37890625, 132.07421875) .. (264.65625, 130.6796875) .. + controls (267.93359375, 129.28515625) and (271.34375, 128.0078125) .. (274.88671875, 126.83984375) .. + controls (278.42578125, 125.671875) and (281.93359375, 124.55859375) .. (285.3984375, 123.50390625) -- + (288.2265625, 122.82421875) -- + (288.2265625, 114.4609375) .. + controls (288.2265625, 109.03515625) and (287.1875, 105.19140625) .. (285.1171875, 102.9296875) .. + controls (283.04296875, 100.66796875) and (280.2734375, 99.5390625) .. (276.80859375, 99.5390625) .. + controls (272.73828125, 99.5390625) and (269.91015625, 100.51953125) .. (268.328125, 102.4765625) .. + controls (266.74609375, 104.4375) and (265.953125, 106.80859375) .. (265.953125, 109.59765625) .. + controls (265.953125, 111.1796875) and (265.78515625, 112.7265625) .. (265.4453125, 114.234375) .. + controls (265.109375, 115.7421875) and (264.5234375, 117.05859375) .. (263.6953125, 118.19140625) .. + controls (262.8671875, 119.3203125) and (261.6796875, 120.2265625) .. (260.1328125, 120.90234375) .. + controls (258.58984375, 121.58203125) and (256.6484375, 121.921875) .. (254.3125, 121.921875) .. + controls (250.6953125, 121.921875) and (247.7578125, 120.8828125) .. (245.49609375, 118.8125) .. + controls (243.234375, 116.73828125) and (242.10546875, 114.12109375) .. (242.10546875, 110.953125) .. + controls (242.10546875, 108.015625) and (243.1015625, 105.28515625) .. (245.09765625, 102.76171875) .. + controls (247.09765625, 100.234375) and (249.7890625, 98.06640625) .. (253.18359375, 96.26171875) .. + controls (256.57421875, 94.44921875) and (260.4921875, 93.01953125) .. (264.9375, 91.96484375) .. + controls (269.3828125, 90.91015625) and (274.09375, 90.3828125) .. (279.06640625, 90.3828125) .. + controls (285.171875, 90.3828125) and (290.4296875, 90.9296875) .. (294.83984375, 92.01953125) .. + controls (299.24609375, 93.11328125) and (302.8828125, 94.67578125) .. (305.74609375, 96.7109375) .. + controls (308.609375, 98.74609375) and (310.71875, 101.1953125) .. (312.07421875, 104.05859375) .. + controls (313.43359375, 106.921875) and (314.109375, 110.12890625) .. (314.109375, 113.66796875) -- + cycle; +\end{tikzpicture} diff --git a/doc/src/assets/preamble.tex b/doc/src/assets/preamble.tex new file mode 100644 index 0000000000000..2c492535c16b0 --- /dev/null +++ b/doc/src/assets/preamble.tex @@ -0,0 +1,48 @@ +%% Copied from the default preamble of `Documenter.jl`. +%% +%% With patch: +%% - inserting a custom cover + + +%% Default preamble BEGIN +\documentclass[oneside, a4paper]{memoir} + +\usepackage{./documenter} +\usepackage{./custom} + + +%% TOC settings +% -- TOC depth +% value: [part, chapter, section, subsection, +% subsubsection, paragraph, subparagraph] +\settocdepth{section} % show "part+chapter+section" in TOC +% -- TOC spacing +% ref: https://tex.stackexchange.com/questions/60317/toc-spacing-in-memoir +% doc: memoir/memman.pdf +% - Figure 9.2: Layout of a ToC +% - Table 9.3: Value of K in macros for styling entries +\makeatletter +% {part} to {chapter} +\setlength{\cftbeforepartskip}{1.5em \@plus \p@} +% {chapter} to {chapter} +\setlength{\cftbeforechapterskip}{0.0em \@plus \p@} +% Chapter num to chapter title spacing (Figure 9.2@memman) +\setlength{\cftchapternumwidth}{2.5em \@plus \p@} +% indent before section number +\setlength{\cftsectionindent}{2.5em \@plus \p@} +% Section num to section title spacing (Figure 9.2@memman) +\setlength{\cftsectionnumwidth}{4.0em \@plus \p@} +\makeatother + +%% Main document begin +\begin{document} + +\frontmatter +%% ---- Custom cover page +% \maketitle +\input{assets/cover.tex} % insert cover page +%% ---- Custom cover page +\cleardoublepage % makes the next page a odd-numbered page +\tableofcontents +\mainmatter +%% preamble END diff --git a/doc/src/base/arrays.md b/doc/src/base/arrays.md index 1dc2d8ed926af..6585f98360585 100644 --- a/doc/src/base/arrays.md +++ b/doc/src/base/arrays.md @@ -30,6 +30,9 @@ Base.StridedArray Base.StridedVector Base.StridedMatrix Base.StridedVecOrMat +Base.Slices +Base.RowSlices +Base.ColumnSlices Base.getindex(::Type, ::Any...) Base.zeros Base.ones @@ -142,6 +145,7 @@ Base.vcat Base.hcat Base.hvcat Base.hvncat +Base.stack Base.vect Base.circshift Base.circshift! diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 93d0547098706..7e45e2176478d 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -27,17 +27,17 @@ Base.exit Base.atexit Base.isinteractive Base.summarysize -Base.require -Base.compilecache Base.__precompile__ Base.include Base.MainInclude.include Base.include_string Base.include_dependency +__init__ Base.which(::Any, ::Any) Base.methods Base.@show ans +err Base.active_project Base.set_active_project ``` @@ -59,14 +59,16 @@ However, you can create variables with names: Finally: `where` is parsed as an infix operator for writing parametric method and type definitions; `in` and `isa` are parsed as infix operators; -and `outer` is parsed as a keyword when used to modify the scope of a variable in an iteration specification of a `for` loop or `generator` expression. -Creation of variables named `where`, `in`, `isa` or `outer` is allowed though. +`outer` is parsed as a keyword when used to modify the scope of a variable in an iteration specification of a `for` loop; +and `as` is used as a keyword to rename an identifier brought into scope by `import` or `using`. +Creation of variables named `where`, `in`, `isa`, `outer` and `as` is allowed, though. ```@docs module export import using +as baremodule function macro @@ -85,9 +87,11 @@ finally quote local global +outer const struct mutable struct +@kwdef abstract type primitive type where @@ -142,12 +146,15 @@ Base.hasproperty Core.getfield Core.setfield! Core.isdefined +Core.getglobal +Core.setglobal! Base.@isdefined Base.convert Base.promote Base.oftype Base.widen Base.identity +Base.WeakRef ``` ## Properties of Types @@ -173,6 +180,7 @@ Base.isdispatchtuple ```@docs Base.ismutable Base.isimmutable +Base.ismutabletype Base.isabstracttype Base.isprimitivetype Base.issingletontype @@ -186,6 +194,7 @@ Base.fieldcount Base.hasfield Core.nfields Base.isconst +Base.isfieldatomic ``` ### Memory layout @@ -284,7 +293,6 @@ Base.@label Base.@simd Base.@polly Base.@generated -Base.@pure Base.@assume_effects Base.@deprecate ``` @@ -330,8 +338,10 @@ Base.@timev Base.@timed Base.@elapsed Base.@allocated +Base.@allocations Base.EnvDict Base.ENV +Base.Sys.STDLIB Base.Sys.isunix Base.Sys.isapple Base.Sys.islinux @@ -344,6 +354,12 @@ Base.Sys.iswindows Base.Sys.windows_version Base.Sys.free_memory Base.Sys.total_memory +Base.Sys.free_physical_memory +Base.Sys.total_physical_memory +Base.Sys.uptime +Base.Sys.isjsvm +Base.Sys.loadavg +Base.Sys.isexecutable Base.@static ``` @@ -385,6 +401,7 @@ Core.OutOfMemoryError Core.ReadOnlyMemoryError Core.OverflowError Base.ProcessFailedException +Base.TaskFailedException Core.StackOverflowError Base.SystemError Core.TypeError @@ -413,6 +430,7 @@ Base.nameof(::Module) Base.parentmodule Base.pathof(::Module) Base.pkgdir(::Module) +Base.pkgversion(::Module) Base.moduleroot __module__ __source__ @@ -428,6 +446,15 @@ Base.functionloc(::Method) Base.@locals ``` +## Code loading + +```@docs +Base.identify_package +Base.locate_package +Base.require +Base.compilecache +``` + ## Internals ```@docs diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index 511ab786e158c..96f540086d021 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -96,6 +96,7 @@ Base.unique! Base.allunique Base.allequal Base.reduce(::Any, ::Any) +Base.reduce(::Any, ::AbstractArray) Base.foldl(::Any, ::Any) Base.foldr(::Any, ::Any) Base.maximum @@ -140,6 +141,7 @@ Base.replace(::Any, ::Pair...) Base.replace(::Base.Callable, ::Any) Base.replace! Base.rest +Base.split_rest ``` ## Indexable Collections diff --git a/doc/src/base/constants.md b/doc/src/base/constants.md index 4ba0e627b0c54..14ddbc02698d0 100644 --- a/doc/src/base/constants.md +++ b/doc/src/base/constants.md @@ -23,6 +23,3 @@ See also: * [`stderr`](@ref) * [`ENV`](@ref) * [`ENDIAN_BOM`](@ref) - * `Libc.MS_ASYNC` - * `Libc.MS_INVALIDATE` - * `Libc.MS_SYNC` diff --git a/doc/src/base/file.md b/doc/src/base/file.md index 86a1f2bab5dcd..9a9dc5d8a72f8 100644 --- a/doc/src/base/file.md +++ b/doc/src/base/file.md @@ -58,6 +58,8 @@ Base.Filesystem.normpath Base.Filesystem.realpath Base.Filesystem.relpath Base.Filesystem.expanduser +Base.Filesystem.contractuser +Base.Filesystem.samefile Base.Filesystem.splitdir Base.Filesystem.splitdrive Base.Filesystem.splitext diff --git a/doc/src/base/iterators.md b/doc/src/base/iterators.md index 8afc54b3bd11b..1c4831e52bc14 100644 --- a/doc/src/base/iterators.md +++ b/doc/src/base/iterators.md @@ -14,6 +14,7 @@ Base.Iterators.cycle Base.Iterators.repeated Base.Iterators.product Base.Iterators.flatten +Base.Iterators.flatmap Base.Iterators.partition Base.Iterators.map Base.Iterators.filter diff --git a/doc/src/base/math.md b/doc/src/base/math.md index bdf91c991183f..62368424629c6 100644 --- a/doc/src/base/math.md +++ b/doc/src/base/math.md @@ -208,5 +208,5 @@ The complete list is in the parser code: Those that are parsed like `*` (in terms of precedence) include `* / ÷ % & ⋅ ∘ × |\\| ∩ ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛ ⊍ ▷ ⨝ ⟕ ⟖ ⟗` and those that are parsed like `+` include -`+ - |\|| ⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ ⩛ ⩝ ⩡ ⩢ ⩣` +`+ - |\|| ⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⟇ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ ⩛ ⩝ ⩡ ⩢ ⩣` There are many others that are related to arrows, comparisons, and powers. diff --git a/doc/src/base/multi-threading.md b/doc/src/base/multi-threading.md index 6760d3f25f5d4..fb75b21479707 100644 --- a/doc/src/base/multi-threading.md +++ b/doc/src/base/multi-threading.md @@ -5,7 +5,12 @@ Base.Threads.@threads Base.Threads.foreach Base.Threads.@spawn Base.Threads.threadid +Base.Threads.maxthreadid Base.Threads.nthreads +Base.Threads.threadpool +Base.Threads.nthreadpools +Base.Threads.threadpoolsize +Base.Threads.ngcthreads ``` See also [Multi-Threading](@ref man-multithreading). @@ -49,7 +54,7 @@ Base.Threads.atomic_min! Base.Threads.atomic_fence ``` -## ccall using a threadpool (Experimental) +## ccall using a libuv threadpool (Experimental) ```@docs Base.@threadcall diff --git a/doc/src/base/numbers.md b/doc/src/base/numbers.md index b92bf6a1d8768..8167650ac17d1 100644 --- a/doc/src/base/numbers.md +++ b/doc/src/base/numbers.md @@ -2,6 +2,37 @@ ## Standard Numeric Types +A type tree for all subtypes of `Number` in `Base` is shown below. +Abstract types have been marked, the rest are concrete types. +``` +Number (Abstract Type) +├─ Complex +└─ Real (Abstract Type) + ├─ AbstractFloat (Abstract Type) + │ ├─ Float16 + │ ├─ Float32 + │ ├─ Float64 + │ └─ BigFloat + ├─ Integer (Abstract Type) + │ ├─ Bool + │ ├─ Signed (Abstract Type) + │ │ ├─ Int8 + │ │ ├─ Int16 + │ │ ├─ Int32 + │ │ ├─ Int64 + │ │ ├─ Int128 + │ │ └─ BigInt + │ └─ Unsigned (Abstract Type) + │ ├─ UInt8 + │ ├─ UInt16 + │ ├─ UInt32 + │ ├─ UInt64 + │ └─ UInt128 + ├─ Rational + └─ AbstractIrrational (Abstract Type) + └─ Irrational +``` + ### Abstract number types ```@docs @@ -72,9 +103,11 @@ Base.MathConstants.catalan Base.MathConstants.eulergamma Base.MathConstants.golden Base.Inf +Base.Inf64 Base.Inf32 Base.Inf16 Base.NaN +Base.NaN64 Base.NaN32 Base.NaN16 Base.issubnormal diff --git a/doc/src/base/parallel.md b/doc/src/base/parallel.md index 47f18d77f654c..c9f24429fd0e5 100644 --- a/doc/src/base/parallel.md +++ b/doc/src/base/parallel.md @@ -26,13 +26,12 @@ Base.schedule ## [Synchronization](@id lib-task-sync) -## Synchronization - ```@docs Base.errormonitor Base.@sync Base.wait Base.fetch(t::Task) +Base.fetch(x::Any) Base.timedwait Base.Condition @@ -56,6 +55,7 @@ Base.ReentrantLock ## Channels ```@docs +Base.AbstractChannel Base.Channel Base.Channel(::Function) Base.put!(::Channel, ::Any) diff --git a/doc/src/base/punctuation.md b/doc/src/base/punctuation.md index 526f11d831127..dbea97e4e3cb5 100644 --- a/doc/src/base/punctuation.md +++ b/doc/src/base/punctuation.md @@ -1,4 +1,4 @@ -# Punctuation +# [Punctuation](@id man-punctuation) Extended documentation for mathematical symbols & functions is [here](@ref math-ops). @@ -30,7 +30,7 @@ Extended documentation for mathematical symbols & functions is [here](@ref math- | `a[]` | [array indexing](@ref man-array-indexing) (calling [`getindex`](@ref) or [`setindex!`](@ref)) | | `[,]` | [vector literal constructor](@ref man-array-literals) (calling [`vect`](@ref Base.vect)) | | `[;]` | [vertical concatenation](@ref man-array-concatenation) (calling [`vcat`](@ref) or [`hvcat`](@ref)) | -| `[   ]` | with space-separated expressions, [horizontal concatenation](@ref man-concatenation) (calling [`hcat`](@ref) or [`hvcat`](@ref)) | +| `[ ]` | with space-separated expressions, [horizontal concatenation](@ref man-concatenation) (calling [`hcat`](@ref) or [`hvcat`](@ref)) | | `T{ }` | curly braces following a type list that type's [parameters](@ref Parametric-Types) | | `{}` | curly braces can also be used to group multiple [`where`](@ref) expressions in function declarations | | `;` | semicolons separate statements, begin a list of keyword arguments in function declarations or calls, or are used to separate array literals for vertical concatenation | @@ -58,3 +58,4 @@ Extended documentation for mathematical symbols & functions is [here](@ref math- | `->` | right arrow using a hyphen defines an [anonymous function](@ref man-anonymous-functions) on a single line | | [`\|>`](@ref) | pipe operator passes output from the left argument to input of the right argument, usually a [function](@ref Function-composition-and-piping) | | `∘` | function composition operator (typed with \circ{tab}) combines two functions as though they are a single larger [function](@ref Function-composition-and-piping) | +| `_` | underscores may be assigned values which will not be saved, often used to ignore [multiple return values](@ref destructuring-assignment) or create repetitive [comprehensions](@ref man-comprehensions) | diff --git a/doc/src/base/sort.md b/doc/src/base/sort.md index 9f00381ab892c..16e1839cf64a2 100644 --- a/doc/src/base/sort.md +++ b/doc/src/base/sort.md @@ -21,7 +21,8 @@ julia> sort([2,3,1], rev=true) 1 ``` -To sort an array in-place, use the "bang" version of the sort function: +`sort` constructs a sorted copy leaving its input unchanged. Use the "bang" version of +the sort function to mutate an existing array: ```jldoctest julia> a = [2,3,1]; @@ -134,69 +135,44 @@ Base.Sort.partialsortperm! ## Sorting Algorithms -There are currently four sorting algorithms available in base Julia: +There are currently four sorting algorithms publicly available in base Julia: * [`InsertionSort`](@ref) * [`QuickSort`](@ref) * [`PartialQuickSort(k)`](@ref) * [`MergeSort`](@ref) -`InsertionSort` is an O(n^2) stable sorting algorithm. It is efficient for very small `n`, and -is used internally by `QuickSort`. - -`QuickSort` is an O(n log n) sorting algorithm which is in-place, very fast, but not stable – -i.e. elements which are considered equal will not remain in the same order in which they originally -appeared in the array to be sorted. `QuickSort` is the default algorithm for numeric values, including -integers and floats. - -`PartialQuickSort(k)` is similar to `QuickSort`, but the output array is only sorted up to index -`k` if `k` is an integer, or in the range of `k` if `k` is an `OrdinalRange`. For example: - -```julia -x = rand(1:500, 100) -k = 50 -k2 = 50:100 -s = sort(x; alg=QuickSort) -ps = sort(x; alg=PartialQuickSort(k)) -qs = sort(x; alg=PartialQuickSort(k2)) -map(issorted, (s, ps, qs)) # => (true, false, false) -map(x->issorted(x[1:k]), (s, ps, qs)) # => (true, true, false) -map(x->issorted(x[k2]), (s, ps, qs)) # => (true, false, true) -s[1:k] == ps[1:k] # => true -s[k2] == qs[k2] # => true -``` - -`MergeSort` is an O(n log n) stable sorting algorithm but is not in-place – it requires a temporary -array of half the size of the input array – and is typically not quite as fast as `QuickSort`. -It is the default algorithm for non-numeric data. - -The default sorting algorithms are chosen on the basis that they are fast and stable, or *appear* -to be so. For numeric types indeed, `QuickSort` is selected as it is faster and indistinguishable -in this case from a stable sort (unless the array records its mutations in some way). The stability -property comes at a non-negligible cost, so if you don't need it, you may want to explicitly specify -your preferred algorithm, e.g. `sort!(v, alg=QuickSort)`. - -The mechanism by which Julia picks default sorting algorithms is implemented via the `Base.Sort.defalg` -function. It allows a particular algorithm to be registered as the default in all sorting functions -for specific arrays. For example, here are the two default methods from [`sort.jl`](https://github.com/JuliaLang/julia/blob/master/base/sort.jl): - +By default, the `sort` family of functions uses stable sorting algorithms that are fast +on most inputs. The exact algorithm choice is an implementation detail to allow for +future performance improvements. Currently, a hybrid of `RadixSort`, `ScratchQuickSort`, +`InsertionSort`, and `CountingSort` is used based on input type, size, and composition. +Implementation details are subject to change but currently available in the extended help +of `??Base.DEFAULT_STABLE` and the docstrings of internal sorting algorithms listed there. + +You can explicitly specify your preferred algorithm with the `alg` keyword +(e.g. `sort!(v, alg=PartialQuickSort(10:20))`) or reconfigure the default sorting algorithm +for custom types by adding a specialized method to the `Base.Sort.defalg` function. +For example, [InlineStrings.jl](https://github.com/JuliaStrings/InlineStrings.jl/blob/v1.3.2/src/InlineStrings.jl#L903) +defines the following method: ```julia -defalg(v::AbstractArray) = MergeSort -defalg(v::AbstractArray{<:Number}) = QuickSort +Base.Sort.defalg(::AbstractArray{<:Union{SmallInlineStrings, Missing}}) = InlineStringSort ``` -As for numeric arrays, choosing a non-stable default algorithm for array types for which the notion -of a stable sort is meaningless (i.e. when two values comparing equal can not be distinguished) -may make sense. +!!! compat "Julia 1.9" + The default sorting algorithm (returned by `Base.Sort.defalg`) is guaranteed to + be stable since Julia 1.9. Previous versions had unstable edge cases when + sorting numeric arrays. ## Alternate orderings By default, `sort` and related functions use [`isless`](@ref) to compare two elements in order to determine which should come first. The [`Base.Order.Ordering`](@ref) abstract type provides a mechanism for defining -alternate orderings on the same set of elements. Instances of `Ordering` define -a [total order](https://en.wikipedia.org/wiki/Total_order) on a set of elements, -so that for any elements `a`, `b`, `c` the following hold: +alternate orderings on the same set of elements: when calling a sorting function like +`sort`, an instance of `Ordering` can be provided with the keyword argument `order`. + +Instances of `Ordering` define a [total order](https://en.wikipedia.org/wiki/Total_order) +on a set of elements, so that for any elements `a`, `b`, `c` the following hold: * Exactly one of the following is true: `a` is less than `b`, `b` is less than `a`, or `a` and `b` are equal (according to [`isequal`](@ref)). diff --git a/doc/src/base/strings.md b/doc/src/base/strings.md index 9346f2c1203b7..263c0019788c3 100644 --- a/doc/src/base/strings.md +++ b/doc/src/base/strings.md @@ -1,6 +1,7 @@ # [Strings](@id lib-strings) ```@docs +Core.AbstractString Core.AbstractChar Core.Char Base.codepoint @@ -14,6 +15,8 @@ Base.repeat(::AbstractChar, ::Integer) Base.repr(::Any) Core.String(::AbstractString) Base.SubString +Base.LazyString +Base.@lazy_str Base.transcode Base.unsafe_string Base.ncodeunits(::AbstractString) diff --git a/doc/src/devdocs/EscapeAnalysis.md b/doc/src/devdocs/EscapeAnalysis.md index c4a5f14faa8ec..983a6782ccc79 100644 --- a/doc/src/devdocs/EscapeAnalysis.md +++ b/doc/src/devdocs/EscapeAnalysis.md @@ -1,3 +1,5 @@ +# `EscapeAnalysis` + `Core.Compiler.EscapeAnalysis` is a compiler utility module that aims to analyze escape information of [Julia's SSA-form IR](@ref Julia-SSA-form-IR) a.k.a. `IRCode`. @@ -18,8 +20,7 @@ This escape analysis aims to: You can give a try to the escape analysis by loading the `EAUtils.jl` utility script that define the convenience entries `code_escapes` and `@code_escapes` for testing and debugging purposes: ```@repl EAUtils -include(normpath(Sys.BINDIR::String, "..", "share", "julia", "test", "testhelpers", "EAUtils.jl")) -using EAUtils +include(normpath(Sys.BINDIR, "..", "share", "julia", "test", "compiler", "EscapeAnalysis", "EAUtils.jl")); using .EAUtils mutable struct SafeRef{T} x::T diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 83f8c1cb2b695..9fd03ad9a667a 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -249,14 +249,16 @@ types exist in lowered form: While almost every part of a surface AST is represented by an `Expr`, the IR uses only a limited number of `Expr`s, mostly for calls and some top-level-only forms. - * `Slot` + * `SlotNumber` - Identifies arguments and local variables by consecutive numbering. `Slot` is an abstract type - with subtypes `SlotNumber` and `TypedSlot`. Both types have an integer-valued `id` field giving - the slot index. Most slots have the same type at all uses, and so are represented with `SlotNumber`. - The types of these slots are found in the `slottypes` field of their `CodeInfo` object. - Slots that require per-use type annotations are represented with `TypedSlot`, which has a `typ` - field. + Identifies arguments and local variables by consecutive numbering. It has an + integer-valued `id` field giving the slot index. + The types of these slots can be found in the `slottypes` field of their `CodeInfo` object. + When a slot has different types at different uses and thus requires per-use type annotations, + they are converted to temporary `Core.Compiler.TypedSlot` object. This object has an + additional `typ` field as well as the `id` field. Note that `Core.Compiler.TypedSlot` + only appears in an unoptimized lowered form that is scheduled for optimization, + and it never appears elsewhere. * `Argument` @@ -322,7 +324,7 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form. * `=` - Assignment. In the IR, the first argument is always a Slot or a GlobalRef. + Assignment. In the IR, the first argument is always a `SlotNumber` or a `GlobalRef`. * `method` @@ -425,7 +427,7 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form. * `splatnew` Similar to `new`, except field values are passed as a single tuple. Works similarly to - `Base.splat(new)` if `new` were a first-class function, hence the name. + `splat(new)` if `new` were a first-class function, hence the name. * `isdefined` @@ -581,7 +583,7 @@ A unique'd container describing the shared metadata for a single method. Pointers to non-AST things that have been interpolated into the AST, required by compression of the AST, type-inference, or the generation of native code. - * `nargs`, `isva`, `called`, `isstaged`, `pure` + * `nargs`, `isva`, `called`, `is_for_opaque_closure`, Descriptive bit-fields for the source code of this Method. @@ -655,7 +657,7 @@ for important details on how to modify these fields safely. The ABI to use when calling `fptr`. Some significant ones include: * 0 - Not compiled yet - * 1 - JL_CALLABLE `jl_value_t *(*)(jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` + * 1 - `JL_CALLABLE` `jl_value_t *(*)(jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` * 2 - Constant (value stored in `rettype_const`) * 3 - With Static-parameters forwarded `jl_value_t *(*)(jl_svec_t *sparams, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` * 4 - Run in interpreter `jl_value_t *(*)(jl_method_instance_t *meth, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` @@ -683,10 +685,10 @@ A (usually temporary) container for holding lowered source code. A `UInt8` array of slot properties, represented as bit flags: - * 2 - assigned (only false if there are *no* assignment statements with this var on the left) - * 8 - const (currently unused for local variables) - * 16 - statically assigned once - * 32 - might be used before assigned. This flag is only valid after type inference. + * 0x02 - assigned (only false if there are *no* assignment statements with this var on the left) + * 0x08 - used (if there is any read or write of the slot) + * 0x10 - statically assigned once + * 0x20 - might be used before assigned. This flag is only valid after type inference. * `ssavaluetypes` @@ -699,11 +701,13 @@ A (usually temporary) container for holding lowered source code. Statement-level flags for each expression in the function. Many of these are reserved, but not yet implemented: - * 0 = inbounds - * 1,2 = inlinehint,always-inline,noinline - * 3 = strict-ieee (strictfp) - * 4-6 = - * 7 = has out-of-band info + * 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` @@ -733,6 +737,10 @@ Optional Fields: The `MethodInstance` that "owns" this object (if applicable). + * `edges` + + Forward edges to method instances that must be invalidated. + * `min_world`/`max_world` The range of world ages for which this code was valid at the time when it had been inferred. @@ -753,7 +761,22 @@ Boolean properties: Whether this should propagate `@inbounds` when inlined for the purpose of eliding `@boundscheck` blocks. - * `pure` - Whether this is known to be a pure function of its arguments, without respect to the - state of the method caches or other mutable global state. +`UInt8` settings: + + * `constprop` + + * 0 = use heuristic + * 1 = aggressive + * 2 = none + + * `purity` + Constructed from 5 bit flags: + + * 0x01 << 0 = this method is guaranteed to return or terminate consistently (`:consistent`) + * 0x01 << 1 = this method is free from externally semantically visible side effects (`:effect_free`) + * 0x01 << 2 = this method is guaranteed to not throw an exception (`:nothrow`) + * 0x01 << 3 = this method is guaranteed to terminate (`:terminates_globally`) + * 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. diff --git a/doc/src/devdocs/boundscheck.md b/doc/src/devdocs/boundscheck.md index f840a0283ea15..0935257526885 100644 --- a/doc/src/devdocs/boundscheck.md +++ b/doc/src/devdocs/boundscheck.md @@ -47,12 +47,12 @@ function sum(A::AbstractArray) for i in 1:length(A) @inbounds r += A[i] end - return r + return r end ``` Which quietly assumes 1-based indexing and therefore exposes unsafe memory access when used -with [`OffsetArrays`](@ref man-custom-indice): +with [`OffsetArrays`](@ref man-custom-indices): ```julia-repl julia> using OffsetArrays diff --git a/doc/src/devdocs/build/build.md b/doc/src/devdocs/build/build.md index 11c5fa7e8d56c..ad3871c2e70f0 100644 --- a/doc/src/devdocs/build/build.md +++ b/doc/src/devdocs/build/build.md @@ -144,7 +144,7 @@ Notes for various architectures: Building Julia requires that the following software be installed: - **[GNU make]** — building dependencies. -- **[gcc & g++][gcc]** (>= 5.1) or **[Clang][clang]** (>= 3.5, >= 6.0 for Apple Clang) — compiling and linking C, C++. +- **[gcc & g++][gcc]** (>= 7.1) or **[Clang][clang]** (>= 5.0, >= 9.3 for Apple Clang) — compiling and linking C, C++. - **[libatomic][gcc]** — provided by **[gcc]** and needed to support atomic operations. - **[python]** (>=2.7) — needed to build LLVM. - **[gfortran]** — compiling and linking Fortran libraries. @@ -167,9 +167,9 @@ Julia uses the following external libraries, which are automatically downloaded (or in a few cases, included in the Julia source repository) and then compiled from source the first time you run `make`. The specific version numbers of these libraries that Julia -uses are listed in [`deps/Versions.make`](https://github.com/JuliaLang/julia/blob/master/deps/Versions.make): +uses are listed in [`deps/$(libname).version`](https://github.com/JuliaLang/julia/blob/master/deps/): -- **[LLVM]** (9.0 + [patches](https://github.com/JuliaLang/julia/tree/master/deps/patches)) — compiler infrastructure (see [note below](#llvm)). +- **[LLVM]** (15.0 + [patches](https://github.com/JuliaLang/llvm-project/tree/julia-release/15.x)) — compiler infrastructure (see [note below](#llvm)). - **[FemtoLisp]** — packaged with Julia source, and used to implement the compiler front-end. - **[libuv]** (custom fork) — portable, high-performance event-based I/O library. - **[OpenLibm]** — portable libm library containing elementary math functions. @@ -187,6 +187,7 @@ uses are listed in [`deps/Versions.make`](https://github.com/JuliaLang/julia/blo - **[mbedtls]** — library used for cryptography and transport layer security, used by libssh2 - **[utf8proc]** — a library for processing UTF-8 encoded Unicode strings. - **[LLVM libunwind]** — LLVM's fork of [libunwind], a library that determines the call-chain of a program. +- **[ITTAPI]** — Intel's Instrumentation and Tracing Technology and Just-In-Time API. [GNU make]: https://www.gnu.org/software/make [patch]: https://www.gnu.org/software/patch @@ -222,6 +223,7 @@ uses are listed in [`deps/Versions.make`](https://github.com/JuliaLang/julia/blo [pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/ [powershell]: https://docs.microsoft.com/en-us/powershell/scripting/wmf/overview [which]: https://carlowood.github.io/which/ +[ITTAPI]: https://github.com/intel/ittapi ## Build dependencies @@ -236,7 +238,7 @@ The most complicated dependency is LLVM, for which we require additional patches 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 `deps/llvm.mk`, and the patches themselves are in `deps/patches/`. + * 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 remaining patches are all upstream bug fixes, and have been contributed into upstream LLVM. @@ -282,3 +284,71 @@ LLVM_ASSERTIONS=1 ``` Please note that assert builds of Julia will be slower than regular (non-assert) builds. + +## Building 32-bit Julia on a 64-bit machine + +Occasionally, bugs specific to 32-bit architectures may arise, and when this happens it is useful to be able to debug the problem on your local machine. Since most modern 64-bit systems support running programs built for 32-bit ones, if you don't have to recompile Julia from source (e.g. you mainly need to inspect the behavior of a 32-bit Julia without having to touch the C code), you can likely use a 32-bit build of Julia for your system that you can obtain from the [official downloads page](https://julialang.org/downloads/). +However, if you do need to recompile Julia from source one option is to use a Docker container of a 32-bit system. At least for now, building a 32-bit version of Julia is relatively straightforward using [ubuntu 32-bit docker images](https://hub.docker.com/r/i386/ubuntu). In brief, after setting up `docker` here are the required steps: + +```sh +$ docker pull i386/ubuntu +$ docker run --platform i386 -i -t i386/ubuntu /bin/bash +``` + +At this point you should be in a 32-bit machine console (note that `uname` reports the host architecture, so will still say 64-bit, but this will not affect the Julia build). You can add packages and compile code; when you `exit`, all the changes will be lost, so be sure to finish your analysis in a single session or set up a copy/pastable script you can use to set up your environment. + +From this point, you should + +```sh +# apt update +``` +(Note that `sudo` isn't installed, but neither is it necessary since you are running as `root`, so you can omit `sudo` from all commands.) + +Then add all the [build dependencies](#required-build-tools-and-external-libraries), a console-based editor of your choice, `git`, and anything else you'll need (e.g., `gdb`, `rr`, etc). Pick a directory to work in and `git clone` Julia, check out the branch you wish to debug, and build Julia as usual. + + +## Update the version number of a dependency + +There are two types of builds +1. Build everything (`deps/` and `src/`) from source code. + (Add `USE_BINARYBUILDER=0` to `Make.user`, see [Building Julia](#building-julia)) +2. Build from source (`src/`) with pre-compiled dependencies (default) + +When you want to update the version number of a dependency in `deps/`, +you may want to use the following checklist: + +```md +### Check list + +Version numbers: +- [ ] `deps/$(libname).version`: `LIBNAME_VER`, `LIBNAME_BRANCH`, `LIBNAME_SHA1` and `LIBNAME_JLL_VER` +- [ ] `stdlib/$(LIBNAME_JLL_NAME)_jll/Project.toml`: `version` + +Checksum: +- [ ] `deps/checksums/$(libname)` +- [ ] `deps/checksums/$(LIBNAME_JLL_NAME)-*/`: `md5` and `sha512` + +Patches: +- [ ] `deps/$(libname).mk` +- [ ] `deps/patches/$(libname)-*.patch` +``` + +Note: +- For specific dependencies, some items in the checklist may not exist. +- For checksum file, it may be **a single file** without a suffix, or **a folder** containing two files. + + +### Example: `OpenLibm` + +1. Update Version numbers in `deps/openlibm.version` + - `OPENLIBM_VER := 0.X.Y` + - `OPENLIBM_BRANCH = v0.X.Y` + - `OPENLIBM_SHA1 = new-sha1-hash` +2. Update Version number in `stdlib/OpenLibm_jll/Project.toml` + - `version = "0.X.Y+0"` +3. Update checksums in `deps/checksums/openlibm` + - `make -f contrib/refresh_checksums.mk openlibm` +4. Check if the patch files `deps/patches/openlibm-*.patch` exist + - if patches don't exist, skip. + - if patches exist, check if they have been merged into the new version and need to be removed. + When deleting a patch, remember to modify the corresponding Makefile file (`deps/openlibm.mk`). diff --git a/doc/src/devdocs/build/windows.md b/doc/src/devdocs/build/windows.md index fef4413db7d1a..7192bb8a7a544 100644 --- a/doc/src/devdocs/build/windows.md +++ b/doc/src/devdocs/build/windows.md @@ -66,13 +66,10 @@ MinGW-w64 compilers available through Cygwin's package manager. 6. For 64 bit Julia, and also from the *Devel* category: `mingw64-x86_64-gcc-g++` and `mingw64-x86_64-gcc-fortran` - 4. At the *'Resolving Dependencies'* step, be sure to leave *'Select required - packages (RECOMMENDED)'* enabled. - - 5. Allow Cygwin installation to finish, then start from the installed shortcut + 4. Allow Cygwin installation to finish, then start from the installed shortcut a *'Cygwin Terminal'*, or *'Cygwin64 Terminal'*, respectively. - 6. Build Julia and its dependencies from source: + 5. Build Julia and its dependencies from source: 1. Get the Julia sources ```sh @@ -93,7 +90,8 @@ MinGW-w64 compilers available through Cygwin's package manager. 3. Start the build ```sh - make -j 4 # Adjust the number of threads (4) to match your build environment. + make -j 4 # Adjust the number of threads (4) to match your build environment. + make -j 4 debug # This builds julia-debug.exe ``` @@ -110,7 +108,7 @@ MinGW-w64 compilers available through Cygwin's package manager. > make -C julia-win64 # build for Windows x86-64 in julia-win64 folder > ``` - 7. Run Julia using the Julia executables directly + 6. Run Julia using the Julia executables directly ```sh usr/bin/julia.exe usr/bin/julia-debug.exe @@ -118,11 +116,67 @@ MinGW-w64 compilers available through Cygwin's package manager. ### Compiling with MinGW/MSYS2 -Compiling Julia from source using [MSYS2](https://msys2.github.io) has worked in the past -but is not actively supported. Pull requests to restore support would be welcome. See a -[past version of this -file](https://github.com/JuliaLang/julia/blob/v0.6.0/README.windows.md) for the former -instructions for compiling using MSYS2. +> MSYS2 provides a robust MSYS experience. + +Note: MSYS2 requires **64 bit** Windows 7 or newer. + + 1. Install and configure [MSYS2](https://www.msys2.org/), Software Distribution + and Building Platform for Windows. + + 1. Download and run the latest installer for the + [64-bit](https://github.com/msys2/msys2-installer/releases/latest) distribution. + The installer will have a name like `msys2-x86_64-yyyymmdd.exe`. + + 2. Open MSYS2. Update package database and base packages: + ```sh + pacman -Syu + ``` + + 3. Exit and restart MSYS2, Update the rest of the base packages: + ```sh + pacman -Syu + ``` + + 3. Then install tools required to build julia: + ```sh + # tools + pacman -S cmake diffutils git m4 make patch tar p7zip curl python + + # For 64 bit Julia, install x86_64 + pacman -S mingw-w64-x86_64-gcc + # For 32 bit Julia, install i686 + pacman -S mingw-w64-i686-gcc + ``` + + 4. Configuration of MSYS2 is complete. Now `exit` the MSYS2 shell. + + + 2. Build Julia and its dependencies with pre-build dependencies. + + 1. Open a new [**MINGW64/MINGW32 shell**](https://www.msys2.org/docs/environments/#overview). + Currently we can't use both mingw32 and mingw64, + so if you want to build the x86_64 and i686 versions, + you'll need to build them in each environment separately. + + 2. and clone the Julia sources + ```sh + git clone https://github.com/JuliaLang/julia.git + cd julia + ``` + + 3. Start the build + ```sh + make -j$(nproc) + ``` + + > Protip: build in dir + > ```sh + > make O=julia-mingw-w64 configure + > echo 'ifeq ($(BUILDROOT),$(JULIAHOME)) + > $(error "in-tree build disabled") + > endif' >> Make.user + > make -C julia-mingw-w64 + > ``` ### Cross-compiling from Unix (Linux/Mac/WSL) diff --git a/doc/src/devdocs/eval.md b/doc/src/devdocs/eval.md index 1aea5161ad23a..6a153c67daa13 100644 --- a/doc/src/devdocs/eval.md +++ b/doc/src/devdocs/eval.md @@ -25,7 +25,7 @@ The 10,000 foot view of the whole process is as follows: 1. The user starts `julia`. 2. The C function `main()` from `cli/loader_exe.c` gets called. This function processes the command line arguments, filling in the `jl_options` struct and setting the variable `ARGS`. It then initializes - Julia (by calling [`julia_init` in `task.c`](https://github.com/JuliaLang/julia/blob/master/src/task.c), + Julia (by calling [`julia_init` in `init.c`](https://github.com/JuliaLang/julia/blob/master/src/init.c), which may load a previously compiled [sysimg](@ref dev-sysimg)). Finally, it passes off control to Julia by calling [`Base._start()`](https://github.com/JuliaLang/julia/blob/master/base/client.jl). 3. When `_start()` takes over control, the subsequent sequence of commands depends on the command diff --git a/doc/src/devdocs/external_profilers.md b/doc/src/devdocs/external_profilers.md new file mode 100644 index 0000000000000..956d66508fc89 --- /dev/null +++ b/doc/src/devdocs/external_profilers.md @@ -0,0 +1,98 @@ +# External Profiler Support + +Julia provides explicit support for some external tracing profilers, enabling you to obtain a high-level overview of the runtime's execution behavior. + +The currently supported profilers are: +- [Tracy](https://github.com/wolfpld/tracy) +- [Intel VTune (ITTAPI)](https://github.com/intel/ittapi) + +### Adding New Zones + +To add new zones, use the `JL_TIMING` macro. You can find numerous examples throughout the codebase by searching for `JL_TIMING`. To add a new type of zone +you add it to `JL_TIMING_OWNERS` (and possibly `JL_TIMING_EVENTS`). + +### Dynamically Enabling and Disabling Zones + +The `JULIA_TIMING_SUBSYSTEMS` environment variable allows you to enable or disable zones for a specific Julia run. For instance, setting the variable to `+GC,-INFERENCE` will enable the `GC` zones and disable the `INFERENCE` +zones. + +## Tracy Profiler + +[Tracy](https://github.com/wolfpld/tracy) is a flexible profiler that can be optionally integrated with Julia. + +A typical Tracy session might look like this: + +![Typical Tracy usage](tracy.png) + +### Building Julia with Tracy + +To enable Tracy integration, build Julia with the extra option `WITH_TRACY=1` in the `Make.user` file. + +### Installing the Tracy Profile Viewer + +The easiest way to obtain the profile viewer is by adding the `TracyProfiler_jll` package and launching the profiler with: + +```julia +run(TracyProfiler_jll.tracy()) +``` + +!!! note + On macOS, you may want to set the `TRACY_DPI_SCALE` environment variable to `1.0` if the UI elements in the profiler appear excessively large. + +To run a "headless" instance that saves the trace to disk, use `TracyProfiler_jll.capture() -o mytracefile.tracy` instead. + +For information on using the Tracy UI, refer to the Tracy manual. + +### Profiling Julia with Tracy + +A typical workflow for profiling Julia with Tracy involves starting Julia using: + +```julia +JULIA_WAIT_FOR_TRACY=1 ./julia -e '...' +``` + +The environment variable ensures that Julia waits until it has successfully connected to the Tracy profiler before continuing execution. Afterward, use the Tracy profiler UI, click `Connect`, and Julia execution should resume and profiling should start. + +### Profiling package precompilation with Tracy + +To profile a package precompilation process it is easiest to explicitly call into `Base.compilecache` with the package you want to precompile: + +```julia +pkg = Base.identify_package("SparseArrays") +withenv("JULIA_WAIT_FOR_TRACY" => 1, "TRACY_PORT" => 9001) do + Base.compilecache(pkg) +end +``` + +Here, we use a custom port for tracy which makes it easier to find the correct client in the Tracy UI to connect to. + +### Adding metadata to zones + +The various `jl_timing_show_*` and `jl_timing_printf` functions can be used to attach a string (or strings) to a zone. For example, the trace zone for inference shows the method instance that is being inferred. + +The `TracyCZoneColor` function can be used to set the color of a certain zone. Search through the codebase to see how it is used. + +### Viewing Tracy files in your browser + +Visit https://topolarity.github.io/trace-viewer/ for an (experimental) web viewer for Tracy traces. + +You can open a local `.tracy` file or provide a URL from the web (e.g. a file in a Github repo). If you load a trace file from the web, you can also share the page URL directly with others, enabling them to view the same trace. + +### Enabling stack trace samples + +To enable call stack sampling in Tracy, build Julia with these options in your `Make.user` file: +``` +WITH_TRACY := 1 +WITH_TRACY_CALLSTACKS := 1 +USE_BINARYBUILDER_LIBTRACYCLIENT := 0 +``` + +You may also need to run `make -C deps clean-libtracyclient` to force a re-build of Tracy. + +This feature has a significant impact on trace size and profiling overhead, so it is recommended to leave call stack sampling off when possible, especially if you intend to share your trace files online. + +Note that the Julia JIT runtime does not yet have integration for Tracy's symbolification, so Julia functions will typically be unknown in these stack traces. + +## Intel VTune (ITTAPI) Profiler + +*This section is yet to be written.* diff --git a/doc/src/devdocs/functions.md b/doc/src/devdocs/functions.md index 13f863cd26d81..283f63b2d0dce 100644 --- a/doc/src/devdocs/functions.md +++ b/doc/src/devdocs/functions.md @@ -48,7 +48,7 @@ jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs); Given the above dispatch process, conceptually all that is needed to add a new method is (1) a tuple type, and (2) code for the body of the method. `jl_method_def` implements this operation. -`jl_first_argument_datatype` is called to extract the relevant method table from what would be +`jl_method_table_for` is called to extract the relevant method table from what would be the type of the first argument. This is much more complicated than the corresponding procedure during dispatch, since the argument tuple type might be abstract. For example, we can define: @@ -141,9 +141,9 @@ but works reasonably well. ## Keyword arguments -Keyword arguments work by associating a special, hidden function object with each method table -that has definitions with keyword arguments. This function is called the "keyword argument sorter" -or "keyword sorter", or "kwsorter", and is stored in the `kwsorter` field of `MethodTable` objects. +Keyword arguments work by adding methods to the kwcall function. This function +is usually the "keyword argument sorter" or "keyword sorter", which then calls +the inner body of the function (defined anonymously). Every definition in the kwsorter function has the same arguments as some definition in the normal method table, except with a single `NamedTuple` argument prepended, which gives the names and values of passed keyword arguments. The kwsorter's job is to move keyword arguments @@ -220,10 +220,10 @@ circle((0,0), 1.0, color = red; other...) is lowered to: ```julia -kwfunc(circle)(merge((color = red,), other), circle, (0,0), 1.0) +kwcall(merge((color = red,), other), circle, (0,0), 1.0) ``` - `kwfunc` (also in`Core`) fetches the kwsorter for the called function. + `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). diff --git a/doc/src/devdocs/gc-sa.md b/doc/src/devdocs/gc-sa.md index 85d16c1e4e195..ffbb7451fce5f 100644 --- a/doc/src/devdocs/gc-sa.md +++ b/doc/src/devdocs/gc-sa.md @@ -2,13 +2,23 @@ ## Running the analysis -The analyzer plugin that drives the anlysis ships with julia. Its +The analyzer plugin that drives the analysis ships with julia. Its source code can be found in `src/clangsa`. Running it requires the clang dependency to be build. Set the `BUILD_LLVM_CLANG` variable in your Make.user in order to build an appropriate version of clang. You may also want to use the prebuilt binaries using the -`USE_BINARYBUILDER_LLVM` options. Afterwards, running the analysis -over the source tree is as simple as running `make -C src analyzegc`. +`USE_BINARYBUILDER_LLVM` options. + +Alternatively (or if these do not suffice), try + +```sh +make -C src install-analysis-deps +``` + +from Julia's toplevel directory. + + +Afterwards, running the analysis over the source tree is as simple as running `make -C src analyzegc`. ## General Overview diff --git a/doc/src/devdocs/gc.md b/doc/src/devdocs/gc.md new file mode 100644 index 0000000000000..c072912e77c3f --- /dev/null +++ b/doc/src/devdocs/gc.md @@ -0,0 +1,72 @@ +# Garbage Collection in Julia + +## 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. + +## Memory layout of objects and GC bits + +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). + +Objects are aligned by a multiple of 4 bytes to ensure this pointer tagging is legal. + +## Pool allocation + +Sufficiently small objects (up to 2032 bytes) are allocated on per-thread object +pools. + +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. + +## Malloc'd arrays and big objects + +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`). + +Sweeping these objects consists of unlinking them from their list and calling `free` on the +corresponding address. + +## Generational and remembered sets + +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. + +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: + +- 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. + +- Roots (e.g. thread locals) are enqueued. + +- Object graph is traversed and mark bits are set. + +- 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). + +## Heuristics + +GC heuristics tune the GC by changing the size of the allocation interval between garbage collections. +If a GC was unproductive, then we increase the size of the allocation interval to allow objects more time to die. +If a GC returns a lot of space we can shrink the interval. The goal is to find a steady state where we are +allocating just about the same amount as we are collecting. diff --git a/doc/src/devdocs/inference.md b/doc/src/devdocs/inference.md index 68d63600f1bb1..b6614d060a0c8 100644 --- a/doc/src/devdocs/inference.md +++ b/doc/src/devdocs/inference.md @@ -2,12 +2,12 @@ ## How inference works -[Type inference](https://en.wikipedia.org/wiki/Type_inference) refers -to the process of deducing the types of later values from the types of -input values. Julia's approach to inference has been described in blog -posts -([1](https://juliacomputing.com/blog/2016/04/inference-convergence/), -[2](https://juliacomputing.com/blog/2017/05/inference-converage2/)). +In Julia compiler, "type inference" refers to the process of deducing the types of later +values from the types of input values. Julia's approach to inference has been described in +the blog posts below: +1. [Shows a simplified implementation of the data-flow analysis algorithm, that Julia's type inference routine is based on.](https://aviatesk.github.io/posts/data-flow-problem/) +2. [Gives a high level view of inference with a focus on its inter-procedural convergence guarantee.](https://info.juliahub.com/inference-convergence-algorithm-in-julia) +3. [Explains a refinement on the algorithm introduced in 2.](https://info.juliahub.com/inference-convergence-algorithm-in-julia-revisited) ## Debugging compiler.jl @@ -96,7 +96,7 @@ Each statement gets analyzed for its total cost in a function called as follows: ```jldoctest; filter=r"tuple.jl:\d+" julia> Base.print_statement_costs(stdout, map, (typeof(sqrt), Tuple{Int},)) # map(sqrt, (2,)) -map(f, t::Tuple{Any}) in Base at tuple.jl:179 +map(f, t::Tuple{Any}) @ Base tuple.jl:273 0 1 ─ %1 = Base.getfield(_3, 1, true)::Int64 1 │ %2 = Base.sitofp(Float64, %1)::Float64 2 │ %3 = Base.lt_float(%2, 0.0)::Bool diff --git a/doc/src/devdocs/init.md b/doc/src/devdocs/init.md index cf954884c57b6..1e0e1173f8695 100644 --- a/doc/src/devdocs/init.md +++ b/doc/src/devdocs/init.md @@ -6,9 +6,9 @@ How does the Julia runtime execute `julia -e 'println("Hello World!")'` ? Execution starts at [`main()` in `cli/loader_exe.c`](https://github.com/JuliaLang/julia/blob/master/cli/loader_exe.c), which calls `jl_load_repl()` in [`cli/loader_lib.c`](https://github.com/JuliaLang/julia/blob/master/cli/loader_lib.c) -which loads a few libraries, eventually calling [`repl_entrypoint()` in `src/jlapi.c`](https://github.com/JuliaLang/julia/blob/master/src/jlapi.c). +which loads a few libraries, eventually calling [`jl_repl_entrypoint()` in `src/jlapi.c`](https://github.com/JuliaLang/julia/blob/master/src/jlapi.c). -`repl_entrypoint()` calls [`libsupport_init()`](https://github.com/JuliaLang/julia/blob/master/src/support/libsupportinit.c) +`jl_repl_entrypoint()` calls [`libsupport_init()`](https://github.com/JuliaLang/julia/blob/master/src/support/libsupportinit.c) to set the C library locale and to initialize the "ios" library (see [`ios_init_stdstreams()`](https://github.com/JuliaLang/julia/blob/master/src/support/ios.c) and [Legacy `ios.c` library](@ref Legacy-ios.c-library)). @@ -20,7 +20,7 @@ or early initialization. Other options are handled later by [`exec_options()` in ## `julia_init()` -[`julia_init()` in `task.c`](https://github.com/JuliaLang/julia/blob/master/src/task.c) is called +[`julia_init()` in `init.c`](https://github.com/JuliaLang/julia/blob/master/src/init.c) is called by `main()` and calls [`_julia_init()` in `init.c`](https://github.com/JuliaLang/julia/blob/master/src/init.c). `_julia_init()` begins by calling `libsupport_init()` again (it does nothing the second time). @@ -118,7 +118,7 @@ Other signals (`SIGINFO, SIGBUS, SIGILL, SIGTERM, SIGABRT, SIGQUIT, SIGSYS` and hooked up to [`sigdie_handler()`](https://github.com/JuliaLang/julia/blob/master/src/signals-unix.c) which prints a backtrace. -[`jl_init_restored_modules()`](https://github.com/JuliaLang/julia/blob/master/src/staticdata.c) calls +[`jl_init_restored_module()`](https://github.com/JuliaLang/julia/blob/master/src/staticdata.c) calls [`jl_module_run_initializer()`](https://github.com/JuliaLang/julia/blob/master/src/module.c) for each deserialized module to run the `__init__()` function. @@ -185,32 +185,32 @@ Hello World! | `jl_uv_write()` | `jl_uv.c` | called though [`ccall`](@ref) | | `julia_write_282942` | `stream.jl` | function `write!(s::IO, a::Array{T}) where T` | | `julia_print_284639` | `ascii.jl` | `print(io::IO, s::String) = (write(io, s); nothing)` | -| `jlcall_print_284639` |   |   | -| `jl_apply()` | `julia.h` |   | -| `jl_trampoline()` | `builtins.c` |   | -| `jl_apply()` | `julia.h` |   | +| `jlcall_print_284639` | | | +| `jl_apply()` | `julia.h` | | +| `jl_trampoline()` | `builtins.c` | | +| `jl_apply()` | `julia.h` | | | `jl_apply_generic()` | `gf.c` | `Base.print(Base.TTY, String)` | -| `jl_apply()` | `julia.h` |   | -| `jl_trampoline()` | `builtins.c` |   | -| `jl_apply()` | `julia.h` |   | +| `jl_apply()` | `julia.h` | | +| `jl_trampoline()` | `builtins.c` | | +| `jl_apply()` | `julia.h` | | | `jl_apply_generic()` | `gf.c` | `Base.print(Base.TTY, String, Char, Char...)` | -| `jl_apply()` | `julia.h` |   | -| `jl_f_apply()` | `builtins.c` |   | -| `jl_apply()` | `julia.h` |   | -| `jl_trampoline()` | `builtins.c` |   | -| `jl_apply()` | `julia.h` |   | +| `jl_apply()` | `julia.h` | | +| `jl_f_apply()` | `builtins.c` | | +| `jl_apply()` | `julia.h` | | +| `jl_trampoline()` | `builtins.c` | | +| `jl_apply()` | `julia.h` | | | `jl_apply_generic()` | `gf.c` | `Base.println(Base.TTY, String, String...)` | -| `jl_apply()` | `julia.h` |   | -| `jl_trampoline()` | `builtins.c` |   | -| `jl_apply()` | `julia.h` |   | +| `jl_apply()` | `julia.h` | | +| `jl_trampoline()` | `builtins.c` | | +| `jl_apply()` | `julia.h` | | | `jl_apply_generic()` | `gf.c` | `Base.println(String,)` | -| `jl_apply()` | `julia.h` |   | -| `do_call()` | `interpreter.c` |   | -| `eval_body()` | `interpreter.c` |   | -| `jl_interpret_toplevel_thunk` | `interpreter.c` |   | -| `jl_toplevel_eval_flex` | `toplevel.c` |   | -| `jl_toplevel_eval_in` | `toplevel.c` |   | -| `Core.eval` | `boot.jl` |   | +| `jl_apply()` | `julia.h` | | +| `do_call()` | `interpreter.c` | | +| `eval_body()` | `interpreter.c` | | +| `jl_interpret_toplevel_thunk` | `interpreter.c` | | +| `jl_toplevel_eval_flex` | `toplevel.c` | | +| `jl_toplevel_eval_in` | `toplevel.c` | | +| `Core.eval` | `boot.jl` | | Since our example has just one function call, which has done its job of printing "Hello World!", the stack now rapidly unwinds back to `main()`. diff --git a/doc/src/devdocs/llvm.md b/doc/src/devdocs/llvm.md index 840822f136004..93b241d703714 100644 --- a/doc/src/devdocs/llvm.md +++ b/doc/src/devdocs/llvm.md @@ -9,18 +9,36 @@ Julia dynamically links against LLVM by default. Build with `USE_LLVM_SHLIB=0` t The code for lowering Julia AST to LLVM IR or interpreting it directly is in directory `src/`. -| File | Description | -|:------------------- |:---------------------------------------------------------- | -| `builtins.c` | Builtin functions | -| `ccall.cpp` | Lowering [`ccall`](@ref) | -| `cgutils.cpp` | Lowering utilities, notably for array and tuple accesses | -| `codegen.cpp` | Top-level of code generation, pass list, lowering builtins | -| `debuginfo.cpp` | Tracks debug information for JIT code | -| `disasm.cpp` | Handles native object file and JIT code diassembly | -| `gf.c` | Generic functions | -| `intrinsics.cpp` | Lowering intrinsics | -| `llvm-simdloop.cpp` | Custom LLVM pass for [`@simd`](@ref) | -| `sys.c` | I/O and operating system utility functions | +| File | Description | +|:-------------------------------- |:------------------------------------------------------------------ | +| `aotcompile.cpp` | Legacy pass manager pipeline, compiler C-interface entry | +| `builtins.c` | Builtin functions | +| `ccall.cpp` | Lowering [`ccall`](@ref) | +| `cgutils.cpp` | Lowering utilities, notably for array and tuple accesses | +| `codegen.cpp` | Top-level of code generation, pass list, lowering builtins | +| `debuginfo.cpp` | Tracks debug information for JIT code | +| `disasm.cpp` | Handles native object file and JIT code diassembly | +| `gf.c` | Generic functions | +| `intrinsics.cpp` | Lowering intrinsics | +| `jitlayers.cpp` | JIT-specific code, ORC compilation layers/utilities | +| `llvm-alloc-helpers.cpp` | Julia-specific escape analysis | +| `llvm-alloc-opt.cpp` | Custom LLVM pass to demote heap allocations to the stack | +| `llvm-cpufeatures.cpp` | Custom LLVM pass to lower CPU-based functions (e.g. haveFMA) | +| `llvm-demote-float16.cpp` | Custom LLVM pass to lower 16b float ops to 32b float ops | +| `llvm-final-gc-lowering.cpp` | Custom LLVM pass to lower GC calls to their final form | +| `llvm-gc-invariant-verifier.cpp` | Custom LLVM pass to verify Julia GC invariants | +| `llvm-julia-licm.cpp` | Custom LLVM pass to hoist/sink Julia-specific intrinsics | +| `llvm-late-gc-lowering.cpp` | Custom LLVM pass to root GC-tracked values | +| `llvm-lower-handlers.cpp` | Custom LLVM pass to lower try-catch blocks | +| `llvm-muladd.cpp` | Custom LLVM pass for fast-match FMA | +| `llvm-multiversioning.cpp` | Custom LLVM pass to generate sysimg code on multiple architectures | +| `llvm-propagate-addrspaces.cpp` | Custom LLVM pass to canonicalize addrspaces | +| `llvm-ptls.cpp` | Custom LLVM pass to lower TLS operations | +| `llvm-remove-addrspaces.cpp` | Custom LLVM pass to remove Julia addrspaces | +| `llvm-remove-ni.cpp` | Custom LLVM pass to remove Julia non-integral addrspaces | +| `llvm-simdloop.cpp` | Custom LLVM pass for [`@simd`](@ref) | +| `pipeline.cpp` | New pass manager pipeline, pass pipeline parsing | +| `sys.c` | I/O and operating system utility functions | Some of the `.cpp` files form a group that compile to a single object. @@ -38,7 +56,7 @@ The `-O` option enables LLVM's [Basic Alias Analysis](https://llvm.org/docs/Alia ## Building Julia with a different version of LLVM -The default version of LLVM is specified in `deps/Versions.make`. You can override it by creating +The default version of LLVM is specified in `deps/llvm.version`. You can override it by creating a file called `Make.user` in the top-level directory and adding a line to it such as: ``` @@ -64,6 +82,8 @@ Here are example settings using `bash` syntax: * `export JULIA_LLVM_ARGS=-debug-only=loop-vectorize` dumps LLVM `DEBUG(...)` diagnostics for loop vectorizer. If you get warnings about "Unknown command line argument", rebuild LLVM with `LLVM_ASSERTIONS = 1`. + * `export JULIA_LLVM_ARGS=-help` shows a list of available options. + * `export JULIA_LLVM_ARGS="-fatal-warnings -print-options"` is an example how to use multiple options. ## Debugging LLVM transformations in isolation @@ -77,12 +97,18 @@ LLVM tools as usual. `libjulia` can function as an LLVM pass plugin and can be loaded into LLVM tools, to make julia-specific passes available in this environment. In addition, it exposes the `-julia` meta-pass, which runs the entire Julia pass-pipeline over the IR. As an example, to generate a system -image, one could do: +image with the old pass manager, one could do: ``` opt -enable-new-pm=0 -load libjulia-codegen.so -julia -o opt.bc unopt.bc llc -o sys.o opt.bc cc -shared -o sys.so sys.o ``` +To generate a system image with the new pass manager, one could do: +``` +opt -load-pass-plugin=libjulia-codegen.so --passes='julia' -o opt.bc unopt.bc +llc -o sys.o opt.bc +cc -shared -o sys.so sys.o +``` This system image can then be loaded by `julia` as usual. It is also possible to dump an LLVM IR module for just one Julia function, @@ -127,15 +153,11 @@ array. However, this would betray the SSA nature of the uses at the call site, making optimizations (including GC root placement), significantly harder. Instead, we emit it as follows: ```llvm -%bitcast = bitcast @any_unoptimized_call to %jl_value_t *(*)(%jl_value_t *, %jl_value_t *) -call cc 37 %jl_value_t *%bitcast(%jl_value_t *%arg1, %jl_value_t *%arg2) +call %jl_value_t *@julia.call(jl_value_t *(*)(...) @any_unoptimized_call, %jl_value_t *%arg1, %jl_value_t *%arg2) ``` -The special `cc 37` annotation marks the fact that this call site is really using -the jlcall calling convention. This allows us to retain the SSA-ness of the +This allows us to retain the SSA-ness of the uses throughout the optimizer. GC root placement will later lower this call to -the original C ABI. In the code the calling convention number is represented by -the `JLCALL_F_CC` constant. In addition, there is the `JLCALL_CC` calling -convention which functions similarly, but omits the first argument. +the original C ABI. ## GC root placement diff --git a/doc/src/devdocs/locks.md b/doc/src/devdocs/locks.md index f01209cc73e52..bef1419b1c8f8 100644 --- a/doc/src/devdocs/locks.md +++ b/doc/src/devdocs/locks.md @@ -28,13 +28,22 @@ The following are definitely leaf locks (level 1), and must not try to acquire a > * gc_perm_lock > * flisp > * jl_in_stackwalk (Win32) +> * ResourcePool::mutex +> * RLST_mutex +> * jl_locked_stream::mutex +> * debuginfo_asyncsafe +> * inference_timing_mutex +> * ExecutionEngine::SessionLock > > > flisp itself is already threadsafe, this lock only protects the `jl_ast_context_list_t` pool +> > likewise, the ResourcePool::mutexes just protect the associated resource pool The following is a leaf lock (level 2), and only acquires level 1 locks (safepoint) internally: > * typecache > * Module->lock +> * JLDebuginfoPlugin::PluginMutex +> * newly_inferred_mutex The following is a level 3 lock, which can only acquire level 1 or level 2 locks internally: @@ -46,6 +55,18 @@ The following is a level 4 lock, which can only recurse to acquire level 1, 2, o No Julia code may be called while holding a lock above this point. +orc::ThreadSafeContext (TSCtx) locks occupy a special spot in the locking hierarchy. They are used to +protect LLVM's global non-threadsafe state, but there may be an arbitrary number of them. By default, +all of these locks may be treated as level 5 locks for the purposes of comparing with the rest of the +hierarchy. Acquiring a TSCtx should only be done from the JIT's pool of TSCtx's, and all locks on +that TSCtx should be released prior to returning it to the pool. If multiple TSCtx locks must be +acquired at the same time (due to recursive compilation), then locks should be acquired in the order +that the TSCtxs were borrowed from the pool. + +The following is a level 5 lock + +> * JuliaOJIT::EmissionMutex + The following are a level 6 lock, which can only recurse to acquire locks at lower levels: > * codegen @@ -112,7 +133,7 @@ These data structures each need locks due to being shared mutable global state. list for the above lock priority list. This list does not include level 1 leaf resources due to their simplicity. -MethodTable modifications (def, cache, kwsorter type) : MethodTable->writelock +MethodTable modifications (def, cache) : MethodTable->writelock Type declarations : toplevel lock diff --git a/doc/src/devdocs/object.md b/doc/src/devdocs/object.md index 8cba7c8ba4500..caba6c3f12190 100644 --- a/doc/src/devdocs/object.md +++ b/doc/src/devdocs/object.md @@ -189,6 +189,8 @@ then tagged with its type: jl_value_t *jl_gc_allocobj(size_t nbytes); void jl_set_typeof(jl_value_t *v, jl_datatype_t *type); ``` +!!! note "Out of date Warning" + The documentation and usage for the function `jl_gc_allocobj` may be out of date Note that all objects are allocated in multiples of 4 bytes and aligned to the platform pointer size. Memory is allocated from a pool for smaller objects, or directly with `malloc()` for large @@ -199,4 +201,3 @@ objects. 0 bytes, and consist only of their metadata. e.g. `nothing::Nothing`. See [Singleton Types](@ref man-singleton-types) and [Nothingness and missing values](@ref) - diff --git a/doc/src/devdocs/pkgimg.md b/doc/src/devdocs/pkgimg.md new file mode 100644 index 0000000000000..f97fc36750f18 --- /dev/null +++ b/doc/src/devdocs/pkgimg.md @@ -0,0 +1,49 @@ +# Package Images + +Julia package images provide object (native code) caches for Julia packages. +They are similar to Julia's [system image](@ref dev-sysimg) and support many of the same features. +In fact the underlying serialization format is the same, and the system image is the base image that the package images are build against. + +## High-level overview + +Package images are shared libraries that contain both code and data. Like `.ji` cache files, they are generated per package. The data section contains both global data (global variables in the package) as well as the necessary metadata about what methods and types are defined by the package. The code section contains native objects that cache the final output of Julia's LLVM-based compiler. + +The command line option `--pkgimages=no` can be used to turn off object caching for this session. Note that this means that cache files have to likely be regenerated. +See [`JULIA_MAX_NUM_PRECOMPILE_FILES`](@ref env-max-num-precompile-files) for the upper limit of variants Julia caches per default. + +!!! note + While the package images present themselves as native shared libraries, they are only an approximation thereof. You will not be able to link against them from a native program and they must be loaded from Julia. + + +## Linking + +Since the package images contain native code, we must run a linker over them before we can use them. You can set the environment variable `JULIA_VERBOSE_LINKING` to `true` to make the package image linking process verbose. + +Furthermore, we cannot assume that the user has a working system linker installed. Therefore, Julia ships with LLD, the LLVM linker, to provide a working out of the box experience. In `base/linking.jl`, we implement a limited interface to be able to link package images on all supported platforms. + +### Quirks +Despite LLD being a multi-platform linker, it does not provide a consistent interface across platforms. Furthermore, it is meant to be used from `clang` or +another compiler driver, we therefore reimplement some of the logic from `llvm-project/clang/lib/Driver/ToolChains`. Thankfully one can use `lld -flavor` to set lld to the right platform + +#### Windows +To avoid having to deal with `link.exe` we use `-flavor gnu`, effectively turning `lld` into a cross-linker from a mingw32 environment. Windows DLLs are required to contain a `_DllMainCRTStartup` function and to minimize our dependence on mingw32 libraries, we inject a stub definition ourselves. + +#### MacOS +Dynamic libraries on macOS need to link against `-lSystem`. On recent macOS versions, `-lSystem` is only available for linking when Xcode is available. +To that effect we link with `-undefined dynamic_lookup`. + +## Package images optimized for multiple microarchitectures +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, +you can set the environment variable `JULIA_CPU_TARGET=generic` to multi-version the object caches. + +## Flags that impact package image creation and selection + +These are the Julia command line flags that impact cache selection. Package images +that were created with different flags will be rejected. + +- `-g`, `--debug-info`: Exact match required since it changes code generation. +- `--check-bounds`: Exact match required since it changes code generation. +- `--inline`: Exact match required since it changes code generation. +- `--pkgimages`: To allow running without object caching enabled. +- `-O`, `--optimize`: Reject package images generated for a lower optimization level, + but allow for higher optimization levels to be loaded. diff --git a/doc/src/devdocs/probes.md b/doc/src/devdocs/probes.md index c5cd21b7c91db..d15723e945462 100644 --- a/doc/src/devdocs/probes.md +++ b/doc/src/devdocs/probes.md @@ -13,40 +13,42 @@ and with instrumentation off the overhead is minimal. ## Enabling support -On Linux install the `systemtap` package that has a version of `dtrace`. +On Linux install the `systemtap` package that has a version of `dtrace` and create a `Make.user` file containing ``` WITH_DTRACE=1 ``` +to enable USDT probes. + ### Verifying ``` > readelf -n usr/lib/libjulia-internal.so.1 Displaying notes found in: .note.gnu.build-id - Owner Data size Description - GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) + Owner Data size Description + GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: 57161002f35548772a87418d2385c284ceb3ead8 Displaying notes found in: .note.stapsdt - Owner Data size Description - stapsdt 0x00000029 NT_STAPSDT (SystemTap probe descriptors) + Owner Data size Description + stapsdt 0x00000029 NT_STAPSDT (SystemTap probe descriptors) Provider: julia Name: gc__begin Location: 0x000000000013213e, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cac Arguments: - stapsdt 0x00000032 NT_STAPSDT (SystemTap probe descriptors) + stapsdt 0x00000032 NT_STAPSDT (SystemTap probe descriptors) Provider: julia Name: gc__stop_the_world Location: 0x0000000000132144, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cae Arguments: - stapsdt 0x00000027 NT_STAPSDT (SystemTap probe descriptors) + stapsdt 0x00000027 NT_STAPSDT (SystemTap probe descriptors) Provider: julia Name: gc__end Location: 0x000000000013214a, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cb0 Arguments: - stapsdt 0x0000002d NT_STAPSDT (SystemTap probe descriptors) + stapsdt 0x0000002d NT_STAPSDT (SystemTap probe descriptors) Provider: julia Name: gc__finalizer Location: 0x0000000000132150, Base: 0x00000000002bb4da, Semaphore: 0x0000000000346cb2 @@ -306,7 +308,7 @@ An example probe in the bpftrace format looks like: ``` usdt:usr/lib/libjulia-internal.so:julia:gc__begin { - @start[pid] = nsecs; + @start[pid] = nsecs; } ``` diff --git a/doc/src/devdocs/reflection.md b/doc/src/devdocs/reflection.md index ec307012c17d5..e9da82475fd68 100644 --- a/doc/src/devdocs/reflection.md +++ b/doc/src/devdocs/reflection.md @@ -131,7 +131,7 @@ top: } ``` -For more informations see [`@code_lowered`](@ref), [`@code_typed`](@ref), [`@code_warntype`](@ref), +For more information see [`@code_lowered`](@ref), [`@code_typed`](@ref), [`@code_warntype`](@ref), [`@code_llvm`](@ref), and [`@code_native`](@ref). ### Printing of debug information @@ -151,4 +151,3 @@ CodeInfo( Possible values for `debuginfo` are: `:none`, `:source`, and `:default`. Per default debug information is not printed, but that can be changed by setting `Base.IRShow.default_debuginfo[] = :source`. - diff --git a/doc/src/devdocs/sanitizers.md b/doc/src/devdocs/sanitizers.md index fa359ca29b17e..5eaf4b45d9f57 100644 --- a/doc/src/devdocs/sanitizers.md +++ b/doc/src/devdocs/sanitizers.md @@ -1,5 +1,24 @@ # Sanitizer support +[Sanitizers](https://github.com/google/sanitizers) can be used in custom Julia builds to make it +easier to detect certain kinds of errors in Julia's internal C/C++ code. + +## Address Sanitizer: easy build + +From a source-checkout of Julia, you should be able to build a version +supporting address sanitization in Julia and LLVM as follows: + +```sh +$ mkdir /tmp/julia +$ contrib/asan/build.sh /tmp/julia/ +``` + +Here we've chosen `/tmp/julia` as a build directory, but you can +choose whatever you wish. Once built, run the workload you wish to +test with `/tmp/julia/julia`. Memory bugs will result in errors. + +If you require customization or further detail, see the documentation below. + ## General considerations Using Clang's sanitizers obviously requires you to use Clang (`USECLANG=1`), but there's another diff --git a/doc/src/devdocs/ssair.md b/doc/src/devdocs/ssair.md index 46d33c177f469..6d3de6d1f5758 100644 --- a/doc/src/devdocs/ssair.md +++ b/doc/src/devdocs/ssair.md @@ -3,7 +3,7 @@ ## Background Beginning in Julia 0.7, parts of the compiler use a new [SSA-form](https://en.wikipedia.org/wiki/Static_single_assignment_form) -intermediate representation. Historically, the compiler would directly generate LLVM IR from a lowered form of the Julia +intermediate representation (IR). Historically, the compiler would directly generate LLVM IR from a lowered form of the Julia AST. This form had most syntactic abstractions removed, but still looked a lot like an abstract syntax tree. Over time, in order to facilitate optimizations, SSA values were introduced to this IR and the IR was linearized (i.e. turned into a form where function arguments could only be SSA values or constants). However, non-SSA values @@ -23,7 +23,7 @@ Phi nodes are part of generic SSA abstraction (see the link above if you're not the concept). In the Julia IR, these nodes are represented as: ``` struct PhiNode - edges::Vector{Int} + edges::Vector{Int32} values::Vector{Any} end ``` diff --git a/doc/src/devdocs/sysimg.md b/doc/src/devdocs/sysimg.md index 5c976875846d3..6706e30ce97b1 100644 --- a/doc/src/devdocs/sysimg.md +++ b/doc/src/devdocs/sysimg.md @@ -8,6 +8,9 @@ as many platforms as possible, so as to give vastly improved startup times. On not ship with a precompiled system image file, one can be generated from the source files shipped in Julia's `DATAROOTDIR/julia/base` folder. +Julia will by default generate its system image on half of the available system threads. This +may be controlled by the [`JULIA_IMAGE_THREADS`](@ref env-image-threads) environment variable. + This operation is useful for multiple reasons. A user may: * Build a precompiled shared library system image on a platform that did not ship with one, thereby @@ -19,7 +22,7 @@ This operation is useful for multiple reasons. A user may: The [`PackageCompiler.jl` package](https://github.com/JuliaLang/PackageCompiler.jl) contains convenient wrapper functions to automate this process. -## System image optimized for multiple microarchitectures +## [System image optimized for multiple microarchitectures](@id sysimg-multi-versioning) The system image can be compiled simultaneously for multiple CPU microarchitectures under the same instruction set architecture (ISA). Multiple versions of the same function @@ -101,7 +104,7 @@ See code comments for each components for more implementation details. (see comments in `MultiVersioning::runOnModule` for how this is done), the pass also generates metadata so that the runtime can load and initialize the system image correctly. - A detail description of the metadata is available in `src/processor.h`. + A detailed description of the metadata is available in `src/processor.h`. 2. System image loading diff --git a/doc/src/devdocs/tracy.png b/doc/src/devdocs/tracy.png new file mode 100644 index 0000000000000..a0371be9db63e Binary files /dev/null and b/doc/src/devdocs/tracy.png differ diff --git a/doc/src/devdocs/types.md b/doc/src/devdocs/types.md index 003574f99c182..c3afc26600c65 100644 --- a/doc/src/devdocs/types.md +++ b/doc/src/devdocs/types.md @@ -198,7 +198,6 @@ TypeName defs: Nothing nothing cache: Nothing nothing max_args: Int64 0 - kwsorter: #undef module: Module Core : Int64 0 : Int64 0 diff --git a/doc/src/devdocs/valgrind.md b/doc/src/devdocs/valgrind.md index 8a11cb411a6fd..7e62aeb176f3c 100644 --- a/doc/src/devdocs/valgrind.md +++ b/doc/src/devdocs/valgrind.md @@ -16,7 +16,7 @@ memory pools disabled. The compile-time flag `MEMDEBUG` disables memory pools i `MEMDEBUG2` disables memory pools in FemtoLisp. To build `julia` with both flags, add the following line to `Make.user`: -```julia +```make CFLAGS = -DMEMDEBUG -DMEMDEBUG2 ``` @@ -55,6 +55,32 @@ valgrind --smc-check=all-non-file --trace-children=yes --suppressions=$PWD/../co If you would like to see a report of "definite" memory leaks, pass the flags `--leak-check=full --show-leak-kinds=definite` to `valgrind` as well. +## Additional spurious warnings + +This section covers Valgrind warnings which cannot be added to the +suppressions file yet are nonetheless safe to ignore. + +### Unhandled rr system calls + +Valgrind will emit a warning if it encounters any of the [system calls +that are specific to +rr](https://github.com/rr-debugger/rr/blob/master/src/preload/rrcalls.h), +the [Record and Replay Framework](https://rr-project.org/). In +particular, a warning about an unhandled `1008` syscall will be shown +when julia tries to detect whether it is running under rr: + +``` +--xxxxxx-- WARNING: unhandled amd64-linux syscall: 1008 +--xxxxxx-- You may be able to write your own handler. +--xxxxxx-- Read the file README_MISSING_SYSCALL_OR_IOCTL. +--xxxxxx-- Nevertheless we consider this a bug. Please report +--xxxxxx-- it at http://valgrind.org/support/bug_reports.html. +``` + +This issue +[has been reported](https://bugs.kde.org/show_bug.cgi?id=446401) +to the Valgrind developers as they have requested. + ## Caveats Valgrind currently [does not support multiple rounding modes](https://bugs.kde.org/show_bug.cgi?id=136779), diff --git a/doc/src/index.md b/doc/src/index.md index e9319b18a9041..bb758d14b4cf2 100644 --- a/doc/src/index.md +++ b/doc/src/index.md @@ -32,7 +32,19 @@ Markdown.parse(""" """) ``` -### [Introduction](@id man-introduction) +## [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. + +- [Julia Homepage](https://julialang.org) +- [Download Julia](https://julialang.org/downloads/) +- [Discussion forum](https://discourse.julialang.org) +- [Julia YouTube](https://www.youtube.com/user/JuliaLanguage) +- [Find Julia Packages](https://julialang.org/packages/) +- [Learning Resources](https://julialang.org/learning/) +- [Read and write blogs on Julia](https://forem.julialang.org) + +## [Introduction](@id man-introduction) Scientific computing has traditionally required the highest performance, yet domain experts have largely moved to slower dynamic languages for daily work. We believe there are many good reasons @@ -46,10 +58,13 @@ with performance comparable to traditional statically-typed languages. Because Julia's compiler is different from the interpreters used for languages like Python or R, you may find that Julia's performance is unintuitive at first. If you find that something is slow, we highly recommend reading through the [Performance Tips](@ref man-performance-tips) section before trying anything -else. Once you understand how Julia works, it's easy to write code that's nearly as fast as C. +else. Once you understand how Julia works, it is easy to write code that is nearly as fast as C. + +## [Julia Compared to Other Languages](@id man-julia-compared-other-languages) Julia features optional typing, multiple dispatch, and good performance, achieved using type inference -and [just-in-time (JIT) compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation), +and [just-in-time (JIT) compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation) (and +[optional ahead-of-time compilation](https://github.com/JuliaLang/PackageCompiler.jl)), implemented using [LLVM](https://en.wikipedia.org/wiki/Low_Level_Virtual_Machine). It is multi-paradigm, combining features of imperative, functional, and object-oriented programming. Julia provides ease and expressiveness for high-level numerical computing, in the same way as languages such @@ -69,14 +84,16 @@ The most significant departures of Julia from typical dynamic languages are: * Automatic generation of efficient, specialized code for different argument types * Good performance, approaching that of statically-compiled languages like C -Although one sometimes speaks of dynamic languages as being "typeless", they are definitely not: -every object, whether primitive or user-defined, has a type. The lack of type declarations in +Although one sometimes speaks of dynamic languages as being "typeless", they are definitely not. +Every object, whether primitive or user-defined, has a type. The lack of type declarations in most dynamic languages, however, means that one cannot instruct the compiler about the types of values, and often cannot explicitly talk about types at all. In static languages, on the other hand, while one can -- and usually must -- annotate types for the compiler, types exist only at compile time and cannot be manipulated or expressed at run time. In Julia, types are themselves run-time objects, and can also be used to convey information to the compiler. +### [What Makes Julia, Julia?](@id man-what-makes-julia) + While the casual programmer need not explicitly use types or multiple dispatch, they are the core unifying features of Julia: functions are defined on different combinations of argument types, and applied by dispatching to the most specific matching definition. This model is a good fit @@ -92,6 +109,8 @@ languages. For large scale numerical problems, speed always has been, continues always will be crucial: the amount of data being processed has easily kept pace with Moore's Law over the past decades. +### [Advantages of Julia](@id man-advantages-of-julia) + Julia aims to create an unprecedented combination of ease-of-use, power, and efficiency in a single language. In addition to the above, some advantages of Julia over comparable systems include: diff --git a/doc/src/manual/arrays.md b/doc/src/manual/arrays.md index f6e4350726269..f9e60d83ff052 100644 --- a/doc/src/manual/arrays.md +++ b/doc/src/manual/arrays.md @@ -1,4 +1,4 @@ -# [Multi-dimensional Arrays](@id man-multi-dim-arrays) +# [Single- and multi-dimensional Arrays](@id man-multi-dim-arrays) Julia, like most technical computing languages, provides a first-class array implementation. Most technical computing languages pay a lot of attention to their array implementation at the expense @@ -26,7 +26,7 @@ it makes avoiding unwanted copying of arrays difficult. By convention, a function name ending with a `!` indicates that it will mutate or destroy the value of one or more of its arguments (compare, for example, [`sort`](@ref) and [`sort!`](@ref)). Callees must make explicit copies to ensure that they don't modify inputs that -they don't intend to change. Many non- mutating functions are implemented by +they don't intend to change. Many non-mutating functions are implemented by calling a function of the same name with an added `!` at the end on an explicit copy of the input, and returning that copy. @@ -65,7 +65,7 @@ omitted it will default to [`Float64`](@ref). | [`deepcopy(A)`](@ref) | copy `A`, recursively copying its elements | | [`similar(A, T, dims...)`](@ref) | an uninitialized array of the same type as `A` (dense, sparse, etc.), but with the specified element type and dimensions. The second and third arguments are both optional, defaulting to the element type and dimensions of `A` if omitted. | | [`reinterpret(T, A)`](@ref) | an array with the same binary data as `A`, but with element type `T` | -| [`rand(T, dims...)`](@ref) | an `Array` with random, iid [^1] and uniformly distributed values in the half-open interval ``[0, 1)`` | +| [`rand(T, dims...)`](@ref) | an `Array` with random, iid [^1] and uniformly distributed values. For floating point types `T`, the values lie in the half-open interval ``[0, 1)``. | | [`randn(T, dims...)`](@ref) | an `Array` with random, iid and standard normally distributed values | | [`Matrix{T}(I, m, n)`](@ref) | `m`-by-`n` identity matrix. Requires `using LinearAlgebra` for [`I`](@ref). | | [`range(start, stop, n)`](@ref) | a range of `n` linearly spaced elements from `start` to `stop` | @@ -103,7 +103,8 @@ same type, then that is its `eltype`. If they all have a common [promotion type](@ref conversion-and-promotion) then they get converted to that type using [`convert`](@ref) and that type is the array's `eltype`. Otherwise, a heterogeneous array that can hold anything — a `Vector{Any}` — is constructed; this includes the literal `[]` -where no arguments are given. +where no arguments are given. [Array literal can be typed](@ref man-array-typed-literal) with +the syntax `T[A, B, C, ...]` where `T` is a type. ```jldoctest julia> [1,2,3] # An array of `Int`s @@ -121,6 +122,12 @@ julia> [1, 2.3, 4//5] # Thus that's the element type of this Array 2.3 0.8 +julia> Float32[1, 2.3, 4//5] # Specify element type manually +3-element Vector{Float32}: + 1.0 + 2.3 + 0.8 + julia> [] Any[] ``` @@ -324,7 +331,7 @@ These syntaxes are shorthands for function calls that themselves are convenience | `[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 | -### Typed array literals +### [Typed array literals](@id man-array-typed-literal) An array with a specific element type can be constructed using the syntax `T[A, B, C, ...]`. This will construct a 1-d array with element type `T`, initialized to contain elements `A`, `B`, `C`, @@ -646,7 +653,7 @@ indices and can be converted to such by [`to_indices`](@ref): * [`CartesianIndex{N}`](@ref)s, which behave like an `N`-tuple of integers spanning multiple dimensions (see below for more details) 2. An array of scalar indices. This includes: * Vectors and multidimensional arrays of integers - * Empty arrays like `[]`, which select no elements + * Empty arrays like `[]`, which select no elements e.g. `A[[]]` (not to be confused with `A[]`) * Ranges like `a:c` or `a:b:c`, which select contiguous or strided subsections from `a` to `c` (inclusive) * Any custom array of scalar indices that is a subtype of `AbstractArray` * Arrays of `CartesianIndex{N}` (see below for more details) @@ -872,10 +879,15 @@ slower than multiplication. While some arrays — like [`Array`](@ref) itself are implemented using a linear chunk of memory and directly use a linear index 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). As such, when iterating over an entire array, it's -much better to iterate over [`eachindex(A)`](@ref) instead of `1:length(A)`. -Not only will the former be much faster in cases where `A` is `IndexCartesian`, -but it will also support [OffsetArrays](https://github.com/JuliaArrays/OffsetArrays.jl), too. +introspect which is which). + +!!! warnings + + When iterating over all the indices for an array, it is + better to iterate over [`eachindex(A)`](@ref) instead of `1:length(A)`. + Not only will this be faster in cases where `A` is `IndexCartesian`, + but it will also support arrays with custom indexing, such as [OffsetArrays](https://github.com/JuliaArrays/OffsetArrays.jl). + If only the values are needed, then is better to just iterate the array directly, i.e. `for a in A`. #### Omitted and extra indices @@ -967,8 +979,11 @@ i = CartesianIndex(2, 2) i = CartesianIndex(3, 2) ``` -In contrast with `for i = 1:length(A)`, iterating with [`eachindex`](@ref) provides an efficient way to -iterate over any array type. +!!! note + + In contrast with `for i = 1:length(A)`, iterating with [`eachindex`](@ref) provides an efficient way to + iterate over any array type. Besides, this also supports generic arrays with custom indexing such as + [OffsetArrays](https://github.com/JuliaArrays/OffsetArrays.jl). ## Array traits diff --git a/doc/src/manual/asynchronous-programming.md b/doc/src/manual/asynchronous-programming.md index 4eee0fccf7da2..5b43ba971ee1c 100644 --- a/doc/src/manual/asynchronous-programming.md +++ b/doc/src/manual/asynchronous-programming.md @@ -289,7 +289,7 @@ julia> @elapsed while n > 0 # print out results 0.029772311 ``` -Instead of `errormonitor(t)`, a more robust solution may be use use `bind(results, t)`, as that will +Instead of `errormonitor(t)`, a more robust solution may be to use `bind(results, t)`, as that will not only log any unexpected failures, but also force the associated resources to close and propagate the exception everywhere. diff --git a/doc/src/manual/calling-c-and-fortran-code.md b/doc/src/manual/calling-c-and-fortran-code.md index 5529018217c1a..eab901adc2043 100644 --- a/doc/src/manual/calling-c-and-fortran-code.md +++ b/doc/src/manual/calling-c-and-fortran-code.md @@ -4,8 +4,8 @@ Though most code can be written in Julia, there are many high-quality, mature li computing already written in C and Fortran. To allow easy use of this existing code, Julia makes it simple and efficient to call C and Fortran functions. Julia has a "no boilerplate" philosophy: functions can be called directly from Julia without any "glue" code, code generation, or compilation --- even from the interactive prompt. This is accomplished just by making an appropriate call with -[`ccall`](@ref) syntax, which looks like an ordinary function call. +-- even from the interactive prompt. This is accomplished just by making an appropriate call with the +[`@ccall`](@ref) macro (or the less convenient [`ccall`](@ref) syntax, see the [`ccall` syntax section](@ref ccall-interface)). The code to be called must be available as a shared library. Most C and Fortran libraries ship compiled as shared libraries already, but if you are compiling the code yourself using GCC (or @@ -13,50 +13,34 @@ Clang), you will need to use the `-shared` and `-fPIC` options. The machine inst by Julia's JIT are the same as a native C call would be, so the resulting overhead is the same as calling a library function from C code. [^1] -Shared libraries and functions are referenced by a tuple of the form `(:function, "library")` -or `("function", "library")` where `function` is the C-exported function name, and `library` refers -to the shared library name. Shared libraries available in the (platform-specific) load path will -be resolved by name. The full path to the library may also be specified. - -A function name may be used alone in place of the tuple (just `:function` or `"function"`). In -this case the name is resolved within the current process. This form can be used to call C library -functions, functions in the Julia runtime, or functions in an application linked to Julia. - By default, Fortran compilers [generate mangled names](https://en.wikipedia.org/wiki/Name_mangling#Fortran) (for example, converting function names to lowercase or uppercase, often appending an -underscore), and so to call a Fortran function via [`ccall`](@ref) you must pass +underscore), and so to call a Fortran function you must pass the mangled identifier corresponding to the rule followed by your Fortran -compiler. Also, when calling a Fortran function, all inputs must be passed as +compiler. Also, when calling a Fortran function, all inputs must be passed as pointers to allocated values on the heap or stack. This applies not only to arrays and other mutable objects which are normally heap-allocated, but also to scalar values such as integers and floats which are normally stack-allocated and commonly passed in registers when using C or Julia calling conventions. -Finally, you can use [`ccall`](@ref) to actually generate a call to the library function. The arguments -to [`ccall`](@ref) are: - -1. A `(:function, "library")` pair (most common), - - OR - - a `:function` name symbol or `"function"` name string (for symbols in the current process or libc), - - OR - - a function pointer (for example, from `dlsym`). - -2. The function's return type - -3. A tuple of input types, corresponding to the function signature - -4. The actual argument values to be passed to the function, if any; each is a separate parameter. +The syntax for [`@ccall`](@ref) to generate a call to the library function is: -!!! note - The `(:function, "library")` pair, return type, and input types must be literal constants - (i.e., they can't be variables, but see [Non-constant Function Specifications](@ref) below). +```julia + @ccall library.function_name(argvalue1::argtype1, ...)::returntype + @ccall function_name(argvalue1::argtype1, ...)::returntype + @ccall $function_pointer(argvalue1::argtype1, ...)::returntype +``` - The remaining parameters are evaluated at compile time, when the containing method is defined. +where `library` is a string constant or literal (but see [Non-constant Function +Specifications](@ref) below). The library may be omitted, in which case the +function name is resolved in the current process. This form can be used to call +C library functions, functions in the Julia runtime, or functions in an +application linked to Julia. The full path to the library may also be specified. +Alternatively, `@ccall` may also be used to call a function pointer +`$function_pointer`, such as one returned by `Libdl.dlsym`. The `argtype`s +corresponds to the C-function signature and the `argvalue`s are the actual +argument values to be passed to the function. !!! note See below for how to [map C types to Julia types](@ref mapping-c-types-to-julia). @@ -65,41 +49,25 @@ As a complete but simple example, the following calls the `clock` function from library on most Unix-derived systems: ```julia-repl -julia> t = ccall(:clock, Int32, ()) -2292761 - -julia> t +julia> t = @ccall clock()::Int32 2292761 julia> typeof(t) Int32 ``` -`clock` takes no arguments and returns an [`Int32`](@ref). One common mistake is forgetting that a 1-tuple of -argument types must be written with a trailing comma. For example, to call the `getenv` function +`clock` takes no arguments and returns an `Int32`. To call the `getenv` function to get a pointer to the value of an environment variable, one makes a call like this: ```julia-repl -julia> path = ccall(:getenv, Cstring, (Cstring,), "SHELL") +julia> path = @ccall getenv("SHELL"::Cstring)::Cstring Cstring(@0x00007fff5fbffc45) julia> unsafe_string(path) "/bin/bash" ``` -Note that the argument type tuple must be written as `(Cstring,)`, not `(Cstring)`. This -is because `(Cstring)` is just the expression `Cstring` surrounded by parentheses, rather than -a 1-tuple containing `Cstring`: - -```jldoctest -julia> (Cstring) -Cstring - -julia> (Cstring,) -(Cstring,) -``` - -In practice, especially when providing reusable functionality, one generally wraps [`ccall`](@ref) +In practice, especially when providing reusable functionality, one generally wraps `@ccall` uses in Julia functions that set up arguments and then check for errors in whatever manner the C or Fortran function specifies. And if an error occurs it is thrown as a normal Julia exception. This is especially important since C and Fortran APIs are notoriously inconsistent about how they indicate error @@ -108,7 +76,7 @@ which is a simplified version of the actual definition from [`env.jl`](https://g ```julia function getenv(var::AbstractString) - val = ccall(:getenv, Cstring, (Cstring,), var) + val = @ccall getenv(var::Cstring)::Cstring if val == C_NULL error("getenv: undefined variable: ", var) end @@ -116,9 +84,9 @@ function getenv(var::AbstractString) end ``` -The C `getenv` function indicates an error by returning `NULL`, but other standard C functions -indicate errors in various different ways, including by returning -1, 0, 1 and other special values. -This wrapper throws an exception clearly indicating the problem if the caller tries to get a non-existent +The C `getenv` function indicates an error by returning `C_NULL`, but other standard C functions +indicate errors in different ways, including by returning -1, 0, 1, and other special values. +This wrapper throws an exception indicating the problem if the caller tries to get a non-existent environment variable: ```julia-repl @@ -126,20 +94,15 @@ julia> getenv("SHELL") "/bin/bash" julia> getenv("FOOBAR") -getenv: undefined variable: FOOBAR +ERROR: getenv: undefined variable: FOOBAR ``` Here is a slightly more complex example that discovers the local machine's hostname. -In this example, the networking library code is assumed to be in a shared library named "libc". -In practice, this function is usually part of the C standard library, and so the "libc" -portion should be omitted, but we wish to show here the usage of this syntax. ```julia function gethostname() hostname = Vector{UInt8}(undef, 256) # MAXHOSTNAMELEN - err = ccall((:gethostname, "libc"), Int32, - (Ptr{UInt8}, Csize_t), - hostname, sizeof(hostname)) + err = @ccall gethostname(hostname::Ptr{UInt8}, sizeof(hostname)::Csize_t)::Int32 Base.systemerror("gethostname", err != 0) hostname[end] = 0 # ensure null-termination return GC.@preserve hostname unsafe_string(pointer(hostname)) @@ -148,19 +111,39 @@ end This example first allocates an array of bytes. It then calls the C library function `gethostname` to populate the array with the hostname. Finally, it takes a pointer to the hostname buffer, and -converts the pointer to a Julia string, assuming that it is a NUL-terminated C string. +converts the pointer to a Julia string, assuming that it is a null-terminated C string. It is common for C libraries to use this pattern of requiring the caller to allocate memory to be passed to the callee and populated. Allocation of memory from Julia like this is generally accomplished by creating an uninitialized array and passing a pointer to its data to the C function. This is why we don't use the `Cstring` type here: as the array is uninitialized, it could contain -NUL bytes. Converting to a `Cstring` as part of the [`ccall`](@ref) checks for contained NUL bytes +null bytes. Converting to a `Cstring` as part of the `@ccall` checks for contained null bytes and could therefore throw a conversion error. Dereferencing `pointer(hostname)` with `unsafe_string` is an unsafe operation as it requires access to the memory allocated for `hostname` that may have been in the meanwhile garbage collected. The macro [`GC.@preserve`](@ref) prevents this from happening and therefore accessing an invalid memory location. +Finally, here is an example of specifying a library via a path. +We create a shared library with the following content + +```c +#include + +void say_y(int y) +{ + printf("Hello from C: got y = %d.\n", y); +} +``` + +and compile it with `gcc -fPIC -shared -o mylib.so mylib.c`. +It can then be called by specifying the (absolute) path as the library name: + +```julia-repl +julia> @ccall "./mylib.so".say_y(5::Cint)::Cvoid +Hello from C: got y = 5. +``` + ## Creating C-Compatible Julia Function Pointers It is possible to pass Julia functions to native C functions that accept function pointer arguments. @@ -178,7 +161,7 @@ Julia function. The arguments to [`@cfunction`](@ref) are: 3. A tuple of input types, corresponding to the function signature !!! note - As with `ccall`, the return type and tuple of input types must be literal constants. + As with `@ccall`, the return type and the input types must be literal constants. !!! note Currently, only the platform-default C calling convention is supported. This means that @@ -193,11 +176,11 @@ Julia function. The arguments to [`@cfunction`](@ref) are: A classic example is the standard C library `qsort` function, declared as: ```c -void qsort(void *base, size_t nmemb, size_t size, +void qsort(void *base, size_t nitems, size_t size, int (*compare)(const void*, const void*)); ``` -The `base` argument is a pointer to an array of length `nmemb`, with elements of `size` bytes +The `base` argument is a pointer to an array of length `nitems`, with elements of `size` bytes each. `compare` is a callback function which takes pointers to two elements `a` and `b` and returns an integer less/greater than zero if `a` should appear before/after `b` (or zero if any order is permitted). @@ -209,8 +192,7 @@ calling `qsort` and passing arguments, we need to write a comparison function: ```jldoctest mycompare julia> function mycompare(a, b)::Cint return (a < b) ? -1 : ((a > b) ? +1 : 0) - end -mycompare (generic function with 1 method) + end; ``` `qsort` expects a comparison function that return a C `int`, so we annotate the return type @@ -229,15 +211,9 @@ julia> mycompare_c = @cfunction(mycompare, Cint, (Ref{Cdouble}, Ref{Cdouble})); The final call to `qsort` looks like this: ```jldoctest mycompare -julia> A = [1.3, -2.7, 4.4, 3.1] -4-element Vector{Float64}: - 1.3 - -2.7 - 4.4 - 3.1 +julia> A = [1.3, -2.7, 4.4, 3.1]; -julia> ccall(:qsort, Cvoid, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Cvoid}), - A, length(A), sizeof(eltype(A)), mycompare_c) +julia> @ccall qsort(A::Ptr{Cdouble}, length(A)::Csize_t, sizeof(eltype(A))::Csize_t, mycompare_c::Ptr{Cvoid})::Cvoid julia> A 4-element Vector{Float64}: @@ -271,15 +247,16 @@ Julia automatically inserts calls to the [`Base.cconvert`](@ref) function to con to the specified type. For example, the following call: ```julia -ccall((:foo, "libfoo"), Cvoid, (Int32, Float64), x, y) +@ccall "libfoo".foo(x::Int32, y::Float64)::Cvoid ``` will behave as if it were written like this: ```julia -ccall((:foo, "libfoo"), Cvoid, (Int32, Float64), - Base.unsafe_convert(Int32, Base.cconvert(Int32, x)), - Base.unsafe_convert(Float64, Base.cconvert(Float64, y))) +@ccall "libfoo".foo( + Base.unsafe_convert(Int32, Base.cconvert(Int32, x))::Int32, + Base.unsafe_convert(Float64, Base.cconvert(Float64, y))::Float64 + )::Cvoid ``` [`Base.cconvert`](@ref) normally just calls [`convert`](@ref), but can be defined to return an @@ -345,7 +322,7 @@ same: that the element type of the array matches `T`, and the address of the first element is passed. Therefore, if an `Array` contains data in the wrong format, it will have to be explicitly converted - using a call such as `trunc(Int32, a)`. + using a call such as `trunc.(Int32, A)`. To pass an array `A` as a pointer of a different type *without* converting the data beforehand (for example, to pass a `Float64` array to a function that operates on uninterpreted bytes), you @@ -387,7 +364,7 @@ an `Int` in Julia). | `void` and `[[noreturn]]` or `_Noreturn` | | | `Union{}` | | `void*` | | | `Ptr{Cvoid}` (or similarly `Ref{Cvoid}`) | | `T*` (where T represents an appropriately defined type) | | | `Ref{T}` (T may be safely mutated only if T is an isbits type) | -| `char*` (or `char[]`, e.g. a string) | `CHARACTER*N` | | `Cstring` if NUL-terminated, or `Ptr{UInt8}` if not | +| `char*` (or `char[]`, e.g. a string) | `CHARACTER*N` | | `Cstring` if null-terminated, or `Ptr{UInt8}` if not | | `char**` (or `*char[]`) | | | `Ptr{Ptr{UInt8}}` | | `jl_value_t*` (any Julia Type) | | | `Any` | | `jl_value_t* const*` (a reference to a Julia value) | | | `Ref{Any}` (const, since mutation would require a write barrier, which is not possible to insert correctly) | @@ -396,13 +373,13 @@ an `Int` in Julia). | `...` (variadic function specification) | | | `; va_arg1::T, va_arg2::S, etc.` (only supported with `@ccall` macro) | The [`Cstring`](@ref) type is essentially a synonym for `Ptr{UInt8}`, except the conversion to `Cstring` -throws an error if the Julia string contains any embedded NUL characters (which would cause the -string to be silently truncated if the C routine treats NUL as the terminator). If you are passing -a `char*` to a C routine that does not assume NUL termination (e.g. because you pass an explicit -string length), or if you know for certain that your Julia string does not contain NUL and want +throws an error if the Julia string contains any embedded null characters (which would cause the +string to be silently truncated if the C routine treats null as the terminator). If you are passing +a `char*` to a C routine that does not assume null termination (e.g. because you pass an explicit +string length), or if you know for certain that your Julia string does not contain null and want to skip the check, you can use `Ptr{UInt8}` as the argument type. `Cstring` can also be used as the [`ccall`](@ref) return type, but in that case it obviously does not introduce any extra -checks and is only meant to improve readability of the call. +checks and is only meant to improve the readability of the call. **System Dependent Types** @@ -419,26 +396,26 @@ checks and is only meant to improve readability of the call. `Ref{..}` wrapper around their type specification. !!! warning - For string arguments (`char*`) the Julia type should be `Cstring` (if NUL- terminated data is + For string arguments (`char*`) the Julia type should be `Cstring` (if null-terminated data is expected), or either `Ptr{Cchar}` or `Ptr{UInt8}` otherwise (these two pointer types have the same effect), as described above, not `String`. Similarly, for array arguments (`T[]` or `T*`), the Julia type should again be `Ptr{T}`, not `Vector{T}`. !!! warning - Julia's `Char` type is 32 bits, which is not the same as the wide character type (`wchar_t` or + Julia's `Char` type is 32 bits, which is not the same as the wide-character type (`wchar_t` or `wint_t`) on all platforms. !!! warning A return type of `Union{}` means the function will not return, i.e., C++11 `[[noreturn]]` or C11 `_Noreturn` (e.g. `jl_throw` or `longjmp`). Do not use this for functions that return no value - (`void`) but do return, use `Cvoid` instead. + (`void`) but do return, for those, use `Cvoid` instead. !!! note For `wchar_t*` arguments, the Julia type should be [`Cwstring`](@ref) (if the C routine expects a - NUL-terminated string), or `Ptr{Cwchar_t}` otherwise. Note also that UTF-8 string data in Julia is - internally NUL-terminated, so it can be passed to C functions expecting NUL-terminated data without + null-terminated string), or `Ptr{Cwchar_t}` otherwise. Note also that UTF-8 string data in Julia is + internally null-terminated, so it can be passed to C functions expecting null-terminated data without making a copy (but using the `Cwstring` type will cause an error to be thrown if the string itself - contains NUL characters). + contains null characters). !!! note C functions that take an argument of type `char**` can be called by using a `Ptr{Ptr{UInt8}}` @@ -452,7 +429,7 @@ checks and is only meant to improve readability of the call. ```julia argv = [ "a.out", "arg1", "arg2" ] - ccall(:main, Int32, (Int32, Ptr{Ptr{UInt8}}), length(argv), argv) + @ccall main(length(argv)::Int32, argv::Ptr{Ptr{UInt8}})::Int32 ``` !!! note @@ -481,7 +458,7 @@ checks and is only meant to improve readability of the call. Fortran compilers *may* also add other hidden arguments for pointers, assumed-shape (`:`) and assumed-size (`*`) arrays. Such behaviour can be avoided by using `ISO_C_BINDING` and including `bind(c)` in the definition of the subroutine, which is strongly recommended for - interoperable code. In this case there will be no hidden arguments, at the cost of some + interoperable code. In this case, there will be no hidden arguments, at the cost of some language features (e.g. only `character(len=1)` will be permitted to pass strings). !!! note @@ -505,7 +482,7 @@ You can get an approximation of a `union` if you know, a priori, the field that the greatest size (potentially including padding). When translating your fields to Julia, declare the Julia field to be only of that type. -Arrays of parameters can be expressed with `NTuple`. For example, the struct in C notation written as +Arrays of parameters can be expressed with `NTuple`. For example, the struct in C notation is written as ```c struct B { @@ -546,7 +523,7 @@ unsafe_string(str + Core.sizeof(Cint), len) ### Type Parameters -The type arguments to `ccall` and `@cfunction` are evaluated statically, +The type arguments to `@ccall` and `@cfunction` are evaluated statically, when the method containing the usage is defined. They therefore must take the form of a literal tuple, not a variable, and cannot reference local variables. @@ -559,9 +536,9 @@ However, while the type layout must be known statically to compute the intended the static parameters of the function are considered to be part of this static environment. The static parameters of the function may be used as type parameters in the call signature, as long as they don't affect the layout of the type. -For example, `f(x::T) where {T} = ccall(:valid, Ptr{T}, (Ptr{T},), x)` +For example, `f(x::T) where {T} = @ccall valid(x::Ptr{T})::Ptr{T}` is valid, since `Ptr` is always a word-size primitive type. -But, `g(x::T) where {T} = ccall(:notvalid, T, (T,), x)` +But, `g(x::T) where {T} = @ccall notvalid(x::T)::T` is not valid, since the type layout of `T` is not known statically. ### SIMD Values @@ -569,7 +546,7 @@ is not valid, since the type layout of `T` is not known statically. Note: This feature is currently implemented on 64-bit x86 and AArch64 platforms only. If a C/C++ routine has an argument or return value that is a native SIMD type, the corresponding -Julia type is a homogeneous tuple of `VecElement` that naturally maps to the SIMD type. Specifically: +Julia type is a homogeneous tuple of `VecElement` that naturally maps to the SIMD type. Specifically: > * The tuple must be the same size as the SIMD type. For example, a tuple representing an `__m128` > on x86 must have a size of 16 bytes. @@ -596,18 +573,18 @@ a = m256(ntuple(i -> VecElement(sin(Float32(i))), 8)) b = m256(ntuple(i -> VecElement(cos(Float32(i))), 8)) function call_dist(a::m256, b::m256) - ccall((:dist, "libdist"), m256, (m256, m256), a, b) + @ccall "libdist".dist(a::m256, b::m256)::m256 end println(call_dist(a,b)) ``` -The host machine must have the requisite SIMD registers. For example, the code above will not +The host machine must have the requisite SIMD registers. For example, the code above will not work on hosts without AVX support. ### Memory Ownership -**malloc/free** +**`malloc`/`free`** Memory allocation and deallocation of such objects must be handled by calls to the appropriate cleanup routines in the libraries being used, just like in any C program. Do not try to free an @@ -615,13 +592,13 @@ object received from a C library with [`Libc.free`](@ref) in Julia, as this may being called via the wrong library and cause the process to abort. The reverse (passing an object allocated in Julia to be freed by an external library) is equally invalid. -### When to use T, Ptr{T} and Ref{T} +### When to use `T`, `Ptr{T}` and `Ref{T}` In Julia code wrapping calls to external C routines, ordinary (non-pointer) data should be declared -to be of type `T` inside the [`ccall`](@ref), as they are passed by value. For C code accepting +to be of type `T` inside the `@ccall`, as they are passed by value. For C code accepting pointers, [`Ref{T}`](@ref) should generally be used for the types of input arguments, allowing the use of pointers to memory managed by either Julia or C through the implicit call to [`Base.cconvert`](@ref). -In contrast, pointers returned by the C function called should be declared to be of output type +In contrast, pointers returned by the C function called should be declared to be of the output type [`Ptr{T}`](@ref), reflecting that the memory pointed to is managed by C only. Pointers contained in C structs should be represented as fields of type `Ptr{T}` within the corresponding Julia struct types designed to mimic the internal structure of corresponding C structs. @@ -633,7 +610,7 @@ Fortran subroutines, or a `T` for Fortran functions returning the type `T`. ## Mapping C Functions to Julia -### `ccall` / `@cfunction` argument translation guide +### `@ccall` / `@cfunction` argument translation guide For translating a C argument list to Julia: @@ -651,7 +628,7 @@ For translating a C argument list to Julia: * depends on how this parameter is used, first translate this to the intended pointer type, then determine the Julia equivalent using the remaining rules in this list - * this argument may be declared as `Ptr{Cvoid}`, if it really is just an unknown pointer + * this argument may be declared as `Ptr{Cvoid}` if it really is just an unknown pointer * `jl_value_t*` * `Any` @@ -683,7 +660,7 @@ For translating a C argument list to Julia: * not supported by `ccall` or `@cfunction` -### `ccall` / `@cfunction` return type translation guide +### `@ccall` / `@cfunction` return type translation guide For translating a C return type to Julia: @@ -704,7 +681,7 @@ For translating a C return type to Julia: * depends on how this parameter is used, first translate this to the intended pointer type, then determine the Julia equivalent using the remaining rules in this list - * this argument may be declared as `Ptr{Cvoid}`, if it really is just an unknown pointer + * this argument may be declared as `Ptr{Cvoid}` if it really is just an unknown pointer * `jl_value_t*` * `Any` @@ -725,20 +702,20 @@ For translating a C return type to Julia: * `Ptr{T}`, where `T` is the Julia type corresponding to `T` * `T (*)(...)` (e.g. a pointer to a function) - * `Ptr{Cvoid}` to call this directly from Julia you will need to pass this as the first argument to [`ccall`](@ref). + * `Ptr{Cvoid}` to call this directly from Julia you will need to pass this as the first argument to `@ccall`. See [Indirect Calls](@ref). ### Passing Pointers for Modifying Inputs Because C doesn't support multiple return values, often C functions will take pointers to data -that the function will modify. To accomplish this within a [`ccall`](@ref), you need to first +that the function will modify. To accomplish this within a `@ccall`, you need to first encapsulate the value inside a [`Ref{T}`](@ref) of the appropriate type. When you pass this `Ref` object as an argument, Julia will automatically pass a C pointer to the encapsulated data: ```julia width = Ref{Cint}(0) range = Ref{Cfloat}(0) -ccall(:foo, Cvoid, (Ref{Cint}, Ref{Cfloat}), width, range) +@ccall foo(width::Ref{Cint}, range::Ref{Cfloat})::Cvoid ``` Upon return, the contents of `width` and `range` can be retrieved (if they were changed by `foo`) @@ -755,12 +732,7 @@ end # The corresponding C signature is # gsl_permutation * gsl_permutation_alloc (size_t n); function permutation_alloc(n::Integer) - output_ptr = ccall( - (:gsl_permutation_alloc, :libgsl), # name of C function and library - Ptr{gsl_permutation}, # output type - (Csize_t,), # tuple of input types - n # name of Julia variable to pass in - ) + output_ptr = @ccall "libgsl".gsl_permutation_alloc(n::Csize_t)::Ptr{gsl_permutation} if output_ptr == C_NULL # Could not allocate memory throw(OutOfMemoryError()) end @@ -773,13 +745,13 @@ through `:libgsl`) defines an opaque pointer, `gsl_permutation *`, as the return function `gsl_permutation_alloc`. As user code never has to look inside the `gsl_permutation` struct, the corresponding Julia wrapper simply needs a new type declaration, `gsl_permutation`, that has no internal fields and whose sole purpose is to be placed in the type parameter of a -`Ptr` type. The return type of the [`ccall`](@ref) is declared as `Ptr{gsl_permutation}`, since +`Ptr` type. The return type of the [`ccall`](@ref) is declared as `Ptr{gsl_permutation}`, since the memory allocated and pointed to by `output_ptr` is controlled by C. The input `n` is passed by value, and so the function's input signature is -simply declared as `(Csize_t,)` without any `Ref` or `Ptr` necessary. (If the +simply declared as `::Csize_t` without any `Ref` or `Ptr` necessary. (If the wrapper was calling a Fortran function instead, the corresponding function input -signature would instead be `(Ref{Csize_t},)`, since Fortran variables are +signature would instead be `::Ref{Csize_t}`, since Fortran variables are passed by pointers.) Furthermore, `n` can be any type that is convertible to a `Csize_t` integer; the [`ccall`](@ref) implicitly calls [`Base.cconvert(Csize_t, n)`](@ref). @@ -789,29 +761,11 @@ Here is a second example wrapping the corresponding destructor: ```julia # The corresponding C signature is # void gsl_permutation_free (gsl_permutation * p); -function permutation_free(p::Ref{gsl_permutation}) - ccall( - (:gsl_permutation_free, :libgsl), # name of C function and library - Cvoid, # output type - (Ref{gsl_permutation},), # tuple of input types - p # name of Julia variable to pass in - ) +function permutation_free(p::Ptr{gsl_permutation}) + @ccall "libgsl".gsl_permutation_free(p::Ptr{gsl_permutation})::Cvoid end ``` -Here, the input `p` is declared to be of type `Ref{gsl_permutation}`, meaning that the memory -that `p` points to may be managed by Julia or by C. A pointer to memory allocated by C should -be of type `Ptr{gsl_permutation}`, but it is convertible using [`Base.cconvert`](@ref) and therefore - -Now if you look closely enough at this example, you may notice that it is incorrect, given our explanation -above of preferred declaration types. Do you see it? The function we are calling is going to free the -memory. This type of operation cannot be given a Julia object (it will crash or cause memory corruption). -Therefore, it may be preferable to declare the `p` type as `Ptr{gsl_permutation }`, to make it harder for the -user to mistakenly pass another sort of object there than one obtained via `gsl_permutation_alloc`. - -If the C wrapper never expects the user to pass pointers to memory managed by Julia, then using -`p::Ptr{gsl_permutation}` for the method signature of the wrapper and similarly in the [`ccall`](@ref) -is also acceptable. Here is a third example passing Julia arrays: @@ -824,12 +778,8 @@ function sf_bessel_Jn_array(nmin::Integer, nmax::Integer, x::Real) throw(DomainError()) end result_array = Vector{Cdouble}(undef, nmax - nmin + 1) - errorcode = ccall( - (:gsl_sf_bessel_Jn_array, :libgsl), # name of C function and library - Cint, # output type - (Cint, Cint, Cdouble, Ref{Cdouble}),# tuple of input types - nmin, nmax, x, result_array # names of Julia variables to pass in - ) + errorcode = @ccall "libgsl".gsl_sf_bessel_Jn_array( + nmin::Cint, nmax::Cint, x::Cdouble, result_array::Ref{Cdouble})::Cint if errorcode != 0 error("GSL error code $errorcode") end @@ -846,9 +796,9 @@ the Julia pointer to a Julia array data structure into a form understandable by ## Fortran Wrapper Example The following example utilizes `ccall` to call a function in a common Fortran library (libBLAS) to -computes a dot product. Notice that the argument mapping is a bit different here than above, as -we need to map from Julia to Fortran. On every argument type, we specify `Ref` or `Ptr`. This -mangling convention may be specific to your fortran compiler and operating system, and is likely +compute a dot product. Notice that the argument mapping is a bit different here than above, as +we need to map from Julia to Fortran. On every argument type, we specify `Ref` or `Ptr`. This +mangling convention may be specific to your Fortran compiler and operating system and is likely undocumented. However, wrapping each in a `Ref` (or `Ptr`, where equivalent) is a frequent requirement of Fortran compiler implementations: @@ -857,10 +807,8 @@ function compute_dot(DX::Vector{Float64}, DY::Vector{Float64}) @assert length(DX) == length(DY) n = length(DX) incx = incy = 1 - product = ccall((:ddot_, "libLAPACK"), - Float64, - (Ref{Int32}, Ptr{Float64}, Ref{Int32}, Ptr{Float64}, Ref{Int32}), - n, DX, incx, DY, incy) + product = @ccall "libLAPACK".ddot( + n::Ref{Int32}, DX::Ptr{Float64}, incx::Ref{Int32}, DY::Ptr{Float64}, incy::Ref{Int32})::Float64 return product end ``` @@ -868,12 +816,12 @@ end ## Garbage Collection Safety -When passing data to a [`ccall`](@ref), it is best to avoid using the [`pointer`](@ref) function. -Instead define a convert method and pass the variables directly to the [`ccall`](@ref). [`ccall`](@ref) +When passing data to a `@ccall`, it is best to avoid using the [`pointer`](@ref) function. +Instead define a [`Base.cconvert`](@ref) method and pass the variables directly to the `@ccall`. `@ccall` automatically arranges that all of its arguments will be preserved from garbage collection until -the call returns. If a C API will store a reference to memory allocated by Julia, after the [`ccall`](@ref) +the call returns. If a C API will store a reference to memory allocated by Julia, after the `@ccall` returns, you must ensure that the object remains visible to the garbage collector. The suggested -way to do this is to make a global variable of type `Array{Ref,1}` to hold these values, until +way to do this is to make a global variable of type `Array{Ref,1}` to hold these values until the C library notifies you that it is finished with them. Whenever you have created a pointer to Julia data, you must ensure the original data exists until @@ -891,8 +839,8 @@ it must be handled in other ways. ## Non-constant Function Specifications In some cases, the exact name or path of the needed library is not known in advance and must -be computed at run time. To handle such cases, the library component of a `(name, library)` -specification can be a function call, e.g. `(:dgemm_, find_blas())`. The call expression will +be computed at run time. To handle such cases, the library component +specification can be a function call, e.g. `find_blas().dgemm`. The call expression will be executed when the `ccall` itself is executed. However, it is assumed that the library location does not change once it is determined, so the result of the call can be cached and reused. Therefore, the number of times the expression executes is unspecified, and returning @@ -901,11 +849,11 @@ different values for multiple calls results in unspecified behavior. If even more flexibility is needed, it is possible to use computed values as function names by staging through [`eval`](@ref) as follows: -``` -@eval ccall(($(string("a", "b")), "lib"), ... +```julia +@eval @ccall "lib".$(string("a", "b"))()::Cint ``` -This expression constructs a name using `string`, then substitutes this name into a new [`ccall`](@ref) +This expression constructs a name using `string`, then substitutes this name into a new `@ccall` expression, which is then evaluated. Keep in mind that `eval` only operates at the top level, so within this expression local variables will not be available (unless their values are substituted with `$`). For this reason, `eval` is typically only used to form top-level definitions, for example @@ -918,16 +866,16 @@ The next section discusses how to use indirect calls to efficiently achieve a si ## Indirect Calls -The first argument to [`ccall`](@ref) can also be an expression evaluated at run time. In this +The first argument to `@ccall` can also be an expression evaluated at run time. In this case, the expression must evaluate to a `Ptr`, which will be used as the address of the native -function to call. This behavior occurs when the first [`ccall`](@ref) argument contains references +function to call. This behavior occurs when the first `@ccall` argument contains references to non-constants, such as local variables, function arguments, or non-constant globals. For example, you might look up the function via `dlsym`, then cache it in a shared reference for that session. For example: ```julia -macro dlsym(func, lib) +macro dlsym(lib, func) z = Ref{Ptr{Cvoid}}(C_NULL) quote let zlocal = $z[] @@ -941,7 +889,7 @@ macro dlsym(func, lib) end mylibvar = Libdl.dlopen("mylib") -ccall(@dlsym("myfunc", mylibvar), Cvoid, ()) +@ccall $(@dlsym(mylibvar, "myfunc"))()::Cvoid ``` ## Closure cfunctions @@ -960,8 +908,7 @@ function qsort(a::Vector{T}, cmp) where T callback = @cfunction $cmp Cint (Ref{T}, Ref{T}) # Here, `callback` isa Base.CFunction, which will be converted to Ptr{Cvoid} # (and protected against finalization) by the ccall - ccall(:qsort, Cvoid, (Ptr{T}, Csize_t, Csize_t, Ptr{Cvoid}), - a, length(a), Base.elsize(a), callback) + @ccall qsort(a::Ptr{T}, length(a)::Csize_t, Base.elsize(a)::Csize_t, callback::Ptr{Cvoid}) # We could instead use: # GC.@preserve callback begin # use(Base.unsafe_convert(Ptr{Cvoid}, callback)) @@ -972,7 +919,7 @@ end ``` !!! note - Closure [`@cfunction`](@ref) rely on LLVM trampolines, which are not available on all + Closure [`@cfunction`](@ref) relies on LLVM trampolines, which are not available on all platforms (for example ARM and PowerPC). @@ -987,21 +934,79 @@ and load in the new changes. One can either restart Julia or use the ```julia lib = Libdl.dlopen("./my_lib.so") # Open the library explicitly. sym = Libdl.dlsym(lib, :my_fcn) # Get a symbol for the function to call. -ccall(sym, ...) # Use the pointer `sym` instead of the (symbol, library) tuple (remaining arguments are the same). +@ccall $sym(...) # Use the pointer `sym` instead of the library.symbol tuple. Libdl.dlclose(lib) # Close the library explicitly. ``` -Note that when using `ccall` with the tuple input -(e.g., `ccall((:my_fcn, "./my_lib.so"), ...)`), the library is opened implicitly +Note that when using `@ccall` with the input +(e.g., `@ccall "./my_lib.so".my_fcn(...)::Cvoid`), the library is opened implicitly and it may not be explicitly closed. -## Calling Convention +## Variadic function calls + +To call variadic C functions a `semicolon` can be used in the argument list to +separate required arguments from variadic arguments. An example with the +`printf` function is given below: + +```julia-repl +julia> @ccall printf("%s = %d\n"::Cstring ; "foo"::Cstring, foo::Cint)::Cint +foo = 3 +8 +``` + +## [`ccall` interface](@id ccall-interface) + +There is another alternative interface to `@ccall`. +This interface is slightly less convenient but it does allow one to specify a [calling convention](@ref calling-convention). + +The arguments to [`ccall`](@ref) are: + +1. A `(:function, "library")` pair (most common), -The second argument to [`ccall`](@ref) can optionally be a calling convention specifier (immediately -preceding return type). Without any specifier, the platform-default C calling convention is used. -Other supported conventions are: `stdcall`, `cdecl`, `fastcall`, and `thiscall` (no-op on 64-bit Windows). -For example (from `base/libc.jl`) we see the same `gethostname`[`ccall`](@ref) as above, but with the correct -signature for Windows: + OR + + a `:function` name symbol or `"function"` name string (for symbols in the current process or libc), + + OR + + a function pointer (for example, from `dlsym`). + +2. The function's return type + +3. A tuple of input types, corresponding to the function signature. One common mistake is forgetting that a 1-tuple of + argument types must be written with a trailing comma. + +4. The actual argument values to be passed to the function, if any; each is a separate parameter. + + +!!! note + The `(:function, "library")` pair, return type, and input types must be literal constants + (i.e., they can't be variables, but see [Non-constant Function Specifications](@ref)). + + The remaining parameters are evaluated at compile-time, when the containing method is defined. + + +A table of translations between the macro and function interfaces is given below. + +| `@ccall` | `ccall` | +|------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| `@ccall clock()::Int32` | `ccall(:clock, Int32, ())` | +| `@ccall f(a::Cint)::Cint` | `ccall(:a, Cint, (Cint,), a)` | +| `@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(:gethostname, stdcall, Int32, (Ptr{UInt8}, UInt32), hn, length(hn))` | + +## [Calling Convention](@id calling-convention) + +The second argument to `ccall` (immediatel preceding return type) can optionally +be a calling convention specifier (the `@ccall` macro currently does not support +giving a calling convention). Without any specifier, the platform-default C +calling convention is used. Other supported conventions are: `stdcall`, `cdecl`, +`fastcall`, and `thiscall` (no-op on 64-bit Windows). For example (from +`base/libc.jl`) we see the same `gethostname``ccall` as above, but with the +correct signature for Windows: ```julia hn = Vector{UInt8}(undef, 256) @@ -1065,7 +1070,7 @@ the result will be a reference to this object, and the object will not be copied careful in this case to ensure that the object was always visible to the garbage collector (pointers do not count, but the new reference does) to ensure the memory is not prematurely freed. Note that if the object was not originally allocated by Julia, the new object will never be finalized -by Julia's garbage collector. If the `Ptr` itself is actually a `jl_value_t*`, it can be converted +by Julia's garbage collector. If the `Ptr` itself is actually a `jl_value_t*`, it can be converted back to a Julia object reference by [`unsafe_pointer_to_objref(ptr)`](@ref). (Julia values `v` can be converted to `jl_value_t*` pointers, as `Ptr{Cvoid}`, by calling [`pointer_from_objref(v)`](@ref).) @@ -1079,7 +1084,7 @@ a bug so that it can be resolved. If the pointer of interest is a plain-data array (primitive type or immutable struct), the function [`unsafe_wrap(Array, ptr,dims, own = false)`](@ref) may be more useful. The final parameter should be true if Julia should "take ownership" of the -underlying buffer and call `free(ptr)` when the returned `Array` object is finalized. If the +underlying buffer and call `free(ptr)` when the returned `Array` object is finalized. If the `own` parameter is omitted or false, the caller must ensure the buffer remains in existence until all access is complete. @@ -1113,9 +1118,7 @@ For more details on how to pass callbacks to C libraries, see this [blog post](h ## C++ -For direct C++ interfacing, see the [Cxx](https://github.com/Keno/Cxx.jl) package. For tools to create C++ -bindings, see the [CxxWrap](https://github.com/JuliaInterop/CxxWrap.jl) package. - +For tools to create C++ bindings, see the [CxxWrap](https://github.com/JuliaInterop/CxxWrap.jl) package. [^1]: Non-library function calls in both C and Julia can be inlined and thus may have diff --git a/doc/src/manual/code-loading.md b/doc/src/manual/code-loading.md index d6f359f83d5cb..d3806ee180f32 100644 --- a/doc/src/manual/code-loading.md +++ b/doc/src/manual/code-loading.md @@ -349,17 +349,62 @@ The subscripted `rootsᵢ`, `graphᵢ` and `pathsᵢ` variables correspond to th Since the primary environment is typically the environment of a project you're working on, while environments later in the stack contain additional tools, this is the right trade-off: it's better to break your development tools but keep the project working. When such incompatibilities occur, you'll typically want to upgrade your dev tools to versions that are compatible with the main project. -### Package/Environment Preferences +### [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. + +```toml +name = "MyPackage" + +[compat] +ExtDep = "1.0" +OtherExtDep = "1.0" + +[weakdeps] +ExtDep = "c9a23..." # uuid +OtherExtDep = "862e..." # uuid + +[extensions] +BarExt = ["ExtDep", "OtherExtDep"] +FooExt = "ExtDep" +... +``` + +The keys under `extensions` are the name 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. +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: + +``` +module FooExt + +# Load main package and extension dependencies +using MyPackage, ExtDep + +# Extend functionality in main package with types from the extension dependencies +MyPackage.func(x::ExtDep.SomeStruct) = ... + +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 +dependencies. + +### [Package/Environment Preferences](@id preferences) Preferences are dictionaries of metadata that influence package behavior within an environment. -The preferences system supports reading preferences at compile-time, which means that at code-loading time, we must ensure that a particular `.ji` file was built with the same preferences as the current environment before loading it. +The preferences system supports reading preferences at compile-time, which means that at code-loading time, we must ensure that the precompilation files selected by Julia were built with the same preferences as the current environment before loading them. The public API for modifying Preferences is contained within the [Preferences.jl](https://github.com/JuliaPackaging/Preferences.jl) package. Preferences are stored as TOML dictionaries within a `(Julia)LocalPreferences.toml` file next to the currently-active project. If a preference is "exported", it is instead stored within the `(Julia)Project.toml` instead. The intention is to allow shared projects to contain shared preferences, while allowing for users themselves to override those preferences with their own settings in the LocalPreferences.toml file, which should be .gitignored as the name implies. -Preferences that are accessed during compilation are automatically marked as compile-time preferences, and any change recorded to these preferences will cause the Julia compiler to recompile any cached precompilation `.ji` files for that module. -This is done by serializing the hash of all compile-time preferences during compilation, then checking that hash against the current environment when searching for the proper `.ji` file to load. +Preferences that are accessed during compilation are automatically marked as compile-time preferences, and any change recorded to these preferences will cause the Julia compiler to recompile any cached precompilation file(s) (`.ji` and corresponding `.so`, `.dll`, or `.dylib` files) for that module. +This is done by serializing the hash of all compile-time preferences during compilation, then checking that hash against the current environment when searching for the proper file(s) to load. Preferences can be set with depot-wide defaults; if package Foo is installed within your global environment and it has preferences set, these preferences will apply as long as your global environment is part of your `LOAD_PATH`. Preferences in environments higher up in the environment stack get overridden by the more proximal entries in the load path, ending with the currently active project. diff --git a/doc/src/manual/command-line-interface.md b/doc/src/manual/command-line-interface.md new file mode 100644 index 0000000000000..8164299f01250 --- /dev/null +++ b/doc/src/manual/command-line-interface.md @@ -0,0 +1,150 @@ +# Command-line Interface + +## Using arguments inside scripts + +When running a script using `julia`, you can pass additional arguments to your script: + +``` +$ julia script.jl arg1 arg2... +``` + +These additional command-line arguments are passed in the global constant `ARGS`. The +name of the script itself is passed in as the global `PROGRAM_FILE`. Note that `ARGS` is +also set when a Julia expression is given using the `-e` option on the command line (see the +`julia` help output below) but `PROGRAM_FILE` will be empty. For example, to just print the +arguments given to a script, you could do this: + +``` +$ julia -e 'println(PROGRAM_FILE); for x in ARGS; println(x); end' foo bar + +foo +bar +``` + +Or you could put that code into a script and run it: + +``` +$ echo 'println(PROGRAM_FILE); for x in ARGS; println(x); end' > script.jl +$ julia script.jl foo bar +script.jl +foo +bar +``` + +The `--` delimiter can be used to separate command-line arguments intended for the script file from arguments intended for Julia: + +``` +$ julia --color=yes -O -- script.jl arg1 arg2.. +``` + +See also [Scripting](@ref man-scripting) for more information on writing Julia scripts. + + +## Parallel mode + +Julia can be started in parallel mode with either the `-p` or the `--machine-file` options. `-p n` +will launch an additional `n` worker processes, while `--machine-file file` will launch a worker +for each line in file `file`. The machines defined in `file` must be accessible via a password-less +`ssh` login, with Julia installed at the same location as the current host. Each machine definition +takes the form `[count*][user@]host[:port] [bind_addr[:port]]`. `user` defaults to current user, +`port` to the standard ssh port. `count` is the number of workers to spawn on the node, and defaults +to 1. The optional `bind-to bind_addr[:port]` specifies the IP address and port that other workers +should use to connect to this worker. + + +## Startup file + +If you have code that you want executed whenever Julia is run, you can put it in +`~/.julia/config/startup.jl`: + +``` +$ echo 'println("Greetings! 你好! 안녕하세요?")' > ~/.julia/config/startup.jl +$ julia +Greetings! 你好! 안녕하세요? + +... +``` + +Note that although you should have a `~/.julia` directory once you've run Julia for the +first time, you may need to create the `~/.julia/config` folder and the +`~/.julia/config/startup.jl` file if you use it. + +To have startup code run only in [The Julia REPL](@ref) (and not when `julia` is *e.g.* run +on a script), use [`atreplinit`](@ref) in `startup.jl`: + +```julia +atreplinit() do repl + # ... +end +``` + + +## [Command-line switches for Julia](@id command-line-interface) + +There are various ways to run Julia code and provide options, similar to those available for the +`perl` and `ruby` programs: + +``` +julia [switches] -- [programfile] [args...] +``` + +The following is a complete list of command-line switches available when launching julia (a '*' marks the default value, if applicable; settings marked '($)' may trigger package precompilation): + +|Switch |Description| +|:--- |:---| +|`-v`, `--version` |Display version information| +|`-h`, `--help` |Print command-line options (this message).| +|`--help-hidden` |Uncommon options not shown by `-h`| +|`--project[={\|@.}]` |Set `` as the home 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` 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}` |Enable or disable incremental precompilation of modules| +|`--pkgimages={yes*\|no}` |Enable or disable usage of native code caching in the form of pkgimages| +|`-e`, `--eval ` |Evaluate ``| +|`-E`, `--print ` |Evaluate `` and display the result| +|`-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.| +|`-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| +|`-q`, `--quiet` |Quiet startup: no banner, suppress REPL warnings| +|`--banner={yes\|no\|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) ($)| +|`--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)| +|`--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`.| +|`--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)| +|`--strip-metadata` |Remove docstrings and source location info from system image| +|`--strip-ir` |Remove IR (intermediate representation) of compiled functions| +|`--output-unopt-bc ` |Generate unoptimized LLVM bitcode (.bc)| +|`--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| +|`--image-codegen` |Force generate code in imaging mode| + + +!!! compat "Julia 1.1" + In Julia 1.0, the default `--project=@.` option did not search up from the root + directory of a Git repository for the `Project.toml` file. From Julia 1.1 forward, it + does. diff --git a/doc/src/manual/command-line-options.md b/doc/src/manual/command-line-options.md deleted file mode 100644 index f3ad39a6aed16..0000000000000 --- a/doc/src/manual/command-line-options.md +++ /dev/null @@ -1,117 +0,0 @@ -# [Command-line Options](@id command-line-options) - -## Using arguments inside scripts - -When running a script using `julia`, you can pass additional arguments to your script: - -``` -$ julia script.jl arg1 arg2... -``` - -These additional command-line arguments are passed in the global constant `ARGS`. The -name of the script itself is passed in as the global `PROGRAM_FILE`. Note that `ARGS` is -also set when a Julia expression is given using the `-e` option on the command line (see the -`julia` help output below) but `PROGRAM_FILE` will be empty. For example, to just print the -arguments given to a script, you could do this: - -``` -$ julia -e 'println(PROGRAM_FILE); for x in ARGS; println(x); end' foo bar - -foo -bar -``` - -Or you could put that code into a script and run it: - -``` -$ echo 'println(PROGRAM_FILE); for x in ARGS; println(x); end' > script.jl -$ julia script.jl foo bar -script.jl -foo -bar -``` - -The `--` delimiter can be used to separate command-line arguments intended for the script file from arguments intended for Julia: - -``` -$ julia --color=yes -O -- script.jl arg1 arg2.. -``` - -See also [Scripting](@ref man-scripting) for more information on writing Julia scripts. - -Julia can be started in parallel mode with either the `-p` or the `--machine-file` options. `-p n` -will launch an additional `n` worker processes, while `--machine-file file` will launch a worker -for each line in file `file`. The machines defined in `file` must be accessible via a password-less -`ssh` login, with Julia installed at the same location as the current host. Each machine definition -takes the form `[count*][user@]host[:port] [bind_addr[:port]]`. `user` defaults to current user, -`port` to the standard ssh port. `count` is the number of workers to spawn on the node, and defaults -to 1. The optional `bind-to bind_addr[:port]` specifies the IP address and port that other workers -should use to connect to this worker. - -If you have code that you want executed whenever Julia is run, you can put it in -`~/.julia/config/startup.jl`: - -``` -$ echo 'println("Greetings! 你好! 안녕하세요?")' > ~/.julia/config/startup.jl -$ julia -Greetings! 你好! 안녕하세요? - -... -``` - -Note that although you should have a `~/.julia` directory once you've run Julia for the -first time, you may need to create the `~/.julia/config` folder and the -`~/.julia/config/startup.jl` file if you use it. - -## Command-line switches for Julia - -There are various ways to run Julia code and provide options, similar to those available for the -`perl` and `ruby` programs: - -``` -julia [switches] -- [programfile] [args...] -``` - -The following is a complete list of command-line switches available when launching julia, e.g. - - -|Switch |Description| -|:--- |:---| -|`-v`, `--version` |Display version information| -|`-h`, `--help` |Print command-line options (this message).| -|`--project[={\|@.}]` |Set `` as the home 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/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}` |Enable or disable incremental precompilation of modules| -|`-e`, `--eval ` |Evaluate ``| -|`-E`, `--print ` |Evaluate `` and display the result| -|`-L`, `--load ` |Load `` immediately on all processors| -|`-t`, `--threads {N\|auto`} |Enable N threads; `auto` currently sets N to the number of local CPU threads but this might change in the future| -|`-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| -|`-q`, `--quiet` |Quiet startup: no banner, suppress REPL warnings| -|`--banner={yes\|no\|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| -|`-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 (default level is 2 if unspecified or 3 if used without a level)| -|`--min-optlevel={0,1,2,3}` |Set the lower bound on per-module optimization (default is 0)| -|`-g`, `-g ` |Enable or set the level of debug info generation (default level is 1 if unspecified or 2 if 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)| -|`--code-coverage={none\|user\|all}` |Count executions of source lines| -|`--code-coverage` |equivalent to `--code-coverage=user`| -|`--track-allocation={none\|user\|all}` |Count bytes allocated by each source line| -|`--track-allocation` |equivalent to `--track-allocation=user`| - -!!! compat "Julia 1.1" - In Julia 1.0, the default `--project=@.` option did not search up from the root - directory of a Git repository for the `Project.toml` file. From Julia 1.1 forward, it - does. diff --git a/doc/src/manual/complex-and-rational-numbers.md b/doc/src/manual/complex-and-rational-numbers.md index 94ad70982bbae..9cab2ed1e4f24 100644 --- a/doc/src/manual/complex-and-rational-numbers.md +++ b/doc/src/manual/complex-and-rational-numbers.md @@ -36,7 +36,7 @@ julia> (-1 + 2im)^2 -3 - 4im julia> (-1 + 2im)^2.5 -2.7296244647840084 - 6.960664459571898im +2.729624464784009 - 6.9606644595719im julia> (-1 + 2im)^(1 + 1im) -0.27910381075826657 + 0.08708053414102428im @@ -48,7 +48,7 @@ julia> 3(2 - 5im)^2 -63 - 60im julia> 3(2 - 5im)^-1.0 -0.20689655172413796 + 0.5172413793103449im +0.20689655172413793 + 0.5172413793103449im ``` The promotion mechanism ensures that combinations of operands of different types just work: @@ -140,7 +140,7 @@ when applied to `-1` versus `-1 + 0im` even though `-1 == -1 + 0im`: ```jldoctest julia> sqrt(-1) ERROR: DomainError with -1.0: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). Stacktrace: [...] @@ -254,7 +254,7 @@ julia> float(3//4) ``` Conversion from rational to floating-point respects the following identity for any integral values -of `a` and `b`, with the exception of the case `a == 0` and `b == 0`: +of `a` and `b`, with the exception of the two cases `b == 0` and `a == 0 && b < 0`: ```jldoctest julia> a = 1; b = 2; diff --git a/doc/src/manual/constructors.md b/doc/src/manual/constructors.md index 39d6d6bcaf0f5..6ec206dade335 100644 --- a/doc/src/manual/constructors.md +++ b/doc/src/manual/constructors.md @@ -244,8 +244,8 @@ ERROR: UndefRefError: access to undefined reference This avoids the need to continually check for `null` values. However, not all object fields are references. Julia considers some types to be "plain data", meaning all of their data is self-contained and does not reference other objects. The plain data types consist of primitive types (e.g. `Int`) -and immutable structs of other plain data types. The initial contents of a plain data type is -undefined: +and immutable structs of other plain data types (see also: [`isbits`](@ref), [`isbitstype`](@ref)). +The initial contents of a plain data type is undefined: ```julia-repl julia> struct HasPlain @@ -372,8 +372,13 @@ However, other similar calls still don't work: ```jldoctest parametric2 julia> Point(1.5,2) ERROR: MethodError: no method matching Point(::Float64, ::Int64) + Closest candidates are: - Point(::T, !Matched::T) where T<:Real at none:1 + Point(::T, !Matched::T) where T<:Real + @ Main none:1 + +Stacktrace: +[...] ``` For a more general way to make all such calls work sensibly, see [Conversion and Promotion](@ref conversion-and-promotion). @@ -550,8 +555,11 @@ julia> struct SummedArray{T<:Number,S<:Number} julia> SummedArray(Int32[1; 2; 3], Int32(6)) ERROR: MethodError: no method matching SummedArray(::Vector{Int32}, ::Int32) + Closest candidates are: - SummedArray(::Vector{T}) where T at none:4 + SummedArray(::Vector{T}) where T + @ Main none:4 + Stacktrace: [...] ``` diff --git a/doc/src/manual/control-flow.md b/doc/src/manual/control-flow.md index 63832cc4c90c9..5d12530892b1e 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 Stacktrace: [1] test(::Int64, ::Int64) at ./none:7 ``` @@ -388,15 +388,13 @@ loop. Here is an example of a `while` loop: ```jldoctest julia> i = 1; -julia> while i <= 5 +julia> while i <= 3 println(i) global i += 1 end 1 2 3 -4 -5 ``` The `while` loop evaluates the condition expression (`i <= 5` in this case), and as long it remains @@ -408,39 +406,53 @@ down like the above `while` loop does is so common, it can be expressed more con `for` loop: ```jldoctest -julia> for i = 1:5 +julia> for i = 1:3 println(i) end 1 2 3 -4 -5 ``` -Here the `1:5` is a range object, representing the sequence of numbers 1, 2, 3, 4, 5. The `for` +Here the `1:3` is a range object, representing the sequence of numbers 1, 2, 3. The `for` loop iterates through these values, assigning each one in turn to the variable `i`. One rather important distinction between the previous `while` loop form and the `for` loop form is the scope -during which the variable is visible. If the variable `i` has not been introduced in another -scope, in the `for` loop form, it is visible only inside of the `for` loop, and not -outside/afterwards. You'll either need a new interactive session instance or a different variable +during which the variable is visible. A `for` loop always introduces a new iteration variable in +its body, regardless of whether a variable of the same name exists in the enclosing scope. +This implies that on the one hand `i` need not be declared before the loop. On the other hand it +will not be visible outside the loop, nor will an outside variable of the same name be affected. +You'll either need a new interactive session instance or a different variable name to test this: ```jldoctest -julia> for j = 1:5 +julia> for j = 1:3 println(j) end 1 2 3 -4 -5 julia> j -ERROR: UndefVarError: j not defined +ERROR: UndefVarError: `j` not defined +``` + +```jldoctest +julia> j = 0; + +julia> for j = 1:3 + println(j) + end +1 +2 +3 + +julia> j +0 ``` -See [Scope of Variables](@ref scope-of-variables) for a detailed explanation of variable scope and how it works in +Use `for outer` to modify the latter behavior and reuse an existing local variable. + +See [Scope of Variables](@ref scope-of-variables) for a detailed explanation of variable scope, [`outer`](@ref), and how it works in Julia. In general, the `for` loop construct can iterate over any container. In these cases, the alternative @@ -475,7 +487,7 @@ julia> i = 1; julia> while true println(i) - if i >= 5 + if i >= 3 break end global i += 1 @@ -483,20 +495,16 @@ julia> while true 1 2 3 -4 -5 julia> for j = 1:1000 println(j) - if j >= 5 + if j >= 3 break end end 1 2 3 -4 -5 ``` Without the `break` keyword, the above `while` loop would never terminate on its own, and the `for` loop would iterate up to 1000. These loops are both exited early by using `break`. @@ -615,7 +623,7 @@ real value: ```jldoctest julia> sqrt(-1) ERROR: DomainError with -1.0: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). Stacktrace: [...] ``` @@ -661,7 +669,7 @@ Additionally, some exception types take one or more arguments that are used for ```jldoctest julia> throw(UndefVarError(:x)) -ERROR: UndefVarError: x not defined +ERROR: UndefVarError: `x` not defined ``` This mechanism can be implemented easily by custom exception types following the way [`UndefVarError`](@ref) @@ -789,7 +797,7 @@ julia> sqrt_second(9) julia> sqrt_second(-9) ERROR: DomainError with -9.0: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). Stacktrace: [...] ``` @@ -819,6 +827,44 @@ no error has occurred, but the ability to unwind the stack and pass a value to a is desirable. Julia provides the [`rethrow`](@ref), [`backtrace`](@ref), [`catch_backtrace`](@ref) and [`current_exceptions`](@ref) functions for more advanced error handling. +### `else` Clauses + +!!! compat "Julia 1.8" + This functionality requires at least Julia 1.8. + +In some cases, one may not only want to appropriately handle the error case, but also want to run +some code only if the `try` block succeeds. For this, an `else` clause can be specified after the +`catch` block that is run whenever no error was thrown previously. The advantage over including +this code in the `try` block instead is that any further errors don't get silently caught by the +`catch` clause. + +```julia +local x +try + x = read("file", String) +catch + # handle read errors +else + # do something with x +end +``` + +!!! note + The `try`, `catch`, `else`, and `finally` clauses each introduce their own scope blocks, so if + a variable is only defined in the `try` block, it can not be accessed by the `else` or `finally` + clause: + ```jldoctest + julia> try + foo = 1 + catch + else + foo + end + ERROR: UndefVarError: `foo` not defined + ``` + Use the [`local` keyword](@ref local-scope) outside the `try` block to make the variable + accessible from anywhere within the outer scope. + ### `finally` Clauses In code that performs state changes or uses resources like files, there is typically clean-up diff --git a/doc/src/manual/conversion-and-promotion.md b/doc/src/manual/conversion-and-promotion.md index 63ae37660cff4..f0c156f21ea62 100644 --- a/doc/src/manual/conversion-and-promotion.md +++ b/doc/src/manual/conversion-and-promotion.md @@ -181,7 +181,7 @@ For example, this definition states that it's valid to `convert` any `Number` ty any other by calling a 1-argument constructor: ```julia -convert(::Type{T}, x::Number) where {T<:Number} = T(x) +convert(::Type{T}, x::Number) where {T<:Number} = T(x)::T ``` This means that new `Number` types only need to define constructors, since this @@ -233,11 +233,11 @@ julia> promote(1 + 2im, 3//4) ``` Floating-point values are promoted to the largest of the floating-point argument types. Integer -values are promoted to the larger of either the native machine word size or the largest integer -argument type. Mixtures of integers and floating-point values are promoted to a floating-point -type big enough to hold all the values. Integers mixed with rationals are promoted to rationals. -Rationals mixed with floats are promoted to floats. Complex values mixed with real values are -promoted to the appropriate kind of complex value. +values are promoted to the largest of the integer argument types. If the types are the same size +but differ in signedness, the unsigned type is chosen. Mixtures of integers and floating-point +values are promoted to a floating-point type big enough to hold all the values. Integers mixed +with rationals are promoted to rationals. Rationals mixed with floats are promoted to floats. +Complex values mixed with real values are promoted to the appropriate kind of complex value. That is really all there is to using promotions. The rest is just a matter of clever application, the most typical "clever" application being the definition of catch-all methods for numeric operations diff --git a/doc/src/manual/distributed-computing.md b/doc/src/manual/distributed-computing.md index abaf47a53b39c..4531506d5c49d 100644 --- a/doc/src/manual/distributed-computing.md +++ b/doc/src/manual/distributed-computing.md @@ -33,7 +33,7 @@ You can wait for a remote call to finish by calling [`wait`](@ref) on the return and you can obtain the full value of the result using [`fetch`](@ref). On the other hand, [`RemoteChannel`](@ref) s are rewritable. For example, multiple processes can -co-ordinate their processing by referencing the same remote `Channel`. +coordinate their processing by referencing the same remote `Channel`. Each process has an associated identifier. The process providing the interactive Julia prompt always has an `id` equal to 1. The processes used by default for parallel operations are referred @@ -209,7 +209,7 @@ MyType(7) julia> fetch(@spawnat 2 MyType(7)) ERROR: On worker 2: -UndefVarError: MyType not defined +UndefVarError: `MyType` not defined ⋮ julia> fetch(@spawnat 2 DummyModule.MyType(7)) @@ -250,6 +250,11 @@ The base Julia installation has in-built support for two types of clusters: to 1. The optional `bind-to bind_addr[:port]` specifies the IP address and port that other workers should use to connect to this worker. +!!! note + While Julia generally strives for backward compatibility, distribution of code to worker processes relies on + [`Serialization.serialize`](@ref). As pointed out in the corresponding documentation, this can not be guaranteed to work across + different Julia versions, so it is advised that all workers on all machines use the same version. + Functions [`addprocs`](@ref), [`rmprocs`](@ref), [`workers`](@ref), and others are available as a programmatic means of adding, removing and querying the processes in a cluster. @@ -1258,20 +1263,21 @@ 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, or -[DistributedArrays.jl](https://github.com/JuliaParallel/Distributedarrays.jl), 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 presented in [Shared Arrays](@ref). + A mention must be made of Julia's GPU programming ecosystem, which includes: -1. Low-level (C kernel) based operations [OpenCL.jl](https://github.com/JuliaGPU/OpenCL.jl) and [CUDAdrv.jl](https://github.com/JuliaGPU/CUDAdrv.jl) which are respectively an OpenCL interface and a CUDA wrapper. +1. [CUDA.jl](https://github.com/JuliaGPU/CUDA.jl) wraps the various CUDA libraries and supports compiling Julia kernels for Nvidia GPUs. -2. Low-level (Julia Kernel) interfaces like [CUDAnative.jl](https://github.com/JuliaGPU/CUDAnative.jl) which is a Julia native CUDA implementation. +2. [oneAPI.jl](https://github.com/JuliaGPU/oneAPI.jl) wraps the oneAPI unified programming model, and supports executing Julia kernels on supported accelerators. Currently only Linux is supported. -3. High-level vendor-specific abstractions like [CuArrays.jl](https://github.com/JuliaGPU/CuArrays.jl) and [CLArrays.jl](https://github.com/JuliaGPU/CLArrays.jl) +3. [AMDGPU.jl](https://github.com/JuliaGPU/AMDGPU.jl) wraps the AMD ROCm libraries and supports compiling Julia kernels for AMD GPUs. Currently only Linux is supported. -4. High-level libraries like [ArrayFire.jl](https://github.com/JuliaComputing/ArrayFire.jl) and [GPUArrays.jl](https://github.com/JuliaGPU/GPUArrays.jl) +4. High-level libraries like [KernelAbstractions.jl](https://github.com/JuliaGPU/KernelAbstractions.jl), [Tullio.jl](https://github.com/mcabbott/Tullio.jl) and [ArrayFire.jl](https://github.com/JuliaComputing/ArrayFire.jl). -In the following example we will use both `DistributedArrays.jl` and `CuArrays.jl` to distribute an array across multiple +In the following example we will use both `DistributedArrays.jl` and `CUDA.jl` to distribute an array across multiple processes by first casting it through `distribute()` and `CuArray()`. Remember when importing `DistributedArrays.jl` to import it across all processes using [`@everywhere`](@ref) @@ -1284,7 +1290,7 @@ julia> addprocs() julia> @everywhere using DistributedArrays -julia> using CuArrays +julia> using CUDA julia> B = ones(10_000) ./ 2; @@ -1322,9 +1328,8 @@ true julia> typeof(cuC) CuArray{Float64,1} ``` -Keep in mind that some Julia features are not currently supported by CUDAnative.jl[^2] , especially some functions like `sin` will need to be replaced with `CUDAnative.sin`(cc: @maleadt). -In the following example we will use both `DistributedArrays.jl` and `CuArrays.jl` to distribute an array across multiple +In the following example we will use both `DistributedArrays.jl` and `CUDA.jl` to distribute an array across multiple processes and call a generic function on it. ```julia @@ -1407,6 +1412,3 @@ mpirun -np 4 ./julia example.jl introduced a new set of communication mechanisms, collectively referred to as Remote Memory Access (RMA). The motivation for adding rma to the MPI standard was to facilitate one-sided communication patterns. For additional information on the latest MPI standard, see . - -[^2]: - [Julia GPU man pages](https://juliagpu.github.io/CUDAnative.jl/stable/man/usage.html#Julia-support-1) diff --git a/doc/src/manual/documentation.md b/doc/src/manual/documentation.md index 99d46e364b3eb..4c724e1deaaeb 100644 --- a/doc/src/manual/documentation.md +++ b/doc/src/manual/documentation.md @@ -17,7 +17,7 @@ environments provide a way to access documentation directly: You can also use the Julia panel in the sidebar to search for documentation. - In [Pluto](https://github.com/fonsp/Pluto.jl), open the "Live Docs" panel on the bottom right. - In [Juno](https://junolab.org) using `Ctrl-J, Ctrl-D` will show the documentation for the object -under the cursor. + under the cursor. ## Writing Documentation @@ -310,18 +310,18 @@ end @doc "`subtract(a,b)` subtracts `b` from `a`" subtract ``` -Documentation written in non-toplevel blocks, such as `begin`, `if`, `for`, and `let`, is -added to the documentation system as blocks are evaluated. For example: +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: ```julia if condition() - "..." + @doc "..." f(x) = x end ``` will add documentation to `f(x)` when `condition()` is `true`. Note that even if `f(x)` goes -out of scope at the end of the block, its documentation will remain. +out of scope at the end of a block, its documentation will remain. It is possible to make use of metaprogramming to assist in the creation of documentation. When using string-interpolation within the docstring you will need to use an extra `$` as diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index 22c2f66f9b8b0..2b6e48c533849 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -1,16 +1,19 @@ # Embedding Julia -As we have seen in [Calling C and Fortran Code](@ref), Julia has a simple and efficient way to -call functions written in C. But there are situations where the opposite is needed: calling Julia -function from C code. This can be used to integrate Julia code into a larger C/C++ project, without -the need to rewrite everything in C/C++. Julia has a C API to make this possible. As almost all -programming languages have some way to call C functions, the Julia C API can also be used to build -further language bridges (e.g. calling Julia from Python or C#). +As we have seen in [Calling C and Fortran Code](@ref), Julia has a simple and efficient way +to call functions written in C. But there are situations where the opposite is needed: +calling Julia functions from C code. This can be used to integrate Julia code into a larger +C/C++ project, without the need to rewrite everything in C/C++. Julia has a C API to make +this possible. As almost all programming languages have some way to call C functions, the +Julia C API can also be used to build further language bridges (e.g. calling Julia from +Python, Rust or C#). Even though Rust and C++ can use the C embedding API directly, both +have packages helping with it, for C++ [Jluna](https://github.com/Clemapfel/jluna) is useful. ## High-Level Embedding -__Note__: This section covers embedding Julia code in C on Unix-like operating systems. For doing -this on Windows, please see the section following this. +__Note__: This section covers embedding Julia code in C on Unix-like operating systems. For +doing this on Windows, please see the section following this, +[High-Level Embedding on Windows with Visual Studio](@ref). We start with a simple C program that initializes Julia and calls some Julia code: @@ -36,9 +39,9 @@ int main(int argc, char *argv[]) } ``` -In order to build this program you have to put the path to the Julia header into the include path -and link against `libjulia`. For instance, when Julia is installed to `$JULIA_DIR`, one can compile -the above test program `test.c` with `gcc` using: +In order to build this program you must add the path to the Julia header to the include path +and link against `libjulia`. For instance, when Julia is installed to `$JULIA_DIR`, one can +compile the above test program `test.c` with `gcc` using: ``` gcc -o test -fPIC -I$JULIA_DIR/include/julia -L$JULIA_DIR/lib -Wl,-rpath,$JULIA_DIR/lib test.c -ljulia @@ -48,15 +51,15 @@ Alternatively, look at the `embedding.c` program in the Julia source tree in the The file `cli/loader_exe.c` program is another simple example of how to set `jl_options` options while linking against `libjulia`. -The first thing that has to be done before calling any other Julia C function is to initialize -Julia. This is done by calling `jl_init`, which tries to automatically determine Julia's install -location. If you need to specify a custom location, or specify which system image to load, -use `jl_init_with_image` instead. +The first thing that must be done before calling any other Julia C function is to +initialize Julia. This is done by calling `jl_init`, which tries to automatically determine +Julia's install location. If you need to specify a custom location, or specify which system +image to load, use `jl_init_with_image` instead. The second statement in the test program evaluates a Julia statement using a call to `jl_eval_string`. -Before the program terminates, it is strongly recommended to call `jl_atexit_hook`. The above -example program calls this before returning from `main`. +Before the program terminates, it is strongly recommended that `jl_atexit_hook` is called. +The above example program calls this just before returning from `main`. !!! note Currently, dynamically linking with the `libjulia` shared library requires passing the `RTLD_GLOBAL` @@ -70,17 +73,18 @@ example program calls this before returning from `main`. ``` !!! note - If the julia program needs to access symbols from the main executable, it may be necessary to - add `-Wl,--export-dynamic` linker flag at compile time on Linux in addition to the ones generated - by `julia-config.jl` described below. This is not necessary when compiling a shared library. + If the julia program needs to access symbols from the main executable, it may be + necessary to add the `-Wl,--export-dynamic` linker flag at compile time on Linux in + addition to the ones generated by `julia-config.jl` described below. This is not + necessary when compiling a shared library. ### Using julia-config to automatically determine build parameters -The script `julia-config.jl` was created to aid in determining what build parameters are required -by a program that uses embedded Julia. This script uses the build parameters and system configuration -of the particular Julia distribution it is invoked by to export the necessary compiler flags for -an embedding program to interact with that distribution. This script is located in the Julia -shared data directory. +The script `julia-config.jl` was created to aid in determining what build parameters are +required by a program that uses embedded Julia. This script uses the build parameters and +system configuration of the particular Julia distribution it is invoked by to export the +necessary compiler flags for an embedding program to interact with that distribution. This +script is located in the Julia shared data directory. #### Example @@ -98,18 +102,18 @@ int main(int argc, char *argv[]) #### On the command line -A simple use of this script is from the command line. Assuming that `julia-config.jl` is located -in `/usr/local/julia/share/julia`, it can be invoked on the command line directly and takes any -combination of 3 flags: +A simple use of this script is from the command line. Assuming that `julia-config.jl` is +located in `/usr/local/julia/share/julia`, it can be invoked on the command line directly +and takes any combination of three flags: ``` /usr/local/julia/share/julia/julia-config.jl Usage: julia-config [--cflags|--ldflags|--ldlibs] ``` -If the above example source is saved in the file `embed_example.c`, then the following command -will compile it into a running program on Linux and Windows (MSYS2 environment), or if on OS/X, -then substitute `clang` for `gcc`.: +If the above example source is saved in the file `embed_example.c`, then the following +command will compile it into an executable program on Linux and Windows (MSYS2 environment). +On macOS, substitute `clang` for `gcc`.: ``` /usr/local/julia/share/julia/julia-config.jl --cflags --ldflags --ldlibs | xargs gcc embed_example.c @@ -117,12 +121,12 @@ then substitute `clang` for `gcc`.: #### Use in Makefiles -But in general, embedding projects will be more complicated than the above, and so the following -allows general makefile support as well – assuming GNU make because of the use of the **shell** -macro expansions. Additionally, though many times `julia-config.jl` may be found in the directory -`/usr/local`, this is not necessarily the case, but Julia can be used to locate `julia-config.jl` -too, and the makefile can be used to take advantage of that. The above example is extended to -use a Makefile: +In general, embedding projects will be more complicated than the above example, and so the +following allows general makefile support as well – assuming GNU make because of the use of +the **shell** macro expansions. Furthermore, although `julia-config.jl` is usually in the +`/usr/local` directory, if it isn't, then Julia itself can be used to find +`julia-config.jl`, and the makefile can take advantage of this. The above example is +extended to use a makefile: ``` JL_SHARE = $(shell julia -e 'print(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia"))') @@ -141,8 +145,8 @@ Now the build command is simply `make`. If the `JULIA_DIR` environment variable hasn't been setup, add it using the System panel before starting Visual Studio. The `bin` folder under JULIA_DIR should be on the system PATH. -We start by opening Visual Studio and creating a new Console Application project. To the 'stdafx.h' -header file, add the following lines at the end: +We start by opening Visual Studio and creating a new Console Application project. Open the +'stdafx.h' header file, and add the following lines at the end: ```c #include @@ -170,7 +174,7 @@ int main(int argc, char *argv[]) ``` The next step is to set up the project to find the Julia include files and the libraries. It's important to -know whether the Julia installation is 32- or 64-bits. Remove any platform configuration that doesn't correspond +know whether the Julia installation is 32- or 64-bit. Remove any platform configuration that doesn't correspond to the Julia installation before proceeding. Using the project Properties dialog, go to `C/C++` | `General` and add `$(JULIA_DIR)\include\julia\` to the @@ -182,11 +186,12 @@ At this point, the project should build and run. ## Converting Types -Real applications will not just need to execute expressions, but also return their values to the -host program. `jl_eval_string` returns a `jl_value_t*`, which is a pointer to a heap-allocated -Julia object. Storing simple data types like [`Float64`](@ref) in this way is called `boxing`, -and extracting the stored primitive data is called `unboxing`. Our improved sample program that -calculates the square root of 2 in Julia and reads back the result in C looks as follows: +Real applications will not only need to execute expressions, but also return their values to +the host program. `jl_eval_string` returns a `jl_value_t*`, which is a pointer to a +heap-allocated Julia object. Storing simple data types like [`Float64`](@ref) in this way is +called `boxing`, and extracting the stored primitive data is called `unboxing`. Our improved +sample program that calculates the square root of 2 in Julia and reads back the result in C +has a body that now contains this code: ```c jl_value_t *ret = jl_eval_string("sqrt(2.0)"); @@ -241,18 +246,31 @@ jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs) Its second argument `args` is an array of `jl_value_t*` arguments and `nargs` is the number of 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 +the C side. The `sqrt` example above would with `@cfunction` be written as: + +```c +double (*sqrt_jl)(double) = jl_unbox_voidpointer(jl_eval_string("@cfunction(sqrt, Float64, (Float64,))")); +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. + ## Memory Management -As we have seen, Julia objects are represented in C as pointers. This raises the question of who +As we have seen, Julia objects are represented in C as pointers of type `jl_value_t*`. This raises the question of who is responsible for freeing these objects. -Typically, Julia objects are freed by a garbage collector (GC), but the GC does not automatically +Typically, Julia objects are freed by the garbage collector (GC), but the GC does not automatically know that we are holding a reference to a Julia value from C. This means the GC can free objects out from under you, rendering pointers invalid. -The GC can only run when Julia objects are allocated. Calls like `jl_box_float64` perform allocation, -and allocation might also happen at any point in running Julia code. However, it is generally -safe to use pointers in between `jl_...` calls. But in order to make sure that values can survive +The GC will only run when new Julia objects are being allocated. Calls like `jl_box_float64` perform allocation, +but allocation might also happen at any point in running Julia code. + +When writing code that embeds Julia, it is generally safe to use `jl_value_t*` values in between `jl_...` calls +(as GC will only get triggered by those calls). But in order to make sure that values can survive `jl_...` calls, we have to tell Julia that we still hold a reference to Julia [root](https://www.cs.purdue.edu/homes/hosking/690M/p611-fenichel.pdf) values, a process called "GC rooting". Rooting a value will ensure that the garbage collector does not accidentally @@ -271,9 +289,14 @@ The `JL_GC_POP` call releases the references established by the previous `JL_GC_ before the scope is exited. That is, before the function returns, or control flow otherwise leaves the block in which the `JL_GC_PUSH` was invoked. -Several Julia values can be pushed at once using the `JL_GC_PUSH2` , `JL_GC_PUSH3` , `JL_GC_PUSH4` , -`JL_GC_PUSH5` , and `JL_GC_PUSH6` macros. To push an array of Julia values one can use the -`JL_GC_PUSHARGS` macro, which can be used as follows: +Several Julia values can be pushed at once using the `JL_GC_PUSH2` to `JL_GC_PUSH6` macros: +``` +JL_GC_PUSH2(&ret1, &ret2); +// ... +JL_GC_PUSH6(&ret1, &ret2, &ret3, &ret4, &ret5, &ret6); +``` + +To push an array of Julia values one can use the `JL_GC_PUSHARGS` macro, which can be used as follows: ```c jl_value_t **args; @@ -284,8 +307,8 @@ args[1] = some_other_value; JL_GC_POP(); ``` -Each scope must have only one call to `JL_GC_PUSH*`. Hence, if all variables cannot be pushed once by -a single call to `JL_GC_PUSH*`, or if there are more than 6 variables to be pushed and using an array +Each scope must have only one call to `JL_GC_PUSH*`, and should be paired with only a single `JL_GC_POP` call. +If all necessary variables you want to root cannot be pushed by a one single call to `JL_GC_PUSH*`, or if there are more than 6 variables to be pushed and using an array of arguments is not an option, then one can use inner blocks: ```c @@ -302,6 +325,19 @@ jl_value_t *ret2 = 0; JL_GC_POP(); // This pops ret1. ``` +Note that it is not necessary to have valid `jl_value_t*` values before calling +`JL_GC_PUSH*`. It is fine to have a number of them initialized to `NULL`, pass those +to `JL_GC_PUSH*` and then create the actual Julia values. For example: + +``` +jl_value_t *ret1 = NULL, *ret2 = NULL; +JL_GC_PUSH2(&ret1, &ret2); +ret1 = jl_eval_string("sqrt(2.0)"); +ret2 = jl_eval_string("sqrt(3.0)"); +// Use ret1 and ret2 +JL_GC_POP(); +``` + If it is required to hold the pointer to a variable between functions (or block scopes), then it is not possible to use `JL_GC_PUSH*`. In this case, it is necessary to create and keep a reference to the variable in the Julia global scope. One simple way to accomplish this is to use a global `IdDict` that @@ -371,14 +407,18 @@ As an alternative for very simple cases, it is possible to just create a global per pointer using ```c -jl_set_global(jl_main_module, jl_symbol("var"), var); +jl_module_t *mod = jl_main_module; +jl_sym_t *var = jl_symbol("var"); +jl_binding_t *bp = jl_get_binding_wr(mod, var); +jl_checked_assignment(bp, mod, var, val); ``` ### Updating fields of GC-managed objects -The garbage collector operates under the assumption that it is aware of every old-generation -object pointing to a young-generation one. Any time a pointer is updated breaking that assumption, -it must be signaled to the collector with the `jl_gc_wb` (write barrier) function like so: +The garbage collector also operates under the assumption that it is aware of every +older-generation object pointing to a younger-generation one. Any time a pointer is updated +breaking that assumption, it must be signaled to the collector with the `jl_gc_wb` (write +barrier) function like so: ```c jl_value_t *parent = some_old_value, *child = some_young_value; @@ -386,10 +426,10 @@ jl_value_t *parent = some_old_value, *child = some_young_value; jl_gc_wb(parent, child); ``` -It is in general impossible to predict which values will be old at runtime, so the write barrier -must be inserted after all explicit stores. One notable exception is if the `parent` object was -just allocated and garbage collection was not run since then. Remember that most `jl_...` functions -can sometimes invoke garbage collection. +It is in general impossible to predict which values will be old at runtime, so the write +barrier must be inserted after all explicit stores. One notable exception is if the `parent` +object has just been allocated and no garbage collection has run since then. Note that most +`jl_...` functions can sometimes invoke garbage collection. The write barrier is also necessary for arrays of pointers when updating their data directly. For example: @@ -402,7 +442,7 @@ data[0] = some_value; jl_gc_wb(some_array, some_value); ``` -### Manipulating the Garbage Collector +### Controlling the Garbage Collector There are some functions to control the GC. In normal use cases, these should not be necessary. @@ -424,8 +464,8 @@ struct that contains: * A pointer to the data block * Information about the sizes of the array -To keep things simple, we start with a 1D array. Creating an array containing Float64 elements -of length 10 is done by: +To keep things simple, we start with a 1D array. Creating an array containing Float64 +elements of length 10 can be done like this: ```c jl_value_t* array_type = jl_apply_array_type((jl_value_t*)jl_float64_type, 1); @@ -444,7 +484,7 @@ The last argument is a boolean indicating whether Julia should take ownership of this argument is non-zero, the GC will call `free` on the data pointer when the array is no longer referenced. -In order to access the data of x, we can use `jl_array_data`: +In order to access the data of `x`, we can use `jl_array_data`: ```c double *xData = (double*)jl_array_data(x); @@ -486,7 +526,7 @@ that creates a 2D array and accesses its properties: ```c // Create 2D array of float64 type -jl_value_t *array_type = jl_apply_array_type(jl_float64_type, 2); +jl_value_t *array_type = jl_apply_array_type((jl_value_t*)jl_float64_type, 2); jl_array_t *x = jl_alloc_array_2d(array_type, 10, 5); // Get array pointer @@ -551,3 +591,111 @@ jl_errorf("argument x = %d is too large", x); ``` where in this example `x` is assumed to be an integer. + + +### Thread-safety + +In general, the Julia C API is not fully thread-safe. When embedding Julia in a multi-threaded application care needs to be taken not to violate +the following restrictions: + +* `jl_init()` may only be called once in the application life-time. The same applies to `jl_atexit_hook()`, and it may only be called after `jl_init()`. +* `jl_...()` API functions may only be called from the thread in which `jl_init()` was called, *or from threads started by the Julia runtime*. Calling Julia API functions from user-started threads is not supported, and may lead to undefined behaviour and crashes. + +The second condition above implies that you can not safely call `jl_...()` functions from threads that were not started by Julia (the thread calling `jl_init()` being the exception). For example, the following is not supported and will most likely segfault: + +```c +void *func(void*) +{ + // Wrong, jl_eval_string() called from thread that was not started by Julia + jl_eval_string("println(Threads.threadid())"); + return NULL; +} + +int main() +{ + pthread_t t; + + jl_init(); + + // Start a new thread + pthread_create(&t, NULL, func, NULL); + pthread_join(t, NULL); + + jl_atexit_hook(0); +} +``` + +Instead, performing all Julia calls from the same user-created thread will work: + +```c +void *func(void*) +{ + // Okay, all jl_...() calls from the same thread, + // even though it is not the main application thread + jl_init(); + jl_eval_string("println(Threads.threadid())"); + jl_atexit_hook(0); + return NULL; +} + +int main() +{ + pthread_t t; + // Create a new thread, which runs func() + pthread_create(&t, NULL, func, NULL); + pthread_join(t, NULL); +} +``` + +An example of calling the Julia C API from a thread started by Julia itself: + +```c +#include +JULIA_DEFINE_FAST_TLS + +double c_func(int i) +{ + printf("[C %08x] i = %d\n", pthread_self(), i); + + // Call the Julia sqrt() function to compute the square root of i, and return it + jl_function_t *sqrt = jl_get_function(jl_base_module, "sqrt"); + jl_value_t* arg = jl_box_int32(i); + double ret = jl_unbox_float64(jl_call1(sqrt, arg)); + + return ret; +} + +int main() +{ + jl_init(); + + // Define a Julia function func() that calls our c_func() defined in C above + jl_eval_string("func(i) = ccall(:c_func, Float64, (Int32,), i)"); + + // Call func() multiple times, using multiple threads to do so + jl_eval_string("println(Threads.threadpoolsize())"); + jl_eval_string("use(i) = println(\"[J $(Threads.threadid())] i = $(i) -> $(func(i))\")"); + jl_eval_string("Threads.@threads for i in 1:5 use(i) end"); + + jl_atexit_hook(0); +} +``` + +If we run this code with 2 Julia threads we get the following output (note: the output will vary per run and system): + +```sh +$ JULIA_NUM_THREADS=2 ./thread_example +2 +[C 3bfd9c00] i = 1 +[C 23938640] i = 4 +[J 1] i = 1 -> 1.0 +[C 3bfd9c00] i = 2 +[J 1] i = 2 -> 1.4142135623730951 +[C 3bfd9c00] i = 3 +[J 2] i = 4 -> 2.0 +[C 23938640] i = 5 +[J 1] i = 3 -> 1.7320508075688772 +[J 2] i = 5 -> 2.23606797749979 +``` + +As can be seen, Julia thread 1 corresponds to pthread ID 3bfd9c00, and Julia thread 2 corresponds to ID 23938640, showing that indeed multiple threads are used at the C level, and that we can safely call Julia C API routines from those threads. diff --git a/doc/src/manual/environment-variables.md b/doc/src/manual/environment-variables.md index 4243c43537cf3..ac5a6fad6cc08 100644 --- a/doc/src/manual/environment-variables.md +++ b/doc/src/manual/environment-variables.md @@ -78,6 +78,7 @@ and a global configuration search path of A directory path that indicates which project should be the initial active project. Setting this environment variable has the same effect as specifying the `--project` start-up option, but `--project` has higher precedence. If the variable is set to `@.` +(note the trailing dot) then Julia tries to find a project directory that contains `Project.toml` or `JuliaProject.toml` file from the current directory and its parents. See also the chapter on [Code Loading](@ref code-loading). @@ -147,6 +148,12 @@ or if it must have a value, set it to the string `:`. On Windows, path elements are separated by the `;` character, as is the case with most path lists on Windows. Replace `:` with `;` in the above paragraph. +!!! note + `JULIA_DEPOT_PATH` must be defined before starting julia; defining it in + `startup.jl` is too late in the startup process; at that point you can instead + directly modify the `DEPOT_PATH` array, which is populated from the environment + variable. + ### `JULIA_HISTORY` The absolute path `REPL.find_hist_file()` of the REPL's history file. If @@ -156,10 +163,14 @@ The absolute path `REPL.find_hist_file()` of the REPL's history file. If $(DEPOT_PATH[1])/logs/repl_history.jl ``` -### `JULIA_MAX_NUM_PRECOMPILE_FILES` +### [`JULIA_MAX_NUM_PRECOMPILE_FILES`](@id env-max-num-precompile-files) Sets the maximum number of different instances of a single package that are to be stored in the precompile cache (default = 10). +### `JULIA_VERBOSE_LINKING` + +If set to true, linker commands will be displayed during precompilation. + ## Pkg.jl ### `JULIA_CI` @@ -266,7 +277,7 @@ To use Visual Studio Code on Windows, set `$JULIA_EDITOR` to `code.cmd`. ## Parallelization -### `JULIA_CPU_THREADS` +### [`JULIA_CPU_THREADS`](@id env-cpu-threads) Overrides the global variable [`Base.Sys.CPU_THREADS`](@ref), the number of logical CPU cores available. @@ -305,6 +316,27 @@ then spinning threads never sleep. Otherwise, `$JULIA_THREAD_SLEEP_THRESHOLD` is interpreted as an unsigned 64-bit integer (`uint64_t`) and gives, in nanoseconds, the amount of time after which spinning threads should sleep. +### [`JULIA_NUM_GC_THREADS`](@id env-gc-threads) + +Sets the number of threads used by Garbage Collection. If unspecified is set to +half of the number of worker threads. + +!!! compat "Julia 1.10" + The environment variable was added in 1.10 + +### [`JULIA_IMAGE_THREADS`](@id env-image-threads) + +An unsigned 32-bit integer that sets the number of threads used by image +compilation in this Julia process. The value of this variable may be +ignored if the module is a small module. If left unspecified, the smaller +of the value of [`JULIA_CPU_THREADS`](@ref env-cpu-threads) or half the +number of logical CPU cores is used in its place. + +### `JULIA_IMAGE_TIMINGS` + +A boolean value that determines if detailed timing information is printed during +during image compilation. Defaults to 0. + ### `JULIA_EXCLUSIVE` If set to anything besides `0`, then Julia's thread policy is consistent with @@ -424,4 +456,3 @@ On debug builds of Julia this is always enabled. Recommended to use with `-g 2`. ### `JULIA_LLVM_ARGS` Arguments to be passed to the LLVM backend. - diff --git a/doc/src/manual/faq.md b/doc/src/manual/faq.md index f3373f5a32ee5..e3960ee1a4690 100644 --- a/doc/src/manual/faq.md +++ b/doc/src/manual/faq.md @@ -103,43 +103,25 @@ which may or may not be caused by CTRL-C, use [`atexit`](@ref). Alternatively, you can use `julia -e 'include(popfirst!(ARGS))' file.jl` to execute a script while being able to catch `InterruptException` in the [`try`](@ref) block. +Note that with this strategy [`PROGRAM_FILE`](@ref) will not be set. ### How do I pass options to `julia` using `#!/usr/bin/env`? -Passing options to `julia` in so-called shebang by, e.g., -`#!/usr/bin/env julia --startup-file=no` may not work in some -platforms such as Linux. This is because argument parsing in shebang -is platform-dependent and not well-specified. In a Unix-like -environment, a reliable way to pass options to `julia` in an -executable script would be to start the script as a `bash` script and -use `exec` to replace the process to `julia`: +Passing options to `julia` in a so-called shebang line, as in +`#!/usr/bin/env julia --startup-file=no`, will not work on many +platforms (BSD, macOS, Linux) where the kernel, unlike the shell, does +not split arguments at space characters. The option `env -S`, which +splits a single argument string into multiple arguments at spaces, +similar to a shell, offers a simple workaround: ```julia -#!/bin/bash -#= -exec julia --color=yes --startup-file=no "${BASH_SOURCE[0]}" "$@" -=# - +#!/usr/bin/env -S julia --color=yes --startup-file=no @show ARGS # put any Julia code here ``` -In the example above, the code between `#=` and `=#` is run as a `bash` -script. Julia ignores this part since it is a multi-line comment for -Julia. The Julia code after `=#` is ignored by `bash` since it stops -parsing the file once it reaches to the `exec` statement. - !!! note - In order to [catch CTRL-C](@ref catch-ctrl-c) in the script you can use - ```julia - #!/bin/bash - #= - exec julia --color=yes --startup-file=no -e 'include(popfirst!(ARGS))' \ - "${BASH_SOURCE[0]}" "$@" - =# - - @show ARGS # put any Julia code here - ``` - instead. Note that with this strategy [`PROGRAM_FILE`](@ref) will not be set. + Option `env -S` appeared in FreeBSD 6.0 (2005), macOS Sierra (2016) + and GNU/Linux coreutils 8.30 (2018). ### Why doesn't `run` support `*` or pipes for scripting external programs? @@ -172,7 +154,7 @@ while x < 10 end ``` and notice that it works fine in an interactive environment (like the Julia REPL), -but gives `UndefVarError: x not defined` when you try to run it in script or other +but gives ```UndefVarError: `x` not defined``` when you try to run it in script or other file. What is going on is that Julia generally requires you to **be explicit about assigning to global variables in a local scope**. Here, `x` is a global variable, `while` defines a [local scope](@ref scope-of-variables), and `x += 1` is @@ -420,7 +402,7 @@ Certain operations make mathematical sense but result in errors: ```jldoctest julia> sqrt(-2.0) ERROR: DomainError with -2.0: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). Stacktrace: [...] ``` @@ -443,7 +425,7 @@ julia> sqrt(-2.0+0im) ### How can I constrain or compute type parameters? The parameters of a [parametric type](@ref Parametric-Types) can hold either -types or bits values, and the type itself chooses how it makes use of these parameters. +types or bits values, and the type itself chooses how it makes use of these parameters. For example, `Array{Float64, 2}` is parameterized by the type `Float64` to express its element type and the integer value `2` to express its number of dimensions. When defining your own parametric type, you can use subtype constraints to declare that a @@ -723,7 +705,7 @@ julia> module Foo julia> Foo.foo() ERROR: On worker 2: -UndefVarError: Foo not defined +UndefVarError: `Foo` not defined Stacktrace: [...] ``` @@ -744,7 +726,7 @@ julia> @everywhere module Foo julia> Foo.foo() ERROR: On worker 2: -UndefVarError: gvar not defined +UndefVarError: `gvar` not defined Stacktrace: [...] ``` @@ -780,7 +762,7 @@ bar (generic function with 1 method) julia> remotecall_fetch(bar, 2) ERROR: On worker 2: -UndefVarError: #bar not defined +UndefVarError: `#bar` not defined [...] julia> anon_bar = ()->1 @@ -802,8 +784,13 @@ foo (generic function with 1 method) julia> foo([1]) ERROR: MethodError: no method matching foo(::Vector{Int64}) + Closest candidates are: - foo(!Matched::Vector{Real}) at none:1 + foo(!Matched::Vector{Real}) + @ Main none:1 + +Stacktrace: +[...] ``` This is because `Vector{Real}` is not a supertype of `Vector{Int}`! You can solve this problem with something @@ -831,10 +818,13 @@ to strings); similarly, `repeat` can be used instead of `^` to repeat strings. T ### What is the difference between "using" and "import"? -There is only one difference, and on the surface (syntax-wise) it may seem very minor. The difference -between `using` and `import` is that with `using` you need to say `function Foo.bar(..` to -extend module Foo's function bar with a new method, but with `import Foo.bar`, -you only need to say `function bar(...` and it automatically extends module Foo's function bar. +There are several differences between `using` and `import` +(see the [Modules section](https://docs.julialang.org/en/v1/manual/modules/#modules)), +but there is an important difference that may not seem intuitive at first glance, +and on the surface (i.e. syntax-wise) it may seem very minor. When loading modules with `using`, +you need to say `function Foo.bar(...` to extend module `Foo`'s function `bar` with a new method, +but with `import Foo.bar`, you only need to say `function bar(...` and it automatically extends +module `Foo`'s function `bar`. The reason this is important enough to have been given separate syntax is that you don't want to accidentally extend a function that you didn't know existed, because that could easily cause @@ -1062,12 +1052,12 @@ The Stable version of Julia is the latest released version of Julia, this is the It has the latest features, including improved performance. The Stable version of Julia is versioned according to [SemVer](https://semver.org/) as v1.x.y. A new minor release of Julia corresponding to a new Stable version is made approximately every 4-5 months after a few weeks of testing as a release candidate. -Unlike the LTS version the a Stable version will not normally receive bugfixes after another Stable version of Julia has been released. +Unlike the LTS version the Stable version will not normally receive bugfixes after another Stable version of Julia has been released. However, upgrading to the next Stable release will always be possible as each release of Julia v1.x will continue to run code written for earlier versions. You may prefer the LTS (Long Term Support) version of Julia if you are looking for a very stable code base. -The current LTS version of Julia is versioned according to SemVer as v1.0.x; -this branch will continue to receive bugfixes until a new LTS branch is chosen, at which point the v1.0.x series will no longer received regular bug fixes and all but the most conservative users will be advised to upgrade to the new LTS version series. +The current LTS version of Julia is versioned according to SemVer as v1.6.x; +this branch will continue to receive bugfixes until a new LTS branch is chosen, at which point the v1.6.x series will no longer received regular bug fixes and all but the most conservative users will be advised to upgrade to the new LTS version series. As a package developer, you may prefer to develop for the LTS version, to maximize the number of users who can use your package. As per SemVer, code written for v1.0 will continue to work for all future LTS and Stable versions. In general, even if targeting the LTS, one can develop and run code in the latest Stable version, to take advantage of the improved performance; so long as one avoids using new features (such as added library functions or new methods). diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index b0c70a378df89..a724f450dccfa 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -54,14 +54,54 @@ julia> ∑(2, 3) 5 ``` -## Argument Passing Behavior +## [Argument Passing Behavior](@id man-argument-passing) Julia function arguments follow a convention sometimes called "pass-by-sharing", which means that values are not copied when they are passed to functions. Function arguments themselves act as -new variable *bindings* (new locations that can refer to values), but the values they refer to +new variable *bindings* (new "names" that can refer to values), much like +[assignments](@ref man-assignment-expressions) `argument_name = argument_value`, so that the objects they refer to are identical to the passed values. Modifications to mutable values (such as `Array`s) made within -a function will be visible to the caller. This is the same behavior found in Scheme, most Lisps, -Python, Ruby and Perl, among other dynamic languages. +a function will be visible to the caller. (This is the same behavior found in Scheme, most Lisps, +Python, Ruby and Perl, among other dynamic languages.) + +For example, in the function +```julia +function f(x, y) + x[1] = 42 # mutates x + y = 7 + y # new binding for y, no mutation + return y +end +``` +The statement `x[1] = 42` *mutates* the object `x`, and hence this change *will* be visible in the array passed +by the caller for this argument. On the other hand, the assignment `y = 7 + y` changes the *binding* ("name") +`y` to refer to a new value `7 + y`, rather than mutating the *original* object referred to by `y`, +and hence does *not* change the corresponding argument passed by the caller. This can be seen if we call `f(x, y)`: +```julia-repl +julia> a = [4,5,6] +3-element Vector{Int64}: + 4 + 5 + 6 + +julia> b = 3 +3 + +julia> f(a, b) # returns 7 + b == 10 +10 + +julia> a # a[1] is changed to 42 by f +3-element Vector{Int64}: + 42 + 5 + 6 + +julia> b # not changed +3 +``` +As a common convention in Julia (not a syntactic requirement), such a function would +[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. + ## Argument-type declarations @@ -224,16 +264,20 @@ Under the name `f`, the function does not support infix notation, however. A few special expressions correspond to calls to functions with non-obvious names. These are: -| Expression | Calls | -|:----------------- |:----------------------- | -| `[A B C ...]` | [`hcat`](@ref) | -| `[A; B; C; ...]` | [`vcat`](@ref) | -| `[A B; C D; ...]` | [`hvcat`](@ref) | -| `A'` | [`adjoint`](@ref) | -| `A[i]` | [`getindex`](@ref) | -| `A[i] = x` | [`setindex!`](@ref) | -| `A.n` | [`getproperty`](@ref Base.getproperty) | -| `A.n = x` | [`setproperty!`](@ref Base.setproperty!) | +| Expression | Calls | +|:--------------------- |:----------------------- | +| `[A B C ...]` | [`hcat`](@ref) | +| `[A; B; C; ...]` | [`vcat`](@ref) | +| `[A B; C D; ...]` | [`hvcat`](@ref) | +| `[A; B;; C; D;; ...]` | [`hvncat`](@ref) | +| `A'` | [`adjoint`](@ref) | +| `A[i]` | [`getindex`](@ref) | +| `A[i] = x` | [`setindex!`](@ref) | +| `A.n` | [`getproperty`](@ref Base.getproperty) | +| `A.n = x` | [`setproperty!`](@ref Base.setproperty!) | + +Note that expressions similar to `[A; B;; C; D;; ...]` but with more than two +consecutive `;` also correspond to `hvncat` calls. ## [Anonymous Functions](@id man-anonymous-functions) @@ -304,7 +348,7 @@ get(()->time(), dict, key) ``` The call to [`time`](@ref) is delayed by wrapping it in a 0-argument anonymous function -that is called only when the requested key is absent from `dict`. +that is called only if the requested key is absent from `dict`. ## Tuples @@ -348,9 +392,8 @@ julia> x.a 2 ``` -Named tuples are very similar to tuples, except that fields can additionally be accessed by name -using dot syntax (`x.a`) in addition to the regular indexing syntax -(`x[1]`). +The fields of named tuples can be accessed by name using dot syntax (`x.a`) in +addition to the regular indexing syntax (`x[1]` or `x[:a]`). ## [Destructuring Assignment and Multiple Return Values](@id destructuring-assignment) @@ -475,6 +518,57 @@ Base.Iterators.Rest{Base.Generator{UnitRange{Int64}, typeof(abs2)}, Int64}(Base. See [`Base.rest`](@ref) for details on the precise handling and customization for specific iterators. +!!! compat "Julia 1.9" + `...` in non-final position of an assignment requires Julia 1.9 + +Slurping in assignments can also occur in any other position. As opposed to slurping the end +of a collection however, this will always be eager. + +```jldoctest +julia> a, b..., c = 1:5 +1:5 + +julia> a +1 + +julia> b +3-element Vector{Int64}: + 2 + 3 + 4 + +julia> c +5 + +julia> front..., tail = "Hi!" +"Hi!" + +julia> front +"Hi" + +julia> tail +'!': ASCII/Unicode U+0021 (category Po: Punctuation, other) +``` + +This is implemented in terms of the function [`Base.split_rest`](@ref). + +Note that for variadic function definitions, slurping is still only allowed in final position. +This does not apply to [single argument destructuring](@ref man-argument-destructuring) though, +as that does not affect method dispatch: + +```jldoctest +julia> f(x..., y) = x +ERROR: syntax: invalid "..." on non-final argument +Stacktrace: +[...] + +julia> f((x..., y)) = x +f (generic function with 1 method) + +julia> f((1, 2, 3)) +(1, 2) +``` + ## Property destructuring Instead of destructuring based on iteration, the right side of assignments can also be destructured using property names. @@ -492,7 +586,7 @@ julia> b 2 ``` -## Argument destructuring +## [Argument destructuring](@id man-argument-destructuring) The destructuring feature can also be used within a function argument. If a function argument name is written as a tuple (e.g. `(x, y)`) instead of just @@ -644,8 +738,13 @@ julia> args = [1,2,3] julia> baz(args...) ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64) + Closest candidates are: - baz(::Any, ::Any) at none:1 + baz(::Any, ::Any) + @ Main none:1 + +Stacktrace: +[...] ``` As you can see, if the wrong number of elements are in the splatted container, then the function @@ -660,12 +759,15 @@ from `Dates` module constructs a `Date` type for a given year `y`, month `m` and However, `m` and `d` arguments are optional and their default value is `1`. This behavior can be expressed concisely as: -```julia -function Date(y::Int64, m::Int64=1, d::Int64=1) - err = validargs(Date, y, m, d) - err === nothing || throw(err) - return Date(UTD(totaldays(y, m, d))) -end +```jldoctest date_default_args +julia> using Dates + +julia> function date(y::Int64, m::Int64=1, d::Int64=1) + err = Dates.validargs(Date, y, m, d) + err === nothing || throw(err) + return Date(Dates.UTD(Dates.totaldays(y, m, d))) + end +date (generic function with 3 methods) ``` Observe, that this definition calls another method of the `Date` function that takes one argument @@ -674,22 +776,28 @@ of type `UTInstant{Day}`. With this definition, the function can be called with either one, two or three arguments, and `1` is automatically passed when only one or two of the arguments are specified: -```jldoctest -julia> using Dates - -julia> Date(2000, 12, 12) +```jldoctest date_default_args +julia> date(2000, 12, 12) 2000-12-12 -julia> Date(2000, 12) +julia> date(2000, 12) 2000-12-01 -julia> Date(2000) +julia> date(2000) 2000-01-01 ``` Optional arguments are actually just a convenient syntax for writing multiple method definitions with different numbers of arguments (see [Note on Optional and keyword Arguments](@ref)). -This can be checked for our `Date` function example by calling `methods` function. +This can be checked for our `date` function example by calling the `methods` function: + +```julia-repl +julia> methods(date) +# 3 methods for generic function "date": +[1] date(y::Int64) in Main at REPL[1]:1 +[2] date(y::Int64, m::Int64) in Main at REPL[1]:1 +[3] date(y::Int64, m::Int64, d::Int64) in Main at REPL[1]:1 +``` ## Keyword Arguments @@ -745,7 +853,8 @@ end ``` Inside `f`, `kwargs` will be an immutable key-value iterator over a named tuple. -Named tuples (as well as dictionaries with keys of `Symbol`) can be passed as +Named tuples (as well as dictionaries with keys of `Symbol`, and other iterators +yielding two-value collections with symbol as first values) can be passed as keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`. If a keyword argument is not assigned a default value in the method definition, @@ -923,7 +1032,7 @@ julia> ["a", "list", "of", "strings"] .|> [uppercase, reverse, titlecase, length 7 ``` -When combining pipes with anonymous functions, parentheses must be used if subsequent pipes are not to parsed as part of the anonymous function's body. Compare: +When combining pipes with anonymous functions, parentheses must be used if subsequent pipes are not to be parsed as part of the anonymous function's body. Compare: ```jldoctest julia> 1:3 .|> (x -> x^2) |> sum |> sqrt @@ -1041,7 +1150,7 @@ they are equivalent to `broadcast` calls and are fused with other nested "dot" c You can also combine dot operations with function chaining using [`|>`](@ref), as in this example: ```jldoctest -julia> [1:5;] .|> [x->x^2, inv, x->2*x, -, isodd] +julia> 1:5 .|> [x->x^2, inv, x->2*x, -, isodd] 5-element Vector{Real}: 1 0.5 diff --git a/doc/src/manual/getting-started.md b/doc/src/manual/getting-started.md index a3a92c6d7c93c..16dab24afecf9 100644 --- a/doc/src/manual/getting-started.md +++ b/doc/src/manual/getting-started.md @@ -34,8 +34,7 @@ command: $ julia script.jl ``` -You can pass additional arguments to Julia, and to your program `script.jl`. A detailed list of all the available switches can be found at [Command-line Options](@ref -command-line-options). +You can pass additional arguments to Julia, and to your program `script.jl`. A detailed list of all the available options can be found under [Command-line Interface](@ref). ## Resources diff --git a/doc/src/manual/handling-operating-system-variation.md b/doc/src/manual/handling-operating-system-variation.md index d8dc3abd93d7f..26583b1379e45 100644 --- a/doc/src/manual/handling-operating-system-variation.md +++ b/doc/src/manual/handling-operating-system-variation.md @@ -40,4 +40,3 @@ When nesting conditionals, the `@static` must be repeated for each level ```julia @static Sys.iswindows() ? :a : (@static Sys.isapple() ? :b : :c) ``` - diff --git a/doc/src/manual/integers-and-floating-point-numbers.md b/doc/src/manual/integers-and-floating-point-numbers.md index 24c7a8c5a0eeb..173ca7847616e 100644 --- a/doc/src/manual/integers-and-floating-point-numbers.md +++ b/doc/src/manual/integers-and-floating-point-numbers.md @@ -21,15 +21,15 @@ The following are Julia's primitive numeric types: | Type | Signed? | Number of bits | Smallest value | Largest value | |:----------------- |:------- |:-------------- |:-------------- |:------------- | | [`Int8`](@ref) | ✓ | 8 | -2^7 | 2^7 - 1 | -| [`UInt8`](@ref) |   | 8 | 0 | 2^8 - 1 | +| [`UInt8`](@ref) | | 8 | 0 | 2^8 - 1 | | [`Int16`](@ref) | ✓ | 16 | -2^15 | 2^15 - 1 | -| [`UInt16`](@ref) |   | 16 | 0 | 2^16 - 1 | +| [`UInt16`](@ref) | | 16 | 0 | 2^16 - 1 | | [`Int32`](@ref) | ✓ | 32 | -2^31 | 2^31 - 1 | -| [`UInt32`](@ref) |   | 32 | 0 | 2^32 - 1 | +| [`UInt32`](@ref) | | 32 | 0 | 2^32 - 1 | | [`Int64`](@ref) | ✓ | 64 | -2^63 | 2^63 - 1 | -| [`UInt64`](@ref) |   | 64 | 0 | 2^64 - 1 | +| [`UInt64`](@ref) | | 64 | 0 | 2^64 - 1 | | [`Int128`](@ref) | ✓ | 128 | -2^127 | 2^127 - 1 | -| [`UInt128`](@ref) |   | 128 | 0 | 2^128 - 1 | +| [`UInt128`](@ref) | | 128 | 0 | 2^128 - 1 | | [`Bool`](@ref) | N/A | 8 | `false` (0) | `true` (1) | * **Floating-point types:** @@ -185,7 +185,9 @@ determining storage size of a literal. So `0x01` is a `UInt8` while `0x0001` is That allows the user to control the size. -Values which cannot be stored in `UInt128` cannot be written as such literals. +Unsigned literals (starting with `0x`) that encode integers too large to be represented as +`UInt128` values will construct `BigInt` values instead. This is not an unsigned type but +it is the only built-in type big enough to represent such large integer values. Binary, octal, and hexadecimal literals may be signed by a `-` immediately preceding the unsigned literal. They produce an unsigned integer of the same size as the unsigned literal diff --git a/doc/src/manual/interfaces.md b/doc/src/manual/interfaces.md index 86f0fd8327327..bcb15da69dedf 100644 --- a/doc/src/manual/interfaces.md +++ b/doc/src/manual/interfaces.md @@ -7,17 +7,17 @@ to generically build upon those behaviors. ## [Iteration](@id man-interface-iteration) -| Required methods |   | Brief description | +| Required methods | | Brief description | |:------------------------------ |:---------------------- |:------------------------------------------------------------------------------------- | -| `iterate(iter)` |   | Returns either a tuple of the first item and initial state or [`nothing`](@ref) if empty | -| `iterate(iter, state)` |   | Returns either a tuple of the next item and next state or `nothing` if no items remain | +| `iterate(iter)` | | Returns either a tuple of the first item and initial state or [`nothing`](@ref) if empty | +| `iterate(iter, state)` | | Returns either a tuple of the next item and next state or `nothing` if no items remain | | **Important optional methods** | **Default definition** | **Brief description** | | `Base.IteratorSize(IterType)` | `Base.HasLength()` | One of `Base.HasLength()`, `Base.HasShape{N}()`, `Base.IsInfinite()`, or `Base.SizeUnknown()` as appropriate | | `Base.IteratorEltype(IterType)`| `Base.HasEltype()` | Either `Base.EltypeUnknown()` or `Base.HasEltype()` as appropriate | | `eltype(IterType)` | `Any` | The type of the first entry of the tuple returned by `iterate()` | | `length(iter)` | (*undefined*) | The number of items, if known | | `size(iter, [dim])` | (*undefined*) | The number of items in each dimension, if known | -| `Base.isdone(iter[, state])` | `missing` | Fast-path hint for iterator completion. Should be defined for mutable iterators, or else `isempty(iter)` will call `iterate(iter[, state])` and may mutate the iterator. | +| `Base.isdone(iter[, state])` | `missing` | Fast-path hint for iterator completion. Should be defined for stateful iterators, or else `isempty(iter)` may call `iterate(iter[, state])` and mutate the iterator. | | Value returned by `IteratorSize(IterType)` | Required Methods | |:------------------------------------------ |:------------------------------------------ | @@ -85,20 +85,14 @@ julia> for item in Squares(7) ``` We can use many of the builtin methods that work with iterables, -like [`in`](@ref), or [`mean`](@ref) and [`std`](@ref) from the -`Statistics` standard library module: +like [`in`](@ref) or [`sum`](@ref): ```jldoctest squaretype julia> 25 in Squares(10) true -julia> using Statistics - -julia> mean(Squares(100)) -3383.5 - -julia> std(Squares(100)) -3024.355854282583 +julia> sum(Squares(100)) +338350 ``` There are a few more methods we can extend to give Julia more information about this iterable @@ -221,15 +215,15 @@ ourselves, we can officially define it as a subtype of an [`AbstractArray`](@ref ## [Abstract Arrays](@id man-interface-array) -| Methods to implement |   | Brief description | +| Methods to implement | | Brief description | |:----------------------------------------------- |:-------------------------------------- |:------------------------------------------------------------------------------------- | -| `size(A)` |   | Returns a tuple containing the dimensions of `A` | -| `getindex(A, i::Int)` |   | (if `IndexLinear`) Linear scalar indexing | -| `getindex(A, I::Vararg{Int, N})` |   | (if `IndexCartesian`, where `N = ndims(A)`) N-dimensional scalar indexing | -| `setindex!(A, v, i::Int)` |   | (if `IndexLinear`) Scalar indexed assignment | -| `setindex!(A, v, I::Vararg{Int, N})` |   | (if `IndexCartesian`, where `N = ndims(A)`) N-dimensional scalar indexed assignment | +| `size(A)` | | Returns a tuple containing the dimensions of `A` | +| `getindex(A, i::Int)` | | (if `IndexLinear`) Linear scalar indexing | +| `getindex(A, I::Vararg{Int, N})` | | (if `IndexCartesian`, where `N = ndims(A)`) N-dimensional scalar indexing | | **Optional methods** | **Default definition** | **Brief description** | | `IndexStyle(::Type)` | `IndexCartesian()` | Returns either `IndexLinear()` or `IndexCartesian()`. See the description below. | +| `setindex!(A, v, i::Int)` | | (if `IndexLinear`) Scalar indexed assignment | +| `setindex!(A, v, I::Vararg{Int, N})` | | (if `IndexCartesian`, where `N = ndims(A)`) N-dimensional scalar indexed assignment | | `getindex(A, I...)` | defined in terms of scalar `getindex` | [Multidimensional and nonscalar indexing](@ref man-array-indexing) | | `setindex!(A, X, I...)` | defined in terms of scalar `setindex!` | [Multidimensional and nonscalar indexed assignment](@ref man-array-indexing) | | `iterate` | defined in terms of scalar `getindex` | Iteration | @@ -239,7 +233,7 @@ ourselves, we can officially define it as a subtype of an [`AbstractArray`](@ref | `similar(A, dims::Dims)` | `similar(A, eltype(A), dims)` | Return a mutable array with the same element type and size *dims* | | `similar(A, ::Type{S}, dims::Dims)` | `Array{S}(undef, dims)` | Return a mutable array with the specified element type and size | | **Non-traditional indices** | **Default definition** | **Brief description** | -| `axes(A)` | `map(OneTo, size(A))` | Return a tuple of `AbstractUnitRange{<:Integer}` of valid indices | +| `axes(A)` | `map(OneTo, size(A))` | Return a tuple of `AbstractUnitRange{<:Integer}` of valid indices. The axes should be their own axes, that is `axes.(axes(A),1) == axes(A)` should be satisfied. | | `similar(A, ::Type{S}, inds)` | `similar(A, S, Base.to_shape(inds))` | Return a mutable array with the specified indices `inds` (see below) | | `similar(T::Union{Type,Function}, inds)` | `T(Base.to_shape(inds))` | Return an array similar to `T` with the specified indices `inds` (see below) | @@ -258,7 +252,7 @@ provides a traits-based mechanism to enable efficient generic code for all array This distinction determines which scalar indexing methods the type must define. `IndexLinear()` arrays are simple: just define `getindex(A::ArrayType, i::Int)`. When the array is subsequently -indexed with a multidimensional set of indices, the fallback `getindex(A::AbstractArray, I...)()` +indexed with a multidimensional set of indices, the fallback `getindex(A::AbstractArray, I...)` efficiently converts the indices into one linear index and then calls the above method. `IndexCartesian()` arrays, on the other hand, require methods to be defined for each supported dimensionality with `ndims(A)` `Int` indices. For example, [`SparseMatrixCSC`](@ref) from the `SparseArrays` standard @@ -468,10 +462,17 @@ Not all types support `axes` and indexing, but many are convenient to allow in b The [`Base.broadcastable`](@ref) function is called on each argument to broadcast, allowing it to return something different that supports `axes` and indexing. By default, this is the identity function for all `AbstractArray`s and `Number`s — they already -support `axes` and indexing. For a handful of other types (including but not limited to -types themselves, functions, special singletons like [`missing`](@ref) and [`nothing`](@ref), and dates), -`Base.broadcastable` returns the argument wrapped in a `Ref` to act as a 0-dimensional -"scalar" for the purposes of broadcasting. Custom types can similarly specialize +support `axes` and indexing. + +If a type is intended to act like a "0-dimensional scalar" (a single object) rather than as a +container for broadcasting, then the following method should be defined: +```julia +Base.broadcastable(o::MyType) = Ref(o) +``` +that returns the argument wrapped in a 0-dimensional [`Ref`](@ref) container. For example, such a wrapper +method is defined for types themselves, functions, special singletons like [`missing`](@ref) and [`nothing`](@ref), and dates. + +Custom array-like types can specialize `Base.broadcastable` to define their shape, but they should follow the convention that `collect(Base.broadcastable(x)) == collect(x)`. A notable exception is `AbstractString`; strings are special-cased to behave as scalars for the purposes of broadcast even though @@ -740,3 +741,103 @@ yields another `SparseVecStyle`, that its combination with a 2-dimensional array yields a `SparseMatStyle`, and anything of higher dimensionality falls back to the dense arbitrary-dimensional framework. These rules allow broadcasting to keep the sparse representation for operations that result in one or two dimensional outputs, but produce an `Array` for any other dimensionality. + +## [Instance Properties](@id man-instance-properties) + +| Methods to implement | Default definition | Brief description | +|:--------------------------------- |:---------------------------- |:------------------------------------------------------------------------------------- | +| `propertynames(x::ObjType, private::Bool=false)` | `fieldnames(typeof(x))` | Return a tuple of the properties (`x.property`) of an object `x`. If `private=true`, also return property names intended to be kept as private | +| `getproperty(x::ObjType, s::Symbol)` | `getfield(x, s)` | Return property `s` of `x`. `x.s` calls `getproperty(x, :s)`. | +| `setproperty!(x::ObjType, s::Symbol, v)` | `setfield!(x, s, v)` | Set property `s` of `x` to `v`. `x.s = v` calls `setproperty!(x, :s, v)`. Should return `v`.| + +Sometimes, it is desirable to change how the end-user interacts with the fields of an object. +Instead of granting direct access to type fields, an extra layer of abstraction between +the user and the code can be provided by overloading `object.field`. Properties are what the +user *sees of* the object, fields what the object *actually is*. + +By default, properties and fields are the same. However, this behavior can be changed. +For example, take this representation of a point in a plane in [polar coordinates](https://en.wikipedia.org/wiki/Polar_coordinate_system): + +```jldoctest polartype +julia> mutable struct Point + r::Float64 + ϕ::Float64 + end + +julia> p = Point(7.0, pi/4) +Point(7.0, 0.7853981633974483) +``` + +As described in the table above dot access `p.r` is the same as `getproperty(p, :r)` which is by default the same as `getfield(p, :r)`: + +```jldoctest polartype +julia> propertynames(p) +(:r, :ϕ) + +julia> getproperty(p, :r), getproperty(p, :ϕ) +(7.0, 0.7853981633974483) + +julia> p.r, p.ϕ +(7.0, 0.7853981633974483) + +julia> getfield(p, :r), getproperty(p, :ϕ) +(7.0, 0.7853981633974483) +``` + +However, we may want users to be unaware that `Point` stores the coordinates as `r` and `ϕ` (fields), +and instead interact with `x` and `y` (properties). The methods in the first column can be +defined to add new functionality: + +```jldoctest polartype +julia> Base.propertynames(::Point, private::Bool=false) = private ? (:x, :y, :r, :ϕ) : (:x, :y) + +julia> function Base.getproperty(p::Point, s::Symbol) + if s === :x + return getfield(p, :r) * cos(getfield(p, :ϕ)) + elseif s === :y + return getfield(p, :r) * sin(getfield(p, :ϕ)) + else + # This allows accessing fields with p.r and p.ϕ + return getfield(p, s) + end + end + +julia> function Base.setproperty!(p::Point, s::Symbol, f) + if s === :x + y = p.y + setfield!(p, :r, sqrt(f^2 + y^2)) + setfield!(p, :ϕ, atan(y, f)) + return f + elseif s === :y + x = p.x + setfield!(p, :r, sqrt(x^2 + f^2)) + setfield!(p, :ϕ, atan(f, x)) + return f + else + # This allow modifying fields with p.r and p.ϕ + return setfield!(p, s, f) + end + end +``` + +It is important that `getfield` and `setfield` are used inside `getproperty` and `setproperty!` instead of the dot syntax, +since the dot syntax would make the functions recursive which can lead to type inference issues. We can now +try out the new functionality: + +```jldoctest polartype +julia> propertynames(p) +(:x, :y) + +julia> p.x +4.949747468305833 + +julia> p.y = 4.0 +4.0 + +julia> p.r +6.363961030678928 +``` + +Finally, it is worth noting that adding instance properties like this is quite +rarely done in Julia and should in general only be done if there is a good +reason for doing so. diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index a374b9c879e6a..2d7deae0f1c54 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -11,6 +11,21 @@ code in Julia are represented by Julia data structures, powerful [reflection](ht capabilities are available to explore the internals of a program and its types just like any other data. +!!! warning + Metaprogramming is a powerful tool, but it introduces complexity that can make code more + difficult to understand. For example, it can be surprisingly hard to get scope rules + correct. Metaprogramming should typically be used only when other approaches such as + [higher order functions](@ref man-anonymous-functions) and + [closures](https://en.wikipedia.org/wiki/Closure_(computer_programming)) cannot be applied. + + `eval` and defining new macros should be typically used as a last resort. It is almost + never a good idea to use `Meta.parse` or convert an arbitrary string into Julia code. For + manipulating Julia code, use the `Expr` data structure directly to avoid the complexity + of how Julia syntax is parsed. + + The best uses of metaprogramming often implement most of their functionality in runtime + helper functions, striving to minimize the amount of code they generate. + ## Program representation Every Julia program starts life as a string: @@ -102,7 +117,7 @@ julia> Meta.show_sexpr(ex3) The `:` character has two syntactic purposes in Julia. The first form creates a [`Symbol`](@ref), an [interned string](https://en.wikipedia.org/wiki/String_interning) used as one building-block -of expressions: +of expressions, from valid identifiers: ```jldoctest julia> s = :foo @@ -116,9 +131,12 @@ The [`Symbol`](@ref) constructor takes any number of arguments and creates a new their string representations together: ```jldoctest -julia> :foo == Symbol("foo") +julia> :foo === Symbol("foo") true +julia> Symbol("1foo") # `:1foo` would not work, as `1foo` is not a valid identifier +Symbol("1foo") + julia> Symbol("func",10) :func10 @@ -126,9 +144,6 @@ julia> Symbol(:var,'_',"sym") :var_sym ``` -Note that to use `:` syntax, the symbol's name must be a valid identifier. -Otherwise the `Symbol(str)` constructor must be used. - In the context of an expression, symbols are used to indicate access to variables; when an expression is evaluated, a symbol is replaced with the value bound to that symbol in the appropriate [scope](@ref scope-of-variables). @@ -364,7 +379,7 @@ julia> ex = :(a + b) :(a + b) julia> eval(ex) -ERROR: UndefVarError: b not defined +ERROR: UndefVarError: `b` not defined [...] julia> a = 1; b = 2; @@ -382,7 +397,7 @@ julia> ex = :(x = 1) :(x = 1) julia> x -ERROR: UndefVarError: x not defined +ERROR: UndefVarError: `x` not defined julia> eval(ex) 1 @@ -425,7 +440,7 @@ value 1 and the variable `b`. Note the important distinction between the way `a` As hinted above, one extremely useful feature of Julia is the capability to generate and manipulate Julia code within Julia itself. We have already seen one example of a function returning [`Expr`](@ref) -objects: the [`parse`](@ref) function, which takes a string of Julia code and returns the corresponding +objects: the [`Meta.parse`](@ref) function, which takes a string of Julia code and returns the corresponding `Expr`. A function can also take one or more `Expr` objects as arguments, and return another `Expr`. Here is a simple, motivating example: @@ -1353,7 +1368,8 @@ Both these implementations, although different, do essentially the same thing: a over the dimensions of the array, collecting the offset in each dimension into the final index. However, all the information we need for the loop is embedded in the type information of the arguments. -Thus, we can utilize generated functions to move the iteration to compile-time; in compiler parlance, +This allows the compiler to move the iteration to compile time and eliminate the runtime loops +altogether. We can utilize generated functions to achieve a similar effect; in compiler parlance, we use generated functions to manually unroll the loop. The body becomes almost identical, but instead of calculating the linear index, we build up an *expression* that calculates the index: diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 1985292d66611..8ca00aa1cfe76 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -76,18 +76,33 @@ 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) + Closest candidates are: - f(::Float64, !Matched::Float64) at none:1 + f(::Float64, !Matched::Float64) + @ Main none:1 + +Stacktrace: +[...] julia> f(Float32(2.0), 3.0) ERROR: MethodError: no method matching f(::Float32, ::Float64) + Closest candidates are: - f(!Matched::Float64, ::Float64) at none:1 + f(!Matched::Float64, ::Float64) + @ Main none:1 + +Stacktrace: +[...] julia> f(2.0, "3.0") ERROR: MethodError: no method matching f(::Float64, ::String) + Closest candidates are: - f(::Float64, !Matched::Float64) at none:1 + f(::Float64, !Matched::Float64) + @ Main none:1 + +Stacktrace: +[...] julia> f("2.0", "3.0") ERROR: MethodError: no method matching f(::String, ::String) @@ -149,14 +164,25 @@ and applying it will still result in a [`MethodError`](@ref): ```jldoctest fofxy julia> f("foo", 3) ERROR: MethodError: no method matching f(::String, ::Int64) + Closest candidates are: - f(!Matched::Number, ::Number) at none:1 + f(!Matched::Number, ::Number) + @ Main none:1 + +Stacktrace: +[...] julia> f() ERROR: MethodError: no method matching f() + Closest candidates are: - f(!Matched::Float64, !Matched::Float64) at none:1 - f(!Matched::Number, !Matched::Number) at none:1 + f(!Matched::Float64, !Matched::Float64) + @ Main none:1 + f(!Matched::Number, !Matched::Number) + @ Main none:1 + +Stacktrace: +[...] ``` You can easily see which methods exist for a function by entering the function object itself in @@ -172,9 +198,11 @@ of those methods are, use the [`methods`](@ref) function: ```jldoctest fofxy julia> methods(f) -# 2 methods for generic function "f": -[1] f(x::Float64, y::Float64) in Main at none:1 -[2] f(x::Number, y::Number) in Main at none:1 +# 2 methods for generic function "f" from Main: + [1] f(x::Float64, y::Float64) + @ none:1 + [2] f(x::Number, y::Number) + @ none:1 ``` which shows that `f` has two methods, one taking two `Float64` arguments and one taking arguments @@ -190,10 +218,13 @@ julia> f(x,y) = println("Whoa there, Nelly.") f (generic function with 3 methods) julia> methods(f) -# 3 methods for generic function "f": -[1] f(x::Float64, y::Float64) in Main at none:1 -[2] f(x::Number, y::Number) in Main at none:1 -[3] f(x, y) in Main at none:1 +# 3 methods for generic function "f" from Main: + [1] f(x::Float64, y::Float64) + @ none:1 + [2] f(x::Number, y::Number) + @ none:1 + [3] f(x, y) + @ none:1 julia> f("foo", 1) Whoa there, Nelly. @@ -234,8 +265,40 @@ julia> methods(+) ``` Multiple dispatch together with the flexible parametric type system give Julia its ability to -abstractly express high-level algorithms decoupled from implementation details, yet generate efficient, -specialized code to handle each case at run time. +abstractly express high-level algorithms decoupled from implementation details. + +## [Method specializations](@id man-method-specializations) + +When you create multiple methods of the same function, this is sometimes called +"specialization." In this case, you're specializing the *function* by adding additional +methods to it: each new method is a new specialization of the function. +As shown above, these specializations are returned by `methods`. + +There's another kind of specialization that occurs without programmer intervention: +Julia's compiler can automatically specialize the *method* for the specific argument types used. +Such specializations are *not* listed by `methods`, as this doesn't create new `Method`s, but tools like [`@code_typed`](@ref) allow you to inspect such specializations. + +For example, if you create a method + +``` +mysum(x::Real, y::Real) = x + y +``` + +you've given the function `mysum` one new method (possibly its only method), and that method takes any pair of `Real` number inputs. But if you then execute + +```julia-repl +julia> mysum(1, 2) +3 + +julia> mysum(1.0, 2.0) +3.0 +``` + +Julia will compile `mysum` twice, once for `x::Int, y::Int` and again for `x::Float64, y::Float64`. +The point of compiling twice is performance: the methods that get called for `+` (which `mysum` uses) vary depending on the specific types of `x` and `y`, and by compiling different specializations Julia can do all the method lookup ahead of time. This allows the program to run much more quickly, since it does not have to bother with method lookup while it is running. +Julia's automatic specialization allows you to write generic algorithms and expect that the compiler will generate efficient, specialized code to handle each case you need. + +In cases where the number of potential specializations might be effectively unlimited, Julia may avoid this default specialization. See [Be aware of when Julia avoids specializing](@ref) for more information. ## [Method Ambiguities](@id man-ambiguities) @@ -256,11 +319,19 @@ julia> g(2, 3.0) 8.0 julia> g(2.0, 3.0) -ERROR: MethodError: g(::Float64, ::Float64) is ambiguous. Candidates: - g(x::Float64, y) in Main at none:1 - g(x, y::Float64) in Main at none:1 +ERROR: MethodError: g(::Float64, ::Float64) is ambiguous. + +Candidates: + g(x, y::Float64) + @ Main none:1 + g(x::Float64, y) + @ Main none:1 + Possible fix, define g(::Float64, ::Float64) + +Stacktrace: +[...] ``` Here the call `g(2.0, 3.0)` could be handled by either the `g(Float64, Any)` or the `g(Any, Float64)` @@ -347,8 +418,11 @@ julia> myappend([1,2,3],4) julia> myappend([1,2,3],2.5) ERROR: MethodError: no method matching myappend(::Vector{Int64}, ::Float64) + Closest candidates are: - myappend(::Vector{T}, !Matched::T) where T at none:1 + myappend(::Vector{T}, !Matched::T) where T + @ Main none:1 + Stacktrace: [...] @@ -361,8 +435,11 @@ 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) + Closest candidates are: - myappend(::Vector{T}, !Matched::T) where T at none:1 + myappend(::Vector{T}, !Matched::T) where T + @ Main none:1 + Stacktrace: [...] ``` @@ -403,9 +480,15 @@ true julia> same_type_numeric("foo", 2.0) ERROR: MethodError: no method matching same_type_numeric(::String, ::Float64) + Closest candidates are: - same_type_numeric(!Matched::T, ::T) where T<:Number at none:1 - same_type_numeric(!Matched::Number, ::Number) at none:1 + same_type_numeric(!Matched::T, ::T) where T<:Number + @ Main none:1 + same_type_numeric(!Matched::Number, ::Number) + @ Main none:1 + +Stacktrace: +[...] julia> same_type_numeric("foo", "bar") ERROR: MethodError: no method matching same_type_numeric(::String, ::String) @@ -605,7 +688,6 @@ For instance, you might have some sort of abstract array with an arbitrary eleme and want to write your computation on it with a specific element type. We must implement a method for each `AbstractArray{T}` subtype that describes how to compute this type transform. There is no general transform of one subtype into another subtype with a different parameter. -(Quick review: do you see why this is?) The subtypes of `AbstractArray` typically implement two methods to achieve this: @@ -792,16 +874,26 @@ bar (generic function with 1 method) julia> bar(1,2,3) ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64) + Closest candidates are: - bar(::Any, ::Any, ::Any, !Matched::Any) at none:1 + bar(::Any, ::Any, ::Any, !Matched::Any) + @ Main none:1 + +Stacktrace: +[...] julia> bar(1,2,3,4) (1, 2, (3, 4)) julia> bar(1,2,3,4,5) ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64, ::Int64, ::Int64) + Closest candidates are: - bar(::Any, ::Any, ::Any, ::Any) at none:1 + bar(::Any, ::Any, ::Any, ::Any) + @ Main none:1 + +Stacktrace: +[...] ``` More usefully, it is possible to constrain varargs methods by a parameter. For example: @@ -1105,4 +1197,56 @@ reduced likelihood of ambiguities. Moreover, it extends the "public" `myfilter` interface: a user who wants to control the padding explicitly can call the `NoPad` variant directly. +## Defining methods in local scope + +You can define methods within a [local scope](@ref scope-of-variables), for example + +```jldoctest +julia> function f(x) + g(y::Int) = y + x + g(y) = y - x + g + end +f (generic function with 1 method) + +julia> h = f(3); + +julia> h(4) +7 + +julia> h(4.0) +1.0 +``` + +However, you should *not* define local methods conditionally or subject to control flow, as in + +```julia +function f2(inc) + if inc + g(x) = x + 1 + else + g(x) = x - 1 + end +end + +function f3() + function g end + return g + g() = 0 +end +``` +as it is not clear what function will end up getting defined. In the future, it might be an error to define local methods in this manner. + +For cases like this use anonymous functions instead: + +```julia +function f2(inc) + g = if inc + x -> x + 1 + else + x -> x - 1 + end +end +``` + [^Clarke61]: Arthur C. Clarke, *Profiles of the Future* (1961): Clarke's Third Law. diff --git a/doc/src/manual/missing.md b/doc/src/manual/missing.md index 4c6d36c7381b2..9bddcdfbb2ac2 100644 --- a/doc/src/manual/missing.md +++ b/doc/src/manual/missing.md @@ -325,15 +325,15 @@ julia> sum(skipmissing([1, missing])) This convenience function returns an iterator which filters out `missing` values efficiently. It can therefore be used with any function which supports iterators: -```jldoctest skipmissing; setup = :(using Statistics) +```jldoctest skipmissing julia> x = skipmissing([3, missing, 2, 1]) skipmissing(Union{Missing, Int64}[3, missing, 2, 1]) julia> maximum(x) 3 -julia> mean(x) -2.0 +julia> sum(x) +6 julia> mapreduce(sqrt, +, x) 4.146264369941973 diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index 9fc3a90046d8e..4ffb1bca26e50 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -9,7 +9,7 @@ Modules in Julia help organize code into coherent units. They are delimited synt 2. Modules have facilities for detailed namespace management: each defines a set of names it `export`s, and can import names from other modules with `using` and `import` (we explain these below). -3. Modules can be precompiled for faster loading, and contain code for runtime initialization. +3. Modules can be precompiled for faster loading, and may contain code for runtime initialization. Typically, in larger Julia packages you will see module code organized into files, eg @@ -78,7 +78,7 @@ module-local. ### Export lists Names (referring to functions, types, global variables, and constants) can be added to the -*export list* of a module with `export`. Typically, they are at or near the top of the module definition +*export list* of a module with `export`: these are the symbols that are imported when `using` the module. Typically, they are at or near the top of the module definition so that readers of the source code can find them easily, as in ```jldoctest module_manual @@ -171,7 +171,7 @@ julia> using .NiceStuff: nice julia> struct Cat end julia> nice(::Cat) = "nice 😸" -ERROR: error in method definition: function NiceStuff.nice must be explicitly imported to be extended +ERROR: invalid method definition in Main: function NiceStuff.nice must be explicitly imported to be extended Stacktrace: [1] top-level scope @ none:0 @@ -281,7 +281,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 ``` Here, Julia cannot decide which `f` you are referring to, so you have to make a choice. The following solutions are commonly used: @@ -325,7 +325,17 @@ include(p) = Base.include(Mod, p) end ``` -If even `Core` is not wanted, a module that imports nothing and defines no names at all can be defined with `Module(:YourNameHere, false, false)` and code can be evaluated into it with [`@eval`](@ref) or [`Core.eval`](@ref). +If even `Core` is not wanted, a module that imports nothing and defines no names at all can be defined with `Module(:YourNameHere, false, false)` and code can be evaluated into it with [`@eval`](@ref) or [`Core.eval`](@ref): +```jldoctest +julia> arithmetic = Module(:arithmetic, false, false) +Main.arithmetic + +julia> @eval arithmetic add(x, y) = $(+)(x, y) +add (generic function with 1 method) + +julia> arithmetic.add(12, 13) +25 +``` ### Standard modules @@ -387,7 +397,7 @@ x = 0 module Sub using ..TestPackage -z = y # ERROR: UndefVarError: y not defined +z = y # ERROR: UndefVarError: `y` not defined end y = 1 @@ -403,7 +413,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 end module C @@ -419,11 +429,14 @@ Large modules can take several seconds to load because executing all of the stat often involves compiling a large amount of code. Julia creates precompiled caches of the module to reduce this time. -The incremental precompiled module file are created and used automatically when using `import` -or `using` to load a module. This will cause it to be automatically compiled the first time -it is imported. Alternatively, you can manually call [`Base.compilecache(modulename)`](@ref). The resulting -cache files will be stored in `DEPOT_PATH[1]/compiled/`. Subsequently, the module is automatically -recompiled upon `using` or `import` whenever any of its dependencies change; dependencies are modules it +Precompiled module files (sometimes called "cache files") are created and used automatically when `import` or `using` loads a module. If the cache file(s) do not yet exist, the module will be compiled and saved for future reuse. You can also manually call [`Base.compilecache(Base.identify_package("modulename"))`](@ref) to create these files without loading the module. The resulting +cache files will be stored in the `compiled` subfolder of `DEPOT_PATH[1]`. If nothing about your system changes, +such cache files will be used when you load the module with `import` or `using`. + +Precompilation cache files store definitions of modules, types, methods, and constants. They may also store method specializations and the code generated for them, but this typically requires that the developer add explicit [`precompile`](@ref) directives or execute workloads that force compilation during the package build. + +However, if you update the module's dependencies or change its source code, the module is automatically +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). @@ -435,6 +448,7 @@ by the search logic in `require` matches the path that had created the precompil 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 the running system and the precompile cache. +Finally, it takes account of changes in any [compile-time preferences](@ref preferences). If you know that a module is *not* safe to precompile (for example, for one of the reasons described below), you should @@ -579,6 +593,12 @@ A few other points to be aware of: 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. `Base.compilecache` can still be called +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. + +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". diff --git a/doc/src/manual/multi-threading.md b/doc/src/manual/multi-threading.md index 14d42013ae2ff..be64390e473f2 100644 --- a/doc/src/manual/multi-threading.md +++ b/doc/src/manual/multi-threading.md @@ -19,8 +19,8 @@ The number of execution threads is controlled either by using the specified, then `-t`/`--threads` takes precedence. The number of threads can either be specified as an integer (`--threads=4`) or as `auto` -(`--threads=auto`), where `auto` sets the number of threads to the number of local CPU -threads. +(`--threads=auto`), where `auto` tries to infer a useful default number of threads to use +(see [Command-line Options](@ref command-line-interface) for more details). !!! compat "Julia 1.5" The `-t`/`--threads` command line argument requires at least Julia 1.5. @@ -72,7 +72,77 @@ julia> Threads.threadid() three processes have 2 threads enabled. For more fine grained control over worker threads use [`addprocs`](@ref) and pass `-t`/`--threads` as `exeflags`. -## Data-race freedom +### Multiple GC Threads + +The Garbage Collector (GC) can use multiple threads. The amount used is either half the number +of compute worker threads or configured by either the `--gcthreads` command line argument or by using the +[`JULIA_NUM_GC_THREADS`](@ref env-gc-threads) environment variable. + +!!! compat "Julia 1.10" + The `--gcthreads` command line argument requires at least Julia 1.10. + +## [Threadpools](@id man-threadpools) + +When a program's threads are busy with many tasks to run, tasks may experience +delays which may negatively affect the responsiveness and interactivity of the +program. To address this, you can specify that a task is interactive when you +[`Threads.@spawn`](@ref) it: + +```julia +using Base.Threads +@spawn :interactive f() +``` + +Interactive tasks should avoid performing high latency operations, and if they +are long duration tasks, should yield frequently. + +Julia may be started with one or more threads reserved to run interactive tasks: + +```bash +$ julia --threads 3,1 +``` + +The environment variable `JULIA_NUM_THREADS` can also be used similarly: +```bash +export JULIA_NUM_THREADS=3,1 +``` + +This starts Julia with 3 threads in the `:default` threadpool and 1 thread in +the `:interactive` threadpool: + +```julia-repl +julia> using Base.Threads + +julia> nthreadpools() +2 + +julia> threadpool() +:default + +julia> nthreads(:default) +3 + +julia> nthreads(:interactive) +1 + +julia> nthreads() +3 +``` + +!!! note + The zero-argument version of `nthreads` returns the number of threads + in the default 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 @@ -213,7 +283,7 @@ avoid the race: ```julia-repl julia> using Base.Threads -julia> nthreads() +julia> Threads.nthreads() 4 julia> acc = Ref(0) @@ -307,9 +377,6 @@ threads in Julia: multiple threads where at least one thread modifies the collection (common examples include `push!` on arrays, or inserting items into a `Dict`). - * `@threads` currently uses a static schedule, using all threads and assigning - equal iteration counts to each. In the future the default schedule is likely - to change to be dynamic. * The schedule used by `@spawn` is nondeterministic and should not be relied on. * Compute-bound, non-memory-allocating tasks can prevent garbage collection from running in other threads that are allocating memory. In these cases it may @@ -364,7 +431,7 @@ There are a few approaches to dealing with this problem: 3. A related third strategy is to use a yield-free queue. We don't currently have a lock-free queue implemented in Base, but - `Base.InvasiveLinkedListSynchronized{T}` is suitable. This can frequently be a + `Base.IntrusiveLinkedListSynchronized{T}` is suitable. This can frequently be a good strategy to use for code with event loops. For example, this strategy is employed by `Gtk.jl` to manage lifetime ref-counting. In this approach, we don't do any explicit work inside the `finalizer`, and instead add it to a queue diff --git a/doc/src/manual/networking-and-streams.md b/doc/src/manual/networking-and-streams.md index fc62632433850..1ee2f33de5c23 100644 --- a/doc/src/manual/networking-and-streams.md +++ b/doc/src/manual/networking-and-streams.md @@ -368,7 +368,7 @@ UDP can use special multicast addresses to allow simultaneous communication betw To transmit data over UDP multicast, simply `recv` on the socket, and the first packet received will be returned. Note that it may not be the first packet that you sent however! -``` +```julia using Sockets group = ip"228.5.6.7" socket = Sockets.UDPSocket() @@ -384,7 +384,7 @@ close(socket) To transmit data over UDP multicast, simply `send` to the socket. Notice that it is not necessary for a sender to join the multicast group. -``` +```julia using Sockets group = ip"228.5.6.7" socket = Sockets.UDPSocket() @@ -397,7 +397,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") socket = Sockets.UDPSocket() @@ -409,7 +409,7 @@ close(socket) ``` Sender: -``` +```julia using Sockets group = Sockets.IPv6("ff05::5:6:7") socket = Sockets.UDPSocket() diff --git a/doc/src/manual/noteworthy-differences.md b/doc/src/manual/noteworthy-differences.md index f1adc2b13b72c..470ec9a315ce4 100644 --- a/doc/src/manual/noteworthy-differences.md +++ b/doc/src/manual/noteworthy-differences.md @@ -8,7 +8,7 @@ may trip up Julia users accustomed to MATLAB: * Julia arrays are indexed with square brackets, `A[i,j]`. * Julia arrays are not copied when assigned to another variable. After `A = B`, changing elements of `B` - will modify `A` as well. + will modify `A` as well. To avoid this, use `A = copy(B)`. * Julia values are not copied when passed to a function. If a function modifies an array, the changes will be visible in the caller. * Julia does not automatically grow arrays in an assignment statement. Whereas in MATLAB `a(4) = 3.2` @@ -214,6 +214,7 @@ For users coming to Julia from R, these are some noteworthy differences: Python's special interpretation of negative indexing, `a[-1]` and `a[-2]`, should be written `a[end]` and `a[end-1]` in Julia. * Julia requires `end` for indexing until the last element. `x[1:]` in Python is equivalent to `x[2:end]` in Julia. + * In Julia, `:` before any object creates a [`Symbol`](@ref) or *quotes* an expression; so, `x[:5]` is same as `x[5]`. If you want to get the first `n` elements of an array, then use range indexing. * Julia's range indexing has the format of `x[start:step:stop]`, whereas Python's format is `x[start:(stop+1):step]`. Hence, `x[0:10:2]` in Python is equivalent to `x[1:2:10]` in Julia. Similarly, `x[::-1]` in Python, which refers to the reversed array, is equivalent to `x[end:-1:1]` in Julia. * In Julia, ranges can be constructed independently as `start:step:stop`, the same syntax it uses in array-indexing. The `range` function is also supported. @@ -351,6 +352,97 @@ 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 + * 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). + * `using MyNamespace::myfun` (C++) corresponds roughly to `import MyModule: myfun` (Julia). + * `using namespace MyNamespace` (C++) corresponds roughly to `using MyModule` (Julia) + * In Julia, only `export`ed symbols are made available to the calling module. + * In C++, only elements found in the included (public) header files are made available. + * Caveat: `import`/`using` keywords (Julia) also *load* modules (see below). + * 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 + * 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. + * Reminder: Julia `module`s are global scopes (not necessarily "software modules"). + * **Instead of build/`make` scripts**, Julia uses "Project Environments" (sometimes called + either "Project" or "Environment"). + * Build scripts are only needed for more complex applications + (like those needing to compile or download C/C++ executables). + * To develop application or project in Julia, you can initialize its root directory + as a "Project Environment", and house application-specific code/packages there. + This provides good control over project dependencies, and future reproducibility. + * Available packages are added to a "Project Environment" with the `Pkg.add()` function or Pkg REPL mode. + (This does not **load** said package, however). + * The list of available packages (direct dependencies) for a "Project Environment" are + saved in its `Project.toml` file. + * The *full* dependency information for a "Project Environment" is auto-generated & saved + in its `Manifest.toml` file by `Pkg.resolve()`. + * Packages ("software modules") available to the "Project Environment" are loaded with + `import` or `using`. + * In C/C++, you `#include ` to get object/function declarations, and link in + libraries when you build the executable. + * In Julia, calling using/import again just brings the existing module into scope, but does not load it again + (similar to adding the non-standard `#pragma once` to C/C++). + * **Directory-based package repositories** (Julia) can be made available by adding repository + 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 + libraries of "software 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. + * In turn, `[PkgName].jl` typically loads associated source files with calls to + `include "[someotherfile].jl"`. + * `include "./path/to/somefile.jl"` (Julia) is very similar to + `#include "./path/to/somefile.jl"` (C/C++). + * However `include "..."` (Julia) is not used to include header files (not required). + * **Do not use** `include "..."` (Julia) to load code from other "software modules" + (use `import`/`using` instead). + * `include "path/to/some/module.jl"` (Julia) would instantiate multiple versions of the + same code in different modules (creating *distinct* types (etc.) with the *same* names). + * `include "somefile.jl"` is typically used to assemble multiple files *within the same + 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 + * C++ exposes interfaces using "public" `.h`/`.hpp` files whereas Julia `module`s `export` + symbols that are intended for their users. + * Often, Julia `module`s simply add functionality by generating new "methods" to existing + functions (ex: `Base.push!`). + * Developers of Julia packages therefore cannot rely on header files for interface + documentation. + * Interfaces for Julia packages are typically described using docstrings, README.md, + static web pages, ... + * Some developers choose not to `export` all symbols required to use their package/module. + * 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 + +| Software Concept | Julia | C/C++ | +| :--- | :--- | :--- | +| unnamed scope | `begin` ... `end` | `{` ... `}` | +| function scope | `function x()` ... `end` | `int x() {` ... `}` | +| global scope | `module MyMod` ... `end` | `namespace MyNS {` ... `}` | +| software module | A Julia "package" | `.h`/`.hpp` files
+compiled `somelib.a` | +| assembling
software modules | `SomePkg.jl`: ...
`import("subfile1.jl")`
`import("subfile2.jl")`
... | `$(AR) *.o` ⇒ `somelib.a` | +| import
software module | `import SomePkg` | `#include `
+link in `somelib.a` | +| module library | `LOAD_PATH[]`, \*Git repository,
\*\*custom package registry | more `.h`/`.hpp` files
+bigger compiled `somebiglib.a` | + +\* The Julia package manager supports registering multiple packages from a single Git repository.
+\* This allows users to house a library of related packages in a single repository.
+\*\* Julia registries are primarily designed to provide versioning \& distribution of packages.
+\*\* Custom package registries can be used to create a type of module library. + + ## Noteworthy differences from Common Lisp - Julia uses 1-based indexing for arrays by default, and it can also handle arbitrary [index offsets](@ref man-custom-indices). diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 3dd09b207ddda..ffb84333e8e78 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -13,7 +13,7 @@ The functions should take arguments, instead of operating directly on global var ## Avoid untyped global variables -An untyped global variable might have its value, and therefore possibly its type, changed at any point. This makes +The value of an untyped global variable might change at any point, possibly leading to a change of its type. This makes it difficult for the compiler to optimize code using global variables. This also applies to type-valued variables, i.e. type aliases on the global level. Variables should be local, or passed as arguments to functions, whenever possible. @@ -90,7 +90,14 @@ On the first call (`@time sum_global()`) the function gets compiled. (If you've in this session, it will also compile functions needed for timing.) You should not take the results of this run seriously. For the second run, note that in addition to reporting the time, it also indicated that a significant amount of memory was allocated. We are here just computing a sum over all elements in -a vector of 64-bit floats so there should be no need to allocate memory (at least not on the heap which is what `@time` reports). +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 +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. Unexpected memory allocation is almost always a sign of some problem with your code, usually a problem with type-stability or creating many small temporary arrays. @@ -98,8 +105,8 @@ Consequently, in addition to the allocation itself, it's very likely that the code generated for your function is far from optimal. Take such indications seriously and follow the advice below. -If we instead pass `x` as an argument to the function it no longer allocates memory -(the allocation reported below is due to running the `@time` macro in global scope) +In this particular case, the memory allocation is due to the usage of a type-unstable global variable `x`, so if we instead pass `x` as an argument to the function it no longer allocates memory +(the remaining allocation reported below is due to running the `@time` macro in global scope) and is significantly faster after the first call: ```jldoctest sumarg; setup = :(using Random; Random.seed!(1234)), filter = r"[0-9\.]+ seconds \(.*?\)" @@ -518,7 +525,7 @@ at the time `k` is compiled. ### Be aware of when Julia avoids specializing -As a heuristic, Julia avoids automatically specializing on argument type parameters in three +As a heuristic, Julia avoids automatically [specializing](@ref man-method-specializations) on argument type parameters in three specific cases: `Type`, `Function`, and `Vararg`. Julia will always specialize when the argument is used within the method, but not if the argument is just passed through to another function. This usually has no performance impact at runtime and @@ -577,7 +584,7 @@ h_vararg(x::Vararg{Any, N}) where {N} = tuple(x...) Note that [`@code_typed`](@ref) and friends will always show you specialized code, even if Julia would not normally specialize that method call. You need to check the [method internals](@ref ast-lowered-method) if you want to see whether specializations are generated -when argument types are changed, i.e., if `(@which f(...)).specializations` contains specializations +when argument types are changed, i.e., if `Base.specializations(@which f(...))` contains specializations for the argument in question. ## Break functions into multiple definitions @@ -604,8 +611,8 @@ end This can be written more concisely and efficiently as: ```julia -norm(x::Vector) = sqrt(real(dot(x, x))) -norm(A::Matrix) = maximum(svdvals(A)) +mynorm(x::Vector) = sqrt(real(dot(x, x))) +mynorm(A::Matrix) = maximum(svdvals(A)) ``` It should however be noted that the compiler is quite efficient at optimizing away the dead branches in code @@ -824,10 +831,10 @@ This might be worthwhile when either of the following are true: * You require CPU-intensive processing on each `Car`, and it becomes vastly more efficient if you know the `Make` and `Model` at compile time and the total number of different `Make` or `Model` that will be used is not too large. - * You have homogenous lists of the same type of `Car` to process, so that you can store them all + * You have homogeneous lists of the same type of `Car` to process, so that you can store them all in an `Array{Car{:Honda,:Accord},N}`. -When the latter holds, a function processing such a homogenous array can be productively specialized: +When the latter holds, a function processing such a homogeneous array can be productively specialized: Julia knows the type of each element in advance (all objects in the container have the same concrete type), so Julia can "look up" the correct method calls when the function is being compiled (obviating the need to check at run-time) and thereby emit efficient code for processing the whole list. @@ -1048,11 +1055,10 @@ julia> @time f.(x); That is, `fdot(x)` is ten times faster and allocates 1/6 the memory of `f(x)`, because each `*` and `+` operation in `f(x)` allocates -a new temporary array and executes in a separate loop. (Of course, -if you just do `f.(x)` then it is as fast as `fdot(x)` in this -example, but in many contexts it is more convenient to just sprinkle -some dots in your expressions rather than defining a separate function -for each vectorized operation.) +a new temporary array and executes in a separate loop. In this example +`f.(x)` is as fast as `fdot(x)` but in many contexts it is more +convenient to sprinkle some dots in your expressions than to +define a separate function for each vectorized operation. ## [Consider using views for slices](@id man-performance-views) @@ -1095,42 +1101,41 @@ of the `fview` version of the function. Arrays are stored contiguously in memory, lending themselves to CPU vectorization and fewer memory accesses due to caching. These are the same reasons that it is recommended -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. +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 operating on it can result -in a large speedup, such as in the example below. Here, a matrix and a vector are being accessed at -800,000 of their randomly-shuffled indices before being multiplied. Copying the views into -plain arrays speeds up the multiplication even with the cost of the copying operation. +Copying irregularly-accessed data into a contiguous array before repeated access 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. ```julia-repl julia> using Random -julia> x = randn(1_000_000); - -julia> inds = shuffle(1:1_000_000)[1:800000]; +julia> A = randn(3000, 3000); -julia> A = randn(50, 1_000_000); +julia> x = randn(2000); -julia> xtmp = zeros(800_000); +julia> inds = shuffle(1:3000)[1:2000]; -julia> Atmp = zeros(50, 800_000); +julia> function iterated_neural_network(A, x, depth) + for _ in 1:depth + x .= max.(0, A * x) + end + argmax(x) + end -julia> @time sum(view(A, :, inds) * view(x, inds)) - 0.412156 seconds (14 allocations: 960 bytes) --4256.759568345458 +julia> @time iterated_neural_network(view(A, inds, inds), x, 10) + 0.324903 seconds (12 allocations: 157.562 KiB) +1569 -julia> @time begin - copyto!(xtmp, view(x, inds)) - copyto!(Atmp, view(A, :, inds)) - sum(Atmp * xtmp) - end - 0.285923 seconds (14 allocations: 960 bytes) --4256.759568345134 +julia> @time iterated_neural_network(A[inds, inds], x, 10) + 0.054576 seconds (13 allocations: 30.671 MiB, 13.33% gc time) +1569 ``` -Provided there is enough memory for the copies, the cost of copying the view to an array is -far outweighed by the speed boost from doing the matrix multiplication on a contiguous array. +Provided there is enough memory, the cost of copying the view to an array is outweighed +by the speed boost from doing the repeated matrix multiplications on a contiguous array. ## Consider StaticArrays.jl for small fixed-size vector/matrix operations @@ -1477,11 +1482,13 @@ julia> function f(x) end; julia> @code_warntype f(3.2) -Variables +MethodInstance for f(::Float64) + from f(x) @ Main REPL[9]:1 +Arguments #self#::Core.Const(f) x::Float64 - y::UNION{FLOAT64, INT64} - +Locals + y::Union{Float64, Int64} Body::Float64 1 ─ (y = Main.pos(x)) │ %2 = (y * x)::Float64 @@ -1502,7 +1509,7 @@ At the top, the inferred return type of the function is shown as `Body::Float64` The next lines represent the body of `f` in Julia's SSA IR form. The numbered boxes are labels and represent targets for jumps (via `goto`) in your code. Looking at the body, you can see that the first thing that happens is that `pos` is called and the -return value has been inferred as the `Union` type `UNION{FLOAT64, INT64}` shown in uppercase since +return value has been inferred as the `Union` type `Union{Float64, Int64}` shown in uppercase since it is a non-concrete type. This means that we cannot know the exact return type of `pos` based on the input types. However, the result of `y*x`is a `Float64` no matter if `y` is a `Float64` or `Int64` The net result is that `f(x::Float64)` will not be type-unstable @@ -1524,20 +1531,20 @@ are color highlighted in yellow, instead of red. The following examples may help you interpret expressions marked as containing non-leaf types: - * Function body starting with `Body::UNION{T1,T2})` + * Function body starting with `Body::Union{T1,T2})` * Interpretation: function with unstable return type * Suggestion: make the return value type-stable, even if you have to annotate it - * `invoke Main.g(%%x::Int64)::UNION{FLOAT64, INT64}` + * `invoke Main.g(%%x::Int64)::Union{Float64, Int64}` * Interpretation: call to a type-unstable function `g`. * Suggestion: fix the function, or if necessary annotate the return value - * `invoke Base.getindex(%%x::Array{Any,1}, 1::Int64)::ANY` + * `invoke Base.getindex(%%x::Array{Any,1}, 1::Int64)::Any` * Interpretation: accessing elements of poorly-typed arrays * Suggestion: use arrays with better-defined types, or if necessary annotate the type of individual element accesses - * `Base.getfield(%%x, :(:data))::ARRAY{FLOAT64,N} WHERE N` + * `Base.getfield(%%x, :(:data))::Array{Float64,N} where N` * Interpretation: getting a field that is of non-leaf type. In this case, the type of `x`, say `ArrayContainer`, had a field `data::Array{T}`. But `Array` needs the dimension `N`, too, to be a concrete type. * Suggestion: use concrete types like `Array{T,3}` or `Array{T,N}`, where `N` is now a parameter diff --git a/doc/src/manual/profile.md b/doc/src/manual/profile.md index c3dc1ca090a46..e5f1d6c417fa6 100644 --- a/doc/src/manual/profile.md +++ b/doc/src/manual/profile.md @@ -59,11 +59,13 @@ julia> @profile myfunc() To see the profiling results, there are several graphical browsers. One "family" of visualizers is based on [FlameGraphs.jl](https://github.com/timholy/FlameGraphs.jl), with each family member providing a different user interface: -- [Juno](https://junolab.org/) is a full IDE with built-in support for profile visualization +- [VS Code](https://www.julia-vscode.org/) is a full IDE with built-in support for profile visualization - [ProfileView.jl](https://github.com/timholy/ProfileView.jl) is a stand-alone visualizer based on GTK - [ProfileVega.jl](https://github.com/davidanthoff/ProfileVega.jl) uses VegaLight and integrates well with Jupyter notebooks -- [StatProfilerHTML](https://github.com/tkluck/StatProfilerHTML.jl) produces HTML and presents some additional summaries, and also integrates well with Jupyter notebooks -- [ProfileSVG](https://github.com/timholy/ProfileSVG.jl) renders SVG +- [StatProfilerHTML.jl](https://github.com/tkluck/StatProfilerHTML.jl) produces HTML and presents some additional summaries, and also integrates well with Jupyter notebooks +- [ProfileSVG.jl](https://github.com/timholy/ProfileSVG.jl) renders SVG +- [PProf.jl](https://github.com/JuliaPerf/PProf.jl) serves a local website for inspecting graphs, flamegraphs and more +- [ProfileCanvas.jl](https://github.com/pfitzseb/ProfileCanvas.jl) is a HTML canvas based profile viewer UI, used by the [Julia VS Code extension](https://www.julia-vscode.org/), but can also generate interactive HTML files. An entirely independent approach to profile visualization is [PProf.jl](https://github.com/vchuravy/PProf.jl), which uses the external `pprof` tool. @@ -302,31 +304,11 @@ provides several tools measure this: ### `@time` -The total amount of allocation can be measured with [`@time`](@ref) and [`@allocated`](@ref), and -specific lines triggering allocation can often be inferred from profiling via the cost of garbage +The total amount of allocation can be measured with [`@time`](@ref), [`@allocated`](@ref) and [`@allocations`](@ref), +and specific lines triggering allocation can often be inferred from profiling via the cost of garbage collection that these lines incur. However, sometimes it is more efficient to directly measure the amount of memory allocated by each line of code. -### Line-by-Line Allocation Tracking - -To measure allocation line-by-line, start Julia with the `--track-allocation=` command-line -option, for which you can choose `none` (the default, do not measure allocation), `user` (measure -memory allocation everywhere except Julia's core code), or `all` (measure memory allocation at -each line of Julia code). Allocation gets measured for each line of compiled code. When you quit -Julia, the cumulative results are written to text files with `.mem` appended after the file name, -residing in the same directory as the source file. Each line lists the total number of bytes -allocated. The [`Coverage` package](https://github.com/JuliaCI/Coverage.jl) contains some elementary -analysis tools, for example to sort the lines in order of number of bytes allocated. - -In interpreting the results, there are a few important details. Under the `user` setting, the -first line of any function directly called from the REPL will exhibit allocation due to events -that happen in the REPL code itself. More significantly, JIT-compilation also adds to allocation -counts, because much of Julia's compiler is written in Julia (and compilation usually requires -memory allocation). The recommended procedure is to force compilation by executing all the commands -you want to analyze, then call [`Profile.clear_malloc_data()`](@ref) to reset all allocation counters. - Finally, execute the desired commands and quit Julia to trigger the generation of the `.mem` -files. - ### GC Logging While [`@time`](@ref) logs high-level stats about memory usage and garbage collection over the course @@ -336,17 +318,20 @@ and how much garbage it collects each time. This can be enabled with [`GC.enable_logging(true)`](@ref), which causes Julia to log to stderr every time a garbage collection happens. -### Allocation Profiler +### [Allocation Profiler](@id allocation-profiler) + +!!! compat "Julia 1.8" + This functionality requires at least Julia 1.8. The allocation profiler records the stack trace, type, and size of each allocation while it is running. It can be invoked with [`Profile.Allocs.@profile`](@ref). This information about the allocations is returned as an array of `Alloc` -objects, wrapped in an `AllocResults` object. The best way to visualize -these is currently with the [PProf.jl](https://github.com/JuliaPerf/PProf.jl) -library, which can visualize the call stacks which are making the most -allocations. +objects, wrapped in an `AllocResults` object. The best way to visualize these is +currently with the [PProf.jl](https://github.com/JuliaPerf/PProf.jl) and +[ProfileCanvas.jl](https://github.com/pfitzseb/ProfileCanvas.jl) packages, which +can visualize the call stacks which are making the most allocations. The allocation profiler does have significant overhead, so a `sample_rate` argument can be passed to speed it up by making it skip some allocations. @@ -361,7 +346,33 @@ Passing `sample_rate=1.0` will make it record everything (which is slow); `Profile.Allocs.UnknownType`. You can read more about the missing types and the plan to improve this, here: - https://github.com/JuliaLang/julia/issues/43688. + [issue #43688](https://github.com/JuliaLang/julia/issues/43688). + +#### Line-by-Line Allocation Tracking + +An alternative way to measure allocations is to start Julia with the `--track-allocation=` command-line +option, for which you can choose `none` (the default, do not measure allocation), `user` (measure +memory allocation everywhere except Julia's core code), or `all` (measure memory allocation at +each line of Julia code). Allocation gets measured for each line of compiled code. When you quit +Julia, the cumulative results are written to text files with `.mem` appended after the file name, +residing in the same directory as the source file. Each line lists the total number of bytes +allocated. The [`Coverage` package](https://github.com/JuliaCI/Coverage.jl) contains some elementary +analysis tools, for example to sort the lines in order of number of bytes allocated. + +In interpreting the results, there are a few important details. Under the `user` setting, the +first line of any function directly called from the REPL will exhibit allocation due to events +that happen in the REPL code itself. More significantly, JIT-compilation also adds to allocation +counts, because much of Julia's compiler is written in Julia (and compilation usually requires +memory allocation). The recommended procedure is to force compilation by executing all the commands +you want to analyze, then call [`Profile.clear_malloc_data()`](@ref) to reset all allocation counters. + Finally, execute the desired commands and quit Julia to trigger the generation of the `.mem` +files. + +!!! note + + `--track-allocation` changes code generation to log the allocations, and so the allocations may + be different than what happens without the option. We recommend using the + [allocation profiler](@ref allocation-profiler) instead. ## External Profiling diff --git a/doc/src/manual/strings.md b/doc/src/manual/strings.md index ee2b3f9d71d54..fca4fc75d9e0f 100644 --- a/doc/src/manual/strings.md +++ b/doc/src/manual/strings.md @@ -48,7 +48,7 @@ to a numeric value representing a [Unicode code point](https://en.wikipedia.org/wiki/Code_point). (Julia packages may define other subtypes of `AbstractChar`, e.g. to optimize operations for other [text encodings](https://en.wikipedia.org/wiki/Character_encoding).) Here is how `Char` values are -input and shown: +input and shown (note that character literals are delimited with single quotes, not double quotes): ```jldoctest julia> c = 'x' @@ -156,7 +156,7 @@ julia> 'A' + 1 ## String Basics -String literals are delimited by double quotes or triple double quotes: +String literals are delimited by double quotes or triple double quotes (not single quotes): ```jldoctest helloworldstring julia> str = "Hello, world.\n" @@ -242,8 +242,9 @@ The former is a single character value of type `Char`, while the latter is a str happens to contain only a single character. In Julia these are very different things. Range indexing makes a copy of the selected part of the original string. -Alternatively, it is possible to create a view into a string using the type [`SubString`](@ref), -for example: +Alternatively, it is possible to create a view into a string using the type [`SubString`](@ref). +More simply, using the [`@views`](@ref) macro on a block of code converts all string slices +into substrings. For example: ```jldoctest julia> str = "long string" @@ -254,6 +255,9 @@ julia> substr = SubString(str, 1, 4) julia> typeof(substr) SubString{String} + +julia> @views typeof(str[1:4]) # @views converts slices to SubStrings +SubString{String} ``` Several standard functions like [`chop`](@ref), [`chomp`](@ref) or [`strip`](@ref) @@ -531,7 +535,9 @@ Constructing strings using concatenation can become a bit cumbersome, however. T verbose calls to [`string`](@ref) or repeated multiplications, Julia allows interpolation into string literals using `$`, as in Perl: -```jldoctest stringconcat +```jldoctest +julia> greet = "Hello"; whom = "world"; + julia> "$greet, $whom.\n" "Hello, world.\n" ``` @@ -766,9 +772,10 @@ are some examples of non-standard string literals. Users and packages may also d Further documentation is given in the [Metaprogramming](@ref meta-non-standard-string-literals) section. ## [Regular Expressions](@id man-regex-literals) +Sometimes you are not looking for an exact string, but a particular *pattern*. For example, suppose you are trying to extract a single date from a large text file. You don’t know what that date is (that’s why you are searching for it), but you do know it will look something like `YYYY-MM-DD`. Regular expressions allow you to specify these patterns and search for them. -Julia has Perl-compatible regular expressions (regexes), as provided by the [PCRE](https://www.pcre.org/) -library (a description of the syntax can be found [here](https://www.pcre.org/current/doc/html/pcre2syntax.html)). Regular expressions are related to strings in two ways: the obvious connection is that +Julia uses version 2 of Perl-compatible regular expressions (regexes), as provided by the [PCRE](https://www.pcre.org/) +library (see the [PCRE2 syntax description](https://www.pcre.org/current/doc/html/pcre2syntax.html) for more details). Regular expressions are related to strings in two ways: the obvious connection is that regular expressions are used to find regular patterns in strings; the other connection is that regular expressions are themselves input as strings, which are parsed into a state machine that can be used to efficiently search for patterns in strings. In Julia, regular expressions are input @@ -1033,8 +1040,11 @@ true ``` Note the use of the `\Q...\E` escape sequence. All characters between the `\Q` and the `\E` -are interpreted as literal characters (after string interpolation). This escape sequence can -be useful when interpolating, possibly malicious, user input. +are interpreted as literal characters. This is convenient for matching characters that +would otherwise be regex metacharacters. However, caution is needed when using this feature +together with string interpolation, since the interpolated string might itself contain +the `\E` sequence, unexpectedly terminating literal matching. User inputs need to be sanitized +before inclusion in a regex. ## [Byte Array Literals](@id man-byte-array-literals) diff --git a/doc/src/manual/style-guide.md b/doc/src/manual/style-guide.md index cbe7e9b94eefc..d250fdd811387 100644 --- a/doc/src/manual/style-guide.md +++ b/doc/src/manual/style-guide.md @@ -378,7 +378,7 @@ You generally want to use [`isa`](@ref) and [`<:`](@ref) for testing types, not `==`. Checking types for exact equality typically only makes sense when comparing to a known concrete type (e.g. `T == Float64`), or if you *really, really* know what you're doing. -## Do not write `x->f(x)` +## Don't write a trivial anonymous function `x->f(x)` for a named function `f` Since higher-order functions are often called with anonymous functions, it is easy to conclude that this is desirable or even necessary. But any function can be passed directly, without being diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index 2a4d7a4e05b6c..430a006c67788 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -108,9 +108,26 @@ local x::Int8 # in a local declaration x::Int8 = 10 # as the left-hand side of an assignment ``` -and applies to the whole current scope, even before the declaration. Currently, type declarations -cannot be used in global scope, e.g. in the REPL, since Julia does not yet have constant-type -globals. +and applies to the whole current scope, even before the declaration. + +As of Julia 1.8, type declarations can now be used in global scope i.e. +type annotations can be added to global variables to make accessing them type stable. +```julia +julia> x::Int = 10 +10 + +julia> x = 3.5 +ERROR: InexactError: Int64(3.5) + +julia> function foo(y) + global x = 15.8 # throws an error when foo is called + return x + y + end +foo (generic function with 1 method) + +julia> foo(10) +ERROR: InexactError: Int64(15.8) +``` Declarations can also be attached to function definitions: @@ -182,15 +199,14 @@ The [`Number`](@ref) type is a direct child type of `Any`, and [`Real`](@ref) is In turn, `Real` has two children (it has more, but only two are shown here; we'll get to the others later): [`Integer`](@ref) and [`AbstractFloat`](@ref), separating the world into representations of integers and representations of real numbers. Representations of real -numbers include, of course, floating-point types, but also include other types, such as -rationals. Hence, `AbstractFloat` is a proper subtype of `Real`, including only -floating-point representations of real numbers. Integers are further subdivided into -[`Signed`](@ref) and [`Unsigned`](@ref) varieties. +numbers include floating-point types, but also include other types, such as rationals. +`AbstractFloat` includes only floating-point representations of real numbers. Integers +are further subdivided into [`Signed`](@ref) and [`Unsigned`](@ref) varieties. -The `<:` operator in general means "is a subtype of", and, used in declarations like this, declares -the right-hand type to be an immediate supertype of the newly declared type. It can also be used -in expressions as a subtype operator which returns `true` when its left operand is a subtype of -its right operand: +The `<:` operator in general means "is a subtype of", and, used in declarations like those above, +declares the right-hand type to be an immediate supertype of the newly declared type. It can also +be used in expressions as a subtype operator which returns `true` when its left operand is a +subtype of its right operand: ```jldoctest julia> Integer <: Number @@ -231,8 +247,8 @@ default method by many combinations of concrete types. Thanks to multiple dispat has full control over whether the default or more specific method is used. An important point to note is that there is no loss in performance if the programmer relies on -a function whose arguments are abstract types, because it is recompiled for each tuple of argument -concrete types with which it is invoked. (There may be a performance issue, however, in the case +a function whose arguments are abstract types, because it is recompiled for each tuple of concrete +argument types with which it is invoked. (There may be a performance issue, however, in the case of function arguments that are containers of abstract types; see [Performance Tips](@ref man-performance-abstract-container).) ## Primitive Types @@ -410,6 +426,9 @@ There is much more to say about how instances of composite types are created, bu depends on both [Parametric Types](@ref) and on [Methods](@ref), and is sufficiently important to be addressed in its own section: [Constructors](@ref man-constructors). +For many user-defined types `X`, you may want to define a method [`Base.broadcastable(x::X) = Ref(x)`](@ref man-interfaces-broadcasting) +so that instances of that type act as 0-dimensional "scalars" for [broadcasting](@ref Broadcasting). + ## Mutable Composite Types If a composite type is declared with `mutable struct` instead of `struct`, then instances of @@ -430,6 +449,9 @@ julia> bar.baz = 1//2 1//2 ``` +An extra interface between the fields and the user can be provided through [Instance Properties](@ref man-instance-properties). +This grants more control on what can be accessed and modified using the `bar.baz` notation. + In order to support mutation, such objects are generally allocated on the heap, and have stable memory addresses. A mutable object is like a little container that might hold different values over time, @@ -726,8 +748,13 @@ 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) + Closest candidates are: - Point(::T, !Matched::T) where T at none:2 + Point(::T, !Matched::T) where T + @ Main none:2 + +Stacktrace: +[...] ``` Constructor methods to appropriately handle such mixed cases can be defined, but that will not @@ -951,24 +978,29 @@ alias for `Tuple{Vararg{T,N}}`, i.e. a tuple type containing exactly `N` element Named tuples are instances of the [`NamedTuple`](@ref) type, which has two parameters: a tuple of symbols giving the field names, and a tuple type giving the field types. +For convenience, `NamedTuple` types are printed using the [`@NamedTuple`](@ref) macro which provides a +convenient `struct`-like syntax for declaring these types via `key::Type` declarations, +where an omitted `::Type` corresponds to `::Any`. + ```jldoctest -julia> typeof((a=1,b="hello")) -NamedTuple{(:a, :b), Tuple{Int64, String}} +julia> typeof((a=1,b="hello")) # prints in macro form +@NamedTuple{a::Int64, b::String} + +julia> NamedTuple{(:a, :b), Tuple{Int64, String}} # long form of the type +@NamedTuple{a::Int64, b::String} ``` -The [`@NamedTuple`](@ref) macro provides a more convenient `struct`-like syntax for declaring -`NamedTuple` types via `key::Type` declarations, where an omitted `::Type` corresponds to `::Any`. +The `begin ... end` form of the `@NamedTuple` macro allows the declarations to be +split across multiple lines (similar to a struct declaration), but is otherwise equivalent: -```jldoctest -julia> @NamedTuple{a::Int, b::String} -NamedTuple{(:a, :b), Tuple{Int64, String}} +```jldoctest julia> @NamedTuple begin a::Int b::String end -NamedTuple{(:a, :b), Tuple{Int64, String}} +@NamedTuple{a::Int64, b::String} ``` A `NamedTuple` type can be used as a constructor, accepting a single tuple argument. @@ -976,10 +1008,10 @@ The constructed `NamedTuple` type can be either a concrete type, with both param or a type that specifies only field names: ```jldoctest -julia> @NamedTuple{a::Float32,b::String}((1,"")) +julia> @NamedTuple{a::Float32,b::String}((1, "")) (a = 1.0f0, b = "") -julia> NamedTuple{(:a, :b)}((1,"")) +julia> NamedTuple{(:a, :b)}((1, "")) (a = 1, b = "") ``` @@ -1116,16 +1148,16 @@ Parametric types can be singleton types when the above condition holds. For exam julia> struct NoFieldsParam{T} end -julia> Base.issingletontype(NoFieldsParam) # can't be a singleton type ... +julia> Base.issingletontype(NoFieldsParam) # Can't be a singleton type ... false julia> NoFieldsParam{Int}() isa NoFieldsParam # ... because it has ... true -julia> NoFieldsParam{Bool}() isa NoFieldsParam # ... multiple instances +julia> NoFieldsParam{Bool}() isa NoFieldsParam # ... multiple instances. true -julia> Base.issingletontype(NoFieldsParam{Int}) # parametrized, it is a singleton +julia> Base.issingletontype(NoFieldsParam{Int}) # Parametrized, it is a singleton. true julia> NoFieldsParam{Int}() === NoFieldsParam{Int}() @@ -1169,11 +1201,14 @@ Types of closures are not necessarily singletons. julia> addy(y) = x -> x + y addy (generic function with 1 method) -julia> Base.issingletontype(addy(1)) -false +julia> typeof(addy(1)) === typeof(addy(2)) +true julia> addy(1) === addy(2) false + +julia> Base.issingletontype(typeof(addy(1))) +false ``` ## [`Type{T}` type selectors](@id man-typet-type) @@ -1514,7 +1549,7 @@ when the `:compact` property is set to `true`, falling back to the long representation if the property is `false` or absent: ```jldoctest polartype julia> function Base.show(io::IO, z::Polar) - if get(io, :compact, false) + if get(io, :compact, false)::Bool print(io, z.r, "ℯ", z.Θ, "im") else print(io, z.r, " * exp(", z.Θ, "im)") @@ -1545,8 +1580,8 @@ floating-point numbers, tuples, etc.) as type parameters. A common example is t parameter in `Array{T,N}`, where `T` is a type (e.g., [`Float64`](@ref)) but `N` is just an `Int`. You can create your own custom types that take values as parameters, and use them to control dispatch -of custom types. By way of illustration of this idea, let's introduce a parametric type, `Val{x}`, -and a constructor `Val(x) = Val{x}()`, which serves as a customary way to exploit this technique +of custom types. By way of illustration of this idea, let's introduce the parametric type `Val{x}`, +and its constructor `Val(x) = Val{x}()`, which serves as a customary way to exploit this technique for cases where you don't need a more elaborate hierarchy. [`Val`](@ref) is defined as: @@ -1585,5 +1620,5 @@ in unfavorable cases, you can easily end up making the performance of your code In particular, you would never want to write actual code as illustrated above. For more information about the proper (and improper) uses of `Val`, please read [the more extensive discussion in the performance tips](@ref man-performance-value-type). -[^1]: "Small" is defined by the `MAX_UNION_SPLITTING` constant, which is currently set to 4. +[^1]: "Small" is defined by the `max_union_splitting` configuration, which currently defaults to 4. [^2]: A few popular languages have singleton types, including Haskell, Scala and Ruby. diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index 6e94037f3e564..8bd62fe7ee5bf 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -90,13 +90,7 @@ julia> module B julia> module D b = a # errors as D's global scope is separate from A's end; -ERROR: UndefVarError: a not defined - -julia> module E - import ..A # make module A available - A.a = 2 # throws below error - end; -ERROR: cannot assign variables in other modules +ERROR: UndefVarError: `a` not defined ``` If a top-level expression contains a variable declaration with keyword `local`, @@ -117,14 +111,14 @@ x = 1 Note that the interactive prompt (aka REPL) is in the global scope of the module `Main`. -## Local Scope +## [Local Scope](@id local-scope) A new local scope is introduced by most code blocks (see above [table](@ref man-scope-table) for a complete list). If such a block is syntactically nested inside of another local scope, the scope it creates is nested inside of all the local scopes that it appears within, which are all ultimately nested inside of the global scope of the module in which the code is evaluated. Variables in -outer scopes are visible from any scope they contain — meaning that they can be +outer scopes are visible from any scope they contain — meaning that they can be read and written in inner scopes — unless there is a local variable with the same name that "shadows" the outer variable of the same name. This is true even if the outer local is declared after (in the sense of textually below) an inner @@ -193,7 +187,7 @@ julia> greet() hello julia> x # global -ERROR: UndefVarError: x not defined +ERROR: UndefVarError: `x` not defined ``` Inside of the `greet` function, the assignment `x = "hello"` causes `x` to be a new local variable @@ -262,7 +256,7 @@ julia> sum_to(10) 55 julia> s # global -ERROR: UndefVarError: s not defined +ERROR: UndefVarError: `s` not defined ``` Since `s` is local to the function `sum_to`, calling the function has no effect on the global @@ -349,7 +343,7 @@ hello hello julia> x -ERROR: UndefVarError: x not defined +ERROR: UndefVarError: `x` not defined ``` Since the global `x` is not defined when the `for` loop is evaluated, the first clause of the soft @@ -414,7 +408,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 ``` Here we use [`include_string`](@ref), to evaluate `code` as though it were the contents of a file. @@ -532,7 +526,7 @@ prints this very direct warning: This addresses both issues while preserving the "programming at scale" benefits of the 1.0 behavior: global variables have no spooky effect on the meaning of code that may be far away; in the REPL copy-and-paste debugging works and beginners don't have any issues; any time someone either forgets -a `global` annotation or accidentally shadows an existing global with a local in a soft scope, +a `global` annotation or accidentally shadows an existing global with a local in a soft scope, which would be confusing anyway, they get a nice clear warning. An important property of this design is that any code that executes in a file without a warning will @@ -565,7 +559,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 ``` 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 f61503d99a67c..6c22719c1ce86 100644 --- a/doc/src/manual/variables.md +++ b/doc/src/manual/variables.md @@ -81,13 +81,13 @@ julia> pi π = 3.1415926535897... julia> pi = 3 -ERROR: cannot assign a value to variable MathConstants.pi from module Main +ERROR: cannot assign a value to imported variable Base.pi from module Main julia> sqrt(100) 10.0 julia> sqrt = 4 -ERROR: cannot assign a value to variable Base.sqrt from module Main +ERROR: cannot assign a value to imported variable Base.sqrt from module Main ``` ## [Allowed Variable Names](@id man-allowed-variable-names) @@ -111,9 +111,8 @@ variable name. For example, if `+ᵃ` is an operator, then `+ᵃx` must be writt it from `+ ᵃx` where `ᵃx` is the variable name. -A particular class of variable names is one that contains only underscores. These identifiers can only be assigned values but cannot be used to assign values to other variables. -More technically, they can only be used as an [L-value](https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue), but not as an - [R-value](https://en.wikipedia.org/wiki/R-value): +A particular class of variable names is one that contains only underscores. These identifiers can only be assigned values, which are immediately discarded, and cannot therefore be used to assign values to other variables (i.e., they cannot be used as [`rvalues`](https://en.wikipedia.org/wiki/Value_(computer_science)#Assignment:_l-values_and_r-values)) or use the last value +assigned to them in any way. ```julia-repl julia> x, ___ = size([2 2; 1 1]) @@ -121,6 +120,9 @@ julia> x, ___ = size([2 2; 1 1]) julia> y = ___ ERROR: syntax: all-underscore identifier used as rvalue + +julia> println(___) +ERROR: syntax: all-underscore identifier used as rvalue ``` The only explicitly disallowed names for variables are the names of the built-in [Keywords](@ref Keywords): @@ -135,7 +137,7 @@ ERROR: syntax: unexpected "=" Some Unicode characters are considered to be equivalent in identifiers. Different ways of entering Unicode combining characters (e.g., accents) -are treated as equivalent (specifically, Julia identifiers are [NFC](http://www.macchiato.com/unicode/nfc-faq)-normalized). +are treated as equivalent (specifically, Julia identifiers are [NFC](https://en.wikipedia.org/wiki/Unicode_equivalence). Julia also includes a few non-standard equivalences for characters that are visually similar and are easily entered by some input methods. The Unicode characters `ɛ` (U+025B: Latin small letter open e) and `µ` (U+00B5: micro sign) @@ -145,6 +147,79 @@ are treated as equivalent to the corresponding Greek letters. The middle dot treated as the mathematical dot operator `⋅` (U+22C5). The minus sign `−` (U+2212) is treated as equivalent to the hyphen-minus sign `-` (U+002D). +## [Assignment expressions and assignment versus mutation](@id man-assignment-expressions) + +An assignment `variable = value` "binds" the name `variable` to the `value` computed +on the right-hand side, and the whole assignment is treated by Julia as an expression +equal to the right-hand-side `value`. This means that assignments can be *chained* +(the same `value` assigned to multiple variables with `variable1 = variable2 = value`) +or used in other expressions, and is also why their result is shown in the REPL as +the value of the right-hand side. (In general, the REPL displays the value of whatever +expression you evaluate.) For example, here the value `4` of `b = 2+2` is +used in another arithmetic operation and assignment: + +```jldoctest +julia> a = (b = 2+2) + 3 +7 + +julia> a +7 + +julia> b +4 +``` + +A common confusion is the distinction between *assignment* (giving a new "name" to a value) +and *mutation* (changing a value). If you run `a = 2` followed by `a = 3`, you have changed +the "name" `a` to refer to a new value `3` … you haven't changed the number `2`, so `2+2` +will still give `4` and not `6`! This distinction becomes more clear when dealing with +*mutable* types like [arrays](@ref lib-arrays), whose contents *can* be changed: + +```jldoctest mutation_vs_rebind +julia> a = [1,2,3] # an array of 3 integers +3-element Vector{Int64}: + 1 + 2 + 3 + +julia> b = a # both b and a are names for the same array! +3-element Vector{Int64}: + 1 + 2 + 3 +``` + +Here, the line `b = a` does *not* make a copy of the array `a`, it simply binds the name +`b` to the *same* array `a`: both `b` and `a` "point" to one array `[1,2,3]` in memory. +In contrast, an assignment `a[i] = value` *changes* the *contents* of the array, and the +modified array will be visible through both the names `a` and `b`: + +```jldoctest mutation_vs_rebind +julia> a[1] = 42 # change the first element +42 + +julia> a = 3.14159 # a is now the name of a different object +3.14159 + +julia> b # b refers to the original array object, which has been mutated +3-element Vector{Int64}: + 42 + 2 + 3 +``` +That is, `a[i] = value` (an alias for [`setindex!`](@ref)) *mutates* an existing array object +in memory, accessible via either `a` or `b`. Subsequently setting `a = 3.14159` +does not change this array, it simply binds `a` to a different object; the array is still +accessible via `b`. The other common syntax to mutate an existing object is +`a.field = value` (an alias for [`setproperty!`](@ref)), which can be used to change +a [`mutable struct`](@ref). + +When you call a [function](@ref man-functions) in Julia, it behaves as if you *assigned* +the argument values to new variable names corresponding to the function arguments, as discussed +in [Argument-Passing Behavior](@ref man-argument-passing). (By [convention](@ref man-punctuation), +functions that mutate one or more of their arguments have names ending with `!`.) + + ## Stylistic Conventions While Julia imposes few restrictions on valid names, it has become useful to adopt the following diff --git a/doc/src/manual/workflow-tips.md b/doc/src/manual/workflow-tips.md index 7ee4b6aefba77..4085a51ff9131 100644 --- a/doc/src/manual/workflow-tips.md +++ b/doc/src/manual/workflow-tips.md @@ -64,8 +64,9 @@ line. A common pattern includes the following elements: ## Browser-based workflow -It is also possible to interact with a Julia REPL in the browser via [IJulia](https://github.com/JuliaLang/IJulia.jl). -See the package home for details. +There are a few ways to interact with Julia in a browser: +- Using Pluto notebooks through [Pluto.jl](https://github.com/fonsp/Pluto.jl) +- Using Jupyter notebooks through [IJulia.jl](https://github.com/JuliaLang/IJulia.jl) ## Revise-based workflows diff --git a/julia.spdx.json b/julia.spdx.json index 22d28a44280ee..bea7bdc6c3a5d 100644 --- a/julia.spdx.json +++ b/julia.spdx.json @@ -3,31 +3,28 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "julia-spdx", - "documentNamespace": "https://julialang.org/spdxdocs/julia-spdx-7b93ad83-27bf-433f-b769-cde3288fe3a1", + "documentNamespace": "https://julialang.org/spdxdocs/julia-spdx-156599cd-b5aa-442c-a0d4-72ed73a46d16", "creationInfo": { "creators": [ - "Organization: julialang.org ()", - "Person: Simon Avery ()" + "Organization: julialang.org ()", + "Person: Simon Avery ()" ], - "created": "2021-12-21T07:13:19Z" + "created": "2022-05-19T06:17:33Z" }, - "documentDescribes": [ - "SPDXRef-JuliaMain" - ], "packages": [ { "name": "Julia", "SPDXID": "SPDXRef-JuliaMain", - "versionInfo": "1.8.0-DEV", + "versionInfo": "1.9.0-DEV", "packageFileName": "./", - "downloadLocation": "git+https://github.com/JuliaLang/julia.git@v1.8.0-DEV", + "downloadLocation": "git+https://github.com/JuliaLang/julia.git@v1.9.0-DEV", "filesAnalyzed": false, "homepage": "https://julialang.org", "licenseConcluded": "MIT", "licenseDeclared": "MIT", - "copyrightText": "Copyright (c) 2009-2021: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors", + "copyrightText": "Copyright (c) 2009-2022: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors", "summary": "Julia is a high-level, high-performance dynamic language for technical computing.", - "comment": "In addition to the source code described by this package, Julia pulls in code from many other respositories, which are also described in this document. See relationships for details." + "comment": "In addition to the source code described by this package, Julia pulls in code from many other repositories, which are also described in this document. See relationships for details." }, { "name": "Pkg.jl", @@ -44,9 +41,9 @@ { "name": "Statistics.jl", "SPDXID": "SPDXRef-JuliaStatistics", - "downloadLocation": "git+https://github.com/JuliaLang/Statistics.jl.git", + "downloadLocation": "git+https://github.com/JuliaStats/Statistics.jl.git", "filesAnalyzed": false, - "homepage": "https://julialang.org", + "homepage": "https://juliastats.org", "sourceInfo": "The git hash of the version in use can be found in the file stdlib/Statistics.version", "licenseConcluded": "MIT", "licenseDeclared": "MIT", @@ -155,7 +152,7 @@ "downloadLocation": "git+https://github.com/MersenneTwister-Lab/dSFMT.git", "filesAnalyzed": false, "homepage": "https://github.com/MersenneTwister-Lab/dSFMT", - "sourceInfo": "The git hash of the version in use can be found in the file deps/Versions.make", + "sourceInfo": "The git hash of the version in use can be found in the file deps/dsfmt.version", "licenseConcluded": "BSD-3-Clause", "licenseDeclared": "BSD-3-Clause", "copyrightText": "Copyright (c) 2007, 2008, 2009 Mutsuo Saito, Makoto Matsumoto and Hiroshima University. Copyright (c) 2011, 2002 Mutsuo Saito, Makoto Matsumoto, Hiroshima University and The University of Tokyo.", @@ -179,7 +176,7 @@ "downloadLocation": "https://gmplib.org/download/gmp/", "filesAnalyzed": false, "homepage": "https://gmplib.org/", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/gmp.version", "licenseConcluded": "LGPL-3.0-or-later", "licenseDeclared": "LGPL-3.0-or-later OR GPL-2.0-or-later", "copyrightText": "Copyright 1991, 1996, 1999, 2000, 2007 Free Software Foundation, Inc.", @@ -203,7 +200,7 @@ "downloadLocation": "git+https://github.com/curl/curl.git", "filesAnalyzed": false, "homepage": "https://curl.se", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/curl.version", "licenseConcluded": "curl", "licenseDeclared": "curl", "copyrightText": "Copyright (c) 1996 - 2021, Daniel Stenberg, daniel@haxx.se, and many contributors, see the THANKS file.", @@ -227,7 +224,7 @@ "downloadLocation": "git+https://github.com/ARMmbed/mbedtls.git", "filesAnalyzed": false, "homepage": "https://tls.mbed.org", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/mbedtls.version", "licenseConcluded": "Apache-2.0", "licenseDeclared": "Apache-2.0", "copyrightText": "NOASSERTION", @@ -239,7 +236,7 @@ "downloadLocation": "https://www.mpfr.org/", "filesAnalyzed": false, "homepage": "https://www.mpfr.org/", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/mpfr.version", "licenseConcluded": "LGPL-3.0-or-later", "licenseDeclared": "LGPL-3.0-or-later", "copyrightText": "Copyright 2000-2020 Free Software Foundation, Inc.", @@ -263,7 +260,7 @@ "downloadLocation": "https://www.netlib.org/lapack/", "filesAnalyzed": false, "homepage": "https://netlib.org/", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/openblas.version", "licenseConcluded": "BSD-3-Clause", "licenseDeclared": "BSD-3-Clause", "copyrightText": "Copyright (c) 1992-2013 The University of Tennessee and The University of Tennessee Research Foundation. All rights reserved.\nCopyright (c) 2000-2013 The University of California Berkeley. All rights reserved.\nCopyright (c) 2006-2013 The University of Colorado Denver. All rights reserved.", @@ -275,7 +272,7 @@ "downloadLocation": "git+https://github.com/PhilipHazel/pcre2.git", "filesAnalyzed": false, "homepage": "https://www.pcre.org", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/pcre.version", "licenseConcluded": "BSD-3-Clause", "licenseDeclared": "BSD-3-Clause", "copyrightText": "Copyright (c) 1997-2021 University of Cambridge All rights reserved.\nCopyright(c) 2009-2021 Zoltan Herczeg\n", @@ -288,7 +285,7 @@ "downloadLocation": "git+https://github.com/DrTimothyAldenDavis/SuiteSparse.git", "filesAnalyzed": false, "homepage": "https://people.engr.tamu.edu/davis/suitesparse.html", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/libsuitesparse.version", "licenseConcluded": "GPL-2.0-or-later", "licenseDeclared": "LGPL-2.0-or-later AND GPL-2.0-or-later AND BSD-3 AND Apache-2.0 ", "licenseComments": "SuiteSparse consists of many modules, each of which is licensed separately.", @@ -325,7 +322,7 @@ "downloadLocation": "git+https://github.com/libunwind/libunwind.git", "filesAnalyzed": false, "homepage": "http://www.nongnu.org/libunwind/", - "sourceInfo": "The git hash of the version in use can be found in the file deps/Versions.make", + "sourceInfo": "The git hash of the version in use can be found in the file deps/unwind.version", "licenseConcluded": "MIT", "licenseDeclared": "MIT", "copyrightText": "Copyright (c) 2002 Hewlett-Packard Co.", @@ -379,7 +376,7 @@ "downloadLocation": "https://sourceforge.net/projects/p7zip/files/p7zip", "filesAnalyzed": false, "homepage": "https://www.7-zip.org", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/p7zip.version", "licenseConcluded": "LGPL-3.0-or-later", "licenseDeclared": "LGPL-3.0-or-later AND BSD-3", "copyrightText": "Copyright (C) 1999-2021 Igor Pavlov", @@ -403,12 +400,12 @@ "downloadLocation": "git+https://github.com/NixOS/patchelf.git", "filesAnalyzed": false, "homepage": "https://nixos.org/patchelf.html", - "sourceInfo": "The version in use can be found in the file deps/Versions.make", + "sourceInfo": "The version in use can be found in the file deps/patchelf.version", "licenseConcluded": "GPL-3.0-or-later", "licenseDeclared": "GPL-3.0-or-later", "copyrightText": "Copyright (C) 2007 Free Software Foundation, Inc. ", "summary": "A small utility to modify the dynamic linker and RPATH of ELF executables.", - "comment": "PATCHELF is not part of the Julia binary. It is a tool used as part of building the binary, a bit like a compiler. Julia chooses to build the tool from source during the build process as a convienence." + "comment": "PATCHELF is not part of the Julia binary. It is a tool used as part of building the binary, a bit like a compiler. Julia chooses to build the tool from source during the build process as a convenience." }, { "name": "objconv", @@ -420,28 +417,30 @@ "licenseDeclared": "GPL-3.0-or-later", "copyrightText": "By Agner Fog © 2018", "summary": "A utility for cross-platform development of function libraries, for converting and modifying object files and for dumping and disassembling object and executable files for all x86 and x86-64 platforms.", - "comment": "OBJCONV is not part of the Julia binary. It is a tool used as part of building the binary, a bit like a compiler. Julia chooses to build the tool from source during the build process as a convienence." + "comment": "OBJCONV is not part of the Julia binary. It is a tool used as part of building the binary, a bit like a compiler. Julia chooses to build the tool from source during the build process as a convenience." }, { "name": "libwhich", "SPDXID": "SPDXRef-libwhich", "downloadLocation": "git+https://github.com/vtjnash/libwhich.git", - "sourceInfo": "The git hash of the version in use can be found in the file stdlib/libwhich.version", "filesAnalyzed": false, "homepage": "https://github.com/vtjnash/libwhich", + "sourceInfo": "The git hash of the version in use can be found in the file stdlib/libwhich.version", "licenseConcluded": "MIT", "licenseDeclared": "MIT", "copyrightText": "Copyright (c) 2017 Jameson Nash", "summary": "Like `which`, for dynamic libraries", - "comment": "LIBWHICH is not part of the Julia binary. It is a tool used as part of building the binary, a bit like a compiler. Julia chooses to build the tool from source during the build process as a convienence." + "comment": "LIBWHICH is not part of the Julia binary. It is a tool used as part of building the binary, a bit like a compiler. Julia chooses to build the tool from source during the build process as a convenience." } ], - "relationships": [ + "hasExtractedLicensingInfos": [ { - "spdxElementId": "SPDXRef-DOCUMENT", - "relationshipType": "DESCRIBES", - "relatedSpdxElement": "SPDXRef-JuliaMain" - }, + "licenseId": "LicenseRef-GPL-2.0-only-with-libgit2-exception", + "extractedText": "Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated.\n----------------------------------------------------------------------\nIn addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled version of this library into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combined executable.)\n----------------------------------------------------------------------\nGNU GENERAL PUBLIC LICENSE\nVersion 2, June 1991\n\nCopyright (C) 1989, 1991 Free Software Foundation, Inc.\n59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\nEveryone is permitted to copy and distribute verbatim copies\nof this license document, but changing it is not allowed.\n... [more text]", + "name": "GPL-2.0-only-with-libgit2-exception" + } + ], + "relationships": [ { "spdxElementId": "SPDXRef-JuliaPkg", "relationshipType": "BUILD_DEPENDENCY_OF", @@ -482,6 +481,11 @@ "relationshipType": "BUILD_DEPENDENCY_OF", "relatedSpdxElement": "SPDXRef-JuliaMain" }, + { + "spdxElementId": "SPDXRef-JuliaSparseArrays", + "relationshipType": "BUILD_DEPENDENCY_OF", + "relatedSpdxElement": "SPDXRef-JuliaMain" + }, { "spdxElementId": "SPDXRef-JuliaSHA", "relationshipType": "BUILD_DEPENDENCY_OF", @@ -603,11 +607,7 @@ "relatedSpdxElement": "SPDXRef-JuliaMain" } ], - "hasExtractedLicensingInfos": [ - { - "licenseId": "LicenseRef-GPL-2.0-only-with-libgit2-exception", - "extractedText": "Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated.\n----------------------------------------------------------------------\nIn addition to the permissions in the GNU General Public License, the authors give you unlimited permission to link the compiled version of this library into combinations with other programs, and to distribute those combinations without any restriction coming from the use of this file. (The General Public License restrictions do apply in other respects; for example, they cover modification of the file, and distribution when not linked into a combined executable.)\n----------------------------------------------------------------------\nGNU GENERAL PUBLIC LICENSE\nVersion 2, June 1991\n\nCopyright (C) 1989, 1991 Free Software Foundation, Inc.\n59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\nEveryone is permitted to copy and distribute verbatim copies\nof this license document, but changing it is not allowed.\n... [more text]", - "name": "GPL-2.0-only-with-libgit2-exception" - } + "documentDescribes": [ + "SPDXRef-JuliaMain" ] } diff --git a/pkgimage.mk b/pkgimage.mk new file mode 100644 index 0000000000000..dcf9dd1303d47 --- /dev/null +++ b/pkgimage.mk @@ -0,0 +1,124 @@ +SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +BUILDDIR := . +JULIAHOME := $(SRCDIR) +include $(JULIAHOME)/Make.inc + +VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) + +export JULIA_DEPOT_PATH := $(build_prefix)/share/julia + +$(JULIA_DEPOT_PATH): + mkdir -p $@ + +print-depot-path: + @$(call PRINT_JULIA, $(call spawn,$(JULIA_EXECUTABLE)) --startup-file=no -e '@show Base.DEPOT_PATH') + +STDLIBS := ArgTools Artifacts Base64 CRC32c FileWatching Libdl NetworkOptions SHA Serialization \ + GMP_jll LLVMLibUnwind_jll LibUV_jll LibUnwind_jll MbedTLS_jll OpenLibm_jll PCRE2_jll \ + Zlib_jll dSFMT_jll libLLVM_jll libblastrampoline_jll OpenBLAS_jll Printf Random Tar \ + LibSSH2_jll MPFR_jll LinearAlgebra Dates Distributed Future LibGit2 Profile SparseArrays UUIDs \ + SharedArrays TOML Test LibCURL Downloads Pkg Dates LazyArtifacts Sockets Unicode Markdown \ + InteractiveUtils REPL DelimitedFiles + +all-release: $(addprefix cache-release-, $(STDLIBS)) +all-debug: $(addprefix cache-debug-, $(STDLIBS)) + +define pkgimg_builder +$1_SRCS := $$(shell find $$(build_datarootdir)/julia/stdlib/$$(VERSDIR)/$1/src -name \*.jl) \ + $$(wildcard $$(build_prefix)/manifest/$$(VERSDIR)/$1) +$$(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 -e 'Base.compilecache(Base.identify_package("$1"))') + touch $$@ +cache-release-$1: $$(BUILDDIR)/stdlib/$1.release.image +$$(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 -e 'Base.compilecache(Base.identify_package("$1"))') +cache-debug-$1: $$(BUILDDIR)/stdlib/$1.debug.image +.SECONDARY: $$(BUILDDIR)/stdlib/$1.release.image $$(BUILDDIR)/stdlib/$1.debug.image +endef + +# Used to just define them in the dependency graph +# reside in the system image +define sysimg_builder +$$(BUILDDIR)/stdlib/$1.release.image: + touch $$@ +cache-release-$1: $$(BUILDDIR)/stdlib/$1.release.image +$$(BUILDDIR)/stdlib/$1.debug.image: + touch $$@ +cache-debug-$1: $$(BUILDDIR)/stdlib/$1.debug.image +.SECONDARY: $$(BUILDDIR)/stdlib/$1.release.image $$(BUILDDIR)/stdlib/$1.debug.image +endef + +# no dependencies +$(eval $(call pkgimg_builder,MozillaCACerts_jll,)) +$(eval $(call sysimg_builder,ArgTools,)) +$(eval $(call sysimg_builder,Artifacts,)) +$(eval $(call sysimg_builder,Base64,)) +$(eval $(call sysimg_builder,CRC32c,)) +$(eval $(call sysimg_builder,FileWatching,)) +$(eval $(call sysimg_builder,Libdl,)) +$(eval $(call sysimg_builder,Logging,)) +$(eval $(call sysimg_builder,Mmap,)) +$(eval $(call sysimg_builder,NetworkOptions,)) +$(eval $(call sysimg_builder,SHA,)) +$(eval $(call sysimg_builder,Serialization,)) +$(eval $(call sysimg_builder,Sockets,)) +$(eval $(call sysimg_builder,Unicode,)) +$(eval $(call pkgimg_builder,Profile,)) + +# 1-depth packages +$(eval $(call pkgimg_builder,GMP_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,LLVMLibUnwind_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,LibUV_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,LibUnwind_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,MbedTLS_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,nghttp2_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,OpenLibm_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,PCRE2_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,Zlib_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,dSFMT_jll,Artifacts Libdl)) +$(eval $(call pkgimg_builder,libLLVM_jll,Artifacts Libdl)) +$(eval $(call sysimg_builder,libblastrampoline_jll,Artifacts Libdl)) +$(eval $(call sysimg_builder,OpenBLAS_jll,Artifacts Libdl)) +$(eval $(call sysimg_builder,Markdown,Base64)) +$(eval $(call sysimg_builder,Printf,Unicode)) +$(eval $(call sysimg_builder,Random,SHA)) +$(eval $(call sysimg_builder,Tar,ArgTools,SHA)) +$(eval $(call pkgimg_builder,DelimitedFiles,Mmap)) + +# 2-depth packages +$(eval $(call pkgimg_builder,LLD_jll,Zlib_jll libLLVM_jll Artifacts Libdl)) +$(eval $(call pkgimg_builder,LibSSH2_jll,Artifacts Libdl MbedTLS_jll)) +$(eval $(call pkgimg_builder,MPFR_jll,Artifacts Libdl GMP_jll)) +$(eval $(call sysimg_builder,LinearAlgebra,Libdl libblastrampoline_jll OpenBLAS_jll)) +$(eval $(call sysimg_builder,Dates,Printf)) +$(eval $(call pkgimg_builder,Distributed,Random Serialization Sockets)) +$(eval $(call sysimg_builder,Future,Random)) +$(eval $(call sysimg_builder,InteractiveUtils,Markdown)) +$(eval $(call sysimg_builder,LibGit2,NetworkOptions Printf SHA Base64)) +$(eval $(call sysimg_builder,UUIDs,Random SHA)) + + # 3-depth packages + # LibGit2_jll +$(eval $(call pkgimg_builder,LibCURL_jll,LibSSH2_jll nghttp2_jll MbedTLS_jll Zlib_jll Artifacts Libdl)) +$(eval $(call sysimg_builder,REPL,InteractiveUtils Markdown Sockets Unicode)) +$(eval $(call pkgimg_builder,SharedArrays,Distributed Mmap Random Serialization)) +$(eval $(call sysimg_builder,TOML,Dates)) +$(eval $(call pkgimg_builder,Test,Logging Random Serialization InteractiveUtils)) + +# 4-depth packages +$(eval $(call sysimg_builder,LibCURL,LibCURL_jll MozillaCACerts_jll)) + +# 5-depth packages +$(eval $(call sysimg_builder,Downloads,ArgTools FileWatching LibCURL NetworkOptions)) + +# 6-depth packages +$(eval $(call sysimg_builder,Pkg,Dates LibGit2 Libdl Logging Printf Random SHA UUIDs)) # Markdown REPL + +# 7-depth packages +$(eval $(call pkgimg_builder,LazyArtifacts,Artifacts Pkg)) + +$(eval $(call pkgimg_builder,SparseArrays,Libdl LinearAlgebra Random Serialization)) +# SuiteSparse_jll +# Statistics diff --git a/src/.gitignore b/src/.gitignore index 388e971d4f12d..4ddd75fbb5d62 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -21,6 +21,7 @@ /julia_version.h /flisp/host /support/host +/base/ # Clang compilation database /compile_commands*.json diff --git a/src/APInt-C.cpp b/src/APInt-C.cpp index bc0a62e21dd3e..f06d4362bf958 100644 --- a/src/APInt-C.cpp +++ b/src/APInt-C.cpp @@ -316,7 +316,7 @@ void LLVMByteSwap(unsigned numbits, integerPart *pa, integerPart *pr) { void LLVMFPtoInt(unsigned numbits, void *pa, unsigned onumbits, integerPart *pr, bool isSigned, bool *isExact) { double Val; if (numbits == 16) - Val = __gnu_h2f_ieee(*(uint16_t*)pa); + Val = julia__gnu_h2f_ieee(*(uint16_t*)pa); else if (numbits == 32) Val = *(float*)pa; else if (numbits == 64) @@ -391,7 +391,7 @@ void LLVMSItoFP(unsigned numbits, integerPart *pa, unsigned onumbits, integerPar val = a.roundToDouble(true); } if (onumbits == 16) - *(uint16_t*)pr = __gnu_f2h_ieee(val); + *(uint16_t*)pr = julia__gnu_f2h_ieee(val); else if (onumbits == 32) *(float*)pr = val; else if (onumbits == 64) @@ -408,7 +408,7 @@ void LLVMUItoFP(unsigned numbits, integerPart *pa, unsigned onumbits, integerPar val = a.roundToDouble(false); } if (onumbits == 16) - *(uint16_t*)pr = __gnu_f2h_ieee(val); + *(uint16_t*)pr = julia__gnu_f2h_ieee(val); else if (onumbits == 32) *(float*)pr = val; else if (onumbits == 64) diff --git a/src/Makefile b/src/Makefile index b7235597fd08c..382e904818838 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,6 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) JULIAHOME := $(abspath $(SRCDIR)/..) BUILDDIR := . -include $(JULIAHOME)/deps/Versions.make include $(JULIAHOME)/Make.inc include $(JULIAHOME)/deps/llvm-ver.make @@ -41,28 +40,23 @@ ifeq ($(OS),FreeBSD) FLAGS += -I$(LOCALBASE)/include endif -RUNTIME_SRCS := \ +SRCS := \ jltypes gf typemap smallintset ast builtins module interpreter symbol \ - dlload sys init task array dump staticdata toplevel jl_uv datatype \ - simplevector runtime_intrinsics precompile \ + dlload sys init task array staticdata toplevel jl_uv datatype \ + simplevector runtime_intrinsics precompile jloptions \ threading partr stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler method \ - jlapi signal-handling safepoint timing subtype \ - crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage -SRCS := jloptions runtime_ccall rtutils -ifeq ($(OS),WINNT) -SRCS += win32_ucontext -endif + jlapi signal-handling safepoint timing subtype rtutils gc-heap-snapshot \ + crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage runtime_ccall RT_LLVMLINK := CG_LLVMLINK := ifeq ($(JULIACODEGEN),LLVM) -CODEGEN_SRCS := codegen llvm-ptls -RUNTIME_CODEGEN_SRCS := jitlayers aotcompile debuginfo disasm llvm-simdloop llvm-muladd \ - llvm-final-gc-lowering llvm-pass-helpers llvm-late-gc-lowering \ +CODEGEN_SRCS := codegen jitlayers aotcompile debuginfo disasm llvm-simdloop llvm-muladd \ + llvm-final-gc-lowering llvm-pass-helpers llvm-late-gc-lowering llvm-ptls \ llvm-lower-handlers llvm-gc-invariant-verifier llvm-propagate-addrspaces \ llvm-multiversioning llvm-alloc-opt llvm-alloc-helpers cgmemmgr llvm-remove-addrspaces \ - llvm-remove-ni llvm-julia-licm llvm-demote-float16 llvm-cpufeatures + llvm-remove-ni llvm-julia-licm llvm-demote-float16 llvm-cpufeatures pipeline FLAGS += -I$(shell $(LLVM_CONFIG_HOST) --includedir) CG_LLVM_LIBS := all ifeq ($(USE_POLLY),1) @@ -85,9 +79,9 @@ endif RT_LLVM_LIBS := support -SRCS += $(RUNTIME_SRCS) - -CODEGEN_SRCS += $(RUNTIME_CODEGEN_SRCS) +ifeq ($(OS),WINNT) +SRCS += win32_ucontext +endif ifeq ($(WITH_DTRACE),1) DTRACE_HEADERS := uprobes.h.gen @@ -97,6 +91,7 @@ endif else DTRACE_HEADERS := endif +.SECONDARY: $(addprefix $(BUILDDIR)/,$(DTRACE_HEADERS)) # headers are used for dependency tracking, while public headers will be part of the dist UV_HEADERS := @@ -104,37 +99,47 @@ ifeq ($(USE_SYSTEM_LIBUV),0) UV_HEADERS += uv.h UV_HEADERS += uv/*.h endif -PUBLIC_HEADERS := $(BUILDDIR)/julia_version.h $(wildcard $(SRCDIR)/support/*.h) $(addprefix $(SRCDIR)/,julia.h julia_assert.h julia_threads.h julia_fasttls.h julia_locks.h julia_atomics.h jloptions.h) +PUBLIC_HEADERS := $(BUILDDIR)/julia_version.h $(wildcard $(SRCDIR)/support/*.h) $(addprefix $(SRCDIR)/,work-stealing-queue.h julia.h julia_assert.h julia_threads.h julia_fasttls.h julia_locks.h julia_atomics.h jloptions.h) ifeq ($(OS),WINNT) PUBLIC_HEADERS += $(addprefix $(SRCDIR)/,win32_ucontext.h) endif -HEADERS := $(PUBLIC_HEADERS) $(addprefix $(SRCDIR)/,julia_internal.h options.h timing.h) $(addprefix $(BUILDDIR)/,$(DTRACE_HEADERS) jl_internal_funcs.inc) +HEADERS := $(PUBLIC_HEADERS) $(addprefix $(SRCDIR)/,julia_internal.h options.h timing.h passes.h) $(addprefix $(BUILDDIR)/,$(DTRACE_HEADERS) jl_internal_funcs.inc) PUBLIC_HEADERS += $(addprefix $(SRCDIR)/,julia_gcext.h) PUBLIC_HEADER_TARGETS := $(addprefix $(build_includedir)/julia/,$(notdir $(PUBLIC_HEADERS)) $(UV_HEADERS)) LLVM_LDFLAGS := $(shell $(LLVM_CONFIG_HOST) --ldflags) LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG_HOST) --cxxflags) +# llvm-config --cxxflags does not return -DNDEBUG +ifeq ($(shell $(LLVM_CONFIG_HOST) --assertion-mode),OFF) +LLVM_CXXFLAGS += -DNDEBUG +endif + ifeq ($(JULIACODEGEN),LLVM) ifneq ($(USE_SYSTEM_LLVM),0) +# USE_SYSTEM_LLVM != 0 CG_LLVMLINK += $(LLVM_LDFLAGS) $(shell $(LLVM_CONFIG_HOST) --libs --system-libs) # HACK: llvm-config doesn't correctly point to shared libs on all platforms # https://github.com/JuliaLang/julia/issues/29981 else +# USE_SYSTEM_LLVM == 0 ifneq ($(USE_LLVM_SHLIB),1) +# USE_LLVM_SHLIB != 1 CG_LLVMLINK += $(LLVM_LDFLAGS) $(shell $(LLVM_CONFIG_HOST) --libs $(CG_LLVM_LIBS) --link-static) $($(LLVM_LDFLAGS) $(shell $(LLVM_CONFIG_HOST) --system-libs 2> /dev/null) else +# USE_LLVM_SHLIB == 1 ifeq ($(OS), Darwin) CG_LLVMLINK += $(LLVM_LDFLAGS) -lLLVM else -CG_LLVMLINK += $(LLVM_LDFLAGS) -lLLVM-13jl -endif -endif -endif +CG_LLVMLINK += $(LLVM_LDFLAGS) $(LLVM_SHARED_LINK_FLAG) +endif # OS +endif # USE_LLVM_SHLIB +endif # USE_SYSTEM_LLVM + ifeq ($(USE_LLVM_SHLIB),1) FLAGS += -DLLVM_SHLIB endif # USE_LLVM_SHLIB == 1 -endif +endif # JULIACODEGEN == LLVM RT_LLVM_LINK_ARGS := $(shell $(LLVM_CONFIG_HOST) --libs $(RT_LLVM_LIBS) --system-libs --link-static) RT_LLVMLINK += $(LLVM_LDFLAGS) $(RT_LLVM_LINK_ARGS) @@ -146,15 +151,18 @@ CLANG_LDFLAGS := $(LLVM_LDFLAGS) ifeq ($(OS), Darwin) CLANG_LDFLAGS += -Wl,-undefined,dynamic_lookup OSLIBS += $(SRCDIR)/mach_dyld_atfork.tbd +LIBJULIA_PATH_REL := @rpath/libjulia +else +LIBJULIA_PATH_REL := libjulia endif COMMON_LIBPATHS := -L$(build_libdir) -L$(build_shlibdir) -RT_LIBS := $(LIBUV) $(LIBUTF8PROC) $(NO_WHOLE_ARCHIVE) $(LIBUNWIND) $(RT_LLVMLINK) $(OSLIBS) -CG_LIBS := $(NO_WHOLE_ARCHIVE) $(LIBUV) $(LIBUNWIND) $(CG_LLVMLINK) $(OSLIBS) +RT_LIBS := $(WHOLE_ARCHIVE) $(LIBUV) $(WHOLE_ARCHIVE) $(LIBUTF8PROC) $(NO_WHOLE_ARCHIVE) $(LIBUNWIND) $(RT_LLVMLINK) $(OSLIBS) $(LIBTRACYCLIENT) $(LIBITTAPI) +CG_LIBS := $(LIBUNWIND) $(CG_LLVMLINK) $(OSLIBS) $(LIBTRACYCLIENT) $(LIBITTAPI) RT_DEBUG_LIBS := $(COMMON_LIBPATHS) $(WHOLE_ARCHIVE) $(BUILDDIR)/flisp/libflisp-debug.a $(WHOLE_ARCHIVE) $(BUILDDIR)/support/libsupport-debug.a -ljulia-debug $(RT_LIBS) -CG_DEBUG_LIBS := $(COMMON_LIBPATHS) $(WHOLE_ARCHIVE) $(WHOLE_ARCHIVE) $(CG_LIBS) -ljulia-debug -ljulia-internal-debug +CG_DEBUG_LIBS := $(COMMON_LIBPATHS) $(CG_LIBS) -ljulia-debug -ljulia-internal-debug RT_RELEASE_LIBS := $(COMMON_LIBPATHS) $(WHOLE_ARCHIVE) $(BUILDDIR)/flisp/libflisp.a $(WHOLE_ARCHIVE) $(BUILDDIR)/support/libsupport.a -ljulia $(RT_LIBS) -CG_RELEASE_LIBS := $(COMMON_LIBPATHS) $(WHOLE_ARCHIVE) $(WHOLE_ARCHIVE) $(CG_LIBS) -ljulia -ljulia-internal +CG_RELEASE_LIBS := $(COMMON_LIBPATHS) $(CG_LIBS) -ljulia -ljulia-internal OBJS := $(SRCS:%=$(BUILDDIR)/%.o) DOBJS := $(SRCS:%=$(BUILDDIR)/%.dbg.obj) @@ -162,16 +170,16 @@ DOBJS := $(SRCS:%=$(BUILDDIR)/%.dbg.obj) CODEGEN_OBJS := $(CODEGEN_SRCS:%=$(BUILDDIR)/%.o) CODEGEN_DOBJS := $(CODEGEN_SRCS:%=$(BUILDDIR)/%.dbg.obj) -DEBUGFLAGS += $(FLAGS) -DLIBRARY_EXPORTS -SHIPFLAGS += $(FLAGS) -DLIBRARY_EXPORTS +SHIPFLAGS += $(FLAGS) +DEBUGFLAGS += $(FLAGS) # if not absolute, then relative to the directory of the julia executable SHIPFLAGS += "-DJL_SYSTEM_IMAGE_PATH=\"$(build_private_libdir_rel)/sys.$(SHLIB_EXT)\"" DEBUGFLAGS += "-DJL_SYSTEM_IMAGE_PATH=\"$(build_private_libdir_rel)/sys-debug.$(SHLIB_EXT)\"" # Add SONAME defines so we can embed proper `dlopen()` calls. -SHIPFLAGS += "-DJL_LIBJULIA_SONAME=\"libjulia.$(JL_MAJOR_SHLIB_EXT)\"" "-DJL_LIBJULIA_INTERNAL_SONAME=\"libjulia-internal.$(JL_MAJOR_SHLIB_EXT)\"" -DEBUGFLAGS += "-DJL_LIBJULIA_SONAME=\"libjulia-debug.$(JL_MAJOR_SHLIB_EXT)\"" "-DJL_LIBJULIA_INTERNAL_SONAME=\"libjulia-internal-debug.$(JL_MAJOR_SHLIB_EXT)\"" +SHIPFLAGS += "-DJL_LIBJULIA_SONAME=\"$(LIBJULIA_PATH_REL).$(JL_MAJOR_SHLIB_EXT)\"" +DEBUGFLAGS += "-DJL_LIBJULIA_SONAME=\"$(LIBJULIA_PATH_REL)-debug.$(JL_MAJOR_SHLIB_EXT)\"" ifeq ($(USE_CROSS_FLISP), 1) FLISPDIR := $(BUILDDIR)/flisp/host @@ -214,13 +222,13 @@ $(BUILDDIR)/jl_internal_funcs.inc: $(SRCDIR)/jl_exported_funcs.inc # source file rules $(BUILDDIR)/%.o: $(SRCDIR)/%.c $(HEADERS) | $(BUILDDIR) - @$(call PRINT_CC, $(CC) $(JCPPFLAGS) $(JCFLAGS) $(SHIPFLAGS) $(DISABLE_ASSERTIONS) -c $< -o $@) + @$(call PRINT_CC, $(CC) $(JCPPFLAGS) $(JCFLAGS) $(JL_CFLAGS) $(SHIPFLAGS) $(DISABLE_ASSERTIONS) -c $< -o $@) $(BUILDDIR)/%.dbg.obj: $(SRCDIR)/%.c $(HEADERS) | $(BUILDDIR) - @$(call PRINT_CC, $(CC) $(JCPPFLAGS) $(JCFLAGS) $(DEBUGFLAGS) -c $< -o $@) + @$(call PRINT_CC, $(CC) $(JCPPFLAGS) $(JCFLAGS) $(JL_CFLAGS) $(DEBUGFLAGS) -c $< -o $@) $(BUILDDIR)/%.o: $(SRCDIR)/%.cpp $(SRCDIR)/llvm-version.h $(HEADERS) $(LLVM_CONFIG_ABSOLUTE) | $(BUILDDIR) - @$(call PRINT_CC, $(CXX) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(SHIPFLAGS) $(CXX_DISABLE_ASSERTION) -c $< -o $@) + @$(call PRINT_CC, $(CXX) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(JL_CXXFLAGS) $(SHIPFLAGS) $(CXX_DISABLE_ASSERTION) -c $< -o $@) $(BUILDDIR)/%.dbg.obj: $(SRCDIR)/%.cpp $(SRCDIR)/llvm-version.h $(HEADERS) $(LLVM_CONFIG_ABSOLUTE) | $(BUILDDIR) - @$(call PRINT_CC, $(CXX) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(DEBUGFLAGS) -c $< -o $@) + @$(call PRINT_CC, $(CXX) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(JL_CXXFLAGS) $(DEBUGFLAGS) -c $< -o $@) $(BUILDDIR)/%.o : $(SRCDIR)/%.d @$(call PRINT_DTRACE, $(DTRACE) -G -s $< -o $@) $(BUILDDIR)/%.dbg.obj : $(SRCDIR)/%.d @@ -247,7 +255,7 @@ else JULIA_SPLITDEBUG := 0 endif $(build_shlibdir)/libccalltest.$(SHLIB_EXT): $(SRCDIR)/ccalltest.c - @$(call PRINT_CC, $(CC) $(JCFLAGS) $(JCPPFLAGS) $(FLAGS) -O3 $< $(fPIC) -shared -o $@.tmp $(LDFLAGS)) + @$(call PRINT_CC, $(CC) $(JCFLAGS) $(JL_CFLAGS) $(JCPPFLAGS) $(FLAGS) -O3 $< $(fPIC) -shared -o $@.tmp $(LDFLAGS)) $(INSTALL_NAME_CMD)libccalltest.$(SHLIB_EXT) $@.tmp ifeq ($(JULIA_SPLITDEBUG),1) @# Create split debug info file for libccalltest stacktraces test @@ -259,7 +267,7 @@ endif @## clang should have made the dSYM split-debug directory, @## but we are intentionally not going to give it the correct name @## because we want to test the non-default debug configuration - @#rm -r $@.dSYM && mv $@.tmp.dSYM $@.dSYM + @#rm -rf $@.dSYM && mv $@.tmp.dSYM $@.dSYM mv $@.tmp $@ $(INSTALL_NAME_CMD)libccalltest.$(SHLIB_EXT) $@ @@ -279,37 +287,44 @@ $(BUILDDIR)/julia_flisp.boot: $(addprefix $(SRCDIR)/,jlfrontend.scm flisp/aliase # additional dependency links $(BUILDDIR)/codegen-stubs.o $(BUILDDIR)/codegen-stubs.dbg.obj: $(SRCDIR)/intrinsics.h -$(BUILDDIR)/aotcompile.o $(BUILDDIR)/aotcompile.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/codegen_shared.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)/codegen.o $(BUILDDIR)/codegen.dbg.obj: $(addprefix $(SRCDIR)/,\ - intrinsics.cpp jitlayers.h intrinsics.h codegen_shared.h cgutils.cpp ccall.cpp abi_*.cpp processor.h builtin_proto.h) -$(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: $(addprefix $(SRCDIR)/,debuginfo.h processor.h) + 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 +$(BUILDDIR)/debuginfo.o $(BUILDDIR)/debuginfo.dbg.obj: $(addprefix $(SRCDIR)/,debuginfo.h processor.h jitlayers.h debug-registry.h) $(BUILDDIR)/disasm.o $(BUILDDIR)/disasm.dbg.obj: $(SRCDIR)/debuginfo.h $(SRCDIR)/processor.h -$(BUILDDIR)/dump.o $(BUILDDIR)/dump.dbg.obj: $(addprefix $(SRCDIR)/,common_symbols1.inc common_symbols2.inc builtin_proto.h serialize.h) $(BUILDDIR)/gc-debug.o $(BUILDDIR)/gc-debug.dbg.obj: $(SRCDIR)/gc.h $(BUILDDIR)/gc-pages.o $(BUILDDIR)/gc-pages.dbg.obj: $(SRCDIR)/gc.h -$(BUILDDIR)/gc.o $(BUILDDIR)/gc.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-alloc-profiler.h +$(BUILDDIR)/gc.o $(BUILDDIR)/gc.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-heap-snapshot.h $(SRCDIR)/gc-alloc-profiler.h +$(BUILDDIR)/gc-heap-snapshot.o $(BUILDDIR)/gc-heap-snapshot.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-heap-snapshot.h +$(BUILDDIR)/gc-alloc-profiler.o $(BUILDDIR)/gc-alloc-profiler.dbg.obj: $(SRCDIR)/gc.h $(SRCDIR)/gc-alloc-profiler.h $(BUILDDIR)/init.o $(BUILDDIR)/init.dbg.obj: $(SRCDIR)/builtin_proto.h $(BUILDDIR)/interpreter.o $(BUILDDIR)/interpreter.dbg.obj: $(SRCDIR)/builtin_proto.h -$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/codegen_shared.h +$(BUILDDIR)/jitlayers.o $(BUILDDIR)/jitlayers.dbg.obj: $(SRCDIR)/jitlayers.h $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/jltypes.o $(BUILDDIR)/jltypes.dbg.obj: $(SRCDIR)/builtin_proto.h -$(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/codegen_shared.h $(BUILDDIR)/julia_version.h -$(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h -$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h -$(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h -$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h -$(BUILDDIR)/llvm-late-gc-lowering.o $(BUILDDIR)/llvm-late-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-lower-handlers.o $(BUILDDIR)/llvm-lower-handlers.dbg.obj: $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-multiversioning.o $(BUILDDIR)/llvm-multiversioning.dbg.obj: $(SRCDIR)/codegen_shared.h $(SRCDIR)/processor.h -$(BUILDDIR)/llvm-pass-helpers.o $(BUILDDIR)/llvm-pass-helpers.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/codegen_shared.h -$(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/codegen_shared.h +$(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/julia_version.h +$(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h +$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h +$(BUILDDIR)/llvm-cpufeatures.o $(BUILDDIR)/llvm-cpufeatures.dbg.obj: $(SRCDIR)/jitlayers.h +$(BUILDDIR)/llvm-demote-float16.o $(BUILDDIR)/llvm-demote-float16.dbg.obj: $(SRCDIR)/jitlayers.h +$(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-julia-licm.o $(BUILDDIR)/llvm-julia-licm.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-alloc-helpers.h $(SRCDIR)/llvm-pass-helpers.h +$(BUILDDIR)/llvm-late-gc-lowering.o $(BUILDDIR)/llvm-late-gc-lowering.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-lower-handlers.o $(BUILDDIR)/llvm-lower-handlers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-multiversioning.o $(BUILDDIR)/llvm-multiversioning.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/processor.h +$(BUILDDIR)/llvm-pass-helpers.o $(BUILDDIR)/llvm-pass-helpers.dbg.obj: $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-propagate-addrspaces.o $(BUILDDIR)/llvm-propagate-addrspaces.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-remove-addrspaces.o $(BUILDDIR)/llvm-remove-addrspaces.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h +$(BUILDDIR)/llvm-ptls.o $(BUILDDIR)/llvm-ptls.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(BUILDDIR)/processor.o $(BUILDDIR)/processor.dbg.obj: $(addprefix $(SRCDIR)/,processor_*.cpp processor.h features_*.h) $(BUILDDIR)/signal-handling.o $(BUILDDIR)/signal-handling.dbg.obj: $(addprefix $(SRCDIR)/,signals-*.c) -$(BUILDDIR)/staticdata.o $(BUILDDIR)/staticdata.dbg.obj: $(SRCDIR)/processor.h $(SRCDIR)/builtin_proto.h +$(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)/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) $(addprefix $(BUILDDIR)/,APInt-C.o APInt-C.dbg.obj runtime_intrinsics.o runtime_intrinsics.dbg.obj): $(SRCDIR)/APInt-C.h @@ -338,7 +353,7 @@ $(BUILDDIR)/julia_version.h: $(JULIAHOME)/VERSION @echo "#ifndef JL_VERSION_H" >> $@.$(JULIA_BUILD_MODE).tmp @echo "#define JL_VERSION_H" >> $@.$(JULIA_BUILD_MODE).tmp @echo "#define JULIA_VERSION_STRING" \"$(JULIA_VERSION)\" >> $@.$(JULIA_BUILD_MODE).tmp - @echo $(JULIA_VERSION) | awk 'BEGIN {FS="[.,-]"} \ + @echo $(JULIA_VERSION) | awk 'BEGIN {FS="[.,+-]"} \ {print "#define JULIA_VERSION_MAJOR " $$1 "\n" \ "#define JULIA_VERSION_MINOR " $$2 "\n" \ "#define JULIA_VERSION_PATCH " $$3 ; \ @@ -349,13 +364,13 @@ $(BUILDDIR)/julia_version.h: $(JULIAHOME)/VERSION CXXLD = $(CXX) -shared $(build_shlibdir)/libjulia-internal.$(JL_MAJOR_MINOR_SHLIB_EXT): $(SRCDIR)/julia.expmap $(OBJS) $(BUILDDIR)/flisp/libflisp.a $(BUILDDIR)/support/libsupport.a $(LIBUV) - @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(CXXLDFLAGS) $(SHIPFLAGS) $(OBJS) $(RPATH_LIB) -o $@ \ + @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(JL_CXXFLAGS) $(CXXLDFLAGS) $(SHIPFLAGS) $(OBJS) $(RPATH_LIB) -o $@ \ $(JLDFLAGS) $(JLIBLDFLAGS) $(RT_RELEASE_LIBS) $(call SONAME_FLAGS,libjulia-internal.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia-internal.$(SHLIB_EXT) $@ $(DSYMUTIL) $@ $(build_shlibdir)/libjulia-internal-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(SRCDIR)/julia.expmap $(DOBJS) $(BUILDDIR)/flisp/libflisp-debug.a $(BUILDDIR)/support/libsupport-debug.a $(LIBUV) - @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(CXXLDFLAGS) $(DEBUGFLAGS) $(DOBJS) $(RPATH_LIB) -o $@ \ + @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(JL_CXXFLAGS) $(CXXLDFLAGS) $(DEBUGFLAGS) $(DOBJS) $(RPATH_LIB) -o $@ \ $(JLDFLAGS) $(JLIBLDFLAGS) $(RT_DEBUG_LIBS) $(call SONAME_FLAGS,libjulia-internal-debug.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia-internal-debug.$(SHLIB_EXT) $@ $(DSYMUTIL) $@ @@ -367,6 +382,8 @@ $(build_shlibdir)/libjulia-internal.$(JL_MAJOR_SHLIB_EXT) $(build_shlibdir)/libj $(build_shlibdir)/libjulia-internal.$(SHLIB_EXT) $(build_shlibdir)/libjulia-internal-debug.$(SHLIB_EXT): $(build_shlibdir)/libjulia-internal%.$(SHLIB_EXT): \ $(build_shlibdir)/libjulia-internal%.$(JL_MAJOR_MINOR_SHLIB_EXT) @$(call PRINT_LINK, ln -sf $(notdir $<) $@) +$(build_shlibdir)/libjulia-codegen.$(JL_MAJOR_MINOR_SHLIB_EXT): $(build_shlibdir)/libjulia-internal.$(SHLIB_EXT) +$(build_shlibdir)/libjulia-codegen-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(build_shlibdir)/libjulia-internal-debug.$(SHLIB_EXT) libjulia-internal-release: $(build_shlibdir)/libjulia-internal.$(JL_MAJOR_SHLIB_EXT) $(build_shlibdir)/libjulia-internal.$(SHLIB_EXT) libjulia-internal-debug: $(build_shlibdir)/libjulia-internal-debug.$(JL_MAJOR_SHLIB_EXT) $(build_shlibdir)/libjulia-internal-debug.$(SHLIB_EXT) endif @@ -375,13 +392,13 @@ libjulia-internal-debug: $(build_shlibdir)/libjulia-internal-debug.$(JL_MAJOR_MI libjulia-internal-debug libjulia-internal-release: $(PUBLIC_HEADER_TARGETS) $(build_shlibdir)/libjulia-codegen.$(JL_MAJOR_MINOR_SHLIB_EXT): $(SRCDIR)/julia.expmap $(CODEGEN_OBJS) $(BUILDDIR)/support/libsupport.a $(build_shlibdir)/libjulia-internal.$(JL_MAJOR_MINOR_SHLIB_EXT) - @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(CXXLDFLAGS) $(SHIPFLAGS) $(CODEGEN_OBJS) $(RPATH_LIB) -o $@ \ + @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(JL_CXXFLAGS) $(CXXLDFLAGS) $(SHIPFLAGS) $(CODEGEN_OBJS) $(RPATH_LIB) -o $@ \ $(JLDFLAGS) $(JLIBLDFLAGS) $(CG_RELEASE_LIBS) $(call SONAME_FLAGS,libjulia-codegen.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia-codegen.$(SHLIB_EXT) $@ $(DSYMUTIL) $@ $(build_shlibdir)/libjulia-codegen-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(SRCDIR)/julia.expmap $(CODEGEN_DOBJS) $(BUILDDIR)/support/libsupport-debug.a $(build_shlibdir)/libjulia-internal-debug.$(JL_MAJOR_MINOR_SHLIB_EXT) - @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(CXXLDFLAGS) $(DEBUGFLAGS) $(CODEGEN_DOBJS) $(RPATH_LIB) -o $@ \ + @$(call PRINT_LINK, $(CXXLD) $(call IMPLIB_FLAGS,$@) $(JCXXFLAGS) $(JL_CXXFLAGS) $(CXXLDFLAGS) $(DEBUGFLAGS) $(CODEGEN_DOBJS) $(RPATH_LIB) -o $@ \ $(JLDFLAGS) $(JLIBLDFLAGS) $(CG_DEBUG_LIBS) $(call SONAME_FLAGS,libjulia-codegen-debug.$(JL_MAJOR_SHLIB_EXT))) @$(INSTALL_NAME_CMD)libjulia-codegen-debug.$(SHLIB_EXT) $@ $(DSYMUTIL) $@ @@ -400,6 +417,12 @@ libjulia-codegen-release: $(build_shlibdir)/libjulia-codegen.$(JL_MAJOR_MINOR_SH libjulia-codegen-debug: $(build_shlibdir)/libjulia-codegen-debug.$(JL_MAJOR_MINOR_SHLIB_EXT) libjulia-codegen-debug libjulia-codegen-release: $(PUBLIC_HEADER_TARGETS) +# set the exports for the source files based on where they are getting linked +$(OBJS): SHIPFLAGS += -DJL_LIBRARY_EXPORTS_INTERNAL +$(DOBJS): DEBUGFLAGS += -DJL_LIBRARY_EXPORTS_INTERNAL +$(CODEGEN_OBJS): SHIPFLAGS += -DJL_LIBRARY_EXPORTS_CODEGEN +$(CODEGEN_DOBJS): DEBUGFLAGS += -DJL_LIBRARY_EXPORTS_CODEGEN + clean: -rm -fr $(build_shlibdir)/libjulia-internal* $(build_shlibdir)/libjulia-codegen* $(build_shlibdir)/libccalltest* $(build_shlibdir)/libllvmcalltest* -rm -f $(BUILDDIR)/julia_flisp.boot $(BUILDDIR)/julia_flisp.boot.inc $(BUILDDIR)/jl_internal_funcs.inc @@ -423,8 +446,14 @@ $(build_shlibdir)/lib%Plugin.$(SHLIB_EXT): $(SRCDIR)/clangsa/%.cpp $(LLVM_CONFIG # before attempting this static analysis, so that all necessary headers # and dependencies are properly installed: # make -C src install-analysis-deps +ANALYSIS_DEPS := llvm clang llvm-tools libuv utf8proc +ifeq ($(OS),Darwin) +ANALYSIS_DEPS += llvmunwind +else ifneq ($(OS),WINNT) +ANALYSIS_DEPS += unwind +endif install-analysis-deps: - $(MAKE) -C $(JULIAHOME)/deps install-llvm install-clang install-llvm-tools install-libuv install-utf8proc install-unwind + $(MAKE) -C $(JULIAHOME)/deps $(addprefix install-,$(ANALYSIS_DEPS)) analyzegc-deps-check: $(BUILDDIR)/julia_version.h $(BUILDDIR)/julia_flisp.boot.inc $(BUILDDIR)/jl_internal_funcs.inc ifeq ($(USE_BINARYBUILDER_LLVM),0) @@ -435,53 +464,59 @@ endif clangsa: $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) clangsa: $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) -# TODO: clangsa: $(build_shlibdir)/libImplicitAtomics2Plugin.$(SHLIB_EXT) +# optarg is a required_argument for these +SA_EXCEPTIONS-jloptions.c := -Xanalyzer -analyzer-config -Xanalyzer silence-checkers="core.NonNullParamChecker;unix.cstring.NullArg" + # clang doesn't understand that e->vars has the same value in save_env (NULL) and restore_env (assumed non-NULL) +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 +clang-sagc-%: JL_CXXFLAGS += -UNDEBUG clang-sagc-%: $(SRCDIR)/%.c $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang -D__clang_gcanalyzer__ --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text --analyzer-no-default-checks \ -Xclang -load -Xclang $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) -Xclang -analyzer-checker=core$(COMMA)julia.GCChecker \ - $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -x c $<) + $(SA_EXCEPTIONS-$(notdir $<)) \ + $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(JL_CFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -x c $<) clang-sagc-%: $(SRCDIR)/%.cpp $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang -D__clang_gcanalyzer__ --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text --analyzer-no-default-checks \ -Xclang -load -Xclang $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) -Xclang -analyzer-checker=core$(COMMA)julia.GCChecker \ - $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -x c++ $<) - - # optarg is a required_argument for these -SA_EXCEPTIONS-jloptions.c := -Xanalyzer -analyzer-disable-checker=core.NonNullParamChecker,unix.cstring.NullArg - # clang doesn't understand that e->vars has the same value in save_env (NULL) and restore_env (assumed non-NULL) -SA_EXCEPTIONS-subtype.c := -Xanalyzer -analyzer-disable-checker=core.uninitialized.Assign,core.UndefinedBinaryOperatorResult - # these need to be annotated (and possibly fixed) -SKIP_IMPLICIT_ATOMICS := dump.c gf.c jitlayers.cpp module.c precompile.c rtutils.c staticdata.c toplevel.c codegen.cpp + $(SA_EXCEPTIONS-$(notdir $<)) \ + $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(JL_CXXFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -x c++ $<) -clang-sa-%: $(SRCDIR)/%.c $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check +clang-sa-%: JL_CXXFLAGS += -UNDEBUG +clang-sa-%: $(SRCDIR)/%.c .FORCE | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text \ - $(if $(findstring $(notdir $<),$(SKIP_IMPLICIT_ATOMICS)),,-Xclang -load -Xclang $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) -Xclang -analyzer-checker=julia.ImplicitAtomics) \ -Xanalyzer -analyzer-disable-checker=deadcode.DeadStores \ - --analyzer-no-default-checks \ $(SA_EXCEPTIONS-$(notdir $<)) \ - $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -Werror -x c $<) -clang-sa-%: $(SRCDIR)/%.cpp $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check + $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(JL_CFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -Werror -x c $<) +clang-sa-%: $(SRCDIR)/%.cpp .FORCE | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text \ - $(if $(findstring $(notdir $<),$(SKIP_IMPLICIT_ATOMICS)),,-Xclang -load -Xclang $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) -Xclang -analyzer-checker=julia.ImplicitAtomics) \ -Xanalyzer -analyzer-disable-checker=deadcode.DeadStores \ - --analyzer-no-default-checks \ $(SA_EXCEPTIONS-$(notdir $<)) \ - $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -Werror -x c++ $<) + $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(JL_CXXFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -Werror -x c++ $<) -clang-tidy-%: $(SRCDIR)/%.c $(build_shlibdir)/libImplicitAtomics2Plugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check +clang-tidy-%: $(SRCDIR)/%.c $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang-tidy $< -header-filter='.*' --quiet \ - -load $(build_shlibdir)/libImplicitAtomics2Plugin.$(SHLIB_EXT) --checks='-clang-analyzer-*$(COMMA)-clang-diagnostic-*$(COMMA)concurrency-implicit-atomics' --warnings-as-errors='*' \ - -- $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -fno-caret-diagnostics -x c) -clang-tidy-%: $(SRCDIR)/%.cpp $(build_shlibdir)/libImplicitAtomics2Plugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check + -load $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) --checks='-clang-analyzer-*$(COMMA)-clang-diagnostic-*$(COMMA)concurrency-implicit-atomics' --warnings-as-errors='*' \ + -- $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(JL_CFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics -fno-caret-diagnostics -x c) +clang-tidy-%: $(SRCDIR)/%.cpp $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) .FORCE | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang-tidy $< -header-filter='.*' --quiet \ - -load $(build_shlibdir)/libImplicitAtomics2Plugin.$(SHLIB_EXT) --checks='-clang-analyzer-*$(COMMA)-clang-diagnostic-*$(COMMA)concurrency-implicit-atomics' --warnings-as-errors='*' \ - -- $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics --system-header-prefix=llvm -Wno-deprecated-declarations -fno-caret-diagnostics -x c++) + -load $(build_shlibdir)/libImplicitAtomicsPlugin.$(SHLIB_EXT) --checks='-clang-analyzer-*$(COMMA)-clang-diagnostic-*$(COMMA)concurrency-implicit-atomics' --warnings-as-errors='*' \ + -- $(CLANGSA_FLAGS) $(CLANGSA_CXXFLAGS) $(LLVM_CXXFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(JL_CXXFLAGS) $(DEBUGFLAGS) -fcolor-diagnostics --system-header-prefix=llvm -Wno-deprecated-declarations -fno-caret-diagnostics -x c++) +# set the exports for the source files based on where they are getting linked +clang-sa-% clang-sagc-% clang-tidy-%: DEBUGFLAGS += -DJL_LIBRARY_EXPORTS # Add C files as a target of `analyzesrc` and `analyzegc` and `tidysrc` -tidysrc: $(addprefix clang-tidy-,$(filter-out $(basename $(SKIP_IMPLICIT_ATOMICS)), $(SRCS))) -analyzesrc: $(addprefix clang-sa-,$(SRCS)) -analyzegc: analyzesrc $(addprefix clang-sagc-,$(RUNTIME_SRCS)) +tidysrc: $(addprefix clang-tidy-,$(filter-out $(basename $(SKIP_IMPLICIT_ATOMICS)),$(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 clean-analyzegc: rm -f $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) diff --git a/src/abi_aarch64.cpp b/src/abi_aarch64.cpp index 1a3f160329c6c..514c3c5a81a6d 100644 --- a/src/abi_aarch64.cpp +++ b/src/abi_aarch64.cpp @@ -43,9 +43,11 @@ Type *get_llvm_vectype(jl_datatype_t *dt, LLVMContext &ctx) const // the homogeneity check. jl_datatype_t *ft0 = (jl_datatype_t*)jl_field_type(dt, 0); // `ft0` should be a `VecElement` type and the true element type - // should be a primitive type - if (ft0->name != jl_vecelement_typename || - ((jl_datatype_t*)jl_field_type(ft0, 0))->layout->nfields) + // should be a primitive type (nfields == 0) + if (!jl_is_datatype(ft0) || ft0->name != jl_vecelement_typename) + return nullptr; + jl_datatype_t *ft00 = (jl_datatype_t*)jl_field_type(ft0, 0); + if (!jl_is_datatype(ft00) || ft00->layout->nfields) return nullptr; for (size_t i = 1; i < nfields; i++) { if (jl_field_type(dt, i) != (jl_value_t*)ft0) { @@ -120,15 +122,17 @@ bool isHFAorHVA(jl_datatype_t *dt, size_t dsz, size_t &nele, ElementType &ele, L // For composite types, find the first non zero sized member size_t i; size_t fieldsz; - for (i = 0;i < nfields;i++) { + for (i = 0; i < nfields; i++) { if ((fieldsz = jl_field_size(dt, i))) { break; } } assert(i < nfields); - // If there's only one non zero sized member, try again on this member + // If there's only one non-zero sized member, try again on this member if (fieldsz == dsz) { dt = (jl_datatype_t*)jl_field_type(dt, i); + if (!jl_is_datatype(dt)) + return false; continue; } if (Type *vectype = get_llvm_vectype(dt, ctx)) { @@ -140,11 +144,13 @@ bool isHFAorHVA(jl_datatype_t *dt, size_t dsz, size_t &nele, ElementType &ele, L return true; } // Otherwise, process each members - for (;i < nfields;i++) { + for (; i < nfields; i++) { size_t fieldsz = jl_field_size(dt, i); if (fieldsz == 0) continue; jl_datatype_t *fieldtype = (jl_datatype_t*)jl_field_type(dt, i); + if (!jl_is_datatype(dt)) + return false; // Check element count. // This needs to be done after the zero size member check if (nele > 3 || !isHFAorHVA(fieldtype, fieldsz, nele, ele, ctx)) { diff --git a/src/abi_arm.cpp b/src/abi_arm.cpp index 4987d07657ae6..441aa95b1fdf6 100644 --- a/src/abi_arm.cpp +++ b/src/abi_arm.cpp @@ -91,6 +91,8 @@ size_t isLegalHA(jl_datatype_t *dt, Type *&base, LLVMContext &ctx) const size_t parent_members = jl_datatype_nfields(dt); for (size_t i = 0; i < parent_members; ++i) { jl_datatype_t *fdt = (jl_datatype_t*)jl_field_type(dt,i); + if (!jl_is_datatype(fdt)) + return 0; Type *T = isLegalHAType(fdt, ctx); if (T) diff --git a/src/abi_ppc64le.cpp b/src/abi_ppc64le.cpp index 016eebd455525..2e18acdbd4f4b 100644 --- a/src/abi_ppc64le.cpp +++ b/src/abi_ppc64le.cpp @@ -44,6 +44,9 @@ 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) + return 9; + size_t i, l = ty->layout->nfields; // handle homogeneous float aggregates if (l == 0) { @@ -52,7 +55,7 @@ unsigned isHFA(jl_datatype_t *ty, jl_datatype_t **ty0, bool *hva) const *hva = false; if (*ty0 == NULL) *ty0 = ty; - else if (*hva || ty->size != (*ty0)->size) + else if (*hva || jl_datatype_size(ty) != jl_datatype_size(*ty0)) return 9; return 1; } @@ -69,7 +72,7 @@ unsigned isHFA(jl_datatype_t *ty, jl_datatype_t **ty0, bool *hva) const *hva = true; if (*ty0 == NULL) *ty0 = ty; - else if (!*hva || ty->size != (*ty0)->size) + else if (!*hva || jl_datatype_size(ty) != jl_datatype_size(*ty0)) return 9; for (i = 1; i < l; i++) { jl_datatype_t *fld = (jl_datatype_t*)jl_field_type(ty, i); diff --git a/src/abi_x86_64.cpp b/src/abi_x86_64.cpp index 2a06ee6be36a6..c3d12417e6de8 100644 --- a/src/abi_x86_64.cpp +++ b/src/abi_x86_64.cpp @@ -153,6 +153,10 @@ void classifyType(Classification& accum, jl_datatype_t *dt, uint64_t offset) con jl_value_t *ty = jl_field_type(dt, i); if (jl_field_isptr(dt, i)) ty = (jl_value_t*)jl_voidpointer_type; + else if (!jl_is_datatype(ty)) { // inline union + accum.addField(offset, Memory); + continue; + } classifyType(accum, (jl_datatype_t*)ty, offset + jl_field_offset(dt, i)); } } @@ -202,7 +206,6 @@ bool needPassByRef(jl_datatype_t *dt, AttrBuilder &ab, LLVMContext &ctx, Type *T else if (jl_is_structtype(dt)) { // spill to memory even though we would ordinarily pass // it in registers - Type* Ty = preferred_llvm_type(dt, false, ctx); ab.addByValAttr(Ty); return true; } diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 1c5ccbebcb0a7..cf6378b4f926b 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -5,14 +5,11 @@ // target support #include +#include #include #include #include -#if JL_LLVM_VERSION >= 140000 #include -#else -#include -#endif #include // analysis passes @@ -20,17 +17,24 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include +#include #include +#include +#include +#include #if defined(USE_POLLY) #include #include @@ -43,50 +47,52 @@ // for outputting code #include #include +#include #include "llvm/Object/ArchiveWriter.h" #include #include #include +#include +#include using namespace llvm; -// our passes -namespace llvm { - extern Pass *createLowerSimdLoopPass(); -} - -#include "julia.h" -#include "julia_internal.h" #include "jitlayers.h" +#include "serialize.h" #include "julia_assert.h" +#include "llvm-codegen-shared.h" +#include "processor.h" + +#define DEBUG_TYPE "julia_aotcompile" -template // for GlobalObject's -static T *addComdat(T *G) +STATISTIC(CICacheLookups, "Number of codeinst cache lookups"); +STATISTIC(CreateNativeCalls, "Number of jl_create_native calls made"); +STATISTIC(CreateNativeMethods, "Number of methods compiled for jl_create_native"); +STATISTIC(CreateNativeMax, "Max number of methods compiled at once for jl_create_native"); +STATISTIC(CreateNativeGlobals, "Number of globals compiled for jl_create_native"); + +static void addComdat(GlobalValue *G, Triple &T) { -#if defined(_OS_WINDOWS_) - if (!G->isDeclaration()) { + if (T.isOSBinFormatCOFF() && !G->isDeclaration()) { // add __declspec(dllexport) to everything marked for export - if (G->getLinkage() == GlobalValue::ExternalLinkage) - G->setDLLStorageClass(GlobalValue::DLLExportStorageClass); - else - G->setDLLStorageClass(GlobalValue::DefaultStorageClass); + assert(G->hasExternalLinkage() && "Cannot set DLLExport on non-external linkage!"); + G->setDLLStorageClass(GlobalValue::DLLExportStorageClass); } -#endif - return G; } typedef struct { - std::unique_ptr M; + orc::ThreadSafeModule M; std::vector jl_sysimg_fvars; std::vector jl_sysimg_gvars; std::map> jl_fvar_map; - std::map jl_value_to_llvm; // uses 1-based indexing + std::vector jl_value_to_llvm; + std::vector jl_external_to_llvm; } jl_native_code_desc_t; -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN void jl_get_function_id_impl(void *native_code, jl_code_instance_t *codeinst, int32_t *func_idx, int32_t *specfunc_idx) { @@ -100,46 +106,40 @@ void jl_get_function_id_impl(void *native_code, jl_code_instance_t *codeinst, } } -extern "C" JL_DLLEXPORT -int32_t jl_get_llvm_gv_impl(void *native_code, jl_value_t *p) +extern "C" JL_DLLEXPORT_CODEGEN +void jl_get_llvm_gvs_impl(void *native_code, arraylist_t *gvs) { - // map a jl_value_t memory location to a GlobalVariable + // map a memory location (jl_value_t or jl_binding_t) to a GlobalVariable jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; - if (data) { - auto it = data->jl_value_to_llvm.find(p); - if (it != data->jl_value_to_llvm.end()) { - return it->second; - } - } - return 0; + arraylist_grow(gvs, data->jl_value_to_llvm.size()); + memcpy(gvs->items, data->jl_value_to_llvm.data(), gvs->len * sizeof(void*)); } -extern "C" JL_DLLEXPORT -Module* jl_get_llvm_module_impl(void *native_code) +extern "C" JL_DLLEXPORT_CODEGEN +void jl_get_llvm_external_fns_impl(void *native_code, arraylist_t *external_fns) { jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; - if (data) - return data->M.get(); - else - return NULL; + arraylist_grow(external_fns, data->jl_external_to_llvm.size()); + memcpy(external_fns->items, data->jl_external_to_llvm.data(), + external_fns->len * sizeof(jl_code_instance_t*)); } -extern "C" JL_DLLEXPORT -GlobalValue* jl_get_llvm_function_impl(void *native_code, uint32_t idx) +extern "C" JL_DLLEXPORT_CODEGEN +LLVMOrcThreadSafeModuleRef jl_get_llvm_module_impl(void *native_code) { jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; if (data) - return data->jl_sysimg_fvars[idx]; + return wrap(&data->M); else return NULL; } -extern "C" JL_DLLEXPORT -LLVMContext* jl_get_llvm_context_impl(void *native_code) +extern "C" JL_DLLEXPORT_CODEGEN +GlobalValue* jl_get_llvm_function_impl(void *native_code, uint32_t idx) { jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; if (data) - return &data->M->getContext(); + return data->jl_sysimg_fvars[idx]; else return NULL; } @@ -149,7 +149,6 @@ static void emit_offset_table(Module &mod, const std::vector &vars { // Emit a global variable with all the variable addresses. // The cloning pass will convert them into offsets. - assert(!vars.empty()); size_t nvars = vars.size(); std::vector addrs(nvars); for (size_t i = 0; i < nvars; i++) { @@ -157,10 +156,12 @@ static void emit_offset_table(Module &mod, const std::vector &vars addrs[i] = ConstantExpr::getBitCast(var, T_psize); } ArrayType *vars_type = ArrayType::get(T_psize, nvars); - new GlobalVariable(mod, vars_type, true, + auto GV = new GlobalVariable(mod, vars_type, true, GlobalVariable::ExternalLinkage, ConstantArray::get(vars_type, addrs), name); + GV->setVisibility(GlobalValue::HiddenVisibility); + GV->setDSOLocal(true); } static bool is_safe_char(unsigned char c) @@ -224,28 +225,31 @@ static void makeSafeName(GlobalObject &G) 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) { + ++CICacheLookups; jl_value_t *ci = cgparams.lookup(mi, world, world); JL_GC_PROMISE_ROOTED(ci); jl_code_instance_t *codeinst = NULL; if (ci != jl_nothing) { codeinst = (jl_code_instance_t*)ci; - *src_out = (jl_code_info_t*)codeinst->inferred; + *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_array_t*)*src_out); + *src_out = jl_uncompress_ir(def, codeinst, (jl_value_t*)*src_out); } if (*src_out == NULL || !jl_is_code_info(*src_out)) { - if (cgparams.lookup != jl_rettype_inferred) { + 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 && !codeinst->inferred) - codeinst->inferred = jl_nothing; + if ((*src_out)->inferred) { + jl_value_t *null = nullptr; + jl_atomic_cmpswap_relaxed(&codeinst->inferred, &null, jl_nothing); + } } } } @@ -255,33 +259,54 @@ static void jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance // takes the running content that has collected in the shadow module and dump it to disk // this builds the object file portion of the sysimage files for fast startup, and can // also be used be extern consumers like GPUCompiler.jl to obtain a module containing -// all reachable & inferrrable functions. The `policy` flag switches between the default -// mode `0`, the extern mode `1`, and imaging mode `2`. -extern "C" JL_DLLEXPORT -void *jl_create_native_impl(jl_array_t *methods, const jl_cgparams_t *cgparams, int _policy) +// all reachable & inferrrable functions. +// The `policy` flag switches between the default mode `0` and the extern mode `1` used by GPUCompiler. +// `_imaging_mode` controls if raw pointers can be embedded (e.g. the code will be loaded into the same session). +// `_external_linkage` create linkages between pkgimages. +extern "C" JL_DLLEXPORT_CODEGEN +void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage, size_t _world) { + JL_TIMING(NATIVE_AOT, NATIVE_Create); + ++CreateNativeCalls; + CreateNativeMax.updateMax(jl_array_len(methods)); if (cgparams == NULL) cgparams = &jl_default_cgparams; jl_native_code_desc_t *data = new jl_native_code_desc_t; - jl_codegen_params_t params; - params.params = cgparams; - std::map emitted; + CompilationPolicy policy = (CompilationPolicy) _policy; + bool imaging = imaging_default() || _imaging_mode == 1; + jl_workqueue_t emitted; jl_method_instance_t *mi = NULL; jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); - JL_LOCK(&jl_codegen_lock); + auto ct = jl_current_task; + bool timed = (ct->reentrant_timing & 1) == 0; + if (timed) + ct->reentrant_timing |= 1; + orc::ThreadSafeContext ctx; + orc::ThreadSafeModule backing; + if (!llvmmod) { + ctx = jl_ExecutionEngine->acquireContext(); + backing = jl_create_ts_module("text", ctx, imaging); + } + orc::ThreadSafeModule &clone = llvmmod ? *unwrap(llvmmod) : backing; + auto ctxt = clone.getContext(); + uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); - CompilationPolicy policy = (CompilationPolicy) _policy; - if (policy == CompilationPolicy::ImagingMode) - imaging_mode = 1; - std::unique_ptr clone(jl_create_llvm_module("text")); - // compile all methods for the current world and type-inference world - size_t compile_for[] = { jl_typeinf_world, jl_atomic_load_acquire(&jl_world_counter) }; + + JL_LOCK(&jl_codegen_lock); + auto target_info = clone.withModuleDo([&](Module &M) { + return std::make_pair(M.getDataLayout(), Triple(M.getTargetTriple())); + }); + jl_codegen_params_t params(ctxt, std::move(target_info.first), std::move(target_info.second)); + params.params = cgparams; + params.imaging = imaging; + params.external_linkage = _external_linkage; + size_t compile_for[] = { jl_typeinf_world, _world }; for (int worlds = 0; worlds < 2; worlds++) { params.world = compile_for[worlds]; if (!params.world) @@ -296,7 +321,7 @@ void *jl_create_native_impl(jl_array_t *methods, const jl_cgparams_t *cgparams, jl_value_t *item = jl_array_ptr_ref(methods, i); if (jl_is_simplevector(item)) { if (worlds == 1) - jl_compile_extern_c(clone.get(), ¶ms, NULL, jl_svecref(item, 0), jl_svecref(item, 1)); + jl_compile_extern_c(wrap(&clone), ¶ms, NULL, jl_svecref(item, 0), jl_svecref(item, 1)); continue; } mi = (jl_method_instance_t*)item; @@ -311,30 +336,62 @@ void *jl_create_native_impl(jl_array_t *methods, const jl_cgparams_t *cgparams, if (src && !emitted.count(codeinst)) { // now add it to our compilation results JL_GC_PROMISE_ROOTED(codeinst->rettype); - jl_compile_result_t result = jl_emit_code(mi, src, codeinst->rettype, params); - if (std::get<0>(result)) - emitted[codeinst] = std::move(result); + orc::ThreadSafeModule result_m = jl_create_ts_module(name_from_method_instance(codeinst->def), + params.tsctx, params.imaging, + clone.getModuleUnlocked()->getDataLayout(), + Triple(clone.getModuleUnlocked()->getTargetTriple())); + jl_llvm_functions_t decls = jl_emit_code(result_m, mi, src, codeinst->rettype, params); + if (result_m) + emitted[codeinst] = {std::move(result_m), std::move(decls)}; } } } // finally, make sure all referenced methods also get compiled or fixed up - jl_compile_workqueue(emitted, params, policy); + jl_compile_workqueue(emitted, *clone.getModuleUnlocked(), params, policy); } + JL_UNLOCK(&jl_codegen_lock); // Might GC JL_GC_POP(); // process the globals array, before jl_merge_module destroys them - std::vector gvars; + std::vector gvars(params.globals.size()); + data->jl_value_to_llvm.resize(params.globals.size()); + StringSet<> gvars_names; + DenseSet gvars_set; + size_t idx = 0; for (auto &global : params.globals) { - gvars.push_back(std::string(global.second->getName())); - data->jl_value_to_llvm[global.first] = gvars.size(); + gvars[idx] = global.second->getName().str(); + assert(gvars_set.insert(global.second).second && "Duplicate gvar in params!"); + assert(gvars_names.insert(gvars[idx]).second && "Duplicate gvar name in params!"); + data->jl_value_to_llvm[idx] = global.first; + idx++; + } + CreateNativeMethods += emitted.size(); + + size_t offset = gvars.size(); + data->jl_external_to_llvm.resize(params.external_fns.size()); + + for (auto &extern_fn : params.external_fns) { + jl_code_instance_t *this_code = std::get<0>(extern_fn.first); + bool specsig = std::get<1>(extern_fn.first); + assert(specsig && "Error external_fns doesn't handle non-specsig yet"); + (void) specsig; + GlobalVariable *F = extern_fn.second; + size_t idx = gvars.size() - offset; + assert(idx >= 0); + assert(idx < data->jl_external_to_llvm.size()); + data->jl_external_to_llvm[idx] = this_code; + assert(gvars_set.insert(F).second && "Duplicate gvar in params!"); + assert(gvars_names.insert(F->getName()).second && "Duplicate gvar name in params!"); + gvars.push_back(std::string(F->getName())); } // clones the contents of the module `m` to the shadow_output collector // while examining and recording what kind of function pointer we have + Linker L(*clone.getModuleUnlocked()); for (auto &def : emitted) { - jl_merge_module(clone.get(), std::move(std::get<0>(def.second))); + jl_merge_module(clone, std::move(std::get<0>(def.second))); jl_code_instance_t *this_code = def.first; jl_llvm_functions_t decls = std::get<1>(def.second); StringRef func = decls.functionObject; @@ -348,75 +405,81 @@ void *jl_create_native_impl(jl_array_t *methods, const jl_cgparams_t *cgparams, func_id = -2; } else { - data->jl_sysimg_fvars.push_back(cast(clone->getNamedValue(func))); + //Safe b/c context is locked by params + data->jl_sysimg_fvars.push_back(cast(clone.getModuleUnlocked()->getNamedValue(func))); func_id = data->jl_sysimg_fvars.size(); } if (!cfunc.empty()) { - data->jl_sysimg_fvars.push_back(cast(clone->getNamedValue(cfunc))); + //Safe b/c context is locked by params + data->jl_sysimg_fvars.push_back(cast(clone.getModuleUnlocked()->getNamedValue(cfunc))); cfunc_id = data->jl_sysimg_fvars.size(); } data->jl_fvar_map[this_code] = std::make_tuple(func_id, cfunc_id); } if (params._shared_module) { - std::unique_ptr shared(params._shared_module); - params._shared_module = NULL; - jl_merge_module(clone.get(), std::move(shared)); + bool error = L.linkInModule(std::move(params._shared_module)); + assert(!error && "Error linking in shared module"); + (void)error; } // now get references to the globals in the merged module // and set them to be internalized and initialized at startup for (auto &global : gvars) { - GlobalVariable *G = cast(clone->getNamedValue(global)); + //Safe b/c context is locked by params + GlobalVariable *G = cast(clone.getModuleUnlocked()->getNamedValue(global)); G->setInitializer(ConstantPointerNull::get(cast(G->getValueType()))); - G->setLinkage(GlobalVariable::InternalLinkage); + G->setLinkage(GlobalValue::ExternalLinkage); + G->setVisibility(GlobalValue::HiddenVisibility); + G->setDSOLocal(true); data->jl_sysimg_gvars.push_back(G); } - -#if defined(_OS_WINDOWS_) && defined(_CPU_X86_64_) - // setting the function personality enables stack unwinding and catching exceptions - // so make sure everything has something set - Type *T_int32 = Type::getInt32Ty(clone->getContext()); - Function *juliapersonality_func = - Function::Create(FunctionType::get(T_int32, true), - Function::ExternalLinkage, "__julia_personality", clone.get()); - juliapersonality_func->setDLLStorageClass(GlobalValue::DLLImportStorageClass); -#endif + CreateNativeGlobals += gvars.size(); + + //Safe b/c context is locked by params + auto TT = Triple(clone.getModuleUnlocked()->getTargetTriple()); + Function *juliapersonality_func = nullptr; + if (TT.isOSWindows() && TT.getArch() == Triple::x86_64) { + // setting the function personality enables stack unwinding and catching exceptions + // so make sure everything has something set + Type *T_int32 = Type::getInt32Ty(clone.getModuleUnlocked()->getContext()); + juliapersonality_func = Function::Create(FunctionType::get(T_int32, true), + Function::ExternalLinkage, "__julia_personality", clone.getModuleUnlocked()); + juliapersonality_func->setDLLStorageClass(GlobalValue::DLLImportStorageClass); + } // move everything inside, now that we've merged everything // (before adding the exported headers) if (policy == CompilationPolicy::Default) { - for (GlobalObject &G : clone->global_objects()) { + //Safe b/c context is locked by params + for (GlobalObject &G : clone.getModuleUnlocked()->global_objects()) { if (!G.isDeclaration()) { - G.setLinkage(Function::InternalLinkage); + G.setLinkage(GlobalValue::ExternalLinkage); + G.setVisibility(GlobalValue::HiddenVisibility); + G.setDSOLocal(true); makeSafeName(G); - addComdat(&G); -#if defined(_OS_WINDOWS_) && defined(_CPU_X86_64_) - // Add unwind exception personalities to functions to handle async exceptions - if (Function *F = dyn_cast(&G)) - F->setPersonalityFn(juliapersonality_func); -#endif + if (TT.isOSWindows() && TT.getArch() == Triple::x86_64) { + // Add unwind exception personalities to functions to handle async exceptions + if (Function *F = dyn_cast(&G)) + F->setPersonalityFn(juliapersonality_func); + } } } } data->M = std::move(clone); - if (measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); - if (policy == CompilationPolicy::ImagingMode) - imaging_mode = 0; - JL_UNLOCK(&jl_codegen_lock); // Might GC + if (timed) { + if (measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } + ct->reentrant_timing &= ~1ull; + } + if (ctx.getContext()) { + jl_ExecutionEngine->releaseContext(std::move(ctx)); + } return (void*)data; } - -static void emit_result(std::vector &Archive, SmallVectorImpl &OS, - StringRef Name, std::vector &outputs) -{ - outputs.push_back({ OS.data(), OS.size() }); - Archive.push_back(NewArchiveMember(MemoryBufferRef(outputs.back(), Name))); - OS.clear(); -} - static object::Archive::Kind getDefaultForHost(Triple &triple) { if (triple.isOSDarwin()) @@ -431,147 +494,1207 @@ static void reportWriterError(const ErrorInfoBase &E) jl_safe_printf("ERROR: failed to emit output file %s\n", err.c_str()); } +#if JULIA_FLOAT16_ABI == 1 +static void injectCRTAlias(Module &M, StringRef name, StringRef alias, FunctionType *FT) +{ + Function *target = M.getFunction(alias); + if (!target) { + target = Function::Create(FT, Function::ExternalLinkage, alias, M); + } + Function *interposer = Function::Create(FT, Function::InternalLinkage, name, M); + appendToCompilerUsed(M, {interposer}); + + llvm::IRBuilder<> builder(BasicBlock::Create(M.getContext(), "top", interposer)); + SmallVector CallArgs; + for (auto &arg : interposer->args()) + CallArgs.push_back(&arg); + auto val = builder.CreateCall(target, CallArgs); + builder.CreateRet(val); +} +#endif +void multiversioning_preannotate(Module &M); + +// See src/processor.h for documentation about this table. Corresponds to jl_image_shard_t. +static GlobalVariable *emit_shard_table(Module &M, Type *T_size, Type *T_psize, unsigned threads) { + SmallVector tables(sizeof(jl_image_shard_t) / sizeof(void *) * threads); + for (unsigned i = 0; i < threads; i++) { + auto suffix = "_" + std::to_string(i); + auto create_gv = [&](StringRef name, bool constant) { + auto gv = new GlobalVariable(M, T_size, constant, + GlobalValue::ExternalLinkage, nullptr, name + suffix); + gv->setVisibility(GlobalValue::HiddenVisibility); + gv->setDSOLocal(true); + 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_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_idxs) / sizeof(void*)] = create_gv("jl_clone_idxs", true); + } + auto tables_arr = ConstantArray::get(ArrayType::get(T_psize, tables.size()), tables); + auto tables_gv = new GlobalVariable(M, tables_arr->getType(), false, + GlobalValue::ExternalLinkage, tables_arr, "jl_shard_tables"); + tables_gv->setVisibility(GlobalValue::HiddenVisibility); + tables_gv->setDSOLocal(true); + return tables_gv; +} + +// See src/processor.h for documentation about this table. Corresponds to jl_image_ptls_t. +static GlobalVariable *emit_ptls_table(Module &M, Type *T_size, Type *T_psize) { + std::array ptls_table{ + new GlobalVariable(M, T_size, false, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), "jl_pgcstack_func_slot"), + new GlobalVariable(M, T_size, false, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), "jl_pgcstack_key_slot"), + new GlobalVariable(M, T_size, false, GlobalValue::ExternalLinkage, Constant::getNullValue(T_size), "jl_tls_offset"), + }; + for (auto &gv : ptls_table) { + cast(gv)->setVisibility(GlobalValue::HiddenVisibility); + cast(gv)->setDSOLocal(true); + } + auto ptls_table_arr = ConstantArray::get(ArrayType::get(T_psize, ptls_table.size()), ptls_table); + auto ptls_table_gv = new GlobalVariable(M, ptls_table_arr->getType(), false, + GlobalValue::ExternalLinkage, ptls_table_arr, "jl_ptls_table"); + ptls_table_gv->setVisibility(GlobalValue::HiddenVisibility); + ptls_table_gv->setDSOLocal(true); + return ptls_table_gv; +} + +// See src/processor.h for documentation about this table. Corresponds to jl_image_header_t. +static GlobalVariable *emit_image_header(Module &M, unsigned threads, unsigned nfvars, unsigned ngvars) { + constexpr uint32_t version = 1; + std::array header{ + version, + threads, + nfvars, + ngvars, + }; + auto header_arr = ConstantDataArray::get(M.getContext(), header); + auto header_gv = new GlobalVariable(M, header_arr->getType(), false, + GlobalValue::InternalLinkage, header_arr, "jl_image_header"); + return header_gv; +} + +// Grab fvars and gvars data from the module +static void get_fvars_gvars(Module &M, DenseMap &fvars, DenseMap &gvars) { + auto fvars_gv = M.getGlobalVariable("jl_fvars"); + auto gvars_gv = M.getGlobalVariable("jl_gvars"); + auto fvars_idxs = M.getGlobalVariable("jl_fvar_idxs"); + auto gvars_idxs = M.getGlobalVariable("jl_gvar_idxs"); + assert(fvars_gv); + assert(gvars_gv); + assert(fvars_idxs); + assert(gvars_idxs); + auto fvars_init = cast(fvars_gv->getInitializer()); + auto gvars_init = cast(gvars_gv->getInitializer()); + for (unsigned i = 0; i < fvars_init->getNumOperands(); ++i) { + auto gv = cast(fvars_init->getOperand(i)->stripPointerCasts()); + assert(gv && gv->hasName() && "fvar must be a named global"); + assert(!fvars.count(gv) && "Duplicate fvar"); + fvars[gv] = i; + } + assert(fvars.size() == fvars_init->getNumOperands()); + for (unsigned i = 0; i < gvars_init->getNumOperands(); ++i) { + auto gv = cast(gvars_init->getOperand(i)->stripPointerCasts()); + assert(gv && gv->hasName() && "gvar must be a named global"); + assert(!gvars.count(gv) && "Duplicate gvar"); + gvars[gv] = i; + } + assert(gvars.size() == gvars_init->getNumOperands()); + fvars_gv->eraseFromParent(); + gvars_gv->eraseFromParent(); + fvars_idxs->eraseFromParent(); + gvars_idxs->eraseFromParent(); +} + +// Weight computation +// It is important for multithreaded image building to be able to split work up +// among the threads equally. The weight calculated here is an estimation of +// how expensive a particular function is going to be to compile. + +struct FunctionInfo { + size_t weight; + size_t bbs; + size_t insts; + size_t clones; +}; + +static FunctionInfo getFunctionWeight(const Function &F) +{ + FunctionInfo info; + info.weight = 1; + info.bbs = F.size(); + info.insts = 0; + info.clones = 1; + for (const BasicBlock &BB : F) { + info.insts += BB.size(); + } + if (F.hasFnAttribute("julia.mv.clones")) { + auto val = F.getFnAttribute("julia.mv.clones").getValueAsString(); + // base16, so must be at most 4 * length bits long + // popcount gives number of clones + info.clones = APInt(val.size() * 4, val, 16).countPopulation() + 1; + } + info.weight += info.insts; + // more basic blocks = more complex than just sum of insts, + // add some weight to it + info.weight += info.bbs; + info.weight *= info.clones; + return info; +} + +struct ModuleInfo { + size_t globals; + size_t funcs; + size_t bbs; + size_t insts; + size_t clones; + size_t weight; +}; + +ModuleInfo compute_module_info(Module &M) { + ModuleInfo info; + info.globals = 0; + info.funcs = 0; + info.bbs = 0; + info.insts = 0; + info.clones = 0; + info.weight = 0; + for (auto &G : M.global_values()) { + if (G.isDeclaration()) { + continue; + } + info.globals++; + if (auto F = dyn_cast(&G)) { + info.funcs++; + auto func_info = getFunctionWeight(*F); + info.bbs += func_info.bbs; + info.insts += func_info.insts; + info.clones += func_info.clones; + info.weight += func_info.weight; + } else { + info.weight += 1; + } + } + return info; +} + +struct Partition { + StringSet<> globals; + StringMap fvars; + StringMap gvars; + size_t weight; +}; + +static inline bool verify_partitioning(const SmallVectorImpl &partitions, const Module &M, size_t fvars_size, size_t gvars_size) { + bool bad = false; +#ifndef JL_NDEBUG + SmallVector fvars(fvars_size); + SmallVector gvars(gvars_size); + StringMap GVNames; + for (uint32_t i = 0; i < partitions.size(); i++) { + for (auto &name : partitions[i].globals) { + if (GVNames.count(name.getKey())) { + bad = true; + dbgs() << "Duplicate global name " << name.getKey() << " in partitions " << i << " and " << GVNames[name.getKey()] << "\n"; + } + GVNames[name.getKey()] = i; + } + for (auto &fvar : partitions[i].fvars) { + if (fvars[fvar.second] != 0) { + bad = true; + dbgs() << "Duplicate fvar " << fvar.first() << " in partitions " << i << " and " << fvars[fvar.second] - 1 << "\n"; + } + fvars[fvar.second] = i+1; + } + for (auto &gvar : partitions[i].gvars) { + if (gvars[gvar.second] != 0) { + bad = true; + dbgs() << "Duplicate gvar " << gvar.first() << " in partitions " << i << " and " << gvars[gvar.second] - 1 << "\n"; + } + gvars[gvar.second] = i+1; + } + } + for (auto &GV : M.globals()) { + if (GV.isDeclaration()) { + if (GVNames.count(GV.getName())) { + bad = true; + dbgs() << "Global " << GV.getName() << " is a declaration but is in partition " << GVNames[GV.getName()] << "\n"; + } + } else { + if (!GVNames.count(GV.getName())) { + bad = true; + dbgs() << "Global " << GV << " not in any partition\n"; + } + if (!GV.hasExternalLinkage()) { + bad = true; + dbgs() << "Global " << GV << " has non-external linkage " << GV.getLinkage() << " but is in partition " << GVNames[GV.getName()] << "\n"; + } + } + } + for (uint32_t i = 0; i < fvars_size; i++) { + if (fvars[i] == 0) { + bad = true; + dbgs() << "fvar " << i << " not in any partition\n"; + } + } + for (uint32_t i = 0; i < gvars_size; i++) { + if (gvars[i] == 0) { + bad = true; + dbgs() << "gvar " << i << " not in any partition\n"; + } + } +#endif + return !bad; +} + +// Chop a module up as equally as possible by weight into threads partitions +static SmallVector partitionModule(Module &M, unsigned threads) { + //Start by stripping fvars and gvars, which helpfully removes their uses as well + DenseMap fvars, gvars; + get_fvars_gvars(M, fvars, gvars); + + // Partition by union-find, since we only have def->use traversal right now + struct Partitioner { + struct Node { + GlobalValue *GV; + unsigned parent; + unsigned size; + size_t weight; + }; + std::vector nodes; + DenseMap node_map; + unsigned merged; + + unsigned make(GlobalValue *GV, size_t weight) { + unsigned idx = nodes.size(); + nodes.push_back({GV, idx, 1, weight}); + node_map[GV] = idx; + return idx; + } + + unsigned find(unsigned idx) { + while (nodes[idx].parent != idx) { + nodes[idx].parent = nodes[nodes[idx].parent].parent; + idx = nodes[idx].parent; + } + return idx; + } + + unsigned merge(unsigned x, unsigned y) { + x = find(x); + y = find(y); + if (x == y) + return x; + if (nodes[x].size < nodes[y].size) + std::swap(x, y); + nodes[y].parent = x; + nodes[x].size += nodes[y].size; + nodes[x].weight += nodes[y].weight; + merged++; + return x; + } + }; + + Partitioner partitioner; + + for (auto &G : M.global_values()) { + if (G.isDeclaration()) + continue; + if (isa(G)) { + partitioner.make(&G, getFunctionWeight(cast(G)).weight); + } else { + partitioner.make(&G, 1); + } + } + + // Merge all uses to go together into the same partition + for (unsigned i = 0; i < partitioner.nodes.size(); ++i) { + for (ConstantUses uses(partitioner.nodes[i].GV, M); !uses.done(); uses.next()) { + auto val = uses.get_info().val; + auto idx = partitioner.node_map.find(val); + assert(idx != partitioner.node_map.end()); + partitioner.merge(i, idx->second); + } + } + + SmallVector partitions(threads); + // always get the smallest partition first + auto pcomp = [](const Partition *p1, const Partition *p2) { + return p1->weight > p2->weight; + }; + std::priority_queue, decltype(pcomp)> pq(pcomp); + for (unsigned i = 0; i < threads; ++i) { + pq.push(&partitions[i]); + } + + std::vector idxs(partitioner.nodes.size()); + std::iota(idxs.begin(), idxs.end(), 0); + std::sort(idxs.begin(), idxs.end(), [&](unsigned a, unsigned b) { + //because roots have more weight than their children, + //we can sort by weight and get the roots first + return partitioner.nodes[a].weight > partitioner.nodes[b].weight; + }); + + // Assign the root of each partition to a partition, then assign its children to the same one + for (unsigned idx = 0; idx < idxs.size(); ++idx) { + auto i = idxs[idx]; + auto root = partitioner.find(i); + assert(root == i || partitioner.nodes[root].GV == nullptr); + if (partitioner.nodes[root].GV) { + auto &node = partitioner.nodes[root]; + auto &P = *pq.top(); + pq.pop(); + auto name = node.GV->getName(); + P.globals.insert(name); + if (fvars.count(node.GV)) + P.fvars[name] = fvars[node.GV]; + if (gvars.count(node.GV)) + P.gvars[name] = gvars[node.GV]; + P.weight += node.weight; + node.GV = nullptr; + node.size = &P - partitions.data(); + pq.push(&P); + } + if (root != i) { + auto &node = partitioner.nodes[i]; + assert(node.GV != nullptr); + // we assigned its root already, so just add it to the root's partition + // don't touch the priority queue, since we're not changing the weight + auto &P = partitions[partitioner.nodes[root].size]; + auto name = node.GV->getName(); + P.globals.insert(name); + if (fvars.count(node.GV)) + P.fvars[name] = fvars[node.GV]; + if (gvars.count(node.GV)) + P.gvars[name] = gvars[node.GV]; + node.GV = nullptr; + node.size = partitioner.nodes[root].size; + } + } + + bool verified = verify_partitioning(partitions, M, fvars.size(), gvars.size()); + assert(verified && "Partitioning failed to partition globals correctly"); + (void) verified; + + return partitions; +} + +struct ImageTimer { + uint64_t elapsed = 0; + std::string name; + std::string desc; + + void startTimer() { + elapsed = jl_hrtime(); + } + + void stopTimer() { + elapsed = jl_hrtime() - elapsed; + } + + void init(const Twine &name, const Twine &desc) { + this->name = name.str(); + this->desc = desc.str(); + } + + operator bool() const { + return elapsed != 0; + } + + void print(raw_ostream &out, bool clear=false) { + if (!*this) + return; + out << llvm::formatv("{0:F3} ", elapsed / 1e9) << name << " " << desc << "\n"; + if (clear) + elapsed = 0; + } +}; + +struct ShardTimers { + ImageTimer deserialize; + ImageTimer materialize; + ImageTimer construct; + ImageTimer deletion; + // impl timers + ImageTimer unopt; + ImageTimer optimize; + ImageTimer opt; + ImageTimer obj; + ImageTimer asm_; + + std::string name; + std::string desc; + + void print(raw_ostream &out, bool clear=false) { + StringRef sep = "===-------------------------------------------------------------------------==="; + out << formatv("{0}\n{1}\n{0}\n", sep, fmt_align(name + " : " + desc, AlignStyle::Center, sep.size())); + auto total = deserialize.elapsed + materialize.elapsed + construct.elapsed + deletion.elapsed + + unopt.elapsed + optimize.elapsed + opt.elapsed + obj.elapsed + asm_.elapsed; + out << "Time (s) Name Description\n"; + deserialize.print(out, clear); + materialize.print(out, clear); + construct.print(out, clear); + deletion.print(out, clear); + unopt.print(out, clear); + optimize.print(out, clear); + opt.print(out, clear); + obj.print(out, clear); + asm_.print(out, clear); + out << llvm::formatv("{0:F3} total Total time taken\n", total / 1e9); + } +}; + +void emitFloat16Wrappers(Module &M, bool external); + +// Perform the actual optimization and emission of the output files +static void add_output_impl(Module &M, TargetMachine &SourceTM, std::string *outputs, const std::string *names, + NewArchiveMember *unopt, NewArchiveMember *opt, NewArchiveMember *obj, NewArchiveMember *asm_, + ShardTimers &timers, unsigned shardidx) { + auto TM = std::unique_ptr( + SourceTM.getTarget().createTargetMachine( + SourceTM.getTargetTriple().str(), + SourceTM.getTargetCPU(), + SourceTM.getTargetFeatureString(), + SourceTM.Options, + SourceTM.getRelocationModel(), + SourceTM.getCodeModel(), + SourceTM.getOptLevel())); + + if (unopt) { + timers.unopt.startTimer(); + raw_string_ostream OS(*outputs); + PassBuilder PB; + AnalysisManagers AM{*TM, PB, OptimizationLevel::O0}; + ModulePassManager MPM; + MPM.addPass(BitcodeWriterPass(OS)); + MPM.run(M, AM.MAM); + *unopt = NewArchiveMember(MemoryBufferRef(*outputs++, *names++)); + timers.unopt.stopTimer(); + } + if (!opt && !obj && !asm_) { + return; + } + assert(!verifyModule(M, &errs())); + + timers.optimize.startTimer(); + +#ifndef JL_USE_NEW_PM + legacy::PassManager optimizer; + addTargetPasses(&optimizer, TM->getTargetTriple(), TM->getTargetIRAnalysis()); + addOptimizationPasses(&optimizer, jl_options.opt_level, true, true); + addMachinePasses(&optimizer, jl_options.opt_level); +#else + + auto PMTM = std::unique_ptr( + SourceTM.getTarget().createTargetMachine( + SourceTM.getTargetTriple().str(), + SourceTM.getTargetCPU(), + SourceTM.getTargetFeatureString(), + SourceTM.Options, + SourceTM.getRelocationModel(), + SourceTM.getCodeModel(), + SourceTM.getOptLevel())); + NewPM optimizer{std::move(PMTM), getOptLevel(jl_options.opt_level), OptimizationOptions::defaults(true, true)}; +#endif + optimizer.run(M); + assert(!verifyModule(M, &errs())); + bool inject_aliases = false; + for (auto &F : M.functions()) { + if (!F.isDeclaration() && F.getName() != "_DllMainCRTStartup") { + inject_aliases = true; + break; + } + } + // 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. + 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", + FunctionType::get(Type::getFloatTy(M.getContext()), { Type::getHalfTy(M.getContext()) }, false)); + injectCRTAlias(M, "__gnu_f2h_ieee", "julia__gnu_f2h_ieee", + FunctionType::get(Type::getHalfTy(M.getContext()), { Type::getFloatTy(M.getContext()) }, false)); + injectCRTAlias(M, "__truncsfhf2", "julia__gnu_f2h_ieee", + 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 + } + timers.optimize.stopTimer(); + + if (opt) { + timers.opt.startTimer(); + raw_string_ostream OS(*outputs); + PassBuilder PB; + AnalysisManagers AM{*TM, PB, OptimizationLevel::O0}; + ModulePassManager MPM; + MPM.addPass(BitcodeWriterPass(OS)); + MPM.run(M, AM.MAM); + *opt = NewArchiveMember(MemoryBufferRef(*outputs++, *names++)); + timers.opt.stopTimer(); + } + + if (obj) { + timers.obj.startTimer(); + SmallVector Buffer; + raw_svector_ostream OS(Buffer); + legacy::PassManager emitter; + addTargetPasses(&emitter, TM->getTargetTriple(), TM->getTargetIRAnalysis()); + if (TM->addPassesToEmitFile(emitter, OS, nullptr, CGFT_ObjectFile, false)) + jl_safe_printf("ERROR: target does not support generation of object files\n"); + emitter.run(M); + *outputs = { Buffer.data(), Buffer.size() }; + *obj = NewArchiveMember(MemoryBufferRef(*outputs++, *names++)); + timers.obj.stopTimer(); + } + + if (asm_) { + timers.asm_.startTimer(); + SmallVector Buffer; + raw_svector_ostream OS(Buffer); + legacy::PassManager emitter; + addTargetPasses(&emitter, TM->getTargetTriple(), TM->getTargetIRAnalysis()); + if (TM->addPassesToEmitFile(emitter, OS, nullptr, CGFT_AssemblyFile, false)) + jl_safe_printf("ERROR: target does not support generation of assembly files\n"); + emitter.run(M); + *outputs = { Buffer.data(), Buffer.size() }; + *asm_ = NewArchiveMember(MemoryBufferRef(*outputs++, *names++)); + timers.asm_.stopTimer(); + } +} + +// serialize module to bitcode +static auto serializeModule(const Module &M) { + assert(!verifyModule(M, &errs()) && "Serializing invalid module!"); + SmallVector ClonedModuleBuffer; + BitcodeWriter BCWriter(ClonedModuleBuffer); + BCWriter.writeModule(M); + BCWriter.writeSymtab(); + BCWriter.writeStrtab(); + return ClonedModuleBuffer; +} + +// 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 +// deleted, we then materialize the entire module to make use-lists +// consistent. +static void materializePreserved(Module &M, Partition &partition) { + DenseSet Preserve; + for (auto &GV : M.global_values()) { + if (!GV.isDeclaration()) { + if (partition.globals.count(GV.getName())) { + Preserve.insert(&GV); + } + } + } + for (auto &F : M.functions()) { + if (!F.isDeclaration()) { + if (!Preserve.contains(&F)) { + F.deleteBody(); + F.setLinkage(GlobalValue::ExternalLinkage); + F.setVisibility(GlobalValue::HiddenVisibility); + F.setDSOLocal(true); + } + } + } + for (auto &GV : M.globals()) { + if (!GV.isDeclaration()) { + if (!Preserve.contains(&GV)) { + GV.setInitializer(nullptr); + GV.setLinkage(GlobalValue::ExternalLinkage); + GV.setVisibility(GlobalValue::HiddenVisibility); + GV.setDSOLocal(true); + } + } + } + // Global aliases are a pain to deal with. It is illegal to have an alias to a declaration, + // so we need to replace them with either a function or a global variable declaration. However, + // we can't just delete the alias, because that would break the users of the alias. Therefore, + // we do a dance where we point each global alias to a dummy function or global variable, + // then materialize the module to access use-lists, then replace all the uses, and finally commit + // to deleting the old alias. + SmallVector> DeletedAliases; + for (auto &GA : M.aliases()) { + if (!GA.isDeclaration()) { + if (!Preserve.contains(&GA)) { + if (GA.getValueType()->isFunctionTy()) { + auto F = Function::Create(cast(GA.getValueType()), GlobalValue::ExternalLinkage, "", &M); + // This is an extremely sad hack to make sure the global alias never points to an extern function + auto BB = BasicBlock::Create(M.getContext(), "", F); + new UnreachableInst(M.getContext(), BB); + GA.setAliasee(F); + + DeletedAliases.push_back({ &GA, F }); + } + else { + auto GV = new GlobalVariable(M, GA.getValueType(), false, GlobalValue::ExternalLinkage, Constant::getNullValue(GA.getValueType())); + DeletedAliases.push_back({ &GA, GV }); + } + } + } + } + cantFail(M.materializeAll()); + for (auto &Deleted : DeletedAliases) { + Deleted.second->takeName(Deleted.first); + Deleted.first->replaceAllUsesWith(Deleted.second); + Deleted.first->eraseFromParent(); + // undo our previous sad hack + if (auto F = dyn_cast(Deleted.second)) { + F->deleteBody(); + } else { + cast(Deleted.second)->setInitializer(nullptr); + } + } +} + +// Reconstruct jl_fvars, jl_gvars, jl_fvars_idxs, and jl_gvars_idxs from the partition +static void construct_vars(Module &M, Partition &partition) { + std::vector> fvar_pairs; + fvar_pairs.reserve(partition.fvars.size()); + for (auto &fvar : partition.fvars) { + auto F = M.getFunction(fvar.first()); + assert(F); + assert(!F->isDeclaration()); + fvar_pairs.push_back({ fvar.second, F }); + } + std::vector fvars; + std::vector fvar_idxs; + fvars.reserve(fvar_pairs.size()); + fvar_idxs.reserve(fvar_pairs.size()); + std::sort(fvar_pairs.begin(), fvar_pairs.end()); + for (auto &fvar : fvar_pairs) { + fvars.push_back(fvar.second); + fvar_idxs.push_back(fvar.first); + } + std::vector> gvar_pairs; + gvar_pairs.reserve(partition.gvars.size()); + for (auto &gvar : partition.gvars) { + auto GV = M.getGlobalVariable(gvar.first()); + assert(GV); + assert(!GV->isDeclaration()); + gvar_pairs.push_back({ gvar.second, GV }); + } + std::vector gvars; + std::vector gvar_idxs; + gvars.reserve(gvar_pairs.size()); + gvar_idxs.reserve(gvar_pairs.size()); + std::sort(gvar_pairs.begin(), gvar_pairs.end()); + for (auto &gvar : gvar_pairs) { + gvars.push_back(gvar.second); + gvar_idxs.push_back(gvar.first); + } + + // 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 fidxs = ConstantDataArray::get(M.getContext(), fvar_idxs); + auto fidxs_var = new GlobalVariable(M, fidxs->getType(), true, + GlobalVariable::ExternalLinkage, + fidxs, "jl_fvar_idxs"); + fidxs_var->setVisibility(GlobalValue::HiddenVisibility); + fidxs_var->setDSOLocal(true); + auto gidxs = ConstantDataArray::get(M.getContext(), gvar_idxs); + auto gidxs_var = new GlobalVariable(M, gidxs->getType(), true, + GlobalVariable::ExternalLinkage, + gidxs, "jl_gvar_idxs"); + gidxs_var->setVisibility(GlobalValue::HiddenVisibility); + gidxs_var->setDSOLocal(true); +} + +// Materialization will leave many unused declarations, which multiversioning would otherwise clone. +// This function removes them to avoid unnecessary cloning of declarations. +// The GlobalDCEPass is much better at this, but we only care about removing unused +// declarations, not actually about seeing if code is dead (codegen knows it is live, by construction). +static void dropUnusedGlobals(Module &M) { + std::vector unused; + for (auto &G : M.global_values()) { + if (G.isDeclaration() && G.use_empty()) + unused.push_back(&G); + } + for (auto &G : unused) + G->eraseFromParent(); +} + +// Entrypoint to optionally-multithreaded image compilation. This handles global coordination of the threading, +// as well as partitioning, serialization, and deserialization. +static void add_output(Module &M, TargetMachine &TM, std::vector &outputs, StringRef name, + std::vector &unopt, std::vector &opt, + std::vector &obj, std::vector &asm_, + bool unopt_out, bool opt_out, bool obj_out, bool asm_out, + unsigned threads, ModuleInfo module_info) { + unsigned outcount = unopt_out + opt_out + obj_out + asm_out; + assert(outcount); + outputs.resize(outputs.size() + outcount * threads * 2); + auto names_start = outputs.data() + outputs.size() - outcount * threads * 2; + auto outputs_start = names_start + outcount * threads; + unopt.resize(unopt.size() + unopt_out * threads); + opt.resize(opt.size() + opt_out * threads); + obj.resize(obj.size() + obj_out * threads); + asm_.resize(asm_.size() + asm_out * threads); + // Timers for timing purposes + TimerGroup timer_group("add_output", ("Time to optimize and emit LLVM module " + name).str()); + SmallVector timers(threads); + for (unsigned i = 0; i < threads; ++i) { + auto idx = std::to_string(i); + timers[i].name = "shard_" + idx; + timers[i].desc = ("Timings for " + name + " module shard " + idx).str(); + timers[i].deserialize.init("deserialize_" + idx, "Deserialize module"); + timers[i].materialize.init("materialize_" + idx, "Materialize declarations"); + timers[i].construct.init("construct_" + idx, "Construct partitioned definitions"); + timers[i].deletion.init("deletion_" + idx, "Delete dead declarations"); + timers[i].unopt.init("unopt_" + idx, "Emit unoptimized bitcode"); + timers[i].optimize.init("optimize_" + idx, "Optimize shard"); + timers[i].opt.init("opt_" + idx, "Emit optimized bitcode"); + timers[i].obj.init("obj_" + idx, "Emit object file"); + timers[i].asm_.init("asm_" + idx, "Emit assembly file"); + } + Timer partition_timer("partition", "Partition module", timer_group); + Timer serialize_timer("serialize", "Serialize module", timer_group); + Timer output_timer("output", "Add outputs", timer_group); + bool report_timings = false; + if (auto env = getenv("JULIA_IMAGE_TIMINGS")) { + char *endptr; + unsigned long val = strtoul(env, &endptr, 10); + if (endptr != env && !*endptr && val <= 1) { + report_timings = val; + } else { + if (StringRef("true").compare_insensitive(env) == 0) + report_timings = true; + else if (StringRef("false").compare_insensitive(env) == 0) + report_timings = false; + else + errs() << "WARNING: Invalid value for JULIA_IMAGE_TIMINGS: " << env << "\n"; + } + } + for (unsigned i = 0; i < threads; ++i) { + auto start = names_start + i * outcount; + auto istr = std::to_string(i); + if (unopt_out) + *start++ = (name + "_unopt#" + istr + ".bc").str(); + if (opt_out) + *start++ = (name + "_opt#" + istr + ".bc").str(); + if (obj_out) + *start++ = (name + "#" + istr + ".o").str(); + if (asm_out) + *start++ = (name + "#" + istr + ".s").str(); + } + // Single-threaded case + if (threads == 1) { + output_timer.startTimer(); + add_output_impl(M, TM, outputs_start, names_start, + unopt_out ? unopt.data() + unopt.size() - 1 : nullptr, + opt_out ? opt.data() + opt.size() - 1 : nullptr, + obj_out ? obj.data() + obj.size() - 1 : nullptr, + asm_out ? asm_.data() + asm_.size() - 1 : nullptr, + timers[0], 0); + output_timer.stopTimer(); + + if (!report_timings) { + timer_group.clear(); + } else { + timer_group.print(dbgs(), true); + for (auto &t : timers) { + t.print(dbgs(), true); + } + } + return; + } + + partition_timer.startTimer(); + uint64_t counter = 0; + // Partitioning requires all globals to have names. + // We use a prefix to avoid name conflicts with user code. + for (auto &G : M.global_values()) { + if (!G.isDeclaration() && !G.hasName()) { + G.setName("jl_ext_" + Twine(counter++)); + } + } + auto partitions = partitionModule(M, threads); + partition_timer.stopTimer(); + + serialize_timer.startTimer(); + auto serialized = serializeModule(M); + serialize_timer.stopTimer(); + + output_timer.startTimer(); + + auto unoptstart = unopt_out ? unopt.data() + unopt.size() - threads : nullptr; + auto optstart = opt_out ? opt.data() + opt.size() - threads : nullptr; + auto objstart = obj_out ? obj.data() + obj.size() - threads : nullptr; + auto asmstart = asm_out ? asm_.data() + asm_.size() - threads : nullptr; + + // Start all of the worker threads + std::vector workers(threads); + for (unsigned i = 0; i < threads; i++) { + workers[i] = std::thread([&, i](){ + LLVMContext 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"); + timers[i].deserialize.stopTimer(); + + timers[i].materialize.startTimer(); + materializePreserved(*M, partitions[i]); + 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))); + // 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), "."); + for (DICompileUnit *CU : M->debug_compile_units()) + CU->replaceOperandWith(0, topfile); + timers[i].construct.stopTimer(); + + timers[i].deletion.startTimer(); + dropUnusedGlobals(*M); + timers[i].deletion.stopTimer(); + + add_output_impl(*M, TM, outputs_start + i * outcount, names_start + i * outcount, + unoptstart ? unoptstart + i : nullptr, + optstart ? optstart + i : nullptr, + objstart ? objstart + i : nullptr, + asmstart ? asmstart + i : nullptr, + timers[i], i); + }); + } + + // Wait for all of the worker threads to finish + for (auto &w : workers) + w.join(); + + output_timer.stopTimer(); + + if (!report_timings) { + timer_group.clear(); + } else { + timer_group.print(dbgs(), true); + for (auto &t : timers) { + t.print(dbgs(), true); + } + dbgs() << "Partition weights: ["; + bool comma = false; + for (auto &p : partitions) { + if (comma) + dbgs() << ", "; + else + comma = true; + dbgs() << p.weight; + } + dbgs() << "]\n"; + } +} + +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 + // 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) { + LLVM_DEBUG(dbgs() << "Small module, using a single thread\n"); + return 1; + } + + unsigned threads = std::max(jl_cpu_threads() / 2, 1); + + auto max_threads = info.globals / 100; + if (max_threads < threads) { + LLVM_DEBUG(dbgs() << "Low global count limiting threads to " << max_threads << " (" << info.globals << "globals)\n"); + threads = max_threads; + } + + // environment variable override + const char *env_threads = getenv("JULIA_IMAGE_THREADS"); + bool env_threads_set = false; + if (env_threads) { + char *endptr; + unsigned long requested = strtoul(env_threads, &endptr, 10); + if (*endptr || !requested) { + jl_safe_printf("WARNING: invalid value '%s' for JULIA_IMAGE_THREADS\n", env_threads); + } else { + LLVM_DEBUG(dbgs() << "Overriding threads to " << requested << " due to JULIA_IMAGE_THREADS\n"); + threads = requested; + env_threads_set = true; + } + } + + // more defaults + if (!env_threads_set && threads > 1) { + if (auto fallbackenv = getenv("JULIA_CPU_THREADS")) { + char *endptr; + unsigned long requested = strtoul(fallbackenv, &endptr, 10); + if (*endptr || !requested) { + jl_safe_printf("WARNING: invalid value '%s' for JULIA_CPU_THREADS\n", fallbackenv); + } else if (requested < threads) { + LLVM_DEBUG(dbgs() << "Overriding threads to " << requested << " due to JULIA_CPU_THREADS\n"); + threads = requested; + } + } + } + + threads = std::max(threads, 1u); + + return threads; +} // takes the running content that has collected in the shadow module and dump it to disk // this builds the object file portion of the sysimage files for fast startup -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN void jl_dump_native_impl(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, - const char *sysimg_data, size_t sysimg_len) + const char *sysimg_data, size_t sysimg_len, ios_t *s) { - JL_TIMING(NATIVE_DUMP); + JL_TIMING(NATIVE_AOT, NATIVE_Dump); jl_native_code_desc_t *data = (jl_native_code_desc_t*)native_code; - LLVMContext &Context = data->M->getContext(); + if (!bc_fname && !unopt_bc_fname && !obj_fname && !asm_fname) { + LLVM_DEBUG(dbgs() << "No output requested, skipping native code dump?\n"); + delete data; + return; + } + auto TSCtx = data->M.getContext(); + auto lock = TSCtx.getLock(); + LLVMContext &Context = *TSCtx.getContext(); // We don't want to use MCJIT's target machine because // it uses the large code model and we may potentially // want less optimizations there. - Triple TheTriple = Triple(jl_TargetMachine->getTargetTriple()); // make sure to emit the native object format, even if FORCE_ELF was set in codegen -#if defined(_OS_WINDOWS_) - TheTriple.setObjectFormat(Triple::COFF); -#elif defined(_OS_DARWIN_) - TheTriple.setObjectFormat(Triple::MachO); - TheTriple.setOS(llvm::Triple::MacOSX); -#endif - std::unique_ptr TM( - jl_TargetMachine->getTarget().createTargetMachine( + Triple TheTriple(data->M.getModuleUnlocked()->getTargetTriple()); + if (TheTriple.isOSWindows()) { + TheTriple.setObjectFormat(Triple::COFF); + } else if (TheTriple.isOSDarwin()) { + TheTriple.setObjectFormat(Triple::MachO); + TheTriple.setOS(llvm::Triple::MacOSX); + } + Optional RelocModel; + if (TheTriple.isOSLinux() || TheTriple.isOSFreeBSD()) { + RelocModel = Reloc::PIC_; + } + CodeModel::Model CMModel = CodeModel::Small; + if (TheTriple.isPPC()) { + // On PPC the small model is limited to 16bit offsets + CMModel = CodeModel::Medium; + } + std::unique_ptr SourceTM( + jl_ExecutionEngine->getTarget().createTargetMachine( TheTriple.getTriple(), - jl_TargetMachine->getTargetCPU(), - jl_TargetMachine->getTargetFeatureString(), - jl_TargetMachine->Options, -#if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) - Reloc::PIC_, -#else - Optional(), -#endif -#if defined(_CPU_PPC_) || defined(_CPU_PPC64_) - // On PPC the small model is limited to 16bit offsets - CodeModel::Medium, -#else - // Use small model so that we can use signed 32bits offset in the function and GV tables - CodeModel::Small, -#endif + jl_ExecutionEngine->getTargetCPU(), + jl_ExecutionEngine->getTargetFeatureString(), + jl_ExecutionEngine->getTargetOptions(), + RelocModel, + CMModel, CodeGenOpt::Aggressive // -O3 TODO: respect command -O0 flag? )); - legacy::PassManager PM; - addTargetPasses(&PM, TM.get()); - - // set up optimization passes - SmallVector bc_Buffer; - SmallVector obj_Buffer; - SmallVector asm_Buffer; - SmallVector unopt_bc_Buffer; - raw_svector_ostream bc_OS(bc_Buffer); - raw_svector_ostream obj_OS(obj_Buffer); - raw_svector_ostream asm_OS(asm_Buffer); - raw_svector_ostream unopt_bc_OS(unopt_bc_Buffer); + std::vector bc_Archive; std::vector obj_Archive; std::vector asm_Archive; std::vector unopt_bc_Archive; std::vector outputs; - if (unopt_bc_fname) - PM.add(createBitcodeWriterPass(unopt_bc_OS)); - if (bc_fname || obj_fname || asm_fname) { - addOptimizationPasses(&PM, jl_options.opt_level, true, true); - addMachinePasses(&PM, TM.get(), jl_options.opt_level); - } - if (bc_fname) - PM.add(createBitcodeWriterPass(bc_OS)); - if (obj_fname) - if (TM->addPassesToEmitFile(PM, obj_OS, nullptr, CGFT_ObjectFile, false)) - jl_safe_printf("ERROR: target does not support generation of object files\n"); - if (asm_fname) - if (TM->addPassesToEmitFile(PM, asm_OS, nullptr, CGFT_AssemblyFile, false)) - jl_safe_printf("ERROR: target does not support generation of object files\n"); - // Reset the target triple to make sure it matches the new target machine - data->M->setTargetTriple(TM->getTargetTriple().str()); - DataLayout DL = TM->createDataLayout(); - DL.reset(DL.getStringRepresentation() + "-ni:10:11:12:13"); - data->M->setDataLayout(DL); - Type *T_size; - if (sizeof(size_t) == 8) - T_size = Type::getInt64Ty(Context); - else - T_size = Type::getInt32Ty(Context); + auto dataM = data->M.getModuleUnlocked(); + dataM->setTargetTriple(SourceTM->getTargetTriple().str()); + dataM->setDataLayout(jl_create_datalayout(*SourceTM)); + + Type *T_size = dataM->getDataLayout().getIntPtrType(Context); Type *T_psize = T_size->getPointerTo(); + bool imaging_mode = imaging_default() || jl_options.outputo; + + unsigned threads = 1; + unsigned nfvars = 0; + unsigned ngvars = 0; + + ModuleInfo module_info = compute_module_info(*dataM); + LLVM_DEBUG(dbgs() + << "Dumping module with stats:\n" + << " globals: " << module_info.globals << "\n" + << " functions: " << module_info.funcs << "\n" + << " basic blocks: " << module_info.bbs << "\n" + << " instructions: " << module_info.insts << "\n" + << " clones: " << module_info.clones << "\n" + << " weight: " << module_info.weight << "\n" + ); + // add metadata information if (imaging_mode) { - emit_offset_table(*data->M, data->jl_sysimg_gvars, "jl_sysimg_gvars", T_psize); - emit_offset_table(*data->M, data->jl_sysimg_fvars, "jl_sysimg_fvars", T_psize); + multiversioning_preannotate(*dataM); + { + DenseSet fvars(data->jl_sysimg_fvars.begin(), data->jl_sysimg_fvars.end()); + for (auto &F : *dataM) { + if (F.hasFnAttribute("julia.mv.reloc") || F.hasFnAttribute("julia.mv.fvar")) { + if (fvars.insert(&F).second) { + data->jl_sysimg_fvars.push_back(&F); + } + } + } + } + threads = compute_image_thread_count(module_info); + 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); + std::vector idxs; + idxs.resize(data->jl_sysimg_gvars.size()); + std::iota(idxs.begin(), idxs.end(), 0); + auto gidxs = ConstantDataArray::get(Context, idxs); + auto gidxs_var = new GlobalVariable(*dataM, gidxs->getType(), true, + GlobalVariable::ExternalLinkage, + gidxs, "jl_gvar_idxs"); + gidxs_var->setVisibility(GlobalValue::HiddenVisibility); + gidxs_var->setDSOLocal(true); + idxs.clear(); + idxs.resize(data->jl_sysimg_fvars.size()); + std::iota(idxs.begin(), idxs.end(), 0); + auto fidxs = ConstantDataArray::get(Context, idxs); + auto fidxs_var = new GlobalVariable(*dataM, fidxs->getType(), true, + GlobalVariable::ExternalLinkage, + fidxs, "jl_fvar_idxs"); + fidxs_var->setVisibility(GlobalValue::HiddenVisibility); + fidxs_var->setDSOLocal(true); + dataM->addModuleFlag(Module::Error, "julia.mv.suffix", MDString::get(Context, "_0")); // 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(data->M.get()); - addComdat(new GlobalVariable(*data->M, + GlobalValue *jlRTLD_DEFAULT_var = jl_emit_RTLD_DEFAULT_var(dataM); + addComdat(new GlobalVariable(*dataM, jlRTLD_DEFAULT_var->getType(), true, GlobalVariable::ExternalLinkage, jlRTLD_DEFAULT_var, - "jl_RTLD_DEFAULT_handle_pointer")); - } - - // do the actual work - auto add_output = [&] (Module &M, StringRef unopt_bc_Name, StringRef bc_Name, StringRef obj_Name, StringRef asm_Name) { - PM.run(M); - if (unopt_bc_fname) - emit_result(unopt_bc_Archive, unopt_bc_Buffer, unopt_bc_Name, outputs); - if (bc_fname) - emit_result(bc_Archive, bc_Buffer, bc_Name, outputs); - if (obj_fname) - emit_result(obj_Archive, obj_Buffer, obj_Name, outputs); - if (asm_fname) - emit_result(asm_Archive, asm_Buffer, asm_Name, outputs); - }; + "jl_RTLD_DEFAULT_handle_pointer"), TheTriple); + + // let the compiler know we are going to internalize a copy of this, + // if it has a current usage with ExternalLinkage + auto small_typeof_copy = dataM->getGlobalVariable("small_typeof"); + if (small_typeof_copy) { + small_typeof_copy->setVisibility(GlobalValue::HiddenVisibility); + small_typeof_copy->setDSOLocal(true); + } + } + + // Reserve space for the output files and names + // DO NOT DELETE, this is necessary to ensure memorybuffers + // have a stable backing store for both their object files and + // their names + outputs.reserve((threads + 1) * (!!unopt_bc_fname + !!bc_fname + !!obj_fname + !!asm_fname) * 2); - add_output(*data->M, "unopt.bc", "text.bc", "text.o", "text.s"); + auto compile = [&](Module &M, StringRef name, unsigned threads) { add_output( + M, *SourceTM, outputs, name, + unopt_bc_Archive, bc_Archive, obj_Archive, asm_Archive, + !!unopt_bc_fname, !!bc_fname, !!obj_fname, !!asm_fname, + threads, module_info + ); }; - std::unique_ptr sysimage(new Module("sysimage", Context)); - sysimage->setTargetTriple(data->M->getTargetTriple()); - sysimage->setDataLayout(data->M->getDataLayout()); + compile(*dataM, "text", threads); + + auto sysimageM = std::make_unique("sysimage", Context); + sysimageM->setTargetTriple(dataM->getTargetTriple()); + sysimageM->setDataLayout(dataM->getDataLayout()); #if JL_LLVM_VERSION >= 130000 - sysimage->setStackProtectorGuard(data->M->getStackProtectorGuard()); - sysimage->setOverrideStackAlignment(data->M->getOverrideStackAlignment()); + sysimageM->setStackProtectorGuard(dataM->getStackProtectorGuard()); + sysimageM->setOverrideStackAlignment(dataM->getOverrideStackAlignment()); #endif - data->M.reset(); // free memory for data->M + + if (TheTriple.isOSWindows()) { + // Windows expect that the function `_DllMainStartup` is present in an dll. + // Normal compilers use something like Zig's crtdll.c instead we provide a + // a stub implementation. + auto T_pvoid = Type::getInt8Ty(Context)->getPointerTo(); + auto T_int32 = Type::getInt32Ty(Context); + auto FT = FunctionType::get(T_int32, {T_pvoid, T_int32, T_pvoid}, false); + auto F = Function::Create(FT, Function::ExternalLinkage, "_DllMainCRTStartup", *sysimageM); + F->setCallingConv(CallingConv::X86_StdCall); + + llvm::IRBuilder<> builder(BasicBlock::Create(Context, "top", F)); + builder.CreateRet(ConstantInt::get(T_int32, 1)); + } + bool has_veccall = dataM->getModuleFlag("julia.mv.veccall"); + data->M = orc::ThreadSafeModule(); // free memory for data->M if (sysimg_data) { Constant *data = ConstantDataArray::get(Context, ArrayRef((const unsigned char*)sysimg_data, sysimg_len)); - addComdat(new GlobalVariable(*sysimage, data->getType(), false, + auto sysdata = new GlobalVariable(*sysimageM, data->getType(), false, GlobalVariable::ExternalLinkage, - data, "jl_system_image_data"))->setAlignment(Align(64)); + data, "jl_system_image_data"); + sysdata->setAlignment(Align(64)); + addComdat(sysdata, TheTriple); Constant *len = ConstantInt::get(T_size, sysimg_len); - addComdat(new GlobalVariable(*sysimage, len->getType(), true, + addComdat(new GlobalVariable(*sysimageM, len->getType(), true, GlobalVariable::ExternalLinkage, - len, "jl_system_image_size")); + len, "jl_system_image_size"), TheTriple); + } + if (imaging_mode) { + auto specs = jl_get_llvm_clone_targets(); + const uint32_t base_flags = has_veccall ? JL_TARGET_VEC_CALL : 0; + std::vector data; + auto push_i32 = [&] (uint32_t v) { + uint8_t buff[4]; + memcpy(buff, &v, 4); + data.insert(data.end(), buff, buff + 4); + }; + push_i32(specs.size()); + for (uint32_t i = 0; i < specs.size(); i++) { + push_i32(base_flags | (specs[i].flags & JL_TARGET_UNKNOWN_NAME)); + auto &specdata = specs[i].data; + data.insert(data.end(), specdata.begin(), specdata.end()); + } + auto value = ConstantDataArray::get(Context, data); + auto target_ids = new GlobalVariable(*sysimageM, value->getType(), true, + GlobalVariable::InternalLinkage, + value, "jl_dispatch_target_ids"); + auto shards = emit_shard_table(*sysimageM, T_size, T_psize, threads); + auto ptls = emit_ptls_table(*sysimageM, T_size, T_psize); + auto header = emit_image_header(*sysimageM, threads, nfvars, ngvars); + auto AT = ArrayType::get(T_size, sizeof(small_typeof) / sizeof(void*)); + auto small_typeof_copy = new GlobalVariable(*sysimageM, AT, false, + GlobalVariable::ExternalLinkage, + Constant::getNullValue(AT), + "small_typeof"); + small_typeof_copy->setVisibility(GlobalValue::HiddenVisibility); + small_typeof_copy->setDSOLocal(true); + AT = ArrayType::get(T_psize, 5); + auto pointers = new GlobalVariable(*sysimageM, AT, false, + GlobalVariable::ExternalLinkage, + ConstantArray::get(AT, { + ConstantExpr::getBitCast(header, T_psize), + ConstantExpr::getBitCast(shards, T_psize), + ConstantExpr::getBitCast(ptls, T_psize), + ConstantExpr::getBitCast(small_typeof_copy, T_psize), + ConstantExpr::getBitCast(target_ids, T_psize) + }), + "jl_image_pointers"); + addComdat(pointers, TheTriple); + if (s) { + write_int32(s, data.size()); + ios_write(s, (const char *)data.data(), data.size()); + } } - add_output(*sysimage, "data.bc", "data.bc", "data.o", "data.s"); + + compile(*sysimageM, "data", 1); object::Archive::Kind Kind = getDefaultForHost(TheTriple); if (unopt_bc_fname) @@ -590,14 +1713,14 @@ void jl_dump_native_impl(void *native_code, delete data; } -void addTargetPasses(legacy::PassManagerBase *PM, TargetMachine *TM) +void addTargetPasses(legacy::PassManagerBase *PM, const Triple &triple, TargetIRAnalysis analysis) { - PM->add(new TargetLibraryInfoWrapperPass(Triple(TM->getTargetTriple()))); - PM->add(createTargetTransformInfoWrapperPass(TM->getTargetIRAnalysis())); + PM->add(new TargetLibraryInfoWrapperPass(triple)); + PM->add(createTargetTransformInfoWrapperPass(std::move(analysis))); } -void addMachinePasses(legacy::PassManagerBase *PM, TargetMachine *TM, int optlevel) +void addMachinePasses(legacy::PassManagerBase *PM, int optlevel) { // TODO: don't do this on CPUs that natively support Float16 PM->add(createDemoteFloat16Pass()); @@ -608,7 +1731,8 @@ void addMachinePasses(legacy::PassManagerBase *PM, TargetMachine *TM, int optlev // this defines the set of optimization passes defined for Julia at various optimization levels. // it assumes that the TLI and TTI wrapper passes have already been added. void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, - bool lower_intrinsics, bool dump_native) + bool lower_intrinsics, bool dump_native, + bool external_use) { // Note: LLVM 12 disabled the hoisting of common instruction // before loop vectorization (https://reviews.llvm.org/D84108). @@ -617,7 +1741,19 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, // to merge allocations and sometimes eliminate them, // since AllocOpt does not handle PhiNodes. // Enable this instruction hoisting because of this and Union benchmarks. - auto simplifyCFGOptions = SimplifyCFGOptions().hoistCommonInsts(true); + auto basicSimplifyCFGOptions = SimplifyCFGOptions() + .convertSwitchRangeToICmp(true) + .convertSwitchToLookupTable(true) + .forwardSwitchCondToPhi(true); + auto aggressiveSimplifyCFGOptions = SimplifyCFGOptions() + .convertSwitchRangeToICmp(true) + .convertSwitchToLookupTable(true) + .forwardSwitchCondToPhi(true) + //These mess with loop rotation, so only do them after that + .hoistCommonInsts(true) + // Causes an SRET assertion error in late-gc-lowering + // .sinkCommonInsts(true) + ; #ifdef JL_DEBUG_BUILD PM->add(createGCInvariantVerifierPass(true)); PM->add(createVerifierPass()); @@ -632,7 +1768,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, if (opt_level == 1) PM->add(createInstSimplifyLegacyPass()); } - PM->add(createCFGSimplificationPass(simplifyCFGOptions)); + PM->add(createCFGSimplificationPass(basicSimplifyCFGOptions)); if (opt_level == 1) { PM->add(createSROAPass()); PM->add(createInstructionCombiningPass()); @@ -657,22 +1793,24 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, } PM->add(createLowerSimdLoopPass()); // Annotate loop marked with "loopinfo" as LLVM parallel loop if (dump_native) { - PM->add(createMultiVersioningPass()); + PM->add(createMultiVersioningPass(external_use)); PM->add(createCPUFeaturesPass()); // minimal clean-up to get rid of CPU feature checks if (opt_level == 1) { PM->add(createInstSimplifyLegacyPass()); - PM->add(createCFGSimplificationPass(simplifyCFGOptions)); + PM->add(createCFGSimplificationPass(basicSimplifyCFGOptions)); } } +#if JL_LLVM_VERSION < 150000 #if defined(_COMPILER_ASAN_ENABLED_) PM->add(createAddressSanitizerFunctionPass()); #endif #if defined(_COMPILER_MSAN_ENABLED_) - PM->add(createMemorySanitizerPass(true)); + PM->add(createMemorySanitizerLegacyPassPass()); #endif #if defined(_COMPILER_TSAN_ENABLED_) PM->add(createThreadSanitizerLegacyPassPass()); +#endif #endif return; } @@ -683,7 +1821,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, PM->add(createBasicAAWrapperPass()); } - PM->add(createCFGSimplificationPass(simplifyCFGOptions)); + PM->add(createCFGSimplificationPass(basicSimplifyCFGOptions)); PM->add(createDeadCodeEliminationPass()); PM->add(createSROAPass()); @@ -697,9 +1835,9 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, PM->add(createAllocOptPass()); // consider AggressiveInstCombinePass at optlevel > 2 PM->add(createInstructionCombiningPass()); - PM->add(createCFGSimplificationPass(simplifyCFGOptions)); + PM->add(createCFGSimplificationPass(basicSimplifyCFGOptions)); if (dump_native) - PM->add(createMultiVersioningPass()); + PM->add(createMultiVersioningPass(external_use)); PM->add(createCPUFeaturesPass()); PM->add(createSROAPass()); PM->add(createInstSimplifyLegacyPass()); @@ -728,7 +1866,11 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, PM->add(createLowerSimdLoopPass()); // Annotate loop marked with "loopinfo" as LLVM parallel loop PM->add(createLICMPass()); PM->add(createJuliaLICMPass()); +#if JL_LLVM_VERSION >= 150000 + PM->add(createSimpleLoopUnswitchLegacyPass()); +#else PM->add(createLoopUnswitchPass()); +#endif PM->add(createLICMPass()); PM->add(createJuliaLICMPass()); PM->add(createInductiveRangeCheckEliminationPass()); // Must come before indvars @@ -767,14 +1909,15 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, PM->add(createGVNPass()); // Must come after JumpThreading and before LoopVectorize } PM->add(createDeadStoreEliminationPass()); + // see if all of the constant folding has exposed more loops + // to simplification and deletion + // this helps significantly with cleaning up iteration + PM->add(createCFGSimplificationPass(aggressiveSimplifyCFGOptions)); // More dead allocation (store) deletion before loop optimization // consider removing this: + // Moving this after aggressive CFG simplification helps deallocate when allocations are hoisted PM->add(createAllocOptPass()); - // see if all of the constant folding has exposed more loops - // to simplification and deletion - // this helps significantly with cleaning up iteration - PM->add(createCFGSimplificationPass()); // See note above, don't hoist instructions before LV PM->add(createLoopDeletionPass()); PM->add(createInstructionCombiningPass()); PM->add(createLoopVectorizePass()); @@ -782,12 +1925,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, // Cleanup after LV pass PM->add(createInstructionCombiningPass()); PM->add(createCFGSimplificationPass( // Aggressive CFG simplification - SimplifyCFGOptions() - .forwardSwitchCondToPhi(true) - .convertSwitchToLookupTable(true) - .needCanonicalLoops(false) - .hoistCommonInsts(true) - // .sinkCommonInsts(true) // FIXME: Causes assertion in llvm-late-lowering + aggressiveSimplifyCFGOptions )); PM->add(createSLPVectorizerPass()); // might need this after LLVM 11: @@ -798,7 +1936,7 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, if (lower_intrinsics) { // LowerPTLS removes an indirect call. As a result, it is likely to trigger // LLVM's devirtualization heuristics, which would result in the entire - // pass pipeline being re-exectuted. Prevent this by inserting a barrier. + // pass pipeline being re-executed. Prevent this by inserting a barrier. PM->add(createBarrierNoopPass()); PM->add(createLowerExcHandlersPass()); PM->add(createGCInvariantVerifierPass(false)); @@ -824,20 +1962,22 @@ void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, } PM->add(createCombineMulAddPass()); PM->add(createDivRemPairsPass()); +#if JL_LLVM_VERSION < 150000 #if defined(_COMPILER_ASAN_ENABLED_) PM->add(createAddressSanitizerFunctionPass()); #endif #if defined(_COMPILER_MSAN_ENABLED_) - PM->add(createMemorySanitizerPass(true)); + PM->add(createMemorySanitizerLegacyPassPass()); #endif #if defined(_COMPILER_TSAN_ENABLED_) PM->add(createThreadSanitizerLegacyPassPass()); #endif +#endif } // An LLVM module pass that just runs all julia passes in order. Useful for // debugging -template +template class JuliaPipeline : public Pass { public: static char ID; @@ -851,23 +1991,30 @@ class JuliaPipeline : public Pass { (void)jl_init_llvm(); PMTopLevelManager *TPM = Stack.top()->getTopLevelManager(); TPMAdapter Adapter(TPM); - addTargetPasses(&Adapter, jl_TargetMachine); - addOptimizationPasses(&Adapter, OptLevel); - addMachinePasses(&Adapter, jl_TargetMachine, OptLevel); + addTargetPasses(&Adapter, jl_ExecutionEngine->getTargetTriple(), jl_ExecutionEngine->getTargetIRAnalysis()); + addOptimizationPasses(&Adapter, OptLevel, true, dump_native, true); + addMachinePasses(&Adapter, OptLevel); } JuliaPipeline() : Pass(PT_PassManager, ID) {} Pass *createPrinterPass(raw_ostream &O, const std::string &Banner) const override { return createPrintModulePass(O, Banner); } }; -template<> char JuliaPipeline<0>::ID = 0; -template<> char JuliaPipeline<2>::ID = 0; -template<> char JuliaPipeline<3>::ID = 0; -static RegisterPass> X("juliaO0", "Runs the entire julia pipeline (at -O0)", false, false); -static RegisterPass> Y("julia", "Runs the entire julia pipeline (at -O2)", false, false); -static RegisterPass> Z("juliaO3", "Runs the entire julia pipeline (at -O3)", false, false); - -extern "C" JL_DLLEXPORT +template<> char JuliaPipeline<0,false>::ID = 0; +template<> char JuliaPipeline<2,false>::ID = 0; +template<> char JuliaPipeline<3,false>::ID = 0; +template<> char JuliaPipeline<0,true>::ID = 0; +template<> char JuliaPipeline<2,true>::ID = 0; +template<> char JuliaPipeline<3,true>::ID = 0; +static RegisterPass> X("juliaO0", "Runs the entire julia pipeline (at -O0)", false, false); +static RegisterPass> Y("julia", "Runs the entire julia pipeline (at -O2)", false, false); +static RegisterPass> Z("juliaO3", "Runs the entire julia pipeline (at -O3)", false, false); + +static RegisterPass> XS("juliaO0-sysimg", "Runs the entire julia pipeline (at -O0/sysimg mode)", false, false); +static RegisterPass> YS("julia-sysimg", "Runs the entire julia pipeline (at -O2/sysimg mode)", false, false); +static RegisterPass> ZS("juliaO3-sysimg", "Runs the entire julia pipeline (at -O3/sysimg mode)", false, false); + +extern "C" JL_DLLEXPORT_CODEGEN void jl_add_optimization_passes_impl(LLVMPassManagerRef PM, int opt_level, int lower_intrinsics) { addOptimizationPasses(unwrap(PM), opt_level, lower_intrinsics); } @@ -877,60 +2024,66 @@ void jl_add_optimization_passes_impl(LLVMPassManagerRef PM, int opt_level, int l // for use in reflection from Julia. // this is paired with jl_dump_function_ir, jl_dump_function_asm, jl_dump_method_asm in particular ways: // misuse will leak memory or cause read-after-free -extern "C" JL_DLLEXPORT -void *jl_get_llvmf_defn_impl(jl_method_instance_t *mi, size_t world, char getwrapper, char optimize, const jl_cgparams_t params) +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) { // not a generic function - return NULL; - } - - static legacy::PassManager *PM; - if (!PM) { - PM = new legacy::PassManager(); - addTargetPasses(PM, jl_TargetMachine); - addOptimizationPasses(PM, jl_options.opt_level); - addMachinePasses(PM, jl_TargetMachine, jl_options.opt_level); + 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_GC_PUSH2(&src, &jlrettype); - jl_value_t *ci = jl_rettype_inferred(mi, world, world); - if (ci != jl_nothing) { - jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; - src = (jl_code_info_t*)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_array_t*)src); - jlrettype = codeinst->rettype; + 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)) { + 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); } - 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) : (jl_code_info_t*)mi->def.method->source; - if (src && !jl_is_code_info(src) && jl_is_method(mi->def.method)) - src = jl_uncompress_ir(mi->def.method, NULL, (jl_array_t*)src); + else { + jl_value_t *ci = jl_rettype_inferred_addr(mi, world, world); + if (ci != jl_nothing) { + 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)) + 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 } - // TODO: use mi->uninferred } // emit this function into a new llvm module if (src && jl_is_code_info(src)) { - jl_codegen_params_t output; - output.world = world; - output.params = ¶ms; - std::unique_ptr m; - jl_llvm_functions_t decls; - JL_LOCK(&jl_codegen_lock); + auto ctx = jl_ExecutionEngine->getContext(); + orc::ThreadSafeModule m = jl_create_ts_module(name_from_method_instance(mi), *ctx, imaging_default()); uint64_t compiler_start_time = 0; uint8_t measure_compile_time_enabled = jl_atomic_load_relaxed(&jl_measure_compile_time_enabled); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); - std::tie(m, decls) = jl_emit_code(mi, src, jlrettype, output); + JL_LOCK(&jl_codegen_lock); + auto target_info = m.withModuleDo([&](Module &M) { + 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; + auto decls = jl_emit_code(m, mi, src, jlrettype, output); + JL_UNLOCK(&jl_codegen_lock); // Might GC Function *F = NULL; if (m) { @@ -940,8 +2093,20 @@ void *jl_get_llvmf_defn_impl(jl_method_instance_t *mi, size_t world, char getwra // and will better match what's actually in sysimg. for (auto &global : output.globals) global.second->setLinkage(GlobalValue::ExternalLinkage); - if (optimize) - PM->run(*m.get()); + assert(!verifyModule(*m.getModuleUnlocked(), &errs())); + if (optimize) { +#ifndef JL_USE_NEW_PM + legacy::PassManager PM; + addTargetPasses(&PM, jl_ExecutionEngine->getTargetTriple(), jl_ExecutionEngine->getTargetIRAnalysis()); + addOptimizationPasses(&PM, jl_options.opt_level); + addMachinePasses(&PM, jl_options.opt_level); +#else + NewPM PM{jl_ExecutionEngine->cloneTargetMachine(), getOptLevel(jl_options.opt_level)}; +#endif + //Safe b/c context lock is held by output + PM.run(*m.getModuleUnlocked()); + assert(!verifyModule(*m.getModuleUnlocked(), &errs())); + } const std::string *fname; if (decls.functionObject == "jl_fptr_args" || decls.functionObject == "jl_fptr_sparam") getwrapper = false; @@ -949,15 +2114,18 @@ void *jl_get_llvmf_defn_impl(jl_method_instance_t *mi, size_t world, char getwra fname = &decls.specFunctionObject; else fname = &decls.functionObject; - F = cast(m->getNamedValue(*fname)); - m.release(); // the return object `llvmf` will be the owning pointer + F = cast(m.getModuleUnlocked()->getNamedValue(*fname)); } JL_GC_POP(); - if (measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); - JL_UNLOCK(&jl_codegen_lock); // Might GC - if (F) - return F; + if (measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } + if (F) { + dump->TSM = wrap(new orc::ThreadSafeModule(std::move(m))); + dump->F = wrap(F); + return; + } } const char *mname = name_from_method_instance(mi); diff --git a/src/array.c b/src/array.c index 728dbf40bd4e1..5226c729d32e7 100644 --- a/src/array.c +++ b/src/array.c @@ -509,7 +509,7 @@ JL_DLLEXPORT jl_value_t *jl_alloc_string(size_t len) jl_throw(jl_memory_exception); s = jl_gc_big_alloc_noinline(ptls, allocsz); } - jl_set_typeof(s, jl_string_type); + 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; @@ -617,7 +617,7 @@ JL_DLLEXPORT void jl_arrayset(jl_array_t *a JL_ROOTING_ARGUMENT, jl_value_t *rhs arrayassign_safe(hasptr, jl_array_owner(a), &((char*)a->data)[i * a->elsize], rhs, a->elsize); } else { - jl_atomic_store_relaxed(((_Atomic(jl_value_t*)*)a->data) + i, rhs); + jl_atomic_store_release(((_Atomic(jl_value_t*)*)a->data) + i, rhs); jl_gc_wb(jl_array_owner(a), rhs); } } @@ -1161,7 +1161,7 @@ JL_DLLEXPORT void jl_array_sizehint(jl_array_t *a, size_t 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; + if (dec <= a->maxsize / 8) return; jl_array_shrink(a, dec); } else { @@ -1198,7 +1198,7 @@ static NOINLINE ssize_t jl_array_ptr_copy_forward(jl_value_t *owner, _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_relaxed(dest_pa + i, val); + 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); @@ -1216,7 +1216,7 @@ static NOINLINE ssize_t jl_array_ptr_copy_backward(jl_value_t *owner, _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_relaxed(dest_pa + n - i - 1, val); + 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); @@ -1255,7 +1255,7 @@ JL_DLLEXPORT void jl_array_ptr_copy(jl_array_t *dest, void **dest_p, JL_DLLEXPORT void jl_array_ptr_1d_push(jl_array_t *a, jl_value_t *item) { - assert(jl_typeis(a, jl_array_any_type)); + 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); @@ -1263,8 +1263,8 @@ 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) { - assert(jl_typeis(a, jl_array_any_type)); - assert(jl_typeis(a2, jl_array_any_type)); + 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); diff --git a/src/ast.c b/src/ast.c index 5dfd2107d6e3e..97bbc6e8227ba 100644 --- a/src/ast.c +++ b/src/ast.c @@ -66,7 +66,6 @@ JL_DLLEXPORT jl_sym_t *jl_boundscheck_sym; JL_DLLEXPORT jl_sym_t *jl_inbounds_sym; JL_DLLEXPORT jl_sym_t *jl_copyast_sym; JL_DLLEXPORT jl_sym_t *jl_cfunction_sym; -JL_DLLEXPORT jl_sym_t *jl_pure_sym; JL_DLLEXPORT jl_sym_t *jl_loopinfo_sym; JL_DLLEXPORT jl_sym_t *jl_meta_sym; JL_DLLEXPORT jl_sym_t *jl_inert_sym; @@ -156,8 +155,8 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint (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_binding_t *b = jl_get_module_binding(ctx->module, var); - return (b != NULL && b->owner == ctx->module) ? fl_ctx->T : fl_ctx->F; + 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 @@ -177,6 +176,15 @@ static value_t fl_julia_current_line(fl_context_t *fl_ctx, value_t *args, uint32 return fixnum(jl_lineno); } +static int jl_is_number(jl_value_t *v) +{ + jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); + for (; t->super != t; t = t->super) + if (t == jl_number_type) + return 1; + return 0; +} + // Check whether v is a scalar for purposes of inlining fused-broadcast // arguments when lowering; should agree with broadcast.jl on what is a // scalar. When in doubt, return false, since this is only an optimization. @@ -187,7 +195,7 @@ static value_t fl_julia_scalar(fl_context_t *fl_ctx, value_t *args, uint32_t nar return fl_ctx->T; else if (iscvalue(args[0]) && fl_ctx->jl_sym == cv_type((cvalue_t*)ptr(args[0]))) { jl_value_t *v = *(jl_value_t**)cptr(args[0]); - if (jl_isa(v,(jl_value_t*)jl_number_type) || jl_is_string(v)) + if (jl_is_number(v) || jl_is_string(v)) return fl_ctx->T; } return fl_ctx->F; @@ -198,7 +206,7 @@ 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 { "current-julia-module-counter", fl_current_module_counter }, - { "julia-scalar?", fl_julia_scalar }, // TODO: can we kill this safepoint? (from jl_isa) + { "julia-scalar?", fl_julia_scalar }, { "julia-current-file", fl_julia_current_file }, { "julia-current-line", fl_julia_current_line }, { NULL, NULL } @@ -319,7 +327,6 @@ void jl_init_common_symbols(void) jl_newvar_sym = jl_symbol("newvar"); jl_copyast_sym = jl_symbol("copyast"); jl_loopinfo_sym = jl_symbol("loopinfo"); - jl_pure_sym = jl_symbol("pure"); jl_meta_sym = jl_symbol("meta"); jl_list_sym = jl_symbol("list"); jl_unused_sym = jl_symbol("#unused#"); @@ -497,6 +504,13 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m return jl_true; else if (hd == jl_ast_ctx(fl_ctx)->false_sym && llength(e) == 1) return jl_false; + else if (hd == fl_ctx->jl_char_sym && llength(e) == 2) { + value_t v = car_(cdr_(e)); + if (!(iscprim(v) && cp_class((cprim_t*)ptr(v)) == fl_ctx->uint32type)) + jl_error("malformed julia char"); + uint32_t c = *(uint32_t*)cp_data((cprim_t*)ptr(v)); + return jl_box_char(c); + } } if (issymbol(hd)) sym = scmsym_to_julia(fl_ctx, hd); @@ -672,8 +686,8 @@ static value_t julia_to_scm_noalloc2(fl_context_t *fl_ctx, jl_value_t *v, int ch if (check_valid) { if (jl_is_ssavalue(v)) lerror(fl_ctx, symbol(fl_ctx, "error"), "SSAValue objects should not occur in an AST"); - if (jl_is_slot(v)) - lerror(fl_ctx, symbol(fl_ctx, "error"), "Slot objects should not occur in an AST"); + if (jl_is_slotnumber(v)) + lerror(fl_ctx, symbol(fl_ctx, "error"), "SlotNumber objects should not occur in an AST"); } value_t opaque = cvalue(fl_ctx, jl_ast_ctx(fl_ctx)->jvtype, sizeof(void*)); *(jl_value_t**)cv_data((cvalue_t*)ptr(opaque)) = v; @@ -686,11 +700,11 @@ static value_t julia_to_scm_noalloc(fl_context_t *fl_ctx, jl_value_t *v, int che if (julia_to_scm_noalloc1(fl_ctx, v, &retval)) return retval; assert(!jl_is_expr(v) && - !jl_typeis(v, jl_linenumbernode_type) && - !jl_typeis(v, jl_gotonode_type) && - !jl_typeis(v, jl_quotenode_type) && - !jl_typeis(v, jl_newvarnode_type) && - !jl_typeis(v, jl_globalref_type)); + !jl_typetagis(v, jl_linenumbernode_type) && + !jl_typetagis(v, jl_gotonode_type) && + !jl_typetagis(v, jl_quotenode_type) && + !jl_typetagis(v, jl_newvarnode_type) && + !jl_typetagis(v, jl_globalref_type)); return julia_to_scm_noalloc2(fl_ctx, v, check_valid); } @@ -731,7 +745,7 @@ static value_t julia_to_scm_(fl_context_t *fl_ctx, jl_value_t *v, int check_vali // GC Note: jl_fieldref(v, 0) allocates for GotoNode // but we don't need a GC root here because julia_to_list2_noalloc // shouldn't allocate in this case. - if (jl_typeis(v, jl_linenumbernode_type)) { + if (jl_typetagis(v, jl_linenumbernode_type)) { jl_value_t *file = jl_fieldref_noalloc(v,1); jl_value_t *line = jl_fieldref(v,0); value_t args = julia_to_list2_noalloc(fl_ctx, line, file, check_valid); @@ -741,13 +755,13 @@ static value_t julia_to_scm_(fl_context_t *fl_ctx, jl_value_t *v, int check_vali fl_free_gc_handles(fl_ctx, 1); return scmv; } - if (jl_typeis(v, jl_gotonode_type)) + if (jl_typetagis(v, jl_gotonode_type)) return julia_to_list2_noalloc(fl_ctx, (jl_value_t*)jl_goto_sym, jl_fieldref(v,0), check_valid); - if (jl_typeis(v, jl_quotenode_type)) + if (jl_typetagis(v, jl_quotenode_type)) return julia_to_list2(fl_ctx, (jl_value_t*)jl_inert_sym, jl_fieldref_noalloc(v,0), 0); - if (jl_typeis(v, jl_newvarnode_type)) + if (jl_typetagis(v, jl_newvarnode_type)) return julia_to_list2_noalloc(fl_ctx, (jl_value_t*)jl_newvar_sym, jl_fieldref(v,0), check_valid); - if (jl_typeis(v, jl_globalref_type)) { + if (jl_typetagis(v, jl_globalref_type)) { jl_module_t *m = jl_globalref_mod(v); jl_sym_t *sym = jl_globalref_name(v); if (m == jl_core_module) @@ -769,7 +783,8 @@ JL_DLLEXPORT jl_value_t *jl_fl_parse(const char *text, size_t text_len, jl_value_t *filename, size_t lineno, size_t offset, jl_value_t *options) { - JL_TIMING(PARSING); + JL_TIMING(PARSING, PARSING); + jl_timing_show_filename(jl_string_data(filename), JL_TIMING_CURRENT_BLOCK); if (offset > text_len) { jl_value_t *textstr = jl_pchar_to_string(text, text_len); JL_GC_PUSH1(&textstr); @@ -916,7 +931,7 @@ JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr) jl_array_t *values = (jl_array_t*)jl_fieldref_noalloc(expr, 0); JL_GC_PUSH1(&values); values = jl_array_copy(values); - jl_value_t *ret = jl_new_struct(jl_phinode_type, values); + jl_value_t *ret = jl_new_struct(jl_phicnode_type, values); JL_GC_POP(); return ret; } @@ -986,7 +1001,7 @@ int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, size_t world, int throw_load_error) { jl_task_t *ct = jl_current_task; - JL_TIMING(MACRO_INVOCATION); + JL_TIMING(MACRO_INVOCATION, MACRO_INVOCATION); size_t nargs = jl_array_len(args) + 1; JL_NARGSV("macrocall", 3); // macro name, location, and module jl_value_t **margs; @@ -996,7 +1011,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule // __source__ argument jl_value_t *lno = jl_array_ptr_ref(args, 1); margs[1] = lno; - if (!jl_typeis(lno, jl_linenumbernode_type)) { + if (!jl_typetagis(lno, jl_linenumbernode_type)) { margs[1] = jl_new_struct(jl_linenumbernode_type, jl_box_long(0), jl_nothing); } margs[2] = (jl_value_t*)inmodule; @@ -1010,10 +1025,10 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule jl_value_t *result; JL_TRY { margs[0] = jl_toplevel_eval(*ctx, margs[0]); - jl_method_instance_t *mfunc = jl_method_lookup(margs, nargs, world); + jl_method_instance_t *mfunc = jl_method_lookup(margs, nargs, ct->world_age); JL_GC_PROMISE_ROOTED(mfunc); if (mfunc == NULL) { - jl_method_error(margs[0], &margs[1], nargs, world); + jl_method_error(margs[0], &margs[1], nargs, ct->world_age); // unreachable } *ctx = mfunc->def.method->module; @@ -1125,7 +1140,7 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str JL_DLLEXPORT jl_value_t *jl_macroexpand(jl_value_t *expr, jl_module_t *inmodule) { - JL_TIMING(LOWERING); + JL_TIMING(LOWERING, LOWERING); JL_GC_PUSH1(&expr); expr = jl_copy_ast(expr); expr = jl_expand_macros(expr, inmodule, NULL, 0, jl_atomic_load_acquire(&jl_world_counter), 0); @@ -1136,7 +1151,7 @@ JL_DLLEXPORT jl_value_t *jl_macroexpand(jl_value_t *expr, jl_module_t *inmodule) JL_DLLEXPORT jl_value_t *jl_macroexpand1(jl_value_t *expr, jl_module_t *inmodule) { - JL_TIMING(LOWERING); + JL_TIMING(LOWERING, LOWERING); JL_GC_PUSH1(&expr); expr = jl_copy_ast(expr); expr = jl_expand_macros(expr, inmodule, NULL, 1, jl_atomic_load_acquire(&jl_world_counter), 0); @@ -1162,7 +1177,7 @@ JL_DLLEXPORT jl_value_t *jl_expand_with_loc(jl_value_t *expr, jl_module_t *inmod JL_DLLEXPORT jl_value_t *jl_expand_in_world(jl_value_t *expr, jl_module_t *inmodule, const char *file, int line, size_t world) { - JL_TIMING(LOWERING); + JL_TIMING(LOWERING, LOWERING); JL_GC_PUSH1(&expr); expr = jl_copy_ast(expr); expr = jl_expand_macros(expr, inmodule, NULL, 0, world, 1); @@ -1175,7 +1190,7 @@ JL_DLLEXPORT jl_value_t *jl_expand_in_world(jl_value_t *expr, jl_module_t *inmod JL_DLLEXPORT jl_value_t *jl_expand_with_loc_warn(jl_value_t *expr, jl_module_t *inmodule, const char *file, int line) { - JL_TIMING(LOWERING); + JL_TIMING(LOWERING, LOWERING); jl_array_t *kwargs = NULL; JL_GC_PUSH2(&expr, &kwargs); expr = jl_copy_ast(expr); @@ -1223,7 +1238,7 @@ JL_DLLEXPORT jl_value_t *jl_expand_with_loc_warn(jl_value_t *expr, jl_module_t * JL_DLLEXPORT jl_value_t *jl_expand_stmt_with_loc(jl_value_t *expr, jl_module_t *inmodule, const char *file, int line) { - JL_TIMING(LOWERING); + JL_TIMING(LOWERING, LOWERING); JL_GC_PUSH1(&expr); expr = jl_copy_ast(expr); expr = jl_expand_macros(expr, inmodule, NULL, 0, ~(size_t)0, 1); @@ -1244,8 +1259,8 @@ JL_DLLEXPORT jl_value_t *jl_expand_stmt(jl_value_t *expr, jl_module_t *inmodule) // Internal C entry point to parser // `text` is passed as a pointer to allow raw non-String buffers to be used // without copying. -JL_DLLEXPORT jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, - size_t lineno, size_t offset, jl_value_t *options) +jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, + size_t lineno, size_t offset, jl_value_t *options) { jl_value_t *core_parse = NULL; if (jl_core_module) { diff --git a/src/ast.scm b/src/ast.scm index 688b4e852e7c4..88220c03a7aa6 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -79,13 +79,15 @@ ((char? e) (string "'" e "'")) ((atom? e) (string e)) ((eq? (car e) '|.|) - (string (deparse (cadr e)) '|.| - (cond ((and (pair? (caddr e)) (memq (caaddr e) '(quote inert))) - (deparse-colon-dot (cadr (caddr e)))) - ((and (pair? (caddr e)) (eq? (caaddr e) 'copyast)) - (deparse-colon-dot (cadr (cadr (caddr e))))) - (else - (string #\( (deparse (caddr e)) #\)))))) + (if (length= e 2) + (string "(." (deparse (cadr e)) ")") + (string (deparse (cadr e)) '|.| + (cond ((and (pair? (caddr e)) (memq (caaddr e) '(quote inert))) + (deparse-colon-dot (cadr (caddr e)))) + ((and (pair? (caddr e)) (eq? (caaddr e) 'copyast)) + (deparse-colon-dot (cadr (cadr (caddr e))))) + (else + (string #\( (deparse (caddr e)) #\))))))) ((memq (car e) '(... |'|)) (string (deparse (cadr e)) (car e))) ((or (syntactic-op? (car e)) (eq? (car e) '|<:|) (eq? (car e) '|>:|) (eq? (car e) '-->)) @@ -177,7 +179,7 @@ (cdr e) (list e))) (deparse-block (string (car e) " " (deparse (cadr e))) - (block-stmts (caddr e)) + (if (null? (cddr e)) '() (block-stmts (caddr e))) ilvl)) ((return) (string "return " (deparse (cadr e)))) ((break continue) (string (car e))) @@ -224,13 +226,13 @@ "")) "") (string.rep " " ilvl) "end")) - ((do) - (let ((call (cadr e)) - (args (cdr (cadr (caddr e)))) - (body (caddr (caddr e)))) - (deparse-block (string (deparse call) " do" (if (null? args) "" " ") - (deparse-arglist args)) - (cdr body) ilvl))) + ((do) + (let ((call (cadr e)) + (args (cdr (cadr (caddr e)))) + (body (caddr (caddr e)))) + (deparse-block (string (deparse call) " do" (if (null? args) "" " ") + (deparse-arglist args)) + (cdr body) ilvl))) ((struct) (string (if (equal? (cadr e) '(true)) "mutable " "") "struct " @@ -327,8 +329,8 @@ (else (case (car v) ((...) - (arg-name (cadr v)) ;; to check for errors - (decl-var (cadr v))) + (arg-name (cadr v)) ;; to check for errors + (decl-var (cadr v))) ((|::|) (if (not (symbol? (cadr v))) (bad-formal-argument (cadr v))) @@ -445,7 +447,7 @@ (if (dotop-named? e) (error (string "invalid function name \"" (deparse e) "\"")) (if (pair? e) - (if (eq? (car e) '|.|) + (if (and (eq? (car e) '|.|) (length= e 3)) (check-dotop (caddr e)) (if (quoted? e) (check-dotop (cadr e)))))) @@ -468,9 +470,6 @@ (define (make-assignment l r) `(= ,l ,r)) (define (assignment? e) (and (pair? e) (eq? (car e) '=))) (define (return? e) (and (pair? e) (eq? (car e) 'return))) -(define (complex-return? e) (and (return? e) - (let ((x (cadr e))) - (not (simple-atom? x))))) (define (tuple-call? e) (and (length> e 1) @@ -524,6 +523,21 @@ (and (if one (length= e 3) (length> e 2)) (eq? (car e) 'meta) (memq (cadr e) '(nospecialize specialize)))) +(define (meta? e) + (and (length> e 1) (eq? (car e) 'meta))) + +(define (method-meta-sym? x) + (memq x '(inline noinline aggressive_constprop no_constprop propagate_inbounds))) + +(define (propagate-method-meta e) + `(meta ,@(filter (lambda (x) + (or (method-meta-sym? x) + (and (pair? x) (eq? (car x) 'purity)))) + (cdr e)))) + +(define (argwide-nospecialize-meta? e) + (and (length= e 2) (eq? (car e) 'meta) (memq (cadr e) '(nospecialize specialize)))) + (define (if-generated? e) (and (length= e 4) (eq? (car e) 'if) (equal? (cadr e) '(generated)))) diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 7b11813e7a58b..64e3fbd1af366 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -55,13 +55,13 @@ 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_invoke_kwsorter); -#ifdef DEFINE_BUILTIN_GLOBALS -JL_DLLEXPORT jl_fptr_args_t jl_f_invoke_kwsorter_addr = &jl_f_invoke_kwsorter; -#else -JL_DLLEXPORT extern jl_fptr_args_t jl_f_invoke_kwsorter_addr; -#endif JL_CALLABLE(jl_f__structtype); JL_CALLABLE(jl_f__abstracttype); JL_CALLABLE(jl_f__primitivetype); @@ -69,8 +69,8 @@ JL_CALLABLE(jl_f__setsuper); JL_CALLABLE(jl_f__equiv_typedef); JL_CALLABLE(jl_f_get_binding_type); JL_CALLABLE(jl_f_set_binding_type); -JL_CALLABLE(jl_f_donotdelete); - +JL_CALLABLE(jl_f__compute_sparams); +JL_CALLABLE(jl_f__svec_ref); #ifdef __cplusplus } #endif diff --git a/src/builtins.c b/src/builtins.c index ca2f56adaf6d8..a6c904c851c95 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -35,8 +35,8 @@ extern "C" { static int bits_equal(const void *a, const void *b, int sz) JL_NOTSAFEPOINT { switch (sz) { - case 1: return *(int8_t*)a == *(int8_t*)b; - // Let compiler constant folds the following. + case 1: return *(uint8_t*)a == *(uint8_t*)b; + // Let compiler constant folds the following, though we may not know alignment of them case 2: return memcmp(a, b, 2) == 0; case 4: return memcmp(a, b, 4) == 0; case 8: return memcmp(a, b, 8) == 0; @@ -94,24 +94,29 @@ static int NOINLINE compare_fields(const jl_value_t *a, const jl_value_t *b, jl_ else { jl_datatype_t *ft = (jl_datatype_t*)jl_field_type_concrete(dt, f); if (jl_is_uniontype(ft)) { - uint8_t asel = ((uint8_t*)ao)[jl_field_size(dt, f) - 1]; - uint8_t bsel = ((uint8_t*)bo)[jl_field_size(dt, f) - 1]; + size_t idx = jl_field_size(dt, f) - 1; + uint8_t asel = ((uint8_t*)ao)[idx]; + uint8_t bsel = ((uint8_t*)bo)[idx]; if (asel != bsel) return 0; ft = (jl_datatype_t*)jl_nth_union_component((jl_value_t*)ft, asel); } else if (ft->layout->first_ptr >= 0) { - // If the field is a inline immutable that can be can be undef - // we need to check to check for undef first since undef struct + // If the field is a inline immutable that can be undef + // we need to check for undef first since undef struct // may have fields that are different but should still be treated as equal. - jl_value_t *ptra = ((jl_value_t**)ao)[ft->layout->first_ptr]; - jl_value_t *ptrb = ((jl_value_t**)bo)[ft->layout->first_ptr]; - if (ptra == NULL && ptrb == NULL) { - return 1; + int32_t idx = ft->layout->first_ptr; + jl_value_t *ptra = ((jl_value_t**)ao)[idx]; + jl_value_t *ptrb = ((jl_value_t**)bo)[idx]; + if ((ptra == NULL) != (ptrb == NULL)) { + return 0; + } + else if (ptra == NULL) { // implies ptrb == NULL + continue; // skip this field (it is #undef) } } if (!ft->layout->haspadding) { - if (!bits_equal(ao, bo, ft->size)) + if (!bits_equal(ao, bo, ft->layout->size)) return 0; } else { @@ -142,10 +147,10 @@ static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *en { if (a == b) return 1; - jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(a); - if (dt != (jl_datatype_t*)jl_typeof(b)) + uintptr_t dtag = jl_typetagof(a); + if (dtag != jl_typetagof(b)) return 0; - if (dt == jl_datatype_type) { + if (dtag == jl_datatype_tag << 4) { jl_datatype_t *dta = (jl_datatype_t*)a; jl_datatype_t *dtb = (jl_datatype_t*)b; if (dta->name != dtb->name) @@ -159,7 +164,7 @@ static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *en } return 1; } - if (dt == jl_tvar_type) { + if (dtag == jl_tvar_tag << 4) { jl_typeenv_t *pe = env; while (pe != NULL) { if (pe->var == (jl_tvar_t*)a) @@ -168,7 +173,7 @@ static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *en } return 0; } - if (dt == jl_unionall_type) { + if (dtag == jl_unionall_tag << 4) { jl_unionall_t *ua = (jl_unionall_t*)a; jl_unionall_t *ub = (jl_unionall_t*)b; if (tvar_names && ua->var->name != ub->var->name) @@ -178,11 +183,11 @@ static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *en jl_typeenv_t e = { ua->var, (jl_value_t*)ub->var, env }; return egal_types(ua->body, ub->body, &e, tvar_names); } - if (dt == jl_uniontype_type) { + if (dtag == jl_uniontype_tag << 4) { return egal_types(((jl_uniontype_t*)a)->a, ((jl_uniontype_t*)b)->a, env, tvar_names) && egal_types(((jl_uniontype_t*)a)->b, ((jl_uniontype_t*)b)->b, env, tvar_names); } - if (dt == jl_vararg_type) { + if (dtag == jl_vararg_tag << 4) { jl_vararg_t *vma = (jl_vararg_t*)a; jl_vararg_t *vmb = (jl_vararg_t*)b; jl_value_t *vmaT = vma->T ? vma->T : (jl_value_t*)jl_any_type; @@ -193,10 +198,8 @@ static int egal_types(const jl_value_t *a, const jl_value_t *b, jl_typeenv_t *en return egal_types(vma->N, vmb->N, env, tvar_names); return !vma->N && !vmb->N; } - if (dt == jl_symbol_type) - return 0; - assert(!dt->name->mutabl); - return jl_egal__bits(a, b, dt); + assert(dtag == jl_symbol_tag << 4 || dtag == jl_module_tag << 4 || !((jl_datatype_t*)jl_typeof(a))->name->mutabl); + return jl_egal__bitstag(a, b, dtag); } JL_DLLEXPORT int jl_types_egal(jl_value_t *a, jl_value_t *b) @@ -210,36 +213,72 @@ JL_DLLEXPORT int (jl_egal)(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value return jl_egal(a, b); } -JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT +JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, uintptr_t dtag) JL_NOTSAFEPOINT { // warning: a,b may NOT have been gc-rooted by the caller - return jl_egal__unboxed_(a, b, dt); -} - -int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT -{ - if (dt == jl_simplevector_type) - return compare_svec((jl_svec_t*)a, (jl_svec_t*)b); - if (dt == jl_datatype_type) { - jl_datatype_t *dta = (jl_datatype_t*)a; - jl_datatype_t *dtb = (jl_datatype_t*)b; - if (dta->name != dtb->name) - return 0; - if (dta->name != jl_tuple_typename && (dta->isconcretetype || dtb->isconcretetype)) - return 0; - return compare_svec(dta->parameters, dtb->parameters); - } - if (dt == jl_string_type) { - size_t l = jl_string_len(a); - if (jl_string_len(b) != l) + return jl_egal__unboxed_(a, b, dtag); +} + +JL_DLLEXPORT int jl_egal__bitstag(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, uintptr_t dtag) JL_NOTSAFEPOINT +{ + if (dtag < jl_max_tags << 4) { + switch ((enum jlsmall_typeof_tags)(dtag >> 4)) { + case jl_int8_tag: + case jl_uint8_tag: + return *(uint8_t*)a == *(uint8_t*)b; + case jl_int16_tag: + case jl_uint16_tag: + return *(uint16_t*)a == *(uint16_t*)b; + case jl_int32_tag: + case jl_uint32_tag: + case jl_char_tag: + return *(uint32_t*)a == *(uint32_t*)b; + case jl_int64_tag: + case jl_uint64_tag: + return *(uint64_t*)a == *(uint64_t*)b; + case jl_unionall_tag: + return egal_types(a, b, NULL, 1); + case jl_uniontype_tag: + return compare_fields(a, b, jl_uniontype_type); + case jl_vararg_tag: + return compare_fields(a, b, jl_vararg_type); + case jl_task_tag: + case jl_tvar_tag: + case jl_symbol_tag: + case jl_module_tag: + case jl_bool_tag: return 0; - return !memcmp(jl_string_data(a), jl_string_data(b), l); + case jl_simplevector_tag: + return compare_svec((jl_svec_t*)a, (jl_svec_t*)b); + case jl_string_tag: { + size_t l = jl_string_len(a); + if (jl_string_len(b) != l) + return 0; + return !memcmp(jl_string_data(a), jl_string_data(b), l); + } + case jl_datatype_tag: { + jl_datatype_t *dta = (jl_datatype_t*)a; + jl_datatype_t *dtb = (jl_datatype_t*)b; + if (dta->name != dtb->name) + return 0; + if (dta->name != jl_tuple_typename && (dta->isconcretetype || dtb->isconcretetype)) + return 0; + return compare_svec(dta->parameters, dtb->parameters); + } +#ifndef NDEBUG + default: +#endif + case jl_max_tags: + case jl_null_tag: + case jl_typeofbottom_tag: + case jl_tags_count: + abort(); + } } - assert(0 && "unreachable"); - return 0; + return jl_egal__bits(a, b, (jl_datatype_t*)dtag); } -int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT +inline int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT { size_t sz = jl_datatype_size(dt); if (sz == 0) @@ -247,8 +286,6 @@ int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_ size_t nf = jl_datatype_nfields(dt); if (nf == 0 || !dt->layout->haspadding) return bits_equal(a, b, sz); - if (dt == jl_unionall_type) - return egal_types(a, b, NULL, 1); return compare_fields(a, b, dt); } @@ -342,6 +379,8 @@ static uintptr_t type_object_id_(jl_value_t *v, jl_varidx_t *env) JL_NOTSAFEPOIN } if (tv == jl_symbol_type) return ((jl_sym_t*)v)->hash; + if (tv == jl_module_type) + return ((jl_module_t*)v)->hash; assert(!tv->name->mutabl); return immut_id_(tv, v, tv->hash); } @@ -409,6 +448,10 @@ static uintptr_t NOINLINE jl_object_id__cold(jl_datatype_t *dt, jl_value_t *v) J 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) return inthash((uintptr_t)v); return immut_id_(dt, v, dt->hash); @@ -564,7 +607,7 @@ STATIC_INLINE void _grow_to(jl_value_t **root, jl_value_t ***oldargs, jl_svec_t *n_alloc = newalloc; } -static jl_value_t *do_apply( jl_value_t **args, uint32_t nargs, jl_value_t *iterate) +static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *iterate) { jl_function_t *f = args[0]; if (nargs == 2) { @@ -888,10 +931,14 @@ static inline size_t get_checked_fieldindex(const char *name, jl_datatype_t *st, if (idx >= jl_datatype_nfields(st)) jl_bounds_error(v, arg); } - else { - JL_TYPECHKS(name, symbol, arg); + else if (jl_is_symbol(arg)) { idx = jl_field_index(st, (jl_sym_t*)arg, 1); } + else { + jl_value_t *ts[2] = {(jl_value_t*)jl_long_type, (jl_value_t*)jl_symbol_type}; + jl_value_t *t = jl_type_union(ts, 2); + jl_type_error("getfield", t, arg); + } 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)); @@ -916,22 +963,18 @@ JL_CALLABLE(jl_f_getfield) } jl_value_t *v = args[0]; jl_value_t *vt = jl_typeof(v); - if (vt == (jl_value_t*)jl_module_type) { - JL_TYPECHK(getfield, symbol, args[1]); - v = jl_eval_global_var((jl_module_t*)v, (jl_sym_t*)args[1]); // is seq_cst already - } - else { - jl_datatype_t *st = (jl_datatype_t*)vt; - size_t idx = get_checked_fieldindex("getfield", st, v, args[1], 0); - int isatomic = jl_field_isatomic(st, idx); - if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) - 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 (vt == (jl_value_t*)jl_module_type) + return jl_f_getglobal(NULL, args, 2); // we just ignore the atomic order and boundschecks + jl_datatype_t *st = (jl_datatype_t*)vt; + size_t idx = get_checked_fieldindex("getfield", st, v, args[1], 0); + int isatomic = jl_field_isatomic(st, idx); + if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) + 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 return v; } @@ -940,7 +983,7 @@ JL_CALLABLE(jl_f_setfield) enum jl_memory_order order = jl_memory_order_notatomic; JL_NARGS(setfield!, 3, 4); if (nargs == 4) { - JL_TYPECHK(getfield, symbol, args[3]); + JL_TYPECHK(setfield!, symbol, args[3]); order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); } jl_value_t *v = args[0]; @@ -1175,26 +1218,121 @@ JL_CALLABLE(jl_f_isdefined) } +// module bindings + +JL_CALLABLE(jl_f_getglobal) +{ + enum jl_memory_order order = jl_memory_order_monotonic; + JL_NARGS(getglobal, 2, 3); + if (nargs == 3) { + JL_TYPECHK(getglobal, symbol, args[2]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 0); + } + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *sym = (jl_sym_t*)args[1]; + JL_TYPECHK(getglobal, module, (jl_value_t*)mod); + 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 + return v; +} + +JL_CALLABLE(jl_f_setglobal) +{ + enum jl_memory_order order = jl_memory_order_release; + JL_NARGS(setglobal!, 3, 4); + if (nargs == 4) { + JL_TYPECHK(setglobal!, symbol, args[3]); + order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); + } + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(setglobal!, module, (jl_value_t*)mod); + 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 + jl_binding_t *b = jl_get_binding_wr(mod, var); + jl_checked_assignment(b, mod, var, args[2]); + return args[2]; +} + +JL_CALLABLE(jl_f_get_binding_type) +{ + JL_NARGS(get_binding_type, 2, 2); + jl_module_t *mod = (jl_module_t*)args[0]; + jl_sym_t *var = (jl_sym_t*)args[1]; + JL_TYPECHK(get_binding_type, module, (jl_value_t*)mod); + JL_TYPECHK(get_binding_type, symbol, (jl_value_t*)var); + jl_value_t *ty = jl_get_binding_type(mod, var); + if (ty == (jl_value_t*)jl_nothing) { + jl_binding_t *b = jl_get_module_binding(mod, var, 0); + if (b == NULL) + return (jl_value_t*)jl_any_type; + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b) + return (jl_value_t*)jl_any_type; + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + return jl_atomic_load_relaxed(&b->ty); + } + return ty; +} + +JL_CALLABLE(jl_f_set_binding_type) +{ + JL_NARGS(set_binding_type!, 2, 3); + jl_module_t *m = (jl_module_t*)args[0]; + jl_sym_t *s = (jl_sym_t*)args[1]; + JL_TYPECHK(set_binding_type!, module, (jl_value_t*)m); + JL_TYPECHK(set_binding_type!, symbol, (jl_value_t*)s); + jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; + 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; + 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; +} + + // apply_type ----------------------------------------------------------------- -int jl_valid_type_param(jl_value_t *v) +static int is_nestable_type_param(jl_value_t *t) { - if (jl_is_tuple(v)) { + if (jl_is_namedtuple_type(t)) + t = jl_tparam1(t); + if (jl_is_tuple_type(t)) { // NOTE: tuples of symbols are not currently bits types, but have been // allowed as type parameters. this is a bit ugly. - jl_value_t *tt = jl_typeof(v); - size_t i, l = jl_nparams(tt); - for(i=0; i < l; i++) { - jl_value_t *pi = jl_tparam(tt,i); - if (!(pi == (jl_value_t*)jl_symbol_type || jl_isbits(pi))) + size_t i, l = jl_nparams(t); + for (i = 0; i < l; i++) { + jl_value_t *pi = jl_tparam(t, i); + if (!(pi == (jl_value_t*)jl_symbol_type || jl_isbits(pi) || is_nestable_type_param(pi) || + jl_is_module(pi))) return 0; } return 1; } + return 0; +} + +int jl_valid_type_param(jl_value_t *v) +{ + if (jl_is_tuple(v) || jl_is_namedtuple(v)) + return is_nestable_type_param(jl_typeof(v)); if (jl_is_vararg(v)) return 0; // TODO: maybe more things - return jl_is_type(v) || jl_is_typevar(v) || jl_is_symbol(v) || jl_isbits(jl_typeof(v)); + return jl_is_type(v) || jl_is_typevar(v) || jl_is_symbol(v) || jl_isbits(jl_typeof(v)) || + jl_is_module(v); } JL_CALLABLE(jl_f_apply_type) @@ -1214,7 +1352,7 @@ JL_CALLABLE(jl_f_apply_type) jl_type_error_rt("Tuple", "parameter", (jl_value_t*)jl_type_type, pi); } } - return (jl_value_t*)jl_apply_tuple_type_v(&args[1], nargs-1); + return jl_apply_tuple_type_v(&args[1], nargs-1); } else if (args[0] == (jl_value_t*)jl_uniontype_type) { // Union{} has extra restrictions, so it needs to be checked after @@ -1270,50 +1408,6 @@ JL_CALLABLE(jl_f_invoke) return res; } -JL_CALLABLE(jl_f_invoke_kwsorter) -{ - JL_NARGSV(invoke, 3); - jl_value_t *kwargs = args[0]; - // args[1] is `invoke` itself - jl_value_t *func = args[2]; - jl_value_t *argtypes = args[3]; - jl_value_t *kws = jl_get_keyword_sorter(func); - JL_GC_PUSH1(&argtypes); - if (jl_is_tuple_type(argtypes)) { - // construct a tuple type for invoking a keyword sorter by putting the kw container type - // and the type of the function at the front. - size_t i, nt = jl_nparams(argtypes) + 2; - if (nt < jl_page_size/sizeof(jl_value_t*)) { - jl_value_t **types = (jl_value_t**)alloca(nt*sizeof(jl_value_t*)); - types[0] = (jl_value_t*)jl_namedtuple_type; - types[1] = jl_typeof(func); - for (i = 2; i < nt; i++) - types[i] = jl_tparam(argtypes, i - 2); - argtypes = (jl_value_t*)jl_apply_tuple_type_v(types, nt); - } - else { - jl_svec_t *types = jl_alloc_svec_uninit(nt); - JL_GC_PUSH1(&types); - jl_svecset(types, 0, jl_namedtuple_type); - jl_svecset(types, 1, jl_typeof(func)); - for (i = 2; i < nt; i++) - jl_svecset(types, i, jl_tparam(argtypes, i - 2)); - argtypes = (jl_value_t*)jl_apply_tuple_type(types); - JL_GC_POP(); - } - } - else { - // invoke will throw an error - } - args[0] = kws; - args[1] = argtypes; - args[2] = kwargs; - args[3] = func; - jl_value_t *res = jl_f_invoke(NULL, args, nargs); - JL_GC_POP(); - return res; -} - // Expr constructor for internal use ------------------------------------------ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n) @@ -1355,6 +1449,7 @@ JL_DLLEXPORT jl_tvar_t *jl_new_typevar(jl_sym_t *name, jl_value_t *lb, jl_value_ jl_type_error_rt("TypeVar", "upper bound", (jl_value_t *)jl_type_type, ub); jl_task_t *ct = jl_current_task; jl_tvar_t *tv = (jl_tvar_t *)jl_gc_alloc(ct->ptls, sizeof(jl_tvar_t), jl_tvar_type); + jl_set_typetagof(tv, jl_tvar_tag, 0); tv->name = name; tv->lb = lb; tv->ub = ub; @@ -1486,16 +1581,25 @@ JL_CALLABLE(jl_f__primitivetype) static void jl_set_datatype_super(jl_datatype_t *tt, jl_value_t *super) { - if (!jl_is_datatype(super) || !jl_is_abstracttype(super) || - tt->super != NULL || - tt->name == ((jl_datatype_t*)super)->name || - jl_is_tuple_type(super) || - jl_is_namedtuple_type(super) || - jl_subtype(super, (jl_value_t*)jl_type_type) || - jl_subtype(super, (jl_value_t*)jl_builtin_type)) { - jl_errorf("invalid subtyping in definition of %s", - jl_symbol_name(tt->name->name)); - } + const char *error = NULL; + if (!jl_is_datatype(super)) + error = "can only subtype data types"; + else if (tt->super != NULL) + error = "type already has a supertype"; + else if (tt->name == ((jl_datatype_t*)super)->name) + error = "a type cannot subtype itself"; + else if (jl_is_tuple_type(super)) + error = "cannot subtype a tuple type"; + else if (jl_is_namedtuple_type(super)) + error = "cannot subtype a named tuple type"; + else if (jl_subtype(super, (jl_value_t*)jl_type_type)) + error = "cannot add subtypes to Type"; + else if (jl_subtype(super, (jl_value_t*)jl_builtin_type)) + error = "cannot add subtypes to Core.Builtin"; + else if (!jl_is_abstracttype(super)) + error = "can only subtype abstract types"; + if (error) + jl_errorf("invalid subtyping in definition of %s: %s.", jl_symbol_name(tt->name->name), error); tt->super = (jl_datatype_t*)super; jl_gc_wb(tt, tt->super); } @@ -1514,6 +1618,58 @@ JL_CALLABLE(jl_f_donotdelete) return jl_nothing; } +JL_CALLABLE(jl_f_compilerbarrier) +{ + JL_NARGS(compilerbarrier, 2, 2); + JL_TYPECHK(compilerbarrier, symbol, args[0]) + jl_sym_t *setting = (jl_sym_t*)args[0]; + if (!(setting == jl_symbol("type") || + setting == jl_symbol("const") || + setting == jl_symbol("conditional"))) + jl_error("The first argument of `compilerbarrier` must be either of `:type`, `:const` or `:conditional`."); + jl_value_t *val = args[1]; + return val; +} + +JL_CALLABLE(jl_f_finalizer) +{ + // NOTE the compiler may temporarily insert additional argument for the later inlining pass + JL_NARGS(finalizer, 2, 4); + jl_task_t *ct = jl_current_task; + jl_gc_add_finalizer_(ct->ptls, args[1], args[0]); + return jl_nothing; +} + +JL_CALLABLE(jl_f__compute_sparams) +{ + JL_NARGSV(_compute_sparams, 1); + jl_method_t *m = (jl_method_t*)args[0]; + JL_TYPECHK(_compute_sparams, method, (jl_value_t*)m); + jl_datatype_t *tt = jl_inst_arg_tuple_type(args[1], &args[2], nargs-1, 1); + jl_svec_t *env = jl_emptysvec; + JL_GC_PUSH2(&env, &tt); + jl_type_intersection_env((jl_value_t*)tt, m->sig, &env); + JL_GC_POP(); + return (jl_value_t*)env; +} + +JL_CALLABLE(jl_f__svec_ref) +{ + JL_NARGS(_svec_ref, 3, 3); + jl_value_t *b = args[0]; + jl_svec_t *s = (jl_svec_t*)args[1]; + jl_value_t *i = (jl_value_t*)args[2]; + JL_TYPECHK(_svec_ref, bool, b); + JL_TYPECHK(_svec_ref, simplevector, (jl_value_t*)s); + JL_TYPECHK(_svec_ref, long, i); + size_t len = jl_svec_len(s); + ssize_t idx = jl_unbox_long(i); + if (idx < 1 || idx > len) { + jl_bounds_error_int((jl_value_t*)s, idx); + } + return jl_svec_ref(s, idx-1); +} + static int equiv_field_types(jl_value_t *old, jl_value_t *ft) { size_t nf = jl_svec_len(ft); @@ -1527,7 +1683,7 @@ static int equiv_field_types(jl_value_t *old, jl_value_t *ft) if (!jl_has_free_typevars(tb) || !jl_egal(ta, tb)) return 0; } - else if (jl_has_free_typevars(tb) || jl_typeof(ta) != jl_typeof(tb) || + else if (jl_has_free_typevars(tb) || jl_typetagof(ta) != jl_typetagof(tb) || !jl_types_equal(ta, tb)) { return 0; } @@ -1640,12 +1796,13 @@ static int equiv_type(jl_value_t *ta, jl_value_t *tb) if (!jl_is_datatype(dta)) return 0; jl_datatype_t *dtb = (jl_datatype_t*)jl_unwrap_unionall(tb); - if (!(jl_typeof(dta) == jl_typeof(dtb) && + if (!(jl_typetagof(dta) == jl_typetagof(dtb) && dta->name->name == dtb->name->name && dta->name->abstract == dtb->name->abstract && dta->name->mutabl == dtb->name->mutabl && dta->name->n_uninitialized == dtb->name->n_uninitialized && - (jl_svec_len(jl_field_names(dta)) != 0 || dta->size == dtb->size) && + dta->isprimitivetype == dtb->isprimitivetype && + (!dta->isprimitivetype || dta->layout->size == dtb->layout->size) && (dta->name->atomicfields == NULL ? dtb->name->atomicfields == NULL : (dtb->name->atomicfields != NULL && @@ -1697,44 +1854,6 @@ JL_CALLABLE(jl_f__equiv_typedef) return equiv_type(args[0], args[1]) ? jl_true : jl_false; } -JL_CALLABLE(jl_f_get_binding_type) -{ - JL_NARGS(get_binding_type, 2, 2); - JL_TYPECHK(get_binding_type, module, args[0]); - JL_TYPECHK(get_binding_type, symbol, args[1]); - jl_module_t *mod = (jl_module_t*)args[0]; - jl_sym_t *sym = (jl_sym_t*)args[1]; - jl_value_t *ty = jl_binding_type(mod, sym); - if (ty == (jl_value_t*)jl_nothing) { - jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); - if (b) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); - return jl_atomic_load_relaxed(&b->ty); - } - return (jl_value_t*)jl_any_type; - } - return ty; -} - -JL_CALLABLE(jl_f_set_binding_type) -{ - JL_NARGS(set_binding_type!, 2, 3); - JL_TYPECHK(set_binding_type!, module, args[0]); - JL_TYPECHK(set_binding_type!, symbol, args[1]); - jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; - JL_TYPECHK(set_binding_type!, type, ty); - jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1); - 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; - jl_errorf("cannot set type for global %s. It already has a value or is already set to a different type.", - jl_symbol_name(b->name)); - } - return jl_nothing; -} - // IntrinsicFunctions --------------------------------------------------------- static void (*runtime_fp[num_intrinsics])(void); @@ -1807,7 +1926,7 @@ static void add_intrinsic_properties(enum intrinsic f, unsigned nargs, void (*pf static void add_intrinsic(jl_module_t *inm, const char *name, enum intrinsic f) JL_GC_DISABLED { - jl_value_t *i = jl_permbox32(jl_intrinsic_type, (int32_t)f); + jl_value_t *i = jl_permbox32(jl_intrinsic_type, 0, (int32_t)f); jl_sym_t *sym = jl_symbol(name); jl_set_const(inm, sym, i); jl_module_export(inm, sym); @@ -1826,7 +1945,7 @@ void jl_init_intrinsic_properties(void) JL_GC_DISABLED void jl_init_intrinsic_functions(void) JL_GC_DISABLED { - jl_module_t *inm = jl_new_module(jl_symbol("Intrinsics")); + jl_module_t *inm = jl_new_module(jl_symbol("Intrinsics"), NULL); inm->parent = jl_core_module; jl_set_const(jl_core_module, jl_symbol("Intrinsics"), (jl_value_t*)inm); jl_mk_builtin_func(jl_intrinsic_type, "IntrinsicFunction", jl_f_intrinsic_call); @@ -1834,6 +1953,11 @@ void jl_init_intrinsic_functions(void) JL_GC_DISABLED (jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_opaque_closure_type), "OpaqueClosure", jl_f_opaque_closure_call); + // Save a reference to the just created OpaqueClosure method, so we can provide special + // codegen for it later. + jl_opaque_closure_method = (jl_method_t*)jl_methtable_lookup(jl_opaque_closure_typename->mt, + (jl_value_t*)jl_anytuple_type, 1); + #define ADD_I(name, nargs) add_intrinsic(inm, #name, name); #define ADD_HIDDEN(name, nargs) #define ALIAS ADD_I @@ -1884,6 +2008,12 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_nfields = add_builtin_func("nfields", jl_f_nfields); jl_builtin_isdefined = add_builtin_func("isdefined", jl_f_isdefined); + // module bindings + jl_builtin_getglobal = add_builtin_func("getglobal", jl_f_getglobal); + 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); @@ -1893,11 +2023,6 @@ void jl_init_primitives(void) JL_GC_DISABLED // method table utils jl_builtin_applicable = add_builtin_func("applicable", jl_f_applicable); jl_builtin_invoke = add_builtin_func("invoke", jl_f_invoke); - jl_typename_t *itn = ((jl_datatype_t*)jl_typeof(jl_builtin_invoke))->name; - jl_value_t *ikws = jl_new_generic_function_with_supertype(itn->name, jl_core_module, jl_builtin_type); - itn->mt->kwsorter = ikws; - jl_gc_wb(itn->mt, ikws); - jl_mk_builtin_func((jl_datatype_t*)jl_typeof(ikws), jl_symbol_name(jl_gf_name(ikws)), jl_f_invoke_kwsorter); // internal functions jl_builtin_apply_type = add_builtin_func("apply_type", jl_f_apply_type); @@ -1915,9 +2040,11 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin_func("_setsuper!", jl_f__setsuper); jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody); add_builtin_func("_equiv_typedef", jl_f__equiv_typedef); - add_builtin_func("get_binding_type", jl_f_get_binding_type); - add_builtin_func("set_binding_type!", jl_f_set_binding_type); jl_builtin_donotdelete = add_builtin_func("donotdelete", jl_f_donotdelete); + jl_builtin_compilerbarrier = add_builtin_func("compilerbarrier", jl_f_compilerbarrier); + 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); // builtin types add_builtin("Any", (jl_value_t*)jl_any_type); @@ -1933,6 +2060,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("Tuple", (jl_value_t*)jl_anytuple_type); add_builtin("TypeofVararg", (jl_value_t*)jl_vararg_type); add_builtin("SimpleVector", (jl_value_t*)jl_simplevector_type); + add_builtin("Vararg", (jl_value_t*)jl_wrap_vararg(NULL, NULL)); add_builtin("Module", (jl_value_t*)jl_module_type); add_builtin("MethodTable", (jl_value_t*)jl_methtable_type); @@ -1942,9 +2070,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("TypeMapLevel", (jl_value_t*)jl_typemap_level_type); add_builtin("Symbol", (jl_value_t*)jl_symbol_type); add_builtin("SSAValue", (jl_value_t*)jl_ssavalue_type); - add_builtin("Slot", (jl_value_t*)jl_abstractslot_type); add_builtin("SlotNumber", (jl_value_t*)jl_slotnumber_type); - add_builtin("TypedSlot", (jl_value_t*)jl_typedslot_type); add_builtin("Argument", (jl_value_t*)jl_argument_type); add_builtin("Const", (jl_value_t*)jl_const_type); add_builtin("PartialStruct", (jl_value_t*)jl_partial_struct_type); @@ -1978,15 +2104,17 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("UpsilonNode", (jl_value_t*)jl_upsilonnode_type); add_builtin("QuoteNode", (jl_value_t*)jl_quotenode_type); add_builtin("NewvarNode", (jl_value_t*)jl_newvarnode_type); + add_builtin("Binding", (jl_value_t*)jl_binding_type); add_builtin("GlobalRef", (jl_value_t*)jl_globalref_type); add_builtin("NamedTuple", (jl_value_t*)jl_namedtuple_type); add_builtin("Bool", (jl_value_t*)jl_bool_type); add_builtin("UInt8", (jl_value_t*)jl_uint8_type); - add_builtin("Int32", (jl_value_t*)jl_int32_type); - add_builtin("Int64", (jl_value_t*)jl_int64_type); + add_builtin("UInt16", (jl_value_t*)jl_uint16_type); add_builtin("UInt32", (jl_value_t*)jl_uint32_type); add_builtin("UInt64", (jl_value_t*)jl_uint64_type); + add_builtin("Int32", (jl_value_t*)jl_int32_type); + add_builtin("Int64", (jl_value_t*)jl_int64_type); #ifdef _P64 add_builtin("Int", (jl_value_t*)jl_int64_type); #else diff --git a/src/ccall.cpp b/src/ccall.cpp index 332c057afa5c4..90f7417c03524 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -2,47 +2,87 @@ // --- the ccall, cglobal, and llvm intrinsics --- +// Mark our stats as being from ccall +#undef DEBUG_TYPE +#define DEBUG_TYPE "julia_irgen_ccall" + +STATISTIC(RuntimeSymLookups, "Number of runtime symbol lookups emitted"); +STATISTIC(PLTThunks, "Number of PLT Thunks emitted"); +STATISTIC(PLT, "Number of direct PLT entries emitted"); +STATISTIC(EmittedCGlobals, "Number of C globals emitted"); +STATISTIC(EmittedLLVMCalls, "Number of llvmcall intrinsics emitted"); + +#define _CCALL_STAT(name) jl_transformed_ccall__##name +#define CCALL_STAT(name) _CCALL_STAT(name) +#define TRANSFORMED_CCALL_STAT(name) STATISTIC(_CCALL_STAT(name), "Number of " #name " ccalls intercepted") +TRANSFORMED_CCALL_STAT(jl_array_ptr); +TRANSFORMED_CCALL_STAT(jl_value_ptr); +TRANSFORMED_CCALL_STAT(jl_cpu_pause); +TRANSFORMED_CCALL_STAT(jl_cpu_wake); +TRANSFORMED_CCALL_STAT(jl_gc_safepoint); +TRANSFORMED_CCALL_STAT(jl_get_ptls_states); +TRANSFORMED_CCALL_STAT(jl_threadid); +TRANSFORMED_CCALL_STAT(jl_gc_enable_disable_finalizers_internal); +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(memcpy); +TRANSFORMED_CCALL_STAT(memset); +TRANSFORMED_CCALL_STAT(memmove); +TRANSFORMED_CCALL_STAT(jl_object_id); +#undef TRANSFORMED_CCALL_STAT + +STATISTIC(EmittedCCalls, "Number of ccalls emitted"); +STATISTIC(DeferredCCallLookups, "Number of ccalls looked up at runtime"); +STATISTIC(LiteralCCalls, "Number of ccalls directly emitted through a pointer"); +STATISTIC(RetBoxedCCalls, "Number of ccalls that were retboxed"); +STATISTIC(SRetCCalls, "Number of ccalls that were marked sret"); + // somewhat unusual variable, in that aotcompile wants to get the address of this for a sanity check GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M) { return prepare_global_in(M, jlRTLD_DEFAULT_var); } + // Find or create the GVs for the library and symbol lookup. // Return `runtime_lib` (whether the library name is a string) // The `lib` and `sym` GV returned may not be in the current module. -static bool runtime_sym_gvs(jl_codegen_params_t &emission_context, LLVMContext &ctxt, const char *f_lib, const char *f_name, +static bool runtime_sym_gvs(jl_codectx_t &ctx, const char *f_lib, const char *f_name, GlobalVariable *&lib, GlobalVariable *&sym) { - Module *M = emission_context.shared_module(ctxt); + auto M = &ctx.emission_context.shared_module(); bool runtime_lib = false; GlobalVariable *libptrgv; jl_codegen_params_t::SymMapGV *symMap; -#ifdef _OS_WINDOWS_ if ((intptr_t)f_lib == (intptr_t)JL_EXE_LIBNAME) { libptrgv = prepare_global_in(M, jlexe_var); - symMap = &emission_context.symMapExe; + symMap = &ctx.emission_context.symMapExe; } else if ((intptr_t)f_lib == (intptr_t)JL_LIBJULIA_INTERNAL_DL_LIBNAME) { libptrgv = prepare_global_in(M, jldlli_var); - symMap = &emission_context.symMapDlli; + symMap = &ctx.emission_context.symMapDlli; } else if ((intptr_t)f_lib == (intptr_t)JL_LIBJULIA_DL_LIBNAME) { libptrgv = prepare_global_in(M, jldll_var); - symMap = &emission_context.symMapDll; + symMap = &ctx.emission_context.symMapDll; } - else -#endif - if (f_lib == NULL) { + else if (f_lib == NULL) { libptrgv = jl_emit_RTLD_DEFAULT_var(M); - symMap = &emission_context.symMapDefault; + symMap = &ctx.emission_context.symMapDefault; } else { std::string name = "ccalllib_"; name += llvm::sys::path::filename(f_lib); - name += std::to_string(globalUnique++); + name += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); runtime_lib = true; - auto &libgv = emission_context.libMapGV[f_lib]; + auto &libgv = ctx.emission_context.libMapGV[f_lib]; if (libgv.first == NULL) { libptrgv = new GlobalVariable(*M, getInt8PtrTy(M->getContext()), false, GlobalVariable::ExternalLinkage, @@ -60,7 +100,7 @@ static bool runtime_sym_gvs(jl_codegen_params_t &emission_context, LLVMContext & std::string name = "ccall_"; name += f_name; name += "_"; - name += std::to_string(globalUnique++); + name += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); auto T_pvoidfunc = JuliaType::get_pvoidfunc_ty(M->getContext()); llvmgv = new GlobalVariable(*M, T_pvoidfunc, false, GlobalVariable::ExternalLinkage, @@ -81,6 +121,7 @@ static Value *runtime_sym_lookup( GlobalVariable *libptrgv, GlobalVariable *llvmgv, bool runtime_lib) { + ++RuntimeSymLookups; // in pseudo-code, this function emits the following: // global HMODULE *libptrgv // global void **llvmgv @@ -124,7 +165,7 @@ static Value *runtime_sym_lookup( } else { // f_lib is actually one of the special sentinel values - libname = ConstantExpr::getIntToPtr(ConstantInt::get(getSizeTy(irbuilder.getContext()), (uintptr_t)f_lib), getInt8PtrTy(irbuilder.getContext())); + libname = ConstantExpr::getIntToPtr(ConstantInt::get(emission_context.DL.getIntPtrType(irbuilder.getContext()), (uintptr_t)f_lib), getInt8PtrTy(irbuilder.getContext())); } llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jldlsym_func), { libname, nameval, libptrgv }); @@ -169,13 +210,13 @@ static Value *runtime_sym_lookup( std::string gvname = "libname_"; gvname += f_name; gvname += "_"; - gvname += std::to_string(globalUnique++); + gvname += std::to_string(jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1)); llvmgv = new GlobalVariable(*jl_Module, T_pvoidfunc, false, GlobalVariable::ExternalLinkage, Constant::getNullValue(T_pvoidfunc), gvname); } else { - runtime_lib = runtime_sym_gvs(ctx.emission_context, ctx.builder.getContext(), f_lib, f_name, libptrgv, llvmgv); + runtime_lib = runtime_sym_gvs(ctx, f_lib, f_name, libptrgv, llvmgv); libptrgv = prepare_global_in(jl_Module, libptrgv); } llvmgv = prepare_global_in(jl_Module, llvmgv); @@ -185,18 +226,19 @@ static Value *runtime_sym_lookup( // Emit a "PLT" entry that will be lazily initialized // when being called the first time. static GlobalVariable *emit_plt_thunk( - jl_codegen_params_t &emission_context, + jl_codectx_t &ctx, FunctionType *functype, const AttributeList &attrs, CallingConv::ID cc, const char *f_lib, const char *f_name, GlobalVariable *libptrgv, GlobalVariable *llvmgv, bool runtime_lib) { - Module *M = emission_context.shared_module(functype->getContext()); + ++PLTThunks; + auto M = &ctx.emission_context.shared_module(); PointerType *funcptype = PointerType::get(functype, 0); libptrgv = prepare_global_in(M, libptrgv); llvmgv = prepare_global_in(M, llvmgv); std::string fname; - raw_string_ostream(fname) << "jlplt_" << f_name << "_" << globalUnique++; + raw_string_ostream(fname) << "jlplt_" << f_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); Function *plt = Function::Create(functype, GlobalVariable::ExternalLinkage, fname, M); @@ -211,15 +253,16 @@ static GlobalVariable *emit_plt_thunk( fname); BasicBlock *b0 = BasicBlock::Create(M->getContext(), "top", plt); IRBuilder<> irbuilder(b0); - Value *ptr = runtime_sym_lookup(emission_context, irbuilder, NULL, funcptype, f_lib, NULL, f_name, plt, libptrgv, + Value *ptr = runtime_sym_lookup(ctx.emission_context, irbuilder, NULL, funcptype, f_lib, NULL, f_name, plt, libptrgv, llvmgv, runtime_lib); StoreInst *store = irbuilder.CreateAlignedStore(irbuilder.CreateBitCast(ptr, T_pvoidfunc), got, Align(sizeof(void*))); store->setAtomic(AtomicOrdering::Release); SmallVector args; for (Function::arg_iterator arg = plt->arg_begin(), arg_e = plt->arg_end(); arg != arg_e; ++arg) args.push_back(&*arg); + assert(cast(ptr->getType())->isOpaqueOrPointeeTypeMatches(functype)); CallInst *ret = irbuilder.CreateCall( - cast(ptr->getType()->getPointerElementType()), + functype, ptr, ArrayRef(args)); ret->setAttributes(attrs); if (cc != CallingConv::C) @@ -233,13 +276,13 @@ static GlobalVariable *emit_plt_thunk( else { // musttail support is very bad on ARM, PPC, PPC64 (as of LLVM 3.9) // Known failures includes vararg (not needed here) and sret. -#if (defined(_CPU_X86_) || defined(_CPU_X86_64_) || \ - defined(_CPU_AARCH64_)) - // Ref https://bugs.llvm.org/show_bug.cgi?id=47058 - // LLVM, as of 10.0.1 emits wrong/worse code when musttail is set - if (!attrs.hasAttrSomewhere(Attribute::ByVal)) - ret->setTailCallKind(CallInst::TCK_MustTail); -#endif + if (ctx.emission_context.TargetTriple.isX86() || (ctx.emission_context.TargetTriple.isAArch64() && !ctx.emission_context.TargetTriple.isOSDarwin())) { + // Ref https://bugs.llvm.org/show_bug.cgi?id=47058 + // LLVM, as of 10.0.1 emits wrong/worse code when musttail is set + // Apple silicon macs give an LLVM ERROR if musttail is set here #44107. + if (!attrs.hasAttrSomewhere(Attribute::ByVal)) + ret->setTailCallKind(CallInst::TCK_MustTail); + } if (functype->getReturnType() == getVoidTy(irbuilder.getContext())) { irbuilder.CreateRetVoid(); } @@ -258,20 +301,21 @@ static Value *emit_plt( const AttributeList &attrs, CallingConv::ID cc, const char *f_lib, const char *f_name) { - assert(imaging_mode); + ++PLT; + assert(ctx.emission_context.imaging); // Don't do this for vararg functions so that the `musttail` is only // an optimization and is not required to function correctly. assert(!functype->isVarArg()); GlobalVariable *libptrgv; GlobalVariable *llvmgv; - bool runtime_lib = runtime_sym_gvs(ctx.emission_context, ctx.builder.getContext(), f_lib, f_name, libptrgv, llvmgv); + bool runtime_lib = runtime_sym_gvs(ctx, f_lib, f_name, libptrgv, llvmgv); PointerType *funcptype = PointerType::get(functype, 0); auto &pltMap = ctx.emission_context.allPltMap[attrs]; auto key = std::make_tuple(llvmgv, functype, cc); GlobalVariable *&sharedgot = pltMap[key]; if (!sharedgot) { - sharedgot = emit_plt_thunk(ctx.emission_context, + sharedgot = emit_plt_thunk(ctx, functype, attrs, cc, f_lib, f_name, libptrgv, llvmgv, runtime_lib); } GlobalVariable *got = prepare_global_in(jl_Module, sharedgot); @@ -345,7 +389,7 @@ static bool is_native_simd_type(jl_datatype_t *dt) { #elif defined _CPU_PPC64_ typedef ABI_PPC64leLayout DefaultAbiState; #else -# warning "ccall is defaulting to llvm ABI, since no platform ABI has been defined for this CPU/OS combination" +# pragma message("ccall is defaulting to llvm ABI, since no platform ABI has been defined for this CPU/OS combination") typedef ABI_LLVMLayout DefaultAbiState; #endif @@ -422,7 +466,7 @@ static Value *runtime_apply_type_env(jl_codectx_t &ctx, jl_value_t *ty) ctx.builder.CreateInBoundsGEP( ctx.types().T_prjlvalue, ctx.spvals_ptr, - ConstantInt::get(getSizeTy(ctx.builder.getContext()), sizeof(jl_svec_t) / sizeof(jl_value_t*))) + 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)); addRetAttr(call, Attribute::getWithAlignment(ctx.builder.getContext(), Align(16))); @@ -469,7 +513,7 @@ static void typeassert_input(jl_codectx_t &ctx, const jl_cgval_t &jvinfo, jl_val ctx.builder.CreateCondBr(istype, passBB, failBB); ctx.builder.SetInsertPoint(failBB); - emit_type_error(ctx, mark_julia_type(ctx, vx, true, jl_any_type), boxed(ctx, jlto_runtime), msg); + just_emit_type_error(ctx, mark_julia_type(ctx, vx, true, jl_any_type), boxed(ctx, jlto_runtime), msg); ctx.builder.CreateUnreachable(); ctx.builder.SetInsertPoint(passBB); } @@ -502,10 +546,12 @@ static Value *julia_to_native( // since those are immutable. Value *slot = emit_static_alloca(ctx, to); if (!jvinfo.ispointer()) { - tbaa_decorate(jvinfo.tbaa, ctx.builder.CreateStore(emit_unbox(ctx, to, jvinfo, jlto), slot)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, jvinfo.tbaa); + ai.decorateInst(ctx.builder.CreateStore(emit_unbox(ctx, to, jvinfo, jlto), slot)); } else { - emit_memcpy(ctx, slot, jvinfo.tbaa, jvinfo, jl_datatype_size(jlto), julia_alignment(jlto)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, jvinfo.tbaa); + emit_memcpy(ctx, slot, ai, jvinfo, jl_datatype_size(jlto), julia_alignment(jlto)); } return slot; } @@ -519,8 +565,15 @@ typedef struct { jl_value_t *gcroot; } native_sym_arg_t; +static inline const char *invalid_symbol_err_msg(bool ccall) +{ + return ccall ? + "ccall: first argument not a pointer or valid constant expression" : + "cglobal: first argument not a pointer or valid constant expression"; +} + // --- parse :sym or (:sym, :lib) argument into address info --- -static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_value_t *arg, const char *fname, bool llvmcall) +static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_value_t *arg, bool ccall, bool llvmcall) { Value *&jl_ptr = out.jl_ptr; void (*&fptr)(void) = out.fptr; @@ -550,13 +603,11 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va jl_cgval_t arg1 = emit_expr(ctx, arg); jl_value_t *ptr_ty = arg1.typ; if (!jl_is_cpointer_type(ptr_ty)) { - const char *errmsg = !strcmp(fname, "ccall") ? - "ccall: first argument not a pointer or valid constant expression" : - "cglobal: first argument not a pointer or valid constant expression"; + const char *errmsg = invalid_symbol_err_msg(ccall); emit_cpointercheck(ctx, arg1, errmsg); } arg1 = update_julia_type(ctx, arg1, (jl_value_t*)jl_voidpointer_type); - jl_ptr = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), arg1, (jl_value_t*)jl_voidpointer_type); + jl_ptr = emit_unbox(ctx, ctx.types().T_size, arg1, (jl_value_t*)jl_voidpointer_type); } else { out.gcroot = ptr; @@ -577,16 +628,12 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va std::string iname("i"); iname += f_name; if (jl_dlsym(jl_libjulia_internal_handle, iname.c_str(), &symaddr, 0)) { -#ifdef _OS_WINDOWS_ f_lib = JL_LIBJULIA_INTERNAL_DL_LIBNAME; -#endif f_name = jl_symbol_name(jl_symbol(iname.c_str())); } -#ifdef _OS_WINDOWS_ else { - f_lib = jl_dlfind_win32(f_name); + f_lib = jl_dlfind(f_name); } -#endif } } else if (jl_is_cpointer_type(jl_typeof(ptr))) { @@ -598,8 +645,6 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va f_name = jl_symbol_name((jl_sym_t*)t0); else if (jl_is_string(t0)) f_name = jl_string_data(t0); - else - JL_TYPECHKS(fname, symbol, t0); jl_value_t *t1 = jl_fieldref(ptr, 1); if (jl_is_symbol(t1)) @@ -607,10 +652,7 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va else if (jl_is_string(t1)) f_lib = jl_string_data(t1); else - JL_TYPECHKS(fname, symbol, t1); - } - else { - JL_TYPECHKS(fname, pointer, ptr); + f_name = NULL; } } } @@ -621,6 +663,7 @@ static jl_cgval_t emit_runtime_call(jl_codectx_t &ctx, JL_I::intrinsic f, const static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) { + ++EmittedCGlobals; JL_NARGS(cglobal, 1, 2); jl_value_t *rt = NULL; Value *res; @@ -631,7 +674,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(ctx.builder.getContext()), jl_cgval_t(ctx.builder.getContext())}; + jl_cgval_t argv[2] = {jl_cgval_t(), jl_cgval_t()}; argv[0] = emit_expr(ctx, args[1]); argv[1] = emit_expr(ctx, args[2]); return emit_runtime_call(ctx, JL_I::cglobal, argv, nargs); @@ -643,24 +686,32 @@ 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 = getSizeTy(ctx.builder.getContext()); + Type *lrt = ctx.types().T_size; assert(lrt == julia_type_to_llvm(ctx, rt)); - interpret_symbol_arg(ctx, sym, args[1], "cglobal", false); + interpret_symbol_arg(ctx, sym, args[1], /*ccall=*/false, false); + + if (sym.f_name == NULL && sym.fptr == NULL && sym.jl_ptr == NULL && sym.gcroot != NULL) { + const char *errmsg = invalid_symbol_err_msg(/*ccall=*/false); + jl_cgval_t arg1 = emit_expr(ctx, args[1]); + emit_type_error(ctx, arg1, literal_pointer_val(ctx, (jl_value_t *)jl_pointer_type), errmsg); + JL_GC_POP(); + return jl_cgval_t(); + } if (sym.jl_ptr != NULL) { res = ctx.builder.CreateBitCast(sym.jl_ptr, lrt); } else if (sym.fptr != NULL) { res = ConstantInt::get(lrt, (uint64_t)sym.fptr); - if (imaging_mode) + if (ctx.emission_context.imaging) jl_printf(JL_STDERR,"WARNING: literal address used in cglobal for %s; code cannot be statically compiled\n", sym.f_name); } else { if (sym.lib_expr) { res = runtime_sym_lookup(ctx, cast(getInt8PtrTy(ctx.builder.getContext())), NULL, sym.lib_expr, sym.f_name, ctx.f); } - else if (imaging_mode) { + 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); } @@ -668,7 +719,8 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg void *symaddr; void* libsym = jl_get_library_(sym.f_lib, 0); - if (!libsym || !jl_dlsym(libsym, sym.f_name, &symaddr, 0)) { + int symbol_found = jl_dlsym(libsym, sym.f_name, &symaddr, 0); + if (!libsym || !symbol_found) { // Error mode, either the library or the symbol couldn't be find during compiletime. // Fallback to a runtime symbol lookup. res = runtime_sym_lookup(ctx, cast(getInt8PtrTy(ctx.builder.getContext())), sym.f_lib, NULL, sym.f_name, ctx.f); @@ -689,6 +741,7 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) { + ++EmittedLLVMCalls; // parse and validate arguments // // two forms of llvmcall are supported: @@ -706,7 +759,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar ir = static_eval(ctx, ir_arg); if (!ir) { emit_error(ctx, "error statically evaluating llvm IR argument"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + 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); @@ -717,7 +771,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar rt = static_eval(ctx, args[2]); if (!rt) { emit_error(ctx, "error statically evaluating llvmcall return type"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } } if (jl_is_ssavalue(args[3]) && !jl_is_long(ctx.source->ssavaluetypes)) { @@ -729,31 +784,36 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar at = static_eval(ctx, args[3]); if (!at) { emit_error(ctx, "error statically evaluating llvmcall argument tuple"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } } if (jl_is_tuple(ir)) { // if the IR is a tuple, we expect (mod, fn) if (jl_nfields(ir) != 2) { emit_error(ctx, "Tuple as first argument to llvmcall must have exactly two children"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } entry = jl_fieldref(ir, 1); if (!jl_is_string(entry)) { emit_error(ctx, "Function name passed to llvmcall must be a string"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } ir = jl_fieldref(ir, 0); - if (!jl_is_string(ir) && !jl_typeis(ir, jl_array_uint8_type)) { + if (!jl_is_string(ir) && !jl_typetagis(ir, jl_array_uint8_type)) { emit_error(ctx, "Module IR passed to llvmcall must be a string or an array of bytes"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } } else { if (!jl_is_string(ir)) { emit_error(ctx, "Function IR passed to llvmcall must be a string"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } } @@ -773,7 +833,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar * type. Otherwise we pass a pointer to a jl_value_t. */ std::vector argtypes; - Value **argvals = (Value**)alloca(nargt * sizeof(Value*)); + SmallVector argvals(nargt); for (size_t i = 0; i < nargt; ++i) { jl_value_t *tti = jl_svecref(tt,i); bool toboxed; @@ -781,7 +841,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar argtypes.push_back(t); if (4 + i > nargs) { emit_error(ctx, "Missing arguments to llvmcall!"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } jl_value_t *argi = args[4 + i]; jl_cgval_t arg = emit_expr(ctx, argi); @@ -797,7 +858,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar // Make sure to find a unique name std::string ir_name; while (true) { - raw_string_ostream(ir_name) << (ctx.f->getName().str()) << "u" << globalUnique++; + raw_string_ostream(ir_name) << (ctx.f->getName().str()) << "u" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); if (jl_Module->getFunction(ir_name) == NULL) break; } @@ -835,7 +896,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar raw_string_ostream stream(message); Err.print("", stream, true); emit_error(ctx, stream.str()); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } Function *f = Mod->getFunction(ir_name); @@ -852,7 +914,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar raw_string_ostream stream(message); Err.print("", stream, true); emit_error(ctx, stream.str()); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } } else { @@ -869,7 +932,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar raw_string_ostream stream(message); stream << Message; emit_error(ctx, stream.str()); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } Mod = std::move(ModuleOrErr.get()); } @@ -877,7 +941,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar Function *f = Mod->getFunction(jl_string_data(entry)); if (!f) { emit_error(ctx, "Module IR does not contain specified entry function"); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } f->setName(ir_name); @@ -893,10 +958,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar // copy module properties that should always match Mod->setTargetTriple(jl_Module->getTargetTriple()); Mod->setDataLayout(jl_Module->getDataLayout()); -#if JL_LLVM_VERSION >= 130000 Mod->setStackProtectorGuard(jl_Module->getStackProtectorGuard()); Mod->setOverrideStackAlignment(jl_Module->getOverrideStackAlignment()); -#endif // verify the definition Function *def = Mod->getFunction(ir_name); @@ -905,7 +968,8 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar raw_string_ostream stream(message); if (verifyFunction(*def, &stream)) { emit_error(ctx, stream.str()); - return jl_cgval_t(ctx.builder.getContext()); + JL_GC_POP(); + return jl_cgval_t(); } def->setLinkage(GlobalVariable::LinkOnceODRLinkage); @@ -914,7 +978,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar Function *decl = Function::Create(decl_typ, def->getLinkage(), def->getAddressSpace(), def->getName(), jl_Module); decl->setAttributes(def->getAttributes()); - CallInst *inst = ctx.builder.CreateCall(decl, ArrayRef(&argvals[0], nargt)); + CallInst *inst = ctx.builder.CreateCall(decl, argvals); // save the module to be linked later. // we cannot do this right now, because linking mutates the destination module, @@ -929,7 +993,7 @@ static jl_cgval_t emit_llvmcall(jl_codectx_t &ctx, jl_value_t **args, size_t nar stream << "llvmcall return type " << *inst->getType() << " does not match declared return type" << *rettype; emit_error(ctx, stream.str()); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } return mark_julia_type(ctx, inst, retboxed, rtt); @@ -1013,53 +1077,54 @@ std::string generate_func_sig(const char *fname) { assert(rt && !jl_is_abstract_ref_type(rt)); - std::vector paramattrs; + std::vector paramattrs; std::unique_ptr abi; if (llvmcall) abi.reset(new ABI_LLVMLayout()); else abi.reset(new DefaultAbiState()); sret = 0; - + LLVMContext &LLVMCtx = lrt->getContext(); if (type_is_ghost(lrt)) { - prt = lrt = getVoidTy(lrt->getContext()); - abi->use_sret(jl_nothing_type, lrt->getContext()); + prt = lrt = getVoidTy(LLVMCtx); + abi->use_sret(jl_nothing_type, LLVMCtx); } else { if (retboxed || jl_is_cpointer_type(rt) || lrt->isPointerTy()) { prt = lrt; // passed as pointer - abi->use_sret(jl_voidpointer_type, lrt->getContext()); + abi->use_sret(jl_voidpointer_type, LLVMCtx); } - else if (abi->use_sret((jl_datatype_t*)rt, lrt->getContext())) { - AttrBuilder retattrs = AttrBuilder(); -#if !defined(_OS_WINDOWS_) // llvm used to use the old mingw ABI, skipping this marking works around that difference - retattrs.addStructRetAttr(lrt); -#endif + else if (abi->use_sret((jl_datatype_t*)rt, LLVMCtx)) { + AttrBuilder retattrs(LLVMCtx); + if (!ctx->TargetTriple.isOSWindows()) { + // llvm used to use the old mingw ABI, skipping this marking works around that difference + retattrs.addStructRetAttr(lrt); + } retattrs.addAttribute(Attribute::NoAlias); - paramattrs.push_back(std::move(retattrs)); + paramattrs.push_back(AttributeSet::get(LLVMCtx, retattrs)); fargt_sig.push_back(PointerType::get(lrt, 0)); sret = 1; prt = lrt; } else { - prt = abi->preferred_llvm_type((jl_datatype_t*)rt, true, lrt->getContext()); + prt = abi->preferred_llvm_type((jl_datatype_t*)rt, true, LLVMCtx); if (prt == NULL) prt = lrt; } } for (size_t i = 0; i < nccallargs; ++i) { - AttrBuilder ab; + AttrBuilder ab(LLVMCtx); jl_value_t *tti = jl_svecref(at, i); Type *t = NULL; bool isboxed; if (jl_is_abstract_ref_type(tti)) { tti = (jl_value_t*)jl_voidpointer_type; - t = getInt8PtrTy(lrt->getContext()); + t = getInt8PtrTy(LLVMCtx); isboxed = false; } else if (llvmcall && jl_is_llvmpointer_type(tti)) { - t = bitstype_to_llvm(tti, lrt->getContext(), true); + t = bitstype_to_llvm(tti, LLVMCtx, true); tti = (jl_value_t*)jl_voidpointer_type; isboxed = false; } @@ -1076,8 +1141,8 @@ std::string generate_func_sig(const char *fname) } } - t = _julia_struct_to_llvm(ctx, lrt->getContext(), tti, &isboxed, llvmcall); - if (t == getVoidTy(lrt->getContext())) { + t = _julia_struct_to_llvm(ctx, LLVMCtx, tti, &isboxed, llvmcall); + if (t == getVoidTy(LLVMCtx)) { return make_errmsg(fname, i + 1, " type doesn't correspond to a C type"); } } @@ -1088,7 +1153,8 @@ std::string generate_func_sig(const char *fname) } // Whether or not LLVM wants us to emit a pointer to the data - bool byRef = abi->needPassByRef((jl_datatype_t*)tti, ab, lrt->getContext(), t); + assert(t && "LLVM type should not be null"); + bool byRef = abi->needPassByRef((jl_datatype_t*)tti, ab, LLVMCtx, t); if (jl_is_cpointer_type(tti)) { pat = t; @@ -1097,7 +1163,7 @@ std::string generate_func_sig(const char *fname) pat = PointerType::get(t, AddressSpace::Derived); } else { - pat = abi->preferred_llvm_type((jl_datatype_t*)tti, false, lrt->getContext()); + pat = abi->preferred_llvm_type((jl_datatype_t*)tti, false, LLVMCtx); if (pat == NULL) pat = t; } @@ -1120,21 +1186,18 @@ std::string generate_func_sig(const char *fname) fargt.push_back(t); fargt_isboxed.push_back(isboxed); fargt_sig.push_back(pat); - paramattrs.push_back(AttributeSet::get(lrt->getContext(), ab)); + paramattrs.push_back(AttributeSet::get(LLVMCtx, ab)); } - for (size_t i = 0; i < nccallargs + sret; ++i) { - const auto &as = paramattrs.at(i); - if (!as.hasAttributes()) - continue; - attributes = addAttributesAtIndex(attributes, lrt->getContext(), i + 1, as); - } + AttributeSet FnAttrs; + AttributeSet RetAttrs; // If return value is boxed it must be non-null. if (retboxed) - attributes = addRetAttribute(attributes, lrt->getContext(), Attribute::NonNull); - if (rt == jl_bottom_type) { - attributes = addFnAttribute(attributes, lrt->getContext(), Attribute::NoReturn); - } + RetAttrs = RetAttrs.addAttribute(LLVMCtx, Attribute::NonNull); + if (rt == jl_bottom_type) + FnAttrs = FnAttrs.addAttribute(LLVMCtx, Attribute::NoReturn); + assert(attributes.isEmpty()); + attributes = AttributeList::get(LLVMCtx, FnAttrs, RetAttrs, paramattrs); return ""; } }; @@ -1275,16 +1338,22 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) bool llvmcall = false; std::tie(cc, llvmcall) = convert_cconv(cc_sym); - interpret_symbol_arg(ctx, symarg, args[1], "ccall", llvmcall); + interpret_symbol_arg(ctx, symarg, args[1], /*ccall=*/true, llvmcall); Value *&jl_ptr = symarg.jl_ptr; void (*&fptr)(void) = symarg.fptr; const char *&f_name = symarg.f_name; const char *&f_lib = symarg.f_lib; if (f_name == NULL && fptr == NULL && jl_ptr == NULL) { - emit_error(ctx, "ccall: null function pointer"); + if (symarg.gcroot != NULL) { // static_eval(ctx, args[1]) could not be interpreted to a function pointer + const char *errmsg = invalid_symbol_err_msg(/*ccall=*/true); + jl_cgval_t arg1 = emit_expr(ctx, args[1]); + emit_type_error(ctx, arg1, literal_pointer_val(ctx, (jl_value_t *)jl_pointer_type), errmsg); + } else { + emit_error(ctx, "ccall: null function pointer"); + } JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } auto ccallarg = [=] (size_t i) { @@ -1296,25 +1365,26 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) if ((uintptr_t)fptr == ptr) return true; if (f_lib) { -#ifdef _OS_WINDOWS_ if ((f_lib == JL_EXE_LIBNAME) || // preventing invalid pointer access (f_lib == JL_LIBJULIA_INTERNAL_DL_LIBNAME) || - (f_lib == JL_LIBJULIA_DL_LIBNAME) || - (!strcmp(f_lib, jl_crtdll_basename))) { + (f_lib == JL_LIBJULIA_DL_LIBNAME)) { + // libjulia-like + } + else +#ifdef _OS_WINDOWS_ + if (strcmp(f_lib, jl_crtdll_basename) == 0) { // libjulia-like } else - return false; -#else - return false; #endif + return false; } return f_name && f_name == name; }; #define is_libjulia_func(name) _is_libjulia_func((uintptr_t)&(name), StringRef(XSTR(name))) // emit arguments - jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nccallargs); + SmallVector argv(nccallargs); for (size_t i = 0; i < nccallargs; i++) { // Julia (expression) value of current parameter jl_value_t *argi = ccallarg(i); @@ -1341,7 +1411,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) if (jl_is_abstract_ref_type(rt)) { if (!verify_ref_type(ctx, jl_tparam0(rt), unionall, 0, "ccall")) { JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } rt = (jl_value_t*)jl_any_type; // convert return type to jl_value_t* } @@ -1375,10 +1445,10 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) if (!err.empty()) { emit_error(ctx, "ccall " + err); JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } if (rt != args[2] && rt != (jl_value_t*)jl_any_type) - jl_add_method_root(ctx, rt); + rt = jl_ensure_rooted(ctx, rt); function_sig_t sig("ccall", lrt, rt, retboxed, (jl_svec_t*)at, unionall, nreqargs, cc, llvmcall, &ctx.emission_context); @@ -1387,7 +1457,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) if (jl_is_abstract_ref_type(tti)) { if (!verify_ref_type(ctx, jl_tparam0(tti), unionall, i + 1, "ccall")) { JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } } } @@ -1396,7 +1466,8 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) bool isVa = nreqargs > 0; (void)isVa; // prevent compiler warning if (is_libjulia_func(jl_array_ptr)) { - assert(lrt == getSizeTy(ctx.builder.getContext())); + ++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(); @@ -1404,14 +1475,15 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_value_ptr)) { - assert(retboxed ? lrt == ctx.types().T_prjlvalue : lrt == getSizeTy(ctx.builder.getContext())); + ++CCALL_STAT(jl_value_ptr); + assert(retboxed ? lrt == ctx.types().T_prjlvalue : lrt == ctx.types().T_size); 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 = getSizeTy(ctx.builder.getContext()); + largty = ctx.types().T_size; isboxed = false; } else { @@ -1433,56 +1505,74 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) JL_GC_POP(); return mark_or_box_ccall_result(ctx, retval, retboxed, rt, unionall, static_rt); } - else if (is_libjulia_func(jl_cpu_pause)) { + else if (is_libjulia_func(jl_cpu_pause)||is_libjulia_func(jl_cpu_suspend)) { + ++CCALL_STAT(jl_cpu_pause); // Keep in sync with the julia_threads.h version assert(lrt == getVoidTy(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 0); #ifdef __MIC__ - // TODO -#elif defined(_CPU_X86_64_) || defined(_CPU_X86_) /* !__MIC__ */ - static auto pauseinst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "pause", - "~{memory}", true); - ctx.builder.CreateCall(pauseinst); - JL_GC_POP(); - return ghostValue(ctx, jl_nothing_type); -#elif defined(_CPU_AARCH64_) || (defined(_CPU_ARM_) && __ARM_ARCH >= 7) - static auto wfeinst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "wfe", - "~{memory}", true); - ctx.builder.CreateCall(wfeinst); - JL_GC_POP(); - return ghostValue(ctx, jl_nothing_type); + //TODO #else - JL_GC_POP(); - return ghostValue(ctx, jl_nothing_type); + if (ctx.emission_context.TargetTriple.isX86()) { + auto pauseinst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "pause", + "~{memory}", true); + ctx.builder.CreateCall(pauseinst); + JL_GC_POP(); + return ghostValue(ctx, jl_nothing_type); + } else if (ctx.emission_context.TargetTriple.isAArch64() + || (ctx.emission_context.TargetTriple.isARM() + && ctx.emission_context.TargetTriple.getSubArch() != Triple::SubArchType::NoSubArch + // ARMv7 and above is < armv6 + && ctx.emission_context.TargetTriple.getSubArch() < Triple::SubArchType::ARMSubArch_v6)) { + InlineAsm* wait_inst; + if (is_libjulia_func(jl_cpu_pause)) + wait_inst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "isb", + "~{memory}", true); + else + wait_inst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "wfe", + "~{memory}", true); + ctx.builder.CreateCall(wait_inst); + JL_GC_POP(); + return ghostValue(ctx, jl_nothing_type); + } else { + JL_GC_POP(); + return ghostValue(ctx, jl_nothing_type); + } #endif } else if (is_libjulia_func(jl_cpu_wake)) { + ++CCALL_STAT(jl_cpu_wake); // Keep in sync with the julia_threads.h version assert(lrt == getVoidTy(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 0); #if JL_CPU_WAKE_NOOP == 1 JL_GC_POP(); return ghostValue(ctx, jl_nothing_type); -#elif defined(_CPU_AARCH64_) || (defined(_CPU_ARM_) && __ARM_ARCH >= 7) - static auto sevinst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "sev", - "~{memory}", true); - ctx.builder.CreateCall(sevinst); - JL_GC_POP(); - return ghostValue(ctx, jl_nothing_type); #endif + if (ctx.emission_context.TargetTriple.isAArch64() + || (ctx.emission_context.TargetTriple.isARM() + && ctx.emission_context.TargetTriple.getSubArch() != Triple::SubArchType::NoSubArch + // ARMv7 and above is < armv6 + && ctx.emission_context.TargetTriple.getSubArch() < Triple::SubArchType::ARMSubArch_v6)) { + auto sevinst = InlineAsm::get(FunctionType::get(getVoidTy(ctx.builder.getContext()), false), "sev", + "~{memory}", true); + ctx.builder.CreateCall(sevinst); + JL_GC_POP(); + return ghostValue(ctx, jl_nothing_type); + } } else if (is_libjulia_func(jl_gc_safepoint)) { + ++CCALL_STAT(jl_gc_safepoint); assert(lrt == getVoidTy(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); ctx.builder.CreateCall(prepare_call(gcroot_flush_func)); - emit_signal_fence(ctx); - ctx.builder.CreateLoad(getSizeTy(ctx.builder.getContext()), get_current_signal_page(ctx), true); - emit_signal_fence(ctx); + emit_gc_safepoint(ctx.builder, ctx.types().T_size, get_current_ptls(ctx), ctx.tbaa().tbaa_const); return ghostValue(ctx, jl_nothing_type); } else if (is_libjulia_func("jl_get_ptls_states")) { - assert(lrt == getSizeTy(ctx.builder.getContext())); + ++CCALL_STAT(jl_get_ptls_states); + assert(lrt == ctx.types().T_size); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); return mark_or_box_ccall_result(ctx, @@ -1490,14 +1580,16 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_threadid)) { + ++CCALL_STAT(jl_threadid); assert(lrt == getInt16Ty(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); Value *ptask_i16 = emit_bitcast(ctx, get_current_task(ctx), getInt16PtrTy(ctx.builder.getContext())); const int tid_offset = offsetof(jl_task_t, tid); - Value *ptid = ctx.builder.CreateInBoundsGEP(getInt16Ty(ctx.builder.getContext()), ptask_i16, ConstantInt::get(getSizeTy(ctx.builder.getContext()), tid_offset / sizeof(int16_t))); + Value *ptid = ctx.builder.CreateInBoundsGEP(getInt16Ty(ctx.builder.getContext()), ptask_i16, ConstantInt::get(ctx.types().T_size, tid_offset / sizeof(int16_t))); LoadInst *tid = ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), ptid, Align(sizeof(int16_t))); - tbaa_decorate(ctx.tbaa().tbaa_gcframe, tid); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + ai.decorateInst(tid); return mark_or_box_ccall_result(ctx, tid, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_gc_disable_finalizers_internal) @@ -1508,7 +1600,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) JL_GC_POP(); Value *ptls_i32 = emit_bitcast(ctx, get_current_ptls(ctx), getInt32PtrTy(ctx.builder.getContext())); const int finh_offset = offsetof(jl_tls_states_t, finalizers_inhibited); - Value *pfinh = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), ptls_i32, ConstantInt::get(getSizeTy(ctx.builder.getContext()), finh_offset / 4)); + Value *pfinh = ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), ptls_i32, ConstantInt::get(ctx.types().T_size, finh_offset / 4)); LoadInst *finh = ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), pfinh, Align(sizeof(int32_t))); Value *newval; if (is_libjulia_func(jl_gc_disable_finalizers_internal)) { @@ -1523,6 +1615,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) return ghostValue(ctx, jl_nothing_type); } else if (is_libjulia_func(jl_get_current_task)) { + ++CCALL_STAT(jl_get_current_task); assert(lrt == ctx.types().T_prjlvalue); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); @@ -1530,16 +1623,18 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) return mark_or_box_ccall_result(ctx, ct, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_set_next_task)) { + ++CCALL_STAT(jl_set_next_task); assert(lrt == getVoidTy(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 1); JL_GC_POP(); Value *ptls_pv = emit_bitcast(ctx, get_current_ptls(ctx), ctx.types().T_ppjlvalue); const int nt_offset = offsetof(jl_tls_states_t, next_task); - Value *pnt = ctx.builder.CreateInBoundsGEP(ctx.types().T_pjlvalue, ptls_pv, ConstantInt::get(getSizeTy(ctx.builder.getContext()), nt_offset / sizeof(void*))); + Value *pnt = ctx.builder.CreateInBoundsGEP(ctx.types().T_pjlvalue, ptls_pv, ConstantInt::get(ctx.types().T_size, nt_offset / sizeof(void*))); ctx.builder.CreateStore(emit_pointer_from_objref(ctx, boxed(ctx, argv[0])), pnt); return ghostValue(ctx, jl_nothing_type); } else if (is_libjulia_func(jl_sigatomic_begin)) { + ++CCALL_STAT(jl_sigatomic_begin); assert(lrt == getVoidTy(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); @@ -1552,6 +1647,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) return ghostValue(ctx, jl_nothing_type); } else if (is_libjulia_func(jl_sigatomic_end)) { + ++CCALL_STAT(jl_sigatomic_end); assert(lrt == getVoidTy(ctx.builder.getContext())); assert(!isVa && !llvmcall && nccallargs == 0); JL_GC_POP(); @@ -1574,8 +1670,9 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) checkBB, contBB); ctx.builder.SetInsertPoint(checkBB); ctx.builder.CreateLoad( - getSizeTy(ctx.builder.getContext()), - ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), get_current_signal_page(ctx), -1), + ctx.types().T_size, + ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_size, + get_current_signal_page_from_ptls(ctx.builder, ctx.types().T_size, get_current_ptls(ctx), ctx.tbaa().tbaa_const), -1), true); ctx.builder.CreateBr(contBB); ctx.f->getBasicBlockList().push_back(contBB); @@ -1583,56 +1680,39 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) 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(getSizeTy(ctx.builder.getContext()), jl_svec_len(svecv.constant)); + len = ConstantInt::get(ctx.types().T_size, jl_svec_len(svecv.constant)); } else { - auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), getSizePtrTy(ctx.builder.getContext())); - len = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ptr, Align(sizeof(size_t))); + auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_size->getPointerTo()); + len = ctx.builder.CreateAlignedLoad(ctx.types().T_size, ptr, ctx.types().alignof_ptr); // 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) - tbaa_decorate(ctx.tbaa().tbaa_const, cast(len)); + 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(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), INTPTR_MAX / sizeof(void*) - 1)); + 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_isassigned) && - argv[1].typ == (jl_value_t*)jl_long_type) { - assert(!isVa && !llvmcall && nccallargs == 2); - const jl_cgval_t &svecv = argv[0]; - const jl_cgval_t &idxv = argv[1]; - Value *idx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), idxv, (jl_value_t*)jl_long_type); - idx = ctx.builder.CreateAdd(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)); - auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_pprjlvalue); - Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, - decay_derived(ctx, ptr), idx); - LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, - Align(sizeof(void*))); - 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) - tbaa_decorate(ctx.tbaa().tbaa_const, load); - Value *res = ctx.builder.CreateZExt(ctx.builder.CreateICmpNE(load, Constant::getNullValue(ctx.types().T_prjlvalue)), getInt8Ty(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_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, getSizeTy(ctx.builder.getContext()), idxv, (jl_value_t*)jl_long_type); - idx = ctx.builder.CreateAdd(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 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)); auto ptr = emit_bitcast(ctx, boxed(ctx, svecv), ctx.types().T_pprjlvalue); Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, decay_derived(ctx, ptr), idx); @@ -1641,14 +1721,16 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) 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) - tbaa_decorate(ctx.tbaa().tbaa_const, load); - null_pointer_check(ctx, load); + 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]; @@ -1663,20 +1745,21 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) false, rt, unionall, static_rt); } else if (!jl_has_free_typevars(ety)) { - Value *idx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), idxv, (jl_value_t*)jl_ulong_type); + 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(getSizeTy(ctx.builder.getContext()), stride)); - idx = ctx.builder.CreateAdd(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), ((jl_datatype_t*)ety)->layout->first_ptr)); + 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)); } Value *slot_addr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, arrayptr, idx); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, slot_addr, Align(sizeof(void*))); load->setAtomic(AtomicOrdering::Unordered); - tbaa_decorate(ctx.tbaa().tbaa_ptrarraybuf, load); + 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); @@ -1684,19 +1767,21 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } } else if (is_libjulia_func(jl_string_ptr)) { - assert(lrt == getSizeTy(ctx.builder.getContext())); + ++CCALL_STAT(jl_string_ptr); + assert(lrt == ctx.types().T_size); 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, getSizeTy(ctx.builder.getContext())); + strp = ctx.builder.CreatePtrToInt(strp, ctx.types().T_size); JL_GC_POP(); return mark_or_box_ccall_result(ctx, strp, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(jl_symbol_name)) { - assert(lrt == getSizeTy(ctx.builder.getContext())); + ++CCALL_STAT(jl_symbol_name); + assert(lrt == ctx.types().T_size); assert(!isVa && !llvmcall && nccallargs == 1); auto obj = emit_bitcast(ctx, emit_pointer_from_objref(ctx, boxed(ctx, argv[0])), ctx.types().T_pprjlvalue); @@ -1704,40 +1789,42 @@ 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, getSizeTy(ctx.builder.getContext())); + strp = ctx.builder.CreatePtrToInt(strp, ctx.types().T_size); JL_GC_POP(); return mark_or_box_ccall_result(ctx, strp, retboxed, rt, unionall, static_rt); } 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, getSizeTy(ctx.builder.getContext()), dst, (jl_value_t*)jl_voidpointer_type); + Value *destp = emit_unbox(ctx, ctx.types().T_size, dst, (jl_value_t*)jl_voidpointer_type); ctx.builder.CreateMemCpy( emit_inttoptr(ctx, destp, getInt8PtrTy(ctx.builder.getContext())), MaybeAlign(1), emit_inttoptr(ctx, - emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), src, (jl_value_t*)jl_voidpointer_type), + emit_unbox(ctx, ctx.types().T_size, src, (jl_value_t*)jl_voidpointer_type), getInt8PtrTy(ctx.builder.getContext())), MaybeAlign(0), - emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), n, (jl_value_t*)jl_ulong_type), + emit_unbox(ctx, ctx.types().T_size, n, (jl_value_t*)jl_ulong_type), false); JL_GC_POP(); return rt == (jl_value_t*)jl_nothing_type ? ghostValue(ctx, jl_nothing_type) : mark_or_box_ccall_result(ctx, destp, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(memset) && (rt == (jl_value_t*)jl_nothing_type || jl_is_cpointer_type(rt))) { + ++CCALL_STAT(memset); 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, getSizeTy(ctx.builder.getContext()), dst, (jl_value_t*)jl_voidpointer_type); + Value *destp = emit_unbox(ctx, ctx.types().T_size, 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())), val8, - emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), n, (jl_value_t*)jl_ulong_type), + emit_unbox(ctx, ctx.types().T_size, n, (jl_value_t*)jl_ulong_type), MaybeAlign(1) ); JL_GC_POP(); @@ -1745,19 +1832,20 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) mark_or_box_ccall_result(ctx, destp, retboxed, rt, unionall, static_rt); } else if (is_libjulia_func(memmove) && (rt == (jl_value_t*)jl_nothing_type || jl_is_cpointer_type(rt))) { + ++CCALL_STAT(memmove); 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, getSizeTy(ctx.builder.getContext()), dst, (jl_value_t*)jl_voidpointer_type); + Value *destp = emit_unbox(ctx, ctx.types().T_size, dst, (jl_value_t*)jl_voidpointer_type); ctx.builder.CreateMemMove( emit_inttoptr(ctx, destp, getInt8PtrTy(ctx.builder.getContext())), MaybeAlign(0), emit_inttoptr(ctx, - emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), src, (jl_value_t*)jl_voidpointer_type), + emit_unbox(ctx, ctx.types().T_size, src, (jl_value_t*)jl_voidpointer_type), getInt8PtrTy(ctx.builder.getContext())), MaybeAlign(0), - emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), n, (jl_value_t*)jl_ulong_type), + emit_unbox(ctx, ctx.types().T_size, n, (jl_value_t*)jl_ulong_type), false); JL_GC_POP(); return rt == (jl_value_t*)jl_nothing_type ? ghostValue(ctx, jl_nothing_type) : @@ -1765,14 +1853,16 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } else if (is_libjulia_func(jl_object_id) && nccallargs == 1 && rt == (jl_value_t*)jl_ulong_type) { + ++CCALL_STAT(jl_object_id); jl_cgval_t val = argv[0]; if (val.typ == (jl_value_t*)jl_symbol_type) { JL_GC_POP(); const int hash_offset = offsetof(jl_sym_t, hash); - Value *ph1 = emit_bitcast(ctx, decay_derived(ctx, boxed(ctx, val)), getSizePtrTy(ctx.builder.getContext())); - Value *ph2 = ctx.builder.CreateInBoundsGEP(getSizeTy(ctx.builder.getContext()), ph1, ConstantInt::get(getSizeTy(ctx.builder.getContext()), hash_offset / sizeof(size_t))); - LoadInst *hashval = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ph2, Align(sizeof(size_t))); - tbaa_decorate(ctx.tbaa().tbaa_const, hashval); + Value *ph1 = emit_bitcast(ctx, decay_derived(ctx, boxed(ctx, val)), ctx.types().T_size->getPointerTo()); + Value *ph2 = ctx.builder.CreateInBoundsGEP(ctx.types().T_size, ph1, ConstantInt::get(ctx.types().T_size, hash_offset / ctx.types().sizeof_ptr)); + LoadInst *hashval = ctx.builder.CreateAlignedLoad(ctx.types().T_size, ph2, ctx.types().alignof_ptr); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + ai.decorateInst(hashval); return mark_or_box_ccall_result(ctx, hashval, retboxed, rt, unionall, static_rt); } else if (!val.isboxed) { @@ -1782,7 +1872,7 @@ 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_boxed(ctx, val), + emit_typeof(ctx, val), val.isghost ? ConstantPointerNull::get(T_pint8_derived) : ctx.builder.CreateBitCast( decay_derived(ctx, data_pointer(ctx, val)), @@ -1797,7 +1887,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) jl_cgval_t retval = sig.emit_a_ccall( ctx, symarg, - argv, + argv.data(), gc_uses, static_rt); JL_GC_POP(); @@ -1811,14 +1901,15 @@ jl_cgval_t function_sig_t::emit_a_ccall( SmallVector &gc_uses, bool static_rt) const { + ++EmittedCCalls; if (!err_msg.empty()) { emit_error(ctx, err_msg); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } FunctionType *functype = this->functype(ctx.builder.getContext()); - Value **argvals = (Value**) alloca((nccallargs + sret) * sizeof(Value*)); + SmallVector argvals(nccallargs + sret); for (size_t ai = 0; ai < nccallargs; ai++) { // Current C function parameter jl_cgval_t &arg = argv[ai]; @@ -1835,7 +1926,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( jl_svec_len(ctx.linfo->sparam_vals) > 0) { jargty_in_env = jl_instantiate_type_in_env(jargty_in_env, unionall_env, jl_svec_data(ctx.linfo->sparam_vals)); if (jargty_in_env != jargty) - jl_add_method_root(ctx, jargty_in_env); + jargty_in_env = jl_ensure_rooted(ctx, jargty_in_env); } Value *v; @@ -1860,13 +1951,15 @@ jl_cgval_t function_sig_t::emit_a_ccall( } if (isa(v)) { - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } assert(v->getType() == pargty); argvals[ai + sret] = v; } Value *result = NULL; + //This is only needed if !retboxed && srt && !jlretboxed + Type *sretty = nullptr; // First, if the ABI requires us to provide the space for the return // argument, allocate the box and store that as the first argument type bool sretboxed = false; @@ -1874,15 +1967,15 @@ jl_cgval_t function_sig_t::emit_a_ccall( assert(!retboxed && jl_is_datatype(rt) && "sret return type invalid"); if (jl_is_pointerfree(rt)) { result = emit_static_alloca(ctx, lrt); + sretty = lrt; argvals[0] = ctx.builder.CreateBitCast(result, fargt_sig.at(0)); } else { // 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` - assert(jl_datatype_size(rt) > 0 && "sret shouldn't be a singleton instance"); - result = emit_allocobj(ctx, jl_datatype_size(rt), - literal_pointer_val(ctx, (jl_value_t*)rt)); + result = emit_allocobj(ctx, (jl_datatype_t*)rt); + sretty = ctx.types().T_jlvalue; sretboxed = true; gc_uses.push_back(result); argvals[0] = ctx.builder.CreateBitCast(emit_pointer_from_objref(ctx, result), fargt_sig.at(0)); @@ -1894,51 +1987,79 @@ jl_cgval_t function_sig_t::emit_a_ccall( // optimize the global pointer load in the common case Value *llvmf; if (llvmcall) { + ++EmittedLLVMCalls; if (symarg.jl_ptr != NULL) { emit_error(ctx, "llvmcall doesn't support dynamic pointers"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } else if (symarg.fptr != NULL) { emit_error(ctx, "llvmcall doesn't support static pointers"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } else if (symarg.f_lib != NULL) { emit_error(ctx, "llvmcall doesn't support dynamic libraries"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } else { assert(symarg.f_name != NULL); - const char* f_name = symarg.f_name; - bool f_extern = (strncmp(f_name, "extern ", 7) == 0); - if (f_extern) - f_name += 7; - llvmf = jl_Module->getOrInsertFunction(f_name, functype).getCallee(); - if (!f_extern && (!isa(llvmf) || - cast(llvmf)->getIntrinsicID() == - Intrinsic::not_intrinsic)) { + StringRef f_name(symarg.f_name); + bool f_extern = f_name.consume_front("extern "); + llvmf = NULL; + if (f_extern) { + llvmf = jl_Module->getOrInsertFunction(f_name, functype).getCallee(); + if (!isa(llvmf) || cast(llvmf)->isIntrinsic() || cast(llvmf)->getFunctionType() != functype) + llvmf = NULL; + } + else if (f_name.startswith("llvm.")) { + // compute and verify auto-mangling for intrinsic name + auto ID = Function::lookupIntrinsicID(f_name); + if (ID != Intrinsic::not_intrinsic) { + // Accumulate an array of overloaded types for the given intrinsic + // and compute the new name mangling schema + SmallVector overloadTys; + SmallVector Table; + getIntrinsicInfoTableEntries(ID, Table); + ArrayRef TableRef = Table; + auto res = Intrinsic::matchIntrinsicSignature(functype, TableRef, overloadTys); + if (res == Intrinsic::MatchIntrinsicTypes_Match) { + bool matchvararg = !Intrinsic::matchIntrinsicVarArg(functype->isVarArg(), TableRef); + if (matchvararg) { + Function *intrinsic = Intrinsic::getDeclaration(jl_Module, ID, overloadTys); + assert(intrinsic->getFunctionType() == functype); + if (intrinsic->getName() == f_name || Intrinsic::getBaseName(ID) == f_name) + llvmf = intrinsic; + } + } + } + } + if (llvmf == NULL) { emit_error(ctx, "llvmcall only supports intrinsic calls"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } } } else if (symarg.jl_ptr != NULL) { + ++LiteralCCalls; null_pointer_check(ctx, symarg.jl_ptr); Type *funcptype = PointerType::get(functype, 0); llvmf = emit_inttoptr(ctx, symarg.jl_ptr, funcptype); } else if (symarg.fptr != NULL) { + ++LiteralCCalls; Type *funcptype = PointerType::get(functype, 0); llvmf = literal_static_pointer_val((void*)(uintptr_t)symarg.fptr, funcptype); - if (imaging_mode) + if (ctx.emission_context.imaging) jl_printf(JL_STDERR,"WARNING: literal address used in ccall for %s; code cannot be statically compiled\n", symarg.f_name); } else { assert(symarg.f_name != NULL); PointerType *funcptype = PointerType::get(functype, 0); if (symarg.lib_expr) { + ++DeferredCCallLookups; llvmf = runtime_sym_lookup(ctx, funcptype, NULL, symarg.lib_expr, symarg.f_name, ctx.f); } - else if (imaging_mode) { + else if (ctx.emission_context.imaging) { + ++DeferredCCallLookups; // vararg requires musttail, // but musttail is incompatible with noreturn. if (functype->isVarArg()) @@ -1949,11 +2070,14 @@ jl_cgval_t function_sig_t::emit_a_ccall( else { void *symaddr; void *libsym = jl_get_library_(symarg.f_lib, 0); - if (!libsym || !jl_dlsym(libsym, symarg.f_name, &symaddr, 0)) { + int symbol_found = jl_dlsym(libsym, symarg.f_name, &symaddr, 0); + if (!libsym || !symbol_found) { + ++DeferredCCallLookups; // either the library or the symbol could not be found, place a runtime // lookup here instead. llvmf = runtime_sym_lookup(ctx, funcptype, symarg.f_lib, NULL, symarg.f_name, ctx.f); } else { + ++LiteralCCalls; // since we aren't saving this code, there's no sense in // putting anything complicated here: just JIT the function address llvmf = literal_static_pointer_val(symaddr, funcptype); @@ -1964,21 +2088,21 @@ jl_cgval_t function_sig_t::emit_a_ccall( OperandBundleDef OpBundle("jl_roots", gc_uses); // the actual call CallInst *ret = ctx.builder.CreateCall(functype, llvmf, - ArrayRef(&argvals[0], nccallargs + sret), + argvals, ArrayRef(&OpBundle, gc_uses.empty() ? 0 : 1)); ((CallInst*)ret)->setAttributes(attributes); if (cc != CallingConv::C) ((CallInst*)ret)->setCallingConv(cc); if (!sret) - result = ret; + result = ret; // no need to update sretty here because we know !sret if (0) { // Enable this to turn on SSPREQ (-fstack-protector) on the function containing this ccall ctx.f->addFnAttr(Attribute::StackProtectReq); } if (rt == jl_bottom_type) { CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } // Finally we need to box the result into julia type @@ -1989,14 +2113,16 @@ jl_cgval_t function_sig_t::emit_a_ccall( if (retboxed) { assert(!sret); jlretboxed = true; + ++RetBoxedCCalls; } else if (sret) { jlretboxed = sretboxed; if (!jlretboxed) { // something alloca'd above is SSA if (static_rt) - return mark_julia_slot(result, rt, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); - result = ctx.builder.CreateLoad(cast(result->getType())->getElementType(), result); + return mark_julia_slot(result, rt, NULL, ctx.tbaa().tbaa_stack); + ++SRetCCalls; + result = ctx.builder.CreateLoad(sretty, result); } } else { @@ -2010,22 +2136,21 @@ jl_cgval_t function_sig_t::emit_a_ccall( else if (jlretboxed && !retboxed) { assert(jl_is_datatype(rt)); if (static_rt) { - Value *runtime_bt = literal_pointer_val(ctx, rt); - size_t rtsz = jl_datatype_size(rt); - assert(rtsz > 0); - Value *strct = emit_allocobj(ctx, rtsz, runtime_bt); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)rt); MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; int boxalign = julia_alignment(rt); // copy the data from the return value to the new struct const DataLayout &DL = ctx.builder.GetInsertBlock()->getModule()->getDataLayout(); auto resultTy = result->getType(); + size_t rtsz = jl_datatype_size(rt); if (DL.getTypeStoreSize(resultTy) > rtsz) { // ARM and AArch64 can use a LLVM type larger than the julia type. // When this happens, cast through memory. auto slot = emit_static_alloca(ctx, resultTy); slot->setAlignment(Align(boxalign)); ctx.builder.CreateAlignedStore(result, slot, Align(boxalign)); - emit_memcpy(ctx, strct, tbaa, slot, tbaa, rtsz, boxalign); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + emit_memcpy(ctx, strct, ai, slot, ai, rtsz, boxalign); } else { init_bits_value(ctx, strct, result, tbaa, boxalign); @@ -2042,3 +2167,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( return mark_or_box_ccall_result(ctx, result, jlretboxed, rt, unionall_env, static_rt); } + +// Reset us back to codegen debug type +#undef DEBUG_TYPE +#define DEBUG_TYPE "julia_irgen_codegen" diff --git a/src/ccalltest.c b/src/ccalltest.c index 64a6a3aabfb0b..e35ff38eb7dc8 100644 --- a/src/ccalltest.c +++ b/src/ccalltest.c @@ -13,7 +13,9 @@ #ifdef _OS_WINDOWS_ # define DLLEXPORT __declspec(dllexport) #else -# if defined(_OS_LINUX_) +# if defined(_OS_LINUX_) && !defined(_COMPILER_CLANG_) +// Clang and ld disagree about the proper relocation for STV_PROTECTED, causing +// linker errors. # define DLLEXPORT __attribute__ ((visibility("protected"))) # else # define DLLEXPORT __attribute__ ((visibility("default"))) diff --git a/src/cgmemmgr.cpp b/src/cgmemmgr.cpp index 37e02b0efccbb..15d28ff270c55 100644 --- a/src/cgmemmgr.cpp +++ b/src/cgmemmgr.cpp @@ -218,7 +218,12 @@ static _Atomic(size_t) map_offset{0}; // Hopefully no one will set a ulimit for this to be a problem... static constexpr size_t map_size_inc_default = 128 * 1024 * 1024; static size_t map_size = 0; -static uv_mutex_t shared_map_lock; +static struct _make_shared_map_lock { + uv_mutex_t mtx; + _make_shared_map_lock() { + uv_mutex_init(&mtx); + }; +} shared_map_lock; static size_t get_map_size_inc() { @@ -264,7 +269,7 @@ static void *alloc_shared_page(size_t size, size_t *id, bool exec) *id = off; size_t map_size_inc = get_map_size_inc(); if (__unlikely(off + size > map_size)) { - uv_mutex_lock(&shared_map_lock); + uv_mutex_lock(&shared_map_lock.mtx); size_t old_size = map_size; while (off + size > map_size) map_size += map_size_inc; @@ -275,7 +280,7 @@ static void *alloc_shared_page(size_t size, size_t *id, bool exec) abort(); } } - uv_mutex_unlock(&shared_map_lock); + uv_mutex_unlock(&shared_map_lock.mtx); } return create_shared_map(size, off); } @@ -295,7 +300,7 @@ ssize_t pwrite_addr(int fd, const void *buf, size_t nbyte, uintptr_t addr) // However, it seems possible to change this at kernel compile time. // pwrite doesn't support offset with sign bit set but lseek does. - // This is obviously not thread safe but none of the mem manager does anyway... + // This is obviously not thread-safe but none of the mem manager does anyway... // From the kernel code, `lseek` with `SEEK_SET` can't fail. // However, this can possibly confuse the glibc wrapper to think that // we have invalid input value. Use syscall directly to be sure. @@ -313,7 +318,6 @@ ssize_t pwrite_addr(int fd, const void *buf, size_t nbyte, uintptr_t addr) // Use `get_self_mem_fd` which has a guard to call this only once. static int _init_self_mem() { - uv_mutex_init(&shared_map_lock); struct utsname kernel; uname(&kernel); int major, minor; @@ -856,8 +860,11 @@ uint8_t *RTDyldMemoryManagerJL::allocateCodeSection(uintptr_t Size, StringRef SectionName) { // allocating more than one code section can confuse libunwind. +#if !defined(_COMPILER_MSAN_ENABLED_) && !defined(_COMPILER_ASAN_ENABLED_) + // TODO: Figure out why msan and now asan too need this. assert(!code_allocated); code_allocated = true; +#endif total_allocated += Size; if (exe_alloc) return (uint8_t*)exe_alloc->alloc(Size, Alignment); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index b219498315905..9e42a6b246e9b 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -2,6 +2,47 @@ // utility procedures used in code generation +// Mark our stats as being from cgutils +#undef DEBUG_TYPE +#define DEBUG_TYPE "julia_irgen_cgutils" + +STATISTIC(EmittedPointerFromObjref, "Number of emitted pointer_from_objref calls"); +STATISTIC(EmittedPointerBitcast, "Number of emitted pointer bitcasts"); +STATISTIC(EmittedTypeof, "Number of emitted typeof instructions"); +STATISTIC(EmittedErrors, "Number of emitted errors"); +STATISTIC(EmittedConditionalErrors, "Number of emitted conditional errors"); +STATISTIC(EmittedExceptions, "Number of emitted exceptions"); +STATISTIC(EmittedConditionalExceptions, "Number of emitted conditional exceptions"); +STATISTIC(EmittedNullchecks, "Number of emitted nullchecks"); +STATISTIC(EmittedGuards, "Number of emitted guards"); +STATISTIC(EmittedIsaUnions, "Number of emitted isa-union checks"); +STATISTIC(EmittedIsa, "Number of emitted isa checks"); +STATISTIC(EmittedTypechecks, "Number of emitted typechecks"); +STATISTIC(EmittedConcretechecks, "Number of emitted concrete checks"); +STATISTIC(EmittedBoundschecks, "Number of emitted boundschecks"); +STATISTIC(EmittedLockstates, "Number of emitted lockstate value calls"); +STATISTIC(EmittedMemcpys, "Number of emitted memcpy instructions"); +STATISTIC(SkippedMemcpys, "Number of skipped memcpy instructions"); +STATISTIC(EmittedGetfieldUnknowns, "Number of unknown getfield calls emitted"); +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(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"); +STATISTIC(EmittedAllocObjs, "Number of object allocations emitted"); +STATISTIC(EmittedWriteBarriers, "Number of write barriers emitted"); +STATISTIC(EmittedNewStructs, "Number of new structs emitted"); +STATISTIC(EmittedDeferSignal, "Number of deferred signals emitted"); + static Value *track_pjlvalue(jl_codectx_t &ctx, Value *V) { assert(V->getType() == ctx.types().T_pjlvalue); @@ -24,7 +65,7 @@ static Value *decay_derived(jl_codectx_t &ctx, Value *V) if (cast(T)->getAddressSpace() == AddressSpace::Derived) return V; // Once llvm deletes pointer element types, we won't need it here any more either. - Type *NewT = PointerType::get(cast(T)->getElementType(), AddressSpace::Derived); + Type *NewT = PointerType::getWithSamePointeeType(cast(T), AddressSpace::Derived); return ctx.builder.CreateAddrSpaceCast(V, NewT); } @@ -34,7 +75,7 @@ static Value *maybe_decay_tracked(jl_codectx_t &ctx, Value *V) Type *T = V->getType(); if (cast(T)->getAddressSpace() != AddressSpace::Tracked) return V; - Type *NewT = PointerType::get(cast(T)->getElementType(), AddressSpace::Derived); + Type *NewT = PointerType::getWithSamePointeeType(cast(T), AddressSpace::Derived); return ctx.builder.CreateAddrSpaceCast(V, NewT); } @@ -78,7 +119,10 @@ static Value *stringConstPtr( GlobalVariable *gv = get_pointer_to_constant(emission_context, Data, "_j_str", *M); Value *zero = ConstantInt::get(Type::getInt32Ty(irbuilder.getContext()), 0); Value *Args[] = { zero, zero }; - return irbuilder.CreateInBoundsGEP(gv->getValueType(), gv, Args); + return irbuilder.CreateInBoundsGEP(gv->getValueType(), + // Addrspacecast in case globals are in non-0 AS + irbuilder.CreateAddrSpaceCast(gv, gv->getValueType()->getPointerTo(0)), + Args); } @@ -109,11 +153,44 @@ Metadata *to_md_tree(jl_value_t *val, LLVMContext &ctxt) { // --- Debug info --- -static DIType *_julia_type_to_di(jl_codegen_params_t *ctx, jl_value_t *jt, DIBuilder *dbuilder, bool isboxed) +static DICompileUnit *getOrCreateJuliaCU(Module &M, + DICompileUnit::DebugEmissionKind emissionKind, + DICompileUnit::DebugNameTableKind tableKind) +{ + // TODO: share debug objects globally in the context, instead of allocating a new one every time + // or figure out how to delete them afterwards? + // But at least share them a little bit here + auto CUs = M.debug_compile_units(); + for (DICompileUnit *CU : CUs) { + if (CU->getEmissionKind() == emissionKind && + CU->getNameTableKind() == tableKind) + return CU; + } + DIFile *topfile = DIFile::get(M.getContext(), "julia", "."); + DIBuilder dbuilder(M); + DICompileUnit *CU = + dbuilder.createCompileUnit(llvm::dwarf::DW_LANG_Julia + ,topfile // File + ,"julia" // Producer + ,true // isOptimized + ,"" // Flags + ,0 // RuntimeVersion + ,"" // SplitName + ,emissionKind // Kind + ,0 // DWOId + ,true // SplitDebugInlining + ,false // DebugInfoForProfiling + ,tableKind // NameTableKind + ); + dbuilder.finalize(); + return CU; +} + +static DIType *_julia_type_to_di(jl_codegen_params_t *ctx, jl_debugcache_t &debuginfo, jl_value_t *jt, DIBuilder *dbuilder, bool isboxed) { jl_datatype_t *jdt = (jl_datatype_t*)jt; if (isboxed || !jl_is_datatype(jt) || !jdt->isconcretetype) - return jl_pvalue_dillvmt; + return debuginfo.jl_pvalue_dillvmt; assert(jdt->layout); DIType* _ditype = NULL; DIType* &ditype = (ctx ? ctx->ditypes[jdt] : _ditype); @@ -131,10 +208,10 @@ static DIType *_julia_type_to_di(jl_codegen_params_t *ctx, jl_value_t *jt, DIBui jl_value_t *el = jl_field_type_concrete(jdt, i); DIType *di; if (jl_field_isptr(jdt, i)) - di = jl_pvalue_dillvmt; + di = debuginfo.jl_pvalue_dillvmt; // TODO: elseif jl_islayout_inline else - di = _julia_type_to_di(ctx, el, dbuilder, false); + di = _julia_type_to_di(ctx, debuginfo, el, dbuilder, false); Elements[i] = di; } DINodeArray ElemArray = dbuilder->getOrCreateArray(Elements); @@ -157,14 +234,56 @@ static DIType *_julia_type_to_di(jl_codegen_params_t *ctx, jl_value_t *jt, DIBui } else { // return a typealias for types with hidden content - ditype = dbuilder->createTypedef(jl_pvalue_dillvmt, tname, NULL, 0, NULL); + ditype = dbuilder->createTypedef(debuginfo.jl_pvalue_dillvmt, tname, NULL, 0, NULL); } return ditype; } -static DIType *julia_type_to_di(jl_codectx_t &ctx, jl_value_t *jt, DIBuilder *dbuilder, bool isboxed) +static DIType *julia_type_to_di(jl_codectx_t &ctx, jl_debugcache_t &debuginfo, jl_value_t *jt, DIBuilder *dbuilder, bool isboxed) { - return _julia_type_to_di(&ctx.emission_context, jt, dbuilder, isboxed); + return _julia_type_to_di(&ctx.emission_context, debuginfo, jt, dbuilder, isboxed); +} + +void jl_debugcache_t::initialize(Module *m) { + if (initialized) { + return; + } + initialized = true; + // add needed base debugging definitions to our LLVM environment + DIBuilder dbuilder(*m); + DIFile *julia_h = dbuilder.createFile("julia.h", ""); + DICompositeType *jl_value_dillvmt = dbuilder.createStructType(nullptr, + "jl_value_t", + julia_h, + 71, // At the time of this writing. Not sure if it's worth it to keep this in sync + 0 * 8, // sizeof(jl_value_t) * 8, + __alignof__(void*) * 8, // __alignof__(jl_value_t) * 8, + DINode::FlagZero, // Flags + nullptr, // Derived from + nullptr); // Elements - will be corrected later + + jl_pvalue_dillvmt = dbuilder.createPointerType(jl_value_dillvmt, sizeof(jl_value_t*) * 8, + __alignof__(jl_value_t*) * 8); + + SmallVector Elts; + std::vector diargs(0); + Elts.push_back(jl_pvalue_dillvmt); + dbuilder.replaceArrays(jl_value_dillvmt, + dbuilder.getOrCreateArray(Elts)); + + jl_ppvalue_dillvmt = dbuilder.createPointerType(jl_pvalue_dillvmt, sizeof(jl_value_t**) * 8, + __alignof__(jl_value_t**) * 8); + + diargs.push_back(jl_pvalue_dillvmt); // Return Type (ret value) + diargs.push_back(jl_pvalue_dillvmt); // First Argument (function) + diargs.push_back(jl_ppvalue_dillvmt); // Second Argument (argv) + // Third argument (length(argv)) + diargs.push_back(_julia_type_to_di(NULL, *this, (jl_value_t*)jl_int32_type, &dbuilder, false)); + + jl_di_func_sig = dbuilder.createSubroutineType( + dbuilder.getOrCreateTypeArray(diargs)); + jl_di_func_null_sig = dbuilder.createSubroutineType( + dbuilder.getOrCreateTypeArray(None)); } static Value *emit_pointer_from_objref(jl_codectx_t &ctx, Value *V) @@ -179,6 +298,7 @@ static Value *emit_pointer_from_objref(jl_codectx_t &ctx, Value *V) Function *F = prepare_call(pointer_from_objref_func); CallInst *Call = ctx.builder.CreateCall(F, V); Call->setAttributes(F->getAttributes()); + ++EmittedPointerFromObjref; return Call; } @@ -203,7 +323,7 @@ static Value *get_gc_root_for(const jl_cgval_t &x) static inline Constant *literal_static_pointer_val(const void *p, Type *T); -static Value *julia_pgv(jl_codectx_t &ctx, const char *cname, void *addr) +static Constant *julia_pgv(jl_codectx_t &ctx, const char *cname, void *addr) { // emit a GlobalVariable for a jl_value_t named "cname" // store the name given so we can reuse it (facilitating merging later) @@ -223,7 +343,7 @@ static Value *julia_pgv(jl_codectx_t &ctx, const char *cname, void *addr) } if (gv == nullptr) gv = new GlobalVariable(*M, ctx.types().T_pjlvalue, - false, GlobalVariable::PrivateLinkage, + false, GlobalVariable::ExternalLinkage, NULL, localname); // LLVM passes sometimes strip metadata when moving load around // since the load at the new location satisfy the same condition as the original one. @@ -235,40 +355,40 @@ static Value *julia_pgv(jl_codectx_t &ctx, const char *cname, void *addr) return gv; } -static Value *julia_pgv(jl_codectx_t &ctx, const char *prefix, jl_sym_t *name, jl_module_t *mod, void *addr) +static Constant *julia_pgv(jl_codectx_t &ctx, const char *prefix, jl_sym_t *name, jl_module_t *mod, void *addr) { // emit a GlobalVariable for a jl_value_t, using the prefix, name, and module to - // to create a readable name of the form prefixModA.ModB.name - size_t len = strlen(jl_symbol_name(name)) + strlen(prefix) + 1; + // to create a readable name of the form prefixModA.ModB.name# + // reverse-of-reverse algorithm + std::string finalname; + StringRef name_str(jl_symbol_name(name)); + finalname.resize(name_str.size() + 1); + finalname[0] = '#'; + std::reverse_copy(name_str.begin(), name_str.end(), finalname.begin() + 1); jl_module_t *parent = mod, *prev = NULL; - while (parent != NULL && parent != prev) { - len += strlen(jl_symbol_name(parent->name))+1; + while (parent && parent != prev) { + size_t orig_end = finalname.size() + 1; + StringRef parent_name(jl_symbol_name(parent->name)); + finalname.resize(orig_end + parent_name.size()); + finalname[orig_end - 1] = '.'; + std::reverse_copy(parent_name.begin(), parent_name.end(), finalname.begin() + orig_end); prev = parent; parent = parent->parent; } - char *fullname = (char*)alloca(len); - strcpy(fullname, prefix); - len -= strlen(jl_symbol_name(name)) + 1; - strcpy(fullname + len, jl_symbol_name(name)); - parent = mod; - prev = NULL; - while (parent != NULL && parent != prev) { - size_t part = strlen(jl_symbol_name(parent->name)) + 1; - strcpy(fullname + len - part, jl_symbol_name(parent->name)); - fullname[len - 1] = '.'; - len -= part; - prev = parent; - parent = parent->parent; - } - return julia_pgv(ctx, fullname, addr); + size_t orig_end = finalname.size(); + StringRef prefix_name(prefix); + finalname.resize(orig_end + prefix_name.size()); + std::reverse_copy(prefix_name.begin(), prefix_name.end(), finalname.begin() + orig_end); + std::reverse(finalname.begin(), finalname.end()); + return julia_pgv(ctx, finalname.c_str(), addr); } static JuliaVariable *julia_const_gv(jl_value_t *val); -static Value *literal_pointer_val_slot(jl_codectx_t &ctx, jl_value_t *p) +static Constant *literal_pointer_val_slot(jl_codectx_t &ctx, jl_value_t *p) { // emit a pointer to a jl_value_t* which will allow it to be valid across reloading code // also, try to give it a nice name for gdb, for easy identification - if (!imaging_mode) { + if (!ctx.emission_context.imaging) { // TODO: this is an optimization, but is it useful or premature // (it'll block any attempt to cache these, but can be simply deleted) Module *M = jl_Module; @@ -284,6 +404,12 @@ static Value *literal_pointer_val_slot(jl_codectx_t &ctx, jl_value_t *p) } if (jl_is_datatype(p)) { jl_datatype_t *addr = (jl_datatype_t*)p; + if (addr->smalltag) { + // some common builtin datatypes have a special pool for accessing them by smalltag id + Constant *tag = ConstantInt::get(getInt32Ty(ctx.builder.getContext()), addr->smalltag << 4); + Constant *smallp = ConstantExpr::getInBoundsGetElementPtr(getInt8Ty(ctx.builder.getContext()), prepare_global_in(jl_Module, jlsmall_typeof_var), tag); + return ConstantExpr::getBitCast(smallp, ctx.types().T_ppjlvalue); + } // DataTypes are prefixed with a + return julia_pgv(ctx, "+", addr->name->name, addr->name->module, p); } @@ -338,17 +464,16 @@ static unsigned julia_alignment(jl_value_t *jt) return alignment; } -static inline void maybe_mark_argument_dereferenceable(Argument *A, jl_value_t *jt) +static inline void maybe_mark_argument_dereferenceable(AttrBuilder &B, jl_value_t *jt) { - AttrBuilder B; B.addAttribute(Attribute::NonNull); - // The `dereferencable` below does not imply `nonnull` for non addrspace(0) pointers. + B.addAttribute(Attribute::NoUndef); + // The `dereferenceable` below does not imply `nonnull` for non addrspace(0) pointers. size_t size = dereferenceable_size(jt); if (size) { B.addDereferenceableAttr(size); B.addAlignmentAttr(julia_alignment(jt)); } - A->addAttrs(B); } static inline Instruction *maybe_mark_load_dereferenceable(Instruction *LI, bool can_be_null, @@ -356,7 +481,7 @@ static inline Instruction *maybe_mark_load_dereferenceable(Instruction *LI, bool { if (isa(LI->getType())) { if (!can_be_null) - // The `dereferencable` below does not imply `nonnull` for non addrspace(0) pointers. + // The `dereferenceable` below does not imply `nonnull` for non addrspace(0) pointers. LI->setMetadata(LLVMContext::MD_nonnull, MDNode::get(LI->getContext(), None)); if (size) { Metadata *OP = ConstantAsMetadata::get(ConstantInt::get(getInt64Ty(LI->getContext()), size)); @@ -385,10 +510,11 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p) { if (p == NULL) return Constant::getNullValue(ctx.types().T_pjlvalue); - if (!imaging_mode) + if (!ctx.emission_context.imaging) return literal_static_pointer_val(p, ctx.types().T_pjlvalue); Value *pgv = literal_pointer_val_slot(ctx, p); - return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, jl_typeof(p))); } @@ -399,11 +525,13 @@ static Value *literal_pointer_val(jl_codectx_t &ctx, jl_binding_t *p) // emit a pointer to any jl_value_t which will be valid across reloading code if (p == NULL) return Constant::getNullValue(ctx.types().T_pjlvalue); - if (!imaging_mode) + if (!ctx.emission_context.imaging) return literal_static_pointer_val(p, ctx.types().T_pjlvalue); // bindings are prefixed with jl_bnd# - Value *pgv = julia_pgv(ctx, "jl_bnd#", p->name, p->owner, p); - return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + jl_globalref_t *gr = p->globalref; + Value *pgv = gr ? julia_pgv(ctx, "jl_bnd#", gr->name, gr->mod, p) : julia_pgv(ctx, "jl_bnd#", p); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*))), false, sizeof(jl_binding_t), alignof(jl_binding_t))); } @@ -414,9 +542,8 @@ static Value *emit_bitcast(jl_codectx_t &ctx, Value *v, Type *jl_value) if (isa(jl_value) && v->getType()->getPointerAddressSpace() != jl_value->getPointerAddressSpace()) { // Cast to the proper address space - Type *jl_value_addr = - PointerType::get(cast(jl_value)->getElementType(), - v->getType()->getPointerAddressSpace()); + Type *jl_value_addr = PointerType::getWithSamePointeeType(cast(jl_value), v->getType()->getPointerAddressSpace()); + ++EmittedPointerBitcast; return ctx.builder.CreateBitCast(v, jl_value_addr); } else { @@ -430,25 +557,26 @@ static Value *maybe_bitcast(jl_codectx_t &ctx, Value *V, Type *to) { return V; } -static Value *julia_binding_gv(jl_codectx_t &ctx, Value *bv) +static Value *julia_binding_pvalue(jl_codectx_t &ctx, Value *bv) { - Value *offset = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_binding_t, value) / sizeof(size_t)); + bv = emit_bitcast(ctx, bv, ctx.types().T_pprjlvalue); + Value *offset = ConstantInt::get(ctx.types().T_size, offsetof(jl_binding_t, value) / ctx.types().sizeof_ptr); return ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, bv, offset); } static Value *julia_binding_gv(jl_codectx_t &ctx, jl_binding_t *b) { - // emit a literal_pointer_val to the value field of a jl_binding_t + // emit a literal_pointer_val to a jl_binding_t // binding->value are prefixed with * - Value *bv; - if (imaging_mode) - bv = emit_bitcast(ctx, - tbaa_decorate(ctx.tbaa().tbaa_const, - ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, julia_pgv(ctx, "*", b->name, b->owner, b), Align(sizeof(void*)))), - ctx.types().T_pprjlvalue); - else - bv = ConstantExpr::getBitCast(literal_static_pointer_val(b, ctx.types().T_pjlvalue), ctx.types().T_pprjlvalue); - return julia_binding_gv(ctx, bv); + if (ctx.emission_context.imaging) { + jl_globalref_t *gr = b->globalref; + Value *pgv = gr ? julia_pgv(ctx, "*", gr->name, gr->mod, b) : julia_pgv(ctx, "*jl_bnd#", b); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, pgv, Align(sizeof(void*)))); + } + else { + return literal_static_pointer_val(b, ctx.types().T_pjlvalue); + } } // --- mapping between julia and llvm types --- @@ -507,10 +635,10 @@ static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed return _julia_type_to_llvm(&ctx.emission_context, ctx.builder.getContext(), jt, isboxed); } -extern "C" JL_DLLEXPORT -Type *jl_type_to_llvm_impl(jl_value_t *jt, bool *isboxed) +extern "C" JL_DLLEXPORT_CODEGEN +Type *jl_type_to_llvm_impl(jl_value_t *jt, LLVMContextRef ctxt, bool *isboxed) { - return _julia_type_to_llvm(NULL, jl_LLVMContext, jt, isboxed); + return _julia_type_to_llvm(NULL, *unwrap(ctxt), jt, isboxed); } @@ -519,7 +647,7 @@ static Type *bitstype_to_llvm(jl_value_t *bt, LLVMContext &ctxt, bool llvmcall = { assert(jl_is_primitivetype(bt)); if (bt == (jl_value_t*)jl_bool_type) - return getInt8Ty(ctxt); + return llvmcall ? getInt1Ty(ctxt) : getInt8Ty(ctxt); if (bt == (jl_value_t*)jl_int32_type) return getInt32Ty(ctxt); if (bt == (jl_value_t*)jl_int64_type) @@ -761,7 +889,8 @@ static bool is_uniontype_allunboxed(jl_value_t *typ) return for_each_uniontype_small([&](unsigned, jl_datatype_t*) {}, typ, counter); } -static Value *emit_typeof_boxed(jl_codectx_t &ctx, const jl_cgval_t &p); +static Value *emit_typeof(jl_codectx_t &ctx, Value *v, bool maybenull, bool justtag, bool notag=false); +static Value *emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybenull=false, bool justtag=false); static unsigned get_box_tindex(jl_datatype_t *jt, jl_value_t *ut) { @@ -781,85 +910,214 @@ static unsigned get_box_tindex(jl_datatype_t *jt, jl_value_t *ut) // --- generating various field accessors --- -static Value *emit_nthptr_addr(jl_codectx_t &ctx, Value *v, ssize_t n, bool gctracked = true) +static Constant *julia_const_to_llvm(jl_codectx_t &ctx, jl_value_t *e); + +static Value *data_pointer(jl_codectx_t &ctx, const jl_cgval_t &x) { - return ctx.builder.CreateInBoundsGEP( - ctx.types().T_prjlvalue, - emit_bitcast(ctx, maybe_decay_tracked(ctx, v), ctx.types().T_pprjlvalue), - ConstantInt::get(getSizeTy(ctx.builder.getContext()), n)); + assert(x.ispointer()); + Value *data; + if (x.constant) { + Constant *val = julia_const_to_llvm(ctx, x.constant); + if (val) + data = get_pointer_to_constant(ctx.emission_context, val, "_j_const", *jl_Module); + else + data = literal_pointer_val(ctx, x.constant); + } + else if (x.V == NULL) { + // might be a ghost union with tindex but no actual pointer + data = NULL; + } + else { + data = maybe_decay_tracked(ctx, x.V); + } + return data; } -static Value *emit_nthptr_addr(jl_codectx_t &ctx, Value *v, Value *idx) +static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, + jl_aliasinfo_t const &src_ai, uint64_t sz, unsigned align, bool is_volatile) { - return ctx.builder.CreateInBoundsGEP( - ctx.types().T_prjlvalue, - emit_bitcast(ctx, maybe_decay_tracked(ctx, v), ctx.types().T_pprjlvalue), - idx); + if (sz == 0) + return; + assert(align && "align must be specified"); + // If the types are small and simple, use load and store directly. + // Going through memcpy can cause LLVM (e.g. SROA) to create bitcasts between float and int + // that interferes with other optimizations. + // TODO: Restore this for opaque pointers? Needs extra type information from the caller. + if (ctx.builder.getContext().supportsTypedPointers() && sz <= 64) { + // The size limit is arbitrary but since we mainly care about floating points and + // machine size vectors this should be enough. + const DataLayout &DL = jl_Module->getDataLayout(); + auto srcty = cast(src->getType()); + //TODO unsafe nonopaque pointer + auto srcel = srcty->getNonOpaquePointerElementType(); + auto dstty = cast(dst->getType()); + //TODO unsafe nonopaque pointer + auto dstel = dstty->getNonOpaquePointerElementType(); + while (srcel->isArrayTy() && srcel->getArrayNumElements() == 1) { + src = ctx.builder.CreateConstInBoundsGEP2_32(srcel, src, 0, 0); + srcel = srcel->getArrayElementType(); + srcty = srcel->getPointerTo(); + } + while (dstel->isArrayTy() && dstel->getArrayNumElements() == 1) { + dst = ctx.builder.CreateConstInBoundsGEP2_32(dstel, dst, 0, 0); + dstel = dstel->getArrayElementType(); + dstty = dstel->getPointerTo(); + } + + llvm::Type *directel = nullptr; + if (srcel->isSized() && srcel->isSingleValueType() && DL.getTypeStoreSize(srcel) == sz) { + directel = srcel; + dst = emit_bitcast(ctx, dst, srcty); + } + else if (dstel->isSized() && dstel->isSingleValueType() && + DL.getTypeStoreSize(dstel) == sz) { + directel = dstel; + src = emit_bitcast(ctx, src, dstty); + } + if (directel) { + auto val = src_ai.decorateInst(ctx.builder.CreateAlignedLoad(directel, src, Align(align), is_volatile)); + dst_ai.decorateInst(ctx.builder.CreateAlignedStore(val, dst, Align(align), is_volatile)); + ++SkippedMemcpys; + return; + } + } + ++EmittedMemcpys; + + // the memcpy intrinsic does not allow to specify different alias tags + // for the load part (x.tbaa) and the store part (ctx.tbaa().tbaa_stack). + // since the tbaa lattice has to be a tree we have unfortunately + // x.tbaa ∪ ctx.tbaa().tbaa_stack = tbaa_root if x.tbaa != ctx.tbaa().tbaa_stack + + // Now that we use scoped aliases to label disparate regions of memory, the TBAA + // metadata should be revisited so that it only represents memory layouts. Once + // that's done, we can expect that in most cases tbaa(src) == tbaa(dst) and the + // above problem won't be as serious. + + auto merged_ai = dst_ai.merge(src_ai); + ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, + merged_ai.tbaa, merged_ai.tbaa_struct, merged_ai.scope, merged_ai.noalias); } -static LoadInst *emit_nthptr_recast(jl_codectx_t &ctx, Value *v, Value *idx, MDNode *tbaa, Type *type) +static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, + jl_aliasinfo_t const &src_ai, Value *sz, unsigned align, bool is_volatile) { - // p = (jl_value_t**)v; *(type*)&p[n] - Value *vptr = emit_nthptr_addr(ctx, v, idx); - return cast(tbaa_decorate(tbaa, ctx.builder.CreateLoad(type, - emit_bitcast(ctx, vptr, PointerType::get(type, 0))))); + if (auto const_sz = dyn_cast(sz)) { + emit_memcpy_llvm(ctx, dst, dst_ai, src, src_ai, const_sz->getZExtValue(), align, is_volatile); + return; + } + ++EmittedMemcpys; + + auto merged_ai = dst_ai.merge(src_ai); + ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, + merged_ai.tbaa, merged_ai.tbaa_struct, merged_ai.scope, merged_ai.noalias); } -static LoadInst *emit_nthptr_recast(jl_codectx_t &ctx, Value *v, ssize_t n, MDNode *tbaa, Type *type) +template +static void emit_memcpy(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, Value *src, + jl_aliasinfo_t const &src_ai, T1 &&sz, unsigned align, bool is_volatile=false) { - // p = (jl_value_t**)v; *(type*)&p[n] - Value *vptr = emit_nthptr_addr(ctx, v, n); - return cast(tbaa_decorate(tbaa, ctx.builder.CreateLoad(type, - emit_bitcast(ctx, vptr, PointerType::get(type, 0))))); - } + emit_memcpy_llvm(ctx, dst, dst_ai, src, src_ai, sz, align, is_volatile); +} -static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &v); +template +static void emit_memcpy(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const &dst_ai, const jl_cgval_t &src, + T1 &&sz, unsigned align, bool is_volatile=false) +{ + auto src_ai = jl_aliasinfo_t::fromTBAA(ctx, src.tbaa); + emit_memcpy_llvm(ctx, dst, dst_ai, data_pointer(ctx, src), src_ai, sz, align, is_volatile); +} -// Returns ctx.types().T_prjlvalue -static Value *emit_typeof(jl_codectx_t &ctx, Value *tt) +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); + 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) { - assert(tt != NULL && !isa(tt) && "expected a conditionally boxed value"); - return ctx.builder.CreateCall(prepare_call(jl_typeof_func), {tt}); + if (dt->smalltag) + return ConstantInt::get(ctx.types().T_size, dt->smalltag << 4); + return ctx.builder.CreatePtrToInt(literal_pointer_val(ctx, (jl_value_t*)dt), ctx.types().T_size); } -static jl_cgval_t emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p) +// Returns justtag ? ctx.types.T_size : ctx.types().T_prjlvalue +static Value *emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p, bool maybenull, bool justtag) { // given p, compute its type + jl_datatype_t *dt = NULL; if (p.constant) - return mark_julia_const(ctx, jl_typeof(p.constant)); - if (p.isboxed && !jl_is_concrete_type(p.typ)) { - if (jl_is_type_type(p.typ)) { - jl_value_t *tp = jl_tparam0(p.typ); - if (!jl_is_type(tp) || jl_is_concrete_type(tp)) { - // convert 1::Type{1} ==> typeof(1) ==> Int - return mark_julia_const(ctx, jl_typeof(tp)); - } + dt = (jl_datatype_t*)jl_typeof(p.constant); + else if (jl_is_concrete_type(p.typ)) + dt = (jl_datatype_t*)p.typ; + if (dt) { + if (justtag) + return emit_tagfrom(ctx, dt); + return track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)dt)); + } + auto notag = [justtag] (jl_value_t *typ) { + // compute if the tag is always a type (not a builtin tag) + // based on having no intersection with one of the special types + // this doesn't matter if the user just wants the tag value + if (justtag) + return false; + jl_value_t *uw = jl_unwrap_unionall(typ); + if (jl_is_datatype(uw)) { // quick path to catch common cases + jl_datatype_t *dt = (jl_datatype_t*)uw; + assert(!dt->smalltag); + if (!dt->name->abstract) + return true; + if (dt == jl_any_type) + return false; } - return mark_julia_type(ctx, emit_typeof(ctx, p.V), true, jl_datatype_type); - } + if (jl_has_intersect_type_not_kind(typ)) + return false; + for (size_t i = 0; i < jl_tags_count; i++) { + jl_datatype_t *dt = small_typeof[(i << 4) / sizeof(*small_typeof)]; + if (dt && !jl_has_empty_intersection((jl_value_t*)dt, typ)) + return false; + } + return true; + }; + if (p.isboxed) + return emit_typeof(ctx, p.V, maybenull, justtag, notag(p.typ)); if (p.TIndex) { Value *tindex = ctx.builder.CreateAnd(p.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); bool allunboxed = is_uniontype_allunboxed(p.typ); - Value *datatype_or_p = imaging_mode ? Constant::getNullValue(ctx.types().T_ppjlvalue) : Constant::getNullValue(ctx.types().T_prjlvalue); + Type *expr_type = justtag ? ctx.types().T_size : ctx.emission_context.imaging ? ctx.types().T_pjlvalue : ctx.types().T_prjlvalue; + Value *datatype_or_p = Constant::getNullValue(ctx.emission_context.imaging ? expr_type->getPointerTo() : expr_type); unsigned counter = 0; for_each_uniontype_small( [&](unsigned idx, jl_datatype_t *jt) { Value *cmp = ctx.builder.CreateICmpEQ(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), idx)); - Value *ptr; - if (imaging_mode) { - ptr = literal_pointer_val_slot(ctx, (jl_value_t*)jt); - } - else { - ptr = track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jt)); + Constant *ptr; + if (justtag && jt->smalltag) { + ptr = ConstantInt::get(expr_type, jt->smalltag << 4); + if (ctx.emission_context.imaging) + ptr = get_pointer_to_constant(ctx.emission_context, ptr, "_j_tag", *jl_Module); } + else if (ctx.emission_context.imaging) + ptr = ConstantExpr::getBitCast(literal_pointer_val_slot(ctx, (jl_value_t*)jt), datatype_or_p->getType()); + else if (justtag) + ptr = ConstantInt::get(expr_type, (uintptr_t)jt); + else + ptr = ConstantExpr::getAddrSpaceCast(literal_static_pointer_val((jl_value_t*)jt, ctx.types().T_pjlvalue), expr_type); datatype_or_p = ctx.builder.CreateSelect(cmp, ptr, datatype_or_p); }, p.typ, counter); auto emit_unboxty = [&] () -> Value* { - if (imaging_mode) - return track_pjlvalue( - ctx, tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, datatype_or_p, Align(sizeof(void*))))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + if (ctx.emission_context.imaging) { + Value *datatype = ai.decorateInst(ctx.builder.CreateAlignedLoad(expr_type, datatype_or_p, Align(sizeof(void*)))); + return justtag ? datatype : track_pjlvalue(ctx, datatype); + } return datatype_or_p; }; Value *res; @@ -870,7 +1128,7 @@ static jl_cgval_t emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p) BasicBlock *mergeBB = BasicBlock::Create(ctx.builder.getContext(), "merge", ctx.f); ctx.builder.CreateCondBr(isnull, boxBB, unboxBB); ctx.builder.SetInsertPoint(boxBB); - auto boxTy = emit_typeof(ctx, p.Vboxed); + auto boxTy = emit_typeof(ctx, p.Vboxed, maybenull, justtag, notag(p.typ)); ctx.builder.CreateBr(mergeBB); boxBB = ctx.builder.GetInsertBlock(); // could have changed ctx.builder.SetInsertPoint(unboxBB); @@ -878,7 +1136,7 @@ static jl_cgval_t emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p) ctx.builder.CreateBr(mergeBB); unboxBB = ctx.builder.GetInsertBlock(); // could have changed ctx.builder.SetInsertPoint(mergeBB); - auto phi = ctx.builder.CreatePHI(ctx.types().T_prjlvalue, 2); + auto phi = ctx.builder.CreatePHI(boxTy->getType(), 2); phi->addIncoming(boxTy, boxBB); phi->addIncoming(unboxTy, unboxBB); res = phi; @@ -886,36 +1144,37 @@ static jl_cgval_t emit_typeof(jl_codectx_t &ctx, const jl_cgval_t &p) else { res = emit_unboxty(); } - return mark_julia_type(ctx, res, true, jl_datatype_type); + return res; } - return mark_julia_const(ctx, p.typ); -} - -// Returns ctx.types().T_prjlvalue -static Value *emit_typeof_boxed(jl_codectx_t &ctx, const jl_cgval_t &p) -{ - return boxed(ctx, emit_typeof(ctx, p)); + assert(0 && "what is this struct"); abort(); } static Value *emit_datatype_types(jl_codectx_t &ctx, Value *dt) { Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), ctx.types().T_ppjlvalue); - Value *Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_t, types) / sizeof(void*)); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad( + Value *Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_t, types) / sizeof(void*)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad( ctx.types().T_pjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_pjlvalue, Ptr, Idx), Align(sizeof(void*)))); } static Value *emit_datatype_nfields(jl_codectx_t &ctx, Value *dt) { - Value *type_svec = emit_bitcast(ctx, emit_datatype_types(ctx, dt), getSizePtrTy(ctx.builder.getContext())); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), type_svec, Align(sizeof(void*)))); + Value *type_svec = emit_bitcast(ctx, emit_datatype_types(ctx, dt), ctx.types().T_size->getPointerTo()); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_size, type_svec, Align(sizeof(void*)))); } static Value *emit_datatype_size(jl_codectx_t &ctx, Value *dt) { - Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), getInt32PtrTy(ctx.builder.getContext())); - Value *Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_t, size) / sizeof(int)); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), ctx.builder.CreateInBoundsGEP(getInt32Ty(ctx.builder.getContext()), Ptr, Idx), Align(sizeof(int32_t)))); + 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()); + Value *Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_t, layout) / sizeof(int32_t*)); + 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); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), Ptr, Align(sizeof(int32_t)))); } /* this is valid code, it's simply unused @@ -941,7 +1200,7 @@ static Value *emit_sizeof(jl_codectx_t &ctx, const jl_cgval_t &p) ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); ctx.builder.CreateCondBr(isboxed, dynloadBB, postBB); ctx.builder.SetInsertPoint(dynloadBB); - Value *datatype = emit_typeof(p.V); + Value *datatype = emit_typeof(ctx, p.V, false, false); Value *dyn_size = emit_datatype_size(ctx, datatype); ctx.builder.CreateBr(postBB); dynloadBB = ctx.builder.GetInsertBlock(); // could have changed @@ -961,38 +1220,47 @@ static Value *emit_sizeof(jl_codectx_t &ctx, const jl_cgval_t &p) return ConstantInt::get(getInt32Ty(ctx.builder.getContext()), jl_datatype_size(p.typ)); } else { - Value *datatype = emit_typeof_boxed(ctx, p); + Value *datatype = emit_typeof(ctx, p, false, false); Value *dyn_size = emit_datatype_size(ctx, datatype); return dyn_size; } } -*/ static Value *emit_datatype_mutabl(jl_codectx_t &ctx, Value *dt) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); Value *Ptr = emit_bitcast(ctx, decay_derived(ctx, dt), ctx.types().T_ppint8); - Value *Idx = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_datatype_t, name)); - Value *Nam = tbaa_decorate(ctx.tbaa().tbaa_const, + Value *Idx = ConstantInt::get(ctx.types().T_size, offsetof(jl_datatype_t, name)); + Value *Nam = ai.decorateInst( ctx.builder.CreateAlignedLoad(getInt8PtrTy(ctx.builder.getContext()), ctx.builder.CreateInBoundsGEP(getInt8PtrTy(ctx.builder.getContext()), Ptr, Idx), Align(sizeof(int8_t*)))); - Value *Idx2 = ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_typename_t, n_uninitialized) + sizeof(((jl_typename_t*)nullptr)->n_uninitialized)); - Value *mutabl = tbaa_decorate(ctx.tbaa().tbaa_const, + Value *Idx2 = ConstantInt::get(ctx.types().T_size, offsetof(jl_typename_t, n_uninitialized) + sizeof(((jl_typename_t*)nullptr)->n_uninitialized)); + Value *mutabl = ai.decorateInst( ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), Nam, Idx2), Align(1))); mutabl = ctx.builder.CreateLShr(mutabl, 1); return ctx.builder.CreateTrunc(mutabl, getInt1Ty(ctx.builder.getContext())); } +*/ -static Value *emit_datatype_isprimitivetype(jl_codectx_t &ctx, Value *dt) +static Value *emit_datatype_isprimitivetype(jl_codectx_t &ctx, Value *typ) { - Value *immut = ctx.builder.CreateNot(emit_datatype_mutabl(ctx, dt)); - Value *nofields = ctx.builder.CreateICmpEQ(emit_datatype_nfields(ctx, dt), Constant::getNullValue(getSizeTy(ctx.builder.getContext()))); - Value *sized = ctx.builder.CreateICmpSGT(emit_datatype_size(ctx, dt), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); - return ctx.builder.CreateAnd(immut, ctx.builder.CreateAnd(nofields, sized)); + Value *isprimitive; + isprimitive = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, decay_derived(ctx, typ), getInt8PtrTy(ctx.builder.getContext())), offsetof(jl_datatype_t, hash) + sizeof(((jl_datatype_t*)nullptr)->hash)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + isprimitive = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isprimitive, Align(1))); + isprimitive = ctx.builder.CreateLShr(isprimitive, 7); + isprimitive = ctx.builder.CreateTrunc(isprimitive, getInt1Ty(ctx.builder.getContext())); + return isprimitive; } static Value *emit_datatype_name(jl_codectx_t &ctx, Value *dt) { - Value *vptr = emit_nthptr_addr(ctx, dt, (ssize_t)(offsetof(jl_datatype_t, name) / sizeof(char*))); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, vptr, Align(sizeof(void*)))); + unsigned n = offsetof(jl_datatype_t, name) / sizeof(char*); + Value *vptr = ctx.builder.CreateInBoundsGEP( + ctx.types().T_pjlvalue, + emit_bitcast(ctx, maybe_decay_tracked(ctx, dt), ctx.types().T_ppjlvalue), + ConstantInt::get(ctx.types().T_size, n)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, vptr, Align(sizeof(void*)))); } // --- generating various error checks --- @@ -1002,6 +1270,7 @@ static Value *emit_datatype_name(jl_codectx_t &ctx, Value *dt) static void just_emit_error(jl_codectx_t &ctx, Function *F, const std::string &txt) { + ++EmittedErrors; ctx.builder.CreateCall(F, stringConstPtr(ctx.emission_context, ctx.builder, txt)); } @@ -1021,6 +1290,7 @@ static void emit_error(jl_codectx_t &ctx, const std::string &txt) // DO NOT PASS IN A CONST CONDITION! static void error_unless(jl_codectx_t &ctx, Value *cond, const std::string &msg) { + ++EmittedConditionalErrors; BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); ctx.builder.CreateCondBr(cond, passBB, failBB); @@ -1034,6 +1304,7 @@ static void error_unless(jl_codectx_t &ctx, Value *cond, const std::string &msg) static void raise_exception(jl_codectx_t &ctx, Value *exc, BasicBlock *contBB=nullptr) { + ++EmittedExceptions; ctx.builder.CreateCall(prepare_call(jlthrow_func), { mark_callee_rooted(ctx, exc) }); ctx.builder.CreateUnreachable(); if (!contBB) { @@ -1048,6 +1319,7 @@ static void raise_exception(jl_codectx_t &ctx, Value *exc, // DO NOT PASS IN A CONST CONDITION! static void raise_exception_unless(jl_codectx_t &ctx, Value *cond, Value *exc) { + ++EmittedConditionalExceptions; BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(),"fail",ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(),"pass"); ctx.builder.CreateCondBr(cond, passBB, failBB); @@ -1057,6 +1329,7 @@ static void raise_exception_unless(jl_codectx_t &ctx, Value *cond, Value *exc) static Value *null_pointer_cmp(jl_codectx_t &ctx, Value *v) { + ++EmittedNullchecks; return ctx.builder.CreateICmpNE(v, Constant::getNullValue(v->getType())); } @@ -1081,6 +1354,7 @@ static Value *emit_guarded_test(jl_codectx_t &ctx, Value *ifnot, Value *defval, return defval; return func(); } + ++EmittedGuards; BasicBlock *currBB = ctx.builder.GetInsertBlock(); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "guard_pass", ctx.f); BasicBlock *exitBB = BasicBlock::Create(ctx.builder.getContext(), "guard_exit", ctx.f); @@ -1129,13 +1403,56 @@ static Value *emit_nullcheck_guard2(jl_codectx_t &ctx, Value *nullcheck1, }); } -static void emit_type_error(jl_codectx_t &ctx, const jl_cgval_t &x, Value *type, const std::string &msg) +// Returns typeof(v), or null if v is a null pointer at run time and maybenull is true. +// This is used when the value might have come from an undefined value (a PhiNode), +// yet jl_max_tags try to read its type to compute a union index when moving the value (a PiNode). +// Returns a ctx.types().T_prjlvalue typed Value +static Value *emit_typeof(jl_codectx_t &ctx, Value *v, bool maybenull, bool justtag, bool notag) +{ + ++EmittedTypeof; + assert(v != NULL && !isa(v) && "expected a conditionally boxed value"); + Value *nonnull = maybenull ? null_pointer_cmp(ctx, v) : ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); + Function *typeof = prepare_call(jl_typeof_func); + return emit_guarded_test(ctx, nonnull, Constant::getNullValue(justtag ? ctx.types().T_size : typeof->getReturnType()), [&] { + // e.g. emit_typeof(ctx, v) + Value *typetag = ctx.builder.CreateCall(typeof, {v}); + if (notag) + return typetag; + Value *tag = ctx.builder.CreatePtrToInt(emit_pointer_from_objref(ctx, typetag), ctx.types().T_size); + if (justtag) + return tag; + auto issmall = ctx.builder.CreateICmpULT(tag, ConstantInt::get(tag->getType(), (uintptr_t)jl_max_tags << 4)); + return emit_guarded_test(ctx, issmall, typetag, [&] { + // we lied a bit: this wasn't really an object (though it was valid for GC rooting) + // and we need to use it as an index to get the real object now + Module *M = jl_Module; + Value *smallp = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), prepare_global_in(M, jlsmall_typeof_var), tag); + smallp = ctx.builder.CreateBitCast(smallp, typetag->getType()->getPointerTo(0)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + auto small = ctx.builder.CreateAlignedLoad(typetag->getType(), smallp, M->getDataLayout().getPointerABIAlignment(0)); + small->setMetadata(LLVMContext::MD_nonnull, MDNode::get(M->getContext(), None)); + return ai.decorateInst(small); + }); + }); +} + +static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &v, bool is_promotable=false); + +static void just_emit_type_error(jl_codectx_t &ctx, const jl_cgval_t &x, Value *type, const std::string &msg) { Value *msg_val = stringConstPtr(ctx.emission_context, ctx.builder, msg); ctx.builder.CreateCall(prepare_call(jltypeerror_func), { msg_val, maybe_decay_untracked(ctx, type), mark_callee_rooted(ctx, boxed(ctx, x))}); } +static void emit_type_error(jl_codectx_t &ctx, const jl_cgval_t &x, Value *type, const std::string &msg) +{ + just_emit_type_error(ctx, x, type, msg); + ctx.builder.CreateUnreachable(); + BasicBlock *cont = BasicBlock::Create(ctx.builder.getContext(), "after_type_error", ctx.f); + ctx.builder.SetInsertPoint(cont); +} + // Should agree with `emit_isa` below static bool _can_optimize_isa(jl_value_t *type, int &counter) { @@ -1146,6 +1463,8 @@ static bool _can_optimize_isa(jl_value_t *type, int &counter) return (_can_optimize_isa(((jl_uniontype_t*)type)->a, counter) && _can_optimize_isa(((jl_uniontype_t*)type)->b, counter)); } + if (type == (jl_value_t*)jl_type_type) + return true; if (jl_is_type_type(type) && jl_pointer_egal(type)) return true; if (jl_has_intersect_type_not_kind(type)) @@ -1165,12 +1484,38 @@ 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_value_t *dt) -{ - assert(jl_is_concrete_type(dt)); - return ctx.builder.CreateICmpEQ( - emit_typeof_boxed(ctx, arg), - track_pjlvalue(ctx, literal_pointer_val(ctx, dt))); +static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_datatype_t *dt) +{ + 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)); + return ctx.builder.CreateICmpEQ(xtindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), tindex)); + } + 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)); + BasicBlock *currBB = ctx.builder.GetInsertBlock(); + BasicBlock *isaBB = BasicBlock::Create(ctx.builder.getContext(), "isa", ctx.f); + BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_isa", ctx.f); + ctx.builder.CreateCondBr(isboxed, isaBB, postBB); + ctx.builder.SetInsertPoint(isaBB); + Value *istype_boxed = ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg.Vboxed, false, true), emit_tagfrom(ctx, dt)); + ctx.builder.CreateBr(postBB); + isaBB = ctx.builder.GetInsertBlock(); // could have changed + ctx.builder.SetInsertPoint(postBB); + PHINode *istype = ctx.builder.CreatePHI(getInt1Ty(ctx.builder.getContext()), 2); + istype->addIncoming(ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), currBB); + istype->addIncoming(istype_boxed, isaBB); + return istype; + } else { + // handle the case where we know that `arg` is unboxed (but of unknown type), but that concrete type `type` cannot be unboxed + return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); + } + } + return ctx.builder.CreateICmpEQ(emit_typeof(ctx, arg, false, true), emit_tagfrom(ctx, dt)); } static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, @@ -1179,6 +1524,7 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, static void emit_isa_union(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *type, SmallVectorImpl,Value*>> &bbs) { + ++EmittedIsaUnions; if (jl_is_uniontype(type)) { emit_isa_union(ctx, x, ((jl_uniontype_t*)type)->a, bbs); emit_isa_union(ctx, x, ((jl_uniontype_t*)type)->b, bbs); @@ -1195,6 +1541,7 @@ static void emit_isa_union(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *t // Should agree with `_can_optimize_isa` above static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *type, const std::string *msg) { + ++EmittedIsa; // TODO: The subtype check below suffers from incorrectness issues due to broken // subtyping for kind types (see https://github.com/JuliaLang/julia/issues/27078). For // actual `isa` calls, this optimization should already have been performed upstream @@ -1214,9 +1561,6 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, if (known_isa) { if (!*known_isa && msg) { emit_type_error(ctx, x, literal_pointer_val(ctx, type), *msg); - ctx.builder.CreateUnreachable(); - BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); - ctx.builder.SetInsertPoint(failBB); } return std::make_pair(ConstantInt::get(getInt1Ty(ctx.builder.getContext()), *known_isa), true); } @@ -1227,6 +1571,22 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, auto ptr = track_pjlvalue(ctx, literal_pointer_val(ctx, jl_tparam0(intersected_type))); return {ctx.builder.CreateICmpEQ(boxed(ctx, x), ptr), false}; } + if (intersected_type == (jl_value_t*)jl_type_type) { + // Inline jl_is_kind(jl_typeof(x)) + // N.B. We do the comparison with untracked pointers, because that gives + // LLVM more optimization opportunities. That means it is possible for + // `typ` to get GC'ed, but we don't actually care, because we don't ever + // dereference it. + Value *typ = emit_typeof(ctx, x, false, true); + auto val = ctx.builder.CreateOr( + ctx.builder.CreateOr( + ctx.builder.CreateICmpEQ(typ, emit_tagfrom(ctx, jl_uniontype_type)), + ctx.builder.CreateICmpEQ(typ, emit_tagfrom(ctx, jl_datatype_type))), + ctx.builder.CreateOr( + ctx.builder.CreateICmpEQ(typ, emit_tagfrom(ctx, jl_unionall_type)), + ctx.builder.CreateICmpEQ(typ, emit_tagfrom(ctx, jl_typeofbottom_type)))); + return std::make_pair(val, false); + } // intersection with Type needs to be handled specially if (jl_has_intersect_type_not_kind(type) || jl_has_intersect_type_not_kind(intersected_type)) { Value *vx = boxed(ctx, x); @@ -1241,36 +1601,7 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, } // tests for isa concretetype can be handled with pointer comparisons if (jl_is_concrete_type(intersected_type)) { - if (x.TIndex) { - unsigned tindex = get_box_tindex((jl_datatype_t*)intersected_type, x.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(x.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); - return std::make_pair(ctx.builder.CreateICmpEQ(xtindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), tindex)), false); - } - else if (x.Vboxed) { - // test for (x.TIndex == 0x80 && typeof(x.V) == type) - Value *isboxed = ctx.builder.CreateICmpEQ(x.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x80)); - BasicBlock *currBB = ctx.builder.GetInsertBlock(); - BasicBlock *isaBB = BasicBlock::Create(ctx.builder.getContext(), "isa", ctx.f); - BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_isa", ctx.f); - ctx.builder.CreateCondBr(isboxed, isaBB, postBB); - ctx.builder.SetInsertPoint(isaBB); - Value *istype_boxed = ctx.builder.CreateICmpEQ(emit_typeof(ctx, x.Vboxed), - track_pjlvalue(ctx, literal_pointer_val(ctx, intersected_type))); - ctx.builder.CreateBr(postBB); - isaBB = ctx.builder.GetInsertBlock(); // could have changed - ctx.builder.SetInsertPoint(postBB); - PHINode *istype = ctx.builder.CreatePHI(getInt1Ty(ctx.builder.getContext()), 2); - istype->addIncoming(ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), currBB); - istype->addIncoming(istype_boxed, isaBB); - return std::make_pair(istype, false); - } else { - // handle the case where we know that `x` is unboxed (but of unknown type), but that concrete type `type` cannot be unboxed - return std::make_pair(ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), false); - } - } - return std::make_pair(emit_exactly_isa(ctx, x, intersected_type), false); + return std::make_pair(emit_exactly_isa(ctx, x, (jl_datatype_t*)intersected_type), false); } jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(intersected_type); if (jl_is_datatype(dt) && !dt->name->abstract && jl_subtype(dt->name->wrapper, type)) { @@ -1278,8 +1609,8 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, // so the isa test reduces to a comparison of the typename by pointer return std::make_pair( ctx.builder.CreateICmpEQ( - mark_callee_rooted(ctx, emit_datatype_name(ctx, emit_typeof_boxed(ctx, x))), - mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)dt->name))), + emit_datatype_name(ctx, emit_typeof(ctx, x, false, false)), + literal_pointer_val(ctx, (jl_value_t*)dt->name)), false); } if (jl_is_uniontype(intersected_type) && @@ -1307,23 +1638,38 @@ static std::pair emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, // everything else can be handled via subtype tests return std::make_pair(ctx.builder.CreateICmpNE( ctx.builder.CreateCall(prepare_call(jlsubtype_func), - { emit_typeof_boxed(ctx, x), + { emit_typeof(ctx, x, false, false), track_pjlvalue(ctx, literal_pointer_val(ctx, type)) }), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)), false); } +// If this might have been sourced from a PhiNode object, it is possible our +// Vboxed pointer itself is null (undef) at runtime even if we thought we should +// know exactly the type of the bytes that should have been inside. +// +// n.b. It is also possible the value is a ghost of some sort, and we will +// declare that the pointer is legal (for zero bytes) even though it might be undef. +static Value *emit_isa_and_defined(jl_codectx_t &ctx, const jl_cgval_t &val, jl_value_t *typ) +{ + return emit_nullcheck_guard(ctx, val.ispointer() ? val.V : nullptr, [&] { + return emit_isa(ctx, val, typ, nullptr).first; + }); +} + + static void emit_typecheck(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *type, const std::string &msg) { Value *istype; bool handled_msg; std::tie(istype, handled_msg) = emit_isa(ctx, x, type, &msg); if (!handled_msg) { + ++EmittedTypechecks; BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); ctx.builder.CreateCondBr(istype, passBB, failBB); ctx.builder.SetInsertPoint(failBB); - emit_type_error(ctx, x, literal_pointer_val(ctx, type), msg); + just_emit_type_error(ctx, x, literal_pointer_val(ctx, type), msg); ctx.builder.CreateUnreachable(); ctx.f->getBasicBlockList().push_back(passBB); @@ -1335,7 +1681,8 @@ static Value *emit_isconcrete(jl_codectx_t &ctx, Value *typ) { Value *isconcrete; isconcrete = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, decay_derived(ctx, typ), getInt8PtrTy(ctx.builder.getContext())), offsetof(jl_datatype_t, hash) + sizeof(((jl_datatype_t*)nullptr)->hash)); - isconcrete = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isconcrete, Align(1))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + isconcrete = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), isconcrete, Align(1))); isconcrete = ctx.builder.CreateLShr(isconcrete, 1); isconcrete = ctx.builder.CreateTrunc(isconcrete, getInt1Ty(ctx.builder.getContext())); return isconcrete; @@ -1343,6 +1690,7 @@ static Value *emit_isconcrete(jl_codectx_t &ctx, Value *typ) static void emit_concretecheck(jl_codectx_t &ctx, Value *typ, const std::string &msg) { + ++EmittedConcretechecks; assert(typ->getType() == ctx.types().T_prjlvalue); emit_typecheck(ctx, mark_julia_type(ctx, typ, true, jl_any_type), (jl_value_t*)jl_datatype_type, msg); error_unless(ctx, emit_isconcrete(ctx, typ), msg); @@ -1365,9 +1713,10 @@ 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(getSizeTy(ctx.builder.getContext()), 1)); + 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); BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); @@ -1403,11 +1752,9 @@ static Value *emit_bounds_check(jl_codectx_t &ctx, const jl_cgval_t &ainfo, jl_v return im1; } -static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_value_t *jt, Value* dest, MDNode *tbaa_dest, bool isVolatile = false); -static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_value_t *jt) -{ - return emit_unbox(ctx, to, x, jt, nullptr, nullptr, false); -} +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 void emit_write_barrier(jl_codectx_t&, Value*, ArrayRef); static void emit_write_barrier(jl_codectx_t&, Value*, Value*); static void emit_write_multibarrier(jl_codectx_t&, Value*, Value*, jl_value_t*); @@ -1421,12 +1768,8 @@ std::vector first_ptr(Type *T) num_elements = AT->getNumElements(); else { VectorType *VT = cast(T); -#if JL_LLVM_VERSION >= 120000 ElementCount EC = VT->getElementCount(); num_elements = EC.getKnownMinValue(); -#else - num_elements = VT->getNumElements(); -#endif } if (num_elements == 0) return {}; @@ -1458,6 +1801,7 @@ Value *extract_first_ptr(jl_codectx_t &ctx, Value *V) 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); } @@ -1475,22 +1819,28 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j bool maybe_null_if_boxed = true, unsigned alignment = 0, Value **nullcheck = nullptr) { + // 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)) 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; - if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy()) { - const DataLayout &DL = jl_Module->getDataLayout(); - unsigned nb = DL.getTypeSizeInBits(elty); - intcast = ctx.builder.CreateAlloca(elty); - elty = Type::getIntNTy(ctx.builder.getContext(), nb); + if (Order == AtomicOrdering::NotAtomic) { + if (!isboxed && !aliasscope && elty->isAggregateType() && !CountTrackedPointers(elty).count) + intcast = emit_static_alloca(ctx, elty); + } + else { + if (!isboxed && !elty->isIntOrPtrTy()) { + intcast = emit_static_alloca(ctx, elty); + elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb); + } } Type *realelty = elty; if (Order != AtomicOrdering::NotAtomic && isa(elty)) { - unsigned nb = cast(elty)->getBitWidth(); unsigned nb2 = PowerOf2Ceil(nb); if (nb != nb2) - elty = Type::getIntNTy(ctx.builder.getContext(), nb2); + elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb2); } Type *ptrty = PointerType::get(elty, ptr->getType()->getPointerAddressSpace()); Value *data; @@ -1500,45 +1850,52 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j data = ptr; if (idx_0based) data = ctx.builder.CreateInBoundsGEP(elty, data, idx_0based); - Value *instr; - // TODO: can only lazy load if we can create a gc root for ptr for the lifetime of elt - //if (elty->isAggregateType() && tbaa == ctx.tbaa().tbaa_immut && !alignment) { // can lazy load on demand, no copy needed - // elt = data; - //} - //else { - if (isboxed) - alignment = sizeof(void*); - else if (!alignment) - alignment = julia_alignment(jltype); + Value *instr = nullptr; + if (isboxed) + alignment = sizeof(void*); + else if (!alignment) + alignment = julia_alignment(jltype); + if (intcast && Order == AtomicOrdering::NotAtomic) { + emit_memcpy(ctx, intcast, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), data, jl_aliasinfo_t::fromTBAA(ctx, tbaa), nb, alignment); + } + else { LoadInst *load = ctx.builder.CreateAlignedLoad(elty, data, Align(alignment), false); load->setOrdering(Order); - if (aliasscope) - load->setMetadata("alias.scope", aliasscope); if (isboxed) maybe_mark_load_dereferenceable(load, true, jltype); - if (tbaa) - tbaa_decorate(tbaa, load); + 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) { ctx.builder.CreateStore(instr, ctx.builder.CreateBitCast(intcast, instr->getType()->getPointerTo())); - instr = ctx.builder.CreateLoad(intcast->getAllocatedType(), intcast); + instr = nullptr; } - if (maybe_null_if_boxed) { - Value *first_ptr = isboxed ? instr : extract_first_ptr(ctx, instr); - if (first_ptr) - null_pointer_check(ctx, first_ptr, nullcheck); - } - //} + } + if (maybe_null_if_boxed) { + if (intcast) + 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, nullcheck); + if (intcast && !first_ptr) + instr = nullptr; + } if (jltype == (jl_value_t*)jl_bool_type) { // "freeze" undef memory to a valid value // NOTE: if we zero-initialize arrays, this optimization should become valid //load->setMetadata(LLVMContext::MD_range, MDNode::get(ctx.builder.getContext(), { // ConstantAsMetadata::get(ConstantInt::get(T_int8, 0)), // ConstantAsMetadata::get(ConstantInt::get(T_int8, 2)) })); + if (intcast) + instr = ctx.builder.CreateLoad(intcast->getAllocatedType(), intcast); instr = ctx.builder.CreateTrunc(instr, getInt1Ty(ctx.builder.getContext())); } - return mark_julia_type(ctx, instr, isboxed, jltype); + if (instr) + return mark_julia_type(ctx, instr, isboxed, jltype); + else + return mark_julia_slot(intcast, jltype, NULL, ctx.tbaa().tbaa_stack); } static jl_cgval_t typed_store(jl_codectx_t &ctx, @@ -1551,18 +1908,16 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, { auto newval = [&](const jl_cgval_t &lhs) { const jl_cgval_t argv[3] = { cmp, lhs, rhs }; - jl_cgval_t ret(ctx.builder.getContext()); + jl_cgval_t ret; if (modifyop) { ret = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type); } else { - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, JLCALL_F_CC); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, julia_call); ret = mark_julia_type(ctx, callval, true, jl_any_type); } - if (!jl_subtype(ret.typ, jltype)) { - emit_typecheck(ctx, ret, jltype, fname); - ret = update_julia_type(ctx, ret, jltype); - } + emit_typecheck(ctx, ret, jltype, fname); + ret = update_julia_type(ctx, ret, jltype); return ret; }; assert(!needlock || parent != nullptr); @@ -1590,35 +1945,34 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, return emit_new_struct(ctx, (jl_value_t*)rettyp, 2, argv); } } + unsigned nb = isboxed ? sizeof(void*) : jl_datatype_size(jltype); AllocaInst *intcast = nullptr; if (!isboxed && Order != AtomicOrdering::NotAtomic && !elty->isIntOrPtrTy()) { - const DataLayout &DL = jl_Module->getDataLayout(); - unsigned nb = DL.getTypeSizeInBits(elty); if (!issetfield) - intcast = ctx.builder.CreateAlloca(elty); - elty = Type::getIntNTy(ctx.builder.getContext(), nb); + intcast = emit_static_alloca(ctx, elty); + elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb); } Type *realelty = elty; if (Order != AtomicOrdering::NotAtomic && isa(elty)) { - unsigned nb = cast(elty)->getBitWidth(); unsigned nb2 = PowerOf2Ceil(nb); if (nb != nb2) - elty = Type::getIntNTy(ctx.builder.getContext(), nb2); + elty = Type::getIntNTy(ctx.builder.getContext(), 8 * nb2); } Value *r = nullptr; if (issetfield || isswapfield || isreplacefield) { - if (!isboxed) - r = emit_unbox(ctx, realelty, rhs, jltype); - else + if (isboxed) r = boxed(ctx, rhs); - if (realelty != elty) - r = ctx.builder.CreateZExt(r, elty); + else if (aliasscope || Order != AtomicOrdering::NotAtomic || CountTrackedPointers(realelty).count) { + r = emit_unbox(ctx, realelty, rhs, jltype); + if (realelty != elty) + r = ctx.builder.CreateZExt(r, elty); + } } Type *ptrty = PointerType::get(elty, ptr->getType()->getPointerAddressSpace()); if (ptr->getType() != ptrty) ptr = ctx.builder.CreateBitCast(ptr, ptrty); if (idx_0based) - ptr = ctx.builder.CreateInBoundsGEP(r->getType(), ptr, idx_0based); + ptr = ctx.builder.CreateInBoundsGEP(elty, ptr, idx_0based); if (isboxed) alignment = sizeof(void*); else if (!alignment) @@ -1633,33 +1987,34 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, if (issetfield || (Order == AtomicOrdering::NotAtomic && isswapfield)) { if (isswapfield) { auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); - if (aliasscope) - load->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, load); + if (isboxed) + load->setOrdering(AtomicOrdering::Unordered); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(load); assert(realelty == elty); instr = load; } - StoreInst *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); - store->setOrdering(Order); - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + if (r) { + StoreInst *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); + store->setOrdering(Order == AtomicOrdering::NotAtomic && isboxed ? AtomicOrdering::Release : Order); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); + } + else { + assert(Order == AtomicOrdering::NotAtomic && !isboxed && rhs.typ == jltype); + emit_unbox_store(ctx, rhs, ptr, tbaa, alignment); + } } else if (isswapfield && !isboxed) { + assert(Order != AtomicOrdering::NotAtomic && r); // we can't handle isboxed here as a workaround for really bad LLVM // design issue: plain Xchg only works with integers -#if JL_LLVM_VERSION >= 130000 auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Align(alignment), Order); -#else - auto *store = ctx.builder.CreateAtomicRMW(AtomicRMWInst::Xchg, ptr, r, Order); - store->setAlignment(Align(alignment)); -#endif - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); instr = store; } else { @@ -1681,12 +2036,10 @@ 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)); - load->setOrdering(FailOrder); - if (aliasscope) - load->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, load); - instr = load; + 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); + instr = ai.decorateInst(load); ctx.builder.CreateBr(DoneBB); ctx.builder.SetInsertPoint(DoneBB); Succ = ctx.builder.CreatePHI(getInt1Ty(ctx.builder.getContext()), 2); @@ -1712,12 +2065,10 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, } else { // swap or modify LoadInst *Current = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); - Current->setOrdering(Order == AtomicOrdering::NotAtomic ? Order : AtomicOrdering::Monotonic); - if (aliasscope) - Current->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, Current); - Compare = Current; + Current->setOrdering(Order == AtomicOrdering::NotAtomic && !isboxed ? Order : AtomicOrdering::Monotonic); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + Compare = ai.decorateInst(Current); needloop = !isswapfield || Order != AtomicOrdering::NotAtomic; } BasicBlock *BB = NULL; @@ -1748,16 +2099,18 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, null_pointer_check(ctx, first_ptr, nullptr); } if (intcast) - oldval = mark_julia_slot(intcast, jltype, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + oldval = mark_julia_slot(intcast, jltype, NULL, ctx.tbaa().tbaa_stack); else oldval = mark_julia_type(ctx, realCompare, isboxed, jltype); rhs = newval(oldval); - if (!isboxed) - r = emit_unbox(ctx, realelty, rhs, jltype); - else + if (isboxed) { r = boxed(ctx, rhs); - if (realelty != elty) - r = ctx.builder.CreateZExt(r, elty); + } + else if (Order != AtomicOrdering::NotAtomic || CountTrackedPointers(realelty).count) { + r = emit_unbox(ctx, realelty, rhs, jltype); + if (realelty != elty) + r = ctx.builder.CreateZExt(r, elty); + } if (needlock) emit_lockstate_value(ctx, parent, true); cmp = oldval; @@ -1767,10 +2120,11 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, // modifyfield or replacefield assert(elty == realelty && !intcast); auto *load = ctx.builder.CreateAlignedLoad(elty, ptr, Align(alignment)); - if (aliasscope) - load->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(load); + if (isboxed) + load->setOrdering(AtomicOrdering::Monotonic); Value *first_ptr = nullptr; if (maybe_null_if_boxed && !ismodifyfield) first_ptr = isboxed ? load : extract_first_ptr(ctx, load); @@ -1784,31 +2138,33 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, BasicBlock *XchgBB = BasicBlock::Create(ctx.builder.getContext(), "xchg", ctx.f); ctx.builder.CreateCondBr(Success, XchgBB, needloop && ismodifyfield ? BB : DoneBB); ctx.builder.SetInsertPoint(XchgBB); - auto *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + if (r) { + auto *store = ctx.builder.CreateAlignedStore(r, ptr, Align(alignment)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); + } + else { + assert(!isboxed && rhs.typ == jltype); + emit_unbox_store(ctx, rhs, ptr, tbaa, alignment); + } ctx.builder.CreateBr(DoneBB); instr = load; } else { + assert(r); if (Order == AtomicOrdering::Unordered) Order = AtomicOrdering::Monotonic; + if (Order == AtomicOrdering::Monotonic && isboxed) + Order = AtomicOrdering::Release; if (!isreplacefield) FailOrder = AtomicOrdering::Monotonic; else if (FailOrder == AtomicOrdering::Unordered) FailOrder = AtomicOrdering::Monotonic; -#if JL_LLVM_VERSION >= 130000 auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Align(alignment), Order, FailOrder); -#else - auto *store = ctx.builder.CreateAtomicCmpXchg(ptr, Compare, r, Order, FailOrder); - store->setAlignment(Align(alignment)); -#endif - if (aliasscope) - store->setMetadata("noalias", aliasscope); - if (tbaa) - tbaa_decorate(tbaa, store); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.noalias = MDNode::concatenate(aliasscope, ai.noalias); + ai.decorateInst(store); instr = ctx.builder.Insert(ExtractValueInst::Create(store, 0)); Success = ctx.builder.Insert(ExtractValueInst::Create(store, 1)); Done = Success; @@ -1818,7 +2174,7 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, realinstr = ctx.builder.CreateTrunc(realinstr, realelty); if (intcast) { ctx.builder.CreateStore(realinstr, ctx.builder.CreateBitCast(intcast, realinstr->getType()->getPointerTo())); - oldval = mark_julia_slot(intcast, jltype, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + oldval = mark_julia_slot(intcast, jltype, NULL, ctx.tbaa().tbaa_stack); if (maybe_null_if_boxed) realinstr = ctx.builder.CreateLoad(intcast->getAllocatedType(), intcast); } @@ -1855,16 +2211,18 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, emit_lockstate_value(ctx, parent, false); if (parent != NULL) { if (isreplacefield) { - // TOOD: avoid this branch if we aren't making a write barrier + // 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); ctx.builder.CreateCondBr(Success, BB, DoneBB); ctx.builder.SetInsertPoint(BB); } - if (!isboxed) - emit_write_multibarrier(ctx, parent, r, rhs.typ); - else if (!type_is_permalloc(rhs.typ)) - emit_write_barrier(ctx, parent, r); + if (r) { + if (!isboxed) + emit_write_multibarrier(ctx, parent, r, rhs.typ); + else if (!type_is_permalloc(rhs.typ)) + emit_write_barrier(ctx, parent, r); + } if (isreplacefield) { ctx.builder.CreateBr(DoneBB); ctx.builder.SetInsertPoint(DoneBB); @@ -1880,14 +2238,21 @@ static jl_cgval_t typed_store(jl_codectx_t &ctx, instr = ctx.builder.Insert(CastInst::Create(Instruction::Trunc, instr, realelty)); if (intcast) { ctx.builder.CreateStore(instr, ctx.builder.CreateBitCast(intcast, instr->getType()->getPointerTo())); - instr = ctx.builder.CreateLoad(intcast->getAllocatedType(), intcast); + instr = nullptr; } if (maybe_null_if_boxed) { + if (intcast) + 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); + if (intcast && !first_ptr) + instr = nullptr; } - oldval = mark_julia_type(ctx, instr, isboxed, jltype); + if (instr) + oldval = mark_julia_type(ctx, instr, isboxed, jltype); + else + oldval = mark_julia_slot(intcast, jltype, NULL, ctx.tbaa().tbaa_stack); if (isreplacefield) { Success = ctx.builder.CreateZExt(Success, getInt8Ty(ctx.builder.getContext())); const jl_cgval_t argv[2] = {oldval, mark_julia_type(ctx, Success, false, jl_bool_type)}; @@ -1909,98 +2274,6 @@ static Value *julia_bool(jl_codectx_t &ctx, Value *cond) // --- accessing the representations of built-in data types --- -static Constant *julia_const_to_llvm(jl_codectx_t &ctx, jl_value_t *e); - -static Value *data_pointer(jl_codectx_t &ctx, const jl_cgval_t &x) -{ - assert(x.ispointer()); - Value *data = x.V; - if (x.constant) { - Constant *val = julia_const_to_llvm(ctx, x.constant); - if (val) - data = get_pointer_to_constant(ctx.emission_context, val, "_j_const", *jl_Module); - else - data = literal_pointer_val(ctx, x.constant); - } - return data; -} - -static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, - uint64_t sz, unsigned align, bool is_volatile) -{ - if (sz == 0) - return; - assert(align && "align must be specified"); - // If the types are small and simple, use load and store directly. - // Going through memcpy can cause LLVM (e.g. SROA) to create bitcasts between float and int - // that interferes with other optimizations. - if (sz <= 64) { - // The size limit is arbitrary but since we mainly care about floating points and - // machine size vectors this should be enough. - const DataLayout &DL = jl_Module->getDataLayout(); - auto srcty = cast(src->getType()); - auto srcel = srcty->getElementType(); - auto dstty = cast(dst->getType()); - auto dstel = dstty->getElementType(); - if (srcel->isArrayTy() && srcel->getArrayNumElements() == 1) { - src = ctx.builder.CreateConstInBoundsGEP2_32(srcel, src, 0, 0); - srcel = srcel->getArrayElementType(); - srcty = srcel->getPointerTo(); - } - if (dstel->isArrayTy() && dstel->getArrayNumElements() == 1) { - dst = ctx.builder.CreateConstInBoundsGEP2_32(dstel, dst, 0, 0); - dstel = dstel->getArrayElementType(); - dstty = dstel->getPointerTo(); - } - - llvm::Type *directel = nullptr; - if (srcel->isSized() && srcel->isSingleValueType() && DL.getTypeStoreSize(srcel) == sz) { - directel = srcel; - dst = emit_bitcast(ctx, dst, srcty); - } - else if (dstel->isSized() && dstel->isSingleValueType() && - DL.getTypeStoreSize(dstel) == sz) { - directel = dstel; - src = emit_bitcast(ctx, src, dstty); - } - if (directel) { - auto val = tbaa_decorate(tbaa_src, ctx.builder.CreateAlignedLoad(directel, src, Align(align), is_volatile)); - tbaa_decorate(tbaa_dst, ctx.builder.CreateAlignedStore(val, dst, Align(align), is_volatile)); - return; - } - } - // the memcpy intrinsic does not allow to specify different alias tags - // for the load part (x.tbaa) and the store part (ctx.tbaa().tbaa_stack). - // since the tbaa lattice has to be a tree we have unfortunately - // x.tbaa ∪ ctx.tbaa().tbaa_stack = tbaa_root if x.tbaa != ctx.tbaa().tbaa_stack - ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, MDNode::getMostGenericTBAA(tbaa_dst, tbaa_src)); -} - -static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, - Value *sz, unsigned align, bool is_volatile) -{ - if (auto const_sz = dyn_cast(sz)) { - emit_memcpy_llvm(ctx, dst, tbaa_dst, src, tbaa_src, const_sz->getZExtValue(), align, is_volatile); - return; - } - ctx.builder.CreateMemCpy(dst, MaybeAlign(align), src, MaybeAlign(0), sz, is_volatile, MDNode::getMostGenericTBAA(tbaa_dst, tbaa_src)); -} - -template -static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, Value *src, MDNode *tbaa_src, - T1 &&sz, unsigned align, bool is_volatile=false) -{ - emit_memcpy_llvm(ctx, dst, tbaa_dst, src, tbaa_src, sz, align, is_volatile); -} - -template -static void emit_memcpy(jl_codectx_t &ctx, Value *dst, MDNode *tbaa_dst, const jl_cgval_t &src, - T1 &&sz, unsigned align, bool is_volatile=false) -{ - emit_memcpy_llvm(ctx, dst, tbaa_dst, data_pointer(ctx, src), src.tbaa, sz, align, is_volatile); -} - - static void emit_atomic_error(jl_codectx_t &ctx, const std::string &msg) { emit_error(ctx, prepare_call(jlatomicerror_func), msg); @@ -2015,14 +2288,15 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, Value *idx, jl_datatype_t *stt, jl_value_t *inbounds, enum jl_memory_order order) { + ++EmittedGetfieldUnknowns; size_t nfields = jl_datatype_nfields(stt); bool maybe_null = (unsigned)stt->name->n_uninitialized != 0; auto idx0 = [&]() { - return emit_bounds_check(ctx, strct, (jl_value_t*)stt, idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), nfields), inbounds); + return emit_bounds_check(ctx, strct, (jl_value_t*)stt, idx, ConstantInt::get(ctx.types().T_size, nfields), inbounds); }; if (nfields == 0) { (void)idx0(); - *ret = jl_cgval_t(ctx.builder.getContext()); + *ret = jl_cgval_t(); return true; } if (nfields == 1) { @@ -2093,7 +2367,7 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, if (strct.ispointer() && !maybeatomic) { // boxed or stack if (order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { emit_atomic_error(ctx, "getfield: non-atomic field cannot be accessed atomically"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } if (is_datatype_all_pointers(stt)) { @@ -2112,11 +2386,12 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, } Value *fldptr = ctx.builder.CreateInBoundsGEP( ctx.types().T_prjlvalue, - maybe_decay_tracked(ctx, emit_bitcast(ctx, data_pointer(ctx, strct), ctx.types().T_pprjlvalue)), + emit_bitcast(ctx, data_pointer(ctx, strct), ctx.types().T_pprjlvalue), idx0()); LoadInst *fld = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fldptr, Align(sizeof(void*))); fld->setOrdering(AtomicOrdering::Unordered); - tbaa_decorate(strct.tbaa, fld); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, strct.tbaa); + ai.decorateInst(fld); maybe_mark_load_dereferenceable(fld, maybe_null, minimum_field_size, minimum_align); if (maybe_null) null_pointer_check(ctx, fld); @@ -2128,20 +2403,20 @@ static bool emit_getfield_unknownidx(jl_codectx_t &ctx, jl_value_t *jft = jl_svecref(stt->types, 0); // n.b. jl_get_fieldtypes assigned stt->types for here assert(jl_is_concrete_type(jft)); idx = idx0(); - Value *ptr = maybe_decay_tracked(ctx, data_pointer(ctx, strct)); + Value *ptr = data_pointer(ctx, strct); if (!stt->name->mutabl && !(maybe_null && (jft == (jl_value_t*)jl_bool_type || ((jl_datatype_t*)jft)->layout->npointers))) { // just compute the pointer and let user load it when necessary Type *fty = julia_type_to_llvm(ctx, jft); Value *addr = ctx.builder.CreateInBoundsGEP(fty, emit_bitcast(ctx, ptr, PointerType::get(fty, 0)), idx); - *ret = mark_julia_slot(addr, jft, NULL, ctx.tbaa(), strct.tbaa); + *ret = mark_julia_slot(addr, jft, NULL, strct.tbaa); return true; } *ret = typed_load(ctx, ptr, idx, jft, strct.tbaa, nullptr, false, AtomicOrdering::NotAtomic, maybe_null); return true; } else if (strct.isboxed) { - idx = ctx.builder.CreateSub(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)); + idx = ctx.builder.CreateSub(idx, ConstantInt::get(ctx.types().T_size, 1)); Value *fld = ctx.builder.CreateCall(prepare_call(jlgetnthfieldchecked_func), { boxed(ctx, strct), idx }); *ret = mark_julia_type(ctx, fld, true, jl_any_type); return true; @@ -2154,7 +2429,9 @@ static jl_cgval_t emit_unionload(jl_codectx_t &ctx, Value *addr, Value *ptindex, jl_value_t *jfty, size_t fsz, size_t al, MDNode *tbaa, bool mutabl, unsigned union_max, MDNode *tbaa_ptindex) { - Instruction *tindex0 = tbaa_decorate(tbaa_ptindex, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), ptindex, Align(1))); + ++EmittedUnionLoads; + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa_ptindex); + Instruction *tindex0 = ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), ptindex, Align(1))); tindex0->setMetadata(LLVMContext::MD_range, MDNode::get(ctx.builder.getContext(), { ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), union_max)) })); @@ -2165,10 +2442,11 @@ static jl_cgval_t emit_unionload(jl_codectx_t &ctx, Value *addr, Value *ptindex, AllocaInst *lv = emit_static_alloca(ctx, AT); if (al > 1) lv->setAlignment(Align(al)); - emit_memcpy(ctx, lv, tbaa, addr, tbaa, fsz, al); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + emit_memcpy(ctx, lv, ai, addr, ai, fsz, al); addr = lv; } - return mark_julia_slot(fsz > 0 ? addr : nullptr, jfty, tindex, ctx.tbaa(), tbaa); + return mark_julia_slot(fsz > 0 ? addr : nullptr, jfty, tindex, tbaa); } // If `nullcheck` is not NULL and a pointer NULL check is necessary @@ -2182,18 +2460,18 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st 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(ctx.builder.getContext()); // unreachable + return jl_cgval_t(); // unreachable } if (isatomic && order == jl_memory_order_notatomic) { emit_atomic_error(ctx, "getfield: atomic field cannot be accessed non-atomically"); - return jl_cgval_t(ctx.builder.getContext()); // unreachable + return jl_cgval_t(); // unreachable } if (order == jl_memory_order_unspecified) { order = isatomic ? jl_memory_order_unordered : jl_memory_order_notatomic; } if (jfty == jl_bottom_type) { raise_exception(ctx, literal_pointer_val(ctx, jl_undefref_exception)); - return jl_cgval_t(ctx.builder.getContext()); // unreachable + return jl_cgval_t(); // unreachable } if (type_is_ghost(julia_type_to_llvm(ctx, jfty))) return ghostValue(ctx, jfty); @@ -2204,7 +2482,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st if (tbaa == ctx.tbaa().tbaa_datatype && byte_offset != offsetof(jl_datatype_t, types)) tbaa = ctx.tbaa().tbaa_const; if (strct.ispointer()) { - Value *staddr = maybe_decay_tracked(ctx, data_pointer(ctx, strct)); + Value *staddr = data_pointer(ctx, strct); bool isboxed; Type *lt = julia_type_to_llvm(ctx, (jl_value_t*)jt, &isboxed); Value *addr; @@ -2216,7 +2494,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st addr = ctx.builder.CreateInBoundsGEP( getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, staddr, getInt8PtrTy(ctx.builder.getContext())), - ConstantInt::get(getSizeTy(ctx.builder.getContext()), byte_offset)); + ConstantInt::get(ctx.types().T_size, byte_offset)); } else { addr = staddr; @@ -2235,7 +2513,8 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st LoadInst *Load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, maybe_bitcast(ctx, addr, ctx.types().T_pprjlvalue), Align(sizeof(void*))); Load->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); maybe_mark_load_dereferenceable(Load, maybe_null, jl_field_type(jt, idx)); - Value *fldv = tbaa_decorate(tbaa, Load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + Value *fldv = ai.decorateInst(Load); if (maybe_null) null_pointer_check(ctx, fldv, nullcheck); return mark_julia_type(ctx, fldv, true, jfty); @@ -2259,20 +2538,20 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st if (jl_field_isconst(jt, idx) && !(maybe_null && (jfty == (jl_value_t*)jl_bool_type || ((jl_datatype_t*)jfty)->layout->npointers))) { // just compute the pointer and let user load it when necessary - return mark_julia_slot(addr, jfty, NULL, ctx.tbaa(), tbaa); + return mark_julia_slot(addr, jfty, NULL, tbaa); } unsigned align = jl_field_align(jt, idx); if (needlock) emit_lockstate_value(ctx, strct, true); jl_cgval_t ret = typed_load(ctx, addr, NULL, jfty, tbaa, nullptr, false, - needlock ? AtomicOrdering::NotAtomic : get_llvm_atomic_order(order), // TODO: we should use unordered for anything with CountTrackedPointers(elty).count > 0 + needlock ? AtomicOrdering::NotAtomic : get_llvm_atomic_order(order), maybe_null, align, nullcheck); if (needlock) emit_lockstate_value(ctx, strct, false); return ret; } else if (isa(strct.V)) { - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } else { Value *obj = strct.V; // aka emit_unbox @@ -2316,7 +2595,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st } Value *tindex0 = ctx.builder.CreateExtractValue(obj, makeArrayRef(ptindex)); Value *tindex = ctx.builder.CreateNUWAdd(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1), tindex0); - return mark_julia_slot(lv, jfty, tindex, ctx.tbaa(), ctx.tbaa().tbaa_stack); + return mark_julia_slot(lv, jfty, tindex, ctx.tbaa().tbaa_stack); } else { unsigned st_idx; @@ -2340,6 +2619,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st // emit length of vararg tuple static Value *emit_n_varargs(jl_codectx_t &ctx) { + ++EmittedVarargsLength; Value *valen = NULL; if (ctx.nvargs != -1) { valen = ConstantInt::get(getInt32Ty(ctx.builder.getContext()), ctx.nvargs); @@ -2413,7 +2693,7 @@ static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, Value * MDNode *tbaa = ctx.tbaa().tbaa_arraysize; if (arraytype_constdim(tinfo.typ, &ndim)) { if (ndim == 0) - return ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1); + return ConstantInt::get(ctx.types().T_size, 1); if (ndim == 1) { if (auto d = dyn_cast(dim)) { if (d->getZExtValue() == 1) { @@ -2424,19 +2704,20 @@ static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, Value * if (ndim > 1) { if (tinfo.constant && isa(dim)) { auto n = cast(dim)->getZExtValue() - 1; - return ConstantInt::get(getSizeTy(ctx.builder.getContext()), jl_array_dim(tinfo.constant, n)); + 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, getSizeTy(ctx.builder.getContext())); + tbaa, ctx.types().T_size); MDBuilder MDB(ctx.builder.getContext()); - auto rng = MDB.createRange(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), arraytype_maxsize(tinfo.typ))); + 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; } @@ -2458,23 +2739,25 @@ static Value *emit_arraylen_prim(jl_codectx_t &ctx, const jl_cgval_t &tinfo) MDNode *tbaa = ctx.tbaa().tbaa_arraylen; if (arraytype_constdim(ty, &ndim)) { if (ndim == 0) - return ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1); + return ConstantInt::get(ctx.types().T_size, 1); if (ndim != 1) { if (tinfo.constant) - return ConstantInt::get(getSizeTy(ctx.builder.getContext()), jl_array_len(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 - LoadInst *len = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), addr, Align(sizeof(size_t))); + LoadInst *len = ctx.builder.CreateAlignedLoad(ctx.types().T_size, addr, ctx.types().alignof_ptr); len->setOrdering(AtomicOrdering::NotAtomic); MDBuilder MDB(ctx.builder.getContext()); - auto rng = MDB.createRange(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), arraytype_maxsize(tinfo.typ))); + 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); - return tbaa_decorate(tbaa, len); + 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) @@ -2484,6 +2767,7 @@ static Value *emit_arraylen(jl_codectx_t &ctx, const jl_cgval_t &tinfo) static Value *emit_arrayptr_internal(jl_codectx_t &ctx, const jl_cgval_t &tinfo, Value *t, unsigned AS, bool isboxed) { + ++EmittedArrayptr; Value *addr = ctx.builder.CreateStructGEP(ctx.types().T_jlarray, emit_bitcast(ctx, t, ctx.types().T_pjlarray), 0); // Normally allocated array of 0 dimension always have a inline pointer. @@ -2506,7 +2790,9 @@ static Value *emit_arrayptr_internal(jl_codectx_t &ctx, const jl_cgval_t &tinfo, LoadInst *LI = ctx.builder.CreateAlignedLoad(LoadT, addr, Align(sizeof(char *))); LI->setOrdering(AtomicOrdering::NotAtomic); LI->setMetadata(LLVMContext::MD_nonnull, MDNode::get(ctx.builder.getContext(), None)); - tbaa_decorate(arraytype_constshape(tinfo.typ) ? ctx.tbaa().tbaa_const : ctx.tbaa().tbaa_arrayptr, LI); + jl_aliasinfo_t aliasinfo = jl_aliasinfo_t::fromTBAA(ctx, arraytype_constshape(tinfo.typ) ? ctx.tbaa().tbaa_const : ctx.tbaa().tbaa_arrayptr); + aliasinfo.decorateInst(LI); + return LI; } @@ -2535,17 +2821,20 @@ static Value *emit_arraysize(jl_codectx_t &ctx, const jl_cgval_t &tinfo, jl_valu 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); - return tbaa_decorate(ctx.tbaa().tbaa_arrayflags, ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayflags); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); } static Value *emit_arrayndims(jl_codectx_t &ctx, const jl_cgval_t &ary) { + ++EmittedArrayNDims; Value *flags = emit_arrayflags(ctx, ary); cast(flags)->setMetadata(LLVMContext::MD_invariant_load, MDNode::get(ctx.builder.getContext(), None)); flags = ctx.builder.CreateLShr(flags, 2); @@ -2555,16 +2844,19 @@ static Value *emit_arrayndims(jl_codectx_t &ctx, const jl_cgval_t &ary) 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); - return tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt16Ty(ctx.builder.getContext()), addr, Align(sizeof(int16_t)))); } 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); @@ -2574,7 +2866,8 @@ static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int n ctx.types().T_jlarray, emit_bitcast(ctx, decay_derived(ctx, t), ctx.types().T_pjlarray), offset_field); - return tbaa_decorate(ctx.tbaa().tbaa_arrayoffset, ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_arrayoffset); + return ai.decorateInst(ctx.builder.CreateAlignedLoad(getInt32Ty(ctx.builder.getContext()), addr, Align(sizeof(int32_t)))); } // Returns the size of the array represented by `tinfo` for the given dimension `dim` if @@ -2582,7 +2875,7 @@ static Value *emit_arrayoffset(jl_codectx_t &ctx, const jl_cgval_t &tinfo, int n 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(getSizeTy(ctx.builder.getContext()), 1) : emit_arraysize(ctx, tinfo, ex, dim); + return dim > nd ? ConstantInt::get(ctx.types().T_size, 1) : emit_arraysize(ctx, tinfo, ex, dim); } // `nd == -1` means the dimension is unknown. @@ -2590,9 +2883,10 @@ 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) { + ++EmittedArrayNdIndex; Value *a = boxed(ctx, ainfo); - Value *i = Constant::getNullValue(getSizeTy(ctx.builder.getContext())); - Value *stride = ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1); + 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; @@ -2601,13 +2895,13 @@ static Value *emit_array_nd_index( endBB = BasicBlock::Create(ctx.builder.getContext(), "idxend"); } #endif - Value **idxs = (Value**)alloca(sizeof(Value*) * nidxs); + SmallVector idxs(nidxs); for (size_t k = 0; k < nidxs; k++) { - idxs[k] = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), argv[k], (jl_value_t*)jl_long_type); // type asserted by caller + 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(getSizeTy(ctx.builder.getContext()), 1)); + 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); @@ -2652,23 +2946,23 @@ static Value *emit_array_nd_index( 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); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)), dimsokBB, failBB); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)), dimsokBB, failBB); ctx.f->getBasicBlockList().push_back(dimsokBB); ctx.builder.SetInsertPoint(dimsokBB); } Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nd, nd); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)), endBB, failBB); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(ctx.types().T_size, 1)), 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(getSizeTy(ctx.builder.getContext()), ConstantInt::get(getSizeTy(ctx.builder.getContext()), nidxs)); + Value *tmp = ctx.builder.CreateAlloca(ctx.types().T_size, ConstantInt::get(ctx.types().T_size, nidxs)); for (size_t k = 0; k < nidxs; k++) { - ctx.builder.CreateAlignedStore(idxs[k], ctx.builder.CreateInBoundsGEP(getSizeTy(ctx.builder.getContext()), tmp, ConstantInt::get(getSizeTy(ctx.builder.getContext()), k)), Align(sizeof(size_t))); + 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(getSizeTy(ctx.builder.getContext()), nidxs) }); + { mark_callee_rooted(ctx, a), tmp, ConstantInt::get(ctx.types().T_size, nidxs) }); ctx.builder.CreateUnreachable(); ctx.f->getBasicBlockList().push_back(endBB); @@ -2681,13 +2975,14 @@ static Value *emit_array_nd_index( // --- boxing --- -static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt); +static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt); 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 { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); // newv should already be tagged - tbaa_decorate(tbaa, ctx.builder.CreateAlignedStore(v, emit_bitcast(ctx, newv, + ai.decorateInst(ctx.builder.CreateAlignedStore(v, emit_bitcast(ctx, newv, PointerType::get(v->getType(), 0)), Align(alignment))); } @@ -2695,7 +2990,7 @@ 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, tbaa, v, jl_datatype_size(v.typ), sizeof(void*)); + emit_memcpy(ctx, newv, jl_aliasinfo_t::fromTBAA(ctx, tbaa), v, jl_datatype_size(v.typ), sizeof(void*)); } else { init_bits_value(ctx, newv, v.V, tbaa); @@ -2743,12 +3038,8 @@ 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)) { -#if JL_LLVM_VERSION >= 130000 // SVE: Elsewhere we use `getMinKownValue` nargs = CAZ->getElementCount().getFixedValue(); -#else - nargs = CAZ->getNumElements(); -#endif } else if (const auto *CDS = dyn_cast(constant)) nargs = CDS->getNumElements(); @@ -2781,7 +3072,8 @@ static jl_value_t *static_constant_instance(const llvm::DataLayout &DL, Constant return obj; } -static Value *call_with_attrs(jl_codectx_t &ctx, JuliaFunction *intr, Value *v) +template +static Value *call_with_attrs(jl_codectx_t &ctx, JuliaFunction *intr, Value *v) { Function *F = prepare_call(intr); CallInst *Call = ctx.builder.CreateCall(F, v); @@ -2789,7 +3081,7 @@ static Value *call_with_attrs(jl_codectx_t &ctx, JuliaFunction *intr, Value *v) return Call; } -static void jl_add_method_root(jl_codectx_t &ctx, jl_value_t *val); +static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val); static Value *as_value(jl_codectx_t &ctx, Type *to, const jl_cgval_t &v) { @@ -2802,8 +3094,9 @@ static Value *load_i8box(jl_codectx_t &ctx, Value *v, jl_datatype_t *ty) auto jvar = ty == jl_int8_type ? jlboxed_int8_cache : jlboxed_uint8_cache; GlobalVariable *gv = prepare_global_in(jl_Module, jvar); Value *idx[] = {ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0), ctx.builder.CreateZExt(v, getInt32Ty(ctx.builder.getContext()))}; - auto slot = ctx.builder.CreateInBoundsGEP(gv->getType()->getElementType(), gv, idx); - return tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable( + auto slot = ctx.builder.CreateInBoundsGEP(gv->getValueType(), gv, idx); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + return ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, slot, Align(sizeof(void*))), false, (jl_value_t*)ty)); } @@ -2822,7 +3115,7 @@ static Value *_boxed_special(jl_codectx_t &ctx, const jl_cgval_t &vinfo, Type *t if (Constant *c = dyn_cast(vinfo.V)) { jl_value_t *s = static_constant_instance(jl_Module->getDataLayout(), c, jt); if (s) { - jl_add_method_root(ctx, s); + s = jl_ensure_rooted(ctx, s); return track_pjlvalue(ctx, literal_pointer_val(ctx, s)); } } @@ -2869,14 +3162,14 @@ static Value *_boxed_special(jl_codectx_t &ctx, const jl_cgval_t &vinfo, Type *t return box; } -static Value *compute_box_tindex(jl_codectx_t &ctx, Value *datatype, jl_value_t *supertype, jl_value_t *ut) +static Value *compute_box_tindex(jl_codectx_t &ctx, Value *datatype_tag, jl_value_t *supertype, jl_value_t *ut) { Value *tindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0); unsigned counter = 0; for_each_uniontype_small( [&](unsigned idx, jl_datatype_t *jt) { if (jl_subtype((jl_value_t*)jt, supertype)) { - Value *cmp = ctx.builder.CreateICmpEQ(track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jt)), datatype); + Value *cmp = ctx.builder.CreateICmpEQ(emit_tagfrom(ctx, jt), datatype_tag); tindex = ctx.builder.CreateSelect(cmp, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), idx), tindex); } }, @@ -2885,42 +3178,16 @@ static Value *compute_box_tindex(jl_codectx_t &ctx, Value *datatype, jl_value_t return tindex; } -// Returns typeof(v), or null if v is a null pointer at run time. -// This is used when the value might have come from an undefined variable, -// yet we try to read its type to compute a union index when moving the value. -static Value *emit_typeof_or_null(jl_codectx_t &ctx, Value *v) -{ - BasicBlock *nonnull = BasicBlock::Create(ctx.builder.getContext(), "nonnull", ctx.f); - BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "postnull", ctx.f); - Value *isnull = ctx.builder.CreateICmpEQ(v, Constant::getNullValue(v->getType())); - ctx.builder.CreateCondBr(isnull, postBB, nonnull); - BasicBlock *entry = ctx.builder.GetInsertBlock(); - ctx.builder.SetInsertPoint(nonnull); - Value *typof = emit_typeof(ctx, v); - ctx.builder.CreateBr(postBB); - nonnull = ctx.builder.GetInsertBlock(); // could have changed - ctx.builder.SetInsertPoint(postBB); - PHINode *ti = ctx.builder.CreatePHI(typof->getType(), 2); - ti->addIncoming(Constant::getNullValue(typof->getType()), entry); - ti->addIncoming(typof, nonnull); - return ti; -} - // get the runtime tindex value, assuming val is already converted to type typ if it has a TIndex -static Value *compute_tindex_unboxed(jl_codectx_t &ctx, const jl_cgval_t &val, jl_value_t *typ) +static Value *compute_tindex_unboxed(jl_codectx_t &ctx, const jl_cgval_t &val, jl_value_t *typ, bool maybenull=false) { if (val.typ == jl_bottom_type) return UndefValue::get(getInt8Ty(ctx.builder.getContext())); if (val.constant) return ConstantInt::get(getInt8Ty(ctx.builder.getContext()), get_box_tindex((jl_datatype_t*)jl_typeof(val.constant), typ)); - if (val.TIndex) return ctx.builder.CreateAnd(val.TIndex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); - Value *typof; - if (val.isboxed && !jl_is_concrete_type(val.typ) && !jl_is_type_type(val.typ)) - typof = emit_typeof_or_null(ctx, val.V); - else - typof = emit_typeof_boxed(ctx, val); + Value *typof = emit_typeof(ctx, val, maybenull, true); return compute_box_tindex(ctx, typof, val.typ, typ); } @@ -3013,7 +3280,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, jl_datatype_size(jt), literal_pointer_val(ctx, (jl_value_t*)jt)); + box = emit_allocobj(ctx, jt); init_bits_cgval(ctx, box, vinfo_r, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } } @@ -3044,11 +3311,69 @@ static Value *box_union(jl_codectx_t &ctx, const jl_cgval_t &vinfo, const SmallB return box_merge; } +static Function *mangleIntrinsic(IntrinsicInst *call) //mangling based on replaceIntrinsicUseWith +{ + Intrinsic::ID ID = call->getIntrinsicID(); + auto nargs = call->arg_size(); + SmallVector argTys(nargs); + auto oldfType = call->getFunctionType(); + for (unsigned i = 0; i < oldfType->getNumParams(); i++) { + auto argi = call->getArgOperand(i); + argTys[i] = argi->getType(); + } + + auto newfType = FunctionType::get( + oldfType->getReturnType(), + makeArrayRef(argTys).slice(0, oldfType->getNumParams()), + oldfType->isVarArg()); + + // Accumulate an array of overloaded types for the given intrinsic + // and compute the new name mangling schema + SmallVector overloadTys; + { + SmallVector Table; + getIntrinsicInfoTableEntries(ID, Table); + ArrayRef TableRef = Table; + auto res = Intrinsic::matchIntrinsicSignature(newfType, TableRef, overloadTys); + assert(res == Intrinsic::MatchIntrinsicTypes_Match); + (void)res; + bool matchvararg = !Intrinsic::matchIntrinsicVarArg(newfType->isVarArg(), TableRef); + assert(matchvararg); + (void)matchvararg; + } + auto newF = Intrinsic::getDeclaration(call->getModule(), ID, overloadTys); + assert(newF->getFunctionType() == newfType); + newF->setCallingConv(call->getCallingConv()); + return newF; +} + + +//Used for allocation hoisting in *boxed +static void recursively_adjust_ptr_type(llvm::Value *Val, unsigned FromAS, unsigned ToAS) +{ + for (auto *User : Val->users()) { + if (isa(User)) { + GetElementPtrInst *Inst = cast(User); + Inst->mutateType(PointerType::getWithSamePointeeType(cast(Inst->getType()), ToAS)); + recursively_adjust_ptr_type(Inst, FromAS, ToAS); + } + else if (isa(User)) { + IntrinsicInst *call = cast(User); + call->setCalledFunction(mangleIntrinsic(call)); + } + else if (isa(User)) { + BitCastInst *Inst = cast(User); + Inst->mutateType(PointerType::getWithSamePointeeType(cast(Inst->getType()), ToAS)); + recursively_adjust_ptr_type(Inst, FromAS, ToAS); + } + } +} + // this is used to wrap values for generic contexts, where a // dynamically-typed value is required (e.g. argument to unknown function). // if it's already a pointer it's left alone. // Returns ctx.types().T_prjlvalue -static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo) +static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotable) { jl_value_t *jt = vinfo.typ; if (jt == jl_bottom_type || jt == NULL) @@ -3077,8 +3402,25 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo) assert(!type_is_ghost(t)); // ghost values should have been handled by vinfo.constant above! box = _boxed_special(ctx, vinfo, t); if (!box) { - box = emit_allocobj(ctx, jl_datatype_size(jt), literal_pointer_val(ctx, (jl_value_t*)jt)); - init_bits_cgval(ctx, box, vinfo, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); + bool do_promote = vinfo.promotion_point; + if (do_promote && is_promotable) { + auto IP = ctx.builder.saveIP(); + ctx.builder.SetInsertPoint(vinfo.promotion_point); + box = emit_allocobj(ctx, (jl_datatype_t*)jt); + Value *decayed = decay_derived(ctx, box); + AllocaInst *originalAlloca = cast(vinfo.V); + decayed = maybe_bitcast(ctx, decayed, PointerType::getWithSamePointeeType(originalAlloca->getType(), AddressSpace::Derived)); + // Warning: Very illegal IR here temporarily + originalAlloca->mutateType(decayed->getType()); + recursively_adjust_ptr_type(originalAlloca, 0, AddressSpace::Derived); + originalAlloca->replaceAllUsesWith(decayed); + // end illegal IR + originalAlloca->eraseFromParent(); + ctx.builder.restoreIP(IP); + } else { + box = emit_allocobj(ctx, (jl_datatype_t*)jt); + init_bits_cgval(ctx, box, vinfo, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); + } } } return box; @@ -3088,28 +3430,31 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo) static void emit_unionmove(jl_codectx_t &ctx, Value *dest, MDNode *tbaa_dst, const jl_cgval_t &src, Value *skip, bool isVolatile=false) { if (AllocaInst *ai = dyn_cast(dest)) - // TODO: make this a lifetime_end & dereferencable annotation? + // TODO: make this a lifetime_end & dereferenceable annotation? ctx.builder.CreateAlignedStore(UndefValue::get(ai->getAllocatedType()), ai, ai->getAlign()); if (jl_is_concrete_type(src.typ) || src.constant) { jl_value_t *typ = src.constant ? jl_typeof(src.constant) : src.typ; - Type *store_ty = julia_type_to_llvm(ctx, typ); assert(skip || jl_is_pointerfree(typ)); if (jl_is_pointerfree(typ)) { + unsigned alignment = julia_alignment(typ); if (!src.ispointer() || src.constant) { - emit_unbox(ctx, store_ty, src, typ, dest, tbaa_dst, isVolatile); + emit_unbox_store(ctx, src, dest, tbaa_dst, alignment, isVolatile); } else { Value *src_ptr = data_pointer(ctx, src); unsigned nb = jl_datatype_size(typ); - unsigned alignment = julia_alignment(typ); - Value *nbytes = ConstantInt::get(getSizeTy(ctx.builder.getContext()), nb); - if (skip) { - // TODO: this Select is very bad for performance, but is necessary to work around LLVM bugs with the undef option that we want to use: - // select copy dest -> dest to simulate an undef value / conditional copy - // src_ptr = ctx.builder.CreateSelect(skip, dest, src_ptr); - nbytes = ctx.builder.CreateSelect(skip, Constant::getNullValue(getSizeTy(ctx.builder.getContext())), nbytes); - } - emit_memcpy(ctx, dest, tbaa_dst, src_ptr, src.tbaa, nbytes, alignment, isVolatile); + // TODO: this branch may be bad for performance, but is necessary to work around LLVM bugs with the undef option that we want to use: + // select copy dest -> dest to simulate an undef value / conditional copy + // if (skip) src_ptr = ctx.builder.CreateSelect(skip, dest, src_ptr); + auto f = [&] { + (void)emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src_ptr, + jl_aliasinfo_t::fromTBAA(ctx, src.tbaa), nb, alignment, isVolatile); + return nullptr; + }; + if (skip) + emit_guarded_test(ctx, skip, nullptr, f); + else + f(); } } } @@ -3139,8 +3484,8 @@ static void emit_unionmove(jl_codectx_t &ctx, Value *dest, MDNode *tbaa_dst, con ctx.builder.CreateUnreachable(); return; } else { - emit_memcpy(ctx, dest, tbaa_dst, src_ptr, - src.tbaa, nb, alignment, isVolatile); + emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src_ptr, + jl_aliasinfo_t::fromTBAA(ctx, src.tbaa), nb, alignment, isVolatile); } } ctx.builder.CreateBr(postBB); @@ -3162,30 +3507,34 @@ static void emit_unionmove(jl_codectx_t &ctx, Value *dest, MDNode *tbaa_dst, con } else { assert(src.isboxed && "expected boxed value for sizeof/alignment computation"); - Value *datatype = emit_typeof_boxed(ctx, src); - Value *copy_bytes = emit_datatype_size(ctx, datatype); - if (skip) { - copy_bytes = ctx.builder.CreateSelect(skip, ConstantInt::get(copy_bytes->getType(), 0), copy_bytes); - } - emit_memcpy(ctx, dest, tbaa_dst, src, copy_bytes, /*TODO: min-align*/1, isVolatile); + auto f = [&] { + Value *datatype = emit_typeof(ctx, src, false, false); + Value *copy_bytes = emit_datatype_size(ctx, datatype); + emit_memcpy(ctx, dest, jl_aliasinfo_t::fromTBAA(ctx, tbaa_dst), src, copy_bytes, /*TODO: min-align*/1, isVolatile); + return nullptr; + }; + if (skip) + emit_guarded_test(ctx, skip, nullptr, f); + else + f(); } } static void emit_cpointercheck(jl_codectx_t &ctx, const jl_cgval_t &x, const std::string &msg) { - Value *t = emit_typeof_boxed(ctx, x); - emit_typecheck(ctx, mark_julia_type(ctx, t, true, jl_any_type), (jl_value_t*)jl_datatype_type, msg); + ++EmittedCPointerChecks; + Value *t = emit_typeof(ctx, x, false, false); Value *istype = - ctx.builder.CreateICmpEQ(mark_callee_rooted(ctx, emit_datatype_name(ctx, t)), - mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_pointer_typename))); - BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(),"fail",ctx.f); - BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(),"pass"); + ctx.builder.CreateICmpEQ(emit_datatype_name(ctx, t), + literal_pointer_val(ctx, (jl_value_t*)jl_pointer_typename)); + BasicBlock *failBB = BasicBlock::Create(ctx.builder.getContext(), "fail", ctx.f); + BasicBlock *passBB = BasicBlock::Create(ctx.builder.getContext(), "pass"); ctx.builder.CreateCondBr(istype, passBB, failBB); ctx.builder.SetInsertPoint(failBB); - emit_type_error(ctx, x, literal_pointer_val(ctx, (jl_value_t*)jl_pointer_type), msg); + 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); @@ -3193,15 +3542,24 @@ static void emit_cpointercheck(jl_codectx_t &ctx, const jl_cgval_t &x, const std } // allocation for known size object +// returns a prjlvalue static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt) { + ++EmittedAllocObjs; Value *current_task = get_current_task(ctx); Function *F = prepare_call(jl_alloc_obj_func); - auto call = ctx.builder.CreateCall(F, {current_task, ConstantInt::get(getSizeTy(ctx.builder.getContext()), static_size), maybe_decay_untracked(ctx, 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)); return call; } +static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt) +{ + return emit_allocobj(ctx, jl_datatype_size(jt), ctx.builder.CreateIntToPtr(emit_tagfrom(ctx, jt), ctx.types().T_pjlvalue)); +} + // allocation for unknown object from an untracked pointer static Value *emit_new_bits(jl_codectx_t &ctx, Value *jt, Value *pval) { @@ -3220,6 +3578,7 @@ static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, Value *ptr) static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, ArrayRef ptrs) { + ++EmittedWriteBarriers; // if there are no child objects we can skip emission if (ptrs.empty()) return; @@ -3271,14 +3630,15 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, bool needlock, bool issetfield, bool isreplacefield, bool isswapfield, bool ismodifyfield, const jl_cgval_t *modifyop, const std::string &fname) { + ++EmittedSetfield; assert(strct.ispointer()); size_t byte_offset = jl_field_offset(sty, idx0); Value *addr = data_pointer(ctx, strct); if (byte_offset > 0) { addr = ctx.builder.CreateInBoundsGEP( getInt8Ty(ctx.builder.getContext()), - emit_bitcast(ctx, maybe_decay_tracked(ctx, addr), getInt8PtrTy(ctx.builder.getContext())), - ConstantInt::get(getSizeTy(ctx.builder.getContext()), byte_offset)); // TODO: use emit_struct_gep + emit_bitcast(ctx, addr, getInt8PtrTy(ctx.builder.getContext())), + ConstantInt::get(ctx.types().T_size, byte_offset)); // TODO: use emit_struct_gep } jl_value_t *jfty = jl_field_type(sty, idx0); if (!jl_field_isptr(sty, idx0) && jl_is_uniontype(jfty)) { @@ -3289,11 +3649,13 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, // 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(ctx.builder.getContext()); - Value *ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, maybe_decay_tracked(ctx, addr), getInt8PtrTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), fsz)); + return jl_cgval_t(); + Value *ptindex = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), + emit_bitcast(ctx, addr, getInt8PtrTy(ctx.builder.getContext())), + ConstantInt::get(ctx.types().T_size, fsz)); if (needlock) emit_lockstate_value(ctx, strct, true); - BasicBlock *ModifyBB; + BasicBlock *ModifyBB = NULL; if (ismodifyfield) { ModifyBB = BasicBlock::Create(ctx.builder.getContext(), "modify_xchg", ctx.f); ctx.builder.CreateBr(ModifyBB); @@ -3313,16 +3675,14 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, rhs = emit_invoke(ctx, *modifyop, argv, 3, (jl_value_t*)jl_any_type); } else { - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, JLCALL_F_CC); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, 3, julia_call); rhs = mark_julia_type(ctx, callval, true, jl_any_type); } - if (!jl_subtype(rhs.typ, jfty)) { - emit_typecheck(ctx, rhs, jfty, fname); - rhs = update_julia_type(ctx, rhs, jfty); - } + 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(ctx.builder.getContext()); + return jl_cgval_t(); if (needlock) emit_lockstate_value(ctx, strct, true); cmp = oldval; @@ -3336,7 +3696,8 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, } Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jfty); tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore(tindex, ptindex, Align(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); @@ -3366,14 +3727,15 @@ static jl_cgval_t emit_setfield(jl_codectx_t &ctx, 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 ? maybe_bitcast(ctx, data_pointer(ctx, strct), ctx.types().T_pjlvalue) : nullptr, + wb ? boxed(ctx, strct) : nullptr, isboxed, Order, FailOrder, align, needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, maybe_null, modifyop, fname); } } -static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv) +static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv, bool is_promotable) { + ++EmittedNewStructs; assert(jl_is_datatype(ty)); assert(jl_is_concrete_type(ty)); jl_datatype_t *sty = (jl_datatype_t*)ty; @@ -3394,6 +3756,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg init_as_value = true; } + Instruction *promotion_point = nullptr; + ssize_t promotion_ssa = -1; Value *strct; if (type_is_ghost(lt)) { strct = NULL; @@ -3407,14 +3771,25 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else { strct = emit_static_alloca(ctx, lt); if (tracked.count) - undef_derived_strct(ctx.builder, strct, sty, ctx.tbaa().tbaa_stack); + undef_derived_strct(ctx, strct, sty, ctx.tbaa().tbaa_stack); } for (unsigned i = 0; i < na; i++) { jl_value_t *jtype = jl_svecref(sty->types, i); // n.b. ty argument must be concrete jl_cgval_t fval_info = argv[i]; + + IRBuilderBase::InsertPoint savedIP; emit_typecheck(ctx, fval_info, jtype, "new"); fval_info = update_julia_type(ctx, fval_info, jtype); + if (fval_info.typ == jl_bottom_type) + return jl_cgval_t(); + // TODO: Use (post-)domination instead. + bool field_promotable = !jl_is_uniontype(jtype) && !init_as_value && fval_info.promotion_ssa != -1 && + fval_info.promotion_point && fval_info.promotion_point->getParent() == ctx.builder.GetInsertBlock(); + if (field_promotable) { + savedIP = ctx.builder.saveIP(); + ctx.builder.SetInsertPoint(fval_info.promotion_point); + } if (type_is_ghost(lt)) continue; Type *fty = julia_type_to_llvm(ctx, jtype); @@ -3426,21 +3801,38 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg if (!init_as_value) { // avoid unboxing the argument explicitly // and use memcpy instead - dest = ctx.builder.CreateConstInBoundsGEP2_32(lt, strct, 0, llvm_idx); + Instruction *inst; + dest = inst = cast(ctx.builder.CreateConstInBoundsGEP2_32(lt, strct, 0, llvm_idx)); + // Our promotion point needs to come before + // A) All of our arguments' promotion points + // B) Any instructions we insert at any of our arguments' promotion points + // N.B.: Do not use Instruction::comesBefore here. LLVM invalidates its instruction numbering after + // every insert, so querying it here makes code generation accidentally quadartic. + if (field_promotable) { + if (promotion_ssa == -1 || fval_info.promotion_ssa < promotion_ssa) { + promotion_point = inst; + promotion_ssa = fval_info.promotion_ssa; + } + } + else if (!promotion_point) { + promotion_point = inst; + } } Value *fval = NULL; if (jl_field_isptr(sty, i)) { - fval = boxed(ctx, fval_info); - if (!init_as_value) - cast(tbaa_decorate(ctx.tbaa().tbaa_stack, - ctx.builder.CreateAlignedStore(fval, dest, Align(jl_field_align(sty, i))))) - ->setOrdering(AtomicOrdering::Unordered); + fval = boxed(ctx, fval_info, field_promotable); + if (!init_as_value) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); + StoreInst *SI = cast(ai.decorateInst( + ctx.builder.CreateAlignedStore(fval, dest, Align(jl_field_align(sty, i))))); + SI->setOrdering(AtomicOrdering::Unordered); + } } else if (jl_is_uniontype(jtype)) { // compute tindex from rhs jl_cgval_t rhs_union = convert_julia_type(ctx, fval_info, jtype); if (rhs_union.typ == jl_bottom_type) - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); Value *tindex = compute_tindex_unboxed(ctx, rhs_union, jtype); tindex = ctx.builder.CreateNUWSub(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1)); size_t fsz = 0, al = 0; @@ -3462,7 +3854,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg unsigned i = 0; for (; i < fsz / al; i++) { Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(ET, lv, i); - Value *fldv = tbaa_decorate(ctx.tbaa().tbaa_stack, ctx.builder.CreateAlignedLoad(ET, fldp, Align(al))); + 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)); } // emit remaining bytes up to tindex @@ -3471,7 +3864,8 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg staddr = ctx.builder.CreateBitCast(staddr, getInt8PtrTy(ctx.builder.getContext())); for (; i < ptindex - llvm_idx; i++) { Value *fldp = ctx.builder.CreateConstInBoundsGEP1_32(getInt8Ty(ctx.builder.getContext()), staddr, i); - Value *fldv = tbaa_decorate(ctx.tbaa().tbaa_stack, ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), fldp, Align(1))); + 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)); } } @@ -3483,13 +3877,21 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg } else { Value *ptindex = emit_struct_gep(ctx, lt, strct, offs + fsz); - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore(tindex, ptindex, Align(1))); + 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) emit_unionmove(ctx, dest, ctx.tbaa().tbaa_stack, fval_info, nullptr); } } else { - fval = emit_unbox(ctx, fty, fval_info, jtype, dest, ctx.tbaa().tbaa_stack); + if (field_promotable) { + fval_info.V->replaceAllUsesWith(dest); + cast(fval_info.V)->eraseFromParent(); + } else if (init_as_value) { + fval = emit_unbox(ctx, fty, fval_info, jtype); + } else { + emit_unbox_store(ctx, fval_info, dest, ctx.tbaa().tbaa_stack, jl_field_align(sty, i)); + } } if (init_as_value) { assert(fval); @@ -3502,6 +3904,9 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg else assert(false); } + if (field_promotable) { + ctx.builder.restoreIP(savedIP); + } } for (size_t i = nargs; i < nf; i++) { if (!jl_field_isptr(sty, i) && jl_is_uniontype(jl_field_type(sty, i))) { @@ -3510,44 +3915,56 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg 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)); - else - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore( + else { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore( ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), ctx.builder.CreateConstInBoundsGEP2_32(lt, strct, 0, llvm_idx), Align(1))); + } } } if (type_is_ghost(lt)) return mark_julia_const(ctx, sty->instance); else if (init_as_value) return mark_julia_type(ctx, strct, false, ty); - else - return mark_julia_slot(strct, ty, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + else { + jl_cgval_t ret = mark_julia_slot(strct, ty, NULL, ctx.tbaa().tbaa_stack); + if (is_promotable && promotion_point) { + ret.promotion_point = promotion_point; + ret.promotion_ssa = promotion_ssa; + } + return ret; + } } - Value *strct = emit_allocobj(ctx, jl_datatype_size(sty), - literal_pointer_val(ctx, (jl_value_t*)ty)); + Value *strct = emit_allocobj(ctx, sty); jl_cgval_t strctinfo = mark_julia_type(ctx, strct, true, ty); strct = decay_derived(ctx, strct); - undef_derived_strct(ctx.builder, strct, sty, strctinfo.tbaa); + undef_derived_strct(ctx, strct, sty, strctinfo.tbaa); for (size_t i = nargs; i < nf; i++) { if (!jl_field_isptr(sty, i) && jl_is_uniontype(jl_field_type(sty, i))) { - tbaa_decorate(ctx.tbaa().tbaa_unionselbyte, ctx.builder.CreateAlignedStore( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_unionselbyte); + ai.decorateInst(ctx.builder.CreateAlignedStore( ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0), ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, strct, getInt8PtrTy(ctx.builder.getContext())), - ConstantInt::get(getSizeTy(ctx.builder.getContext()), jl_field_offset(sty, i) + jl_field_size(sty, i) - 1)), + ConstantInt::get(ctx.types().T_size, jl_field_offset(sty, i) + jl_field_size(sty, i) - 1)), Align(1))); } } // TODO: verify that nargs <= nf (currently handled by front-end) for (size_t i = 0; i < nargs; i++) { - const jl_cgval_t &rhs = argv[i]; + jl_cgval_t rhs = argv[i]; bool need_wb; // set to true if the store might cause the allocation of a box newer than the struct if (jl_field_isptr(sty, i)) need_wb = !rhs.isboxed; else need_wb = false; - emit_typecheck(ctx, rhs, jl_svecref(sty->types, i), "new"); // n.b. ty argument must be concrete - emit_setfield(ctx, sty, strctinfo, i, rhs, jl_cgval_t(ctx.builder.getContext()), need_wb, AtomicOrdering::NotAtomic, AtomicOrdering::NotAtomic, false, true, false, false, false, nullptr, ""); + jl_value_t *ft = jl_svecref(sty->types, i); + emit_typecheck(ctx, rhs, ft, "new"); // n.b. ty argument must be concrete + 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, ""); } return strctinfo; } @@ -3564,15 +3981,16 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg static void emit_signal_fence(jl_codectx_t &ctx) { - ctx.builder.CreateFence(AtomicOrdering::SequentiallyConsistent, SyncScope::SingleThread); + emit_signal_fence(ctx.builder); } static Value *emit_defer_signal(jl_codectx_t &ctx) { + ++EmittedDeferSignal; Value *ptls = emit_bitcast(ctx, get_current_ptls(ctx), - PointerType::get(ctx.types().T_sigatomic, 0)); + PointerType::get(ctx.types().T_sigatomic, 0)); Constant *offset = ConstantInt::getSigned(getInt32Ty(ctx.builder.getContext()), - offsetof(jl_tls_states_t, defer_signal) / sizeof(sig_atomic_t)); + offsetof(jl_tls_states_t, defer_signal) / sizeof(sig_atomic_t)); return ctx.builder.CreateInBoundsGEP(ctx.types().T_sigatomic, ptls, ArrayRef(offset), "jl_defer_signal"); } @@ -3589,3 +4007,7 @@ static int compare_cgparams(const jl_cgparams_t *a, const jl_cgparams_t *b) (a->generic_context == b->generic_context); } #endif + +// 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 38bd012ff46fc..086d925802f63 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -37,6 +37,16 @@ static const Stmt *getStmtForDiagnostics(const ExplodedNode *N) return N->getStmtForDiagnostics(); } +static unsigned getStackFrameHeight(const LocationContext *stack) +{ + // TODO: or use getID ? + unsigned depth = 0; + while (stack) { + depth++; + stack = stack->getParent(); + } + return depth; +} class GCChecker : public Checker< @@ -53,8 +63,8 @@ class GCChecker check::Location> { mutable std::unique_ptr BT; template - void report_error(callback f, CheckerContext &C, const char *message) const; - void report_error(CheckerContext &C, const char *message) const { + void report_error(callback f, CheckerContext &C, StringRef message) const; + void report_error(CheckerContext &C, StringRef message) const { return report_error([](PathSensitiveBugReport *) {}, C, message); } void @@ -124,8 +134,8 @@ class GCChecker return ValueState(Rooted, Root, Depth); } static ValueState getForArgument(const FunctionDecl *FD, - const ParmVarDecl *PVD) { - bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD); + const ParmVarDecl *PVD, + bool isFunctionSafepoint) { bool maybeUnrooted = declHasAnnotation(PVD, "julia_maybe_unrooted"); if (!isFunctionSafepoint || maybeUnrooted) { ValueState VS = getAllocated(); @@ -181,15 +191,6 @@ class GCChecker } return f(TD->getName()); } - static bool isValueCollection(QualType QT) { - if (QT->isPointerType() || QT->isArrayType()) - return isValueCollection( - clang::QualType(QT->getPointeeOrArrayElementType(), 0)); - const TagDecl *TD = QT->getUnqualifiedDesugaredType()->getAsTagDecl(); - if (!TD) - return false; - return declHasAnnotation(TD, "julia_rooted_value_collection"); - } template static SymbolRef walkToRoot(callback f, const ProgramStateRef &State, const MemRegion *Region); @@ -199,8 +200,9 @@ class GCChecker bool isGloballyRootedType(QualType Type) const; static void dumpState(const ProgramStateRef &State); static bool declHasAnnotation(const clang::Decl *D, const char *which); - static bool isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD); - bool isSafepoint(const CallEvent &Call) const; + static bool isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD, const SourceManager &SM); + static const SourceManager &getSM(CheckerContext &C) { return C.getSourceManager(); } + bool isSafepoint(const CallEvent &Call, CheckerContext &C) const; bool processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const; bool processAllocationOfResult(const CallEvent &Call, CheckerContext &C, @@ -214,7 +216,9 @@ class GCChecker const MemRegion *R, bool Debug = false); bool gcEnabledHere(CheckerContext &C) const; + bool gcEnabledHere(ProgramStateRef State) const; bool safepointEnabledHere(CheckerContext &C) const; + bool safepointEnabledHere(ProgramStateRef State) const; bool propagateArgumentRootedness(CheckerContext &C, ProgramStateRef &State) const; SymbolRef getSymbolForResult(const Expr *Result, const ValueState *OldValS, @@ -463,7 +467,7 @@ PDP GCChecker::GCValueBugVisitor::VisitNode(const ExplodedNode *N, } else { if (NewValueState->FD) { bool isFunctionSafepoint = - !isFDAnnotatedNotSafepoint(NewValueState->FD); + !isFDAnnotatedNotSafepoint(NewValueState->FD, BRC.getSourceManager()); bool maybeUnrooted = declHasAnnotation(NewValueState->PVD, "julia_maybe_unrooted"); assert(isFunctionSafepoint || maybeUnrooted); @@ -509,7 +513,7 @@ PDP GCChecker::GCValueBugVisitor::VisitNode(const ExplodedNode *N, template void GCChecker::report_error(callback f, CheckerContext &C, - const char *message) const { + StringRef message) const { // Generate an error node. ExplodedNode *N = C.generateErrorNode(); if (!N) @@ -544,12 +548,20 @@ void GCChecker::report_value_error(CheckerContext &C, SymbolRef Sym, } bool GCChecker::gcEnabledHere(CheckerContext &C) const { - unsigned disabledAt = C.getState()->get(); + return gcEnabledHere(C.getState()); +} + +bool GCChecker::gcEnabledHere(ProgramStateRef State) const { + unsigned disabledAt = State->get(); return disabledAt == (unsigned)-1; } bool GCChecker::safepointEnabledHere(CheckerContext &C) const { - unsigned disabledAt = C.getState()->get(); + return safepointEnabledHere(C.getState()); +} + +bool GCChecker::safepointEnabledHere(ProgramStateRef State) const { + unsigned disabledAt = State->get(); return disabledAt == (unsigned)-1; } @@ -617,8 +629,8 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { // otherwise const auto *LCtx = C.getLocationContext(); const auto *FD = dyn_cast(LCtx->getDecl()); - if (!FD) - return; + assert(FD); + unsigned CurrentHeight = getStackFrameHeight(C.getStackFrame()); ProgramStateRef State = C.getState(); bool Change = false; if (C.inTopFrame()) { @@ -626,15 +638,14 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { State = State->set((unsigned)-1); Change = true; } - if (State->get() == (unsigned)-1) { - if (declHasAnnotation(FD, "julia_gc_disabled")) { - State = State->set(C.getStackFrame()->getIndex()); - Change = true; - } + if (gcEnabledHere(State) && declHasAnnotation(FD, "julia_gc_disabled")) { + State = State->set(CurrentHeight); + Change = true; } - if (State->get() == (unsigned)-1 && - isFDAnnotatedNotSafepoint(FD)) { - State = State->set(C.getStackFrame()->getIndex()); + bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD, getSM(C)); + if (safepointEnabledHere(State) && + (!isFunctionSafepoint || declHasAnnotation(FD, "julia_notsafepoint_leave"))) { + State = State->set(CurrentHeight); Change = true; } if (!C.inTopFrame()) { @@ -654,7 +665,7 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { continue; assert(AssignedSym); State = State->set(AssignedSym, - ValueState::getForArgument(FD, P)); + ValueState::getForArgument(FD, P, isFunctionSafepoint)); Change = true; } } @@ -666,8 +677,10 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, CheckerContext &C) const { ProgramStateRef State = C.getState(); + const auto *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast(LCtx->getDecl()); - if (RS && gcEnabledHere(C) && RS->getRetValue() && isGCTracked(RS->getRetValue())) { + if (RS && gcEnabledHere(State) && RS->getRetValue() && isGCTracked(RS->getRetValue())) { auto ResultVal = C.getSVal(RS->getRetValue()); SymbolRef Sym = ResultVal.getAsSymbol(true); const ValueState *ValS = Sym ? State->get(Sym) : nullptr; @@ -676,12 +689,16 @@ void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, } } + unsigned CurrentHeight = getStackFrameHeight(C.getStackFrame()); bool Changed = false; - if (State->get() == C.getStackFrame()->getIndex()) { + if (State->get() == CurrentHeight) { State = State->set((unsigned)-1); Changed = true; } - if (State->get() == C.getStackFrame()->getIndex()) { + if (State->get() == CurrentHeight) { + if (!isFDAnnotatedNotSafepoint(FD, getSM(C)) && !(FD && declHasAnnotation(FD, "julia_notsafepoint_enter"))) { + report_error(C, "Safepoints disabled at end of function"); + } State = State->set((unsigned)-1); Changed = true; } @@ -689,8 +706,10 @@ void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, C.addTransition(State); if (!C.inTopFrame()) return; - if (C.getState()->get() > 0) + unsigned CurrentDepth = C.getState()->get(); + if (CurrentDepth != 0) { report_error(C, "Non-popped GC frame present at end of function"); + } } bool GCChecker::declHasAnnotation(const clang::Decl *D, const char *which) { @@ -701,8 +720,38 @@ bool GCChecker::declHasAnnotation(const clang::Decl *D, const char *which) { return false; } -bool GCChecker::isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD) { - return declHasAnnotation(FD, "julia_not_safepoint"); +bool GCChecker::isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD, const SourceManager &SM) { + if (declHasAnnotation(FD, "julia_not_safepoint")) + return true; + SourceLocation Loc = FD->getLocation(); + StringRef Name = SM.getFilename(Loc); + Name = llvm::sys::path::filename(Name); + if (Name.startswith("llvm-")) + return true; + return false; +} + +static bool isMutexLock(StringRef name) { + return name == "uv_mutex_lock" || + //name == "uv_mutex_trylock" || + name == "pthread_mutex_lock" || + //name == "pthread_mutex_trylock" || + name == "pthread_spin_lock" || + //name == "pthread_spin_trylock" || + name == "uv_rwlock_rdlock" || + //name == "uv_rwlock_tryrdlock" || + name == "uv_rwlock_wrlock" || + //name == "uv_rwlock_trywrlock" || + false; +} + +static bool isMutexUnlock(StringRef name) { + return name == "uv_mutex_unlock" || + name == "pthread_mutex_unlock" || + name == "pthread_spin_unlock" || + name == "uv_rwlock_rdunlock" || + name == "uv_rwlock_wrunlock" || + false; } #if LLVM_VERSION_MAJOR >= 13 @@ -710,8 +759,7 @@ bool GCChecker::isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD) { #endif bool GCChecker::isGCTrackedType(QualType QT) { - return isValueCollection(QT) || - isJuliaType( + return isJuliaType( [](StringRef Name) { if (Name.endswith_lower("jl_value_t") || Name.endswith_lower("jl_svec_t") || @@ -745,6 +793,7 @@ bool GCChecker::isGCTrackedType(QualType QT) { Name.endswith_lower("jl_method_match_t") || Name.endswith_lower("jl_vararg_t") || Name.endswith_lower("jl_opaque_closure_t") || + Name.endswith_lower("jl_globalref_t") || // Probably not technically true for these, but let's allow it Name.endswith_lower("typemap_intersection_env") || Name.endswith_lower("interpreter_state") || @@ -778,14 +827,20 @@ bool GCChecker::isGloballyRootedType(QualType QT) const { [](StringRef Name) { return Name.endswith("jl_sym_t"); }, QT); } -bool GCChecker::isSafepoint(const CallEvent &Call) const { +bool GCChecker::isSafepoint(const CallEvent &Call, CheckerContext &C) const { bool isCalleeSafepoint = true; if (Call.isInSystemHeader()) { // defined by -isystem per // https://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-in-system-headers isCalleeSafepoint = false; } else { - auto *Decl = Call.getDecl(); + const clang::Decl *Decl = Call.getDecl(); // we might not have a simple call, or we might have an SVal + const clang::Expr *Callee = nullptr; + if (auto CE = dyn_cast_or_null(Call.getOriginExpr())) { + Callee = CE->getCallee(); + if (Decl == nullptr) + Decl = CE->getCalleeDecl(); // ignores dyn_cast, so it could also be a MemberDecl, etc. + } const DeclContext *DC = Decl ? Decl->getDeclContext() : nullptr; while (DC) { // Anything in llvm or std is not a safepoint @@ -796,9 +851,9 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const { } const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; if (!Decl || !FD) { - const clang::Expr *Callee = - dyn_cast(Call.getOriginExpr())->getCallee(); - if (const TypedefType *TDT = dyn_cast(Callee->getType())) { + if (Callee == nullptr) { + isCalleeSafepoint = true; + } else if (const TypedefType *TDT = dyn_cast(Callee->getType())) { isCalleeSafepoint = !declHasAnnotation(TDT->getDecl(), "julia_not_safepoint"); } else if (const CXXPseudoDestructorExpr *PDE = @@ -819,7 +874,7 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const { FD->getName() != "uv_run") isCalleeSafepoint = false; else - isCalleeSafepoint = !isFDAnnotatedNotSafepoint(FD); + isCalleeSafepoint = !isFDAnnotatedNotSafepoint(FD, getSM(C)); } } return isCalleeSafepoint; @@ -828,7 +883,7 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const { bool GCChecker::processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const { - if (!isSafepoint(Call)) + if (!isSafepoint(Call, C)) return false; bool DidChange = false; if (!gcEnabledHere(C)) @@ -1112,8 +1167,9 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, dyn_cast(C.getLocationContext()->getDecl()); if (FD) { inheritedState = true; + bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD, getSM(C)); Updated = - ValueState::getForArgument(FD, cast(VR->getDecl())); + ValueState::getForArgument(FD, cast(VR->getDecl()), isFunctionSafepoint); } } else { VR = Helpers::walk_back_to_global_VR(Region); @@ -1221,16 +1277,34 @@ void GCChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { return; unsigned NumArgs = Call.getNumArgs(); ProgramStateRef State = C.getState(); - bool isCalleeSafepoint = isSafepoint(Call); + bool isCalleeSafepoint = isSafepoint(Call, C); auto *Decl = Call.getDecl(); const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; - if (!safepointEnabledHere(C) && isCalleeSafepoint) { + StringRef FDName = + FD && FD->getDeclName().isIdentifier() ? FD->getName() : ""; + if (isMutexUnlock(FDName) || (FD && declHasAnnotation(FD, "julia_notsafepoint_leave"))) { + const auto *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast(LCtx->getDecl()); + if (State->get() == getStackFrameHeight(C.getStackFrame()) && + !isFDAnnotatedNotSafepoint(FD, getSM(C))) { + State = State->set((unsigned)-1); + C.addTransition(State); + } + } + if (!safepointEnabledHere(State) && isCalleeSafepoint) { // Suppress this warning if the function is noreturn. // We could separate out "not safepoint, except for noreturn functions", // but that seems like a lot of effort with little benefit. if (!FD || !FD->isNoReturn()) { - report_error(C, "Calling potential safepoint from function annotated " - "JL_NOTSAFEPOINT"); + report_error( + [&](PathSensitiveBugReport *Report) { + if (FD) + Report->addNote( + "Tried to call method defined here", + PathDiagnosticLocation::create(FD, C.getSourceManager())); + }, + C, ("Calling potential safepoint as " + + Call.getKindAsString() + " from function annotated JL_NOTSAFEPOINT").str()); return; } } @@ -1324,7 +1398,7 @@ 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_PUSH7" || name == "JL_GC_PUSH8") { ProgramStateRef State = C.getState(); // Transform slots to roots, transform values to rooted unsigned NumArgs = CE->getNumArgs(); @@ -1440,7 +1514,7 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } else { cast(Arg.getAsSymbol())->getStmt()->dump(); } - bool EnabledNow = State->get() == (unsigned)-1; + bool EnabledNow = gcEnabledHere(State); if (!EnabledAfter) { State = State->set((unsigned)-2); } else { @@ -1452,22 +1526,16 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { C.addTransition(State->BindExpr(CE, C.getLocationContext(), Result)); return true; } - else if (name == "uv_mutex_lock") { - ProgramStateRef State = C.getState(); - if (State->get() == (unsigned)-1) { - C.addTransition(State->set(C.getStackFrame()->getIndex())); - return true; - } - } - else if (name == "uv_mutex_unlock") { - ProgramStateRef State = C.getState(); - const auto *LCtx = C.getLocationContext(); - const auto *FD = dyn_cast(LCtx->getDecl()); - if (State->get() == (unsigned)C.getStackFrame()->getIndex() && - !isFDAnnotatedNotSafepoint(FD)) { - C.addTransition(State->set(-1)); - return true; - } + { + auto *Decl = Call.getDecl(); + const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; + if (isMutexLock(name) || (FD && declHasAnnotation(FD, "julia_notsafepoint_enter"))) { + ProgramStateRef State = C.getState(); + if (State->get() == (unsigned)-1) { + C.addTransition(State->set(getStackFrameHeight(C.getStackFrame()))); + return true; + } + } } return false; } diff --git a/src/clangsa/ImplicitAtomics.cpp b/src/clangsa/ImplicitAtomics.cpp index ed4ce6c1944a8..b9ffc43bc22f8 100644 --- a/src/clangsa/ImplicitAtomics.cpp +++ b/src/clangsa/ImplicitAtomics.cpp @@ -1,45 +1,26 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -#include "clang/AST/ExprObjC.h" -#include "clang/AST/ExprOpenMP.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/Support/raw_ostream.h" -#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" - +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang-tidy/ClangTidy.h" +#include "clang-tidy/ClangTidyCheck.h" +#include "clang-tidy/ClangTidyModule.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" using namespace clang; -using namespace ento; - -namespace { -class ImplicitAtomicsChecker - : public Checker< check::PreStmt, - check::PreStmt, - check::PreStmt, - check::PreCall> { - //check::Bind - //check::Location - BugType ImplicitAtomicsBugType{this, "Implicit Atomic seq_cst synchronization", "Atomics"}; - - void reportBug(const Stmt *S, CheckerContext &C) const; - void reportBug(const Stmt *S, CheckerContext &C, StringRef desc) const; - void reportBug(const CallEvent &S, CheckerContext &C, StringRef desc="") const; +using namespace clang::tidy; +using namespace clang::ast_matchers; + +class ImplicitAtomicsChecker : public ClangTidyCheck { + void reportBug(const Stmt *S, StringRef desc=""); public: - //void checkLocation(SVal location, bool isLoad, const Stmt* S, - // CheckerContext &C) const; - //void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; - void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; - void checkPreStmt(const UnaryOperator *UOp, CheckerContext &C) const; - void checkPreStmt(const BinaryOperator *BOp, CheckerContext &C) const; - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + ImplicitAtomicsChecker(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: }; -} // end anonymous namespace // Checks if RD has name in Names and is in std namespace static bool hasStdClassWithName(const CXXRecordDecl *RD, @@ -70,12 +51,8 @@ static bool isStdAtomic(const Expr *E) { return E->getType()->isAtomicType(); } -void ImplicitAtomicsChecker::reportBug(const CallEvent &S, CheckerContext &C, StringRef desc) const { - reportBug(S.getOriginExpr(), C, desc); -} - -// try to find the "best" node to attach this to, so we generate fewer duplicate reports -void ImplicitAtomicsChecker::reportBug(const Stmt *S, CheckerContext &C) const { +void ImplicitAtomicsChecker::reportBug(const Stmt *S, StringRef desc) { + // try to find the "best" node to attach this to, so we generate fewer duplicate reports while (1) { const auto *expr = dyn_cast(S); if (!expr) @@ -90,115 +67,89 @@ void ImplicitAtomicsChecker::reportBug(const Stmt *S, CheckerContext &C) const { else break; } - reportBug(S, C, ""); -} - -void ImplicitAtomicsChecker::reportBug(const Stmt *S, CheckerContext &C, StringRef desc) const { SmallString<100> buf; llvm::raw_svector_ostream os(buf); - os << ImplicitAtomicsBugType.getDescription() << desc; - PathDiagnosticLocation N = PathDiagnosticLocation::createBegin( - S, C.getSourceManager(), C.getLocationContext()); - auto report = std::make_unique(ImplicitAtomicsBugType, buf.str(), N); - C.emitReport(std::move(report)); + os << "Implicit Atomic seq_cst synchronization" << desc; + diag(S->getBeginLoc(), buf.str()); } -void ImplicitAtomicsChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { - //if (isStdAtomic(CE) != isStdAtomic(CE->getSubExpr())) { // AtomicToNonAtomic or NonAtomicToAtomic CastExpr - if (CE->getCastKind() == CK_AtomicToNonAtomic) { - reportBug(CE, C); - } -} -void ImplicitAtomicsChecker::checkPreStmt(const UnaryOperator *UOp, - CheckerContext &C) const { - if (UOp->getOpcode() == UO_AddrOf) - return; - const Expr *Sub = UOp->getSubExpr(); - if (isStdAtomic(UOp) || isStdAtomic(Sub)) - reportBug(UOp, C); +ImplicitAtomicsChecker:: + ImplicitAtomicsChecker(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) { } -void ImplicitAtomicsChecker::checkPreStmt(const BinaryOperator *BOp, - CheckerContext &C) const { - const Expr *Lhs = BOp->getLHS(); - const Expr *Rhs = BOp->getRHS(); - if (isStdAtomic(Lhs) || isStdAtomic(Rhs) || isStdAtomic(BOp)) - reportBug(BOp, C); +void ImplicitAtomicsChecker::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(castExpr(hasCastKind(CK_AtomicToNonAtomic)) + .bind("cast"), + this); + Finder->addMatcher(unaryOperator(unless(hasAnyOperatorName("&"))) + .bind("unary-op"), + this); + Finder->addMatcher(binaryOperator() + .bind("binary-op"), + this); + Finder->addMatcher(cxxOperatorCallExpr() + .bind("cxxcall"), + this); + Finder->addMatcher(cxxMemberCallExpr() + .bind("cxxcall"), + this); } -void ImplicitAtomicsChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - const auto *MC = dyn_cast(&Call); - if (!MC || !isStdAtomicCall(MC->getCXXThisExpr())) - return; - if (const auto *OC = dyn_cast(&Call)) { - OverloadedOperatorKind OOK = OC->getOverloadedOperator(); - if (CXXOperatorCallExpr::isAssignmentOp(OOK) || OOK == OO_PlusPlus || OOK == OO_MinusMinus) { - reportBug(Call, C, " (std::atomic)"); - } +void ImplicitAtomicsChecker::check(const MatchFinder::MatchResult &Result) { + if (const auto *UOp = Result.Nodes.getNodeAs("unary-op")) { + const Expr *Sub = UOp->getSubExpr(); + if (isStdAtomic(UOp) || isStdAtomic(Sub)) + reportBug(UOp); } - else if (const auto *Convert = dyn_cast(MC->getDecl())) { - reportBug(Call, C, " (std::atomic)"); + if (const auto *BOp = Result.Nodes.getNodeAs("binary-op")) { + const Expr *Lhs = BOp->getLHS(); + const Expr *Rhs = BOp->getRHS(); + if (isStdAtomic(Lhs) || isStdAtomic(Rhs) || isStdAtomic(BOp)) + reportBug(BOp); + } + if (const auto *CE = Result.Nodes.getNodeAs("cast")) { + reportBug(CE); + } + if (const auto *Call = Result.Nodes.getNodeAs("cxxcall")) { + if (const auto *OC = dyn_cast(Call)) { + const auto *CXXThisExpr = OC->getArg(0); + if (isStdAtomicCall(CXXThisExpr)) { + OverloadedOperatorKind OOK = OC->getOperator(); + if (CXXOperatorCallExpr::isAssignmentOp(OOK) || OOK == OO_PlusPlus || OOK == OO_MinusMinus) { + reportBug(CXXThisExpr, " (std::atomic operator)"); + } + } + } + else if (const auto *OC = dyn_cast(Call)) { + const auto *CXXThisExpr = OC->getImplicitObjectArgument(); + if (isStdAtomicCall(CXXThisExpr)) { + if (isa(OC->getMethodDecl())) { + reportBug(CXXThisExpr, " (std::atomic cast)"); + } + } + } } } - -//// These seem probably unnecessary: -// -//static const Expr *getDereferenceExpr(const Stmt *S, bool IsBind=false) { -// const Expr *E = nullptr; -// -// // Walk through lvalue casts to get the original expression -// // that syntactically caused the load. -// if (const Expr *expr = dyn_cast(S)) -// E = expr->IgnoreParenLValueCasts(); -// -// if (IsBind) { -// const VarDecl *VD; -// const Expr *Init; -// std::tie(VD, Init) = parseAssignment(S); -// if (VD && Init) -// E = Init; -// } -// return E; -//} -// -//// load or bare symbol -//void ImplicitAtomicsChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, -// CheckerContext &C) const { -// const Expr *expr = getDereferenceExpr(S); -// assert(expr); -// if (isStdAtomic(expr)) -// reportBug(S, C); -//} -// -//// auto &r = *l, or store -//void ImplicitAtomicsChecker::checkBind(SVal L, SVal V, const Stmt *S, -// CheckerContext &C) const { -// const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true); -// assert(expr); -// if (isStdAtomic(expr)) -// reportBug(S, C, " (bind)"); -//} +class ImplicitAtomicsCheckerModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("concurrency-implicit-atomics"); + } +}; namespace clang { -namespace ento { -void registerImplicitAtomicsChecker(CheckerManager &mgr) { - mgr.registerChecker(); -} -bool shouldRegisterImplicitAtomicsChecker(const CheckerManager &mgr) { - return true; -} -} // namespace ento -} // namespace clang +namespace tidy { -#ifdef CLANG_PLUGIN -extern "C" const char clang_analyzerAPIVersionString[] = - CLANG_ANALYZER_API_VERSION_STRING; -extern "C" void clang_registerCheckers(CheckerRegistry ®istry) { - registry.addChecker( - "julia.ImplicitAtomics", "Flags implicit atomic operations", "" - ); -} -#endif +// Register the ImplicitAtomicsCheckerModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add<::ImplicitAtomicsCheckerModule> + X("concurrency-module", "Adds my concurrency checks."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ImplicitAtomicsCheckerModule. +volatile int ImplicitAtomicsCheckerModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/src/clangsa/ImplicitAtomics2.cpp b/src/clangsa/ImplicitAtomics2.cpp deleted file mode 100644 index b9ffc43bc22f8..0000000000000 --- a/src/clangsa/ImplicitAtomics2.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// This file is a part of Julia. License is MIT: https://julialang.org/license - -#include "clang/AST/ASTContext.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang-tidy/ClangTidy.h" -#include "clang-tidy/ClangTidyCheck.h" -#include "clang-tidy/ClangTidyModule.h" -#include "clang-tidy/ClangTidyModuleRegistry.h" - -using namespace clang; -using namespace clang::tidy; -using namespace clang::ast_matchers; - -class ImplicitAtomicsChecker : public ClangTidyCheck { - void reportBug(const Stmt *S, StringRef desc=""); - -public: - ImplicitAtomicsChecker(StringRef Name, ClangTidyContext *Context); - void registerMatchers(ast_matchers::MatchFinder *Finder) override; - void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - -private: -}; - -// Checks if RD has name in Names and is in std namespace -static bool hasStdClassWithName(const CXXRecordDecl *RD, - ArrayRef Names) { - // or could check ASTContext::getQualifiedTemplateName()->isDerivedFrom() ? - if (!RD || !RD->getDeclContext()->isStdNamespace()) - return false; - if (RD->getDeclName().isIdentifier()) { - StringRef Name = RD->getName(); - return llvm::any_of(Names, [&Name](StringRef GivenName) -> bool { - return Name == GivenName; - }); - } - return false; -} - -constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"atomic", "atomic_ref"}; - -static bool isStdAtomic(const CXXRecordDecl *RD) { - return hasStdClassWithName(RD, STD_PTR_NAMES); -} - -static bool isStdAtomicCall(const Expr *E) { - return E && isStdAtomic(E->IgnoreImplicit()->getType()->getAsCXXRecordDecl()); -} - -static bool isStdAtomic(const Expr *E) { - return E->getType()->isAtomicType(); -} - -void ImplicitAtomicsChecker::reportBug(const Stmt *S, StringRef desc) { - // try to find the "best" node to attach this to, so we generate fewer duplicate reports - while (1) { - const auto *expr = dyn_cast(S); - if (!expr) - break; - expr = expr->IgnoreParenCasts(); - if (const auto *UO = dyn_cast(expr)) - S = UO->getSubExpr(); - else if (const auto *BO = dyn_cast(expr)) - S = isStdAtomic(BO->getLHS()) ? BO->getLHS() : - isStdAtomic(BO->getRHS()) ? BO->getRHS() : - BO->getLHS(); - else - break; - } - SmallString<100> buf; - llvm::raw_svector_ostream os(buf); - os << "Implicit Atomic seq_cst synchronization" << desc; - diag(S->getBeginLoc(), buf.str()); -} - - -ImplicitAtomicsChecker:: - ImplicitAtomicsChecker(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) { -} - -void ImplicitAtomicsChecker::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher(castExpr(hasCastKind(CK_AtomicToNonAtomic)) - .bind("cast"), - this); - Finder->addMatcher(unaryOperator(unless(hasAnyOperatorName("&"))) - .bind("unary-op"), - this); - Finder->addMatcher(binaryOperator() - .bind("binary-op"), - this); - Finder->addMatcher(cxxOperatorCallExpr() - .bind("cxxcall"), - this); - Finder->addMatcher(cxxMemberCallExpr() - .bind("cxxcall"), - this); -} - -void ImplicitAtomicsChecker::check(const MatchFinder::MatchResult &Result) { - if (const auto *UOp = Result.Nodes.getNodeAs("unary-op")) { - const Expr *Sub = UOp->getSubExpr(); - if (isStdAtomic(UOp) || isStdAtomic(Sub)) - reportBug(UOp); - } - if (const auto *BOp = Result.Nodes.getNodeAs("binary-op")) { - const Expr *Lhs = BOp->getLHS(); - const Expr *Rhs = BOp->getRHS(); - if (isStdAtomic(Lhs) || isStdAtomic(Rhs) || isStdAtomic(BOp)) - reportBug(BOp); - } - if (const auto *CE = Result.Nodes.getNodeAs("cast")) { - reportBug(CE); - } - if (const auto *Call = Result.Nodes.getNodeAs("cxxcall")) { - if (const auto *OC = dyn_cast(Call)) { - const auto *CXXThisExpr = OC->getArg(0); - if (isStdAtomicCall(CXXThisExpr)) { - OverloadedOperatorKind OOK = OC->getOperator(); - if (CXXOperatorCallExpr::isAssignmentOp(OOK) || OOK == OO_PlusPlus || OOK == OO_MinusMinus) { - reportBug(CXXThisExpr, " (std::atomic operator)"); - } - } - } - else if (const auto *OC = dyn_cast(Call)) { - const auto *CXXThisExpr = OC->getImplicitObjectArgument(); - if (isStdAtomicCall(CXXThisExpr)) { - if (isa(OC->getMethodDecl())) { - reportBug(CXXThisExpr, " (std::atomic cast)"); - } - } - } - } -} - -class ImplicitAtomicsCheckerModule : public ClangTidyModule { -public: - void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { - CheckFactories.registerCheck("concurrency-implicit-atomics"); - } -}; - -namespace clang { -namespace tidy { - -// Register the ImplicitAtomicsCheckerModule using this statically initialized variable. -static ClangTidyModuleRegistry::Add<::ImplicitAtomicsCheckerModule> - X("concurrency-module", "Adds my concurrency checks."); - -// This anchor is used to force the linker to link in the generated object file -// and thus register the ImplicitAtomicsCheckerModule. -volatile int ImplicitAtomicsCheckerModuleAnchorSource = 0; - -} // namespace tidy -} // namespace clang diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index ffad47a1bf769..1c52f969a11f7 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -12,14 +12,15 @@ JL_DLLEXPORT void jl_dump_native_fallback(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, - const char *sysimg_data, size_t sysimg_len) UNAVAILABLE -JL_DLLEXPORT int32_t jl_get_llvm_gv_fallback(void *native_code, jl_value_t *p) UNAVAILABLE + const char *sysimg_data, size_t sysimg_len, ios_t *s) UNAVAILABLE +JL_DLLEXPORT void jl_get_llvm_gvs_fallback(void *native_code, arraylist_t *gvs) UNAVAILABLE +JL_DLLEXPORT void jl_get_llvm_external_fns_fallback(void *native_code, arraylist_t *gvs) UNAVAILABLE JL_DLLEXPORT void jl_extern_c_fallback(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name) UNAVAILABLE JL_DLLEXPORT jl_value_t *jl_dump_method_asm_fallback(jl_method_instance_t *linfo, size_t world, char raw_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary) UNAVAILABLE -JL_DLLEXPORT jl_value_t *jl_dump_function_ir_fallback(void *f, char strip_ir_metadata, char dump_module, const char *debuginfo) UNAVAILABLE -JL_DLLEXPORT void *jl_get_llvmf_defn_fallback(jl_method_instance_t *linfo, size_t world, char getwrapper, char optimize, const jl_cgparams_t params) UNAVAILABLE +JL_DLLEXPORT jl_value_t *jl_dump_function_ir_fallback(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo) UNAVAILABLE +JL_DLLEXPORT void jl_get_llvmf_defn_fallback(jl_llvmf_dump_t *dump, jl_method_instance_t *linfo, size_t world, char getwrapper, char optimize, const jl_cgparams_t params) UNAVAILABLE JL_DLLEXPORT void *jl_LLVMCreateDisasm_fallback(const char *TripleName, void *DisInfo, int TagType, void *GetOpInfo, void *SymbolLookUp) UNAVAILABLE JL_DLLEXPORT size_t jl_LLVMDisasmInstruction_fallback(void *DC, uint8_t *Bytes, uint64_t BytesSize, uint64_t PC, char *OutString, size_t OutStringSize) UNAVAILABLE @@ -31,10 +32,10 @@ JL_DLLEXPORT int jl_getFunctionInfo_fallback(jl_frame_t **frames, uintptr_t poin return 0; } -JL_DLLEXPORT void jl_register_fptrs_fallback(uint64_t sysimage_base, const struct _jl_sysimg_fptrs_t *fptrs, +JL_DLLEXPORT void jl_register_fptrs_fallback(uint64_t image_base, const struct _jl_image_fptrs_t *fptrs, jl_method_instance_t **linfos, size_t n) { - (void)sysimage_base; (void)fptrs; (void)linfos; (void)n; + (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) @@ -47,17 +48,19 @@ JL_DLLEXPORT void jl_generate_fptr_for_unspecialized_fallback(jl_code_instance_t 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 uint32_t jl_get_LLVM_VERSION_fallback(void) { return 0; } -JL_DLLEXPORT int jl_compile_extern_c_fallback(void *llvmmod, void *params, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) +JL_DLLEXPORT int jl_compile_extern_c_fallback(LLVMOrcThreadSafeModuleRef llvmmod, void *params, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) { return 0; } -JL_DLLEXPORT void jl_teardown_codegen_fallback(void) +JL_DLLEXPORT void jl_teardown_codegen_fallback(void) JL_NOTSAFEPOINT { } @@ -66,15 +69,7 @@ JL_DLLEXPORT size_t jl_jit_total_bytes_fallback(void) return 0; } -JL_DLLEXPORT void jl_lock_profile_fallback(void) -{ -} - -JL_DLLEXPORT void jl_unlock_profile_fallback(void) -{ -} - -JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, const jl_cgparams_t *cgparams, int _policy) UNAVAILABLE +JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage, size_t _world) UNAVAILABLE JL_DLLEXPORT void jl_dump_compiles_fallback(void *s) { @@ -90,18 +85,17 @@ JL_DLLEXPORT void jl_dump_llvm_opt_fallback(void *s) JL_DLLEXPORT jl_value_t *jl_dump_fptr_asm_fallback(uint64_t fptr, char raw_mc, const char* asm_variant, const char *debuginfo, char binary) UNAVAILABLE -JL_DLLEXPORT jl_value_t *jl_dump_function_asm_fallback(void *F, char raw_mc, const char* asm_variant, const char *debuginfo, char binary) UNAVAILABLE +JL_DLLEXPORT jl_value_t *jl_dump_function_asm_fallback(jl_llvmf_dump_t* dump, char raw_mc, const char* asm_variant, const char *debuginfo, char binary) UNAVAILABLE JL_DLLEXPORT void jl_get_function_id_fallback(void *native_code, jl_code_instance_t *ncode, int32_t *func_idx, int32_t *specfunc_idx) UNAVAILABLE -JL_DLLEXPORT void *jl_get_llvm_context_fallback(void *native_code) UNAVAILABLE JL_DLLEXPORT void *jl_get_llvm_function_fallback(void *native_code, uint32_t idx) UNAVAILABLE -JL_DLLEXPORT void *jl_get_llvm_module_fallback(void *native_code) UNAVAILABLE +JL_DLLEXPORT LLVMOrcThreadSafeModuleRef jl_get_llvm_module_fallback(void *native_code) UNAVAILABLE -JL_DLLEXPORT void *jl_type_to_llvm_fallback(jl_value_t *jt, bool_t *isboxed) UNAVAILABLE +JL_DLLEXPORT void *jl_type_to_llvm_fallback(jl_value_t *jt, LLVMContextRef llvmctxt, bool_t *isboxed) UNAVAILABLE JL_DLLEXPORT jl_value_t *jl_get_libllvm_fallback(void) JL_NOTSAFEPOINT { diff --git a/src/codegen.cpp b/src/codegen.cpp index be6d8e2f66325..a5d54f16ed2e6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1,18 +1,8 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license +#undef DEBUG #include "llvm-version.h" #include "platform.h" -#if defined(_OS_WINDOWS_) -// use ELF because RuntimeDyld COFF i686 support didn't exist -// use ELF because RuntimeDyld COFF X86_64 doesn't seem to work (fails to generate function pointers)? -#define FORCE_ELF -#endif -#if defined(_CPU_X86_) -#define JL_NEED_FLOATTEMP_VAR 1 -#endif -#if defined(_OS_WINDOWS_) || defined(_OS_FREEBSD_) -#define JL_DISABLE_FPO -#endif #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS @@ -30,11 +20,7 @@ // target machine computation #include -#if JL_LLVM_VERSION >= 140000 #include -#else -#include -#endif #include #include #include @@ -92,6 +78,27 @@ using namespace llvm; +static bool jl_fpo_disabled(const Triple &TT) { +#ifdef JL_DISABLE_FPO + return true; +#endif +#ifdef _COMPILER_MSAN_ENABLED_ + // MSAN doesn't support FPO + return true; +#endif + if (TT.isOSLinux() || TT.isOSWindows() || TT.isOSFreeBSD()) { + return true; + } + return false; +} + +static bool jl_floattemp_var_needed(const Triple &TT) { +#ifdef JL_NEED_FLOATTEMP_VAR + return true; +#endif + return TT.getArch() == Triple::x86; +} + //Drag some useful type functions into our namespace //to reduce verbosity of our code auto getInt1Ty(LLVMContext &ctxt) { @@ -127,13 +134,6 @@ auto getVoidTy(LLVMContext &ctxt) { auto getCharTy(LLVMContext &ctxt) { return getInt32Ty(ctxt); } -auto getSizeTy(LLVMContext &ctxt) { - if (sizeof(size_t) > sizeof(uint32_t)) { - return getInt64Ty(ctxt); - } else { - return getInt32Ty(ctxt); - } -} auto getInt8PtrTy(LLVMContext &ctxt) { return Type::getInt8PtrTy(ctxt); } @@ -152,13 +152,6 @@ auto getFloatPtrTy(LLVMContext &ctxt) { auto getDoublePtrTy(LLVMContext &ctxt) { return Type::getDoublePtrTy(ctxt); } -auto getSizePtrTy(LLVMContext &ctxt) { - if (sizeof(size_t) > sizeof(uint32_t)) { - return getInt64PtrTy(ctxt); - } else { - return getInt32PtrTy(ctxt); - } -} typedef Instruction TerminatorInst; @@ -166,18 +159,40 @@ typedef Instruction TerminatorInst; #define NOMINMAX #endif -#include "julia.h" -#include "julia_internal.h" #include "jitlayers.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "processor.h" #include "julia_assert.h" -JL_STREAM *dump_emitted_mi_name_stream = NULL; -extern "C" JL_DLLEXPORT +#undef DEBUG_TYPE //LLVM occasionally likes to set DEBUG_TYPE in a header... +#define DEBUG_TYPE "julia_irgen_codegen" + +STATISTIC(EmittedAllocas, "Number of allocas emitted"); +STATISTIC(EmittedIntToPtrs, "Number of inttoptrs emitted"); +STATISTIC(ModulesCreated, "Number of LLVM Modules created"); +STATISTIC(EmittedBoxCompares, "Number of box compares emitted"); +STATISTIC(EmittedBitsUnionCompares, "Number of bitsunion compares emitted"); +STATISTIC(EmittedBitsCompares, "Number of bits compares emitted"); +STATISTIC(EmittedEgals, "Number of egals emitted"); +STATISTIC(EmittedOpfields, "Number of opfields emitted"); +STATISTIC(EmittedBuiltinCalls, "Number of builtin calls emitted"); +STATISTIC(EmittedJLCalls, "Number of jlcalls emitted"); +STATISTIC(EmittedSpecfunCalls, "Number of specialized calls emitted"); +STATISTIC(EmittedInvokes, "Number of invokes emitted"); +STATISTIC(EmittedCalls, "Number of calls emitted"); +STATISTIC(EmittedUndefVarErrors, "Number of undef var errors emitted"); +STATISTIC(EmittedOpaqueClosureFunctions, "Number of opaque closures emitted"); +STATISTIC(EmittedToJLInvokes, "Number of tojlinvoke calls emitted"); +STATISTIC(EmittedCFuncInvalidates, "Number of C function invalidates emitted"); +STATISTIC(GeneratedCFuncWrappers, "Number of C function wrappers generated"); +STATISTIC(GeneratedCCallables, "Number of C-callable functions generated"); +STATISTIC(GeneratedInvokeWrappers, "Number of invoke wrappers generated"); +STATISTIC(EmittedFunctions, "Number of functions emitted"); + +extern "C" JL_DLLEXPORT_CODEGEN void jl_dump_emitted_mi_name_impl(void *s) { - dump_emitted_mi_name_stream = (JL_STREAM*)s; + **jl_ExecutionEngine->get_dump_emitted_mi_name_stream() = (ios_t*)s; } extern "C" { @@ -207,19 +222,14 @@ extern void _chkstk(void); #endif } -// for image reloading -bool imaging_mode = false; - // shared llvm state -static LLVMContext &jl_LLVMContext = *(new LLVMContext()); -TargetMachine *jl_TargetMachine; -static DataLayout &jl_data_layout = *(new DataLayout("")); #define jl_Module ctx.f->getParent() #define jl_builderModule(builder) (builder).GetInsertBlock()->getParent()->getParent() #define prepare_call(Callee) prepare_call_in(jl_Module, (Callee)) // types struct jl_typecache_t { + Type *T_size; Type *T_jlvalue; Type *T_pjlvalue; Type *T_prjlvalue; @@ -233,6 +243,8 @@ struct jl_typecache_t { IntegerType *T_sigatomic; Type *T_ppint8; + unsigned sizeof_ptr; + Align alignof_ptr; bool initialized; @@ -242,13 +254,17 @@ struct jl_typecache_t { T_pjlarray(nullptr), T_jlfunc(nullptr), T_jlfuncparams(nullptr), T_sigatomic(nullptr), T_ppint8(nullptr), initialized(false) {} - void initialize(LLVMContext &context) { + void initialize(LLVMContext &context, const DataLayout &DL) { if (initialized) { return; } initialized = true; T_ppint8 = PointerType::get(getInt8PtrTy(context), 0); - T_sigatomic = Type::getIntNTy(jl_LLVMContext, sizeof(sig_atomic_t) * 8); + T_sigatomic = Type::getIntNTy(context, sizeof(sig_atomic_t) * 8); + T_size = DL.getIntPtrType(context); + sizeof_ptr = DL.getPointerSize(); + // use pointer abi alignment for intptr_t + alignof_ptr = DL.getPointerABIAlignment(0); T_jlvalue = JuliaType::get_jlvalue_ty(context); T_pjlvalue = PointerType::get(T_jlvalue, 0); T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked); @@ -261,7 +277,7 @@ struct jl_typecache_t { assert(T_jlfuncparams != NULL); Type *vaelts[] = {PointerType::get(getInt8Ty(context), AddressSpace::Loaded) - , getSizeTy(context) + , T_size , getInt16Ty(context) , getInt16Ty(context) , getInt32Ty(context) @@ -351,12 +367,71 @@ struct jl_tbaacache_t { } }; -// Basic DITypes -static DICompositeType *jl_value_dillvmt; -static DIDerivedType *jl_pvalue_dillvmt; -static DIDerivedType *jl_ppvalue_dillvmt; -static DISubroutineType *jl_di_func_sig; -static DISubroutineType *jl_di_func_null_sig; +struct jl_noaliascache_t { + // Each domain operates completely independently. + // "No aliasing" is inferred if it is implied by any domain. + + // memory regions domain + struct jl_regions_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 *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) {} + + void initialize(llvm::LLVMContext &context) { + MDBuilder mbuilder(context); + MDNode *domain = mbuilder.createAliasScopeDomain("jnoalias"); + + this->gcframe = mbuilder.createAliasScope("jnoalias_gcframe", domain); + this->stack = mbuilder.createAliasScope("jnoalias_stack", domain); + this->data = mbuilder.createAliasScope("jnoalias_data", domain); + this->type_metadata = mbuilder.createAliasScope("jnoalias_typemd", domain); + this->constant = mbuilder.createAliasScope("jnoalias_const", domain); + } + } regions; + + // `@aliasscope` domain + struct jl_aliasscope_t { + MDNode *current; + + jl_aliasscope_t(): current(nullptr) {} + + // No init required, this->current is only used to store the currently active aliasscope + void initialize(llvm::LLVMContext &context) {} + } aliasscope; + + bool initialized; + + jl_noaliascache_t(): regions(), aliasscope(), initialized(false) {} + + void initialize(llvm::LLVMContext &context) { + if (initialized) { + assert(®ions.constant->getContext() == &context); + return; + } + initialized = true; + regions.initialize(context); + aliasscope.initialize(context); + } +}; + +struct jl_debugcache_t { + // Basic DITypes + DIDerivedType *jl_pvalue_dillvmt; + DIDerivedType *jl_ppvalue_dillvmt; + DISubroutineType *jl_di_func_sig; + DISubroutineType *jl_di_func_null_sig; + bool initialized; + + jl_debugcache_t() + : jl_pvalue_dillvmt(nullptr), jl_ppvalue_dillvmt(nullptr), + jl_di_func_sig(nullptr), jl_di_func_null_sig(nullptr), initialized(false) {} + + void initialize(Module *m); +}; // constants @@ -400,14 +475,15 @@ struct JuliaVariable { public: StringLiteral name; bool isconst; - Type *(*_type)(LLVMContext &C); + Type *(*_type)(Type *T_size); JuliaVariable(const JuliaVariable&) = delete; JuliaVariable(const JuliaVariable&&) = delete; GlobalVariable *realize(Module *m) { if (GlobalValue *V = m->getNamedValue(name)) return cast(V); - return new GlobalVariable(*m, _type(m->getContext()), + auto T_size = m->getDataLayout().getIntPtrType(m->getContext()); + return new GlobalVariable(*m, _type(T_size), isconst, GlobalVariable::ExternalLinkage, NULL, name); } @@ -418,10 +494,31 @@ static inline void add_named_global(JuliaVariable *name, void *addr) add_named_global(name->name, addr); } + +typedef FunctionType *(*TypeFnContextOnly)(LLVMContext &C); +typedef FunctionType *(*TypeFnContextAndSizeT)(LLVMContext &C, Type *T_size); +typedef FunctionType *(*TypeFnContextAndTriple)(LLVMContext &C, const Triple &triple); + +FunctionType *invoke_type(TypeFnContextOnly f, Module &M) +{ + return f(M.getContext()); +} + +FunctionType *invoke_type(TypeFnContextAndSizeT f, Module &M) +{ + return f(M.getContext(), M.getDataLayout().getIntPtrType(M.getContext())); +} + +FunctionType *invoke_type(TypeFnContextAndTriple f, Module &M) +{ + return f(M.getContext(), Triple(M.getTargetTriple())); +} + +template struct JuliaFunction { public: llvm::StringLiteral name; - llvm::FunctionType *(*_type)(llvm::LLVMContext &C); + TypeFn_t _type; llvm::AttributeList (*_attrs)(llvm::LLVMContext &C); JuliaFunction(const JuliaFunction&) = delete; @@ -429,7 +526,7 @@ struct JuliaFunction { llvm::Function *realize(llvm::Module *m) { if (llvm::GlobalValue *V = m->getNamedValue(name)) return llvm::cast(V); - llvm::Function *F = llvm::Function::Create(_type(m->getContext()), + llvm::Function *F = llvm::Function::Create(invoke_type(_type, *m), llvm::Function::ExternalLinkage, name, m); if (_attrs) @@ -438,8 +535,8 @@ struct JuliaFunction { } }; -template -static inline void add_named_global(JuliaFunction *name, T *addr) +template +static inline void add_named_global(JuliaFunction *name, T *addr) { // cast through integer to avoid c++ pedantic warning about casting between // data and code pointers @@ -464,24 +561,27 @@ AttributeSet Attributes(LLVMContext &C, std::initializer_list(getInt8PtrTy(C)); }, + [](Type *T_size) -> Type * { return getInt8PtrTy(T_size->getContext()); }, }; -#ifdef _OS_WINDOWS_ static const auto jlexe_var = new JuliaVariable{ XSTR(jl_exe_handle), true, - [](LLVMContext &C) { return static_cast(getInt8PtrTy(C)); }, + [](Type *T_size) -> Type * { return getInt8PtrTy(T_size->getContext()); }, }; static const auto jldll_var = new JuliaVariable{ XSTR(jl_libjulia_handle), true, - [](LLVMContext &C) { return static_cast(getInt8PtrTy(C)); }, + [](Type *T_size) -> Type * { return getInt8PtrTy(T_size->getContext()); }, }; static const auto jldlli_var = new JuliaVariable{ XSTR(jl_libjulia_internal_handle), true, - [](LLVMContext &C) { return static_cast(getInt8PtrTy(C)); }, + [](Type *T_size) -> Type * { return getInt8PtrTy(T_size->getContext()); }, +}; +static const auto jlsmall_typeof_var = new JuliaVariable{ + XSTR(small_typeof), + true, + [](Type *T_size) -> Type * { return getInt8Ty(T_size->getContext()); }, }; -#endif //_OS_WINDOWS_ static const auto jlstack_chk_guard_var = new JuliaVariable{ XSTR(__stack_chk_guard), true, - get_pjlvalue, + [](Type *T_size) -> Type * { return get_pjlvalue(T_size->getContext()); }, }; static const auto jlgetworld_global = new JuliaVariable{ XSTR(jl_world_counter), false, - [](LLVMContext &C) { return (Type*)getSizeTy(C); }, + [](Type *T_size) -> Type * { return T_size; }, }; static const auto jlboxed_int8_cache = new JuliaVariable{ XSTR(jl_boxed_int8_cache), true, - [](LLVMContext &C) { return (Type*)ArrayType::get(get_pjlvalue(C), 256); }, + [](Type *T_size) -> Type * { return ArrayType::get(get_pjlvalue(T_size->getContext()), 256); }, }; static const auto jlboxed_uint8_cache = new JuliaVariable{ XSTR(jl_boxed_uint8_cache), true, - [](LLVMContext &C) { return (Type*)ArrayType::get(get_pjlvalue(C), 256); }, + [](Type *T_size) -> Type * { return ArrayType::get(get_pjlvalue(T_size->getContext()), 256); }, }; -static const auto jlpgcstack_func = new JuliaFunction{ +static const auto jlpgcstack_func = new JuliaFunction<>{ "julia.get_pgcstack", [](LLVMContext &C) { return FunctionType::get(PointerType::get(JuliaType::get_ppjlvalue_ty(C), 0), false); }, nullptr, }; +static const auto jladoptthread_func = new JuliaFunction<>{ + "julia.get_pgcstack_or_new", + jlpgcstack_func->_type, + jlpgcstack_func->_attrs, +}; // important functions // Symbols are not gc-tracked, but we'll treat them as callee rooted anyway, // because they may come from a gc-rooted location -static const auto jlnew_func = new JuliaFunction{ +static const auto jlnew_func = new JuliaFunction<>{ XSTR(jl_new_structv), get_func_sig, get_func_attrs, }; -static const auto jlsplatnew_func = new JuliaFunction{ +static const auto jlsplatnew_func = new JuliaFunction<>{ XSTR(jl_new_structt), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); return FunctionType::get(T_prjlvalue, {T_prjlvalue, T_prjlvalue}, false); }, - get_func_attrs, + get_attrs_basic, }; -static const auto jlthrow_func = new JuliaFunction{ +static const auto jlthrow_func = new JuliaFunction<>{ XSTR(jl_throw), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, get_attrs_noreturn, }; -static const auto jlerror_func = new JuliaFunction{ +static const auto jlerror_func = new JuliaFunction<>{ XSTR(jl_error), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {getInt8PtrTy(C)}, false); }, get_attrs_noreturn, }; -static const auto jlatomicerror_func = new JuliaFunction{ +static const auto jlatomicerror_func = new JuliaFunction<>{ XSTR(jl_atomic_error), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {getInt8PtrTy(C)}, false); }, get_attrs_noreturn, }; -static const auto jltypeerror_func = new JuliaFunction{ +static const auto jltypeerror_func = new JuliaFunction<>{ XSTR(jl_type_error), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {getInt8PtrTy(C), JuliaType::get_prjlvalue_ty(C), PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, get_attrs_noreturn, }; -static const auto jlundefvarerror_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); }, get_attrs_noreturn, }; -static const auto jlboundserrorv_func = new JuliaFunction{ +static const auto jlboundserrorv_func = new JuliaFunction{ XSTR(jl_bounds_error_ints), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted), getSizePtrTy(C), getSizeTy(C)}, false); }, + [](LLVMContext &C, Type *T_size) { return FunctionType::get(getVoidTy(C), + {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted), T_size->getPointerTo(), T_size}, false); }, get_attrs_noreturn, }; -static const auto jlboundserror_func = new JuliaFunction{ +static const auto jlboundserror_func = new JuliaFunction{ XSTR(jl_bounds_error_int), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted), getSizeTy(C)}, false); }, + [](LLVMContext &C, Type *T_size) { return FunctionType::get(getVoidTy(C), + {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted), T_size}, false); }, get_attrs_noreturn, }; -static const auto jlvboundserror_func = new JuliaFunction{ +static const auto jlvboundserror_func = new JuliaFunction{ XSTR(jl_bounds_error_tuple_int), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {JuliaType::get_pprjlvalue_ty(C), getSizeTy(C), getSizeTy(C)}, false); }, + [](LLVMContext &C, Type *T_size) { return FunctionType::get(getVoidTy(C), + {JuliaType::get_pprjlvalue_ty(C), T_size, T_size}, false); }, get_attrs_noreturn, }; -static const auto jluboundserror_func = new JuliaFunction{ +static const auto jluboundserror_func = new JuliaFunction{ XSTR(jl_bounds_error_unboxed_int), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {PointerType::get(getInt8Ty(C), AddressSpace::Derived), JuliaType::get_pjlvalue_ty(C), getSizeTy(C)}, false); }, + [](LLVMContext &C, Type *T_size) { + return FunctionType::get(getVoidTy(C), + {PointerType::get(getInt8Ty(C), AddressSpace::Derived), JuliaType::get_pjlvalue_ty(C), T_size}, false); }, get_attrs_noreturn, }; -static const auto jlcheckassign_func = new JuliaFunction{ +static const auto jlcheckassign_func = new JuliaFunction<>{ XSTR(jl_checked_assignment), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {JuliaType::get_pjlvalue_ty(C), PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, nullptr, }; -static const auto jldeclareconst_func = new JuliaFunction{ +static const auto jldeclareconst_func = new JuliaFunction<>{ XSTR(jl_declare_constant), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {JuliaType::get_pjlvalue_ty(C)}, false); }, + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false); }, nullptr, }; -static const auto jlgetbindingorerror_func = new JuliaFunction{ +static const auto jlgetbindingorerror_func = new JuliaFunction<>{ XSTR(jl_get_binding_or_error), [](LLVMContext &C) { auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); @@ -658,7 +779,16 @@ static const auto jlgetbindingorerror_func = new JuliaFunction{ }, nullptr, }; -static const auto jlboundp_func = new JuliaFunction{ +static const auto jlgetbindingwrorerror_func = new JuliaFunction<>{ + XSTR(jl_get_binding_wr), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(T_pjlvalue, + {T_pjlvalue, T_pjlvalue}, false); + }, + nullptr, +}; +static const auto jlboundp_func = new JuliaFunction<>{ XSTR(jl_boundp), [](LLVMContext &C) { auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); @@ -667,7 +797,7 @@ static const auto jlboundp_func = new JuliaFunction{ }, nullptr, }; -static const auto jltopeval_func = new JuliaFunction{ +static const auto jltopeval_func = new JuliaFunction<>{ XSTR(jl_toplevel_eval), [](LLVMContext &C) { auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); @@ -679,7 +809,7 @@ static const auto jltopeval_func = new JuliaFunction{ Attributes(C, {Attribute::NonNull}), None); }, }; -static const auto jlcopyast_func = new JuliaFunction{ +static const auto jlcopyast_func = new JuliaFunction<>{ XSTR(jl_copy_ast), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -691,34 +821,21 @@ static const auto jlcopyast_func = new JuliaFunction{ Attributes(C, {Attribute::NonNull}), None); }, }; -//static const auto jlnsvec_func = new JuliaFunction{ -// XSTR(jl_svec), -// [](LLVMContext &C) { return FunctionType::get(T_prjlvalue, -// {getSizeTy(C)}, true); }, -// [](LLVMContext &C) { return AttributeList::get(C, -// AttributeSet(), -// Attributes(C, {Attribute::NonNull}), -// None); }, -//}; -static const auto jlapplygeneric_func = new JuliaFunction{ +static const auto jlapplygeneric_func = new JuliaFunction<>{ XSTR(jl_apply_generic), get_func_sig, get_func_attrs, }; -static const auto jlinvoke_func = new JuliaFunction{ +static const auto jlinvoke_func = new JuliaFunction<>{ XSTR(jl_invoke), - [](LLVMContext &C) { - auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); - return FunctionType::get(T_prjlvalue, - {T_prjlvalue, PointerType::get(T_prjlvalue, 0), getInt32Ty(C), T_prjlvalue}, false); - }, + get_func2_sig, [](LLVMContext &C) { return AttributeList::get(C, AttributeSet(), Attributes(C, {Attribute::NonNull}), {AttributeSet(), Attributes(C, {Attribute::ReadOnly, Attribute::NoCapture})}); }, }; -static const auto jlmethod_func = new JuliaFunction{ +static const auto jlmethod_func = new JuliaFunction<>{ XSTR(jl_method_def), [](LLVMContext &C) { auto T_jlvalue = JuliaType::get_jlvalue_ty(C); @@ -729,19 +846,18 @@ static const auto jlmethod_func = new JuliaFunction{ }, nullptr, }; -static const auto jlgenericfunction_func = new JuliaFunction{ +static const auto jlgenericfunction_func = new JuliaFunction<>{ XSTR(jl_generic_function_def), [](LLVMContext &C) { auto T_jlvalue = JuliaType::get_jlvalue_ty(C); auto T_pjlvalue = PointerType::get(T_jlvalue, 0); auto T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked); auto T_pprjlvalue = PointerType::get(T_prjlvalue, 0); - return FunctionType::get(T_prjlvalue, - {T_pjlvalue, T_pjlvalue, T_pprjlvalue, T_pjlvalue, T_pjlvalue}, false); + return FunctionType::get(T_prjlvalue, {T_pjlvalue, T_pjlvalue, T_pprjlvalue, T_pjlvalue}, false); }, nullptr, }; -static const auto jllockvalue_func = new JuliaFunction{ +static const auto jllockvalue_func = new JuliaFunction<>{ XSTR(jl_lock_value), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, @@ -750,7 +866,7 @@ static const auto jllockvalue_func = new JuliaFunction{ AttributeSet(), {Attributes(C, {Attribute::NoCapture})}); }, }; -static const auto jlunlockvalue_func = new JuliaFunction{ +static const auto jlunlockvalue_func = new JuliaFunction<>{ XSTR(jl_unlock_value), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, @@ -759,59 +875,69 @@ static const auto jlunlockvalue_func = new JuliaFunction{ AttributeSet(), {Attributes(C, {Attribute::NoCapture})}); }, }; -static const auto jlenter_func = new JuliaFunction{ +static const auto jlenter_func = new JuliaFunction<>{ XSTR(jl_enter_handler), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {getInt8PtrTy(C)}, false); }, nullptr, }; -static const auto jl_current_exception_func = new JuliaFunction{ +static const auto jl_current_exception_func = new JuliaFunction<>{ XSTR(jl_current_exception), [](LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), false); }, nullptr, }; -static const auto jlleave_func = new JuliaFunction{ +static const auto jlleave_func = new JuliaFunction<>{ XSTR(jl_pop_handler), [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {getInt32Ty(C)}, false); }, nullptr, }; -static const auto jl_restore_excstack_func = new JuliaFunction{ +static const auto jl_restore_excstack_func = new JuliaFunction{ XSTR(jl_restore_excstack), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {getSizeTy(C)}, false); }, + [](LLVMContext &C, Type *T_size) { return FunctionType::get(getVoidTy(C), + {T_size}, false); }, nullptr, }; -static const auto jl_excstack_state_func = new JuliaFunction{ +static const auto jl_excstack_state_func = new JuliaFunction{ XSTR(jl_excstack_state), - [](LLVMContext &C) { return FunctionType::get(getSizeTy(C), false); }, + [](LLVMContext &C, Type *T_size) { return FunctionType::get(T_size, false); }, nullptr, }; -static const auto jlegalx_func = new JuliaFunction{ +static const auto jlegalx_func = new JuliaFunction{ XSTR(jl_egal__unboxed), - [](LLVMContext &C) { + [](LLVMContext &C, Type *T_size) { Type *T = PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::Derived); - return FunctionType::get(getInt32Ty(C), {T, T, JuliaType::get_prjlvalue_ty(C)}, false); }, + 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); }, }; -static const auto jl_alloc_obj_func = new JuliaFunction{ +static const auto jl_alloc_obj_func = new JuliaFunction{ "julia.gc_alloc_obj", - [](LLVMContext &C) { + [](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); return FunctionType::get(T_prjlvalue, - {T_ppjlvalue, getSizeTy(C), T_prjlvalue}, false); + {T_ppjlvalue, 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); +#endif + auto RetAttrs = AttrBuilder(C); + RetAttrs.addAttribute(Attribute::NoAlias); + RetAttrs.addAttribute(Attribute::NonNull); + return AttributeList::get(C, + AttributeSet::get(C, FnAttrs), + AttributeSet::get(C, RetAttrs), + None); }, - [](LLVMContext &C) { return AttributeList::get(C, - AttributeSet::get(C, makeArrayRef({Attribute::getWithAllocSizeArgs(C, 1, None)})), // returns %1 bytes - Attributes(C, {Attribute::NoAlias, Attribute::NonNull}), - None); }, }; -static const auto jl_newbits_func = new JuliaFunction{ +static const auto jl_newbits_func = new JuliaFunction<>{ XSTR(jl_new_bits), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -826,7 +952,7 @@ static const auto jl_newbits_func = new JuliaFunction{ // `julia.typeof` does read memory, but it is effectively readnone before we lower // the allocation function. This is OK as long as we lower `julia.typeof` no later than // `julia.gc_alloc_obj`. -static const auto jl_typeof_func = new JuliaFunction{ +static const auto jl_typeof_func = new JuliaFunction<>{ "julia.typeof", [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -838,7 +964,7 @@ static const auto jl_typeof_func = new JuliaFunction{ Attributes(C, {Attribute::NonNull}), None); }, }; -static const auto jl_loopinfo_marker_func = new JuliaFunction{ +static const auto jl_loopinfo_marker_func = new JuliaFunction<>{ "julia.loopinfo_marker", [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), false); }, [](LLVMContext &C) { return AttributeList::get(C, @@ -846,7 +972,7 @@ static const auto jl_loopinfo_marker_func = new JuliaFunction{ AttributeSet(), None); }, }; -static const auto jl_write_barrier_func = new JuliaFunction{ +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); }, @@ -855,7 +981,8 @@ static const auto jl_write_barrier_func = new JuliaFunction{ AttributeSet(), {Attributes(C, {Attribute::ReadOnly})}); }, }; -static const auto jlisa_func = new JuliaFunction{ + +static const auto jlisa_func = new JuliaFunction<>{ XSTR(jl_isa), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -865,7 +992,7 @@ static const auto jlisa_func = new JuliaFunction{ nullptr, }; -static const auto jlsubtype_func = new JuliaFunction{ +static const auto jlsubtype_func = new JuliaFunction<>{ XSTR(jl_subtype), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -874,7 +1001,7 @@ static const auto jlsubtype_func = new JuliaFunction{ }, nullptr, }; -static const auto jlapplytype_func = new JuliaFunction{ +static const auto jlapplytype_func = new JuliaFunction<>{ XSTR(jl_instantiate_type_in_env), [](LLVMContext &C) { auto T_jlvalue = JuliaType::get_jlvalue_ty(C); @@ -892,48 +1019,49 @@ static const auto jlapplytype_func = new JuliaFunction{ None); }, }; -static const auto jl_object_id__func = new JuliaFunction{ +static const auto jl_object_id__func = new JuliaFunction{ XSTR(jl_object_id_), - [](LLVMContext &C) { return FunctionType::get(getSizeTy(C), + [](LLVMContext &C, Type *T_size) { return FunctionType::get(T_size, {JuliaType::get_prjlvalue_ty(C), PointerType::get(getInt8Ty(C), AddressSpace::Derived)}, false); }, nullptr, }; -static const auto setjmp_func = new JuliaFunction{ +static const auto setjmp_func = new JuliaFunction{ jl_setjmp_name, - [](LLVMContext &C) { return FunctionType::get(getInt32Ty(C), - {getInt8PtrTy(C), -#ifndef _OS_WINDOWS_ - getInt32Ty(C), -#endif - }, false); }, + [](LLVMContext &C, const Triple &T) { + if (T.isOSWindows()) + return FunctionType::get(getInt32Ty(C), + {getInt8PtrTy(C)}, false); + return FunctionType::get(getInt32Ty(C), + {getInt8PtrTy(C), getInt32Ty(C)}, false); + }, [](LLVMContext &C) { return AttributeList::get(C, Attributes(C, {Attribute::ReturnsTwice}), AttributeSet(), None); }, }; -static const auto memcmp_func = new JuliaFunction{ +static const auto memcmp_func = new JuliaFunction{ XSTR(memcmp), - [](LLVMContext &C) { return FunctionType::get(getInt32Ty(C), - {getInt8PtrTy(C), getInt8PtrTy(C), getSizeTy(C)}, false); }, + [](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}), AttributeSet(), None); }, // TODO: inferLibFuncAttributes(*memcmp_func, TLI); }; -static const auto jldlsym_func = new JuliaFunction{ +static const auto jldlsym_func = new JuliaFunction<>{ XSTR(jl_load_and_lookup), [](LLVMContext &C) { return FunctionType::get(JuliaType::get_pvoidfunc_ty(C), {getInt8PtrTy(C), getInt8PtrTy(C), PointerType::get(getInt8PtrTy(C), 0)}, false); }, nullptr, }; -static const auto jllazydlsym_func = new JuliaFunction{ +static const auto jllazydlsym_func = new JuliaFunction<>{ XSTR(jl_lazy_load_and_lookup), [](LLVMContext &C) { return FunctionType::get(JuliaType::get_pvoidfunc_ty(C), {JuliaType::get_prjlvalue_ty(C), getInt8PtrTy(C)}, false); }, nullptr, }; -static const auto jltypeassert_func = new JuliaFunction{ +static const auto jltypeassert_func = new JuliaFunction<>{ XSTR(jl_typeassert), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -942,19 +1070,31 @@ static const auto jltypeassert_func = new JuliaFunction{ }, nullptr, }; -static const auto jlgetnthfieldchecked_func = new JuliaFunction{ +static const auto jlgetnthfieldchecked_func = new JuliaFunction{ XSTR(jl_get_nth_field_checked), - [](LLVMContext &C) { + [](LLVMContext &C, Type *T_size) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); return FunctionType::get(T_prjlvalue, - {T_prjlvalue, getSizeTy(C)}, false); + {T_prjlvalue, T_size}, false); }, [](LLVMContext &C) { return AttributeList::get(C, AttributeSet(), Attributes(C, {Attribute::NonNull}), None); }, }; -static const auto jlgetcfunctiontrampoline_func = new JuliaFunction{ +static const auto jlfieldisdefinedchecked_func = new JuliaFunction{ + XSTR(jl_field_isdefined_checked), + [](LLVMContext &C, Type *T_size) { + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(getInt32Ty(C), + {T_prjlvalue, T_size}, false); + }, + [](LLVMContext &C) { return AttributeList::get(C, + AttributeSet(), + Attributes(C, {}), + None); }, +}; +static const auto jlgetcfunctiontrampoline_func = new JuliaFunction<>{ XSTR(jl_get_cfunction_trampoline), [](LLVMContext &C) { auto T_jlvalue = JuliaType::get_jlvalue_ty(C); @@ -978,18 +1118,18 @@ static const auto jlgetcfunctiontrampoline_func = new JuliaFunction{ Attributes(C, {Attribute::NonNull}), None); }, }; -static const auto diff_gc_total_bytes_func = new JuliaFunction{ +static const auto diff_gc_total_bytes_func = new JuliaFunction<>{ XSTR(jl_gc_diff_total_bytes), [](LLVMContext &C) { return FunctionType::get(getInt64Ty(C), false); }, nullptr, }; -static const auto sync_gc_total_bytes_func = new JuliaFunction{ +static const auto sync_gc_total_bytes_func = new JuliaFunction<>{ XSTR(jl_gc_sync_total_bytes), [](LLVMContext &C) { return FunctionType::get(getInt64Ty(C), {getInt64Ty(C)}, false); }, nullptr, }; -static const auto jlarray_data_owner_func = new JuliaFunction{ +static const auto jlarray_data_owner_func = new JuliaFunction<>{ XSTR(jl_array_data_owner), [](LLVMContext &C) { auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); @@ -1002,7 +1142,7 @@ static const auto jlarray_data_owner_func = new JuliaFunction{ None); }, }; #define BOX_FUNC(ct,at,attrs) \ -static const auto box_##ct##_func = new JuliaFunction{ \ +static const auto box_##ct##_func = new JuliaFunction<>{ \ XSTR(jl_box_##ct), \ [](LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C),\ {at}, false); }, \ @@ -1015,29 +1155,38 @@ 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_func_attrs); -BOX_FUNC(float64, getDoubleTy(C), get_func_attrs); -BOX_FUNC(ssavalue, getSizeTy(C), get_func_attrs); +BOX_FUNC(float32, getFloatTy(C), get_attrs_basic); +BOX_FUNC(float64, getDoubleTy(C), get_attrs_basic); #undef BOX_FUNC +static const auto box_ssavalue_func = new JuliaFunction{ + XSTR(jl_box_ssavalue), + [](LLVMContext &C, Type *T_size) { + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {T_size}, false); + }, + get_attrs_basic, +}; + // placeholder functions -static const auto gcroot_flush_func = new JuliaFunction{ +static const auto gcroot_flush_func = new JuliaFunction<>{ "julia.gcroot_flush", [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), false); }, nullptr, }; -static const auto gc_preserve_begin_func = new JuliaFunction{ +static const auto gc_preserve_begin_func = new JuliaFunction<>{ "llvm.julia.gc_preserve_begin", [](LLVMContext &C) { return FunctionType::get(Type::getTokenTy(C), true); }, nullptr, }; -static const auto gc_preserve_end_func = new JuliaFunction { +static const auto gc_preserve_end_func = new JuliaFunction<> { "llvm.julia.gc_preserve_end", [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), {Type::getTokenTy(C)}, false); }, nullptr, }; -static const auto except_enter_func = new JuliaFunction{ +static const auto except_enter_func = new JuliaFunction<>{ "julia.except_enter", [](LLVMContext &C) { return FunctionType::get(getInt32Ty(C), false); }, [](LLVMContext &C) { return AttributeList::get(C, @@ -1045,7 +1194,7 @@ static const auto except_enter_func = new JuliaFunction{ AttributeSet(), None); }, }; -static const auto pointer_from_objref_func = new JuliaFunction{ +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); }, @@ -1055,12 +1204,87 @@ static const auto pointer_from_objref_func = new JuliaFunction{ None); }, }; -static const auto jltuple_func = new JuliaFunction{XSTR(jl_f_tuple), get_func_sig, get_func_attrs}; -static std::map builtin_func_map; +// julia.call represents a call with julia calling convention, it is used as +// +// ptr julia.call(ptr %fptr, ptr %f, ptr %arg1, ptr %arg2, ...) +// +// In late lowering the call will then be rewritten as +// +// ptr %fptr(ptr %f, ptr args, i64 nargs) +// +// with all the spelled out args appropriately moved into the argument stack buffer. +// By representing it this way rather than allocating the stack buffer earlier, we +// allow LLVM to make more aggressive optimizations on the call arguments. +static const auto julia_call = new JuliaFunction<>{ + "julia.call", + [](LLVMContext &C) { + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {get_func_sig(C)->getPointerTo(), + T_prjlvalue}, // %f + true); }, // %args + get_attrs_basic, +}; + +// julia.call2 is like julia.call, except that %arg1 gets passed as a register +// argument at the end of the argument list. +static const auto julia_call2 = new JuliaFunction<>{ + "julia.call2", + [](LLVMContext &C) { + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(T_prjlvalue, + {get_func2_sig(C)->getPointerTo(), + T_prjlvalue, // %arg1 + T_prjlvalue}, // %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 &builtin_func_map() { + static std::map*> builtins = { + { jl_f_is_addr, new JuliaFunction<>{XSTR(jl_f_is), get_func_sig, get_func_attrs} }, + { jl_f_typeof_addr, new JuliaFunction<>{XSTR(jl_f_typeof), get_func_sig, get_func_attrs} }, + { jl_f_sizeof_addr, new JuliaFunction<>{XSTR(jl_f_sizeof), get_func_sig, get_func_attrs} }, + { jl_f_issubtype_addr, new JuliaFunction<>{XSTR(jl_f_issubtype), get_func_sig, get_func_attrs} }, + { jl_f_isa_addr, new JuliaFunction<>{XSTR(jl_f_isa), get_func_sig, get_func_attrs} }, + { jl_f_typeassert_addr, new JuliaFunction<>{XSTR(jl_f_typeassert), get_func_sig, get_func_attrs} }, + { jl_f_ifelse_addr, new JuliaFunction<>{XSTR(jl_f_ifelse), get_func_sig, get_func_attrs} }, + { jl_f__apply_iterate_addr, new JuliaFunction<>{XSTR(jl_f__apply_iterate), get_func_sig, get_func_attrs} }, + { jl_f__apply_pure_addr, new JuliaFunction<>{XSTR(jl_f__apply_pure), get_func_sig, get_func_attrs} }, + { jl_f__call_latest_addr, new JuliaFunction<>{XSTR(jl_f__call_latest), get_func_sig, get_func_attrs} }, + { jl_f__call_in_world_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world), get_func_sig, get_func_attrs} }, + { jl_f__call_in_world_total_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world_total), get_func_sig, get_func_attrs} }, + { jl_f_throw_addr, new JuliaFunction<>{XSTR(jl_f_throw), get_func_sig, get_func_attrs} }, + { jl_f_tuple_addr, jltuple_func }, + { jl_f_svec_addr, new JuliaFunction<>{XSTR(jl_f_svec), get_func_sig, get_func_attrs} }, + { jl_f_applicable_addr, new JuliaFunction<>{XSTR(jl_f_applicable), get_func_sig, get_func_attrs} }, + { jl_f_invoke_addr, new JuliaFunction<>{XSTR(jl_f_invoke), get_func_sig, get_func_attrs} }, + { jl_f_isdefined_addr, new JuliaFunction<>{XSTR(jl_f_isdefined), get_func_sig, get_func_attrs} }, + { jl_f_getfield_addr, new JuliaFunction<>{XSTR(jl_f_getfield), get_func_sig, get_func_attrs} }, + { jl_f_setfield_addr, new JuliaFunction<>{XSTR(jl_f_setfield), get_func_sig, get_func_attrs} }, + { jl_f_swapfield_addr, new JuliaFunction<>{XSTR(jl_f_swapfield), get_func_sig, get_func_attrs} }, + { jl_f_modifyfield_addr, new JuliaFunction<>{XSTR(jl_f_modifyfield), get_func_sig, get_func_attrs} }, + { jl_f_fieldtype_addr, new JuliaFunction<>{XSTR(jl_f_fieldtype), get_func_sig, get_func_attrs} }, + { 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_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} }, + { jl_f_finalizer_addr, new JuliaFunction<>{XSTR(jl_f_finalizer), get_func_sig, get_func_attrs} }, + { jl_f__svec_ref_addr, new JuliaFunction<>{XSTR(jl_f__svec_ref), get_func_sig, get_func_attrs} } + }; + return builtins; +} -static const auto jl_new_opaque_closure_jlcall_func = new JuliaFunction{XSTR(jl_new_opaque_closure_jlcall), get_func_sig, get_func_attrs}; +static const auto jl_new_opaque_closure_jlcall_func = new JuliaFunction<>{XSTR(jl_new_opaque_closure_jlcall), get_func_sig, get_func_attrs}; -static int globalUnique = 0; +static _Atomic(int) globalUniqueGeneratedNames{1}; // --- code generation --- extern "C" { @@ -1071,7 +1295,8 @@ extern "C" { 1, #endif (int) DICompileUnit::DebugEmissionKind::FullDebug, - jl_rettype_inferred, NULL }; + 1, + jl_rettype_inferred_addr, NULL }; } @@ -1128,6 +1353,69 @@ static bool deserves_sret(jl_value_t *dt, Type *T) return (size_t)jl_datatype_size(dt) > sizeof(void*) && !T->isFloatingPointTy() && !T->isVectorTy(); } +// Alias Analysis Info (analogous to llvm::AAMDNodes) +struct jl_aliasinfo_t { + MDNode *tbaa = nullptr; // '!tbaa': Struct-path TBAA. TBAA graph forms a tree (indexed by offset). + // Two pointers do not alias if they are not transitive parents + // (effectively, subfields) of each other or equal. + MDNode *tbaa_struct = nullptr; // '!tbaa.struct': Describes memory layout of struct. + MDNode *scope = nullptr; // '!alias.scope': Generic "noalias" memory access sets. + // If alias.scope(inst_a) ⊆ noalias(inst_b) (in any "domain") + // => inst_a, inst_b do not alias. + MDNode *noalias = nullptr; // '!noalias': See '!alias.scope' above. + + enum class Region { unknown, gcframe, stack, data, constant, type_metadata }; // See jl_regions_t + + explicit jl_aliasinfo_t() = default; + explicit jl_aliasinfo_t(jl_codectx_t &ctx, Region r, MDNode *tbaa); + explicit jl_aliasinfo_t(MDNode *tbaa, MDNode *tbaa_struct, MDNode *scope, MDNode *noalias) + : tbaa(tbaa), tbaa_struct(tbaa_struct), scope(scope), noalias(noalias) {} + jl_aliasinfo_t(const jl_aliasinfo_t &) = default; + + // Add !tbaa, !tbaa.struct, !alias.scope, !noalias annotations to an instruction. + // + // Also adds `invariant.load` to load instructions in the constant !noalias scope. + Instruction *decorateInst(Instruction *inst) const { + + if (this->tbaa) + inst->setMetadata(LLVMContext::MD_tbaa, this->tbaa); + if (this->tbaa_struct) + inst->setMetadata(LLVMContext::MD_tbaa_struct, this->tbaa_struct); + if (this->scope) + inst->setMetadata(LLVMContext::MD_alias_scope, this->scope); + if (this->noalias) + inst->setMetadata(LLVMContext::MD_noalias, this->noalias); + + if (this->scope && isa(inst)) { + // If this is in the read-only region, mark the load with "!invariant.load" + if (this->scope->getNumOperands() == 1) { + MDNode *operand = cast(this->scope->getOperand(0)); + auto scope_name = cast(operand->getOperand(0))->getString(); + if (scope_name == "jnoalias_const") + inst->setMetadata(LLVMContext::MD_invariant_load, MDNode::get(inst->getContext(), None)); + } + } + + return inst; + } + + // Merge two sets of alias information. + jl_aliasinfo_t merge(const jl_aliasinfo_t &other) const { + jl_aliasinfo_t result; + result.tbaa = MDNode::getMostGenericTBAA(this->tbaa, other.tbaa); + result.tbaa_struct = nullptr; + result.scope = MDNode::getMostGenericAliasScope(this->scope, other.scope); + result.noalias = MDNode::intersect(this->noalias, other.noalias); + return result; + } + + // Create alias information based on the provided TBAA metadata. + // + // This function only exists to help transition to using !noalias to encode + // memory region non-aliasing. It should be deleted once the TBAA metadata + // is improved to encode only memory layout and *not* memory regions. + static jl_aliasinfo_t fromTBAA(jl_codectx_t &ctx, MDNode *tbaa); +}; // metadata tracking for a llvm Value* during codegen struct jl_cgval_t { @@ -1145,29 +1433,52 @@ struct jl_cgval_t { bool isboxed; // whether this value is a jl_value_t* allocated on the heap with the right type tag bool isghost; // whether this value is "ghost" MDNode *tbaa; // The related tbaa node. Non-NULL iff this holds an address. + // If non-null, this memory location may be promoted on use, by hoisting the + // destination memory above the promotion point. + Instruction *promotion_point; + // If promotion_ssa is non-null, the julia src ssa value that corresponds + // to the promotion point. This is used for dominator analysis, since LLVM's + // dominator analysis has algorithmic problems for large basic blocks. + ssize_t promotion_ssa; bool ispointer() const { // whether this value is compatible with `data_pointer` return tbaa != nullptr; } - jl_cgval_t(Value *V, Value *gcroot, bool isboxed, jl_value_t *typ, Value *tindex, jl_tbaacache_t &tbaa_cache) : // general constructor (with pointer type auto-detect) - V(V), // V is allowed to be NULL in a jl_varinfo_t context, but not during codegen contexts - Vboxed(isboxed ? V : nullptr), + jl_cgval_t(Value *Vval, jl_value_t *typ, Value *tindex) : // general value constructor + V(Vval), // V is allowed to be NULL in a jl_varinfo_t context, but not during codegen contexts + Vboxed(nullptr), + TIndex(tindex), + constant(NULL), + typ(typ), + isboxed(false), + isghost(false), + tbaa(nullptr), + promotion_point(nullptr), + promotion_ssa(-1) + { + assert(TIndex == NULL || TIndex->getType() == getInt8Ty(TIndex->getContext())); + } + jl_cgval_t(Value *Vptr, bool isboxed, jl_value_t *typ, Value *tindex, MDNode *tbaa) : // general pointer constructor + V(Vptr), + Vboxed(isboxed ? Vptr : nullptr), TIndex(tindex), constant(NULL), typ(typ), isboxed(isboxed), isghost(false), - tbaa(isboxed ? best_tbaa(tbaa_cache, typ) : nullptr) + tbaa(tbaa), + promotion_point(nullptr), + promotion_ssa(-1) { if (Vboxed) assert(Vboxed->getType() == JuliaType::get_prjlvalue_ty(Vboxed->getContext())); - assert(gcroot == nullptr); + assert(tbaa != NULL); assert(!(isboxed && TIndex != NULL)); assert(TIndex == NULL || TIndex->getType() == getInt8Ty(TIndex->getContext())); } explicit jl_cgval_t(jl_value_t *typ) : // ghost value constructor - // mark explicit to avoid being used implicitly for conversion from NULL (use jl_cgval_t(ctx.builder.getContext()) instead) + // mark explicit to avoid being used implicitly for conversion from NULL (use jl_cgval_t() instead) V(NULL), Vboxed(NULL), TIndex(NULL), @@ -1175,7 +1486,9 @@ struct jl_cgval_t { typ(typ), isboxed(false), isghost(true), - tbaa(nullptr) + tbaa(nullptr), + promotion_point(nullptr), + promotion_ssa(-1) { assert(jl_is_datatype(typ)); assert(constant); @@ -1188,7 +1501,9 @@ struct jl_cgval_t { typ(typ), isboxed(v.isboxed), isghost(v.isghost), - tbaa(v.tbaa) + tbaa(v.tbaa), + promotion_point(v.promotion_point), + promotion_ssa(v.promotion_ssa) { if (Vboxed) assert(Vboxed->getType() == JuliaType::get_prjlvalue_ty(Vboxed->getContext())); @@ -1201,15 +1516,17 @@ struct jl_cgval_t { assert(isboxed || v.typ == typ || tindex); } } - explicit jl_cgval_t(LLVMContext &ctxt) : // undef / unreachable constructor - V(UndefValue::get(getVoidTy(ctxt))), + explicit jl_cgval_t() : // undef / unreachable constructor + V(NULL), Vboxed(NULL), TIndex(NULL), constant(NULL), typ(jl_bottom_type), isboxed(false), isghost(true), - tbaa(nullptr) + tbaa(nullptr), + promotion_point(nullptr), + promotion_ssa(-1) { } }; @@ -1230,7 +1547,7 @@ struct jl_varinfo_t { bool used; jl_varinfo_t(LLVMContext &ctxt) : boxroot(NULL), - value(jl_cgval_t(ctxt)), + value(jl_cgval_t()), pTIndex(NULL), dinfo(NULL), defFlag(NULL), @@ -1249,8 +1566,9 @@ class jl_codectx_t { public: IRBuilder<> builder; jl_codegen_params_t &emission_context; - jl_codegen_call_targets_t &call_targets; + llvm::MapVector call_targets; std::map &global_targets; + std::map, GlobalVariable*> &external_calls; Function *f = NULL; // local var info. globals are not in here. std::vector slots; @@ -1258,23 +1576,23 @@ class jl_codectx_t { std::vector SAvalues; std::vector> PhiNodes; std::vector ssavalue_assigned; - std::vector> oc_modules; + std::vector ssavalue_usecount; + std::vector oc_modules; jl_module_t *module = NULL; jl_typecache_t type_cache; jl_tbaacache_t tbaa_cache; + jl_noaliascache_t aliasscope_cache; jl_method_instance_t *linfo = NULL; jl_value_t *rettype = NULL; jl_code_info_t *source = NULL; jl_array_t *code = NULL; size_t world = 0; - jl_array_t *roots = NULL; const char *name = NULL; StringRef file{}; ssize_t *line = NULL; Value *spvals_ptr = NULL; Value *argArray = NULL; Value *argCount = NULL; - MDNode *aliasscope = NULL; std::string funcName; int vaSlot = -1; // name of vararg argument int nReqArgs = 0; @@ -1282,26 +1600,29 @@ class jl_codectx_t { int nvargs = -1; bool is_opaque_closure = false; - CallInst *pgcstack = NULL; - Value *world_age_field = NULL; + Value *pgcstack = NULL; + Instruction *topalloca = NULL; bool debug_enabled = false; bool use_cache = false; + bool external_linkage = false; const jl_cgparams_t *params = NULL; - std::vector> llvmcall_modules; + std::vector> llvmcall_modules; jl_codectx_t(LLVMContext &llvmctx, jl_codegen_params_t ¶ms) : builder(llvmctx), emission_context(params), - call_targets(params.workqueue), + call_targets(), global_targets(params.globals), + external_calls(params.external_fns), world(params.world), use_cache(params.cache), + external_linkage(params.external_linkage), params(params.params) { } jl_typecache_t &types() { - type_cache.initialize(builder.getContext()); + type_cache.initialize(builder.getContext(), emission_context.DL); return type_cache; } @@ -1310,8 +1631,15 @@ class jl_codectx_t { return tbaa_cache; } + jl_noaliascache_t &noalias() { + aliasscope_cache.initialize(builder.getContext()); + return aliasscope_cache; + } + ~jl_codectx_t() { - assert(this->roots == NULL); + // Transfer local delayed calls to the global queue + for (auto call_target : call_targets) + emission_context.workqueue.push_back(call_target); } }; @@ -1319,38 +1647,108 @@ GlobalVariable *JuliaVariable::realize(jl_codectx_t &ctx) { return realize(jl_Module); } +jl_aliasinfo_t::jl_aliasinfo_t(jl_codectx_t &ctx, Region r, MDNode *tbaa): tbaa(tbaa), tbaa_struct(nullptr) { + MDNode *alias_scope = nullptr; + jl_noaliascache_t::jl_regions_t regions = ctx.noalias().regions; + switch (r) { + case Region::unknown: + alias_scope = nullptr; + break; + case Region::gcframe: + alias_scope = regions.gcframe; + break; + case Region::stack: + alias_scope = regions.stack; + break; + case Region::data: + alias_scope = regions.data; + break; + case Region::constant: + alias_scope = regions.constant; + break; + case Region::type_metadata: + alias_scope = regions.type_metadata; + break; + } + + MDNode *all_scopes[5] = { regions.gcframe, regions.stack, regions.data, regions.type_metadata, regions.constant }; + if (alias_scope) { + // The matching region is added to !alias.scope + // All other regions are added to !noalias + + int i = 0; + Metadata *scopes[1] = { alias_scope }; + Metadata *noaliases[4]; + for (auto const &scope: all_scopes) { + if (scope == alias_scope) continue; + noaliases[i++] = scope; + } + + this->scope = MDNode::get(ctx.builder.getContext(), ArrayRef(scopes)); + this->noalias = MDNode::get(ctx.builder.getContext(), ArrayRef(noaliases)); + } +} + +jl_aliasinfo_t jl_aliasinfo_t::fromTBAA(jl_codectx_t &ctx, MDNode *tbaa) { + auto cache = ctx.tbaa(); + + // Each top-level TBAA node has a corresponding !alias.scope scope + MDNode *tbaa_srcs[5] = { cache.tbaa_gcframe, cache.tbaa_stack, cache.tbaa_data, cache.tbaa_array, cache.tbaa_const }; + Region regions[5] = { Region::gcframe, Region::stack, Region::data, Region::type_metadata, Region::constant }; + + if (tbaa != nullptr) { + MDNode *node = cast(tbaa->getOperand(1)); + if (cast(node->getOperand(0))->getString() != "jtbaa") { + + // Climb up to node just before root + MDNode *parent_node = cast(node->getOperand(1)); + while (cast(parent_node->getOperand(0))->getString() != "jtbaa") { + node = parent_node; + parent_node = cast(node->getOperand(1)); + } + + // Find the matching node's index + for (int i = 0; i < 5; i++) { + if (cast(tbaa_srcs[i]->getOperand(1)) == node) + return jl_aliasinfo_t(ctx, regions[i], tbaa); + } + } + } + + return jl_aliasinfo_t(ctx, Region::unknown, tbaa); +} + static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed = NULL); -static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure); +static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure); 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_sparam(jl_codectx_t &ctx, size_t i); static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const std::string &msg); -static void allocate_gc_frame(jl_codectx_t &ctx, BasicBlock *b0); static Value *get_current_task(jl_codectx_t &ctx); static Value *get_current_ptls(jl_codectx_t &ctx); -static Value *get_current_signal_page(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, Function *theFptr, Value *theF, - const jl_cgval_t *args, size_t nargs, CallingConv::ID cc); -static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction *theFptr, Value *theF, - const jl_cgval_t *args, size_t nargs, CallingConv::ID cc); +static CallInst *emit_jlcall(jl_codectx_t &ctx, FunctionCallee theFptr, Value *theF, + const jl_cgval_t *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); static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1 = nullptr, Value *nullcheck2 = nullptr); -static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t nargs, const jl_cgval_t *argv); +static jl_cgval_t emit_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 Value *literal_pointer_val(jl_codectx_t &ctx, jl_value_t *p); static GlobalVariable *prepare_global_in(Module *M, GlobalVariable *G); -Instruction *tbaa_decorate(MDNode *md, Instruction *inst); static GlobalVariable *prepare_global_in(Module *M, JuliaVariable *G) { return G->realize(M); } -static Function *prepare_call_in(Module *M, JuliaFunction *G) +template +static Function *prepare_call_in(Module *M, JuliaFunction *G) { return G->realize(M); } @@ -1408,51 +1806,54 @@ static GlobalVariable *get_pointer_to_constant(jl_codegen_params_t &emission_con static AllocaInst *emit_static_alloca(jl_codectx_t &ctx, Type *lty) { - return new AllocaInst(lty, 0, "", /*InsertBefore=*/ctx.pgcstack); + ++EmittedAllocas; + return new AllocaInst(lty, ctx.topalloca->getModule()->getDataLayout().getAllocaAddrSpace(), "", /*InsertBefore=*/ctx.topalloca); } -static void undef_derived_strct(IRBuilder<> &irbuilder, Value *ptr, jl_datatype_t *sty, MDNode *tbaa) +static void undef_derived_strct(jl_codectx_t &ctx, Value *ptr, jl_datatype_t *sty, MDNode *tbaa) { assert(ptr->getType()->getPointerAddressSpace() != AddressSpace::Tracked); size_t first_offset = sty->layout->nfields ? jl_field_offset(sty, 0) : 0; if (first_offset != 0) - irbuilder.CreateMemSet(ptr, ConstantInt::get(getInt8Ty(irbuilder.getContext()), 0), first_offset, MaybeAlign(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) return; - auto T_prjlvalue = JuliaType::get_prjlvalue_ty(irbuilder.getContext()); - ptr = irbuilder.CreateBitCast(ptr, T_prjlvalue->getPointerTo(ptr->getType()->getPointerAddressSpace())); + 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++) { - Value *fld = irbuilder.CreateConstInBoundsGEP1_32(T_prjlvalue, ptr, jl_ptr_offset(sty, i)); - tbaa_decorate(tbaa, irbuilder.CreateStore(Constant::getNullValue(T_prjlvalue), fld)); + Value *fld = ctx.builder.CreateConstInBoundsGEP1_32(T_prjlvalue, ptr, jl_ptr_offset(sty, i)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(ctx.builder.CreateStore(Constant::getNullValue(T_prjlvalue), fld)); } } static Value *emit_inttoptr(jl_codectx_t &ctx, Value *v, Type *ty) { - // Almost all of our inttoptr are generated due to representing `Ptr` with `getSizeTy(ctx.builder.getContext())` + // Almost all of our inttoptr are generated due to representing `Ptr` with `ctx.types().T_size` // in LLVM and most of these integers are generated from `ptrtoint` in the first place. if (auto I = dyn_cast(v)) { auto ptr = I->getOperand(0); if (ty->getPointerAddressSpace() == ptr->getType()->getPointerAddressSpace()) return ctx.builder.CreateBitCast(ptr, ty); - else if (ty->getPointerElementType() == ptr->getType()->getPointerElementType()) + else if (cast(ty)->hasSameElementTypeAs(cast(ptr->getType()))) return ctx.builder.CreateAddrSpaceCast(ptr, ty); } + ++EmittedIntToPtrs; return ctx.builder.CreateIntToPtr(v, ty); } static inline jl_cgval_t ghostValue(jl_codectx_t &ctx, jl_value_t *typ) { if (typ == jl_bottom_type) - return jl_cgval_t(ctx.builder.getContext()); // Undef{} + return jl_cgval_t(); // Undef{} if (typ == (jl_value_t*)jl_typeofbottom_type) { // normalize TypeofBottom to Type{Union{}} typ = (jl_value_t*)jl_typeofbottom_type->super; } if (jl_is_type_type(typ)) { // replace T::Type{T} with T, by assuming that T must be a leaftype of some sort - jl_cgval_t constant(NULL, NULL, true, typ, NULL, ctx.tbaa()); + jl_cgval_t constant(NULL, true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); constant.constant = jl_tparam0(typ); return constant; } @@ -1474,18 +1875,16 @@ static inline jl_cgval_t mark_julia_const(jl_codectx_t &ctx, jl_value_t *jv) if (jl_is_datatype_singleton((jl_datatype_t*)typ)) return ghostValue(ctx, typ); } - jl_cgval_t constant(NULL, NULL, true, typ, NULL, ctx.tbaa()); + jl_cgval_t constant(NULL, true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); constant.constant = jv; return constant; } -static inline jl_cgval_t mark_julia_slot(Value *v, jl_value_t *typ, Value *tindex, jl_tbaacache_t &tbaa_cache, MDNode *tbaa) +static inline jl_cgval_t mark_julia_slot(Value *v, jl_value_t *typ, Value *tindex, MDNode *tbaa) { // this enables lazy-copying of immutable values and stack or argument slots - assert(tbaa); - jl_cgval_t tagval(v, NULL, false, typ, tindex, tbaa_cache); - tagval.tbaa = tbaa; + jl_cgval_t tagval(v, false, typ, tindex, tbaa); return tagval; } @@ -1513,7 +1912,7 @@ static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, Value *v, jl_value_ loc = emit_static_alloca(ctx, v->getType()); ctx.builder.CreateStore(v, loc); } - return mark_julia_slot(loc, typ, tindex, ctx.tbaa(), ctx.tbaa().tbaa_stack); + return mark_julia_slot(loc, typ, tindex, ctx.tbaa().tbaa_stack); } static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, const jl_cgval_t &v) { @@ -1539,12 +1938,14 @@ static inline jl_cgval_t mark_julia_type(jl_codectx_t &ctx, Value *v, bool isbox if (type_is_ghost(T)) { return ghostValue(ctx, typ); } - if (v && !isboxed && v->getType()->isAggregateType() && !jl_is_vecelement_type(typ) && CountTrackedPointers(v->getType()).count == 0) { + if (v && !isboxed && v->getType()->isAggregateType() && CountTrackedPointers(v->getType()).count == 0) { // eagerly put this back onto the stack // llvm mem2reg pass will remove this if unneeded return value_to_pointer(ctx, v, typ, NULL); } - return jl_cgval_t(v, NULL, isboxed, typ, NULL, ctx.tbaa()); + if (isboxed) + return jl_cgval_t(v, isboxed, typ, NULL, best_tbaa(ctx.tbaa(), typ)); + return jl_cgval_t(v, typ, NULL); } static inline jl_cgval_t mark_julia_type(jl_codectx_t &ctx, Value *v, bool isboxed, jl_datatype_t *typ) @@ -1555,13 +1956,15 @@ static inline jl_cgval_t mark_julia_type(jl_codectx_t &ctx, Value *v, bool isbox // see if it might be profitable (and cheap) to change the type of v to typ static inline jl_cgval_t update_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_value_t *typ) { - if (v.typ == jl_bottom_type || v.constant || typ == (jl_value_t*)jl_any_type || jl_egal(v.typ, typ)) + if (v.typ == jl_bottom_type || typ == (jl_value_t*)jl_any_type || jl_egal(v.typ, typ)) return v; // fast-path + if (v.constant) + return jl_isa(v.constant, typ) ? v : jl_cgval_t(); if (jl_is_concrete_type(v.typ) && !jl_is_kind(v.typ)) { if (jl_is_concrete_type(typ) && !jl_is_kind(typ)) { // type mismatch: changing from one leaftype to another CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } return v; // doesn't improve type info } @@ -1576,12 +1979,12 @@ static inline jl_cgval_t update_julia_type(jl_codectx_t &ctx, const jl_cgval_t & if (alwaysboxed) { // discovered that this union-split type must actually be isboxed if (v.Vboxed) { - return jl_cgval_t(v.Vboxed, nullptr, true, typ, NULL, ctx.tbaa()); + 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) CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } } } @@ -1591,6 +1994,11 @@ static inline jl_cgval_t update_julia_type(jl_codectx_t &ctx, const jl_cgval_t & Type *T = julia_type_to_llvm(ctx, typ); if (type_is_ghost(T)) return ghostValue(ctx, typ); + else if (v.TIndex && v.V == NULL) { + // type mismatch (there weren't any non-ghost values in the union) + CreateTrap(ctx.builder); + return jl_cgval_t(); + } return jl_cgval_t(v, typ, NULL); } @@ -1739,7 +2147,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & if (!union_isaBB) { union_isaBB = BasicBlock::Create(ctx.builder.getContext(), "union_isa", ctx.f); ctx.builder.SetInsertPoint(union_isaBB); - union_box_dt = emit_typeof_or_null(ctx, v.Vboxed); + union_box_dt = emit_typeof(ctx, v.Vboxed, skip != NULL, true); post_union_isaBB = ctx.builder.GetInsertBlock(); } }; @@ -1757,7 +2165,7 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & if (old_idx == 0) { // didn't handle this item before, select its new union index maybe_setup_union_isa(); - Value *cmp = ctx.builder.CreateICmpEQ(track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jt)), union_box_dt); + 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); } }, @@ -1790,36 +2198,30 @@ static jl_cgval_t convert_julia_type_union(jl_codectx_t &ctx, const jl_cgval_t & boxv = ctx.builder.CreateSelect( ctx.builder.CreateAnd(wasboxed, isboxed), v.Vboxed, boxv); } + Value *slotv; + MDNode *tbaa; if (v.V == NULL) { // v.V might be NULL if it was all ghost objects before - return jl_cgval_t(boxv, NULL, false, typ, new_tindex, ctx.tbaa()); + slotv = NULL; + tbaa = ctx.tbaa().tbaa_const; } else { Value *isboxv = ctx.builder.CreateIsNotNull(boxv); - Value *slotv; - MDNode *tbaa; - if (v.ispointer()) { - slotv = v.V; - tbaa = v.tbaa; - } - else { - slotv = emit_static_alloca(ctx, v.V->getType()); - ctx.builder.CreateStore(v.V, slotv); - tbaa = ctx.tbaa().tbaa_stack; - } + jl_cgval_t oldv = value_to_pointer(ctx, v); + slotv = oldv.V; + tbaa = oldv.tbaa; slotv = ctx.builder.CreateSelect(isboxv, decay_derived(ctx, boxv), decay_derived(ctx, emit_bitcast(ctx, slotv, boxv->getType()))); - jl_cgval_t newv = jl_cgval_t(slotv, NULL, false, typ, new_tindex, ctx.tbaa()); - assert(boxv->getType() == ctx.types().T_prjlvalue); - newv.Vboxed = boxv; - newv.tbaa = tbaa; - return newv; } + jl_cgval_t newv = jl_cgval_t(slotv, false, typ, new_tindex, tbaa); + assert(boxv->getType() == ctx.types().T_prjlvalue); + newv.Vboxed = boxv; + return newv; } } else { - return jl_cgval_t(boxed(ctx, v), NULL, true, typ, NULL, ctx.tbaa()); + return jl_cgval_t(boxed(ctx, v), true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); } return jl_cgval_t(v, typ, new_tindex); } @@ -1837,23 +2239,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)) { - assert(skip == nullptr && "skip only valid for union type return"); 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, nullptr, true, typ, NULL, ctx.tbaa()); + 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 - CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + 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) && !jl_is_kind(v.typ)) { if (jl_is_concrete_type(typ) && !jl_is_kind(typ)) { // type mismatch: changing from one leaftype to another - CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + if (skip) + *skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); + else + CreateTrap(ctx.builder); + return jl_cgval_t(); } } } @@ -1870,11 +2277,7 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_ new_tindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), new_idx); if (v.V && !v.ispointer()) { // TODO: remove this branch once all consumers of v.TIndex understand how to handle a non-ispointer value - Value *slotv = emit_static_alloca(ctx, v.V->getType()); - ctx.builder.CreateStore(v.V, slotv); - jl_cgval_t newv = jl_cgval_t(slotv, NULL, false, typ, new_tindex, ctx.tbaa()); - newv.tbaa = ctx.tbaa().tbaa_stack; - return newv; + return value_to_pointer(ctx, v.V, typ, new_tindex); } } else if (jl_subtype(v.typ, typ)) { @@ -1883,12 +2286,12 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_ else if (skip) { // undef *skip = ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } else { // unreachable CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } } else if (!v.isboxed) { @@ -1896,138 +2299,141 @@ static jl_cgval_t convert_julia_type(jl_codectx_t &ctx, const jl_cgval_t &v, jl_ } if (makeboxed) { // convert to a simple isboxed value - return jl_cgval_t(boxed(ctx, v), NULL, true, typ, NULL, ctx.tbaa()); + return jl_cgval_t(boxed(ctx, v), true, typ, NULL, best_tbaa(ctx.tbaa(), typ)); } } return jl_cgval_t(v, typ, new_tindex); } -static void jl_setup_module(Module *m, const jl_cgparams_t *params = &jl_default_cgparams) +std::unique_ptr jl_create_llvm_module(StringRef name, LLVMContext &context, bool imaging_mode, const DataLayout &DL, const Triple &triple) { + ++ModulesCreated; + auto m = std::make_unique(name, context); // Some linkers (*cough* OS X) don't understand DWARF v4, so we use v2 in // imaging mode. The structure of v4 is slightly nicer for debugging JIT // code. if (!m->getModuleFlag("Dwarf Version")) { int dwarf_version = 4; -#ifdef _OS_DARWIN_ - if (imaging_mode) + if (triple.isOSDarwin()) { + if (imaging_mode) { dwarf_version = 2; -#endif - m->addModuleFlag(llvm::Module::Warning, "Dwarf Version", dwarf_version); + } + } + m->addModuleFlag(llvm::Module::Warning, "Dwarf Version", dwarf_version); } if (!m->getModuleFlag("Debug Info Version")) m->addModuleFlag(llvm::Module::Warning, "Debug Info Version", llvm::DEBUG_METADATA_VERSION); - m->setDataLayout(jl_data_layout); - m->setTargetTriple(jl_TargetMachine->getTargetTriple().str()); - -#if defined(_OS_WINDOWS_) && !defined(_CPU_X86_64_) && JL_LLVM_VERSION >= 130000 - // tell Win32 to assume the stack is always 16-byte aligned, - // and to ensure that it is 16-byte aligned for out-going calls, - // to ensure compatibility with GCC codes - m->setOverrideStackAlignment(16); -#endif -#if defined(JL_DEBUG_BUILD) && JL_LLVM_VERSION >= 130000 - m->setStackProtectorGuard("global"); -#endif -} + m->setDataLayout(DL); + m->setTargetTriple(triple.str()); -Module *_jl_create_llvm_module(StringRef name, LLVMContext &context, const jl_cgparams_t *params) -{ - Module *M = new Module(name, context); - jl_setup_module(M, params); - return M; -} + if (triple.isOSWindows() && triple.getArch() == Triple::x86) { + // tell Win32 to assume the stack is always 16-byte aligned, + // and to ensure that it is 16-byte aligned for out-going calls, + // to ensure compatibility with GCC codes + m->setOverrideStackAlignment(16); + } -Module *jl_create_llvm_module(StringRef name) -{ - return _jl_create_llvm_module(name, jl_LLVMContext, &jl_default_cgparams); +#if defined(JL_DEBUG_BUILD) + m->setStackProtectorGuard("global"); +#endif + return m; } -static void jl_init_function(Function *F) +static void jl_init_function(Function *F, const Triple &TT) { // set any attributes that *must* be set on all functions -#if defined(_OS_WINDOWS_) && !defined(_CPU_X86_64_) - // tell Win32 to realign the stack to the next 16-byte boundary - // upon entry to any function. This achieves compatibility - // with both MinGW-GCC (which assumes an 16-byte-aligned stack) and - // i686 Windows (which uses a 4-byte-aligned stack) - AttrBuilder attr; - attr.addStackAlignmentAttr(16); - F->addAttributes(AttributeList::FunctionIndex, attr); + AttrBuilder attr(F->getContext()); + if (TT.isOSWindows() && TT.getArch() == Triple::x86) { + // tell Win32 to assume the stack is always 16-byte aligned, + // and to ensure that it is 16-byte aligned for out-going calls, + // to ensure compatibility with GCC codes + 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 defined(_OS_WINDOWS_) && defined(_CPU_X86_64_) - F->setHasUWTable(); // force NeedsWinEH + } + if (jl_fpo_disabled(TT)) + attr.addAttribute("frame-pointer", "all"); + if (!TT.isOSWindows()) { +#if !defined(_COMPILER_ASAN_ENABLED_) + // ASAN won't like us accessing undefined memory causing spurious issues, + // and Windows has platform-specific handling which causes it to mishandle + // this annotation. Other platforms should just ignore this if they don't + // implement it. + attr.addAttribute("probe-stack", "inline-asm"); + //attr.addAttribute("stack-probe-size", "4096"); // can use this to change the default #endif -#ifdef JL_DISABLE_FPO - F->addFnAttr("frame-pointer", "all"); + } +#if defined(_COMPILER_ASAN_ENABLED_) + attr.addAttribute(Attribute::SanitizeAddress); #endif -#if !defined(_COMPILER_ASAN_ENABLED_) && !defined(_OS_WINDOWS_) - // ASAN won't like us accessing undefined memory causing spurious issues, - // and Windows has platform-specific handling which causes it to mishandle - // this annotation. Other platforms should just ignore this if they don't - // implement it. - F->addFnAttr("probe-stack", "inline-asm"); - //F->addFnAttr("stack-probe-size", 4096); // can use this to change the default +#if defined(_COMPILER_MSAN_ENABLED_) + attr.addAttribute(Attribute::SanitizeMemory); #endif + F->addFnAttrs(attr); } -static std::pair uses_specsig(jl_method_instance_t *lam, jl_value_t *rettype, bool prefer_specsig) +static bool uses_specsig(jl_value_t *sig, bool needsparams, bool va, jl_value_t *rettype, bool prefer_specsig) { - size_t nreq = jl_is_method(lam->def.method) ? lam->def.method->nargs : 0; - int va = 0; - if (nreq > 0 && lam->def.method->isva) { - nreq--; - va = 1; - } - jl_value_t *sig = lam->specTypes; - bool needsparams = false; - if (jl_is_method(lam->def.method)) { - if ((size_t)jl_subtype_env_size(lam->def.method->sig) != jl_svec_len(lam->sparam_vals)) - needsparams = true; - for (size_t i = 0; i < jl_svec_len(lam->sparam_vals); ++i) { - if (jl_is_typevar(jl_svecref(lam->sparam_vals, i))) - needsparams = true; - } - } if (needsparams) - return std::make_pair(false, true); + return false; if (sig == (jl_value_t*)jl_anytuple_type) - return std::make_pair(false, false); + return false; if (!jl_is_datatype(sig)) - return std::make_pair(false, false); + return false; if (jl_nparams(sig) == 0) - return std::make_pair(false, false); + return false; if (va) { if (jl_is_vararg(jl_tparam(sig, jl_nparams(sig) - 1))) - return std::make_pair(false, false); + return false; } // not invalid, consider if specialized signature is worthwhile if (prefer_specsig) - return std::make_pair(true, false); + return true; if (!deserves_retbox(rettype) && !jl_is_datatype_singleton((jl_datatype_t*)rettype) && rettype != (jl_value_t*)jl_bool_type) - return std::make_pair(true, false); + return true; if (jl_is_uniontype(rettype)) { bool allunbox; size_t nbytes, align, min_align; union_alloca_type((jl_uniontype_t*)rettype, allunbox, nbytes, align, min_align); if (nbytes > 0) - return std::make_pair(true, false); // some elements of the union could be returned unboxed avoiding allocation + return true; // some elements of the union could be returned unboxed avoiding allocation } if (jl_nparams(sig) <= 3) // few parameters == more efficient to pass directly - return std::make_pair(true, false); + return true; bool allSingleton = true; for (size_t i = 0; i < jl_nparams(sig); i++) { jl_value_t *sigt = jl_tparam(sig, i); bool issing = jl_is_datatype(sigt) && jl_is_datatype_singleton((jl_datatype_t*)sigt); allSingleton &= issing; if (!deserves_argbox(sigt) && !issing) { - return std::make_pair(true, false); + return true; } } if (allSingleton) - return std::make_pair(true, false); - return std::make_pair(false, false); // jlcall sig won't require any box allocations + return true; + return false; // jlcall sig won't require any box allocations +} + +static std::pair uses_specsig(jl_method_instance_t *lam, jl_value_t *rettype, bool prefer_specsig) +{ + int va = lam->def.method->isva; + jl_value_t *sig = lam->specTypes; + bool needsparams = false; + if (jl_is_method(lam->def.method)) { + if ((size_t)jl_subtype_env_size(lam->def.method->sig) != jl_svec_len(lam->sparam_vals)) + needsparams = true; + for (size_t i = 0; i < jl_svec_len(lam->sparam_vals); ++i) { + if (jl_is_typevar(jl_svecref(lam->sparam_vals, i))) + needsparams = true; + } + } + return std::make_pair(uses_specsig(sig, needsparams, va, rettype, prefer_specsig), needsparams); } @@ -2040,7 +2446,7 @@ JL_DLLEXPORT uint64_t *jl_malloc_data_pointer(StringRef filename, int line); static void visitLine(jl_codectx_t &ctx, uint64_t *ptr, Value *addend, const char *name) { Value *pv = ConstantExpr::getIntToPtr( - ConstantInt::get(getSizeTy(ctx.builder.getContext()), (uintptr_t)ptr), + ConstantInt::get(ctx.types().T_size, (uintptr_t)ptr), getInt64PtrTy(ctx.builder.getContext())); Value *v = ctx.builder.CreateLoad(getInt64Ty(ctx.builder.getContext()), pv, true, name); v = ctx.builder.CreateAdd(v, addend); @@ -2052,7 +2458,8 @@ static void visitLine(jl_codectx_t &ctx, uint64_t *ptr, Value *addend, const cha static void coverageVisitLine(jl_codectx_t &ctx, StringRef filename, int line) { - assert(!imaging_mode); + if (ctx.emission_context.imaging) + return; // TODO if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) return; visitLine(ctx, jl_coverage_data_pointer(filename, line), ConstantInt::get(getInt64Ty(ctx.builder.getContext()), 1), "lcnt"); @@ -2062,7 +2469,8 @@ static void coverageVisitLine(jl_codectx_t &ctx, StringRef filename, int line) static void mallocVisitLine(jl_codectx_t &ctx, StringRef filename, int line, Value *sync) { - assert(!imaging_mode); + if (ctx.emission_context.imaging) + return; // TODO if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) return; Value *addend = sync @@ -2078,9 +2486,9 @@ static void show_source_loc(jl_codectx_t &ctx, JL_STREAM *out) jl_printf(out, "in %s at %s", ctx.name, ctx.file.str().c_str()); } -static void cg_bdw(jl_codectx_t &ctx, jl_binding_t *b) +static void cg_bdw(jl_codectx_t &ctx, jl_sym_t *var, jl_binding_t *b) { - jl_binding_deprecation_warning(ctx.module, b); + jl_binding_deprecation_warning(ctx.module, var, b); if (b->deprecated == 1 && jl_options.depwarn) { show_source_loc(ctx, JL_STDERR); jl_printf(JL_STDERR, "\n"); @@ -2090,7 +2498,7 @@ static void cg_bdw(jl_codectx_t &ctx, jl_binding_t *b) static jl_value_t *static_apply_type(jl_codectx_t &ctx, const jl_cgval_t *args, size_t nargs) { assert(nargs > 1); - jl_value_t **v = (jl_value_t**)alloca(sizeof(jl_value_t*) * nargs); + SmallVector v(nargs); for (size_t i = 0; i < nargs; i++) { if (!args[i].constant) return NULL; @@ -2102,7 +2510,7 @@ static jl_value_t *static_apply_type(jl_codectx_t &ctx, const jl_cgval_t *args, jl_current_task->world_age = 1; jl_value_t *result; JL_TRY { - result = jl_apply(v, nargs); + result = jl_apply(v.data(), nargs); } JL_CATCH { result = NULL; @@ -2121,7 +2529,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) return jl_get_global(ctx.module, sym); return NULL; } - if (jl_is_slot(ex) || jl_is_argument(ex)) + if (jl_is_slotnumber(ex) || jl_is_argument(ex)) return NULL; if (jl_is_ssavalue(ex)) { ssize_t idx = ((jl_ssavalue_t*)ex)->id - 1; @@ -2142,8 +2550,8 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_t *b = jl_get_binding(jl_globalref_mod(ex), s); if (b && b->constp) { if (b->deprecated) - cg_bdw(ctx, b); - return b->value; + cg_bdw(ctx, s, b); + return jl_atomic_load_relaxed(&b->value); } return NULL; } @@ -2152,7 +2560,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) if (e->head == jl_call_sym) { jl_value_t *f = static_eval(ctx, jl_exprarg(e, 0)); if (f) { - if (jl_array_dim0(e->args) == 3 && f == jl_builtin_getfield) { + if (jl_array_dim0(e->args) == 3 && (f == jl_builtin_getfield || f == jl_builtin_getglobal)) { m = (jl_module_t*)static_eval(ctx, jl_exprarg(e, 1)); // Check the tag before evaluating `s` so that a value of random // type won't be corrupted. @@ -2164,8 +2572,8 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) jl_binding_t *b = jl_get_binding(m, s); if (b && b->constp) { if (b->deprecated) - cg_bdw(ctx, b); - return b->value; + cg_bdw(ctx, s, b); + return jl_atomic_load_relaxed(&b->value); } } } @@ -2215,7 +2623,7 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex) static bool slot_eq(jl_value_t *e, int sl) { - return (jl_is_slot(e) || jl_is_argument(e)) && jl_slot_number(e)-1 == sl; + return (jl_is_slotnumber(e) || jl_is_argument(e)) && jl_slot_number(e)-1 == sl; } // --- code gen for intrinsic functions --- @@ -2258,7 +2666,7 @@ static std::set assigned_in_try(jl_array_t *stmts, int s, long l) if (jl_is_expr(st)) { if (((jl_expr_t*)st)->head == jl_assign_sym) { jl_value_t *ar = jl_exprarg(st, 0); - if (jl_is_slot(ar)) { + if (jl_is_slotnumber(ar)) { av.insert(jl_slot_number(ar)-1); } } @@ -2296,54 +2704,55 @@ static void mark_volatile_vars(jl_array_t *stmts, std::vector &slo // a very simple, conservative use analysis // to eagerly remove slot assignments that are never read from -static void simple_use_analysis(jl_codectx_t &ctx, jl_value_t *expr) + +template +static void general_use_analysis(jl_codectx_t &ctx, jl_value_t *expr, callback &f) { - if (jl_is_slot(expr) || jl_is_argument(expr)) { - int i = jl_slot_number(expr) - 1; - ctx.slots[i].used = true; + if (f(expr)) { + return; } else if (jl_is_expr(expr)) { jl_expr_t *e = (jl_expr_t*)expr; if (e->head == jl_method_sym) { - simple_use_analysis(ctx, jl_exprarg(e, 0)); + general_use_analysis(ctx, jl_exprarg(e, 0), f); if (jl_expr_nargs(e) > 1) { - simple_use_analysis(ctx, jl_exprarg(e, 1)); - simple_use_analysis(ctx, jl_exprarg(e, 2)); + general_use_analysis(ctx, jl_exprarg(e, 1), f); + general_use_analysis(ctx, jl_exprarg(e, 2), f); } } else if (e->head == jl_assign_sym) { // don't consider assignment LHS as a variable "use" - simple_use_analysis(ctx, jl_exprarg(e, 1)); + general_use_analysis(ctx, jl_exprarg(e, 1), f); } else { size_t i, elen = jl_array_dim0(e->args); for (i = 0; i < elen; i++) { - simple_use_analysis(ctx, jl_exprarg(e, i)); + general_use_analysis(ctx, jl_exprarg(e, i), f); } } } else if (jl_is_returnnode(expr)) { jl_value_t *retexpr = jl_returnnode_value(expr); if (retexpr != NULL) - simple_use_analysis(ctx, retexpr); + general_use_analysis(ctx, retexpr, f); } else if (jl_is_gotoifnot(expr)) { - simple_use_analysis(ctx, jl_gotoifnot_cond(expr)); + general_use_analysis(ctx, jl_gotoifnot_cond(expr), f); } else if (jl_is_pinode(expr)) { - simple_use_analysis(ctx, jl_fieldref_noalloc(expr, 0)); + general_use_analysis(ctx, jl_fieldref_noalloc(expr, 0), f); } else if (jl_is_upsilonnode(expr)) { jl_value_t *val = jl_fieldref_noalloc(expr, 0); if (val) - simple_use_analysis(ctx, val); + general_use_analysis(ctx, val, f); } 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); for (i = 0; i < elen; i++) { jl_value_t *v = jl_array_ptr_ref(values, i); - simple_use_analysis(ctx, v); + general_use_analysis(ctx, v, f); } } else if (jl_is_phinode(expr)) { @@ -2352,60 +2761,104 @@ static void simple_use_analysis(jl_codectx_t &ctx, jl_value_t *expr) for (i = 0; i < elen; i++) { jl_value_t *v = jl_array_ptr_ref(values, i); if (v) - simple_use_analysis(ctx, v); + general_use_analysis(ctx, v, f); } } } +static void simple_use_analysis(jl_codectx_t &ctx, jl_value_t *expr) +{ + auto scan_slot_arg = [&](jl_value_t *expr) { + if (jl_is_slotnumber(expr) || jl_is_argument(expr)) { + int i = jl_slot_number(expr) - 1; + ctx.slots[i].used = true; + return true; + } + return false; + }; + return general_use_analysis(ctx, expr, scan_slot_arg); +} + // --- gc root utils --- // ---- Get Element Pointer (GEP) instructions within the GC frame ---- -static void jl_add_method_root(jl_codectx_t &ctx, jl_value_t *val) +static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val) { - if (jl_is_concrete_type(val) || jl_is_bool(val) || jl_is_symbol(val) || val == jl_nothing || - val == (jl_value_t*)jl_any_type || val == (jl_value_t*)jl_bottom_type || val == (jl_value_t*)jl_core_module) - return; - JL_GC_PUSH1(&val); - if (ctx.roots == NULL) { - ctx.roots = jl_alloc_vec_any(1); - jl_array_ptr_set(ctx.roots, 0, val); - } - else { - size_t rlen = jl_array_dim0(ctx.roots); - for (size_t i = 0; i < rlen; i++) { - if (jl_array_ptr_ref(ctx.roots,i) == val) { - JL_GC_POP(); - return; + if (jl_is_globally_rooted(val)) + return val; + jl_method_t *m = ctx.linfo->def.method; + if (jl_is_method(m)) { + // the method might have a root for this already; use it if so + JL_LOCK(&m->writelock); + if (m->roots) { + size_t i, len = jl_array_dim0(m->roots); + for (i = 0; i < len; i++) { + jl_value_t *mval = jl_array_ptr_ref(m->roots, i); + if (mval == val || jl_egal(mval, val)) { + JL_UNLOCK(&m->writelock); + return mval; + } } } - jl_array_ptr_1d_push(ctx.roots, val); + JL_UNLOCK(&m->writelock); } - JL_GC_POP(); + return jl_as_global_root(val); } // --- generating function calls --- -static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name) +static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name, AtomicOrdering order) { jl_binding_t *bnd = NULL; Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false); - if (bnd && bnd->value != NULL) { - if (bnd->constp) { - return mark_julia_const(ctx, bnd->value); + if (bp == NULL) + return jl_cgval_t(); + bp = julia_binding_pvalue(ctx, bp); + 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*))); + 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); } - LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - v->setOrdering(AtomicOrdering::Unordered); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); - return mark_julia_type(ctx, v, true, bnd->ty); } // todo: use type info to avoid undef check return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); } +static bool emit_globalset(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, const jl_cgval_t &rval_info, AtomicOrdering Order) +{ + 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); + 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; +} + static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1, Value *nullcheck2) { + ++EmittedBoxCompares; if (jl_pointer_egal(arg1.typ) || jl_pointer_egal(arg2.typ)) { // if we can be certain we won't try to load from the pointer (because // we know boxed is trivial), we can skip the separate null checks @@ -2421,8 +2874,8 @@ static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const } Value *neq = ctx.builder.CreateICmpNE(varg1, varg2); return emit_guarded_test(ctx, neq, true, [&] { - Value *dtarg = emit_typeof_boxed(ctx, arg1); - Value *dt_eq = ctx.builder.CreateICmpEQ(dtarg, emit_typeof_boxed(ctx, arg2)); + Value *dtarg = emit_typeof(ctx, arg1, false, true); + Value *dt_eq = ctx.builder.CreateICmpEQ(dtarg, emit_typeof(ctx, arg2, false, true)); return emit_guarded_test(ctx, dt_eq, false, [&] { return ctx.builder.CreateTrunc(ctx.builder.CreateCall(prepare_call(jlegalx_func), {varg1, varg2, dtarg}), getInt1Ty(ctx.builder.getContext())); @@ -2435,6 +2888,7 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a static Value *emit_bitsunion_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2) { + ++EmittedBitsUnionCompares; assert(jl_egal(arg1.typ, arg2.typ) && arg1.TIndex && arg2.TIndex && jl_is_uniontype(arg1.typ) && "unimplemented"); Value *tindex = arg1.TIndex; tindex = ctx.builder.CreateAnd(tindex, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0x7f)); @@ -2477,6 +2931,7 @@ static Value *emit_bitsunion_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t arg2) { + ++EmittedBitsCompares; bool isboxed; Type *at = julia_type_to_llvm(ctx, arg1.typ, &isboxed); assert(jl_is_datatype(arg1.typ) && arg1.typ == arg2.typ && !isboxed); @@ -2485,7 +2940,7 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1); if (at->isIntegerTy() || at->isPointerTy() || at->isFloatingPointTy()) { - Type *at_int = INTT(at); + Type *at_int = INTT(at, ctx.emission_context.DL); Value *varg1 = emit_unbox(ctx, at_int, arg1, arg1.typ); Value *varg2 = emit_unbox(ctx, at_int, arg2, arg2.typ); return ctx.builder.CreateICmpEQ(varg1, varg2); @@ -2513,9 +2968,9 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a jl_datatype_t *sty = (jl_datatype_t*)arg1.typ; size_t sz = jl_datatype_size(sty); if (sz > 512 && !sty->layout->haspadding) { - Value *varg1 = arg1.ispointer() ? maybe_decay_tracked(ctx, data_pointer(ctx, arg1)) : + Value *varg1 = arg1.ispointer() ? data_pointer(ctx, arg1) : value_to_pointer(ctx, arg1).V; - Value *varg2 = arg2.ispointer() ? maybe_decay_tracked(ctx, data_pointer(ctx, arg2)) : + 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); @@ -2529,20 +2984,24 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a 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(getSizeTy(ctx.builder.getContext()), sz) }, + ConstantInt::get(ctx.types().T_size, sz) }, ArrayRef(&OpBundle, nroots ? 1 : 0)); - MDNode *tbaa = nullptr; - if (!arg1.tbaa) { - tbaa = arg2.tbaa; - } - else if (!arg2.tbaa) { - tbaa = arg1.tbaa; - } - else { - tbaa = MDNode::getMostGenericTBAA(arg1.tbaa, arg2.tbaa); + + if (arg1.tbaa || arg2.tbaa) { + jl_aliasinfo_t ai; + if (!arg1.tbaa) { + ai = jl_aliasinfo_t::fromTBAA(ctx, arg2.tbaa); + } + else if (!arg2.tbaa) { + ai = jl_aliasinfo_t::fromTBAA(ctx, arg1.tbaa); + } + else { + jl_aliasinfo_t arg1_ai = jl_aliasinfo_t::fromTBAA(ctx, arg1.tbaa); + jl_aliasinfo_t arg2_ai = jl_aliasinfo_t::fromTBAA(ctx, arg2.tbaa); + ai = arg1_ai.merge(arg2_ai); + } + ai.decorateInst(answer); } - if (tbaa) - tbaa_decorate(tbaa, answer); return ctx.builder.CreateICmpEQ(answer, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); } else { @@ -2583,6 +3042,7 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2, Value *nullcheck1, Value *nullcheck2) { + ++EmittedEgals; // handle simple static expressions with no side-effects if (arg1.constant && arg2.constant) return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), jl_egal(arg1.constant, arg2.constant)); @@ -2600,11 +3060,11 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva // since it is normalized to `::Type{Union{}}` instead... if (arg1.TIndex) return emit_nullcheck_guard(ctx, nullcheck1, [&] { - return emit_exactly_isa(ctx, arg1, rt2); // rt2 is a singleton type + return emit_exactly_isa(ctx, arg1, (jl_datatype_t*)rt2); // rt2 is a singleton type }); if (arg2.TIndex) return emit_nullcheck_guard(ctx, nullcheck2, [&] { - return emit_exactly_isa(ctx, arg2, rt1); // rt1 is a singleton type + return emit_exactly_isa(ctx, arg2, (jl_datatype_t*)rt1); // rt1 is a singleton type }); if (!(arg1.isboxed || arg1.constant) || !(arg2.isboxed || arg2.constant)) // not TIndex && not boxed implies it is an unboxed value of a different type from this singleton @@ -2627,8 +3087,8 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva bool justbits2 = jl_is_concrete_immutable(rt2); if (justbits1 || justbits2) { // whether this type is unique'd by value return emit_nullcheck_guard2(ctx, nullcheck1, nullcheck2, [&] () -> Value* { - jl_value_t *typ = justbits1 ? rt1 : rt2; - if (typ == (jl_value_t*)jl_bool_type) { // aka jl_pointer_egal + jl_datatype_t *typ = (jl_datatype_t*)(justbits1 ? rt1 : rt2); + if (typ == jl_bool_type) { // aka jl_pointer_egal // some optimizations for bool, since pointer comparison may be better if ((arg1.isboxed || arg1.constant) && (arg2.isboxed || arg2.constant)) { // aka have-fast-pointer Value *varg1 = arg1.constant ? literal_pointer_val(ctx, arg1.constant) : maybe_bitcast(ctx, arg1.Vboxed, ctx.types().T_pjlvalue); @@ -2638,14 +3098,14 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva } if (rt1 == rt2) return emit_bits_compare(ctx, arg1, arg2); - Value *same_type = emit_exactly_isa(ctx, (typ == rt2 ? arg1 : arg2), typ); + Value *same_type = emit_exactly_isa(ctx, (justbits1 ? arg2 : arg1), typ); BasicBlock *currBB = ctx.builder.GetInsertBlock(); BasicBlock *isaBB = BasicBlock::Create(ctx.builder.getContext(), "is", ctx.f); BasicBlock *postBB = BasicBlock::Create(ctx.builder.getContext(), "post_is", ctx.f); ctx.builder.CreateCondBr(same_type, isaBB, postBB); ctx.builder.SetInsertPoint(isaBB); - Value *bitcmp = emit_bits_compare(ctx, jl_cgval_t(arg1, typ, NULL), - jl_cgval_t(arg2, typ, NULL)); + Value *bitcmp = emit_bits_compare(ctx, jl_cgval_t(arg1, (jl_value_t*)typ, NULL), + jl_cgval_t(arg2, (jl_value_t*)typ, NULL)); isaBB = ctx.builder.GetInsertBlock(); // might have changed ctx.builder.CreateBr(postBB); ctx.builder.SetInsertPoint(postBB); @@ -2667,14 +3127,54 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva return emit_box_compare(ctx, arg1, arg2, nullcheck1, nullcheck2); } +static bool emit_f_opglobal(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, + const jl_cgval_t *argv, size_t nargs, const jl_cgval_t *modifyop) +{ + const jl_cgval_t &mod = argv[1]; + const jl_cgval_t &sym = argv[2]; + const jl_cgval_t &val = argv[3]; + enum jl_memory_order order = jl_memory_order_unspecified; + 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 + return false; + } + 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"); + *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 + return true; + } + } + + return false; +} + static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, const jl_cgval_t *argv, size_t nargs, const jl_cgval_t *modifyop) { + ++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; - const jl_cgval_t undefval(ctx.builder.getContext()); + 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]; @@ -2698,33 +3198,35 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } 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(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } jl_datatype_t *uty = (jl_datatype_t*)jl_unwrap_unionall(obj.typ); if (jl_is_datatype(uty) && jl_struct_try_layout(uty)) { ssize_t idx = -1; - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + if (fld.constant && jl_is_symbol(fld.constant)) { idx = jl_field_index(uty, (jl_sym_t*)fld.constant, 0); } else if (fld.constant && fld.typ == (jl_value_t*)jl_long_type) { ssize_t i = jl_unbox_long(fld.constant); - if (i > 0 && i <= jl_datatype_nfields(uty)) + if (i > 0 && i <= (ssize_t)jl_datatype_nfields(uty)) idx = i - 1; } if (idx != -1) { jl_value_t *ft = jl_field_type(uty, idx); if (!jl_has_free_typevars(ft)) { - if (!ismodifyfield && !jl_subtype(val.typ, ft)) { + if (!ismodifyfield) { emit_typecheck(ctx, val, ft, fname); val = update_julia_type(ctx, val, ft); + if (val.typ == jl_bottom_type) + return true; } // TODO: attempt better codegen for approximate types bool isboxed = jl_field_isptr(uty, idx); bool isatomic = jl_field_isatomic(uty, idx); bool needlock = isatomic && !isboxed && jl_datatype_size(jl_field_type(uty, idx)) > MAX_ATOMIC_SIZE; - *ret = jl_cgval_t(ctx.builder.getContext()); + *ret = jl_cgval_t(); if (isatomic == (order == jl_memory_order_notatomic)) { emit_atomic_error(ctx, issetfield ? @@ -2761,11 +3263,11 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else { *ret = emit_setfield(ctx, uty, obj, idx, val, cmp, true, (needlock || order <= jl_memory_order_notatomic) - ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - : get_llvm_atomic_order(order), + ? AtomicOrdering::NotAtomic + : get_llvm_atomic_order(order), (needlock || fail_order <= jl_memory_order_notatomic) - ? (isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic) // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - : get_llvm_atomic_order(fail_order), + ? AtomicOrdering::NotAtomic + : get_llvm_atomic_order(fail_order), needlock, issetfield, isreplacefield, isswapfield, ismodifyfield, modifyop, fname); } @@ -2776,19 +3278,20 @@ static bool emit_f_opfield(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return false; } -static std::pair, jl_llvm_functions_t> +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, - LLVMContext &ctxt); + jl_codegen_params_t ¶ms); 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, - jl_expr_t *ex) + jl_expr_t *ex, bool is_promotable) // returns true if the call has been handled { + ++EmittedBuiltinCalls; if (f == jl_builtin_is && nargs == 2) { // emit comparison test Value *ans = emit_f_is(ctx, argv[1], argv[2]); @@ -2797,7 +3300,13 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else if (f == jl_builtin_typeof && nargs == 1) { - *ret = emit_typeof(ctx, argv[1]); + const jl_cgval_t &p = argv[1]; + if (p.constant) + *ret = mark_julia_const(ctx, jl_typeof(p.constant)); + else if (jl_is_concrete_type(p.typ)) + *ret = mark_julia_const(ctx, p.typ); + else + *ret = mark_julia_type(ctx, emit_typeof(ctx, p, false, false), true, jl_datatype_type); return true; } @@ -2807,7 +3316,7 @@ 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); emit_typecheck(ctx, arg, tp0, "typeassert"); - *ret = arg; + *ret = update_julia_type(ctx, arg, tp0); return true; } if (jl_subtype(ty.typ, (jl_value_t*)jl_type_type)) { @@ -2852,7 +3361,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, #ifdef _P64 nva = ctx.builder.CreateTrunc(nva, getInt32Ty(ctx.builder.getContext())); #endif - Value *theArgs = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, ConstantInt::get(getSizeTy(ctx.builder.getContext()), ctx.nReqArgs)); + Value *theArgs = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, ConstantInt::get(ctx.types().T_size, ctx.nReqArgs)); Value *r = ctx.builder.CreateCall(prepare_call(jlapplygeneric_func), { theF, theArgs, nva }); *ret = mark_julia_type(ctx, r, true, jl_any_type); return true; @@ -2866,7 +3375,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]); + *ret = emit_new_struct(ctx, rt, nargs, &argv[1], is_promotable); return true; } } @@ -2874,7 +3383,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else if (f == jl_builtin_throw && nargs == 1) { Value *arg1 = boxed(ctx, argv[1]); raise_exception(ctx, arg1); - *ret = jl_cgval_t(ctx.builder.getContext()); + *ret = jl_cgval_t(); return true; } @@ -2894,22 +3403,22 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, return true; } else if (idx_const > ndims) { - *ret = mark_julia_type(ctx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1), false, jl_long_type); + *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, getSizeTy(ctx.builder.getContext()), idx, (jl_value_t*)jl_long_type); - error_unless(ctx, ctx.builder.CreateICmpSGT(idx_dyn, Constant::getNullValue(getSizeTy(ctx.builder.getContext()))), + Value *idx_dyn = emit_unbox(ctx, ctx.types().T_size, idx, (jl_value_t*)jl_long_type); + error_unless(ctx, ctx.builder.CreateICmpSGT(idx_dyn, Constant::getNullValue(ctx.types().T_size)), "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"); ctx.builder.CreateCondBr(ctx.builder.CreateICmpSLE(idx_dyn, - ConstantInt::get(getSizeTy(ctx.builder.getContext()), ndims)), + ConstantInt::get(ctx.types().T_size, ndims)), inBB, outBB); ctx.builder.SetInsertPoint(outBB); - Value *v_one = ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1); + Value *v_one = ConstantInt::get(ctx.types().T_size, 1); ctx.builder.CreateBr(ansBB); ctx.f->getBasicBlockList().push_back(inBB); ctx.builder.SetInsertPoint(inBB); @@ -2918,7 +3427,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, inBB = ctx.builder.GetInsertBlock(); // could have changed ctx.f->getBasicBlockList().push_back(ansBB); ctx.builder.SetInsertPoint(ansBB); - PHINode *result = ctx.builder.CreatePHI(getSizeTy(ctx.builder.getContext()), 2); + PHINode *result = ctx.builder.CreatePHI(ctx.types().T_size, 2); result->addIncoming(v_one, outBB); result->addIncoming(v_sz, inBB); *ret = mark_julia_type(ctx, result, false, jl_long_type); @@ -2969,7 +3478,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // 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)); - Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, getSizeTy(ctx.builder.getContext()))); + Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, ctx.types().T_size)); Value *selidx_m = emit_arraylen(ctx, ary); Value *selidx = ctx.builder.CreateSelect(is_vector, selidx_v, selidx_m); ptindex = ctx.builder.CreateInBoundsGEP(AT, data, selidx); @@ -2981,7 +3490,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, *ret = emit_unionload(ctx, data, ptindex, ety, elsz, al, ctx.tbaa().tbaa_arraybuf, true, union_max, ctx.tbaa().tbaa_arrayselbyte); } else { - MDNode *aliasscope = (f == jl_builtin_const_arrayref) ? ctx.aliasscope : nullptr; + MDNode *aliasscope = (f == jl_builtin_const_arrayref) ? ctx.noalias().aliasscope.current : nullptr; *ret = typed_load(ctx, emit_arrayptr(ctx, ary, ary_ex), idx, ety, @@ -3010,10 +3519,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, 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)) { - if (!jl_subtype(val.typ, ety)) { - emit_typecheck(ctx, val, ety, "arrayset"); - val = update_julia_type(ctx, val, ety); - } + 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); @@ -3048,7 +3557,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_bitcast(ctx, decay_derived(ctx, aryv), ctx.types().T_pprjlvalue), jl_array_data_owner_offset(nd) / sizeof(jl_value_t*)), Align(sizeof(void*))); - tbaa_decorate(ctx.tbaa().tbaa_const, maybe_mark_load_dereferenceable(own_ptr, false, (jl_value_t*)jl_array_any_type)); + 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( @@ -3076,7 +3586,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, 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)); - Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, getSizeTy(ctx.builder.getContext()))); + Value *selidx_v = ctx.builder.CreateSub(emit_vectormaxsize(ctx, ary), ctx.builder.CreateZExt(offset, ctx.types().T_size)); Value *selidx_m = emit_arraylen(ctx, ary); Value *selidx = ctx.builder.CreateSelect(is_vector, selidx_v, selidx_m); ptindex = ctx.builder.CreateInBoundsGEP(AT, data, selidx); @@ -3085,7 +3595,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, 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); - tbaa_decorate(ctx.tbaa().tbaa_arrayselbyte, ctx.builder.CreateStore(tindex, 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); @@ -3094,13 +3605,13 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else { typed_store(ctx, emit_arrayptr(ctx, ary, ary_ex, isboxed), - idx, val, jl_cgval_t(ctx.builder.getContext()), ety, + idx, val, jl_cgval_t(), ety, isboxed ? ctx.tbaa().tbaa_ptrarraybuf : ctx.tbaa().tbaa_arraybuf, - ctx.aliasscope, + ctx.noalias().aliasscope.current, data_owner, isboxed, - isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic, // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 - isboxed ? AtomicOrdering::Unordered : AtomicOrdering::NotAtomic, // TODO: we should do this for anything with CountTrackedPointers(elty).count > 0 + 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, @@ -3137,7 +3648,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else if (nargs == 3) { const jl_cgval_t &arg3 = argv[3]; - if (arg3.typ == (jl_value_t*)jl_symbol_type && arg3.constant) + if (arg3.constant && jl_is_symbol(arg3.constant)) order = jl_get_atomic_order((jl_sym_t*)arg3.constant, true, false); else if (arg3.constant == jl_false) boundscheck = jl_false; @@ -3146,7 +3657,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } if (order == jl_memory_order_invalid) { emit_atomic_error(ctx, "invalid atomic ordering"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } @@ -3154,10 +3665,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (jl_is_type_type((jl_value_t*)utt) && jl_is_concrete_type(jl_tparam0(utt))) utt = (jl_datatype_t*)jl_typeof(jl_tparam0(utt)); - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + if (fld.constant && jl_is_symbol(fld.constant)) { jl_sym_t *name = (jl_sym_t*)fld.constant; if (obj.constant && jl_is_module(obj.constant)) { - *ret = emit_globalref(ctx, (jl_module_t*)obj.constant, name); + *ret = emit_globalref(ctx, (jl_module_t*)obj.constant, name, order == jl_memory_order_unspecified ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); return true; } @@ -3175,15 +3686,16 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (LoadInst *load = dyn_cast_or_null(obj.V)) { if (load->getPointerOperand() == ctx.slots[ctx.vaSlot].boxroot && ctx.argArray) { Value *valen = emit_n_varargs(ctx); - jl_cgval_t va_ary( // fake instantiation of a cgval, in order to call emit_bounds_check - ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, ConstantInt::get(getSizeTy(ctx.builder.getContext()), ctx.nReqArgs)), - NULL, false, NULL, NULL, ctx.tbaa()); - Value *idx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), fld, (jl_value_t*)jl_long_type); + jl_cgval_t va_ary( // fake instantiation of a cgval, in order to call emit_bounds_check (it only checks the `.V` field) + ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, ConstantInt::get(ctx.types().T_size, ctx.nReqArgs)), + NULL, NULL); + Value *idx = emit_unbox(ctx, ctx.types().T_size, fld, (jl_value_t*)jl_long_type); idx = emit_bounds_check(ctx, va_ary, NULL, idx, valen, boundscheck); - idx = ctx.builder.CreateAdd(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), ctx.nReqArgs)); + idx = ctx.builder.CreateAdd(idx, ConstantInt::get(ctx.types().T_size, ctx.nReqArgs)); Instruction *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, ctx.argArray, idx), Align(sizeof(void*))); // if we know the result type of this load, we will mark that information here too - tbaa_decorate(ctx.tbaa().tbaa_value, maybe_mark_load_dereferenceable(v, false, rt)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_value); + ai.decorateInst(maybe_mark_load_dereferenceable(v, false, rt)); *ret = mark_julia_type(ctx, v, /*boxed*/ true, rt); return true; } @@ -3204,12 +3716,13 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } else { // unknown index - Value *vidx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), fld, (jl_value_t*)jl_long_type); + Value *vidx = emit_unbox(ctx, ctx.types().T_size, fld, (jl_value_t*)jl_long_type); if (emit_getfield_unknownidx(ctx, ret, obj, vidx, utt, boundscheck, order)) { return true; } } } + Value *vidx = emit_unbox(ctx, ctx.types().T_size, fld, (jl_value_t*)jl_long_type); if (jl_is_tuple_type(utt) && is_tupletype_homogeneous(utt->parameters, true)) { // For tuples, we can emit code even if we don't know the exact // type (e.g. because we don't know the length). This is possible @@ -3217,38 +3730,80 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (obj.ispointer()) { if (order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { emit_atomic_error(ctx, "getfield: non-atomic field cannot be accessed atomically"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } - // Determine which was the type that was homogenous + // Determine which was the type that was homogeneous jl_value_t *jt = jl_tparam0(utt); if (jl_is_vararg(jt)) jt = jl_unwrap_vararg(jt); assert(jl_is_datatype(jt)); - Value *vidx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), fld, (jl_value_t*)jl_long_type); // This is not necessary for correctness, but allows to omit // the extra code for getting the length of the tuple if (!bounds_check_enabled(ctx, boundscheck)) { - vidx = ctx.builder.CreateSub(vidx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)); - } else { + vidx = ctx.builder.CreateSub(vidx, ConstantInt::get(ctx.types().T_size, 1)); + } + else { vidx = emit_bounds_check(ctx, obj, (jl_value_t*)obj.typ, vidx, - emit_datatype_nfields(ctx, emit_typeof_boxed(ctx, obj)), + emit_datatype_nfields(ctx, emit_typeof(ctx, obj, false, false)), jl_true); } bool isboxed = !jl_datatype_isinlinealloc((jl_datatype_t*)jt, 0); - Value *ptr = maybe_decay_tracked(ctx, data_pointer(ctx, obj)); + Value *ptr = data_pointer(ctx, obj); *ret = typed_load(ctx, ptr, vidx, isboxed ? (jl_value_t*)jl_any_type : jt, obj.tbaa, nullptr, isboxed, AtomicOrdering::NotAtomic, false); return true; } } + + // Unknown object, but field known to be integer + vidx = ctx.builder.CreateSub(vidx, ConstantInt::get(ctx.types().T_size, 1)); + Value *fld_val = ctx.builder.CreateCall(prepare_call(jlgetnthfieldchecked_func), { boxed(ctx, obj), vidx }); + *ret = mark_julia_type(ctx, fld_val, true, jl_any_type); + return true; } } // TODO: generic getfield func with more efficient calling convention return false; } + else if (f == jl_builtin_getglobal && (nargs == 2 || nargs == 3)) { + const jl_cgval_t &mod = argv[1]; + const jl_cgval_t &sym = argv[2]; + enum jl_memory_order order = jl_memory_order_unspecified; + + if (nargs == 3) { + const jl_cgval_t &arg3 = argv[3]; + if (arg3.constant && jl_is_symbol(arg3.constant)) + order = jl_get_atomic_order((jl_sym_t*)arg3.constant, true, false); + else + return false; + } + else + order = jl_memory_order_monotonic; + + if (order == jl_memory_order_invalid || order == jl_memory_order_notatomic) { + emit_atomic_error(ctx, order == jl_memory_order_invalid ? "invalid atomic ordering" : "getglobal: module binding cannot be read 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)) { + *ret = emit_globalref(ctx, (jl_module_t*)mod.constant, name, get_llvm_atomic_order(order)); + return true; + } + } + + return false; + } + + else if (f == jl_builtin_setglobal && (nargs == 3 || nargs == 4)) { + return emit_f_opglobal(ctx, ret, f, argv, nargs, nullptr); + } + else if ((f == jl_builtin_setfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_swapfield && (nargs == 3 || nargs == 4)) || (f == jl_builtin_replacefield && (nargs == 4 || nargs == 5 || nargs == 6)) || @@ -3281,9 +3836,9 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } Value *sz; if (nf != -1) - sz = ConstantInt::get(getSizeTy(ctx.builder.getContext()), nf); + sz = ConstantInt::get(ctx.types().T_size, nf); else - sz = emit_datatype_nfields(ctx, emit_typeof_boxed(ctx, obj)); + sz = emit_datatype_nfields(ctx, emit_typeof(ctx, obj, false, false)); *ret = mark_julia_type(ctx, sz, false, jl_long_type); return true; } @@ -3298,13 +3853,14 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *tyv = boxed(ctx, typ); Value *types_svec = emit_datatype_types(ctx, tyv); Value *types_len = emit_datatype_nfields(ctx, tyv); - Value *idx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), fld, (jl_value_t*)jl_long_type); + Value *idx = emit_unbox(ctx, ctx.types().T_size, fld, (jl_value_t*)jl_long_type); jl_value_t *boundscheck = (nargs == 3 ? argv[3].constant : jl_true); if (nargs == 3) emit_typecheck(ctx, argv[3], (jl_value_t*)jl_bool_type, "fieldtype"); emit_bounds_check(ctx, typ, (jl_value_t*)jl_datatype_type, idx, types_len, boundscheck); Value *fieldtyp_p = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, decay_derived(ctx, emit_bitcast(ctx, types_svec, ctx.types().T_pprjlvalue)), idx); - Value *fieldtyp = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fieldtyp_p, Align(sizeof(void*)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *fieldtyp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, fieldtyp_p, Align(sizeof(void*)))); *ret = mark_julia_type(ctx, fieldtyp, true, (jl_value_t*)jl_type_type); return true; } @@ -3324,22 +3880,23 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, else { sz = (1 + jl_svec_len(obj.constant)) * sizeof(void*); } - *ret = mark_julia_type(ctx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), sz), false, jl_long_type); + *ret = mark_julia_type(ctx, ConstantInt::get(ctx.types().T_size, sz), false, jl_long_type); return true; } // String and SimpleVector's length fields have the same layout - auto ptr = emit_bitcast(ctx, boxed(ctx, obj), getSizePtrTy(ctx.builder.getContext())); - Value *len = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ptr, Align(sizeof(size_t)))); + auto ptr = emit_bitcast(ctx, boxed(ctx, obj), ctx.types().T_size->getPointerTo()); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *len = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_size, ptr, ctx.types().alignof_ptr)); MDBuilder MDB(ctx.builder.getContext()); if (sty == jl_simplevector_type) { auto rng = MDB.createRange( - Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), INTPTR_MAX / sizeof(void*) - 1)); + Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, INTPTR_MAX / sizeof(void*) - 1)); cast(len)->setMetadata(LLVMContext::MD_range, rng); - len = ctx.builder.CreateMul(len, ConstantInt::get(getSizeTy(ctx.builder.getContext()), sizeof(void*))); - len = ctx.builder.CreateAdd(len, ConstantInt::get(getSizeTy(ctx.builder.getContext()), sizeof(void*))); + len = ctx.builder.CreateMul(len, ConstantInt::get(ctx.types().T_size, sizeof(void*))); + len = ctx.builder.CreateAdd(len, ConstantInt::get(ctx.types().T_size, sizeof(void*))); } else { - auto rng = MDB.createRange(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), ConstantInt::get(getSizeTy(ctx.builder.getContext()), INTPTR_MAX)); + auto rng = MDB.createRange(Constant::getNullValue(ctx.types().T_size), ConstantInt::get(ctx.types().T_size, INTPTR_MAX)); cast(len)->setMetadata(LLVMContext::MD_range, rng); } *ret = mark_julia_type(ctx, len, false, jl_long_type); @@ -3350,10 +3907,10 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, Value *elsize; size_t elsz; if (arraytype_constelsize(sty, &elsz)) { - elsize = ConstantInt::get(getSizeTy(ctx.builder.getContext()), elsz); + elsize = ConstantInt::get(ctx.types().T_size, elsz); } else { - elsize = ctx.builder.CreateZExt(emit_arrayelsize(ctx, obj), getSizeTy(ctx.builder.getContext())); + elsize = ctx.builder.CreateZExt(emit_arrayelsize(ctx, obj), ctx.types().T_size); } *ret = mark_julia_type(ctx, ctx.builder.CreateMul(len, elsize), false, jl_long_type); return true; @@ -3365,7 +3922,7 @@ 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_add_method_root(ctx, ty); + ty = jl_ensure_rooted(ctx, ty); *ret = mark_julia_const(ctx, ty); return true; } @@ -3376,6 +3933,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, const jl_cgval_t &obj = argv[1]; const jl_cgval_t &fld = argv[2]; jl_datatype_t *stt = (jl_datatype_t*)obj.typ; + ssize_t fieldidx = -1; if (jl_is_type_type((jl_value_t*)stt)) { // the representation type of Type{T} is either typeof(T), or unknown // TODO: could use `issingletontype` predicate here, providing better type knowledge @@ -3387,12 +3945,11 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } if (!jl_is_concrete_type((jl_value_t*)stt) || jl_is_array_type(stt) || stt == jl_module_type) { // TODO: use ->layout here instead of concrete_type - return false; + goto isdefined_unknown_idx; } assert(jl_is_datatype(stt)); - ssize_t fieldidx = -1; - if (fld.constant && fld.typ == (jl_value_t*)jl_symbol_type) { + if (fld.constant && jl_is_symbol(fld.constant)) { jl_sym_t *sym = (jl_sym_t*)fld.constant; fieldidx = jl_field_index(stt, sym, 0); } @@ -3400,7 +3957,15 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, fieldidx = jl_unbox_long(fld.constant) - 1; } else { - return false; +isdefined_unknown_idx: + if (nargs == 3 || fld.typ != (jl_value_t*)jl_long_type) + return false; + Value *vidx = emit_unbox(ctx, ctx.types().T_size, fld, (jl_value_t*)jl_long_type); + vidx = ctx.builder.CreateSub(vidx, ConstantInt::get(ctx.types().T_size, 1)); + Value *isd = ctx.builder.CreateCall(prepare_call(jlfieldisdefinedchecked_func), { boxed(ctx, obj), vidx }); + isd = ctx.builder.CreateTrunc(isd, getInt8Ty(ctx.builder.getContext())); + *ret = mark_julia_type(ctx, isd, false, jl_bool_type); + return true; } enum jl_memory_order order = jl_memory_order_unspecified; if (nargs == 3) { @@ -3412,14 +3977,14 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } if (order == jl_memory_order_invalid) { emit_atomic_error(ctx, "invalid atomic ordering"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } ssize_t nf = jl_datatype_nfields(stt); if (fieldidx < 0 || fieldidx >= nf) { if (order != jl_memory_order_unspecified) { emit_atomic_error(ctx, "isdefined: atomic ordering cannot be specified for nonexistent field"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } *ret = mark_julia_const(ctx, jl_false); @@ -3428,12 +3993,12 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, bool isatomic = jl_field_isatomic(stt, fieldidx); if (!isatomic && order != jl_memory_order_notatomic && order != jl_memory_order_unspecified) { emit_atomic_error(ctx, "isdefined: non-atomic field cannot be accessed atomically"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } if (isatomic && order == jl_memory_order_notatomic) { emit_atomic_error(ctx, "isdefined: atomic field cannot be accessed non-atomically"); - *ret = jl_cgval_t(ctx.builder.getContext()); // unreachable + *ret = jl_cgval_t(); // unreachable return true; } else if (fieldidx < nf - stt->name->n_uninitialized) { @@ -3448,11 +4013,12 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (obj.ispointer()) { if (!jl_field_isptr(stt, fieldidx)) offs += ((jl_datatype_t*)jl_field_type(stt, fieldidx))->layout->first_ptr; - Value *ptr = emit_bitcast(ctx, maybe_decay_tracked(ctx, data_pointer(ctx, obj)), ctx.types().T_pprjlvalue); + Value *ptr = emit_bitcast(ctx, data_pointer(ctx, obj), ctx.types().T_pprjlvalue); Value *addr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, ptr, offs); // emit this using the same type as emit_getfield_knownidx // so that LLVM may be able to load-load forward them and fold the result - fldv = tbaa_decorate(tbaa, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, addr, Align(sizeof(size_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + fldv = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, addr, ctx.types().alignof_ptr)); cast(fldv)->setOrdering(order <= jl_memory_order_notatomic ? AtomicOrdering::Unordered : get_llvm_atomic_order(order)); } else { @@ -3479,8 +4045,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // For now we emit this as a vararg call to the builtin // (which doesn't look at the arguments). In the future, // this should be an LLVM builtin. - auto it = builtin_func_map.find(jl_f_donotdelete_addr); - if (it == builtin_func_map.end()) { + auto it = builtin_func_map().find(jl_f_donotdelete_addr); + if (it == builtin_func_map().end()) { return false; } @@ -3509,58 +4075,55 @@ 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, Function *theFptr, Value *theF, - const jl_cgval_t *argv, size_t nargs, CallingConv::ID cc) +static CallInst *emit_jlcall(jl_codectx_t &ctx, FunctionCallee theFptr, Value *theF, + const jl_cgval_t *argv, size_t nargs, JuliaFunction<> *trampoline) { + ++EmittedJLCalls; + Function *TheTrampoline = prepare_call(trampoline); // emit arguments - SmallVector theArgs; - SmallVector argsT; - if (theF) { + SmallVector theArgs; + theArgs.push_back(theFptr.getCallee()); + if (theF) theArgs.push_back(theF); - argsT.push_back(ctx.types().T_prjlvalue); - } for (size_t i = 0; i < nargs; i++) { Value *arg = boxed(ctx, argv[i]); theArgs.push_back(arg); - argsT.push_back(ctx.types().T_prjlvalue); - } - FunctionType *FTy = FunctionType::get(ctx.types().T_prjlvalue, argsT, false); - CallInst *result = ctx.builder.CreateCall(FTy, - ctx.builder.CreateBitCast(theFptr, FTy->getPointerTo()), - theArgs); - addRetAttr(result, Attribute::NonNull); - result->setCallingConv(cc); + } + CallInst *result = ctx.builder.CreateCall(TheTrampoline, theArgs); + result->setAttributes(TheTrampoline->getAttributes()); + // TODO: we could add readonly attributes in many cases to the args return result; } + // 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, CallingConv::ID cc) +static CallInst *emit_jlcall(jl_codectx_t &ctx, JuliaFunction<> *theFptr, Value *theF, + const jl_cgval_t *argv, size_t nargs, JuliaFunction<> *trampoline) { - return emit_jlcall(ctx, prepare_call(theFptr), theF, argv, nargs, cc); + return emit_jlcall(ctx, prepare_call(theFptr), theF, argv, nargs, trampoline); } - -static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_t *mi, jl_value_t *jlretty, StringRef specFunctionObject, +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) { + ++EmittedSpecfunCalls; // emit specialized call site - bool is_opaque_closure = jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure; - jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, specFunctionObject, mi->specTypes, jlretty, is_opaque_closure); - FunctionType *cft = returninfo.decl->getFunctionType(); + jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, callee, specFunctionObject, specTypes, jlretty, is_opaque_closure); + FunctionType *cft = returninfo.decl.getFunctionType(); *cc = returninfo.cc; *return_roots = returninfo.return_roots; size_t nfargs = cft->getNumParams(); - Value **argvals = (Value**)alloca(nfargs * sizeof(Value*)); + SmallVector argvals(nfargs); unsigned idx = 0; - AllocaInst *result; + AllocaInst *result = nullptr; switch (returninfo.cc) { case jl_returninfo_t::Boxed: case jl_returninfo_t::Register: case jl_returninfo_t::Ghosts: break; case jl_returninfo_t::SRet: - result = emit_static_alloca(ctx, cft->getParamType(0)->getPointerElementType()); + result = emit_static_alloca(ctx, getAttributeAtIndex(returninfo.attrs, 1, Attribute::StructRet).getValueAsType()); + assert(cast(result->getType())->hasSameElementTypeAs(cast(cft->getParamType(0)))); argvals[idx] = result; idx++; break; @@ -3580,45 +4143,70 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_ } for (size_t i = 0; i < nargs; i++) { - jl_value_t *jt = (is_opaque_closure && i == 0) ? (jl_value_t*)jl_any_type : - jl_nth_slot_type(mi->specTypes, i); - if (is_uniquerep_Type(jt)) - continue; - bool isboxed = deserves_argbox(jt); - Type *et = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jt); - if (type_is_ghost(et)) - continue; - assert(idx < nfargs); - Type *at = cft->getParamType(idx); + jl_value_t *jt = jl_nth_slot_type(specTypes, i); + // n.b.: specTypes is required to be a datatype by construction for specsig jl_cgval_t arg = argv[i]; - if (isboxed) { - assert(at == ctx.types().T_prjlvalue && et == ctx.types().T_prjlvalue); - argvals[idx] = boxed(ctx, arg); - } - else if (et->isAggregateType()) { + if (is_opaque_closure && i == 0) { + Type *at = cft->getParamType(idx); + // Special optimization for opaque closures: We know that specsig opaque + // closures don't look at their type tag (they are fairly quickly discarded + // for their environments). Therefore, we can just pass these as a pointer, + // rather than a boxed value. arg = value_to_pointer(ctx, arg); - // can lazy load on demand, no copy needed - assert(at == PointerType::get(et, AddressSpace::Derived)); - argvals[idx] = decay_derived(ctx, maybe_bitcast(ctx, - data_pointer(ctx, arg), at)); + argvals[idx] = decay_derived(ctx, maybe_bitcast(ctx, data_pointer(ctx, arg), at)); } - else { - assert(at == et); - Value *val = emit_unbox(ctx, et, arg, jt); - if (!val) { - // There was a type mismatch of some sort - exit early - CreateTrap(ctx.builder); - return jl_cgval_t(ctx.builder.getContext()); + else if (is_uniquerep_Type(jt)) { + continue; + } else { + bool isboxed = deserves_argbox(jt); + Type *et = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jt); + if (type_is_ghost(et)) + continue; + assert(idx < nfargs); + Type *at = cft->getParamType(idx); + if (isboxed) { + assert(at == ctx.types().T_prjlvalue && et == ctx.types().T_prjlvalue); + argvals[idx] = boxed(ctx, arg); + } + else if (et->isAggregateType()) { + arg = value_to_pointer(ctx, arg); + // can lazy load on demand, no copy needed + assert(at == PointerType::get(et, AddressSpace::Derived)); + argvals[idx] = decay_derived(ctx, maybe_bitcast(ctx, data_pointer(ctx, arg), at)); + } + else { + assert(at == et); + Value *val = emit_unbox(ctx, et, arg, jt); + if (!val) { + // There was a type mismatch of some sort - exit early + CreateTrap(ctx.builder); + return jl_cgval_t(); + } + argvals[idx] = val; } - argvals[idx] = val; } idx++; } assert(idx == nfargs); - CallInst *call = ctx.builder.CreateCall(returninfo.decl, ArrayRef(&argvals[0], nfargs)); - call->setAttributes(returninfo.decl->getAttributes()); - - jl_cgval_t retval(ctx.builder.getContext()); + Value *TheCallee = returninfo.decl.getCallee(); + if (fromexternal) { + std::string namep("p"); + namep += cast(returninfo.decl.getCallee())->getName(); + GlobalVariable *GV = cast_or_null(jl_Module->getNamedValue(namep)); + if (GV == nullptr) { + GV = new GlobalVariable(*jl_Module, TheCallee->getType(), false, + GlobalVariable::ExternalLinkage, + Constant::getNullValue(TheCallee->getType()), + namep); + ctx.external_calls[std::make_tuple(fromexternal, true)] = GV; + } + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + TheCallee = ai.decorateInst(ctx.builder.CreateAlignedLoad(TheCallee->getType(), GV, Align(sizeof(void*)))); + } + CallInst *call = ctx.builder.CreateCall(cft, TheCallee, argvals); + call->setAttributes(returninfo.attrs); + + jl_cgval_t retval; switch (returninfo.cc) { case jl_returninfo_t::Boxed: retval = mark_julia_type(ctx, call, true, jlretty); @@ -3627,7 +4215,8 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_ retval = mark_julia_type(ctx, call, false, jlretty); break; case jl_returninfo_t::SRet: - retval = mark_julia_slot(result, jlretty, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + assert(result); + retval = mark_julia_slot(result, jlretty, NULL, ctx.tbaa().tbaa_stack); break; case jl_returninfo_t::Union: { Value *box = ctx.builder.CreateExtractValue(call, 0); @@ -3642,27 +4231,50 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_ retval = mark_julia_slot(derived, jlretty, tindex, - ctx.tbaa(), ctx.tbaa().tbaa_stack); retval.Vboxed = box; break; } case jl_returninfo_t::Ghosts: - retval = mark_julia_slot(NULL, jlretty, call, ctx.tbaa(), ctx.tbaa().tbaa_stack); + retval = mark_julia_slot(NULL, jlretty, call, ctx.tbaa().tbaa_stack); break; } // see if inference has a different / better type for the call than the lambda return update_julia_type(ctx, retval, inferred_retty); } -static jl_cgval_t emit_call_specfun_boxed(jl_codectx_t &ctx, jl_value_t *jlretty, StringRef specFunctionObject, +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) +{ + 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, + specFunctionObject, fromexternal, argv, nargs, cc, return_roots, inferred_retty); +} + +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) { - auto theFptr = cast( - jl_Module->getOrInsertFunction(specFunctionObject, ctx.types().T_jlfunc).getCallee()); - addRetAttr(theFptr, Attribute::NonNull); - theFptr->addFnAttr(Attribute::get(ctx.builder.getContext(), "thunk")); - Value *ret = emit_jlcall(ctx, theFptr, nullptr, argv, nargs, JLCALL_F_CC); + Value *theFptr; + if (fromexternal) { + std::string namep("p"); + namep += specFunctionObject; + GlobalVariable *GV = cast_or_null(jl_Module->getNamedValue(namep)); + Type *pfunc = ctx.types().T_jlfunc->getPointerTo(); + if (GV == nullptr) { + GV = new GlobalVariable(*jl_Module, pfunc, false, + GlobalVariable::ExternalLinkage, + Constant::getNullValue(pfunc), + namep); + ctx.external_calls[std::make_tuple(fromexternal, false)] = GV; + } + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + theFptr = ai.decorateInst(ctx.builder.CreateAlignedLoad(pfunc, GV, Align(sizeof(void*)))); + } + else { + theFptr = jl_Module->getOrInsertFunction(specFunctionObject, ctx.types().T_jlfunc).getCallee(); + addRetAttr(cast(theFptr), Attribute::NonNull); + } + Value *ret = emit_jlcall(ctx, FunctionCallee(ctx.types().T_jlfunc, theFptr), nullptr, argv, nargs, julia_call); return update_julia_type(ctx, mark_julia_type(ctx, ret, true, jlretty), inferred_retty); } @@ -3674,19 +4286,20 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) assert(arglen >= 2); jl_cgval_t lival = emit_expr(ctx, args[0]); - jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + SmallVector argv(nargs); for (size_t i = 0; i < nargs; ++i) { argv[i] = emit_expr(ctx, args[i + 1]); if (argv[i].typ == jl_bottom_type) - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } - return emit_invoke(ctx, lival, argv, nargs, rt); + return emit_invoke(ctx, lival, argv.data(), 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) { + ++EmittedInvokes; bool handled = false; - jl_cgval_t result(ctx.builder.getContext()); + jl_cgval_t result; if (lival.constant) { jl_method_instance_t *mi = (jl_method_instance_t*)lival.constant; assert(jl_is_method_instance(mi)); @@ -3696,12 +4309,12 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const FunctionType *ft = ctx.f->getFunctionType(); StringRef protoname = ctx.f->getName(); if (ft == ctx.types().T_jlfunc) { - result = emit_call_specfun_boxed(ctx, ctx.rettype, protoname, argv, nargs, rt); + result = emit_call_specfun_boxed(ctx, ctx.rettype, protoname, nullptr, argv, nargs, rt); handled = true; } else if (ft != ctx.types().T_jlfuncparams) { unsigned return_roots = 0; - result = emit_call_specfun_other(ctx, mi, ctx.rettype, protoname, argv, nargs, &cc, &return_roots, rt); + result = emit_call_specfun_other(ctx, mi, ctx.rettype, protoname, nullptr, argv, nargs, &cc, &return_roots, rt); handled = true; } } @@ -3709,7 +4322,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const jl_value_t *ci = ctx.params->lookup(mi, ctx.world, ctx.world); // TODO: need to use the right pair world here jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; if (ci != jl_nothing) { - auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); + auto invoke = jl_atomic_load_acquire(&codeinst->invoke); // check if we know how to handle this specptr if (invoke == jl_fptr_const_return_addr) { result = mark_julia_const(ctx, codeinst->rettype_const); @@ -3721,39 +4334,64 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, const std::string name; StringRef protoname; bool need_to_emit = true; - if (ctx.use_cache) { + bool cache_valid = ctx.use_cache || ctx.external_linkage; + bool external = false; + + // Check if we already queued this up + auto it = ctx.call_targets.find(codeinst); + if (need_to_emit && it != ctx.call_targets.end()) { + protoname = std::get<2>(it->second)->getName(); + need_to_emit = cache_valid = false; + } + + // Check if it is already compiled (either JIT or externally) + if (cache_valid) { // optimization: emit the correct name immediately, if we know it // TODO: use `emitted` map here too to try to consolidate names? - auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); + // 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. auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); if (fptr) { - if (specsig ? codeinst->isspecsig : invoke == jl_fptr_args_addr) { + while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + invoke = jl_atomic_load_relaxed(&codeinst->invoke); + if (specsig ? jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b1 : invoke == jl_fptr_args_addr) { protoname = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)fptr, codeinst); - need_to_emit = false; + if (ctx.external_linkage) { + // TODO: Add !specsig support to aotcompile.cpp + // Check that the codeinst is containing native code + if (specsig && jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b100) { + external = true; + need_to_emit = false; + } + } + else { // ctx.use_cache + need_to_emit = false; + } } } } if (need_to_emit) { - raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << globalUnique++; + raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); protoname = StringRef(name); } jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed; unsigned return_roots = 0; if (specsig) - result = emit_call_specfun_other(ctx, mi, codeinst->rettype, protoname, argv, nargs, &cc, &return_roots, rt); + result = emit_call_specfun_other(ctx, mi, codeinst->rettype, protoname, external ? codeinst : nullptr, argv, nargs, &cc, &return_roots, rt); else - result = emit_call_specfun_boxed(ctx, codeinst->rettype, protoname, argv, nargs, rt); + result = emit_call_specfun_boxed(ctx, codeinst->rettype, protoname, external ? codeinst : nullptr, argv, nargs, rt); handled = true; if (need_to_emit) { Function *trampoline_decl = cast(jl_Module->getNamedValue(protoname)); - ctx.call_targets.push_back(std::make_tuple(codeinst, cc, return_roots, trampoline_decl, specsig)); + ctx.call_targets[codeinst] = std::make_tuple(cc, return_roots, trampoline_decl, specsig); } } } } } if (!handled) { - Value *r = emit_jlcall(ctx, jlinvoke_func, boxed(ctx, lival), argv, nargs, JLCALL_F2_CC); + Value *r = emit_jlcall(ctx, jlinvoke_func, boxed(ctx, lival), argv, nargs, julia_call2); result = mark_julia_type(ctx, r, true, rt); } if (result.typ == jl_bottom_type) @@ -3763,46 +4401,75 @@ 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); size_t arglen = jl_array_dim0(ex->args); size_t nargs = arglen - 1; assert(arglen >= 2); jl_cgval_t lival = emit_expr(ctx, args[0]); - jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + SmallVector argv(nargs); for (size_t i = 0; i < nargs; ++i) { argv[i] = emit_expr(ctx, args[i + 1]); if (argv[i].typ == jl_bottom_type) - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } const jl_cgval_t &f = argv[0]; - jl_cgval_t ret(ctx.builder.getContext()); + jl_cgval_t ret; if (f.constant && f.constant == jl_builtin_modifyfield) { - if (emit_f_opfield(ctx, &ret, jl_builtin_modifyfield, argv, nargs - 1, &lival)) + 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, JLCALL_F_CC); + 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_typeis(f.constant, jl_intrinsic_type)) { + 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, nargs - 1, &lival); + return emit_atomic_pointerop(ctx, fi, argv.data(), nargs - 1, &lival); } // emit function and arguments - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv, nargs, JLCALL_F_CC); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, argv.data(), nargs, julia_call); return mark_julia_type(ctx, callval, true, rt); } -static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) +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) +{ + jl_datatype_t *oc_argt = (jl_datatype_t *)jl_tparam0(oc_type); + jl_value_t *oc_rett = jl_tparam1(oc_type); + jl_svec_t *types = jl_get_fieldtypes((jl_datatype_t*)oc_argt); + size_t ntypes = jl_svec_len(types); + for (size_t i = 0; i < nargs-1; ++i) { + jl_value_t *typ = i >= ntypes ? jl_svecref(types, ntypes-1) : jl_svecref(types, i); + if (jl_is_vararg(typ)) + typ = jl_unwrap_vararg(typ); + emit_typecheck(ctx, argv[i+1], typ, "typeassert"); + argv[i+1] = update_julia_type(ctx, argv[i+1], typ); + } + jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed; + unsigned return_roots = 0; + + // Load specptr + jl_cgval_t &theArg = argv[0]; + jl_cgval_t closure_specptr = emit_getfield_knownidx(ctx, theArg, 4, (jl_datatype_t*)oc_type, jl_memory_order_notatomic); + Value *specptr = emit_unbox(ctx, ctx.types().T_size, closure_specptr, (jl_value_t*)jl_long_type); + JL_GC_PUSH1(&sigtype); + jl_cgval_t r = emit_call_specfun_other(ctx, true, sigtype, oc_rett, specptr, "", NULL, argv, nargs, + &cc, &return_roots, oc_rett); + JL_GC_POP(); + return r; +} + +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); size_t nargs = jl_array_dim0(ex->args); assert(nargs >= 1); jl_cgval_t f = emit_expr(ctx, args[0]); - if (f.constant && jl_typeis(f.constant, jl_intrinsic_type)) { + if (f.constant && jl_typetagis(f.constant, jl_intrinsic_type)) { JL_I::intrinsic fi = (intrinsic)*(uint32_t*)jl_data_ptr(f.constant); return emit_intrinsic(ctx, fi, args, nargs - 1); } @@ -3810,8 +4477,8 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) jl_value_t *context = ctx.params->generic_context == jl_nothing ? nullptr : ctx.params->generic_context; size_t n_generic_args = nargs + (context ? 1 : 0); - jl_cgval_t *generic_argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * n_generic_args); - jl_cgval_t *argv = generic_argv; + SmallVector generic_argv(n_generic_args); + jl_cgval_t *argv = generic_argv.data(); if (context) { generic_argv[0] = mark_julia_const(ctx, context); argv = &generic_argv[1]; @@ -3820,28 +4487,43 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) for (size_t i = 1; i < nargs; ++i) { argv[i] = emit_expr(ctx, args[i]); if (argv[i].typ == jl_bottom_type) - return jl_cgval_t(ctx.builder.getContext()); // anything past here is unreachable + 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(ctx.builder.getContext()); - bool handled = emit_builtin_call(ctx, &result, f.constant, argv, nargs - 1, rt, ex); + 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 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, JLCALL_F_CC); + 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); return mark_julia_type(ctx, ret, true, rt); } } + // handle calling an OpaqueClosure + if (jl_is_concrete_type(f.typ) && jl_subtype(f.typ, (jl_value_t*)jl_opaque_closure_type)) { + jl_value_t *oc_argt = jl_tparam0(f.typ); + jl_value_t *oc_rett = jl_tparam1(f.typ); + if (jl_is_datatype(oc_argt) && jl_tupletype_length_compat(oc_argt, nargs-1)) { + jl_value_t *sigtype = jl_argtype_with_function_type((jl_value_t*)f.typ, (jl_value_t*)oc_argt); + if (uses_specsig(sigtype, false, true, oc_rett, true)) { + JL_GC_PUSH1(&sigtype); + jl_cgval_t r = emit_specsig_oc_call(ctx, f.typ, sigtype, argv, nargs); + JL_GC_POP(); + return r; + } + } + } + // emit function and arguments - Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, generic_argv, n_generic_args, JLCALL_F_CC); + Value *callval = emit_jlcall(ctx, jlapplygeneric_func, nullptr, generic_argv.data(), n_generic_args, julia_call); return mark_julia_type(ctx, callval, true, rt); } @@ -3849,6 +4531,7 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt) 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); @@ -3866,50 +4549,58 @@ static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name) static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, jl_binding_t **pbnd, bool assign) { - jl_binding_t *b = NULL; + jl_binding_t *b = jl_get_module_binding(m, s, 1); if (assign) { - b = jl_get_binding_wr(m, s, 0); - assert(b != NULL); - if (b->owner != m) { - char *msg; - (void)asprintf(&msg, "cannot assign a value to variable %s.%s from module %s", - jl_symbol_name(b->owner->name), jl_symbol_name(s), jl_symbol_name(m->name)); - emit_error(ctx, msg); - free(msg); - } + if (jl_atomic_load_relaxed(&b->owner) == NULL) + // not yet declared + b = NULL; } else { - b = jl_get_binding(m, s); - if (b == NULL) { - // var not found. switch to delayed lookup. - Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); - GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, - false, GlobalVariable::PrivateLinkage, initnul); - LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); - cachedval->setOrdering(AtomicOrdering::Unordered); - BasicBlock *have_val = BasicBlock::Create(ctx.builder.getContext(), "found"); - BasicBlock *not_found = BasicBlock::Create(ctx.builder.getContext(), "notfound"); - BasicBlock *currentbb = ctx.builder.GetInsertBlock(); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpNE(cachedval, initnul), have_val, not_found); - ctx.f->getBasicBlockList().push_back(not_found); - ctx.builder.SetInsertPoint(not_found); - Value *bval = ctx.builder.CreateCall(prepare_call(jlgetbindingorerror_func), + b = jl_atomic_load_relaxed(&b->owner); + if (b == NULL) + // try to look this up now + b = jl_get_binding(m, s); + } + if (b == NULL) { + // var not found. switch to delayed lookup. + Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); + GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, + false, GlobalVariable::PrivateLinkage, initnul); + LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); + cachedval->setOrdering(AtomicOrdering::Unordered); + BasicBlock *have_val = BasicBlock::Create(ctx.builder.getContext(), "found"); + BasicBlock *not_found = BasicBlock::Create(ctx.builder.getContext(), "notfound"); + BasicBlock *currentbb = ctx.builder.GetInsertBlock(); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpNE(cachedval, initnul), have_val, not_found); + ctx.f->getBasicBlockList().push_back(not_found); + ctx.builder.SetInsertPoint(not_found); + Value *bval = ctx.builder.CreateCall(prepare_call(assign ? jlgetbindingwrorerror_func : jlgetbindingorerror_func), + { literal_pointer_val(ctx, (jl_value_t*)m), + literal_pointer_val(ctx, (jl_value_t*)s) }); + ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); + ctx.builder.CreateBr(have_val); + ctx.f->getBasicBlockList().push_back(have_val); + ctx.builder.SetInsertPoint(have_val); + PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); + p->addIncoming(cachedval, currentbb); + p->addIncoming(bval, not_found); + return p; + } + if (assign) { + if (jl_atomic_load_relaxed(&b->owner) != b) { + // this will fail at runtime, so defer to the runtime to create the error + ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func), { literal_pointer_val(ctx, (jl_value_t*)m), literal_pointer_val(ctx, (jl_value_t*)s) }); - ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); - ctx.builder.CreateBr(have_val); - ctx.f->getBasicBlockList().push_back(have_val); - ctx.builder.SetInsertPoint(have_val); - PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); - p->addIncoming(cachedval, currentbb); - p->addIncoming(bval, not_found); - return julia_binding_gv(ctx, emit_bitcast(ctx, p, ctx.types().T_pprjlvalue)); + CreateTrap(ctx.builder); + return NULL; } + } + else { if (b->deprecated) - cg_bdw(ctx, b); + cg_bdw(ctx, s, b); } - if (pbnd) - *pbnd = b; + *pbnd = b; return julia_binding_gv(ctx, b); } @@ -3919,8 +4610,10 @@ static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, if (isvol) v->setVolatile(true); v->setOrdering(AtomicOrdering::Unordered); - if (tbaa) - tbaa_decorate(tbaa, v); + if (tbaa) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(v); + } undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name); return mark_julia_type(ctx, v, true, jl_any_type); } @@ -3938,41 +4631,22 @@ static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i) ctx.types().T_prjlvalue, ctx.spvals_ptr, i + sizeof(jl_svec_t) / sizeof(jl_value_t*)); - Value *sp = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); - Value *isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp), - track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_tvar_type))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *sp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); + Value *isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp, false, true), emit_tagfrom(ctx, jl_tvar_type)); jl_unionall_t *sparam = (jl_unionall_t*)ctx.linfo->def.method->sig; for (size_t j = 0; j < i; j++) { sparam = (jl_unionall_t*)sparam->body; assert(jl_is_unionall(sparam)); } - undef_var_error_ifnot(ctx, isnull, sparam->var->name); - return mark_julia_type(ctx, sp, true, jl_any_type); -} - -static jl_cgval_t emit_global(jl_codectx_t &ctx, jl_sym_t *sym) -{ - jl_binding_t *jbp = NULL; - Value *bp = global_binding_pointer(ctx, ctx.module, sym, &jbp, false); - assert(bp != NULL); - if (jbp && jbp->value != NULL) { - if (jbp->constp) - return mark_julia_const(ctx, jbp->value); - // double-check that a global variable is actually defined. this - // can be a problem in parallel when a definition is missing on - // one machine. - LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - v->setOrdering(AtomicOrdering::Unordered); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); - return mark_julia_type(ctx, v, true, jl_any_type); - } - return emit_checked_var(ctx, bp, sym, false, ctx.tbaa().tbaa_binding); + undef_var_error_ifnot(ctx, isnull, sparam->var->name); + return mark_julia_type(ctx, sp, true, jl_any_type); } static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) { Value *isnull = NULL; - if (jl_is_slot(sym) || jl_is_argument(sym)) { + if (jl_is_slotnumber(sym) || jl_is_argument(sym)) { size_t sl = jl_slot_number(sym) - 1; jl_varinfo_t &vi = ctx.slots[sl]; if (!vi.usedUndef) @@ -4012,9 +4686,9 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) ctx.types().T_prjlvalue, ctx.spvals_ptr, i + sizeof(jl_svec_t) / sizeof(jl_value_t*)); - Value *sp = tbaa_decorate(ctx.tbaa().tbaa_const, ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); - isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp), - track_pjlvalue(ctx, literal_pointer_val(ctx, (jl_value_t*)jl_tvar_type))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *sp = ai.decorateInst(ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)))); + isnull = ctx.builder.CreateICmpNE(emit_typeof(ctx, sp, false, true), emit_tagfrom(ctx, jl_tvar_type)); } else { jl_module_t *modu; @@ -4030,11 +4704,13 @@ 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 (bnd->value != NULL) + if (jl_atomic_load_relaxed(&bnd->value) != NULL) return mark_julia_const(ctx, jl_true); Value *bp = julia_binding_gv(ctx, bnd); + bp = julia_binding_pvalue(ctx, bp); LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); - tbaa_decorate(ctx.tbaa().tbaa_binding, v); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding); + ai.decorateInst(v); v->setOrdering(AtomicOrdering::Unordered); isnull = ctx.builder.CreateICmpNE(v, Constant::getNullValue(ctx.types().T_prjlvalue)); } @@ -4049,9 +4725,9 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym) return mark_julia_type(ctx, isnull, false, jl_bool_type); } -static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *varname, jl_value_t *better_typ=NULL) { - jl_value_t *typ = better_typ ? better_typ : vi.value.typ; - jl_cgval_t v(ctx.builder.getContext()); +static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *varname) { + jl_value_t *typ = vi.value.typ; + jl_cgval_t v; Value *isnull = NULL; if (vi.boxroot == NULL || vi.pTIndex != NULL) { if ((!vi.isVolatile && vi.isSA) || vi.isArgument || vi.value.constant || !vi.value.V) { @@ -4075,12 +4751,12 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va else { const DataLayout &DL = jl_Module->getDataLayout(); uint64_t sz = DL.getTypeStoreSize(T); - emit_memcpy(ctx, ssaslot, ctx.tbaa().tbaa_stack, vi.value, sz, ssaslot->getAlign().value()); + emit_memcpy(ctx, ssaslot, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), vi.value, sz, ssaslot->getAlign().value()); } Value *tindex = NULL; if (vi.pTIndex) tindex = ctx.builder.CreateAlignedLoad(getInt8Ty(ctx.builder.getContext()), vi.pTIndex, Align(1), vi.isVolatile); - v = mark_julia_slot(ssaslot, vi.value.typ, tindex, ctx.tbaa(), ctx.tbaa().tbaa_stack); + v = mark_julia_slot(ssaslot, vi.value.typ, tindex, ctx.tbaa().tbaa_stack); } if (vi.boxroot == NULL) v = update_julia_type(ctx, v, typ); @@ -4127,14 +4803,7 @@ static jl_cgval_t emit_local(jl_codectx_t &ctx, jl_value_t *slotload) size_t sl = jl_slot_number(slotload) - 1; jl_varinfo_t &vi = ctx.slots[sl]; jl_sym_t *sym = slot_symbol(ctx, sl); - jl_value_t *typ = NULL; - if (jl_typeis(slotload, jl_typedslot_type)) { - // use the better type from inference for this load - typ = jl_typedslot_get_type(slotload); - if (jl_is_typevar(typ)) - typ = ((jl_tvar_t*)typ)->ub; - } - return emit_varinfo(ctx, vi, sym, typ); + return emit_varinfo(ctx, vi, sym); } static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Value *isboxed, jl_cgval_t rval_info) @@ -4165,7 +4834,8 @@ static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Valu Type *dest_ty = store_ty->getPointerTo(); if (dest_ty != dest->getType()) dest = emit_bitcast(ctx, dest, dest_ty); - tbaa_decorate(ctx.tbaa().tbaa_stack, ctx.builder.CreateStore( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack); + ai.decorateInst(ctx.builder.CreateStore( emit_unbox(ctx, store_ty, rval_info, rval_info.typ), dest, vi.isVolatile)); @@ -4181,12 +4851,12 @@ static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Valu // This check should probably mostly catch the relevant situations. if (vi.value.V != rval_info.V) { Value *copy_bytes = ConstantInt::get(getInt32Ty(ctx.builder.getContext()), jl_datatype_size(vi.value.typ)); - emit_memcpy(ctx, vi.value.V, ctx.tbaa().tbaa_stack, rval_info, copy_bytes, + emit_memcpy(ctx, vi.value.V, jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_stack), rval_info, copy_bytes, julia_alignment(rval_info.typ), vi.isVolatile); } } else { - emit_unionmove(ctx, vi.value.V, ctx.tbaa().tbaa_stack, rval_info, isboxed, vi.isVolatile); + emit_unionmove(ctx, vi.value.V, ctx.tbaa().tbaa_stack, rval_info, /*skip*/isboxed, vi.isVolatile); } } } @@ -4235,7 +4905,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) 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(), ctx.tbaa().tbaa_stack); // XXX: this TBAA is wrong for ptr_phi + jl_cgval_t val = mark_julia_slot(ptr, phiType, Tindex_phi, ctx.tbaa().tbaa_stack); // XXX: this TBAA is wrong for ptr_phi val.Vboxed = ptr_phi; ctx.PhiNodes.push_back(std::make_tuple(val, BB, dest, ptr_phi, r)); ctx.SAvalues.at(idx) = val; @@ -4245,7 +4915,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) else if (allunbox) { PHINode *Tindex_phi = PHINode::Create(getInt8Ty(ctx.builder.getContext()), jl_array_len(edges), "tindex_phi"); BB->getInstList().insert(InsertPt, Tindex_phi); - jl_cgval_t val = mark_julia_slot(NULL, phiType, Tindex_phi, ctx.tbaa(), ctx.tbaa().tbaa_stack); + 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.at(idx) = val; ctx.ssavalue_assigned.at(idx) = true; @@ -4263,7 +4933,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) ctx.ssavalue_assigned.at(idx) = true; return; } - jl_cgval_t slot(ctx.builder.getContext()); + jl_cgval_t slot; PHINode *value_phi = NULL; if (vtype->isAggregateType() && CountTrackedPointers(vtype).count == 0) { // the value will be moved into dest in the predecessor critical block. @@ -4274,7 +4944,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) dest, MaybeAlign(0), jl_datatype_size(phiType), false); ctx.builder.CreateLifetimeEnd(dest); - slot = mark_julia_slot(phi, phiType, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + slot = mark_julia_slot(phi, phiType, NULL, ctx.tbaa().tbaa_stack); } else { value_phi = PHINode::Create(vtype, jl_array_len(edges), "value_phi"); @@ -4287,36 +4957,36 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) return; } -static void emit_ssaval_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) +static void emit_ssaval_assign(jl_codectx_t &ctx, ssize_t ssaidx_0based, jl_value_t *r) { - assert(!ctx.ssavalue_assigned.at(idx)); + assert(!ctx.ssavalue_assigned.at(ssaidx_0based)); if (jl_is_phinode(r)) { - return emit_phinode_assign(ctx, idx, r); + return emit_phinode_assign(ctx, ssaidx_0based, r); } - jl_cgval_t slot(ctx.builder.getContext()); + jl_cgval_t slot; if (jl_is_phicnode(r)) { - auto it = ctx.phic_slots.find(idx); + auto it = ctx.phic_slots.find(ssaidx_0based); if (it == ctx.phic_slots.end()) { - it = ctx.phic_slots.emplace(idx, jl_varinfo_t(ctx.builder.getContext())).first; + it = ctx.phic_slots.emplace(ssaidx_0based, jl_varinfo_t(ctx.builder.getContext())).first; } slot = emit_varinfo(ctx, it->second, jl_symbol("phic")); } else { - slot = emit_expr(ctx, r, idx); // slot could be a jl_value_t (unboxed) or jl_value_t* (ispointer) + slot = emit_expr(ctx, r, ssaidx_0based); // slot could be a jl_value_t (unboxed) or jl_value_t* (ispointer) } if (slot.isboxed || slot.TIndex) { // see if inference suggested a different type for the ssavalue than the expression // e.g. sometimes the information is inconsistent after inlining getfield on a Tuple jl_value_t *ssavalue_types = (jl_value_t*)ctx.source->ssavaluetypes; if (jl_is_array(ssavalue_types)) { - jl_value_t *declType = jl_array_ptr_ref(ssavalue_types, idx); + jl_value_t *declType = jl_array_ptr_ref(ssavalue_types, ssaidx_0based); if (declType != slot.typ) { slot = update_julia_type(ctx, slot, declType); } } } - ctx.SAvalues.at(idx) = slot; // now SAvalues[idx] contains the SAvalue - ctx.ssavalue_assigned.at(idx) = true; + ctx.SAvalues.at(ssaidx_0based) = slot; // now SAvalues[ssaidx_0based] contains the SAvalue + ctx.ssavalue_assigned.at(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) @@ -4378,34 +5048,29 @@ static void emit_varinfo_assign(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_cgval_t static void emit_assignment(jl_codectx_t &ctx, jl_value_t *l, jl_value_t *r, ssize_t ssaval) { assert(!jl_is_ssavalue(l)); + jl_cgval_t rval_info = emit_expr(ctx, r, ssaval); - jl_sym_t *s = NULL; - jl_binding_t *bnd = NULL; - Value *bp = NULL; - if (jl_is_symbol(l)) - s = (jl_sym_t*)l; - else if (jl_is_globalref(l)) - bp = global_binding_pointer(ctx, jl_globalref_mod(l), jl_globalref_name(l), &bnd, true); // now bp != NULL - else - assert(jl_is_slot(l)); - if (bp == NULL && s != NULL) - bp = global_binding_pointer(ctx, ctx.module, s, &bnd, true); - if (bp != NULL) { // it's a global - assert(bnd); - Value *rval = mark_callee_rooted(ctx, boxed(ctx, emit_expr(ctx, r, ssaval))); - ctx.builder.CreateCall(prepare_call(jlcheckassign_func), - { literal_pointer_val(ctx, bnd), - rval }); - // Global variable. Does not need debug info because the debugger knows about - // its memory location. - return; + if (jl_is_slotnumber(l)) { + 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); } - int sl = jl_slot_number(l) - 1; - // it's a local variable - jl_varinfo_t &vi = ctx.slots[sl]; - jl_cgval_t rval_info = emit_expr(ctx, r, ssaval); - emit_varinfo_assign(ctx, vi, rval_info, l); + jl_module_t *mod; + jl_sym_t *sym; + if (jl_is_symbol(l)) { + mod = ctx.module; + sym = (jl_sym_t*)l; + } + else { + assert(jl_is_globalref(l)); + mod = jl_globalref_mod(l); + sym = jl_globalref_name(l); + } + emit_globalset(ctx, mod, sym, rval_info, AtomicOrdering::Release); + // Global variable. Does not need debug info because the debugger knows about + // its memory location. } static void emit_upsilonnode(jl_codectx_t &ctx, ssize_t phic, jl_value_t *val) @@ -4491,7 +5156,7 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) { if (jl_is_ssavalue(expr) && ssaval_result == -1) return; // value not used, no point in attempting codegen for it - if (jl_is_slot(expr) && ssaval_result == -1) { + if (jl_is_slotnumber(expr) && ssaval_result == -1) { size_t sl = jl_slot_number(expr) - 1; jl_varinfo_t &vi = ctx.slots[sl]; if (vi.usedUndef) @@ -4503,7 +5168,7 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) } if (jl_is_newvarnode(expr)) { jl_value_t *var = jl_fieldref(expr, 0); - assert(jl_is_slot(var)); + assert(jl_is_slotnumber(var)); jl_varinfo_t &vi = ctx.slots[jl_slot_number(var)-1]; if (vi.usedUndef) { // create a new uninitialized variable @@ -4536,18 +5201,11 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) } 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() == getSizeTy(ctx.builder.getContext())); + assert(excstack_state.V && excstack_state.V->getType() == ctx.types().T_size); ctx.builder.CreateCall(prepare_call(jl_restore_excstack_func), excstack_state.V); return; } else { - if (!jl_is_method(ctx.linfo->def.method) && !ctx.is_opaque_closure) { - // TODO: inference is invalid if this has any effect (which it often does) - LoadInst *world = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), - prepare_global_in(jl_Module, jlgetworld_global), Align(sizeof(size_t))); - world->setOrdering(AtomicOrdering::Acquire); - ctx.builder.CreateAlignedStore(world, ctx.world_age_field, Align(sizeof(size_t))); - } assert(ssaval_result != -1); emit_ssaval_assign(ctx, ssaval_result, expr); } @@ -4566,23 +5224,30 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met for (size_t i = 0; i < jl_svec_len(argt_typ->parameters); ++i) { jl_svecset(sig_args, 1+i, jl_svecref(argt_typ->parameters, i)); } - sigtype = (jl_value_t*)jl_apply_tuple_type_v(jl_svec_data(sig_args), nsig); + 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(mi, ctx.world, ctx.world); + jl_code_instance_t *ci = (jl_code_instance_t*)jl_rettype_inferred_addr(mi, ctx.world, ctx.world); - if (ci == NULL || (jl_value_t*)ci == jl_nothing || ci->inferred == NULL || ci->inferred == jl_nothing) { + if (ci == NULL || (jl_value_t*)ci == jl_nothing) { JL_GC_POP(); return std::make_pair((Function*)NULL, (Function*)NULL); } + auto inferred = jl_atomic_load_relaxed(&ci->inferred); + if (!inferred || inferred == jl_nothing) { + JL_GC_POP(); + return std::make_pair((Function*)NULL, (Function*)NULL); + } + ++EmittedOpaqueClosureFunctions; - ir = jl_uncompress_ir(closure_method, ci, (jl_array_t*)ci->inferred); + ir = jl_uncompress_ir(closure_method, ci, (jl_value_t*)inferred); // TODO: Emit this inline and outline it late using LLVM's coroutine support. - std::unique_ptr closure_m; - jl_llvm_functions_t closure_decls; - std::tie(closure_m, closure_decls) = emit_function(mi, ir, rettype, ctx.emission_context, - ctx.builder.getContext()); + orc::ThreadSafeModule closure_m = jl_create_ts_module( + name_from_method_instance(mi), ctx.emission_context.tsctx, + ctx.emission_context.imaging, + jl_Module->getDataLayout(), Triple(jl_Module->getTargetTriple())); + jl_llvm_functions_t closure_decls = emit_function(closure_m, mi, ir, rettype, ctx.emission_context); assert(closure_decls.functionObject != "jl_fptr_sparam"); bool isspecsig = closure_decls.functionObject != "jl_fptr_args"; @@ -4594,20 +5259,22 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met if (GlobalValue *V = jl_Module->getNamedValue(fname)) { F = cast(V); } else { - F = Function::Create(get_func_sig(jl_LLVMContext), + F = Function::Create(get_func_sig(ctx.builder.getContext()), Function::ExternalLinkage, fname, jl_Module); - F->setAttributes(get_func_attrs(jl_LLVMContext)); + jl_init_function(F, ctx.emission_context.TargetTriple); + F->setAttributes(AttributeList::get(ctx.builder.getContext(), {get_func_attrs(ctx.builder.getContext()), F->getAttributes()})); } Function *specF = NULL; if (!isspecsig) { specF = F; } else { - specF = closure_m->getFunction(closure_decls.specFunctionObject); + //emission context holds context lock so can get module + specF = closure_m.getModuleUnlocked()->getFunction(closure_decls.specFunctionObject); if (specF) { - jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, + jl_returninfo_t returninfo = get_specsig_function(ctx, jl_Module, NULL, closure_decls.specFunctionObject, sigtype, rettype, true); - specF = returninfo.decl; + specF = cast(returninfo.decl.getCallee()); } } ctx.oc_modules.push_back(std::move(closure_m)); @@ -4617,13 +5284,13 @@ static std::pair get_oc_function(jl_codectx_t &ctx, jl_met // `expr` is not clobbered in JL_TRY JL_GCC_IGNORE_START("-Wclobbered") -static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) +static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_0based) { if (jl_is_symbol(expr)) { jl_sym_t *sym = (jl_sym_t*)expr; - return emit_global(ctx, sym); + return emit_globalref(ctx, ctx.module, sym, AtomicOrdering::Unordered); } - if (jl_is_slot(expr) || jl_is_argument(expr)) { + if (jl_is_slotnumber(expr) || jl_is_argument(expr)) { return emit_local(ctx, expr); } if (jl_is_ssavalue(expr)) { @@ -4631,14 +5298,14 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) assert(idx >= 0); if (!ctx.ssavalue_assigned.at(idx)) { ctx.ssavalue_assigned.at(idx) = true; // (assignment, not comparison test) - return jl_cgval_t(ctx.builder.getContext()); // dead code branch + return jl_cgval_t(); // dead code branch } else { return ctx.SAvalues.at(idx); // at this point, SAvalues[idx] actually contains the SAvalue } } if (jl_is_globalref(expr)) { - return emit_globalref(ctx, jl_globalref_mod(expr), jl_globalref_name(expr)); + return emit_globalref(ctx, jl_globalref_mod(expr), jl_globalref_name(expr), AtomicOrdering::Unordered); } if (jl_is_linenode(expr)) { jl_error("LineNumberNode in value position"); @@ -4650,38 +5317,16 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) jl_error("GotoIfNot in value position"); } if (jl_is_pinode(expr)) { - return convert_julia_type(ctx, emit_expr(ctx, jl_fieldref_noalloc(expr, 0)), jl_fieldref_noalloc(expr, 1)); + Value *skip = NULL; + return convert_julia_type(ctx, emit_expr(ctx, jl_fieldref_noalloc(expr, 0)), jl_fieldref_noalloc(expr, 1), &skip); } if (!jl_is_expr(expr)) { - int needroot = true; - if (jl_is_quotenode(expr)) { - expr = jl_fieldref_noalloc(expr,0); - } - // numeric literals - if (jl_is_int32(expr)) { - int32_t val = jl_unbox_int32(expr); - if ((uint32_t)(val+512) < 1024) { - // this can be gotten from the box cache - needroot = false; - expr = jl_box_int32(val); - } - } - else if (jl_is_int64(expr)) { - uint64_t val = jl_unbox_uint64(expr); - if ((uint64_t)(val+512) < 1024) { - // this can be gotten from the box cache - needroot = false; - expr = jl_box_int64(val); - } - } - else if (jl_is_uint8(expr)) { - expr = jl_box_uint8(jl_unbox_uint8(expr)); - needroot = false; - } - if (needroot && jl_is_method(ctx.linfo->def.method)) { // toplevel exprs and some integers are already rooted - jl_add_method_root(ctx, expr); - } - return mark_julia_const(ctx, expr); + jl_value_t *val = expr; + if (jl_is_quotenode(expr)) + val = jl_fieldref_noalloc(expr, 0); + if (jl_is_method(ctx.linfo->def.method)) // toplevel exprs are already rooted + val = jl_ensure_rooted(ctx, val); + return mark_julia_const(ctx, val); } jl_expr_t *ex = (jl_expr_t*)expr; @@ -4709,27 +5354,33 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) return ghostValue(ctx, jl_nothing_type); } else if (head == jl_invoke_sym) { - assert(ssaval >= 0); + assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaval); + jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); return emit_invoke(ctx, ex, expr_t); } else if (head == jl_invoke_modify_sym) { - assert(ssaval >= 0); + assert(ssaidx_0based >= 0); jl_value_t *expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : - jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaval); + jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); return emit_invoke_modify(ctx, ex, expr_t); } else if (head == jl_call_sym) { jl_value_t *expr_t; - if (ssaval < 0) + bool is_promotable = false; + if (ssaidx_0based < 0) // TODO: this case is needed for the call to emit_expr in emit_llvmcall expr_t = (jl_value_t*)jl_any_type; - else - expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaval); - jl_cgval_t res = emit_call(ctx, ex, expr_t); + else { + expr_t = jl_is_long(ctx.source->ssavaluetypes) ? (jl_value_t*)jl_any_type : jl_array_ptr_ref(ctx.source->ssavaluetypes, ssaidx_0based); + is_promotable = ctx.ssavalue_usecount.at(ssaidx_0based) == 1; + } + jl_cgval_t res = emit_call(ctx, ex, expr_t, is_promotable); // some intrinsics (e.g. typeassert) can return a wider type // than what's actually possible + if (is_promotable && res.promotion_point && res.promotion_ssa == -1) { + res.promotion_ssa = ssaidx_0based; + } res = update_julia_type(ctx, res, expr_t); if (res.typ == jl_bottom_type || expr_t == jl_bottom_type) { CreateTrap(ctx.builder); @@ -4746,7 +5397,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } else if (head == jl_assign_sym) { assert(nargs == 2); - emit_assignment(ctx, args[0], args[1], ssaval); + emit_assignment(ctx, args[0], args[1], ssaidx_0based); return ghostValue(ctx, jl_nothing_type); } else if (head == jl_static_parameter_sym) { @@ -4756,9 +5407,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) else if (head == jl_method_sym) { if (nargs == 1) { jl_value_t *mn = args[0]; - assert(jl_is_symbol(mn) || jl_is_slot(mn)); + assert(jl_is_symbol(mn) || jl_is_slotnumber(mn)); - Value *bp = NULL, *name, *bp_owner = Constant::getNullValue(ctx.types().T_pjlvalue); + Value *bp = NULL, *name; jl_binding_t *bnd = NULL; bool issym = jl_is_symbol(mn); bool isglobalref = !issym && jl_is_globalref(mn); @@ -4776,25 +5427,23 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } JL_CATCH { jl_value_t *e = jl_current_exception(); - // errors. boo. root it somehow :( - bnd = jl_get_binding_wr(ctx.module, (jl_sym_t*)jl_gensym(), 1); - bnd->value = e; - bnd->constp = 1; + // errors. boo. :( + e = jl_as_global_root(e); raise_exception(ctx, literal_pointer_val(ctx, e)); return ghostValue(ctx, jl_nothing_type); } bp = julia_binding_gv(ctx, bnd); - bp_owner = literal_pointer_val(ctx, (jl_value_t*)mod); + bp = julia_binding_pvalue(ctx, bp); } - else if (jl_is_slot(mn) || jl_is_argument(mn)) { + else if (jl_is_slotnumber(mn) || jl_is_argument(mn)) { + // XXX: eval_methoddef does not have this code branch int sl = jl_slot_number(mn)-1; jl_varinfo_t &vi = ctx.slots[sl]; bp = vi.boxroot; name = literal_pointer_val(ctx, (jl_value_t*)slot_symbol(ctx, sl)); } if (bp) { - Value *mdargs[5] = { name, literal_pointer_val(ctx, (jl_value_t*)mod), bp, - bp_owner, literal_pointer_val(ctx, bnd) }; + 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)), @@ -4803,7 +5452,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) return gf; } emit_error(ctx, "method: invalid declaration"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } assert(nargs == 3); Value *a1 = boxed(ctx, emit_expr(ctx, args[1])); @@ -4831,14 +5480,19 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } if (jl_is_symbol(sym)) { jl_binding_t *bnd = NULL; - (void)global_binding_pointer(ctx, mod, sym, &bnd, true); assert(bnd); - ctx.builder.CreateCall(prepare_call(jldeclareconst_func), - literal_pointer_val(ctx, bnd)); + Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true); + if (bp) + ctx.builder.CreateCall(prepare_call(jldeclareconst_func), + { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) }); } } else if (head == jl_new_sym) { + bool is_promotable = false; + if (ssaidx_0based >= 0) { + is_promotable = ctx.ssavalue_usecount.at(ssaidx_0based) == 1; + } assert(nargs > 0); - jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + SmallVector argv(nargs); for (size_t i = 0; i < nargs; ++i) { argv[i] = emit_expr(ctx, args[i]); } @@ -4847,15 +5501,18 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) jl_is_datatype(jl_tparam0(ty)) && jl_is_concrete_type(jl_tparam0(ty))) { assert(nargs <= jl_datatype_nfields(jl_tparam0(ty)) + 1); - return emit_new_struct(ctx, jl_tparam0(ty), nargs - 1, &argv[1]); + jl_cgval_t res = emit_new_struct(ctx, jl_tparam0(ty), nargs - 1, argv.data() + 1, 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, nargs, JLCALL_F_CC); + Value *val = emit_jlcall(ctx, jlnew_func, nullptr, argv.data(), 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); } else if (head == jl_splatnew_sym) { - jl_cgval_t argv[2] = {jl_cgval_t(ctx.builder.getContext()), jl_cgval_t(ctx.builder.getContext())}; + jl_cgval_t argv[2] = {jl_cgval_t(), jl_cgval_t()}; assert(nargs == 2); argv[0] = emit_expr(ctx, args[0]); argv[1] = emit_expr(ctx, args[1]); @@ -4868,7 +5525,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } else if (head == jl_new_opaque_closure_sym) { assert(nargs >= 4 && "Not enough arguments in new_opaque_closure"); - SmallVector argv(nargs, jl_cgval_t(ctx.builder.getContext())); + SmallVector argv(nargs, jl_cgval_t()); for (size_t i = 0; i < nargs; ++i) { argv[i] = emit_expr(ctx, args[i]); } @@ -4879,8 +5536,8 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) if (source.constant == NULL) { // For now, we require non-constant source to be handled by using // eval. This should probably be a verifier error and an abort here. - emit_error(ctx, "(internal error) invalid IR: opaque closure source be constant"); - return jl_cgval_t(ctx.builder.getContext()); + emit_error(ctx, "(internal error) invalid IR: opaque closure source must be constant"); + return jl_cgval_t(); } bool can_optimize = argt.constant != NULL && lb.constant != NULL && ub.constant != NULL && jl_is_tuple_type(argt.constant) && @@ -4890,35 +5547,33 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) if (can_optimize) { jl_value_t *closure_t = NULL; - jl_tupletype_t *env_t = NULL; + jl_value_t *env_t = NULL; JL_GC_PUSH2(&closure_t, &env_t); - jl_value_t **env_component_ts = (jl_value_t**)alloca(sizeof(jl_value_t*) * (nargs-4)); + SmallVector env_component_ts(nargs-4); for (size_t i = 0; i < nargs - 4; ++i) { env_component_ts[i] = argv[4+i].typ; } - env_t = jl_apply_tuple_type_v(env_component_ts, nargs-4); + env_t = jl_apply_tuple_type_v(env_component_ts.data(), nargs-4); // we need to know the full env type to look up the right specialization - if (jl_is_concrete_type((jl_value_t*)env_t)) { + if (jl_is_concrete_type(env_t)) { jl_tupletype_t *argt_typ = (jl_tupletype_t*)argt.constant; Function *F, *specF; - std::tie(F, specF) = get_oc_function(ctx, (jl_method_t*)source.constant, env_t, argt_typ, ub.constant); + std::tie(F, specF) = get_oc_function(ctx, (jl_method_t*)source.constant, (jl_datatype_t*)env_t, argt_typ, ub.constant); if (F) { jl_cgval_t jlcall_ptr = mark_julia_type(ctx, F, false, jl_voidpointer_type); - jl_cgval_t world_age = mark_julia_type(ctx, - tbaa_decorate(ctx.tbaa().tbaa_gcframe, - ctx.builder.CreateAlignedLoad(ctx.world_age_field, Align(sizeof(size_t)))), - false, - jl_long_type); - jl_cgval_t fptr(ctx.builder.getContext()); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + Instruction *I = ctx.builder.CreateAlignedLoad(ctx.types().T_size, get_last_age_field(ctx), ctx.types().alignof_ptr); + jl_cgval_t world_age = mark_julia_type(ctx, ai.decorateInst(I), false, jl_long_type); + jl_cgval_t fptr; if (specF) fptr = mark_julia_type(ctx, specF, false, jl_voidpointer_type); else - fptr = mark_julia_type(ctx, (llvm::Value*)Constant::getNullValue(getSizeTy(ctx.builder.getContext())), false, jl_voidpointer_type); + fptr = mark_julia_type(ctx, (llvm::Value*)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, (jl_value_t*)env_t, nargs-4, &argv.data()[4]); + jl_cgval_t env = emit_new_struct(ctx, env_t, nargs-4, &argv.data()[4]); jl_cgval_t closure_fields[5] = { env, @@ -4939,7 +5594,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } return mark_julia_type(ctx, - emit_jlcall(ctx, jl_new_opaque_closure_jlcall_func, Constant::getNullValue(ctx.types().T_prjlvalue), argv.data(), nargs, JLCALL_F_CC), + emit_jlcall(ctx, jl_new_opaque_closure_jlcall_func, Constant::getNullValue(ctx.types().T_prjlvalue), argv.data(), nargs, julia_call), true, jl_any_type); } else if (head == jl_exc_sym) { @@ -4971,7 +5626,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) MDNode* MD = MDNode::get(ctx.builder.getContext(), MDs); CallInst *I = ctx.builder.CreateCall(prepare_call(jl_loopinfo_marker_func)); I->setMetadata("julia.loopinfo", MD); - return jl_cgval_t(ctx.builder.getContext()); + 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 @@ -4982,7 +5637,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) return mark_julia_const(ctx, bounds_check_enabled(ctx, jl_true) ? jl_true : jl_false); } else if (head == jl_gc_preserve_begin_sym) { - jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + SmallVector argv(nargs); for (size_t i = 0; i < nargs; ++i) { argv[i] = emit_expr(ctx, args[i]); } @@ -5002,7 +5657,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) Value *token = vals.empty() ? (Value*)ConstantTokenNone::get(ctx.builder.getContext()) : ctx.builder.CreateCall(prepare_call(gc_preserve_begin_func), vals); - jl_cgval_t tok(token, NULL, false, (jl_value_t*)jl_nothing_type, NULL, ctx.tbaa()); + jl_cgval_t tok(token, (jl_value_t*)jl_nothing_type, NULL); return tok; } else if (head == jl_gc_preserve_end_sym) { @@ -5033,67 +5688,53 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) jl_errorf("unsupported or misplaced expression \"%s\" in function %s", jl_symbol_name(head), ctx.name); } - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } JL_GCC_IGNORE_STOP // --- generate function bodies --- // gc frame emission -static void allocate_gc_frame(jl_codectx_t &ctx, BasicBlock *b0) +static void allocate_gc_frame(jl_codectx_t &ctx, BasicBlock *b0, bool or_new=false) { - // TODO: requires the runtime, but is generated unconditionally // allocate a placeholder gc instruction - ctx.pgcstack = ctx.builder.CreateCall(prepare_call(jlpgcstack_func)); + // this will require the runtime, but it gets deleted later if unused + ctx.topalloca = ctx.builder.CreateCall(prepare_call(or_new ? jladoptthread_func : jlpgcstack_func)); + ctx.pgcstack = ctx.topalloca; } static Value *get_current_task(jl_codectx_t &ctx) { - const int ptls_offset = offsetof(jl_task_t, gcstack); - return ctx.builder.CreateInBoundsGEP( - ctx.types().T_pjlvalue, emit_bitcast(ctx, ctx.pgcstack, ctx.types().T_ppjlvalue), - ConstantInt::get(getSizeTy(ctx.builder.getContext()), -(ptls_offset / sizeof(void *))), - "current_task"); + return get_current_task_from_pgcstack(ctx.builder, ctx.types().T_size, ctx.pgcstack); } // Get PTLS through current task. static Value *get_current_ptls(jl_codectx_t &ctx) { - return get_current_ptls_from_task(ctx.builder, get_current_task(ctx), ctx.tbaa().tbaa_gcframe); + return get_current_ptls_from_task(ctx.builder, ctx.types().T_size, get_current_task(ctx), ctx.tbaa().tbaa_gcframe); } -// Store world age at the entry block of the function. This function should be -// called right after `allocate_gc_frame` and there should be no context switch. -static void emit_last_age_field(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) { - auto ptls = get_current_task(ctx); - assert(ctx.builder.GetInsertBlock() == ctx.pgcstack->getParent()); - ctx.world_age_field = ctx.builder.CreateInBoundsGEP( - getSizeTy(ctx.builder.getContext()), - ctx.builder.CreateBitCast(ptls, getSizePtrTy(ctx.builder.getContext())), - ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_task_t, world_age) / sizeof(size_t)), + Value *ct = get_current_task(ctx); + return ctx.builder.CreateInBoundsGEP( + ctx.types().T_size, + ctx.builder.CreateBitCast(ct, ctx.types().T_size->getPointerTo()), + ConstantInt::get(ctx.types().T_size, offsetof(jl_task_t, world_age) / ctx.types().sizeof_ptr), "world_age"); } -// Get signal page through current task. -static Value *get_current_signal_page(jl_codectx_t &ctx) -{ - // return ctx.builder.CreateCall(prepare_call(reuse_signal_page_func)); - auto ptls = get_current_ptls(ctx); - int nthfield = offsetof(jl_tls_states_t, safepoint) / sizeof(void *); - return emit_nthptr_recast(ctx, ptls, nthfield, ctx.tbaa().tbaa_const, getSizePtrTy(ctx.builder.getContext())); -} - static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_codegen_params_t ¶ms) { + ++EmittedToJLInvokes; jl_codectx_t ctx(M->getContext(), params); std::string name; - raw_string_ostream(name) << "tojlinvoke" << globalUnique++; + raw_string_ostream(name) << "tojlinvoke" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); Function *f = Function::Create(ctx.types().T_jlfunc, GlobalVariable::InternalLinkage, name, M); - jl_init_function(f); - f->addFnAttr(Attribute::get(M->getContext(), "thunk")); + jl_init_function(f, params.TargetTriple); //f->setAlwaysInline(); ctx.f = f; // for jl_Module BasicBlock *b0 = BasicBlock::Create(ctx.builder.getContext(), "top", f); @@ -5101,7 +5742,9 @@ static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_cod Function *theFunc; Value *theFarg; auto invoke = jl_atomic_load_relaxed(&codeinst->invoke); - if (params.cache && invoke != NULL) { + bool cache_valid = params.cache; + + if (cache_valid && invoke != NULL) { StringRef theFptrName = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)invoke, codeinst); theFunc = cast( M->getOrInsertFunction(theFptrName, jlinvoke_func->_type(ctx.builder.getContext())).getCallee()); @@ -5119,13 +5762,22 @@ static Function *emit_tojlinvoke(jl_code_instance_t *codeinst, Module *M, jl_cod return f; } +static Type *get_returnroots_type(jl_codectx_t &ctx, unsigned rootcount) { + return ArrayType::get(ctx.types().T_prjlvalue, rootcount); +} + +static Type *get_unionbytes_type(LLVMContext &C, unsigned unionbytes) { + return ArrayType::get(getInt8Ty(C), unionbytes); +} + static void emit_cfunc_invalidate( Function *gf_thunk, jl_returninfo_t::CallingConv cc, unsigned return_roots, - jl_value_t *calltype, jl_value_t *rettype, + jl_value_t *calltype, jl_value_t *rettype, bool is_for_opaque_closure, size_t nargs, jl_codegen_params_t ¶ms, Function *target) { + ++EmittedCFuncInvalidates; jl_codectx_t ctx(gf_thunk->getParent()->getContext(), params); ctx.f = gf_thunk; @@ -5136,15 +5788,26 @@ static void emit_cfunc_invalidate( allocate_gc_frame(ctx, b0); Function::arg_iterator AI = gf_thunk->arg_begin(); - jl_cgval_t *myargs = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + SmallVector myargs(nargs); if (cc == jl_returninfo_t::SRet || cc == jl_returninfo_t::Union) ++AI; if (return_roots) ++AI; for (size_t i = 0; i < nargs; i++) { jl_value_t *jt = jl_nth_slot_type(calltype, i); - bool isboxed = deserves_argbox(jt); - Type *et = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jt); + // n.b. specTypes is required to be a datatype by construction for specsig + bool isboxed = false; + Type *et; + if (i == 0 && is_for_opaque_closure) { + et = PointerType::get(ctx.types().T_jlvalue, AddressSpace::Derived); + } + else if (deserves_argbox(jt)) { + et = ctx.types().T_prjlvalue; + isboxed = true; + } + else { + et = julia_type_to_llvm(ctx, jt); + } if (is_uniquerep_Type(jt)) { myargs[i] = mark_julia_const(ctx, jl_tparam0(jt)); } @@ -5156,8 +5819,8 @@ static void emit_cfunc_invalidate( Value *arg_v = &*AI; ++AI; Type *at = arg_v->getType(); - if (!isboxed && et->isAggregateType()) { - myargs[i] = mark_julia_slot(arg_v, jt, NULL, ctx.tbaa(), ctx.tbaa().tbaa_const); + if ((i == 0 && is_for_opaque_closure) || (!isboxed && et->isAggregateType())) { + myargs[i] = mark_julia_slot(arg_v, jt, NULL, ctx.tbaa().tbaa_const); } else { assert(at == et); @@ -5167,7 +5830,7 @@ static void emit_cfunc_invalidate( } } assert(AI == gf_thunk->arg_end()); - Value *gf_ret = emit_jlcall(ctx, target, nullptr, myargs, nargs, JLCALL_F_CC); + Value *gf_ret = emit_jlcall(ctx, target, nullptr, myargs.data(), 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"); @@ -5191,17 +5854,19 @@ static void emit_cfunc_invalidate( case jl_returninfo_t::SRet: { if (return_roots) { Value *root1 = gf_thunk->arg_begin() + 1; // root1 has type [n x {}*]* - root1 = ctx.builder.CreateConstInBoundsGEP2_32(root1->getType()->getPointerElementType(), root1, 0, 0); + assert(cast(root1->getType())->isOpaqueOrPointeeTypeMatches(get_returnroots_type(ctx, return_roots))); + root1 = ctx.builder.CreateConstInBoundsGEP2_32(get_returnroots_type(ctx, return_roots), root1, 0, 0); ctx.builder.CreateStore(gf_ret, root1); } - emit_memcpy(ctx, &*gf_thunk->arg_begin(), nullptr, gf_ret, nullptr, jl_datatype_size(rettype), julia_alignment(rettype)); + emit_memcpy(ctx, &*gf_thunk->arg_begin(), jl_aliasinfo_t::fromTBAA(ctx, nullptr), gf_ret, + jl_aliasinfo_t::fromTBAA(ctx, nullptr), jl_datatype_size(rettype), julia_alignment(rettype)); ctx.builder.CreateRetVoid(); break; } case jl_returninfo_t::Union: { Type *retty = gf_thunk->getReturnType(); Value *gf_retval = UndefValue::get(retty); - Value *tindex = compute_box_tindex(ctx, emit_typeof_boxed(ctx, gf_retbox), (jl_value_t*)jl_any_type, rettype); + 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)); gf_retval = ctx.builder.CreateInsertValue(gf_retval, gf_ret, 0); gf_retval = ctx.builder.CreateInsertValue(gf_retval, tindex, 1); @@ -5218,20 +5883,22 @@ 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, + jl_value_t *calltype, jl_value_t *rettype, bool is_for_opaque_closure, size_t nargs, jl_codegen_params_t ¶ms) { - emit_cfunc_invalidate(gf_thunk, cc, return_roots, calltype, rettype, nargs, params, + emit_cfunc_invalidate(gf_thunk, cc, return_roots, calltype, rettype, is_for_opaque_closure, nargs, params, prepare_call_in(gf_thunk->getParent(), jlapplygeneric_func)); } +#include 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) { + ++GeneratedCFuncWrappers; // Generate a c-callable wrapper assert(into); size_t nargs = sig.nccallargs; @@ -5249,18 +5916,27 @@ static Function* gen_cfun_wrapper( if (lam && params.cache) { // TODO: this isn't ideal to be unconditionally calling type inference (and compile) from here codeinst = jl_compile_method_internal(lam, world); - assert(codeinst->invoke); - if (codeinst->invoke == jl_fptr_args_addr) { - callptr = codeinst->specptr.fptr; + auto invoke = jl_atomic_load_acquire(&codeinst->invoke); + auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); + assert(invoke); + if (fptr) { + while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + invoke = jl_atomic_load_relaxed(&codeinst->invoke); + } + // WARNING: this invoke load is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this. + if (invoke == jl_fptr_args_addr) { + callptr = fptr; calltype = 1; } - else if (codeinst->invoke == jl_fptr_const_return_addr) { + else if (invoke == jl_fptr_const_return_addr) { // don't need the fptr callptr = (void*)codeinst->rettype_const; calltype = 2; } - else if (codeinst->isspecsig) { - callptr = codeinst->specptr.fptr; + else if (jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b1) { + callptr = fptr; calltype = 3; } astrt = codeinst->rettype; @@ -5274,9 +5950,9 @@ static Function* gen_cfun_wrapper( } std::string funcName; - raw_string_ostream(funcName) << "jlcapi_" << name << "_" << globalUnique++; + raw_string_ostream(funcName) << "jlcapi_" << name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); - Module *M = into; + Module *M = into; // Safe because ctx lock is held by params AttributeList attributes = sig.attributes; FunctionType *functype; if (nest) { @@ -5290,13 +5966,8 @@ static Function* gen_cfun_wrapper( // we are adding the extra nest parameter after sret arg. std::vector> newAttributes; newAttributes.reserve(attributes.getNumAttrSets() + 1); -#if JL_LLVM_VERSION >= 140000 auto it = *attributes.indexes().begin(); const auto it_end = *attributes.indexes().end(); -#else - auto it = attributes.index_begin(); - const auto it_end = attributes.index_end(); -#endif // Skip past FunctionIndex if (it == AttributeList::AttrIndex::FunctionIndex) { @@ -5311,7 +5982,7 @@ static Function* gen_cfun_wrapper( } // Add the new nest attribute - AttrBuilder attrBuilder; + AttrBuilder attrBuilder(M->getContext()); attrBuilder.addAttribute(Attribute::Nest); newAttributes.emplace_back(it, AttributeSet::get(M->getContext(), attrBuilder)); @@ -5340,8 +6011,8 @@ static Function* gen_cfun_wrapper( Function *cw = Function::Create(functype, GlobalVariable::ExternalLinkage, funcName, M); - cw->setAttributes(attributes); - jl_init_function(cw); + jl_init_function(cw, params.TargetTriple); + cw->setAttributes(AttributeList::get(M->getContext(), {attributes, cw->getAttributes()})); jl_codectx_t ctx(M->getContext(), params); ctx.f = cw; @@ -5353,42 +6024,35 @@ static Function* gen_cfun_wrapper( ctx.builder.SetInsertPoint(b0); DebugLoc noDbg; ctx.builder.SetCurrentDebugLocation(noDbg); - allocate_gc_frame(ctx, b0); - emit_last_age_field(ctx); - - Value *dummy_world = ctx.builder.CreateAlloca(getSizeTy(ctx.builder.getContext())); - Value *have_tls = ctx.builder.CreateIsNotNull(ctx.pgcstack); - // TODO: in the future, try to initialize a full TLS context here - // for now, just use a dummy field to avoid a branch in this function - ctx.world_age_field = ctx.builder.CreateSelect(have_tls, ctx.world_age_field, dummy_world); - Value *last_age = tbaa_decorate(ctx.tbaa().tbaa_gcframe, ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), ctx.world_age_field, Align(sizeof(size_t)))); - Value *world_v = ctx.builder.CreateAlignedLoad(getSizeTy(ctx.builder.getContext()), - prepare_global_in(jl_Module, jlgetworld_global), Align(sizeof(size_t))); + allocate_gc_frame(ctx, b0, true); + + Value *world_age_field = get_last_age_field(ctx); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + Value *last_age = ai.decorateInst( + ctx.builder.CreateAlignedLoad(ctx.types().T_size, world_age_field, ctx.types().alignof_ptr)); + + Value *world_v = ctx.builder.CreateAlignedLoad(ctx.types().T_size, + prepare_global_in(jl_Module, jlgetworld_global), ctx.types().alignof_ptr); cast(world_v)->setOrdering(AtomicOrdering::Acquire); Value *age_ok = NULL; if (calltype) { LoadInst *lam_max = ctx.builder.CreateAlignedLoad( - getSizeTy(ctx.builder.getContext()), + ctx.types().T_size, ctx.builder.CreateConstInBoundsGEP1_32( - getSizeTy(ctx.builder.getContext()), - emit_bitcast(ctx, literal_pointer_val(ctx, (jl_value_t*)codeinst), getSizePtrTy(ctx.builder.getContext())), - offsetof(jl_code_instance_t, max_world) / sizeof(size_t)), - Align(sizeof(size_t))); - // XXX: age is always OK if we don't have a TLS. This is a hack required due to `@threadcall` abuse. - // and adds quite a bit of complexity here, even though it's still wrong - // (anything that tries to interact with the runtime will fault) + ctx.types().T_size, + emit_bitcast(ctx, literal_pointer_val(ctx, (jl_value_t*)codeinst), ctx.types().T_size->getPointerTo()), + offsetof(jl_code_instance_t, max_world) / ctx.types().sizeof_ptr), + ctx.types().alignof_ptr); age_ok = ctx.builder.CreateICmpUGE(lam_max, world_v); - world_v = ctx.builder.CreateSelect(ctx.builder.CreateOr(have_tls, age_ok), world_v, lam_max); - age_ok = ctx.builder.CreateOr(ctx.builder.CreateNot(have_tls), age_ok); } - ctx.builder.CreateStore(world_v, ctx.world_age_field); + ctx.builder.CreateStore(world_v, world_age_field); // first emit code to record the arguments Function::arg_iterator AI = cw->arg_begin(); Value *sretPtr = sig.sret ? &*AI++ : NULL; Value *nestPtr = nest ? &*AI++ : NULL; - jl_cgval_t *inputargs = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * (nargs + 1)); + SmallVector inputargs(nargs + 1); if (ff) { // we need to pass the function object even if (even though) it is a singleton inputargs[0] = mark_julia_const(ctx, ff); @@ -5467,7 +6131,7 @@ static Function* gen_cfun_wrapper( 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); + Value *isrtboxed = ctx.builder.CreateIsNull(val); // XXX: this is the wrong condition and should be inspecting runtime_dt intead ctx.builder.CreateCondBr(isrtboxed, boxedBB, loadBB); ctx.builder.SetInsertPoint(boxedBB); Value *p1 = ctx.builder.CreateBitCast(val, ctx.types().T_pjlvalue); @@ -5540,7 +6204,7 @@ static Function* gen_cfun_wrapper( // Create the call bool jlfunc_sret; - jl_cgval_t retval(ctx.builder.getContext()); + jl_cgval_t retval; if (calltype == 2) { nargs = 0; // arguments not needed -- TODO: not really true, should emit an age_ok test and jlcall jlfunc_sret = false; @@ -5556,13 +6220,12 @@ static Function* gen_cfun_wrapper( if (!theFptr) { theFptr = Function::Create(ctx.types().T_jlfunc, GlobalVariable::ExternalLinkage, fname, jl_Module); - jl_init_function(theFptr); + jl_init_function(theFptr, ctx.emission_context.TargetTriple); + addRetAttr(theFptr, Attribute::NonNull); } else { assert(theFptr->getFunctionType() == ctx.types().T_jlfunc); } - addRetAttr(theFptr, Attribute::NonNull); - theFptr->addFnAttr(Attribute::get(ctx.builder.getContext(), "thunk")); } BasicBlock *b_generic, *b_jlcall, *b_after; Value *ret_jlcall; @@ -5576,11 +6239,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, JLCALL_F_CC); + ret_jlcall = emit_jlcall(ctx, theFptr, theF, &inputargs[1], nargs, julia_call); ctx.builder.CreateBr(b_after); ctx.builder.SetInsertPoint(b_generic); } - Value *ret = emit_jlcall(ctx, jlapplygeneric_func, NULL, inputargs, nargs + 1, JLCALL_F_CC); + Value *ret = emit_jlcall(ctx, jlapplygeneric_func, NULL, inputargs.data(), nargs + 1, julia_call); if (age_ok) { ctx.builder.CreateBr(b_after); ctx.builder.SetInsertPoint(b_after); @@ -5596,8 +6259,8 @@ static Function* gen_cfun_wrapper( assert(calltype == 3); // emit a specsig call StringRef protoname = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)callptr, codeinst); - jl_returninfo_t returninfo = get_specsig_function(ctx, M, protoname, lam->specTypes, astrt, is_opaque_closure); - FunctionType *cft = returninfo.decl->getFunctionType(); + jl_returninfo_t returninfo = get_specsig_function(ctx, M, NULL, protoname, lam->specTypes, astrt, is_opaque_closure); + FunctionType *cft = returninfo.decl.getFunctionType(); jlfunc_sret = (returninfo.cc == jl_returninfo_t::SRet); // TODO: Can use use emit_call_specfun_other here? @@ -5609,12 +6272,18 @@ static Function* gen_cfun_wrapper( result = emit_bitcast(ctx, sretPtr, cft->getParamType(0)); } else { - result = emit_static_alloca(ctx, cft->getParamType(0)->getPointerElementType()); + if (jlfunc_sret) { + result = emit_static_alloca(ctx, getAttributeAtIndex(returninfo.attrs, 1, Attribute::StructRet).getValueAsType()); + assert(cast(result->getType())->hasSameElementTypeAs(cast(cft->getParamType(0)))); + } else { + result = emit_static_alloca(ctx, get_unionbytes_type(ctx.builder.getContext(), returninfo.union_bytes)); + assert(cast(result->getType())->hasSameElementTypeAs(cast(cft->getParamType(0)))); + } } args.push_back(result); } if (returninfo.return_roots) { - AllocaInst *return_roots = emit_static_alloca(ctx, ArrayType::get(ctx.types().T_prjlvalue, returninfo.return_roots)); + AllocaInst *return_roots = emit_static_alloca(ctx, get_returnroots_type(ctx, returninfo.return_roots)); args.push_back(return_roots); } for (size_t i = 0; i < nargs + 1; i++) { @@ -5623,6 +6292,7 @@ static Function* gen_cfun_wrapper( Value *arg; jl_value_t *spect = (i == 0 && is_opaque_closure) ? (jl_value_t*)jl_any_type : jl_nth_slot_type(lam->specTypes, i); + // n.b. specTypes is required to be a datatype by construction for specsig bool isboxed = deserves_argbox(spect); Type *T = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, spect); if (is_uniquerep_Type(spect)) { @@ -5648,24 +6318,25 @@ static Function* gen_cfun_wrapper( // add to argument list args.push_back(arg); } - Value *theFptr = returninfo.decl; + Value *theFptr = returninfo.decl.getCallee(); assert(theFptr); if (age_ok) { funcName += "_gfthunk"; - Function *gf_thunk = Function::Create(returninfo.decl->getFunctionType(), + Function *gf_thunk = Function::Create(returninfo.decl.getFunctionType(), GlobalVariable::InternalLinkage, funcName, M); - gf_thunk->setAttributes(returninfo.decl->getAttributes()); - jl_init_function(gf_thunk); + jl_init_function(gf_thunk, ctx.emission_context.TargetTriple); + gf_thunk->setAttributes(AttributeList::get(M->getContext(), {returninfo.attrs, gf_thunk->getAttributes()})); // 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, 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); theFptr = ctx.builder.CreateSelect(age_ok, theFptr, gf_thunk); } + assert(cast(theFptr->getType())->isOpaqueOrPointeeTypeMatches(returninfo.decl.getFunctionType())); CallInst *call = ctx.builder.CreateCall( - cast(theFptr->getType()->getPointerElementType()), + returninfo.decl.getFunctionType(), theFptr, ArrayRef(args)); - call->setAttributes(returninfo.decl->getAttributes()); + call->setAttributes(returninfo.attrs); switch (returninfo.cc) { case jl_returninfo_t::Boxed: retval = mark_julia_type(ctx, call, true, astrt); @@ -5674,7 +6345,7 @@ static Function* gen_cfun_wrapper( retval = mark_julia_type(ctx, call, false, astrt); break; case jl_returninfo_t::SRet: - retval = mark_julia_slot(result, astrt, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + retval = mark_julia_slot(result, astrt, NULL, ctx.tbaa().tbaa_stack); break; case jl_returninfo_t::Union: { Value *box = ctx.builder.CreateExtractValue(call, 0); @@ -5688,14 +6359,13 @@ static Function* gen_cfun_wrapper( retval = mark_julia_slot(derived, astrt, tindex, - ctx.tbaa(), ctx.tbaa().tbaa_stack); assert(box->getType() == ctx.types().T_prjlvalue); retval.Vboxed = box; break; } case jl_returninfo_t::Ghosts: - retval = mark_julia_slot(NULL, astrt, call, ctx.tbaa(), ctx.tbaa().tbaa_stack); + retval = mark_julia_slot(NULL, astrt, call, ctx.tbaa().tbaa_stack); break; } } @@ -5717,8 +6387,6 @@ static Function* gen_cfun_wrapper( } else if (!type_is_ghost(sig.lrt)) { Type *prt = sig.prt; - if (sig.sret) - prt = sig.fargt_sig[0]->getContainedType(0); // sret is a PointerType bool issigned = jl_signed_type && jl_subtype(declrt, (jl_value_t*)jl_signed_type); Value *v = emit_unbox(ctx, sig.lrt, retval, retval.typ); r = llvm_type_rewrite(ctx, v, prt, issigned); @@ -5731,7 +6399,7 @@ static Function* gen_cfun_wrapper( r = NULL; } - ctx.builder.CreateStore(last_age, ctx.world_age_field); + ctx.builder.CreateStore(last_age, world_age_field); ctx.builder.CreateRet(r); ctx.builder.SetCurrentDebugLocation(noDbg); @@ -5748,7 +6416,7 @@ static Function* gen_cfun_wrapper( FunctionType::get(getInt8PtrTy(ctx.builder.getContext()), { getInt8PtrTy(ctx.builder.getContext()), ctx.types().T_ppjlvalue }, false), GlobalVariable::ExternalLinkage, funcName, M); - jl_init_function(cw_make); + jl_init_function(cw_make, ctx.emission_context.TargetTriple); BasicBlock *b0 = BasicBlock::Create(ctx.builder.getContext(), "top", cw_make); IRBuilder<> cwbuilder(b0); Function::arg_iterator AI = cw_make->arg_begin(); @@ -5784,7 +6452,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con if (jl_is_abstract_ref_type(declrt)) { declrt = jl_tparam0(declrt); if (!verify_ref_type(ctx, declrt, unionall_env, 0, "cfunction")) { - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } if (unionall_env) declrt = jl_rewrap_unionall(declrt, (jl_value_t*)unionall_env); @@ -5813,17 +6481,17 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con if (!err.empty()) { emit_error(ctx, "cfunction " + err); JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } if (rt != declrt && rt != (jl_value_t*)jl_any_type) - jl_add_method_root(ctx, rt); + rt = jl_ensure_rooted(ctx, rt); function_sig_t sig("cfunction", lrt, rt, retboxed, argt, unionall_env, false, CallingConv::C, false, &ctx.emission_context); assert(sig.fargt.size() + sig.sret == sig.fargt_sig.size()); if (!sig.err_msg.empty()) { emit_error(ctx, sig.err_msg); JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } // compute+verify the dispatch signature, and see if it depends on the environment sparams @@ -5838,7 +6506,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con jargty = jl_tparam0(jargty); if (!verify_ref_type(ctx, jargty, unionall_env, i + 1, "cfunction")) { JL_GC_POP(); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } } if (unionall_env && jl_has_typevar_from_unionall(jargty, unionall_env)) { @@ -5853,19 +6521,19 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con sigt = NULL; } else { - sigt = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)sigt); + sigt = jl_apply_tuple_type((jl_svec_t*)sigt); } if (sigt && !(unionall_env && jl_has_typevar_from_unionall(rt, unionall_env))) { unionall_env = NULL; } bool nest = (!fexpr_rt.constant || unionall_env); -#if defined(_CPU_AARCH64_) || defined(_CPU_ARM_) || defined(_CPU_PPC64_) - if (nest) { - emit_error(ctx, "cfunction: closures are not supported on this platform"); - return jl_cgval_t(ctx.builder.getContext()); + 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"); + return jl_cgval_t(); + } } -#endif size_t world = jl_atomic_load_acquire(&jl_world_counter); size_t min_valid = 0; size_t max_valid = ~(size_t)0; @@ -5889,9 +6557,9 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con for (size_t i = 0; i < n; i++) { jl_svecset(fill, i, jl_array_ptr_ref(closure_types, i)); } - jl_add_method_root(ctx, (jl_value_t*)fill); + fill = (jl_svec_t*)jl_ensure_rooted(ctx, (jl_value_t*)fill); } - Type *T_htable = ArrayType::get(getSizeTy(ctx.builder.getContext()), sizeof(htable_t) / sizeof(void*)); + Type *T_htable = ArrayType::get(ctx.types().T_size, sizeof(htable_t) / sizeof(void*)); Value *cache = new GlobalVariable(*jl_Module, T_htable, false, GlobalVariable::PrivateLinkage, ConstantAggregateZero::get(T_htable)); @@ -5907,22 +6575,22 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con outboxed = true; } else { - F = ctx.builder.CreatePtrToInt(F, getSizeTy(ctx.builder.getContext())); + F = ctx.builder.CreatePtrToInt(F, ctx.types().T_size); 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_size(output_type), - literal_pointer_val(ctx, (jl_value_t*)output_type)); - Value *derived_strct = emit_bitcast(ctx, decay_derived(ctx, strct), getSizePtrTy(ctx.builder.getContext())); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)output_type); + Value *derived_strct = emit_bitcast(ctx, decay_derived(ctx, strct), ctx.types().T_size->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), output_type); - tbaa_decorate(tbaa, ctx.builder.CreateStore(F, derived_strct)); - tbaa_decorate(tbaa, ctx.builder.CreateStore( - ctx.builder.CreatePtrToInt(literal_pointer_val(ctx, fexpr_rt.constant), getSizeTy(ctx.builder.getContext())), - ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), derived_strct, 1))); - tbaa_decorate(tbaa, ctx.builder.CreateStore(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), - ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), derived_strct, 2))); - tbaa_decorate(tbaa, ctx.builder.CreateStore(Constant::getNullValue(getSizeTy(ctx.builder.getContext())), - ctx.builder.CreateConstInBoundsGEP1_32(getSizeTy(ctx.builder.getContext()), derived_strct, 3))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(ctx.builder.CreateStore(F, derived_strct)); + ai.decorateInst(ctx.builder.CreateStore( + ctx.builder.CreatePtrToInt(literal_pointer_val(ctx, fexpr_rt.constant), ctx.types().T_size), + ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_size, derived_strct, 1))); + ai.decorateInst(ctx.builder.CreateStore(Constant::getNullValue(ctx.types().T_size), + ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_size, derived_strct, 2))); + ai.decorateInst(ctx.builder.CreateStore(Constant::getNullValue(ctx.types().T_size), + ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_size, derived_strct, 3))); F = strct; } } @@ -5932,8 +6600,9 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con // do codegen to create a C-callable alias/wrapper, or if sysimg_handle is set, // restore one from a loaded system image. -const char *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms, LLVMContext &ctxt) +const char *jl_generate_ccallable(LLVMOrcThreadSafeModuleRef llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms) { + ++GeneratedCCallables; jl_datatype_t *ft = (jl_datatype_t*)jl_tparam0(sigt); jl_value_t *ff = ft->instance; assert(ff); @@ -5944,7 +6613,7 @@ const char *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t crt = (jl_value_t*)jl_any_type; } bool toboxed; - Type *lcrt = _julia_struct_to_llvm(¶ms, ctxt, crt, &toboxed); + Type *lcrt = _julia_struct_to_llvm(¶ms, *params.tsctx.getContext(), crt, &toboxed); if (toboxed) lcrt = JuliaType::get_prjlvalue_ty(lcrt->getContext()); size_t nargs = jl_nparams(sigt)-1; @@ -5971,7 +6640,8 @@ const char *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t } else { jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world, &min_valid, &max_valid, 0); - gen_cfun_wrapper((Module*)llvmmod, params, sig, ff, name, declrt, lam, NULL, NULL, NULL); + //Safe b/c params holds context lock + gen_cfun_wrapper(unwrap(llvmmod)->getModuleUnlocked(), params, sig, ff, name, declrt, lam, NULL, NULL, NULL); } JL_GC_POP(); return name; @@ -5985,10 +6655,10 @@ const char *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlretty, const jl_returninfo_t &f, int retarg, StringRef funcName, Module *M, jl_codegen_params_t ¶ms) { - Function *w = Function::Create(JuliaType::get_jlfunc_ty(M->getContext()), GlobalVariable::ExternalLinkage, funcName, M); - addRetAttr(w, Attribute::NonNull); - w->addFnAttr(Attribute::get(M->getContext(), "thunk")); - jl_init_function(w); + ++GeneratedInvokeWrappers; + Function *w = Function::Create(get_func_sig(M->getContext()), GlobalVariable::ExternalLinkage, funcName, M); + jl_init_function(w, params.TargetTriple); + w->setAttributes(AttributeList::get(M->getContext(), {get_func_attrs(M->getContext()), w->getAttributes()})); Function::arg_iterator AI = w->arg_begin(); Value *funcArg = &*AI++; Value *argArray = &*AI++; @@ -6009,9 +6679,9 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret allocate_gc_frame(ctx, b0); // TODO: replace this with emit_call_specfun_other? - FunctionType *ftype = f.decl->getFunctionType(); + FunctionType *ftype = const_cast(f.decl).getFunctionType(); size_t nfargs = ftype->getNumParams(); - Value **args = (Value**) alloca(nfargs * sizeof(Value*)); + SmallVector args(nfargs); unsigned idx = 0; AllocaInst *result = NULL; switch (f.cc) { @@ -6020,7 +6690,8 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret case jl_returninfo_t::Ghosts: break; case jl_returninfo_t::SRet: - result = ctx.builder.CreateAlloca(ftype->getParamType(0)->getPointerElementType()); + assert(cast(ftype->getParamType(0))->isOpaqueOrPointeeTypeMatches(getAttributeAtIndex(f.attrs, 1, Attribute::StructRet).getValueAsType())); + result = ctx.builder.CreateAlloca(getAttributeAtIndex(f.attrs, 1, Attribute::StructRet).getValueAsType()); args[idx] = result; idx++; break; @@ -6042,20 +6713,27 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret for (size_t i = 0; i < jl_nparams(lam->specTypes) && idx < nfargs; ++i) { jl_value_t *ty = ((i == 0) && is_opaque_closure) ? (jl_value_t*)jl_any_type : jl_nth_slot_type(lam->specTypes, i); + // n.b. specTypes is required to be a datatype by construction for specsig bool isboxed = deserves_argbox(ty); Type *lty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, ty); if (type_is_ghost(lty) || is_uniquerep_Type(ty)) continue; Value *theArg; if (i == 0) { - theArg = funcArg; + // This function adapts from generic jlcall to OC specsig. Generic jlcall pointers + // come in as ::Tracked, but specsig expected ::Derived. + if (is_opaque_closure) + theArg = decay_derived(ctx, funcArg); + else + theArg = funcArg; } else { Value *argPtr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, argArray, i - 1); - theArg = maybe_mark_load_dereferenceable( + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + theArg = ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, argPtr, Align(sizeof(void*))), false, - ty); + ty)); } if (!isboxed) { theArg = decay_derived(ctx, emit_bitcast(ctx, theArg, PointerType::get(lty, 0))); @@ -6066,10 +6744,10 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret args[idx] = theArg; idx++; } - CallInst *call = ctx.builder.CreateCall(f.decl, ArrayRef(&args[0], nfargs)); - call->setAttributes(f.decl->getAttributes()); + CallInst *call = ctx.builder.CreateCall(f.decl, args); + call->setAttributes(f.attrs); - jl_cgval_t retval(ctx.builder.getContext()); + jl_cgval_t retval; if (retarg != -1) { Value *theArg; if (retarg == 0) @@ -6089,7 +6767,7 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret retval = mark_julia_type(ctx, call, false, jlretty); break; case jl_returninfo_t::SRet: - retval = mark_julia_slot(result, jlretty, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + retval = mark_julia_slot(result, jlretty, NULL, ctx.tbaa().tbaa_stack); break; case jl_returninfo_t::Union: // result is technically not right here, but `boxed` will only look at it @@ -6097,28 +6775,30 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret retval = mark_julia_slot(result, jlretty, ctx.builder.CreateExtractValue(call, 1), - ctx.tbaa(), ctx.tbaa().tbaa_stack); retval.Vboxed = ctx.builder.CreateExtractValue(call, 0); assert(retval.Vboxed->getType() == ctx.types().T_prjlvalue); break; case jl_returninfo_t::Ghosts: - retval = mark_julia_slot(NULL, jlretty, call, ctx.tbaa(), ctx.tbaa().tbaa_stack); + retval = mark_julia_slot(NULL, jlretty, call, ctx.tbaa().tbaa_stack); break; } } ctx.builder.CreateRet(boxed(ctx, retval)); - assert(!ctx.roots); return w; } -static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure) +static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure) { jl_returninfo_t props = {}; SmallVector fsig; Type *rt = NULL; Type *srt = NULL; - if (jl_is_structtype(jlrettype) && jl_is_datatype_singleton((jl_datatype_t*)jlrettype)) { + if (jlrettype == (jl_value_t*)jl_bottom_type) { + rt = getVoidTy(ctx.builder.getContext()); + props.cc = jl_returninfo_t::Register; + } + else if (jl_is_structtype(jlrettype) && jl_is_datatype_singleton((jl_datatype_t*)jlrettype)) { rt = getVoidTy(ctx.builder.getContext()); props.cc = jl_returninfo_t::Register; } @@ -6150,7 +6830,9 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, String if (tracked.count && !tracked.all) props.return_roots = tracked.count; props.cc = jl_returninfo_t::SRet; - fsig.push_back(rt->getPointerTo()); + // sret is always passed from alloca + assert(M); + fsig.push_back(rt->getPointerTo(M->getDataLayout().getAllocaAddrSpace())); srt = rt; rt = getVoidTy(ctx.builder.getContext()); } @@ -6162,100 +6844,124 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, String rt = ctx.types().T_prjlvalue; } - AttributeList attributes; // function declaration attributes + SmallVector attrs; // function declaration attributes if (props.cc == jl_returninfo_t::SRet) { assert(srt); - unsigned argno = 1; -#if JL_LLVM_VERSION < 120000 - attributes = attributes.addAttribute(ctx.builder.getContext(), argno, Attribute::StructRet); - (void)srt; // silence unused variable error -#else - Attribute sret = Attribute::getWithStructRetType(ctx.builder.getContext(), srt); - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, sret); -#endif - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, Attribute::NoAlias); - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, Attribute::NoCapture); + AttrBuilder param(ctx.builder.getContext()); + param.addStructRetAttr(srt); + param.addAttribute(Attribute::NoAlias); + param.addAttribute(Attribute::NoCapture); + param.addAttribute(Attribute::NoUndef); + attrs.push_back(AttributeSet::get(ctx.builder.getContext(), param)); + assert(fsig.size() == 1); } if (props.cc == jl_returninfo_t::Union) { - unsigned argno = 1; - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, Attribute::NoAlias); - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, Attribute::NoCapture); + AttrBuilder param(ctx.builder.getContext()); + param.addAttribute(Attribute::NoAlias); + param.addAttribute(Attribute::NoCapture); + param.addAttribute(Attribute::NoUndef); + attrs.push_back(AttributeSet::get(ctx.builder.getContext(), param)); + assert(fsig.size() == 1); } if (props.return_roots) { - fsig.push_back(ArrayType::get(ctx.types().T_prjlvalue, props.return_roots)->getPointerTo(0)); - unsigned argno = fsig.size(); - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, Attribute::NoAlias); - attributes = addAttributeAtIndex(attributes, ctx.builder.getContext(), argno, Attribute::NoCapture); + AttrBuilder param(ctx.builder.getContext()); + param.addAttribute(Attribute::NoAlias); + param.addAttribute(Attribute::NoCapture); + param.addAttribute(Attribute::NoUndef); + attrs.push_back(AttributeSet::get(ctx.builder.getContext(), param)); + fsig.push_back(get_returnroots_type(ctx, props.return_roots)->getPointerTo(0)); } for (size_t i = 0; i < jl_nparams(sig); i++) { jl_value_t *jt = jl_tparam(sig, i); + bool isboxed = false; + Type *ty = NULL; if (i == 0 && is_opaque_closure) { - jt = (jl_value_t*)jl_any_type; + ty = PointerType::get(ctx.types().T_jlvalue, AddressSpace::Derived); + isboxed = true; // true-ish anyway - we might not have the type tag + } + else { + if (is_uniquerep_Type(jt)) + continue; + isboxed = deserves_argbox(jt); + ty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jt); } - if (is_uniquerep_Type(jt)) - continue; - bool isboxed = deserves_argbox(jt); - Type *ty = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, jt); if (type_is_ghost(ty)) continue; - unsigned argno = fsig.size(); + AttrBuilder param(ctx.builder.getContext()); if (ty->isAggregateType()) { // aggregate types are passed by pointer - attributes = attributes.addParamAttribute(ctx.builder.getContext(), argno, Attribute::NoCapture); - attributes = attributes.addParamAttribute(ctx.builder.getContext(), argno, Attribute::ReadOnly); + param.addAttribute(Attribute::NoCapture); + param.addAttribute(Attribute::ReadOnly); ty = PointerType::get(ty, AddressSpace::Derived); } else if (isboxed && jl_is_immutable_datatype(jt)) { - attributes = attributes.addParamAttribute(ctx.builder.getContext(), argno, Attribute::ReadOnly); + param.addAttribute(Attribute::ReadOnly); } else if (jl_is_primitivetype(jt) && ty->isIntegerTy()) { bool issigned = jl_signed_type && jl_subtype(jt, (jl_value_t*)jl_signed_type); Attribute::AttrKind attr = issigned ? Attribute::SExt : Attribute::ZExt; - attributes = attributes.addParamAttribute(ctx.builder.getContext(), argno, attr); + param.addAttribute(attr); } + attrs.push_back(AttributeSet::get(ctx.builder.getContext(), param)); fsig.push_back(ty); } + AttributeSet FnAttrs; + AttributeSet RetAttrs; + if (jlrettype == (jl_value_t*)jl_bottom_type) + FnAttrs = FnAttrs.addAttribute(ctx.builder.getContext(), Attribute::NoReturn); + else if (rt == ctx.types().T_prjlvalue) + RetAttrs = RetAttrs.addAttribute(ctx.builder.getContext(), Attribute::NonNull); + AttributeList attributes = AttributeList::get(ctx.builder.getContext(), FnAttrs, RetAttrs, attrs); + FunctionType *ftype = FunctionType::get(rt, fsig, false); - Function *f = M ? cast_or_null(M->getNamedValue(name)) : NULL; - if (f == NULL) { - f = Function::Create(ftype, GlobalVariable::ExternalLinkage, name, M); - f->setAttributes(attributes); - jl_init_function(f); + if (fval == NULL) { + Function *f = M ? cast_or_null(M->getNamedValue(name)) : NULL; + if (f == NULL) { + f = Function::Create(ftype, GlobalVariable::ExternalLinkage, name, M); + jl_init_function(f, ctx.emission_context.TargetTriple); + f->setAttributes(AttributeList::get(f->getContext(), {attributes, f->getAttributes()})); + } + else { + assert(f->getFunctionType() == ftype); + } + fval = f; } else { - assert(f->getFunctionType() == ftype); + if (fval->getType()->isIntegerTy()) + fval = emit_inttoptr(ctx, fval, ftype->getPointerTo()); + else + fval = emit_bitcast(ctx, fval, ftype->getPointerTo()); } - if (rt == ctx.types().T_prjlvalue) - addRetAttr(f, Attribute::NonNull); - props.decl = f; + + props.decl = FunctionCallee(ftype, fval); + props.attrs = attributes; return props; } -static void emit_sret_roots(jl_codectx_t &ctx, bool isptr, Value *Src, Type *T, Value *Shadow, unsigned count) +static void emit_sret_roots(jl_codectx_t &ctx, bool isptr, Value *Src, Type *T, Value *Shadow, Type *ShadowT, unsigned count) { - if (isptr) - Src = maybe_decay_tracked(ctx, Src); - if (isptr && Src->getType()->getPointerElementType() != T) + if (isptr && !cast(Src->getType())->isOpaqueOrPointeeTypeMatches(T)) Src = ctx.builder.CreateBitCast(Src, T->getPointerTo(Src->getType()->getPointerAddressSpace())); - unsigned emitted = TrackWithShadow(Src, T, isptr, Shadow, ctx.builder); + unsigned emitted = TrackWithShadow(Src, T, isptr, Shadow, ShadowT, ctx.builder); //This comes from Late-GC-Lowering?? assert(emitted == count); (void)emitted; (void)count; } static DISubroutineType * -get_specsig_di(jl_codectx_t &ctx, jl_value_t *rt, jl_value_t *sig, DIBuilder &dbuilder) +get_specsig_di(jl_codectx_t &ctx, jl_debugcache_t &debuginfo, jl_value_t *rt, jl_value_t *sig, DIBuilder &dbuilder) { size_t nargs = jl_nparams(sig); // TODO: if this is a Varargs function, our debug info for the `...` var may be misleading std::vector ditypes(nargs + 1); - ditypes[0] = julia_type_to_di(ctx, rt, &dbuilder, false); + ditypes[0] = julia_type_to_di(ctx, debuginfo, rt, &dbuilder, false); for (size_t i = 0; i < nargs; i++) { jl_value_t *jt = jl_tparam(sig, i); - ditypes[i + 1] = julia_type_to_di(ctx, jt, &dbuilder, false); + ditypes[i + 1] = julia_type_to_di(ctx, debuginfo, jt, &dbuilder, false); } return dbuilder.createSubroutineType(dbuilder.getOrCreateTypeArray(ditypes)); } +/* aka Core.Compiler.tuple_tfunc */ static jl_datatype_t *compute_va_type(jl_method_instance_t *lam, size_t nreq) { size_t nvargs = jl_nparams(lam->specTypes)-nreq; @@ -6263,33 +6969,62 @@ static jl_datatype_t *compute_va_type(jl_method_instance_t *lam, size_t nreq) JL_GC_PUSH1(&tupargs); for (size_t i = nreq; i < jl_nparams(lam->specTypes); ++i) { jl_value_t *argType = jl_nth_slot_type(lam->specTypes, i); + // n.b. specTypes is required to be a datatype by construction for specsig + if (is_uniquerep_Type(argType)) + argType = jl_typeof(jl_tparam0(argType)); + else if (jl_has_intersect_type_not_kind(argType)) { + jl_value_t *ts[2] = {argType, (jl_value_t*)jl_type_type}; + argType = jl_type_union(ts, 2); + } jl_svecset(tupargs, i-nreq, argType); } - jl_datatype_t *typ = jl_apply_tuple_type(tupargs); + jl_value_t *typ = jl_apply_tuple_type(tupargs); JL_GC_POP(); - return typ; + return (jl_datatype_t*)typ; +} + +static std::string get_function_name(bool specsig, bool needsparams, const char *unadorned_name, const Triple &TargetTriple) +{ + std::string _funcName; + raw_string_ostream funcName(_funcName); + // try to avoid conflicts in the global symbol table + if (specsig) + funcName << "julia_"; // api 5 + else if (needsparams) + funcName << "japi3_"; + else + funcName << "japi1_"; + if (TargetTriple.isOSLinux()) { + if (unadorned_name[0] == '@') + unadorned_name++; + } + funcName << unadorned_name << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1); + return funcName.str(); } // Compile to LLVM IR, using a specialized signature if applicable. -static std::pair, jl_llvm_functions_t> +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, - LLVMContext &ctxt) + jl_codegen_params_t ¶ms) { + ++EmittedFunctions; // step 1. unpack AST and allocate codegen context for this function jl_llvm_functions_t declarations; - jl_codectx_t ctx(ctxt, params); - JL_GC_PUSH2(&ctx.code, &ctx.roots); + jl_codectx_t ctx(*params.tsctx.getContext(), params); + jl_datatype_t *vatyp = NULL; + JL_GC_PUSH2(&ctx.code, &vatyp); ctx.code = src->code; + ctx.source = src; std::map labels; bool toplevel = false; ctx.module = jl_is_method(lam->def.method) ? lam->def.method->module : lam->def.module; ctx.linfo = lam; - ctx.name = name_from_method_instance(lam); + ctx.name = TSM.getModuleUnlocked()->getModuleIdentifier().data(); size_t nreq = 0; int va = 0; if (jl_is_method(lam->def.method)) { @@ -6306,13 +7041,12 @@ static std::pair, jl_llvm_functions_t> } ctx.nReqArgs = nreq; if (va) { - jl_sym_t *vn = (jl_sym_t*)jl_array_ptr_ref(src->slotnames, ctx.nargs - 1); + jl_sym_t *vn = slot_symbol(ctx, ctx.nargs-1); if (vn != jl_unused_sym) ctx.vaSlot = ctx.nargs - 1; } toplevel = !jl_is_method(lam->def.method); ctx.rettype = jlrettype; - ctx.source = src; ctx.funcName = ctx.name; ctx.spvals_ptr = NULL; jl_array_t *stmts = ctx.code; @@ -6335,7 +7069,7 @@ static std::pair, jl_llvm_functions_t> 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_long(jl_fieldref(locinfo, 3)); + toplineno = jl_unbox_int32(jl_fieldref(locinfo, 3)); } if (ctx.file.empty()) ctx.file = ""; @@ -6357,7 +7091,8 @@ static std::pair, jl_llvm_functions_t> // create SAvalue locations for SSAValue objects ctx.ssavalue_assigned.assign(n_ssavalues, false); - ctx.SAvalues.assign(n_ssavalues, jl_cgval_t(ctx.builder.getContext())); + ctx.SAvalues.assign(n_ssavalues, jl_cgval_t()); + ctx.ssavalue_usecount.assign(n_ssavalues, 0); bool specsig, needsparams; std::tie(specsig, needsparams) = uses_specsig(lam, jlrettype, params.params->prefer_specsig); @@ -6369,10 +7104,12 @@ static std::pair, jl_llvm_functions_t> for (i = 0; i < nreq; i++) { jl_varinfo_t &varinfo = ctx.slots[i]; varinfo.isArgument = true; - jl_sym_t *argname = (jl_sym_t*)jl_array_ptr_ref(src->slotnames, i); + jl_sym_t *argname = slot_symbol(ctx, i); if (argname == jl_unused_sym) continue; jl_value_t *ty = jl_nth_slot_type(lam->specTypes, i); + // TODO: jl_nth_slot_type should call jl_rewrap_unionall + // specTypes is required to be a datatype by construction for specsig, but maybe not otherwise // OpaqueClosure implicitly loads the env if (i == 0 && ctx.is_opaque_closure) { if (jl_is_array(src->slottypes)) { @@ -6387,7 +7124,7 @@ static std::pair, jl_llvm_functions_t> if (va && ctx.vaSlot != -1) { jl_varinfo_t &varinfo = ctx.slots[ctx.vaSlot]; varinfo.isArgument = true; - jl_datatype_t *vatyp = specsig ? compute_va_type(lam, nreq) : (jl_tuple_type); + vatyp = specsig ? compute_va_type(lam, nreq) : (jl_tuple_type); varinfo.value = mark_julia_type(ctx, (Value*)NULL, false, vatyp); } @@ -6412,33 +7149,22 @@ static std::pair, jl_llvm_functions_t> if (!specsig) ctx.nReqArgs--; // function not part of argArray in jlcall - std::string _funcName; - raw_string_ostream funcName(_funcName); - // try to avoid conflicts in the global symbol table - if (specsig) - funcName << "julia_"; // api 5 - else if (needsparams) - funcName << "japi3_"; - else - funcName << "japi1_"; - const char* unadorned_name = ctx.name; -#if defined(_OS_LINUX_) - if (unadorned_name[0] == '@') - unadorned_name++; -#endif - funcName << unadorned_name << "_" << globalUnique++; - declarations.specFunctionObject = funcName.str(); + std::string _funcName = get_function_name(specsig, needsparams, ctx.name, ctx.emission_context.TargetTriple); + declarations.specFunctionObject = _funcName; // allocate Function declarations and wrapper objects - Module *M = _jl_create_llvm_module(ctx.name, ctx.builder.getContext(), ctx.params); + //Safe because params holds ctx lock + Module *M = TSM.getModuleUnlocked(); + jl_debugcache_t debuginfo; + debuginfo.initialize(M); jl_returninfo_t returninfo = {}; Function *f = NULL; bool has_sret = false; if (specsig) { // assumes !va and !needsparams - returninfo = get_specsig_function(ctx, M, declarations.specFunctionObject, lam->specTypes, jlrettype, ctx.is_opaque_closure); - f = returninfo.decl; + returninfo = get_specsig_function(ctx, M, NULL, declarations.specFunctionObject, lam->specTypes, jlrettype, ctx.is_opaque_closure); + f = cast(returninfo.decl.getCallee()); has_sret = (returninfo.cc == jl_returninfo_t::SRet || returninfo.cc == jl_returninfo_t::Union); - jl_init_function(f); + jl_init_function(f, ctx.emission_context.TargetTriple); // common pattern: see if all return statements are an argument in that // case the apply-generic call can re-use the original box for the return @@ -6465,103 +7191,77 @@ static std::pair, jl_llvm_functions_t> }(); std::string wrapName; - raw_string_ostream(wrapName) << "jfptr_" << unadorned_name << "_" << globalUnique++; + raw_string_ostream(wrapName) << "jfptr_" << ctx.name << "_" << jl_atomic_fetch_add(&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) + // TODO: add attributes: dereferenceable + // TODO: (if needsparams) add attributes: dereferenceable, readonly, nocapture } else { f = Function::Create(needsparams ? ctx.types().T_jlfuncparams : ctx.types().T_jlfunc, GlobalVariable::ExternalLinkage, declarations.specFunctionObject, M); - jl_init_function(f); - addRetAttr(f, Attribute::NonNull); - f->addFnAttr(Attribute::get(ctx.builder.getContext(), "thunk")); - // TODO: (if needsparams) add attributes: dereferenceable, readonly, nocapture - // TODO: add attributes: dereferenceable, readonly, nocapture - e.g. maybe_mark_argument_dereferenceable(Arg, argType); - // TODO: add attributes: dereferenceable, readonly, nocapture + jl_init_function(f, ctx.emission_context.TargetTriple); + f->setAttributes(AttributeList::get(ctx.builder.getContext(), {get_func_attrs(ctx.builder.getContext()), f->getAttributes()})); returninfo.decl = f; declarations.functionObject = needsparams ? "jl_fptr_sparam" : "jl_fptr_args"; } + AttrBuilder FnAttrs(ctx.builder.getContext(), f->getAttributes().getFnAttrs()); + AttrBuilder RetAttrs(ctx.builder.getContext(), f->getAttributes().getRetAttrs()); + if (jlrettype == (jl_value_t*)jl_bottom_type) - f->setDoesNotReturn(); + FnAttrs.addAttribute(Attribute::NoReturn); #ifdef USE_POLLY - if (!jl_has_meta(stmts, jl_polly_sym) || jl_options.polly == JL_OPTIONS_POLLY_OFF) { - f->addFnAttr(polly::PollySkipFnAttr); - } + if (!jl_has_meta(stmts, jl_polly_sym) || jl_options.polly == JL_OPTIONS_POLLY_OFF) + FnAttrs.addAttribute(polly::PollySkipFnAttr); #endif - if (jl_has_meta(stmts, jl_noinline_sym)) { - f->addFnAttr(Attribute::NoInline); - } - - if (returninfo.cc == jl_returninfo_t::Union) { - addAttributeAtIndex(f, 1, Attribute::getWithDereferenceableBytes(ctx.builder.getContext(), returninfo.union_bytes)); - addAttributeAtIndex(f, 1, Attribute::getWithAlignment(ctx.builder.getContext(), Align(returninfo.union_align))); - } + if (src->inlining == 2) + FnAttrs.addAttribute(Attribute::NoInline); #ifdef JL_DEBUG_BUILD - f->addFnAttr(Attribute::StackProtectStrong); + FnAttrs.addAttribute(Attribute::StackProtectStrong); #endif #ifdef _COMPILER_TSAN_ENABLED_ // TODO: enable this only when a argument like `-race` is passed to Julia // add a macro for no_sanitize_thread - f->addFnAttr(llvm::Attribute::SanitizeThread); + FnAttrs.addAttribute(llvm::Attribute::SanitizeThread); #endif // add the optimization level specified for this module, if any int optlevel = jl_get_module_optlevel(ctx.module); if (optlevel >= 0 && optlevel <= 3) { static const char* const optLevelStrings[] = { "0", "1", "2", "3" }; - f->addFnAttr("julia-optimization-level", optLevelStrings[optlevel]); + FnAttrs.addAttribute("julia-optimization-level", optLevelStrings[optlevel]); } ctx.f = f; // Step 4b. determine debug info signature and other type info for locals - DIBuilder dbuilder(*M); + DICompileUnit::DebugEmissionKind emissionKind = (DICompileUnit::DebugEmissionKind) ctx.params->debug_info_kind; + DICompileUnit::DebugNameTableKind tableKind; + if (JL_FEAT_TEST(ctx, gnu_pubnames)) + tableKind = DICompileUnit::DebugNameTableKind::GNU; + else + tableKind = DICompileUnit::DebugNameTableKind::None; + DIBuilder dbuilder(*M, true, ctx.debug_enabled ? getOrCreateJuliaCU(*M, emissionKind, tableKind) : NULL); DIFile *topfile = NULL; DISubprogram *SP = NULL; DebugLoc noDbg, topdebugloc; if (ctx.debug_enabled) { - DICompileUnit::DebugEmissionKind emissionKind = (DICompileUnit::DebugEmissionKind) ctx.params->debug_info_kind; - DICompileUnit::DebugNameTableKind tableKind; - - if (JL_FEAT_TEST(ctx, gnu_pubnames)) { - tableKind = DICompileUnit::DebugNameTableKind::GNU; - } - else { - tableKind = DICompileUnit::DebugNameTableKind::None; - } topfile = dbuilder.createFile(ctx.file, "."); - DICompileUnit *CU = - dbuilder.createCompileUnit(llvm::dwarf::DW_LANG_Julia - ,topfile // File - ,"julia" // Producer - ,true // isOptimized - ,"" // Flags - ,0 // RuntimeVersion - ,"" // SplitName - ,emissionKind // Kind - ,0 // DWOId - ,true // SplitDebugInlining - ,false // DebugInfoForProfiling - ,tableKind // NameTableKind - ); - DISubroutineType *subrty; - if (jl_options.debug_level <= 1) { - subrty = jl_di_func_null_sig; - } - else if (!specsig) { - subrty = jl_di_func_sig; - } - else { - subrty = get_specsig_di(ctx, jlrettype, lam->specTypes, dbuilder); - } - SP = dbuilder.createFunction(CU + if (jl_options.debug_level <= 1) + subrty = debuginfo.jl_di_func_null_sig; + else if (!specsig) + subrty = debuginfo.jl_di_func_sig; + else + subrty = get_specsig_di(ctx, debuginfo, jlrettype, lam->specTypes, dbuilder); + SP = dbuilder.createFunction(nullptr ,dbgFuncName // Name ,f->getName() // LinkageName ,topfile // File @@ -6580,7 +7280,7 @@ static std::pair, jl_llvm_functions_t> const bool AlwaysPreserve = true; // Go over all arguments and local variables and initialize their debug information for (i = 0; i < nreq; i++) { - jl_sym_t *argname = (jl_sym_t*)jl_array_ptr_ref(src->slotnames, i); + jl_sym_t *argname = slot_symbol(ctx, i); if (argname == jl_unused_sym) continue; jl_varinfo_t &varinfo = ctx.slots[i]; @@ -6591,7 +7291,7 @@ static std::pair, jl_llvm_functions_t> topfile, // File toplineno == -1 ? 0 : toplineno, // Line // Variable type - julia_type_to_di(ctx, varinfo.value.typ, &dbuilder, false), + julia_type_to_di(ctx, debuginfo, varinfo.value.typ, &dbuilder, false), AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero); // Flags (TODO: Do we need any) } @@ -6602,12 +7302,12 @@ static std::pair, 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, ctx.slots[ctx.vaSlot].value.typ, &dbuilder, false), + julia_type_to_di(ctx, debuginfo, ctx.slots[ctx.vaSlot].value.typ, &dbuilder, false), AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero); // Flags (TODO: Do we need any) } for (i = 0; i < vinfoslen; i++) { - jl_sym_t *s = (jl_sym_t*)jl_array_ptr_ref(src->slotnames, i); + jl_sym_t *s = slot_symbol(ctx, i); jl_varinfo_t &varinfo = ctx.slots[i]; if (varinfo.isArgument || s == jl_empty_sym || s == jl_unused_sym) continue; @@ -6617,7 +7317,7 @@ static std::pair, 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, varinfo.value.typ, &dbuilder, false), // Variable type + julia_type_to_di(ctx, debuginfo, varinfo.value.typ, &dbuilder, false), // Variable type AlwaysPreserve, // May be deleted if optimized out DINode::FlagZero // Flags (TODO: Do we need any) ); @@ -6650,10 +7350,11 @@ static std::pair, jl_llvm_functions_t> // step 6. set up GC frame allocate_gc_frame(ctx, b0); Value *last_age = NULL; - emit_last_age_field(ctx); + Value *world_age_field = get_last_age_field(ctx); if (toplevel || ctx.is_opaque_closure) { - last_age = tbaa_decorate(ctx.tbaa().tbaa_gcframe, ctx.builder.CreateAlignedLoad( - getSizeTy(ctx.builder.getContext()), ctx.world_age_field, Align(sizeof(size_t)))); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_gcframe); + last_age = ai.decorateInst(ctx.builder.CreateAlignedLoad( + ctx.types().T_size, world_age_field, ctx.types().alignof_ptr)); } // step 7. allocate local variables slots @@ -6677,7 +7378,7 @@ static std::pair, jl_llvm_functions_t> Value *lv = try_emit_union_alloca(ctx, (jl_uniontype_t*)jt, allunbox, align, nbytes); if (lv) { lv->setName(jl_symbol_name(s)); - varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); varinfo.pTIndex = emit_static_alloca(ctx, getInt8Ty(ctx.builder.getContext())); } else if (allunbox) { @@ -6698,15 +7399,15 @@ static std::pair, jl_llvm_functions_t> Type *vtype = julia_type_to_llvm(ctx, jt, &isboxed); assert(!isboxed); assert(!type_is_ghost(vtype) && "constants should already be handled"); - Value *lv = new AllocaInst(vtype, 0, jl_symbol_name(s), /*InsertBefore*/ctx.pgcstack); + Value *lv = new AllocaInst(vtype, M->getDataLayout().getAllocaAddrSpace(), NULL, Align(jl_datatype_align(jt)), jl_symbol_name(s), /*InsertBefore*/ctx.topalloca); if (CountTrackedPointers(vtype).count) { StoreInst *SI = new StoreInst(Constant::getNullValue(vtype), lv, false, Align(sizeof(void*))); - SI->insertAfter(ctx.pgcstack); + SI->insertAfter(ctx.topalloca); } - varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa(), ctx.tbaa().tbaa_stack); + varinfo.value = mark_julia_slot(lv, jt, NULL, ctx.tbaa().tbaa_stack); alloc_def_flag(ctx, varinfo); if (ctx.debug_enabled && varinfo.dinfo) { - assert((Metadata*)varinfo.dinfo->getType() != jl_pvalue_dillvmt); + assert((Metadata*)varinfo.dinfo->getType() != debuginfo.jl_pvalue_dillvmt); dbuilder.insertDeclare(lv, varinfo.dinfo, dbuilder.createExpression(), topdebugloc, ctx.builder.GetInsertBlock()); @@ -6717,14 +7418,14 @@ static std::pair, jl_llvm_functions_t> 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, 0, - jl_symbol_name(s), /*InsertBefore*/ctx.pgcstack); + AllocaInst *av = new AllocaInst(ctx.types().T_prjlvalue, M->getDataLayout().getAllocaAddrSpace(), + 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.pgcstack); + SI->insertAfter(ctx.topalloca); varinfo.boxroot = av; if (ctx.debug_enabled && varinfo.dinfo) { DIExpression *expr; - if ((Metadata*)varinfo.dinfo->getType() == jl_pvalue_dillvmt) { + if ((Metadata*)varinfo.dinfo->getType() == debuginfo.jl_pvalue_dillvmt) { expr = dbuilder.createExpression(); } else { @@ -6756,9 +7457,20 @@ static std::pair, jl_llvm_functions_t> // Scan for PhiC nodes, emit their slots and record which upsilon nodes // yield to them. + // Also count ssavalue uses. { for (size_t i = 0; i < jl_array_len(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; + return true; + } + return false; + }; + general_use_analysis(ctx, stmt, scan_ssavalue); + 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) { @@ -6779,75 +7491,139 @@ static std::pair, jl_llvm_functions_t> // step 8. move args into local variables Function::arg_iterator AI = f->arg_begin(); + std::vector attrs(f->arg_size()); // function declaration attributes auto get_specsig_arg = [&](jl_value_t *argType, Type *llvmArgType, bool isboxed) { - jl_cgval_t theArg(ctx.builder.getContext()); if (type_is_ghost(llvmArgType)) { // this argument is not actually passed - theArg = ghostValue(ctx, argType); + return ghostValue(ctx, argType); } else if (is_uniquerep_Type(argType)) { - theArg = mark_julia_const(ctx, jl_tparam0(argType)); + return mark_julia_const(ctx, jl_tparam0(argType)); } - else if (llvmArgType->isAggregateType()) { - Argument *Arg = &*AI; ++AI; - maybe_mark_argument_dereferenceable(Arg, argType); - theArg = mark_julia_slot(Arg, argType, NULL, ctx.tbaa(), ctx.tbaa().tbaa_const); // this argument is by-pointer + Argument *Arg = &*AI; + ++AI; + AttrBuilder param(ctx.builder.getContext(), f->getAttributes().getParamAttrs(Arg->getArgNo())); + jl_cgval_t theArg; + if (llvmArgType->isAggregateType()) { + maybe_mark_argument_dereferenceable(param, argType); + theArg = mark_julia_slot(Arg, argType, NULL, ctx.tbaa().tbaa_const); // this argument is by-pointer } else { - Argument *Arg = &*AI; ++AI; if (isboxed) // e.g. is-pointer - maybe_mark_argument_dereferenceable(Arg, argType); + maybe_mark_argument_dereferenceable(param, argType); theArg = mark_julia_type(ctx, Arg, isboxed, argType); if (theArg.tbaa == ctx.tbaa().tbaa_immut) theArg.tbaa = ctx.tbaa().tbaa_const; } + attrs.at(Arg->getArgNo()) = AttributeSet::get(Arg->getContext(), param); // function declaration attributes return theArg; }; - if (has_sret) - AI++; // skip sret slot - if (returninfo.return_roots) - AI++; // skip return_roots slot + if (has_sret) { + Argument *Arg = &*AI; + ++AI; + AttrBuilder param(ctx.builder.getContext(), f->getAttributes().getParamAttrs(Arg->getArgNo())); + if (returninfo.cc == jl_returninfo_t::Union) { + param.addAttribute(Attribute::NonNull); + // The `dereferenceable` below does not imply `nonnull` for non addrspace(0) pointers. + param.addDereferenceableAttr(returninfo.union_bytes); + param.addAlignmentAttr(returninfo.union_align); + } + else { + const DataLayout &DL = jl_Module->getDataLayout(); + Type *RT = Arg->getParamStructRetType(); + TypeSize sz = DL.getTypeAllocSize(RT); + Align al = DL.getPrefTypeAlign(RT); + param.addAttribute(Attribute::NonNull); + // The `dereferenceable` below does not imply `nonnull` for non addrspace(0) pointers. + param.addDereferenceableAttr(sz); + param.addAlignmentAttr(al); + } + attrs.at(Arg->getArgNo()) = AttributeSet::get(Arg->getContext(), param); // function declaration attributes + } + if (returninfo.return_roots) { + Argument *Arg = &*AI; + ++AI; + AttrBuilder param(ctx.builder.getContext(), f->getAttributes().getParamAttrs(Arg->getArgNo())); + param.addAttribute(Attribute::NonNull); + // The `dereferenceable` below does not imply `nonnull` for non addrspace(0) pointers. + size_t size = returninfo.return_roots * sizeof(jl_value_t*); + param.addDereferenceableAttr(size); + param.addAlignmentAttr(Align(sizeof(jl_value_t*))); + attrs.at(Arg->getArgNo()) = AttributeSet::get(Arg->getContext(), param); // function declaration attributes + } for (i = 0; i < nreq; i++) { - jl_sym_t *s = (jl_sym_t*)jl_array_ptr_ref(src->slotnames, i); - jl_value_t *argType = (i == 0 && ctx.is_opaque_closure) ? (jl_value_t*)jl_any_type : - jl_nth_slot_type(lam->specTypes, i); + jl_sym_t *s = slot_symbol(ctx, i); + jl_value_t *argType = jl_nth_slot_type(lam->specTypes, i); + // TODO: jl_nth_slot_type should call jl_rewrap_unionall? + // specTypes is required to be a datatype by construction for specsig, but maybe not otherwise bool isboxed = deserves_argbox(argType); - Type *llvmArgType = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, argType); + Type *llvmArgType = NULL; + if (i == 0 && ctx.is_opaque_closure) { + isboxed = true; + llvmArgType = PointerType::get(ctx.types().T_jlvalue, AddressSpace::Derived); + argType = (jl_value_t*)jl_any_type; + } + else { + llvmArgType = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, argType); + } if (s == jl_unused_sym) { if (specsig && !type_is_ghost(llvmArgType) && !is_uniquerep_Type(argType)) ++AI; continue; } jl_varinfo_t &vi = ctx.slots[i]; - jl_cgval_t theArg(ctx.builder.getContext()); + jl_cgval_t theArg; if (s == jl_unused_sym || vi.value.constant) { assert(vi.boxroot == NULL); if (specsig && !type_is_ghost(llvmArgType) && !is_uniquerep_Type(argType)) ++AI; } else { - if (specsig) { + // If this is an opaque closure, implicitly load the env and switch + // the world age. + if (i == 0 && ctx.is_opaque_closure) { + // Load closure world + Value *oc_this = decay_derived(ctx, &*AI++); + Value *argaddr = emit_bitcast(ctx, oc_this, getInt8PtrTy(ctx.builder.getContext())); + Value *worldaddr = ctx.builder.CreateInBoundsGEP( + getInt8Ty(ctx.builder.getContext()), argaddr, + ConstantInt::get(ctx.types().T_size, offsetof(jl_opaque_closure_t, world))); + + jl_cgval_t closure_world = typed_load(ctx, worldaddr, NULL, (jl_value_t*)jl_long_type, + nullptr, nullptr, false, AtomicOrdering::NotAtomic, false, ctx.types().alignof_ptr.value()); + emit_unbox_store(ctx, closure_world, world_age_field, ctx.tbaa().tbaa_gcframe, ctx.types().alignof_ptr.value()); + + // Load closure env + Value *envaddr = ctx.builder.CreateInBoundsGEP( + getInt8Ty(ctx.builder.getContext()), argaddr, + ConstantInt::get(ctx.types().T_size, offsetof(jl_opaque_closure_t, captures))); + + jl_cgval_t closure_env = typed_load(ctx, envaddr, NULL, (jl_value_t*)jl_any_type, + nullptr, nullptr, true, AtomicOrdering::NotAtomic, false, sizeof(void*)); + theArg = update_julia_type(ctx, closure_env, vi.value.typ); + } + else if (specsig) { theArg = get_specsig_arg(argType, llvmArgType, isboxed); } else { if (i == 0) { // first (function) arg is separate in jlcall - theArg = mark_julia_type(ctx, fArg, true, ctx.is_opaque_closure ? - argType : vi.value.typ); + theArg = mark_julia_type(ctx, fArg, true, vi.value.typ); } else { - Value *argPtr = ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, argArray, ConstantInt::get(getSizeTy(ctx.builder.getContext()), i-1)); - Value *load = maybe_mark_load_dereferenceable( + Value *argPtr = ctx.builder.CreateConstInBoundsGEP1_32(ctx.types().T_prjlvalue, argArray, i - 1); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_const); + Value *load = ai.decorateInst(maybe_mark_load_dereferenceable( ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, argPtr, Align(sizeof(void*))), - false, vi.value.typ); + false, vi.value.typ)); theArg = mark_julia_type(ctx, load, true, vi.value.typ); if (ctx.debug_enabled && vi.dinfo && !vi.boxroot && !vi.value.V) { 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() != jl_pvalue_dillvmt) + if ((Metadata*)vi.dinfo->getType() != debuginfo.jl_pvalue_dillvmt) addr.push_back(llvm::dwarf::DW_OP_deref); dbuilder.insertDeclare(pargArray, vi.dinfo, dbuilder.createExpression(addr), topdebugloc, @@ -6856,28 +7632,6 @@ static std::pair, jl_llvm_functions_t> } } - // If this is an opaque closure, implicitly load the env and switch - // the world age. - if (i == 0 && ctx.is_opaque_closure) { - // Load closure world - Value *argaddr = emit_bitcast(ctx, maybe_decay_tracked(ctx, data_pointer(ctx, theArg)), getInt8PtrTy(ctx.builder.getContext())); - Value *worldaddr = ctx.builder.CreateInBoundsGEP( - getInt8Ty(ctx.builder.getContext()), argaddr, - ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_opaque_closure_t, world))); - - jl_cgval_t closure_world = typed_load(ctx, worldaddr, NULL, (jl_value_t*)jl_long_type, - theArg.tbaa, nullptr, false, AtomicOrdering::NotAtomic, false, sizeof(size_t)); - emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), closure_world, (jl_value_t*)jl_long_type, ctx.world_age_field, ctx.tbaa().tbaa_gcframe); - - // Load closure env - Value *envaddr = ctx.builder.CreateInBoundsGEP( - getInt8Ty(ctx.builder.getContext()), argaddr, - ConstantInt::get(getSizeTy(ctx.builder.getContext()), offsetof(jl_opaque_closure_t, captures))); - - jl_cgval_t closure_env = typed_load(ctx, envaddr, NULL, (jl_value_t*)jl_any_type, - theArg.tbaa, nullptr, true, AtomicOrdering::NotAtomic, false, sizeof(void*)); - theArg = convert_julia_type(ctx, closure_env, vi.value.typ); - } if (vi.boxroot == NULL) { assert(vi.value.V == NULL && "unexpected variable slot created for argument"); @@ -6888,7 +7642,7 @@ static std::pair, jl_llvm_functions_t> Value *parg; if (theArg.ispointer()) { parg = theArg.V; - if ((Metadata*)vi.dinfo->getType() != jl_pvalue_dillvmt) + if ((Metadata*)vi.dinfo->getType() != debuginfo.jl_pvalue_dillvmt) addr.push_back(llvm::dwarf::DW_OP_deref); } else { @@ -6916,20 +7670,21 @@ static std::pair, jl_llvm_functions_t> } else if (specsig) { ctx.nvargs = jl_nparams(lam->specTypes) - nreq; - jl_cgval_t *vargs = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * ctx.nvargs); + SmallVector vargs(ctx.nvargs); for (size_t i = nreq; i < jl_nparams(lam->specTypes); ++i) { jl_value_t *argType = jl_nth_slot_type(lam->specTypes, i); + // n.b. specTypes is required to be a datatype by construction for specsig bool isboxed = deserves_argbox(argType); Type *llvmArgType = isboxed ? ctx.types().T_prjlvalue : julia_type_to_llvm(ctx, argType); 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); + jl_cgval_t tuple = emit_new_struct(ctx, vi.value.typ, ctx.nvargs, vargs.data()); emit_varinfo_assign(ctx, vi, tuple); } else { restTuple = emit_jlcall(ctx, jltuple_func, Constant::getNullValue(ctx.types().T_prjlvalue), - vargs, ctx.nvargs, JLCALL_F_CC); + vargs.data(), ctx.nvargs, julia_call); jl_cgval_t tuple = mark_julia_type(ctx, restTuple, true, vi.value.typ); emit_varinfo_assign(ctx, vi, tuple); } @@ -6941,7 +7696,7 @@ static std::pair, jl_llvm_functions_t> ctx.builder.CreateCall(F, { Constant::getNullValue(ctx.types().T_prjlvalue), ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, argArray, - ConstantInt::get(getSizeTy(ctx.builder.getContext()), nreq - 1)), + ConstantInt::get(ctx.types().T_size, nreq - 1)), ctx.builder.CreateSub(argCount, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), nreq - 1)) }); restTuple->setAttributes(F->getAttributes()); @@ -6949,6 +7704,10 @@ static std::pair, jl_llvm_functions_t> } } + AttributeList attributes = AttributeList::get(ctx.builder.getContext(), AttributeSet::get(f->getContext(), FnAttrs), AttributeSet::get(f->getContext(), RetAttrs), attrs); + // attributes should be a superset of f->getAttributes() based on how we constructed it, but we merge just in case it isn't + f->setAttributes(AttributeList::get(ctx.builder.getContext(), {attributes, f->getAttributes()})); + // step 10. Compute properties for each statements // This needs to be computed by iterating in the IR order // instead of control flow order. @@ -6956,15 +7715,20 @@ static std::pair, 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) { + return jl_options.tracked_path != NULL && file.startswith(jl_options.tracked_path); + }; bool mod_is_user_mod = in_user_mod(ctx.module); + bool mod_is_tracked = in_tracked_path(ctx.file); struct DebugLineTable { DebugLoc loc; StringRef file; ssize_t line; 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.inlined_at == inlined_at; + 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; } }; std::vector linetable; @@ -6977,26 +7741,28 @@ static std::pair, jl_llvm_functions_t> 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::Int, inlined_at::Int) + // 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_typeis(locinfo, jl_lineinfonode_type)); + 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_long(jl_fieldref(locinfo, 3)); - info.inlined_at = jl_unbox_long(jl_fieldref(locinfo, 4)); + 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.file = jl_symbol_name(filesym); - if (info.file.empty()) - info.file = ""; + info.is_tracked = in_tracked_path(info.file); if (ctx.debug_enabled) { StringRef fname; if (jl_is_method_instance(method)) @@ -7019,7 +7785,7 @@ static std::pair, jl_llvm_functions_t> ,fname // LinkageName ,difile // File ,0 // LineNo - ,jl_di_func_null_sig // Ty + ,debuginfo.jl_di_func_null_sig // Ty ,0 // ScopeLine ,DINode::FlagZero // Flags ,DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized // SPFlags @@ -7070,8 +7836,19 @@ static std::pair, jl_llvm_functions_t> Instruction &prologue_end = ctx.builder.GetInsertBlock()->back(); + // step 11a. For top-level code, load the world age + if (toplevel && !ctx.is_opaque_closure) { + LoadInst *world = ctx.builder.CreateAlignedLoad(ctx.types().T_size, + prepare_global_in(jl_Module, jlgetworld_global), ctx.types().alignof_ptr); + world->setOrdering(AtomicOrdering::Acquire); + ctx.builder.CreateAlignedStore(world, world_age_field, ctx.types().alignof_ptr); + } + + // step 11b. Emit the entry safepoint + if (JL_FEAT_TEST(ctx, safepoint_on_entry)) + emit_gc_safepoint(ctx.builder, ctx.types().T_size, get_current_ptls(ctx), ctx.tbaa().tbaa_const); - // step 11. Do codegen in control flow order + // step 11c. Do codegen in control flow order std::vector workstack; std::map BB; std::map come_from_bb; @@ -7110,13 +7887,15 @@ static std::pair, jl_llvm_functions_t> cursor = -1; }; - auto do_coverage = [&] (bool in_user_code) { + auto do_coverage = [&] (bool in_user_code, bool is_tracked) { return (coverage_mode == JL_LOG_ALL || - (coverage_mode == JL_LOG_USER && in_user_code)); + (in_user_code && coverage_mode == JL_LOG_USER) || + (is_tracked && coverage_mode == JL_LOG_PATH)); }; - auto do_malloc_log = [&] (bool in_user_code) { + auto do_malloc_log = [&] (bool in_user_code, bool is_tracked) { return (malloc_log_mode == JL_LOG_ALL || - (malloc_log_mode == JL_LOG_USER && in_user_code)); + (in_user_code && malloc_log_mode == JL_LOG_USER) || + (is_tracked && malloc_log_mode == JL_LOG_PATH)); }; std::vector current_lineinfo, new_lineinfo; auto coverageVisitStmt = [&] (size_t dbg) { @@ -7135,15 +7914,15 @@ static std::pair, jl_llvm_functions_t> if (newdbg != current_lineinfo[dbg]) { current_lineinfo[dbg] = newdbg; const auto &info = linetable.at(newdbg); - if (do_coverage(info.is_user_code)) + if (do_coverage(info.is_user_code, info.is_tracked)) coverageVisitLine(ctx, info.file, info.line); } } new_lineinfo.clear(); }; auto mallocVisitStmt = [&] (unsigned dbg, Value *sync) { - if (!do_malloc_log(mod_is_user_mod) || dbg == 0) { - if (do_malloc_log(true) && sync) + if (!do_malloc_log(mod_is_user_mod, mod_is_tracked) || dbg == 0) { + if (do_malloc_log(true, mod_is_tracked) && sync) ctx.builder.CreateCall(prepare_call(sync_gc_total_bytes_func), {sync}); return; } @@ -7154,7 +7933,7 @@ static std::pair, jl_llvm_functions_t> 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)) + if (do_coverage(info.is_user_code, info.is_tracked)) jl_coverage_alloc_line(info.file, info.line); } @@ -7209,7 +7988,7 @@ static std::pair, jl_llvm_functions_t> } Value *sync_bytes = nullptr; - if (do_malloc_log(true)) + 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.at(0); @@ -7217,7 +7996,7 @@ static std::pair, jl_llvm_functions_t> if (topinfo == linetable.at(1)) current_lineinfo.push_back(1); } - if (do_coverage(topinfo.is_user_code)) + if (do_coverage(topinfo.is_user_code, topinfo.is_tracked)) coverageVisitLine(ctx, topinfo.file, topinfo.line); } @@ -7229,7 +8008,7 @@ static std::pair, jl_llvm_functions_t> ctx.builder.SetCurrentDebugLocation(linetable.at(debuginfoloc).loc); coverageVisitStmt(debuginfoloc); } - ctx.aliasscope = aliasscopes[cursor]; + 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)) { @@ -7242,6 +8021,11 @@ static std::pair, jl_llvm_functions_t> // this is basically a copy of emit_assignment, // but where the assignment slot is the retval jl_cgval_t retvalinfo = emit_expr(ctx, retexpr); + + if (ctx.is_opaque_closure) { + emit_typecheck(ctx, retvalinfo, jlrettype, "OpaqueClosure"); + } + retvalinfo = convert_julia_type(ctx, retvalinfo, jlrettype); if (retvalinfo.typ == jl_bottom_type) { CreateTrap(ctx.builder, false); @@ -7305,12 +8089,12 @@ static std::pair, jl_llvm_functions_t> if (retvalinfo.ispointer()) { if (returninfo.return_roots) { Type *store_ty = julia_type_to_llvm(ctx, retvalinfo.typ); - emit_sret_roots(ctx, true, data_pointer(ctx, retvalinfo), store_ty, f->arg_begin() + 1, returninfo.return_roots); + emit_sret_roots(ctx, true, data_pointer(ctx, retvalinfo), store_ty, f->arg_begin() + 1, get_returnroots_type(ctx, returninfo.return_roots), returninfo.return_roots); } if (returninfo.cc == jl_returninfo_t::SRet) { assert(jl_is_concrete_type(jlrettype)); - emit_memcpy(ctx, sret, nullptr, retvalinfo, jl_datatype_size(jlrettype), - julia_alignment(jlrettype)); + emit_memcpy(ctx, sret, jl_aliasinfo_t::fromTBAA(ctx, nullptr), retvalinfo, + jl_datatype_size(jlrettype), julia_alignment(jlrettype)); } else { // must be jl_returninfo_t::Union emit_unionmove(ctx, sret, nullptr, retvalinfo, /*skip*/isboxed_union); @@ -7322,7 +8106,7 @@ static std::pair, jl_llvm_functions_t> Value *Val = retvalinfo.V; if (returninfo.return_roots) { assert(julia_type_to_llvm(ctx, retvalinfo.typ) == store_ty); - emit_sret_roots(ctx, false, Val, store_ty, f->arg_begin() + 1, returninfo.return_roots); + emit_sret_roots(ctx, false, Val, store_ty, f->arg_begin() + 1, get_returnroots_type(ctx, returninfo.return_roots), returninfo.return_roots); } if (dest_ty != sret->getType()) sret = emit_bitcast(ctx, sret, dest_ty); @@ -7333,7 +8117,7 @@ static std::pair, jl_llvm_functions_t> mallocVisitStmt(debuginfoloc, sync_bytes); if (toplevel || ctx.is_opaque_closure) - ctx.builder.CreateStore(last_age, ctx.world_age_field); + ctx.builder.CreateStore(last_age, world_age_field); assert(type_is_ghost(retty) || returninfo.cc == jl_returninfo_t::SRet || retval->getType() == ctx.f->getReturnType()); ctx.builder.CreateRet(retval); @@ -7377,8 +8161,7 @@ static std::pair, jl_llvm_functions_t> Value *excstack_state = ctx.builder.CreateCall(prepare_call(jl_excstack_state_func)); assert(!ctx.ssavalue_assigned.at(cursor)); - ctx.SAvalues.at(cursor) = jl_cgval_t(excstack_state, NULL, false, - (jl_value_t*)jl_ulong_type, NULL, ctx.tbaa()); + ctx.SAvalues.at(cursor) = jl_cgval_t(excstack_state, (jl_value_t*)jl_ulong_type, NULL); ctx.ssavalue_assigned.at(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 @@ -7412,7 +8195,7 @@ static std::pair, jl_llvm_functions_t> std::map, BasicBlock*> BB_rewrite_map; std::vector ToDelete; for (auto &tup : ctx.PhiNodes) { - jl_cgval_t phi_result(ctx.builder.getContext()); + jl_cgval_t phi_result; PHINode *VN; jl_value_t *r; AllocaInst *dest; @@ -7476,7 +8259,6 @@ static std::pair, jl_llvm_functions_t> if (val.constant) val = mark_julia_const(ctx, val.constant); // be over-conservative at making sure `.typ` is set concretely, not tindex if (!jl_is_uniontype(phiType) || !TindexN) { - Type *lty = julia_type_to_llvm(ctx, phiType); if (VN) { Value *V; if (val.typ == (jl_value_t*)jl_bottom_type) { @@ -7490,7 +8272,7 @@ static std::pair, jl_llvm_functions_t> else { // must be careful to emit undef here (rather than a bitcast or // load of val) if the runtime type of val isn't phiType - Value *isvalid = emit_isa(ctx, val, phiType, NULL).first; + Value *isvalid = emit_isa_and_defined(ctx, val, phiType); V = emit_guarded_test(ctx, isvalid, undef_value_for_type(VN->getType()), [&] { return emit_unbox(ctx, VN->getType(), val, phiType); }); @@ -7501,10 +8283,9 @@ static std::pair, jl_llvm_functions_t> else if (dest && val.typ != (jl_value_t*)jl_bottom_type) { // must be careful to emit undef here (rather than a bitcast or // load of val) if the runtime type of val isn't phiType - assert(lty != ctx.types().T_prjlvalue); - Value *isvalid = emit_isa(ctx, val, phiType, NULL).first; + Value *isvalid = emit_isa_and_defined(ctx, val, phiType); emit_guarded_test(ctx, isvalid, nullptr, [&] { - (void)emit_unbox(ctx, lty, val, phiType, maybe_decay_tracked(ctx, dest), ctx.tbaa().tbaa_stack); + emit_unbox_store(ctx, update_julia_type(ctx, val, phiType), dest, ctx.tbaa().tbaa_stack, julia_alignment(phiType)); return nullptr; }); } @@ -7530,9 +8311,8 @@ static std::pair, jl_llvm_functions_t> else { if (VN) V = Constant::getNullValue(ctx.types().T_prjlvalue); - Type *lty = julia_type_to_llvm(ctx, val.typ); - if (dest && !type_is_ghost(lty)) // basically, if !ghost union - emit_unbox(ctx, lty, val, val.typ, dest, ctx.tbaa().tbaa_stack); + if (dest) + emit_unbox_store(ctx, val, dest, ctx.tbaa().tbaa_stack, julia_alignment(val.typ)); RTindex = ConstantInt::get(getInt8Ty(ctx.builder.getContext()), tindex); } } @@ -7544,7 +8324,7 @@ static std::pair, jl_llvm_functions_t> RTindex = new_union.TIndex; if (!RTindex) { assert(new_union.isboxed && new_union.Vboxed && "convert_julia_type failed"); - RTindex = compute_tindex_unboxed(ctx, new_union, phiType); + RTindex = compute_tindex_unboxed(ctx, new_union, phiType, true); 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. @@ -7621,12 +8401,12 @@ static std::pair, jl_llvm_functions_t> } // step 12. Perform any delayed instantiations - if (ctx.debug_enabled) { - bool in_prologue = true; - for (auto &BB : *ctx.f) { - for (auto &I : BB) { - CallBase *call = dyn_cast(&I); - if (call && !I.getDebugLoc()) { + bool in_prologue = true; + for (auto &BB : *ctx.f) { + for (auto &I : BB) { + CallBase *call = dyn_cast(&I); + if (call) { + if (ctx.debug_enabled && !I.getDebugLoc()) { // LLVM Verifier: inlinable function call in a function with debug info must have a !dbg location // make sure that anything we attempt to call has some inlining info, just in case optimization messed up // (except if we know that it is an intrinsic used in our prologue, which should never have its own debug subprogram) @@ -7635,12 +8415,24 @@ static std::pair, jl_llvm_functions_t> I.setDebugLoc(topdebugloc); } } - if (&I == &prologue_end) - in_prologue = false; + if (toplevel && !ctx.is_opaque_closure && !in_prologue) { + // we're at toplevel; insert an atomic barrier between every instruction + // TODO: inference is invalid if this has any effect (which it often does) + LoadInst *world = new LoadInst(ctx.types().T_size, + prepare_global_in(jl_Module, jlgetworld_global), Twine(), + /*isVolatile*/false, ctx.types().alignof_ptr, /*insertBefore*/&I); + world->setOrdering(AtomicOrdering::Acquire); + StoreInst *store_world = new StoreInst(world, world_age_field, + /*isVolatile*/false, ctx.types().alignof_ptr, /*insertBefore*/&I); + (void)store_world; + } } + if (&I == &prologue_end) + in_prologue = false; } - dbuilder.finalize(); } + if (ctx.debug_enabled) + dbuilder.finalize(); if (ctx.vaSlot > 0) { // remove VA allocation if we never referenced it @@ -7680,60 +8472,36 @@ static std::pair, jl_llvm_functions_t> } } - // copy ctx.roots into m->roots - // if we created any new roots during codegen - if (ctx.roots) { - jl_method_t *m = lam->def.method; - JL_LOCK(&m->writelock); - if (m->roots == NULL) { - m->roots = ctx.roots; - jl_gc_wb(m, m->roots); - } - else { - size_t i, ilen = jl_array_dim0(ctx.roots); - size_t j, jlen = jl_array_dim0(m->roots); - for (i = 0; i < ilen; i++) { - jl_value_t *ival = jl_array_ptr_ref(ctx.roots, i); - for (j = 0; j < jlen; j++) { - jl_value_t *jval = jl_array_ptr_ref(m->roots, j); - if (ival == jval) - break; - } - if (j == jlen) // not found - add to array - jl_add_method_root(m, jl_precompile_toplevel_module, ival); - } - } - ctx.roots = NULL; - JL_UNLOCK(&m->writelock); - } - // link the dependent llvmcall modules, but switch their function's linkage to internal // so that they don't conflict when they show up in the execution engine. + Linker L(*jl_Module); for (auto &Mod : ctx.llvmcall_modules) { SmallVector Exports; for (const auto &F: Mod->functions()) if (!F.isDeclaration()) Exports.push_back(F.getName().str()); - if (Linker::linkModules(*jl_Module, std::move(Mod))) { - jl_error("Failed to link LLVM bitcode"); - } + bool error = L.linkInModule(std::move(Mod)); + assert(!error && "linking llvmcall modules failed"); + (void)error; for (auto FN: Exports) jl_Module->getFunction(FN)->setLinkage(GlobalVariable::InternalLinkage); } // link in opaque closure modules - for (auto &Mod : ctx.oc_modules) { + for (auto &TSMod : ctx.oc_modules) { SmallVector Exports; - for (const auto &F: Mod->functions()) - if (!F.isDeclaration()) - Exports.push_back(F.getName().str()); - jl_merge_module(jl_Module, std::move(Mod)); + TSMod.withModuleDo([&](Module &Mod) { + for (const auto &F: Mod.functions()) + if (!F.isDeclaration()) + Exports.push_back(F.getName().str()); + }); + jl_merge_module(TSM, std::move(TSMod)); for (auto FN: Exports) jl_Module->getFunction(FN)->setLinkage(GlobalVariable::InternalLinkage); } JL_GC_POP(); - return std::make_pair(std::unique_ptr(M), declarations); + return declarations; } // --- entry point --- @@ -7741,78 +8509,107 @@ static std::pair, jl_llvm_functions_t> void jl_add_code_in_flight(StringRef name, jl_code_instance_t *codeinst, const DataLayout &DL); JL_GCC_IGNORE_START("-Wclobbered") -jl_compile_result_t jl_emit_code( +jl_llvm_functions_t jl_emit_code( + orc::ThreadSafeModule &m, jl_method_instance_t *li, jl_code_info_t *src, jl_value_t *jlrettype, jl_codegen_params_t ¶ms) { - JL_TIMING(CODEGEN); + JL_TIMING(CODEGEN, CODEGEN_LLVM); + jl_timing_show_func_sig((jl_value_t *)li->specTypes, JL_TIMING_CURRENT_BLOCK); // caller must hold codegen_lock jl_llvm_functions_t decls = {}; - std::unique_ptr m; assert((params.params == &jl_default_cgparams /* fast path */ || !params.cache || compare_cgparams(params.params, &jl_default_cgparams)) && "functions compiled with custom codegen params must not be cached"); JL_TRY { - std::tie(m, decls) = emit_function(li, src, jlrettype, params, jl_LLVMContext); - if (dump_emitted_mi_name_stream != NULL) { - jl_printf(dump_emitted_mi_name_stream, "%s\t", decls.specFunctionObject.c_str()); + decls = emit_function(m, li, src, jlrettype, params); + auto stream = *jl_ExecutionEngine->get_dump_emitted_mi_name_stream(); + if (stream) { + jl_printf(stream, "%s\t", decls.specFunctionObject.c_str()); // NOTE: We print the Type Tuple without surrounding quotes, because the quotes // break CSV parsing if there are any internal quotes in the Type name (e.g. in // Symbol("...")). The \t delineator should be enough to ensure whitespace is // handled correctly. (And we don't need to worry about any tabs in the printed // string, because tabs are printed as "\t" by `show`.) - jl_static_show(dump_emitted_mi_name_stream, li->specTypes); - jl_printf(dump_emitted_mi_name_stream, "\n"); + jl_static_show(stream, li->specTypes); + jl_printf(stream, "\n"); } } JL_CATCH { // Something failed! This is very, very bad. // Try to pretend that it isn't and attempt to recover. - m.reset(); + std::string mname = m.getModuleUnlocked()->getModuleIdentifier(); + m = orc::ThreadSafeModule(); decls.functionObject = ""; decls.specFunctionObject = ""; - const char *mname = name_from_method_instance(li); - jl_printf((JL_STREAM*)STDERR_FILENO, "Internal error: encountered unexpected error during compilation of %s:\n", mname); + 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_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } - return std::make_tuple(std::move(m), decls); + return decls; +} + +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); + ctx.name = M->getModuleIdentifier().data(); + std::string funcName = get_function_name(true, false, ctx.name, ctx.emission_context.TargetTriple); + jl_llvm_functions_t declarations; + declarations.functionObject = "jl_f_opaque_closure_call"; + if (uses_specsig(mi->specTypes, false, true, rettype, true)) { + jl_returninfo_t returninfo = get_specsig_function(ctx, M, NULL, funcName, mi->specTypes, rettype, 1); + 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); + declarations.specFunctionObject = funcName; + } + return declarations; } -jl_compile_result_t jl_emit_codeinst( +jl_llvm_functions_t jl_emit_codeinst( + orc::ThreadSafeModule &m, jl_code_instance_t *codeinst, jl_code_info_t *src, jl_codegen_params_t ¶ms) { - JL_TIMING(CODEGEN); + JL_TIMING(CODEGEN, CODEGEN_Codeinst); + jl_timing_show_method_instance(codeinst->def, JL_TIMING_CURRENT_BLOCK); JL_GC_PUSH1(&src); if (!src) { - src = (jl_code_info_t*)codeinst->inferred; + src = (jl_code_info_t*)jl_atomic_load_relaxed(&codeinst->inferred); jl_method_t *def = codeinst->def->def.method; + // Check if this is the generic method for opaque closure wrappers - + // if so, generate the specsig -> invoke converter. + if (def == jl_opaque_closure_method) { + JL_GC_POP(); + return jl_emit_oc_wrapper(m, params, codeinst->def, codeinst->rettype); + } if (src && (jl_value_t*)src != jl_nothing && jl_is_method(def)) - src = jl_uncompress_ir(def, codeinst, (jl_array_t*)src); + src = jl_uncompress_ir(def, codeinst, (jl_value_t*)src); if (!src || !jl_is_code_info(src)) { JL_GC_POP(); - return jl_compile_result_t(); // failed + m = orc::ThreadSafeModule(); + return jl_llvm_functions_t(); // failed } } - jl_compile_result_t result = jl_emit_code(codeinst->def, src, codeinst->rettype, params); + jl_llvm_functions_t decls = jl_emit_code(m, codeinst->def, src, codeinst->rettype, params); - const jl_llvm_functions_t &decls = std::get<1>(result); const std::string &specf = decls.specFunctionObject; const std::string &f = decls.functionObject; if (params.cache && !f.empty()) { - const Module *m = std::get<0>(result).get(); // Prepare debug info to receive this function // record that this function name came from this linfo, // so we can build a reverse mapping for debug-info. bool toplevel = !jl_is_method(codeinst->def->def.method); if (!toplevel) { - const DataLayout &DL = m->getDataLayout(); + //Safe b/c params holds context lock + const DataLayout &DL = m.getModuleUnlocked()->getDataLayout(); // but don't remember toplevel thunks because // they may not be rooted in the gc for the life of the program, // and the runtime doesn't notify us when the code becomes unreachable :( @@ -7822,49 +8619,52 @@ jl_compile_result_t jl_emit_codeinst( jl_add_code_in_flight(f, codeinst, DL); } - if (// don't alter `inferred` when the code is not directly being used - params.world && + 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 - codeinst->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 - jl_options.debug_level > 1) { - // update the stored code - if (codeinst->inferred != (jl_value_t*)src) { - if (jl_is_method(def)) { - src = (jl_code_info_t*)jl_compress_ir(def, src); - assert(jl_typeis(src, jl_array_uint8_type)); - codeinst->relocatability = ((uint8_t*)jl_array_data(src))[jl_array_len(src)-1]; + 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 + 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); + } + } + else if (jl_is_method(def)) {// don't delete toplevel code + if (// and there is something to delete (test this before calling jl_ir_inlining_cost) + inferred != jl_nothing && + // don't delete inlineable code, unless it is constant + (jl_atomic_load_relaxed(&codeinst->invoke) == jl_fptr_const_return_addr || + (jl_ir_inlining_cost(inferred) == UINT16_MAX)) && + // don't delete code when generating a precompile file + !(params.imaging || jl_options.incremental)) { + // if not inlineable, code won't be needed again + jl_atomic_store_release(&codeinst->inferred, jl_nothing); } - codeinst->inferred = (jl_value_t*)src; - jl_gc_wb(codeinst, src); } - } - else if (// don't delete toplevel code - jl_is_method(def) && - // and there is something to delete (test this before calling jl_ir_flag_inlineable) - codeinst->inferred != jl_nothing && - // don't delete inlineable code, unless it is constant - (codeinst->invoke == jl_fptr_const_return_addr || !jl_ir_flag_inlineable((jl_array_t*)codeinst->inferred)) && - // don't delete code when generating a precompile file - !imaging_mode) { - // if not inlineable, code won't be needed again - codeinst->inferred = jl_nothing; } } } JL_GC_POP(); - return result; + return decls; } void jl_compile_workqueue( - std::map &emitted, + jl_workqueue_t &emitted, + Module &original, jl_codegen_params_t ¶ms, CompilationPolicy policy) { - JL_TIMING(CODEGEN); + JL_TIMING(CODEGEN, CODEGEN_Workqueue); jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); while (!params.workqueue.empty()) { @@ -7873,26 +8673,37 @@ void jl_compile_workqueue( jl_returninfo_t::CallingConv proto_cc; bool proto_specsig; unsigned proto_return_roots; - std::tie(codeinst, proto_cc, proto_return_roots, protodecl, proto_specsig) = params.workqueue.back(); + auto it = params.workqueue.back(); + codeinst = it.first; + std::tie(proto_cc, proto_return_roots, protodecl, proto_specsig) = 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_relaxed(&codeinst->invoke); - if (params.cache && invoke != NULL) { + 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) { auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr); + if (fptr) { + while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + // in case we are racing with another thread that is emitting this function + invoke = jl_atomic_load_relaxed(&codeinst->invoke); + } if (invoke == jl_fptr_args_addr) { preal_decl = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)fptr, codeinst); } - else if (codeinst->isspecsig) { + else if (jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b1) { preal_decl = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)fptr, codeinst); preal_specsig = true; } } else { - jl_compile_result_t &result = emitted[codeinst]; + auto &result = emitted[codeinst]; jl_llvm_functions_t *decls = NULL; if (std::get<0>(result)) { decls = &std::get<1>(result); @@ -7901,13 +8712,24 @@ void jl_compile_workqueue( // Reinfer the function. The JIT came along and removed the inferred // method body. See #34993 if (policy != CompilationPolicy::Default && - codeinst->inferred && codeinst->inferred == jl_nothing) { + jl_atomic_load_relaxed(&codeinst->inferred) == jl_nothing) { src = jl_type_infer(codeinst->def, jl_atomic_load_acquire(&jl_world_counter), 0); - if (src) - result = jl_emit_code(codeinst->def, src, src->rettype, params); + if (src) { + orc::ThreadSafeModule result_m = + jl_create_ts_module(name_from_method_instance(codeinst->def), + params.tsctx, params.imaging, + original.getDataLayout(), Triple(original.getTargetTriple())); + result.second = jl_emit_code(result_m, codeinst->def, src, src->rettype, params); + result.first = std::move(result_m); + } } else { - result = jl_emit_codeinst(codeinst, NULL, params); + orc::ThreadSafeModule result_m = + jl_create_ts_module(name_from_method_instance(codeinst->def), + params.tsctx, params.imaging, + original.getDataLayout(), Triple(original.getTargetTriple())); + result.second = jl_emit_codeinst(result_m, codeinst, NULL, params); + result.first = std::move(result_m); } if (std::get<0>(result)) decls = &std::get<1>(result); @@ -7934,10 +8756,10 @@ void jl_compile_workqueue( Function *preal = emit_tojlinvoke(codeinst, mod, params); protodecl->setLinkage(GlobalVariable::InternalLinkage); //protodecl->setAlwaysInline(); - jl_init_function(protodecl); + jl_init_function(protodecl, 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(protodecl, proto_cc, proto_return_roots, codeinst->def->specTypes, codeinst->rettype, nrealargs, params, preal); + emit_cfunc_invalidate(protodecl, proto_cc, proto_return_roots, codeinst->def->specTypes, codeinst->rettype, false, nrealargs, params, preal); preal_decl = ""; // no need to fixup the name } else { @@ -7981,63 +8803,76 @@ static JuliaVariable *julia_const_gv(jl_value_t *val) return nullptr; } -static void init_julia_llvm_env(Module *m) +// Handle FLOAT16 ABI v2 +#if JULIA_FLOAT16_ABI == 2 +static void makeCastCall(Module &M, StringRef wrapperName, StringRef calledName, FunctionType *FTwrapper, FunctionType *FTcalled, bool external) { - // every variable or function mapped in this function must be - // exported from libjulia, to support static compilation - - // add needed base debugging definitions to our LLVM environment - DIBuilder dbuilder(*m); - DIFile *julia_h = dbuilder.createFile("julia.h", ""); - jl_value_dillvmt = dbuilder.createStructType(nullptr, - "jl_value_t", - julia_h, - 71, // At the time of this writing. Not sure if it's worth it to keep this in sync - 0 * 8, // sizeof(jl_value_t) * 8, - __alignof__(void*) * 8, // __alignof__(jl_value_t) * 8, - DINode::FlagZero, // Flags - nullptr, // Derived from - nullptr); // Elements - will be corrected later - - jl_pvalue_dillvmt = dbuilder.createPointerType(jl_value_dillvmt, sizeof(jl_value_t*) * 8, - __alignof__(jl_value_t*) * 8); - - SmallVector Elts; - std::vector diargs(0); - Elts.push_back(jl_pvalue_dillvmt); - dbuilder.replaceArrays(jl_value_dillvmt, - dbuilder.getOrCreateArray(Elts)); - - jl_ppvalue_dillvmt = dbuilder.createPointerType(jl_pvalue_dillvmt, sizeof(jl_value_t**) * 8, - __alignof__(jl_value_t**) * 8); + 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); +} - diargs.push_back(jl_pvalue_dillvmt); // Return Type (ret value) - diargs.push_back(jl_pvalue_dillvmt); // First Argument (function) - diargs.push_back(jl_ppvalue_dillvmt); // Second Argument (argv) - // Third argument (length(argv)) - diargs.push_back(_julia_type_to_di(NULL, (jl_value_t*)jl_int32_type, &dbuilder, false)); +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); +} - jl_di_func_sig = dbuilder.createSubroutineType( - dbuilder.getOrCreateTypeArray(diargs)); - jl_di_func_null_sig = dbuilder.createSubroutineType( - dbuilder.getOrCreateTypeArray(None)); +static void init_f16_funcs(void) +{ + auto ctx = jl_ExecutionEngine->acquireContext(); + auto TSM = jl_create_ts_module("F16Wrappers", ctx, imaging_default()); + auto aliasM = TSM.getModuleUnlocked(); + emitFloat16Wrappers(*aliasM, true); + jl_ExecutionEngine->addModule(std::move(TSM)); } +#endif static void init_jit_functions(void) { + add_named_global(jlsmall_typeof_var, &small_typeof); add_named_global(jlstack_chk_guard_var, &__stack_chk_guard); add_named_global(jlRTLD_DEFAULT_var, &jl_RTLD_DEFAULT_handle); -#ifdef _OS_WINDOWS_ add_named_global(jlexe_var, &jl_exe_handle); add_named_global(jldll_var, &jl_libjulia_handle); add_named_global(jldlli_var, &jl_libjulia_internal_handle); -#endif - global_jlvalue_to_llvm(new JuliaVariable{"jl_true", true, get_pjlvalue}, &jl_true); - global_jlvalue_to_llvm(new JuliaVariable{"jl_false", true, get_pjlvalue}, &jl_false); - global_jlvalue_to_llvm(new JuliaVariable{"jl_emptysvec", true, get_pjlvalue}, (jl_value_t**)&jl_emptysvec); - global_jlvalue_to_llvm(new JuliaVariable{"jl_emptytuple", true, get_pjlvalue}, &jl_emptytuple); - global_jlvalue_to_llvm(new JuliaVariable{"jl_diverror_exception", true, get_pjlvalue}, &jl_diverror_exception); - global_jlvalue_to_llvm(new JuliaVariable{"jl_undefref_exception", true, get_pjlvalue}, &jl_undefref_exception); + auto size2pjlvalue = [](Type *T_size) -> Type * { + return get_pjlvalue(T_size->getContext()); + }; + global_jlvalue_to_llvm(new JuliaVariable{"jl_true", true, size2pjlvalue}, &jl_true); + global_jlvalue_to_llvm(new JuliaVariable{"jl_false", true, size2pjlvalue}, &jl_false); + global_jlvalue_to_llvm(new JuliaVariable{"jl_nothing", true, size2pjlvalue}, &jl_nothing); + global_jlvalue_to_llvm(new JuliaVariable{"jl_emptysvec", true, size2pjlvalue}, (jl_value_t**)&jl_emptysvec); + global_jlvalue_to_llvm(new JuliaVariable{"jl_emptytuple", true, size2pjlvalue}, &jl_emptytuple); + global_jlvalue_to_llvm(new JuliaVariable{"jl_diverror_exception", true, size2pjlvalue}, &jl_diverror_exception); + global_jlvalue_to_llvm(new JuliaVariable{"jl_undefref_exception", true, size2pjlvalue}, &jl_undefref_exception); add_named_global(jlgetworld_global, &jl_world_counter); add_named_global("__stack_chk_fail", &__stack_chk_fail); add_named_global(jlpgcstack_func, (void*)NULL); @@ -8057,8 +8892,9 @@ static void init_jit_functions(void) add_named_global(jlcheckassign_func, &jl_checked_assignment); add_named_global(jldeclareconst_func, &jl_declare_constant); add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error); + add_named_global(jlgetbindingwrorerror_func, &jl_get_binding_wr); add_named_global(jlboundp_func, &jl_boundp); - for (auto it : builtin_func_map) + for (auto it : builtin_func_map()) add_named_global(it.second, it.first); add_named_global(jlapplygeneric_func, &jl_apply_generic); add_named_global(jlinvoke_func, &jl_invoke); @@ -8084,6 +8920,7 @@ static void init_jit_functions(void) add_named_global(jl_typeof_func, (void*)NULL); add_named_global(jl_write_barrier_func, (void*)NULL); add_named_global(jldlsym_func, &jl_load_and_lookup); + add_named_global("jl_adopt_thread", &jl_adopt_thread); add_named_global(jlgetcfunctiontrampoline_func, &jl_get_cfunction_trampoline); add_named_global(jlgetnthfieldchecked_func, &jl_get_nth_field_checked); add_named_global(diff_gc_total_bytes_func, &jl_gc_diff_total_bytes); @@ -8094,6 +8931,8 @@ static void init_jit_functions(void) add_named_global(gc_preserve_end_func, (void*)NULL); add_named_global(pointer_from_objref_func, (void*)NULL); add_named_global(except_enter_func, (void*)NULL); + add_named_global(julia_call, (void*)NULL); + add_named_global(julia_call2, (void*)NULL); #ifdef _OS_WINDOWS_ #if defined(_CPU_X86_64_) @@ -8121,8 +8960,6 @@ static void init_jit_functions(void) #undef BOX_F } -char jl_using_gdb_jitevents = 0; - #ifdef JL_USE_INTEL_JITEVENTS char jl_using_intel_jitevents; // Non-zero if running under Intel VTune Amplifier #endif @@ -8135,50 +8972,11 @@ char jl_using_oprofile_jitevents = 0; // Non-zero if running under OProfile char jl_using_perf_jitevents = 0; #endif -void jl_init_debuginfo(void); - extern "C" void jl_init_llvm(void) { - builtin_func_map = - { { jl_f_is_addr, new JuliaFunction{XSTR(jl_f_is), get_func_sig, get_func_attrs} }, - { jl_f_typeof_addr, new JuliaFunction{XSTR(jl_f_typeof), get_func_sig, get_func_attrs} }, - { jl_f_sizeof_addr, new JuliaFunction{XSTR(jl_f_sizeof), get_func_sig, get_func_attrs} }, - { jl_f_issubtype_addr, new JuliaFunction{XSTR(jl_f_issubtype), get_func_sig, get_func_attrs} }, - { jl_f_isa_addr, new JuliaFunction{XSTR(jl_f_isa), get_func_sig, get_func_attrs} }, - { jl_f_typeassert_addr, new JuliaFunction{XSTR(jl_f_typeassert), get_func_sig, get_func_attrs} }, - { jl_f_ifelse_addr, new JuliaFunction{XSTR(jl_f_ifelse), get_func_sig, get_func_attrs} }, - { jl_f__apply_iterate_addr, new JuliaFunction{XSTR(jl_f__apply_iterate), get_func_sig, get_func_attrs} }, - { jl_f__apply_pure_addr, new JuliaFunction{XSTR(jl_f__apply_pure), get_func_sig, get_func_attrs} }, - { jl_f__call_latest_addr, new JuliaFunction{XSTR(jl_f__call_latest), get_func_sig, get_func_attrs} }, - { jl_f__call_in_world_addr, new JuliaFunction{XSTR(jl_f__call_in_world), get_func_sig, get_func_attrs} }, - { jl_f__call_in_world_total_addr, new JuliaFunction{XSTR(jl_f__call_in_world_total), get_func_sig, get_func_attrs} }, - { jl_f_throw_addr, new JuliaFunction{XSTR(jl_f_throw), get_func_sig, get_func_attrs} }, - { jl_f_tuple_addr, jltuple_func }, - { jl_f_svec_addr, new JuliaFunction{XSTR(jl_f_svec), get_func_sig, get_func_attrs} }, - { jl_f_applicable_addr, new JuliaFunction{XSTR(jl_f_applicable), get_func_sig, get_func_attrs} }, - { jl_f_invoke_addr, new JuliaFunction{XSTR(jl_f_invoke), get_func_sig, get_func_attrs} }, - { jl_f_invoke_kwsorter_addr, new JuliaFunction{XSTR(jl_f_invoke_kwsorter), get_func_sig, get_func_attrs} }, - { jl_f_isdefined_addr, new JuliaFunction{XSTR(jl_f_isdefined), get_func_sig, get_func_attrs} }, - { jl_f_getfield_addr, new JuliaFunction{XSTR(jl_f_getfield), get_func_sig, get_func_attrs} }, - { jl_f_setfield_addr, new JuliaFunction{XSTR(jl_f_setfield), get_func_sig, get_func_attrs} }, - { jl_f_swapfield_addr, new JuliaFunction{XSTR(jl_f_swapfield), get_func_sig, get_func_attrs} }, - { jl_f_modifyfield_addr, new JuliaFunction{XSTR(jl_f_modifyfield), get_func_sig, get_func_attrs} }, - { jl_f_fieldtype_addr, new JuliaFunction{XSTR(jl_f_fieldtype), get_func_sig, get_func_attrs} }, - { 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_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_func_sig, get_donotdelete_func_attrs} } - }; - + jl_page_size = jl_getpagesize(); jl_default_debug_info_kind = (int) DICompileUnit::DebugEmissionKind::FullDebug; - imaging_mode = jl_options.image_codegen || (jl_generating_output() && !jl_options.incremental); jl_default_cgparams.generic_context = jl_nothing; - jl_init_debuginfo(); InitializeNativeTarget(); InitializeNativeTargetAsmPrinter(); @@ -8188,7 +8986,9 @@ 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); @@ -8211,104 +9011,40 @@ extern "C" void jl_init_llvm(void) clopt = llvmopts.lookup("enable-tail-merge"); // NOO TOUCHIE; NO TOUCH! See #922 if (clopt->getNumOccurrences() == 0) cl::ProvidePositionalOption(clopt, "0", 1); +#ifdef JL_USE_NEW_PM + // For parity with LoopUnswitch + clopt = llvmopts.lookup("unswitch-threshold"); + if (clopt->getNumOccurrences() == 0) + cl::ProvidePositionalOption(clopt, "100", 1); +#endif // if the patch adding this option has been applied, lower its limit to provide // better DAGCombiner performance. clopt = llvmopts.lookup("combiner-store-merge-dependence-limit"); if (clopt && clopt->getNumOccurrences() == 0) cl::ProvidePositionalOption(clopt, "4", 1); - TargetOptions options = TargetOptions(); - //options.PrintMachineCode = true; //Print machine code produced during JIT compiling -#if defined(_OS_WINDOWS_) && !defined(_CPU_X86_64_) && JL_LLVM_VERSION < 130000 - // tell Win32 to assume the stack is always 16-byte aligned, - // and to ensure that it is 16-byte aligned for out-going calls, - // to ensure compatibility with GCC codes - // In LLVM 13 and onwards this has turned into a module option - options.StackAlignmentOverride = 16; -#endif -#if defined(JL_DEBUG_BUILD) && JL_LLVM_VERSION < 130000 - // LLVM defaults to tls stack guard, which causes issues with Julia's tls implementation - options.StackProtectorGuard = StackProtectorGuards::Global; -#endif - Triple TheTriple(sys::getProcessTriple()); -#if defined(FORCE_ELF) - TheTriple.setObjectFormat(Triple::ELF); -#endif - uint32_t target_flags = 0; - auto target = jl_get_llvm_target(imaging_mode, target_flags); - auto &TheCPU = target.first; - SmallVector targetFeatures(target.second.begin(), target.second.end()); - std::string errorstr; - const Target *TheTarget = TargetRegistry::lookupTarget("", TheTriple, errorstr); - if (!TheTarget) - jl_errorf("%s", errorstr.c_str()); - if (jl_processor_print_help || (target_flags & JL_TARGET_UNKNOWN_NAME)) { - std::unique_ptr MSTI( - TheTarget->createMCSubtargetInfo(TheTriple.str(), "", "")); - if (!MSTI->isCPUStringValid(TheCPU)) - jl_errorf("Invalid CPU name \"%s\".", TheCPU.c_str()); - if (jl_processor_print_help) { - // This is the only way I can find to print the help message once. - // It'll be nice if we can iterate through the features and print our own help - // message... - MSTI->setDefaultFeatures("help", "", ""); - } - } - // Package up features to be passed to target/subtarget - std::string FeaturesStr; - if (!targetFeatures.empty()) { - SubtargetFeatures Features; - for (unsigned i = 0; i != targetFeatures.size(); ++i) - Features.AddFeature(targetFeatures[i]); - FeaturesStr = Features.getString(); - } - // Allocate a target... - Optional codemodel = -#if defined(JL_USE_JITLINK) - // JITLink can patch up relocations between far objects so we can use the - // small code model – which is good, as the large code model is unmaintained - // on MachO/AArch64. - CodeModel::Small; -#elif defined(_P64) - // Make sure we are using the large code model on 64bit - // Let LLVM pick a default suitable for jitting on 32bit - CodeModel::Large; +#if JL_LLVM_VERSION >= 150000 + clopt = llvmopts.lookup("opaque-pointers"); + if (clopt && clopt->getNumOccurrences() == 0) { +#ifdef JL_LLVM_OPAQUE_POINTERS + cl::ProvidePositionalOption(clopt, "true", 1); #else - None; + cl::ProvidePositionalOption(clopt, "false", 1); +#endif + } #endif - auto optlevel = CodeGenOptLevelFor(jl_options.opt_level); - jl_TargetMachine = TheTarget->createTargetMachine( - TheTriple.getTriple(), TheCPU, FeaturesStr, - options, - Reloc::Static, // Generate simpler code for JIT - codemodel, - optlevel, - true // JIT - ); - assert(jl_TargetMachine && "Failed to select target machine -" - " Is the LLVM backend for this CPU enabled?"); - #if (!defined(_CPU_ARM_) && !defined(_CPU_PPC64_)) - // FastISel seems to be buggy for ARM. Ref #13321 - if (jl_options.opt_level < 2) - jl_TargetMachine->setFastISel(true); - #endif - - jl_ExecutionEngine = new JuliaOJIT(*jl_TargetMachine, &jl_LLVMContext); - // Mark our address spaces as non-integral - jl_data_layout = jl_ExecutionEngine->getDataLayout(); - std::string DL = jl_data_layout.getStringRepresentation() + "-ni:10:11:12:13"; - jl_data_layout.reset(DL); + jl_ExecutionEngine = new JuliaOJIT(); + bool jl_using_gdb_jitevents = false; // Register GDB event listener #if defined(JL_DEBUG_BUILD) - jl_using_gdb_jitevents = 1; -# else + jl_using_gdb_jitevents = true; +#endif const char *jit_gdb = getenv("ENABLE_GDBLISTENER"); - if (jit_gdb && atoi(jit_gdb)) { - jl_using_gdb_jitevents = 1; + if (jit_gdb) { + jl_using_gdb_jitevents = !!atoi(jit_gdb); } -#endif if (jl_using_gdb_jitevents) jl_ExecutionEngine->enableJITDebuggingSupport(); @@ -8316,7 +9052,7 @@ extern "C" void jl_init_llvm(void) defined(JL_USE_OPROFILE_JITEVENTS) || \ defined(JL_USE_PERF_JITEVENTS) #ifdef JL_USE_JITLINK -#error "JIT profiling support (JL_USE_*_JITEVENTS) not yet available on platforms that use JITLink" +#pragma message("JIT profiling support (JL_USE_*_JITEVENTS) not yet available on platforms that use JITLink") #else const char *jit_profiling = getenv("ENABLE_JITPROFILING"); @@ -8338,6 +9074,7 @@ extern "C" void jl_init_llvm(void) } #endif +#ifndef JL_USE_JITLINK #ifdef JL_USE_INTEL_JITEVENTS if (jl_using_intel_jitevents) jl_ExecutionEngine->RegisterJITEventListener(JITEventListener::createIntelJITEventListener()); @@ -8353,28 +9090,28 @@ extern "C" void jl_init_llvm(void) jl_ExecutionEngine->RegisterJITEventListener(JITEventListener::createPerfJITEventListener()); #endif #endif +#endif #endif cl::PrintOptionValues(); } -extern "C" JL_DLLEXPORT void jl_init_codegen_impl(void) +extern "C" JL_DLLEXPORT_CODEGEN void jl_init_codegen_impl(void) { jl_init_llvm(); // Now that the execution engine exists, initialize all modules - jl_init_jit(); init_jit_functions(); - - Module *m = _jl_create_llvm_module("julia", jl_LLVMContext, &jl_default_cgparams); - init_julia_llvm_env(m); - - jl_init_intrinsic_functions_codegen(); +#if JULIA_FLOAT16_ABI == 2 + init_f16_funcs(); +#endif } -extern "C" JL_DLLEXPORT void jl_teardown_codegen_impl() +extern "C" JL_DLLEXPORT_CODEGEN void jl_teardown_codegen_impl() JL_NOTSAFEPOINT { // output LLVM timings and statistics - reportAndResetTimings(); + // Guard against exits before we have initialized the ExecutionEngine + if (jl_ExecutionEngine) + jl_ExecutionEngine->printTimers(); PrintStatistics(); } @@ -8446,17 +9183,21 @@ extern void jl_write_bitcode_module(void *M, char *fname) { #include -extern "C" JL_DLLEXPORT jl_value_t *jl_get_libllvm_impl(void) JL_NOTSAFEPOINT +extern "C" JL_DLLEXPORT_CODEGEN jl_value_t *jl_get_libllvm_impl(void) JL_NOTSAFEPOINT { #if defined(_OS_WINDOWS_) HMODULE mod; if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)&llvm::DebugFlag, &mod)) return jl_nothing; - - char path[MAX_PATH]; - if (!GetModuleFileNameA(mod, path, sizeof(path))) + wchar_t path16[MAX_PATH]; + DWORD n16 = GetModuleFileNameW(mod, path16, MAX_PATH); + if (n16 <= 0) + return jl_nothing; + path16[n16++] = 0; + char path8[MAX_PATH * 3]; + if (!WideCharToMultiByte(CP_UTF8, 0, path16, n16, path8, MAX_PATH * 3, NULL, NULL)) return jl_nothing; - return (jl_value_t*) jl_symbol(path); + return (jl_value_t*) jl_symbol(path8); #else Dl_info dli; if (!dladdr((void*)LLVMContextCreate, &dli)) diff --git a/src/codegen_shared.h b/src/codegen_shared.h deleted file mode 100644 index 181cf51cffc0e..0000000000000 --- a/src/codegen_shared.h +++ /dev/null @@ -1,303 +0,0 @@ -// This file is a part of Julia. License is MIT: https://julialang.org/license - -#include -#include -#include -#include -#include -#include -#include "julia.h" - -#define STR(csym) #csym -#define XSTR(csym) STR(csym) - -enum AddressSpace { - Generic = 0, - Tracked = 10, - Derived = 11, - CalleeRooted = 12, - Loaded = 13, - FirstSpecial = Tracked, - LastSpecial = Loaded, -}; - -namespace JuliaType { - static inline llvm::StructType* get_jlvalue_ty(llvm::LLVMContext &C) { - return llvm::StructType::get(C); - } - - static inline llvm::PointerType* get_pjlvalue_ty(llvm::LLVMContext &C) { - return llvm::PointerType::get(get_jlvalue_ty(C), 0); - } - - static inline llvm::PointerType* get_prjlvalue_ty(llvm::LLVMContext &C) { - return llvm::PointerType::get(get_jlvalue_ty(C), AddressSpace::Tracked); - } - - static inline llvm::PointerType* get_ppjlvalue_ty(llvm::LLVMContext &C) { - return llvm::PointerType::get(get_pjlvalue_ty(C), 0); - } - - static inline llvm::PointerType* get_pprjlvalue_ty(llvm::LLVMContext &C) { - return llvm::PointerType::get(get_prjlvalue_ty(C), 0); - } - - static inline auto get_jlfunc_ty(llvm::LLVMContext &C) { - auto T_prjlvalue = get_prjlvalue_ty(C); - auto T_pprjlvalue = llvm::PointerType::get(T_prjlvalue, 0); - std::vector ftargs(0); - ftargs.push_back(T_prjlvalue); // function - ftargs.push_back(T_pprjlvalue); // args[] - ftargs.push_back(llvm::Type::getInt32Ty(C)); // nargs - return llvm::FunctionType::get(T_prjlvalue, ftargs, false); - } - - static inline auto get_jlfuncparams_ty(llvm::LLVMContext &C) { - auto T_prjlvalue = get_prjlvalue_ty(C); - auto T_pprjlvalue = llvm::PointerType::get(T_prjlvalue, 0); - std::vector ftargs(0); - ftargs.push_back(T_prjlvalue); // function - ftargs.push_back(T_pprjlvalue); // args[] - ftargs.push_back(llvm::Type::getInt32Ty(C)); // nargs - ftargs.push_back(T_pprjlvalue); // linfo->sparam_vals - return llvm::FunctionType::get(T_prjlvalue, ftargs, false); - } - - static inline auto get_pvoidfunc_ty(llvm::LLVMContext &C) { - return llvm::FunctionType::get(llvm::Type::getVoidTy(C), /*isVarArg*/false)->getPointerTo(); - } -} - -// JLCALL with API arguments ([extra], arg0, arg1, arg2, ...) has the following ABI calling conventions defined: -#define JLCALL_F_CC (CallingConv::ID)37 // (jl_value_t *arg0, jl_value_t **argv, uint32_t nargv) -#define JLCALL_F2_CC (CallingConv::ID)38 // (jl_value_t *arg0, jl_value_t **argv, uint32_t nargv, jl_value_t *extra) - -// return how many Tracked pointers are in T (count > 0), -// and if there is anything else in T (all == false) -struct CountTrackedPointers { - unsigned count = 0; - bool all = true; - bool derived = false; - CountTrackedPointers(llvm::Type *T); -}; - -unsigned TrackWithShadow(llvm::Value *Src, llvm::Type *T, bool isptr, llvm::Value *Dst, llvm::IRBuilder<> &irbuilder); -std::vector ExtractTrackedValues(llvm::Value *Src, llvm::Type *STy, bool isptr, llvm::IRBuilder<> &irbuilder, llvm::ArrayRef perm_offsets={}); - -static inline void llvm_dump(llvm::Value *v) -{ - v->print(llvm::dbgs(), true); - llvm::dbgs() << "\n"; -} - -static inline void llvm_dump(llvm::Type *v) -{ - v->print(llvm::dbgs(), true); - llvm::dbgs() << "\n"; -} - -static inline void llvm_dump(llvm::Function *f) -{ - f->print(llvm::dbgs(), nullptr, false, true); -} - -static inline void llvm_dump(llvm::Module *m) -{ - m->print(llvm::dbgs(), nullptr); -} - -static inline void llvm_dump(llvm::Metadata *m) -{ - m->print(llvm::dbgs()); - llvm::dbgs() << "\n"; -} - -static inline void llvm_dump(llvm::DebugLoc *dbg) -{ - dbg->print(llvm::dbgs()); - llvm::dbgs() << "\n"; -} - -static inline std::pair tbaa_make_child_with_context(llvm::LLVMContext &ctxt, const char *name, llvm::MDNode *parent=nullptr, bool isConstant=false) -{ - llvm::MDBuilder mbuilder(ctxt); - llvm::MDNode *jtbaa = mbuilder.createTBAARoot("jtbaa"); - llvm::MDNode *tbaa_root = mbuilder.createTBAAScalarTypeNode("jtbaa", jtbaa); - llvm::MDNode *scalar = mbuilder.createTBAAScalarTypeNode(name, parent ? parent : tbaa_root); - llvm::MDNode *n = mbuilder.createTBAAStructTagNode(scalar, scalar, 0, isConstant); - return std::make_pair(n, scalar); -} - -static inline llvm::MDNode *get_tbaa_const(llvm::LLVMContext &ctxt) { - return tbaa_make_child_with_context(ctxt, "jtbaa_const", nullptr, true).first; -} - -static inline llvm::Instruction *tbaa_decorate(llvm::MDNode *md, llvm::Instruction *inst) -{ - 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)); - return inst; -} - -// bitcast a value, but preserve its address space when dealing with pointer types -static inline llvm::Value *emit_bitcast_with_builder(llvm::IRBuilder<> &builder, llvm::Value *v, llvm::Type *jl_value) -{ - using namespace llvm; - if (isa(jl_value) && - v->getType()->getPointerAddressSpace() != jl_value->getPointerAddressSpace()) { - // Cast to the proper address space - Type *jl_value_addr = - PointerType::get(cast(jl_value)->getElementType(), - v->getType()->getPointerAddressSpace()); - return builder.CreateBitCast(v, jl_value_addr); - } - else { - return builder.CreateBitCast(v, jl_value); - } -} - -// Get PTLS through current task. -static inline llvm::Value *get_current_ptls_from_task(llvm::IRBuilder<> &builder, llvm::Value *current_task, llvm::MDNode *tbaa) -{ - using namespace llvm; - auto T_ppjlvalue = JuliaType::get_ppjlvalue_ty(builder.getContext()); - auto T_pjlvalue = JuliaType::get_pjlvalue_ty(builder.getContext()); - auto T_size = builder.GetInsertBlock()->getModule()->getDataLayout().getIntPtrType(builder.getContext()); - const int ptls_offset = offsetof(jl_task_t, ptls); - llvm::Value *pptls = builder.CreateInBoundsGEP( - T_pjlvalue, current_task, - ConstantInt::get(T_size, ptls_offset / sizeof(void *)), - "ptls_field"); - LoadInst *ptls_load = builder.CreateAlignedLoad(T_pjlvalue, - emit_bitcast_with_builder(builder, pptls, T_ppjlvalue), Align(sizeof(void *)), "ptls_load"); - // Note: Corresponding store (`t->ptls = ptls`) happens in `ctx_switch` of tasks.c. - tbaa_decorate(tbaa, ptls_load); - // Using `CastInst::Create` to get an `Instruction*` without explicit cast: - auto ptls = CastInst::Create(Instruction::BitCast, ptls_load, T_ppjlvalue, "ptls"); - builder.Insert(ptls); - return ptls; -} - -// Compatibility shims for LLVM attribute APIs that were renamed in LLVM 14. -// -// Once we no longer support LLVM < 14, these can be mechanically removed by -// translating foo(Bar, …) into Bar->foo(…) resp. Bar.foo(…). -namespace { -using namespace llvm; - -inline void addFnAttr(CallInst *Target, Attribute::AttrKind Attr) -{ -#if JL_LLVM_VERSION >= 140000 - Target->addFnAttr(Attr); -#else - Target->addAttribute(AttributeList::FunctionIndex, Attr); -#endif -} - -template -inline void addRetAttr(T *Target, A Attr) -{ -#if JL_LLVM_VERSION >= 140000 - Target->addRetAttr(Attr); -#else - Target->addAttribute(AttributeList::ReturnIndex, Attr); -#endif -} - -inline void addAttributeAtIndex(Function *F, unsigned Index, Attribute Attr) -{ -#if JL_LLVM_VERSION >= 140000 - F->addAttributeAtIndex(Index, Attr); -#else - F->addAttribute(Index, Attr); -#endif -} - -inline AttributeSet getFnAttrs(const AttributeList &Attrs) -{ -#if JL_LLVM_VERSION >= 140000 - return Attrs.getFnAttrs(); -#else - return Attrs.getFnAttributes(); -#endif -} - -inline AttributeSet getRetAttrs(const AttributeList &Attrs) -{ -#if JL_LLVM_VERSION >= 140000 - return Attrs.getRetAttrs(); -#else - return Attrs.getRetAttributes(); -#endif -} - -inline bool hasFnAttr(const AttributeList &L, Attribute::AttrKind Kind) -{ -#if JL_LLVM_VERSION >= 140000 - return L.hasFnAttr(Kind); -#else - return L.hasAttribute(AttributeList::FunctionIndex, Kind); -#endif -} - -inline AttributeList addAttributeAtIndex(const AttributeList &L, LLVMContext &C, - unsigned Index, Attribute::AttrKind Kind) -{ -#if JL_LLVM_VERSION >= 140000 - return L.addAttributeAtIndex(C, Index, Kind); -#else - return L.addAttribute(C, Index, Kind); -#endif -} - -inline AttributeList addAttributeAtIndex(const AttributeList &L, LLVMContext &C, - unsigned Index, Attribute Attr) -{ -#if JL_LLVM_VERSION >= 140000 - return L.addAttributeAtIndex(C, Index, Attr); -#else - return L.addAttribute(C, Index, Attr); -#endif -} - -inline AttributeList addAttributesAtIndex(const AttributeList &L, LLVMContext &C, - unsigned Index, const AttrBuilder &Builder) -{ -#if JL_LLVM_VERSION >= 140000 - return L.addAttributesAtIndex(C, Index, Builder); -#else - return L.addAttributes(C, Index, Builder); -#endif -} - -inline AttributeList addFnAttribute(const AttributeList &L, LLVMContext &C, - Attribute::AttrKind Kind) -{ -#if JL_LLVM_VERSION >= 140000 - return L.addFnAttribute(C, Kind); -#else - return L.addAttribute(C, AttributeList::FunctionIndex, Kind); -#endif -} - -inline AttributeList addRetAttribute(const AttributeList &L, LLVMContext &C, - Attribute::AttrKind Kind) -{ -#if JL_LLVM_VERSION >= 140000 - return L.addRetAttribute(C, Kind); -#else - return L.addAttribute(C, AttributeList::ReturnIndex, Kind); -#endif -} - -inline bool hasAttributesAtIndex(const AttributeList &L, unsigned Index) -{ -#if JL_LLVM_VERSION >= 140000 - return L.hasAttributesAtIndex(Index); -#else - return L.hasAttributes(Index); -#endif -} - -} diff --git a/src/common_symbols1.inc b/src/common_symbols1.inc index 7d445289e80fa..547d5d0eabede 100644 --- a/src/common_symbols1.inc +++ b/src/common_symbols1.inc @@ -70,7 +70,7 @@ jl_symbol("toInt64"), jl_symbol("arraylen"), jl_symbol("typeassert"), jl_symbol("map"), -jl_symbol("kwfunc"), +jl_symbol("kwcall"), jl_symbol("ArgumentError"), jl_symbol("lshr_int"), jl_symbol("axes"), @@ -96,4 +96,3 @@ jl_symbol("structdiff"), jl_symbol("undef"), jl_symbol("sizeof"), jl_symbol("String"), -jl_symbol("namedtuple.jl"), diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index c9f4e41b83e33..b5a334172dd76 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -1,3 +1,4 @@ +jl_symbol("namedtuple.jl"), jl_symbol("pop"), jl_symbol("inbounds"), jl_symbol("strings/string.jl"), @@ -251,4 +252,3 @@ jl_symbol("view"), jl_symbol("GitError"), jl_symbol("zeros"), jl_symbol("InexactError"), -jl_symbol("LogLevel"), diff --git a/src/coverage.cpp b/src/coverage.cpp index 4ce33c105691c..95924f326524b 100644 --- a/src/coverage.cpp +++ b/src/coverage.cpp @@ -17,7 +17,7 @@ using namespace llvm; static int codegen_imaging_mode(void) { - return jl_options.image_codegen || (jl_generating_output() && !jl_options.incremental); + return jl_options.image_codegen || (jl_generating_output() && jl_options.use_pkgimages); } // Logging for code coverage and memory allocation @@ -192,7 +192,7 @@ static void write_lcov_data(logdata_t &logData, const std::string &outfile) outf.close(); } -extern "C" JL_DLLEXPORT void jl_write_coverage_data(const char *output) +extern "C" void jl_write_coverage_data(const char *output) { if (output) { StringRef output_pattern(output); @@ -201,14 +201,14 @@ extern "C" JL_DLLEXPORT void jl_write_coverage_data(const char *output) } else { std::string stm; - raw_string_ostream(stm) << "." << jl_getpid() << ".cov"; + raw_string_ostream(stm) << "." << uv_os_getpid() << ".cov"; write_log_data(coverageData, stm.c_str()); } } -extern "C" JL_DLLEXPORT void jl_write_malloc_log(void) +extern "C" void jl_write_malloc_log(void) { std::string stm; - raw_string_ostream(stm) << "." << jl_getpid() << ".mem"; + raw_string_ostream(stm) << "." << uv_os_getpid() << ".mem"; write_log_data(mallocData, stm.c_str()); } diff --git a/src/crc32c.c b/src/crc32c.c index 1e57d8aef85db..4ca8db06459a1 100644 --- a/src/crc32c.c +++ b/src/crc32c.c @@ -204,7 +204,11 @@ static crc32c_func_t crc32c_dispatch(void) # define crc32c_dispatch_ifunc "crc32c_dispatch" # endif #elif defined(_CPU_AARCH64_) +#ifdef _COMPILER_CLANG_ +#define CRC_TARGET __attribute__((target("crc"))) +#else #define CRC_TARGET __attribute__((target("+crc"))) +#endif /* Compute CRC-32C using the ARMv8 CRC32 extension. */ CRC_TARGET static inline uint32_t crc32cx(uint32_t crc, uint64_t val) { @@ -346,7 +350,7 @@ static crc32c_func_t crc32c_dispatch(unsigned long hwcap) # define crc32c_dispatch() crc32c_dispatch(getauxval(AT_HWCAP)) # define crc32c_dispatch_ifunc "crc32c_dispatch" # else -# warning CRC32 feature detection not implemented for this OS. Falling back to software version. +# pragma message("CRC32 feature detection not implemented for this OS. Falling back to software version.") # endif #else // If we don't have any accelerated version to define, just make the _sw version define diff --git a/src/datatype.c b/src/datatype.c index e7f1ab22365b8..95c3b11c9abdc 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -51,10 +51,9 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo 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->cache, jl_nothing); - mt->max_args = 0; - mt->kwsorter = NULL; + jl_atomic_store_relaxed(&mt->max_args, 0); mt->backedges = NULL; - JL_MUTEX_INIT(&mt->writelock); + JL_MUTEX_INIT(&mt->writelock, "methodtable->writelock"); mt->offs = 0; mt->frozen = 0; return mt; @@ -69,10 +68,12 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->name = name; tn->module = module; tn->wrapper = NULL; + jl_atomic_store_relaxed(&tn->Typeofwrapper, NULL); jl_atomic_store_relaxed(&tn->cache, jl_emptysvec); jl_atomic_store_relaxed(&tn->linearcache, jl_emptysvec); tn->names = NULL; - tn->hash = bitmix(bitmix(module ? module->build_id : 0, name->hash), 0xa1ada1da); + tn->hash = bitmix(bitmix(module ? module->build_id.lo : 0, name->hash), 0xa1ada1da); + tn->_reserved = 0; tn->abstract = abstract; tn->mutabl = mutabl; tn->mayinlinealloc = 0; @@ -80,6 +81,7 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->partial = NULL; tn->atomicfields = NULL; tn->constfields = NULL; + tn->max_methods = 0; return tn; } @@ -94,13 +96,18 @@ jl_datatype_t *jl_new_uninitialized_datatype(void) { jl_task_t *ct = jl_current_task; jl_datatype_t *t = (jl_datatype_t*)jl_gc_alloc(ct->ptls, sizeof(jl_datatype_t), jl_datatype_type); + jl_set_typetagof(t, jl_datatype_tag, 0); t->hash = 0; t->hasfreetypevars = 0; t->isdispatchtuple = 0; t->isbitstype = 0; + t->isprimitivetype = 0; t->zeroinit = 0; t->has_concrete_subtype = 1; - t->cached_by_hash = 0; + t->maybe_subtype_of_cache = 1; + t->ismutationfree = 0; + t->isidentityfree = 0; + t->smalltag = 0; t->name = NULL; t->super = NULL; t->parameters = NULL; @@ -110,7 +117,65 @@ jl_datatype_t *jl_new_uninitialized_datatype(void) return t; } -static jl_datatype_layout_t *jl_get_layout(uint32_t nfields, +#include "support/htable.inc" + +static uint32_t _hash_djb2(uint32_t hash, const char *mem, size_t s) JL_NOTSAFEPOINT +{ + for (size_t i = 0; i < s; i++) + hash = ((hash << 5) + hash) + mem[i]; + return hash; +} + +static uint32_t _hash_layout_djb2(uintptr_t _layout, void *unused) JL_NOTSAFEPOINT +{ + (void)unused; + jl_datatype_layout_t* layout = (jl_datatype_layout_t *)_layout; + assert(layout); + 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); + const char *pointers = jl_dt_layout_ptrs(layout); + assert(pointers); + size_t pointers_size = (layout->npointers << layout->fielddesc_type); + + uint_t hash = 5381; + hash = _hash_djb2(hash, (char *)layout, own_size); + hash = _hash_djb2(hash, fields, fields_size); + hash = _hash_djb2(hash, pointers, pointers_size); + return hash; +} + +static int layout_eq(void *_l1, void *_l2, void *unused) JL_NOTSAFEPOINT +{ + (void)unused; + jl_datatype_layout_t *l1 = (jl_datatype_layout_t *)_l1; + jl_datatype_layout_t *l2 = (jl_datatype_layout_t *)_l2; + if (memcmp(l1, l2, sizeof(jl_datatype_layout_t))) + 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); + 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); + if (memcmp(p1, p2, pointers_size)) + return 0; + return 1; +} + +//HTPROT(layoutcache) +static void **layoutcache_lookup_bp_r(htable_t *h, void *key, void *ctx) JL_NOTSAFEPOINT; +static void **layoutcache_peek_bp_r(htable_t *h, void *key, void *ctx) JL_NOTSAFEPOINT; +HTPROT_R(layoutcache) +HTIMPL_R(layoutcache, _hash_layout_djb2, layout_eq) +static htable_t layoutcache; +static int layoutcache_initialized = 0; + +static jl_datatype_layout_t *jl_get_layout(uint32_t sz, + uint32_t nfields, uint32_t npointers, uint32_t alignment, int haspadding, @@ -144,23 +209,28 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t nfields, } } - // allocate a new descriptor - // TODO: lots of these are the same--take advantage of the fact these are immutable to combine them - uint32_t fielddesc_size = jl_fielddesc_size(fielddesc_type); - jl_datatype_layout_t *flddesc = (jl_datatype_layout_t*)jl_gc_perm_alloc( - sizeof(jl_datatype_layout_t) + nfields * fielddesc_size + (npointers << fielddesc_type), - 0, 4, 0); + // 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 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); + jl_datatype_layout_t *allocamem = (jl_datatype_layout_t *)(should_malloc ? NULL : alloca(flddesc_sz)); + jl_datatype_layout_t *flddesc = should_malloc ? mallocmem : allocamem; + assert(flddesc); + flddesc->size = sz; flddesc->nfields = nfields; flddesc->alignment = alignment; flddesc->haspadding = haspadding; flddesc->fielddesc_type = fielddesc_type; + flddesc->padding = 0; flddesc->npointers = npointers; flddesc->first_ptr = (npointers > 0 ? pointers[0] : -1); // fill out the fields of the new descriptor - jl_fielddesc8_t* desc8 = (jl_fielddesc8_t*)jl_dt_layout_fields(flddesc); - jl_fielddesc16_t* desc16 = (jl_fielddesc16_t*)jl_dt_layout_fields(flddesc); - jl_fielddesc32_t* desc32 = (jl_fielddesc32_t*)jl_dt_layout_fields(flddesc); + jl_fielddesc8_t *desc8 = (jl_fielddesc8_t *)jl_dt_layout_fields(flddesc); + jl_fielddesc16_t *desc16 = (jl_fielddesc16_t *)jl_dt_layout_fields(flddesc); + jl_fielddesc32_t *desc32 = (jl_fielddesc32_t *)jl_dt_layout_fields(flddesc); for (size_t i = 0; i < nfields; i++) { if (fielddesc_type == 0) { desc8[i].offset = desc[i].offset; @@ -178,9 +248,9 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t nfields, 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); + 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]; @@ -192,7 +262,32 @@ static jl_datatype_layout_t *jl_get_layout(uint32_t nfields, ptrs32[i] = pointers[i]; } } - return flddesc; + + if (__unlikely(!layoutcache_initialized)) { + htable_new(&layoutcache, 4096); + layoutcache_initialized = 1; + } + + // Check the cache to see if this object already exists. + // Add to cache if not present, free temp buffer, return. + jl_datatype_layout_t *ret = + (jl_datatype_layout_t *)layoutcache_get_r(&layoutcache, flddesc, NULL); + if ((void*)ret == HT_NOTFOUND) { + if (!should_malloc) { + char *perm_mem = (char *)jl_gc_perm_alloc(flddesc_sz, 0, 4, 0); + assert(perm_mem); + ret = (jl_datatype_layout_t *)perm_mem; + memcpy(perm_mem, flddesc, flddesc_sz); + } + else { + ret = mallocmem; + } + layoutcache_put_r(&layoutcache, ret, ret, NULL); + return ret; + } + + if (should_malloc) free(flddesc); + return ret; } // Determine if homogeneous tuple with fields of type t will have @@ -355,6 +450,34 @@ static void throw_ovf(int should_malloc, void *desc, jl_datatype_t* st, int offs jl_errorf("type %s has field offset %d that exceeds the page size", jl_symbol_name(st->name->name), offset); } +static int is_type_mutationfree(jl_value_t *t) +{ + t = jl_unwrap_unionall(t); + if (jl_is_uniontype(t)) { + jl_uniontype_t *u = (jl_uniontype_t*)t; + return is_type_mutationfree(u->a) && is_type_mutationfree(u->b); + } + if (jl_is_datatype(t)) { + return ((jl_datatype_t*)t)->ismutationfree; + } + // Free tvars, etc. + return 0; +} + +static int is_type_identityfree(jl_value_t *t) +{ + t = jl_unwrap_unionall(t); + if (jl_is_uniontype(t)) { + jl_uniontype_t *u = (jl_uniontype_t*)t; + return is_type_identityfree(u->a) && is_type_identityfree(u->b); + } + if (jl_is_datatype(t)) { + return ((jl_datatype_t*)t)->isidentityfree; + } + // Free tvars, etc. + return 0; +} + void jl_compute_field_offsets(jl_datatype_t *st) { const uint64_t max_offset = (((uint64_t)1) << 32) - 1; @@ -366,16 +489,16 @@ void jl_compute_field_offsets(jl_datatype_t *st) if (st == w && st->layout) { // this check allows us to force re-computation of the layout for some types during init st->layout = NULL; - st->size = 0; st->zeroinit = 0; st->has_concrete_subtype = 1; } int isbitstype = st->isconcretetype && st->name->mayinlinealloc; + int ismutationfree = !w->layout || !jl_is_layout_opaque(w->layout); + int isidentityfree = !st->name->mutabl; // If layout doesn't depend on type parameters, it's stored in st->name->wrapper // and reused by all subtypes. if (w->layout) { st->layout = w->layout; - st->size = w->size; st->zeroinit = w->zeroinit; st->has_concrete_subtype = w->has_concrete_subtype; if (!jl_is_layout_opaque(st->layout)) { // e.g. jl_array_typename @@ -391,18 +514,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, 1, -1, 1, 0, 0}; + static const jl_datatype_layout_t opaque_byte_layout = {0, 0, 1, -1, 1, 0, 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, 1, -1, sizeof(void*), 0, 0}; + static const jl_datatype_layout_t opaque_ptr_layout = {0, 0, 1, -1, sizeof(void*), 0, 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, -1, 1, 0, 0}; + static const jl_datatype_layout_t singleton_layout = {0, 0, 0, -1, 1, 0, 0}; st->layout = &singleton_layout; } } @@ -422,9 +545,11 @@ void jl_compute_field_offsets(jl_datatype_t *st) } } - for (i = 0; isbitstype && i < nfields; i++) { + for (i = 0; (isbitstype || isidentityfree || ismutationfree) && i < nfields; i++) { jl_value_t *fld = jl_field_type(st, i); - isbitstype = jl_isbits(fld); + isbitstype &= jl_isbits(fld); + ismutationfree &= (!st->name->mutabl || jl_field_isconst(st, i)) && is_type_mutationfree(fld); + isidentityfree &= is_type_identityfree(fld); } // if we didn't reuse the layout above, compute it now @@ -523,9 +648,10 @@ void jl_compute_field_offsets(jl_datatype_t *st) if (al > alignm) alignm = al; } - st->size = LLT_ALIGN(sz, alignm); - if (st->size > sz) + if (LLT_ALIGN(sz, alignm) > sz) { haspadding = 1; + sz = LLT_ALIGN(sz, alignm); + } if (should_malloc && npointers) pointers = (uint32_t*)malloc_s(npointers * sizeof(uint32_t)); else @@ -544,7 +670,7 @@ void jl_compute_field_offsets(jl_datatype_t *st) } } assert(ptr_i == npointers); - st->layout = jl_get_layout(nfields, npointers, alignm, haspadding, desc, pointers); + st->layout = jl_get_layout(sz, nfields, npointers, alignm, haspadding, desc, pointers); if (should_malloc) { free(desc); if (npointers) @@ -555,6 +681,8 @@ void jl_compute_field_offsets(jl_datatype_t *st) // now finish deciding if this instantiation qualifies for special properties assert(!isbitstype || st->layout->npointers == 0); // the definition of isbits st->isbitstype = isbitstype; + st->ismutationfree = ismutationfree; + st->isidentityfree = isidentityfree; jl_maybe_allocate_singleton_instance(st); return; } @@ -592,7 +720,6 @@ JL_DLLEXPORT jl_datatype_t *jl_new_datatype( jl_gc_wb(t, t->parameters); t->types = ftypes; if (ftypes != NULL) jl_gc_wb(t, t->types); - t->size = 0; t->name = NULL; if (jl_is_typename(name)) { @@ -698,9 +825,14 @@ JL_DLLEXPORT jl_datatype_t *jl_new_primitivetype(jl_value_t *name, jl_module_t * uint32_t alignm = next_power_of_two(nbytes); if (alignm > MAX_ALIGN) alignm = MAX_ALIGN; + // memoize isprimitivetype, since it is much easier than checking + // (dta->name->names == svec() && dta->layout && dta->layout->size != 0) + // and we easily have a free bit for it in the DataType flags + bt->isprimitivetype = 1; + bt->ismutationfree = 1; + bt->isidentityfree = 1; bt->isbitstype = (parameters == jl_emptysvec); - bt->size = nbytes; - bt->layout = jl_get_layout(0, 0, alignm, 0, NULL, NULL); + bt->layout = jl_get_layout(nbytes, 0, 0, alignm, 0, NULL, NULL); bt->instance = NULL; return bt; } @@ -715,15 +847,16 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, { jl_datatype_t *bt = jl_new_datatype(name, module, super, jl_emptysvec, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); - bt->size = large ? GC_MAX_SZCLASS+1 : 0; jl_datatype_layout_t *layout = (jl_datatype_layout_t *) jl_gc_perm_alloc(sizeof(jl_datatype_layout_t) + sizeof(jl_fielddescdyn_t), 0, 4, 0); + 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; jl_fielddescdyn_t * desc = (jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout)); desc->markfunc = markfunc; @@ -733,12 +866,27 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name, return bt; } +JL_DLLEXPORT int jl_reinit_foreign_type(jl_datatype_t *dt, + jl_markfunc_t markfunc, + jl_sweepfunc_t sweepfunc) +{ + if (!jl_is_foreign_type(dt)) + return 0; + const jl_datatype_layout_t *layout = dt->layout; + jl_fielddescdyn_t * desc = + (jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout)); + assert(!desc->markfunc); + assert(!desc->sweepfunc); + desc->markfunc = markfunc; + desc->sweepfunc = sweepfunc; + return 1; +} + JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt) { return jl_is_datatype(dt) && dt->layout && dt->layout->fielddesc_type == 3; } - // bits constructors ---------------------------------------------------------- #if MAX_ATOMIC_SIZE > MAX_POINTERATOMIC_SIZE @@ -817,6 +965,7 @@ JL_DLLEXPORT jl_value_t *jl_new_bits(jl_value_t *dt, const void *data) if (bt == jl_uint16_type) return jl_box_uint16(*(uint16_t*)data); if (bt == jl_char_type) return jl_box_char(*(uint32_t*)data); + assert(!bt->smalltag); jl_task_t *ct = jl_current_task; jl_value_t *v = jl_gc_alloc(ct->ptls, nb, bt); memcpy(jl_assume_aligned(v, sizeof(void*)), data, nb); @@ -842,6 +991,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_new_bits(jl_value_t *dt, const char *data) if (bt == jl_uint16_type) return jl_box_uint16(jl_atomic_load((_Atomic(uint16_t)*)data)); if (bt == jl_char_type) return jl_box_char(jl_atomic_load((_Atomic(uint32_t)*)data)); + assert(!bt->smalltag); jl_task_t *ct = jl_current_task; jl_value_t *v = jl_gc_alloc(ct->ptls, nb, bt); // data is aligned to the power of two, @@ -909,6 +1059,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_swap_bits(jl_value_t *dt, char *dst, const jl if (bt == jl_uint16_type) return jl_box_uint16(jl_atomic_exchange((_Atomic(uint16_t)*)dst, *(uint16_t*)src)); if (bt == jl_char_type) return jl_box_char(jl_atomic_exchange((_Atomic(uint32_t)*)dst, *(uint32_t*)src)); + assert(!bt->smalltag); jl_task_t *ct = jl_current_task; jl_value_t *v = jl_gc_alloc(ct->ptls, jl_datatype_size(bt), bt); if (nb == 1) @@ -977,7 +1128,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t // 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 : rettyp->size, isptr ? dt : rettyp); + 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) { @@ -1066,7 +1217,7 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t } if (isptr) { JL_GC_PUSH1(&y); - jl_value_t *z = jl_gc_alloc(ct->ptls, rettyp->size, rettyp); + jl_value_t *z = jl_gc_alloc(ct->ptls, jl_datatype_size(rettyp), rettyp); *(jl_value_t**)z = y; JL_GC_POP(); y = z; @@ -1076,34 +1227,30 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, jl_datatype_t return y; } - - // used by boot.jl -JL_DLLEXPORT jl_value_t *jl_typemax_uint(jl_value_t *bt) +JL_DLLEXPORT jl_value_t *jl_typemax_uint(jl_datatype_t *bt) { 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; } -#define PERMBOXN_FUNC(nb,nw) \ - jl_value_t *jl_permbox##nb(jl_datatype_t *t, int##nb##_t x) \ - { /* NOTE: t must be a concrete isbits datatype */ \ - assert(jl_datatype_size(t) == sizeof(x)); \ - jl_value_t *v = jl_gc_permobj(nw * sizeof(void*), t); \ - *(int##nb##_t*)jl_data_ptr(v) = x; \ +#define PERMBOXN_FUNC(nb) \ + jl_value_t *jl_permbox##nb(jl_datatype_t *t, uintptr_t tag, uint##nb##_t x) \ + { /* n.b. t must be a concrete isbits datatype of the right size */ \ + jl_value_t *v = jl_gc_permobj(LLT_ALIGN(nb, sizeof(void*)), t); \ + if (tag) jl_set_typetagof(v, tag, GC_OLD_MARKED); \ + *(uint##nb##_t*)jl_data_ptr(v) = x; \ return v; \ } -PERMBOXN_FUNC(8, 1) -PERMBOXN_FUNC(16, 1) -PERMBOXN_FUNC(32, 1) -#ifdef _P64 -PERMBOXN_FUNC(64, 1) -#else -PERMBOXN_FUNC(64, 2) -#endif +PERMBOXN_FUNC(8) +PERMBOXN_FUNC(16) +PERMBOXN_FUNC(32) +PERMBOXN_FUNC(64) #define UNBOX_FUNC(j_type,c_type) \ JL_DLLEXPORT c_type jl_unbox_##j_type(jl_value_t *v) \ @@ -1126,27 +1273,27 @@ UNBOX_FUNC(float64, double) UNBOX_FUNC(voidpointer, void*) UNBOX_FUNC(uint8pointer, uint8_t*) -#define BOX_FUNC(typ,c_type,pfx,nw) \ +#define BOX_FUNC(typ,c_type,pfx) \ JL_DLLEXPORT jl_value_t *pfx##_##typ(c_type x) \ { \ jl_task_t *ct = jl_current_task; \ - jl_value_t *v = jl_gc_alloc(ct->ptls, nw * sizeof(void*), \ + jl_value_t *v = jl_gc_alloc(ct->ptls, LLT_ALIGN(sizeof(x), sizeof(void*)), \ jl_##typ##_type); \ *(c_type*)jl_data_ptr(v) = x; \ return v; \ } -BOX_FUNC(float32, float, jl_box, 1) -BOX_FUNC(voidpointer, void*, jl_box, 1) -BOX_FUNC(uint8pointer, uint8_t*, jl_box, 1) -#ifdef _P64 -BOX_FUNC(float64, double, jl_box, 1) -#else -BOX_FUNC(float64, double, jl_box, 2) -#endif +BOX_FUNC(float32, float, jl_box) +BOX_FUNC(float64, double, jl_box) +BOX_FUNC(voidpointer, void*, jl_box) +BOX_FUNC(uint8pointer, uint8_t*, jl_box) #define NBOX_C 1024 -#define SIBOX_FUNC(typ,c_type,nw)\ +// some shims to support UIBOX_FUNC definition +#define jl_ssavalue_tag (((uintptr_t)jl_ssavalue_type) >> 4) +#define jl_slotnumber_tag (((uintptr_t)jl_slotnumber_type) >> 4) + +#define SIBOX_FUNC(typ,c_type) \ static jl_value_t *boxed_##typ##_cache[NBOX_C]; \ JL_DLLEXPORT jl_value_t *jl_box_##typ(c_type x) \ { \ @@ -1154,36 +1301,33 @@ BOX_FUNC(float64, double, jl_box, 2) c_type idx = x+NBOX_C/2; \ if ((u##c_type)idx < (u##c_type)NBOX_C) \ return boxed_##typ##_cache[idx]; \ - jl_value_t *v = jl_gc_alloc(ct->ptls, nw * sizeof(void*), \ + jl_value_t *v = jl_gc_alloc(ct->ptls, LLT_ALIGN(sizeof(x), sizeof(void*)), \ jl_##typ##_type); \ + jl_set_typetagof(v, jl_##typ##_tag, 0); \ *(c_type*)jl_data_ptr(v) = x; \ return v; \ } -#define UIBOX_FUNC(typ,c_type,nw) \ +#define UIBOX_FUNC(typ,c_type) \ static jl_value_t *boxed_##typ##_cache[NBOX_C]; \ JL_DLLEXPORT jl_value_t *jl_box_##typ(c_type x) \ { \ jl_task_t *ct = jl_current_task; \ if (x < NBOX_C) \ return boxed_##typ##_cache[x]; \ - jl_value_t *v = jl_gc_alloc(ct->ptls, nw * sizeof(void*), \ + jl_value_t *v = jl_gc_alloc(ct->ptls, LLT_ALIGN(sizeof(x), sizeof(void*)), \ jl_##typ##_type); \ + jl_set_typetagof(v, jl_##typ##_tag, 0); \ *(c_type*)jl_data_ptr(v) = x; \ return v; \ } -SIBOX_FUNC(int16, int16_t, 1) -SIBOX_FUNC(int32, int32_t, 1) -UIBOX_FUNC(uint16, uint16_t, 1) -UIBOX_FUNC(uint32, uint32_t, 1) -UIBOX_FUNC(ssavalue, size_t, 1) -UIBOX_FUNC(slotnumber, size_t, 1) -#ifdef _P64 -SIBOX_FUNC(int64, int64_t, 1) -UIBOX_FUNC(uint64, uint64_t, 1) -#else -SIBOX_FUNC(int64, int64_t, 2) -UIBOX_FUNC(uint64, uint64_t, 2) -#endif +SIBOX_FUNC(int16, int16_t) +SIBOX_FUNC(int32, int32_t) +UIBOX_FUNC(uint16, uint16_t) +UIBOX_FUNC(uint32, uint32_t) +UIBOX_FUNC(ssavalue, size_t) +UIBOX_FUNC(slotnumber, size_t) +SIBOX_FUNC(int64, int64_t) +UIBOX_FUNC(uint64, uint64_t) static jl_value_t *boxed_char_cache[128]; JL_DLLEXPORT jl_value_t *jl_box_char(uint32_t x) @@ -1193,6 +1337,7 @@ JL_DLLEXPORT jl_value_t *jl_box_char(uint32_t x) if (u < 128) return boxed_char_cache[(uint8_t)u]; jl_value_t *v = jl_gc_alloc(ct->ptls, sizeof(void*), jl_char_type); + jl_set_typetagof(v, jl_char_tag, 0); *(uint32_t*)jl_data_ptr(v) = x; return v; } @@ -1212,35 +1357,35 @@ void jl_init_int32_int64_cache(void) { int64_t i; for(i=0; i < NBOX_C; i++) { - boxed_int32_cache[i] = jl_permbox32(jl_int32_type, i-NBOX_C/2); - boxed_int64_cache[i] = jl_permbox64(jl_int64_type, i-NBOX_C/2); + 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); #ifdef _P64 - boxed_ssavalue_cache[i] = jl_permbox64(jl_ssavalue_type, i); - boxed_slotnumber_cache[i] = jl_permbox64(jl_slotnumber_type, i); + boxed_ssavalue_cache[i] = jl_permbox64(jl_ssavalue_type, 0, i); + boxed_slotnumber_cache[i] = jl_permbox64(jl_slotnumber_type, 0, i); #else - boxed_ssavalue_cache[i] = jl_permbox32(jl_ssavalue_type, i); - boxed_slotnumber_cache[i] = jl_permbox32(jl_slotnumber_type, i); + boxed_ssavalue_cache[i] = jl_permbox32(jl_ssavalue_type, 0, i); + boxed_slotnumber_cache[i] = jl_permbox32(jl_slotnumber_type, 0, i); #endif } for(i=0; i < 256; i++) { - jl_boxed_uint8_cache[i] = jl_permbox8(jl_uint8_type, i); + jl_boxed_uint8_cache[i] = jl_permbox8(jl_uint8_type, jl_uint8_tag, i); } } void jl_init_box_caches(void) { - int64_t i; - for(i=0; i < 128; i++) { - boxed_char_cache[i] = jl_permbox32(jl_char_type, i << 24); + uint32_t i; + for (i = 0; i < 128; i++) { + boxed_char_cache[i] = jl_permbox32(jl_char_type, jl_char_tag, i << 24); } - for(i=0; i < 256; i++) { - jl_boxed_int8_cache[i] = jl_permbox8(jl_int8_type, i); + for (i = 0; i < 256; i++) { + jl_boxed_int8_cache[i] = jl_permbox8(jl_int8_type, jl_int8_tag, i); } - for(i=0; i < NBOX_C; i++) { - boxed_int16_cache[i] = jl_permbox16(jl_int16_type, i-NBOX_C/2); - boxed_uint16_cache[i] = jl_permbox16(jl_uint16_type, i); - boxed_uint32_cache[i] = jl_permbox32(jl_uint32_type, i); - boxed_uint64_cache[i] = jl_permbox64(jl_uint64_type, i); + 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); } } @@ -1257,10 +1402,15 @@ 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) { + jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); + } va_list args; size_t i, nf = jl_datatype_nfields(type); va_start(args, type); jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(type), type); + if (type->smalltag) // TODO: move to callers? + jl_set_typetagof(jv, type->smalltag, 0); if (nf > 0 && jl_field_offset(type, 0) != 0) { memset(jv, 0, jl_field_offset(type, 0)); } @@ -1274,7 +1424,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->layout == NULL) { + if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL) { jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); } size_t nf = jl_datatype_nfields(type); @@ -1288,6 +1438,8 @@ JL_DLLEXPORT jl_value_t *jl_new_structv(jl_datatype_t *type, jl_value_t **args, if (type->instance != NULL) return type->instance; jl_value_t *jv = jl_gc_alloc(ct->ptls, jl_datatype_size(type), type); + if (type->smalltag) // TODO: do we need this? + jl_set_typetagof(jv, type->smalltag, 0); if (jl_datatype_nfields(type) > 0) { if (jl_field_offset(type, 0) != 0) { memset(jl_data_ptr(jv), 0, jl_field_offset(type, 0)); @@ -1311,7 +1463,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->layout == NULL) + if (!jl_is_datatype(type) || !type->isconcretetype || type->layout == NULL) 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); @@ -1329,6 +1481,8 @@ JL_DLLEXPORT jl_value_t *jl_new_structt(jl_datatype_t *type, jl_value_t *tup) } 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? + jl_set_typetagof(jv, type->smalltag, 0); if (nf == 0) return jv; jl_value_t *fi = NULL; @@ -1357,8 +1511,13 @@ 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) { + jl_type_error("new", (jl_value_t*)jl_datatype_type, (jl_value_t*)type); + } 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? + jl_set_typetagof(jv, type->smalltag, 0); if (size > 0) memset(jl_data_ptr(jv), 0, size); return jv; @@ -1468,6 +1627,7 @@ static inline void memassign_safe(int hasptr, jl_value_t *parent, char *dst, con 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 { @@ -1489,7 +1649,7 @@ void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, return; } if (jl_field_isptr(st, i)) { - jl_atomic_store_relaxed((_Atomic(jl_value_t*)*)((char*)v + offs), rhs); + jl_atomic_store_release((_Atomic(jl_value_t*)*)((char*)v + offs), rhs); jl_gc_wb(v, rhs); } else { @@ -1736,7 +1896,7 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val rty = jl_nth_union_component(rty, *psel); } assert(!jl_field_isptr(rettyp, 0)); - r = jl_gc_alloc(ct->ptls, rettyp->size, (jl_value_t*)rettyp); + 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); @@ -1787,9 +1947,19 @@ JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT return fval != NULL ? 1 : 0; } -JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) JL_NOTSAFEPOINT +JL_DLLEXPORT int jl_field_isdefined_checked(jl_value_t *v, size_t i) +{ + if (jl_is_module(v)) { + jl_type_error("isdefined", (jl_value_t*)jl_symbol_type, jl_box_long(i + 1)); + } + if (i >= jl_nfields(v)) + return 0; + return !!jl_field_isdefined(v, i); +} + +JL_DLLEXPORT size_t jl_get_field_offset(jl_datatype_t *ty, int field) { - if (ty->layout == NULL || field > jl_datatype_nfields(ty) || field < 1) + if (!jl_struct_try_layout(ty) || field > jl_datatype_nfields(ty) || field < 1) jl_bounds_error_int((jl_value_t*)ty, field); return jl_field_offset(ty, field - 1); } diff --git a/src/debug-registry.h b/src/debug-registry.h new file mode 100644 index 0000000000000..bad59f205acb3 --- /dev/null +++ b/src/debug-registry.h @@ -0,0 +1,145 @@ +#include +#include +#include + +#include "julia.h" + +#include +#include +#include + +typedef struct { + const llvm::object::ObjectFile *obj; + llvm::DIContext *ctx; + int64_t slide; +} objfileentry_t; + +// Central registry for resolving function addresses to `jl_method_instance_t`s and +// originating `ObjectFile`s (for the DWARF debug info). +// +// A global singleton instance is notified by the JIT whenever a new object is emitted, +// and later queried by the various function info APIs. We also use the chance to handle +// some platform-specific unwind info registration (which is unrelated to the query +// functionality). +class JITDebugInfoRegistry +{ +public: + template + struct Locked { + + template + struct Lock { + std::unique_lock lock; + CResourceT &resource; + + Lock(std::mutex &mutex, CResourceT &resource) JL_NOTSAFEPOINT : lock(mutex), resource(resource) {} + Lock(Lock &&) JL_NOTSAFEPOINT = default; + Lock &operator=(Lock &&) JL_NOTSAFEPOINT = default; + + CResourceT &operator*() JL_NOTSAFEPOINT { + return resource; + } + + const CResourceT &operator*() const JL_NOTSAFEPOINT { + return resource; + } + + CResourceT *operator->() JL_NOTSAFEPOINT { + return &**this; + } + + const CResourceT *operator->() const JL_NOTSAFEPOINT { + return &**this; + } + + operator const CResourceT &() const JL_NOTSAFEPOINT { + return resource; + } + + ~Lock() JL_NOTSAFEPOINT = default; + }; + private: + + mutable std::mutex mutex; + ResourceT resource; + public: + typedef Lock LockT; + typedef Lock ConstLockT; + + Locked(ResourceT resource = ResourceT()) JL_NOTSAFEPOINT : mutex(), resource(std::move(resource)) {} + + LockT operator*() JL_NOTSAFEPOINT { + return LockT(mutex, resource); + } + + ConstLockT operator*() const JL_NOTSAFEPOINT { + return ConstLockT(mutex, resource); + } + + ~Locked() JL_NOTSAFEPOINT = default; + }; + + struct image_info_t { + uint64_t base; + jl_image_fptrs_t fptrs; + jl_method_instance_t **fvars_linfo; + size_t fvars_n; + }; + + struct libc_frames_t { +#if defined(_OS_DARWIN_) && defined(LLVM_SHLIB) + std::atomic libc_register_frame_{nullptr}; + std::atomic libc_deregister_frame_{nullptr}; + + void libc_register_frame(const char *Entry) JL_NOTSAFEPOINT; + + void libc_deregister_frame(const char *Entry) JL_NOTSAFEPOINT; +#endif + }; +private: + + struct ObjectInfo { + const llvm::object::ObjectFile *object = nullptr; + size_t SectionSize = 0; + ptrdiff_t slide = 0; + llvm::object::SectionRef Section{}; + llvm::DIContext *context = nullptr; + }; + + template + using rev_map = std::map>; + + typedef rev_map objectmap_t; + typedef rev_map objfilemap_t; + + objectmap_t objectmap{}; + rev_map> linfomap{}; + + // Maintain a mapping of unrealized function names -> linfo objects + // so that when we see it get emitted, we can add a link back to the linfo + // that it came from (providing name, type signature, file info, etc.) + Locked> codeinst_in_flight{}; + + Locked> image_info{}; + + Locked objfilemap{}; + + static std::string mangle(llvm::StringRef Name, const llvm::DataLayout &DL) JL_NOTSAFEPOINT; + +public: + + JITDebugInfoRegistry() JL_NOTSAFEPOINT; + ~JITDebugInfoRegistry() JL_NOTSAFEPOINT = default; + + libc_frames_t libc_frames{}; + + void add_code_in_flight(llvm::StringRef name, jl_code_instance_t *codeinst, const llvm::DataLayout &DL) JL_NOTSAFEPOINT; + jl_method_instance_t *lookupLinfo(size_t pointer) JL_NOTSAFEPOINT; + void registerJITObject(const llvm::object::ObjectFile &Object, + std::function getLoadAddress, + std::function lookupWriteAddress) JL_NOTSAFEPOINT; + objectmap_t& getObjectMap() JL_NOTSAFEPOINT; + void add_image_info(image_info_t info) JL_NOTSAFEPOINT; + bool get_image_info(uint64_t base, image_info_t *info) const JL_NOTSAFEPOINT; + Locked::LockT get_objfile_map() JL_NOTSAFEPOINT; +}; diff --git a/src/debuginfo.cpp b/src/debuginfo.cpp index 42d67bd6f89c7..35e41fe657045 100644 --- a/src/debuginfo.cpp +++ b/src/debuginfo.cpp @@ -20,10 +20,13 @@ #include #include +#ifdef _OS_DARWIN_ +#include +#endif + using namespace llvm; -#include "julia.h" -#include "julia_internal.h" +#include "jitlayers.h" #include "debuginfo.h" #if defined(_OS_LINUX_) # include @@ -34,42 +37,115 @@ using namespace llvm; #include #include #include +#include #include "julia_assert.h" +#include "debug-registry.h" -#ifdef _OS_DARWIN_ -#include -#endif +static JITDebugInfoRegistry *DebugRegistry = new JITDebugInfoRegistry; -typedef object::SymbolRef SymRef; +static JITDebugInfoRegistry &getJITDebugRegistry() JL_NOTSAFEPOINT { + return *DebugRegistry; +} + +struct debug_link_info { + StringRef filename; + uint32_t crc32; +}; -// Any function that acquires this lock must be either a unmanaged thread -// or in the GC safe region and must NOT allocate anything through the GC -// while holding this lock. -// Certain functions in this file might be called from an unmanaged thread -// and cannot have any interaction with the julia runtime -static uv_rwlock_t threadsafe; +#if (defined(_OS_LINUX_) || defined(_OS_FREEBSD_) || (defined(_OS_DARWIN_) && defined(LLVM_SHLIB))) +extern "C" void __register_frame(void*) JL_NOTSAFEPOINT; +extern "C" void __deregister_frame(void*) JL_NOTSAFEPOINT; + +template +static void processFDEs(const char *EHFrameAddr, size_t EHFrameSize, callback f) +{ + const char *P = EHFrameAddr; + const char *End = P + EHFrameSize; + do { + const char *Entry = P; + P += 4; + assert(P <= End); + uint32_t Length = *(const uint32_t*)Entry; + // Length == 0: Terminator + if (Length == 0) + break; + assert(P + Length <= End); + uint32_t Offset = *(const uint32_t*)P; + // Offset == 0: CIE + if (Offset != 0) + f(Entry); + P += Length; + } while (P != End); +} +#endif -void jl_init_debuginfo(void) +std::string JITDebugInfoRegistry::mangle(StringRef Name, const DataLayout &DL) { - uv_rwlock_init(&threadsafe); + std::string MangledName; + { + raw_string_ostream MangledNameStream(MangledName); + Mangler::getNameWithPrefix(MangledNameStream, Name, DL); + } + return MangledName; +} + +void JITDebugInfoRegistry::add_code_in_flight(StringRef name, jl_code_instance_t *codeinst, const DataLayout &DL) { + (**codeinst_in_flight)[mangle(name, DL)] = codeinst; } -extern "C" JL_DLLEXPORT void jl_lock_profile_impl(void) +jl_method_instance_t *JITDebugInfoRegistry::lookupLinfo(size_t pointer) { - uv_rwlock_rdlock(&threadsafe); + jl_lock_profile(); + auto region = linfomap.lower_bound(pointer); + jl_method_instance_t *linfo = NULL; + if (region != linfomap.end() && pointer < region->first + region->second.first) + linfo = region->second.second; + jl_unlock_profile(); + return linfo; } -extern "C" JL_DLLEXPORT void jl_unlock_profile_impl(void) +//Protected by debuginfo_asyncsafe (profile) lock +JITDebugInfoRegistry::objectmap_t & +JITDebugInfoRegistry::getObjectMap() { - uv_rwlock_rdunlock(&threadsafe); + return objectmap; +} + +void JITDebugInfoRegistry::add_image_info(image_info_t info) { + (**this->image_info)[info.base] = info; +} + + +bool JITDebugInfoRegistry::get_image_info(uint64_t base, JITDebugInfoRegistry::image_info_t *info) const { + auto infos = *this->image_info; + auto it = infos->find(base); + if (it != infos->end()) { + *info = it->second; + return true; + } + return false; +} + +JITDebugInfoRegistry::Locked::LockT +JITDebugInfoRegistry::get_objfile_map() { + return *this->objfilemap; } +JITDebugInfoRegistry::JITDebugInfoRegistry() { } + +struct unw_table_entry +{ + int32_t start_ip_offset; + int32_t fde_offset; +}; + // some actions aren't signal (especially profiler) safe so we acquire a lock // around them to establish a mutual exclusion with unwinding from a signal template -static void jl_profile_atomic(T f) +static void jl_profile_atomic(T f) JL_NOTSAFEPOINT { - uv_rwlock_wrlock(&threadsafe); + assert(0 == jl_lock_profile_rd_held()); + jl_lock_profile_wr(); #ifndef _OS_WINDOWS_ sigset_t sset; sigset_t oset; @@ -80,36 +156,14 @@ static void jl_profile_atomic(T f) #ifndef _OS_WINDOWS_ pthread_sigmask(SIG_SETMASK, &oset, NULL); #endif - uv_rwlock_wrunlock(&threadsafe); + jl_unlock_profile_wr(); } // --- storing and accessing source location metadata --- - -struct ObjectInfo { - const object::ObjectFile *object; - size_t SectionSize; - ptrdiff_t slide; - object::SectionRef Section; - DIContext *context; -}; - -// Maintain a mapping of unrealized function names -> linfo objects -// so that when we see it get emitted, we can add a link back to the linfo -// that it came from (providing name, type signature, file info, etc.) -static StringMap codeinst_in_flight; -static std::string mangle(StringRef Name, const DataLayout &DL) -{ - std::string MangledName; - { - raw_string_ostream MangledNameStream(MangledName); - Mangler::getNameWithPrefix(MangledNameStream, Name, DL); - } - return MangledName; -} void jl_add_code_in_flight(StringRef name, jl_code_instance_t *codeinst, const DataLayout &DL) { - codeinst_in_flight[mangle(name, DL)] = codeinst; + getJITDebugRegistry().add_code_in_flight(name, codeinst, DL); } @@ -135,7 +189,7 @@ static void create_PRUNTIME_FUNCTION(uint8_t *Code, size_t Size, StringRef fnnam if (mod_size && !SymLoadModuleEx(GetCurrentProcess(), NULL, NULL, NULL, (DWORD64)Section, mod_size, NULL, SLMFLAG_VIRTUAL)) { static int warned = 0; if (!warned) { - jl_printf(JL_STDERR, "WARNING: failed to insert module info for backtrace: %lu\n", GetLastError()); + jl_safe_printf("WARNING: failed to insert module info for backtrace: %lu\n", GetLastError()); warned = 1; } } @@ -148,17 +202,17 @@ static void create_PRUNTIME_FUNCTION(uint8_t *Code, size_t Size, StringRef fnnam name[len-1] = 0; if (!SymAddSymbol(GetCurrentProcess(), (ULONG64)Section, name, (DWORD64)Code, (DWORD)Size, 0)) { - jl_printf(JL_STDERR, "WARNING: failed to insert function name %s into debug info: %lu\n", name, GetLastError()); + jl_safe_printf("WARNING: failed to insert function name %s into debug info: %lu\n", name, GetLastError()); } } uv_mutex_unlock(&jl_in_stackwalk); } #if defined(_CPU_X86_64_) - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { if (!RtlAddFunctionTable(tbl, 1, (DWORD64)Section)) { static int warned = 0; if (!warned) { - jl_printf(JL_STDERR, "WARNING: failed to insert function stack unwind info: %lu\n", GetLastError()); + jl_safe_printf("WARNING: failed to insert function stack unwind info: %lu\n", GetLastError()); warned = 1; } } @@ -167,218 +221,178 @@ static void create_PRUNTIME_FUNCTION(uint8_t *Code, size_t Size, StringRef fnnam } #endif -struct revcomp { - bool operator() (const size_t& lhs, const size_t& rhs) const - { return lhs>rhs; } -}; - - -// Central registry for resolving function addresses to `jl_method_instance_t`s and -// originating `ObjectFile`s (for the DWARF debug info). -// -// A global singleton instance is notified by the JIT whenever a new object is emitted, -// and later queried by the various function info APIs. We also use the chance to handle -// some platform-specific unwind info registration (which is unrelated to the query -// functionality). -class JITObjectRegistry +void JITDebugInfoRegistry::registerJITObject(const object::ObjectFile &Object, + std::function getLoadAddress, + std::function lookupWriteAddress) { - std::map objectmap; - std::map, revcomp> linfomap; - -public: - jl_method_instance_t *lookupLinfo(size_t pointer) JL_NOTSAFEPOINT - { - uv_rwlock_rdlock(&threadsafe); - auto region = linfomap.lower_bound(pointer); - jl_method_instance_t *linfo = NULL; - if (region != linfomap.end() && pointer < region->first + region->second.first) - linfo = region->second.second; - uv_rwlock_rdunlock(&threadsafe); - return linfo; - } - - void registerJITObject(const object::ObjectFile &Object, - std::function getLoadAddress, - std::function lookupWriteAddress) - { - jl_ptls_t ptls = jl_current_task->ptls; - // This function modify codeinst->fptr in GC safe region. - // This should be fine since the GC won't scan this field. - int8_t gc_state = jl_gc_safe_enter(ptls); - - object::section_iterator EndSection = Object.section_end(); + object::section_iterator EndSection = Object.section_end(); #ifdef _CPU_ARM_ - // ARM does not have/use .eh_frame - uint64_t arm_exidx_addr = 0; - size_t arm_exidx_len = 0; - uint64_t arm_text_addr = 0; - size_t arm_text_len = 0; - for (auto §ion: Object.sections()) { - bool istext = false; - if (section.isText()) { - istext = true; - } - else { - auto sName = section.getName(); - if (!sName) - continue; - if (sName.get() != ".ARM.exidx") { - continue; - } + // ARM does not have/use .eh_frame + uint64_t arm_exidx_addr = 0; + size_t arm_exidx_len = 0; + uint64_t arm_text_addr = 0; + size_t arm_text_len = 0; + for (auto §ion: Object.sections()) { + bool istext = false; + if (section.isText()) { + istext = true; + } + else { + auto sName = section.getName(); + if (!sName) + continue; + if (sName.get() != ".ARM.exidx") { + continue; } - uint64_t loadaddr = L.getSectionLoadAddress(section); - size_t seclen = section.getSize(); - if (istext) { - arm_text_addr = loadaddr; - arm_text_len = seclen; - if (!arm_exidx_addr) { - continue; - } + } + uint64_t loadaddr = getLoadAddress(section.getName().get()); + size_t seclen = section.getSize(); + if (istext) { + arm_text_addr = loadaddr; + arm_text_len = seclen; + if (!arm_exidx_addr) { + continue; } - else { - arm_exidx_addr = loadaddr; - arm_exidx_len = seclen; - if (!arm_text_addr) { - continue; - } + } + else { + arm_exidx_addr = loadaddr; + arm_exidx_len = seclen; + if (!arm_text_addr) { + continue; } - unw_dyn_info_t *di = new unw_dyn_info_t; - di->gp = 0; - di->format = UNW_INFO_FORMAT_ARM_EXIDX; - di->start_ip = (uintptr_t)arm_text_addr; - di->end_ip = (uintptr_t)(arm_text_addr + arm_text_len); - di->u.rti.name_ptr = 0; - di->u.rti.table_data = arm_exidx_addr; - di->u.rti.table_len = arm_exidx_len; - jl_profile_atomic([&]() { - _U_dyn_register(di); - }); - break; } + unw_dyn_info_t *di = new unw_dyn_info_t; + di->gp = 0; + di->format = UNW_INFO_FORMAT_ARM_EXIDX; + di->start_ip = (uintptr_t)arm_text_addr; + di->end_ip = (uintptr_t)(arm_text_addr + arm_text_len); + di->u.rti.name_ptr = 0; + di->u.rti.table_data = arm_exidx_addr; + di->u.rti.table_len = arm_exidx_len; + jl_profile_atomic([&]() JL_NOTSAFEPOINT { + _U_dyn_register(di); + }); + break; + } #endif #if defined(_OS_WINDOWS_) - uint64_t SectionAddrCheck = 0; - uint64_t SectionLoadCheck = 0; (void)SectionLoadCheck; - uint64_t SectionWriteCheck = 0; (void)SectionWriteCheck; - uint8_t *UnwindData = NULL; + uint64_t SectionAddrCheck = 0; + uint64_t SectionLoadCheck = 0; (void)SectionLoadCheck; + uint64_t SectionWriteCheck = 0; (void)SectionWriteCheck; + uint8_t *UnwindData = NULL; #if defined(_CPU_X86_64_) - uint8_t *catchjmp = NULL; - for (const object::SymbolRef &sym_iter : Object.symbols()) { - StringRef sName = cantFail(sym_iter.getName()); - if (sName.equals("__UnwindData") || sName.equals("__catchjmp")) { - uint64_t Addr = cantFail(sym_iter.getAddress()); - auto Section = cantFail(sym_iter.getSection()); - assert(Section != EndSection && Section->isText()); - uint64_t SectionAddr = Section->getAddress(); - StringRef secName = cantFail(Section->getName()); - uint64_t SectionLoadAddr = getLoadAddress(secName); - assert(SectionLoadAddr); - if (SectionAddrCheck) // assert that all of the Sections are at the same location - assert(SectionAddrCheck == SectionAddr && - SectionLoadCheck == SectionLoadAddr); - SectionAddrCheck = SectionAddr; - SectionLoadCheck = SectionLoadAddr; - SectionWriteCheck = SectionLoadAddr; - if (lookupWriteAddress) - SectionWriteCheck = (uintptr_t)lookupWriteAddress((void*)SectionLoadAddr); - Addr += SectionWriteCheck - SectionLoadCheck; - if (sName.equals("__UnwindData")) { - UnwindData = (uint8_t*)Addr; - } - else if (sName.equals("__catchjmp")) { - catchjmp = (uint8_t*)Addr; - } - } - } - assert(catchjmp); - assert(UnwindData); - assert(SectionAddrCheck); - assert(SectionLoadCheck); - assert(!memcmp(catchjmp, "\0\0\0\0\0\0\0\0\0\0\0\0", 12) && - !memcmp(UnwindData, "\0\0\0\0\0\0\0\0\0\0\0\0", 12)); - catchjmp[0] = 0x48; - catchjmp[1] = 0xb8; // mov RAX, QWORD PTR [&__julia_personality] - *(uint64_t*)(&catchjmp[2]) = (uint64_t)&__julia_personality; - catchjmp[10] = 0xff; - catchjmp[11] = 0xe0; // jmp RAX - UnwindData[0] = 0x09; // version info, UNW_FLAG_EHANDLER - UnwindData[1] = 4; // size of prolog (bytes) - UnwindData[2] = 2; // count of unwind codes (slots) - UnwindData[3] = 0x05; // frame register (rbp) = rsp - UnwindData[4] = 4; // second instruction - UnwindData[5] = 0x03; // mov RBP, RSP - UnwindData[6] = 1; // first instruction - UnwindData[7] = 0x50; // push RBP - *(DWORD*)&UnwindData[8] = (DWORD)(catchjmp - (uint8_t*)SectionWriteCheck); // relative location of catchjmp - UnwindData -= SectionWriteCheck - SectionLoadCheck; -#endif // defined(_OS_X86_64_) -#endif // defined(_OS_WINDOWS_) - - auto symbols = object::computeSymbolSizes(Object); - bool first = true; - for (const auto &sym_size : symbols) { - const object::SymbolRef &sym_iter = sym_size.first; - object::SymbolRef::Type SymbolType = cantFail(sym_iter.getType()); - if (SymbolType != object::SymbolRef::ST_Function) continue; + uint8_t *catchjmp = NULL; + for (const object::SymbolRef &sym_iter : Object.symbols()) { + StringRef sName = cantFail(sym_iter.getName()); + if (sName.equals("__UnwindData") || sName.equals("__catchjmp")) { uint64_t Addr = cantFail(sym_iter.getAddress()); auto Section = cantFail(sym_iter.getSection()); - if (Section == EndSection) continue; - if (!Section->isText()) continue; + assert(Section != EndSection && Section->isText()); uint64_t SectionAddr = Section->getAddress(); StringRef secName = cantFail(Section->getName()); uint64_t SectionLoadAddr = getLoadAddress(secName); - Addr -= SectionAddr - SectionLoadAddr; - StringRef sName = cantFail(sym_iter.getName()); - uint64_t SectionSize = Section->getSize(); - size_t Size = sym_size.second; -#if defined(_OS_WINDOWS_) - if (SectionAddrCheck) + assert(SectionLoadAddr); + if (SectionAddrCheck) // assert that all of the Sections are at the same location assert(SectionAddrCheck == SectionAddr && - SectionLoadCheck == SectionLoadAddr); + SectionLoadCheck == SectionLoadAddr); SectionAddrCheck = SectionAddr; SectionLoadCheck = SectionLoadAddr; - create_PRUNTIME_FUNCTION( - (uint8_t*)(uintptr_t)Addr, (size_t)Size, sName, - (uint8_t*)(uintptr_t)SectionLoadAddr, (size_t)SectionSize, UnwindData); + SectionWriteCheck = SectionLoadAddr; + if (lookupWriteAddress) + SectionWriteCheck = (uintptr_t)lookupWriteAddress((void*)SectionLoadAddr); + Addr += SectionWriteCheck - SectionLoadCheck; + if (sName.equals("__UnwindData")) { + UnwindData = (uint8_t*)Addr; + } + else if (sName.equals("__catchjmp")) { + catchjmp = (uint8_t*)Addr; + } + } + } + assert(catchjmp); + assert(UnwindData); + assert(SectionAddrCheck); + assert(SectionLoadCheck); + assert(!memcmp(catchjmp, "\0\0\0\0\0\0\0\0\0\0\0\0", 12) && + !memcmp(UnwindData, "\0\0\0\0\0\0\0\0\0\0\0\0", 12)); + catchjmp[0] = 0x48; + catchjmp[1] = 0xb8; // mov RAX, QWORD PTR [&__julia_personality] + *(uint64_t*)(&catchjmp[2]) = (uint64_t)&__julia_personality; + catchjmp[10] = 0xff; + catchjmp[11] = 0xe0; // jmp RAX + UnwindData[0] = 0x09; // version info, UNW_FLAG_EHANDLER + UnwindData[1] = 4; // size of prolog (bytes) + UnwindData[2] = 2; // count of unwind codes (slots) + UnwindData[3] = 0x05; // frame register (rbp) = rsp + UnwindData[4] = 4; // second instruction + UnwindData[5] = 0x03; // mov RBP, RSP + UnwindData[6] = 1; // first instruction + UnwindData[7] = 0x50; // push RBP + *(DWORD*)&UnwindData[8] = (DWORD)(catchjmp - (uint8_t*)SectionWriteCheck); // relative location of catchjmp + UnwindData -= SectionWriteCheck - SectionLoadCheck; +#endif // defined(_OS_X86_64_) +#endif // defined(_OS_WINDOWS_) + + auto symbols = object::computeSymbolSizes(Object); + bool first = true; + for (const auto &sym_size : symbols) { + const object::SymbolRef &sym_iter = sym_size.first; + object::SymbolRef::Type SymbolType = cantFail(sym_iter.getType()); + if (SymbolType != object::SymbolRef::ST_Function) continue; + uint64_t Addr = cantFail(sym_iter.getAddress()); + auto Section = cantFail(sym_iter.getSection()); + if (Section == EndSection) continue; + if (!Section->isText()) continue; + uint64_t SectionAddr = Section->getAddress(); + StringRef secName = cantFail(Section->getName()); + uint64_t SectionLoadAddr = getLoadAddress(secName); + Addr -= SectionAddr - SectionLoadAddr; + StringRef sName = cantFail(sym_iter.getName()); + uint64_t SectionSize = Section->getSize(); + size_t Size = sym_size.second; +#if defined(_OS_WINDOWS_) + if (SectionAddrCheck) + assert(SectionAddrCheck == SectionAddr && + SectionLoadCheck == SectionLoadAddr); + SectionAddrCheck = SectionAddr; + SectionLoadCheck = SectionLoadAddr; + create_PRUNTIME_FUNCTION( + (uint8_t*)(uintptr_t)Addr, (size_t)Size, sName, + (uint8_t*)(uintptr_t)SectionLoadAddr, (size_t)SectionSize, UnwindData); #endif + jl_code_instance_t *codeinst = NULL; + { + auto lock = *this->codeinst_in_flight; + auto &codeinst_in_flight = *lock; StringMap::iterator codeinst_it = codeinst_in_flight.find(sName); - jl_code_instance_t *codeinst = NULL; if (codeinst_it != codeinst_in_flight.end()) { codeinst = codeinst_it->second; codeinst_in_flight.erase(codeinst_it); } - jl_profile_atomic([&]() { - if (codeinst) - linfomap[Addr] = std::make_pair(Size, codeinst->def); - if (first) { - ObjectInfo tmp = {&Object, - (size_t)SectionSize, - (ptrdiff_t)(SectionAddr - SectionLoadAddr), - *Section, - nullptr, - }; - objectmap[SectionLoadAddr] = tmp; - first = false; - } - }); } - jl_gc_safe_leave(ptls, gc_state); - } - - std::map& getObjectMap() JL_NOTSAFEPOINT - { - return objectmap; + jl_profile_atomic([&]() JL_NOTSAFEPOINT { + if (codeinst) + linfomap[Addr] = std::make_pair(Size, codeinst->def); + if (first) { + objectmap[SectionLoadAddr] = {&Object, + (size_t)SectionSize, + (ptrdiff_t)(SectionAddr - SectionLoadAddr), + *Section, + nullptr, + }; + first = false; + } + }); } -}; +} -static JITObjectRegistry jl_jit_object_registry; void jl_register_jit_object(const object::ObjectFile &Object, std::function getLoadAddress, - std::function lookupWriteAddress) + std::function lookupWriteAddress) JL_NOTSAFEPOINT { - jl_jit_object_registry.registerJITObject(Object, getLoadAddress, lookupWriteAddress); + getJITDebugRegistry().registerJITObject(Object, getLoadAddress, lookupWriteAddress); } // TODO: convert the safe names from aotcomile.cpp:makeSafeName back into symbols @@ -448,9 +462,10 @@ static int lookup_pointer( // DWARFContext/DWARFUnit update some internal tables during these queries, so // a lock is needed. - uv_rwlock_wrlock(&threadsafe); + assert(0 == jl_lock_profile_rd_held()); + jl_lock_profile_wr(); auto inlineInfo = context->getInliningInfoForAddress(makeAddress(Section, pointer + slide), infoSpec); - uv_rwlock_wrunlock(&threadsafe); + jl_unlock_profile_wr(); int fromC = (*frames)[0].fromC; int n_frames = inlineInfo.getNumberOfFrames(); @@ -473,9 +488,9 @@ static int lookup_pointer( info = inlineInfo.getFrame(i); } else { - uv_rwlock_wrlock(&threadsafe); + jl_lock_profile_wr(); info = context->getLineInfoForAddress(makeAddress(Section, pointer + slide), infoSpec); - uv_rwlock_wrunlock(&threadsafe); + jl_unlock_profile_wr(); } jl_frame_t *frame = &(*frames)[i]; @@ -488,7 +503,7 @@ static int lookup_pointer( std::size_t semi_pos = func_name.find(';'); if (semi_pos != std::string::npos) { func_name = func_name.substr(0, semi_pos); - frame->linfo = NULL; // TODO: if (new_frames[n_frames - 1].linfo) frame->linfo = lookup(func_name in linfo)? + frame->linfo = NULL; // Looked up on Julia side } } } @@ -519,13 +534,37 @@ static int lookup_pointer( #ifndef _OS_WINDOWS_ #include #endif -typedef struct { - const llvm::object::ObjectFile *obj; - DIContext *ctx; - int64_t slide; -} objfileentry_t; -typedef std::map obfiletype; -static obfiletype objfilemap; + + + +#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_); + 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_); + } + assert(libc_register_frame_); + jl_profile_atomic([&]() JL_NOTSAFEPOINT { + libc_register_frame_(const_cast(Entry)); + __register_frame(const_cast(Entry)); + }); +} + +void JITDebugInfoRegistry::libc_frames_t::libc_deregister_frame(const char *Entry) { + auto 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_); + } + assert(libc_deregister_frame_); + jl_profile_atomic([&]() JL_NOTSAFEPOINT { + libc_deregister_frame_(const_cast(Entry)); + __deregister_frame(const_cast(Entry)); + }); +} +#endif static bool getObjUUID(llvm::object::MachOObjectFile *obj, uint8_t uuid[16]) JL_NOTSAFEPOINT { @@ -538,11 +577,6 @@ static bool getObjUUID(llvm::object::MachOObjectFile *obj, uint8_t uuid[16]) JL_ } return false; } - -struct debug_link_info { - StringRef filename; - uint32_t crc32; -}; static debug_link_info getDebuglink(const object::ObjectFile &Obj) JL_NOTSAFEPOINT { debug_link_info info = {}; @@ -569,7 +603,7 @@ static debug_link_info getDebuglink(const object::ObjectFile &Obj) JL_NOTSAFEPOI * code or tables extracted from it, as desired without restriction. */ static uint32_t -calc_gnu_debuglink_crc32(const void *buf, size_t size) +calc_gnu_debuglink_crc32(const void *buf, size_t size) JL_NOTSAFEPOINT { static const uint32_t g_crc32_tab[] = { @@ -627,7 +661,7 @@ calc_gnu_debuglink_crc32(const void *buf, size_t size) } static Expected> -openDebugInfo(StringRef debuginfopath, const debug_link_info &info) +openDebugInfo(StringRef debuginfopath, const debug_link_info &info) JL_NOTSAFEPOINT { auto SplitFile = MemoryBuffer::getFile(debuginfopath); if (std::error_code EC = SplitFile.getError()) { @@ -653,19 +687,11 @@ openDebugInfo(StringRef debuginfopath, const debug_link_info &info) std::move(error_splitobj.get()), std::move(SplitFile.get())); } - -static uint64_t jl_sysimage_base; -static jl_sysimg_fptrs_t sysimg_fptrs; -static jl_method_instance_t **sysimg_fvars_linfo; -static size_t sysimg_fvars_n; -extern "C" JL_DLLEXPORT -void jl_register_fptrs_impl(uint64_t sysimage_base, const jl_sysimg_fptrs_t *fptrs, +extern "C" JL_DLLEXPORT_CODEGEN +void jl_register_fptrs_impl(uint64_t image_base, const jl_image_fptrs_t *fptrs, jl_method_instance_t **linfos, size_t n) { - jl_sysimage_base = (uintptr_t)sysimage_base; - sysimg_fptrs = *fptrs; - sysimg_fvars_linfo = linfos; - sysimg_fvars_n = n; + getJITDebugRegistry().add_image_info({(uintptr_t) image_base, *fptrs, linfos, n}); } template @@ -676,12 +702,9 @@ static inline void ignoreError(T &err) JL_NOTSAFEPOINT #endif } -static void get_function_name_and_base(llvm::object::SectionRef Section, size_t pointer, int64_t slide, bool insysimage, +static void get_function_name_and_base(llvm::object::SectionRef Section, size_t pointer, int64_t slide, bool inimage, void **saddr, char **name, bool untrusted_dladdr) JL_NOTSAFEPOINT { - // Assume we only need base address for sysimg for now - if (!insysimage || !sysimg_fptrs.base) - saddr = nullptr; bool needs_saddr = saddr && (!*saddr || untrusted_dladdr); bool needs_name = name && (!*name || untrusted_dladdr); // Try platform specific methods first since they are usually faster @@ -706,7 +729,7 @@ static void get_function_name_and_base(llvm::object::SectionRef Section, size_t } if (Section.getObject() && (needs_saddr || needs_name)) { size_t distance = (size_t)-1; - SymRef sym_found; + object::SymbolRef sym_found; for (auto sym : Section.getObject()->symbols()) { if (!Section.containsSymbol(sym)) continue; @@ -762,7 +785,7 @@ static void get_function_name_and_base(llvm::object::SectionRef Section, size_t } #ifdef _OS_WINDOWS_ // For ntdll and msvcrt since we are currently only parsing DWARF debug info through LLVM - if (!insysimage && needs_name) { + if (!inimage && needs_name) { static char frame_info_func[ sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; @@ -781,7 +804,7 @@ static void get_function_name_and_base(llvm::object::SectionRef Section, size_t #endif } -static objfileentry_t &find_object_file(uint64_t fbase, StringRef fname) JL_NOTSAFEPOINT +static objfileentry_t find_object_file(uint64_t fbase, StringRef fname) JL_NOTSAFEPOINT { int isdarwin = 0, islinux = 0, iswindows = 0; #if defined(_OS_DARWIN_) @@ -794,12 +817,11 @@ static objfileentry_t &find_object_file(uint64_t fbase, StringRef fname) JL_NOTS (void)iswindows; // GOAL: Read debuginfo from file - // TODO: need read/write lock here for objfilemap synchronization - obfiletype::iterator it = objfilemap.find(fbase); - if (it != objfilemap.end()) + objfileentry_t entry{nullptr, nullptr, 0}; + auto success = getJITDebugRegistry().get_objfile_map()->emplace(fbase, entry); + if (!success.second) // Return cached value - return it->second; - auto &entry = objfilemap[fbase]; // default initialized + return success.first->second; // GOAL: Assign errorobj StringRef objpath; @@ -814,8 +836,10 @@ static objfileentry_t &find_object_file(uint64_t fbase, StringRef fname) JL_NOTS StringRef((const char *)fbase, msize), "", false); auto origerrorobj = llvm::object::ObjectFile::createObjectFile( membuf->getMemBufferRef(), file_magic::unknown); - if (!origerrorobj) + if (!origerrorobj) { + ignoreError(origerrorobj); return entry; + } llvm::object::MachOObjectFile *morigobj = (llvm::object::MachOObjectFile*) origerrorobj.get().get(); @@ -967,8 +991,9 @@ static objfileentry_t &find_object_file(uint64_t fbase, StringRef fname) JL_NOTS auto binary = errorobj->takeBinary(); binary.first.release(); binary.second.release(); - // update cache entry = {debugobj, context, slide}; + // update cache + (*getJITDebugRegistry().get_objfile_map())[fbase] = entry; } else { // TODO: report the error instead of silently consuming it? @@ -992,7 +1017,7 @@ static object::SectionRef getModuleSectionForAddress(const object::ObjectFile *o bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t *slide, llvm::DIContext **context, - bool onlySysImg, bool *isSysImg, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT + bool onlyImage, bool *isImage, uint64_t *_fbase, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT { *Section = object::SectionRef(); *context = NULL; @@ -1026,10 +1051,11 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * if (fname.empty()) // empirically, LoadedImageName might be missing fname = ModuleInfo.ImageName; DWORD64 fbase = ModuleInfo.BaseOfImage; - bool insysimage = (fbase == jl_sysimage_base); - if (isSysImg) - *isSysImg = insysimage; - if (onlySysImg && !insysimage) + JITDebugInfoRegistry::image_info_t image_info; + bool inimage = getJITDebugRegistry().get_image_info(fbase, &image_info); + if (isImage) + *isImage = inimage; + if (onlyImage && !inimage) return false; // If we didn't find the filename before in the debug // info, use the dll name @@ -1037,6 +1063,8 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * jl_copy_str(filename, fname.data()); if (saddr) *saddr = NULL; + if (_fbase) + *_fbase = fbase; #else // ifdef _OS_WINDOWS_ Dl_info dlinfo; @@ -1045,6 +1073,15 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * #ifdef __GLIBC__ struct link_map *extra_info; dladdr_success = dladdr1((void*)pointer, &dlinfo, (void**)&extra_info, RTLD_DL_LINKMAP) != 0; + if (dladdr_success) { + msan_unpoison(&dlinfo, sizeof(dlinfo)); + if (dlinfo.dli_fname) + msan_unpoison_string(dlinfo.dli_fname); + if (dlinfo.dli_sname) + msan_unpoison_string(dlinfo.dli_sname); + msan_unpoison(&extra_info, sizeof(struct link_map*)); + msan_unpoison(extra_info, sizeof(struct link_map)); + } #else #ifdef _OS_DARWIN_ // On macOS 12, dladdr(-1, …) succeeds and returns the main executable image, @@ -1066,27 +1103,33 @@ bool jl_dylib_DI_for_fptr(size_t pointer, object::SectionRef *Section, int64_t * fbase = (uintptr_t)dlinfo.dli_fbase; #endif StringRef fname; - bool insysimage = (fbase == jl_sysimage_base); - if (saddr && !(insysimage && untrusted_dladdr)) + JITDebugInfoRegistry::image_info_t image_info; + bool inimage = getJITDebugRegistry().get_image_info(fbase, &image_info); + if (saddr && !(inimage && untrusted_dladdr)) *saddr = dlinfo.dli_saddr; - if (isSysImg) - *isSysImg = insysimage; - if (onlySysImg && !insysimage) + if (isImage) + *isImage = inimage; + if (onlyImage && !inimage) return false; + if (_fbase) + *_fbase = fbase; // In case we fail with the debug info lookup, we at least still // have the function name, even if we don't have line numbers - if (name && !(insysimage && untrusted_dladdr)) + if (name && !(inimage && untrusted_dladdr)) jl_copy_str(name, dlinfo.dli_sname); if (filename) jl_copy_str(filename, dlinfo.dli_fname); fname = dlinfo.dli_fname; #endif // ifdef _OS_WINDOWS_ - auto &entry = find_object_file(fbase, fname); + auto entry = find_object_file(fbase, fname); *slide = entry.slide; *context = entry.ctx; if (entry.obj) *Section = getModuleSectionForAddress(entry.obj, pointer + entry.slide); - get_function_name_and_base(*Section, pointer, entry.slide, insysimage, saddr, name, untrusted_dladdr); + // Assume we only need base address for sysimg for now + if (!inimage || !image_info.fptrs.base) + saddr = nullptr; + get_function_name_and_base(*Section, pointer, entry.slide, inimage, saddr, name, untrusted_dladdr); return true; } @@ -1115,43 +1158,50 @@ static int jl_getDylibFunctionInfo(jl_frame_t **frames, size_t pointer, int skip object::SectionRef Section; llvm::DIContext *context = NULL; int64_t slide; - bool isSysImg; + bool isImage; void *saddr; - if (!jl_dylib_DI_for_fptr(pointer, &Section, &slide, &context, skipC, &isSysImg, &saddr, &frame0->func_name, &frame0->file_name)) { + uint64_t fbase; + if (!jl_dylib_DI_for_fptr(pointer, &Section, &slide, &context, skipC, &isImage, &fbase, &saddr, &frame0->func_name, &frame0->file_name)) { frame0->fromC = 1; return 1; } - frame0->fromC = !isSysImg; - if (isSysImg && sysimg_fptrs.base && saddr) { - intptr_t diff = (uintptr_t)saddr - (uintptr_t)sysimg_fptrs.base; - for (size_t i = 0; i < sysimg_fptrs.nclones; i++) { - if (diff == sysimg_fptrs.clone_offsets[i]) { - uint32_t idx = sysimg_fptrs.clone_idxs[i] & jl_sysimg_val_mask; - if (idx < sysimg_fvars_n) // items after this were cloned but not referenced directly by a method (such as our ccall PLT thunks) - frame0->linfo = sysimg_fvars_linfo[idx]; - break; + frame0->fromC = !isImage; + { + 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]) { + 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]; + break; + } } - } - for (size_t i = 0; i < sysimg_fvars_n; i++) { - if (diff == sysimg_fptrs.offsets[i]) { - frame0->linfo = sysimg_fvars_linfo[i]; - break; + for (size_t i = 0; i < image.fvars_n; i++) { + if (diff == image.fptrs.offsets[i]) { + frame0->linfo = image.fvars_linfo[i]; + break; + } } } } - return lookup_pointer(Section, context, frames, pointer, slide, isSysImg, noInline); + return lookup_pointer(Section, context, frames, pointer, slide, isImage, noInline); } int jl_DI_for_fptr(uint64_t fptr, uint64_t *symsize, int64_t *slide, object::SectionRef *Section, llvm::DIContext **context) JL_NOTSAFEPOINT { int found = 0; - uv_rwlock_wrlock(&threadsafe); - std::map &objmap = jl_jit_object_registry.getObjectMap(); - std::map::iterator fit = objmap.lower_bound(fptr); + assert(0 == jl_lock_profile_rd_held()); + jl_lock_profile_wr(); if (symsize) *symsize = 0; + + auto &objmap = getJITDebugRegistry().getObjectMap(); + auto fit = objmap.lower_bound(fptr); if (fit != objmap.end() && fptr < fit->first + fit->second.SectionSize) { *slide = fit->second.slide; *Section = fit->second.Section; @@ -1162,12 +1212,12 @@ int jl_DI_for_fptr(uint64_t fptr, uint64_t *symsize, int64_t *slide, } found = 1; } - uv_rwlock_wrunlock(&threadsafe); + jl_unlock_profile_wr(); return found; } // Set *name and *filename to either NULL or malloc'd string -extern "C" JL_DLLEXPORT int jl_getFunctionInfo_impl(jl_frame_t **frames_out, size_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT +extern "C" JL_DLLEXPORT_CODEGEN int jl_getFunctionInfo_impl(jl_frame_t **frames_out, size_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT { // This function is not allowed to reference any TLS variables if noInline // since it can be called from an unmanaged thread on OSX. @@ -1181,7 +1231,7 @@ extern "C" JL_DLLEXPORT int jl_getFunctionInfo_impl(jl_frame_t **frames_out, siz int64_t slide; uint64_t symsize; if (jl_DI_for_fptr(pointer, &symsize, &slide, &Section, &context)) { - frames[0].linfo = jl_jit_object_registry.lookupLinfo(pointer); + frames[0].linfo = getJITDebugRegistry().lookupLinfo(pointer); int nf = lookup_pointer(Section, context, frames_out, pointer, slide, true, noInline); return nf; } @@ -1190,36 +1240,9 @@ extern "C" JL_DLLEXPORT int jl_getFunctionInfo_impl(jl_frame_t **frames_out, siz extern "C" jl_method_instance_t *jl_gdblookuplinfo(void *p) JL_NOTSAFEPOINT { - return jl_jit_object_registry.lookupLinfo((size_t)p); + return getJITDebugRegistry().lookupLinfo((size_t)p); } -#if (defined(_OS_LINUX_) || defined(_OS_FREEBSD_) || (defined(_OS_DARWIN_) && defined(LLVM_SHLIB))) -extern "C" void __register_frame(void*); -extern "C" void __deregister_frame(void*); - -template -static void processFDEs(const char *EHFrameAddr, size_t EHFrameSize, callback f) -{ - const char *P = EHFrameAddr; - const char *End = P + EHFrameSize; - do { - const char *Entry = P; - P += 4; - assert(P <= End); - uint32_t Length = *(const uint32_t*)Entry; - // Length == 0: Terminator - if (Length == 0) - break; - assert(P + Length <= End); - uint32_t Offset = *(const uint32_t*)P; - // Offset == 0: CIE - if (Offset != 0) - f(Entry); - P += Length; - } while (P != End); -} -#endif - #if defined(_OS_DARWIN_) && defined(LLVM_SHLIB) /* @@ -1230,37 +1253,20 @@ static void processFDEs(const char *EHFrameAddr, size_t EHFrameSize, callback f) * ourselves to ensure the right one gets picked. */ -static void (*libc_register_frame)(void*) = NULL; -static void (*libc_deregister_frame)(void*) = NULL; - // This implementation handles frame registration for local targets. void register_eh_frames(uint8_t *Addr, size_t Size) { // On OS X OS X __register_frame takes a single FDE as an argument. // See http://lists.cs.uiuc.edu/pipermail/llvmdev/2013-April/061768.html processFDEs((char*)Addr, Size, [](const char *Entry) { - if (!libc_register_frame) { - libc_register_frame = (void(*)(void*))dlsym(RTLD_NEXT, "__register_frame"); - } - assert(libc_register_frame); - jl_profile_atomic([&]() { - libc_register_frame(const_cast(Entry)); - __register_frame(const_cast(Entry)); - }); + getJITDebugRegistry().libc_frames.libc_register_frame(Entry); }); } void deregister_eh_frames(uint8_t *Addr, size_t Size) { processFDEs((char*)Addr, Size, [](const char *Entry) { - if (!libc_deregister_frame) { - libc_deregister_frame = (void(*)(void*))dlsym(RTLD_NEXT, "__deregister_frame"); - } - assert(libc_deregister_frame); - jl_profile_atomic([&]() { - libc_deregister_frame(const_cast(Entry)); - __deregister_frame(const_cast(Entry)); - }); + getJITDebugRegistry().libc_frames.libc_deregister_frame(Entry); }); } @@ -1269,12 +1275,6 @@ void deregister_eh_frames(uint8_t *Addr, size_t Size) !defined(_CPU_ARM_) // ARM does not have/use .eh_frame, so we handle this elsewhere #include -struct unw_table_entry -{ - int32_t start_ip_offset; - int32_t fde_offset; -}; - // Skip over an arbitrary long LEB128 encoding. // Return the pointer to the first unprocessed byte. static const uint8_t *consume_leb128(const uint8_t *Addr, const uint8_t *End) @@ -1452,7 +1452,7 @@ static DW_EH_PE parseCIE(const uint8_t *Addr, const uint8_t *End) void register_eh_frames(uint8_t *Addr, size_t Size) { // System unwinder - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { __register_frame(Addr); }); @@ -1580,14 +1580,14 @@ void register_eh_frames(uint8_t *Addr, size_t Size) di->start_ip = start_ip; di->end_ip = end_ip; - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { _U_dyn_register(di); }); } void deregister_eh_frames(uint8_t *Addr, size_t Size) { - jl_profile_atomic([&]() { + jl_profile_atomic([&]() JL_NOTSAFEPOINT { __deregister_frame(Addr); }); // Deregistering with our unwinder (_U_dyn_cancel) requires a lookup table @@ -1607,17 +1607,17 @@ void deregister_eh_frames(uint8_t *Addr, size_t Size) #endif -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN uint64_t jl_getUnwindInfo_impl(uint64_t dwAddr) { // Might be called from unmanaged thread - uv_rwlock_rdlock(&threadsafe); - std::map &objmap = jl_jit_object_registry.getObjectMap(); - std::map::iterator it = objmap.lower_bound(dwAddr); + jl_lock_profile(); + auto &objmap = getJITDebugRegistry().getObjectMap(); + auto it = objmap.lower_bound(dwAddr); uint64_t ipstart = 0; // ip of the start of the section (if found) if (it != objmap.end() && dwAddr < it->first + it->second.SectionSize) { ipstart = (uint64_t)(uintptr_t)(*it).first; } - uv_rwlock_rdunlock(&threadsafe); + jl_unlock_profile(); return ipstart; } diff --git a/src/debuginfo.h b/src/debuginfo.h index 5ea34350ac1fb..5b5cdcb82d534 100644 --- a/src/debuginfo.h +++ b/src/debuginfo.h @@ -6,7 +6,7 @@ int jl_DI_for_fptr(uint64_t fptr, uint64_t *symsize, int64_t *slide, llvm::object::SectionRef *Section, llvm::DIContext **context) JL_NOTSAFEPOINT; bool jl_dylib_DI_for_fptr(size_t pointer, llvm::object::SectionRef *Section, int64_t *slide, llvm::DIContext **context, - bool onlySysImg, bool *isSysImg, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT; + bool onlyImage, bool *isImage, uint64_t* fbase, void **saddr, char **name, char **filename) JL_NOTSAFEPOINT; static object::SectionedAddress makeAddress( llvm::object::SectionRef Section, uint64_t address) JL_NOTSAFEPOINT diff --git a/src/disasm.cpp b/src/disasm.cpp index b4c14d020538f..96595d4381987 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -60,6 +60,7 @@ // for outputting disassembly #include #include +#include #include #include #include @@ -91,11 +92,7 @@ #include #include #include -#if JL_LLVM_VERSION >= 140000 #include -#else -#include -#endif #include #include @@ -108,8 +105,8 @@ #include #include -#include "julia.h" -#include "julia_internal.h" +#include + #include "jitlayers.h" #include "processor.h" @@ -132,10 +129,11 @@ class DILineInfoPrinter { output_source = 1, } verbosity = output_source; public: - DILineInfoPrinter(const char *LineStart, bool bracket_outer) + DILineInfoPrinter(const char *LineStart, bool bracket_outer) JL_NOTSAFEPOINT : LineStart(LineStart), bracket_outer(bracket_outer) {}; - void SetVerbosity(const char *c) + ~DILineInfoPrinter() JL_NOTSAFEPOINT = default; + void SetVerbosity(const char *c) JL_NOTSAFEPOINT { if (StringRef("default") == c) { verbosity = output_source; @@ -148,14 +146,14 @@ class DILineInfoPrinter { } } - void emit_finish(raw_ostream &Out); - void emit_lineinfo(raw_ostream &Out, std::vector &DI); + void emit_finish(raw_ostream &Out) JL_NOTSAFEPOINT; + void emit_lineinfo(raw_ostream &Out, std::vector &DI) JL_NOTSAFEPOINT; struct repeat { size_t times; const char *c; }; - struct repeat inlining_indent(const char *c) + struct repeat inlining_indent(const char *c) JL_NOTSAFEPOINT { return repeat{ std::max(inline_depth + bracket_outer, (uint32_t)1) - 1, @@ -163,20 +161,20 @@ class DILineInfoPrinter { } template - void emit_lineinfo(std::string &Out, T &DI) + void emit_lineinfo(std::string &Out, T &DI) JL_NOTSAFEPOINT { raw_string_ostream OS(Out); emit_lineinfo(OS, DI); } - void emit_lineinfo(raw_ostream &Out, DILineInfo &DI) + void emit_lineinfo(raw_ostream &Out, DILineInfo &DI) JL_NOTSAFEPOINT { std::vector DIvec(1); DIvec[0] = DI; emit_lineinfo(Out, DIvec); } - void emit_lineinfo(raw_ostream &Out, DIInliningInfo &DI) + void emit_lineinfo(raw_ostream &Out, DIInliningInfo &DI) JL_NOTSAFEPOINT { uint32_t nframes = DI.getNumberOfFrames(); std::vector DIvec(nframes); @@ -186,14 +184,14 @@ class DILineInfoPrinter { emit_lineinfo(Out, DIvec); } - void emit_finish(std::string &Out) + void emit_finish(std::string &Out) JL_NOTSAFEPOINT { raw_string_ostream OS(Out); emit_finish(OS); } }; -static raw_ostream &operator<<(raw_ostream &Out, struct DILineInfoPrinter::repeat i) +static raw_ostream &operator<<(raw_ostream &Out, struct DILineInfoPrinter::repeat i) JL_NOTSAFEPOINT { while (i.times-- > 0) Out << i.c; @@ -335,27 +333,28 @@ class LineNumberAnnotatedWriter : public AssemblyAnnotationWriter { DenseMap DebugLoc; DenseMap Subprogram; public: - LineNumberAnnotatedWriter(const char *LineStart, bool bracket_outer, const char *debuginfo) + LineNumberAnnotatedWriter(const char *LineStart, bool bracket_outer, const char *debuginfo) JL_NOTSAFEPOINT : LinePrinter(LineStart, bracket_outer) { LinePrinter.SetVerbosity(debuginfo); } - virtual void emitFunctionAnnot(const Function *, formatted_raw_ostream &); - virtual void emitInstructionAnnot(const Instruction *, formatted_raw_ostream &); - virtual void emitInstructionAnnot(const DILocation *, formatted_raw_ostream &); - virtual void emitBasicBlockEndAnnot(const BasicBlock *, formatted_raw_ostream &); - // virtual void printInfoComment(const Value &, formatted_raw_ostream &) {} - - void emitEnd(formatted_raw_ostream &Out) { + ~LineNumberAnnotatedWriter() JL_NOTSAFEPOINT = default; + virtual void emitFunctionAnnot(const Function *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + virtual void emitInstructionAnnot(const Instruction *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + virtual void emitInstructionAnnot(const DILocation *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + virtual void emitBasicBlockEndAnnot(const BasicBlock *, formatted_raw_ostream &) JL_NOTSAFEPOINT; + // virtual void printInfoComment(const Value &, formatted_raw_ostream &) JL_NOTSAFEPOINT {} + + void emitEnd(formatted_raw_ostream &Out) JL_NOTSAFEPOINT { LinePrinter.emit_finish(Out); InstrLoc = nullptr; } - void addSubprogram(const Function *F, DISubprogram *SP) + void addSubprogram(const Function *F, DISubprogram *SP) JL_NOTSAFEPOINT { Subprogram[F] = SP; } - void addDebugLoc(const Instruction *I, DILocation *Loc) + void addDebugLoc(const Instruction *I, DILocation *Loc) JL_NOTSAFEPOINT { DebugLoc[I] = Loc; } @@ -421,7 +420,7 @@ void LineNumberAnnotatedWriter::emitBasicBlockEndAnnot( emitEnd(Out); } -static void jl_strip_llvm_debug(Module *m, bool all_meta, LineNumberAnnotatedWriter *AAW) +static void jl_strip_llvm_debug(Module *m, bool all_meta, LineNumberAnnotatedWriter *AAW) JL_NOTSAFEPOINT { // strip metadata from all instructions in all functions in the module Instruction *deletelast = nullptr; // can't actually delete until the iterator advances @@ -472,32 +471,39 @@ static void jl_strip_llvm_debug(Module *m, bool all_meta, LineNumberAnnotatedWri // m->eraseNamedMetadata(md); } -void jl_strip_llvm_debug(Module *m) +void jl_strip_llvm_debug(Module *m) JL_NOTSAFEPOINT { jl_strip_llvm_debug(m, false, NULL); } -void jl_strip_llvm_addrspaces(Module *m) +void jl_strip_llvm_addrspaces(Module *m) JL_NOTSAFEPOINT { - legacy::PassManager PM; - PM.add(createRemoveJuliaAddrspacesPass()); - PM.run(*m); + PassBuilder PB; + AnalysisManagers AM(PB); + RemoveJuliaAddrspacesPass().run(*m, AM.MAM); } // print an llvm IR acquired from jl_get_llvmf -// warning: this takes ownership of, and destroys, f->getParent() -extern "C" JL_DLLEXPORT -jl_value_t *jl_dump_function_ir_impl(void *f, char strip_ir_metadata, char dump_module, const char *debuginfo) +// warning: this takes ownership of, and destroys, dump->TSM +extern "C" JL_DLLEXPORT_CODEGEN +jl_value_t *jl_dump_function_ir_impl(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo) { std::string code; raw_string_ostream stream(code); { - Function *llvmf = dyn_cast_or_null((Function*)f); + //RAII will release the module + auto TSM = std::unique_ptr(unwrap(dump->TSM)); + //If TSM is not passed in, then the context MUST be locked externally. + //RAII will release the lock + Optional lock; + if (TSM) { + lock.emplace(TSM->getContext().getLock()); + } + Function *llvmf = cast(unwrap(dump->F)); if (!llvmf || (!llvmf->isDeclaration() && !llvmf->getParent())) jl_error("jl_dump_function_ir: Expected Function* in a temporary Module"); - JL_LOCK(&jl_codegen_lock); // Might GC LineNumberAnnotatedWriter AAW{"; ", false, debuginfo}; if (!llvmf->getParent()) { // print the function declaration as-is @@ -505,7 +511,8 @@ jl_value_t *jl_dump_function_ir_impl(void *f, char strip_ir_metadata, char dump_ delete llvmf; } else { - Module *m = llvmf->getParent(); + assert(TSM && TSM->getModuleUnlocked() == llvmf->getParent() && "Passed module was not the same as function parent!"); + auto m = TSM->getModuleUnlocked(); if (strip_ir_metadata) { std::string llvmfn(llvmf->getName()); jl_strip_llvm_addrspaces(m); @@ -519,9 +526,7 @@ jl_value_t *jl_dump_function_ir_impl(void *f, char strip_ir_metadata, char dump_ else { llvmf->print(stream, &AAW); } - delete m; } - JL_UNLOCK(&jl_codegen_lock); // Might GC } return jl_pchar_to_string(stream.str().data(), stream.str().size()); @@ -534,7 +539,7 @@ static void jl_dump_asm_internal( raw_ostream &rstream, const char* asm_variant, const char* debuginfo, - bool binary); + bool binary) JL_NOTSAFEPOINT; // This isn't particularly fast, but neither is printing assembly, and they're only used for interactive mode static uint64_t compute_obj_symsize(object::SectionRef Section, uint64_t offset) @@ -569,7 +574,7 @@ static uint64_t compute_obj_symsize(object::SectionRef Section, uint64_t offset) } // print a native disassembly for the function starting at fptr -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN jl_value_t *jl_dump_fptr_asm_impl(uint64_t fptr, char raw_mc, const char* asm_variant, const char *debuginfo, char binary) { assert(fptr != 0); @@ -583,7 +588,7 @@ jl_value_t *jl_dump_fptr_asm_impl(uint64_t fptr, char raw_mc, const char* asm_va llvm::DIContext *context = NULL; if (!jl_DI_for_fptr(fptr, &symsize, &slide, &Section, &context)) { if (!jl_dylib_DI_for_fptr(fptr, &Section, &slide, &context, - false, NULL, NULL, NULL, NULL)) { + false, NULL, NULL, NULL, NULL, NULL)) { jl_printf(JL_STDERR, "WARNING: Unable to find function pointer\n"); return jl_pchar_to_string("", 0); } @@ -633,20 +638,21 @@ class SymbolTable { uint64_t ip; // virtual instruction pointer of the current instruction int64_t slide; public: - SymbolTable(MCContext &Ctx, const object::ObjectFile *object, int64_t slide, const FuncMCView &MemObj): - Ctx(Ctx), MemObj(MemObj), object(object), ip(0), slide(slide) {} - const FuncMCView &getMemoryObject() const { return MemObj; } - void setPass(int Pass) { this->Pass = Pass; } - int getPass() const { return Pass; } - void insertAddress(uint64_t addr); + SymbolTable(MCContext &Ctx, const object::ObjectFile *object, int64_t slide, const FuncMCView &MemObj) JL_NOTSAFEPOINT + : Ctx(Ctx), MemObj(MemObj), object(object), ip(0), slide(slide) {} + ~SymbolTable() JL_NOTSAFEPOINT = default; + const FuncMCView &getMemoryObject() const JL_NOTSAFEPOINT { return MemObj; } + void setPass(int Pass) JL_NOTSAFEPOINT { this->Pass = Pass; } + int getPass() const JL_NOTSAFEPOINT { return Pass; } + void insertAddress(uint64_t addr) JL_NOTSAFEPOINT; // void createSymbol(const char *name, uint64_t addr); - void createSymbols(); - const char *lookupSymbolName(uint64_t addr); - MCSymbol *lookupSymbol(uint64_t addr); - StringRef getSymbolNameAt(uint64_t offset) const; - const char *lookupLocalPC(size_t addr); - void setIP(uint64_t addr); - uint64_t getIP() const; + void createSymbols() JL_NOTSAFEPOINT; + const char *lookupSymbolName(uint64_t addr) JL_NOTSAFEPOINT; + MCSymbol *lookupSymbol(uint64_t addr) JL_NOTSAFEPOINT; + StringRef getSymbolNameAt(uint64_t offset) const JL_NOTSAFEPOINT; + const char *lookupLocalPC(size_t addr) JL_NOTSAFEPOINT; + void setIP(uint64_t addr) JL_NOTSAFEPOINT; + uint64_t getIP() const JL_NOTSAFEPOINT; }; void SymbolTable::setIP(uint64_t addr) @@ -785,7 +791,13 @@ static const char *SymbolLookup(void *DisInfo, uint64_t ReferenceValue, uint64_t return NULL; } -static int OpInfoLookup(void *DisInfo, uint64_t PC, uint64_t Offset, uint64_t Size, +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; @@ -867,16 +879,10 @@ static void jl_dump_asm_internal( TheTarget->createMCSubtargetInfo(TheTriple.str(), cpu, features)); assert(STI && "Unable to create subtarget info!"); -#if JL_LLVM_VERSION >= 130000 MCContext Ctx(TheTriple, MAI.get(), MRI.get(), STI.get(), &SrcMgr); std::unique_ptr MOFI( TheTarget->createMCObjectFileInfo(Ctx, /*PIC=*/false, /*LargeCodeModel=*/ false)); Ctx.setObjectFileInfo(MOFI.get()); -#else - std::unique_ptr MOFI(new MCObjectFileInfo()); - MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr); - MOFI->InitMCObjectFileInfo(TheTriple, /* PIC */ false, Ctx); -#endif std::unique_ptr DisAsm(TheTarget->createMCDisassembler(*STI, Ctx)); if (!DisAsm) { @@ -900,7 +906,11 @@ 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)); } @@ -1035,10 +1045,14 @@ 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) - Streamer->GetCommentOS() << '\n'; + /*CStream*/ pass != 0 ? Streamer->getCommentOS () : nulls()); + if (pass != 0 && Streamer->getCommentOS ().tell() > 0) + Streamer->getCommentOS () << '\n'; +#undef GetCommentOS switch (S) { case MCDisassembler::Fail: if (insSize == 0) // skip illegible bytes @@ -1150,6 +1164,7 @@ class LineNumberPrinterHandler : public AsmPrinterHandler { LinePrinter("; ", true, debuginfo), RawStream(Buffer), Stream(RawStream) {} + ~LineNumberPrinterHandler() JL_NOTSAFEPOINT = default; void emitAndReset() { Stream.flush(); @@ -1187,28 +1202,31 @@ class LineNumberPrinterHandler : public AsmPrinterHandler { }; // get a native assembly for llvm::Function -extern "C" JL_DLLEXPORT -jl_value_t *jl_dump_function_asm_impl(void *F, char raw_mc, const char* asm_variant, const char *debuginfo, char binary) +extern "C" JL_DLLEXPORT_CODEGEN +jl_value_t *jl_dump_function_asm_impl(jl_llvmf_dump_t* dump, char raw_mc, const char* asm_variant, const char *debuginfo, char binary) { // precise printing via IR assembler SmallVector ObjBufferSV; { // scope block - Function *f = (Function*)F; + auto TSM = std::unique_ptr(unwrap(dump->TSM)); llvm::raw_svector_ostream asmfile(ObjBufferSV); - assert(!f->isDeclaration()); - std::unique_ptr m(f->getParent()); - for (auto &f2 : m->functions()) { - if (f != &f2 && !f->isDeclaration()) - f2.deleteBody(); - } - LLVMTargetMachine *TM = static_cast(jl_TargetMachine); + TSM->withModuleDo([&](Module &m) { + Function *f = cast(unwrap(dump->F)); + assert(!f->isDeclaration()); + for (auto &f2 : m.functions()) { + if (f != &f2 && !f->isDeclaration()) + f2.deleteBody(); + } + }); + auto TMBase = jl_ExecutionEngine->cloneTargetMachine(); + LLVMTargetMachine *TM = static_cast(TMBase.get()); legacy::PassManager PM; - addTargetPasses(&PM, TM); + addTargetPasses(&PM, TM->getTargetTriple(), TM->getTargetIRAnalysis()); if (raw_mc) { raw_svector_ostream obj_OS(ObjBufferSV); if (TM->addPassesToEmitFile(PM, obj_OS, nullptr, CGFT_ObjectFile, false, nullptr)) return jl_an_empty_string; - PM.run(*m); + TSM->withModuleDo([&](Module &m) { PM.run(m); }); } else { MCContext *Context = addPassesToGenerateCode(TM, PM); @@ -1226,12 +1244,17 @@ jl_value_t *jl_dump_function_asm_impl(void *F, char raw_mc, const char* asm_vari if (!strcmp(asm_variant, "intel")) OutputAsmDialect = 1; MCInstPrinter *InstPrinter = TM->getTarget().createMCInstPrinter( - TM->getTargetTriple(), OutputAsmDialect, MAI, MII, MRI); + jl_ExecutionEngine->getTargetTriple(), OutputAsmDialect, MAI, MII, MRI); std::unique_ptr MAB(TM->getTarget().createMCAsmBackend( STI, MRI, TM->Options.MCOptions)); std::unique_ptr MCE; - if (binary) // enable MCAsmStreamer::AddEncodingComment printing + 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( *Context, std::move(FOut), true, @@ -1247,13 +1270,13 @@ jl_value_t *jl_dump_function_asm_impl(void *F, char raw_mc, const char* asm_vari return jl_an_empty_string; PM.add(Printer.release()); PM.add(createFreeMachineFunctionPass()); - PM.run(*m); + TSM->withModuleDo([&](Module &m){ PM.run(m); }); } } return jl_pchar_to_string(ObjBufferSV.data(), ObjBufferSV.size()); } -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN LLVMDisasmContextRef jl_LLVMCreateDisasm_impl( const char *TripleName, void *DisInfo, int TagType, LLVMOpInfoCallback GetOpInfo, LLVMSymbolLookupCallback SymbolLookUp) @@ -1261,8 +1284,8 @@ LLVMDisasmContextRef jl_LLVMCreateDisasm_impl( return LLVMCreateDisasm(TripleName, DisInfo, TagType, GetOpInfo, SymbolLookUp); } -extern "C" JL_DLLEXPORT -JL_DLLEXPORT size_t jl_LLVMDisasmInstruction_impl( +extern "C" JL_DLLEXPORT_CODEGEN +size_t jl_LLVMDisasmInstruction_impl( LLVMDisasmContextRef DC, uint8_t *Bytes, uint64_t BytesSize, uint64_t PC, char *OutString, size_t OutStringSize) { diff --git a/src/dlload.c b/src/dlload.c index 33afe62acad90..3fb5a08ba2438 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -4,6 +4,9 @@ #include #include #include +#ifdef __GLIBC__ +#include +#endif #include "platform.h" #include "julia.h" @@ -67,10 +70,8 @@ const char *jl_crtdll_name = CRTDLL_BASENAME ".dll"; #define PATHBUF 4096 -#define JL_RTLD(flags, FLAG) (flags & JL_RTLD_ ## FLAG ? RTLD_ ## FLAG : 0) - #ifdef _OS_WINDOWS_ -static void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT +void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT { DWORD res; LPWSTR errmsg; @@ -97,19 +98,106 @@ static void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOI } #endif +#if defined(_COMPILER_MSAN_ENABLED_) || defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_) +struct link_map; +typedef void* (dlopen_prototype)(const char* filename, int flags); + +/* This function is copied from the memory sanitizer runtime. + 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 +*/ +static inline uintptr_t RoundUpTo(uintptr_t size, uintptr_t boundary) { + return (size + boundary - 1) & ~(boundary - 1); +} +static inline uintptr_t RoundDownTo(uintptr_t x, uintptr_t boundary) { + return x & ~(boundary - 1); +} +void ForEachMappedRegion(struct link_map *map, void (*cb)(const volatile void *, uintptr_t)) { +#if !defined(_OS_FREEBSD_) + typedef ElfW(Phdr) Elf_Phdr; + typedef ElfW(Ehdr) Elf_Ehdr; +#endif + char *base = (char *)map->l_addr; + Elf_Ehdr *ehdr = (Elf_Ehdr *)base; + char *phdrs = base + ehdr->e_phoff; + char *phdrs_end = phdrs + ehdr->e_phnum * ehdr->e_phentsize; + + // Find the segment with the minimum base so we can "relocate" the p_vaddr + // fields. Typically ET_DYN objects (DSOs) have base of zero and ET_EXEC + // objects have a non-zero base. + uintptr_t preferred_base = (uintptr_t)-1; + for (char *iter = phdrs; iter != phdrs_end; iter += ehdr->e_phentsize) { + Elf_Phdr *phdr = (Elf_Phdr *)iter; + if (phdr->p_type == PT_LOAD && preferred_base > (uintptr_t)phdr->p_vaddr) + preferred_base = (uintptr_t)phdr->p_vaddr; + } + + // Compute the delta from the real base to get a relocation delta. + intptr_t delta = (uintptr_t)base - preferred_base; + // Now we can figure out what the loader really mapped. + for (char *iter = phdrs; iter != phdrs_end; iter += ehdr->e_phentsize) { + Elf_Phdr *phdr = (Elf_Phdr *)iter; + if (phdr->p_type == PT_LOAD) { + uintptr_t seg_start = phdr->p_vaddr + delta; + uintptr_t seg_end = seg_start + phdr->p_memsz; + // None of these values are aligned. We consider the ragged edges of the + // load command as defined, since they are mapped from the file. + seg_start = RoundDownTo(seg_start, jl_page_size); + seg_end = RoundUpTo(seg_end, jl_page_size); + cb((void *)seg_start, seg_end - seg_start); + } + } +} +#endif + +#if defined(_OS_WINDOWS_) JL_DLLEXPORT void *jl_dlopen(const char *filename, unsigned flags) JL_NOTSAFEPOINT { -#if defined(_OS_WINDOWS_) size_t len = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); if (!len) return NULL; WCHAR *wfilename = (WCHAR*)alloca(len * sizeof(WCHAR)); if (!MultiByteToWideChar(CP_UTF8, 0, filename, -1, wfilename, len)) return NULL; - HANDLE lib = LoadLibraryExW(wfilename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); - if (lib) - needsSymRefreshModuleList = 1; + HANDLE lib; + if (flags & JL_RTLD_NOLOAD) { + lib = GetModuleHandleW(wfilename); + } + else { + lib = LoadLibraryExW(wfilename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (lib) + needsSymRefreshModuleList = 1; + } return lib; +} #else - return dlopen(filename, + +#define JL_RTLD(flags, FLAG) (flags & JL_RTLD_ ## FLAG ? RTLD_ ## FLAG : 0) + +JL_DLLEXPORT JL_NO_SANITIZE void *jl_dlopen(const char *filename, unsigned flags) JL_NOTSAFEPOINT +{ + /* The sanitizers break RUNPATH use in dlopen for annoying reasons that are + are hard to fix. Specifically, libc will use the return address of the + caller to determine certain paths and flags that affect .so location lookup. + To work around this, we need to avoid using the sanitizer's dlopen interposition, + instead using the real dlopen directly from the current shared library. + Of course, this does mean that we need to manually perform the work that + the sanitizers would otherwise do. */ +#if (defined(_COMPILER_MSAN_ENABLED_) || defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_)) && __GLIBC__ + static dlopen_prototype *dlopen = NULL; + if (!dlopen) { + dlopen = (dlopen_prototype*)dlsym(RTLD_NEXT, "dlopen"); + if (!dlopen) + return NULL; + void *libdl_handle = dlopen("libdl.so", RTLD_NOW | RTLD_NOLOAD); + assert(libdl_handle); + dlopen = (dlopen_prototype*)dlsym(libdl_handle, "dlopen"); + dlclose(libdl_handle); + assert(dlopen); + } + // The real interceptors check the validity of the string here, but let's + // just skip that for the time being. +#endif + void *hnd = dlopen(filename, (flags & JL_RTLD_NOW ? RTLD_NOW : RTLD_LAZY) | JL_RTLD(flags, LOCAL) | JL_RTLD(flags, GLOBAL) @@ -126,8 +214,15 @@ JL_DLLEXPORT void *jl_dlopen(const char *filename, unsigned flags) JL_NOTSAFEPOI | JL_RTLD(flags, FIRST) #endif ); +#if defined(_COMPILER_MSAN_ENABLED_) && defined(__GLIBC__) + struct link_map *map = (struct link_map*)hnd; + if (filename && map) + ForEachMappedRegion(map, __msan_unpoison); #endif + return hnd; } +#endif + JL_DLLEXPORT int jl_dlclose(void *handle) JL_NOTSAFEPOINT { @@ -145,6 +240,25 @@ JL_DLLEXPORT int jl_dlclose(void *handle) JL_NOTSAFEPOINT #endif } +void *jl_find_dynamic_library_by_addr(void *symbol) { + void *handle; +#ifdef _OS_WINDOWS_ + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)symbol, + (HMODULE*)&handle)) { + jl_error("could not load base module"); + } +#else + Dl_info info; + if (!dladdr(symbol, &info) || !info.dli_fname) { + jl_error("could not load base module"); + } + handle = dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_LOCAL); + dlclose(handle); // Undo ref count increment from `dlopen` +#endif + return handle; +} + JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, int throw_err) { char path[PATHBUF], relocated[PATHBUF]; @@ -156,33 +270,22 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, void *handle; int abspath; int is_atpath; - // number of extensions to try — if modname already ends with the + // number of extensions to try — if modname already ends with the // standard extension, then we don't try adding additional extensions int n_extensions = endswith_extension(modname) ? 1 : N_EXTENSIONS; + int ret; - /* - this branch returns handle of libjulia-internal - */ - if (modname == NULL) { -#ifdef _OS_WINDOWS_ - if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - (LPCWSTR)(uintptr_t)(&jl_load_dynamic_library), - (HMODULE*)&handle)) { - jl_error("could not load base module"); - } -#else - Dl_info info; - if (!dladdr((void*)(uintptr_t)&jl_load_dynamic_library, &info) || !info.dli_fname) { - jl_error("could not load base module"); - } - handle = dlopen(info.dli_fname, RTLD_NOW); -#endif - goto done; - } + // modname == NULL is a sentinel value requesting the handle of libjulia-internal + if (modname == NULL) + return jl_find_dynamic_library_by_addr(&jl_load_dynamic_library); abspath = jl_isabspath(modname); is_atpath = 0; + JL_TIMING(DL_OPEN, DL_OPEN); + if (!(flags & JL_RTLD_NOLOAD)) + jl_timing_puts(JL_TIMING_CURRENT_BLOCK, modname); + // Detect if our `modname` is something like `@rpath/libfoo.dylib` #ifdef _OS_DARWIN_ size_t nameLen = strlen(modname); @@ -205,7 +308,7 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, such as Windows, so we emulate them here. */ if (!abspath && !is_atpath && jl_base_module != NULL) { - jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH")); + jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH"), 0); 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; @@ -228,14 +331,20 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, path[0] = '\0'; if (relocated[len-1] == PATHSEPSTRING[0]) snprintf(path, PATHBUF, "%s%s%s", relocated, modname, ext); - else - snprintf(path, PATHBUF, "%s" PATHSEPSTRING "%s%s", relocated, modname, ext); + else { + ret = snprintf(path, PATHBUF, "%s" PATHSEPSTRING "%s%s", relocated, modname, ext); + if (ret < 0) + jl_errorf("path is longer than %d\n", PATHBUF); + } + #ifdef _OS_WINDOWS_ if (i == 0) { // LoadLibrary already tested the extensions, we just need to check the `stat` result #endif handle = jl_dlopen(path, flags); + if (handle && !(flags & JL_RTLD_NOLOAD)) + jl_timing_puts(JL_TIMING_CURRENT_BLOCK, jl_pathname_for_handle(handle)); if (handle) - goto done; + return handle; #ifdef _OS_WINDOWS_ err = GetLastError(); } @@ -254,11 +363,17 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, path[0] = '\0'; snprintf(path, PATHBUF, "%s%s", modname, ext); handle = jl_dlopen(path, flags); + if (handle && !(flags & JL_RTLD_NOLOAD)) + jl_timing_puts(JL_TIMING_CURRENT_BLOCK, jl_pathname_for_handle(handle)); if (handle) - goto done; + return handle; #ifdef _OS_WINDOWS_ err = GetLastError(); break; // LoadLibrary already tested the rest +#else + // bail out and show the error if file actually exists + if (jl_stat(path, (char*)&stbuf) == 0) + break; #endif } @@ -274,7 +389,6 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, } handle = NULL; -done: return handle; } @@ -299,7 +413,7 @@ JL_DLLEXPORT int jl_dlsym(void *handle, const char *symbol, void ** value, int t */ symbol_found = *value != NULL; #ifndef _OS_WINDOWS_ - const char *err; + const char *err = ""; if (!symbol_found) { dlerror(); /* Reset error status. */ *value = dlsym(handle, symbol); @@ -313,18 +427,13 @@ JL_DLLEXPORT int jl_dlsym(void *handle, const char *symbol, void ** value, int t char err[256]; win32_formatmessage(GetLastError(), err, sizeof(err)); #endif -#ifndef __clang_gcanalyzer__ - // Hide the error throwing from the analyser since there isn't a way to express - // "safepoint only when throwing error" currently. jl_errorf("could not load symbol \"%s\":\n%s", symbol, err); -#endif } return symbol_found; } -#ifdef _OS_WINDOWS_ -//Look for symbols in win32 libraries -JL_DLLEXPORT const char *jl_dlfind_win32(const char *f_name) +// Look for symbols in internal libraries +JL_DLLEXPORT const char *jl_dlfind(const char *f_name) { void * dummy; if (jl_dlsym(jl_exe_handle, f_name, &dummy, 0)) @@ -333,6 +442,7 @@ JL_DLLEXPORT const char *jl_dlfind_win32(const char *f_name) return JL_LIBJULIA_INTERNAL_DL_LIBNAME; if (jl_dlsym(jl_libjulia_handle, f_name, &dummy, 0)) return JL_LIBJULIA_DL_LIBNAME; +#ifdef _OS_WINDOWS_ if (jl_dlsym(jl_kernel32_handle, f_name, &dummy, 0)) return "kernel32"; if (jl_dlsym(jl_crtdll_handle, f_name, &dummy, 0)) // Prefer crtdll over ntdll @@ -341,6 +451,7 @@ JL_DLLEXPORT const char *jl_dlfind_win32(const char *f_name) return "ntdll"; if (jl_dlsym(jl_winsock_handle, f_name, &dummy, 0)) return "ws2_32"; +#endif // additional common libraries (libc?) could be added here, but in general, // it is better to specify the library explicitly in the code. This exists // mainly to ease compatibility with linux, and for libraries that don't @@ -352,7 +463,6 @@ JL_DLLEXPORT const char *jl_dlfind_win32(const char *f_name) // which defaults to jl_libjulia_internal_handle, where we won't find it, and // will throw the appropriate error. } -#endif #ifdef __cplusplus } diff --git a/src/dump.c b/src/dump.c deleted file mode 100644 index f2c8629ca9c8b..0000000000000 --- a/src/dump.c +++ /dev/null @@ -1,2942 +0,0 @@ -// This file is a part of Julia. License is MIT: https://julialang.org/license - -/* - saving and restoring precompiled modules (.ji files) -*/ -#include -#include - -#include "julia.h" -#include "julia_internal.h" -#include "julia_gcext.h" -#include "builtin_proto.h" -#include "serialize.h" - -#ifndef _OS_WINDOWS_ -#include -#endif - -#include "valgrind.h" -#include "julia_assert.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// This file, together with ircode.c, allows (de)serialization between -// modules and *.ji cache files. `jl_save_incremental` gets called as the final step -// during package precompilation, and `_jl_restore_incremental` by `using SomePkg` -// whenever `SomePkg` has not yet been loaded. - -// Types, methods, and method instances form a graph that may have cycles, so -// serialization has to break these cycles. This is handled via "backreferences," -// referring to already (de)serialized items by an index. It is critial to ensure -// that the indexes of these backreferences align precisely during serialization -// and deserialization, to ensure that these integer indexes mean the same thing -// under both circumstances. Consequently, if you are modifying this file, be -// careful to match the sequence, if necessary reserving space for something that will -// be updated later. - -// It is also necessary to save & restore references to externally-defined objects, -// e.g., for package methods that call methods defined in Base or elsewhere. -// Consequently during deserialization there's a distinction between "reference" -// types, methods, and method instances (essentially like a GlobalRef), -// and "recached" version that refer to the actual entity in the running session. -// We complete deserialization before beginning the process of recaching, -// because we need the backreferences during deserialization and the actual -// objects during recaching. - -// Finally, because our backedge graph is not bidirectional, special handling is -// required to identify backedges from external methods that call internal methods. -// These get set aside and restored at the end of deserialization. - -// Note that one should prioritize deserialization performance over serialization performance, -// since deserialization may be performed much more often than serialization. - - -// TODO: put WeakRefs on the weak_refs list during deserialization -// TODO: handle finalizers - -// type => tag hash for a few core types (e.g., Expr, PhiNode, etc) -static htable_t ser_tag; -// tag => type mapping, the reverse of ser_tag -static jl_value_t *deser_tag[256]; -// hash of some common symbols, encoded as CommonSym_tag plus 1 byte -static htable_t common_symbol_tag; -static jl_value_t *deser_symbols[256]; - -// table of all objects that have been deserialized, indexed by pos -// (the order in the serializer stream). the low -// bit is reserved for flagging certain entries and pos is -// left shift by 1 -static htable_t backref_table; -static int backref_table_numel; -static arraylist_t backref_list; -static htable_t new_code_instance_validate; - -// list of (jl_value_t **loc, size_t pos) entries -// for anything that was flagged by the deserializer for later -// type-rewriting of some sort. pos is the index in backref_list. -static arraylist_t flagref_list; -// ref => value hash for looking up the "real" entity from -// the deserialized ref. Used for entities that must be unique, -// like types, methods, and method instances -static htable_t uniquing_table; - -// list of (size_t pos, (void *f)(jl_value_t*)) entries -// for the serializer to mark values in need of rework by function f -// during deserialization later -static arraylist_t reinit_list; - -// list of stuff that is being serialized -// This is not quite globally rooted, but we take care to only -// ever assigned rooted values here. -static jl_array_t *serializer_worklist JL_GLOBALLY_ROOTED; - -// inverse of backedges graph (caller=>callees hash) -htable_t edges_map; - -// list of requested ccallable signatures -static arraylist_t ccallable_list; - -typedef struct { - ios_t *s; - jl_ptls_t ptls; - jl_array_t *loaded_modules_array; -} 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 void write_uint64(ios_t *s, uint64_t i) JL_NOTSAFEPOINT -{ - ios_write(s, (char*)&i, 8); -} - -static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT -{ - write_uint64(s, *((uint64_t*)&x)); -} - -void *jl_lookup_ser_tag(jl_value_t *v) -{ - return ptrhash_get(&ser_tag, v); -} - -void *jl_lookup_common_symbol(jl_value_t *v) -{ - return ptrhash_get(&common_symbol_tag, v); -} - -jl_value_t *jl_deser_tag(uint8_t tag) -{ - return deser_tag[tag]; -} - -jl_value_t *jl_deser_symbol(uint8_t tag) -{ - return deser_symbols[tag]; -} - -// --- serialize --- - -#define jl_serialize_value(s, v) jl_serialize_value_((s), (jl_value_t*)(v), 0) -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED; - -static void jl_serialize_cnull(jl_serializer_state *s, jl_value_t *t) -{ - backref_table_numel++; - write_uint8(s->s, TAG_CNULL); - jl_serialize_value(s, t); -} - -static int module_in_worklist(jl_module_t *mod) JL_NOTSAFEPOINT -{ - int i, l = jl_array_len(serializer_worklist); - for (i = 0; i < l; i++) { - jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i); - if (jl_is_module(workmod) && jl_is_submodule(mod, workmod)) - return 1; - } - return 0; -} - -// compute whether a type references something internal to worklist -// and thus could not have existed before deserialize -// and thus does not need delayed unique-ing -static int type_in_worklist(jl_datatype_t *dt) JL_NOTSAFEPOINT -{ - if (module_in_worklist(dt->name->module)) - return 1; - int i, l = jl_svec_len(dt->parameters); - for (i = 0; i < l; i++) { - jl_value_t *p = jl_unwrap_unionall(jl_tparam(dt, i)); - // TODO: what about Union and TypeVar?? - if (type_in_worklist((jl_datatype_t*)(jl_is_datatype(p) ? p : jl_typeof(p)))) - return 1; - } - return 0; -} - -static int type_recursively_external(jl_datatype_t *dt); - -static int type_parameter_recursively_external(jl_value_t *p0) JL_NOTSAFEPOINT -{ - if (!jl_is_concrete_type(p0)) - return 0; - jl_datatype_t *p = (jl_datatype_t*)p0; - //while (jl_is_unionall(p)) { - // if (!type_parameter_recursively_external(((jl_unionall_t*)p)->var->lb)) - // return 0; - // if (!type_parameter_recursively_external(((jl_unionall_t*)p)->var->ub)) - // return 0; - // p = (jl_datatype_t*)((jl_unionall_t*)p)->body; - //} - if (module_in_worklist(p->name->module)) - return 0; - if (p->name->wrapper != (jl_value_t*)p0) { - if (!type_recursively_external(p)) - return 0; - } - return 1; -} - -// returns true if all of the parameters are tag 6 or 7 -static int type_recursively_external(jl_datatype_t *dt) JL_NOTSAFEPOINT -{ - if (!dt->isconcretetype) - return 0; - if (jl_svec_len(dt->parameters) == 0) - return 1; - - int i, l = jl_svec_len(dt->parameters); - for (i = 0; i < l; i++) { - if (!type_parameter_recursively_external(jl_tparam(dt, i))) - return 0; - } - return 1; -} - - -static void jl_serialize_datatype(jl_serializer_state *s, jl_datatype_t *dt) JL_GC_DISABLED -{ - int tag = 0; - int internal = module_in_worklist(dt->name->module); - if (!internal && jl_unwrap_unionall(dt->name->wrapper) == (jl_value_t*)dt) { - tag = 6; // external primary type - } - else if (jl_is_tuple_type(dt) ? !dt->isconcretetype : dt->hasfreetypevars) { - tag = 0; // normal struct - } - else if (internal) { - if (jl_unwrap_unionall(dt->name->wrapper) == (jl_value_t*)dt) // comes up often since functions create types - tag = 5; // internal, and not in the typename cache - else - tag = 10; // anything else that's internal (just may need recaching) - } - else if (type_recursively_external(dt)) { - tag = 7; // external type that can be immediately recreated (with apply_type) - } - else if (type_in_worklist(dt)) { - tag = 11; // external, but definitely new (still needs caching, but not full unique-ing) - } - else { - // this is eligible for (and possibly requires) unique-ing later, - // so flag this in the backref table as special - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, dt); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - tag = 12; - } - - char *dtname = jl_symbol_name(dt->name->name); - size_t dtnl = strlen(dtname); - if (dtnl > 4 && strcmp(&dtname[dtnl - 4], "##kw") == 0 && !internal && tag != 0) { - /* XXX: yuck, this is horrible, but the auto-generated kw types from the serializer isn't a real type, so we *must* be very careful */ - assert(tag == 6); // other struct types should never exist - tag = 9; - if (jl_type_type_mt->kwsorter != NULL && dt == (jl_datatype_t*)jl_typeof(jl_type_type_mt->kwsorter)) { - dt = jl_datatype_type; // any representative member with this MethodTable - } - else if (jl_nonfunction_mt->kwsorter != NULL && dt == (jl_datatype_t*)jl_typeof(jl_nonfunction_mt->kwsorter)) { - dt = jl_symbol_type; // any representative member with this MethodTable - } - else { - // search for the representative member of this MethodTable - jl_methtable_t *mt = dt->name->mt; - size_t l = strlen(jl_symbol_name(mt->name)); - char *prefixed; - prefixed = (char*)malloc_s(l + 2); - prefixed[0] = '#'; - strcpy(&prefixed[1], jl_symbol_name(mt->name)); - // remove ##kw suffix - prefixed[l-3] = 0; - jl_sym_t *tname = jl_symbol(prefixed); - free(prefixed); - jl_value_t *primarydt = jl_get_global(mt->module, tname); - if (!primarydt) - primarydt = jl_get_global(mt->module, mt->name); - primarydt = jl_unwrap_unionall(primarydt); - assert(jl_is_datatype(primarydt)); - assert(primarydt == (jl_value_t*)jl_any_type || jl_typeof(((jl_datatype_t*)primarydt)->name->mt->kwsorter) == (jl_value_t*)dt); - dt = (jl_datatype_t*)primarydt; - } - } - - write_uint8(s->s, TAG_DATATYPE); - write_uint8(s->s, tag); - if (tag == 6 || tag == 7) { - // for tag==6, copy its typevars in case there are references to them elsewhere - jl_serialize_value(s, dt->name); - jl_serialize_value(s, dt->parameters); - return; - } - if (tag == 9) { - jl_serialize_value(s, dt); - return; - } - - write_int32(s->s, dt->size); - int has_instance = (dt->instance != NULL); - int has_layout = (dt->layout != NULL); - write_uint8(s->s, has_layout | (has_instance << 1)); - write_uint8(s->s, dt->hasfreetypevars - | (dt->isconcretetype << 1) - | (dt->isdispatchtuple << 2) - | (dt->isbitstype << 3) - | (dt->zeroinit << 4) - | (dt->has_concrete_subtype << 5) - | (dt->cached_by_hash << 6)); - write_int32(s->s, dt->hash); - - if (has_layout) { - uint8_t layout = 0; - if (dt->layout == ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->layout) { - layout = 1; - } - else if (dt->layout == jl_nothing_type->layout) { - layout = 2; - } - else if (dt->layout == ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->layout) { - layout = 3; - } - write_uint8(s->s, layout); - if (layout == 0) { - uint32_t nf = dt->layout->nfields; - uint32_t np = dt->layout->npointers; - size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); - ios_write(s->s, (const char*)dt->layout, sizeof(*dt->layout)); - size_t fldsize = nf * fieldsize; - if (dt->layout->first_ptr != -1) - fldsize += np << dt->layout->fielddesc_type; - ios_write(s->s, (const char*)(dt->layout + 1), fldsize); - } - } - - if (has_instance) - jl_serialize_value(s, dt->instance); - jl_serialize_value(s, dt->name); - jl_serialize_value(s, dt->parameters); - jl_serialize_value(s, dt->super); - jl_serialize_value(s, dt->types); -} - -static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) -{ - write_uint8(s->s, TAG_MODULE); - jl_serialize_value(s, m->name); - size_t i; - if (!module_in_worklist(m)) { - if (m == m->parent) { - // top-level module - write_int8(s->s, 2); - int j = 0; - for (i = 0; i < jl_array_len(s->loaded_modules_array); i++) { - jl_module_t *mi = (jl_module_t*)jl_array_ptr_ref(s->loaded_modules_array, i); - if (!module_in_worklist(mi)) { - if (m == mi) { - write_int32(s->s, j); - return; - } - j++; - } - } - assert(0 && "top level module not found in modules array"); - } - else { - write_int8(s->s, 1); - jl_serialize_value(s, m->parent); - } - return; - } - write_int8(s->s, 0); - jl_serialize_value(s, m->parent); - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_serialize_value(s, (jl_value_t*)table[i]); - jl_binding_t *b = (jl_binding_t*)table[i+1]; - jl_serialize_value(s, b->name); - jl_value_t *e = jl_atomic_load_relaxed(&b->value); - if (!b->constp && e && jl_is_cpointer(e) && jl_unbox_voidpointer(e) != (void*)-1 && jl_unbox_voidpointer(e) != NULL) - // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - jl_serialize_cnull(s, jl_typeof(e)); - else - jl_serialize_value(s, e); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref)); - jl_serialize_value(s, b->owner); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty)); - write_int8(s->s, (b->deprecated<<3) | (b->constp<<2) | (b->exportp<<1) | (b->imported)); - } - } - jl_serialize_value(s, NULL); - write_int32(s->s, m->usings.len); - for(i=0; i < m->usings.len; i++) { - jl_serialize_value(s, (jl_value_t*)m->usings.items[i]); - } - write_uint8(s->s, m->istopmod); - write_uint64(s->s, m->uuid.hi); - write_uint64(s->s, m->uuid.lo); - write_uint64(s->s, m->build_id); - write_int32(s->s, m->counter); - write_int32(s->s, m->nospecialize); - write_uint8(s->s, m->optlevel); - write_uint8(s->s, m->compile); - write_uint8(s->s, m->infer); - write_uint8(s->s, m->max_methods); -} - -static int jl_serialize_generic(jl_serializer_state *s, jl_value_t *v) JL_GC_DISABLED -{ - if (v == NULL) { - write_uint8(s->s, TAG_NULL); - return 1; - } - - void *tag = ptrhash_get(&ser_tag, v); - if (tag != HT_NOTFOUND) { - uint8_t t8 = (intptr_t)tag; - if (t8 <= LAST_TAG) - write_uint8(s->s, 0); - write_uint8(s->s, t8); - return 1; - } - - if (jl_is_symbol(v)) { - void *idx = ptrhash_get(&common_symbol_tag, v); - if (idx != HT_NOTFOUND) { - write_uint8(s->s, TAG_COMMONSYM); - write_uint8(s->s, (uint8_t)(size_t)idx); - return 1; - } - } - else if (v == (jl_value_t*)jl_core_module) { - write_uint8(s->s, TAG_CORE); - return 1; - } - else if (v == (jl_value_t*)jl_base_module) { - write_uint8(s->s, TAG_BASE); - return 1; - } - - if (jl_typeis(v, jl_string_type) && jl_string_len(v) == 0) { - jl_serialize_value(s, jl_an_empty_string); - return 1; - } - else if (!jl_is_uint8(v)) { - void **bp = ptrhash_bp(&backref_table, v); - if (*bp != HT_NOTFOUND) { - uintptr_t pos = (char*)*bp - (char*)HT_NOTFOUND - 1; - if (pos < 65536) { - write_uint8(s->s, TAG_SHORT_BACKREF); - write_uint16(s->s, pos); - } - else { - write_uint8(s->s, TAG_BACKREF); - write_int32(s->s, pos); - } - return 1; - } - intptr_t pos = backref_table_numel++; - if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { - // will need to rehash this, later (after types are fully constructed) - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)1); - } - if (jl_is_module(v)) { - jl_module_t *m = (jl_module_t*)v; - if (module_in_worklist(m) && !module_in_worklist(m->parent)) { - // will need to reinsert this into parent bindings, later (in case of any errors during reinsert) - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)2); - } - } - // TypeMapLevels need to be rehashed - if (jl_is_mtable(v)) { - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)3); - } - pos <<= 1; - ptrhash_put(&backref_table, v, (char*)HT_NOTFOUND + pos + 1); - } - - return 0; -} - -static void jl_serialize_code_instance(jl_serializer_state *s, jl_code_instance_t *codeinst, int skip_partial_opaque) JL_GC_DISABLED -{ - if (jl_serialize_generic(s, (jl_value_t*)codeinst)) { - return; - } - - int validate = 0; - if (codeinst->max_world == ~(size_t)0) - validate = 1; // can check on deserialize if this cache entry is still valid - int flags = validate << 0; - if (codeinst->invoke == jl_fptr_const_return) - flags |= 1 << 2; - if (codeinst->precompile) - flags |= 1 << 3; - - // CodeInstances with PartialOpaque return type are currently not allowed - // to be cached. We skip them in serialization here, forcing them to - // be re-infered on reload. - int write_ret_type = validate || codeinst->min_world == 0; - if (write_ret_type && codeinst->rettype_const && - jl_typeis(codeinst->rettype_const, jl_partial_opaque_type)) { - if (skip_partial_opaque) { - jl_serialize_code_instance(s, codeinst->next, skip_partial_opaque); - return; - } - else { - jl_error("Cannot serialize CodeInstance with PartialOpaque rettype"); - } - } - - write_uint8(s->s, TAG_CODE_INSTANCE); - write_uint8(s->s, flags); - write_uint8(s->s, codeinst->ipo_purity_bits); - write_uint8(s->s, codeinst->purity_bits); - jl_serialize_value(s, (jl_value_t*)codeinst->def); - if (write_ret_type) { - jl_serialize_value(s, codeinst->inferred); - jl_serialize_value(s, codeinst->rettype_const); - jl_serialize_value(s, codeinst->rettype); - jl_serialize_value(s, codeinst->argescapes); - } - else { - // skip storing useless data - jl_serialize_value(s, NULL); - jl_serialize_value(s, NULL); - jl_serialize_value(s, jl_any_type); - jl_serialize_value(s, jl_nothing); - } - write_uint8(s->s, codeinst->relocatability); - jl_serialize_code_instance(s, codeinst->next, skip_partial_opaque); -} - -enum METHOD_SERIALIZATION_MODE { - METHOD_INTERNAL = 1, - METHOD_EXTERNAL_MT = 2, -}; - -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED -{ - if (jl_serialize_generic(s, v)) { - return; - } - - size_t i; - if (jl_is_svec(v)) { - size_t l = jl_svec_len(v); - if (l <= 255) { - write_uint8(s->s, TAG_SVEC); - write_uint8(s->s, (uint8_t)l); - } - else { - write_uint8(s->s, TAG_LONG_SVEC); - write_int32(s->s, l); - } - for (i = 0; i < l; i++) { - jl_serialize_value(s, jl_svecref(v, i)); - } - } - else if (jl_is_symbol(v)) { - size_t l = strlen(jl_symbol_name((jl_sym_t*)v)); - if (l <= 255) { - write_uint8(s->s, TAG_SYMBOL); - write_uint8(s->s, (uint8_t)l); - } - else { - write_uint8(s->s, TAG_LONG_SYMBOL); - write_int32(s->s, l); - } - ios_write(s->s, jl_symbol_name((jl_sym_t*)v), l); - } - else if (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) { - 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)); - } - for (i = 0; i < ar->flags.ndims; i++) - jl_serialize_value(s, jl_box_long(jl_array_dim(ar,i))); - jl_serialize_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); - if (e && jl_is_cpointer(e) && jl_unbox_voidpointer(e) != (void*)-1 && jl_unbox_voidpointer(e) != NULL) - // reset Ptr elements to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - jl_serialize_cnull(s, jl_typeof(e)); - else - jl_serialize_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_serialize_value(s, *fld); - start = (const char*)&fld[1]; - } - data += elsz; - if (data != start) - ios_write(s->s, start, data - start); - } - } - else if (jl_is_cpointer_type(et)) { - // reset Ptr elements to C_NULL - const void **data = (const void**)jl_array_data(ar); - for (i = 0; i < l; i++) { - const void *e = data[i]; - if (e != (void*)-1) - e = NULL; - ios_write(s->s, (const char*)&e, sizeof(e)); - } - } - 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); - } - } - else if (jl_is_datatype(v)) { - jl_serialize_datatype(s, (jl_datatype_t*)v); - } - else if (jl_is_unionall(v)) { - write_uint8(s->s, TAG_UNIONALL); - jl_datatype_t *d = (jl_datatype_t*)jl_unwrap_unionall(v); - if (jl_is_datatype(d) && d->name->wrapper == v && - !module_in_worklist(d->name->module)) { - write_uint8(s->s, 1); - jl_serialize_value(s, d->name->module); - jl_serialize_value(s, d->name->name); - } - else { - write_uint8(s->s, 0); - jl_serialize_value(s, ((jl_unionall_t*)v)->var); - jl_serialize_value(s, ((jl_unionall_t*)v)->body); - } - } - else if (jl_is_typevar(v)) { - write_uint8(s->s, TAG_TVAR); - jl_serialize_value(s, ((jl_tvar_t*)v)->name); - jl_serialize_value(s, ((jl_tvar_t*)v)->lb); - jl_serialize_value(s, ((jl_tvar_t*)v)->ub); - } - else if (jl_is_method(v)) { - write_uint8(s->s, TAG_METHOD); - jl_method_t *m = (jl_method_t*)v; - int serialization_mode = 0; - if (m->is_for_opaque_closure || module_in_worklist(m->module)) - serialization_mode |= METHOD_INTERNAL; - if (!(serialization_mode & METHOD_INTERNAL)) { - // flag this in the backref table as special - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - } - jl_serialize_value(s, (jl_value_t*)m->sig); - jl_serialize_value(s, (jl_value_t*)m->module); - if (m->external_mt != NULL) { - assert(jl_typeis(m->external_mt, jl_methtable_type)); - jl_methtable_t *mt = (jl_methtable_t*)m->external_mt; - if (!module_in_worklist(mt->module)) { - serialization_mode |= METHOD_EXTERNAL_MT; - } - } - write_uint8(s->s, serialization_mode); - if (serialization_mode & METHOD_EXTERNAL_MT) { - // We reference this method table by module and binding - jl_methtable_t *mt = (jl_methtable_t*)m->external_mt; - jl_serialize_value(s, mt->module); - jl_serialize_value(s, mt->name); - } - else { - jl_serialize_value(s, (jl_value_t*)m->external_mt); - } - if (!(serialization_mode & METHOD_INTERNAL)) - return; - jl_serialize_value(s, m->specializations); - jl_serialize_value(s, jl_atomic_load_relaxed(&m->speckeyset)); - jl_serialize_value(s, (jl_value_t*)m->name); - jl_serialize_value(s, (jl_value_t*)m->file); - write_int32(s->s, m->line); - write_int32(s->s, m->called); - write_int32(s->s, m->nargs); - write_int32(s->s, m->nospecialize); - write_int32(s->s, m->nkw); - write_int8(s->s, m->isva); - write_int8(s->s, m->pure); - write_int8(s->s, m->is_for_opaque_closure); - write_int8(s->s, m->constprop); - write_uint8(s->s, m->purity.bits); - jl_serialize_value(s, (jl_value_t*)m->slot_syms); - jl_serialize_value(s, (jl_value_t*)m->roots); - jl_serialize_value(s, (jl_value_t*)m->root_blocks); - write_int32(s->s, m->nroots_sysimg); - jl_serialize_value(s, (jl_value_t*)m->ccallable); - jl_serialize_value(s, (jl_value_t*)m->source); - jl_serialize_value(s, (jl_value_t*)m->unspecialized); - jl_serialize_value(s, (jl_value_t*)m->generator); - jl_serialize_value(s, (jl_value_t*)m->invokes); - jl_serialize_value(s, (jl_value_t*)m->recursion_relation); - } - else if (jl_is_method_instance(v)) { - jl_method_instance_t *mi = (jl_method_instance_t*)v; - if (jl_is_method(mi->def.value) && mi->def.method->is_for_opaque_closure) { - jl_error("unimplemented: serialization of MethodInstances for OpaqueClosure"); - } - write_uint8(s->s, TAG_METHOD_INSTANCE); - int internal = 0; - if (!jl_is_method(mi->def.method)) - internal = 1; - else if (module_in_worklist(mi->def.method->module)) - internal = 2; - write_uint8(s->s, internal); - if (!internal) { - // also flag this in the backref table as special - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - } - if (internal == 1) - jl_serialize_value(s, (jl_value_t*)mi->uninferred); - jl_serialize_value(s, (jl_value_t*)mi->specTypes); - jl_serialize_value(s, mi->def.value); - if (!internal) - return; - jl_serialize_value(s, (jl_value_t*)mi->sparam_vals); - jl_array_t *backedges = mi->backedges; - if (backedges) { - // filter backedges to only contain pointers - // to items that we will actually store (internal == 2) - size_t ins, i, l = jl_array_len(backedges); - jl_method_instance_t **b_edges = (jl_method_instance_t**)jl_array_data(backedges); - for (ins = i = 0; i < l; i++) { - jl_method_instance_t *backedge = b_edges[i]; - if (module_in_worklist(backedge->def.method->module)) { - b_edges[ins++] = backedge; - } - } - if (ins != l) - jl_array_del_end(backedges, l - ins); - if (ins == 0) - backedges = NULL; - } - jl_serialize_value(s, (jl_value_t*)backedges); - jl_serialize_value(s, (jl_value_t*)NULL); //callbacks - jl_serialize_code_instance(s, mi->cache, 1); - } - else if (jl_is_code_instance(v)) { - jl_serialize_code_instance(s, (jl_code_instance_t*)v, 0); - } - else if (jl_typeis(v, jl_module_type)) { - jl_serialize_module(s, (jl_module_t*)v); - } - else if (jl_typeis(v, jl_task_type)) { - jl_error("Task cannot be serialized"); - } - else if (jl_typeis(v, jl_opaque_closure_type)) { - jl_error("Live opaque closures cannot be serialized"); - } - else if (jl_typeis(v, jl_string_type)) { - write_uint8(s->s, TAG_STRING); - write_int32(s->s, jl_string_len(v)); - ios_write(s->s, jl_string_data(v), jl_string_len(v)); - } - else if (jl_typeis(v, jl_int64_type)) { - void *data = jl_data_ptr(v); - if (*(int64_t*)data >= INT16_MIN && *(int64_t*)data <= INT16_MAX) { - write_uint8(s->s, TAG_SHORTER_INT64); - write_uint16(s->s, (uint16_t)*(int64_t*)data); - } - else if (*(int64_t*)data >= S32_MIN && *(int64_t*)data <= S32_MAX) { - write_uint8(s->s, TAG_SHORT_INT64); - write_int32(s->s, (int32_t)*(int64_t*)data); - } - else { - write_uint8(s->s, TAG_INT64); - write_int64(s->s, *(int64_t*)data); - } - } - else if (jl_typeis(v, jl_int32_type)) { - void *data = jl_data_ptr(v); - if (*(int32_t*)data >= INT16_MIN && *(int32_t*)data <= INT16_MAX) { - write_uint8(s->s, TAG_SHORT_INT32); - write_uint16(s->s, (uint16_t)*(int32_t*)data); - } - else { - write_uint8(s->s, TAG_INT32); - write_int32(s->s, *(int32_t*)data); - } - } - else if (jl_typeis(v, jl_uint8_type)) { - write_uint8(s->s, TAG_UINT8); - write_int8(s->s, *(int8_t*)jl_data_ptr(v)); - } - else if (jl_is_cpointer(v) && jl_unbox_voidpointer(v) == NULL) { - write_uint8(s->s, TAG_CNULL); - jl_serialize_value(s, jl_typeof(v)); - return; - } - else if (jl_bigint_type && jl_typeis(v, jl_bigint_type)) { - write_uint8(s->s, TAG_SHORT_GENERAL); - write_uint8(s->s, jl_datatype_size(jl_bigint_type)); - jl_serialize_value(s, jl_bigint_type); - jl_value_t *sizefield = jl_get_nth_field(v, 1); - jl_serialize_value(s, sizefield); - void *data = jl_unbox_voidpointer(jl_get_nth_field(v, 2)); - int32_t sz = jl_unbox_int32(sizefield); - size_t nb = (sz == 0 ? 1 : (sz < 0 ? -sz : sz)) * gmp_limb_size; - ios_write(s->s, (char*)data, nb); - } - else { - jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); - if (v == t->instance) { - if (!type_in_worklist(t)) { - // also flag this in the backref table as special - // if it might not be unique (is external) - uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); - assert(*bp != (uintptr_t)HT_NOTFOUND); - *bp |= 1; - } - write_uint8(s->s, TAG_SINGLETON); - jl_serialize_value(s, t); - return; - } - assert(!t->instance && "detected singleton construction corruption"); - - if (t == jl_typename_type) { - void *bttag = ptrhash_get(&ser_tag, ((jl_typename_t*)t)->wrapper); - if (bttag != HT_NOTFOUND) { - write_uint8(s->s, TAG_BITYPENAME); - write_uint8(s->s, (uint8_t)(intptr_t)bttag); - return; - } - } - if (t->size <= 255) { - write_uint8(s->s, TAG_SHORT_GENERAL); - write_uint8(s->s, t->size); - } - else { - write_uint8(s->s, TAG_GENERAL); - write_int32(s->s, t->size); - } - jl_serialize_value(s, t); - if (t == jl_typename_type) { - jl_typename_t *tn = (jl_typename_t*)v; - int internal = module_in_worklist(tn->module); - write_uint8(s->s, internal); - jl_serialize_value(s, tn->module); - jl_serialize_value(s, tn->name); - if (internal) { - jl_serialize_value(s, tn->names); - jl_serialize_value(s, tn->wrapper); - jl_serialize_value(s, tn->mt); - ios_write(s->s, (char*)&tn->hash, sizeof(tn->hash)); - write_uint8(s->s, tn->abstract | (tn->mutabl << 1) | (tn->mayinlinealloc << 2)); - if (!tn->abstract) - write_uint16(s->s, tn->n_uninitialized); - size_t nb = tn->atomicfields ? (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t) : 0; - write_int32(s->s, nb); - if (nb) - ios_write(s->s, (char*)tn->atomicfields, nb); - nb = tn->constfields ? (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t) : 0; - write_int32(s->s, nb); - if (nb) - ios_write(s->s, (char*)tn->constfields, nb); - } - return; - } - - if (jl_is_foreign_type(t)) { - jl_error("Cannot serialize instances of foreign datatypes"); - } - - char *data = (char*)jl_data_ptr(v); - size_t i, j, np = t->layout->npointers; - uint32_t nf = t->layout->nfields; - char *last = data; - for (i = 0, j = 0; i < nf+1; i++) { - char *ptr = data + (i < nf ? jl_field_offset(t, i) : jl_datatype_size(t)); - if (j < np) { - char *prevptr = (char*)&((jl_value_t**)data)[jl_ptr_offset(t, j)]; - while (ptr > prevptr) { - // previous field contained pointers; write them and their interleaved data - if (prevptr > last) - ios_write(s->s, last, prevptr - last); - jl_value_t *e = *(jl_value_t**)prevptr; - JL_GC_PROMISE_ROOTED(e); - if (t->name->mutabl && e && jl_field_isptr(t, i - 1) && jl_is_cpointer(e) && - jl_unbox_voidpointer(e) != (void*)-1 && jl_unbox_voidpointer(e) != NULL) - // reset Ptr fields to C_NULL (but keep MAP_FAILED / INVALID_HANDLE) - jl_serialize_cnull(s, jl_typeof(e)); - else - jl_serialize_value(s, e); - last = prevptr + sizeof(jl_value_t*); - j++; - if (j < np) - prevptr = (char*)&((jl_value_t**)data)[jl_ptr_offset(t, j)]; - else - break; - } - } - if (i == nf) - break; - if (t->name->mutabl && jl_is_cpointer_type(jl_field_type(t, i)) && *(void**)ptr != (void*)-1) { - if (ptr > last) - ios_write(s->s, last, ptr - last); - char *n = NULL; - ios_write(s->s, (char*)&n, sizeof(n)); - last = ptr + sizeof(n); - } - } - char *ptr = data + jl_datatype_size(t); - if (ptr > last) - ios_write(s->s, last, ptr - last); - } -} - -static void jl_collect_missing_backedges_to_mod(jl_methtable_t *mt) -{ - jl_array_t *backedges = mt->backedges; - if (backedges) { - size_t i, l = jl_array_len(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); - jl_array_t **edges = (jl_array_t**)ptrhash_bp(&edges_map, (void*)caller); - if (*edges == HT_NOTFOUND) - *edges = jl_alloc_vec_any(0); - jl_array_ptr_1d_push(*edges, missing_callee); - } - } -} - -// the intent of this function is to invert the backedges tree -// for anything that points to a method not part of the worklist -static void collect_backedges(jl_method_instance_t *callee) JL_GC_DISABLED -{ - jl_array_t *backedges = callee->backedges; - if (backedges) { - size_t i, l = jl_array_len(backedges); - for (i = 0; i < l; i++) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); - jl_array_t **edges = (jl_array_t**)ptrhash_bp(&edges_map, caller); - if (*edges == HT_NOTFOUND) - *edges = jl_alloc_vec_any(0); - jl_array_ptr_1d_push(*edges, (jl_value_t*)callee); - } - } -} - - -static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) JL_GC_DISABLED -{ - jl_array_t *s = (jl_array_t*)closure; - jl_method_t *m = ml->func.method; - if (module_in_worklist(m->module)) { - jl_array_ptr_1d_push(s, (jl_value_t*)m); - jl_array_ptr_1d_push(s, (jl_value_t*)ml->simplesig); - } - else { - jl_svec_t *specializations = m->specializations; - size_t i, l = jl_svec_len(specializations); - for (i = 0; i < l; i++) { - jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(specializations, i); - if ((jl_value_t*)callee != jl_nothing) - collect_backedges(callee); - } - } - return 1; -} - -static void jl_collect_methtable_from_mod(jl_array_t *s, jl_methtable_t *mt) JL_GC_DISABLED -{ - jl_typemap_visitor(mt->defs, jl_collect_methcache_from_mod, (void*)s); -} - -// Collect methods of external functions defined by modules in the worklist -// "extext" = "extending external" -// Also collect relevant backedges -static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) JL_GC_DISABLED -{ - if (module_in_worklist(m)) - return; - size_t i; - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner == m && b->value && b->constp) { - jl_value_t *bv = jl_unwrap_unionall(b->value); - if (jl_is_datatype(bv)) { - jl_typename_t *tn = ((jl_datatype_t*)bv)->name; - if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) { - jl_methtable_t *mt = tn->mt; - if (mt != NULL && - (jl_value_t*)mt != jl_nothing && - (mt != jl_type_type_mt && mt != jl_nonfunction_mt)) { - jl_collect_methtable_from_mod(s, mt); - jl_collect_missing_backedges_to_mod(mt); - } - } - } - else if (jl_is_module(b->value)) { - jl_module_t *child = (jl_module_t*)b->value; - if (child != m && child->parent == m && child->name == b->name) { - // this is the original/primary binding for the submodule - jl_collect_extext_methods_from_mod(s, (jl_module_t*)b->value); - } - } - else if (jl_is_mtable(b->value)) { - jl_methtable_t *mt = (jl_methtable_t*)b->value; - if (mt->module == m && mt->name == b->name) { - // this is probably an external method table, so let's assume so - // as there is no way to precisely distinguish them, - // and the rest of this serializer does not bother - // to handle any method tables specially - jl_collect_methtable_from_mod(s, (jl_methtable_t*)bv); - } - } - } - } - } -} - -// flatten the backedge map reachable from caller into callees -static void jl_collect_backedges_to(jl_method_instance_t *caller, htable_t *all_callees) JL_GC_DISABLED -{ - jl_array_t **pcallees = (jl_array_t**)ptrhash_bp(&edges_map, (void*)caller), - *callees = *pcallees; - if (callees != HT_NOTFOUND) { - *pcallees = (jl_array_t*) HT_NOTFOUND; - size_t i, l = jl_array_len(callees); - for (i = 0; i < l; i++) { - jl_value_t *c = jl_array_ptr_ref(callees, i); - ptrhash_put(all_callees, c, c); - if (jl_is_method_instance(c)) { - jl_collect_backedges_to((jl_method_instance_t*)c, all_callees); - } - } - } -} - -// Extract `edges` and `ext_targets` from `edges_map` -// This identifies internal->external edges in the call graph, pulling them out for special treatment. -static void jl_collect_backedges( /* edges */ jl_array_t *s, /* ext_targets */ jl_array_t *t) -{ - htable_t all_targets; // target => tgtindex mapping - htable_t all_callees; // MIs called by worklist methods (eff. Set{MethodInstance}) - htable_new(&all_targets, 0); - htable_new(&all_callees, 0); - size_t i; - void **table = edges_map.table; // edges is caller => callees - for (i = 0; i < edges_map.size; i += 2) { - jl_method_instance_t *caller = (jl_method_instance_t*)table[i]; - jl_array_t *callees = (jl_array_t*)table[i + 1]; - if (callees != HT_NOTFOUND && module_in_worklist(caller->def.method->module)) { - size_t i, l = jl_array_len(callees); - for (i = 0; i < l; i++) { - jl_value_t *c = jl_array_ptr_ref(callees, i); - ptrhash_put(&all_callees, c, c); - if (jl_is_method_instance(c)) { - jl_collect_backedges_to((jl_method_instance_t*)c, &all_callees); - } - } - callees = jl_alloc_array_1d(jl_array_int32_type, 0); - void **pc = all_callees.table; - size_t j; - int valid = 1; - for (j = 0; valid && j < all_callees.size; j += 2) { - if (pc[j + 1] != HT_NOTFOUND) { - jl_value_t *callee = (jl_value_t*)pc[j]; - void *target = ptrhash_get(&all_targets, (void*)callee); - if (target == HT_NOTFOUND) { - jl_method_instance_t *callee_mi = (jl_method_instance_t*)callee; - jl_value_t *sig; - if (jl_is_method_instance(callee)) { - sig = callee_mi->specTypes; - } - else { - sig = callee; - } - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - int ambig = 0; - jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, -1, 0, jl_atomic_load_acquire(&jl_world_counter), &min_valid, &max_valid, &ambig); - if (matches == jl_false) { - valid = 0; - break; - } - size_t k; - for (k = 0; k < jl_array_len(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); - } - jl_array_ptr_1d_push(t, callee); - jl_array_ptr_1d_push(t, matches); - target = (char*)HT_NOTFOUND + jl_array_len(t) / 2; - ptrhash_put(&all_targets, (void*)callee, target); - } - jl_array_grow_end(callees, 1); - ((int32_t*)jl_array_data(callees))[jl_array_len(callees) - 1] = (char*)target - (char*)HT_NOTFOUND - 1; - } - } - htable_reset(&all_callees, 100); - if (valid) { - jl_array_ptr_1d_push(s, (jl_value_t*)caller); - jl_array_ptr_1d_push(s, (jl_value_t*)callees); - } - } - } - htable_free(&all_targets); - htable_free(&all_callees); -} - -// serialize information about all loaded modules -static void write_mod_list(ios_t *s, jl_array_t *a) -{ - size_t i; - size_t len = jl_array_len(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)); - if (!module_in_worklist(m)) { - const char *modname = jl_symbol_name(m->name); - size_t l = strlen(modname); - write_int32(s, l); - ios_write(s, modname, l); - write_uint64(s, m->uuid.hi); - write_uint64(s, m->uuid.lo); - write_uint64(s, m->build_id); - } - } - write_int32(s, 0); -} - -// "magic" string and version header of .ji file -static const int JI_FORMAT_VERSION = 11; -static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature -static const uint16_t BOM = 0xFEFF; // byte-order marker -static void write_header(ios_t *s) -{ - ios_write(s, JI_MAGIC, strlen(JI_MAGIC)); - write_uint16(s, JI_FORMAT_VERSION); - ios_write(s, (char *) &BOM, 2); - write_uint8(s, sizeof(void*)); - ios_write(s, JL_BUILD_UNAME, strlen(JL_BUILD_UNAME)+1); - ios_write(s, JL_BUILD_ARCH, strlen(JL_BUILD_ARCH)+1); - ios_write(s, JULIA_VERSION_STRING, strlen(JULIA_VERSION_STRING)+1); - const char *branch = jl_git_branch(), *commit = jl_git_commit(); - ios_write(s, branch, strlen(branch)+1); - ios_write(s, commit, strlen(commit)+1); -} - -// serialize information about the result of deserializing this file -static void write_work_list(ios_t *s) -{ - int i, l = jl_array_len(serializer_worklist); - for (i = 0; i < l; i++) { - jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i); - if (workmod->parent == jl_main_module || workmod->parent == workmod) { - size_t l = strlen(jl_symbol_name(workmod->name)); - write_int32(s, l); - ios_write(s, jl_symbol_name(workmod->name), l); - write_uint64(s, workmod->uuid.hi); - write_uint64(s, workmod->uuid.lo); - write_uint64(s, workmod->build_id); - } - } - write_int32(s, 0); -} - -static void write_module_path(ios_t *s, jl_module_t *depmod) JL_NOTSAFEPOINT -{ - if (depmod->parent == jl_main_module || depmod->parent == depmod) - return; - const char *mname = jl_symbol_name(depmod->name); - size_t slen = strlen(mname); - write_module_path(s, depmod->parent); - write_int32(s, slen); - ios_write(s, mname, slen); -} - -// Cache file header -// Serialize the global Base._require_dependencies array of pathnames that -// are include dependencies. Also write Preferences and return -// the location of the srctext "pointer" in the header index. -static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp) -{ - int64_t initial_pos = 0; - int64_t pos = 0; - static jl_array_t *deps = NULL; - if (!deps) - deps = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("_require_dependencies")); - - // unique(deps) to eliminate duplicates while preserving order: - // we preserve order so that the topmost included .jl file comes first - static jl_value_t *unique_func = NULL; - if (!unique_func) - unique_func = jl_get_global(jl_base_module, jl_symbol("unique")); - jl_value_t *uniqargs[2] = {unique_func, (jl_value_t*)deps}; - 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_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL); - ct->world_age = last_age; - - // 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); - if (udeps) { - size_t i, l = jl_array_len(udeps); - 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); - write_int32(s, slen); - ios_write(s, jl_string_data(dep), slen); - write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2))); // 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(serializer_worklist); - for (j = 0; j < lj; j++) { - jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, j); - if (workmod->parent == jl_main_module || workmod->parent == workmod) { - ++provides; - if (workmod == depmod_top) { - write_int32(s, provides); - write_module_path(s, depmod); - break; - } - } - } - write_int32(s, 0); - } - write_int32(s, 0); // terminator, for ease of reading - - // Calculate Preferences hash for current package. - jl_value_t *prefs_hash = NULL; - jl_value_t *prefs_list = NULL; - JL_GC_PUSH1(&prefs_list); - if (jl_base_module) { - // Toplevel module is the module we're currently compiling, use it to get our preferences hash - jl_value_t * toplevel = (jl_value_t*)jl_get_global(jl_base_module, jl_symbol("__toplevel__")); - jl_value_t * prefs_hash_func = jl_get_global(jl_base_module, jl_symbol("get_preferences_hash")); - jl_value_t * get_compiletime_prefs_func = jl_get_global(jl_base_module, jl_symbol("get_compiletime_preferences")); - - if (toplevel && prefs_hash_func && get_compiletime_prefs_func) { - // Temporary invoke in newest world age - size_t last_age = ct->world_age; - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - - // call get_compiletime_prefs(__toplevel__) - jl_value_t *args[3] = {get_compiletime_prefs_func, (jl_value_t*)toplevel, NULL}; - prefs_list = (jl_value_t*)jl_apply(args, 2); - - // Call get_preferences_hash(__toplevel__, prefs_list) - args[0] = prefs_hash_func; - args[2] = prefs_list; - prefs_hash = (jl_value_t*)jl_apply(args, 3); - - // Reset world age to normal - ct->world_age = last_age; - } - } - - // 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); - 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); - write_int32(s, slen); - ios_write(s, jl_string_data(pref_name), slen); - } - write_int32(s, 0); // terminator - write_uint64(s, jl_unbox_uint64(prefs_hash)); - } else { - // This is an error path, but let's at least generate a valid `.ji` file. - // We declare an empty list of preference names, followed by a zero-hash. - // The zero-hash is not what would be generated for an empty set of preferences, - // and so this `.ji` file will be invalidated by a future non-erroring pass - // through this function. - write_int32(s, 0); - write_uint64(s, 0); - } - JL_GC_POP(); // for prefs_list - - // write a dummy file position to indicate the beginning of the source-text - pos = ios_pos(s); - ios_seek(s, initial_pos); - write_uint64(s, pos - initial_pos); - ios_seek(s, pos); - write_int64(s, 0); - } - return pos; -} - -// --- deserialize --- - -static jl_value_t *jl_deserialize_value(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED; - -static jl_value_t *jl_deserialize_datatype(jl_serializer_state *s, int pos, jl_value_t **loc) JL_GC_DISABLED -{ - assert(pos == backref_list.len - 1 && "nothing should have been deserialized since assigning pos"); - int tag = read_uint8(s->s); - if (tag == 6 || tag == 7) { - jl_typename_t *name = (jl_typename_t*)jl_deserialize_value(s, NULL); - jl_value_t *dtv = name->wrapper; - jl_svec_t *parameters = (jl_svec_t*)jl_deserialize_value(s, NULL); - dtv = jl_apply_type(dtv, jl_svec_data(parameters), jl_svec_len(parameters)); - backref_list.items[pos] = dtv; - return dtv; - } - if (tag == 9) { - jl_datatype_t *primarydt = (jl_datatype_t*)jl_deserialize_value(s, NULL); - jl_value_t *dtv = jl_typeof(jl_get_kwsorter((jl_value_t*)primarydt)); - backref_list.items[pos] = dtv; - return dtv; - } - if (!(tag == 0 || tag == 5 || tag == 10 || tag == 11 || tag == 12)) { - assert(0 && "corrupt deserialization state"); - abort(); - } - jl_datatype_t *dt = jl_new_uninitialized_datatype(); - backref_list.items[pos] = dt; - if (loc != NULL && loc != HT_NOTFOUND) - *loc = (jl_value_t*)dt; - size_t size = read_int32(s->s); - uint8_t flags = read_uint8(s->s); - uint8_t memflags = read_uint8(s->s); - dt->size = size; - int has_layout = flags & 1; - int has_instance = (flags >> 1) & 1; - dt->hasfreetypevars = memflags & 1; - dt->isconcretetype = (memflags >> 1) & 1; - dt->isdispatchtuple = (memflags >> 2) & 1; - dt->isbitstype = (memflags >> 3) & 1; - dt->zeroinit = (memflags >> 4) & 1; - dt->has_concrete_subtype = (memflags >> 5) & 1; - dt->cached_by_hash = (memflags >> 6) & 1; - dt->hash = read_int32(s->s); - - if (has_layout) { - uint8_t layout = read_uint8(s->s); - if (layout == 1) { - dt->layout = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_array_type))->layout; - } - else if (layout == 2) { - dt->layout = jl_nothing_type->layout; - } - else if (layout == 3) { - dt->layout = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->layout; - } - else { - assert(layout == 0); - jl_datatype_layout_t buffer; - ios_readall(s->s, (char*)&buffer, sizeof(buffer)); - uint32_t nf = buffer.nfields; - uint32_t np = buffer.npointers; - uint8_t fielddesc_type = buffer.fielddesc_type; - size_t fielddesc_size = nf > 0 ? jl_fielddesc_size(fielddesc_type) : 0; - size_t fldsize = nf * fielddesc_size; - if (buffer.first_ptr != -1) - fldsize += np << fielddesc_type; - jl_datatype_layout_t *layout = (jl_datatype_layout_t*)jl_gc_perm_alloc( - sizeof(jl_datatype_layout_t) + fldsize, - 0, 4, 0); - *layout = buffer; - ios_readall(s->s, (char*)(layout + 1), fldsize); - dt->layout = layout; - } - } - - if (tag == 10 || tag == 11 || tag == 12) { - assert(pos > 0); - arraylist_push(&flagref_list, loc == HT_NOTFOUND ? NULL : loc); - arraylist_push(&flagref_list, (void*)(uintptr_t)pos); - ptrhash_put(&uniquing_table, dt, NULL); - } - - if (has_instance) { - assert(dt->isconcretetype && "there shouldn't be an instance on an abstract type"); - dt->instance = jl_deserialize_value(s, &dt->instance); - jl_gc_wb(dt, dt->instance); - } - dt->name = (jl_typename_t*)jl_deserialize_value(s, (jl_value_t**)&dt->name); - jl_gc_wb(dt, dt->name); - dt->parameters = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->parameters); - jl_gc_wb(dt, dt->parameters); - dt->super = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)&dt->super); - jl_gc_wb(dt, dt->super); - dt->types = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&dt->types); - if (dt->types) jl_gc_wb(dt, dt->types); - - return (jl_value_t*)dt; -} - -static jl_value_t *jl_deserialize_value_svec(jl_serializer_state *s, uint8_t tag, jl_value_t **loc) JL_GC_DISABLED -{ - size_t i, len; - if (tag == TAG_SVEC) - len = read_uint8(s->s); - else - len = read_int32(s->s); - jl_svec_t *sv = jl_alloc_svec(len); - if (loc != NULL) - *loc = (jl_value_t*)sv; - arraylist_push(&backref_list, (jl_value_t*)sv); - jl_value_t **data = jl_svec_data(sv); - for (i = 0; i < len; i++) { - data[i] = jl_deserialize_value(s, &data[i]); - } - return (jl_value_t*)sv; -} - -static jl_value_t *jl_deserialize_value_symbol(jl_serializer_state *s, uint8_t tag) JL_GC_DISABLED -{ - size_t len; - if (tag == TAG_SYMBOL) - len = read_uint8(s->s); - else - len = read_int32(s->s); - char *name = (char*)(len >= 256 ? malloc_s(len + 1) : alloca(len + 1)); - ios_readall(s->s, name, len); - name[len] = '\0'; - jl_value_t *sym = (jl_value_t*)jl_symbol(name); - if (len >= 256) - free(name); - arraylist_push(&backref_list, sym); - return sym; -} - -static jl_value_t *jl_deserialize_value_array(jl_serializer_state *s, uint8_t tag) 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; - } - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, NULL); - size_t *dims = (size_t*)alloca(ndims * sizeof(size_t)); - for (i = 0; i < ndims; i++) { - dims[i] = jl_unbox_long(jl_deserialize_value(s, NULL)); - } - jl_array_t *a = jl_new_array_for_deserialization( - (jl_value_t*)NULL, ndims, dims, !isptr, hasptr, isunion, elsize); - backref_list.items[pos] = a; - jl_value_t *aty = jl_deserialize_value(s, &jl_astaggedvalue(a)->type); - 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); - for (i = 0; i < numel; i++) { - data[i] = jl_deserialize_value(s, &data[i]); - //if (data[i]) // not needed because `a` is new (gc is disabled) - // jl_gc_wb(a, data[i]); - } - 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; - for (i = 0; i < numel; i++) { - char *start = data; - for (j = 0; j < np; j++) { - uint32_t ptr = jl_ptr_offset(et, j); - jl_value_t **fld = &((jl_value_t**)data)[ptr]; - if ((char*)fld != start) - ios_readall(s->s, start, (const char*)fld - start); - *fld = jl_deserialize_value(s, fld); - //if (*fld) // not needed because `a` is new (gc is disabled) - // jl_gc_wb(a, *fld); - start = (char*)&fld[1]; - } - data += elsz; - if (data != start) - ios_readall(s->s, start, data - start); - } - assert(jl_astaggedvalue(a)->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); - } - return (jl_value_t*)a; -} - -static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_method_t *m = - (jl_method_t*)jl_gc_alloc(s->ptls, sizeof(jl_method_t), - jl_method_type); - memset(m, 0, sizeof(jl_method_t)); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, m); - m->sig = (jl_value_t*)jl_deserialize_value(s, (jl_value_t**)&m->sig); - jl_gc_wb(m, m->sig); - m->module = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->module); - jl_gc_wb(m, m->module); - int serialization_mode = read_uint8(s->s); - if (serialization_mode & METHOD_EXTERNAL_MT) { - jl_module_t *mt_mod = (jl_module_t*)jl_deserialize_value(s, NULL); - jl_sym_t *mt_name = (jl_sym_t*)jl_deserialize_value(s, NULL); - m->external_mt = jl_get_global(mt_mod, mt_name); - jl_gc_wb(m, m->external_mt); - assert(jl_typeis(m->external_mt, jl_methtable_type)); - } - else { - m->external_mt = jl_deserialize_value(s, &m->external_mt); - jl_gc_wb(m, m->external_mt); - } - if (!(serialization_mode & METHOD_INTERNAL)) { - assert(loc != NULL && loc != HT_NOTFOUND); - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)pos); - return (jl_value_t*)m; - } - m->specializations = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->specializations); - jl_gc_wb(m, m->specializations); - jl_array_t *speckeyset = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->speckeyset); - jl_atomic_store_relaxed(&m->speckeyset, speckeyset); - jl_gc_wb(m, speckeyset); - m->name = (jl_sym_t*)jl_deserialize_value(s, NULL); - jl_gc_wb(m, m->name); - m->file = (jl_sym_t*)jl_deserialize_value(s, NULL); - m->line = read_int32(s->s); - m->primary_world = jl_atomic_load_acquire(&jl_world_counter); - m->deleted_world = ~(size_t)0; - m->called = read_int32(s->s); - m->nargs = read_int32(s->s); - m->nospecialize = read_int32(s->s); - m->nkw = read_int32(s->s); - m->isva = read_int8(s->s); - m->pure = read_int8(s->s); - m->is_for_opaque_closure = read_int8(s->s); - m->constprop = read_int8(s->s); - m->purity.bits = read_uint8(s->s); - m->slot_syms = jl_deserialize_value(s, (jl_value_t**)&m->slot_syms); - jl_gc_wb(m, m->slot_syms); - m->roots = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->roots); - if (m->roots) - jl_gc_wb(m, m->roots); - m->root_blocks = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->root_blocks); - if (m->root_blocks) - jl_gc_wb(m, m->root_blocks); - m->nroots_sysimg = read_int32(s->s); - m->ccallable = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->ccallable); - if (m->ccallable) { - jl_gc_wb(m, m->ccallable); - arraylist_push(&ccallable_list, m->ccallable); - } - m->source = jl_deserialize_value(s, &m->source); - if (m->source) - jl_gc_wb(m, m->source); - m->unspecialized = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&m->unspecialized); - if (m->unspecialized) - jl_gc_wb(m, m->unspecialized); - m->generator = jl_deserialize_value(s, (jl_value_t**)&m->generator); - if (m->generator) - jl_gc_wb(m, m->generator); - m->invokes = jl_deserialize_value(s, (jl_value_t**)&m->invokes); - jl_gc_wb(m, m->invokes); - m->recursion_relation = jl_deserialize_value(s, (jl_value_t**)&m->recursion_relation); - if (m->recursion_relation) - jl_gc_wb(m, m->recursion_relation); - JL_MUTEX_INIT(&m->writelock); - return (jl_value_t*)m; -} - -static jl_value_t *jl_deserialize_value_method_instance(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_method_instance_t *mi = - (jl_method_instance_t*)jl_gc_alloc(s->ptls, sizeof(jl_method_instance_t), - jl_method_instance_type); - memset(mi, 0, sizeof(jl_method_instance_t)); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, mi); - int internal = read_uint8(s->s); - mi->specTypes = (jl_value_t*)jl_deserialize_value(s, (jl_value_t**)&mi->specTypes); - jl_gc_wb(mi, mi->specTypes); - mi->def.value = jl_deserialize_value(s, &mi->def.value); - jl_gc_wb(mi, mi->def.value); - - if (!internal) { - assert(loc != NULL && loc != HT_NOTFOUND); - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)pos); - return (jl_value_t*)mi; - } - - if (internal == 1) { - mi->uninferred = jl_deserialize_value(s, &mi->uninferred); - jl_gc_wb(mi, mi->uninferred); - } - mi->sparam_vals = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&mi->sparam_vals); - jl_gc_wb(mi, mi->sparam_vals); - mi->backedges = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&mi->backedges); - if (mi->backedges) - jl_gc_wb(mi, mi->backedges); - mi->callbacks = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&mi->callbacks); - if (mi->callbacks) - jl_gc_wb(mi, mi->callbacks); - mi->cache = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&mi->cache); - if (mi->cache) - jl_gc_wb(mi, mi->cache); - return (jl_value_t*)mi; -} - -static jl_value_t *jl_deserialize_value_code_instance(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_code_instance_t *codeinst = - (jl_code_instance_t*)jl_gc_alloc(s->ptls, sizeof(jl_code_instance_t), jl_code_instance_type); - memset(codeinst, 0, sizeof(jl_code_instance_t)); - arraylist_push(&backref_list, codeinst); - int flags = read_uint8(s->s); - int validate = (flags >> 0) & 3; - int constret = (flags >> 2) & 1; - codeinst->ipo_purity_bits = read_uint8(s->s); - codeinst->purity_bits = read_uint8(s->s); - codeinst->def = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->def); - jl_gc_wb(codeinst, codeinst->def); - codeinst->inferred = jl_deserialize_value(s, &codeinst->inferred); - jl_gc_wb(codeinst, codeinst->inferred); - codeinst->rettype_const = jl_deserialize_value(s, &codeinst->rettype_const); - if (codeinst->rettype_const) - jl_gc_wb(codeinst, codeinst->rettype_const); - codeinst->rettype = jl_deserialize_value(s, &codeinst->rettype); - jl_gc_wb(codeinst, codeinst->rettype); - codeinst->argescapes = jl_deserialize_value(s, &codeinst->argescapes); - jl_gc_wb(codeinst, codeinst->argescapes); - if (constret) - codeinst->invoke = jl_fptr_const_return; - if ((flags >> 3) & 1) - codeinst->precompile = 1; - codeinst->relocatability = read_uint8(s->s); - codeinst->next = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->next); - jl_gc_wb(codeinst, codeinst->next); - if (validate) { - codeinst->min_world = jl_atomic_load_acquire(&jl_world_counter); - ptrhash_put(&new_code_instance_validate, codeinst, (void*)(~(uintptr_t)HT_NOTFOUND)); // "HT_FOUND" - } - return (jl_value_t*)codeinst; -} - -static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) JL_GC_DISABLED -{ - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, NULL); - jl_sym_t *mname = (jl_sym_t*)jl_deserialize_value(s, NULL); - int ref_only = read_uint8(s->s); - if (ref_only) { - jl_value_t *m_ref; - if (ref_only == 1) - m_ref = jl_get_global((jl_module_t*)jl_deserialize_value(s, NULL), mname); - else - m_ref = jl_array_ptr_ref(s->loaded_modules_array, read_int32(s->s)); - backref_list.items[pos] = m_ref; - return m_ref; - } - jl_module_t *m = jl_new_module(mname); - backref_list.items[pos] = m; - m->parent = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&m->parent); - jl_gc_wb(m, m->parent); - - while (1) { - jl_sym_t *asname = (jl_sym_t*)jl_deserialize_value(s, NULL); - if (asname == NULL) - break; - jl_binding_t *b = jl_get_binding_wr(m, asname, 1); - b->name = (jl_sym_t*)jl_deserialize_value(s, (jl_value_t**)&b->name); - jl_value_t *bvalue = jl_deserialize_value(s, (jl_value_t**)&b->value); - *(jl_value_t**)&b->value = bvalue; - if (bvalue != NULL) jl_gc_wb(m, bvalue); - jl_value_t *bglobalref = jl_deserialize_value(s, (jl_value_t**)&b->globalref); - *(jl_value_t**)&b->globalref = bglobalref; - if (bglobalref != NULL) jl_gc_wb(m, bglobalref); - b->owner = (jl_module_t*)jl_deserialize_value(s, (jl_value_t**)&b->owner); - if (b->owner != NULL) jl_gc_wb(m, b->owner); - jl_value_t *bty = jl_deserialize_value(s, (jl_value_t**)&b->ty); - *(jl_value_t**)&b->ty = bty; - int8_t flags = read_int8(s->s); - b->deprecated = (flags>>3) & 1; - b->constp = (flags>>2) & 1; - b->exportp = (flags>>1) & 1; - b->imported = (flags) & 1; - } - size_t i = m->usings.len; - size_t ni = read_int32(s->s); - arraylist_grow(&m->usings, ni); - ni += i; - while (i < ni) { - m->usings.items[i] = jl_deserialize_value(s, (jl_value_t**)&m->usings.items[i]); - i++; - } - m->istopmod = read_uint8(s->s); - m->uuid.hi = read_uint64(s->s); - m->uuid.lo = read_uint64(s->s); - m->build_id = read_uint64(s->s); - m->counter = read_int32(s->s); - m->nospecialize = read_int32(s->s); - m->optlevel = read_int8(s->s); - m->compile = read_int8(s->s); - m->infer = read_int8(s->s); - m->max_methods = read_int8(s->s); - m->primary_world = jl_atomic_load_acquire(&jl_world_counter); - return (jl_value_t*)m; -} - -static jl_value_t *jl_deserialize_value_singleton(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - jl_value_t *v = (jl_value_t*)jl_gc_alloc(s->ptls, 0, NULL); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, (void*)v); - // TODO: optimize the case where the value can easily be obtained - // from an external module (tag == 6) as dt->instance - assert(loc != HT_NOTFOUND); - // if loc == NULL, then the caller can't provide the address where the instance will be - // stored. this happens if a field might store a 0-size value, but the field itself is - // not 0 size, e.g. `::Union{Int,Nothing}` - if (loc != NULL) { - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)pos); - } - jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, (jl_value_t**)HT_NOTFOUND); // no loc, since if dt is replaced, then dt->instance would be also - jl_set_typeof(v, dt); - if (dt->instance == NULL) - return v; - return dt->instance; -} - -static void jl_deserialize_struct(jl_serializer_state *s, jl_value_t *v) JL_GC_DISABLED -{ - jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(v); - char *data = (char*)jl_data_ptr(v); - size_t i, np = dt->layout->npointers; - char *start = data; - for (i = 0; i < np; i++) { - uint32_t ptr = jl_ptr_offset(dt, i); - jl_value_t **fld = &((jl_value_t**)data)[ptr]; - if ((char*)fld != start) - ios_readall(s->s, start, (const char*)fld - start); - *fld = jl_deserialize_value(s, fld); - //if (*fld)// a is new (gc is disabled) - // jl_gc_wb(a, *fld); - start = (char*)&fld[1]; - } - data += jl_datatype_size(dt); - if (data != start) - ios_readall(s->s, start, data - start); - if (dt == jl_typemap_entry_type) { - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)v; - if (entry->max_world == ~(size_t)0) { - if (entry->min_world > 1) { - // update world validity to reflect current state of the counter - entry->min_world = jl_atomic_load_acquire(&jl_world_counter); - } - } - else { - // garbage entry - delete it :( - entry->min_world = 1; - entry->max_world = 0; - } - } -} - -static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, uint8_t tag, jl_value_t **loc) JL_GC_DISABLED -{ - int32_t sz = (tag == TAG_SHORT_GENERAL ? read_uint8(s->s) : read_int32(s->s)); - jl_value_t *v = jl_gc_alloc(s->ptls, sz, NULL); - jl_set_typeof(v, (void*)(intptr_t)0x50); - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, v); - jl_datatype_t *dt = (jl_datatype_t*)jl_deserialize_value(s, &jl_astaggedvalue(v)->type); - assert(sz != 0 || loc); - if (dt == jl_typename_type) { - int internal = read_uint8(s->s); - jl_typename_t *tn; - if (internal) { - tn = (jl_typename_t*)jl_gc_alloc( - s->ptls, sizeof(jl_typename_t), jl_typename_type); - memset(tn, 0, sizeof(jl_typename_t)); - tn->cache = jl_emptysvec; // the cache is refilled later (tag 5) - tn->linearcache = jl_emptysvec; // the cache is refilled later (tag 5) - backref_list.items[pos] = tn; - } - jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL); - jl_sym_t *sym = (jl_sym_t*)jl_deserialize_value(s, NULL); - if (internal) { - tn->module = m; - tn->name = sym; - tn->names = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&tn->names); - jl_gc_wb(tn, tn->names); - tn->wrapper = jl_deserialize_value(s, &tn->wrapper); - jl_gc_wb(tn, tn->wrapper); - tn->mt = (jl_methtable_t*)jl_deserialize_value(s, (jl_value_t**)&tn->mt); - jl_gc_wb(tn, tn->mt); - ios_read(s->s, (char*)&tn->hash, sizeof(tn->hash)); - int8_t flags = read_int8(s->s); - tn->abstract = flags & 1; - tn->mutabl = (flags>>1) & 1; - tn->mayinlinealloc = (flags>>2) & 1; - if (tn->abstract) - tn->n_uninitialized = 0; - else - tn->n_uninitialized = read_uint16(s->s); - size_t nfields = read_int32(s->s); - if (nfields) { - tn->atomicfields = (uint32_t*)malloc(nfields); - ios_read(s->s, (char*)tn->atomicfields, nfields); - } - nfields = read_int32(s->s); - if (nfields) { - tn->constfields = (uint32_t*)malloc(nfields); - ios_read(s->s, (char*)tn->constfields, nfields); - } - } - else { - jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(jl_get_global(m, sym)); - assert(jl_is_datatype(dt)); - tn = dt->name; - backref_list.items[pos] = tn; - } - return (jl_value_t*)tn; - } - jl_set_typeof(v, dt); - if ((jl_value_t*)dt == jl_bigint_type) { - jl_value_t *sizefield = jl_deserialize_value(s, NULL); - int32_t sz = jl_unbox_int32(sizefield); - int32_t nw = (sz == 0 ? 1 : (sz < 0 ? -sz : sz)); - size_t nb = nw * gmp_limb_size; - void *buf = jl_gc_counted_malloc(nb); - if (buf == NULL) - jl_throw(jl_memory_exception); - ios_readall(s->s, (char*)buf, nb); - jl_set_nth_field(v, 0, jl_box_int32(nw)); - jl_set_nth_field(v, 1, sizefield); - jl_set_nth_field(v, 2, jl_box_voidpointer(buf)); - } - else { - jl_deserialize_struct(s, v); - } - return v; -} - -static jl_value_t *jl_deserialize_value(jl_serializer_state *s, jl_value_t **loc) JL_GC_DISABLED -{ - assert(!ios_eof(s->s)); - jl_value_t *v; - size_t n; - uintptr_t pos; - uint8_t tag = read_uint8(s->s); - if (tag > LAST_TAG) - return deser_tag[tag]; - switch (tag) { - case TAG_NULL: return NULL; - case 0: - tag = read_uint8(s->s); - return deser_tag[tag]; - case TAG_BACKREF: JL_FALLTHROUGH; case TAG_SHORT_BACKREF: ; - uintptr_t offs = (tag == TAG_BACKREF) ? read_int32(s->s) : read_uint16(s->s); - int isflagref = 0; - isflagref = !!(offs & 1); - offs >>= 1; - // assert(offs >= 0); // offs is unsigned so this is always true - assert(offs < backref_list.len); - jl_value_t *bp = (jl_value_t*)backref_list.items[offs]; - assert(bp); - if (isflagref && loc != HT_NOTFOUND) { - if (loc != NULL) { - // as in jl_deserialize_value_singleton, the caller won't have a place to - // store this reference given a field type like Union{Int,Nothing} - arraylist_push(&flagref_list, loc); - arraylist_push(&flagref_list, (void*)(uintptr_t)-1); - } - } - return (jl_value_t*)bp; - case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC: - return jl_deserialize_value_svec(s, tag, loc); - case TAG_COMMONSYM: - return deser_symbols[read_uint8(s->s)]; - case TAG_SYMBOL: JL_FALLTHROUGH; case TAG_LONG_SYMBOL: - return jl_deserialize_value_symbol(s, tag); - case TAG_ARRAY: JL_FALLTHROUGH; case TAG_ARRAY1D: - return jl_deserialize_value_array(s, tag); - case TAG_UNIONALL: - pos = backref_list.len; - arraylist_push(&backref_list, NULL); - if (read_uint8(s->s)) { - jl_module_t *m = (jl_module_t*)jl_deserialize_value(s, NULL); - jl_sym_t *sym = (jl_sym_t*)jl_deserialize_value(s, NULL); - jl_value_t *v = jl_get_global(m, sym); - assert(jl_is_unionall(v)); - backref_list.items[pos] = v; - return v; - } - v = jl_gc_alloc(s->ptls, sizeof(jl_unionall_t), jl_unionall_type); - backref_list.items[pos] = v; - ((jl_unionall_t*)v)->var = (jl_tvar_t*)jl_deserialize_value(s, (jl_value_t**)&((jl_unionall_t*)v)->var); - jl_gc_wb(v, ((jl_unionall_t*)v)->var); - ((jl_unionall_t*)v)->body = jl_deserialize_value(s, &((jl_unionall_t*)v)->body); - jl_gc_wb(v, ((jl_unionall_t*)v)->body); - return v; - case TAG_TVAR: - v = jl_gc_alloc(s->ptls, sizeof(jl_tvar_t), jl_tvar_type); - jl_tvar_t *tv = (jl_tvar_t*)v; - arraylist_push(&backref_list, tv); - tv->name = (jl_sym_t*)jl_deserialize_value(s, NULL); - jl_gc_wb(tv, tv->name); - tv->lb = jl_deserialize_value(s, &tv->lb); - jl_gc_wb(tv, tv->lb); - tv->ub = jl_deserialize_value(s, &tv->ub); - jl_gc_wb(tv, tv->ub); - return (jl_value_t*)tv; - case TAG_METHOD: - return jl_deserialize_value_method(s, loc); - case TAG_METHOD_INSTANCE: - return jl_deserialize_value_method_instance(s, loc); - case TAG_CODE_INSTANCE: - return jl_deserialize_value_code_instance(s, loc); - case TAG_MODULE: - return jl_deserialize_value_module(s); - case TAG_SHORTER_INT64: - v = jl_box_int64((int16_t)read_uint16(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_SHORT_INT64: - v = jl_box_int64(read_int32(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_INT64: - v = jl_box_int64((int64_t)read_uint64(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_SHORT_INT32: - v = jl_box_int32((int16_t)read_uint16(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_INT32: - v = jl_box_int32(read_int32(s->s)); - arraylist_push(&backref_list, v); - return v; - case TAG_UINT8: - return jl_box_uint8(read_uint8(s->s)); - case TAG_SINGLETON: - return jl_deserialize_value_singleton(s, loc); - case TAG_CORE: - return (jl_value_t*)jl_core_module; - case TAG_BASE: - return (jl_value_t*)jl_base_module; - case TAG_CNULL: - v = jl_gc_alloc(s->ptls, sizeof(void*), NULL); - jl_set_typeof(v, (void*)(intptr_t)0x50); - *(void**)v = NULL; - uintptr_t pos = backref_list.len; - arraylist_push(&backref_list, v); - jl_set_typeof(v, jl_deserialize_value(s, &jl_astaggedvalue(v)->type)); - return v; - case TAG_BITYPENAME: - v = deser_tag[read_uint8(s->s)]; - return (jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(v))->name; - case TAG_STRING: - n = read_int32(s->s); - v = jl_alloc_string(n); - arraylist_push(&backref_list, v); - ios_readall(s->s, jl_string_data(v), n); - return v; - case TAG_DATATYPE: - pos = backref_list.len; - arraylist_push(&backref_list, NULL); - return jl_deserialize_datatype(s, pos, loc); - default: - assert(tag == TAG_GENERAL || tag == TAG_SHORT_GENERAL); - return jl_deserialize_value_any(s, tag, loc); - } -} - -// Add methods to external (non-worklist-owned) functions -static void jl_insert_methods(jl_array_t *list) -{ - size_t i, l = jl_array_len(list); - for (i = 0; i < l; i += 2) { - jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(list, i); - assert(!meth->is_for_opaque_closure); - jl_tupletype_t *simpletype = (jl_tupletype_t*)jl_array_ptr_ref(list, i + 1); - assert(jl_is_method(meth)); - jl_methtable_t *mt = jl_method_get_table(meth); - assert((jl_value_t*)mt != jl_nothing); - jl_method_table_insert(mt, meth, simpletype); - } -} - -// verify that these edges intersect with the same methods as before -static void jl_verify_edges(jl_array_t *targets, jl_array_t **pvalids) -{ - size_t i, l = jl_array_len(targets) / 2; - jl_array_t *valids = jl_alloc_array_1d(jl_array_uint8_type, l); - memset(jl_array_data(valids), 1, l); - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); - *pvalids = valids; - for (i = 0; i < l; i++) { - jl_value_t *callee = jl_array_ptr_ref(targets, i * 2); - jl_method_instance_t *callee_mi = (jl_method_instance_t*)callee; - jl_value_t *sig; - if (jl_is_method_instance(callee)) { - sig = callee_mi->specTypes; - } - else { - sig = callee; - } - jl_array_t *expected = (jl_array_t*)jl_array_ptr_ref(targets, i * 2 + 1); - assert(jl_is_array(expected)); - int valid = 1; - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - int ambig = 0; - // TODO: possibly need to included ambiguities too (for the optimizer correctness)? - jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, -1, 0, jl_atomic_load_acquire(&jl_world_counter), &min_valid, &max_valid, &ambig); - if (matches == jl_false || jl_array_len(matches) != jl_array_len(expected)) { - valid = 0; - } - else { - size_t j, k, l = jl_array_len(expected); - for (k = 0; k < jl_array_len(matches); k++) { - jl_method_match_t *match = (jl_method_match_t*)jl_array_ptr_ref(matches, k); - jl_method_t *m = match->method; - for (j = 0; j < l; j++) { - if (m == (jl_method_t*)jl_array_ptr_ref(expected, j)) - break; - } - if (j == l) { - // intersection has a new method or a method was - // deleted--this is now probably no good, just invalidate - // everything about it now - valid = 0; - break; - } - } - } - jl_array_uint8_set(valids, i, valid); - if (!valid && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)callee); - loctag = jl_cstr_to_string("insert_backedges_callee"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - } - } - JL_GC_POP(); -} - -// Restore backedges to external targets -// `targets` is [callee1, matches1, ...], the global set of non-worklist callees of worklist-owned methods. -// `edges` = [caller1, targets_indexes1, ...], the list of worklist-owned methods calling external methods. -static void jl_insert_backedges(jl_array_t *list, jl_array_t *targets) -{ - // map(enable, ((list[i] => targets[list[i + 1] .* 2]) for i in 1:2:length(list) if all(valids[list[i + 1]]))) - size_t i, l = jl_array_len(list); - jl_array_t *valids = NULL; - jl_value_t *loctag = NULL; - JL_GC_PUSH2(&valids, &loctag); - jl_verify_edges(targets, &valids); - for (i = 0; i < l; i += 2) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(list, i); - assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - assert(caller->def.method->primary_world == jl_atomic_load_acquire(&jl_world_counter)); // caller should be new - jl_array_t *idxs_array = (jl_array_t*)jl_array_ptr_ref(list, i + 1); - assert(jl_isa((jl_value_t*)idxs_array, jl_array_int32_type)); - int32_t *idxs = (int32_t*)jl_array_data(idxs_array); - int valid = 1; - size_t j; - for (j = 0; valid && j < jl_array_len(idxs_array); j++) { - int32_t idx = idxs[j]; - valid = jl_array_uint8_ref(valids, idx); - } - if (valid) { - // if this callee is still valid, add all the backedges - for (j = 0; j < jl_array_len(idxs_array); j++) { - int32_t idx = idxs[j]; - jl_value_t *callee = jl_array_ptr_ref(targets, idx * 2); - if (jl_is_method_instance(callee)) { - jl_method_instance_add_backedge((jl_method_instance_t*)callee, caller); - } - else { - jl_methtable_t *mt = jl_method_table_for(callee); - assert((jl_value_t*)mt != jl_nothing); - jl_method_table_add_backedge(mt, callee, (jl_value_t*)caller); - } - } - // then enable it - jl_code_instance_t *codeinst = caller->cache; - while (codeinst) { - if (codeinst->min_world > 0) - codeinst->max_world = ~(size_t)0; - ptrhash_remove(&new_code_instance_validate, codeinst); // mark it as handled - codeinst = jl_atomic_load_relaxed(&codeinst->next); - } - } - else { - jl_code_instance_t *codeinst = caller->cache; - while (codeinst) { - ptrhash_remove(&new_code_instance_validate, codeinst); // should be left invalid - codeinst = jl_atomic_load_relaxed(&codeinst->next); - } - if (_jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("insert_backedges"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - } - } - } - JL_GC_POP(); -} - -static void validate_new_code_instances(void) -{ - size_t i; - for (i = 0; i < new_code_instance_validate.size; i += 2) { - if (new_code_instance_validate.table[i+1] != HT_NOTFOUND) { - ((jl_code_instance_t*)new_code_instance_validate.table[i])->max_world = ~(size_t)0; - } - } -} - -static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *mod_list) -{ - if (!jl_main_module->build_id) { - return jl_get_exceptionf(jl_errorexception_type, - "Main module uuid state is invalid for module deserialization."); - } - size_t i, l = jl_array_len(mod_list); - for (i = 0; ; i++) { - size_t len = read_int32(s); - if (len == 0 && i == l) - return NULL; // success - if (len == 0 || i == l) - return jl_get_exceptionf(jl_errorexception_type, "Wrong number of entries in module list."); - char *name = (char*)alloca(len + 1); - ios_readall(s, name, len); - name[len] = '\0'; - jl_uuid_t uuid; - uuid.hi = read_uint64(s); - uuid.lo = read_uint64(s); - uint64_t build_id = read_uint64(s); - jl_sym_t *sym = _jl_symbol(name, len); - jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_list, i); - if (!m || !jl_is_module(m) || m->uuid.hi != uuid.hi || m->uuid.lo != uuid.lo || m->name != sym || m->build_id != build_id) { - return jl_get_exceptionf(jl_errorexception_type, - "Invalid input in module list: expected %s.", name); - } - } -} - -static int readstr_verify(ios_t *s, const char *str) -{ - size_t i, len = strlen(str); - for (i = 0; i < len; ++i) - if ((char)read_uint8(s) != str[i]) - return 0; - return 1; -} - -JL_DLLEXPORT int jl_read_verify_header(ios_t *s) -{ - uint16_t bom; - return (readstr_verify(s, JI_MAGIC) && - read_uint16(s) == JI_FORMAT_VERSION && - ios_read(s, (char *) &bom, 2) == 2 && bom == BOM && - read_uint8(s) == sizeof(void*) && - readstr_verify(s, JL_BUILD_UNAME) && !read_uint8(s) && - readstr_verify(s, JL_BUILD_ARCH) && !read_uint8(s) && - readstr_verify(s, JULIA_VERSION_STRING) && !read_uint8(s) && - readstr_verify(s, jl_git_branch()) && !read_uint8(s) && - readstr_verify(s, jl_git_commit()) && !read_uint8(s)); -} - -static void jl_finalize_serializer(jl_serializer_state *s) -{ - size_t i, l; - // save module initialization order - if (jl_module_init_order != NULL) { - l = jl_array_len(jl_module_init_order); - for (i = 0; i < l; i++) { - // verify that all these modules were saved - assert(ptrhash_get(&backref_table, jl_array_ptr_ref(jl_module_init_order, i)) != HT_NOTFOUND); - } - } - jl_serialize_value(s, jl_module_init_order); - - // record list of reinitialization functions - l = reinit_list.len; - for (i = 0; i < l; i += 2) { - write_int32(s->s, (int)((uintptr_t) reinit_list.items[i])); - write_int32(s->s, (int)((uintptr_t) reinit_list.items[i+1])); - } - write_int32(s->s, -1); -} - -static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) -{ - JL_TRY { - switch (how) { - case 1: { // rehash IdDict - jl_array_t **a = (jl_array_t**)v; - // Assume *a don't need a write barrier - *a = jl_idtable_rehash(*a, jl_array_len(*a)); - jl_gc_wb(v, *a); - break; - } - case 2: { // reinsert module v into parent (const) - jl_module_t *mod = (jl_module_t*)v; - if (mod->parent == mod) // top level modules handled by loader - break; - jl_binding_t *b = jl_get_binding_wr(mod->parent, mod->name, 1); - jl_declare_constant(b); // this can throw - if (b->value != NULL) { - if (!jl_is_module(b->value)) { - jl_errorf("Invalid redefinition of constant %s.", - jl_symbol_name(mod->name)); // this also throws - } - if (jl_generating_output() && jl_options.incremental) { - jl_errorf("Cannot replace module %s during incremental precompile.", jl_symbol_name(mod->name)); - } - jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(mod->name)); - } - b->value = v; - jl_gc_wb_binding(b, v); - break; - } - case 3: { // rehash MethodTable - jl_methtable_t *mt = (jl_methtable_t*)v; - if (tracee_list) - arraylist_push(tracee_list, mt); - break; - } - default: - assert(0 && "corrupt deserialization state"); - abort(); - } - } - JL_CATCH { - jl_printf((JL_STREAM*)STDERR_FILENO, "WARNING: error while reinitializing value "); - jl_static_show((JL_STREAM*)STDERR_FILENO, v); - jl_printf((JL_STREAM*)STDERR_FILENO, ":\n"); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); - jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); - jlbacktrace(); // written to STDERR_FILENO - } -} - -static jl_array_t *jl_finalize_deserializer(jl_serializer_state *s, arraylist_t *tracee_list) -{ - jl_array_t *init_order = (jl_array_t*)jl_deserialize_value(s, NULL); - - // run reinitialization functions - int pos = read_int32(s->s); - while (pos != -1) { - jl_reinit_item((jl_value_t*)backref_list.items[pos], read_int32(s->s), tracee_list); - pos = read_int32(s->s); - } - return init_order; -} - -JL_DLLEXPORT void jl_init_restored_modules(jl_array_t *init_order) -{ - int i, l = jl_array_len(init_order); - for (i = 0; i < l; i++) { - jl_value_t *mod = jl_array_ptr_ref(init_order, i); - if (!jl_generating_output() || jl_options.incremental) { - jl_module_run_initializer((jl_module_t*)mod); - } - else { - if (jl_module_init_order == NULL) - jl_module_init_order = jl_alloc_vec_any(0); - jl_array_ptr_1d_push(jl_module_init_order, mod); - } - } -} - - -// --- entry points --- - -// Serialize the modules in `worklist` to file `fname` -JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist) -{ - JL_TIMING(SAVE_MODULE); - ios_t f; - jl_array_t *mod_array = NULL, *udeps = NULL; - if (ios_file(&f, fname, 1, 1, 1, 1) == NULL) { - jl_printf(JL_STDERR, "Cannot open cache file \"%s\" for writing.\n", fname); - return 1; - } - JL_GC_PUSH2(&mod_array, &udeps); - mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) - assert(jl_precompile_toplevel_module == NULL); - jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); - - serializer_worklist = worklist; - write_header(&f); - // write description of contents (name, uuid, buildid) - write_work_list(&f); - // 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. - // Also write Preferences. - int64_t srctextpos = write_dependency_list(&f, &udeps); // srctextpos: position of srctext entry in header index (update later) - // write description of requirements for loading (modules that must be pre-loaded if initialization is to succeed) - // this can return errors during deserialize, - // best to keep it early (before any actual initialization) - write_mod_list(&f, mod_array); - - arraylist_new(&reinit_list, 0); - htable_new(&edges_map, 0); - htable_new(&backref_table, 5000); - ptrhash_put(&backref_table, jl_main_module, (char*)HT_NOTFOUND + 1); - backref_table_numel = 1; - 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")), - jl_symbol("BITS_PER_LIMB"))) / 8; - } - - int en = jl_gc_enable(0); // edges map is not gc-safe - jl_array_t *extext_methods = jl_alloc_vec_any(0); // [method1, simplesig1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist - jl_array_t *ext_targets = jl_alloc_vec_any(0); // [callee1, matches1, ...] non-worklist callees of worklist-owned methods - jl_array_t *edges = jl_alloc_vec_any(0); // [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods - - size_t i; - size_t len = jl_array_len(mod_array); - for (i = 0; i < len; 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 - jl_collect_extext_methods_from_mod(extext_methods, m); - } - jl_collect_methtable_from_mod(extext_methods, jl_type_type_mt); - jl_collect_missing_backedges_to_mod(jl_type_type_mt); - jl_collect_methtable_from_mod(extext_methods, jl_nonfunction_mt); - jl_collect_missing_backedges_to_mod(jl_nonfunction_mt); - - // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges_to_mod accumulate data in edges_map. - // Process this to extract `edges` and `ext_targets`. - jl_collect_backedges(edges, ext_targets); - - jl_serializer_state s = { - &f, - jl_current_task->ptls, - mod_array - }; - jl_serialize_value(&s, worklist); // serialize module-owned items (those accessible from the bindings table) - jl_serialize_value(&s, extext_methods); // serialize new methods for external functions - // The next two allow us to restore backedges from external MethodInstances to internal ones - jl_serialize_value(&s, edges); - jl_serialize_value(&s, ext_targets); - jl_finalize_serializer(&s); - serializer_worklist = NULL; - - jl_gc_enable(en); - htable_reset(&edges_map, 0); - htable_reset(&backref_table, 0); - arraylist_free(&reinit_list); - - // Write the source-text for the dependent files - if (udeps) { - // Go back and update the source-text position to point to the current position - int64_t posfile = ios_pos(&f); - ios_seek(&f, srctextpos); - write_int64(&f, posfile); - ios_seek_end(&f); - // Each source-text file is written as - // int32: length of abspath - // char*: abspath - // uint64: length of src text - // char*: src text - // At the end we write int32(0) as a terminal sentinel. - len = jl_array_len(udeps); - ios_t srctext; - for (i = 0; i < len; i++) { - jl_value_t *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]) - continue; - ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); - if (!srctp) { - jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", - jl_string_data(dep)); - continue; - } - size_t slen = jl_string_len(dep); - write_int32(&f, slen); - ios_write(&f, depstr, 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); - ios_close(&srctext); - ios_seek(&f, posfile); - write_uint64(&f, filelen); - ios_seek_end(&f); - } - } - } - write_int32(&f, 0); // mark the end of the source text - ios_close(&f); - JL_GC_POP(); - jl_precompile_toplevel_module = NULL; - - return 0; -} - -#ifndef JL_NDEBUG -// skip the performance optimizations of jl_types_equal and just use subtyping directly -// one of these types is invalid - that's why we're doing the recache type operation -static int jl_invalid_types_equal(jl_datatype_t *a, jl_datatype_t *b) -{ - return jl_subtype((jl_value_t*)a, (jl_value_t*)b) && jl_subtype((jl_value_t*)b, (jl_value_t*)a); -} -STATIC_INLINE jl_value_t *verify_type(jl_value_t *v) JL_NOTSAFEPOINT -{ - assert(v && jl_typeof(v) && jl_typeof(jl_typeof(v)) == (jl_value_t*)jl_datatype_type); - return v; -} -#endif - - -static jl_datatype_t *recache_datatype(jl_datatype_t *dt) JL_GC_DISABLED; - -static jl_value_t *recache_type(jl_value_t *p) JL_GC_DISABLED -{ - if (jl_is_datatype(p)) { - jl_datatype_t *pdt = (jl_datatype_t*)p; - if (ptrhash_get(&uniquing_table, p) != HT_NOTFOUND) { - p = (jl_value_t*)recache_datatype(pdt); - } - else { - jl_svec_t *tt = pdt->parameters; - // ensure all type parameters are recached - size_t i, l = jl_svec_len(tt); - for (i = 0; i < l; i++) - jl_svecset(tt, i, recache_type(jl_svecref(tt, i))); - ptrhash_put(&uniquing_table, p, p); // ensures this algorithm isn't too exponential - } - } - else if (jl_is_typevar(p)) { - jl_tvar_t *ptv = (jl_tvar_t*)p; - ptv->lb = recache_type(ptv->lb); - ptv->ub = recache_type(ptv->ub); - } - else if (jl_is_uniontype(p)) { - jl_uniontype_t *pu = (jl_uniontype_t*)p; - pu->a = recache_type(pu->a); - pu->b = recache_type(pu->b); - } - else if (jl_is_unionall(p)) { - jl_unionall_t *pa = (jl_unionall_t*)p; - pa->var = (jl_tvar_t*)recache_type((jl_value_t*)pa->var); - pa->body = recache_type(pa->body); - } - else { - jl_datatype_t *pt = (jl_datatype_t*)jl_typeof(p); - jl_datatype_t *cachep = recache_datatype(pt); - if (cachep->instance) - p = cachep->instance; - else if (pt != cachep) - jl_set_typeof(p, cachep); - } - return p; -} - -// Extract pre-existing datatypes from cache, and insert new types into cache -// insertions also update uniquing_table -static jl_datatype_t *recache_datatype(jl_datatype_t *dt) JL_GC_DISABLED -{ - jl_datatype_t *t; // the type after unique'ing - assert(verify_type((jl_value_t*)dt)); - t = (jl_datatype_t*)ptrhash_get(&uniquing_table, dt); - if (t == HT_NOTFOUND) - return dt; - if (t != NULL) - return t; - - jl_svec_t *tt = dt->parameters; - // recache all type parameters - size_t i, l = jl_svec_len(tt); - for (i = 0; i < l; i++) - jl_svecset(tt, i, recache_type(jl_svecref(tt, i))); - - // then recache the type itself - if (jl_svec_len(tt) == 0) { // jl_cache_type doesn't work if length(parameters) == 0 - t = dt; - } - else { - t = jl_lookup_cache_type_(dt); - if (t == NULL) { - jl_cache_type_(dt); - t = dt; - } - assert(t->hash == dt->hash); - assert(jl_invalid_types_equal(t, dt)); - } - ptrhash_put(&uniquing_table, dt, t); - return t; -} - -// Recache everything from flagref_list except methods and method instances -// Cleans out any handled items so that anything left in flagref_list still needs future processing -static void jl_recache_types(void) JL_GC_DISABLED -{ - size_t i; - // first rewrite all the unique'd objects - for (i = 0; i < flagref_list.len; i += 2) { - jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0]; - int offs = (int)(intptr_t)flagref_list.items[i + 1]; - jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs]; - if (!jl_is_method(o) && !jl_is_method_instance(o)) { - jl_datatype_t *dt; - jl_value_t *v; - if (jl_is_datatype(o)) { - dt = (jl_datatype_t*)o; - v = dt->instance; - } - else { - dt = (jl_datatype_t*)jl_typeof(o); - v = o; - } - jl_datatype_t *t = recache_datatype(dt); // get or create cached type (also updates uniquing_table) - if ((jl_value_t*)dt == o && t != dt) { - assert(!type_in_worklist(dt)); - if (loc) - *loc = (jl_value_t*)t; - if (offs > 0) - backref_list.items[offs] = t; - } - if (v == o && t->instance != v) { - assert(t->instance); - assert(loc); - *loc = t->instance; - if (offs > 0) - backref_list.items[offs] = t->instance; - } - } - } - // invalidate the old datatypes to help catch errors - for (i = 0; i < uniquing_table.size; i += 2) { - jl_datatype_t *o = (jl_datatype_t*)uniquing_table.table[i]; // deserialized ref - jl_datatype_t *t = (jl_datatype_t*)uniquing_table.table[i + 1]; // the real type - if (o != t) { - assert(t != NULL && jl_is_datatype(o)); - if (t->instance != o->instance) - jl_set_typeof(o->instance, (void*)(intptr_t)0x20); - jl_set_typeof(o, (void*)(intptr_t)0x10); - } - } - // then do a cleanup pass to drop these from future iterations of flagref_list - i = 0; - while (i < flagref_list.len) { - jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0]; - int offs = (int)(intptr_t)flagref_list.items[i + 1]; - jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs]; - if (jl_is_method(o) || jl_is_method_instance(o)) { - i += 2; - } - else { - // delete this item from the flagref list, so it won't be re-encountered later - flagref_list.len -= 2; - if (i >= flagref_list.len) - break; - flagref_list.items[i + 0] = flagref_list.items[flagref_list.len + 0]; // move end-of-list here (executes a `reverse()`) - flagref_list.items[i + 1] = flagref_list.items[flagref_list.len + 1]; - } - } -} - -// look up a method from a previously deserialized dependent module -static jl_method_t *jl_lookup_method(jl_methtable_t *mt, jl_datatype_t *sig, size_t world) -{ - if (world < jl_main_module->primary_world) - world = jl_main_module->primary_world; - struct jl_typemap_assoc search = {(jl_value_t*)sig, world, NULL, 0, ~(size_t)0}; - jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->defs, &search, /*offs*/0, /*subtype*/0); - return (jl_method_t*)entry->func.value; -} - -static jl_method_t *jl_recache_method(jl_method_t *m) -{ - assert(!m->is_for_opaque_closure); - jl_datatype_t *sig = (jl_datatype_t*)m->sig; - jl_methtable_t *mt = jl_method_get_table(m); - assert((jl_value_t*)mt != jl_nothing); - jl_set_typeof(m, (void*)(intptr_t)0x30); // invalidate the old value to help catch errors - jl_method_t *_new = jl_lookup_method(mt, sig, m->module->primary_world); - return _new; -} - -static jl_value_t *jl_recache_other_(jl_value_t *o); - -static jl_method_instance_t *jl_recache_method_instance(jl_method_instance_t *mi) -{ - jl_method_t *m = mi->def.method; - m = (jl_method_t*)jl_recache_other_((jl_value_t*)m); - assert(jl_is_method(m)); - jl_datatype_t *argtypes = (jl_datatype_t*)mi->specTypes; - jl_set_typeof(mi, (void*)(intptr_t)0x40); // invalidate the old value to help catch errors - jl_svec_t *env = jl_emptysvec; - jl_value_t *ti = jl_type_intersection_env((jl_value_t*)argtypes, (jl_value_t*)m->sig, &env); - //assert(ti != jl_bottom_type); (void)ti; - if (ti == jl_bottom_type) - env = jl_emptysvec; // the intersection may fail now if the type system had made an incorrect subtype env in the past - jl_method_instance_t *_new = jl_specializations_get_linfo(m, (jl_value_t*)argtypes, env); - return _new; -} - -static jl_value_t *jl_recache_other_(jl_value_t *o) -{ - jl_value_t *newo = (jl_value_t*)ptrhash_get(&uniquing_table, o); - if (newo != HT_NOTFOUND) - return newo; - if (jl_is_method(o)) { - // lookup the real Method based on the placeholder sig - newo = (jl_value_t*)jl_recache_method((jl_method_t*)o); - ptrhash_put(&uniquing_table, newo, newo); - } - else if (jl_is_method_instance(o)) { - // lookup the real MethodInstance based on the placeholder specTypes - newo = (jl_value_t*)jl_recache_method_instance((jl_method_instance_t*)o); - } - else { - abort(); - } - ptrhash_put(&uniquing_table, o, newo); - return newo; -} - -static void jl_recache_other(void) -{ - size_t i = 0; - while (i < flagref_list.len) { - jl_value_t **loc = (jl_value_t**)flagref_list.items[i + 0]; - int offs = (int)(intptr_t)flagref_list.items[i + 1]; - jl_value_t *o = loc ? *loc : (jl_value_t*)backref_list.items[offs]; - i += 2; - jl_value_t *newo = jl_recache_other_(o); - if (loc) - *loc = newo; - if (offs > 0) - backref_list.items[offs] = newo; - } - flagref_list.len = 0; -} - -static int trace_method(jl_typemap_entry_t *entry, void *closure) -{ - jl_call_tracer(jl_newmeth_tracer, (jl_value_t*)entry->func.method); - return 1; -} - -// Restore module(s) from a cache file f -static jl_value_t *_jl_restore_incremental(ios_t *f, jl_array_t *mod_array) -{ - JL_TIMING(LOAD_MODULE); - jl_task_t *ct = jl_current_task; - if (ios_eof(f) || !jl_read_verify_header(f)) { - ios_close(f); - return jl_get_exceptionf(jl_errorexception_type, - "Precompile file header verification checks failed."); - } - { // skip past the mod list - size_t len; - while ((len = read_int32(f))) - ios_skip(f, len + 3 * sizeof(uint64_t)); - } - { // skip past the dependency list - size_t deplen = read_uint64(f); - ios_skip(f, deplen); - } - - 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")), - jl_symbol("BITS_PER_LIMB"))) / 8; - } - - // verify that the system state is valid - jl_value_t *verify_fail = read_verify_mod_list(f, mod_array); - if (verify_fail) { - ios_close(f); - return verify_fail; - } - - // prepare to deserialize - int en = jl_gc_enable(0); - jl_gc_enable_finalizers(ct, 0); - jl_atomic_fetch_add(&jl_world_counter, 1); // reserve a world age for the deserialization - - arraylist_new(&backref_list, 4000); - arraylist_push(&backref_list, jl_main_module); - arraylist_new(&flagref_list, 0); - htable_new(&new_code_instance_validate, 0); - arraylist_new(&ccallable_list, 0); - htable_new(&uniquing_table, 0); - - jl_serializer_state s = { - f, - ct->ptls, - mod_array - }; - jl_array_t *restored = (jl_array_t*)jl_deserialize_value(&s, (jl_value_t**)&restored); - serializer_worklist = restored; - assert(jl_isa((jl_value_t*)restored, jl_array_any_type)); - - // See explanation in jl_save_incremental for variables of the same names - jl_value_t *extext_methods = jl_deserialize_value(&s, &extext_methods); - jl_value_t *edges = jl_deserialize_value(&s, &edges); - jl_value_t *ext_targets = jl_deserialize_value(&s, &ext_targets); - - arraylist_t *tracee_list = NULL; - if (jl_newmeth_tracer) // debugging - tracee_list = arraylist_new((arraylist_t*)malloc_s(sizeof(arraylist_t)), 0); - - // at this point, the AST is fully reconstructed, but still completely disconnected - // now all of the interconnects will be created - jl_recache_types(); // make all of the types identities correct - htable_reset(&uniquing_table, 0); - jl_insert_methods((jl_array_t*)extext_methods); // hook up extension methods for external generic functions (needs to be after recache types) - jl_recache_other(); // make all of the other objects identities correct (needs to be after insert methods) - htable_free(&uniquing_table); - jl_array_t *init_order = jl_finalize_deserializer(&s, tracee_list); // done with f and s (needs to be after recache) - if (init_order == NULL) - init_order = (jl_array_t*)jl_an_empty_vec_any; - assert(jl_isa((jl_value_t*)init_order, jl_array_any_type)); - - JL_GC_PUSH4(&init_order, &restored, &edges, &ext_targets); - jl_gc_enable(en); // subtyping can allocate a lot, not valid before recache-other - - jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets); // restore external backedges (needs to be last) - - // check new CodeInstances and validate any that lack external backedges - validate_new_code_instances(); - - serializer_worklist = NULL; - htable_free(&new_code_instance_validate); - arraylist_free(&flagref_list); - arraylist_free(&backref_list); - ios_close(f); - - jl_gc_enable_finalizers(ct, 1); // make sure we don't run any Julia code concurrently before this point - if (tracee_list) { - jl_methtable_t *mt; - while ((mt = (jl_methtable_t*)arraylist_pop(tracee_list)) != NULL) { - JL_GC_PROMISE_ROOTED(mt); - jl_typemap_visitor(mt->defs, trace_method, NULL); - } - arraylist_free(tracee_list); - free(tracee_list); - } - for (int i = 0; i < ccallable_list.len; i++) { - jl_svec_t *item = (jl_svec_t*)ccallable_list.items[i]; - JL_GC_PROMISE_ROOTED(item); - int success = jl_compile_extern_c(NULL, NULL, NULL, jl_svecref(item, 0), jl_svecref(item, 1)); - if (!success) - jl_safe_printf("@ccallable was already defined for this method name\n"); - } - arraylist_free(&ccallable_list); - jl_value_t *ret = (jl_value_t*)jl_svec(2, restored, init_order); - JL_GC_POP(); - - return (jl_value_t*)ret; -} - -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t sz, jl_array_t *mod_array) -{ - ios_t f; - ios_static_buffer(&f, (char*)buf, sz); - return _jl_restore_incremental(&f, mod_array); -} - -JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *mod_array) -{ - ios_t f; - if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) { - return jl_get_exceptionf(jl_errorexception_type, - "Cache file \"%s\" not found.\n", fname); - } - return _jl_restore_incremental(&f, mod_array); -} - -// --- init --- - -void jl_init_serializer(void) -{ - jl_task_t *ct = jl_current_task; - htable_new(&ser_tag, 0); - htable_new(&common_symbol_tag, 0); - htable_new(&backref_table, 0); - - void *vals[] = { jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type, - jl_call_sym, jl_invoke_sym, jl_invoke_modify_sym, jl_goto_ifnot_sym, jl_return_sym, jl_symbol("tuple"), - jl_an_empty_string, jl_an_empty_vec_any, - - // empirical list of very common symbols - #include "common_symbols1.inc" - - jl_box_int32(0), jl_box_int32(1), jl_box_int32(2), - jl_box_int32(3), jl_box_int32(4), jl_box_int32(5), - jl_box_int32(6), jl_box_int32(7), jl_box_int32(8), - jl_box_int32(9), jl_box_int32(10), jl_box_int32(11), - jl_box_int32(12), jl_box_int32(13), jl_box_int32(14), - jl_box_int32(15), jl_box_int32(16), jl_box_int32(17), - jl_box_int32(18), jl_box_int32(19), jl_box_int32(20), - - jl_box_int64(0), jl_box_int64(1), jl_box_int64(2), - jl_box_int64(3), jl_box_int64(4), jl_box_int64(5), - jl_box_int64(6), jl_box_int64(7), jl_box_int64(8), - jl_box_int64(9), jl_box_int64(10), jl_box_int64(11), - jl_box_int64(12), jl_box_int64(13), jl_box_int64(14), - jl_box_int64(15), jl_box_int64(16), jl_box_int64(17), - jl_box_int64(18), jl_box_int64(19), jl_box_int64(20), - - jl_bool_type, jl_linenumbernode_type, jl_pinode_type, - jl_upsilonnode_type, jl_type_type, jl_bottom_type, jl_ref_type, - jl_pointer_type, jl_abstractarray_type, jl_nothing_type, - jl_vararg_type, - jl_densearray_type, jl_function_type, jl_typename_type, - jl_builtin_type, jl_task_type, jl_uniontype_type, - jl_array_any_type, jl_intrinsic_type, - jl_abstractslot_type, jl_methtable_type, jl_typemap_level_type, - jl_voidpointer_type, jl_newvarnode_type, jl_abstractstring_type, - jl_array_symbol_type, jl_anytuple_type, jl_tparam0(jl_anytuple_type), - jl_emptytuple_type, jl_array_uint8_type, jl_code_info_type, - jl_typeofbottom_type, jl_typeofbottom_type->super, - jl_namedtuple_type, jl_array_int32_type, - jl_typedslot_type, jl_uint32_type, jl_uint64_type, - jl_type_type_mt, jl_nonfunction_mt, - jl_opaque_closure_type, - - ct->ptls->root_task, - - NULL }; - - // more common symbols, less common than those above. will get 2-byte encodings. - void *common_symbols[] = { - #include "common_symbols2.inc" - NULL - }; - - deser_tag[TAG_SYMBOL] = (jl_value_t*)jl_symbol_type; - deser_tag[TAG_SSAVALUE] = (jl_value_t*)jl_ssavalue_type; - deser_tag[TAG_DATATYPE] = (jl_value_t*)jl_datatype_type; - 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_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; - deser_tag[TAG_STRING] = (jl_value_t*)jl_string_type; - deser_tag[TAG_MODULE] = (jl_value_t*)jl_module_type; - deser_tag[TAG_TVAR] = (jl_value_t*)jl_tvar_type; - deser_tag[TAG_METHOD_INSTANCE] = (jl_value_t*)jl_method_instance_type; - deser_tag[TAG_METHOD] = (jl_value_t*)jl_method_type; - deser_tag[TAG_CODE_INSTANCE] = (jl_value_t*)jl_code_instance_type; - deser_tag[TAG_GLOBALREF] = (jl_value_t*)jl_globalref_type; - 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; - deser_tag[TAG_GOTOIFNOT] = (jl_value_t*)jl_gotoifnot_type; - deser_tag[TAG_RETURNNODE] = (jl_value_t*)jl_returnnode_type; - deser_tag[TAG_ARGUMENT] = (jl_value_t*)jl_argument_type; - - intptr_t i = 0; - while (vals[i] != NULL) { - deser_tag[LAST_TAG+1+i] = (jl_value_t*)vals[i]; - i += 1; - } - assert(LAST_TAG+1+i < 256); - - for (i = 2; i < 256; i++) { - if (deser_tag[i]) - ptrhash_put(&ser_tag, deser_tag[i], (void*)i); - } - - i = 2; - while (common_symbols[i-2] != NULL) { - ptrhash_put(&common_symbol_tag, common_symbols[i-2], (void*)i); - deser_symbols[i] = (jl_value_t*)common_symbols[i-2]; - i += 1; - } - assert(i <= 256); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/features_x86.h b/src/features_x86.h index 3ef71fb217db6..08f979df546b7 100644 --- a/src/features_x86.h +++ b/src/features_x86.h @@ -74,11 +74,13 @@ JL_FEATURE_DEF(enqcmd, 32 * 3 + 29, 0) // EAX=7,ECX=0: EDX // JL_FEATURE_DEF(avx5124vnniw, 32 * 4 + 2, ?????) // JL_FEATURE_DEF(avx5124fmaps, 32 * 4 + 3, ?????) +JL_FEATURE_DEF(uintr, 32 * 4 + 5, 140000) JL_FEATURE_DEF(avx512vp2intersect, 32 * 4 + 8, 0) JL_FEATURE_DEF(serialize, 32 * 4 + 14, 110000) JL_FEATURE_DEF(tsxldtrk, 32 * 4 + 16, 110000) JL_FEATURE_DEF(pconfig, 32 * 4 + 18, 0) JL_FEATURE_DEF_NAME(amx_bf16, 32 * 4 + 22, 110000, "amx-bf16") +JL_FEATURE_DEF(avx512fp16, 32 * 4 + 23, 140000) JL_FEATURE_DEF_NAME(amx_tile, 32 * 4 + 24, 110000, "amx-tile") JL_FEATURE_DEF_NAME(amx_int8, 32 * 4 + 25, 110000, "amx-int8") @@ -89,7 +91,7 @@ JL_FEATURE_DEF(lzcnt, 32 * 5 + 5, 0) JL_FEATURE_DEF(sse4a, 32 * 5 + 6, 0) JL_FEATURE_DEF(prfchw, 32 * 5 + 8, 0) JL_FEATURE_DEF(xop, 32 * 5 + 11, 0) -JL_FEATURE_DEF(lwp, 32 * 5 + 15, 0) +// JL_FEATURE_DEF(lwp, 32 * 5 + 15, 0) Deprecated JL_FEATURE_DEF(fma4, 32 * 5 + 16, 0) JL_FEATURE_DEF(tbm, 32 * 5 + 21, 0) JL_FEATURE_DEF(mwaitx, 32 * 5 + 29, 0) @@ -108,6 +110,7 @@ JL_FEATURE_DEF(clzero, 32 * 8 + 0, 0) JL_FEATURE_DEF(wbnoinvd, 32 * 8 + 9, 0) // EAX=7,ECX=1: EAX +JL_FEATURE_DEF(avxvnni, 32 * 9 + 4, 120000) JL_FEATURE_DEF(avx512bf16, 32 * 9 + 5, 0) // EAX=0x14,ECX=0: EBX diff --git a/src/file_constants.h b/src/file_constants.h index 846404befd2fd..ee20253064f40 100644 --- a/src/file_constants.h +++ b/src/file_constants.h @@ -3,7 +3,6 @@ #include #include #include -const JL_DUMMY = 0 const JL_O_WRONLY = O_WRONLY const JL_O_RDONLY = O_RDONLY const JL_O_RDWR = O_RDWR @@ -12,17 +11,102 @@ const JL_O_CREAT = O_CREAT const JL_O_EXCL = O_EXCL const JL_O_TRUNC = O_TRUNC #ifdef O_TEMPORARY -const JL_O_TEMPORARY = O_TEMPORARY +const JL_O_TEMPORARY = O_TEMPORARY +#else +const JL_O_TEMPORARY = 0x0000 #endif #ifdef O_SHORT_LIVED -const JL_O_SHORT_LIVED = O_SHORT_LIVED +const JL_O_SHORT_LIVED = O_SHORT_LIVED +#else +const JL_O_SHORT_LIVED = 0x0000 #endif #ifdef O_SEQUENTIAL -const JL_O_SEQUENTIAL = O_SEQUENTIAL +const JL_O_SEQUENTIAL = O_SEQUENTIAL +#else +const JL_O_SEQUENTIAL = 0x0000 #endif #ifdef O_RANDOM -const JL_O_RANDOM = O_RANDOM +const JL_O_RANDOM = O_RANDOM +#else +const JL_O_RANDOM = 0x0000 #endif #ifdef O_NOCTTY -const JL_O_NOCTTY = O_NOCTTY +const JL_O_NOCTTY = O_NOCTTY +#else +const JL_O_NOCTTY = 0x0000 +#endif +#ifdef O_NONBLOCK +const JL_O_NONBLOCK = O_NONBLOCK +#else +const JL_O_NONBLOCK = 0x0000 +#endif +#ifdef O_NDELAY +const JL_O_NDELAY = O_NDELAY +#else +const JL_O_NDELAY = 0x0000 +#endif +#ifdef O_SYNC +const JL_O_SYNC = O_SYNC +#else +const JL_O_SYNC = 0x0000 +#endif +#ifdef O_FSYNC +const JL_O_FSYNC = O_FSYNC +#else +const JL_O_FSYNC = 0x0000 +#endif +#ifdef O_ASYNC +const JL_O_ASYNC = O_ASYNC +#else +const JL_O_ASYNC = 0x0000 +#endif +#ifdef O_LARGEFILE +const JL_O_LARGEFILE = O_LARGEFILE +#else +const JL_O_LARGEFILE = 0x0000 +#endif +#ifdef O_DIRECTORY +const JL_O_DIRECTORY = O_DIRECTORY +#else +const JL_O_DIRECTORY = 0x0000 +#endif +#ifdef O_NOFOLLOW +const JL_O_NOFOLLOW = O_NOFOLLOW +#else +const JL_O_NOFOLLOW = 0x0000 +#endif +#ifdef O_CLOEXEC +const JL_O_CLOEXEC = O_CLOEXEC +#else +const JL_O_CLOEXEC = 0x0000 +#endif +#ifdef O_DIRECT +const JL_O_DIRECT = O_DIRECT +#else +const JL_O_DIRECT = 0x0000 +#endif +#ifdef O_NOATIME +const JL_O_NOATIME = O_NOATIME +#else +const JL_O_NOATIME = 0x0000 +#endif +#ifdef O_PATH +const JL_O_PATH = O_PATH +#else +const JL_O_PATH = 0x0000 +#endif +#ifdef O_TMPFILE +const JL_O_TMPFILE = O_TMPFILE +#else +const JL_O_TMPFILE = 0x0000 +#endif +#ifdef O_DSYNC +const JL_O_DSYNC = O_DSYNC +#else +const JL_O_DSYNC = 0x0000 +#endif +#ifdef O_RSYNC +const JL_O_RSYNC = O_RSYNC +#else +const JL_O_RSYNC = 0x0000 #endif diff --git a/src/flisp/LICENSE b/src/flisp/LICENSE index 34860f4ba63d4..bf599268bffe8 100644 --- a/src/flisp/LICENSE +++ b/src/flisp/LICENSE @@ -23,4 +23,4 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/flisp/Makefile b/src/flisp/Makefile index 7a363b0ec13d7..c2bf30300b041 100644 --- a/src/flisp/Makefile +++ b/src/flisp/Makefile @@ -49,7 +49,7 @@ endif FLAGS := -I$(LLTSRCDIR) $(JCFLAGS) $(HFILEDIRS:%=-I%) \ -I$(LIBUV_INC) -I$(UTF8PROC_INC) -I$(build_includedir) $(LIBDIRS:%=-L%) \ - -DLIBRARY_EXPORTS -DUTF8PROC_EXPORTS + -DJL_LIBRARY_EXPORTS_INTERNAL -DUTF8PROC_EXPORTS ifneq ($(OS), emscripten) FLAGS += -DUSE_COMPUTED_GOTO endif diff --git a/src/flisp/cvalues.c b/src/flisp/cvalues.c index 071a0b1642971..a5635c238ba3c 100644 --- a/src/flisp/cvalues.c +++ b/src/flisp/cvalues.c @@ -108,7 +108,7 @@ static value_t cprim(fl_context_t *fl_ctx, fltype_t *type, size_t sz) return tagptr(pcp, TAG_CPRIM); } -value_t cvalue(fl_context_t *fl_ctx, fltype_t *type, size_t sz) +static value_t _cvalue(fl_context_t *fl_ctx, fltype_t *type, size_t sz, int may_finalize) { cvalue_t *pcv; int str=0; @@ -127,7 +127,7 @@ value_t cvalue(fl_context_t *fl_ctx, fltype_t *type, size_t sz) pcv = (cvalue_t*)alloc_words(fl_ctx, nw); pcv->type = type; pcv->data = &pcv->_space[0]; - if (type->vtable != NULL && type->vtable->finalize != NULL) + if (may_finalize && type->vtable != NULL && type->vtable->finalize != NULL) add_finalizer(fl_ctx, pcv); } else { @@ -148,6 +148,16 @@ value_t cvalue(fl_context_t *fl_ctx, fltype_t *type, size_t sz) return tagptr(pcv, TAG_CVALUE); } +value_t cvalue(fl_context_t *fl_ctx, fltype_t *type, size_t sz) +{ + return _cvalue(fl_ctx, type, sz, 1); +} + +value_t cvalue_no_finalizer(fl_context_t *fl_ctx, fltype_t *type, size_t sz) +{ + return _cvalue(fl_ctx, type, sz, 0); +} + value_t cvalue_from_data(fl_context_t *fl_ctx, fltype_t *type, void *data, size_t sz) { value_t cv; diff --git a/src/flisp/flisp.c b/src/flisp/flisp.c index 6f311fc7c8c1d..32c0008025559 100644 --- a/src/flisp/flisp.c +++ b/src/flisp/flisp.c @@ -41,6 +41,7 @@ #include #include #include +#include // defines dirname #include "platform.h" #include "libsupport.h" @@ -51,13 +52,6 @@ extern "C" { #endif -#if defined(_OS_WINDOWS_) && !defined(_COMPILER_GCC_) -#include -JL_DLLEXPORT char * dirname(char *); -#else -#include -#endif - static const char *const builtin_names[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -2402,6 +2396,7 @@ static void lisp_init(fl_context_t *fl_ctx, size_t initial_heapsize) #endif fl_ctx->jl_sym = symbol(fl_ctx, "julia_value"); + fl_ctx->jl_char_sym = symbol(fl_ctx, "julia_char"); fl_ctx->the_empty_vector = tagptr(alloc_words(fl_ctx, 1), TAG_VECTOR); vector_setsize(fl_ctx->the_empty_vector, 0); diff --git a/src/flisp/flisp.h b/src/flisp/flisp.h index 209a4f2d4fcdb..b031e456cd3fe 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 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); void cv_pin(fl_context_t *fl_ctx, cvalue_t *cv); @@ -502,6 +503,7 @@ struct _fl_context_t { value_t apply_func, apply_v, apply_e; value_t jl_sym; + value_t jl_char_sym; // persistent buffer (avoid repeated malloc/free) // for julia_extensions.c: normalize size_t jlbuflen; diff --git a/src/flisp/flmain.c b/src/flisp/flmain.c index f3861eed9e8a2..401905cc7a7a8 100644 --- a/src/flisp/flmain.c +++ b/src/flisp/flmain.c @@ -8,14 +8,6 @@ extern "C" { #endif -#if defined(__has_feature) -#if __has_feature(address_sanitizer) -const char* __asan_default_options() { - return "detect_leaks=0"; -} -#endif -#endif - static value_t argv_list(fl_context_t *fl_ctx, int argc, char *argv[]) { int i; diff --git a/src/flisp/julia_charmap.h b/src/flisp/julia_charmap.h index 3c54eaf98f484..8471d1e3b3b91 100644 --- a/src/flisp/julia_charmap.h +++ b/src/flisp/julia_charmap.h @@ -10,4 +10,5 @@ static const uint32_t charmap[][2] = { { 0x00B7, 0x22C5 }, // middot char -> dot operator (#25098) { 0x0387, 0x22C5 }, // Greek interpunct -> dot operator (#25098) { 0x2212, 0x002D }, // minus -> hyphen-minus (#26193) + { 0x210F, 0x0127 }, // hbar -> small letter h with stroke (#48870) }; diff --git a/src/flisp/julia_extensions.c b/src/flisp/julia_extensions.c index 9fcd3e9789af4..f29e3972755c5 100644 --- a/src/flisp/julia_extensions.c +++ b/src/flisp/julia_extensions.c @@ -361,6 +361,55 @@ value_t fl_string2normsymbol(fl_context_t *fl_ctx, value_t *args, uint32_t nargs return symbol(fl_ctx, normalize(fl_ctx, (char*)cvalue_data(args[0]))); } +static uint32_t _iterate_continued(uint8_t *s, size_t n, size_t *i, uint32_t u) { + if (u < 0xc0000000) { ++*i; return u; } + uint8_t b; + + if (++*i >= n) return u; + b = s[*i]; // cont byte 1 + if ((b & 0xc0) != 0x80) return u; + u |= (uint32_t)b << 16; + + if (++*i >= n || u < 0xe0000000) return u; + b = s[*i]; // cont byte 2 + if ((b & 0xc0) != 0x80) return u; + u |= (uint32_t)b << 8; + + if (++*i >= n || u < 0xf0000000) return u; + b = s[*i]; // cont byte 3 + if ((b & 0xc0) != 0x80) return u; + u |= (uint32_t)b; ++*i; + + return u; +} + +static uint32_t _string_only_julia_char(uint8_t *s, size_t n) { + if (!(0 < n && n <= 4)) + return -1; + size_t i = 0; + uint8_t b = s[i]; + uint32_t u = (uint32_t)b << 24; + if (0x80 <= b && b <= 0xf7) + u = _iterate_continued(s, n, &i, u); + else + i = 1; + if (i < n) + return -1; + return u; +} + +value_t fl_string_only_julia_char(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) { + argcount(fl_ctx, "string.only-julia-char", nargs, 1); + if (!fl_isstring(fl_ctx, args[0])) + type_error(fl_ctx, "string.only-julia-char", "string", args[0]); + 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) + return fl_ctx->F; + return fl_list2(fl_ctx, fl_ctx->jl_char_sym, mk_uint32(fl_ctx, u)); +} + static const builtinspec_t julia_flisp_func_info[] = { { "skip-ws", fl_skipws }, { "accum-julia-symbol", fl_accum_julia_symbol }, @@ -371,6 +420,7 @@ static const builtinspec_t julia_flisp_func_info[] = { { "strip-op-suffix", fl_julia_strip_op_suffix }, { "underscore-symbol?", fl_julia_underscore_symbolp }, { "string->normsymbol", fl_string2normsymbol }, + { "string.only-julia-char", fl_string_only_julia_char }, { NULL, NULL } }; diff --git a/src/flisp/profile.scm b/src/flisp/profile.scm index f5486996703cf..64a98326c7929 100644 --- a/src/flisp/profile.scm +++ b/src/flisp/profile.scm @@ -69,4 +69,3 @@ (for-each (lambda (k) (put! *profiles* k (cons 0 (cons 0 0)))) (table.keys *profiles*))))) - diff --git a/src/flisp/table.c b/src/flisp/table.c index a24cdf3bc06e8..1d8aed358e88d 100644 --- a/src/flisp/table.c +++ b/src/flisp/table.c @@ -87,9 +87,7 @@ value_t fl_table(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) value_t nt; // prevent small tables from being added to finalizer list if (cnt <= HT_N_INLINE) { - fl_ctx->table_vtable.finalize = NULL; - nt = cvalue(fl_ctx, fl_ctx->tabletype, sizeof(htable_t)); - fl_ctx->table_vtable.finalize = free_htable; + nt = cvalue_no_finalizer(fl_ctx, fl_ctx->tabletype, sizeof(htable_t)); } else { nt = cvalue(fl_ctx, fl_ctx->tabletype, 2*sizeof(void*)); @@ -104,6 +102,12 @@ value_t fl_table(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) else k = arg; } + if (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)); + ((cvalue_t*)ptr(nt))->len = 2*sizeof(void*); + } return nt; } diff --git a/src/gc-alloc-profiler.cpp b/src/gc-alloc-profiler.cpp index a5af9031cd0e3..1bcbeb2189f5f 100644 --- a/src/gc-alloc-profiler.cpp +++ b/src/gc-alloc-profiler.cpp @@ -20,7 +20,7 @@ struct jl_raw_alloc_t { jl_datatype_t *type_address; jl_raw_backtrace_t backtrace; size_t size; - jl_task_t *task; + void *task; uint64_t timestamp; }; @@ -49,15 +49,24 @@ jl_combined_results g_combined_results; // Will live forever. // === stack stuff === jl_raw_backtrace_t get_raw_backtrace() JL_NOTSAFEPOINT { - // A single large buffer to record backtraces onto - static jl_bt_element_t static_bt_data[JL_MAX_BT_SIZE]; + // We first record the backtrace onto a MAX-sized buffer, so that we don't have to + // allocate the buffer until we know the size. To ensure thread-safety, we use a + // per-thread backtrace buffer. + jl_ptls_t ptls = jl_current_task->ptls; + jl_bt_element_t *shared_bt_data_buffer = ptls->profiling_bt_buffer; + if (shared_bt_data_buffer == NULL) { + size_t size = sizeof(jl_bt_element_t) * (JL_MAX_BT_SIZE + 1); + shared_bt_data_buffer = (jl_bt_element_t*) malloc_s(size); + ptls->profiling_bt_buffer = shared_bt_data_buffer; + } - size_t bt_size = rec_backtrace(static_bt_data, JL_MAX_BT_SIZE, 2); + size_t bt_size = rec_backtrace(shared_bt_data_buffer, JL_MAX_BT_SIZE, 2); // Then we copy only the needed bytes out of the buffer into our profile. size_t bt_bytes = bt_size * sizeof(jl_bt_element_t); - jl_bt_element_t *bt_data = (jl_bt_element_t*) malloc(bt_bytes); - memcpy(bt_data, static_bt_data, bt_bytes); + jl_bt_element_t *bt_data = (jl_bt_element_t*) malloc_s(bt_bytes); + memcpy(bt_data, shared_bt_data_buffer, bt_bytes); + return jl_raw_backtrace_t{ bt_data, @@ -71,7 +80,8 @@ extern "C" { // Needed since these functions doesn't take any arguments. JL_DLLEXPORT void jl_start_alloc_profile(double sample_rate) { // We only need to do this once, the first time this is called. - while (g_alloc_profile.per_thread_profiles.size() < jl_n_threads) { + size_t nthreads = jl_atomic_load_acquire(&jl_n_threads); + while (g_alloc_profile.per_thread_profiles.size() < nthreads) { g_alloc_profile.per_thread_profiles.push_back(jl_per_thread_alloc_profile_t{}); } @@ -122,7 +132,10 @@ JL_DLLEXPORT void jl_free_alloc_profile() { void _maybe_record_alloc_to_profile(jl_value_t *val, size_t size, jl_datatype_t *type) JL_NOTSAFEPOINT { auto& global_profile = g_alloc_profile; - auto thread_id = jl_atomic_load_relaxed(&jl_current_task->tid); + size_t thread_id = jl_atomic_load_relaxed(&jl_current_task->tid); + if (thread_id >= global_profile.per_thread_profiles.size()) + return; // ignore allocations on threads started after the alloc-profile started + auto& profile = global_profile.per_thread_profiles[thread_id]; auto sample_val = double(rand()) / double(RAND_MAX); @@ -135,7 +148,7 @@ void _maybe_record_alloc_to_profile(jl_value_t *val, size_t size, jl_datatype_t type, get_raw_backtrace(), size, - jl_current_task, + (void *)jl_current_task, cycleclock() }); } diff --git a/src/gc-alloc-profiler.h b/src/gc-alloc-profiler.h index 8be6fed21a899..3fd8bf4388a0a 100644 --- a/src/gc-alloc-profiler.h +++ b/src/gc-alloc-profiler.h @@ -14,7 +14,7 @@ extern "C" { // The public interface to call from Julia for allocations profiling // --------------------------------------------------------------------- -// Forward-declaration to avoid depenency in header file. +// Forward-declaration to avoid dependency in header file. struct jl_raw_alloc_t; // Defined in gc-alloc-profiler.cpp typedef struct { diff --git a/src/gc-debug.c b/src/gc-debug.c index 8403a9f9f2e1b..a5b779c8161b1 100644 --- a/src/gc-debug.c +++ b/src/gc-debug.c @@ -90,7 +90,7 @@ void add_lostval_parent(jl_value_t *parent) At this point you should be able to run under gdb and use a hw watch to look for writes at the exact addr of the slot (use something like watch *slot_addr if *slot_addr == val). - If it went well you are now stopped at the exact point the problem is happening. - Backtraces in JIT'd code wont work for me (but I'm not sure they should) so in that + Backtraces in JIT'd code won't work for me (but I'm not sure they should) so in that case you can try to jl_throw(something) from gdb. */ // this does not yet detect missing writes from marked to marked_noesc @@ -99,7 +99,7 @@ static arraylist_t bits_save[4]; static void gc_clear_mark_page(jl_gc_pagemeta_t *pg, int bits) { - jl_ptls_t ptls2 = jl_all_tls_states[pg->thread_n]; + jl_ptls_t ptls2 = gc_all_tls_states[pg->thread_n]; jl_gc_pool_t *pool = &ptls2->heap.norm_pools[pg->pool_n]; jl_taggedvalue_t *pv = (jl_taggedvalue_t*)(pg->data + GC_PAGE_OFFSET); char *lim = (char*)pv + GC_PAGE_SZ - GC_PAGE_OFFSET - pool->osize; @@ -164,8 +164,8 @@ static void clear_mark(int bits) } } bigval_t *v; - for (int i = 0;i < jl_n_threads;i++) { - v = jl_all_tls_states[i]->heap.big_objects; + for (int i = 0; i < gc_n_threads; i++) { + v = gc_all_tls_states[i]->heap.big_objects; while (v != NULL) { void *gcv = &v->header; if (!gc_verifying) @@ -198,21 +198,32 @@ static void restore(void) static void gc_verify_track(jl_ptls_t ptls) { - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; + // `gc_verify_track` is limited to single-threaded GC + if (jl_n_gcthreads != 0) + return; do { - jl_gc_mark_sp_t sp; - gc_mark_sp_init(gc_cache, &sp); + jl_gc_markqueue_t mq; + jl_gc_markqueue_t *mq2 = &ptls->mark_queue; + ws_queue_t *cq = &mq.chunk_queue; + ws_queue_t *q = &mq.ptr_queue; + jl_atomic_store_relaxed(&cq->top, 0); + jl_atomic_store_relaxed(&cq->bottom, 0); + jl_atomic_store_relaxed(&cq->array, jl_atomic_load_relaxed(&mq2->chunk_queue.array)); + jl_atomic_store_relaxed(&q->top, 0); + jl_atomic_store_relaxed(&q->bottom, 0); + jl_atomic_store_relaxed(&q->array, jl_atomic_load_relaxed(&mq2->ptr_queue.array)); + arraylist_new(&mq.reclaim_set, 32); arraylist_push(&lostval_parents_done, lostval); jl_safe_printf("Now looking for %p =======\n", lostval); clear_mark(GC_CLEAN); - gc_mark_queue_all_roots(ptls, &sp); - gc_mark_queue_finlist(gc_cache, &sp, &to_finalize, 0); - for (int i = 0;i < jl_n_threads;i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - gc_mark_queue_finlist(gc_cache, &sp, &ptls2->finalizers, 0); + gc_mark_queue_all_roots(ptls, &mq); + gc_mark_finlist(&mq, &to_finalize, 0); + for (int i = 0; i < gc_n_threads;i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + gc_mark_finlist(&mq, &ptls2->finalizers, 0); } - gc_mark_queue_finlist(gc_cache, &sp, &finalizer_list_marked, 0); - gc_mark_loop(ptls, sp); + gc_mark_finlist(&mq, &finalizer_list_marked, 0); + gc_mark_loop_serial_(ptls, &mq); if (lostval_parents.len == 0) { jl_safe_printf("Could not find the missing link. We missed a toplevel root. This is odd.\n"); break; @@ -246,22 +257,35 @@ static void gc_verify_track(jl_ptls_t ptls) void gc_verify(jl_ptls_t ptls) { - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; - jl_gc_mark_sp_t sp; - gc_mark_sp_init(gc_cache, &sp); + // `gc_verify` is limited to single-threaded GC + if (jl_n_gcthreads != 0) { + jl_safe_printf("Warn. GC verify disabled in multi-threaded GC\n"); + return; + } + jl_gc_markqueue_t mq; + jl_gc_markqueue_t *mq2 = &ptls->mark_queue; + ws_queue_t *cq = &mq.chunk_queue; + ws_queue_t *q = &mq.ptr_queue; + jl_atomic_store_relaxed(&cq->top, 0); + jl_atomic_store_relaxed(&cq->bottom, 0); + jl_atomic_store_relaxed(&cq->array, jl_atomic_load_relaxed(&mq2->chunk_queue.array)); + jl_atomic_store_relaxed(&q->top, 0); + jl_atomic_store_relaxed(&q->bottom, 0); + jl_atomic_store_relaxed(&q->array, jl_atomic_load_relaxed(&mq2->ptr_queue.array)); + arraylist_new(&mq.reclaim_set, 32); lostval = NULL; lostval_parents.len = 0; lostval_parents_done.len = 0; clear_mark(GC_CLEAN); gc_verifying = 1; - gc_mark_queue_all_roots(ptls, &sp); - gc_mark_queue_finlist(gc_cache, &sp, &to_finalize, 0); - for (int i = 0;i < jl_n_threads;i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - gc_mark_queue_finlist(gc_cache, &sp, &ptls2->finalizers, 0); + gc_mark_queue_all_roots(ptls, &mq); + gc_mark_finlist(&mq, &to_finalize, 0); + for (int i = 0; i < gc_n_threads;i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + gc_mark_finlist(&mq, &ptls2->finalizers, 0); } - gc_mark_queue_finlist(gc_cache, &sp, &finalizer_list_marked, 0); - gc_mark_loop(ptls, sp); + gc_mark_finlist(&mq, &finalizer_list_marked, 0); + gc_mark_loop_serial_(ptls, &mq); int clean_len = bits_save[GC_CLEAN].len; for(int i = 0; i < clean_len + bits_save[GC_OLD].len; i++) { jl_taggedvalue_t *v = (jl_taggedvalue_t*)bits_save[i >= clean_len ? GC_OLD : GC_CLEAN].items[i >= clean_len ? i - clean_len : i]; @@ -297,7 +321,7 @@ static void gc_verify_tags_page(jl_gc_pagemeta_t *pg) // for all pages in use int p_n = pg->pool_n; int t_n = pg->thread_n; - jl_ptls_t ptls2 = jl_all_tls_states[t_n]; + 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; char *data = pg->data; @@ -345,10 +369,10 @@ static void gc_verify_tags_page(jl_gc_pagemeta_t *pg) if (!in_freelist) { jl_value_t *dt = jl_typeof(jl_valueof(v)); if (dt != (jl_value_t*)jl_buff_tag && - // the following are used by the deserializer to invalidate objects - v->header != 0x10 && v->header != 0x20 && - v->header != 0x30 && v->header != 0x40 && - v->header != 0x50 && v->header != 0x60) { + // the following may be use (by the deserializer) to invalidate objects + v->header != 0xf10 && v->header != 0xf20 && + v->header != 0xf30 && v->header != 0xf40 && + v->header != 0xf50 && v->header != 0xf60) { assert(jl_typeof(dt) == (jl_value_t*)jl_datatype_type); } } @@ -401,8 +425,8 @@ static void gc_verify_tags_pagetable(void) void gc_verify_tags(void) { // verify the freelist chains look valid - for (int t_i = 0; t_i < jl_n_threads; t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; for (int i = 0; i < JL_GC_N_POOLS; i++) { // for all pools, iterate its freelist jl_gc_pool_t *p = &ptls2->heap.norm_pools[i]; @@ -467,10 +491,9 @@ static void gc_debug_alloc_init(jl_alloc_num_t *num, const char *name) return; if (*env == 'r') { env++; - srand((unsigned)uv_hrtime()); - for (int i = 0;i < 3;i++) { + for (int i = 0; i < 3; i++) { while (num->random[i] == 0) { - num->random[i] = rand(); + num->random[i] = jl_rand(); } } } @@ -501,7 +524,7 @@ int jl_gc_debug_check_other(void) return gc_debug_alloc_check(&jl_gc_debug_env.other); } -void jl_gc_debug_print_status(void) +void jl_gc_debug_print_status(void) JL_NOTSAFEPOINT { uint64_t pool_count = jl_gc_debug_env.pool.num; uint64_t other_count = jl_gc_debug_env.other.num; @@ -510,7 +533,7 @@ void jl_gc_debug_print_status(void) pool_count + other_count, pool_count, other_count, gc_num.pause); } -void jl_gc_debug_critical_error(void) +void jl_gc_debug_critical_error(void) JL_NOTSAFEPOINT { jl_gc_debug_print_status(); if (!jl_gc_debug_env.wait_for_debugger) @@ -536,7 +559,7 @@ void gc_scrub_record_task(jl_task_t *t) arraylist_push(&jl_gc_debug_tasks, t); } -static void gc_scrub_range(char *low, char *high) +JL_NO_ASAN static void gc_scrub_range(char *low, char *high) { jl_ptls_t ptls = jl_current_task->ptls; jl_jmp_buf *old_buf = jl_get_safe_restore(); @@ -560,11 +583,11 @@ static void gc_scrub_range(char *low, char *high) // Find the age bit char *page_begin = gc_page_data(tag) + GC_PAGE_OFFSET; int obj_id = (((char*)tag) - page_begin) / osize; - uint8_t *ages = pg->ages + obj_id / 8; + uint32_t *ages = pg->ages + obj_id / 32; // Force this to be a young object to save some memory // (especially on 32bit where it's more likely to have pointer-like // bit patterns) - *ages &= ~(1 << (obj_id % 8)); + *ages &= ~(1 << (obj_id % 32)); memset(tag, 0xff, osize); // set mark to GC_MARKED (young and marked) tag->bits.gc = GC_MARKED; @@ -578,7 +601,7 @@ static void gc_scrub_task(jl_task_t *ta) jl_ptls_t ptls = jl_current_task->ptls; jl_ptls_t ptls2 = NULL; if (tid != -1) - ptls2 = jl_all_tls_states[tid]; + ptls2 = gc_all_tls_states[tid]; char *low; char *high; @@ -947,8 +970,8 @@ void gc_time_mark_pause(int64_t t0, int64_t scanned_bytes, { int64_t last_remset_len = 0; int64_t remset_nptr = 0; - for (int t_i = 0;t_i < jl_n_threads;t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; last_remset_len += ptls2->heap.last_remset->len; remset_nptr = ptls2->heap.remset_nptr; } @@ -973,10 +996,33 @@ void gc_time_sweep_pause(uint64_t gc_end_t, int64_t actual_allocd, "(%.2f ms in post_mark) %s | next in %" PRId64 " kB\n", jl_ns2ms(sweep_pause), live_bytes / 1024, gc_num.freed / 1024, estimate_freed / 1024, - gc_num.freed - estimate_freed, pct, gc_num.since_sweep / 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); } + +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) +{ + if (sweep_full > 0) + jl_safe_printf("TS: %" PRIu64 " Major collection: estimate freed = %" PRIu64 + " live = %" PRIu64 "m new interval = %" PRIu64 + "m time = %" PRIu64 "ms ttsp = %" PRIu64 "us mark time = %" + PRIu64 "ms sweep time = %" PRIu64 "ms \n", + end, freed, live/1024/1024, + interval/1024/1024, pause/1000000, ttsp, + mark/1000000,sweep/1000000); + else + 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", + end, freed, live/1024/1024, + interval/1024/1024, pause/1000000, ttsp, + mark/1000000,sweep/1000000); +} #endif void jl_gc_debug_init(void) @@ -1001,7 +1047,7 @@ void jl_gc_debug_init(void) #endif #ifdef OBJPROFILE - for (int g = 0;g < 3;g++) { + for (int g = 0; g < 3; g++) { htable_new(&obj_counts[g], 0); htable_new(&obj_sizes[g], 0); } @@ -1063,8 +1109,8 @@ void gc_stats_all_pool(void) { size_t nb=0, w, tw=0, no=0, tp=0, nold=0, noldbytes=0, np, nol; for (int i = 0; i < JL_GC_N_POOLS; i++) { - for (int t_i = 0; t_i < jl_n_threads; t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; size_t b = pool_stats(&ptls2->heap.norm_pools[i], &w, &np, &nol); nb += b; no += (b / ptls2->heap.norm_pools[i].osize); @@ -1088,8 +1134,8 @@ void gc_stats_all_pool(void) void gc_stats_big_obj(void) { size_t nused=0, nbytes=0, nused_old=0, nbytes_old=0; - for (int t_i = 0; t_i < jl_n_threads; t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; bigval_t *v = ptls2->heap.big_objects; while (v != NULL) { if (gc_marked(v->bits.gc)) { @@ -1197,7 +1243,7 @@ void gc_count_pool(void) empty_pages = 0; gc_count_pool_pagetable(); jl_safe_printf("****** Pool stat: ******\n"); - for (int i = 0;i < 4;i++) + for (int i = 0; i < 4; i++) jl_safe_printf("bits(%d): %" PRId64 "\n", i, poolobj_sizes[i]); // empty_pages is inaccurate after the sweep since young objects are // also GC_CLEAN @@ -1205,20 +1251,17 @@ void gc_count_pool(void) jl_safe_printf("************************\n"); } -int gc_slot_to_fieldidx(void *obj, void *slot) +int gc_slot_to_fieldidx(void *obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT { - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(obj); int nf = (int)jl_datatype_nfields(vt); - for (int i = 0; i < nf; i++) { - void *fieldaddr = (char*)obj + jl_field_offset(vt, i); - if (fieldaddr >= slot) { - return i; - } + for (int i = 1; i < nf; i++) { + if (slot < (void*)((char*)obj + jl_field_offset(vt, i))) + return i - 1; } - return -1; + return nf - 1; } -int gc_slot_to_arrayidx(void *obj, void *_slot) +int gc_slot_to_arrayidx(void *obj, void *_slot) JL_NOTSAFEPOINT { char *slot = (char*)_slot; jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(obj); @@ -1236,8 +1279,6 @@ int gc_slot_to_arrayidx(void *obj, void *_slot) } else if (vt->name == jl_array_typename) { jl_array_t *a = (jl_array_t*)obj; - if (!a->flags.ptrarray) - return -1; start = (char*)a->data; len = jl_array_len(a); elsize = a->elsize; @@ -1247,139 +1288,6 @@ int gc_slot_to_arrayidx(void *obj, void *_slot) return (slot - start) / elsize; } -// Print a backtrace from the bottom (start) of the mark stack up to `sp` -// `pc_offset` will be added to `sp` for convenience in the debugger. -NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_offset) -{ - jl_jmp_buf *old_buf = jl_get_safe_restore(); - jl_jmp_buf buf; - jl_set_safe_restore(&buf); - if (jl_setjmp(buf, 0) != 0) { - jl_safe_printf("\n!!! ERROR when unwinding gc mark loop -- ABORTING !!!\n"); - jl_set_safe_restore(old_buf); - return; - } - void **top = sp.pc + pc_offset; - jl_gc_mark_data_t *data_top = sp.data; - sp.data = ptls->gc_cache.data_stack; - sp.pc = ptls->gc_cache.pc_stack; - int isroot = 1; - while (sp.pc < top) { - void *pc = *sp.pc; - const char *prefix = isroot ? "r--" : " `-"; - isroot = 0; - if (pc == gc_mark_label_addrs[GC_MARK_L_marked_obj]) { - gc_mark_marked_obj_t *data = gc_repush_markdata(&sp, gc_mark_marked_obj_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: Root object: %p :: %p (bits: %d)\n of type ", - (void*)data, (void*)data->obj, (void*)data->tag, (int)data->bits); - jl_((void*)data->tag); - isroot = 1; - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_scan_only]) { - gc_mark_marked_obj_t *data = gc_repush_markdata(&sp, gc_mark_marked_obj_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: Queued root: %p :: %p (bits: %d)\n of type ", - (void*)data, (void*)data->obj, (void*)data->tag, (int)data->bits); - jl_((void*)data->tag); - isroot = 1; - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_finlist]) { - gc_mark_finlist_t *data = gc_repush_markdata(&sp, gc_mark_finlist_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: Finalizer list from %p to %p\n", - (void*)data, (void*)data->begin, (void*)data->end); - isroot = 1; - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_objarray]) { - gc_mark_objarray_t *data = gc_repush_markdata(&sp, gc_mark_objarray_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: %s Array in object %p :: %p -- [%p, %p)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (void*)data->begin, (void*)data->end); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_obj8]) { - gc_mark_obj8_t *data = gc_repush_markdata(&sp, gc_mark_obj8_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(data->parent); - uint8_t *desc = (uint8_t*)jl_dt_layout_ptrs(vt->layout); - jl_safe_printf("%p: %s Object (8bit) %p :: %p -- [%d, %d)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (int)(data->begin - desc), (int)(data->end - desc)); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_obj16]) { - gc_mark_obj16_t *data = gc_repush_markdata(&sp, gc_mark_obj16_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(data->parent); - uint16_t *desc = (uint16_t*)jl_dt_layout_ptrs(vt->layout); - jl_safe_printf("%p: %s Object (16bit) %p :: %p -- [%d, %d)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (int)(data->begin - desc), (int)(data->end - desc)); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_obj32]) { - gc_mark_obj32_t *data = gc_repush_markdata(&sp, gc_mark_obj32_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(data->parent); - uint32_t *desc = (uint32_t*)jl_dt_layout_ptrs(vt->layout); - jl_safe_printf("%p: %s Object (32bit) %p :: %p -- [%d, %d)\n of type ", - (void*)data, prefix, (void*)data->parent, ((void**)data->parent)[-1], - (int)(data->begin - desc), (int)(data->end - desc)); - jl_(jl_typeof(data->parent)); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_stack]) { - gc_mark_stackframe_t *data = gc_repush_markdata(&sp, gc_mark_stackframe_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: %s Stack frame %p -- %d of %d (%s)\n", - (void*)data, prefix, (void*)data->s, (int)data->i, - (int)data->nroots >> 1, - (data->nroots & 1) ? "indirect" : "direct"); - } - else if (pc == gc_mark_label_addrs[GC_MARK_L_module_binding]) { - // module_binding - gc_mark_binding_t *data = gc_repush_markdata(&sp, gc_mark_binding_t); - if ((jl_gc_mark_data_t *)data > data_top) { - jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); - break; - } - jl_safe_printf("%p: %s Module (bindings) %p (bits %d) -- [%p, %p)\n", - (void*)data, prefix, (void*)data->parent, (int)data->bits, - (void*)data->begin, (void*)data->end); - } - else { - jl_safe_printf("Unknown pc %p --- ABORTING !!!\n", pc); - break; - } - } - jl_set_safe_restore(old_buf); -} - static int gc_logging_enabled = 0; JL_DLLEXPORT void jl_enable_gc_logging(int enable) { diff --git a/src/gc-heap-snapshot.cpp b/src/gc-heap-snapshot.cpp new file mode 100644 index 0000000000000..b1401653d99ff --- /dev/null +++ b/src/gc-heap-snapshot.cpp @@ -0,0 +1,536 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include "gc-heap-snapshot.h" + +#include "julia_internal.h" +#include "gc.h" + +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/DenseMap.h" + +#include +#include +#include + +using std::vector; +using std::string; +using std::ostringstream; +using std::pair; +using std::make_pair; +using llvm::StringMap; +using llvm::DenseMap; +using llvm::StringRef; + +// https://stackoverflow.com/a/33799784/751061 +void print_str_escape_json(ios_t *stream, StringRef s) +{ + ios_putc('"', stream); + for (auto c = s.begin(); c != s.end(); c++) { + switch (*c) { + case '"': ios_write(stream, "\\\"", 2); break; + case '\\': ios_write(stream, "\\\\", 2); break; + case '\b': ios_write(stream, "\\b", 2); break; + case '\f': ios_write(stream, "\\f", 2); break; + case '\n': ios_write(stream, "\\n", 2); break; + case '\r': ios_write(stream, "\\r", 2); break; + case '\t': ios_write(stream, "\\t", 2); break; + default: + if (('\x00' <= *c) & (*c <= '\x1f')) { + ios_printf(stream, "\\u%04x", (int)*c); + } + else { + ios_putc(*c, stream); + } + } + } + ios_putc('"', stream); +} + + +// Edges +// "edge_fields": +// [ "type", "name_or_index", "to_node" ] +// 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. + size_t name_or_index; // name of the field (for objects/modules) or index of array + size_t to_node; +}; + +// Nodes +// "node_fields": +// [ "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 + 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 + // https://github.com/nodejs/node/blob/5fd7a72e1c4fbaf37d3723c4c81dce35c149dc84/deps/v8/include/v8-profiler.h#L739-L745 + int detachedness; // 0 - unknown, 1 - attached, 2 - detached + vector edges; + + ~Node() JL_NOTSAFEPOINT = default; +}; + +struct StringTable { + StringMap map; + vector strings; + + size_t find_or_create_string_id(StringRef key) JL_NOTSAFEPOINT { + auto val = map.insert(make_pair(key, map.size())); + if (val.second) + strings.push_back(val.first->first()); + return val.first->second; + } + + void print_json_array(ios_t *stream, bool newlines) { + ios_printf(stream, "["); + bool first = true; + for (const auto &str : strings) { + if (first) { + first = false; + } + else { + ios_printf(stream, newlines ? ",\n" : ","); + } + print_str_escape_json(stream, str); + } + ios_printf(stream, "]"); + } +}; + +struct HeapSnapshot { + vector nodes; + // edges are stored on each from_node + + StringTable names; + 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. +}; + +// global heap snapshot, mutated by garbage collector +// when snapshotting is on. +int gc_heap_snapshot_enabled = 0; +HeapSnapshot *g_snapshot = nullptr; +extern jl_mutex_t heapsnapshot_lock; + +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); + + +JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one) +{ + HeapSnapshot snapshot; + _add_internal_root(&snapshot); + + jl_mutex_lock(&heapsnapshot_lock); + + // Enable snapshotting + g_snapshot = &snapshot; + gc_heap_snapshot_enabled = true; + + // Do a full GC mark (and incremental sweep), which will invoke our callbacks on `g_snapshot` + jl_gc_collect(JL_GC_FULL); + + // Disable snapshotting + gc_heap_snapshot_enabled = false; + g_snapshot = nullptr; + + jl_mutex_unlock(&heapsnapshot_lock); + + // When we return, the snapshot is full + // Dump the snapshot + serialize_heap_snapshot((ios_t*)stream, snapshot, all_one); +} + +// 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) +{ + Node internal_root{ + snapshot->node_types.find_or_create_string_id("synthetic"), + snapshot->names.find_or_create_string_id(""), // name + 0, // id + 0, // size + 0, // size_t trace_node_id (unused) + 0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached + vector() // outgoing edges + }; + snapshot->nodes.push_back(internal_root); +} + +// 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())); + if (!val.second) { + return val.first->second; + } + + ios_t str_; + bool ios_need_close = 0; + + // Insert a new Node + size_t self_size = 0; + StringRef name = ""; + StringRef node_type = "object"; + + jl_datatype_t *type = (jl_datatype_t*)jl_typeof(a); + + if (jl_is_string(a)) { + node_type = "String"; + name = jl_string_data(a); + self_size = jl_string_len(a); + } + else if (jl_is_symbol(a)) { + node_type = "jl_sym_t"; + name = jl_symbol_name((jl_sym_t*)a); + self_size = name.size(); + } + else if (jl_is_simplevector(a)) { + node_type = "jl_svec_t"; + name = "SimpleVector"; + self_size = sizeof(jl_svec_t) + sizeof(void*) * jl_svec_len(a); + } + else if (jl_is_module(a)) { + node_type = "jl_module_t"; + name = jl_symbol_name_(((_jl_module_t*)a)->name); + self_size = sizeof(jl_module_t); + } + else if (jl_is_task(a)) { + node_type = "jl_task_t"; + name = "Task"; + self_size = sizeof(jl_task_t); + } + else if (jl_is_datatype(a)) { + ios_need_close = 1; + ios_mem(&str_, 0); + JL_STREAM* str = (JL_STREAM*)&str_; + jl_static_show(str, a); + name = StringRef((const char*)str_.buf, str_.size); + node_type = "jl_datatype_t"; + self_size = sizeof(jl_datatype_t); + } + else if (jl_is_array(a)){ + ios_need_close = 1; + ios_mem(&str_, 0); + JL_STREAM* str = (JL_STREAM*)&str_; + jl_static_show(str, (jl_value_t*)type); + name = StringRef((const char*)str_.buf, str_.size); + node_type = "jl_array_t"; + self_size = sizeof(jl_array_t); + } + else { + self_size = (size_t)jl_datatype_size(type); + // print full type into ios buffer and get StringRef to it. + // The ios is cleaned up below. + ios_need_close = 1; + ios_mem(&str_, 0); + JL_STREAM* str = (JL_STREAM*)&str_; + jl_static_show(str, (jl_value_t*)type); + + 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; + (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 + vector() // outgoing edges + }); + + if (ios_need_close) + ios_close(&str_); + + return val.first->second; +} + +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())); + 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; + (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 + vector() // outgoing edges + }); + + return val.first->second; +} + +static string _fieldpath_for_slot(void *obj, void *slot) JL_NOTSAFEPOINT +{ + string res; + jl_datatype_t *objtype = (jl_datatype_t*)jl_typeof(obj); + + while (1) { + int i = gc_slot_to_fieldidx(obj, slot, objtype); + + if (jl_is_tuple_type(objtype) || jl_is_namedtuple_type(objtype)) { + ostringstream ss; + ss << "[" << i << "]"; + res += ss.str(); + } + else { + jl_svec_t *field_names = jl_field_names(objtype); + jl_sym_t *name = (jl_sym_t*)jl_svecref(field_names, i); + res += jl_symbol_name(name); + } + + if (!jl_field_isptr(objtype, i)) { + // Tail recurse + res += "."; + obj = (void*)((char*)obj + jl_field_offset(objtype, i)); + objtype = (jl_datatype_t*)jl_field_type_concrete(objtype, i); + } + else { + return res; + } + } +} + + +void _gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL_NOTSAFEPOINT +{ + record_node_to_gc_snapshot(root); + + 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", internal_root, to_node_idx, edge_label); +} + +// Add a node to the heap snapshot representing a Julia stack frame. +// Each task points at a stack frame, which points at the stack frame of +// the function it's currently calling, forming a linked list. +// 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())); + 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 + (size_t)frame, // id + 1, // size + 0, // size_t trace_node_id (unused) + 0, // int detachedness; // 0 - unknown, 1 - attached; 2 - detached + vector() // outgoing edges + }); + + return val.first->second; +} + +void _gc_heap_snapshot_record_frame_to_object_edge(void *from, jl_value_t *to) JL_NOTSAFEPOINT +{ + 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); +} + +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); +} + +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); +} + +void _gc_heap_snapshot_record_array_edge(jl_value_t *from, jl_value_t *to, size_t index) JL_NOTSAFEPOINT +{ + _record_gc_edge("element", from, to, index); +} + +void _gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t *to, void *slot) JL_NOTSAFEPOINT +{ + string path = _fieldpath_for_slot(from, slot); + _record_gc_edge("property", from, to, + g_snapshot->names.find_or_create_string_id(path)); +} + +void _gc_heap_snapshot_record_module_to_binding(jl_module_t *module, jl_binding_t *binding) 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 &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")); +} + +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("")); +} + +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(""); + + auto from_node_idx = record_node_to_gc_snapshot(from); + const char *alloc_kind; + switch (alloc_type) + { + case 0: + alloc_kind = ""; + break; + case 1: + alloc_kind = ""; + break; + case 2: + alloc_kind = ""; + break; + default: + 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); +} + +static inline void _record_gc_edge(const char *edge_type, jl_value_t *a, + jl_value_t *b, size_t name_or_idx) JL_NOTSAFEPOINT +{ + 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); +} + +void _record_gc_just_edge(const char *edge_type, Node &from_node, 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), + name_or_idx, // edge label + to_idx // to + }); + + g_snapshot->num_edges += 1; +} + +void serialize_heap_snapshot(ios_t *stream, 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, "}"); +} diff --git a/src/gc-heap-snapshot.h b/src/gc-heap-snapshot.h new file mode 100644 index 0000000000000..8c3af5b86bec7 --- /dev/null +++ b/src/gc-heap-snapshot.h @@ -0,0 +1,108 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#ifndef JL_GC_HEAP_SNAPSHOT_H +#define JL_GC_HEAP_SNAPSHOT_H + +#include "julia.h" +#include "ios.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +// --------------------------------------------------------------------- +// Functions to call from GC when heap snapshot is enabled +// --------------------------------------------------------------------- +void _gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL_NOTSAFEPOINT; +void _gc_heap_snapshot_record_frame_to_object_edge(void *from, jl_value_t *to) JL_NOTSAFEPOINT; +void _gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_NOTSAFEPOINT; +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; +// 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). +void _gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT; +// 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; + + +extern int gc_heap_snapshot_enabled; +extern int prev_sweep_full; + +int gc_slot_to_fieldidx(void *_obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT; +int gc_slot_to_arrayidx(void *_obj, void *begin) JL_NOTSAFEPOINT; + +static inline void gc_heap_snapshot_record_frame_to_object_edge(void *from, jl_value_t *to) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_frame_to_object_edge(from, to); + } +} +static inline void gc_heap_snapshot_record_task_to_frame_edge(jl_task_t *from, void *to) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_task_to_frame_edge(from, to); + } +} +static inline void gc_heap_snapshot_record_frame_to_frame_edge(jl_gcframe_t *from, jl_gcframe_t *to) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_frame_to_frame_edge(from, to); + } +} +static inline void gc_heap_snapshot_record_root(jl_value_t *root, char *name) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_root(root, name); + } +} +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)) { + _gc_heap_snapshot_record_array_edge(from, *to, gc_slot_to_arrayidx(from, to)); + } +} +static inline void gc_heap_snapshot_record_object_edge(jl_value_t *from, jl_value_t **to) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_object_edge(from, *to, to); + } +} + +static inline void gc_heap_snapshot_record_module_to_binding(jl_module_t* module, jl_binding_t* binding) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_module_to_binding(module, binding); + } +} + +static inline void gc_heap_snapshot_record_internal_array_edge(jl_value_t *from, jl_value_t *to) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_internal_array_edge(from, to); + } +} + +static inline void gc_heap_snapshot_record_hidden_edge(jl_value_t *from, void* to, size_t bytes, uint16_t alloc_type) JL_NOTSAFEPOINT +{ + if (__unlikely(gc_heap_snapshot_enabled && prev_sweep_full)) { + _gc_heap_snapshot_record_hidden_edge(from, to, bytes, alloc_type); + } +} + +// --------------------------------------------------------------------- +// Functions to call from Julia to take heap snapshot +// --------------------------------------------------------------------- +JL_DLLEXPORT void jl_gc_take_heap_snapshot(ios_t *stream, char all_one); + + +#ifdef __cplusplus +} +#endif + + +#endif // JL_GC_HEAP_SNAPSHOT_H diff --git a/src/gc-pages.c b/src/gc-pages.c index a4ebe0315d71e..d579eb0cd4fbb 100644 --- a/src/gc-pages.c +++ b/src/gc-pages.c @@ -302,9 +302,24 @@ void jl_gc_free_page(void *p) JL_NOTSAFEPOINT } #ifdef _OS_WINDOWS_ VirtualFree(p, decommit_size, MEM_DECOMMIT); +#elif defined(MADV_FREE) + static int supports_madv_free = 1; + if (supports_madv_free) { + if (madvise(p, decommit_size, MADV_FREE) == -1) { + assert(errno == EINVAL); + supports_madv_free = 0; + } + } + if (!supports_madv_free) { + madvise(p, decommit_size, MADV_DONTNEED); + } #else madvise(p, decommit_size, MADV_DONTNEED); #endif + /* TODO: Should we leave this poisoned and rather allow the GC to read poisoned pointers from + * the page when it sweeps pools? + */ + msan_unpoison(p, decommit_size); no_decommit: // new pages are now available starting at max of lb and pagetable_i32 diff --git a/src/gc-stacks.c b/src/gc-stacks.c index b7adf254026ca..b35c1722c82ff 100644 --- a/src/gc-stacks.c +++ b/src/gc-stacks.c @@ -106,6 +106,9 @@ static unsigned select_pool(size_t nb) JL_NOTSAFEPOINT static void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz) { +#ifdef _COMPILER_ASAN_ENABLED_ + __asan_unpoison_stack_memory((uintptr_t)stkbuf, bufsz); +#endif if (bufsz <= pool_sizes[JL_N_STACK_POOLS - 1]) { unsigned pool_id = select_pool(bufsz); if (pool_sizes[pool_id] == bufsz) { @@ -135,6 +138,9 @@ void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task) unsigned pool_id = select_pool(bufsz); if (pool_sizes[pool_id] == bufsz) { task->stkbuf = NULL; +#ifdef _COMPILER_ASAN_ENABLED_ + __asan_unpoison_stack_memory((uintptr_t)stkbuf, bufsz); +#endif arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf); } } @@ -159,9 +165,11 @@ JL_DLLEXPORT void *jl_malloc_stack(size_t *bufsz, jl_task_t *owner) JL_NOTSAFEPO ssize = LLT_ALIGN(ssize, jl_page_size); } if (stk == NULL) { - if (jl_atomic_load_relaxed(&num_stack_mappings) >= MAX_STACK_MAPPINGS) + if (jl_atomic_load_relaxed(&num_stack_mappings) >= MAX_STACK_MAPPINGS) { // we accept that this can go over by as much as nthreads since it's not a CAS + errno = ENOMEM; return NULL; + } // TODO: allocate blocks of stacks? but need to mprotect individually anyways stk = malloc_stack(ssize); if (stk == MAP_FAILED) @@ -188,8 +196,9 @@ void sweep_stack_pools(void) // bufsz = t->bufsz // if (stkbuf) // push(free_stacks[sz], stkbuf) - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; + assert(gc_n_threads); + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; // free half of stacks that remain unused since last sweep for (int p = 0; p < JL_N_STACK_POOLS; p++) { diff --git a/src/gc.c b/src/gc.c index 609c2009bf103..4925ed91ea179 100644 --- a/src/gc.c +++ b/src/gc.c @@ -11,6 +11,16 @@ extern "C" { #endif +// Number of threads currently running the GC mark-loop +_Atomic(int) gc_n_threads_marking; +// `tid` of mutator thread that triggered GC +_Atomic(int) gc_master_tid; +// `tid` of first GC thread +int gc_first_tid; +// Mutex/cond used to synchronize sleep/wakeup of GC threads +uv_mutex_t gc_threads_lock; +uv_cond_t gc_threads_cond; + // Linked list of callback functions typedef void (*jl_gc_cb_func_t)(void); @@ -112,17 +122,6 @@ JL_DLLEXPORT void jl_gc_set_cb_notify_external_free(jl_gc_cb_notify_external_fre jl_gc_deregister_callback(&gc_cblist_notify_external_free, (jl_gc_cb_func_t)cb); } -// Save/restore local mark stack to/from thread-local storage. - -STATIC_INLINE void export_gc_state(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) { - ptls->gc_mark_sp = *sp; -} - -STATIC_INLINE void import_gc_state(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) { - // Has the stack been reallocated in the meantime? - *sp = ptls->gc_mark_sp; -} - // Protect all access to `finalizer_list_marked` and `to_finalize`. // For accessing `ptls->finalizers`, the lock is needed if a thread // is going to realloc the buffer (of its own list) or accessing the @@ -130,6 +129,9 @@ STATIC_INLINE void import_gc_state(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) { static jl_mutex_t finalizers_lock; static uv_mutex_t gc_cache_lock; +// mutex for gc-heap-snapshot. +jl_mutex_t heapsnapshot_lock; + // Flag that tells us whether we need to support conservative marking // of objects. static _Atomic(int) support_conservative_marking = 0; @@ -138,18 +140,18 @@ static _Atomic(int) support_conservative_marking = 0; * Note about GC synchronization: * * When entering `jl_gc_collect()`, `jl_gc_running` is atomically changed from - * `0` to `1` to make sure that only one thread can be running the GC. Other - * threads that enters `jl_gc_collect()` at the same time (or later calling + * `0` to `1` to make sure that only one thread can be running `_jl_gc_collect`. Other + * mutator threads that enters `jl_gc_collect()` at the same time (or later calling * from unmanaged code) will wait in `jl_gc_collect()` until the GC is finished. * - * Before starting the mark phase the GC thread calls `jl_safepoint_gc_start()` + * Before starting the mark phase the GC thread calls `jl_safepoint_start_gc()` * and `jl_gc_wait_for_the_world()` * to make sure all the thread are in a safe state for the GC. The function * activates the safepoint and wait for all the threads to get ready for the * GC (`gc_state != 0`). It also acquires the `finalizers` lock so that no * other thread will access them when the GC is running. * - * During the mark and sweep phase of the GC, the threads that are not running + * During the mark and sweep phase of the GC, the mutator threads that are not running * the GC should either be running unmanaged code (or code section that does * not have a GC critical region mainly including storing to the stack or * another object) or paused at a safepoint and wait for the GC to finish. @@ -168,21 +170,30 @@ static _Atomic(int) support_conservative_marking = 0; jl_gc_num_t gc_num = {0}; static size_t last_long_collect_interval; +int gc_n_threads; +jl_ptls_t* gc_all_tls_states; +const uint64_t _jl_buff_tag[3] = {0x4eadc0004eadc000ull, 0x4eadc0004eadc000ull, 0x4eadc0004eadc000ull}; // aka 0xHEADER00 +JL_DLLEXPORT uintptr_t jl_get_buff_tag(void) +{ + return jl_buff_tag; +} pagetable_t memory_map; // List of marked big objects. Not per-thread. Accessed only by master thread. bigval_t *big_objects_marked = NULL; -// finalization +// -- Finalization -- // `ptls->finalizers` and `finalizer_list_marked` might have tagged pointers. -// If an object pointer has the lowest bit set, the next pointer is an unboxed -// c function pointer. +// If an object pointer has the lowest bit set, the next pointer is an unboxed c function pointer. +// If an object pointer has the second lowest bit set, the current pointer is a c object pointer. +// It must be aligned at least 4, and it finalized immediately (at "quiescence"). // `to_finalize` should not have tagged pointers. arraylist_t finalizer_list_marked; arraylist_t to_finalize; JL_DLLEXPORT _Atomic(int) jl_gc_have_pending_finalizers = 0; + NOINLINE uintptr_t gc_get_stack_ptr(void) { return (uintptr_t)jl_get_frame_addr(); @@ -190,20 +201,28 @@ NOINLINE uintptr_t gc_get_stack_ptr(void) #define should_timeout() 0 -static void jl_gc_wait_for_the_world(void) +void jl_gc_wait_for_the_world(jl_ptls_t* gc_all_tls_states, int gc_n_threads) { - if (jl_n_threads > 1) + JL_TIMING(GC, GC_Stop); +#ifdef USE_TRACY + TracyCZoneCtx ctx = *(JL_TIMING_CURRENT_BLOCK->tracy_ctx); + TracyCZoneColor(ctx, 0x696969); +#endif + assert(gc_n_threads); + if (gc_n_threads > 1) jl_wake_libuv(); - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - // 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? + 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? + } } } @@ -267,17 +286,18 @@ static void schedule_finalization(void *o, void *f) JL_NOTSAFEPOINT jl_atomic_store_relaxed(&jl_gc_have_pending_finalizers, 1); } -static void run_finalizer(jl_task_t *ct, jl_value_t *o, jl_value_t *ff) +static void run_finalizer(jl_task_t *ct, void *o, void *ff) { - if (gc_ptr_tag(o, 1)) { - ((void (*)(void*))ff)(gc_ptr_clear_tag(o, 1)); + int ptr_finalizer = gc_ptr_tag(o, 1); + o = gc_ptr_clear_tag(o, 3); + if (ptr_finalizer) { + ((void (*)(void*))ff)((void*)o); return; } - jl_value_t *args[2] = {ff,o}; JL_TRY { size_t last_age = ct->world_age; ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - jl_apply(args, 2); + jl_apply_generic((jl_value_t*)ff, (jl_value_t**)&o, 1); ct->world_age = last_age; } JL_CATCH { @@ -342,7 +362,7 @@ static void finalize_object(arraylist_t *list, jl_value_t *o, // The first two entries are assumed to be empty and the rest are assumed to // be pointers to `jl_value_t` objects -static void jl_gc_push_arraylist(jl_task_t *ct, arraylist_t *list) +static void jl_gc_push_arraylist(jl_task_t *ct, arraylist_t *list) JL_NOTSAFEPOINT { void **items = list->items; items[0] = (void*)JL_GC_ENCODE_PUSHARGS(list->len - 2); @@ -353,13 +373,16 @@ static void jl_gc_push_arraylist(jl_task_t *ct, arraylist_t *list) // Same assumption as `jl_gc_push_arraylist`. Requires the finalizers lock // to be hold for the current thread and will release the lock when the // function returns. -static void jl_gc_run_finalizers_in_list(jl_task_t *ct, arraylist_t *list) +static void jl_gc_run_finalizers_in_list(jl_task_t *ct, arraylist_t *list) JL_NOTSAFEPOINT_LEAVE { + // Avoid marking `ct` as non-migratable via an `@async` task (as noted in the docstring + // of `finalizer`) in a finalizer: + uint8_t sticky = ct->sticky; // empty out the first two entries for the GC frame arraylist_push(list, list->items[0]); arraylist_push(list, list->items[1]); jl_gc_push_arraylist(ct, list); - jl_value_t **items = (jl_value_t**)list->items; + void **items = list->items; size_t len = list->len; JL_UNLOCK_NOGC(&finalizers_lock); // run finalizers in reverse order they were added, so lower-level finalizers run last @@ -369,6 +392,16 @@ static void jl_gc_run_finalizers_in_list(jl_task_t *ct, arraylist_t *list) run_finalizer(ct, items[len-2], items[len-1]); // matches the jl_gc_push_arraylist above JL_GC_POP(); + ct->sticky = sticky; +} + +static uint64_t finalizer_rngState[JL_RNG_SIZE]; + +void jl_rng_split(uint64_t dst[JL_RNG_SIZE], uint64_t src[JL_RNG_SIZE]) JL_NOTSAFEPOINT; + +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) @@ -392,9 +425,19 @@ static void run_finalizers(jl_task_t *ct) } jl_atomic_store_relaxed(&jl_gc_have_pending_finalizers, 0); arraylist_new(&to_finalize, 0); + + uint64_t save_rngState[JL_RNG_SIZE]; + memcpy(&save_rngState[0], &ct->rngState[0], sizeof(save_rngState)); + jl_rng_split(ct->rngState, finalizer_rngState); + // This releases the finalizers lock. + int8_t was_in_finalizer = ct->ptls->in_finalizer; + ct->ptls->in_finalizer = 1; jl_gc_run_finalizers_in_list(ct, &copied_list); + ct->ptls->in_finalizer = was_in_finalizer; arraylist_free(&copied_list); + + memcpy(&ct->rngState[0], &save_rngState[0], sizeof(save_rngState)); } JL_DLLEXPORT void jl_gc_run_pending_finalizers(jl_task_t *ct) @@ -403,9 +446,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) { - ptls->in_finalizer = 1; run_finalizers(ct); - ptls->in_finalizer = 0; } } @@ -460,6 +501,11 @@ JL_DLLEXPORT void jl_gc_enable_finalizers(jl_task_t *ct, int on) } } +JL_DLLEXPORT int8_t jl_gc_is_in_finalizer(void) +{ + return jl_current_task->ptls->in_finalizer; +} + static void schedule_all_finalizers(arraylist_t *flist) JL_NOTSAFEPOINT { void **items = flist->items; @@ -476,15 +522,22 @@ static void schedule_all_finalizers(arraylist_t *flist) JL_NOTSAFEPOINT void jl_gc_run_all_finalizers(jl_task_t *ct) { + int gc_n_threads; + jl_ptls_t* gc_all_tls_states; + gc_n_threads = jl_atomic_load_acquire(&jl_n_threads); + gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); schedule_all_finalizers(&finalizer_list_marked); - for (int i = 0;i < jl_n_threads;i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - schedule_all_finalizers(&ptls2->finalizers); + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 != NULL) + schedule_all_finalizers(&ptls2->finalizers); } + gc_n_threads = 0; + gc_all_tls_states = NULL; run_finalizers(ct); } -static void gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT +void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT { assert(jl_atomic_load_relaxed(&ptls->gc_state) == 0); arraylist_t *a = &ptls->finalizers; @@ -514,16 +567,23 @@ static void gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT JL_DLLEXPORT void jl_gc_add_ptr_finalizer(jl_ptls_t ptls, jl_value_t *v, void *f) JL_NOTSAFEPOINT { - gc_add_finalizer_(ptls, (void*)(((uintptr_t)v) | 1), f); + jl_gc_add_finalizer_(ptls, (void*)(((uintptr_t)v) | 1), f); +} + +// schedule f(v) to call at the next quiescent interval (aka after the next safepoint/region on all threads) +JL_DLLEXPORT void jl_gc_add_quiescent(jl_ptls_t ptls, void **v, void *f) JL_NOTSAFEPOINT +{ + assert(!gc_ptr_tag(v, 3)); + jl_gc_add_finalizer_(ptls, (void*)(((uintptr_t)v) | 3), f); } JL_DLLEXPORT void jl_gc_add_finalizer_th(jl_ptls_t ptls, jl_value_t *v, jl_function_t *f) JL_NOTSAFEPOINT { - if (__unlikely(jl_typeis(f, jl_voidpointer_type))) { + if (__unlikely(jl_typetagis(f, jl_voidpointer_type))) { jl_gc_add_ptr_finalizer(ptls, v, jl_unbox_voidpointer(f)); } else { - gc_add_finalizer_(ptls, v, f); + jl_gc_add_finalizer_(ptls, v, f); } } @@ -537,11 +597,18 @@ JL_DLLEXPORT void jl_finalize_th(jl_task_t *ct, jl_value_t *o) arraylist_new(&copied_list, 0); // No need to check the to_finalize list since the user is apparently // still holding a reference to the object - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - finalize_object(&ptls2->finalizers, o, &copied_list, jl_atomic_load_relaxed(&ct->tid) != i); + int gc_n_threads; + jl_ptls_t* gc_all_tls_states; + gc_n_threads = jl_atomic_load_acquire(&jl_n_threads); + gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 != NULL) + 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); @@ -552,41 +619,55 @@ JL_DLLEXPORT void jl_finalize_th(jl_task_t *ct, jl_value_t *o) arraylist_free(&copied_list); } +// explicitly scheduled objects for the sweepfunc callback static void gc_sweep_foreign_objs_in_list(arraylist_t *objs) { size_t p = 0; for (size_t i = 0; i < objs->len; i++) { - jl_value_t *v = (jl_value_t *)(objs->items[i]); - jl_datatype_t *t = (jl_datatype_t *)(jl_typeof(v)); + jl_value_t *v = (jl_value_t*)(objs->items[i]); + jl_datatype_t *t = (jl_datatype_t*)(jl_typeof(v)); const jl_datatype_layout_t *layout = t->layout; jl_fielddescdyn_t *desc = (jl_fielddescdyn_t*)jl_dt_layout_fields(layout); - if (!gc_ptr_tag(v, 1)) { + + int bits = jl_astaggedvalue(v)->bits.gc; + if (!gc_marked(bits)) desc->sweepfunc(v); - } - else { + else objs->items[p++] = v; - } } objs->len = p; } static void gc_sweep_foreign_objs(void) { - for (int i = 0;i < jl_n_threads; i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - gc_sweep_foreign_objs_in_list(&ptls2->sweep_objs); + 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) + gc_sweep_foreign_objs_in_list(&ptls2->sweep_objs); } } // GC knobs and self-measurement variables static int64_t last_gc_total_bytes = 0; +// max_total_memory is a suggestion. We try very hard to stay +// under this limit, but we will go above it rather than halting. #ifdef _P64 -#define default_collect_interval (5600*1024*sizeof(void*)) -static size_t max_collect_interval = 1250000000UL; +typedef uint64_t memsize_t; +static const size_t default_collect_interval = 5600 * 1024 * sizeof(void*); +static const size_t max_collect_interval = 1250000000UL; +static size_t total_mem; +// We expose this to the user/ci as jl_gc_set_max_memory +static memsize_t max_total_memory = (memsize_t) 2 * 1024 * 1024 * 1024 * 1024 * 1024; #else -#define default_collect_interval (3200*1024*sizeof(void*)) -static size_t max_collect_interval = 500000000UL; +typedef uint32_t memsize_t; +static const size_t default_collect_interval = 3200 * 1024 * sizeof(void*); +static const size_t max_collect_interval = 500000000UL; +// Work really hard to stay within 2GB +// Alternative is to risk running out of address space +// on 32 bit architectures. +static memsize_t max_total_memory = (memsize_t) 2 * 1024 * 1024 * 1024; #endif // global variables for GC stats @@ -646,16 +727,15 @@ static int mark_reset_age = 0; static int64_t scanned_bytes; // young bytes scanned while marking static int64_t perm_scanned_bytes; // old bytes scanned while marking -static int prev_sweep_full = 1; +int prev_sweep_full = 1; #define inc_sat(v,s) v = (v) >= s ? s : (v)+1 // Full collection heuristics static int64_t live_bytes = 0; static int64_t promoted_bytes = 0; -static int64_t last_full_live = 0; // live_bytes after last full collection static int64_t last_live_bytes = 0; // live_bytes at last collection -static int64_t grown_heap_age = 0; // # of collects since live_bytes grew and remained +static int64_t t_start = 0; // Time GC starts; #ifdef __GLIBC__ // maxrss at last malloc_trim static int64_t last_trim_maxrss = 0; @@ -693,9 +773,11 @@ static void gc_sync_cache(jl_ptls_t ptls) JL_NOTSAFEPOINT // No other threads can be running marking at the same time static void gc_sync_all_caches_nolock(jl_ptls_t ptls) { - for (int t_i = 0; t_i < jl_n_threads; t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; - gc_sync_cache_nolock(ptls, &ptls2->gc_cache); + 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]; + if (ptls2 != NULL) + gc_sync_cache_nolock(ptls, &ptls2->gc_cache); } } @@ -713,21 +795,13 @@ STATIC_INLINE void gc_queue_big_marked(jl_ptls_t ptls, bigval_t *hdr, ptls->gc_cache.nbig_obj = nobj + 1; } -// `gc_setmark_tag` can be called concurrently on multiple threads. -// In all cases, the function atomically sets the mark bits and returns -// the GC bits set as well as if the tag was unchanged by this thread. -// All concurrent calls on the same object are guaranteed to be setting the -// bits to the same value. -// For normal objects, this is the bits with only `GC_MARKED` changed to `1` -// For buffers, this is the bits of the owner object. -// For `mark_reset_age`, this is `GC_MARKED` with `GC_OLD` cleared. -// The return value is `1` if the object was not marked before. -// Returning `0` can happen if another thread marked it in parallel. -STATIC_INLINE int gc_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode, - uintptr_t tag, uint8_t *bits) JL_NOTSAFEPOINT -{ - assert(!gc_marked(tag)); +// Atomically set the mark bit for object and return whether it was previously unmarked +FORCE_INLINE int gc_try_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode) JL_NOTSAFEPOINT +{ assert(gc_marked(mark_mode)); + uintptr_t tag = o->header; + if (gc_marked(tag)) + return 0; if (mark_reset_age) { // Reset the object as if it was just allocated mark_mode = GC_MARKED; @@ -739,7 +813,6 @@ STATIC_INLINE int gc_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode, tag = tag | mark_mode; assert((tag & 0x3) == mark_mode); } - *bits = mark_mode; tag = jl_atomic_exchange_relaxed((_Atomic(uintptr_t)*)&o->header, tag); verify_val(jl_valueof(o)); return !gc_marked(tag); @@ -792,8 +865,8 @@ STATIC_INLINE void gc_setmark_pool_(jl_ptls_t ptls, jl_taggedvalue_t *o, page->has_young = 1; char *page_begin = gc_page_data(o) + GC_PAGE_OFFSET; int obj_id = (((char*)o) - page_begin) / page->osize; - uint8_t *ages = page->ages + obj_id / 8; - jl_atomic_fetch_and_relaxed((_Atomic(uint8_t)*)ages, ~(1 << (obj_id % 8))); + uint32_t *ages = page->ages + obj_id / 32; + jl_atomic_fetch_and_relaxed((_Atomic(uint32_t)*)ages, ~(1 << (obj_id % 32))); } } objprofile_count(jl_typeof(jl_valueof(o)), @@ -822,15 +895,12 @@ STATIC_INLINE void gc_setmark(jl_ptls_t ptls, jl_taggedvalue_t *o, STATIC_INLINE void gc_setmark_buf_(jl_ptls_t ptls, void *o, uint8_t mark_mode, size_t minsz) JL_NOTSAFEPOINT { jl_taggedvalue_t *buf = jl_astaggedvalue(o); - uintptr_t tag = buf->header; - if (gc_marked(tag)) - return; - uint8_t bits; + uint8_t bits = (gc_old(buf->header) && !mark_reset_age) ? GC_OLD_MARKED : GC_MARKED;; // If the object is larger than the max pool size it can't be a pool object. // This should be accurate most of the time but there might be corner cases // where the size estimate is a little off so we do a pool lookup to make // sure. - if (__likely(gc_setmark_tag(buf, mark_mode, tag, &bits)) && !gc_verifying) { + if (__likely(gc_try_setmark_tag(buf, mark_mode)) && !gc_verifying) { if (minsz <= GC_MAX_SZCLASS) { jl_gc_pagemeta_t *page = page_metadata(buf); if (page) { @@ -878,7 +948,7 @@ void jl_gc_force_mark_old(jl_ptls_t ptls, jl_value_t *v) JL_NOTSAFEPOINT jl_gc_queue_root(v); } -static inline void maybe_collect(jl_ptls_t ptls) +STATIC_INLINE void maybe_collect(jl_ptls_t ptls) { if (jl_atomic_load_relaxed(&ptls->gc_num.allocd) >= 0 || jl_gc_debug_check_other()) { jl_gc_collect(JL_GC_AUTO); @@ -902,41 +972,47 @@ JL_DLLEXPORT jl_weakref_t *jl_gc_new_weakref_th(jl_ptls_t ptls, static void clear_weak_refs(void) { - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - size_t n, l = ptls2->heap.weak_refs.len; - void **lst = ptls2->heap.weak_refs.items; - for (n = 0; n < l; n++) { - jl_weakref_t *wr = (jl_weakref_t*)lst[n]; - if (!gc_marked(jl_astaggedvalue(wr->value)->bits.gc)) - wr->value = (jl_value_t*)jl_nothing; + 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) { + size_t n, l = ptls2->heap.weak_refs.len; + void **lst = ptls2->heap.weak_refs.items; + for (n = 0; n < l; n++) { + jl_weakref_t *wr = (jl_weakref_t*)lst[n]; + if (!gc_marked(jl_astaggedvalue(wr->value)->bits.gc)) + wr->value = (jl_value_t*)jl_nothing; + } } } } static void sweep_weak_refs(void) { - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - size_t n = 0; - size_t ndel = 0; - size_t l = ptls2->heap.weak_refs.len; - void **lst = ptls2->heap.weak_refs.items; - if (l == 0) - continue; - while (1) { - jl_weakref_t *wr = (jl_weakref_t*)lst[n]; - if (gc_marked(jl_astaggedvalue(wr)->bits.gc)) - n++; - else - ndel++; - if (n >= l - ndel) - break; - void *tmp = lst[n]; - lst[n] = lst[n + ndel]; - lst[n + ndel] = tmp; + 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) { + size_t n = 0; + size_t ndel = 0; + size_t l = ptls2->heap.weak_refs.len; + void **lst = ptls2->heap.weak_refs.items; + if (l == 0) + continue; + while (1) { + jl_weakref_t *wr = (jl_weakref_t*)lst[n]; + if (gc_marked(jl_astaggedvalue(wr)->bits.gc)) + n++; + else + ndel++; + if (n >= l - ndel) + break; + void *tmp = lst[n]; + lst[n] = lst[n + ndel]; + lst[n + ndel] = tmp; + } + ptls2->heap.weak_refs.len -= ndel; } - ptls2->heap.weak_refs.len -= ndel; } } @@ -944,7 +1020,7 @@ static void sweep_weak_refs(void) // big value list // Size includes the tag and the tag is not cleared!! -static inline jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) +STATIC_INLINE jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) { maybe_collect(ptls); size_t offs = offsetof(bigval_t, header); @@ -976,7 +1052,6 @@ static inline jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) JL_DLLEXPORT jl_value_t *jl_gc_big_alloc(jl_ptls_t ptls, size_t sz) { jl_value_t *val = jl_gc_big_alloc_inner(ptls, sz); - maybe_record_alloc_to_profile(val, sz, jl_gc_unknown_type_tag); return val; } @@ -1034,11 +1109,15 @@ static bigval_t **sweep_big_list(int sweep_full, bigval_t **pv) JL_NOTSAFEPOINT static void sweep_big(jl_ptls_t ptls, int sweep_full) JL_NOTSAFEPOINT { gc_time_big_start(); - for (int i = 0;i < jl_n_threads;i++) - sweep_big_list(sweep_full, &jl_all_tls_states[i]->heap.big_objects); + 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) + sweep_big_list(sweep_full, &ptls2->heap.big_objects); + } if (sweep_full) { bigval_t **last_next = sweep_big_list(sweep_full, &big_objects_marked); - // Move all survivors from big_objects_marked list to big_objects list. + // Move all survivors from big_objects_marked list to the big_objects list of this thread. if (ptls->heap.big_objects) ptls->heap.big_objects->prev = last_next; *last_next = ptls->heap.big_objects; @@ -1077,8 +1156,12 @@ void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT { - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls = jl_all_tls_states[i]; + int gc_n_threads; + jl_ptls_t* gc_all_tls_states; + gc_n_threads = jl_atomic_load_acquire(&jl_n_threads); + gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls = gc_all_tls_states[i]; if (ptls) { dest->allocd += (jl_atomic_load_relaxed(&ptls->gc_num.allocd) + gc_num.interval); dest->freed += jl_atomic_load_relaxed(&ptls->gc_num.freed); @@ -1093,9 +1176,13 @@ static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT static void reset_thread_gc_counts(void) JL_NOTSAFEPOINT { - for (int i = 0; i < jl_n_threads; i++) { - jl_ptls_t ptls = jl_all_tls_states[i]; - if (ptls) { + int gc_n_threads; + jl_ptls_t* gc_all_tls_states; + gc_n_threads = jl_atomic_load_acquire(&jl_n_threads); + gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); + 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)); jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); } @@ -1141,36 +1228,38 @@ static void jl_gc_free_array(jl_array_t *a) JL_NOTSAFEPOINT static void sweep_malloced_arrays(void) JL_NOTSAFEPOINT { gc_time_mallocd_array_start(); - for (int t_i = 0;t_i < jl_n_threads;t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; - mallocarray_t *ma = ptls2->heap.mallocarrays; - mallocarray_t **pma = &ptls2->heap.mallocarrays; - while (ma != NULL) { - mallocarray_t *nxt = ma->next; - int bits = jl_astaggedvalue(ma->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); - ma->next = ptls2->heap.mafreelist; - ptls2->heap.mafreelist = ma; + 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]; + if (ptls2 != NULL) { + mallocarray_t *ma = ptls2->heap.mallocarrays; + mallocarray_t **pma = &ptls2->heap.mallocarrays; + while (ma != NULL) { + mallocarray_t *nxt = ma->next; + int bits = jl_astaggedvalue(ma->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); + ma->next = ptls2->heap.mafreelist; + ptls2->heap.mafreelist = ma; + } + gc_time_count_mallocd_array(bits); + ma = nxt; } - gc_time_count_mallocd_array(bits); - ma = nxt; } } gc_time_mallocd_array_end(); } // pool allocation -static inline jl_taggedvalue_t *reset_page(const jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_taggedvalue_t *fl) JL_NOTSAFEPOINT +STATIC_INLINE jl_taggedvalue_t *reset_page(jl_ptls_t ptls2, const jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_taggedvalue_t *fl) JL_NOTSAFEPOINT { assert(GC_PAGE_OFFSET >= sizeof(void*)); pg->nfree = (GC_PAGE_SZ - GC_PAGE_OFFSET) / p->osize; - jl_ptls_t ptls2 = jl_all_tls_states[pg->thread_n]; pg->pool_n = p - ptls2->heap.norm_pools; memset(pg->ages, 0, GC_PAGE_SZ / 8 / p->osize + 1); jl_taggedvalue_t *beg = (jl_taggedvalue_t*)(pg->data + GC_PAGE_OFFSET); @@ -1206,15 +1295,15 @@ static NOINLINE jl_taggedvalue_t *add_page(jl_gc_pool_t *p) JL_NOTSAFEPOINT jl_ptls_t ptls = jl_current_task->ptls; jl_gc_pagemeta_t *pg = jl_gc_alloc_page(); pg->osize = p->osize; - pg->ages = (uint8_t*)malloc_s(GC_PAGE_SZ / 8 / p->osize + 1); + pg->ages = (uint32_t*)malloc_s(LLT_ALIGN(GC_PAGE_SZ / 8 / p->osize + 1, sizeof(uint32_t))); pg->thread_n = ptls->tid; - jl_taggedvalue_t *fl = reset_page(p, pg, NULL); + jl_taggedvalue_t *fl = reset_page(ptls, p, pg, NULL); p->newpages = fl; return fl; } // Size includes the tag and the tag is not cleared!! -static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset, +STATIC_INLINE jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset, int osize) { // Use the pool offset instead of the pool address as the argument @@ -1232,7 +1321,7 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset jl_atomic_load_relaxed(&ptls->gc_num.poolalloc) + 1); // first try to use the freelist jl_taggedvalue_t *v = p->freelist; - if (v) { + if (v != NULL) { jl_taggedvalue_t *next = v->next; p->freelist = next; if (__unlikely(gc_page_data(v) != gc_page_data(next))) { @@ -1243,6 +1332,7 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset pg->nfree = 0; pg->has_young = 1; } + msan_allocated_memory(v, osize); return jl_valueof(v); } // if the freelist is empty we reuse empty but not freed pages @@ -1251,8 +1341,8 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset // If there's no pages left or the current page is used up, // we need to use the slow path. char *cur_page = gc_page_data((char*)v - 1); - if (__unlikely(!v || cur_page + GC_PAGE_SZ < (char*)next)) { - if (v) { + if (__unlikely(v == NULL || cur_page + GC_PAGE_SZ < (char*)next)) { + if (v != NULL) { // like the freelist case, // but only update the page metadata when it is full jl_gc_pagemeta_t *pg = jl_assume(page_metadata((char*)v - 1)); @@ -1262,11 +1352,12 @@ static inline jl_value_t *jl_gc_pool_alloc_inner(jl_ptls_t ptls, int pool_offset v = *(jl_taggedvalue_t**)cur_page; } // Not an else!! - if (!v) + if (v == NULL) v = add_page(p); next = (jl_taggedvalue_t*)((char*)v + osize); } p->newpages = next; + msan_allocated_memory(v, osize); return jl_valueof(v); } @@ -1275,7 +1366,6 @@ JL_DLLEXPORT jl_value_t *jl_gc_pool_alloc(jl_ptls_t ptls, int pool_offset, int osize) { jl_value_t *val = jl_gc_pool_alloc_inner(ptls, pool_offset, osize); - maybe_record_alloc_to_profile(val, osize, jl_gc_unknown_type_tag); return val; } @@ -1305,7 +1395,7 @@ int64_t lazy_freed_pages = 0; static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_taggedvalue_t **pfl, int sweep_full, int osize) JL_NOTSAFEPOINT { char *data = pg->data; - uint8_t *ages = pg->ages; + uint32_t *ages = pg->ages; jl_taggedvalue_t *v = (jl_taggedvalue_t*)(data + GC_PAGE_OFFSET); char *lim = (char*)v + GC_PAGE_SZ - GC_PAGE_OFFSET - osize; size_t old_nfree = pg->nfree; @@ -1320,7 +1410,8 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t // FIXME - need to do accounting on a per-thread basis // on quick sweeps, keep a few pages empty but allocated for performance if (!sweep_full && lazy_freed_pages <= default_collect_interval / GC_PAGE_SZ) { - jl_taggedvalue_t *begin = reset_page(p, pg, p->newpages); + jl_ptls_t ptls2 = gc_all_tls_states[pg->thread_n]; + jl_taggedvalue_t *begin = reset_page(ptls2, p, pg, p->newpages); p->newpages = begin; begin->next = (jl_taggedvalue_t*)0; lazy_freed_pages++; @@ -1355,20 +1446,29 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t int16_t prev_nold = 0; int pg_nfree = 0; jl_taggedvalue_t **pfl_begin = NULL; - uint8_t msk = 1; // mask for the age bit in the current age byte + uint32_t msk = 1; // mask for the age bit in the current age byte + uint32_t age = *ages; while ((char*)v <= lim) { + if (!msk) { + msk = 1; + *ages = age; + ages++; + age = *ages; + } int bits = v->bits.gc; if (!gc_marked(bits)) { *pfl = v; pfl = &v->next; pfl_begin = pfl_begin ? pfl_begin : pfl; pg_nfree++; - *ages &= ~msk; + age &= ~msk; } else { // marked young or old - if (*ages & msk || bits == GC_OLD_MARKED) { // old enough + if (age & msk || bits == GC_OLD_MARKED) { // old enough // `!age && bits == GC_OLD_MARKED` is possible for - // non-first-class objects like `jl_binding_t` + // non-first-class objects like array buffers + // (they may get promoted by jl_gc_wb_buf for example, + // or explicitly by jl_gc_force_mark_old) if (sweep_full || bits == GC_MARKED) { bits = v->bits.gc = GC_OLD; // promote } @@ -1380,17 +1480,13 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t has_young = 1; } has_marked |= gc_marked(bits); - *ages |= msk; + age |= msk; freedall = 0; } v = (jl_taggedvalue_t*)((char*)v + osize); msk <<= 1; - if (!msk) { - msk = 1; - ages++; - } } - + *ages = age; assert(!freedall); pg->has_marked = has_marked; pg->has_young = has_young; @@ -1418,18 +1514,18 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t } // the actual sweeping over all allocated pages in a memory pool -static inline void sweep_pool_page(jl_taggedvalue_t ***pfl, jl_gc_pagemeta_t *pg, int sweep_full) JL_NOTSAFEPOINT +STATIC_INLINE void sweep_pool_page(jl_taggedvalue_t ***pfl, jl_gc_pagemeta_t *pg, int sweep_full) JL_NOTSAFEPOINT { int p_n = pg->pool_n; int t_n = pg->thread_n; - jl_ptls_t ptls2 = jl_all_tls_states[t_n]; + 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; pfl[t_n * JL_GC_N_POOLS + p_n] = sweep_page(p, pg, pfl[t_n * JL_GC_N_POOLS + p_n], sweep_full, osize); } // sweep over a pagetable0 for all allocated pages -static inline int sweep_pool_pagetable0(jl_taggedvalue_t ***pfl, pagetable0_t *pagetable0, int sweep_full) JL_NOTSAFEPOINT +STATIC_INLINE int sweep_pool_pagetable0(jl_taggedvalue_t ***pfl, pagetable0_t *pagetable0, int sweep_full) JL_NOTSAFEPOINT { unsigned ub = 0; unsigned alloc = 0; @@ -1453,7 +1549,7 @@ static inline int sweep_pool_pagetable0(jl_taggedvalue_t ***pfl, pagetable0_t *p } // sweep over pagetable1 for all pagetable0 that may contain allocated pages -static inline int sweep_pool_pagetable1(jl_taggedvalue_t ***pfl, pagetable1_t *pagetable1, int sweep_full) JL_NOTSAFEPOINT +STATIC_INLINE int sweep_pool_pagetable1(jl_taggedvalue_t ***pfl, pagetable1_t *pagetable1, int sweep_full) JL_NOTSAFEPOINT { unsigned ub = 0; unsigned alloc = 0; @@ -1482,7 +1578,7 @@ static void sweep_pool_pagetable(jl_taggedvalue_t ***pfl, int sweep_full) JL_NOT { if (REGION2_PG_COUNT == 1) { // compile-time optimization pagetable1_t *pagetable1 = memory_map.meta1[0]; - if (pagetable1) + if (pagetable1 != NULL) sweep_pool_pagetable1(pfl, pagetable1, sweep_full); return; } @@ -1534,9 +1630,9 @@ static void gc_sweep_pool(int sweep_full) gc_time_pool_start(); lazy_freed_pages = 0; - // For the benfit of the analyzer, which doesn't know that jl_n_threads + // For the benefit of the analyzer, which doesn't know that gc_n_threads // doesn't change over the course of this function - size_t n_threads = jl_n_threads; + size_t n_threads = gc_n_threads; // allocate enough space to hold the end of the free list chain // for every thread and pool size @@ -1545,7 +1641,13 @@ static void gc_sweep_pool(int sweep_full) // update metadata of pages that were pointed to by freelist or newpages from a pool // i.e. pages being the current allocation target for (int t_i = 0; t_i < n_threads; t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; + if (ptls2 == NULL) { + for (int i = 0; i < JL_GC_N_POOLS; i++) { + pfl[t_i * JL_GC_N_POOLS + i] = NULL; + } + continue; + } 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; @@ -1574,8 +1676,11 @@ static void gc_sweep_pool(int sweep_full) // null out terminal pointers of free lists for (int t_i = 0; t_i < n_threads; t_i++) { - for (int i = 0; i < JL_GC_N_POOLS; i++) { - *pfl[t_i * JL_GC_N_POOLS + i] = NULL; + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; + if (ptls2 != NULL) { + for (int i = 0; i < JL_GC_N_POOLS; i++) { + *pfl[t_i * JL_GC_N_POOLS + i] = NULL; + } } } @@ -1643,31 +1748,15 @@ void jl_gc_queue_multiroot(const jl_value_t *parent, const jl_value_t *ptr) JL_N } } -void gc_queue_binding(jl_binding_t *bnd) -{ - jl_ptls_t ptls = jl_current_task->ptls; - jl_taggedvalue_t *buf = jl_astaggedvalue(bnd); - buf->bits.gc = GC_MARKED; - arraylist_push(&ptls->heap.rem_bindings, bnd); -} - #ifdef JL_DEBUG_BUILD static void *volatile gc_findval; // for usage from gdb, for finding the gc-root for a value #endif -static void *sysimg_base; -static void *sysimg_end; -void jl_gc_set_permalloc_region(void *start, void *end) -{ - sysimg_base = start; - sysimg_end = end; -} - // Handle the case where the stack is only partially copied. STATIC_INLINE uintptr_t gc_get_stack_addr(void *_addr, uintptr_t offset, - uintptr_t lb, uintptr_t ub) + uintptr_t lb, uintptr_t ub) JL_NOTSAFEPOINT { uintptr_t addr = (uintptr_t)_addr; if (addr >= lb && addr < ub) @@ -1676,904 +1765,796 @@ STATIC_INLINE uintptr_t gc_get_stack_addr(void *_addr, uintptr_t offset, } STATIC_INLINE uintptr_t gc_read_stack(void *_addr, uintptr_t offset, - uintptr_t lb, uintptr_t ub) + uintptr_t lb, uintptr_t ub) JL_NOTSAFEPOINT { uintptr_t real_addr = gc_get_stack_addr(_addr, offset, lb, ub); return *(uintptr_t*)real_addr; } JL_NORETURN NOINLINE void gc_assert_datatype_fail(jl_ptls_t ptls, jl_datatype_t *vt, - jl_gc_mark_sp_t sp) + jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { jl_safe_printf("GC error (probable corruption) :\n"); jl_gc_debug_print_status(); jl_(vt); jl_gc_debug_critical_error(); - gc_mark_loop_unwind(ptls, sp, 0); abort(); } -// This stores the label address in the mark loop function. -// We can't directly store that to a global array so we need some hack to get that. -// See the call to `gc_mark_loop` in init with a `NULL` `ptls`. -void *gc_mark_label_addrs[_GC_MARK_L_MAX]; - -// Double the local mark stack (both pc and data) -static void NOINLINE gc_mark_stack_resize(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) JL_NOTSAFEPOINT -{ - jl_gc_mark_data_t *old_data = gc_cache->data_stack; - void **pc_stack = sp->pc_start; - size_t stack_size = (char*)sp->pc_end - (char*)pc_stack; - gc_cache->data_stack = (jl_gc_mark_data_t *)realloc_s(old_data, stack_size * 2 * sizeof(jl_gc_mark_data_t)); - sp->data = (jl_gc_mark_data_t *)(((char*)sp->data) + (((char*)gc_cache->data_stack) - ((char*)old_data))); - - sp->pc_start = gc_cache->pc_stack = (void**)realloc_s(pc_stack, stack_size * 2 * sizeof(void*)); - gc_cache->pc_stack_end = sp->pc_end = sp->pc_start + stack_size * 2; - sp->pc = sp->pc_start + (sp->pc - pc_stack); -} - -// Push a work item to the stack. The type of the work item is marked with `pc`. -// The data needed is in `data` and is of size `data_size`. -// If there isn't enough space on the stack, the stack will be resized with the stack -// lock held. The caller should invalidate any local cache of the stack addresses that's not -// in `gc_cache` or `sp` -// The `sp` will be updated on return if `inc` is true. -STATIC_INLINE void gc_mark_stack_push(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - void *pc, void *data, size_t data_size, int inc) JL_NOTSAFEPOINT +// Check if `nptr` is tagged for `old + refyoung`, +// Push the object to the remset and update the `nptr` counter if necessary. +STATIC_INLINE void gc_mark_push_remset(jl_ptls_t ptls, jl_value_t *obj, + uintptr_t nptr) JL_NOTSAFEPOINT { - assert(data_size <= sizeof(jl_gc_mark_data_t)); - if (__unlikely(sp->pc == sp->pc_end)) - gc_mark_stack_resize(gc_cache, sp); - *sp->pc = pc; - memcpy(sp->data, data, data_size); - if (inc) { - sp->data = (jl_gc_mark_data_t *)(((char*)sp->data) + data_size); - sp->pc++; + if (__unlikely((nptr & 0x3) == 0x3)) { + ptls->heap.remset_nptr += nptr >> 2; + arraylist_t *remset = ptls->heap.remset; + size_t len = remset->len; + if (__unlikely(len >= remset->max)) { + arraylist_push(remset, obj); + } + else { + remset->len = len + 1; + remset->items[len] = obj; + } } } -// Check if the reference is non-NULL and atomically set the mark bit. -// Update `*nptr`, which is the `nptr` field of the parent item, if the object is young. -// Return the tag (with GC bits cleared) and the GC bits in `*ptag` and `*pbits`. -// Return whether the object needs to be scanned / have metadata updated. -STATIC_INLINE int gc_try_setmark(jl_value_t *obj, uintptr_t *nptr, - uintptr_t *ptag, uint8_t *pbits) JL_NOTSAFEPOINT +// 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 { - if (!obj) - return 0; - jl_taggedvalue_t *o = jl_astaggedvalue(obj); - uintptr_t tag = o->header; - if (!gc_marked(tag)) { - uint8_t bits; - int res = gc_setmark_tag(o, GC_MARKED, tag, &bits); - if (!gc_old(bits)) - *nptr = *nptr | 1; - *ptag = tag & ~(uintptr_t)0xf; - *pbits = bits; - return __likely(res); - } - else if (!gc_old(tag)) { - *nptr = *nptr | 1; - } - return 0; + 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)) + arraylist_push(&mq->reclaim_set, old_a); } -// Queue a finalizer list to be scanned in the mark loop. Start marking from index `start`. -void gc_mark_queue_finlist(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - arraylist_t *list, size_t start) +// Pop from the mark queue +STATIC_INLINE jl_value_t *gc_ptr_queue_pop(jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { - size_t len = list->len; - if (len <= start) - return; - jl_value_t **items = (jl_value_t**)list->items; - gc_mark_finlist_t markdata = {items + start, items + len}; - gc_mark_stack_push(gc_cache, sp, gc_mark_label_addrs[GC_MARK_L_finlist], - &markdata, sizeof(markdata), 1); + jl_value_t *v = NULL; + ws_queue_pop(&mq->ptr_queue, &v, sizeof(jl_value_t*)); + return v; } -// Queue a object to be scanned. The object should already be marked and the GC metadata -// should already be updated for it. Only scanning of the object should be performed. -STATIC_INLINE void gc_mark_queue_scan_obj(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - jl_value_t *obj) +// Steal from `mq2` +STATIC_INLINE jl_value_t *gc_ptr_queue_steal_from(jl_gc_markqueue_t *mq2) JL_NOTSAFEPOINT { - jl_taggedvalue_t *o = jl_astaggedvalue(obj); - uintptr_t tag = o->header; - uint8_t bits = tag & 0xf; - tag = tag & ~(uintptr_t)0xf; - gc_mark_marked_obj_t data = {obj, tag, bits}; - gc_mark_stack_push(gc_cache, sp, gc_mark_label_addrs[GC_MARK_L_scan_only], - &data, sizeof(data), 1); + jl_value_t *v = NULL; + ws_queue_steal_from(&mq2->ptr_queue, &v, sizeof(jl_value_t*)); + return v; } -// Mark and queue a object to be scanned. -// The object will be marked atomically which can also happen concurrently. -// It will be queued if the object wasn't marked already (or concurrently by another thread) -// Returns whether the object is young. -STATIC_INLINE int gc_mark_queue_obj(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, void *_obj) JL_NOTSAFEPOINT +// Push chunk `*c` into chunk queue +STATIC_INLINE void gc_chunkqueue_push(jl_gc_markqueue_t *mq, jl_gc_chunk_t *c) JL_NOTSAFEPOINT { - jl_value_t *obj = (jl_value_t*)jl_assume(_obj); - uintptr_t nptr = 0; - uintptr_t tag = 0; - uint8_t bits = 0; - if (!gc_try_setmark(obj, &nptr, &tag, &bits)) - return (int)nptr; - gc_mark_marked_obj_t data = {obj, tag, bits}; - gc_mark_stack_push(gc_cache, sp, gc_mark_label_addrs[GC_MARK_L_marked_obj], - &data, sizeof(data), 1); - return (int)nptr; + ws_array_t *old_a = ws_queue_push(&mq->chunk_queue, c, sizeof(jl_gc_chunk_t)); + // Put `old_a` in `reclaim_set` to be freed after the mark phase + if (__unlikely(old_a != NULL)) + arraylist_push(&mq->reclaim_set, old_a); } -int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_value_t *obj) +// Pop chunk from chunk queue +STATIC_INLINE jl_gc_chunk_t gc_chunkqueue_pop(jl_gc_markqueue_t *mq) JL_NOTSAFEPOINT { - return gc_mark_queue_obj(gc_cache, sp, obj); + jl_gc_chunk_t c = {.cid = GC_empty_chunk}; + ws_queue_pop(&mq->chunk_queue, &c, sizeof(jl_gc_chunk_t)); + return c; } -JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj) +// Steal chunk from `mq2` +STATIC_INLINE jl_gc_chunk_t gc_chunkqueue_steal_from(jl_gc_markqueue_t *mq2) JL_NOTSAFEPOINT { - return gc_mark_queue_obj(&ptls->gc_cache, &ptls->gc_mark_sp, obj); + jl_gc_chunk_t c = {.cid = GC_empty_chunk}; + ws_queue_steal_from(&mq2->chunk_queue, &c, sizeof(jl_gc_chunk_t)); + return c; } -JL_DLLEXPORT void jl_gc_mark_queue_objarray(jl_ptls_t ptls, jl_value_t *parent, - jl_value_t **objs, size_t nobjs) +// Enqueue an unmarked obj. last bit of `nptr` is set if `_obj` is young +STATIC_INLINE void gc_try_claim_and_push(jl_gc_markqueue_t *mq, void *_obj, + uintptr_t *nptr) JL_NOTSAFEPOINT { - gc_mark_objarray_t data = { parent, objs, objs + nobjs, 1, - jl_astaggedvalue(parent)->bits.gc & 2 }; - gc_mark_stack_push(&ptls->gc_cache, &ptls->gc_mark_sp, - gc_mark_label_addrs[GC_MARK_L_objarray], - &data, sizeof(data), 1); + if (_obj == NULL) + return; + jl_value_t *obj = (jl_value_t *)jl_assume(_obj); + jl_taggedvalue_t *o = jl_astaggedvalue(obj); + if (!gc_old(o->header) && nptr) + *nptr |= 1; + if (gc_try_setmark_tag(o, GC_MARKED)) + gc_ptr_queue_push(mq, obj); } - -// Check if `nptr` is tagged for `old + refyoung`, -// Push the object to the remset and update the `nptr` counter if necessary. -STATIC_INLINE void gc_mark_push_remset(jl_ptls_t ptls, jl_value_t *obj, uintptr_t nptr) JL_NOTSAFEPOINT +// Mark object with 8bit field descriptors +STATIC_INLINE jl_value_t *gc_mark_obj8(jl_ptls_t ptls, char *obj8_parent, uint8_t *obj8_begin, + uint8_t *obj8_end, uintptr_t nptr) JL_NOTSAFEPOINT { - if (__unlikely((nptr & 0x3) == 0x3)) { - ptls->heap.remset_nptr += nptr >> 2; - arraylist_t *remset = ptls->heap.remset; - size_t len = remset->len; - if (__unlikely(len >= remset->max)) { - arraylist_push(remset, obj); - } - else { - remset->len = len + 1; - remset->items[len] = obj; + (void)jl_assume(obj8_begin < obj8_end); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t **slot = NULL; + jl_value_t *new_obj = NULL; + for (; obj8_begin < obj8_end; obj8_begin++) { + slot = &((jl_value_t**)obj8_parent)[*obj8_begin]; + new_obj = *slot; + if (new_obj != NULL) { + verify_parent2("object", obj8_parent, slot, "field(%d)", + gc_slot_to_fieldidx(obj8_parent, slot, (jl_datatype_t*)jl_typeof(obj8_parent))); + if (obj8_begin + 1 != obj8_end) { + gc_try_claim_and_push(mq, new_obj, &nptr); + } + else { + // Unroll marking of last item to avoid pushing + // and popping it right away + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + nptr |= !gc_old(o->header); + if (!gc_try_setmark_tag(o, GC_MARKED)) new_obj = NULL; + } + gc_heap_snapshot_record_object_edge((jl_value_t*)obj8_parent, slot); } } + gc_mark_push_remset(ptls, (jl_value_t *)obj8_parent, nptr); + return new_obj; } -// Scan a dense array of object references, see `gc_mark_objarray_t` -STATIC_INLINE int gc_mark_scan_objarray(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, - gc_mark_objarray_t *objary, - jl_value_t **begin, jl_value_t **end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) +// Mark object with 16bit field descriptors +STATIC_INLINE jl_value_t *gc_mark_obj16(jl_ptls_t ptls, char *obj16_parent, uint16_t *obj16_begin, + uint16_t *obj16_end, uintptr_t nptr) JL_NOTSAFEPOINT { - (void)jl_assume(objary == (gc_mark_objarray_t*)sp->data); - for (; begin < end; begin += objary->step) { - *pnew_obj = *begin; - if (*pnew_obj) - verify_parent2("obj array", objary->parent, begin, "elem(%d)", - gc_slot_to_arrayidx(objary->parent, begin)); - if (!gc_try_setmark(*pnew_obj, &objary->nptr, ptag, pbits)) - continue; - begin += objary->step; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - objary->begin = begin; - gc_repush_markdata(sp, gc_mark_objarray_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, objary->parent, objary->nptr); - } - return 1; - } - gc_mark_push_remset(ptls, objary->parent, objary->nptr); - return 0; -} - -// Scan a sparse array of object references, see `gc_mark_objarray_t` -STATIC_INLINE int gc_mark_scan_array8(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, - gc_mark_array8_t *ary8, - jl_value_t **begin, jl_value_t **end, - uint8_t *elem_begin, uint8_t *elem_end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) -{ - (void)jl_assume(ary8 == (gc_mark_array8_t*)sp->data); - size_t elsize = ((jl_array_t*)ary8->elem.parent)->elsize / sizeof(jl_value_t*); - for (; begin < end; begin += elsize) { - for (; elem_begin < elem_end; elem_begin++) { - jl_value_t **slot = &begin[*elem_begin]; - *pnew_obj = *slot; - if (*pnew_obj) - verify_parent2("array", ary8->elem.parent, slot, "elem(%d)", - gc_slot_to_arrayidx(ary8->elem.parent, begin)); - if (!gc_try_setmark(*pnew_obj, &ary8->elem.nptr, ptag, pbits)) - continue; - elem_begin++; - // Found an object to mark - if (elem_begin < elem_end) { - // Haven't done with this one yet. Update the content and push it back - ary8->elem.begin = elem_begin; - ary8->begin = begin; - gc_repush_markdata(sp, gc_mark_array8_t); - } - else { - begin += elsize; - if (begin < end) { - // Haven't done with this array yet. Reset the content and push it back - ary8->elem.begin = ary8->rebegin; - ary8->begin = begin; - gc_repush_markdata(sp, gc_mark_array8_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, ary8->elem.parent, ary8->elem.nptr); - } - } - return 1; - } - elem_begin = ary8->rebegin; - } - gc_mark_push_remset(ptls, ary8->elem.parent, ary8->elem.nptr); - return 0; -} - -// Scan a sparse array of object references, see `gc_mark_objarray_t` -STATIC_INLINE int gc_mark_scan_array16(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, - gc_mark_array16_t *ary16, - jl_value_t **begin, jl_value_t **end, - uint16_t *elem_begin, uint16_t *elem_end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) -{ - (void)jl_assume(ary16 == (gc_mark_array16_t*)sp->data); - size_t elsize = ((jl_array_t*)ary16->elem.parent)->elsize / sizeof(jl_value_t*); - for (; begin < end; begin += elsize) { - for (; elem_begin < elem_end; elem_begin++) { - jl_value_t **slot = &begin[*elem_begin]; - *pnew_obj = *slot; - if (*pnew_obj) - verify_parent2("array", ary16->elem.parent, slot, "elem(%d)", - gc_slot_to_arrayidx(ary16->elem.parent, begin)); - if (!gc_try_setmark(*pnew_obj, &ary16->elem.nptr, ptag, pbits)) - continue; - elem_begin++; - // Found an object to mark - if (elem_begin < elem_end) { - // Haven't done with this one yet. Update the content and push it back - ary16->elem.begin = elem_begin; - ary16->begin = begin; - gc_repush_markdata(sp, gc_mark_array16_t); + (void)jl_assume(obj16_begin < obj16_end); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t **slot = NULL; + jl_value_t *new_obj = NULL; + for (; obj16_begin < obj16_end; obj16_begin++) { + slot = &((jl_value_t **)obj16_parent)[*obj16_begin]; + new_obj = *slot; + if (new_obj != NULL) { + verify_parent2("object", obj16_parent, slot, "field(%d)", + gc_slot_to_fieldidx(obj16_parent, slot, (jl_datatype_t*)jl_typeof(obj16_parent))); + if (obj16_begin + 1 != obj16_end) { + gc_try_claim_and_push(mq, new_obj, &nptr); } else { - begin += elsize; - if (begin < end) { - // Haven't done with this array yet. Reset the content and push it back - ary16->elem.begin = ary16->rebegin; - ary16->begin = begin; - gc_repush_markdata(sp, gc_mark_array16_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, ary16->elem.parent, ary16->elem.nptr); - } + // Unroll marking of last item to avoid pushing + // and popping it right away + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + nptr |= !gc_old(o->header); + if (!gc_try_setmark_tag(o, GC_MARKED)) new_obj = NULL; } - return 1; + gc_heap_snapshot_record_object_edge((jl_value_t*)obj16_parent, slot); } - elem_begin = ary16->rebegin; } - gc_mark_push_remset(ptls, ary16->elem.parent, ary16->elem.nptr); - return 0; + gc_mark_push_remset(ptls, (jl_value_t *)obj16_parent, nptr); + return new_obj; } - -// Scan an object with 8bits field descriptors. see `gc_mark_obj8_t` -STATIC_INLINE int gc_mark_scan_obj8(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mark_obj8_t *obj8, - char *parent, uint8_t *begin, uint8_t *end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) +// Mark object with 32bit field descriptors +STATIC_INLINE jl_value_t *gc_mark_obj32(jl_ptls_t ptls, char *obj32_parent, uint32_t *obj32_begin, + uint32_t *obj32_end, uintptr_t nptr) JL_NOTSAFEPOINT { - (void)jl_assume(obj8 == (gc_mark_obj8_t*)sp->data); - (void)jl_assume(begin < end); - for (; begin < end; begin++) { - jl_value_t **slot = &((jl_value_t**)parent)[*begin]; - *pnew_obj = *slot; - if (*pnew_obj) - verify_parent2("object", parent, slot, "field(%d)", - gc_slot_to_fieldidx(parent, slot)); - if (!gc_try_setmark(*pnew_obj, &obj8->nptr, ptag, pbits)) - continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - obj8->begin = begin; - gc_repush_markdata(sp, gc_mark_obj8_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, obj8->parent, obj8->nptr); - } - return 1; - } - gc_mark_push_remset(ptls, obj8->parent, obj8->nptr); - return 0; -} - -// Scan an object with 16bits field descriptors. see `gc_mark_obj16_t` -STATIC_INLINE int gc_mark_scan_obj16(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mark_obj16_t *obj16, - char *parent, uint16_t *begin, uint16_t *end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) JL_NOTSAFEPOINT -{ - (void)jl_assume(obj16 == (gc_mark_obj16_t*)sp->data); - (void)jl_assume(begin < end); - for (; begin < end; begin++) { - jl_value_t **slot = &((jl_value_t**)parent)[*begin]; - *pnew_obj = *slot; - if (*pnew_obj) - verify_parent2("object", parent, slot, "field(%d)", - gc_slot_to_fieldidx(parent, slot)); - if (!gc_try_setmark(*pnew_obj, &obj16->nptr, ptag, pbits)) - continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - obj16->begin = begin; - gc_repush_markdata(sp, gc_mark_obj16_t); - } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, obj16->parent, obj16->nptr); - } - return 1; - } - gc_mark_push_remset(ptls, obj16->parent, obj16->nptr); - return 0; -} - -// Scan an object with 32bits field descriptors. see `gc_mark_obj32_t` -STATIC_INLINE int gc_mark_scan_obj32(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mark_obj32_t *obj32, - char *parent, uint32_t *begin, uint32_t *end, - jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits) -{ - (void)jl_assume(obj32 == (gc_mark_obj32_t*)sp->data); - (void)jl_assume(begin < end); - for (; begin < end; begin++) { - jl_value_t **slot = &((jl_value_t**)parent)[*begin]; - *pnew_obj = *slot; - if (*pnew_obj) - verify_parent2("object", parent, slot, "field(%d)", - gc_slot_to_fieldidx(parent, slot)); - if (!gc_try_setmark(*pnew_obj, &obj32->nptr, ptag, pbits)) - continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - obj32->begin = begin; - gc_repush_markdata(sp, gc_mark_obj32_t); + (void)jl_assume(obj32_begin < obj32_end); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t **slot = NULL; + jl_value_t *new_obj = NULL; + for (; obj32_begin < obj32_end; obj32_begin++) { + slot = &((jl_value_t **)obj32_parent)[*obj32_begin]; + new_obj = *slot; + if (new_obj != NULL) { + verify_parent2("object", obj32_parent, slot, "field(%d)", + gc_slot_to_fieldidx(obj32_parent, slot, (jl_datatype_t*)jl_typeof(obj32_parent))); + if (obj32_begin + 1 != obj32_end) { + gc_try_claim_and_push(mq, new_obj, &nptr); + } + else { + // Unroll marking of last item to avoid pushing + // and popping it right away + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + nptr |= !gc_old(o->header); + if (!gc_try_setmark_tag(o, GC_MARKED)) new_obj = NULL; + } + gc_heap_snapshot_record_object_edge((jl_value_t*)obj32_parent, slot); } - else { - // Finished scanning this one, finish up by checking the GC invariance - // and let the next item replacing the current one directly. - gc_mark_push_remset(ptls, obj32->parent, obj32->nptr); + } + gc_mark_push_remset(ptls, (jl_value_t *)obj32_parent, nptr); + return new_obj; +} + +// Mark object array +STATIC_INLINE void gc_mark_objarray(jl_ptls_t ptls, jl_value_t *obj_parent, jl_value_t **obj_begin, + jl_value_t **obj_end, uint32_t step, uintptr_t nptr) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + // Decide whether need to chunk objary + (void)jl_assume(step > 0); + if ((nptr & 0x2) == 0x2) { + // pre-scan this object: most of this object should be old, so look for + // the first young object before starting this chunk + // (this also would be valid for young objects, but probably less beneficial) + for (; obj_begin < obj_end; obj_begin += step) { + new_obj = *obj_begin; + if (new_obj != NULL) { + verify_parent2("obj array", obj_parent, obj_begin, "elem(%d)", + gc_slot_to_arrayidx(obj_parent, obj_begin)); + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + if (!gc_old(o->header)) + nptr |= 1; + if (!gc_marked(o->header)) + break; + gc_heap_snapshot_record_array_edge(obj_parent, &new_obj); + } } - return 1; } - gc_mark_push_remset(ptls, obj32->parent, obj32->nptr); - return 0; -} - -#if defined(__GNUC__) && !defined(_OS_EMSCRIPTEN_) -# define gc_mark_laddr(name) (&&name) -# define gc_mark_jmp(ptr) goto *(ptr) -#else -#define gc_mark_laddr(name) ((void*)(uintptr_t)GC_MARK_L_##name) -#define gc_mark_jmp(ptr) do { \ - switch ((int)(uintptr_t)ptr) { \ - case GC_MARK_L_marked_obj: \ - goto marked_obj; \ - case GC_MARK_L_scan_only: \ - goto scan_only; \ - case GC_MARK_L_finlist: \ - goto finlist; \ - case GC_MARK_L_objarray: \ - goto objarray; \ - case GC_MARK_L_array8: \ - goto array8; \ - case GC_MARK_L_array16: \ - goto array16; \ - case GC_MARK_L_obj8: \ - goto obj8; \ - case GC_MARK_L_obj16: \ - goto obj16; \ - case GC_MARK_L_obj32: \ - goto obj32; \ - case GC_MARK_L_stack: \ - goto stack; \ - case GC_MARK_L_excstack: \ - goto excstack; \ - case GC_MARK_L_module_binding: \ - goto module_binding; \ - default: \ - abort(); \ - } \ - } while (0) -#endif - -// This is the main marking loop. -// It uses an iterative (mostly) Depth-first search (DFS) to mark all the objects. -// Instead of using the native stack, two stacks are manually maintained, -// one (fixed-size) pc stack which stores the return address and one (variable-size) -// data stack which stores the local variables needed by the scanning code. -// Using a manually maintained stack has a few advantages -// -// 1. We can resize the stack as we go and never worry about stack overflow -// This is especitally useful when enters the GC in a deep call stack. -// It also removes the very deep GC call stack in a profile. -// 2. We can minimize the number of local variables to save on the stack. -// This includes minimizing the sizes of the stack frames and only saving variables -// that have been changed before making "function calls" (i.e. `goto mark;`) -// 3. We can perform end-of-loop tail-call optimization for common cases. -// 4. The marking can be interrupted more easily since all the states are maintained -// in a well-defined format already. -// This will be useful if we want to have incremental marking again. -// 5. The frames can be stolen by another thread more easily and it is not necessary -// to copy works to be stolen to another queue. Useful for parallel marking. -// (Will still require synchronization in stack popping of course.) -// 6. A flat function (i.e. no or very few function calls) also give the compiler -// opportunity to keep more states in registers that doesn't have to be spilled as often. -// -// We use two stacks so that the thief on another thread can steal the fixed sized pc stack -// and use that to figure out the size of the struct on the variable size data stack. -// -// The main disadvantages are that we bypass some stack-based CPU optimizations including the -// stack engine and return address prediction. -// Using two stacks also double the number of operations on the stack pointer -// though we still only need to use one of them (the pc stack pointer) for bounds check. -// In general, it seems that the reduction of stack memory ops and instructions count -// have a larger positive effect on the performance. =) - -// As a general guide we do not want to make non-inlined function calls in this function -// if possible since a large number of registers has to be spilled when that happens. -// This is especially true on on X86 which doesn't have many (any?) -// callee saved general purpose registers. -// (OTOH, the spill will likely make use of the stack engine which is otherwise idle so -// the performance impact is minimum as long as it's not in the hottest path) - -// There are three external entry points to the loop, corresponding to label -// `marked_obj`, `scan_only` and `finlist` (see the corresponding functions -// `gc_mark_queue_obj`, `gc_mark_queue_scan_obj` and `gc_mark_queue_finlist` above). -// The scanning of the object starts with `goto mark`, which updates the metadata and scans -// the object whose information is stored in `new_obj`, `tag` and `bits`. -// The branches in `mark` will dispatch the object to one of the scan "loop"s to be scanned -// as either a normal julia object or one of the special objects with specific storage format. -// Each of the scan "loop" will perform a DFS of the object in the following way -// -// 1. When encountering an pointer (julia object reference) slots, load, perform NULL check -// and atomically set the mark bits to determine if the object needs to be scanned. -// 2. If yes, it'll push itself back onto the mark stack (after updating fields that are changed) -// using `gc_repush_markdata` to increment the stack pointers. -// This step can also be replaced by a tail call by finishing up the marking of the current -// object when the end of the current object is reached. -// 3. Jump to `mark`. The marking of the current object will be resumed after the child is -// scanned by popping the stack frame back. -// -// Some of the special object scannings use BFS to simplify the code (Task and Module). - -// The jumps from the dispatch to the scan "loop"s are done by first pushing a frame -// to the stacks while only increment the data stack pointer before jumping to the loop -// This way the scan "loop" gets exactly what it expects after a stack pop. -// Additional optimizations are done for some of the common cases by skipping -// the unnecessary data stack pointer increment and the load from the stack -// (i.e. store to load forwaring). See `objary_loaded`, `obj8_loaded` and `obj16_loaded`. -JL_EXTENSION NOINLINE void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp) -{ - if (__unlikely(ptls == NULL)) { - gc_mark_label_addrs[GC_MARK_L_marked_obj] = gc_mark_laddr(marked_obj); - gc_mark_label_addrs[GC_MARK_L_scan_only] = gc_mark_laddr(scan_only); - gc_mark_label_addrs[GC_MARK_L_finlist] = gc_mark_laddr(finlist); - gc_mark_label_addrs[GC_MARK_L_objarray] = gc_mark_laddr(objarray); - gc_mark_label_addrs[GC_MARK_L_array8] = gc_mark_laddr(array8); - gc_mark_label_addrs[GC_MARK_L_array16] = gc_mark_laddr(array16); - gc_mark_label_addrs[GC_MARK_L_obj8] = gc_mark_laddr(obj8); - gc_mark_label_addrs[GC_MARK_L_obj16] = gc_mark_laddr(obj16); - gc_mark_label_addrs[GC_MARK_L_obj32] = gc_mark_laddr(obj32); - gc_mark_label_addrs[GC_MARK_L_stack] = gc_mark_laddr(stack); - gc_mark_label_addrs[GC_MARK_L_excstack] = gc_mark_laddr(excstack); - gc_mark_label_addrs[GC_MARK_L_module_binding] = gc_mark_laddr(module_binding); - return; + size_t too_big = (obj_end - obj_begin) / GC_CHUNK_BATCH_SIZE > step; // use this order of operations to avoid idiv + jl_value_t **scan_end = obj_end; + int pushed_chunk = 0; + if (too_big) { + scan_end = obj_begin + step * GC_CHUNK_BATCH_SIZE; + // case 1: array owner is young, so we won't need to scan through all its elements + // to know that we will never need to push it to the remset. it's fine + // to create a chunk with "incorrect" `nptr` and push it to the chunk-queue + // ASAP in order to expose as much parallelism as possible + // 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_objary_chunk, obj_parent, scan_end, obj_end, NULL, NULL, step, nptr}; + gc_chunkqueue_push(mq, &c); + pushed_chunk = 1; + } } - - jl_value_t *new_obj = NULL; - uintptr_t tag = 0; - uint8_t bits = 0; - int meta_updated = 0; - - gc_mark_objarray_t *objary; - jl_value_t **objary_begin; - jl_value_t **objary_end; - - gc_mark_array8_t *ary8; - gc_mark_array16_t *ary16; - - gc_mark_obj8_t *obj8; - char *obj8_parent; - uint8_t *obj8_begin; - uint8_t *obj8_end; - - gc_mark_obj16_t *obj16; - char *obj16_parent; - uint16_t *obj16_begin; - uint16_t *obj16_end; - -pop: - if (sp.pc == sp.pc_start) { - // TODO: stealing form another thread - return; + for (; obj_begin < scan_end; obj_begin += step) { + new_obj = *obj_begin; + if (new_obj != NULL) { + verify_parent2("obj array", obj_parent, obj_begin, "elem(%d)", + gc_slot_to_arrayidx(obj_parent, obj_begin)); + gc_try_claim_and_push(mq, new_obj, &nptr); + gc_heap_snapshot_record_array_edge(obj_parent, &new_obj); + } } - sp.pc--; - gc_mark_jmp(*sp.pc); // computed goto - -marked_obj: { - // An object that has been marked and needs have metadata updated and scanned. - gc_mark_marked_obj_t *obj = gc_pop_markdata(&sp, gc_mark_marked_obj_t); - new_obj = obj->obj; - tag = obj->tag; - bits = obj->bits; - goto mark; - } - -scan_only: { - // An object that has been marked and needs to be scanned. - gc_mark_marked_obj_t *obj = gc_pop_markdata(&sp, gc_mark_marked_obj_t); - new_obj = obj->obj; - tag = obj->tag; - bits = obj->bits; - meta_updated = 1; - goto mark; - } - -objarray: - objary = gc_pop_markdata(&sp, gc_mark_objarray_t); - objary_begin = objary->begin; - objary_end = objary->end; -objarray_loaded: - if (gc_mark_scan_objarray(ptls, &sp, objary, objary_begin, objary_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -array8: - ary8 = gc_pop_markdata(&sp, gc_mark_array8_t); - objary_begin = ary8->begin; - objary_end = ary8->end; - obj8_begin = ary8->elem.begin; - obj8_end = ary8->elem.end; -array8_loaded: - if (gc_mark_scan_array8(ptls, &sp, ary8, objary_begin, objary_end, obj8_begin, obj8_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -array16: - ary16 = gc_pop_markdata(&sp, gc_mark_array16_t); - objary_begin = ary16->begin; - objary_end = ary16->end; - obj16_begin = ary16->elem.begin; - obj16_end = ary16->elem.end; -array16_loaded: - if (gc_mark_scan_array16(ptls, &sp, ary16, objary_begin, objary_end, obj16_begin, obj16_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -obj8: - obj8 = gc_pop_markdata(&sp, gc_mark_obj8_t); - obj8_parent = (char*)obj8->parent; - obj8_begin = obj8->begin; - obj8_end = obj8->end; -obj8_loaded: - if (gc_mark_scan_obj8(ptls, &sp, obj8, obj8_parent, obj8_begin, obj8_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -obj16: - obj16 = gc_pop_markdata(&sp, gc_mark_obj16_t); - obj16_parent = (char*)obj16->parent; - obj16_begin = obj16->begin; - obj16_end = obj16->end; -obj16_loaded: - if (gc_mark_scan_obj16(ptls, &sp, obj16, obj16_parent, obj16_begin, obj16_end, - &new_obj, &tag, &bits)) - goto mark; - goto pop; - -obj32: { - gc_mark_obj32_t *obj32 = gc_pop_markdata(&sp, gc_mark_obj32_t); - char *parent = (char*)obj32->parent; - uint32_t *begin = obj32->begin; - uint32_t *end = obj32->end; - if (gc_mark_scan_obj32(ptls, &sp, obj32, parent, begin, end, &new_obj, &tag, &bits)) - goto mark; - goto pop; + if (too_big) { + if (!pushed_chunk) { + jl_gc_chunk_t c = {GC_objary_chunk, obj_parent, scan_end, obj_end, NULL, NULL, step, nptr}; + gc_chunkqueue_push(mq, &c); + } } - -stack: { - // Scan the stack. see `gc_mark_stackframe_t` - // The task object this stack belongs to is being scanned separately as a normal - // 8bit field descriptor object. - gc_mark_stackframe_t *stack = gc_pop_markdata(&sp, gc_mark_stackframe_t); - jl_gcframe_t *s = stack->s; - uint32_t i = stack->i; - uint32_t nroots = stack->nroots; - uintptr_t offset = stack->offset; - uintptr_t lb = stack->lb; - uintptr_t ub = stack->ub; - uint32_t nr = nroots >> 2; - uintptr_t nptr = 0; - while (1) { - jl_value_t ***rts = (jl_value_t***)(((void**)s) + 2); - for (; i < nr; i++) { - if (nroots & 1) { - void **slot = (void**)gc_read_stack(&rts[i], offset, lb, ub); - new_obj = (jl_value_t*)gc_read_stack(slot, offset, lb, ub); - } - else { - new_obj = (jl_value_t*)gc_read_stack(&rts[i], offset, lb, ub); - if (gc_ptr_tag(new_obj, 1)) { - // handle tagged pointers in finalizer list - new_obj = gc_ptr_clear_tag(new_obj, 1); - i++; + else { + gc_mark_push_remset(ptls, obj_parent, nptr); + } +} + +// 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, + 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); + // Decide whether need to chunk objary + if ((nptr & 0x2) == 0x2) { + // pre-scan this object: most of this object should be old, so look for + // the first young object before starting this chunk + // (this also would be valid for young objects, but probably less beneficial) + for (; ary8_begin < ary8_end; ary8_begin += elsize) { + int early_end = 0; + for (uint8_t *pindex = elem_begin; pindex < elem_end; pindex++) { + new_obj = ary8_begin[*pindex]; + if (new_obj != NULL) { + verify_parent2("array", ary8_parent, &new_obj, "elem(%d)", + gc_slot_to_arrayidx(ary8_parent, ary8_begin)); + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + if (!gc_old(o->header)) + nptr |= 1; + if (!gc_marked(o->header)){ + early_end = 1; + break; } + gc_heap_snapshot_record_array_edge(ary8_parent, &new_obj); } - if (!gc_try_setmark(new_obj, &nptr, &tag, &bits)) - continue; - i++; - if (i < nr) { - // Haven't done with this one yet. Update the content and push it back - stack->i = i; - gc_repush_markdata(&sp, gc_mark_stackframe_t); - } - else if ((s = (jl_gcframe_t*)gc_read_stack(&s->prev, offset, lb, ub))) { - stack->s = s; - stack->i = 0; - uintptr_t new_nroots = gc_read_stack(&s->nroots, offset, lb, ub); - assert(new_nroots <= UINT32_MAX); - stack->nroots = (uint32_t)new_nroots; - gc_repush_markdata(&sp, gc_mark_stackframe_t); - } - goto mark; } - s = (jl_gcframe_t*)gc_read_stack(&s->prev, offset, lb, ub); - if (s != 0) { - stack->s = s; - i = 0; - uintptr_t new_nroots = gc_read_stack(&s->nroots, offset, lb, ub); - assert(new_nroots <= UINT32_MAX); - nroots = stack->nroots = (uint32_t)new_nroots; - nr = nroots >> 2; - continue; + if (early_end) + break; + } + } + size_t too_big = (ary8_end - ary8_begin) / GC_CHUNK_BATCH_SIZE > elsize; // use this order of operations to avoid idiv + jl_value_t **scan_end = ary8_end; + int pushed_chunk = 0; + if (too_big) { + scan_end = ary8_begin + elsize * GC_CHUNK_BATCH_SIZE; + // case 1: array owner is young, so we won't need to scan through all its elements + // to know that we will never need to push it to the remset. it's fine + // to create a chunk with "incorrect" `nptr` and push it to the chunk-queue + // ASAP in order to expose as much parallelism as possible + // 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}; + gc_chunkqueue_push(mq, &c); + pushed_chunk = 1; + } + } + for (; ary8_begin < ary8_end; ary8_begin += elsize) { + for (uint8_t *pindex = elem_begin; pindex < elem_end; pindex++) { + new_obj = ary8_begin[*pindex]; + if (new_obj != NULL) { + verify_parent2("array", ary8_parent, &new_obj, "elem(%d)", + gc_slot_to_arrayidx(ary8_parent, ary8_begin)); + gc_try_claim_and_push(mq, new_obj, &nptr); + gc_heap_snapshot_record_array_edge(ary8_parent, &new_obj); } - goto pop; } } - -excstack: { - // Scan an exception stack - gc_mark_excstack_t *stackitr = gc_pop_markdata(&sp, gc_mark_excstack_t); - jl_excstack_t *excstack = stackitr->s; - size_t itr = stackitr->itr; - size_t bt_index = stackitr->bt_index; - size_t jlval_index = stackitr->jlval_index; - while (itr > 0) { - size_t bt_size = jl_excstack_bt_size(excstack, itr); - jl_bt_element_t *bt_data = jl_excstack_bt_data(excstack, itr); - for (; bt_index < bt_size; bt_index += jl_bt_entry_size(bt_data + bt_index)) { - jl_bt_element_t *bt_entry = bt_data + bt_index; - if (jl_bt_is_native(bt_entry)) - continue; - // Found an extended backtrace entry: iterate over any - // GC-managed values inside. - size_t njlvals = jl_bt_num_jlvals(bt_entry); - while (jlval_index < njlvals) { - new_obj = jl_bt_entry_jlvalue(bt_entry, jlval_index); - uintptr_t nptr = 0; - jlval_index += 1; - if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->itr = itr; - stackitr->bt_index = bt_index; - stackitr->jlval_index = jlval_index; - gc_repush_markdata(&sp, gc_mark_excstack_t); - goto mark; + 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}; + gc_chunkqueue_push(mq, &c); + } + } + else { + gc_mark_push_remset(ptls, ary8_parent, nptr); + } +} + +// 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, + 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); + // Decide whether need to chunk objary + if ((nptr & 0x2) == 0x2) { + // pre-scan this object: most of this object should be old, so look for + // the first young object before starting this chunk + // (this also would be valid for young objects, but probably less beneficial) + for (; ary16_begin < ary16_end; ary16_begin += elsize) { + int early_end = 0; + for (uint16_t *pindex = elem_begin; pindex < elem_end; pindex++) { + new_obj = ary16_begin[*pindex]; + if (new_obj != NULL) { + verify_parent2("array", ary16_parent, &new_obj, "elem(%d)", + gc_slot_to_arrayidx(ary16_parent, ary16_begin)); + jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); + if (!gc_old(o->header)) + nptr |= 1; + if (!gc_marked(o->header)){ + early_end = 1; + break; } + gc_heap_snapshot_record_array_edge(ary16_parent, &new_obj); } - jlval_index = 0; } - // The exception comes last - mark it - new_obj = jl_excstack_exception(excstack, itr); - itr = jl_excstack_next(excstack, itr); - bt_index = 0; - jlval_index = 0; - uintptr_t nptr = 0; - if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->itr = itr; - stackitr->bt_index = bt_index; - stackitr->jlval_index = jlval_index; - gc_repush_markdata(&sp, gc_mark_excstack_t); - goto mark; + if (early_end) + break; + } + } + size_t too_big = (ary16_end - ary16_begin) / GC_CHUNK_BATCH_SIZE > elsize; // use this order of operations to avoid idiv + jl_value_t **scan_end = ary16_end; + int pushed_chunk = 0; + if (too_big) { + scan_end = ary16_begin + elsize * GC_CHUNK_BATCH_SIZE; + // case 1: array owner is young, so we won't need to scan through all its elements + // to know that we will never need to push it to the remset. it's fine + // to create a chunk with "incorrect" `nptr` and push it to the chunk-queue + // ASAP in order to expose as much parallelism as possible + // 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_ary16_chunk, ary16_parent, scan_end, ary16_end, elem_begin, elem_end, elsize, nptr}; + gc_chunkqueue_push(mq, &c); + pushed_chunk = 1; + } + } + for (; ary16_begin < scan_end; ary16_begin += elsize) { + for (uint16_t *pindex = elem_begin; pindex < elem_end; pindex++) { + new_obj = ary16_begin[*pindex]; + if (new_obj != NULL) { + verify_parent2("array", ary16_parent, &new_obj, "elem(%d)", + gc_slot_to_arrayidx(ary16_parent, ary16_begin)); + gc_try_claim_and_push(mq, new_obj, &nptr); + gc_heap_snapshot_record_array_edge(ary16_parent, &new_obj); } } - goto pop; } + if (too_big) { + if (!pushed_chunk) { + jl_gc_chunk_t c = {GC_ary16_chunk, ary16_parent, scan_end, ary16_end, elem_begin, elem_end, elsize, nptr}; + gc_chunkqueue_push(mq, &c); + } + } + else { + gc_mark_push_remset(ptls, ary16_parent, nptr); + } +} -module_binding: { - // Scan a module. see `gc_mark_binding_t` - // Other fields of the module will be scanned after the bindings are scanned - gc_mark_binding_t *binding = gc_pop_markdata(&sp, gc_mark_binding_t); - jl_binding_t **begin = binding->begin; - jl_binding_t **end = binding->end; - uint8_t mbits = binding->bits; - for (; begin < end; begin += 2) { - jl_binding_t *b = *begin; - if (b == (jl_binding_t*)HT_NOTFOUND) - continue; - if ((void*)b >= sysimg_base && (void*)b < sysimg_end) { - jl_taggedvalue_t *buf = jl_astaggedvalue(b); - uintptr_t tag = buf->header; - uint8_t bits; - if (!gc_marked(tag)) - gc_setmark_tag(buf, GC_OLD_MARKED, tag, &bits); - } - else { - gc_setmark_buf_(ptls, b, mbits, sizeof(jl_binding_t)); - } - void *vb = jl_astaggedvalue(b); - verify_parent1("module", binding->parent, &vb, "binding_buff"); - (void)vb; - jl_value_t *value = jl_atomic_load_relaxed(&b->value); - jl_value_t *globalref = jl_atomic_load_relaxed(&b->globalref); - if (value) { - verify_parent2("module", binding->parent, - &b->value, "binding(%s)", jl_symbol_name(b->name)); - if (gc_try_setmark(value, &binding->nptr, &tag, &bits)) { - new_obj = value; - begin += 2; - binding->begin = begin; - gc_repush_markdata(&sp, gc_mark_binding_t); - uintptr_t gr_tag; - uint8_t gr_bits; - if (gc_try_setmark(globalref, &binding->nptr, &gr_tag, &gr_bits)) { - gc_mark_marked_obj_t data = {globalref, gr_tag, gr_bits}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(marked_obj), - &data, sizeof(data), 1); - } - goto mark; - } - } - if (gc_try_setmark(globalref, &binding->nptr, &tag, &bits)) { - begin += 2; - binding->begin = begin; - gc_repush_markdata(&sp, gc_mark_binding_t); - new_obj = globalref; - goto mark; - } +// Mark chunk of large array +STATIC_INLINE void gc_mark_chunk(jl_ptls_t ptls, jl_gc_markqueue_t *mq, jl_gc_chunk_t *c) JL_NOTSAFEPOINT +{ + switch (c->cid) { + case GC_objary_chunk: { + jl_value_t *obj_parent = c->parent; + jl_value_t **obj_begin = c->begin; + 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); + break; } - jl_module_t *m = binding->parent; - int scanparent = gc_try_setmark((jl_value_t*)m->parent, &binding->nptr, &tag, &bits); - size_t nusings = m->usings.len; - if (nusings) { - // this is only necessary because bindings for "using" modules - // are added only when accessed. therefore if a module is replaced - // after "using" it but before accessing it, this array might - // contain the only reference. - objary_begin = (jl_value_t**)m->usings.items; - objary_end = objary_begin + nusings; - gc_mark_objarray_t data = {(jl_value_t*)m, objary_begin, objary_end, 1, binding->nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &data, sizeof(data), 0); - if (!scanparent) { - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; - } - sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(data)); - sp.pc++; + case GC_ary8_chunk: { + jl_value_t *ary8_parent = c->parent; + jl_value_t **ary8_begin = c->begin; + 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; + uintptr_t nptr = c->nptr; + gc_mark_array8(ptls, ary8_parent, ary8_begin, ary8_end, elem_begin, elem_end, + nptr); + break; } - else { - gc_mark_push_remset(ptls, (jl_value_t*)m, binding->nptr); + case GC_ary16_chunk: { + jl_value_t *ary16_parent = c->parent; + jl_value_t **ary16_begin = c->begin; + 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; + uintptr_t nptr = c->nptr; + gc_mark_array16(ptls, ary16_parent, ary16_begin, ary16_end, elem_begin, elem_end, + nptr); + break; } - if (scanparent) { - new_obj = (jl_value_t*)m->parent; - goto mark; + case GC_finlist_chunk: { + jl_value_t **fl_begin = c->begin; + jl_value_t **fl_end = c->end; + gc_mark_finlist_(mq, fl_begin, fl_end); + break; + } + default: { + // `empty-chunk` should be checked by caller + jl_safe_printf("GC internal error: chunk mismatch cid=%d\n", c->cid); + abort(); } - goto pop; } +} -finlist: { - // Scan a finalizer (or format compatible) list. see `gc_mark_finlist_t` - gc_mark_finlist_t *finlist = gc_pop_markdata(&sp, gc_mark_finlist_t); - jl_value_t **begin = finlist->begin; - jl_value_t **end = finlist->end; - for (; begin < end; begin++) { - new_obj = *begin; - if (__unlikely(!new_obj)) - continue; - if (gc_ptr_tag(new_obj, 1)) { - new_obj = (jl_value_t*)gc_ptr_clear_tag(new_obj, 1); - begin++; - assert(begin < end); +// Mark gc frame +STATIC_INLINE void gc_mark_stack(jl_ptls_t ptls, jl_gcframe_t *s, uint32_t nroots, uintptr_t offset, + uintptr_t lb, uintptr_t ub) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + uint32_t nr = nroots >> 2; + while (1) { + jl_value_t ***rts = (jl_value_t ***)(((void **)s) + 2); + for (uint32_t i = 0; i < nr; i++) { + if (nroots & 1) { + void **slot = (void **)gc_read_stack(&rts[i], offset, lb, ub); + new_obj = (jl_value_t *)gc_read_stack(slot, offset, lb, ub); + if (new_obj == NULL) + continue; + } + else { + new_obj = (jl_value_t *)gc_read_stack(&rts[i], offset, lb, ub); + if (gc_ptr_tag(new_obj, 1)) { + // handle tagged pointers in finalizer list + new_obj = (jl_value_t *)gc_ptr_clear_tag(new_obj, 1); + // skip over the finalizer fptr + i++; + } + if (gc_ptr_tag(new_obj, 2)) + continue; + // conservatively check for the presence of any smalltag type, instead of just NULL + // in the very unlikely event that codegen decides to root the result of julia.typeof + if (new_obj < (jl_value_t*)((uintptr_t)jl_max_tags << 4)) + continue; } - uintptr_t nptr = 0; - if (!gc_try_setmark(new_obj, &nptr, &tag, &bits)) + gc_try_claim_and_push(mq, new_obj, NULL); + gc_heap_snapshot_record_frame_to_object_edge(s, new_obj); + } + jl_gcframe_t *sprev = (jl_gcframe_t *)gc_read_stack(&s->prev, offset, lb, ub); + if (sprev == NULL) + break; + gc_heap_snapshot_record_frame_to_frame_edge(s, sprev); + s = sprev; + uintptr_t new_nroots = gc_read_stack(&s->nroots, offset, lb, ub); + assert(new_nroots <= UINT32_MAX); + nroots = (uint32_t)new_nroots; + nr = nroots >> 2; + } +} + +// Mark exception stack +STATIC_INLINE void gc_mark_excstack(jl_ptls_t ptls, jl_excstack_t *excstack, size_t itr) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + jl_value_t *new_obj; + while (itr > 0) { + size_t bt_size = jl_excstack_bt_size(excstack, itr); + jl_bt_element_t *bt_data = jl_excstack_bt_data(excstack, itr); + for (size_t bt_index = 0; bt_index < bt_size; + bt_index += jl_bt_entry_size(bt_data + bt_index)) { + jl_bt_element_t *bt_entry = bt_data + bt_index; + if (jl_bt_is_native(bt_entry)) continue; - begin++; - // Found an object to mark - if (begin < end) { - // Haven't done with this one yet. Update the content and push it back - finlist->begin = begin; - gc_repush_markdata(&sp, gc_mark_finlist_t); + // Found an extended backtrace entry: iterate over any + // GC-managed values inside. + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t jlval_index = 0; jlval_index < njlvals; jlval_index++) { + new_obj = jl_bt_entry_jlvalue(bt_entry, jlval_index); + gc_try_claim_and_push(mq, new_obj, NULL); + gc_heap_snapshot_record_frame_to_object_edge(bt_entry, new_obj); } - goto mark; } - goto pop; + // The exception comes last - mark it + new_obj = jl_excstack_exception(excstack, itr); + itr = jl_excstack_next(excstack, itr); + gc_try_claim_and_push(mq, new_obj, NULL); + gc_heap_snapshot_record_frame_to_object_edge(excstack, new_obj); } +} -mark: { - // Generic scanning entry point. - // Expects `new_obj`, `tag` and `bits` to be set correctly. -#ifdef JL_DEBUG_BUILD - if (new_obj == gc_findval) - jl_raise_debugger(); -#endif - jl_taggedvalue_t *o = jl_astaggedvalue(new_obj); - jl_datatype_t *vt = (jl_datatype_t*)tag; - int foreign_alloc = 0; - int update_meta = __likely(!meta_updated && !gc_verifying); - if (update_meta && (void*)o >= sysimg_base && (void*)o < sysimg_end) { - foreign_alloc = 1; - update_meta = 0; +// Mark module binding +STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent, jl_binding_t **mb_begin, + jl_binding_t **mb_end, uintptr_t nptr, + uint8_t bits) JL_NOTSAFEPOINT +{ + jl_gc_markqueue_t *mq = &ptls->mark_queue; + for (; mb_begin < mb_end; mb_begin++) { + jl_binding_t *b = *mb_begin; + if (b == (jl_binding_t *)jl_nothing) + continue; + verify_parent1("module", mb_parent, mb_begin, "binding_buff"); + gc_try_claim_and_push(mq, b, &nptr); + } + jl_value_t *bindings = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindings); + gc_try_claim_and_push(mq, bindings, &nptr); + jl_value_t *bindingkeyset = (jl_value_t *)jl_atomic_load_relaxed(&mb_parent->bindingkeyset); + gc_try_claim_and_push(mq, bindingkeyset, &nptr); + gc_try_claim_and_push(mq, (jl_value_t *)mb_parent->parent, &nptr); + size_t nusings = mb_parent->usings.len; + if (nusings > 0) { + // this is only necessary because bindings for "using" modules + // are added only when accessed. therefore if a module is replaced + // after "using" it but before accessing it, this array might + // contain the only reference. + jl_value_t *obj_parent = (jl_value_t *)mb_parent; + jl_value_t **objary_begin = (jl_value_t **)mb_parent->usings.items; + jl_value_t **objary_end = objary_begin + nusings; + gc_mark_objarray(ptls, obj_parent, objary_begin, objary_end, 1, nptr); + } + else { + gc_mark_push_remset(ptls, (jl_value_t *)mb_parent, nptr); + } +} + +void gc_mark_finlist_(jl_gc_markqueue_t *mq, jl_value_t **fl_begin, jl_value_t **fl_end) +{ + jl_value_t *new_obj; + // Decide whether need to chunk finlist + size_t nrefs = (fl_end - fl_begin); + if (nrefs > GC_CHUNK_BATCH_SIZE) { + jl_gc_chunk_t c = {GC_finlist_chunk, NULL, fl_begin + GC_CHUNK_BATCH_SIZE, fl_end, 0, 0, 0, 0}; + gc_chunkqueue_push(mq, &c); + fl_end = fl_begin + GC_CHUNK_BATCH_SIZE; + } + for (; fl_begin < fl_end; fl_begin++) { + new_obj = *fl_begin; + if (__unlikely(!new_obj)) + continue; + if (gc_ptr_tag(new_obj, 1)) { + new_obj = (jl_value_t *)gc_ptr_clear_tag(new_obj, 1); + fl_begin++; + assert(fl_begin < fl_end); } - meta_updated = 0; - // Symbols are always marked - assert(vt != jl_symbol_type); - if (vt == jl_simplevector_type) { - size_t l = jl_svec_len(new_obj); - jl_value_t **data = jl_svec_data(new_obj); - size_t dtsz = l * sizeof(void*) + sizeof(jl_svec_t); - if (update_meta) - gc_setmark(ptls, o, bits, dtsz); - else if (foreign_alloc) - objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); - uintptr_t nptr = (l << 2) | (bits & GC_OLD); - objary_begin = data; - objary_end = data + l; - gc_mark_objarray_t markdata = {new_obj, objary_begin, objary_end, 1, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &markdata, sizeof(markdata), 0); - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; - } - else if (vt->name == jl_array_typename) { - jl_array_t *a = (jl_array_t*)new_obj; - jl_array_flags_t flags = a->flags; - if (update_meta) { - if (flags.pooled) - gc_setmark_pool(ptls, o, bits); - else - gc_setmark_big(ptls, o, bits); - } - else if (foreign_alloc) + if (gc_ptr_tag(new_obj, 2)) + continue; + gc_try_claim_and_push(mq, new_obj, NULL); + } +} + +// Mark finalizer list (or list of objects following same format) +void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) +{ + size_t len = list->len; + if (len <= 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); +} + +JL_DLLEXPORT int jl_gc_mark_queue_obj(jl_ptls_t ptls, jl_value_t *obj) +{ + int may_claim = gc_try_setmark_tag(jl_astaggedvalue(obj), GC_MARKED); + if (may_claim) + gc_ptr_queue_push(&ptls->mark_queue, obj); + return may_claim; +} + +JL_DLLEXPORT void jl_gc_mark_queue_objarray(jl_ptls_t ptls, jl_value_t *parent, + jl_value_t **objs, size_t nobjs) +{ + uintptr_t nptr = (nobjs << 2) | (jl_astaggedvalue(parent)->bits.gc & 2); + 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) +{ + jl_value_t *new_obj = (jl_value_t *)_new_obj; + 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; + int update_meta = __likely(!meta_updated && !gc_verifying); + int foreign_alloc = 0; + // directly point at eyt_obj_in_img to encourage inlining + if (update_meta && o->bits.in_image) { + foreign_alloc = 1; + update_meta = 0; + } + // Symbols are always marked + assert(vtag != (uintptr_t)jl_symbol_type && vtag != jl_symbol_tag << 4); + if (vtag == (jl_datatype_tag << 4) || + vtag == (jl_unionall_tag << 4) || + vtag == (jl_uniontype_tag << 4) || + vtag == (jl_tvar_tag << 4) || + vtag == (jl_vararg_tag << 4)) { + // these objects have pointers in them, but no other special handling + // so we want these to fall through to the end + vtag = (uintptr_t)small_typeof[vtag / sizeof(*small_typeof)]; + } + else if (vtag < jl_max_tags << 4) { + // these objects either have specialing handling + if (vtag == jl_simplevector_tag << 4) { + size_t l = jl_svec_len(new_obj); + jl_value_t **data = jl_svec_data(new_obj); + size_t dtsz = l * sizeof(void *) + sizeof(jl_svec_t); + if (update_meta) + gc_setmark(ptls, o, bits, dtsz); + else if (foreign_alloc) + objprofile_count(jl_simplevector_type, bits == GC_OLD_MARKED, dtsz); + jl_value_t *objary_parent = new_obj; + jl_value_t **objary_begin = data; + jl_value_t **objary_end = data + l; + uint32_t step = 1; + uintptr_t nptr = (l << 2) | (bits & GC_OLD); + gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); + } + else if (vtag == jl_module_tag << 4) { + if (update_meta) + gc_setmark(ptls, o, bits, sizeof(jl_module_t)); + else if (foreign_alloc) + objprofile_count(jl_module_type, bits == GC_OLD_MARKED, sizeof(jl_module_t)); + jl_module_t *mb_parent = (jl_module_t *)new_obj; + jl_svec_t *bindings = jl_atomic_load_relaxed(&mb_parent->bindings); + jl_binding_t **table = (jl_binding_t**)jl_svec_data(bindings); + size_t bsize = jl_svec_len(bindings); + uintptr_t nptr = ((bsize + mb_parent->usings.len + 1) << 2) | (bits & GC_OLD); + jl_binding_t **mb_begin = table + 1; + jl_binding_t **mb_end = table + bsize; + gc_mark_module_binding(ptls, mb_parent, mb_begin, mb_end, nptr, bits); + } + else if (vtag == jl_task_tag << 4) { + if (update_meta) + gc_setmark(ptls, o, bits, sizeof(jl_task_t)); + else if (foreign_alloc) + objprofile_count(jl_task_type, bits == GC_OLD_MARKED, sizeof(jl_task_t)); + jl_task_t *ta = (jl_task_t *)new_obj; + gc_scrub_record_task(ta); + if (gc_cblist_task_scanner) { + int16_t tid = jl_atomic_load_relaxed(&ta->tid); + gc_invoke_callbacks(jl_gc_cb_task_scanner_t, gc_cblist_task_scanner, + (ta, tid != -1 && ta == gc_all_tls_states[tid]->root_task)); + } + #ifdef COPY_STACKS + void *stkbuf = ta->stkbuf; + if (stkbuf && ta->copy_stack) { + gc_setmark_buf_(ptls, stkbuf, bits, ta->bufsz); + // For gc_heap_snapshot_record: + // TODO: attribute size of stack + // TODO: edge to stack data + // TODO: synthetic node for stack data (how big is it?) + } + #endif + jl_gcframe_t *s = ta->gcstack; + size_t nroots; + uintptr_t offset = 0; + uintptr_t lb = 0; + uintptr_t ub = (uintptr_t)-1; + #ifdef COPY_STACKS + if (stkbuf && ta->copy_stack && !ta->ptls) { + int16_t tid = jl_atomic_load_relaxed(&ta->tid); + assert(tid >= 0); + jl_ptls_t ptls2 = gc_all_tls_states[tid]; + ub = (uintptr_t)ptls2->stackbase; + lb = ub - ta->copy_stack; + offset = (uintptr_t)stkbuf - lb; + } + #endif + if (s != NULL) { + nroots = gc_read_stack(&s->nroots, offset, lb, ub); + gc_heap_snapshot_record_task_to_frame_edge(ta, s); + assert(nroots <= UINT32_MAX); + gc_mark_stack(ptls, s, (uint32_t)nroots, offset, lb, ub); + } + if (ta->excstack) { + jl_excstack_t *excstack = ta->excstack; + gc_heap_snapshot_record_task_to_frame_edge(ta, excstack); + size_t itr = ta->excstack->top; + gc_setmark_buf_(ptls, excstack, bits, + sizeof(jl_excstack_t) + + sizeof(uintptr_t) * excstack->reserved_size); + gc_mark_excstack(ptls, excstack, itr); + } + const jl_datatype_layout_t *layout = jl_task_type->layout; + assert(layout->fielddesc_type == 0); + assert(layout->nfields > 0); + uint32_t npointers = layout->npointers; + char *obj8_parent = (char *)ta; + uint8_t *obj8_begin = (uint8_t *)jl_dt_layout_ptrs(layout); + uint8_t *obj8_end = obj8_begin + npointers; + // assume tasks always reference young objects: set lowest bit + uintptr_t nptr = (npointers << 2) | 1 | bits; + new_obj = gc_mark_obj8(ptls, obj8_parent, obj8_begin, obj8_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_ptr_queue_push(mq, new_obj); + } + } + else if (vtag == jl_string_tag << 4) { + size_t dtsz = jl_string_len(new_obj) + sizeof(size_t) + 1; + if (update_meta) + gc_setmark(ptls, o, bits, dtsz); + else if (foreign_alloc) + objprofile_count(jl_string_type, bits == GC_OLD_MARKED, dtsz); + } + else { + jl_datatype_t *vt = small_typeof[vtag / sizeof(*small_typeof)]; + size_t dtsz = jl_datatype_size(vt); + if (update_meta) + gc_setmark(ptls, o, bits, dtsz); + else if (foreign_alloc) + objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); + } + return; + } + else { + jl_datatype_t *vt = (jl_datatype_t *)vtag; + if (__unlikely(!jl_is_datatype(vt) || vt->smalltag)) + gc_assert_datatype_fail(ptls, vt, mq); + } + 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 (update_meta) { + if (flags.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); + } 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)); @@ -2582,6 +2563,7 @@ mark: { 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); if (bits == GC_OLD_MARKED) { ptls->gc_cache.perm_scanned_bytes += jl_array_nbytes(a); } @@ -2593,271 +2575,411 @@ mark: { else if (flags.how == 3) { jl_value_t *owner = jl_array_data_owner(a); uintptr_t nptr = (1 << 2) | (bits & GC_OLD); - int markowner = gc_try_setmark(owner, &nptr, &tag, &bits); + 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); - if (markowner) { - new_obj = owner; - goto mark; - } - goto pop; + return; } - if (a->data == NULL || jl_array_len(a) == 0) - goto pop; + if (!a->data || jl_array_len(a) == 0) + return; if (flags.ptrarray) { - if ((jl_datatype_t*)jl_tparam0(vt) == jl_symbol_type) - goto pop; + if ((jl_datatype_t *)jl_tparam0(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; + uint32_t step = 1; uintptr_t nptr = (l << 2) | (bits & GC_OLD); - objary_begin = (jl_value_t**)a->data; - objary_end = objary_begin + l; - gc_mark_objarray_t markdata = {new_obj, objary_begin, objary_end, 1, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &markdata, sizeof(markdata), 0); - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; + 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); + jl_datatype_t *et = (jl_datatype_t *)jl_tparam0(vt); const jl_datatype_layout_t *layout = et->layout; unsigned npointers = layout->npointers; - unsigned elsize = a->elsize / sizeof(jl_value_t*); + unsigned elsize = a->elsize / sizeof(jl_value_t *); 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 * elsize; + uint32_t step = elsize; uintptr_t nptr = ((l * npointers) << 2) | (bits & GC_OLD); - objary_begin = (jl_value_t**)a->data; - objary_end = objary_begin + l * elsize; if (npointers == 1) { // TODO: detect anytime time stride is uniform? objary_begin += layout->first_ptr; - gc_mark_objarray_t markdata = {new_obj, objary_begin, objary_end, elsize, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), - &markdata, sizeof(markdata), 0); - objary = (gc_mark_objarray_t*)sp.data; - goto objarray_loaded; + gc_mark_objarray(ptls, objary_parent, objary_begin, objary_end, step, nptr); } else if (layout->fielddesc_type == 0) { - obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); - obj8_end = obj8_begin + npointers; - gc_mark_array8_t markdata = {objary_begin, objary_end, obj8_begin, {new_obj, obj8_begin, obj8_end, nptr}}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(array8), - &markdata, sizeof(markdata), 0); - ary8 = (gc_mark_array8_t*)sp.data; - goto array8_loaded; + 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); } else if (layout->fielddesc_type == 1) { - obj16_begin = (uint16_t*)jl_dt_layout_ptrs(layout); - obj16_end = obj16_begin + npointers; - gc_mark_array16_t markdata = {objary_begin, objary_end, obj16_begin, {new_obj, obj16_begin, obj16_end, nptr}}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(array16), - &markdata, sizeof(markdata), 0); - ary16 = (gc_mark_array16_t*)sp.data; - goto array16_loaded; + 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); } else { assert(0 && "unimplemented"); } } - goto pop; + return; } - else if (vt == jl_module_type) { - if (update_meta) - gc_setmark(ptls, o, bits, sizeof(jl_module_t)); - else if (foreign_alloc) - objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_module_t)); - jl_module_t *m = (jl_module_t*)new_obj; - jl_binding_t **table = (jl_binding_t**)m->bindings.table; - size_t bsize = m->bindings.size; - uintptr_t nptr = ((bsize + m->usings.len + 1) << 2) | (bits & GC_OLD); - gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr, bits}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(module_binding), - &markdata, sizeof(markdata), 0); - sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(markdata)); - goto module_binding; - } - else if (vt == jl_task_type) { - if (update_meta) - gc_setmark(ptls, o, bits, sizeof(jl_task_t)); - else if (foreign_alloc) - objprofile_count(vt, bits == GC_OLD_MARKED, sizeof(jl_task_t)); - jl_task_t *ta = (jl_task_t*)new_obj; - gc_scrub_record_task(ta); - if (gc_cblist_task_scanner) { - export_gc_state(ptls, &sp); - int16_t tid = jl_atomic_load_relaxed(&ta->tid); - gc_invoke_callbacks(jl_gc_cb_task_scanner_t, - gc_cblist_task_scanner, - (ta, tid != -1 && ta == jl_all_tls_states[tid]->root_task)); - import_gc_state(ptls, &sp); - } -#ifdef COPY_STACKS - void *stkbuf = ta->stkbuf; - if (stkbuf && ta->copy_stack) - gc_setmark_buf_(ptls, stkbuf, bits, ta->bufsz); -#endif - jl_gcframe_t *s = ta->gcstack; - size_t nroots; - uintptr_t offset = 0; - uintptr_t lb = 0; - uintptr_t ub = (uintptr_t)-1; -#ifdef COPY_STACKS - if (stkbuf && ta->copy_stack && ta->ptls == NULL) { - int16_t tid = jl_atomic_load_relaxed(&ta->tid); - assert(tid >= 0); - jl_ptls_t ptls2 = jl_all_tls_states[tid]; - ub = (uintptr_t)ptls2->stackbase; - lb = ub - ta->copy_stack; - offset = (uintptr_t)stkbuf - lb; + size_t dtsz = jl_datatype_size(vt); + if (update_meta) + gc_setmark(ptls, o, bits, dtsz); + else if (foreign_alloc) + objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); + if (vt == jl_weakref_type) + return; + const jl_datatype_layout_t *layout = vt->layout; + uint32_t npointers = layout->npointers; + if (npointers == 0) + return; + uintptr_t nptr = (npointers << 2 | (bits & GC_OLD)); + assert((layout->nfields > 0 || layout->fielddesc_type == 3) && + "opaque types should have been handled specially"); + if (layout->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; + assert(obj8_begin < obj8_end); + new_obj = gc_mark_obj8(ptls, obj8_parent, obj8_begin, obj8_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_ptr_queue_push(mq, new_obj); } -#endif - if (s) { - nroots = gc_read_stack(&s->nroots, offset, lb, ub); - assert(nroots <= UINT32_MAX); - gc_mark_stackframe_t stackdata = {s, 0, (uint32_t)nroots, offset, lb, ub}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(stack), - &stackdata, sizeof(stackdata), 1); + } + else if (layout->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; + assert(obj16_begin < obj16_end); + new_obj = gc_mark_obj16(ptls, obj16_parent, obj16_begin, obj16_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_ptr_queue_push(mq, new_obj); } - if (ta->excstack) { - gc_setmark_buf_(ptls, ta->excstack, bits, sizeof(jl_excstack_t) + - sizeof(uintptr_t)*ta->excstack->reserved_size); - gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0, 0}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack), - &stackdata, sizeof(stackdata), 1); + } + else if (layout->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; + uint32_t *obj32_begin = (uint32_t *)jl_dt_layout_ptrs(layout); + uint32_t *obj32_end = obj32_begin + npointers; + assert(obj32_begin < obj32_end); + new_obj = gc_mark_obj32(ptls, obj32_parent, obj32_begin, obj32_end, nptr); + if (new_obj != NULL) { + if (!meta_updated) + goto mark_obj; + else + gc_ptr_queue_push(mq, new_obj); } - const jl_datatype_layout_t *layout = jl_task_type->layout; - assert(layout->fielddesc_type == 0); - assert(layout->nfields > 0); - uint32_t npointers = layout->npointers; - obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); - obj8_end = obj8_begin + npointers; - // assume tasks always reference young objects: set lowest bit - uintptr_t nptr = (npointers << 2) | 1 | bits; - gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8), - &markdata, sizeof(markdata), 0); - obj8 = (gc_mark_obj8_t*)sp.data; - obj8_parent = (char*)ta; - goto obj8_loaded; - } - else if (vt == jl_string_type) { - size_t dtsz = jl_string_len(new_obj) + sizeof(size_t) + 1; - if (update_meta) - gc_setmark(ptls, o, bits, dtsz); - else if (foreign_alloc) - objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); - goto pop; } else { - if (__unlikely(!jl_is_datatype(vt))) - gc_assert_datatype_fail(ptls, vt, sp); - size_t dtsz = jl_datatype_size(vt); - if (update_meta) - gc_setmark(ptls, o, bits, dtsz); - else if (foreign_alloc) - objprofile_count(vt, bits == GC_OLD_MARKED, dtsz); - if (vt == jl_weakref_type) - goto pop; - const jl_datatype_layout_t *layout = vt->layout; - uint32_t npointers = layout->npointers; - if (npointers == 0) + assert(layout->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); + if (old && young) + gc_mark_push_remset(ptls, new_obj, young * 4 + 3); + } + } +} + +// Used in gc-debug +void gc_mark_loop_serial_(jl_ptls_t ptls, jl_gc_markqueue_t *mq) +{ + while (1) { + void *new_obj = (void *)gc_ptr_queue_pop(&ptls->mark_queue); + // No more objects to mark + if (__unlikely(new_obj == NULL)) { + return; + } + gc_mark_outrefs(ptls, mq, new_obj, 0); + } +} + +// Drain items from worker's own chunkqueue +void gc_drain_own_chunkqueue(jl_ptls_t ptls, jl_gc_markqueue_t *mq) +{ + jl_gc_chunk_t c = {.cid = GC_empty_chunk}; + do { + c = gc_chunkqueue_pop(mq); + if (c.cid != GC_empty_chunk) { + gc_mark_chunk(ptls, mq, &c); + gc_mark_loop_serial_(ptls, mq); + } + } while (c.cid != GC_empty_chunk); +} + +// 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 +// 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) +{ + gc_mark_loop_serial_(ptls, &ptls->mark_queue); + gc_drain_own_chunkqueue(ptls, &ptls->mark_queue); +} + +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; + void *new_obj; + jl_gc_chunk_t c; + pop : { + new_obj = gc_ptr_queue_pop(mq); + if (new_obj != NULL) { + goto mark; + } + c = gc_chunkqueue_pop(mq); + if (c.cid != GC_empty_chunk) { + gc_mark_chunk(ptls, mq, &c); + goto pop; + } + goto steal; + } + mark : { + gc_mark_outrefs(ptls, mq, new_obj, 0); + goto pop; + } + // Note that for the stealing heuristics, we try to + // steal chunks much more agressively than pointers, + // since we know chunks will likely expand into a lot + // of work for the mark loop + steal : { + // Try to steal chunk from random GC thread + for (int i = 0; i < 4 * jl_n_gcthreads; i++) { + uint32_t v = gc_first_tid + cong(UINT64_MAX, UINT64_MAX, &ptls->rngseed) % jl_n_gcthreads; + jl_gc_markqueue_t *mq2 = &gc_all_tls_states[v]->mark_queue; + c = gc_chunkqueue_steal_from(mq2); + if (c.cid != GC_empty_chunk) { + gc_mark_chunk(ptls, mq, &c); goto pop; - uintptr_t nptr = npointers << 2 | (bits & GC_OLD); - assert((layout->nfields > 0 || layout->fielddesc_type == 3) && "opaque types should have been handled specially"); - if (layout->fielddesc_type == 0) { - obj8_parent = (char*)new_obj; - obj8_begin = (uint8_t*)jl_dt_layout_ptrs(layout); - obj8_end = obj8_begin + npointers; - assert(obj8_begin < obj8_end); - gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8), - &markdata, sizeof(markdata), 0); - obj8 = (gc_mark_obj8_t*)sp.data; - goto obj8_loaded; } - else if (layout->fielddesc_type == 1) { - obj16_parent = (char*)new_obj; - obj16_begin = (uint16_t*)jl_dt_layout_ptrs(layout); - obj16_end = obj16_begin + npointers; - assert(obj16_begin < obj16_end); - gc_mark_obj16_t markdata = {new_obj, obj16_begin, obj16_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj16), - &markdata, sizeof(markdata), 0); - obj16 = (gc_mark_obj16_t*)sp.data; - goto obj16_loaded; - } - else if (layout->fielddesc_type == 2) { - // This is very uncommon - // Do not do store to load forwarding to save some code size - uint32_t *obj32_begin = (uint32_t*)jl_dt_layout_ptrs(layout); - uint32_t *obj32_end = obj32_begin + npointers; - gc_mark_obj32_t markdata = {new_obj, obj32_begin, obj32_end, nptr}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj32), - &markdata, sizeof(markdata), 0); - sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(markdata)); - goto obj32; + } + // Sequentially walk GC threads to try to steal chunk + for (int i = gc_first_tid; i < gc_first_tid + jl_n_gcthreads; i++) { + jl_gc_markqueue_t *mq2 = &gc_all_tls_states[i]->mark_queue; + c = gc_chunkqueue_steal_from(mq2); + if (c.cid != GC_empty_chunk) { + gc_mark_chunk(ptls, mq, &c); + goto pop; } - else { - assert(layout->fielddesc_type == 3); - jl_fielddescdyn_t *desc = (jl_fielddescdyn_t*)jl_dt_layout_fields(layout); - int old = jl_astaggedvalue(new_obj)->bits.gc & 2; - export_gc_state(ptls, &sp); - uintptr_t young = desc->markfunc(ptls, new_obj); - import_gc_state(ptls, &sp); - if (old && young) - gc_mark_push_remset(ptls, new_obj, young * 4 + 3); + } + // 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; } } + // Try to steal pointer from random GC thread + for (int i = 0; i < 4 * jl_n_gcthreads; i++) { + uint32_t v = gc_first_tid + cong(UINT64_MAX, UINT64_MAX, &ptls->rngseed) % jl_n_gcthreads; + jl_gc_markqueue_t *mq2 = &gc_all_tls_states[v]->mark_queue; + new_obj = gc_ptr_queue_steal_from(mq2); + if (new_obj != NULL) + goto mark; + } + // Sequentially walk GC threads to try to steal pointer + for (int i = gc_first_tid; i < gc_first_tid + jl_n_gcthreads; i++) { + jl_gc_markqueue_t *mq2 = &gc_all_tls_states[i]->mark_queue; + new_obj = gc_ptr_queue_steal_from(mq2); + if (new_obj != NULL) + 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; + } } } -static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - jl_ptls_t ptls2) +#define GC_BACKOFF_MIN 4 +#define GC_BACKOFF_MAX 12 + +void gc_mark_backoff(int *i) { - gc_mark_queue_obj(gc_cache, sp, jl_atomic_load_relaxed(&ptls2->current_task)); - gc_mark_queue_obj(gc_cache, sp, ptls2->root_task); - if (ptls2->next_task) - gc_mark_queue_obj(gc_cache, sp, ptls2->next_task); - if (ptls2->previous_task) // shouldn't be necessary, but no reason not to - gc_mark_queue_obj(gc_cache, sp, ptls2->previous_task); - if (ptls2->previous_exception) - gc_mark_queue_obj(gc_cache, sp, ptls2->previous_exception); + if (*i < GC_BACKOFF_MAX) { + (*i)++; + } + for (int j = 0; j < (1 << *i); j++) { + jl_cpu_pause(); + } +} + +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_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); + } + jl_atomic_fetch_add(&gc_n_threads_marking, -1); + // Failed to steal + gc_mark_backoff(&backoff); + } +} + +void gc_mark_loop(jl_ptls_t ptls) +{ + if (jl_n_gcthreads == 0 || gc_heap_snapshot_enabled) { + gc_mark_loop_serial(ptls); + } + else { + gc_mark_loop_parallel(ptls, 1); + } +} + +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(); + } +} + +void gc_mark_clean_reclaim_sets(void) +{ + // Clean up `reclaim-sets` and reset `top/bottom` of queues + for (int i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + arraylist_t *reclaim_set2 = &ptls2->mark_queue.reclaim_set; + ws_array_t *a = NULL; + while ((a = (ws_array_t *)arraylist_pop(reclaim_set2)) != NULL) { + free(a->buffer); + free(a); + } + } +} + +static void gc_premark(jl_ptls_t ptls2) +{ + arraylist_t *remset = ptls2->heap.remset; + ptls2->heap.remset = ptls2->heap.last_remset; + ptls2->heap.last_remset = remset; + ptls2->heap.remset->len = 0; + ptls2->heap.remset_nptr = 0; + // avoid counting remembered objects + // in `perm_scanned_bytes` + size_t len = remset->len; + void **items = remset->items; + for (size_t i = 0; i < len; i++) { + jl_value_t *item = (jl_value_t *)items[i]; + objprofile_count(jl_typeof(item), 2, 0); + jl_astaggedvalue(item)->bits.gc = GC_OLD_MARKED; + } +} + +static void gc_queue_thread_local(jl_gc_markqueue_t *mq, jl_ptls_t ptls2) +{ + jl_task_t *task; + task = ptls2->root_task; + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); + gc_heap_snapshot_record_root((jl_value_t*)task, "root task"); + } + task = jl_atomic_load_relaxed(&ptls2->current_task); + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); + gc_heap_snapshot_record_root((jl_value_t*)task, "current task"); + } + task = ptls2->next_task; + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); + gc_heap_snapshot_record_root((jl_value_t*)task, "next task"); + } + task = ptls2->previous_task; + if (task != NULL) { + gc_try_claim_and_push(mq, task, NULL); + gc_heap_snapshot_record_root((jl_value_t*)task, "previous task"); + } + if (ptls2->previous_exception) { + gc_try_claim_and_push(mq, ptls2->previous_exception, NULL); + gc_heap_snapshot_record_root((jl_value_t*)ptls2->previous_exception, "previous exception"); + } +} + +static void gc_queue_bt_buf(jl_gc_markqueue_t *mq, jl_ptls_t ptls2) +{ + jl_bt_element_t *bt_data = ptls2->bt_data; + size_t bt_size = ptls2->bt_size; + for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { + jl_bt_element_t *bt_entry = bt_data + i; + if (jl_bt_is_native(bt_entry)) + continue; + size_t njlvals = jl_bt_num_jlvals(bt_entry); + for (size_t j = 0; j < njlvals; j++) + gc_try_claim_and_push(mq, jl_bt_entry_jlvalue(bt_entry, j), NULL); + } +} + +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); + } } -void jl_gc_mark_enqueued_tasks(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp); extern jl_value_t *cmpswap_names JL_GLOBALLY_ROOTED; +extern jl_task_t *wait_empty JL_GLOBALLY_ROOTED; // mark the initial root set -static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) +static void gc_mark_roots(jl_gc_markqueue_t *mq) { // modules - gc_mark_queue_obj(gc_cache, sp, jl_main_module); - - // tasks - jl_gc_mark_enqueued_tasks(gc_cache, sp); - + gc_try_claim_and_push(mq, jl_main_module, NULL); + gc_heap_snapshot_record_root((jl_value_t*)jl_main_module, "main_module"); // invisible builtin values - if (jl_an_empty_vec_any != NULL) - gc_mark_queue_obj(gc_cache, sp, jl_an_empty_vec_any); - if (jl_module_init_order != NULL) - gc_mark_queue_obj(gc_cache, sp, jl_module_init_order); + gc_try_claim_and_push(mq, jl_an_empty_vec_any, NULL); + gc_try_claim_and_push(mq, jl_module_init_order, NULL); for (size_t i = 0; i < jl_current_modules.size; i += 2) { if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { - gc_mark_queue_obj(gc_cache, sp, jl_current_modules.table[i]); + 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_mark_queue_obj(gc_cache, sp, jl_anytuple_type_type); + gc_try_claim_and_push(mq, jl_anytuple_type_type, NULL); for (size_t i = 0; i < N_CALL_CACHE; i++) { jl_typemap_entry_t *v = jl_atomic_load_relaxed(&call_cache[i]); - if (v != NULL) - gc_mark_queue_obj(gc_cache, sp, v); + gc_try_claim_and_push(mq, v, NULL); } - if (jl_all_methods != NULL) - gc_mark_queue_obj(gc_cache, sp, jl_all_methods); - if (_jl_debug_method_invalidation != NULL) - gc_mark_queue_obj(gc_cache, sp, _jl_debug_method_invalidation); - + gc_try_claim_and_push(mq, jl_all_methods, NULL); + gc_try_claim_and_push(mq, _jl_debug_method_invalidation, NULL); // constants - gc_mark_queue_obj(gc_cache, sp, jl_emptytuple_type); - if (cmpswap_names != NULL) - gc_mark_queue_obj(gc_cache, sp, cmpswap_names); + gc_try_claim_and_push(mq, jl_emptytuple_type, NULL); + gc_try_claim_and_push(mq, cmpswap_names, NULL); + gc_try_claim_and_push(mq, jl_global_roots_table, NULL); } // find unmarked objects that need to be finalized from the finalizer list "list". @@ -2869,17 +2991,25 @@ static void sweep_finalizer_list(arraylist_t *list) size_t j = 0; for (size_t i=0; i < len; i+=2) { void *v0 = items[i]; - void *v = gc_ptr_clear_tag(v0, 1); + void *v = gc_ptr_clear_tag(v0, 3); if (__unlikely(!v0)) { // remove from this list continue; } void *fin = items[i+1]; - int isfreed = !gc_marked(jl_astaggedvalue(v)->bits.gc); - int isold = (list != &finalizer_list_marked && + int isfreed; + int isold; + if (gc_ptr_tag(v0, 2)) { + isfreed = 1; + isold = 0; + } + else { + isfreed = !gc_marked(jl_astaggedvalue(v)->bits.gc); + isold = (list != &finalizer_list_marked && jl_astaggedvalue(v)->bits.gc == GC_OLD_MARKED && jl_astaggedvalue(fin)->bits.gc == GC_OLD_MARKED); + } if (isfreed || isold) { // remove from this list } @@ -2953,6 +3083,13 @@ JL_DLLEXPORT jl_gc_num_t jl_gc_num(void) return num; } +JL_DLLEXPORT void jl_gc_reset_stats(void) +{ + gc_num.max_pause = 0; + gc_num.max_memory = 0; + gc_num.max_time_to_safepoint = 0; +} + // TODO: these were supposed to be thread local JL_DLLEXPORT int64_t jl_gc_diff_total_bytes(void) JL_NOTSAFEPOINT { @@ -2977,67 +3114,6 @@ JL_DLLEXPORT int64_t jl_gc_live_bytes(void) return live_bytes; } -static void jl_gc_premark(jl_ptls_t ptls2) -{ - arraylist_t *remset = ptls2->heap.remset; - ptls2->heap.remset = ptls2->heap.last_remset; - ptls2->heap.last_remset = remset; - ptls2->heap.remset->len = 0; - ptls2->heap.remset_nptr = 0; - - // avoid counting remembered objects & bindings twice - // in `perm_scanned_bytes` - size_t len = remset->len; - void **items = remset->items; - for (size_t i = 0; i < len; i++) { - jl_value_t *item = (jl_value_t*)items[i]; - objprofile_count(jl_typeof(item), 2, 0); - jl_astaggedvalue(item)->bits.gc = GC_OLD_MARKED; - } - len = ptls2->heap.rem_bindings.len; - items = ptls2->heap.rem_bindings.items; - for (size_t i = 0; i < len; i++) { - void *ptr = items[i]; - jl_astaggedvalue(ptr)->bits.gc = GC_OLD_MARKED; - } -} - -static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, 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++) - gc_mark_queue_scan_obj(gc_cache, sp, (jl_value_t*)items[i]); - int n_bnd_refyoung = 0; - len = ptls2->heap.rem_bindings.len; - items = ptls2->heap.rem_bindings.items; - for (size_t i = 0; i < len; i++) { - jl_binding_t *ptr = (jl_binding_t*)items[i]; - // A null pointer can happen here when the binding is cleaned up - // as an exception is thrown after it was already queued (#10221) - jl_value_t *v = jl_atomic_load_relaxed(&ptr->value); - if (v != NULL && gc_mark_queue_obj(gc_cache, sp, v)) { - items[n_bnd_refyoung] = ptr; - n_bnd_refyoung++; - } - } - ptls2->heap.rem_bindings.len = n_bnd_refyoung; -} - -static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) -{ - jl_bt_element_t *bt_data = ptls2->bt_data; - size_t bt_size = ptls2->bt_size; - for (size_t i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { - jl_bt_element_t *bt_entry = bt_data + i; - if (jl_bt_is_native(bt_entry)) - continue; - size_t njlvals = jl_bt_num_jlvals(bt_entry); - for (size_t j = 0; j < njlvals; j++) - gc_mark_queue_obj(gc_cache, sp, jl_bt_entry_jlvalue(bt_entry, j)); - } -} - size_t jl_maxrss(void); // Only one thread should be running in this function @@ -3045,84 +3121,111 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) { combine_thread_gc_counts(&gc_num); - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; - jl_gc_mark_sp_t sp; - gc_mark_sp_init(gc_cache, &sp); +#ifdef USE_TRACY + TracyCPlot("Heap size", live_bytes + gc_num.allocd); +#endif - uint64_t t0 = jl_hrtime(); + jl_gc_markqueue_t *mq = &ptls->mark_queue; + + uint64_t gc_start_time = jl_hrtime(); int64_t last_perm_scanned_bytes = perm_scanned_bytes; + uint64_t start_mark_time = jl_hrtime(); JL_PROBE_GC_MARK_BEGIN(); + { + JL_TIMING(GC, GC_Mark); + + // 1. fix GC bits of objects in the remset. + 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]; + if (ptls2 != NULL) + gc_premark(ptls2); + } + + assert(gc_n_threads); + int single_threaded = (jl_n_gcthreads == 0 || gc_heap_snapshot_enabled); + for (int t_i = 0; t_i < gc_n_threads; t_i++) { + jl_ptls_t ptls2 = gc_all_tls_states[t_i]; + jl_ptls_t ptls_dest = ptls; + jl_gc_markqueue_t *mq_dest = mq; + if (!single_threaded) { + ptls_dest = gc_all_tls_states[gc_first_tid + t_i % jl_n_gcthreads]; + mq_dest = &ptls_dest->mark_queue; + } + if (ptls2 != NULL) { + // 2.1. mark every thread local root + gc_queue_thread_local(mq_dest, ptls2); + // 2.2. mark any managed objects in the backtrace buffer + // TODO: treat these as roots for gc_heap_snapshot_record + gc_queue_bt_buf(mq_dest, ptls2); + // 2.3. mark every object in the `last_remsets` and `rem_binding` + gc_queue_remset(ptls_dest, ptls2); + } + } + + // 3. walk roots + gc_mark_roots(mq); + if (gc_cblist_root_scanner) { + gc_invoke_callbacks(jl_gc_cb_root_scanner_t, + gc_cblist_root_scanner, (collection)); + } + gc_mark_loop(ptls); + gc_mark_loop_barrier(); + gc_mark_clean_reclaim_sets(); + + // 4. check for objects to finalize + clear_weak_refs(); + // Record the length of the marked list since we need to + // mark the object moved to the marked list from the + // `finalizer_list` by `sweep_finalizer_list` + size_t orig_marked_len = finalizer_list_marked.len; + 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) + sweep_finalizer_list(&ptls2->finalizers); + } + if (prev_sweep_full) { + sweep_finalizer_list(&finalizer_list_marked); + orig_marked_len = 0; + } + 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) + gc_mark_finlist(mq, &ptls2->finalizers, 0); + } + gc_mark_finlist(mq, &finalizer_list_marked, orig_marked_len); + // "Flush" the mark stack before flipping the reset_age bit + // so that the objects are not incorrectly reset. + gc_mark_loop_serial(ptls); + // Conservative marking relies on age to tell allocated objects + // and freelist entries apart. + mark_reset_age = !jl_gc_conservative_gc_support_enabled(); + // Reset the age and old bit for any unmarked objects referenced by the + // `to_finalize` list. These objects are only reachable from this list + // and should not be referenced by any old objects so this won't break + // the GC invariant. + gc_mark_finlist(mq, &to_finalize, 0); + gc_mark_loop_serial(ptls); + mark_reset_age = 0; + } - // 1. fix GC bits of objects in the remset. - for (int t_i = 0; t_i < jl_n_threads; t_i++) - jl_gc_premark(jl_all_tls_states[t_i]); - - for (int t_i = 0; t_i < jl_n_threads; t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; - // 2.1. mark every object in the `last_remsets` and `rem_binding` - jl_gc_queue_remset(gc_cache, &sp, ptls2); - // 2.2. mark every thread local root - jl_gc_queue_thread_local(gc_cache, &sp, ptls2); - // 2.3. mark any managed objects in the backtrace buffer - jl_gc_queue_bt_buf(gc_cache, &sp, ptls2); - } - - // 3. walk roots - mark_roots(gc_cache, &sp); - if (gc_cblist_root_scanner) { - export_gc_state(ptls, &sp); - gc_invoke_callbacks(jl_gc_cb_root_scanner_t, - gc_cblist_root_scanner, (collection)); - import_gc_state(ptls, &sp); - } - gc_mark_loop(ptls, sp); - gc_mark_sp_init(gc_cache, &sp); - gc_num.since_sweep += gc_num.allocd; JL_PROBE_GC_MARK_END(scanned_bytes, perm_scanned_bytes); gc_settime_premark_end(); - gc_time_mark_pause(t0, scanned_bytes, perm_scanned_bytes); - int64_t actual_allocd = gc_num.since_sweep; - // marking is over - - // 4. check for objects to finalize - clear_weak_refs(); - // Record the length of the marked list since we need to - // mark the object moved to the marked list from the - // `finalizer_list` by `sweep_finalizer_list` - size_t orig_marked_len = finalizer_list_marked.len; - for (int i = 0;i < jl_n_threads;i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - sweep_finalizer_list(&ptls2->finalizers); - } - if (prev_sweep_full) { - sweep_finalizer_list(&finalizer_list_marked); - orig_marked_len = 0; - } - for (int i = 0;i < jl_n_threads;i++) { - jl_ptls_t ptls2 = jl_all_tls_states[i]; - gc_mark_queue_finlist(gc_cache, &sp, &ptls2->finalizers, 0); - } - gc_mark_queue_finlist(gc_cache, &sp, &finalizer_list_marked, orig_marked_len); - // "Flush" the mark stack before flipping the reset_age bit - // so that the objects are not incorrectly reset. - gc_mark_loop(ptls, sp); - gc_mark_sp_init(gc_cache, &sp); - // Conservative marking relies on age to tell allocated objects - // and freelist entries apart. - mark_reset_age = !jl_gc_conservative_gc_support_enabled(); - // Reset the age and old bit for any unmarked objects referenced by the - // `to_finalize` list. These objects are only reachable from this list - // and should not be referenced by any old objects so this won't break - // the GC invariant. - gc_mark_queue_finlist(gc_cache, &sp, &to_finalize, 0); - gc_mark_loop(ptls, sp); - mark_reset_age = 0; + gc_time_mark_pause(gc_start_time, scanned_bytes, perm_scanned_bytes); + uint64_t end_mark_time = jl_hrtime(); + uint64_t mark_time = end_mark_time - start_mark_time; + gc_num.mark_time = mark_time; + gc_num.total_mark_time += mark_time; + int64_t allocd = gc_num.allocd; gc_settime_postmark_end(); + // marking is over // Flush everything in mark cache gc_sync_all_caches_nolock(ptls); - int64_t live_sz_ub = live_bytes + actual_allocd; + int64_t live_sz_ub = live_bytes + allocd; int64_t live_sz_est = scanned_bytes + perm_scanned_bytes; int64_t estimate_freed = live_sz_ub - live_sz_est; @@ -3132,57 +3235,57 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) gc_stats_big_obj(); objprofile_printall(); objprofile_reset(); - gc_num.total_allocd += gc_num.since_sweep; + gc_num.total_allocd += gc_num.allocd; if (!prev_sweep_full) promoted_bytes += perm_scanned_bytes - last_perm_scanned_bytes; // 5. next collection decision - int not_freed_enough = (collection == JL_GC_AUTO) && estimate_freed < (7*(actual_allocd/10)); + int not_freed_enough = (collection == JL_GC_AUTO) && estimate_freed < (7*(allocd/10)); int nptr = 0; - for (int i = 0;i < jl_n_threads;i++) - nptr += jl_all_tls_states[i]->heap.remset_nptr; - int large_frontier = nptr*sizeof(void*) >= default_collect_interval; // many pointers in the intergen frontier => "quick" mark is not quick - // trigger a full collection if the number of live bytes doubles since the last full - // collection and then remains at least that high for a while. - if (grown_heap_age == 0) { - if (live_bytes > 2 * last_full_live) - grown_heap_age = 1; - } - else if (live_bytes >= last_live_bytes) { - grown_heap_age++; + 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) + nptr += ptls2->heap.remset_nptr; } + + // many pointers in the intergen frontier => "quick" mark is not quick + int large_frontier = nptr*sizeof(void*) >= default_collect_interval; int sweep_full = 0; int recollect = 0; - if ((large_frontier || - ((not_freed_enough || promoted_bytes >= gc_num.interval) && - (promoted_bytes >= default_collect_interval || prev_sweep_full)) || - grown_heap_age > 1) && gc_num.pause > 1) { - sweep_full = 1; - } + // update heuristics only if this GC was automatically triggered if (collection == JL_GC_AUTO) { - if (sweep_full) { - if (large_frontier) - gc_num.interval = last_long_collect_interval; - if (not_freed_enough || large_frontier) { - if (gc_num.interval <= 2*(max_collect_interval/5)) { - gc_num.interval = 5 * (gc_num.interval / 2); - } - } - last_long_collect_interval = gc_num.interval; + if (large_frontier) { + sweep_full = 1; + gc_num.interval = last_long_collect_interval; } - else { - // reset interval to default, or at least half of live_bytes - int64_t half = live_bytes/2; - if (default_collect_interval < half && half <= max_collect_interval) - gc_num.interval = half; - else - gc_num.interval = default_collect_interval; + if (not_freed_enough || large_frontier) { + gc_num.interval = gc_num.interval * 2; } + + size_t maxmem = 0; +#ifdef _P64 + // on a big memory machine, increase max_collect_interval to totalmem / nthreads / 2 + maxmem = total_mem / (gc_n_threads - jl_n_gcthreads) / 2; +#endif + if (maxmem < max_collect_interval) + maxmem = max_collect_interval; + if (gc_num.interval > maxmem) { + sweep_full = 1; + gc_num.interval = maxmem; + } + } + + // If the live data outgrows the suggested max_total_memory + // we keep going with minimum intervals and full gcs until + // we either free some space or get an OOM error. + if (live_bytes > max_total_memory) { + sweep_full = 1; } if (gc_sweep_always_full) { sweep_full = 1; } - if (collection == JL_GC_FULL) { + if (collection == JL_GC_FULL && !prev_sweep_full) { sweep_full = 1; recollect = 1; } @@ -3191,37 +3294,54 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) // on the first collection after sweep_full, and the current scan perm_scanned_bytes = 0; promoted_bytes = 0; + last_long_collect_interval = gc_num.interval; } scanned_bytes = 0; - // 5. start sweeping + // 6. start sweeping + uint64_t start_sweep_time = jl_hrtime(); JL_PROBE_GC_SWEEP_BEGIN(sweep_full); - sweep_weak_refs(); - sweep_stack_pools(); - gc_sweep_foreign_objs(); - gc_sweep_other(ptls, sweep_full); - gc_scrub(); - gc_verify_tags(); - gc_sweep_pool(sweep_full); - if (sweep_full) - gc_sweep_perm_alloc(); + { + JL_TIMING(GC, GC_Sweep); +#ifdef USE_TRACY + if (sweep_full) { + TracyCZoneCtx ctx = *(JL_TIMING_CURRENT_BLOCK->tracy_ctx); + TracyCZoneColor(ctx, 0xFFA500); + } +#endif + sweep_weak_refs(); + sweep_stack_pools(); + gc_sweep_foreign_objs(); + gc_sweep_other(ptls, sweep_full); + gc_scrub(); + gc_verify_tags(); + gc_sweep_pool(sweep_full); + if (sweep_full) + gc_sweep_perm_alloc(); + } JL_PROBE_GC_SWEEP_END(); + + uint64_t gc_end_time = jl_hrtime(); + uint64_t pause = gc_end_time - gc_start_time; + uint64_t sweep_time = gc_end_time - start_sweep_time; + gc_num.total_sweep_time += sweep_time; + gc_num.sweep_time = sweep_time; + // sweeping is over - // 6. if it is a quick sweep, put back the remembered objects in queued state + // 7. if it is a quick sweep, put back the remembered objects in queued state // so that we don't trigger the barrier again on them. - for (int t_i = 0;t_i < jl_n_threads;t_i++) { - jl_ptls_t ptls2 = jl_all_tls_states[t_i]; + 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]; + if (ptls2 == NULL) + continue; if (!sweep_full) { for (int i = 0; i < ptls2->heap.remset->len; i++) { - jl_astaggedvalue(ptls2->heap.remset->items[i])->bits.gc = GC_MARKED; - } - for (int i = 0; i < ptls2->heap.rem_bindings.len; i++) { - void *ptr = ptls2->heap.rem_bindings.items[i]; + void *ptr = ptls2->heap.remset->items[i]; jl_astaggedvalue(ptr)->bits.gc = GC_MARKED; } } else { ptls2->heap.remset->len = 0; - ptls2->heap.rem_bindings.len = 0; } } @@ -3237,27 +3357,65 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) } #endif - uint64_t gc_end_t = jl_hrtime(); - uint64_t pause = gc_end_t - t0; - _report_gc_finished(pause, gc_num.freed, sweep_full, recollect); - gc_final_pause_end(t0, gc_end_t); - gc_time_sweep_pause(gc_end_t, actual_allocd, live_bytes, + 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_num.full_sweep += sweep_full; - gc_num.allocd = 0; + uint64_t max_memory = last_live_bytes + gc_num.allocd; + if (max_memory > gc_num.max_memory) { + gc_num.max_memory = max_memory; + } + last_live_bytes = live_bytes; - live_bytes += -gc_num.freed + gc_num.since_sweep; - if (prev_sweep_full) { - last_full_live = live_bytes; - grown_heap_age = 0; + live_bytes += -gc_num.freed + gc_num.allocd; + + if (collection == JL_GC_AUTO) { + //If we aren't freeing enough or are seeing lots and lots of pointers let it increase faster + if (!not_freed_enough || large_frontier) { + int64_t tot = 2 * (live_bytes + gc_num.allocd) / 3; + if (gc_num.interval > tot) { + gc_num.interval = tot; + last_long_collect_interval = tot; + } + // If the current interval is larger than half the live data decrease the interval + } + else { + int64_t half = (live_bytes / 2); + if (gc_num.interval > half) + gc_num.interval = half; + } + + // But never go below default + if (gc_num.interval < default_collect_interval) gc_num.interval = default_collect_interval; } + + if (gc_num.interval + live_bytes > max_total_memory) { + if (live_bytes < max_total_memory) { + gc_num.interval = max_total_memory - live_bytes; + last_long_collect_interval = max_total_memory - live_bytes; + } + else { + // We can't stay under our goal so let's go back to + // the minimum interval and hope things get better + gc_num.interval = default_collect_interval; + } + } + + gc_time_summary(sweep_full, t_start, gc_end_time, gc_num.freed, + live_bytes, gc_num.interval, pause, + gc_num.time_to_safepoint, + gc_num.mark_time, gc_num.sweep_time); + prev_sweep_full = sweep_full; gc_num.pause += !recollect; gc_num.total_time += pause; - gc_num.since_sweep = 0; + gc_num.allocd = 0; gc_num.freed = 0; + if (pause > gc_num.max_pause) { + gc_num.max_pause = pause; + } reset_thread_gc_counts(); return recollect; @@ -3282,38 +3440,60 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) jl_atomic_store_release(&ptls->gc_state, JL_GC_STATE_WAITING); // `jl_safepoint_start_gc()` makes sure only one thread can // run the GC. + uint64_t t0 = jl_hrtime(); if (!jl_safepoint_start_gc()) { // Multithread only. See assertion in `safepoint.c` jl_gc_state_set(ptls, old_state, JL_GC_STATE_WAITING); return; } - JL_TIMING(GC); + + JL_TIMING_SUSPEND(GC, ct); + JL_TIMING(GC, GC); + int last_errno = errno; #ifdef _OS_WINDOWS_ DWORD last_error = GetLastError(); #endif // Now we are ready to wait for other threads to hit the safepoint, // we can do a few things that doesn't require synchronization. - // TODO (concurrently queue objects) - // no-op for non-threading - jl_gc_wait_for_the_world(); + // + // We must sync here with the tls_lock operations, so that we have a + // seq-cst order between these events now we know that either the new + // thread must run into our safepoint flag or we must observe the + // existence of the thread in the jl_n_threads count. + // + // TODO: concurrently queue objects + jl_fence(); + gc_n_threads = jl_atomic_load_acquire(&jl_n_threads); + gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); + jl_gc_wait_for_the_world(gc_all_tls_states, gc_n_threads); JL_PROBE_GC_STOP_THE_WORLD(); + uint64_t t1 = jl_hrtime(); + uint64_t duration = t1 - t0; + if (duration > gc_num.max_time_to_safepoint) + gc_num.max_time_to_safepoint = duration; + gc_num.time_to_safepoint = duration; + gc_num.total_time_to_safepoint += duration; + gc_invoke_callbacks(jl_gc_cb_pre_gc_t, gc_cblist_pre_gc, (collection)); if (!jl_atomic_load_relaxed(&jl_gc_disable_counter)) { - JL_LOCK_NOGC(&finalizers_lock); + JL_LOCK_NOGC(&finalizers_lock); // all the other threads are stopped, so this does not make sense, right? otherwise, failing that, this seems like plausibly a deadlock +#ifndef __clang_gcanalyzer__ if (_jl_gc_collect(ptls, collection)) { // recollect int ret = _jl_gc_collect(ptls, JL_GC_AUTO); (void)ret; assert(!ret); } +#endif JL_UNLOCK_NOGC(&finalizers_lock); } - // no-op for non-threading + gc_n_threads = 0; + gc_all_tls_states = NULL; jl_safepoint_end_gc(); jl_gc_state_set(ptls, old_state, JL_GC_STATE_WAITING); JL_PROBE_GC_END(); @@ -3322,10 +3502,8 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) // Doing this on all threads is racy (it's impossible to check // or wait for finalizers on other threads without dead lock). if (!ptls->finalizers_inhibited && ptls->locks.len == 0) { - int8_t was_in_finalizer = ptls->in_finalizer; - ptls->in_finalizer = 1; + JL_TIMING(GC, GC_Finalizers); run_finalizers(ct); - ptls->in_finalizer = was_in_finalizer; } JL_PROBE_GC_FINALIZER(); @@ -3335,14 +3513,21 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection) SetLastError(last_error); #endif errno = last_errno; + +#ifdef USE_TRACY + TracyCPlot("Heap size", jl_gc_live_bytes()); +#endif } -void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_mark_sp_t *sp) +void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_markqueue_t *mq) { - jl_gc_mark_cache_t *gc_cache = &ptls->gc_cache; - for (size_t i = 0; i < jl_n_threads; i++) - jl_gc_queue_thread_local(gc_cache, sp, jl_all_tls_states[i]); - mark_roots(gc_cache, sp); + assert(gc_n_threads); + for (size_t i = 0; i < gc_n_threads; i++) { + jl_ptls_t ptls2 = gc_all_tls_states[i]; + if (ptls2 != NULL) + gc_queue_thread_local(mq, ptls2); + } + gc_mark_roots(mq); } // allocator entry points @@ -3355,8 +3540,6 @@ JL_DLLEXPORT jl_value_t *(jl_gc_alloc)(jl_ptls_t ptls, size_t sz, void *ty) // Per-thread initialization void jl_init_thread_heap(jl_ptls_t ptls) { - if (ptls->tid == 0) - ptls->disable_gc = 1; jl_thread_heap_t *heap = &ptls->heap; jl_gc_pool_t *p = heap->norm_pools; for (int i = 0; i < JL_GC_N_POOLS; i++) { @@ -3369,7 +3552,6 @@ void jl_init_thread_heap(jl_ptls_t ptls) heap->mallocarrays = NULL; heap->mafreelist = NULL; heap->big_objects = NULL; - arraylist_new(&heap->rem_bindings, 0); heap->remset = &heap->_remset[0]; heap->last_remset = &heap->_remset[1]; arraylist_new(heap->remset, 0); @@ -3381,22 +3563,35 @@ void jl_init_thread_heap(jl_ptls_t ptls) gc_cache->perm_scanned_bytes = 0; gc_cache->scanned_bytes = 0; gc_cache->nbig_obj = 0; - size_t init_size = 1024; - gc_cache->pc_stack = (void**)malloc_s(init_size * sizeof(void*)); - gc_cache->pc_stack_end = gc_cache->pc_stack + init_size; - gc_cache->data_stack = (jl_gc_mark_data_t *)malloc_s(init_size * sizeof(jl_gc_mark_data_t)); + + // Initialize GC mark-queue + jl_gc_markqueue_t *mq = &ptls->mark_queue; + ws_queue_t *cq = &mq->chunk_queue; + ws_array_t *wsa = create_ws_array(GC_CHUNK_QUEUE_INIT_SIZE, sizeof(jl_gc_chunk_t)); + jl_atomic_store_relaxed(&cq->top, 0); + jl_atomic_store_relaxed(&cq->bottom, 0); + jl_atomic_store_relaxed(&cq->array, wsa); + ws_queue_t *q = &mq->ptr_queue; + ws_array_t *wsa2 = create_ws_array(GC_PTR_QUEUE_INIT_SIZE, sizeof(jl_value_t *)); + jl_atomic_store_relaxed(&q->top, 0); + jl_atomic_store_relaxed(&q->bottom, 0); + jl_atomic_store_relaxed(&q->array, wsa2); + arraylist_new(&mq->reclaim_set, 32); memset(&ptls->gc_num, 0, sizeof(ptls->gc_num)); - assert(gc_num.interval == default_collect_interval); jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval); } // System-wide initializations void jl_gc_init(void) { - JL_MUTEX_INIT(&finalizers_lock); + + JL_MUTEX_INIT(&heapsnapshot_lock, "heapsnapshot_lock"); + JL_MUTEX_INIT(&finalizers_lock, "finalizers_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); jl_gc_init_page(); jl_gc_debug_init(); @@ -3407,19 +3602,41 @@ void jl_gc_init(void) gc_num.interval = default_collect_interval; last_long_collect_interval = default_collect_interval; gc_num.allocd = 0; + gc_num.max_pause = 0; + gc_num.max_memory = 0; #ifdef _P64 - // on a big memory machine, set max_collect_interval to totalmem / ncores / 2 - uint64_t total_mem = uv_get_total_memory(); + total_mem = uv_get_total_memory(); uint64_t constrained_mem = uv_get_constrained_memory(); if (constrained_mem > 0 && constrained_mem < total_mem) total_mem = constrained_mem; - size_t maxmem = total_mem / jl_cpu_threads() / 2; - if (maxmem > max_collect_interval) - max_collect_interval = maxmem; + double percent; + if (total_mem < 128e9) + percent = total_mem * 2.34375e-12 + 0.6; // 60% at 0 gigs and 90% at 128 to not + else // overcommit too much on memory contrained devices + percent = 0.9; + max_total_memory = total_mem * percent; #endif - jl_gc_mark_sp_t sp = {NULL, NULL, NULL, NULL}; - gc_mark_loop(NULL, sp); + if (jl_options.heap_size_hint) + jl_gc_set_max_memory(jl_options.heap_size_hint); + +#ifdef USE_TRACY + TracyCPlotConfig("Heap size", TracyPlotFormatMemory, /* rectilinear */ 0, /* fill */ 1, /* color */ 0); +#endif + t_start = jl_hrtime(); +} + +JL_DLLEXPORT void jl_gc_set_max_memory(uint64_t max_mem) +{ + if (max_mem > 0 + && max_mem < (uint64_t)1 << (sizeof(memsize_t) * 8 - 1)) { + max_total_memory = max_mem; + } +} + +JL_DLLEXPORT uint64_t jl_gc_get_max_memory(void) +{ + return max_total_memory; } // callback for passing OOM errors from gmp @@ -3434,7 +3651,7 @@ JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz) { jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; maybe_collect(ptls); jl_atomic_store_relaxed(&ptls->gc_num.allocd, @@ -3449,7 +3666,7 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz) { jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; maybe_collect(ptls); jl_atomic_store_relaxed(&ptls->gc_num.allocd, @@ -3465,7 +3682,7 @@ JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz) jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; free(p); - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; jl_atomic_store_relaxed(&ptls->gc_num.freed, jl_atomic_load_relaxed(&ptls->gc_num.freed) + sz); @@ -3478,7 +3695,7 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size { jl_gcframe_t **pgcstack = jl_get_pgcstack(); jl_task_t *ct = jl_current_task; - if (pgcstack && ct->world_age) { + if (pgcstack != NULL && ct->world_age) { jl_ptls_t ptls = ct->ptls; maybe_collect(ptls); if (sz < old) @@ -3575,7 +3792,7 @@ JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz) #endif errno = last_errno; // jl_gc_managed_malloc is currently always used for allocating array buffers. - maybe_record_alloc_to_profile(b, sz, (jl_datatype_t*)jl_buff_tag); + maybe_record_alloc_to_profile((jl_value_t*)b, sz, (jl_datatype_t*)jl_buff_tag); return b; } @@ -3617,7 +3834,7 @@ static void *gc_managed_realloc_(jl_ptls_t ptls, void *d, size_t sz, size_t olds SetLastError(last_error); #endif errno = last_errno; - maybe_record_alloc_to_profile(b, sz, jl_gc_unknown_type_tag); + maybe_record_alloc_to_profile((jl_value_t*)b, sz, jl_gc_unknown_type_tag); return b; } @@ -3685,16 +3902,17 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o #ifdef _OS_WINDOWS_ DWORD last_error = GetLastError(); #endif - uintptr_t base = (uintptr_t)(zero ? calloc(1, sz) : malloc(sz)); - if (base == 0) + void *base = zero ? calloc(1, sz) : malloc(sz); + if (base == NULL) jl_throw(jl_memory_exception); #ifdef _OS_WINDOWS_ SetLastError(last_error); #endif errno = last_errno; jl_may_leak(base); - unsigned diff = (offset - base) % align; - return (void*)(base + diff); + assert(align > 0); + unsigned diff = (offset - (uintptr_t)base) % align; + return (void*)((char*)base + diff); } STATIC_INLINE void *gc_try_perm_alloc_pool(size_t sz, unsigned align, unsigned offset) JL_NOTSAFEPOINT @@ -3802,8 +4020,6 @@ JL_DLLEXPORT jl_value_t *jl_gc_alloc_3w(void) JL_DLLEXPORT int jl_gc_enable_conservative_gc_support(void) { - static_assert(jl_buff_tag % GC_PAGE_SZ == 0, - "jl_buff_tag must be a multiple of GC_PAGE_SZ"); if (jl_is_initialized()) { int result = jl_atomic_fetch_or(&support_conservative_marking, 1); if (!result) { @@ -3854,7 +4070,7 @@ JL_DLLEXPORT jl_value_t *jl_gc_internal_obj_base_ptr(void *p) goto valid_object; } jl_gc_pool_t *pool = - jl_all_tls_states[meta->thread_n]->heap.norm_pools + + gc_all_tls_states[meta->thread_n]->heap.norm_pools + meta->pool_n; if (meta->fl_begin_offset == (uint16_t) -1) { // case 2: this is a page on the newpages list @@ -3903,15 +4119,15 @@ JL_DLLEXPORT jl_value_t *jl_gc_internal_obj_base_ptr(void *p) goto valid_object; // We know now that the age bit reflects liveness status during // the last sweep and that the cell has not been reused since. - if (!(meta->ages[obj_id / 8] & (1 << (obj_id % 8)))) { + if (!(meta->ages[obj_id / 32] & (1 << (obj_id % 32)))) { return NULL; } // Not a freelist entry, therefore a valid object. valid_object: // We have to treat objects with type `jl_buff_tag` differently, // as they must not be passed to the usual marking functions. - // Note that jl_buff_tag is a multiple of GC_PAGE_SZ, thus it - // cannot be a type reference. + // Note that jl_buff_tag is real pointer into libjulia, + // thus it cannot be a type reference. if ((cell->header & ~(uintptr_t) 3) == jl_buff_tag) return NULL; return jl_valueof(cell); diff --git a/src/gc.h b/src/gc.h index 544486d933e10..eb20dd0ac36f6 100644 --- a/src/gc.h +++ b/src/gc.h @@ -9,6 +9,7 @@ #ifndef JL_GC_H #define JL_GC_H +#include #include #include #include @@ -24,6 +25,7 @@ #endif #endif #include "julia_assert.h" +#include "gc-heap-snapshot.h" #include "gc-alloc-profiler.h" #ifdef __cplusplus @@ -41,7 +43,6 @@ extern "C" { typedef struct { uint64_t num; uint64_t next; - uint64_t min; uint64_t interv; uint64_t max; @@ -56,7 +57,7 @@ typedef struct { jl_alloc_num_t print; } jl_gc_debug_env_t; -// This struct must be kept in sync with the Julia type of the same name in base/util.jl +// This struct must be kept in sync with the Julia type of the same name in base/timing.jl typedef struct { int64_t allocd; int64_t deferred_alloc; @@ -68,169 +69,47 @@ typedef struct { uint64_t freecall; uint64_t total_time; uint64_t total_allocd; - uint64_t since_sweep; size_t interval; int pause; int full_sweep; + uint64_t max_pause; + uint64_t max_memory; + uint64_t time_to_safepoint; + uint64_t max_time_to_safepoint; + uint64_t total_time_to_safepoint; + uint64_t sweep_time; + uint64_t mark_time; + uint64_t total_sweep_time; + uint64_t total_mark_time; } jl_gc_num_t; -enum { - GC_MARK_L_marked_obj, - GC_MARK_L_scan_only, - GC_MARK_L_finlist, - GC_MARK_L_objarray, - GC_MARK_L_array8, - GC_MARK_L_array16, - GC_MARK_L_obj8, - GC_MARK_L_obj16, - GC_MARK_L_obj32, - GC_MARK_L_stack, - GC_MARK_L_excstack, - GC_MARK_L_module_binding, - _GC_MARK_L_MAX -}; - -// The following structs (`gc_mark_*_t`) contain iterator state used for the -// scanning of various object types. -// -// The `nptr` member records the number of pointers slots referenced by -// an object to be used in the full collection heuristics as well as whether the object -// references young objects. -// `nptr >> 2` is the number of pointers fields referenced by the object. -// The lowest bit of `nptr` is set if the object references young object. -// The 2nd lowest bit of `nptr` is the GC old bits of the object after marking. -// A `0x3` in the low bits means that the object needs to be in the remset. - -// An generic object that's marked and needs to be scanned -// The metadata might need update too (depend on the PC) -typedef struct { - jl_value_t *obj; // The object - uintptr_t tag; // The tag with the GC bits masked out - uint8_t bits; // The GC bits after tagging (`bits & 1 == 1`) -} gc_mark_marked_obj_t; - -// An object array. This can come from an array, svec, or the using array or a module -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - jl_value_t **begin; // The first slot to be scanned. - jl_value_t **end; // The end address (after the last slot to be scanned) - uint32_t step; // Number of pointers to jump between marks - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_objarray_t; - -// A normal object with 8bits field descriptors -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - uint8_t *begin; // Current field descriptor. - uint8_t *end; // End of field descriptor. - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_obj8_t; - -// A normal object with 16bits field descriptors -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - uint16_t *begin; // Current field descriptor. - uint16_t *end; // End of field descriptor. - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_obj16_t; - -// A normal object with 32bits field descriptors -typedef struct { - jl_value_t *parent; // The parent object to trigger write barrier on. - uint32_t *begin; // Current field descriptor. - uint32_t *end; // End of field descriptor. - uintptr_t nptr; // See notes about `nptr` above. -} gc_mark_obj32_t; - -typedef struct { - jl_value_t **begin; // The first slot to be scanned. - jl_value_t **end; // The end address (after the last slot to be scanned) - uint8_t *rebegin; - gc_mark_obj8_t elem; -} gc_mark_array8_t; - -typedef struct { - jl_value_t **begin; // The first slot to be scanned. - jl_value_t **end; // The end address (after the last slot to be scanned) - uint16_t *rebegin; - gc_mark_obj16_t elem; -} gc_mark_array16_t; - -// Stack frame -typedef struct { - jl_gcframe_t *s; // The current stack frame - uint32_t i; // The current slot index in the frame - uint32_t nroots; // `nroots` fields in the frame - // Parameters to mark the copy_stack range. - uintptr_t offset; - uintptr_t lb; - uintptr_t ub; -} gc_mark_stackframe_t; - -// Exception stack data -typedef struct { - jl_excstack_t *s; // Stack of exceptions - size_t itr; // Iterator into exception stack - size_t bt_index; // Current backtrace buffer entry index - size_t jlval_index; // Index into GC managed values for current bt entry -} gc_mark_excstack_t; - -// Module bindings. This is also the beginning of module scanning. -// The loop will start marking other references in a module after the bindings are marked -typedef struct { - jl_module_t *parent; // The parent module to trigger write barrier on. - jl_binding_t **begin; // The first slot to be scanned. - jl_binding_t **end; // The end address (after the last slot to be scanned) - uintptr_t nptr; // See notes about `nptr` above. - uint8_t bits; // GC bits of the module (the bits to mark the binding buffer with) -} gc_mark_binding_t; - -// Finalizer (or object) list -typedef struct { - jl_value_t **begin; - jl_value_t **end; -} gc_mark_finlist_t; - -// This is used to determine the max size of the data objects on the data stack. -// We'll use this size to determine the size of the data stack corresponding to a -// PC stack size. Since the data objects are not all of the same size, we'll waste -// some memory on the data stack this way but that size is unlikely going to be significant. -union _jl_gc_mark_data { - gc_mark_marked_obj_t marked; - gc_mark_objarray_t objarray; - gc_mark_array8_t array8; - gc_mark_array16_t array16; - gc_mark_obj8_t obj8; - gc_mark_obj16_t obj16; - gc_mark_obj32_t obj32; - gc_mark_stackframe_t stackframe; - gc_mark_excstack_t excstackframe; - gc_mark_binding_t binding; - gc_mark_finlist_t finlist; -}; - -// Pop a data struct from the mark data stack (i.e. decrease the stack pointer) -// This should be used after dispatch and therefore the pc stack pointer is already popped from -// the stack. -STATIC_INLINE void *gc_pop_markdata_(jl_gc_mark_sp_t *sp, size_t size) -{ - jl_gc_mark_data_t *data = (jl_gc_mark_data_t *)(((char*)sp->data) - size); - sp->data = data; - return data; -} -#define gc_pop_markdata(sp, type) ((type*)gc_pop_markdata_(sp, sizeof(type))) - -// Re-push a frame to the mark stack (both data and pc) -// The data and pc are expected to be on the stack (or updated in place) already. -// Mainly useful to pause the current scanning in order to scan an new object. -STATIC_INLINE void *gc_repush_markdata_(jl_gc_mark_sp_t *sp, size_t size) JL_NOTSAFEPOINT -{ - jl_gc_mark_data_t *data = sp->data; - sp->pc++; - sp->data = (jl_gc_mark_data_t *)(((char*)sp->data) + size); - return data; -} -#define gc_repush_markdata(sp, type) ((type*)gc_repush_markdata_(sp, sizeof(type))) +// Array chunks (work items representing suffixes of +// large arrays of pointers left to be marked) + +typedef enum { + GC_empty_chunk = 0, // for sentinel representing no items left in chunk queue + GC_objary_chunk, // for chunk of object array + GC_ary8_chunk, // for chunk of array with 8 bit field descriptors + GC_ary16_chunk, // for chunk of array with 16 bit field descriptors + GC_finlist_chunk, // for chunk of finalizer list +} gc_chunk_id_t; + +typedef struct _jl_gc_chunk_t { + gc_chunk_id_t cid; + struct _jl_value_t *parent; // array owner + struct _jl_value_t **begin; // pointer to first element that needs scanning + struct _jl_value_t **end; // pointer to last element that needs scanning + void *elem_begin; // used to scan pointers within objects when marking `ary8` or `ary16` + void *elem_end; // used to scan pointers within objects when marking `ary8` or `ary16` + uint32_t step; // step-size used when marking objarray + uintptr_t nptr; // (`nptr` & 0x1) if array has young element and (`nptr` & 0x2) if array owner is old +} jl_gc_chunk_t; + +#define GC_CHUNK_BATCH_SIZE (1 << 16) // maximum number of references that can be processed + // without creating a chunk + +#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 // layout for big (>2k) objects @@ -298,7 +177,7 @@ typedef struct { uint16_t fl_end_offset; // offset of last free object in this page uint16_t thread_n; // thread id of the heap that owns this page char *data; - uint8_t *ages; + uint32_t *ages; } jl_gc_pagemeta_t; // Page layout: @@ -370,7 +249,7 @@ typedef struct { int ub; } pagetable_t; -#ifdef __clang_gcanalyzer__ +#ifdef __clang_gcanalyzer__ /* clang may not have __builtin_ffs */ unsigned ffs_u32(uint32_t bitvec) JL_NOTSAFEPOINT; #else STATIC_INLINE unsigned ffs_u32(uint32_t bitvec) @@ -385,6 +264,8 @@ extern bigval_t *big_objects_marked; extern arraylist_t finalizer_list_marked; extern arraylist_t to_finalize; extern int64_t lazy_freed_pages; +extern int gc_n_threads; +extern jl_ptls_t* gc_all_tls_states; STATIC_INLINE bigval_t *bigval_header(jl_taggedvalue_t *o) JL_NOTSAFEPOINT { @@ -497,23 +378,20 @@ STATIC_INLINE void gc_big_object_link(bigval_t *hdr, bigval_t **list) JL_NOTSAFE *list = hdr; } -STATIC_INLINE void gc_mark_sp_init(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) -{ - sp->pc = gc_cache->pc_stack; - sp->data = gc_cache->data_stack; - sp->pc_start = gc_cache->pc_stack; - sp->pc_end = gc_cache->pc_stack_end; -} - -void gc_mark_queue_all_roots(jl_ptls_t ptls, jl_gc_mark_sp_t *sp); -void gc_mark_queue_finlist(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, - arraylist_t *list, size_t start); -void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp); +extern uv_mutex_t gc_threads_lock; +extern uv_cond_t gc_threads_cond; +extern _Atomic(int) gc_n_threads_marking; +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, 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 sweep_stack_pools(void); void jl_gc_debug_init(void); -extern void *gc_mark_label_addrs[_GC_MARK_L_MAX]; - // GC pages void jl_gc_init_page(void); @@ -557,6 +435,10 @@ void gc_time_mark_pause(int64_t t0, int64_t scanned_bytes, void gc_time_sweep_pause(uint64_t gc_end_t, int64_t actual_allocd, int64_t live_bytes, int64_t estimate_freed, int sweep_full); +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); #else #define gc_time_pool_start() STATIC_INLINE void gc_time_count_page(int freedall, int pg_skpd) JL_NOTSAFEPOINT @@ -582,6 +464,8 @@ STATIC_INLINE void gc_time_count_mallocd_array(int bits) JL_NOTSAFEPOINT #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) #endif #ifdef MEMFENCE @@ -592,7 +476,6 @@ static inline void gc_verify_tags(void) } #endif - #ifdef GC_VERIFY extern jl_value_t *lostval; void gc_verify(jl_ptls_t ptls); @@ -632,9 +515,10 @@ extern int gc_verifying; #define verify_parent2(ty,obj,slot,arg1,arg2) do {} while (0) #define gc_verifying (0) #endif -int gc_slot_to_fieldidx(void *_obj, void *slot); -int gc_slot_to_arrayidx(void *_obj, void *begin); -NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_offset); + +int gc_slot_to_fieldidx(void *_obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT; +int gc_slot_to_arrayidx(void *_obj, void *begin) JL_NOTSAFEPOINT; +NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_markqueue_t *mq, int offset) JL_NOTSAFEPOINT; #ifdef GC_DEBUG_ENV JL_DLLEXPORT extern jl_gc_debug_env_t jl_gc_debug_env; diff --git a/src/gf.c b/src/gf.c index 01d03fe77394f..6fcaf6ce9a341 100644 --- a/src/gf.c +++ b/src/gf.c @@ -27,6 +27,9 @@ extern "C" { JL_DLLEXPORT _Atomic(size_t) jl_world_counter = 1; // uses atomic acquire/release JL_DLLEXPORT size_t jl_get_world_counter(void) JL_NOTSAFEPOINT { + jl_task_t *ct = jl_current_task; + if (ct->ptls->in_pure_callback) + return ~(size_t)0; return jl_atomic_load_acquire(&jl_world_counter); } @@ -35,6 +38,36 @@ JL_DLLEXPORT size_t jl_get_tls_world_age(void) JL_NOTSAFEPOINT return jl_current_task->world_age; } +// Compute the maximum number of times to unroll Varargs{T}, based on +// m->max_varargs (if specified) or a heuristic based on the maximum +// number of non-varargs arguments in the provided method table. +// +// If provided, `may_increase` is set to 1 if the returned value is +// heuristic-based and has a chance of increasing in the future. +static size_t get_max_varargs( + jl_method_t *m, + jl_methtable_t *kwmt, + jl_methtable_t *mt, + uint8_t *may_increase) JL_NOTSAFEPOINT +{ + size_t max_varargs = 1; + if (may_increase != NULL) + *may_increase = 0; + + if (m->max_varargs != UINT8_MAX) + max_varargs = m->max_varargs; + else if (kwmt != NULL && kwmt != jl_type_type_mt && kwmt != jl_nonfunction_mt && kwmt != jl_kwcall_mt) { + if (may_increase != NULL) + *may_increase = 1; // `max_args` can increase as new methods are inserted + + max_varargs = jl_atomic_load_relaxed(&kwmt->max_args) + 2; + if (mt == jl_kwcall_mt) + max_varargs += 2; + max_varargs -= m->nargs; + } + return max_varargs; +} + /// ----- Handling for Julia callbacks ----- /// JL_DLLEXPORT int8_t jl_is_in_pure_context(void) @@ -99,19 +132,39 @@ 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 -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) +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 && m->unspecialized) - return m->unspecialized; // handle builtin methods + if (m->sig == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&m->unspecialized) != NULL && m != jl_opaque_closure_method) + 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; - for (int locked = 0; ; locked++) { - jl_array_t *speckeyset = jl_atomic_load_acquire(&m->speckeyset); - jl_svec_t *specializations = jl_atomic_load_acquire(&m->specializations); - size_t i, cl = jl_svec_len(specializations); + jl_array_t *speckeyset = NULL; + jl_value_t *specializations = NULL; + size_t i = -1, cl = 0, lastcl; + for (int locked = 0; locked < 2; locked++) { + if (locked) { + if (!sparams) // can't insert without knowing this + return NULL; + JL_LOCK(&m->writelock); + } + lastcl = cl; + speckeyset = jl_atomic_load_acquire(&m->speckeyset); + specializations = jl_atomic_load_relaxed(&m->specializations); + if (specializations == (jl_value_t*)jl_emptysvec) + continue; + if (!jl_is_svec(specializations)) { + jl_method_instance_t *mi = (jl_method_instance_t*)specializations; + if (jl_types_equal(mi->specTypes, type)) { + if (locked) + JL_UNLOCK(&m->writelock); + return mi; + } + continue; + } + cl = jl_svec_len(specializations); if (hv) { - ssize_t idx = jl_smallintset_lookup(speckeyset, speccache_eq, type, specializations, hv); + ssize_t idx = jl_smallintset_lookup(speckeyset, speccache_eq, type, (jl_svec_t*)specializations, hv); if (idx != -1) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, idx); if (locked) @@ -122,8 +175,9 @@ JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m J else { _Atomic(jl_method_instance_t*) *data = (_Atomic(jl_method_instance_t*)*)jl_svec_data(specializations); JL_GC_PUSH1(&specializations); // clang-sa doesn't realize this loop uses specializations - for (i = cl; i > 0; i--) { - jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i - 1]); + // the last lastcl-i-1 elements are already checked when locked, so start search with the new elements only + for (i += cl - lastcl; i > 0; i--) { + jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i]); if ((jl_value_t*)mi == jl_nothing) break; if (jl_types_equal(mi->specTypes, type)) { @@ -133,55 +187,79 @@ JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m J return mi; } } + // i points to the first unchecked element, or the place to insert JL_GC_POP(); } - if (!sparams) // can't insert without knowing this - return NULL; - if (!locked) { - JL_LOCK(&m->writelock); + } + jl_method_instance_t *mi = mi_insert ? mi_insert : jl_get_specialized(m, type, sparams); + if (specializations == (jl_value_t*)jl_emptysvec) { + jl_atomic_store_release(&m->specializations, (jl_value_t*)mi); + jl_gc_wb(m, mi); + } + else { + JL_GC_PUSH1(&mi); + if (!jl_is_svec(specializations)) { + jl_method_instance_t *mi = (jl_method_instance_t*)specializations; + jl_value_t *type = mi->specTypes; + jl_value_t *ut = jl_is_unionall(type) ? jl_unwrap_unionall(type) : type; + uint_t hv = ((jl_datatype_t*)ut)->hash; + cl = 7; + i = cl - 1; + specializations = (jl_value_t*)jl_svec_fill(cl, jl_nothing); + jl_svecset(specializations, hv ? 0 : i--, mi); + 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); } - else { - if (hv) { - _Atomic(jl_method_instance_t*) *data = (_Atomic(jl_method_instance_t*)*)jl_svec_data(specializations); - for (i = 0; i < cl; i++) { - jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i]); - if ((jl_value_t*)mi == jl_nothing) - break; - assert(!jl_types_equal(mi->specTypes, type)); - } - } - jl_method_instance_t *mi = jl_get_specialized(m, type, sparams); - JL_GC_PUSH1(&mi); - if (hv ? (i + 1 >= cl || jl_svecref(specializations, i + 1) != jl_nothing) : (i <= 1 || jl_svecref(specializations, i - 2) != jl_nothing)) { - size_t ncl = cl < 8 ? 8 : (cl*3)>>1; - jl_svec_t *nc = jl_alloc_svec_uninit(ncl); - if (i > 0) - memcpy((char*)jl_svec_data(nc), jl_svec_data(specializations), sizeof(void*) * i); - for (int j = 0; j < ncl - cl; j++) - jl_svecset(nc, j+i, jl_nothing); - if (i < cl) - memcpy((char*)jl_svec_data(nc) + sizeof(void*) * (i + ncl - cl), - (char*)jl_svec_data(specializations) + sizeof(void*) * i, - sizeof(void*) * (cl - i)); - jl_atomic_store_release(&m->specializations, nc); - jl_gc_wb(m, nc); - specializations = nc; - if (!hv) - i += ncl - cl; + if (hv) { + _Atomic(jl_method_instance_t*) *data = (_Atomic(jl_method_instance_t*)*)jl_svec_data(specializations); + for (i = 0; i < cl; i++) { + jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i]); + if ((jl_value_t*)mi == jl_nothing) + break; + assert(!jl_types_equal(mi->specTypes, type)); } + // i points at the place to insert + } + if (hv ? (i + 1 >= cl || jl_svecref(specializations, i + 1) != jl_nothing) : (i <= 1 || jl_svecref(specializations, i - 2) != jl_nothing)) { + size_t ncl = cl < 7 ? 7 : (cl*3)>>1; + jl_svec_t *nc = jl_alloc_svec_uninit(ncl); + if (i > 0) + memcpy((char*)jl_svec_data(nc), jl_svec_data(specializations), sizeof(void*) * i); + for (int j = 0; j < ncl - cl; j++) + jl_svecset(nc, j+i, jl_nothing); + if (i < cl) + memcpy((char*)jl_svec_data(nc) + sizeof(void*) * (i + ncl - cl), + (char*)jl_svec_data(specializations) + sizeof(void*) * i, + sizeof(void*) * (cl - i)); + specializations = (jl_value_t*)nc; + jl_atomic_store_release(&m->specializations, specializations); + jl_gc_wb(m, specializations); if (!hv) - i -= 1; - assert(jl_svecref(specializations, i) == jl_nothing); - jl_svecset(specializations, i, mi); // jl_atomic_store_release? - if (hv) { - // TODO: fuse lookup and insert steps? - jl_smallintset_insert(&m->speckeyset, (jl_value_t*)m, speccache_hash, i, specializations); - } - JL_UNLOCK(&m->writelock); - JL_GC_POP(); - return mi; + i += ncl - cl; } + 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_GC_POP(); } + JL_UNLOCK(&m->writelock); // may gc + return mi; +} + +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) +{ + return jl_specializations_get_linfo_(m, type, sparams, NULL); +} + +jl_method_instance_t *jl_specializations_get_or_insert(jl_method_instance_t *mi) +{ + jl_method_t *m = mi->def.method; + jl_value_t *type = mi->specTypes; + jl_svec_t *sparams = mi->sparam_vals; + return jl_specializations_get_linfo_(m, type, sparams, mi); } JL_DLLEXPORT jl_value_t *jl_specializations_lookup(jl_method_t *m, jl_value_t *type) @@ -194,8 +272,11 @@ JL_DLLEXPORT jl_value_t *jl_specializations_lookup(jl_method_t *m, jl_value_t *t JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *type, size_t world) { + // 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}; - jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(mt->defs, &search, /*offs*/0, /*subtype*/0); + 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; return sf->func.value; @@ -207,10 +288,8 @@ 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, - uint8_t ipo_effects, uint8_t effects, jl_value_t *argescapes, + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, uint8_t relocatability); -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_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { @@ -237,18 +316,18 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a newentry = jl_typemap_alloc(jl_anytuple_type, NULL, jl_emptysvec, (jl_value_t*)m, 1, ~(size_t)0); - jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, 0); + 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); - m->unspecialized = mi; + 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_mi_cache_insert(mi, codeinst); - codeinst->specptr.fptr1 = fptr; - codeinst->invoke = jl_fptr_args; + jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); + jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); newentry = jl_typemap_alloc(jl_anytuple_type, NULL, jl_emptysvec, (jl_value_t*)mi, 1, ~(size_t)0); @@ -265,27 +344,30 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a // 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_TIMING(INFERENCE); if (jl_typeinf_func == NULL) return NULL; - if (jl_is_method(mi->def.method) && mi->def.method->unspecialized == mi) - return NULL; // avoid inferring the unspecialized method - static int in_inference; - if (in_inference > 2) + jl_task_t *ct = jl_current_task; + if (ct->reentrant_timing & 0b1000) { + // We must avoid attempting to re-enter inference here + assert(0 && "attempted to enter inference while writing out image"); + abort(); + } + // In case we use higher bits later, mask them out + if ((ct->reentrant_timing & 0b1111) >= 0b110) return NULL; jl_code_info_t *src = NULL; #ifdef ENABLE_INFERENCE if (mi->inInference && !force) return NULL; - if (jl_is_method(mi->def.method) && mi->def.method->unspecialized == mi) - return NULL; // be careful never to infer the unspecialized method, this would not be valid - + JL_TIMING(INFERENCE, INFERENCE); jl_value_t **fargs; JL_GC_PUSHARGS(fargs, 3); fargs[0] = (jl_value_t*)jl_typeinf_func; fargs[1] = (jl_value_t*)mi; fargs[2] = jl_box_ulong(world); + + jl_timing_show_method_instance(mi, JL_TIMING_CURRENT_BLOCK); #ifdef TRACE_INFERENCE if (mi->specTypes != (jl_value_t*)jl_emptytuple_type) { jl_printf(JL_STDERR,"inference on "); @@ -293,7 +375,6 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) jl_printf(JL_STDERR, "\n"); } #endif - jl_task_t *ct = jl_current_task; int last_errno = errno; #ifdef _OS_WINDOWS_ DWORD last_error = GetLastError(); @@ -301,19 +382,35 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) size_t last_age = ct->world_age; ct->world_age = jl_typeinf_world; mi->inInference = 1; - in_inference++; + // first bit is for reentrant timing, + // so adding 1 to the bit above performs + // inference reentrancy counter addition. + // Note that this is only safe because + // the counter varies from 0-3; if we + // increase that limit, we'll need to + // allocate another bit for the counter. + ct->reentrant_timing += 0b10; JL_TRY { src = (jl_code_info_t*)jl_apply(fargs, 3); } JL_CATCH { - jl_printf((JL_STREAM*)STDERR_FILENO, "Internal error: encountered unexpected error in runtime:\n"); - jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); - jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); - jlbacktrace(); // written to STDERR_FILENO + jl_value_t *e = jl_current_exception(); + 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, "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_static_show((JL_STREAM*)STDERR_FILENO, e); + jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); + jlbacktrace(); // written to STDERR_FILENO + } src = NULL; } ct->world_age = last_age; - in_inference--; + ct->reentrant_timing -= 0b10; mi->inInference = 0; #ifdef _OS_WINDOWS_ SetLastError(last_error); @@ -325,6 +422,7 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t *mi, size_t world, int force) } JL_GC_POP(); #endif + return src; } @@ -343,14 +441,15 @@ JL_DLLEXPORT jl_value_t *jl_rettype_inferred(jl_method_instance_t *mi, size_t mi 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 = codeinst->inferred; - if (code && (code == jl_nothing || jl_ir_flag_inferred((jl_array_t*)code))) + 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; } codeinst = jl_atomic_load_relaxed(&codeinst->next); } return (jl_value_t*)jl_nothing; } +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( @@ -377,7 +476,7 @@ 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, - uint8_t ipo_effects, uint8_t effects, jl_value_t *argescapes, + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, uint8_t relocatability /*, jl_array_t *edges, int absolute_max*/) { @@ -389,22 +488,22 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( codeinst->min_world = min_world; codeinst->max_world = max_world; codeinst->rettype = rettype; - codeinst->inferred = inferred; + jl_atomic_store_release(&codeinst->inferred, inferred); //codeinst->edges = NULL; if ((const_flags & 2) == 0) inferred_const = NULL; codeinst->rettype_const = inferred_const; - codeinst->invoke = NULL; - codeinst->specptr.fptr = NULL; + jl_atomic_store_relaxed(&codeinst->specptr.fptr, NULL); + jl_atomic_store_relaxed(&codeinst->invoke, NULL); if ((const_flags & 1) != 0) { assert(const_flags & 2); - codeinst->invoke = jl_fptr_const_return; + jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_const_return); } - codeinst->isspecsig = 0; - codeinst->precompile = 0; - codeinst->next = NULL; + jl_atomic_store_relaxed(&codeinst->specsigflags, 0); + jl_atomic_store_relaxed(&codeinst->precompile, 0); + jl_atomic_store_relaxed(&codeinst->next, NULL); codeinst->ipo_purity_bits = ipo_effects; - codeinst->purity_bits = effects; + jl_atomic_store_relaxed(&codeinst->purity_bits, effects); codeinst->argescapes = argescapes; codeinst->relocatability = relocatability; return codeinst; @@ -416,7 +515,10 @@ JL_DLLEXPORT void jl_mi_cache_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMEN JL_GC_PUSH1(&ci); if (jl_is_method(mi->def.method)) JL_LOCK(&mi->def.method->writelock); - ci->next = mi->cache; + jl_code_instance_t *oldci = jl_atomic_load_relaxed(&mi->cache); + jl_atomic_store_relaxed(&ci->next, oldci); + if (oldci) + jl_gc_wb(ci, oldci); jl_atomic_store_release(&mi->cache, ci); jl_gc_wb(mi, ci); if (jl_is_method(mi->def.method)) @@ -427,9 +529,19 @@ JL_DLLEXPORT void jl_mi_cache_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMEN static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) { - jl_svec_t *specializations = def->func.method->specializations; - size_t i, l = jl_svec_len(specializations); size_t world = jl_atomic_load_acquire(&jl_world_counter); + jl_value_t *specializations = jl_atomic_load_relaxed(&def->func.method->specializations); + if (specializations == (jl_value_t*)jl_emptysvec) + return 1; + 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) + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); + return 1; + } + size_t i, l = jl_svec_len(specializations); + JL_GC_PUSH1(&specializations); for (i = 0; i < l; i++) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); if ((jl_value_t*)mi != jl_nothing) { @@ -438,26 +550,32 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); } } + JL_GC_POP(); return 1; } -static int foreach_mtable_in_module( +int foreach_mtable_in_module( jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env) { - size_t i; - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner == m && b->value && b->constp) { - jl_value_t *v = jl_unwrap_unionall(b->value); - if (jl_is_datatype(v)) { - jl_typename_t *tn = ((jl_datatype_t*)v)->name; - if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) { + 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); + if ((void*)b == jl_nothing) + break; + jl_sym_t *name = b->globalref->name; + if (jl_atomic_load_relaxed(&b->owner) == b && b->constp) { + jl_value_t *v = jl_atomic_load_relaxed(&b->value); + if (v) { + jl_value_t *uw = jl_unwrap_unionall(v); + if (jl_is_datatype(uw)) { + jl_typename_t *tn = ((jl_datatype_t*)uw)->name; + if (tn->module == m && tn->name == name && tn->wrapper == v) { + // this is the original/primary binding for the type (name/wrapper) jl_methtable_t *mt = tn->mt; if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { + assert(mt->module == m); if (!visit(mt, env)) return 0; } @@ -465,14 +583,24 @@ static int foreach_mtable_in_module( } else if (jl_is_module(v)) { jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == b->name) { + if (child != m && child->parent == m && child->name == name) { // this is the original/primary binding for the submodule if (!foreach_mtable_in_module(child, visit, env)) return 0; } } + else if (jl_is_mtable(v)) { + jl_methtable_t *mt = (jl_methtable_t*)v; + if (mt->module == m && mt->name == name) { + // this is probably an external method table here, so let's + // assume so as there is no way to precisely distinguish them + if (!visit(mt, env)) + return 0; + } + } } } + table = jl_atomic_load_relaxed(&m->bindings); } return 1; } @@ -510,16 +638,17 @@ int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), voi 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 - mt->leafcache = (jl_array_t*)jl_an_empty_vec_any; - mt->cache = jl_nothing; + jl_atomic_store_release(&mt->leafcache, (jl_array_t*)jl_an_empty_vec_any); + jl_atomic_store_release(&mt->cache, jl_nothing); } - jl_typemap_visitor(mt->defs, get_method_unspec_list, env); + jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), get_method_unspec_list, env); return 1; } -jl_function_t *jl_typeinf_func = NULL; +jl_function_t *jl_typeinf_func JL_GLOBALLY_ROOTED = NULL; JL_DLLEXPORT size_t jl_typeinf_world = 1; JL_DLLEXPORT void jl_set_typeinf_func(jl_value_t *f) @@ -553,15 +682,12 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) JL_NOTSAFEPOINT { sig = jl_unwrap_unionall(sig); size_t len = jl_nparams(sig); - if (len == 0) - return NULL; if (i < len-1) return jl_tparam(sig, i); - if (jl_is_vararg(jl_tparam(sig, len-1))) - return jl_unwrap_vararg(jl_tparam(sig, len-1)); - if (i == len-1) - return jl_tparam(sig, i); - return NULL; + jl_value_t *p = jl_tparam(sig, len-1); + if (jl_is_vararg(p)) + p = jl_unwrap_vararg(p); + return p; } // if concrete_match returns false, the sig may specify `Type{T::DataType}`, while the `tt` contained DataType @@ -582,6 +708,45 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) JL_NOTSAFEPOINT // return 1; //} +static jl_value_t *inst_varargp_in_env(jl_value_t *decl, jl_svec_t *sparams) +{ + jl_value_t *unw = jl_unwrap_unionall(decl); + jl_value_t *vm = jl_tparam(unw, jl_nparams(unw) - 1); + assert(jl_is_vararg(vm)); + int nsp = jl_svec_len(sparams); + if (nsp > 0 && jl_has_free_typevars(vm)) { + JL_GC_PUSH1(&vm); + assert(jl_subtype_env_size(decl) == nsp); + vm = jl_instantiate_type_in_env(vm, (jl_unionall_t*)decl, jl_svec_data(sparams)); + assert(jl_is_vararg(vm)); + // rewrap_unionall(lastdeclt, sparams) if any sparams isa TypeVar + // for example, `Tuple{Vararg{Union{Nothing,Int,Val{T}}}} where T` + // and the user called it with `Tuple{Vararg{Union{Nothing,Int},N}}`, then T is unbound + jl_value_t **sp = jl_svec_data(sparams); + while (jl_is_unionall(decl)) { + jl_tvar_t *v = (jl_tvar_t*)*sp; + if (jl_is_typevar(v)) { + // must unwrap and re-wrap Vararg object explicitly here since jl_type_unionall handles it differently + jl_value_t *T = ((jl_vararg_t*)vm)->T; + jl_value_t *N = ((jl_vararg_t*)vm)->N; + int T_has_tv = T && jl_has_typevar(T, v); + int N_has_tv = N && jl_has_typevar(N, v); // n.b. JL_VARARG_UNBOUND check means this should be false + assert(!N_has_tv || N == (jl_value_t*)v); + vm = T_has_tv ? jl_type_unionall(v, T) : T; + if (N_has_tv) + N = NULL; + vm = (jl_value_t*)jl_wrap_vararg(vm, N); // this cannot throw for these inputs + } + sp++; + decl = ((jl_unionall_t*)decl)->body; + nsp--; + } + assert(nsp == 0); + JL_GC_POP(); + } + return vm; +} + static jl_value_t *ml_matches(jl_methtable_t *mt, jl_tupletype_t *type, int lim, int include_ambiguous, int intersections, size_t world, int cache_result, @@ -589,37 +754,73 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, // get the compilation signature specialization for this method static void jl_compilation_sig( - jl_tupletype_t *const tt, // the original tupletype of the call : this is expected to be a relative simple type (no Varags, Union, UnionAll, etc.) + jl_tupletype_t *const tt, // the original tupletype of the call (or DataType from precompile) jl_svec_t *sparams, jl_method_t *definition, - intptr_t nspec, + intptr_t max_varargs, // output: jl_svec_t **const newparams JL_REQUIRE_ROOTED_SLOT) { + assert(jl_is_tuple_type(tt)); + jl_value_t *decl = definition->sig; + size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); + size_t nspec = max_varargs + nargs; + if (definition->generator) { // staged functions aren't optimized // so assume the caller was intelligent about calling us return; } - if (definition->sig == (jl_value_t*)jl_anytuple_type && definition->unspecialized) { + + if (decl == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&definition->unspecialized)) { *newparams = jl_anytuple_type->parameters; // handle builtin methods return; } - jl_value_t *decl = definition->sig; - assert(jl_is_tuple_type(tt)); + // some early sanity checks size_t i, np = jl_nparams(tt); - size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); + switch (jl_va_tuple_kind((jl_datatype_t*)decl)) { + case JL_VARARG_NONE: + if (jl_is_va_tuple(tt)) + // odd + return; + if (np != nargs) + // there are not enough input parameters to make this into a compilation sig + return; + break; + case JL_VARARG_INT: + case JL_VARARG_BOUND: + if (jl_is_va_tuple(tt)) + // the length needed is not known, but required for compilation + return; + if (np < nargs - 1) + // there are not enough input parameters to make this into a compilation sig + return; + break; + case JL_VARARG_UNBOUND: + if (np < nspec && jl_is_va_tuple(tt)) + // there are insufficient given parameters for jl_isa_compileable_sig now to like this type + // (there were probably fewer methods defined when we first selected this signature, or + // the max varargs limit was not reached indicating the type is already fully-specialized) + return; + break; + } + + jl_value_t *type_i = NULL; + JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(tt, i); + if (jl_is_vararg(elt)) + elt = jl_unwrap_vararg(elt); jl_value_t *decl_i = jl_nth_slot_type(decl, i); + type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); - if (jl_is_kind(decl_i)) { + if (jl_is_kind(type_i)) { // if we can prove the match was against the kind (not a Type) // we want to put that in the cache instead if (!*newparams) *newparams = jl_svec_copy(tt->parameters); - elt = decl_i; + elt = type_i; jl_svecset(*newparams, i, elt); } else if (jl_is_type_type(elt)) { @@ -628,7 +829,7 @@ static void jl_compilation_sig( // and the result of matching the type signature // needs to be restricted to the concrete type 'kind' jl_value_t *kind = jl_typeof(jl_tparam0(elt)); - if (jl_subtype(kind, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) { + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) { // if we can prove the match was against the kind (not a Type) // it's simpler (and thus better) to put that cache instead if (!*newparams) *newparams = jl_svec_copy(tt->parameters); @@ -640,7 +841,7 @@ static void jl_compilation_sig( // not triggered for isdispatchtuple(tt), this attempts to handle // some cases of adapting a random signature into a compilation signature // if we get a kind, where we don't expect to accept one, widen it to something more expected (Type{T}) - if (!(jl_subtype(elt, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i))) { + if (!(jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i))) { if (!*newparams) *newparams = jl_svec_copy(tt->parameters); elt = (jl_value_t*)jl_type_type; jl_svecset(*newparams, i, elt); @@ -658,16 +859,14 @@ static void jl_compilation_sig( if (!jl_has_free_typevars(decl_i) && !jl_is_kind(decl_i)) { if (decl_i != elt) { if (!*newparams) *newparams = jl_svec_copy(tt->parameters); + // n.b. it is possible here that !(elt <: decl_i), if elt was something unusual from intersection + // so this might narrow the result slightly, though still being compatible with the declared signature jl_svecset(*newparams, i, (jl_value_t*)decl_i); } continue; } } - if (jl_is_vararg(elt)) { - continue; - } - if (jl_types_equal(elt, (jl_value_t*)jl_type_type)) { // elt == Type{T} where T // not triggered for isdispatchtuple(tt), this attempts to handle // some cases of adapting a random signature into a compilation signature @@ -679,7 +878,7 @@ static void jl_compilation_sig( jl_svecset(*newparams, i, jl_type_type); } else if (jl_is_type_type(elt)) { // elt isa Type{T} - if (very_general_type(decl_i)) { + if (!jl_has_free_typevars(decl_i) && very_general_type(type_i)) { /* Here's a fairly simple heuristic: if this argument slot's declared type is general (Type or Any), @@ -718,15 +917,13 @@ static void jl_compilation_sig( */ if (!*newparams) *newparams = jl_svec_copy(tt->parameters); if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(decl_i, (jl_value_t*)jl_type_type); + jl_value_t *di = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); assert(di != (jl_value_t*)jl_bottom_type); // issue #11355: DataType has a UID and so would take precedence in the cache if (jl_is_kind(di)) jl_svecset(*newparams, i, (jl_value_t*)jl_type_type); else jl_svecset(*newparams, i, di); - // TODO: recompute static parameter values, so in extreme cases we - // can give `T=Type` instead of `T=Type{Type{Type{...`. /* make editors happy:}}} */ } else { jl_svecset(*newparams, i, (jl_value_t*)jl_type_type); @@ -735,14 +932,15 @@ static void jl_compilation_sig( } int notcalled_func = (i_arg > 0 && i_arg <= 8 && !(definition->called & (1 << (i_arg - 1))) && + !jl_has_free_typevars(decl_i) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (notcalled_func && (decl_i == (jl_value_t*)jl_any_type || - decl_i == (jl_value_t*)jl_function_type || - (jl_is_uniontype(decl_i) && // Base.Callable - ((((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_type_type) || - (((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_type_type))))) { + if (notcalled_func && (type_i == (jl_value_t*)jl_any_type || + type_i == (jl_value_t*)jl_function_type || + (jl_is_uniontype(type_i) && // Base.Callable + ((((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_type_type) || + (((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_type_type))))) { // and attempt to despecialize types marked Function, Callable, or Any // when called with a subtype of Function but is not called if (!*newparams) *newparams = jl_svec_copy(tt->parameters); @@ -754,16 +952,16 @@ static void jl_compilation_sig( // in general, here we want to find the biggest type that's not a // supertype of any other method signatures. so far we are conservative // and the types we find should be bigger. - if (jl_nparams(tt) >= nspec && jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND) { - jl_svec_t *limited = jl_alloc_svec(nspec); - JL_GC_PUSH1(&limited); + if (np >= nspec && jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND) { if (!*newparams) *newparams = tt->parameters; - size_t i; - for (i = 0; i < nspec - 1; i++) { - jl_svecset(limited, i, jl_svecref(*newparams, i)); - } - jl_value_t *lasttype = jl_svecref(*newparams, i - 1); - // if all subsequent arguments are subtypes of lasttype, specialize + if (max_varargs > 0) { + type_i = jl_svecref(*newparams, nspec - 2); + } else { + // If max varargs is zero, always specialize to (Any...) since + // there is no preceding parameter to use for `type_i` + type_i = jl_bottom_type; + } + // if all subsequent arguments are subtypes of type_i, specialize // on that instead of decl. for example, if decl is // (Any...) // and type is @@ -771,60 +969,48 @@ static void jl_compilation_sig( // then specialize as (Symbol...), but if type is // (Symbol, Int32, Expr) // then specialize as (Any...) - size_t j = i; + size_t j = nspec - 1; int all_are_subtypes = 1; for (; j < jl_svec_len(*newparams); j++) { jl_value_t *paramj = jl_svecref(*newparams, j); if (jl_is_vararg(paramj)) paramj = jl_unwrap_vararg(paramj); - if (!jl_subtype(paramj, lasttype)) { + if (!jl_subtype(paramj, type_i)) { all_are_subtypes = 0; break; } } if (all_are_subtypes) { // avoid Vararg{Type{Type{...}}} - if (jl_is_type_type(lasttype) && jl_is_type_type(jl_tparam0(lasttype))) - lasttype = (jl_value_t*)jl_type_type; - jl_svecset(limited, i, jl_wrap_vararg(lasttype, (jl_value_t*)NULL)); + if (jl_is_type_type(type_i) && jl_is_type_type(jl_tparam0(type_i))) + type_i = (jl_value_t*)jl_type_type; + type_i = (jl_value_t*)jl_wrap_vararg(type_i, (jl_value_t*)NULL); // this cannot throw for these inputs } else { - jl_value_t *unw = jl_unwrap_unionall(decl); - jl_value_t *lastdeclt = jl_tparam(unw, jl_nparams(unw) - 1); - assert(jl_is_vararg(lastdeclt)); - int nsp = jl_svec_len(sparams); - if (nsp > 0 && jl_has_free_typevars(lastdeclt)) { - assert(jl_subtype_env_size(decl) == nsp); - lastdeclt = jl_instantiate_type_in_env(lastdeclt, (jl_unionall_t*)decl, jl_svec_data(sparams)); - // TODO: rewrap_unionall(lastdeclt, sparams) if any sparams isa TypeVar??? - // TODO: if we made any replacements above, sparams may now be incorrect - } - jl_svecset(limited, i, lastdeclt); + type_i = inst_varargp_in_env(decl, sparams); + } + jl_svec_t *limited = jl_alloc_svec(nspec); + size_t i; + for (i = 0; i < nspec - 1; i++) { + jl_svecset(limited, i, jl_svecref(*newparams, i)); } + jl_svecset(limited, i, type_i); *newparams = limited; - // now there is a problem: the widened signature is more - // general than just the given arguments, so it might conflict - // with another definition that doesn't have cache instances yet. - // to fix this, we insert guard cache entries for all intersections - // of this signature and definitions. those guard entries will - // supersede this one in conflicted cases, alerting us that there - // should actually be a cache miss. - // TODO: the above analysis assumes that there will never - // be a call attempted that should throw a no-method error - JL_GC_POP(); } + JL_GC_POP(); } // compute whether this type signature is a possible return value from jl_compilation_sig given a concrete-type for `tt` JL_DLLEXPORT int jl_isa_compileable_sig( jl_tupletype_t *type, + jl_svec_t *sparams, jl_method_t *definition) { jl_value_t *decl = definition->sig; if (!jl_is_datatype(type) || jl_has_free_typevars((jl_value_t*)type)) return 0; - if (definition->sig == (jl_value_t*)jl_anytuple_type && definition->unspecialized) + if (definition->sig == (jl_value_t*)jl_anytuple_type && jl_atomic_load_relaxed(&definition->unspecialized)) return jl_egal((jl_value_t*)type, definition->sig); // handle builtin methods size_t i, np = jl_nparams(type); @@ -843,23 +1029,24 @@ JL_DLLEXPORT int jl_isa_compileable_sig( // supertype of any other method signatures. so far we are conservative // and the types we find should be bigger. if (definition->isva) { - unsigned nspec_min = nargs + 1; // min number of non-vararg values before vararg - unsigned nspec_max = INT32_MAX; // max number of non-vararg values before vararg + unsigned nspec_min = nargs + 1; // min number of arg values (including tail vararg) + unsigned nspec_max = INT32_MAX; // max number of arg values (including tail vararg) jl_methtable_t *mt = jl_method_table_for(decl); + jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(decl) : mt; if ((jl_value_t*)mt != jl_nothing) { // try to refine estimate of min and max - if (mt != jl_type_type_mt && mt != jl_nonfunction_mt) - nspec_min = mt->max_args + 2; - else - nspec_max = nspec_min; + uint8_t heuristic_used = 0; + nspec_max = nspec_min = nargs + get_max_varargs(definition, kwmt, mt, &heuristic_used); + if (heuristic_used) + nspec_max = INT32_MAX; // new methods may be added, increasing nspec_min later } - int isbound = (jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND); + int isunbound = (jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND); if (jl_is_vararg(jl_tparam(type, np - 1))) { - if (!isbound || np < nspec_min || np > nspec_max) + if (!isunbound || np < nspec_min || np > nspec_max) return 0; } else { - if (np < nargs - 1 || (isbound && np >= nspec_max)) + if (np < nargs - 1 || (isunbound && np >= nspec_max)) return 0; } } @@ -867,74 +1054,87 @@ JL_DLLEXPORT int jl_isa_compileable_sig( return 0; } + jl_value_t *type_i = NULL; + JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(type, i); - jl_value_t *decl_i = jl_nth_slot_type((jl_value_t*)decl, i); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); if (jl_is_vararg(elt)) { - elt = jl_unwrap_vararg(elt); - if (jl_has_free_typevars(decl_i)) { - // TODO: in this case, answer semi-conservatively that these varargs are always compilable - // we don't have the ability to get sparams, so deciding if elt - // is a potential result of jl_instantiate_type_in_env for decl_i - // for any sparams that is consistent with the rest of the arguments - // seems like it would be extremely difficult - // and hopefully the upstream code probably gave us something reasonable - continue; - } - else if (jl_egal(elt, decl_i)) { - continue; + type_i = inst_varargp_in_env(decl, sparams); + if (jl_has_free_typevars(type_i)) { + JL_GC_POP(); + return 0; // something went badly wrong? } - else if (jl_is_type_type(elt) && jl_is_type_type(jl_tparam0(elt))) { - return 0; + if (jl_egal(elt, type_i)) + continue; // elt could be chosen by inst_varargp_in_env for these sparams + elt = jl_unwrap_vararg(elt); + if (jl_is_type_type(elt) && jl_is_type_type(jl_tparam0(elt))) { + JL_GC_POP(); + return 0; // elt would be set equal to jl_type_type instead } - // else, it needs to meet the usual rules + // else, elt also needs to meet the usual rules } + jl_value_t *decl_i = jl_nth_slot_type(decl, i); + type_i = jl_rewrap_unionall(decl_i, decl); + if (i_arg > 0 && i_arg <= sizeof(definition->nospecialize) * 8 && (definition->nospecialize & (1 << (i_arg - 1)))) { if (!jl_has_free_typevars(decl_i) && !jl_is_kind(decl_i)) { if (jl_egal(elt, decl_i)) continue; + JL_GC_POP(); return 0; } } if (jl_is_kind(elt)) { // kind slots always get guard entries (checking for subtypes of Type) - if (jl_subtype(elt, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) + if (jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) continue; - // TODO: other code paths that could reach here + // TODO: other code paths that could reach here? + JL_GC_POP(); return 0; } - else if (jl_is_kind(decl_i)) { + else if (jl_is_kind(type_i)) { + JL_GC_POP(); return 0; } if (jl_is_type_type(jl_unwrap_unionall(elt))) { - int iscalled = i_arg > 0 && i_arg <= 8 && (definition->called & (1 << (i_arg - 1))); + int iscalled = (i_arg > 0 && i_arg <= 8 && (definition->called & (1 << (i_arg - 1)))) || + jl_has_free_typevars(decl_i); if (jl_types_equal(elt, (jl_value_t*)jl_type_type)) { - if (!iscalled && very_general_type(decl_i)) + if (!iscalled && very_general_type(type_i)) continue; if (i >= nargs && definition->isva) continue; + JL_GC_POP(); return 0; } - if (!iscalled && very_general_type(decl_i)) + if (!iscalled && very_general_type(type_i)) { + JL_GC_POP(); return 0; - if (!jl_is_datatype(elt)) + } + if (!jl_is_datatype(elt)) { + JL_GC_POP(); return 0; + } // if the declared type was not Any or Union{Type, ...}, // then the match must been with kind, such as UnionAll or DataType, // and the result of matching the type signature // needs to be corrected to the concrete type 'kind' (and not to Type) jl_value_t *kind = jl_typeof(jl_tparam0(elt)); - if (kind == jl_bottom_type) + if (kind == jl_bottom_type) { + JL_GC_POP(); return 0; // Type{Union{}} gets normalized to typeof(Union{}) - if (jl_subtype(kind, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) + } + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) { + JL_GC_POP(); return 0; // gets turned into a kind + } else if (jl_is_type_type(jl_tparam0(elt)) && // give up on specializing static parameters for Type{Type{Type{...}}} @@ -947,20 +1147,20 @@ JL_DLLEXPORT int jl_isa_compileable_sig( this can be determined using a type intersection. */ if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(decl_i, (jl_value_t*)jl_type_type); - JL_GC_PUSH1(&di); - assert(di != (jl_value_t*)jl_bottom_type); - if (jl_is_kind(di)) { + type_i = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); + assert(type_i != (jl_value_t*)jl_bottom_type); + if (jl_is_kind(type_i)) { JL_GC_POP(); return 0; } - else if (!jl_types_equal(di, elt)) { + else if (!jl_types_equal(type_i, elt)) { JL_GC_POP(); return 0; } - JL_GC_POP(); + continue; } else { + JL_GC_POP(); return 0; } } @@ -968,24 +1168,29 @@ JL_DLLEXPORT int jl_isa_compileable_sig( } int notcalled_func = (i_arg > 0 && i_arg <= 8 && !(definition->called & (1 << (i_arg - 1))) && + !jl_has_free_typevars(decl_i) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (notcalled_func && (decl_i == (jl_value_t*)jl_any_type || - decl_i == (jl_value_t*)jl_function_type || - (jl_is_uniontype(decl_i) && // Base.Callable - ((((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_type_type) || - (((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_type_type))))) { + if (notcalled_func && (type_i == (jl_value_t*)jl_any_type || + type_i == (jl_value_t*)jl_function_type || + (jl_is_uniontype(type_i) && // Base.Callable + ((((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_type_type) || + (((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_type_type))))) { // and attempt to despecialize types marked Function, Callable, or Any // when called with a subtype of Function but is not called if (elt == (jl_value_t*)jl_function_type) continue; + JL_GC_POP(); return 0; } - if (!jl_is_concrete_type(elt)) + if (!jl_is_concrete_type(elt)) { + JL_GC_POP(); return 0; + } } + JL_GC_POP(); return 1; } @@ -1017,7 +1222,7 @@ static inline jl_typemap_entry_t *lookup_leafcache(jl_array_t *leafcache JL_PROP if (entry->simplesig == (void*)jl_nothing || concretesig_equal(tt, (jl_value_t*)entry->simplesig)) return entry; } - entry = entry->next; + entry = jl_atomic_load_relaxed(&entry->next); } while ((jl_value_t*)entry != jl_nothing); } return NULL; @@ -1053,20 +1258,40 @@ static jl_method_instance_t *cache_method( jl_svec_t *newparams = NULL; JL_GC_PUSH5(&temp, &temp2, &temp3, &newmeth, &newparams); + // Consider if we can cache with the preferred compile signature + // so that we can minimize the number of required cache entries. int cache_with_orig = 1; jl_tupletype_t *compilationsig = tt; - intptr_t nspec = (mt == NULL || mt == jl_type_type_mt || mt == jl_nonfunction_mt ? definition->nargs + 1 : mt->max_args + 2); - jl_compilation_sig(tt, sparams, definition, nspec, &newparams); + jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(definition->sig) : mt; + intptr_t max_varargs = get_max_varargs(definition, kwmt, mt, NULL); + jl_compilation_sig(tt, sparams, definition, max_varargs, &newparams); if (newparams) { - compilationsig = jl_apply_tuple_type(newparams); - temp2 = (jl_value_t*)compilationsig; - // In most cases `!jl_isa_compileable_sig(tt, definition))`, + temp2 = jl_apply_tuple_type(newparams); + // Now there may be a problem: the widened signature is more general + // than just the given arguments, so it might conflict with another + // definition that does not have cache instances yet. To fix this, we + // may insert guard cache entries for all intersections of this + // signature and definitions. Those guard entries will supersede this + // one in conflicted cases, alerting us that there should actually be a + // cache miss. Alternatively, we may use the original signature in the + // cache, but use this return for compilation. + // + // In most cases `!jl_isa_compileable_sig(tt, sparams, definition)`, // although for some cases, (notably Varargs) // we might choose a replacement type that's preferable but not strictly better - cache_with_orig = !jl_subtype((jl_value_t*)compilationsig, definition->sig); + int issubty; + temp = jl_type_intersection_env_s(temp2, (jl_value_t*)definition->sig, &newparams, &issubty); + assert(temp != (jl_value_t*)jl_bottom_type); (void)temp; + if (jl_egal((jl_value_t*)newparams, (jl_value_t*)sparams)) { + cache_with_orig = !issubty; + compilationsig = (jl_datatype_t*)temp2; + } + newparams = NULL; } - // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, definition)); + // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, sparams, definition)); newmeth = jl_specializations_get_linfo(definition, (jl_value_t*)compilationsig, sparams); + if (newmeth->cache_with_orig) + cache_with_orig = 1; jl_tupletype_t *cachett = tt; jl_svec_t* guardsigs = jl_emptysvec; @@ -1076,7 +1301,7 @@ static jl_method_instance_t *cache_method( size_t max_valid2 = ~(size_t)0; temp = ml_matches(mt, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, 1, world, 0, &min_valid2, &max_valid2, NULL); int guards = 0; - if (temp == jl_false) { + if (temp == jl_nothing) { cache_with_orig = 1; } else { @@ -1084,6 +1309,8 @@ static jl_method_instance_t *cache_method( size_t i, l = jl_array_len(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) + continue; jl_svec_t *env = matc->sparams; int k, l; for (k = 0, l = jl_svec_len(env); k < l; k++) { @@ -1102,9 +1329,7 @@ static jl_method_instance_t *cache_method( cache_with_orig = 1; break; } - if (matc->method != definition) { - guards++; - } + guards++; } } if (!cache_with_orig && guards > 0) { @@ -1125,6 +1350,7 @@ static jl_method_instance_t *cache_method( // NULL, jl_emptysvec, /*guard*/NULL, jl_cachearg_offset(mt), other->min_world, other->max_world); } } + assert(guards == jl_svec_len(guardsigs)); } if (!cache_with_orig) { // determined above that there's no ambiguity in also using compilationsig as the cacheablesig @@ -1132,6 +1358,10 @@ static jl_method_instance_t *cache_method( max_valid = max_valid2; cachett = compilationsig; } + else { + // do not revisit this decision + newmeth->cache_with_orig = 1; + } } // now scan `cachett` and ensure that `Type{T}` in the cache will be matched exactly by `typeof(T)` @@ -1159,7 +1389,7 @@ static jl_method_instance_t *cache_method( } } if (newparams) { - simplett = jl_apply_tuple_type(newparams); + simplett = (jl_datatype_t*)jl_apply_tuple_type(newparams); temp2 = (jl_value_t*)simplett; } @@ -1190,11 +1420,15 @@ static jl_method_instance_t *cache_method( jl_cache_type_(tt); JL_UNLOCK(&typecache_lock); // Might GC } - jl_typemap_entry_t *old = (jl_typemap_entry_t*)jl_eqtable_get(mt->leafcache, (jl_value_t*)tt, jl_nothing); - newentry->next = old; + jl_array_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_atomic_store_release(&mt->leafcache, jl_eqtable_put(mt->leafcache, (jl_value_t*)tt, (jl_value_t*)newentry, NULL)); - jl_gc_wb(mt, mt->leafcache); + 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); + if (newcache != oldcache) { + jl_atomic_store_release(&mt->leafcache, newcache); + jl_gc_wb(mt, newcache); + } } else { jl_typemap_insert(cache, parent, newentry, offs); @@ -1204,7 +1438,7 @@ static jl_method_instance_t *cache_method( return newmeth; } -static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid); +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) { @@ -1218,13 +1452,13 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt JL_PROPAGATE } 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(mt->cache, &search, jl_cachearg_offset(mt), /*subtype*/1); + 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; size_t min_valid = 0; size_t max_valid = ~(size_t)0; - jl_method_match_t *matc = _gf_invoke_lookup((jl_value_t*)tt, world, &min_valid, &max_valid); + 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); @@ -1241,24 +1475,29 @@ struct matches_env { struct typemap_intersection_env match; jl_typemap_entry_t *newentry; jl_value_t *shadowed; + jl_typemap_entry_t *replaced; }; + static int get_intersect_visitor(jl_typemap_entry_t *oldentry, struct typemap_intersection_env *closure0) { struct matches_env *closure = container_of(closure0, struct matches_env, match); - if (oldentry == closure->newentry) - return 1; - if (oldentry->max_world < ~(size_t)0 || oldentry->min_world == closure->newentry->min_world) - // skip if no world has both active - // also be careful not to try to scan something from the current dump-reload though - return 1; + 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 == ~(size_t)0 && "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->shadowed == NULL) closure->shadowed = (jl_value_t*)jl_alloc_vec_any(0); jl_array_ptr_1d_push((jl_array_t*)closure->shadowed, (jl_value_t*)oldmethod); return 1; } -static jl_value_t *get_intersect_matches(jl_typemap_t *defs, jl_typemap_entry_t *newentry) +static jl_value_t *get_intersect_matches(jl_typemap_t *defs, jl_typemap_entry_t *newentry, jl_typemap_entry_t **replaced, int8_t offs, size_t world) { jl_tupletype_t *type = newentry->sig; jl_tupletype_t *ttypes = (jl_tupletype_t*)jl_unwrap_unionall((jl_value_t*)type); @@ -1271,11 +1510,16 @@ static jl_value_t *get_intersect_matches(jl_typemap_t *defs, jl_typemap_entry_t else va = NULL; } - struct matches_env env = {{get_intersect_visitor, (jl_value_t*)type, va, + // search for all intersecting methods active in the previous world, to determine the changes needed to be made for the next world + struct matches_env env = {{get_intersect_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0, + /* .min_valid = */ world, /* .max_valid = */ world, /* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0}, - /* .newentry = */ newentry, /* .shadowed */ NULL}; + /* .newentry = */ newentry, /* .shadowed */ NULL, /* .replaced */ NULL}; JL_GC_PUSH3(&env.match.env, &env.match.ti, &env.shadowed); - jl_typemap_intersection_visitor(defs, 0, &env.match); + jl_typemap_intersection_visitor(defs, offs, &env.match); + env.match.env = NULL; + env.match.ti = NULL; + *replaced = env.replaced; JL_GC_POP(); return env.shadowed; } @@ -1303,7 +1547,9 @@ static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue jl_method_t *method = (jl_method_t*)newentry->func.method; jl_module_t *newmod = method->module; jl_module_t *oldmod = oldvalue->module; - jl_datatype_t *dt = jl_first_argument_datatype(oldvalue->sig); + jl_datatype_t *dt = jl_nth_argument_datatype(oldvalue->sig, 1); + if (dt == (jl_datatype_t*)jl_typeof(jl_kwcall_func)) + dt = jl_nth_argument_datatype(oldvalue->sig, 3); int anon = dt && is_anonfn_typename(jl_symbol_name(dt->name->name)); if ((jl_options.warn_overwrite == JL_OPTIONS_WARN_OVERWRITE_ON) || (jl_options.incremental && jl_generating_output()) || anon) { @@ -1328,15 +1574,16 @@ static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue static void update_max_args(jl_methtable_t *mt, jl_value_t *type) { - if (mt == jl_type_type_mt || mt == jl_nonfunction_mt) + if (mt == jl_type_type_mt || mt == jl_nonfunction_mt || mt == jl_kwcall_mt) return; type = jl_unwrap_unionall(type); assert(jl_is_datatype(type)); size_t na = jl_nparams(type); if (jl_va_tuple_kind((jl_datatype_t*)type) == JL_VARARG_UNBOUND) na--; - if (na > mt->max_args) - mt->max_args = na; + // update occurs inside mt->writelock + if (na > jl_atomic_load_relaxed(&mt->max_args)) + jl_atomic_store_relaxed(&mt->max_args, na); } jl_array_t *_jl_debug_method_invalidation JL_GLOBALLY_ROOTED = NULL; @@ -1390,8 +1637,10 @@ static void invalidate_external(jl_method_instance_t *mi, size_t max_world) { } } +static void do_nothing_with_codeinst(jl_code_instance_t *ci) {} + // recursively invalidate cached methods that had an edge to a replaced method -static void invalidate_method_instance(jl_method_instance_t *replaced, size_t max_world, int depth) +static void invalidate_method_instance(void (*f)(jl_code_instance_t*), jl_method_instance_t *replaced, size_t max_world, int depth) { if (_jl_debug_method_invalidation) { jl_value_t *boxeddepth = NULL; @@ -1401,44 +1650,55 @@ static void invalidate_method_instance(jl_method_instance_t *replaced, size_t ma jl_array_ptr_1d_push(_jl_debug_method_invalidation, boxeddepth); JL_GC_POP(); } + //jl_static_show(JL_STDERR, (jl_value_t*)replaced); if (!jl_is_method(replaced->def.method)) return; // shouldn't happen, but better to be safe JL_LOCK(&replaced->def.method->writelock); - jl_code_instance_t *codeinst = replaced->cache; + 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; } assert(codeinst->max_world <= max_world); + JL_GC_PUSH1(&codeinst); + (*f)(codeinst); + JL_GC_POP(); codeinst = jl_atomic_load_relaxed(&codeinst->next); } // 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, l = jl_array_len(backedges); - for (i = 0; i < l; i++) { - jl_method_instance_t *replaced = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); - invalidate_method_instance(replaced, max_world, depth + 1); + 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(); } JL_UNLOCK(&replaced->def.method->writelock); } // invalidate cached methods that overlap this definition -static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, const char *why) +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); 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; - size_t i, l = jl_array_len(backedges); - jl_method_instance_t **replaced = (jl_method_instance_t**)jl_array_ptr_data(backedges); - for (i = 0; i < l; i++) { - invalidate_method_instance(replaced[i], max_world, 1); + JL_GC_PUSH1(&backedges); + 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, 1); } + JL_GC_POP(); } JL_UNLOCK(&replaced_mi->def.method->writelock); if (why && _jl_debug_method_invalidation) { @@ -1451,25 +1711,37 @@ static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_w } // add a backedge from callee to caller -JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_method_instance_t *caller) +JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_value_t *invokesig, jl_method_instance_t *caller) { JL_LOCK(&callee->def.method->writelock); + if (invokesig == jl_nothing) + invokesig = NULL; // julia uses `nothing` but C uses NULL (#undef) + int found = 0; + // TODO: use jl_cache_type_(invokesig) like cache_method does to save memory if (!callee->backedges) { // lazy-init the backedges array - callee->backedges = jl_alloc_vec_any(1); + callee->backedges = jl_alloc_vec_any(0); jl_gc_wb(callee, callee->backedges); - jl_array_ptr_set(callee->backedges, 0, caller); } else { - size_t i, l = jl_array_len(callee->backedges); + size_t i = 0, l = jl_array_len(callee->backedges); for (i = 0; i < l; i++) { - if (jl_array_ptr_ref(callee->backedges, i) == (jl_value_t*)caller) + // 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); + if (mi != (jl_value_t*)caller) + continue; + jl_value_t *invokeTypes = i > 0 ? jl_array_ptr_ref(callee->backedges, i - 1) : NULL; + if (invokeTypes && jl_is_method_instance(invokeTypes)) + invokeTypes = NULL; + if ((invokesig == NULL && invokeTypes == NULL) || + (invokesig && invokeTypes && jl_types_equal(invokesig, invokeTypes))) { + found = 1; break; - } - if (i == l) { - jl_array_ptr_1d_push(callee->backedges, (jl_value_t*)caller); + } } } + if (!found) + push_edge(callee->backedges, invokesig, caller); JL_UNLOCK(&callee->def.method->writelock); } @@ -1485,6 +1757,7 @@ 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); for (i = 1; i < l; i += 2) { if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { @@ -1523,6 +1796,22 @@ static int invalidate_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) break; } } + if (intersects && (jl_value_t*)oldentry->sig != mi->specTypes) { + // the entry may point to a widened MethodInstance, in which case it is worthwhile to check if the new method + // actually has any meaningful intersection with the old one + intersects = !jl_has_empty_intersection((jl_value_t*)oldentry->sig, (jl_value_t*)env->newentry->sig); + } + if (intersects && oldentry->guardsigs != jl_emptysvec) { + // similarly, if it already matches an existing guardsigs, this is already safe to keep + size_t i, l; + for (i = 0, l = jl_svec_len(oldentry->guardsigs); i < l; i++) { + // see corresponding code in jl_typemap_entry_assoc_exact + if (jl_subtype((jl_value_t*)env->newentry->sig, jl_svecref(oldentry->guardsigs, i))) { + intersects = 0; + break; + } + } + } if (intersects) { if (_jl_debug_method_invalidation) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi); @@ -1559,17 +1848,18 @@ static int typemap_search(jl_typemap_entry_t *entry, void *closure) static jl_typemap_entry_t *do_typemap_search(jl_methtable_t *mt JL_PROPAGATES_ROOT, jl_method_t *method) JL_NOTSAFEPOINT; -#ifndef __clang_gcanalyzer__ +#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 { jl_value_t *closure = (jl_value_t*)(method); - if (jl_typemap_visitor(mt->defs, typemap_search, &closure)) + 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, jl_method_t *method, size_t max_world) +static void jl_method_table_invalidate(jl_methtable_t *mt, jl_typemap_entry_t *methodentry, size_t max_world) { + jl_method_t *method = methodentry->func.method; assert(!method->is_for_opaque_closure); method->deleted_world = methodentry->max_world = max_world; // drop this method from mt->cache @@ -1578,7 +1868,7 @@ static void jl_method_table_invalidate(jl_methtable_t *mt, jl_typemap_entry_t *m mt_cache_env.newentry = methodentry; mt_cache_env.shadowed = NULL; mt_cache_env.invalidated = 0; - jl_typemap_visitor(mt->cache, disable_mt_cache, (void*)&mt_cache_env); + 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); for (i = 1; i < l; i += 2) { @@ -1587,22 +1877,28 @@ static void jl_method_table_invalidate(jl_methtable_t *mt, jl_typemap_entry_t *m while ((jl_value_t*)oldentry != jl_nothing) { if (oldentry->max_world == ~(size_t)0) oldentry->max_world = mt_cache_env.max_world; - oldentry = oldentry->next; + oldentry = jl_atomic_load_relaxed(&oldentry->next); } } } // Invalidate the backedges int invalidated = 0; - jl_svec_t *specializations = methodentry->func.method->specializations; + jl_value_t *specializations = jl_atomic_load_relaxed(&method->specializations); + JL_GC_PUSH1(&specializations); + if (!jl_is_svec(specializations)) + specializations = (jl_value_t*)jl_svec1(specializations); l = jl_svec_len(specializations); for (i = 0; i < l; i++) { 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, methodentry->max_world); - invalidate_backedges(mi, methodentry->max_world, "jl_method_table_disable"); + invalidate_external(mi, max_world); + invalidate_backedges(&do_nothing_with_codeinst, 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_value_t *loctag = jl_cstr_to_string("jl_method_table_disable"); @@ -1621,11 +1917,11 @@ JL_DLLEXPORT void jl_method_table_disable(jl_methtable_t *mt, jl_method_t *metho 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, method, world); + jl_method_table_invalidate(mt, methodentry, world); JL_UNLOCK(&mt->writelock); } -static int jl_type_intersection2(jl_value_t *t1, jl_value_t *t2, jl_value_t **isect, jl_value_t **isect2) +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) { *isect2 = NULL; int is_subty = 0; @@ -1651,11 +1947,45 @@ static int jl_type_intersection2(jl_value_t *t1, jl_value_t *t2, jl_value_t **is return 1; } +enum morespec_options { + morespec_unknown, + morespec_isnot, + morespec_is +}; + +// check if `type` is replacing `m` with an ambiguity here, given other methods in `d` that already match it +static int is_replacing(char ambig, jl_value_t *type, jl_method_t *m, jl_method_t *const *d, size_t n, jl_value_t *isect, jl_value_t *isect2, char *morespec) +{ + size_t k; + for (k = 0; k < n; k++) { + jl_method_t *m2 = d[k]; + // see if m2 also fully covered this intersection + if (m == m2 || !(jl_subtype(isect, m2->sig) || (isect2 && jl_subtype(isect2, m2->sig)))) + continue; + if (morespec[k] == (char)morespec_unknown) + morespec[k] = (char)(jl_type_morespecific(m2->sig, type) ? morespec_is : morespec_isnot); + if (morespec[k] == (char)morespec_is) + // not actually shadowing this--m2 will still be better + return 0; + // if type is not more specific than m (thus now dominating it) + // then there is a new ambiguity here, + // since m2 was also a previous match over isect, + // see if m was previously dominant over all m2 + // or if this was already ambiguous before + if (ambig != morespec_is && !jl_type_morespecific(m->sig, m2->sig)) { + // m and m2 were previously ambiguous over the full intersection of mi with type, and will still be ambiguous with addition of type + return 0; + } + } + return 1; +} + JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype) { - JL_TIMING(ADD_METHOD); + JL_TIMING(ADD_METHOD, ADD_METHOD); assert(jl_is_method(method)); assert(jl_is_mtable(mt)); + jl_timing_show_method(method, JL_TIMING_CURRENT_BLOCK); jl_value_t *type = method->sig; jl_value_t *oldvalue = NULL; jl_array_t *oldmi = NULL; @@ -1669,23 +1999,22 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method jl_typemap_entry_t *newentry = NULL; JL_GC_PUSH7(&oldvalue, &oldmi, &newentry, &loctag, &isect, &isect2, &isect3); JL_LOCK(&mt->writelock); - // first find if we have an existing entry to delete - struct jl_typemap_assoc search = {(jl_value_t*)type, method->primary_world, NULL, 0, ~(size_t)0}; - jl_typemap_entry_t *oldentry = jl_typemap_assoc_by_type(mt->defs, &search, /*offs*/0, /*subtype*/0); - // then add our new entry + // 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, 0); - if (oldentry) { - jl_method_t *m = oldentry->func.method; - method_overwrite(newentry, m); - jl_method_table_invalidate(mt, oldentry, m, max_world); + jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, jl_cachearg_offset(mt)); + 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); + int invalidated = 0; + if (replaced) { + oldvalue = (jl_value_t*)replaced; + invalidated = 1; + method_overwrite(newentry, replaced->func.method); + jl_method_table_invalidate(mt, replaced, max_world); } else { - oldvalue = get_intersect_matches(mt->defs, newentry); - - int invalidated = 0; - jl_method_t **d; + jl_method_t *const *d; size_t j, n; if (oldvalue == NULL) { d = NULL; @@ -1714,6 +2043,7 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method // -> less specific or ambiguous with any one of them: can ignore the missing edge (not missing) // -> some may have been ambiguous: still are // -> some may have been called: they may be partly replaced (will be detected in the loop later) + // c.f. `is_replacing`, which is a similar query, but with an existing method match to compare against missing = 1; size_t j; for (j = 0; j < n; j++) { @@ -1731,7 +2061,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(backedge, max_world, 0); + invalidate_method_instance(&do_nothing_with_codeinst, backedge, max_world, 0); invalidated = 1; if (_jl_debug_method_invalidation) jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)backedgetyp); @@ -1748,20 +2078,23 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method } if (oldvalue) { oldmi = jl_alloc_vec_any(0); - enum morespec_options { - morespec_unknown, - morespec_isnot, - morespec_is - }; char *morespec = (char*)alloca(n); memset(morespec, morespec_unknown, n); for (j = 0; j < n; j++) { jl_method_t *m = d[j]; if (morespec[j] == (char)morespec_is) continue; - jl_svec_t *specializations = jl_atomic_load_acquire(&m->specializations); - _Atomic(jl_method_instance_t*) *data = (_Atomic(jl_method_instance_t*)*)jl_svec_data(specializations); - size_t i, l = jl_svec_len(specializations); + loctag = jl_atomic_load_relaxed(&m->specializations); // use loctag for a gcroot + _Atomic(jl_method_instance_t*) *data; + size_t i, l; + if (jl_is_svec(loctag)) { + data = (_Atomic(jl_method_instance_t*)*)jl_svec_data(loctag); + l = jl_svec_len(loctag); + } + else { + data = (_Atomic(jl_method_instance_t*)*) &loctag; + l = 1; + } enum morespec_options ambig = morespec_unknown; for (i = 0; i < l; i++) { jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i]); @@ -1769,6 +2102,11 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method continue; isect3 = jl_type_intersection(m->sig, (jl_value_t*)mi->specTypes); if (jl_type_intersection2(type, isect3, &isect, &isect2)) { + // TODO: this only checks pair-wise for ambiguities, but the ambiguities could arise from the interaction of multiple methods + // and thus might miss a case where we introduce an ambiguity between two existing methods + // We could instead work to sort this into 3 groups `morespecific .. ambiguous .. lesspecific`, with `type` in ambiguous, + // such that everything in `morespecific` dominates everything in `ambiguous`, and everything in `ambiguous` dominates everything in `lessspecific` + // And then compute where each isect falls, and whether it changed group--necessitating invalidation--or not. if (morespec[j] == (char)morespec_unknown) morespec[j] = (char)(jl_type_morespecific(m->sig, type) ? morespec_is : morespec_isnot); if (morespec[j] == (char)morespec_is) @@ -1777,45 +2115,56 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method if (ambig == morespec_unknown) ambig = jl_type_morespecific(type, m->sig) ? morespec_is : morespec_isnot; // replacing a method--see if this really was the selected method previously - // over the intersection - if (ambig == morespec_isnot) { - size_t k; - for (k = 0; k < n; k++) { - jl_method_t *m2 = d[k]; - if (m == m2 || !(jl_subtype(isect, m2->sig) || (isect && jl_subtype(isect, m2->sig)))) - continue; - if (morespec[k] == (char)morespec_unknown) - morespec[k] = (char)(jl_type_morespecific(m2->sig, type) ? morespec_is : morespec_isnot); - if (morespec[k] == (char)morespec_is) - // not actually shadowing this--m2 will still be better - break; - // since m2 was also a previous match over isect, - // see if m was also previously dominant over all m2 - if (!jl_type_morespecific(m->sig, m2->sig)) - break; + // 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"); + // but ignore invoke-type edges + jl_array_t *backedges = mi->backedges; + if (backedges) { + size_t ib = 0, insb = 0, nb = jl_array_len(backedges); + jl_value_t *invokeTypes; + jl_method_instance_t *caller; + while (ib < nb) { + ib = get_next_edge(backedges, ib, &invokeTypes, &caller); + int replaced_edge; + if (invokeTypes) { + // n.b. normally we must have mi.specTypes <: invokeTypes <: m.sig (though it might not strictly hold), so we only need to check the other subtypes + replaced_edge = jl_subtype(invokeTypes, type) && is_replacing(ambig, type, m, d, n, invokeTypes, NULL, morespec); + } + else { + replaced_edge = replaced_dispatch; + } + if (replaced_edge) { + invalidate_method_instance(&do_nothing_with_codeinst, caller, max_world, 1); + invalidated = 1; + } + else { + insb = set_next_edge(backedges, insb, invokeTypes, caller); + } } - if (k != n) - continue; + jl_array_del_end(backedges, nb - insb); } jl_array_ptr_1d_push(oldmi, (jl_value_t*)mi); invalidate_external(mi, max_world); - if (mi->backedges) { - invalidated = 1; - invalidate_backedges(mi, max_world, "jl_method_table_insert"); + 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"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); } } } } if (jl_array_len(oldmi)) { // search mt->cache and leafcache and drop anything that might overlap with the new method - // TODO: keep track of just the `mi` for which shadowing was true (to avoid recomputing that here) + // this is very cheap, so we don't mind being fairly conservative at over-approximating this struct invalidate_mt_env mt_cache_env; mt_cache_env.max_world = max_world; mt_cache_env.shadowed = oldmi; mt_cache_env.newentry = newentry; mt_cache_env.invalidated = 0; - jl_typemap_visitor(mt->cache, invalidate_mt_cache, (void*)&mt_cache_env); + 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); for (i = 1; i < l; i += 2) { @@ -1823,19 +2172,19 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method if (entry) { while (entry != jl_nothing) { invalidate_mt_cache((jl_typemap_entry_t*)entry, (void*)&mt_cache_env); - entry = (jl_value_t*)((jl_typemap_entry_t*)entry)->next; + entry = (jl_value_t*)jl_atomic_load_relaxed(&((jl_typemap_entry_t*)entry)->next); } } } } } - if (invalidated && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_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); } + if (invalidated && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_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_UNLOCK(&mt->writelock); JL_GC_POP(); } @@ -1859,7 +2208,7 @@ static void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args, jl_static_show((JL_STREAM*)STDERR_FILENO,args); jl_printf((JL_STREAM*)STDERR_FILENO,"\n"); jl_ptls_t ptls = jl_current_task->ptls; ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE, 0); - jl_critical_error(0, NULL, jl_current_task); + jl_critical_error(0, 0, NULL, jl_current_task); abort(); } // not reached @@ -1911,15 +2260,17 @@ jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t w // full is a boolean indicating if that method fully covers the input // // lim is the max # of methods to return. if there are more, returns jl_false. -// -1 for no limit. +// Negative values stand for no limit. +// Unless lim == -1, remove matches that are unambiguously covered by earlier ones JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, jl_value_t *mt, int lim, int include_ambiguous, size_t world, size_t *min_valid, size_t *max_valid, int *ambig) { - JL_TIMING(METHOD_MATCH); if (ambig != NULL) *ambig = 0; jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types); - if (jl_is_tuple_type(unw) && jl_tparam0(unw) == jl_bottom_type) + if (!jl_is_tuple_type(unw)) + return (jl_value_t*)jl_an_empty_vec_any; + if (unw == (jl_value_t*)jl_emptytuple_type || jl_tparam0(unw) == jl_bottom_type) return (jl_value_t*)jl_an_empty_vec_any; if (mt == jl_nothing) mt = (jl_value_t*)jl_method_table_for(unw); @@ -1928,23 +2279,35 @@ 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(jl_method_instance_t *method JL_PROPAGATES_ROOT) +jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT) { - // one unspecialized version of a function can be shared among all cached specializations 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 if (!jl_is_method(def) || def->source == NULL) { // generated functions might instead randomly just never get inferred, sorry - return method; + return NULL; } - if (def->unspecialized == NULL) { + jl_method_instance_t *unspec = jl_atomic_load_relaxed(&def->unspecialized); + if (unspec == NULL) { JL_LOCK(&def->writelock); - if (def->unspecialized == NULL) { - def->unspecialized = jl_get_specialized(def, def->sig, jl_emptysvec); - jl_gc_wb(def, def->unspecialized); + unspec = jl_atomic_load_relaxed(&def->unspecialized); + if (unspec == NULL) { + unspec = jl_get_specialized(def, def->sig, jl_emptysvec); + jl_atomic_store_release(&def->unspecialized, unspec); + jl_gc_wb(def, unspec); } JL_UNLOCK(&def->writelock); } - return def->unspecialized; + return unspec; } @@ -1961,6 +2324,8 @@ jl_code_instance_t *jl_method_compiled(jl_method_instance_t *mi, size_t world) return NULL; } +jl_mutex_t precomp_statement_out_lock; + static void record_precompile_statement(jl_method_instance_t *mi) { static ios_t f_precompile; @@ -1971,6 +2336,7 @@ static void record_precompile_statement(jl_method_instance_t *mi) if (!jl_is_method(def)) return; + JL_LOCK(&precomp_statement_out_lock); if (s_precompile == NULL) { const char *t = jl_options.trace_compile; if (!strncmp(t, "stderr", 6)) { @@ -1989,13 +2355,59 @@ static void record_precompile_statement(jl_method_instance_t *mi) 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) { + // quick check if we already have a compiled result jl_code_instance_t *codeinst = jl_method_compiled(mi, world); if (codeinst) return codeinst; + + // if mi has a better (wider) signature preferred for compilation use that + // instead and just copy it here for caching + jl_method_instance_t *mi2 = jl_normalize_to_compilable_mi(mi); + if (mi2 != mi) { + 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); + if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { + codeinst->rettype_const = codeinst2->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); + if (fptr != NULL) { + while (!(specsigflags & 0b10)) { + jl_cpu_pause(); + specsigflags = jl_atomic_load_acquire(&codeinst2->specsigflags); + } + invoke = jl_atomic_load_relaxed(&codeinst2->invoke); + void *prev_fptr = NULL; + // see jitlayers.cpp for the ordering restrictions here + if (jl_atomic_cmpswap_acqrel(&codeinst->specptr.fptr, &prev_fptr, fptr)) { + jl_atomic_store_relaxed(&codeinst->specsigflags, specsigflags & 0b1); + jl_atomic_store_release(&codeinst->invoke, invoke); + jl_atomic_store_release(&codeinst->specsigflags, specsigflags); + } else { + // someone else already compiled it + while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + // codeinst is now set up fully, safe to return + } + } else { + jl_callptr_t prev = NULL; + jl_atomic_cmpswap_acqrel(&codeinst->invoke, &prev, invoke); + } + } + // don't call record_precompile_statement here, since we already compiled it as mi2 which is better + return codeinst; + } + int compile_option = jl_options.compile_enabled; jl_method_t *def = mi->def.method; // disabling compilation per-module can override global setting @@ -2011,31 +2423,43 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t compile_option == JL_OPTIONS_COMPILE_MIN || def->source == jl_nothing) { // copy fptr from the template method definition - if (jl_is_method(def) && def->unspecialized) { - jl_code_instance_t *unspec = jl_atomic_load_relaxed(&def->unspecialized->cache); - if (unspec && jl_atomic_load_relaxed(&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); - codeinst->isspecsig = 0; - codeinst->specptr = unspec->specptr; - codeinst->rettype_const = unspec->rettype_const; - codeinst->invoke = unspec->invoke; - jl_mi_cache_insert(mi, codeinst); - record_precompile_statement(mi); - return codeinst; + if (jl_is_method(def)) { + jl_method_instance_t *unspecmi = jl_atomic_load_relaxed(&def->unspecialized); + if (unspecmi) { + 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); + void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); + if (unspec_fptr) { + // wait until invoke and specsigflags are properly set + while (!(jl_atomic_load_acquire(&unspec->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + unspec_invoke = jl_atomic_load_relaxed(&unspec->invoke); + } + jl_atomic_store_release(&codeinst->specptr.fptr, unspec_fptr); + codeinst->rettype_const = unspec->rettype_const; + jl_atomic_store_release(&codeinst->invoke, unspec_invoke); + jl_mi_cache_insert(mi, codeinst); + record_precompile_statement(mi); + return codeinst; + } } } } + // if that didn't work and compilation is off, try running in the interpreter if (compile_option == JL_OPTIONS_COMPILE_OFF || compile_option == JL_OPTIONS_COMPILE_MIN) { - jl_code_info_t *src = jl_code_for_interpreter(mi); - if (!jl_code_requires_compiler(src)) { + 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); - codeinst->invoke = jl_fptr_interpret_call; + jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); return codeinst; @@ -2049,31 +2473,42 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t codeinst = jl_generate_fptr(mi, world); if (!codeinst) { - jl_method_instance_t *unspec = jl_get_unspecialized(mi); + 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); // ask codegen to make the fptr for unspec - if (jl_atomic_load_relaxed(&ucache->invoke) == NULL) { - if (def->source == jl_nothing && (ucache->def->uninferred == jl_nothing || - ucache->def->uninferred == NULL)) { + 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"); } jl_generate_fptr_for_unspecialized(ucache); + ucache_invoke = jl_atomic_load_acquire(&ucache->invoke); } - assert(jl_atomic_load_relaxed(&ucache->invoke) != NULL); - if (jl_atomic_load_relaxed(&ucache->invoke) != jl_fptr_sparam && - jl_atomic_load_relaxed(&ucache->invoke) != jl_fptr_interpret_call) { + assert(ucache_invoke != NULL); + 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 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->isspecsig = 0; - codeinst->specptr = ucache->specptr; + void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); + if (unspec_fptr) { + // wait until invoke and specsigflags are properly set + while (!(jl_atomic_load_acquire(&ucache->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + ucache_invoke = jl_atomic_load_relaxed(&ucache->invoke); + } + // unspec is always not specsig, but might use specptr + jl_atomic_store_relaxed(&codeinst->specsigflags, jl_atomic_load_relaxed(&ucache->specsigflags) & 0b10); + jl_atomic_store_relaxed(&codeinst->specptr.fptr, unspec_fptr); codeinst->rettype_const = ucache->rettype_const; - codeinst->invoke = ucache->invoke; + jl_atomic_store_release(&codeinst->invoke, ucache_invoke); jl_mi_cache_insert(mi, codeinst); } else { @@ -2083,7 +2518,6 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t return codeinst; } - jl_value_t *jl_fptr_const_return(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m) { return m->rettype_const; @@ -2091,29 +2525,27 @@ jl_value_t *jl_fptr_const_return(jl_value_t *f, jl_value_t **args, uint32_t narg jl_value_t *jl_fptr_args(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m) { - while (1) { - jl_fptr_args_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr1); - if (invoke) - return invoke(f, args, nargs); - } + jl_fptr_args_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr1); + assert(invoke && "Forgot to set specptr for jl_fptr_args!"); + return invoke(f, args, nargs); } jl_value_t *jl_fptr_sparam(jl_value_t *f, jl_value_t **args, uint32_t nargs, jl_code_instance_t *m) { jl_svec_t *sparams = m->def->sparam_vals; assert(sparams != jl_emptysvec); - while (1) { - jl_fptr_sparam_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr3); - if (invoke) - return invoke(f, args, nargs, sparams); - } + jl_fptr_sparam_t invoke = jl_atomic_load_relaxed(&m->specptr.fptr3); + assert(invoke && "Forgot to set specptr for jl_fptr_sparam!"); + return invoke(f, args, nargs, sparams); } -JL_DLLEXPORT jl_callptr_t jl_fptr_args_addr = &jl_fptr_args; +JL_DLLEXPORT const jl_callptr_t jl_fptr_args_addr = &jl_fptr_args; + +JL_DLLEXPORT const jl_callptr_t jl_fptr_const_return_addr = &jl_fptr_const_return; -JL_DLLEXPORT jl_callptr_t jl_fptr_const_return_addr = &jl_fptr_const_return; +JL_DLLEXPORT const jl_callptr_t jl_fptr_sparam_addr = &jl_fptr_sparam; -JL_DLLEXPORT jl_callptr_t jl_fptr_sparam_addr = &jl_fptr_sparam; +JL_DLLEXPORT const jl_callptr_t jl_f_opaque_closure_call_addr = (jl_callptr_t)&jl_f_opaque_closure_call; // Return the index of the invoke api, if known JL_DLLEXPORT int32_t jl_invoke_api(jl_code_instance_t *codeinst) @@ -2137,15 +2569,85 @@ JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_t jl_tupletype_t *tt = NULL; jl_svec_t *newparams = NULL; JL_GC_PUSH2(&tt, &newparams); - intptr_t nspec = (mt == jl_type_type_mt || mt == jl_nonfunction_mt ? m->nargs + 1 : mt->max_args + 2); - jl_compilation_sig(ti, env, m, nspec, &newparams); - tt = (newparams ? jl_apply_tuple_type(newparams) : ti); - int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple || - jl_isa_compileable_sig(tt, m); + jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(m->sig) : mt; + intptr_t max_varargs = get_max_varargs(m, kwmt, mt, NULL); + jl_compilation_sig(ti, env, m, max_varargs, &newparams); + int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple; + if (newparams) { + tt = (jl_datatype_t*)jl_apply_tuple_type(newparams); + if (!is_compileable) { + // compute new env, if used below + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)tt, (jl_value_t*)m->sig, &newparams); + assert(ti != jl_bottom_type); (void)ti; + env = newparams; + } + } + else { + tt = ti; + } + if (!is_compileable) + is_compileable = jl_isa_compileable_sig(tt, env, m); JL_GC_POP(); return is_compileable ? (jl_value_t*)tt : jl_nothing; } +jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT) +{ + jl_method_t *def = mi->def.method; + if (!jl_is_method(def) || !jl_is_datatype(mi->specTypes)) + return mi; + jl_methtable_t *mt = jl_method_get_table(def); + if ((jl_value_t*)mt == jl_nothing) + return mi; + jl_value_t *compilationsig = jl_normalize_to_compilable_sig(mt, (jl_datatype_t*)mi->specTypes, mi->sparam_vals, def); + if (compilationsig == jl_nothing || jl_egal(compilationsig, mi->specTypes)) + return mi; + jl_svec_t *env = NULL; + JL_GC_PUSH2(&compilationsig, &env); + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)compilationsig, (jl_value_t*)def->sig, &env); + assert(ti != jl_bottom_type); (void)ti; + mi = jl_specializations_get_linfo(def, (jl_value_t*)compilationsig, env); + JL_GC_POP(); + return mi; +} + +// return a MethodInstance for a compileable method_match +jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t world, size_t min_valid, size_t max_valid, int mt_cache) +{ + jl_method_t *m = match->method; + jl_svec_t *env = match->sparams; + jl_tupletype_t *ti = match->spec_types; + jl_method_instance_t *mi = NULL; + if (jl_is_datatype(ti)) { + jl_methtable_t *mt = jl_method_get_table(m); + if ((jl_value_t*)mt != jl_nothing) { + // get the specialization, possibly also caching it + if (mt_cache && ((jl_datatype_t*)ti)->isdispatchtuple) { + // Since we also use this presence in the cache + // to trigger compilation when producing `.ji` files, + // inject it there now if we think it will be + // used via dispatch later (e.g. because it was hinted via a call to `precompile`) + JL_LOCK(&mt->writelock); + mi = cache_method(mt, &mt->cache, (jl_value_t*)mt, ti, m, world, min_valid, max_valid, env); + JL_UNLOCK(&mt->writelock); + } + else { + jl_value_t *tt = jl_normalize_to_compilable_sig(mt, ti, env, m); + if (tt != jl_nothing) { + JL_GC_PUSH2(&tt, &env); + if (!jl_egal(tt, (jl_value_t*)ti)) { + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)tt, (jl_value_t*)m->sig, &env); + assert(ti != jl_bottom_type); (void)ti; + } + mi = jl_specializations_get_linfo(m, (jl_value_t*)tt, env); + JL_GC_POP(); + } + } + } + } + return mi; +} + // compile-time method lookup jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache) { @@ -2163,38 +2665,80 @@ 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_false || jl_array_len(matches) != 1 || ambig) + if (matches == jl_nothing || jl_array_len(matches) != 1 || ambig) return NULL; - jl_value_t *tt = NULL; - JL_GC_PUSH2(&matches, &tt); + JL_GC_PUSH1(&matches); jl_method_match_t *match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); - jl_method_t *m = match->method; - jl_svec_t *env = match->sparams; - jl_tupletype_t *ti = match->spec_types; - jl_method_instance_t *nf = NULL; - if (jl_is_datatype(ti)) { - jl_methtable_t *mt = jl_method_table_for((jl_value_t*)ti); - if ((jl_value_t*)mt != jl_nothing) { - // get the specialization without caching it - if (mt_cache && ((jl_datatype_t*)ti)->isdispatchtuple) { - // Since we also use this presence in the cache - // to trigger compilation when producing `.ji` files, - // inject it there now if we think it will be - // used via dispatch later (e.g. because it was hinted via a call to `precompile`) - JL_LOCK(&mt->writelock); - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, ti, m, world, min_valid2, max_valid2, env); - JL_UNLOCK(&mt->writelock); - } - else { - tt = jl_normalize_to_compilable_sig(mt, ti, env, m); - if (tt != jl_nothing) { - nf = jl_specializations_get_linfo(m, (jl_value_t*)tt, env); + jl_method_instance_t *mi = jl_method_match_to_mi(match, world, min_valid2, max_valid2, mt_cache); + JL_GC_POP(); + return mi; +} + +// Get a MethodInstance for a precompile() call. This uses a special kind of lookup that +// tries to find a method for which the requested signature is compileable. +static jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache) +{ + if (jl_has_free_typevars((jl_value_t*)types)) + return NULL; // don't poison the cache due to a malformed query + if (!jl_has_concrete_subtype((jl_value_t*)types)) + return NULL; + + size_t min_valid2 = 1; + size_t max_valid2 = ~(size_t)0; + int ambig = 0; + jl_value_t *matches = jl_matching_methods(types, jl_nothing, -1, 0, world, &min_valid2, &max_valid2, &ambig); + if (*min_valid < min_valid2) + *min_valid = min_valid2; + if (*max_valid > max_valid2) + *max_valid = max_valid2; + size_t i, n = jl_array_len(matches); + if (n == 0) + return NULL; + JL_GC_PUSH1(&matches); + jl_method_match_t *match = NULL; + if (n == 1) { + match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); + } + else if (jl_is_datatype(types)) { + // first, select methods for which `types` is compileable + size_t count = 0; + for (i = 0; i < n; i++) { + jl_method_match_t *match1 = (jl_method_match_t*)jl_array_ptr_ref(matches, i); + if (jl_isa_compileable_sig(types, match1->sparams, match1->method)) + jl_array_ptr_set(matches, count++, (jl_value_t*)match1); + } + jl_array_del_end((jl_array_t*)matches, n - count); + n = count; + // now remove methods that are more specific than others in the list. + // this is because the intent of precompiling e.g. f(::DataType) is to + // compile that exact method if it exists, and not lots of f(::Type{X}) methods + int exclude; + count = 0; + for (i = 0; i < n; i++) { + jl_method_match_t *match1 = (jl_method_match_t*)jl_array_ptr_ref(matches, i); + 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)) { + exclude = 1; + break; } } + if (!exclude) + jl_array_ptr_set(matches, count++, (jl_value_t*)match1); + if (count > 1) + break; } + // at this point if there are 0 matches left we found nothing, or if there are + // more than one the request is ambiguous and we ignore it. + if (count == 1) + match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); } + jl_method_instance_t *mi = NULL; + if (match != NULL) + mi = jl_method_match_to_mi(match, world, min_valid2, max_valid2, mt_cache); JL_GC_POP(); - return nf; + return mi; } static void _generate_from_hint(jl_method_instance_t *mi, size_t world) @@ -2222,16 +2766,10 @@ static void jl_compile_now(jl_method_instance_t *mi) } } -JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) +JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world) { - size_t world = jl_atomic_load_acquire(&jl_world_counter); size_t tworld = jl_typeinf_world; - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - jl_method_instance_t *mi = jl_get_specialization1(types, world, &min_valid, &max_valid, 1); - if (mi == NULL) - return 0; - JL_GC_PROMISE_ROOTED(mi); + jl_atomic_store_relaxed(&mi->precompiled, 1); if (jl_generating_output()) { jl_compile_now(mi); // In addition to full compilation of the compilation-signature, if `types` is more specific (e.g. due to nospecialize), @@ -2239,18 +2777,19 @@ JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) // additional useful methods that should be compiled //ALT: if (jl_is_datatype(types) && ((jl_datatype_t*)types)->isdispatchtuple && !jl_egal(mi->specTypes, types)) //ALT: if (jl_subtype(types, mi->specTypes)) - if (!jl_subtype(mi->specTypes, (jl_value_t*)types)) { + if (types && !jl_subtype(mi->specTypes, (jl_value_t*)types)) { jl_svec_t *tpenv2 = jl_emptysvec; jl_value_t *types2 = NULL; JL_GC_PUSH2(&tpenv2, &types2); types2 = jl_type_intersection_env((jl_value_t*)types, (jl_value_t*)mi->def.method->sig, &tpenv2); - jl_method_instance_t *li2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2); + jl_method_instance_t *mi2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2); JL_GC_POP(); - if (jl_rettype_inferred(li2, world, world) == jl_nothing) - (void)jl_type_infer(li2, world, 1); + 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(li2, tworld, tworld) == jl_nothing) - (void)jl_type_infer(li2, tworld, 1); + if (jl_rettype_inferred(mi2, tworld, tworld) == jl_nothing) + (void)jl_type_infer(mi2, tworld, 1); } } } @@ -2259,25 +2798,39 @@ JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) // we should generate the native code immediately in preparation for use. (void)jl_compile_method_internal(mi, world); } +} + +JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) +{ + size_t world = jl_atomic_load_acquire(&jl_world_counter); + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + jl_method_instance_t *mi = jl_get_compile_hint_specialization(types, world, &min_valid, &max_valid, 1); + if (mi == NULL) + return 0; + JL_GC_PROMISE_ROOTED(mi); + jl_compile_method_instance(mi, types, world); return 1; } // add type of `f` to front of argument tuple type -static jl_value_t *jl_argtype_with_function(jl_function_t *f, jl_value_t *types0) +jl_value_t *jl_argtype_with_function(jl_value_t *f, jl_value_t *types0) +{ + return jl_argtype_with_function_type(jl_is_type(f) ? (jl_value_t*)jl_wrap_Type(f) : jl_typeof(f), types0); +} + +jl_value_t *jl_argtype_with_function_type(jl_value_t *ft JL_MAYBE_UNROOTED, jl_value_t *types0) { jl_value_t *types = jl_unwrap_unionall(types0); size_t l = jl_nparams(types); - jl_value_t *tt = (jl_value_t*)jl_alloc_svec(1+l); - size_t i; - JL_GC_PUSH1(&tt); - if (jl_is_type(f)) - jl_svecset(tt, 0, jl_wrap_Type(f)); - else - jl_svecset(tt, 0, jl_typeof(f)); - for(i=0; i < l; i++) + jl_value_t *tt = NULL; + JL_GC_PUSH2(&tt, &ft); + tt = (jl_value_t*)jl_alloc_svec(1+l); + jl_svecset(tt, 0, ft); + for (size_t i = 0; i < l; i++) jl_svecset(tt, i+1, jl_tparam(types,i)); tt = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)tt); - tt = jl_rewrap_unionall(tt, types0); + tt = jl_rewrap_unionall_(tt, types0); JL_GC_POP(); return tt; } @@ -2310,7 +2863,7 @@ STATIC_INLINE jl_value_t *_jl_invoke(jl_value_t *F, jl_value_t **args, uint32_t jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mfunc->cache); while (codeinst) { if (codeinst->min_world <= world && world <= codeinst->max_world) { - jl_callptr_t invoke = jl_atomic_load_relaxed(&codeinst->invoke); + jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst->invoke); if (invoke != NULL) { jl_value_t *res = invoke(F, args, nargs, codeinst); return verify_type(res); @@ -2330,7 +2883,7 @@ STATIC_INLINE jl_value_t *_jl_invoke(jl_value_t *F, jl_value_t **args, uint32_t errno = last_errno; if (jl_options.malloc_log) jl_gc_sync_total_bytes(last_alloc); // discard allocation count from compilation - jl_callptr_t invoke = jl_atomic_load_relaxed(&codeinst->invoke); + jl_callptr_t invoke = jl_atomic_load_acquire(&codeinst->invoke); jl_value_t *res = invoke(F, args, nargs, codeinst); return verify_type(res); } @@ -2437,15 +2990,15 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t #undef LOOP_BODY i = 4; jl_tupletype_t *tt = NULL; - int64_t last_alloc; + int64_t last_alloc = 0; if (i == 4) { // if no method was found in the associative cache, check the full cache - JL_TIMING(METHOD_LOOKUP_FAST); + JL_TIMING(METHOD_LOOKUP_FAST, METHOD_LOOKUP_FAST); mt = jl_gf_mtable(F); jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); entry = NULL; if (leafcache != (jl_array_t*)jl_an_empty_vec_any && - jl_typeis(jl_atomic_load_relaxed(&mt->cache), jl_typemap_level_type)) { + 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); if (tt != NULL) @@ -2483,7 +3036,7 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t assert(tt); JL_LOCK(&mt->writelock); // cache miss case - JL_TIMING(METHOD_LOOKUP_SLOW); + JL_TIMING(METHOD_LOOKUP_SLOW, METHOD_LOOKUP_SLOW); mfunc = jl_mt_assoc_by_type(mt, tt, world); JL_UNLOCK(&mt->writelock); JL_GC_POP(); @@ -2516,36 +3069,39 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t *F, jl_value_t **args, uint return _jl_invoke(F, args, nargs, mfunc, world); } -static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid) +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) { jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types); - if (jl_is_tuple_type(unw) && jl_tparam0(unw) == jl_bottom_type) + if (!jl_is_tuple_type(unw)) return NULL; - jl_methtable_t *mt = jl_method_table_for(unw); - if ((jl_value_t*)mt == jl_nothing) + if (jl_tparam0(unw) == jl_bottom_type) + return NULL; + if (mt == jl_nothing) + mt = (jl_value_t*)jl_method_table_for(unw); + if (mt == jl_nothing) mt = NULL; - jl_value_t *matches = ml_matches(mt, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, NULL); - if (matches == jl_false || jl_array_len(matches) != 1) + 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) return NULL; jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); return matc; } -JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_value_t *types, size_t world) +JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_value_t *types, jl_value_t *mt, size_t world) { // Deprecated: Use jl_gf_invoke_lookup_worlds for future development size_t min_valid = 0; size_t max_valid = ~(size_t)0; - jl_method_match_t *matc = _gf_invoke_lookup(types, world, &min_valid, &max_valid); + jl_method_match_t *matc = _gf_invoke_lookup(types, mt, world, &min_valid, &max_valid); if (matc == NULL) return jl_nothing; return (jl_value_t*)matc->method; } -JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup_worlds(jl_value_t *types, size_t world, size_t *min_world, size_t *max_world) +JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup_worlds(jl_value_t *types, jl_value_t *mt, size_t world, size_t *min_world, size_t *max_world) { - jl_method_match_t *matc = _gf_invoke_lookup(types, world, min_world, max_world); + jl_method_match_t *matc = _gf_invoke_lookup(types, mt, world, min_world, max_world); if (matc == NULL) return jl_nothing; return (jl_value_t*)matc; @@ -2565,8 +3121,8 @@ jl_value_t *jl_gf_invoke(jl_value_t *types0, jl_value_t *gf, jl_value_t **args, size_t world = jl_current_task->world_age; jl_value_t *types = NULL; JL_GC_PUSH1(&types); - types = jl_argtype_with_function(gf, types0); - jl_method_t *method = (jl_method_t*)jl_gf_invoke_lookup(types, world); + types = jl_argtype_with_function((jl_value_t*)gf, types0); + jl_method_t *method = (jl_method_t*)jl_gf_invoke_lookup(types, jl_nothing, world); JL_GC_PROMISE_ROOTED(method); if ((jl_value_t*)method == jl_nothing) { @@ -2584,8 +3140,9 @@ jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value { jl_method_instance_t *mfunc = NULL; jl_typemap_entry_t *tm = NULL; - if (method->invokes != NULL) - tm = jl_typemap_assoc_exact(method->invokes, gf, args, nargs, 1, 1); + jl_typemap_t *invokes = jl_atomic_load_relaxed(&method->invokes); + if (invokes != jl_nothing) + tm = jl_typemap_assoc_exact(invokes, gf, args, nargs, 1, 1); if (tm) { mfunc = tm->func.linfo; } @@ -2595,16 +3152,20 @@ jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value jl_tupletype_t *tt = NULL; JL_GC_PUSH2(&tpenv, &tt); JL_LOCK(&method->writelock); - tt = arg_type_tuple(gf, args, nargs); - if (jl_is_unionall(method->sig)) { - int sub = jl_subtype_matching((jl_value_t*)tt, (jl_value_t*)method->sig, &tpenv); - assert(sub); (void)sub; + invokes = jl_atomic_load_relaxed(&method->invokes); + tm = jl_typemap_assoc_exact(invokes, gf, args, nargs, 1, 1); + if (tm) { + mfunc = tm->func.linfo; } + else { + tt = arg_type_tuple(gf, args, nargs); + if (jl_is_unionall(method->sig)) { + int sub = jl_subtype_matching((jl_value_t*)tt, (jl_value_t*)method->sig, &tpenv); + assert(sub); (void)sub; + } - if (method->invokes == NULL) - method->invokes = jl_nothing; - - mfunc = cache_method(NULL, &method->invokes, (jl_value_t*)method, tt, method, 1, 1, ~(size_t)0, tpenv); + mfunc = cache_method(NULL, &method->invokes, (jl_value_t*)method, tt, method, 1, 1, ~(size_t)0, tpenv); + } JL_UNLOCK(&method->writelock); JL_GC_POP(); if (jl_options.malloc_log) @@ -2640,39 +3201,6 @@ jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_ return (jl_function_t*)f; } -JL_DLLEXPORT jl_function_t *jl_get_kwsorter(jl_value_t *ty) -{ - jl_methtable_t *mt = jl_argument_method_table(ty); - if ((jl_value_t*)mt == jl_nothing) - jl_error("cannot get keyword sorter for abstract type"); - if (!mt->kwsorter) { - JL_LOCK(&mt->writelock); - if (!mt->kwsorter) { - char *name; - if (mt == jl_nonfunction_mt) { - name = jl_symbol_name(mt->name); - } - else { - jl_datatype_t *dt = (jl_datatype_t*)jl_argument_datatype(ty); - assert(jl_is_datatype(dt)); - name = jl_symbol_name(dt->name->name); - if (name[0] == '#') - name++; - } - size_t l = strlen(name); - char *suffixed = (char*)malloc_s(l+5); - strcpy(&suffixed[0], name); - strcpy(&suffixed[l], "##kw"); - jl_sym_t *fname = jl_symbol(suffixed); - free(suffixed); - mt->kwsorter = jl_new_generic_function_with_supertype(fname, mt->module, jl_function_type); - jl_gc_wb(mt, mt->kwsorter); - } - JL_UNLOCK(&mt->writelock); - } - return mt->kwsorter; -} - jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module) { return jl_new_generic_function_with_supertype(name, module, jl_function_type); @@ -2684,11 +3212,9 @@ struct ml_matches_env { int intersections; size_t world; int lim; + int include_ambiguous; // results: jl_value_t *t; // array of method matches - size_t min_valid; - size_t max_valid; - // temporary: jl_method_match_t *matc; // current working method match }; @@ -2716,22 +3242,22 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio return 1; if (closure->world < ml->min_world) { // ignore method table entries that are part of a later world - if (closure->max_valid >= ml->min_world) - closure->max_valid = ml->min_world - 1; + if (closure->match.max_valid >= ml->min_world) + closure->match.max_valid = ml->min_world - 1; return 1; } else if (closure->world > ml->max_world) { // ignore method table entries that have been replaced in the current world - if (closure->min_valid <= ml->max_world) - closure->min_valid = ml->max_world + 1; + if (closure->match.min_valid <= ml->max_world) + closure->match.min_valid = ml->max_world + 1; return 1; } else { - // intersect the env valid range with method's valid range - if (closure->min_valid < ml->min_world) - closure->min_valid = ml->min_world; - if (closure->max_valid > ml->max_world) - closure->max_valid = ml->max_world; + // 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)) { @@ -2739,6 +3265,9 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio 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) + typemap_slurp_search(ml, &closure->match); closure->matc = make_method_match((jl_tupletype_t*)closure->match.ti, closure->match.env, meth, closure->match.issubty ? FULLY_COVERS : NOT_FULLY_COVERS); @@ -2753,11 +3282,283 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio return 1; } -static int ml_mtable_visitor(jl_methtable_t *mt, void *env) -{ - return jl_typemap_intersection_visitor(mt->defs, 0, (struct typemap_intersection_env*)env); +static int ml_mtable_visitor(jl_methtable_t *mt, void *closure0) +{ + struct typemap_intersection_env* env = (struct typemap_intersection_env*)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 +// if lim==-1, although morespecific is not transitive). +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable +// Inputs: +// * `t`: the array of vertexes (method matches) +// * `idx`: the next vertex to add to the output +// * `visited`: the state of the algorithm for each vertex in `t`: either 1 if we visited it already or 1+depth if we are visiting it now +// * `stack`: the state of the algorithm for the current vertex (up to length equal to `t`): the list of all vertexes currently in the depth-first path or in the current SCC +// * `result`: the output of the algorithm, a sorted list of vertexes (up to length `lim`) +// * `allambig`: a list of all vertexes with an ambiguity (up to length equal to `t`), discovered while running the rest of the algorithm +// * `lim`: either -1 for unlimited matches, or the maximum length for `result` before returning failure (return -1). +// If specified as -1, this will return extra matches that would have been elided from the list because they were already covered by an earlier match. +// This gives a sort of maximal set of matching methods (up to the first minmax method). +// If specified as -1, the sorting will also include all "weak" edges (every ambiguous pair) which will create much larger ambiguity cycles, +// resulting in a less accurate sort order and much less accurate `*has_ambiguity` result. +// * `include_ambiguous`: whether to filter out fully ambiguous matches from `result` +// * `*has_ambiguity`: whether the algorithm does not need to compute if there is an unresolved ambiguity +// * `*found_minmax`: whether there is a minmax method already found, so future fully_covers matches should be ignored +// Outputs: +// * `*has_ambiguity`: whether the caller should check if there remains an unresolved ambiguity (in `allambig`) +// Returns: +// * -1: too many matches for lim, other outputs are undefined +// * 0: the child(ren) have been added to the output +// * 1+: the children are part of this SCC (up to this depth) +// TODO: convert this function into an iterative call, rather than recursive +static int sort_mlmatches(jl_array_t *t, size_t idx, arraylist_t *visited, arraylist_t *stack, arraylist_t *result, arraylist_t *allambig, int lim, int include_ambiguous, int *has_ambiguity, int *found_minmax) +{ + size_t cycle = (size_t)visited->items[idx]; + if (cycle != 0) + return cycle - 1; // depth remaining + jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(t, idx); + jl_method_t *m = matc->method; + jl_value_t *ti = (jl_value_t*)matc->spec_types; + int subt = matc->fully_covers != NOT_FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) + // first check if this new method is actually already fully covered by an + // existing match and we can just ignore this entry quickly + size_t result_len = 0; + if (subt) { + if (*found_minmax == 2) + visited->items[idx] = (void*)1; + } + else if (lim != -1) { + for (; result_len < result->len; result_len++) { + size_t idx2 = (size_t)result->items[result_len]; + jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(t, idx2); + 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)) + continue; + } + visited->items[idx] = (void*)1; + break; + } + } + } + if ((size_t)visited->items[idx] == 1) + return 0; + arraylist_push(stack, (void*)idx); + size_t depth = stack->len; + visited->items[idx] = (void*)(1 + depth); + cycle = depth; + int addambig = 0; + int mayexclude = 0; + // 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++) { + if (childidx == idx) + continue; + int child_cycle = (size_t)visited->items[childidx]; + if (child_cycle == 1) + continue; // already handled + if (child_cycle != 0 && child_cycle - 1 >= cycle) + continue; // already part of this cycle + jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(t, childidx); + jl_method_t *m2 = matc2->method; + int subt2 = matc2->fully_covers != NOT_FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m2->sig) + // TODO: we could change this to jl_has_empty_intersection(ti, (jl_value_t*)matc2->spec_types); + // 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); + if (!msp) { + if (subt || !include_ambiguous || (lim != -1 && msp2)) { + if (subt2 || jl_subtype((jl_value_t*)ti, m2->sig)) { + // this may be filtered out as fully intersected, if applicable later + mayexclude = 1; + } + } + if (!msp2) { + addambig = 1; // record there is a least one previously-undetected ambiguity that may need to be investigated later (between m and m2) + } + } + if (lim == -1 ? msp : !msp2) // include only strong or also weak edges, depending on whether the result size is limited + continue; + // m2 is (lim!=-1 ? better : not-worse), so attempt to visit it first + // if limited, then we want to visit only better edges, because that results in finding k best matches quickest + // if not limited, then we want to visit all edges, since that results in finding the largest SCC cycles, which requires doing the fewest intersections + child_cycle = sort_mlmatches(t, childidx, visited, stack, result, allambig, lim, include_ambiguous, has_ambiguity, found_minmax); + if (child_cycle == -1) + return -1; + if (child_cycle && child_cycle < cycle) { + // record the cycle will resolve at depth "cycle" + cycle = child_cycle; + } + if (stack->len == depth) { + // if this child resolved without hitting a cycle, then there is + // some probability that this method is already fully covered now + // (same check as before), and we can delete this vertex now without + // anyone noticing (too much) + if (subt) { + if (*found_minmax == 2) + visited->items[idx] = (void*)1; + } + else if (lim != -1) { + for (; result_len < result->len; result_len++) { + size_t idx2 = (size_t)result->items[result_len]; + jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(t, idx2); + 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)) + continue; + } + visited->items[idx] = (void*)1; + break; + } + } + } + if ((size_t)visited->items[idx] == 1) { + assert(cycle == depth); + size_t childidx = (size_t)arraylist_pop(stack); + assert(childidx == idx); (void)childidx; + assert(!subt || *found_minmax == 2); + return 0; + } + } + } + if (matc->fully_covers == NOT_FULLY_COVERS && addambig) + arraylist_push(allambig, (void*)idx); + if (cycle != depth) + return cycle; + result_len = result->len; + if (stack->len == depth) { + // Found one "best" method to add right now. But we might exclude it if + // we determined earlier that we had that option. + if (mayexclude) { + if (!subt || *found_minmax == 2) + visited->items[idx] = (void*)1; + } + } + else { + // We have a set of ambiguous methods. Record that. + // This is greatly over-approximated for lim==-1 + *has_ambiguity = 1; + // If we followed weak edges above, then this also fully closed the ambiguity cycle + if (lim == -1) + addambig = 0; + // If we're only returning possible matches, now filter out this method + // if its intersection is fully ambiguous in this SCC group. + // This is a repeat of the "first check", now that we have completed the cycle analysis + for (size_t i = depth - 1; i < stack->len; i++) { + size_t childidx = (size_t)stack->items[i]; + jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(t, childidx); + jl_value_t *ti = (jl_value_t*)matc->spec_types; + int subt = matc->fully_covers != NOT_FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) + if ((size_t)visited->items[childidx] == 1) { + assert(subt); + continue; + } + assert(visited->items[childidx] == (void*)(2 + i)); + // if we only followed strong edges before above + // check also if this set has an unresolved ambiguity missing from it + if (lim != -1 && !addambig) { + for (size_t j = 0; j < allambig->len; j++) { + if ((size_t)allambig->items[j] == childidx) { + addambig = 1; + break; + } + } + } + // always remove fully_covers matches after the first minmax ambiguity group is handled + if (subt) { + if (*found_minmax) + visited->items[childidx] = (void*)1; + continue; + } + else if (lim != -1) { + // when limited, don't include this match if it was covered by an earlier one + for (size_t result_len = 0; result_len < result->len; result_len++) { + size_t idx2 = (size_t)result->items[result_len]; + jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(t, idx2); + 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)) + continue; + } + visited->items[childidx] = (void*)1; + break; + } + } + } + } + if (!include_ambiguous && lim == -1) { + for (size_t i = depth - 1; i < stack->len; i++) { + size_t childidx = (size_t)stack->items[i]; + if ((size_t)visited->items[childidx] == 1) + continue; + jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(t, childidx); + jl_method_t *m = matc->method; + jl_value_t *ti = (jl_value_t*)matc->spec_types; + for (size_t j = depth - 1; j < stack->len; j++) { + if (i == j) + continue; + size_t idx2 = (size_t)stack->items[j]; + jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(t, idx2); + jl_method_t *m2 = matc2->method; + int subt2 = matc2->fully_covers != NOT_FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m2->sig) + // if their intersection contributes to the ambiguity cycle + // and the contribution of m is fully ambiguous with the portion of the cycle from m2 + if (subt2 || jl_subtype((jl_value_t*)ti, m2->sig)) { + // but they aren't themselves simply ordered (here + // 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)) { + visited->items[childidx] = (void*)-1; + break; + } + } + } + } + } + } + // copy this cycle into the results + for (size_t i = depth - 1; i < stack->len; i++) { + size_t childidx = (size_t)stack->items[i]; + if ((size_t)visited->items[childidx] == 1) + continue; + if ((size_t)visited->items[childidx] != -1) { + assert(visited->items[childidx] == (void*)(2 + i)); + visited->items[childidx] = (void*)-1; + if (lim == -1 || result->len < lim) + arraylist_push(result, (void*)childidx); + else + return -1; + } + } + // now finally cleanup the stack + while (stack->len >= depth) { + size_t childidx = (size_t)arraylist_pop(stack); + // always remove fully_covers matches after the first minmax ambiguity group is handled + //jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(t, childidx); + if (matc->fully_covers != NOT_FULLY_COVERS && !addambig) + *found_minmax = 2; + if (visited->items[childidx] != (void*)-1) + continue; + visited->items[childidx] = (void*)1; + } + return 0; } + + // This is the collect form of calling jl_typemap_intersection_visitor // with optimizations to skip fully shadowed methods. // @@ -2773,6 +3574,9 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int intersections, size_t world, int cache_result, size_t *min_valid, size_t *max_valid, int *ambig) { + JL_TIMING(METHOD_MATCH, METHOD_MATCH); + if (world > jl_atomic_load_acquire(&jl_world_counter)) + return jl_nothing; // the future is not enumerable int has_ambiguity = 0; jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)type); assert(jl_is_datatype(unw)); @@ -2785,10 +3589,11 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, else va = NULL; } - struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, + struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0, + /* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid, /* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0}, - intersections, world, lim, /* .t = */ jl_an_empty_vec_any, - /* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid, /* .matc = */ NULL}; + 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}; jl_value_t *isect2 = NULL; JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2); @@ -2851,20 +3656,20 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, return env.t; } } - if (!jl_typemap_intersection_visitor(mt->defs, 0, &env.match)) { + if (!ml_mtable_visitor(mt, &env.match)) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } else { // else: scan everything if (!jl_foreach_reachable_mtable(ml_mtable_visitor, &env.match)) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } - *min_valid = env.min_valid; - *max_valid = env.max_valid; + *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); @@ -2942,7 +3747,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, } else if (lim == 1) { JL_GC_POP(); - return jl_false; + return jl_nothing; } } else { @@ -2952,273 +3757,151 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, len = 1; } } + if (minmax && lim == 0) { + // protect some later algorithms from underflow + JL_GC_POP(); + return jl_nothing; + } } if (len > 1) { - // need to partially domsort the graph now into a list - // (this is an insertion sort attempt) - // if we have a minmax method, we ignore anything less specific - // we'll clean that up next - for (i = 1; i < len; i++) { - env.matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); - jl_method_t *m = env.matc->method; - int subt = env.matc->fully_covers != NOT_FULLY_COVERS; - if ((minmax != NULL || (minmax_ambig && !include_ambiguous)) && subt) { - continue; // already the biggest (skip will filter others) - } - for (j = 0; j < i; j++) { - jl_method_match_t *matc2 = (jl_method_match_t *)jl_array_ptr_ref(env.t, i - j - 1); - jl_method_t *m2 = matc2->method; - int subt2 = matc2->fully_covers != NOT_FULLY_COVERS; - if (!subt2 && subt) - break; - if (subt == subt2) { - if (lim >= 0) { - if (subt || !jl_has_empty_intersection(m->sig, m2->sig)) - if (!jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig)) - break; - } - else { - // if unlimited, use approximate sorting, with the only - // main downside being that it may be overly- - // conservative at reporting existence of ambiguities - if (jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) - break; - } - } - jl_array_ptr_set(env.t, i - j, matc2); - } - jl_array_ptr_set(env.t, i - j, env.matc); - } - char *skip = (char*)alloca(len); - memset(skip, 0, len); + arraylist_t stack, visited, result, allambig; + arraylist_new(&result, lim != -1 && lim < len ? lim : len); + arraylist_new(&stack, 0); + arraylist_new(&visited, len); + arraylist_new(&allambig, len); + arraylist_grow(&visited, len); + memset(visited.items, 0, len * sizeof(size_t)); // if we had a minmax method (any subtypes), now may now be able to - // quickly cleanup some of our sort result - if (minmax != NULL || (minmax_ambig && !include_ambiguous)) { - for (i = 0; i < len; i++) { - jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); - if (minmax != matc && matc->fully_covers != NOT_FULLY_COVERS) { - skip[i] = 1; - } - } - } - if (include_ambiguous && lim == -1 && ambig == NULL && !minmax_ambig) { - // in this case, we don't actually need to compute the ambiguity - // information at all as the user doesn't need us to filter them - // out or report them - } - else { - // now that the results are (mostly) sorted, assign group numbers to each ambiguity - // by computing the specificity-ambiguity matrix covering this query - uint32_t *ambig_groupid = (uint32_t*)alloca(len * sizeof(uint32_t)); - for (i = 0; i < len; i++) - ambig_groupid[i] = i; - // as we go, keep a rough count of how many methods are disjoint, which - // gives us a lower bound on how many methods we will be returning - // and lets us stop early if we reach our limit - int ndisjoint = minmax ? 1 : 0; - for (i = 0; i < len; i++) { - jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); - if (skip[i]) { - // if there was a minmax method, we can just pretend the rest are all in the same group: - // they're all together but unsorted in the list, since we'll drop them all later anyways - assert(matc->fully_covers != NOT_FULLY_COVERS); - if (ambig_groupid[len - 1] > i) - ambig_groupid[len - 1] = i; // ambiguity covering range [i:len) - break; - } - jl_method_t *m = matc->method; - int subt = matc->fully_covers == FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) - int rsubt = jl_egal((jl_value_t*)matc->spec_types, m->sig); - int disjoint = 1; - for (j = len; j > i; j--) { - if (ambig_groupid[j - 1] < i) { - disjoint = 0; - break; - } - jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(env.t, j - 1); - // can't use skip[j - 1] here, since we still need to make sure the minmax dominates - jl_method_t *m2 = matc2->method; - int subt2 = matc2->fully_covers == FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m2->sig) - int rsubt2 = jl_egal((jl_value_t*)matc2->spec_types, m2->sig); - jl_value_t *ti; - if (!subt && !subt2 && rsubt && rsubt2 && lim == -1 && ambig == NULL) - // these would only be filtered out of the list as - // ambiguous if they are also type-equal, as we - // aren't skipping matches and the user doesn't - // care if we report any ambiguities - continue; - if (jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig)) - continue; - if (subt) { - ti = (jl_value_t*)matc2->spec_types; - isect2 = NULL; - } - else if (subt2) { - ti = (jl_value_t*)matc->spec_types; - isect2 = NULL; - } - else { - jl_type_intersection2((jl_value_t*)matc->spec_types, (jl_value_t*)matc2->spec_types, &env.match.ti, &isect2); - ti = env.match.ti; - } - if (ti != jl_bottom_type) { - disjoint = 0; - // m and m2 are ambiguous, but let's see if we can find another method (m3) - // that dominates their intersection, and means we can ignore this - size_t k; - for (k = i; k > 0; k--) { - jl_method_match_t *matc3 = (jl_method_match_t*)jl_array_ptr_ref(env.t, k - 1); - 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)) - break; - } - if (k == 0) { - ambig_groupid[j - 1] = i; // ambiguity covering range [i:j) - isect2 = NULL; - break; - } - } - isect2 = NULL; - } - if (disjoint && lim >= 0) { - ndisjoint += 1; - if (ndisjoint > lim) { - JL_GC_POP(); - return jl_false; - } - } - } - // then we'll merge those numbers to assign each item in the group the same number - uint32_t groupid = 0; - uint32_t grouphi = 0; - for (i = 0; i < len; i++) { - j = len - i - 1; - uint32_t agid = ambig_groupid[j]; - if (agid != j) { // thus agid < j - if (grouphi == 0) { - groupid = agid; - grouphi = j; - } - else if (agid < groupid) { - groupid = agid; - } - } - if (grouphi && j == groupid) { - do { - ambig_groupid[grouphi--] = groupid; - } while (grouphi > j); - ambig_groupid[j] = groupid; - groupid = 0; - grouphi = 0; - } - } - // always remove matches after the first subtype, now that we've sorted the list for ambiguities - for (i = 0; i < len; i++) { - jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); - if (matc->fully_covers == FULLY_COVERS) { // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) - uint32_t agid = ambig_groupid[i]; - while (i < len && agid == ambig_groupid[i]) - i++; // keep ambiguous ones - for (; i < len; i++) - skip[i] = 1; // drop the rest - } + // quickly cleanup some of methods + int found_minmax = 0; + if (minmax != NULL) + found_minmax = 2; + else if (minmax_ambig && !include_ambiguous) + found_minmax = 1; + if (ambig == NULL) // if we don't care about the result, set it now so we won't bother attempting to compute it accurately later + has_ambiguity = 1; + for (i = 0; i < len; i++) { + assert(visited.items[i] == (void*)0 || visited.items[i] == (void*)1); + jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); + if (matc->fully_covers != NOT_FULLY_COVERS && found_minmax) { + // this was already handled above and below, so we won't learn anything new + // by visiting it and it might be a bit costly + continue; } - // when limited, skip matches that are covered by earlier ones (and aren't perhaps ambiguous with them) - if (lim >= 0) { - for (i = 0; i < len; i++) { - if (skip[i]) - continue; - jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); - jl_method_t *m = matc->method; - jl_tupletype_t *ti = matc->spec_types; - if (matc->fully_covers == FULLY_COVERS) - break; // remaining matches are ambiguous or already skipped - for (j = 0; j < i; j++) { - jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(env.t, j); - jl_method_t *m2 = matc2->method; - if (jl_subtype((jl_value_t*)ti, m2->sig)) { - if (ambig_groupid[i] != ambig_groupid[j]) { - skip[i] = 1; - break; - } - else if (!include_ambiguous) { - if (!jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig)) { - skip[i] = 1; - break; - } - } - } - } - } + int child_cycle = sort_mlmatches((jl_array_t*)env.t, i, &visited, &stack, &result, &allambig, lim == -1 || minmax == NULL ? lim : lim - 1, include_ambiguous, &has_ambiguity, &found_minmax); + if (child_cycle == -1) { + arraylist_free(&allambig); + arraylist_free(&visited); + arraylist_free(&stack); + arraylist_free(&result); + JL_GC_POP(); + return jl_nothing; } - // Compute whether anything could be ambiguous by seeing if any two - // remaining methods in the result are in the same ambiguity group. - assert(len > 0); - uint32_t agid = ambig_groupid[0]; - for (i = 1; i < len; i++) { - if (!skip[i]) { - if (agid == ambig_groupid[i]) { - has_ambiguity = 1; - break; - } - agid = ambig_groupid[i]; - } + assert(child_cycle == 0); (void)child_cycle; + assert(stack.len == 0); + assert(visited.items[i] == (void*)1); + } + // now compute whether there were ambiguities left in this cycle + if (has_ambiguity == 0 && allambig.len > 0) { + if (lim == -1) { + // lim is over-approximated, so has_ambiguities is too + has_ambiguity = 1; } - // If we're only returning possible matches, now filter out any method - // whose intersection is fully ambiguous with the group it is in. - if (!include_ambiguous) { - for (i = 0; i < len; i++) { - if (skip[i]) - continue; - uint32_t agid = ambig_groupid[i]; - jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); + else { + // go back and find the additional ambiguous methods and temporary add them to the stack + // (potentially duplicating them from lower on the stack to here) + jl_value_t *ti = NULL; + jl_value_t *isect2 = NULL; + JL_GC_PUSH2(&ti, &isect2); + for (size_t i = 0; i < allambig.len; i++) { + size_t idx = (size_t)allambig.items[i]; + jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, idx); jl_method_t *m = matc->method; - jl_tupletype_t *ti = matc->spec_types; int subt = matc->fully_covers == FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) - char ambig1 = 0; - for (j = agid; j < len && ambig_groupid[j] == agid; j++) { - if (j == i) + for (size_t idx2 = 0; idx2 < jl_array_len(env.t); idx2++) { + if (idx2 == idx) continue; - jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(env.t, j); + // laborious test, checking for existence and coverage of another method (m3) + // outside of the ambiguity group that dominates any ambiguous methods, + // and means we can ignore this for has_ambiguity + // (has_ambiguity is overestimated for lim==-1, since we don't compute skipped matches either) + // n.b. even if we skipped them earlier, they still might + // contribute to the ambiguities (due to lock of transitivity of + // morespecific over subtyping) + // TODO: we could improve this result by checking if the removal of some + // edge earlier means that this subgraph is now well-ordered and then be + // allowed to ignore these vertexes entirely here + jl_method_match_t *matc2 = (jl_method_match_t*)jl_array_ptr_ref(env.t, idx2); jl_method_t *m2 = matc2->method; int subt2 = matc2->fully_covers == FULLY_COVERS; // jl_subtype((jl_value_t*)type, (jl_value_t*)m2->sig) + if (subt) { + ti = (jl_value_t*)matc2->spec_types; + isect2 = NULL; + } + else if (subt2) { + ti = (jl_value_t*)matc->spec_types; + isect2 = NULL; + } + else { + jl_type_intersection2((jl_value_t*)matc->spec_types, (jl_value_t*)matc2->spec_types, &ti, &isect2); + } // if their intersection contributes to the ambiguity cycle - if (subt || subt2 || !jl_has_empty_intersection((jl_value_t*)ti, m2->sig)) { - // and the contribution of m is ambiguous with the portion of the cycle from m2 - if (subt2 || jl_subtype((jl_value_t*)ti, m2->sig)) { - // but they aren't themselves simply ordered (here - // 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)) { - ambig1 = 1; - } + 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)) + continue; + // now look for a third method m3 that dominated these and that fully covered this intersection already + size_t k; + for (k = 0; k < result.len; k++) { + size_t idx3 = (size_t)result.items[k]; + if (idx3 == idx || idx3 == idx2) { + has_ambiguity = 1; + break; } - else { - // otherwise some aspect of m is not ambiguous - ambig1 = 0; + 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)) { + //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 + // j = len; break; } } + if (k == result.len) + has_ambiguity = 1; + isect2 = NULL; + ti = NULL; + if (has_ambiguity) + break; } - if (ambig1) - skip[i] = 1; + if (has_ambiguity) + break; } + JL_GC_POP(); } } - // cleanup array to remove skipped entries - for (i = 0, j = 0; i < len; i++) { + arraylist_free(&allambig); + arraylist_free(&visited); + arraylist_free(&stack); + for (j = 0; j < result.len; j++) { + i = (size_t)result.items[j]; jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i); - if (!skip[i]) { - jl_array_ptr_set(env.t, j++, matc); - // remove our sentinel entry markers - if (matc->fully_covers == SENTINEL) - matc->fully_covers = NOT_FULLY_COVERS; - } + // remove our sentinel entry markers + if (matc->fully_covers == SENTINEL) + matc->fully_covers = NOT_FULLY_COVERS; + result.items[j] = (void*)matc; + } + if (minmax) { + arraylist_push(&result, minmax); + j++; } + memcpy(jl_array_data(env.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; @@ -3229,7 +3912,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_method_t *meth = env.matc->method; jl_svec_t *tpenv = env.matc->sparams; JL_LOCK(&mt->writelock); - cache_method(mt, &mt->cache, (jl_value_t*)mt, (jl_tupletype_t*)unw, meth, world, env.min_valid, env.max_valid, tpenv); + cache_method(mt, &mt->cache, (jl_value_t*)mt, (jl_tupletype_t*)unw, meth, world, env.match.min_valid, env.match.max_valid, tpenv); JL_UNLOCK(&mt->writelock); } } @@ -3237,7 +3920,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, *ambig = has_ambiguity; JL_GC_POP(); if (lim >= 0 && len > lim) - return jl_false; + return jl_nothing; return env.t; } @@ -3256,32 +3939,35 @@ int jl_has_concrete_subtype(jl_value_t *typ) return ((jl_datatype_t*)typ)->has_concrete_subtype; } -// TODO: separate the codegen and typeinf locks -// currently using a coarser lock seems like -// the best way to avoid acquisition priority -// ordering violations -//static jl_mutex_t typeinf_lock; -#define typeinf_lock jl_codegen_lock - -static uint64_t inference_start_time = 0; -static uint8_t inference_is_measuring_compile_time = 0; +JL_DLLEXPORT uint64_t jl_typeinf_timing_begin(void) +{ + jl_task_t *ct = jl_current_task; + if (ct->reentrant_timing & 1) + return 0; + ct->reentrant_timing |= 1; + return jl_hrtime(); +} -JL_DLLEXPORT void jl_typeinf_begin(void) +JL_DLLEXPORT void jl_typeinf_timing_end(uint64_t start) { - JL_LOCK(&typeinf_lock); + if (!start) + return; + jl_task_t *ct = jl_current_task; + ct->reentrant_timing &= ~1u; if (jl_atomic_load_relaxed(&jl_measure_compile_time_enabled)) { - inference_start_time = jl_hrtime(); - inference_is_measuring_compile_time = 1; + uint64_t inftime = jl_hrtime() - start; + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, inftime); } } -JL_DLLEXPORT void jl_typeinf_end(void) +JL_DLLEXPORT void jl_typeinf_lock_begin(void) { - if (typeinf_lock.count == 1 && inference_is_measuring_compile_time) { - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - inference_start_time)); - inference_is_measuring_compile_time = 0; - } - JL_UNLOCK(&typeinf_lock); + JL_LOCK(&jl_codegen_lock); +} + +JL_DLLEXPORT void jl_typeinf_lock_end(void) +{ + JL_UNLOCK(&jl_codegen_lock); } #ifdef __cplusplus diff --git a/src/iddict.c b/src/iddict.c index e6c9eee44b980..1fa8a67d1ae96 100644 --- a/src/iddict.c +++ b/src/iddict.c @@ -81,9 +81,9 @@ static inline int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_ } while (iter <= maxprobe && index != orig); if (empty_slot != -1) { - jl_atomic_store_relaxed(&tab[empty_slot], key); + jl_atomic_store_release(&tab[empty_slot], key); jl_gc_wb(a, key); - jl_atomic_store_relaxed(&tab[empty_slot + 1], val); + jl_atomic_store_release(&tab[empty_slot + 1], val); jl_gc_wb(a, val); return 1; } @@ -159,6 +159,12 @@ jl_value_t *jl_eqtable_get(jl_array_t *h, jl_value_t *key, jl_value_t *deflt) JL 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 +{ + _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) { diff --git a/src/init.c b/src/init.c index 724261704836e..02769e03c668e 100644 --- a/src/init.c +++ b/src/init.c @@ -10,8 +10,8 @@ #include #include #include - #include +#include // defines dirname #if !defined(_OS_WINDOWS_) || defined(_COMPILER_GCC_) #include @@ -34,8 +34,6 @@ extern "C" { #endif -#include - #ifdef _OS_WINDOWS_ extern int needsSymRefreshModuleList; extern BOOL (WINAPI *hSymRefreshModuleList)(HANDLE); @@ -74,16 +72,20 @@ void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi) pthread_attr_getstack(&attr, &stackaddr, &stacksize); pthread_attr_destroy(&attr); *stack_lo = (void*)stackaddr; - *stack_hi = (void*)&stacksize; +#pragma GCC diagnostic push +#if defined(_COMPILER_GCC_) && __GNUC__ >= 12 +#pragma GCC diagnostic ignored "-Wdangling-pointer" +#endif + *stack_hi = (void*)__builtin_frame_address(0); +#pragma GCC diagnostic pop return; # elif defined(_OS_DARWIN_) extern void *pthread_get_stackaddr_np(pthread_t thread); extern size_t pthread_get_stacksize_np(pthread_t thread); pthread_t thread = pthread_self(); void *stackaddr = pthread_get_stackaddr_np(thread); - size_t stacksize = pthread_get_stacksize_np(thread); *stack_lo = (void*)stackaddr; - *stack_hi = (void*)&stacksize; + *stack_hi = (void*)__builtin_frame_address(0); return; # elif defined(_OS_FREEBSD_) pthread_attr_t attr; @@ -94,7 +96,7 @@ void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi) pthread_attr_getstack(&attr, &stackaddr, &stacksize); pthread_attr_destroy(&attr); *stack_lo = (void*)stackaddr; - *stack_hi = (void*)&stacksize; + *stack_hi = (void*)__builtin_frame_address(0); return; # else # warning "Getting precise stack size for thread is not supported." @@ -103,7 +105,7 @@ void jl_init_stack_limits(int ismaster, void **stack_lo, void **stack_hi) struct rlimit rl; getrlimit(RLIMIT_STACK, &rl); size_t stacksize = rl.rlim_cur; - *stack_hi = (void*)&stacksize; + *stack_hi = __builtin_frame_address(0); *stack_lo = (void*)((char*)*stack_hi - stacksize); #endif } @@ -167,8 +169,7 @@ static void jl_close_item_atexit(uv_handle_t *handle) switch(handle->type) { case UV_PROCESS: // cause Julia to forget about the Process object - if (handle->data) - jl_uv_call_close_callback((jl_value_t*)handle->data); + handle->data = NULL; // and make libuv think it is already dead ((uv_process_t*)handle)->pid = 0; // fall-through @@ -196,27 +197,87 @@ static void jl_close_item_atexit(uv_handle_t *handle) } } -JL_DLLEXPORT void jl_atexit_hook(int exitcode) +// This prevents `ct` from returning via error handlers or other unintentional +// means by destroying some old state before we start destroying that state in atexit hooks. +void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT; + +// cause this process to exit with WEXITSTATUS(signo), after waiting to finish all julia, C, and C++ cleanup +JL_DLLEXPORT void jl_exit(int exitcode) +{ + jl_atexit_hook(exitcode); + exit(exitcode); +} + +// cause this process to exit with WTERMSIG(signo), +// fairly aggressively (flushing stderr a bit, and doing a little bit of other +// external cleanup, but no internal cleanup) +JL_DLLEXPORT void jl_raise(int signo) { - if (jl_all_tls_states == NULL) + uv_tty_reset_mode(); + fflush(NULL); +#ifdef _OS_WINDOWS_ + if (signo == SIGABRT) { + signal(signo, SIG_DFL); + abort(); + } + // the exit status could also potentially be set to an NTSTATUS value + // corresponding to a signal number, but this seems somewhat is uncommon on Windows + TerminateProcess(GetCurrentProcess(), 3); // aka _exit + abort(); // prior call does not return, because we passed GetCurrentProcess() +#else + signal(signo, SIG_DFL); + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, signo); + pthread_sigmask(SIG_UNBLOCK, &sset, NULL); + raise(signo); // aka pthread_kill(pthread_self(), signo); + if (signo == SIGABRT) + abort(); + _exit(128 + signo); +#endif +} + +JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER +{ + uv_tty_reset_mode(); + + if (jl_atomic_load_relaxed(&jl_all_tls_states) == NULL) return; - jl_task_t *ct = jl_current_task; + jl_task_t *ct = jl_get_current_task(); + + if (ct) { + if (exitcode == 0) + jl_write_compiler_output(); + // 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); - if (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(); + if (jl_base_module) { jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("_atexit")); if (f != NULL) { + jl_value_t **fargs; + JL_GC_PUSHARGS(fargs, 2); + fargs[0] = f; + fargs[1] = jl_box_int32(exitcode); JL_TRY { + assert(ct); size_t last_age = ct->world_age; ct->world_age = jl_get_world_counter(); - jl_apply(&f, 1); + jl_apply(fargs, 2); ct->world_age = last_age; } JL_CATCH { @@ -225,6 +286,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO } + JL_GC_POP(); } } @@ -233,61 +295,93 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_STDOUT = (uv_stream_t*) STDOUT_FILENO; JL_STDERR = (uv_stream_t*) STDERR_FILENO; - jl_gc_run_all_finalizers(ct); + if (ct) + jl_gc_run_all_finalizers(ct); uv_loop_t *loop = jl_global_event_loop(); - - if (loop == NULL) { - return; - } - - struct uv_shutdown_queue queue = {NULL, NULL}; - JL_UV_LOCK(); - uv_walk(loop, jl_uv_exitcleanup_walk, &queue); - struct uv_shutdown_queue_item *item = queue.first; - if (ct != NULL) { - while (item) { - JL_TRY { - while (item) { - jl_close_item_atexit(item->h); + if (loop != NULL) { + struct uv_shutdown_queue queue = {NULL, NULL}; + JL_UV_LOCK(); + uv_walk(loop, jl_uv_exitcleanup_walk, &queue); + struct uv_shutdown_queue_item *item = queue.first; + if (ct) { + while (item) { + JL_TRY { + while (item) { + jl_close_item_atexit(item->h); + item = next_shutdown_queue_item(item); + } + } + JL_CATCH { + //error handling -- continue cleanup, as much as possible + 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_printf((JL_STREAM*)STDERR_FILENO, "\n"); + jlbacktrace(); // written to STDERR_FILENO item = next_shutdown_queue_item(item); } } - JL_CATCH { - //error handling -- continue cleanup, as much as possible - 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_printf((JL_STREAM*)STDERR_FILENO, "\n"); - jlbacktrace(); // written to STDERR_FILENO + } + else { + while (item) { + jl_close_item_atexit(item->h); item = next_shutdown_queue_item(item); } } + + // force libuv to spin until everything has finished closing + loop->stop_flag = 0; + while (uv_run(loop, UV_RUN_DEFAULT)) { } + jl_wake_libuv(); // set the async pending flag, so that future calls are immediate no-ops on other threads + // we would like to guarantee this, but cannot currently, so there is still a small race window + // that needs to be fixed in libuv } - else { - while (item) { - jl_close_item_atexit(item->h); - item = next_shutdown_queue_item(item); - } + if (ct) + (void)jl_gc_safe_enter(ct->ptls); // park in gc-safe + if (loop != NULL) { + // TODO: consider uv_loop_close(loop) here, before shutdown? + uv_library_shutdown(); + // no JL_UV_UNLOCK(), since it is now torn down } - // force libuv to spin until everything has finished closing - loop->stop_flag = 0; - while (uv_run(loop, UV_RUN_DEFAULT)) { } - JL_UV_UNLOCK(); + // TODO: Destroy threads? - // TODO: Destroy threads - - jl_destroy_timing(); -#ifdef ENABLE_TIMINGS + jl_destroy_timing(); // cleans up the current timing_stack for noreturn +#ifdef USE_TIMING_COUNTS jl_print_timings(); #endif + jl_teardown_codegen(); // prints stats +} - jl_teardown_codegen(); +JL_DLLEXPORT void jl_postoutput_hook(void) +{ + if (jl_atomic_load_relaxed(&jl_all_tls_states) == NULL) + return; + + if (jl_base_module) { + jl_task_t *ct = jl_get_current_task(); + jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("_postoutput")); + if (f != NULL) { + JL_TRY { + size_t last_age = ct->world_age; + ct->world_age = jl_get_world_counter(); + jl_apply(&f, 1); + ct->world_age = last_age; + } + JL_CATCH { + jl_printf((JL_STREAM*)STDERR_FILENO, "\npostoutput hook threw an error: "); + jl_static_show((JL_STREAM*)STDERR_FILENO, jl_current_exception()); + jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); + jlbacktrace(); // written to STDERR_FILENO + } + } + } + return; } -static void post_boot_hooks(void); +void post_boot_hooks(void); JL_DLLEXPORT void *jl_libjulia_internal_handle; JL_DLLEXPORT void *jl_libjulia_handle; @@ -583,6 +677,8 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) jl_options.machine_file = abspath(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); const char **cmdp = jl_options.cmds; if (cmdp) { @@ -595,6 +691,13 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) } } +JL_DLLEXPORT int jl_is_file_tracked(jl_sym_t *path) +{ + const char* path_ = jl_symbol_name(path); + int tpath_len = strlen(jl_options.tracked_path); + return (strlen(path_) >= tpath_len) && (strncmp(path_, jl_options.tracked_path, tpath_len) == 0); +} + static void jl_set_io_wait(int v) { jl_task_t *ct = jl_current_task; @@ -602,6 +705,9 @@ static void jl_set_io_wait(int v) } 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; static void restore_fp_env(void) { @@ -614,17 +720,45 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ JL_DLLEXPORT int jl_default_debug_info_kind; +static void init_global_mutexes(void) { + JL_MUTEX_INIT(&jl_modules_mutex, "jl_modules_mutex"); + JL_MUTEX_INIT(&precomp_statement_out_lock, "precomp_statement_out_lock"); + JL_MUTEX_INIT(&newly_inferred_mutex, "newly_inferred_mutex"); + 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_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) { - jl_default_debug_info_kind = 0; - + // initialize many things, in no particular order + // but generally running from simple platform things to optional + // configuration features jl_init_timing(); // Make sure we finalize the tls callback before starting any threads. (void)jl_get_pgcstack(); - jl_safepoint_init(); + + // initialize backtraces + jl_init_profile_lock(); +#ifdef _OS_WINDOWS_ + uv_mutex_init(&jl_in_stackwalk); + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_IGNORE_CVREC); + if (!SymInitialize(GetCurrentProcess(), "", 1)) { + jl_safe_printf("WARNING: failed to initialize stack walk info\n"); + } + needsSymRefreshModuleList = 0; +#else + // nongnu libunwind initialization is only threadsafe on architecture where the + // author could access TSAN, per https://github.com/libunwind/libunwind/pull/109 + // so we need to do this once early (before threads) + rec_backtrace(NULL, 0, 0); +#endif + libsupport_init(); + jl_safepoint_init(); + jl_page_size = jl_getpagesize(); htable_new(&jl_current_modules, 0); - JL_MUTEX_INIT(&jl_modules_mutex); + init_global_mutexes(); jl_precompile_toplevel_module = NULL; ios_set_io_wait_func = jl_set_io_wait; jl_io_loop = uv_default_loop(); // this loop will internal events (spawning process etc.), @@ -632,34 +766,30 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) jl_init_uv(); init_stdio(); restore_fp_env(); - restore_signals(); + if (jl_options.handle_signals == JL_OPTIONS_HANDLE_SIGNALS_ON) + restore_signals(); + jl_init_intrinsic_properties(); - jl_page_size = jl_getpagesize(); + // Important offset for external codegen. + jl_task_gcstack_offset = offsetof(jl_task_t, gcstack); + jl_task_ptls_offset = offsetof(jl_task_t, ptls); + jl_prep_sanitizers(); void *stack_lo, *stack_hi; jl_init_stack_limits(1, &stack_lo, &stack_hi); - jl_libjulia_internal_handle = jl_load_dynamic_library(NULL, JL_RTLD_DEFAULT, 1); + jl_libjulia_internal_handle = jl_find_dynamic_library_by_addr(&jl_load_dynamic_library); + jl_libjulia_handle = jl_find_dynamic_library_by_addr(&jl_any_type); #ifdef _OS_WINDOWS_ jl_exe_handle = GetModuleHandleA(NULL); jl_RTLD_DEFAULT_handle = jl_libjulia_internal_handle; - if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - (LPCWSTR)&jl_any_type, - (HMODULE*)&jl_libjulia_handle)) { - jl_error("could not load base module"); - } - jl_ntdll_handle = jl_dlopen("ntdll.dll", 0); // bypass julia's pathchecking for system dlls - jl_kernel32_handle = jl_dlopen("kernel32.dll", 0); - jl_crtdll_handle = jl_dlopen(jl_crtdll_name, 0); - jl_winsock_handle = jl_dlopen("ws2_32.dll", 0); - uv_mutex_init(&jl_in_stackwalk); - SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_IGNORE_CVREC); - if (!SymInitialize(GetCurrentProcess(), "", 1)) { - jl_printf(JL_STDERR, "WARNING: failed to initialize stack walk info\n"); - } + jl_ntdll_handle = jl_dlopen("ntdll.dll", JL_RTLD_NOLOAD); // bypass julia's pathchecking for system dlls + jl_kernel32_handle = jl_dlopen("kernel32.dll", JL_RTLD_NOLOAD); + jl_crtdll_handle = jl_dlopen(jl_crtdll_name, JL_RTLD_NOLOAD); + jl_winsock_handle = jl_dlopen("ws2_32.dll", JL_RTLD_NOLOAD); + HMODULE jl_dbghelp = (HMODULE) jl_dlopen("dbghelp.dll", JL_RTLD_NOLOAD); needsSymRefreshModuleList = 0; - HMODULE jl_dbghelp = (HMODULE) jl_dlopen("dbghelp.dll", 0); if (jl_dbghelp) jl_dlsym(jl_dbghelp, "SymRefreshModuleList", (void **)&hSymRefreshModuleList, 1); #else @@ -676,34 +806,51 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) 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_gc_init(); jl_init_tasks(); jl_init_threading(); + jl_init_threadinginfra(); + if (jl_options.handle_signals == JL_OPTIONS_HANDLE_SIGNALS_ON) + jl_install_default_signal_handlers(); + + jl_gc_init(); + + arraylist_new(&jl_linkage_blobs, 0); + arraylist_new(&jl_image_relocs, 0); + arraylist_new(&eytzinger_image_tree, 0); + arraylist_new(&eytzinger_idxs, 0); + arraylist_push(&eytzinger_idxs, (void*)0); + arraylist_push(&eytzinger_image_tree, (void*)1); // outside image jl_ptls_t ptls = jl_init_threadtls(0); +#pragma GCC diagnostic push +#if defined(_COMPILER_GCC_) && __GNUC__ >= 12 +#pragma GCC diagnostic ignored "-Wdangling-pointer" +#endif // 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); +#pragma GCC diagnostic pop JL_GC_PROMISE_ROOTED(ct); _finish_julia_init(rel, ptls, ct); } static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_task_t *ct) { - jl_init_threadinginfra(); - + JL_TIMING(JULIA_INIT, JULIA_INIT); jl_resolve_sysimg_location(rel); // loads sysimg if available, and conditionally sets jl_options.cpu_target if (jl_options.image_file) jl_preload_sysimg_so(jl_options.image_file); if (jl_options.cpu_target == NULL) jl_options.cpu_target = "native"; + jl_init_codegen(); if (jl_options.image_file) { jl_restore_system_image(jl_options.image_file); } else { jl_init_types(); - jl_init_codegen(); + jl_global_roots_table = jl_alloc_vec_any(0); } jl_init_common_symbols(); @@ -711,7 +858,7 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_init_serializer(); if (!jl_options.image_file) { - jl_core_module = jl_new_module(jl_symbol("Core")); + 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; @@ -722,26 +869,13 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ post_boot_hooks(); } - if (jl_base_module != NULL) { - // Do initialization needed before starting child threads - jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("__preinit_threads__")); - if (f) { - size_t last_age = ct->world_age; - ct->world_age = jl_get_world_counter(); - jl_apply(&f, 1); - ct->world_age = last_age; - } - } - else { + if (jl_base_module == NULL) { // nthreads > 1 requires code in Base - jl_n_threads = 1; + jl_atomic_store_relaxed(&jl_n_threads, 1); + jl_n_gcthreads = 0; } jl_start_threads(); - // This needs to be after jl_start_threads - if (jl_options.handle_signals == JL_OPTIONS_HANDLE_SIGNALS_ON) - jl_install_default_signal_handlers(); - jl_gc_enable(1); if (jl_options.image_file && (!jl_generating_output() || jl_options.incremental) && jl_module_init_order) { @@ -760,80 +894,6 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_ jl_install_sigint_handler(); } -static jl_value_t *core(const char *name) -{ - return jl_get_global(jl_core_module, jl_symbol(name)); -} - -// fetch references to things defined in boot.jl -static void post_boot_hooks(void) -{ - jl_char_type = (jl_datatype_t*)core("Char"); - jl_int8_type = (jl_datatype_t*)core("Int8"); - jl_int16_type = (jl_datatype_t*)core("Int16"); - jl_uint16_type = (jl_datatype_t*)core("UInt16"); - jl_float16_type = (jl_datatype_t*)core("Float16"); - jl_float32_type = (jl_datatype_t*)core("Float32"); - jl_float64_type = (jl_datatype_t*)core("Float64"); - jl_floatingpoint_type = (jl_datatype_t*)core("AbstractFloat"); - jl_number_type = (jl_datatype_t*)core("Number"); - jl_signed_type = (jl_datatype_t*)core("Signed"); - jl_datatype_t *jl_unsigned_type = (jl_datatype_t*)core("Unsigned"); - jl_datatype_t *jl_integer_type = (jl_datatype_t*)core("Integer"); - - jl_bool_type->super = jl_integer_type; - jl_uint8_type->super = jl_unsigned_type; - jl_int32_type->super = jl_signed_type; - jl_int64_type->super = jl_signed_type; - jl_uint32_type->super = jl_unsigned_type; - jl_uint64_type->super = jl_unsigned_type; - - jl_errorexception_type = (jl_datatype_t*)core("ErrorException"); - jl_stackovf_exception = jl_new_struct_uninit((jl_datatype_t*)core("StackOverflowError")); - jl_diverror_exception = jl_new_struct_uninit((jl_datatype_t*)core("DivideError")); - jl_undefref_exception = jl_new_struct_uninit((jl_datatype_t*)core("UndefRefError")); - jl_undefvarerror_type = (jl_datatype_t*)core("UndefVarError"); - jl_atomicerror_type = (jl_datatype_t*)core("ConcurrencyViolationError"); - jl_interrupt_exception = jl_new_struct_uninit((jl_datatype_t*)core("InterruptException")); - jl_boundserror_type = (jl_datatype_t*)core("BoundsError"); - jl_memory_exception = jl_new_struct_uninit((jl_datatype_t*)core("OutOfMemoryError")); - jl_readonlymemory_exception = jl_new_struct_uninit((jl_datatype_t*)core("ReadOnlyMemoryError")); - jl_typeerror_type = (jl_datatype_t*)core("TypeError"); -#ifdef SEGV_EXCEPTION - jl_segv_exception = jl_new_struct_uninit((jl_datatype_t*)core("SegmentationFault")); -#endif - jl_argumenterror_type = (jl_datatype_t*)core("ArgumentError"); - jl_methoderror_type = (jl_datatype_t*)core("MethodError"); - jl_loaderror_type = (jl_datatype_t*)core("LoadError"); - jl_initerror_type = (jl_datatype_t*)core("InitError"); - jl_pair_type = core("Pair"); - - jl_weakref_type = (jl_datatype_t*)core("WeakRef"); - jl_vecelement_typename = ((jl_datatype_t*)jl_unwrap_unionall(core("VecElement")))->name; - - jl_init_box_caches(); - - // set module field of primitive types - int i; - void **table = jl_core_module->bindings.table; - for (i = 1; i < jl_core_module->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - jl_value_t *v = jl_atomic_load_relaxed(&b->value); - if (v) { - if (jl_is_unionall(v)) - v = jl_unwrap_unionall(v); - if (jl_is_datatype(v)) { - jl_datatype_t *tt = (jl_datatype_t*)v; - tt->name->module = jl_core_module; - if (tt->name->mt) - tt->name->mt->module = jl_core_module; - } - } - } - } -} - #ifdef __cplusplus } #endif diff --git a/src/interpreter.c b/src/interpreter.c index 26038b4cfef35..c08496f72ce04 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -91,10 +91,9 @@ static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s) if (!jl_is_symbol(fname)) { jl_error("method: invalid declaration"); } - jl_value_t *bp_owner = (jl_value_t*)modu; jl_binding_t *b = jl_get_binding_for_method_def(modu, fname); _Atomic(jl_value_t*) *bp = &b->value; - jl_value_t *gf = jl_generic_function_def(b->name, b->owner, bp, bp_owner, b); + jl_value_t *gf = jl_generic_function_def(fname, modu, bp, b); return gf; } @@ -103,7 +102,7 @@ static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s) fname = eval_value(args[0], s); jl_methtable_t *mt = NULL; - if (jl_typeis(fname, jl_methtable_type)) { + if (jl_typetagis(fname, jl_methtable_type)) { mt = (jl_methtable_t*)fname; } atypes = eval_value(args[1], s); @@ -151,6 +150,14 @@ jl_value_t *jl_eval_global_var(jl_module_t *m, jl_sym_t *e) return v; } +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); + return v; +} + static int jl_source_nslots(jl_code_info_t *src) JL_NOTSAFEPOINT { return jl_array_len(src->slotflags); @@ -177,7 +184,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) else return s->locals[jl_source_nslots(src) + id]; } - if (jl_is_slot(e) || jl_is_argument(e)) { + if (jl_is_slotnumber(e) || jl_is_argument(e)) { ssize_t n = jl_slot_number(e); if (src == NULL || n > jl_source_nslots(src) || n < 1 || s->locals == NULL) jl_error("access to invalid slot number"); @@ -190,7 +197,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) return jl_quotenode_value(e); } if (jl_is_globalref(e)) { - return jl_eval_global_var(jl_globalref_mod(e), jl_globalref_name(e)); + return jl_eval_globalref((jl_globalref_t*)e); } if (jl_is_symbol(e)) { // bare symbols appear in toplevel exprs not wrapped in `thunk` return jl_eval_global_var(s->module, (jl_sym_t*)e); @@ -223,7 +230,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) else if (head == jl_isdefined_sym) { jl_value_t *sym = args[0]; int defined = 0; - if (jl_is_slot(sym) || jl_is_argument(sym)) { + if (jl_is_slotnumber(sym) || jl_is_argument(sym)) { ssize_t n = jl_slot_number(sym); if (src == NULL || n > jl_source_nslots(src) || n < 1 || s->locals == NULL) jl_error("access to invalid slot number"); @@ -289,7 +296,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) argv[i] = eval_value(args[i], s); JL_NARGSV(new_opaque_closure, 4); jl_value_t *ret = (jl_value_t*)jl_new_opaque_closure((jl_tupletype_t*)argv[0], argv[1], argv[2], - argv[3], argv+4, nargs-4); + argv[3], argv+4, nargs-4, 1); JL_GC_POP(); return ret; } @@ -465,7 +472,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, if (head == jl_assign_sym) { jl_value_t *lhs = jl_exprarg(stmt, 0); jl_value_t *rhs = eval_value(jl_exprarg(stmt, 1), s); - if (jl_is_slot(lhs)) { + if (jl_is_slotnumber(lhs)) { ssize_t n = jl_slot_number(lhs); assert(n <= jl_source_nslots(s->src) && n > 0); s->locals[n - 1] = rhs; @@ -483,8 +490,8 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, sym = (jl_sym_t*)lhs; } JL_GC_PUSH1(&rhs); - jl_binding_t *b = jl_get_binding_wr(modu, sym, 1); - jl_checked_assignment(b, rhs); + jl_binding_t *b = jl_get_binding_wr(modu, sym); + jl_checked_assignment(b, modu, sym, rhs); JL_GC_POP(); } } @@ -542,6 +549,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, // leave happens during normal control flow, but we must // longjmp to pop the eval_body call for each enter. s->continue_at = next_ip; + asan_unpoison_task_stack(ct, &eh->eh_ctx); jl_longjmp(eh->eh_ctx, 1); } else if (head == jl_pop_exception_sym) { @@ -600,7 +608,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, } else if (jl_is_newvarnode(stmt)) { jl_value_t *var = jl_fieldref(stmt, 0); - assert(jl_is_slot(var)); + assert(jl_is_slotnumber(var)); ssize_t n = jl_slot_number(var); assert(n <= jl_source_nslots(s->src) && n > 0); s->locals[n - 1] = NULL; @@ -618,9 +626,9 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, // preparing method IR for interpreter -jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi) +jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi, size_t world) { - jl_code_info_t *src = (jl_code_info_t*)mi->uninferred; + jl_code_info_t *src = (jl_code_info_t*)jl_atomic_load_relaxed(&mi->uninferred); if (jl_is_method(mi->def.value)) { if (!src || (jl_value_t*)src == jl_nothing) { if (mi->def.method->source) { @@ -628,13 +636,13 @@ jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *mi) } else { assert(mi->def.method->generator); - src = jl_code_for_staged(mi); + src = jl_code_for_staged(mi, world); } } if (src && (jl_value_t*)src != jl_nothing) { JL_GC_PUSH1(&src); - src = jl_uncompress_ir(mi->def.method, NULL, (jl_array_t*)src); - mi->uninferred = (jl_value_t*)src; + src = jl_uncompress_ir(mi->def.method, NULL, (jl_value_t*)src); + jl_atomic_store_release(&mi->uninferred, (jl_value_t*)src); jl_gc_wb(mi, src); JL_GC_POP(); } @@ -651,9 +659,11 @@ jl_value_t *NOINLINE jl_fptr_interpret_call(jl_value_t *f, jl_value_t **args, ui { interpreter_state *s; jl_method_instance_t *mi = codeinst->def; - jl_code_info_t *src = jl_code_for_interpreter(mi); + jl_task_t *ct = jl_current_task; + size_t world = ct->world_age; + jl_code_info_t *src = jl_code_for_interpreter(mi, world); jl_array_t *stmts = src->code; - assert(jl_typeis(stmts, jl_array_any_type)); + assert(jl_typetagis(stmts, jl_array_any_type)); unsigned nroots = jl_source_nslots(src) + jl_source_nssavalues(src) + 2; jl_value_t **locals = NULL; JL_GC_PUSHFRAME(s, locals, nroots); @@ -688,12 +698,12 @@ jl_value_t *NOINLINE jl_fptr_interpret_call(jl_value_t *f, jl_value_t **args, ui return r; } -JL_DLLEXPORT jl_callptr_t jl_fptr_interpret_call_addr = &jl_fptr_interpret_call; +JL_DLLEXPORT const jl_callptr_t jl_fptr_interpret_call_addr = &jl_fptr_interpret_call; 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_array_t*)source->source); + jl_code_info_t *code = jl_uncompress_ir(source, NULL, (jl_value_t*)source->source); interpreter_state *s; unsigned nroots = jl_source_nslots(code) + jl_source_nssavalues(code) + 2; jl_task_t *ct = jl_current_task; @@ -726,8 +736,8 @@ jl_value_t *jl_interpret_opaque_closure(jl_opaque_closure_t *oc, jl_value_t **ar jl_value_t *r = eval_body(code->code, s, 0, 0); locals[0] = r; // GC root JL_GC_PROMISE_ROOTED(r); - jl_typeassert(r, jl_tparam1(jl_typeof(oc))); ct->world_age = last_age; + jl_typeassert(r, jl_tparam1(jl_typeof(oc))); JL_GC_POP(); return r; } @@ -738,7 +748,7 @@ jl_value_t *NOINLINE jl_interpret_toplevel_thunk(jl_module_t *m, jl_code_info_t unsigned nroots = jl_source_nslots(src) + jl_source_nssavalues(src); JL_GC_PUSHFRAME(s, s->locals, nroots); jl_array_t *stmts = src->code; - assert(jl_typeis(stmts, jl_array_any_type)); + assert(jl_typetagis(stmts, jl_array_any_type)); s->src = src; s->module = m; s->sparam_vals = jl_emptysvec; diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 4ca4794ab7733..7bef27f477534 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -6,6 +6,35 @@ namespace JL_I { #include "ccall.cpp" +//Mark our stats as being from intrinsics irgen +#undef DEBUG_TYPE +#define DEBUG_TYPE "julia_irgen_intrinsics" + +STATISTIC(EmittedConstants, "Number of constants emitted"); +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_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"); +STATISTIC(Emitted_bitcast, "Number of bitcast calls emitted"); +STATISTIC(Emitted_trunc_int, "Number of trunc_int calls emitted"); +STATISTIC(Emitted_sext_int, "Number of sext_int calls emitted"); +STATISTIC(Emitted_zext_int, "Number of zext_int calls emitted"); +STATISTIC(Emitted_uitofp, "Number of uitofp calls emitted"); +STATISTIC(Emitted_sitofp, "Number of sitofp calls emitted"); +STATISTIC(Emitted_fptoui, "Number of fptoui calls emitted"); +STATISTIC(Emitted_fptosi, "Number of fptosi calls emitted"); +STATISTIC(Emitted_fptrunc, "Number of fptrunc calls emitted"); +STATISTIC(Emitted_fpext, "Number of fpext calls emitted"); +STATISTIC(Emitted_not_int, "Number of not_int calls emitted"); +STATISTIC(Emitted_have_fma, "Number of have_fma calls emitted"); +STATISTIC(EmittedUntypedIntrinsics, "Number of untyped intrinsics emitted"); + using namespace JL_I; FunctionType *get_intr_args1(LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), {JuliaType::get_prjlvalue_ty(C)}, false); } @@ -14,20 +43,20 @@ FunctionType *get_intr_args3(LLVMContext &C) { return FunctionType::get(JuliaTyp FunctionType *get_intr_args4(LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), {JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C)}, false); } FunctionType *get_intr_args5(LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), {JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C), JuliaType::get_prjlvalue_ty(C)}, false); } -static JuliaFunction *runtime_func[num_intrinsics] = { -#define ADD_I(name, nargs) new JuliaFunction{XSTR(jl_##name), get_intr_args##nargs, nullptr}, +const auto &runtime_func() { + static struct runtime_funcs_t { + std::array *, num_intrinsics> runtime_func; + runtime_funcs_t() : + runtime_func{ +#define ADD_I(name, nargs) new JuliaFunction<>{XSTR(jl_##name), get_intr_args##nargs, nullptr}, #define ADD_HIDDEN ADD_I #define ALIAS(alias, base) nullptr, INTRINSICS #undef ADD_I #undef ADD_HIDDEN #undef ALIAS -}; - -static bool float_func[num_intrinsics]; - -static void jl_init_intrinsic_functions_codegen(void) -{ + } + { #define ADD_I(name, nargs) #define ADD_HIDDEN(name, nargs) #define ALIAS(alias, base) runtime_func[alias] = runtime_func[base]; @@ -35,42 +64,52 @@ static void jl_init_intrinsic_functions_codegen(void) #undef ADD_I #undef ADD_HIDDEN #undef ALIAS + } + } runtime_funcs; + return runtime_funcs.runtime_func; +} - float_func[neg_float] = true; - float_func[neg_float_fast] = true; - float_func[add_float] = true; - float_func[sub_float] = true; - float_func[mul_float] = true; - float_func[div_float] = true; - float_func[rem_float] = true; - float_func[add_float_fast] = true; - float_func[sub_float_fast] = true; - float_func[mul_float_fast] = true; - float_func[div_float_fast] = true; - float_func[rem_float_fast] = true; - float_func[fma_float] = true; - float_func[muladd_float] = true; - float_func[eq_float] = true; - float_func[ne_float] = true; - float_func[lt_float] = true; - float_func[le_float] = true; - float_func[eq_float_fast] = true; - float_func[ne_float_fast] = true; - float_func[lt_float_fast] = true; - float_func[le_float_fast] = true; - float_func[fpiseq] = true; - float_func[abs_float] = true; - float_func[copysign_float] = true; - float_func[ceil_llvm] = true; - float_func[floor_llvm] = true; - float_func[trunc_llvm] = true; - float_func[rint_llvm] = true; - float_func[sqrt_llvm] = true; - float_func[sqrt_llvm_fast] = true; +const auto &float_func() { + static struct float_funcs_t { + std::bitset float_func; + float_funcs_t() { + float_func[neg_float] = true; + float_func[neg_float_fast] = true; + float_func[add_float] = true; + float_func[sub_float] = true; + float_func[mul_float] = true; + float_func[div_float] = true; + float_func[add_float_fast] = true; + float_func[sub_float_fast] = true; + float_func[mul_float_fast] = true; + float_func[div_float_fast] = true; + float_func[fma_float] = true; + float_func[muladd_float] = true; + float_func[eq_float] = true; + float_func[ne_float] = true; + float_func[lt_float] = true; + float_func[le_float] = true; + float_func[eq_float_fast] = true; + float_func[ne_float_fast] = true; + float_func[lt_float_fast] = true; + float_func[le_float_fast] = true; + float_func[fpiseq] = true; + float_func[abs_float] = true; + float_func[copysign_float] = true; + float_func[ceil_llvm] = true; + float_func[floor_llvm] = true; + float_func[trunc_llvm] = true; + float_func[rint_llvm] = true; + float_func[sqrt_llvm] = true; + float_func[sqrt_llvm_fast] = true; + } + } float_funcs; + + return float_funcs.float_func; } -extern "C" -JL_DLLEXPORT uint32_t jl_get_LLVM_VERSION_impl(void) +extern "C" JL_DLLEXPORT_CODEGEN +uint32_t jl_get_LLVM_VERSION_impl(void) { return 10000 * LLVM_VERSION_MAJOR + 100 * LLVM_VERSION_MINOR #ifdef LLVM_VERSION_PATCH @@ -115,13 +154,13 @@ static Type *FLOATT(Type *t) } // convert an llvm type to same-size int type -static Type *INTT(Type *t) +static Type *INTT(Type *t, const DataLayout &DL) { auto &ctxt = t->getContext(); if (t->isIntegerTy()) return t; if (t->isPointerTy()) - return getSizeTy(ctxt); + return DL.getIntPtrType(t); if (t == getDoubleTy(ctxt)) return getInt64Ty(ctxt); if (t == getFloatTy(ctxt)) @@ -287,15 +326,15 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) bool frompointer = ty->isPointerTy(); bool topointer = to->isPointerTy(); const DataLayout &DL = jl_Module->getDataLayout(); - if (ty == getInt1Ty(ctx.builder.getContext()) && to == getInt8Ty(ctx.builder.getContext())) { + if (ty->isIntegerTy(1) && to->isIntegerTy(8)) { // bools may be stored internally as int8 - unboxed = ctx.builder.CreateZExt(unboxed, getInt8Ty(ctx.builder.getContext())); + unboxed = ctx.builder.CreateZExt(unboxed, to); } - else if (ty == getInt8Ty(ctx.builder.getContext()) && to == getInt1Ty(ctx.builder.getContext())) { + else if (ty->isIntegerTy(8) && to->isIntegerTy(1)) { // bools may be stored internally as int8 - unboxed = ctx.builder.CreateTrunc(unboxed, getInt1Ty(ctx.builder.getContext())); + unboxed = ctx.builder.CreateTrunc(unboxed, to); } - else if (ty == getVoidTy(ctx.builder.getContext()) || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { + else if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { // this can happen in dead code //emit_unreachable(ctx); return UndefValue::get(to); @@ -304,22 +343,19 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) unboxed = emit_bitcast(ctx, unboxed, to); } else if (!ty->isIntOrPtrTy() && !ty->isFloatingPointTy()) { -#ifndef JL_NDEBUG - const DataLayout &DL = jl_Module->getDataLayout(); -#endif assert(DL.getTypeSizeInBits(ty) == DL.getTypeSizeInBits(to)); AllocaInst *cast = ctx.builder.CreateAlloca(ty); ctx.builder.CreateStore(unboxed, cast); unboxed = ctx.builder.CreateLoad(to, ctx.builder.CreateBitCast(cast, to->getPointerTo())); } else if (frompointer) { - Type *INTT_to = INTT(to); + Type *INTT_to = INTT(to, DL); unboxed = ctx.builder.CreatePtrToInt(unboxed, INTT_to); if (INTT_to != to) unboxed = ctx.builder.CreateBitCast(unboxed, to); } else if (topointer) { - Type *INTT_to = INTT(to); + Type *INTT_to = INTT(to, DL); if (to != INTT_to) unboxed = ctx.builder.CreateBitCast(unboxed, INTT_to); unboxed = emit_inttoptr(ctx, unboxed, to); @@ -330,8 +366,8 @@ static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) return unboxed; } -// emit code to unpack a raw value from a box into registers or a stack slot -static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_value_t *jt, Value *dest, MDNode *tbaa_dest, bool isVolatile) +// emit code to unpack a raw value from a box into registers +static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_value_t *jt) { assert(to != getVoidTy(ctx.builder.getContext())); // TODO: fully validate that x.typ == jt? @@ -349,86 +385,107 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va Constant *c = x.constant ? julia_const_to_llvm(ctx, x.constant) : NULL; if (!x.ispointer() || c) { // already unboxed, but sometimes need conversion Value *unboxed = c ? c : x.V; - if (!dest) - return emit_unboxed_coercion(ctx, to, unboxed); - Type *dest_ty = unboxed->getType()->getPointerTo(); - if (dest->getType() != dest_ty) - dest = emit_bitcast(ctx, dest, dest_ty); - tbaa_decorate(tbaa_dest, ctx.builder.CreateAlignedStore(unboxed, dest, Align(julia_alignment(jt)))); - return NULL; + return emit_unboxed_coercion(ctx, to, unboxed); } // bools stored as int8, so an extra Trunc is needed to get an int1 Value *p = x.constant ? literal_pointer_val(ctx, x.constant) : x.V; - if (jt == (jl_value_t*)jl_bool_type || to == getInt1Ty(ctx.builder.getContext())) { - Instruction *unbox_load = tbaa_decorate(x.tbaa, ctx.builder.CreateLoad(getInt8Ty(ctx.builder.getContext()), maybe_bitcast(ctx, p, getInt8PtrTy(ctx.builder.getContext())))); + if (jt == (jl_value_t*)jl_bool_type || to->isIntegerTy(1)) { + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); + Instruction *unbox_load = ai.decorateInst(ctx.builder.CreateLoad(getInt8Ty(ctx.builder.getContext()), maybe_bitcast(ctx, p, getInt8PtrTy(ctx.builder.getContext())))); if (jt == (jl_value_t*)jl_bool_type) unbox_load->setMetadata(LLVMContext::MD_range, MDNode::get(ctx.builder.getContext(), { ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)), ConstantAsMetadata::get(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 2)) })); Value *unboxed; - if (to == getInt1Ty(ctx.builder.getContext())) - unboxed = ctx.builder.CreateTrunc(unbox_load, getInt1Ty(ctx.builder.getContext())); + if (to->isIntegerTy(1)) + unboxed = ctx.builder.CreateTrunc(unbox_load, to); else - unboxed = unbox_load; // `to` must be getInt8Ty(ctx.builder.getContext()) - if (!dest) - return unboxed; - Type *dest_ty = unboxed->getType()->getPointerTo(); - if (dest->getType() != dest_ty) - dest = emit_bitcast(ctx, dest, dest_ty); - tbaa_decorate(tbaa_dest, ctx.builder.CreateStore(unboxed, dest)); - return NULL; + unboxed = unbox_load; // `to` must be Int8Ty + return unboxed; } unsigned alignment = julia_alignment(jt); Type *ptype = to->getPointerTo(); - if (dest) { - emit_memcpy(ctx, dest, tbaa_dest, p, x.tbaa, jl_datatype_size(jt), alignment, false); - return NULL; - } - else { - if (p->getType() != ptype && isa(p)) { - // LLVM's mem2reg can't handle coercion if the load/store type does - // not match the type of the alloca. As such, it is better to - // perform the load using the alloca's type and then perform the - // appropriate coercion manually. - AllocaInst *AI = cast(p); - Type *AllocType = AI->getAllocatedType(); - const DataLayout &DL = jl_Module->getDataLayout(); - if (!AI->isArrayAllocation() && - (AllocType->isFloatingPointTy() || AllocType->isIntegerTy() || AllocType->isPointerTy()) && - (to->isFloatingPointTy() || to->isIntegerTy() || to->isPointerTy()) && - DL.getTypeSizeInBits(AllocType) == DL.getTypeSizeInBits(to)) { - Instruction *load = ctx.builder.CreateAlignedLoad(AllocType, p, Align(alignment)); - return emit_unboxed_coercion(ctx, to, tbaa_decorate(x.tbaa, load)); - } + if (p->getType() != ptype && isa(p)) { + // LLVM's mem2reg can't handle coercion if the load/store type does + // not match the type of the alloca. As such, it is better to + // perform the load using the alloca's type and then perform the + // appropriate coercion manually. + AllocaInst *AI = cast(p); + Type *AllocType = AI->getAllocatedType(); + const DataLayout &DL = jl_Module->getDataLayout(); + if (!AI->isArrayAllocation() && + (AllocType->isFloatingPointTy() || AllocType->isIntegerTy() || AllocType->isPointerTy()) && + (to->isFloatingPointTy() || to->isIntegerTy() || to->isPointerTy()) && + DL.getTypeSizeInBits(AllocType) == DL.getTypeSizeInBits(to)) { + Instruction *load = ctx.builder.CreateAlignedLoad(AllocType, p, Align(alignment)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); + return emit_unboxed_coercion(ctx, to, ai.decorateInst(load)); } - p = maybe_bitcast(ctx, p, ptype); - Instruction *load = ctx.builder.CreateAlignedLoad(to, p, Align(alignment)); - return tbaa_decorate(x.tbaa, load); } + p = maybe_bitcast(ctx, p, ptype); + Instruction *load = ctx.builder.CreateAlignedLoad(to, p, Align(alignment)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, x.tbaa); + return ai.decorateInst(load); +} + +// emit code to store a raw value into a destination +static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dest, MDNode *tbaa_dest, unsigned alignment, bool isVolatile) +{ + if (x.isghost) { + // this can happen when a branch yielding a different type ends + // up being dead code, and type inference knows that the other + // branch's type is the only one that matters. + 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) { + Type *dest_ty = unboxed->getType()->getPointerTo(); + if (dest->getType() != dest_ty) + dest = emit_bitcast(ctx, dest, dest_ty); + StoreInst *store = ctx.builder.CreateAlignedStore(unboxed, dest, Align(alignment)); + store->setVolatile(isVolatile); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa_dest); + ai.decorateInst(store); + return; + } + + 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, isVolatile); } -static jl_value_t *staticeval_bitstype(const jl_cgval_t &targ) +static jl_datatype_t *staticeval_bitstype(const jl_cgval_t &targ) { // evaluate an argument at compile time to determine what type it is - if (jl_is_type_type(targ.typ)) { - jl_value_t *bt = jl_tparam0(targ.typ); + jl_value_t *unw = jl_unwrap_unionall(targ.typ); + if (jl_is_type_type(unw)) { + jl_value_t *bt = jl_tparam0(unw); if (jl_is_primitivetype(bt)) - return bt; + return (jl_datatype_t*)bt; } 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) { - Function *func = prepare_call(runtime_func[f]); - Value **argvalues = (Value**)alloca(sizeof(Value*) * nargs); + Function *func = prepare_call(runtime_func()[f]); + SmallVector argvalues(nargs); for (size_t i = 0; i < nargs; ++i) { argvalues[i] = boxed(ctx, argv[i]); } - Value *r = ctx.builder.CreateCall(func, makeArrayRef(argvalues, nargs)); + Value *r = ctx.builder.CreateCall(func, argvalues); return mark_julia_type(ctx, r, true, (jl_value_t*)jl_any_type); } @@ -438,42 +495,45 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) // Give the arguments names // const jl_cgval_t &bt_value = argv[0]; const jl_cgval_t &v = argv[1]; - jl_value_t *bt = staticeval_bitstype(bt_value); + jl_datatype_t *bt = staticeval_bitstype(bt_value); // it's easier to throw a good error from C than llvm if (!bt) return emit_runtime_call(ctx, bitcast, argv, 2); - Type *llvmt = bitstype_to_llvm(bt, ctx.builder.getContext()); - int nb = jl_datatype_size(bt); + Type *llvmt = bitstype_to_llvm((jl_value_t*)bt, ctx.builder.getContext(), true); + uint32_t nb = jl_datatype_size(bt); + + Value *bt_value_rt = NULL; + if (!jl_is_concrete_type((jl_value_t*)bt)) { + bt_value_rt = boxed(ctx, bt_value); + emit_concretecheck(ctx, bt_value_rt, "bitcast: target type not a leaf primitive type"); + } // Examine the second argument // bool isboxed; Type *vxt = julia_type_to_llvm(ctx, v.typ, &isboxed); - if (!jl_is_primitivetype(v.typ) || jl_datatype_size(v.typ) != nb) { - Value *typ = emit_typeof_boxed(ctx, v); + Value *typ = emit_typeof(ctx, v, false, false); if (!jl_is_primitivetype(v.typ)) { - if (isboxed) { - Value *isprimitive = emit_datatype_isprimitivetype(ctx, typ); - error_unless(ctx, isprimitive, "bitcast: expected primitive type value for second argument"); + if (jl_is_datatype(v.typ) && !jl_is_abstracttype(v.typ)) { + emit_error(ctx, "bitcast: value not a primitive type"); + return jl_cgval_t(); } else { - emit_error(ctx, "bitcast: expected primitive type value for second argument"); - return jl_cgval_t(ctx.builder.getContext()); + Value *isprimitive = emit_datatype_isprimitivetype(ctx, typ); + error_unless(ctx, isprimitive, "bitcast: value not a primitive type"); } } - if (!jl_is_datatype(v.typ) || jl_datatype_size(v.typ) != nb) { - if (isboxed) { - Value *size = emit_datatype_size(ctx, typ); - error_unless(ctx, - ctx.builder.CreateICmpEQ(size, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), nb)), - "bitcast: argument size does not match size of target type"); - } - else { - emit_error(ctx, "bitcast: argument size does not match size of target type"); - return jl_cgval_t(ctx.builder.getContext()); - } + if (jl_is_datatype(v.typ) && !jl_is_abstracttype(v.typ)) { + emit_error(ctx, "bitcast: argument size does not match size of target type"); + return jl_cgval_t(); + } + else { + Value *size = emit_datatype_size(ctx, typ); + error_unless(ctx, + ctx.builder.CreateICmpEQ(size, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), nb)), + "bitcast: argument size does not match size of target type"); } } @@ -489,8 +549,9 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) // but if the v.typ is not well known, use llvmt if (isboxed) vxt = llvmt; - auto storage_type = vxt == getInt1Ty(ctx.builder.getContext()) ? getInt8Ty(ctx.builder.getContext()) : vxt; - vx = tbaa_decorate(v.tbaa, ctx.builder.CreateLoad( + auto storage_type = vxt->isIntegerTy(1) ? getInt8Ty(ctx.builder.getContext()) : vxt; + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, v.tbaa); + vx = ai.decorateInst(ctx.builder.CreateLoad( storage_type, emit_bitcast(ctx, data_pointer(ctx, v), storage_type->getPointerTo()))); @@ -498,9 +559,9 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) vxt = vx->getType(); if (vxt != llvmt) { - if (llvmt == getInt1Ty(ctx.builder.getContext())) + if (llvmt->isIntegerTy(1)) vx = ctx.builder.CreateTrunc(vx, llvmt); - else if (vxt == getInt1Ty(ctx.builder.getContext()) && llvmt == getInt8Ty(ctx.builder.getContext())) + else if (vxt->isIntegerTy(1) && llvmt->isIntegerTy(8)) vx = ctx.builder.CreateZExt(vx, llvmt); else if (vxt->isPointerTy() && !llvmt->isPointerTy()) vx = ctx.builder.CreatePtrToInt(vx, llvmt); @@ -510,13 +571,13 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) vx = emit_bitcast(ctx, vx, llvmt); } - if (jl_is_concrete_type(bt)) { + if (jl_is_concrete_type((jl_value_t*)bt)) { return mark_julia_type(ctx, vx, false, bt); } else { - Value *box = emit_allocobj(ctx, nb, boxed(ctx, bt_value)); + Value *box = emit_allocobj(ctx, nb, bt_value_rt); init_bits_value(ctx, box, vx, ctx.tbaa().tbaa_immut); - return mark_julia_type(ctx, box, true, bt); + return mark_julia_type(ctx, box, true, bt->name->wrapper); } } @@ -525,19 +586,22 @@ static jl_cgval_t generic_cast( intrinsic f, Instruction::CastOps Op, const jl_cgval_t *argv, bool toint, bool fromint) { + auto &TT = ctx.emission_context.TargetTriple; + auto &DL = ctx.emission_context.DL; const jl_cgval_t &targ = argv[0]; const jl_cgval_t &v = argv[1]; - jl_value_t *jlto = staticeval_bitstype(targ); + jl_datatype_t *jlto = staticeval_bitstype(targ); if (!jlto || !jl_is_primitivetype(v.typ)) return emit_runtime_call(ctx, f, argv, 2); - Type *to = bitstype_to_llvm(jlto, ctx.builder.getContext()); - Type *vt = bitstype_to_llvm(v.typ, ctx.builder.getContext()); + uint32_t nb = jl_datatype_size(jlto); + Type *to = bitstype_to_llvm((jl_value_t*)jlto, ctx.builder.getContext(), true); + Type *vt = bitstype_to_llvm(v.typ, ctx.builder.getContext(), true); if (toint) - to = INTT(to); + to = INTT(to, DL); else to = FLOATT(to); if (fromint) - vt = INTT(vt); + vt = INTT(vt, DL); else vt = FLOATT(vt); if (!to || !vt) @@ -546,22 +610,31 @@ static jl_cgval_t generic_cast( if (!CastInst::castIsValid(Op, from, to)) return emit_runtime_call(ctx, f, argv, 2); if (Op == Instruction::FPExt) { -#ifdef JL_NEED_FLOATTEMP_VAR - // Target platform might carry extra precision. - // Force rounding to single precision first. The reason is that it's - // fine to keep working in extended precision as long as it's - // understood that everything is implicitly rounded to 23 bits, - // but if we start looking at more bits we need to actually do the - // rounding first instead of carrying around incorrect low bits. - Value *jlfloattemp_var = emit_static_alloca(ctx, from->getType()); - ctx.builder.CreateStore(from, jlfloattemp_var); - from = ctx.builder.CreateLoad(jlfloattemp_var, /*force this to load from the stack*/true); -#endif + if (jl_floattemp_var_needed(TT)) { + // Target platform might carry extra precision. + // Force rounding to single precision first. The reason is that it's + // fine to keep working in extended precision as long as it's + // understood that everything is implicitly rounded to 23 bits, + // but if we start looking at more bits we need to actually do the + // rounding first instead of carrying around incorrect low bits. + Value *jlfloattemp_var = emit_static_alloca(ctx, from->getType()); + ctx.builder.CreateStore(from, jlfloattemp_var); + from = ctx.builder.CreateLoad(from->getType(), jlfloattemp_var, /*force this to load from the stack*/true); + } } Value *ans = ctx.builder.CreateCast(Op, from, to); if (f == fptosi || f == fptoui) ans = ctx.builder.CreateFreeze(ans); - return mark_julia_type(ctx, ans, false, jlto); + if (jl_is_concrete_type((jl_value_t*)jlto)) { + return mark_julia_type(ctx, ans, false, jlto); + } + 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); + 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) @@ -589,29 +662,29 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) return emit_runtime_pointerref(ctx, argv); if (!is_valid_intrinsic_elptr(ety)) { emit_error(ctx, "pointerref: invalid pointer type"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } - Value *idx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), i, (jl_value_t*)jl_long_type); - Value *im1 = ctx.builder.CreateSub(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)); + Value *idx = emit_unbox(ctx, ctx.types().T_size, i, (jl_value_t*)jl_long_type); + Value *im1 = ctx.builder.CreateSub(idx, ConstantInt::get(ctx.types().T_size, 1)); if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, ctx.builder.CreateInBoundsGEP(ctx.types().T_prjlvalue, thePtr, im1), Align(align_nb)); - tbaa_decorate(ctx.tbaa().tbaa_data, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); + ai.decorateInst(load); return mark_julia_type(ctx, load, true, ety); } else if (!jl_isbits(ety)) { assert(jl_is_datatype(ety)); uint64_t size = jl_datatype_size(ety); - Value *strct = emit_allocobj(ctx, size, - literal_pointer_val(ctx, ety)); - im1 = ctx.builder.CreateMul(im1, ConstantInt::get(getSizeTy(ctx.builder.getContext()), + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + im1 = ctx.builder.CreateMul(im1, ConstantInt::get(ctx.types().T_size, LLT_ALIGN(size, jl_datatype_align(ety)))); Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); thePtr = ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), emit_bitcast(ctx, thePtr, getInt8PtrTy(ctx.builder.getContext())), im1); MDNode *tbaa = best_tbaa(ctx.tbaa(), ety); - emit_memcpy(ctx, strct, tbaa, thePtr, nullptr, size, 1); + emit_memcpy(ctx, strct, jl_aliasinfo_t::fromTBAA(ctx, tbaa), thePtr, jl_aliasinfo_t::fromTBAA(ctx, nullptr), size, 1); return mark_julia_type(ctx, strct, true, ety); } else { @@ -657,28 +730,29 @@ static jl_cgval_t emit_pointerset(jl_codectx_t &ctx, jl_cgval_t *argv) return emit_runtime_pointerset(ctx, argv); if (!is_valid_intrinsic_elptr(ety)) { emit_error(ctx, "pointerset: invalid pointer type"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } emit_typecheck(ctx, x, ety, "pointerset"); - Value *idx = emit_unbox(ctx, getSizeTy(ctx.builder.getContext()), i, (jl_value_t*)jl_long_type); - Value *im1 = ctx.builder.CreateSub(idx, ConstantInt::get(getSizeTy(ctx.builder.getContext()), 1)); + Value *idx = emit_unbox(ctx, ctx.types().T_size, i, (jl_value_t*)jl_long_type); + Value *im1 = ctx.builder.CreateSub(idx, ConstantInt::get(ctx.types().T_size, 1)); Value *thePtr; if (ety == (jl_value_t*)jl_any_type) { // unsafe_store to Ptr{Any} is allowed to implicitly drop GC roots. - thePtr = emit_unbox(ctx, getSizePtrTy(ctx.builder.getContext()), e, e.typ); + thePtr = emit_unbox(ctx, ctx.types().T_size->getPointerTo(), e, e.typ); Instruction *store = ctx.builder.CreateAlignedStore( - ctx.builder.CreatePtrToInt(emit_pointer_from_objref(ctx, boxed(ctx, x)), getSizeTy(ctx.builder.getContext())), - ctx.builder.CreateInBoundsGEP(getSizeTy(ctx.builder.getContext()), thePtr, im1), Align(align_nb)); - tbaa_decorate(ctx.tbaa().tbaa_data, store); + ctx.builder.CreatePtrToInt(emit_pointer_from_objref(ctx, boxed(ctx, x)), ctx.types().T_size), + ctx.builder.CreateInBoundsGEP(ctx.types().T_size, thePtr, im1), Align(align_nb)); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); + ai.decorateInst(store); } else if (!jl_isbits(ety)) { thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); uint64_t size = jl_datatype_size(ety); - im1 = ctx.builder.CreateMul(im1, ConstantInt::get(getSizeTy(ctx.builder.getContext()), + im1 = ctx.builder.CreateMul(im1, ConstantInt::get(ctx.types().T_size, LLT_ALIGN(size, jl_datatype_align(ety)))); - emit_memcpy(ctx, ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), thePtr, im1), nullptr, x, size, align_nb); + emit_memcpy(ctx, ctx.builder.CreateInBoundsGEP(getInt8Ty(ctx.builder.getContext()), thePtr, im1), jl_aliasinfo_t::fromTBAA(ctx, nullptr), x, size, align_nb); } else { bool isboxed; @@ -686,7 +760,7 @@ 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(ctx.builder.getContext()), ety, ctx.tbaa().tbaa_data, nullptr, nullptr, isboxed, + 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, ""); } } @@ -700,7 +774,7 @@ static jl_cgval_t emit_atomicfence(jl_codectx_t &ctx, jl_cgval_t *argv) enum jl_memory_order order = jl_get_atomic_order((jl_sym_t*)ord.constant, true, true); if (order == jl_memory_order_invalid) { emit_atomic_error(ctx, "invalid atomic ordering"); - return jl_cgval_t(ctx.builder.getContext()); // unreachable + return jl_cgval_t(); // unreachable } if (order > jl_memory_order_monotonic) ctx.builder.CreateFence(get_llvm_atomic_order(order)); @@ -722,44 +796,44 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) enum jl_memory_order 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"); - return jl_cgval_t(ctx.builder.getContext()); // unreachable + return jl_cgval_t(); // unreachable } AtomicOrdering llvm_order = get_llvm_atomic_order(order); if (ety == (jl_value_t*)jl_any_type) { Value *thePtr = emit_unbox(ctx, ctx.types().T_pprjlvalue, e, e.typ); LoadInst *load = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, thePtr, Align(sizeof(jl_value_t*))); - tbaa_decorate(ctx.tbaa().tbaa_data, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_data); + ai.decorateInst(load); load->setOrdering(llvm_order); return mark_julia_type(ctx, load, true, ety); } if (!is_valid_intrinsic_elptr(ety)) { emit_error(ctx, "atomic_pointerref: invalid pointer type"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } size_t nb = jl_datatype_size(ety); if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) { emit_error(ctx, "atomic_pointerref: invalid pointer for atomic operation"); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } if (!jl_isbits(ety)) { assert(jl_is_datatype(ety)); - uint64_t size = jl_datatype_size(ety); - Value *strct = emit_allocobj(ctx, size, - literal_pointer_val(ctx, ety)); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); Type *loadT = Type::getIntNTy(ctx.builder.getContext(), nb * 8); thePtr = emit_bitcast(ctx, thePtr, loadT->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), ety); LoadInst *load = ctx.builder.CreateAlignedLoad(loadT, thePtr, Align(nb)); - tbaa_decorate(tbaa, load); + jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); + ai.decorateInst(load); load->setOrdering(llvm_order); thePtr = emit_bitcast(ctx, strct, thePtr->getType()); StoreInst *store = ctx.builder.CreateAlignedStore(load, thePtr, Align(julia_alignment(ety))); - tbaa_decorate(tbaa, store); + ai.decorateInst(store); return mark_julia_type(ctx, strct, true, ety); } else { @@ -788,7 +862,7 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl bool isreplacefield = f == atomic_pointerreplace; bool isswapfield = f == atomic_pointerswap; bool ismodifyfield = f == atomic_pointermodify; - const jl_cgval_t undefval(ctx.builder.getContext()); + const jl_cgval_t undefval; const jl_cgval_t &e = argv[0]; const jl_cgval_t &x = isreplacefield || ismodifyfield ? argv[2] : argv[1]; const jl_cgval_t &y = isreplacefield || ismodifyfield ? argv[1] : undefval; @@ -809,7 +883,7 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl enum jl_memory_order failorder = isreplacefield ? jl_get_atomic_order((jl_sym_t*)failord.constant, true, false) : order; if (order == jl_memory_order_invalid || failorder == jl_memory_order_invalid || failorder > order) { emit_atomic_error(ctx, "invalid atomic ordering"); - return jl_cgval_t(ctx.builder.getContext()); // unreachable + return jl_cgval_t(); // unreachable } AtomicOrdering llvm_order = get_llvm_atomic_order(order); AtomicOrdering llvm_failorder = get_llvm_atomic_order(failorder); @@ -830,7 +904,7 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl std::string msg(StringRef(jl_intrinsic_name((int)f))); msg += ": invalid pointer type"; emit_error(ctx, msg); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } if (!ismodifyfield) emit_typecheck(ctx, x, ety, std::string(jl_intrinsic_name((int)f))); @@ -840,7 +914,7 @@ static jl_cgval_t emit_atomic_pointerop(jl_codectx_t &ctx, intrinsic f, const jl std::string msg(StringRef(jl_intrinsic_name((int)f))); msg += ": invalid pointer for atomic operation"; emit_error(ctx, msg); - return jl_cgval_t(ctx.builder.getContext()); + return jl_cgval_t(); } if (!jl_isbits(ety)) { @@ -921,7 +995,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ jl_value_t *t2 = y.typ; // handle cases where the condition is irrelevant based on type info if (t1 == jl_bottom_type && t2 == jl_bottom_type) - return jl_cgval_t(ctx.builder.getContext()); // undefined + return jl_cgval_t(); // undefined if (t1 == jl_bottom_type) return y; if (t2 == jl_bottom_type) @@ -1034,7 +1108,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ ctx.builder.Insert(ret); tindex = ret; } - jl_cgval_t ret = mark_julia_slot(ifelse_result, rt_hint, tindex, ctx.tbaa(), ifelse_tbaa); + jl_cgval_t ret = mark_julia_slot(ifelse_result, rt_hint, tindex, ifelse_tbaa); if (x_vboxed || y_vboxed) { if (!x_vboxed) x_vboxed = ConstantPointerNull::get(cast(y_vboxed->getType())); @@ -1055,6 +1129,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **args, size_t nargs) { + auto &DL = ctx.emission_context.DL; assert(f < num_intrinsics); if (f == cglobal && nargs == 1) f = cglobal_auto; @@ -1068,9 +1143,14 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar if (f == cglobal_auto || f == cglobal) return emit_cglobal(ctx, args, nargs); - jl_cgval_t *argv = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * nargs); + SmallVector argv(nargs); for (size_t i = 0; i < nargs; ++i) { - argv[i] = emit_expr(ctx, args[i + 1]); + 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 + return jl_cgval_t(); + } + argv[i] = arg; } // this forces everything to use runtime-intrinsics (e.g. for testing) @@ -1078,81 +1158,95 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar 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, nargs); + 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); + return emit_pointerref(ctx, argv.data()); case pointerset: + ++Emitted_pointerset; assert(nargs == 4); - return emit_pointerset(ctx, argv); + return emit_pointerset(ctx, argv.data()); case atomic_fence: + ++Emitted_atomic_fence; assert(nargs == 1); - return emit_atomicfence(ctx, argv); + return emit_atomicfence(ctx, argv.data()); case atomic_pointerref: + ++Emitted_atomic_pointerref; assert(nargs == 2); - return emit_atomic_pointerref(ctx, argv); + return emit_atomic_pointerref(ctx, argv.data()); case atomic_pointerset: case atomic_pointerswap: case atomic_pointermodify: case atomic_pointerreplace: - return emit_atomic_pointerop(ctx, f, argv, nargs, nullptr); + ++Emitted_atomic_pointerop; + return emit_atomic_pointerop(ctx, f, argv.data(), nargs, nullptr); case bitcast: + ++Emitted_bitcast; assert(nargs == 2); - return generic_bitcast(ctx, argv); + return generic_bitcast(ctx, argv.data()); case trunc_int: + ++Emitted_trunc_int; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::Trunc, argv, true, true); + return generic_cast(ctx, f, Instruction::Trunc, argv.data(), true, true); case sext_int: + ++Emitted_sext_int; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::SExt, argv, true, true); + return generic_cast(ctx, f, Instruction::SExt, argv.data(), true, true); case zext_int: + ++Emitted_zext_int; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::ZExt, argv, true, true); + return generic_cast(ctx, f, Instruction::ZExt, argv.data(), true, true); case uitofp: + ++Emitted_uitofp; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::UIToFP, argv, false, true); + return generic_cast(ctx, f, Instruction::UIToFP, argv.data(), false, true); case sitofp: + ++Emitted_sitofp; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::SIToFP, argv, false, true); + return generic_cast(ctx, f, Instruction::SIToFP, argv.data(), false, true); case fptoui: + ++Emitted_fptoui; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPToUI, argv, true, false); + return generic_cast(ctx, f, Instruction::FPToUI, argv.data(), true, false); case fptosi: + ++Emitted_fptosi; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPToSI, argv, true, false); + return generic_cast(ctx, f, Instruction::FPToSI, argv.data(), true, false); case fptrunc: + ++Emitted_fptrunc; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPTrunc, argv, false, false); + return generic_cast(ctx, f, Instruction::FPTrunc, argv.data(), false, false); case fpext: + ++Emitted_fpext; assert(nargs == 2); - return generic_cast(ctx, f, Instruction::FPExt, argv, false, false); + return generic_cast(ctx, f, Instruction::FPExt, argv.data(), 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, nargs); - Type *xt = INTT(bitstype_to_llvm(x.typ, ctx.builder.getContext())); + return emit_runtime_call(ctx, f, argv.data(), 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; - if (x.typ == (jl_value_t*)jl_bool_type) - ans = ctx.builder.CreateXor(from, ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1, true)); - else - ans = ctx.builder.CreateXor(from, ConstantInt::get(xt, -1, true)); + Value *ans = ctx.builder.CreateNot(from); return mark_julia_type(ctx, ans, false, x.typ); } case have_fma: { + ++Emitted_have_fma; 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, nargs); + return emit_runtime_call(ctx, f, argv.data(), nargs); jl_datatype_t *dt = (jl_datatype_t*) x.constant; // select the appropriated overloaded intrinsic @@ -1162,7 +1256,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, nargs); + return emit_runtime_call(ctx, f, argv.data(), nargs); FunctionCallee intr = jl_Module->getOrInsertFunction(intr_name, getInt1Ty(ctx.builder.getContext())); auto ret = ctx.builder.CreateCall(intr); @@ -1175,14 +1269,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, nargs); - Type *xtyp = bitstype_to_llvm(xinfo.typ, ctx.builder.getContext()); - if (float_func[f]) + return emit_runtime_call(ctx, f, argv.data(), nargs); + Type *xtyp = bitstype_to_llvm(xinfo.typ, ctx.builder.getContext(), true); + if (float_func()[f]) xtyp = FLOATT(xtyp); else - xtyp = INTT(xtyp); + xtyp = INTT(xtyp, DL); if (!xtyp) - return emit_runtime_call(ctx, f, argv, nargs); + return emit_runtime_call(ctx, f, argv.data(), 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 @@ -1193,33 +1287,33 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar //if (xtyp == (jl_value_t*)jl_bool_type) // r = getInt1Ty(ctx.builder.getContext()); - Type **argt = (Type**)alloca(sizeof(Type*) * nargs); + SmallVector argt(nargs); argt[0] = xtyp; if (f == shl_int || f == lshr_int || f == ashr_int) { if (!jl_is_primitivetype(argv[1].typ)) - return emit_runtime_call(ctx, f, argv, nargs); - argt[1] = INTT(bitstype_to_llvm(argv[1].typ, ctx.builder.getContext())); + return emit_runtime_call(ctx, f, argv.data(), 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, nargs); + return emit_runtime_call(ctx, f, argv.data(), nargs); argt[i] = xtyp; } } // unbox the arguments - Value **argvalues = (Value**)alloca(sizeof(Value*) * nargs); + SmallVector argvalues(nargs); for (size_t i = 0; i < nargs; ++i) { argvalues[i] = emit_unbox(ctx, argt[i], argv[i], argv[i].typ); } // call the intrinsic jl_value_t *newtyp = xinfo.typ; - Value *r = emit_untyped_intrinsic(ctx, f, argvalues, nargs, (jl_datatype_t**)&newtyp, xinfo.typ); + Value *r = emit_untyped_intrinsic(ctx, f, argvalues.data(), 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() != getInt1Ty(ctx.builder.getContext())) + if (newtyp == (jl_value_t*)jl_bool_type && !r->getType()->isIntegerTy(1)) r = ctx.builder.CreateTrunc(r, getInt1Ty(ctx.builder.getContext())); return mark_julia_type(ctx, r, false, newtyp); } @@ -1230,6 +1324,7 @@ static jl_cgval_t emit_intrinsic(jl_codectx_t &ctx, intrinsic f, jl_value_t **ar static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **argvalues, size_t nargs, jl_datatype_t **newtyp, jl_value_t *xtyp) { + ++EmittedUntypedIntrinsics; Value *x = nargs > 0 ? argvalues[0] : NULL; Value *y = nargs > 1 ? argvalues[1] : NULL; Value *z = nargs > 2 ? argvalues[2] : NULL; @@ -1272,12 +1367,10 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg case sub_float: return math_builder(ctx)().CreateFSub(x, y); case mul_float: return math_builder(ctx)().CreateFMul(x, y); case div_float: return math_builder(ctx)().CreateFDiv(x, y); - case rem_float: return math_builder(ctx)().CreateFRem(x, y); case add_float_fast: return math_builder(ctx, true)().CreateFAdd(x, y); case sub_float_fast: return math_builder(ctx, true)().CreateFSub(x, y); case mul_float_fast: return math_builder(ctx, true)().CreateFMul(x, y); case div_float_fast: return math_builder(ctx, true)().CreateFDiv(x, y); - case rem_float_fast: return math_builder(ctx, true)().CreateFRem(x, y); case fma_float: { assert(y->getType() == x->getType()); assert(z->getType() == y->getType()); @@ -1285,7 +1378,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg return ctx.builder.CreateCall(fmaintr, {x, y, z}); } case muladd_float: { - // LLVM 5.0 can create FMA in the backend for contractable fmul and fadd + // LLVM 5.0 can create FMA in the backend for contractible fmul and fadd // Emitting fmul and fadd here since they are easier for other LLVM passes to // optimize. auto mathb = math_builder(ctx, false, true); @@ -1320,7 +1413,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg jl_value_t *params[2]; params[0] = xtyp; params[1] = (jl_value_t*)jl_bool_type; - jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); + jl_datatype_t *tuptyp = (jl_datatype_t*)jl_apply_tuple_type_v(params, 2); *newtyp = tuptyp; Value *tupval; @@ -1376,7 +1469,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg case fpiseq: { *newtyp = jl_bool_type; - Type *it = INTT(t); + Type *it = INTT(t, ctx.emission_context.DL); Value *xi = ctx.builder.CreateBitCast(x, it); Value *yi = ctx.builder.CreateBitCast(y, it); return ctx.builder.CreateOr(ctx.builder.CreateAnd(ctx.builder.CreateFCmpUNO(x, x), @@ -1500,3 +1593,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg } assert(0 && "unreachable"); } + +//Redefine us as being part of codegen +#undef DEBUG_TYPE +#define DEBUG_TYPE "julia_irgen_codegen" diff --git a/src/intrinsics.h b/src/intrinsics.h index bb67460bbb31f..93747faa74160 100644 --- a/src/intrinsics.h +++ b/src/intrinsics.h @@ -19,7 +19,6 @@ ADD_I(sub_float, 2) \ ADD_I(mul_float, 2) \ ADD_I(div_float, 2) \ - ADD_I(rem_float, 2) \ ADD_I(fma_float, 3) \ ADD_I(muladd_float, 3) \ /* fast arithmetic */ \ @@ -28,7 +27,6 @@ ALIAS(sub_float_fast, sub_float) \ ALIAS(mul_float_fast, mul_float) \ ALIAS(div_float_fast, div_float) \ - ALIAS(rem_float_fast, rem_float) \ /* same-type comparisons */ \ ADD_I(eq_int, 2) \ ADD_I(ne_int, 2) \ diff --git a/src/ircode.c b/src/ircode.c index 73e99f2281491..4121d6691aa5b 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -29,6 +29,34 @@ typedef struct { uint8_t relocatability; } jl_ircode_state; +// type => tag hash for a few core types (e.g., Expr, PhiNode, etc) +static htable_t ser_tag; +// tag => type mapping, the reverse of ser_tag +static jl_value_t *deser_tag[256]; +// hash of some common symbols, encoded as CommonSym_tag plus 1 byte +static htable_t common_symbol_tag; +static jl_value_t *deser_symbols[256]; + +void *jl_lookup_ser_tag(jl_value_t *v) +{ + return ptrhash_get(&ser_tag, v); +} + +void *jl_lookup_common_symbol(jl_value_t *v) +{ + return ptrhash_get(&common_symbol_tag, v); +} + +jl_value_t *jl_deser_tag(uint8_t tag) +{ + return deser_tag[tag]; +} + +jl_value_t *jl_deser_symbol(uint8_t tag) +{ + return deser_symbols[tag]; +} + // --- encoding --- #define jl_encode_value(s, v) jl_encode_value_((s), (jl_value_t*)(v), 0) @@ -71,10 +99,31 @@ static void jl_encode_int32(jl_ircode_state *s, int32_t x) } } +static void jl_encode_as_indexed_root(jl_ircode_state *s, jl_value_t *v) +{ + rle_reference rr; + + literal_val_id(&rr, s, v); + int id = rr.index; + assert(id >= 0); + if (rr.key) { + write_uint8(s->s, TAG_RELOC_METHODROOT); + write_uint64(s->s, rr.key); + } + if (id <= UINT8_MAX) { + write_uint8(s->s, TAG_METHODROOT); + write_uint8(s->s, id); + } + else { + assert(id <= UINT32_MAX); + write_uint8(s->s, TAG_LONG_METHODROOT); + write_uint32(s->s, id); + } +} + static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED { size_t i; - rle_reference rr; if (v == NULL) { write_uint8(s->s, TAG_NULL); @@ -98,7 +147,7 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) else if (v == (jl_value_t*)jl_base_module) { write_uint8(s->s, TAG_BASE); } - else if (jl_typeis(v, jl_string_type) && jl_string_len(v) == 0) { + else if (jl_typetagis(v, jl_string_tag << 4) && jl_string_len(v) == 0) { jl_encode_value(s, jl_an_empty_string); } else if (v == (jl_value_t*)s->method->module) { @@ -148,7 +197,7 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) write_uint8(s->s, TAG_LONG_SSAVALUE); write_uint16(s->s, ((jl_ssavalue_t*)v)->id); } - else if (jl_typeis(v, jl_slotnumber_type) && jl_slot_number(v) <= UINT16_MAX && jl_slot_number(v) >= 0) { + else if (jl_typetagis(v, jl_slotnumber_type) && jl_slot_number(v) <= UINT16_MAX && jl_slot_number(v) >= 0) { write_uint8(s->s, TAG_SLOTNUMBER); write_uint16(s->s, jl_slot_number(v)); } @@ -240,7 +289,17 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) write_uint8(s->s, TAG_RETURNNODE); jl_encode_value(s, jl_get_nth_field(v, 0)); } - else if (jl_typeis(v, jl_int64_type)) { + else if (jl_is_quotenode(v)) { + write_uint8(s->s, TAG_QUOTENODE); + jl_value_t *inner = jl_quotenode_value(v); + // we might need to return this exact object at run time, therefore codegen might + // need to reference it as well, so it is more likely useful to give it a root + if (jl_is_expr(inner) || jl_is_phinode(inner) || jl_is_phicnode(inner)) + jl_encode_as_indexed_root(s, inner); + else + jl_encode_value(s, inner); + } + else if (jl_typetagis(v, jl_int64_tag << 4)) { void *data = jl_data_ptr(v); if (*(int64_t*)data >= INT16_MIN && *(int64_t*)data <= INT16_MAX) { write_uint8(s->s, TAG_SHORTER_INT64); @@ -252,17 +311,17 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) } else { write_uint8(s->s, TAG_INT64); - write_int64(s->s, *(int64_t*)data); + write_uint64(s->s, *(int64_t*)data); } } - else if (jl_typeis(v, jl_int32_type)) { + else if (jl_typetagis(v, jl_int32_tag << 4)) { jl_encode_int32(s, *(int32_t*)jl_data_ptr(v)); } - else if (jl_typeis(v, jl_uint8_type)) { + else if (jl_typetagis(v, jl_uint8_tag << 4)) { write_uint8(s->s, TAG_UINT8); write_int8(s->s, *(int8_t*)jl_data_ptr(v)); } - else if (jl_typeis(v, jl_lineinfonode_type)) { + 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)); @@ -271,7 +330,7 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) write_uint8(s->s, TAG_SINGLETON); jl_encode_value(s, jl_typeof(v)); } - else if (as_literal && jl_typeis(v, jl_string_type)) { + else if (as_literal && jl_typetagis(v, jl_string_tag << 4)) { write_uint8(s->s, TAG_STRING); write_int32(s->s, jl_string_len(v)); ios_write(s->s, jl_string_data(v), jl_string_len(v)); @@ -325,36 +384,18 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) ios_write(s->s, jl_array_typetagdata(ar), l); } } - else { - if (!as_literal && !(jl_is_uniontype(v) || jl_is_newvarnode(v) || jl_is_tuple(v) || - jl_is_linenode(v) || jl_is_upsilonnode(v) || jl_is_pinode(v) || - jl_is_slot(v) || jl_is_ssavalue(v))) { - literal_val_id(&rr, s, v); - int id = rr.index; - assert(id >= 0); - if (rr.key) { - write_uint8(s->s, TAG_RELOC_METHODROOT); - write_int64(s->s, rr.key); - } - if (id < 256) { - write_uint8(s->s, TAG_METHODROOT); - write_uint8(s->s, id); - } - else { - assert(id <= UINT16_MAX); - write_uint8(s->s, TAG_LONG_METHODROOT); - write_uint16(s->s, id); - } - return; - } + 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) || + (jl_isbits(jl_typeof(v)) && jl_datatype_size(jl_typeof(v)) <= 64)) { jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); - if (t->size <= 255) { + size_t tsz = jl_datatype_size(t); + if (tsz <= 255) { write_uint8(s->s, TAG_SHORT_GENERAL); - write_uint8(s->s, t->size); + write_uint8(s->s, tsz); } else { write_uint8(s->s, TAG_GENERAL); - write_int32(s->s, t->size); + write_int32(s->s, tsz); } jl_encode_value(s, t); @@ -388,15 +429,19 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) if (ptr > last) ios_write(s->s, last, ptr - last); } + else { + jl_encode_as_indexed_root(s, v); + } } -static jl_code_info_flags_t code_info_flags(uint8_t pure, uint8_t propagate_inbounds, uint8_t inlineable, uint8_t inferred, uint8_t constprop) +static jl_code_info_flags_t code_info_flags(uint8_t inferred, uint8_t propagate_inbounds, + uint8_t has_fcall, uint8_t inlining, uint8_t constprop) { jl_code_info_flags_t flags; - flags.bits.pure = pure; - flags.bits.propagate_inbounds = propagate_inbounds; - flags.bits.inlineable = inlineable; flags.bits.inferred = inferred; + flags.bits.propagate_inbounds = propagate_inbounds; + flags.bits.has_fcall = has_fcall; + flags.bits.inlining = inlining; flags.bits.constprop = constprop; return flags; } @@ -565,9 +610,12 @@ static jl_value_t *jl_decode_value_any(jl_ircode_state *s, uint8_t tag) JL_GC_DI { int32_t sz = (tag == TAG_SHORT_GENERAL ? read_uint8(s->s) : read_int32(s->s)); jl_value_t *v = jl_gc_alloc(s->ptls, sz, NULL); - jl_set_typeof(v, (void*)(intptr_t)0x50); + jl_set_typeof(v, (void*)(intptr_t)0xf50); jl_datatype_t *dt = (jl_datatype_t*)jl_decode_value(s); - jl_set_typeof(v, dt); + if (dt->smalltag) + jl_set_typetagof(v, dt->smalltag, 0); + else + jl_set_typeof(v, dt); char *data = (char*)jl_data_ptr(v); size_t i, np = dt->layout->npointers; char *start = data; @@ -603,11 +651,17 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED key = read_uint64(s->s); tag = read_uint8(s->s); assert(tag == TAG_METHODROOT || tag == TAG_LONG_METHODROOT); - return lookup_root(s->method, key, tag == TAG_METHODROOT ? read_uint8(s->s) : read_uint16(s->s)); + int index = -1; + if (tag == TAG_METHODROOT) + index = read_uint8(s->s); + else if (tag == TAG_LONG_METHODROOT) + index = read_uint32(s->s); + assert(index >= 0); + return lookup_root(s->method, key, index); case TAG_METHODROOT: return lookup_root(s->method, 0, read_uint8(s->s)); case TAG_LONG_METHODROOT: - return lookup_root(s->method, 0, read_uint16(s->s)); + return lookup_root(s->method, 0, read_uint32(s->s)); case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC: return jl_decode_value_svec(s, tag); case TAG_COMMONSYM: @@ -707,9 +761,11 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED // --- entry points --- -JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) +typedef jl_value_t jl_string_t; // for local expressibility + +JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) { - JL_TIMING(AST_COMPRESS); + JL_TIMING(AST_COMPRESS, AST_COMPRESS); JL_LOCK(&m->writelock); // protect the roots array (Might GC) assert(jl_is_method(m)); assert(jl_is_code_info(code)); @@ -729,9 +785,11 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) 1 }; - jl_code_info_flags_t flags = code_info_flags(code->pure, code->propagate_inbounds, code->inlineable, code->inferred, code->constprop); + jl_code_info_flags_t flags = code_info_flags(code->inferred, code->propagate_inbounds, + code->has_fcall, code->inlining, code->constprop); write_uint8(s.s, flags.packed); write_uint8(s.s, code->purity.bits); + write_uint16(s.s, code->inlining_cost); size_t nslots = jl_array_len(code->slotflags); assert(nslots >= m->nargs && nslots < INT32_MAX); // required by generated functions @@ -755,6 +813,11 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) jl_encode_value_(&s, jl_get_nth_field((jl_value_t*)code, i), copy); } + // For opaque closure, also save the slottypes. We technically only need the first slot type, + // but this is simpler for now. We may want to refactor where this gets stored in the future. + if (m->is_for_opaque_closure) + jl_encode_value_(&s, code->slottypes, 1); + if (m->generator) // can't optimize generated functions jl_encode_value_(&s, (jl_value_t*)jl_compress_argnames(code->slotnames), 1); @@ -780,7 +843,7 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) write_uint8(s.s, s.relocatability); ios_flush(s.s); - jl_array_t *v = jl_take_buffer(&dest); + 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) { m->roots = NULL; @@ -789,22 +852,23 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) jl_gc_enable(en); JL_UNLOCK(&m->writelock); // Might GC JL_GC_POP(); + return v; } -JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t *metadata, jl_array_t *data) +JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t *metadata, jl_string_t *data) { if (jl_is_code_info(data)) return (jl_code_info_t*)data; - JL_TIMING(AST_UNCOMPRESS); + JL_TIMING(AST_UNCOMPRESS, AST_UNCOMPRESS); JL_LOCK(&m->writelock); // protect the roots array (Might GC) assert(jl_is_method(m)); - assert(jl_typeis(data, jl_array_uint8_type)); + assert(jl_is_string(data)); size_t i; ios_t src; ios_mem(&src, 0); - ios_setbuf(&src, (char*)data->data, jl_array_len(data), 0); - src.size = jl_array_len(data); + ios_setbuf(&src, (char*)jl_string_data(data), jl_string_len(data), 0); + src.size = jl_string_len(data); int en = jl_gc_enable(0); // Might GC jl_ircode_state s = { &src, @@ -816,12 +880,13 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t jl_code_info_t *code = jl_new_code_info_uninit(); jl_code_info_flags_t flags; flags.packed = read_uint8(s.s); + code->inlining = flags.bits.inlining; code->constprop = flags.bits.constprop; code->inferred = flags.bits.inferred; - code->inlineable = flags.bits.inlineable; code->propagate_inbounds = flags.bits.propagate_inbounds; - code->pure = flags.bits.pure; + code->has_fcall = flags.bits.has_fcall; code->purity.bits = read_uint8(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); @@ -834,6 +899,8 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t jl_value_t **fld = (jl_value_t**)((char*)jl_data_ptr(code) + jl_field_offset(jl_code_info_type, i)); *fld = jl_decode_value(&s); } + if (m->is_for_opaque_closure) + code->slottypes = jl_decode_value(&s); jl_value_t *slotnames = jl_decode_value(&s); if (!jl_is_string(slotnames)) @@ -870,37 +937,47 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t code->rettype = metadata->rettype; code->parent = metadata->def; } + return code; } -JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_array_t *data) +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_typeis(data, jl_array_uint8_type)); + assert(jl_is_string(data)); jl_code_info_flags_t flags; - flags.packed = ((uint8_t*)data->data)[0]; + flags.packed = jl_string_data(data)[0]; return flags.bits.inferred; } -JL_DLLEXPORT uint8_t jl_ir_flag_inlineable(jl_array_t *data) +JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_string_t *data) { if (jl_is_code_info(data)) - return ((jl_code_info_t*)data)->inlineable; - assert(jl_typeis(data, jl_array_uint8_type)); + return ((jl_code_info_t*)data)->inlining; + assert(jl_is_string(data)); jl_code_info_flags_t flags; - flags.packed = ((uint8_t*)data->data)[0]; - return flags.bits.inlineable; + flags.packed = jl_string_data(data)[0]; + return flags.bits.inlining; } -JL_DLLEXPORT uint8_t jl_ir_flag_pure(jl_array_t *data) +JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_string_t *data) { if (jl_is_code_info(data)) - return ((jl_code_info_t*)data)->pure; - assert(jl_typeis(data, jl_array_uint8_type)); + return ((jl_code_info_t*)data)->has_fcall; + assert(jl_is_string(data)); jl_code_info_flags_t flags; - flags.packed = ((uint8_t*)data->data)[0]; - return flags.bits.pure; + flags.packed = jl_string_data(data)[0]; + return flags.bits.has_fcall; +} + +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); + return res; } JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms) @@ -929,26 +1006,26 @@ JL_DLLEXPORT jl_value_t *jl_compress_argnames(jl_array_t *syms) return str; } -JL_DLLEXPORT ssize_t jl_ir_nslots(jl_array_t *data) +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); } else { - assert(jl_typeis(data, jl_array_uint8_type)); - int nslots = jl_load_unaligned_i32((char*)data->data + 2); + assert(jl_is_string(data)); + int nslots = jl_load_unaligned_i32(jl_string_data(data) + 2 + sizeof(uint16_t)); return nslots; } } -JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_array_t *data, size_t i) +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]; - assert(jl_typeis(data, jl_array_uint8_type)); - return ((uint8_t*)data->data)[2 + sizeof(int32_t) + i]; + assert(jl_is_string(data)); + return jl_string_data(data)[2 + sizeof(uint16_t) + sizeof(int32_t) + i]; } JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms) @@ -994,6 +1071,110 @@ JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i) return jl_nothing; } +void jl_init_serializer(void) +{ + jl_task_t *ct = jl_current_task; + htable_new(&ser_tag, 0); + htable_new(&common_symbol_tag, 0); + + void *vals[] = { jl_emptysvec, jl_emptytuple, jl_false, jl_true, jl_nothing, jl_any_type, + jl_call_sym, jl_invoke_sym, jl_invoke_modify_sym, jl_goto_ifnot_sym, jl_return_sym, jl_symbol("tuple"), + jl_an_empty_string, jl_an_empty_vec_any, + + // empirical list of very common symbols + #include "common_symbols1.inc" + + jl_box_int32(0), jl_box_int32(1), jl_box_int32(2), + jl_box_int32(3), jl_box_int32(4), jl_box_int32(5), + jl_box_int32(6), jl_box_int32(7), jl_box_int32(8), + jl_box_int32(9), jl_box_int32(10), jl_box_int32(11), + jl_box_int32(12), jl_box_int32(13), jl_box_int32(14), + jl_box_int32(15), jl_box_int32(16), jl_box_int32(17), + jl_box_int32(18), jl_box_int32(19), jl_box_int32(20), + + jl_box_int64(0), jl_box_int64(1), jl_box_int64(2), + jl_box_int64(3), jl_box_int64(4), jl_box_int64(5), + jl_box_int64(6), jl_box_int64(7), jl_box_int64(8), + jl_box_int64(9), jl_box_int64(10), jl_box_int64(11), + jl_box_int64(12), jl_box_int64(13), jl_box_int64(14), + jl_box_int64(15), jl_box_int64(16), jl_box_int64(17), + jl_box_int64(18), jl_box_int64(19), jl_box_int64(20), + + jl_bool_type, jl_linenumbernode_type, jl_pinode_type, + jl_upsilonnode_type, jl_type_type, jl_bottom_type, jl_ref_type, + jl_pointer_type, jl_abstractarray_type, jl_nothing_type, + jl_vararg_type, + jl_densearray_type, jl_function_type, jl_typename_type, + jl_builtin_type, jl_task_type, jl_uniontype_type, + jl_array_any_type, jl_intrinsic_type, + jl_methtable_type, jl_typemap_level_type, + jl_voidpointer_type, jl_newvarnode_type, jl_abstractstring_type, + jl_array_symbol_type, jl_anytuple_type, jl_tparam0(jl_anytuple_type), + jl_emptytuple_type, jl_array_uint8_type, jl_code_info_type, + jl_typeofbottom_type, jl_typeofbottom_type->super, + jl_namedtuple_type, jl_array_int32_type, + jl_uint32_type, jl_uint64_type, + jl_type_type_mt, jl_nonfunction_mt, + jl_opaque_closure_type, + + ct->ptls->root_task, + + NULL }; + + // more common symbols, less common than those above. will get 2-byte encodings. + void *common_symbols[] = { + #include "common_symbols2.inc" + NULL + }; + + deser_tag[TAG_SYMBOL] = (jl_value_t*)jl_symbol_type; + deser_tag[TAG_SSAVALUE] = (jl_value_t*)jl_ssavalue_type; + deser_tag[TAG_DATATYPE] = (jl_value_t*)jl_datatype_type; + 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_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; + deser_tag[TAG_STRING] = (jl_value_t*)jl_string_type; + deser_tag[TAG_MODULE] = (jl_value_t*)jl_module_type; + deser_tag[TAG_TVAR] = (jl_value_t*)jl_tvar_type; + deser_tag[TAG_METHOD_INSTANCE] = (jl_value_t*)jl_method_instance_type; + deser_tag[TAG_METHOD] = (jl_value_t*)jl_method_type; + deser_tag[TAG_CODE_INSTANCE] = (jl_value_t*)jl_code_instance_type; + deser_tag[TAG_GLOBALREF] = (jl_value_t*)jl_globalref_type; + 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; + deser_tag[TAG_GOTOIFNOT] = (jl_value_t*)jl_gotoifnot_type; + deser_tag[TAG_RETURNNODE] = (jl_value_t*)jl_returnnode_type; + deser_tag[TAG_ARGUMENT] = (jl_value_t*)jl_argument_type; + + intptr_t i = 0; + while (vals[i] != NULL) { + deser_tag[LAST_TAG+1+i] = (jl_value_t*)vals[i]; + i += 1; + } + assert(LAST_TAG+1+i < 256); + + for (i = 2; i < 256; i++) { + if (deser_tag[i]) + ptrhash_put(&ser_tag, deser_tag[i], (void*)i); + } + + i = 2; + while (common_symbols[i-2] != NULL) { + ptrhash_put(&common_symbol_tag, common_symbols[i-2], (void*)i); + deser_symbols[i] = (jl_value_t*)common_symbols[i-2]; + i += 1; + } + assert(i <= 256); +} + #ifdef __cplusplus } #endif diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index 7c68c8eb561d9..bef04b8aaa5f8 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -2,71 +2,146 @@ #include "llvm-version.h" #include "platform.h" +#include +#include #include "llvm/IR/Mangler.h" +#include #include #include #include #include #include -#if JL_LLVM_VERSION >= 130000 +#include +#include #include -#endif +#include #include #include #include -#if JL_LLVM_VERSION >= 140000 -#include -#else -#include -#endif #include -#include #include #include +#include + +// target machine computation +#include +#include +#include +#include +#include +#include using namespace llvm; -#include "julia.h" -#include "julia_internal.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "jitlayers.h" #include "julia_assert.h" +#include "processor.h" #ifdef JL_USE_JITLINK -# if JL_LLVM_VERSION >= 140000 -# include -# endif +# include # include # include +# if JL_LLVM_VERSION >= 150000 +# include +# endif #else # include #endif -#define DEBUG_TYPE "jitlayers" +#define DEBUG_TYPE "julia_jitlayers" + +STATISTIC(LinkedGlobals, "Number of globals linked"); +STATISTIC(CompiledCodeinsts, "Number of codeinsts compiled directly"); +STATISTIC(MaxWorkqueueSize, "Maximum number of elements in the workqueue"); +STATISTIC(IndirectCodeinsts, "Number of dependent codeinsts compiled"); +STATISTIC(SpecFPtrCount, "Number of specialized function pointers compiled"); +STATISTIC(UnspecFPtrCount, "Number of specialized function pointers compiled"); +STATISTIC(ModulesAdded, "Number of modules added to the JIT"); +STATISTIC(ModulesOptimized, "Number of modules optimized by the JIT"); +STATISTIC(OptO0, "Number of modules optimized at level -O0"); +STATISTIC(OptO1, "Number of modules optimized at level -O1"); +STATISTIC(OptO2, "Number of modules optimized at level -O2"); +STATISTIC(OptO3, "Number of modules optimized at level -O3"); +STATISTIC(ModulesMerged, "Number of modules merged"); +STATISTIC(InternedGlobals, "Number of global constants interned in the string pool"); + +#ifdef _COMPILER_MSAN_ENABLED_ +// TODO: This should not be necessary on ELF x86_64, but LLVM's implementation +// of the TLS relocations is currently broken, so enable this unconditionally. +#define MSAN_EMUTLS_WORKAROUND 1 + +// See https://github.com/google/sanitizers/wiki/MemorySanitizerJIT +namespace msan_workaround { + +extern "C" { + extern __thread unsigned long long __msan_param_tls[]; + extern __thread unsigned int __msan_param_origin_tls[]; + extern __thread unsigned long long __msan_retval_tls[]; + extern __thread unsigned int __msan_retval_origin_tls; + extern __thread unsigned long long __msan_va_arg_tls[]; + extern __thread unsigned int __msan_va_arg_origin_tls[]; + extern __thread unsigned long long __msan_va_arg_overflow_size_tls; + extern __thread unsigned int __msan_origin_tls; +} -void jl_init_jit(void) { } +enum class MSanTLS +{ + param = 1, // __msan_param_tls + param_origin, //__msan_param_origin_tls + retval, // __msan_retval_tls + retval_origin, //__msan_retval_origin_tls + va_arg, // __msan_va_arg_tls + va_arg_origin, // __msan_va_arg_origin_tls + va_arg_overflow_size, // __msan_va_arg_overflow_size_tls + origin, //__msan_origin_tls +}; + +static void *getTLSAddress(void *control) +{ + auto tlsIndex = static_cast(reinterpret_cast(control)); + switch(tlsIndex) + { + case MSanTLS::param: return reinterpret_cast(&__msan_param_tls); + case MSanTLS::param_origin: return reinterpret_cast(&__msan_param_origin_tls); + case MSanTLS::retval: return reinterpret_cast(&__msan_retval_tls); + case MSanTLS::retval_origin: return reinterpret_cast(&__msan_retval_origin_tls); + case MSanTLS::va_arg: return reinterpret_cast(&__msan_va_arg_tls); + case MSanTLS::va_arg_origin: return reinterpret_cast(&__msan_va_arg_origin_tls); + case MSanTLS::va_arg_overflow_size: return reinterpret_cast(&__msan_va_arg_overflow_size_tls); + case MSanTLS::origin: return reinterpret_cast(&__msan_origin_tls); + default: + assert(false && "BAD MSAN TLS INDEX"); + return nullptr; + } +} +} +#endif // Snooping on which functions are being compiled, and how long it takes -JL_STREAM *dump_compiles_stream = NULL; -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN void jl_dump_compiles_impl(void *s) { - dump_compiles_stream = (JL_STREAM*)s; + **jl_ExecutionEngine->get_dump_compiles_stream() = (ios_t*)s; } -JL_STREAM *dump_llvm_opt_stream = NULL; -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN void jl_dump_llvm_opt_impl(void *s) { - dump_llvm_opt_stream = (JL_STREAM*)s; + **jl_ExecutionEngine->get_dump_llvm_opt_stream() = (ios_t*)s; } -static void jl_add_to_ee(std::unique_ptr m); -static void jl_add_to_ee(std::unique_ptr &M, StringMap*> &NewExports); -static uint64_t getAddressForFunction(StringRef fname); - -void jl_link_global(GlobalVariable *GV, void *addr) +static int jl_add_to_ee( + orc::ThreadSafeModule &M, + const StringMap &NewExports, + DenseMap &Queued, + std::vector &Stack) JL_NOTSAFEPOINT; +static void jl_decorate_module(Module &M) JL_NOTSAFEPOINT; +static uint64_t getAddressForFunction(StringRef fname) JL_NOTSAFEPOINT; + +void jl_link_global(GlobalVariable *GV, void *addr) JL_NOTSAFEPOINT { + ++LinkedGlobals; Constant *P = literal_static_pointer_val(addr, GV->getValueType()); GV->setInitializer(P); if (jl_options.image_codegen) { @@ -78,11 +153,12 @@ void jl_link_global(GlobalVariable *GV, void *addr) else { GV->setConstant(true); GV->setLinkage(GlobalValue::PrivateLinkage); + GV->setVisibility(GlobalValue::DefaultVisibility); GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); } } -void jl_jit_globals(std::map &globals) +void jl_jit_globals(std::map &globals) JL_NOTSAFEPOINT { for (auto &global : globals) { jl_link_global(global.second, global.first); @@ -96,43 +172,59 @@ void jl_jit_globals(std::map &globals) static jl_callptr_t _jl_compile_codeinst( jl_code_instance_t *codeinst, jl_code_info_t *src, - size_t world) + size_t world, + orc::ThreadSafeContext context, + bool is_recompile) { // caller must hold codegen_lock // and have disabled finalizers uint64_t start_time = 0; - if (dump_compiles_stream != NULL) + bool timed = !!*jl_ExecutionEngine->get_dump_compiles_stream(); + if (timed) 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"); - assert(src && jl_is_code_info(src)); + JL_TIMING(CODEINST_COMPILE, CODEINST_COMPILE); +#ifdef USE_TRACY + if (is_recompile) { + TracyCZoneCtx ctx = *(JL_TIMING_CURRENT_BLOCK->tracy_ctx); + TracyCZoneColor(ctx, 0xFFA500); + } +#endif jl_callptr_t fptr = NULL; // emit the code in LLVM IR form - jl_codegen_params_t params; + jl_codegen_params_t params(std::move(context), jl_ExecutionEngine->getDataLayout(), jl_ExecutionEngine->getTargetTriple()); // Locks the context params.cache = true; params.world = world; - std::map emitted; + jl_workqueue_t emitted; { - jl_compile_result_t result = jl_emit_codeinst(codeinst, src, params); - if (std::get<0>(result)) - emitted[codeinst] = std::move(result); - jl_compile_workqueue(emitted, params, CompilationPolicy::Default); + orc::ThreadSafeModule result_m = + jl_create_ts_module(name_from_method_instance(codeinst->def), params.tsctx, params.imaging, params.DL, params.TargetTriple); + jl_llvm_functions_t decls = jl_emit_codeinst(result_m, codeinst, src, params); + if (result_m) + emitted[codeinst] = {std::move(result_m), std::move(decls)}; + { + auto temp_module = jl_create_llvm_module(name_from_method_instance(codeinst->def), params.getContext(), params.imaging); + jl_compile_workqueue(emitted, *temp_module, params, CompilationPolicy::Default); + } if (params._shared_module) - jl_add_to_ee(std::unique_ptr(params._shared_module)); - StringMap*> NewExports; + jl_ExecutionEngine->addModule(orc::ThreadSafeModule(std::move(params._shared_module), params.tsctx)); + StringMap NewExports; StringMap NewGlobals; for (auto &global : params.globals) { NewGlobals[global.second->getName()] = global.first; } for (auto &def : emitted) { - std::unique_ptr &M = std::get<0>(def.second); + orc::ThreadSafeModule &TSM = std::get<0>(def.second); + //The underlying context object is still locked because params is not destroyed yet + auto M = TSM.getModuleUnlocked(); for (auto &F : M->global_objects()) { if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { - NewExports[F.getName()] = &M; + NewExports[F.getName()] = &TSM; } } // Let's link all globals here also (for now) @@ -143,16 +235,25 @@ static jl_callptr_t _jl_compile_codeinst( } } } + DenseMap Queued; + std::vector Stack; for (auto &def : emitted) { // Add the results to the execution engine now - std::unique_ptr &M = std::get<0>(def.second); - jl_add_to_ee(M, NewExports); + orc::ThreadSafeModule &M = std::get<0>(def.second); + jl_add_to_ee(M, NewExports, Queued, Stack); + assert(Queued.empty() && Stack.empty() && !M); } + ++CompiledCodeinsts; + MaxWorkqueueSize.updateMax(emitted.size()); + IndirectCodeinsts += emitted.size() - 1; } - JL_TIMING(LLVM_MODULE_FINISH); + size_t i = 0; for (auto &def : emitted) { jl_code_instance_t *this_code = def.first; + if (i < jl_timing_print_limit) + jl_timing_show_func_sig(this_code->def->specTypes, JL_TIMING_CURRENT_BLOCK); + jl_llvm_functions_t decls = std::get<1>(def.second); jl_callptr_t addr; bool isspecsig = false; @@ -162,63 +263,98 @@ static jl_callptr_t _jl_compile_codeinst( else if (decls.functionObject == "jl_fptr_sparam") { addr = jl_fptr_sparam_addr; } + else if (decls.functionObject == "jl_f_opaque_closure_call") { + addr = jl_f_opaque_closure_call_addr; + } else { addr = (jl_callptr_t)getAddressForFunction(decls.functionObject); isspecsig = true; } - if (this_code->invoke == NULL) { - // once set, don't change invoke-ptr, as that leads to race conditions - // with the (not) simultaneous updates to invoke and specptr - if (!decls.specFunctionObject.empty()) { - jl_atomic_store_release(&this_code->specptr.fptr, (void*)getAddressForFunction(decls.specFunctionObject)); - this_code->isspecsig = isspecsig; + if (!decls.specFunctionObject.empty()) { + void *prev_specptr = NULL; + auto spec = (void*)getAddressForFunction(decls.specFunctionObject); + if (jl_atomic_cmpswap_acqrel(&this_code->specptr.fptr, &prev_specptr, spec)) { + // only set specsig and invoke if we were the first to set specptr + jl_atomic_store_relaxed(&this_code->specsigflags, (uint8_t) isspecsig); + // we might overwrite invokeptr here; that's ok, anybody who relied on the identity of invokeptr + // either assumes that specptr was null, doesn't care about specptr, + // or will wait until specsigflags has 0b10 set before reloading invoke + jl_atomic_store_release(&this_code->invoke, addr); + jl_atomic_store_release(&this_code->specsigflags, (uint8_t) (0b10 | isspecsig)); + } else { + //someone else beat us, don't commit any results + while (!(jl_atomic_load_acquire(&this_code->specsigflags) & 0b10)) { + jl_cpu_pause(); + } + addr = jl_atomic_load_relaxed(&this_code->invoke); + } + } else { + jl_callptr_t prev_invoke = NULL; + 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) } - jl_atomic_store_release(&this_code->invoke, addr); - } - else if (this_code->invoke == jl_fptr_const_return_addr && !decls.specFunctionObject.empty()) { - // hack to export this pointer value to jl_dump_method_disasm - jl_atomic_store_release(&this_code->specptr.fptr, (void*)getAddressForFunction(decls.specFunctionObject)); } - if (this_code== codeinst) + if (this_code == codeinst) fptr = addr; + i++; } + if (i > jl_timing_print_limit) + jl_timing_printf(JL_TIMING_CURRENT_BLOCK, "... <%d methods truncated>", i - 10); uint64_t end_time = 0; - if (dump_compiles_stream != NULL) + if (timed) end_time = jl_hrtime(); // If logging of the compilation stream is enabled, // then dump the method-instance specialization type to the stream jl_method_instance_t *mi = codeinst->def; if (jl_is_method(mi->def.method)) { - if (dump_compiles_stream != NULL) { - jl_printf(dump_compiles_stream, "%" PRIu64 "\t\"", end_time - start_time); - jl_static_show(dump_compiles_stream, mi->specTypes); - jl_printf(dump_compiles_stream, "\"\n"); + auto stream = *jl_ExecutionEngine->get_dump_compiles_stream(); + if (stream) { + ios_printf(stream, "%" PRIu64 "\t\"", end_time - start_time); + jl_static_show((JL_STREAM*)stream, mi->specTypes); + ios_printf(stream, "\"\n"); } } return fptr; } -const char *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms, LLVMContext &ctxt); +const char *jl_generate_ccallable(LLVMOrcThreadSafeModuleRef llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t ¶ms); // compile a C-callable alias -extern "C" JL_DLLEXPORT -int jl_compile_extern_c_impl(void *llvmmod, void *p, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) +extern "C" JL_DLLEXPORT_CODEGEN +int jl_compile_extern_c_impl(LLVMOrcThreadSafeModuleRef llvmmod, void *p, void *sysimg, jl_value_t *declrt, jl_value_t *sigt) { - JL_LOCK(&jl_codegen_lock); + 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); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); - jl_codegen_params_t params; + orc::ThreadSafeContext ctx; + auto into = unwrap(llvmmod); jl_codegen_params_t *pparams = (jl_codegen_params_t*)p; + orc::ThreadSafeModule backing; + if (into == NULL) { + if (!pparams) { + ctx = jl_ExecutionEngine->acquireContext(); + } + backing = jl_create_ts_module("cextern", pparams ? pparams->tsctx : ctx, pparams ? pparams->imaging : imaging_default()); + into = &backing; + } + JL_LOCK(&jl_codegen_lock); + auto target_info = into->withModuleDo([&](Module &M) { + return std::make_pair(M.getDataLayout(), Triple(M.getTargetTriple())); + }); + jl_codegen_params_t params(into->getContext(), std::move(target_info.first), std::move(target_info.second)); if (pparams == NULL) pparams = ¶ms; - Module *into = (Module*)llvmmod; - if (into == NULL) - into = jl_create_llvm_module("cextern"); - const char *name = jl_generate_ccallable(into, sysimg, declrt, sigt, *pparams, into->getContext()); + assert(pparams->tsctx.getContext() == into->getContext().getContext()); + const char *name = jl_generate_ccallable(wrap(into), sysimg, declrt, sigt, *pparams); bool success = true; if (!sysimg) { if (jl_ExecutionEngine->getGlobalValueAddress(name)) { @@ -228,19 +364,27 @@ int jl_compile_extern_c_impl(void *llvmmod, void *p, void *sysimg, jl_value_t *d jl_jit_globals(params.globals); assert(params.workqueue.empty()); if (params._shared_module) - jl_add_to_ee(std::unique_ptr(params._shared_module)); + jl_ExecutionEngine->addModule(orc::ThreadSafeModule(std::move(params._shared_module), params.tsctx)); } if (success && llvmmod == NULL) - jl_add_to_ee(std::unique_ptr(into)); + jl_ExecutionEngine->addModule(std::move(*into)); } - if (jl_codegen_lock.count == 1 && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&jl_codegen_lock); + if (timed) { + if (measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } + ct->reentrant_timing &= ~1ull; + } + if (ctx.getContext()) { + jl_ExecutionEngine->releaseContext(std::move(ctx)); + } return success; } // declare a C-callable entry point; called during code loading from the toplevel -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN void jl_extern_c_impl(jl_value_t *declrt, jl_tupletype_t *sigt) { // validate arguments. try to do as many checks as possible here to avoid @@ -256,10 +400,8 @@ void jl_extern_c_impl(jl_value_t *declrt, jl_tupletype_t *sigt) // compute / validate return type if (!jl_is_concrete_type(declrt) || jl_is_kind(declrt)) jl_error("@ccallable: return type must be concrete and correspond to a C type"); - JL_LOCK(&jl_codegen_lock); if (!jl_type_mappable_to_c(declrt)) jl_error("@ccallable: return type doesn't correspond to a C type"); - JL_UNLOCK(&jl_codegen_lock); // validate method signature size_t i, nargs = jl_nparams(sigt); @@ -285,25 +427,36 @@ void jl_extern_c_impl(jl_value_t *declrt, jl_tupletype_t *sigt) } // this compiles li and emits fptr -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world) { - JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion + 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_GC_PUSH1(&src); - jl_value_t *ci = jl_rettype_inferred(mi, world, world); - jl_code_instance_t *codeinst = (ci == jl_nothing ? NULL : (jl_code_instance_t*)ci); + 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*)codeinst->inferred; + 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_array_t*)src); + 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] != '@') { @@ -321,35 +474,63 @@ jl_code_instance_t *jl_generate_fptr_impl(jl_method_instance_t *mi JL_PROPAGATES 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 && !codeinst->inferred) - codeinst->inferred = jl_nothing; + if (src->inferred) { + jl_value_t *null = nullptr; + jl_atomic_cmpswap_relaxed(&codeinst->inferred, &null, jl_nothing); + } } - _jl_compile_codeinst(codeinst, src, world); - if (codeinst->invoke == NULL) + ++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; } - if (jl_codegen_lock.count == 1 && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); 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 +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; + } + 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); + } + JL_UNLOCK(&jl_codegen_lock); // Might GC +} + +extern "C" JL_DLLEXPORT_CODEGEN void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) { if (jl_atomic_load_relaxed(&unspec->invoke) != NULL) { return; } - JL_LOCK(&jl_codegen_lock); + 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); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); - if (unspec->invoke == NULL) { + JL_LOCK(&jl_codegen_lock); + if (jl_atomic_load_relaxed(&unspec->invoke) == NULL) { jl_code_info_t *src = NULL; JL_GC_PUSH1(&src); jl_method_t *def = unspec->def->def.method; @@ -359,37 +540,42 @@ void jl_generate_fptr_for_unspecialized_impl(jl_code_instance_t *unspec) // TODO: this is wrong assert(def->generator); // TODO: jl_code_for_staged can throw - src = jl_code_for_staged(unspec->def); + src = jl_code_for_staged(unspec->def, ~(size_t)0); } if (src && (jl_value_t*)src != jl_nothing) - src = jl_uncompress_ir(def, NULL, (jl_array_t*)src); + src = jl_uncompress_ir(def, NULL, (jl_value_t*)src); } else { - src = (jl_code_info_t*)unspec->def->uninferred; + src = (jl_code_info_t*)jl_atomic_load_relaxed(&unspec->def->uninferred); } assert(src && jl_is_code_info(src)); - _jl_compile_codeinst(unspec, src, unspec->min_world); - if (unspec->invoke == NULL) { - // if we hit a codegen bug (or ran into a broken generated function or llvmcall), fall back to the interpreter as a last resort - jl_atomic_store_release(&unspec->invoke, jl_fptr_interpret_call_addr); - } + ++UnspecFPtrCount; + _jl_compile_codeinst(unspec, src, unspec->min_world, *jl_ExecutionEngine->getContext(), 0); + 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 + jl_atomic_cmpswap(&unspec->invoke, &null, jl_fptr_interpret_call_addr); JL_GC_POP(); } - if (jl_codegen_lock.count == 1 && measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&jl_codegen_lock); // Might GC + if (timed) { + if (measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } + ct->reentrant_timing &= ~1ull; + } } // get a native disassembly for a compiled method -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, char raw_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); if (codeinst) { - uintptr_t fptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->invoke); + uintptr_t fptr = (uintptr_t)jl_atomic_load_acquire(&codeinst->invoke); if (getwrapper) return jl_dump_fptr_asm(fptr, raw_mc, asm_variant, debuginfo, binary); uintptr_t specfptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); @@ -397,11 +583,15 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, // normally we prevent native code from being generated for these functions, // (using sentinel value `1` instead) // so create an exception here so we can print pretty our lies - JL_LOCK(&jl_codegen_lock); // also disables finalizers, to prevent any unexpected recursion + 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); if (measure_compile_time_enabled) compiler_start_time = jl_hrtime(); + 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); @@ -410,34 +600,40 @@ jl_value_t *jl_dump_method_asm_impl(jl_method_instance_t *mi, size_t world, if (jl_is_method(def)) { if (!src) { // TODO: jl_code_for_staged can throw - src = def->generator ? jl_code_for_staged(mi) : (jl_code_info_t*)def->source; + 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_array_t*)src); + src = jl_uncompress_ir(mi->def.method, codeinst, (jl_value_t*)src); } - fptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->invoke); + 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); + fptr = (uintptr_t)_jl_compile_codeinst(codeinst, src, world, *jl_ExecutionEngine->getContext(), 0); specfptr = (uintptr_t)jl_atomic_load_relaxed(&codeinst->specptr.fptr); } } JL_GC_POP(); } - if (measure_compile_time_enabled) - jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, (jl_hrtime() - compiler_start_time)); JL_UNLOCK(&jl_codegen_lock); + if (timed) { + if (measure_compile_time_enabled) { + auto end = jl_hrtime(); + jl_atomic_fetch_add_relaxed(&jl_cumulative_compile_time, end - compiler_start_time); + } + ct->reentrant_timing &= ~1ull; + } } if (specfptr != 0) return jl_dump_fptr_asm(specfptr, raw_mc, asm_variant, debuginfo, binary); } // whatever, that didn't work - use the assembler output instead - void *F = jl_get_llvmf_defn(mi, world, getwrapper, true, jl_default_cgparams); - if (!F) + jl_llvmf_dump_t llvmf_dump; + jl_get_llvmf_defn(&llvmf_dump, mi, world, getwrapper, true, jl_default_cgparams); + if (!llvmf_dump.F) return jl_an_empty_string; - return jl_dump_function_asm(F, raw_mc, asm_variant, debuginfo, binary); + return jl_dump_function_asm(&llvmf_dump, raw_mc, asm_variant, debuginfo, binary); } CodeGenOpt::Level CodeGenOptLevelFor(int optlevel) @@ -451,113 +647,42 @@ CodeGenOpt::Level CodeGenOptLevelFor(int optlevel) #endif } -static void addPassesForOptLevel(legacy::PassManager &PM, TargetMachine &TM, raw_svector_ostream &ObjStream, MCContext *Ctx, int optlevel) -{ - addTargetPasses(&PM, &TM); - addOptimizationPasses(&PM, optlevel); - addMachinePasses(&PM, &TM, optlevel); - if (TM.addPassesToEmitMC(PM, Ctx, ObjStream)) - llvm_unreachable("Target does not support MC emission."); -} - -static auto countBasicBlocks(const Function &F) +static auto countBasicBlocks(const Function &F) JL_NOTSAFEPOINT { return std::distance(F.begin(), F.end()); } -CompilerResultT JuliaOJIT::CompilerT::operator()(Module &M) -{ - uint64_t start_time = 0; - if (dump_llvm_opt_stream != NULL) { - // Print LLVM function statistics _before_ optimization - // Print all the information about this invocation as a YAML object - jl_printf(dump_llvm_opt_stream, "- \n"); - // We print the name and some statistics for each function in the module, both - // before optimization and again afterwards. - jl_printf(dump_llvm_opt_stream, " before: \n"); - for (auto &F : M.functions()) { - if (F.isDeclaration() || F.getName().startswith("jfptr_")) { - continue; - } - // Each function is printed as a YAML object with several attributes - jl_printf(dump_llvm_opt_stream, " \"%s\":\n", F.getName().str().c_str()); - jl_printf(dump_llvm_opt_stream, " instructions: %u\n", F.getInstructionCount()); - jl_printf(dump_llvm_opt_stream, " basicblocks: %lu\n", countBasicBlocks(F)); +void JuliaOJIT::OptSelLayerT::emit(std::unique_ptr R, orc::ThreadSafeModule TSM) { + ++ModulesOptimized; + size_t optlevel = SIZE_MAX; + TSM.withModuleDo([&](Module &M) { + if (jl_generating_output()) { + optlevel = 0; } - - start_time = jl_hrtime(); - } - - JL_TIMING(LLVM_OPT); - - int optlevel; - int optlevel_min; - if (jl_generating_output()) { - optlevel = 0; - } - else { - optlevel = jl_options.opt_level; - optlevel_min = jl_options.opt_level_min; - for (auto &F : M.functions()) { - if (!F.getBasicBlockList().empty()) { - Attribute attr = F.getFnAttribute("julia-optimization-level"); - StringRef val = attr.getValueAsString(); - if (val != "") { - int ol = (int)val[0] - '0'; - if (ol >= 0 && ol < optlevel) - optlevel = ol; + else { + optlevel = std::max(static_cast(jl_options.opt_level), 0); + size_t optlevel_min = std::max(static_cast(jl_options.opt_level_min), 0); + for (auto &F : M.functions()) { + if (!F.getBasicBlockList().empty()) { + Attribute attr = F.getFnAttribute("julia-optimization-level"); + StringRef val = attr.getValueAsString(); + if (val != "") { + size_t ol = (size_t)val[0] - '0'; + if (ol < optlevel) + optlevel = ol; + } } } + optlevel = std::min(std::max(optlevel, optlevel_min), this->count); } - optlevel = std::max(optlevel, optlevel_min); - } - if (optlevel == 0) - jit.PM0.run(M); - else if (optlevel == 1) - jit.PM1.run(M); - else if (optlevel == 2) - jit.PM2.run(M); - else if (optlevel >= 3) - jit.PM3.run(M); - - std::unique_ptr ObjBuffer( - new SmallVectorMemoryBuffer(std::move(jit.ObjBufferSV))); - auto Obj = object::ObjectFile::createObjectFile(ObjBuffer->getMemBufferRef()); - - if (!Obj) { - llvm_dump(&M); - std::string Buf; - raw_string_ostream OS(Buf); - logAllUnhandledErrors(Obj.takeError(), OS, ""); - OS.flush(); - llvm::report_fatal_error(llvm::Twine("FATAL: Unable to compile LLVM Module: '") + Buf + "'\n" - "The module's content was printed above. Please file a bug report"); - } - - uint64_t end_time = 0; - if (dump_llvm_opt_stream != NULL) { - end_time = jl_hrtime(); - jl_printf(dump_llvm_opt_stream, " time_ns: %" PRIu64 "\n", end_time - start_time); - jl_printf(dump_llvm_opt_stream, " optlevel: %d\n", optlevel); - - // Print LLVM function statistics _after_ optimization - jl_printf(dump_llvm_opt_stream, " after: \n"); - for (auto &F : M.functions()) { - if (F.isDeclaration() || F.getName().startswith("jfptr_")) { - continue; - } - jl_printf(dump_llvm_opt_stream, " \"%s\":\n", F.getName().str().c_str()); - jl_printf(dump_llvm_opt_stream, " instructions: %u\n", F.getInstructionCount()); - jl_printf(dump_llvm_opt_stream, " basicblocks: %lu\n", countBasicBlocks(F)); - } - } - - return CompilerResultT(std::move(ObjBuffer)); + }); + assert(optlevel != SIZE_MAX && "Failed to select a valid optimization level!"); + this->optimizers[optlevel]->OptimizeLayer.emit(std::move(R), std::move(TSM)); } void jl_register_jit_object(const object::ObjectFile &debugObj, std::function getLoadAddress, - std::function lookupWriteAddress); + std::function lookupWriteAddress) JL_NOTSAFEPOINT; #ifdef JL_USE_JITLINK @@ -572,6 +697,7 @@ struct JITObjectInfo { }; class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { + std::mutex PluginMutex; std::map> PendingObjs; // Resources from distinct MaterializationResponsibilitys can get merged // after emission, so we can have multiple debug objects per resource key. @@ -592,33 +718,40 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { auto NewObj = cantFail(object::ObjectFile::createObjectFile(NewBuffer->getMemBufferRef())); - assert(PendingObjs.count(&MR) == 0); - PendingObjs[&MR] = std::unique_ptr( - new JITObjectInfo{std::move(NewBuffer), std::move(NewObj), {}}); + { + std::lock_guard lock(PluginMutex); + assert(PendingObjs.count(&MR) == 0); + PendingObjs[&MR] = std::unique_ptr( + new JITObjectInfo{std::move(NewBuffer), std::move(NewObj), {}}); + } } Error notifyEmitted(MaterializationResponsibility &MR) override { - auto It = PendingObjs.find(&MR); - if (It == PendingObjs.end()) - return Error::success(); - - auto NewInfo = PendingObjs[&MR].get(); - auto getLoadAddress = [NewInfo](const StringRef &Name) -> uint64_t { - auto result = NewInfo->SectionLoadAddresses.find(Name); - if (result == NewInfo->SectionLoadAddresses.end()) { - LLVM_DEBUG({ - dbgs() << "JLDebuginfoPlugin: No load address found for section '" - << Name << "'\n"; - }); - return 0; - } - return result->second; - }; + { + std::lock_guard lock(PluginMutex); + auto It = PendingObjs.find(&MR); + if (It == PendingObjs.end()) + return Error::success(); + + auto NewInfo = PendingObjs[&MR].get(); + auto getLoadAddress = [NewInfo](const StringRef &Name) -> uint64_t { + auto result = NewInfo->SectionLoadAddresses.find(Name); + if (result == NewInfo->SectionLoadAddresses.end()) { + LLVM_DEBUG({ + dbgs() << "JLDebuginfoPlugin: No load address found for section '" + << Name << "'\n"; + }); + return 0; + } + return result->second; + }; - jl_register_jit_object(*NewInfo->Object, getLoadAddress, nullptr); + jl_register_jit_object(*NewInfo->Object, getLoadAddress, nullptr); + } cantFail(MR.withResourceKeyDo([&](ResourceKey K) { + std::lock_guard lock(PluginMutex); RegisteredObjs[K].push_back(std::move(PendingObjs[&MR])); PendingObjs.erase(&MR); })); @@ -628,12 +761,14 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { Error notifyFailed(MaterializationResponsibility &MR) override { + std::lock_guard lock(PluginMutex); PendingObjs.erase(&MR); return Error::success(); } Error notifyRemovingResources(ResourceKey K) override { + std::lock_guard lock(PluginMutex); RegisteredObjs.erase(K); // TODO: If we ever unload code, need to notify debuginfo registry. return Error::success(); @@ -641,6 +776,7 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { void notifyTransferringResources(ResourceKey DstKey, ResourceKey SrcKey) override { + std::lock_guard lock(PluginMutex); auto SrcIt = RegisteredObjs.find(SrcKey); if (SrcIt != RegisteredObjs.end()) { for (std::unique_ptr &Info : SrcIt->second) @@ -652,13 +788,16 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { void modifyPassConfig(MaterializationResponsibility &MR, jitlink::LinkGraph &, jitlink::PassConfiguration &PassConfig) override { + std::lock_guard lock(PluginMutex); auto It = PendingObjs.find(&MR); if (It == PendingObjs.end()) return; JITObjectInfo &Info = *It->second; - PassConfig.PostAllocationPasses.push_back([&Info](jitlink::LinkGraph &G) -> Error { + PassConfig.PostAllocationPasses.push_back([&Info, this](jitlink::LinkGraph &G) -> Error { + std::lock_guard lock(PluginMutex); for (const jitlink::Section &Sec : G.sections()) { +#ifdef _OS_DARWIN_ // Canonical JITLink section names have the segment name included, e.g. // "__TEXT,__text" or "__DWARF,__debug_str". There are some special internal // sections without a comma separator, which we can just ignore. @@ -671,30 +810,79 @@ class JLDebuginfoPlugin : public ObjectLinkingLayer::Plugin { continue; } auto SecName = Sec.getName().substr(SepPos + 1); - Info.SectionLoadAddresses[SecName] = jitlink::SectionRange(Sec).getStart(); +#else + auto SecName = Sec.getName(); +#endif + // https://github.com/llvm/llvm-project/commit/118e953b18ff07d00b8f822dfbf2991e41d6d791 + Info.SectionLoadAddresses[SecName] = jitlink::SectionRange(Sec).getStart().getValue(); } return Error::success(); }); } }; + +class JLMemoryUsagePlugin : public ObjectLinkingLayer::Plugin { +private: + std::atomic &total_size; + +public: + + JLMemoryUsagePlugin(std::atomic &total_size) + : total_size(total_size) {} + + Error notifyFailed(orc::MaterializationResponsibility &MR) override { + return Error::success(); + } + Error notifyRemovingResources(orc::ResourceKey K) override { + return Error::success(); + } + void notifyTransferringResources(orc::ResourceKey DstKey, + orc::ResourceKey SrcKey) override {} + + void modifyPassConfig(orc::MaterializationResponsibility &, + jitlink::LinkGraph &, + jitlink::PassConfiguration &Config) override { + Config.PostAllocationPasses.push_back([this](jitlink::LinkGraph &G) { + size_t graph_size = 0; + for (auto block : G.blocks()) { + graph_size += block->getSize(); + } + this->total_size.fetch_add(graph_size, std::memory_order_relaxed); + return Error::success(); + }); + } +}; + +// 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 + return cantFail(orc::MapperJITLinkMemoryManager::CreateWithMapper()); +#endif +} } # ifdef LLVM_SHLIB + +# define EHFRAME_RANGE(name) orc::ExecutorAddrRange name +# define UNPACK_EHFRAME_RANGE(name) \ + name.Start.toPtr(), \ + static_cast(name.size()) + + class JLEHFrameRegistrar final : public jitlink::EHFrameRegistrar { public: - Error registerEHFrames(JITTargetAddress EHFrameSectionAddr, - size_t EHFrameSectionSize) override { + Error registerEHFrames(EHFRAME_RANGE(EHFrameSection)) override { register_eh_frames( - jitTargetAddressToPointer(EHFrameSectionAddr), - EHFrameSectionSize); + UNPACK_EHFRAME_RANGE(EHFrameSection)); return Error::success(); } - Error deregisterEHFrames(JITTargetAddress EHFrameSectionAddr, - size_t EHFrameSectionSize) override { + Error deregisterEHFrames(EHFRAME_RANGE(EHFrameSection)) override { deregister_eh_frames( - jitTargetAddressToPointer(EHFrameSectionAddr), - EHFrameSectionSize); + UNPACK_EHFRAME_RANGE(EHFrameSection)); return Error::success(); } }; @@ -801,27 +989,296 @@ void registerRTDyldJITObject(const object::ObjectFile &Object, ); } #endif +namespace { + static std::unique_ptr createTargetMachine() JL_NOTSAFEPOINT { + TargetOptions options = TargetOptions(); + + Triple TheTriple(sys::getProcessTriple()); + // use ELF because RuntimeDyld COFF i686 support didn't exist + // use ELF because RuntimeDyld COFF X86_64 doesn't seem to work (fails to generate function pointers)? + bool force_elf = TheTriple.isOSWindows(); +#ifdef FORCE_ELF + force_elf = true; +#endif + if (force_elf) { + TheTriple.setObjectFormat(Triple::ELF); + } + //options.PrintMachineCode = true; //Print machine code produced during JIT compiling +#if defined(MSAN_EMUTLS_WORKAROUND) + options.EmulatedTLS = true; + options.ExplicitEmulatedTLS = true; +#endif + uint32_t target_flags = 0; + auto target = jl_get_llvm_target(imaging_default(), target_flags); + auto &TheCPU = target.first; + SmallVector targetFeatures(target.second.begin(), target.second.end()); + std::string errorstr; + const Target *TheTarget = TargetRegistry::lookupTarget("", TheTriple, errorstr); + if (!TheTarget) { + jl_errorf("Internal problem with process triple %s lookup: %s", TheTriple.str().c_str(), errorstr.c_str()); + return nullptr; + } + if (jl_processor_print_help || (target_flags & JL_TARGET_UNKNOWN_NAME)) { + std::unique_ptr MSTI( + TheTarget->createMCSubtargetInfo(TheTriple.str(), "", "")); + if (!MSTI->isCPUStringValid(TheCPU)) { + jl_errorf("Invalid CPU name \"%s\".", TheCPU.c_str()); + return nullptr; + } + if (jl_processor_print_help) { + // This is the only way I can find to print the help message once. + // It'll be nice if we can iterate through the features and print our own help + // message... + MSTI->setDefaultFeatures("help", "", ""); + } + } + // Package up features to be passed to target/subtarget + std::string FeaturesStr; + if (!targetFeatures.empty()) { + SubtargetFeatures Features; + for (unsigned i = 0; i != targetFeatures.size(); ++i) + Features.AddFeature(targetFeatures[i]); + FeaturesStr = Features.getString(); + } + // Allocate a target... + Optional codemodel = +#ifdef _P64 + // Make sure we are using the large code model on 64bit + // Let LLVM pick a default suitable for jitting on 32bit + CodeModel::Large; +#else + None; +#endif + auto optlevel = CodeGenOptLevelFor(jl_options.opt_level); + auto TM = TheTarget->createTargetMachine( + TheTriple.getTriple(), TheCPU, FeaturesStr, + options, + Reloc::Static, // Generate simpler code for JIT + codemodel, + optlevel, + true // JIT + ); + 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); + } + return std::unique_ptr(TM); + } +} // namespace -JuliaOJIT::JuliaOJIT(TargetMachine &TM, LLVMContext *LLVMCtx) - : TM(TM), - DL(TM.createDataLayout()), - ObjStream(ObjBufferSV), - TSCtx(std::unique_ptr(LLVMCtx)), -#if JL_LLVM_VERSION >= 130000 - ES(cantFail(orc::SelfExecutorProcessControl::Create())), +namespace { + +#ifndef JL_USE_NEW_PM + typedef legacy::PassManager PassManager; #else - ES(), + typedef NewPM PassManager; +#endif + + orc::JITTargetMachineBuilder createJTMBFromTM(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT { + return orc::JITTargetMachineBuilder(TM.getTargetTriple()) + .setCPU(TM.getTargetCPU().str()) + .setFeatures(TM.getTargetFeatureString()) + .setOptions(TM.Options) + .setRelocationModel(Reloc::Static) + .setCodeModel(TM.getCodeModel()) + .setCodeGenOptLevel(CodeGenOptLevelFor(optlevel)); + } + + struct TMCreator { + orc::JITTargetMachineBuilder JTMB; + + TMCreator(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : JTMB(createJTMBFromTM(TM, optlevel)) {} + + std::unique_ptr operator()() JL_NOTSAFEPOINT { + return cantFail(JTMB.createTargetMachine()); + } + }; + +#ifndef JL_USE_NEW_PM + struct PMCreator { + std::unique_ptr TM; + int optlevel; + PMCreator(TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : TM(cantFail(createJTMBFromTM(TM, optlevel).createTargetMachine())), optlevel(optlevel) {} + // overload for newpm compatibility + PMCreator(TargetMachine &TM, int optlevel, std::vector> &) JL_NOTSAFEPOINT + : PMCreator(TM, optlevel) {} + PMCreator(const PMCreator &other) JL_NOTSAFEPOINT + : PMCreator(*other.TM, other.optlevel) {} + PMCreator(PMCreator &&other) JL_NOTSAFEPOINT + : TM(std::move(other.TM)), optlevel(other.optlevel) {} + friend void swap(PMCreator &self, PMCreator &other) JL_NOTSAFEPOINT { + using std::swap; + swap(self.TM, other.TM); + swap(self.optlevel, other.optlevel); + } + PMCreator &operator=(PMCreator other) JL_NOTSAFEPOINT { + swap(*this, other); + return *this; + } + auto operator()() JL_NOTSAFEPOINT { + auto PM = std::make_unique(); + addTargetPasses(PM.get(), TM->getTargetTriple(), TM->getTargetIRAnalysis()); + addOptimizationPasses(PM.get(), optlevel); + addMachinePasses(PM.get(), optlevel); + return PM; + } + }; +#else + struct PMCreator { + orc::JITTargetMachineBuilder JTMB; + OptimizationLevel O; + std::vector> &printers; + PMCreator(TargetMachine &TM, int optlevel, std::vector> &printers) JL_NOTSAFEPOINT + : JTMB(createJTMBFromTM(TM, optlevel)), O(getOptLevel(optlevel)), printers(printers) {} + + auto operator()() JL_NOTSAFEPOINT { + auto NPM = std::make_unique(cantFail(JTMB.createTargetMachine()), O); + printers.push_back([NPM = NPM.get()]() JL_NOTSAFEPOINT { + NPM->printTimers(); + }); + return NPM; + } + }; +#endif + + struct OptimizerT { + OptimizerT(TargetMachine &TM, int optlevel, std::vector> &printers) JL_NOTSAFEPOINT + : optlevel(optlevel), PMs(PMCreator(TM, optlevel, printers)) {} + OptimizerT(OptimizerT&) JL_NOTSAFEPOINT = delete; + OptimizerT(OptimizerT&&) JL_NOTSAFEPOINT = default; + + OptimizerResultT operator()(orc::ThreadSafeModule TSM, orc::MaterializationResponsibility &R) JL_NOTSAFEPOINT { + TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { + uint64_t start_time = 0; + std::stringstream before_stats_ss; + bool should_dump_opt_stats = false; + { + auto stream = *jl_ExecutionEngine->get_dump_llvm_opt_stream(); + if (stream) { + // Ensures that we don't _just_ write the second part of the YAML object + should_dump_opt_stats = true; + // We use a stringstream to later atomically write a YAML object + // without the need to hold the stream lock over the optimization + // Print LLVM function statistics _before_ optimization + // Print all the information about this invocation as a YAML object + before_stats_ss << "- \n"; + // We print the name and some statistics for each function in the module, both + // before optimization and again afterwards. + before_stats_ss << " before: \n"; + for (auto &F : M.functions()) { + if (F.isDeclaration() || F.getName().startswith("jfptr_")) { + continue; + } + // Each function is printed as a YAML object with several attributes + before_stats_ss << " \"" << F.getName().str().c_str() << "\":\n"; + before_stats_ss << " instructions: " << F.getInstructionCount() << "\n"; + before_stats_ss << " basicblocks: " << countBasicBlocks(F) << "\n"; + } + + start_time = jl_hrtime(); + } + } + + JL_TIMING(LLVM_OPT, LLVM_OPT); + + //Run the optimization + assert(!verifyModule(M, &errs())); + (***PMs).run(M); + assert(!verifyModule(M, &errs())); + + uint64_t end_time = 0; + { + auto stream = *jl_ExecutionEngine->get_dump_llvm_opt_stream(); + if (stream && should_dump_opt_stats) { + ios_printf(stream, "%s", before_stats_ss.str().c_str()); + end_time = jl_hrtime(); + ios_printf(stream, " time_ns: %" PRIu64 "\n", end_time - start_time); + ios_printf(stream, " optlevel: %d\n", optlevel); + + // Print LLVM function statistics _after_ optimization + ios_printf(stream, " after: \n"); + for (auto &F : M.functions()) { + if (F.isDeclaration() || F.getName().startswith("jfptr_")) { + continue; + } + ios_printf(stream, " \"%s\":\n", F.getName().str().c_str()); + ios_printf(stream, " instructions: %u\n", F.getInstructionCount()); + ios_printf(stream, " basicblocks: %zd\n", countBasicBlocks(F)); + } + } + } + }); + switch (optlevel) { + case 0: + ++OptO0; + break; + case 1: + ++OptO1; + break; + case 2: + ++OptO2; + break; + case 3: + ++OptO3; + break; + default: + llvm_unreachable("optlevel is between 0 and 3!"); + } + return Expected{std::move(TSM)}; + } + private: + int optlevel; + JuliaOJIT::ResourcePool> PMs; + }; + + struct CompilerT : orc::IRCompileLayer::IRCompiler { + + CompilerT(orc::IRSymbolMapper::ManglingOptions MO, TargetMachine &TM, int optlevel) JL_NOTSAFEPOINT + : orc::IRCompileLayer::IRCompiler(MO), TMs(TMCreator(TM, optlevel)) {} + + Expected> operator()(Module &M) override { + return orc::SimpleCompiler(***TMs)(M); + } + + JuliaOJIT::ResourcePool> TMs; + }; +} + +llvm::DataLayout jl_create_datalayout(TargetMachine &TM) { + // Mark our address spaces as non-integral + auto jl_data_layout = TM.createDataLayout(); + jl_data_layout.reset(jl_data_layout.getStringRepresentation() + "-ni:10:11:12:13"); + return jl_data_layout; +} + +JuliaOJIT::PipelineT::PipelineT(orc::ObjectLayer &BaseLayer, TargetMachine &TM, int optlevel, std::vector> &PrintLLVMTimers) + : CompileLayer(BaseLayer.getExecutionSession(), BaseLayer, + std::make_unique(orc::irManglingOptionsFromTargetOptions(TM.Options), TM, optlevel)), + OptimizeLayer(CompileLayer.getExecutionSession(), CompileLayer, + llvm::orc::IRTransformLayer::TransformFunction(OptimizerT(TM, optlevel, PrintLLVMTimers))) {} + +#ifdef _COMPILER_ASAN_ENABLED_ +int64_t ___asan_globals_registered; #endif + +JuliaOJIT::JuliaOJIT() + : TM(createTargetMachine()), + DL(jl_create_datalayout(*TM)), + ES(cantFail(orc::SelfExecutorProcessControl::Create())), GlobalJD(ES.createBareJITDylib("JuliaGlobals")), JD(ES.createBareJITDylib("JuliaOJIT")), + ContextPool([](){ + auto ctx = std::make_unique(); + return orc::ThreadSafeContext(std::move(ctx)); + }), #ifdef JL_USE_JITLINK - // TODO: Port our memory management optimisations to JITLink instead of using the - // default InProcessMemoryManager. -# if JL_LLVM_VERSION < 140000 - ObjectLayer(ES, std::make_unique()), -# else - ObjectLayer(ES, cantFail(jitlink::InProcessMemoryManager::Create())), -# endif + MemMgr(createJITLinkMemoryManager()), + ObjectLayer(ES, *MemMgr), #else MemMgr(createRTDyldMemoryManager()), ObjectLayer( @@ -832,10 +1289,17 @@ JuliaOJIT::JuliaOJIT(TargetMachine &TM, LLVMContext *LLVMCtx) } ), #endif - CompileLayer(ES, ObjectLayer, std::make_unique(this)) + LockLayer(ObjectLayer), + Pipelines{ + std::make_unique(LockLayer, *TM, 0, PrintLLVMTimers), + std::make_unique(LockLayer, *TM, 1, PrintLLVMTimers), + std::make_unique(LockLayer, *TM, 2, PrintLLVMTimers), + std::make_unique(LockLayer, *TM, 3, PrintLLVMTimers), + }, + OptSelLayer(Pipelines) { #ifdef JL_USE_JITLINK -# if defined(_OS_DARWIN_) && defined(LLVM_SHLIB) +# if defined(LLVM_SHLIB) // When dynamically linking against LLVM, use our custom EH frame registration code // also used with RTDyld to inform both our and the libc copy of libunwind. auto ehRegistrar = std::make_unique(); @@ -846,6 +1310,7 @@ JuliaOJIT::JuliaOJIT(TargetMachine &TM, LLVMContext *LLVMCtx) ES, std::move(ehRegistrar))); ObjectLayer.addPlugin(std::make_unique()); + ObjectLayer.addPlugin(std::make_unique(total_size)); #else ObjectLayer.setNotifyLoaded( [this](orc::MaterializationResponsibility &MR, @@ -854,37 +1319,40 @@ JuliaOJIT::JuliaOJIT(TargetMachine &TM, LLVMContext *LLVMCtx) registerRTDyldJITObject(Object, LO, MemMgr); }); #endif - for (int i = 0; i < 4; i++) { - TMs[i] = TM.getTarget().createTargetMachine(TM.getTargetTriple().getTriple(), TM.getTargetCPU(), - TM.getTargetFeatureString(), TM.Options, Reloc::Static, TM.getCodeModel(), - CodeGenOptLevelFor(i), true); - } - addPassesForOptLevel(PM0, *TMs[0], ObjStream, Ctx, 0); - addPassesForOptLevel(PM1, *TMs[1], ObjStream, Ctx, 1); - addPassesForOptLevel(PM2, *TMs[2], ObjStream, Ctx, 2); - addPassesForOptLevel(PM3, *TMs[3], ObjStream, Ctx, 3); + + std::string ErrorStr; + + // Make sure that libjulia-internal is loaded and placed first in the + // DynamicLibrary order so that calls to runtime intrinsics are resolved + // to the correct library when multiple libjulia-*'s have been loaded + // (e.g. when we `ccall` into a PackageCompiler.jl-created shared library) + sys::DynamicLibrary libjulia_internal_dylib = sys::DynamicLibrary::addPermanentLibrary( + jl_libjulia_internal_handle, &ErrorStr); + if(!ErrorStr.empty()) + report_fatal_error(llvm::Twine("FATAL: unable to dlopen libjulia-internal\n") + ErrorStr); // Make sure SectionMemoryManager::getSymbolAddressInProcess can resolve // symbols in the program as well. The nullptr argument to the function // tells DynamicLibrary to load the program, not a library. - std::string ErrorStr; if (sys::DynamicLibrary::LoadLibraryPermanently(nullptr, &ErrorStr)) report_fatal_error(llvm::Twine("FATAL: unable to dlopen self\n") + ErrorStr); + GlobalJD.addGenerator( + std::make_unique( + libjulia_internal_dylib, + DL.getGlobalPrefix(), + orc::DynamicLibrarySearchGenerator::SymbolPredicate())); + GlobalJD.addGenerator( cantFail(orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( DL.getGlobalPrefix()))); // Resolve non-lock free atomic functions in the libatomic1 library. // This is the library that provides support for c11/c++11 atomic operations. - const char *const libatomic = -#if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) - "libatomic.so.1"; -#elif defined(_OS_WINDOWS_) - "libatomic-1.dll"; -#else - NULL; -#endif + auto TT = getTargetTriple(); + const char *const libatomic = TT.isOSLinux() || TT.isOSFreeBSD() ? + "libatomic.so.1" : TT.isOSWindows() ? + "libatomic-1.dll" : nullptr; if (libatomic) { static void *atomic_hdl = jl_load_dynamic_library(libatomic, JL_RTLD_LOCAL, 0); if (atomic_hdl != NULL) { @@ -900,56 +1368,109 @@ JuliaOJIT::JuliaOJIT(TargetMachine &TM, LLVMContext *LLVMCtx) } JD.addToLinkOrder(GlobalJD, orc::JITDylibLookupFlags::MatchExportedSymbolsOnly); + +#if JULIA_FLOAT16_ABI == 1 + orc::SymbolAliasMap jl_crt = { + { 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 } } + }; + cantFail(GlobalJD.define(orc::symbolAliases(jl_crt))); +#endif + +#ifdef MSAN_EMUTLS_WORKAROUND + orc::SymbolMap msan_crt; + msan_crt[mangle("__emutls_get_address")] = JITEvaluatedSymbol::fromPointer(msan_workaround::getTLSAddress, JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_param_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::param)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_param_origin_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::param_origin)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_retval_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::retval)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_retval_origin_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::retval_origin)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_va_arg_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::va_arg)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_va_arg_origin_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::va_arg_origin)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_va_arg_overflow_size_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::va_arg_overflow_size)), JITSymbolFlags::Exported); + msan_crt[mangle("__emutls_v.__msan_origin_tls")] = JITEvaluatedSymbol::fromPointer( + reinterpret_cast(static_cast(msan_workaround::MSanTLS::origin)), JITSymbolFlags::Exported); + cantFail(GlobalJD.define(orc::absoluteSymbols(msan_crt))); +#endif +#ifdef _COMPILER_ASAN_ENABLED_ + orc::SymbolMap asan_crt; + asan_crt[mangle("___asan_globals_registered")] = JITEvaluatedSymbol::fromPointer(&___asan_globals_registered, JITSymbolFlags::Exported); + cantFail(JD.define(orc::absoluteSymbols(asan_crt))); +#endif } -void JuliaOJIT::addGlobalMapping(StringRef Name, uint64_t Addr) +JuliaOJIT::~JuliaOJIT() = default; + +orc::SymbolStringPtr JuliaOJIT::mangle(StringRef Name) { std::string MangleName = getMangledName(Name); - cantFail(JD.define(orc::absoluteSymbols({{ES.intern(MangleName), JITEvaluatedSymbol::fromPointer((void*)Addr)}}))); + return ES.intern(MangleName); } -void JuliaOJIT::addModule(std::unique_ptr M) +void JuliaOJIT::addGlobalMapping(StringRef Name, uint64_t Addr) { - JL_TIMING(LLVM_MODULE_FINISH); - std::vector NewExports; - for (auto &F : M->global_values()) { - if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { - NewExports.push_back(getMangledName(F.getName())); + cantFail(JD.define(orc::absoluteSymbols({{mangle(Name), JITEvaluatedSymbol::fromPointer((void*)Addr)}}))); +} + +void JuliaOJIT::addModule(orc::ThreadSafeModule TSM) +{ + JL_TIMING(LLVM_ORC, LLVM_ORC); + ++ModulesAdded; + orc::SymbolLookupSet NewExports; + TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { + jl_decorate_module(M); + shareStrings(M); + for (auto &F : M.global_values()) { + if (!F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { + auto Name = ES.intern(getMangledName(F.getName())); + NewExports.add(std::move(Name)); + } } - } #if !defined(JL_NDEBUG) && !defined(JL_USE_JITLINK) - // validate the relocations for M (not implemented for the JITLink memory manager yet) - for (Module::global_object_iterator I = M->global_objects().begin(), E = M->global_objects().end(); I != E; ) { - GlobalObject *F = &*I; - ++I; - if (F->isDeclaration()) { - if (F->use_empty()) - F->eraseFromParent(); - else if (!((isa(F) && isIntrinsicFunction(cast(F))) || - findUnmangledSymbol(F->getName()) || - SectionMemoryManager::getSymbolAddressInProcess( - getMangledName(F->getName())))) { - llvm::errs() << "FATAL ERROR: " - << "Symbol \"" << F->getName().str() << "\"" - << "not found"; - abort(); + // validate the relocations for M (not implemented for the JITLink memory manager yet) + for (Module::global_object_iterator I = M.global_objects().begin(), E = M.global_objects().end(); I != E; ) { + GlobalObject *F = &*I; + ++I; + if (F->isDeclaration()) { + if (F->use_empty()) + F->eraseFromParent(); + else if (!((isa(F) && isIntrinsicFunction(cast(F))) || + findUnmangledSymbol(F->getName()) || + SectionMemoryManager::getSymbolAddressInProcess( + getMangledName(F->getName())))) { + llvm::errs() << "FATAL ERROR: " + << "Symbol \"" << F->getName().str() << "\"" + << "not found"; + abort(); + } } } - } #endif + }); + // TODO: what is the performance characteristics of this? - cantFail(CompileLayer.add(JD, orc::ThreadSafeModule(std::move(M), TSCtx))); + cantFail(OptSelLayer.add(JD, std::move(TSM))); // force eager compilation (for now), due to memory management specifics // (can't handle compilation recursion) - for (auto Name : NewExports) - cantFail(ES.lookup({&JD}, Name)); - + for (auto &sym : cantFail(ES.lookup({{&JD, orc::JITDylibLookupFlags::MatchExportedSymbolsOnly}}, NewExports))) { + assert(sym.second); + (void) sym; + } } JL_JITSymbol JuliaOJIT::findSymbol(StringRef Name, bool ExportedSymbolsOnly) { - orc::JITDylib* SearchOrders[2] = {&GlobalJD, &JD}; - ArrayRef SearchOrder = makeArrayRef(&SearchOrders[ExportedSymbolsOnly ? 0 : 1], ExportedSymbolsOnly ? 2 : 1); + orc::JITDylib* SearchOrders[2] = {&JD, &GlobalJD}; + ArrayRef SearchOrder = makeArrayRef(&SearchOrders[0], ExportedSymbolsOnly ? 2 : 1); auto Sym = ES.lookup(SearchOrder, Name); if (Sym) return *Sym; @@ -981,9 +1502,9 @@ uint64_t JuliaOJIT::getFunctionAddress(StringRef Name) return cantFail(addr.getAddress()); } -static int globalUniqueGeneratedNames; StringRef JuliaOJIT::getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *codeinst) { + std::lock_guard lock(RLST_mutex); std::string *fname = &ReverseLocalSymbolTable[(void*)(uintptr_t)Addr]; if (fname->empty()) { std::string string_fname; @@ -1003,7 +1524,7 @@ StringRef JuliaOJIT::getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *cod stream_fname << "jlsys_"; } const char* unadorned_name = jl_symbol_name(codeinst->def->def.method->name); - stream_fname << unadorned_name << "_" << globalUniqueGeneratedNames++; + stream_fname << unadorned_name << "_" << RLST_inc++; *fname = std::move(stream_fname.str()); // store to ReverseLocalSymbolTable addGlobalMapping(*fname, Addr); } @@ -1012,24 +1533,21 @@ StringRef JuliaOJIT::getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *cod #ifdef JL_USE_JITLINK -# if JL_LLVM_VERSION < 140000 -# warning "JIT debugging (GDB integration) not available on LLVM < 14.0 (for JITLink)" -void JuliaOJIT::enableJITDebuggingSupport() {} -# else extern "C" orc::shared::CWrapperFunctionResult llvm_orc_registerJITLoaderGDBAllocAction(const char *Data, size_t Size); void JuliaOJIT::enableJITDebuggingSupport() { - // We do not use GDBJITDebugInfoRegistrationPlugin::Create, as the runtime name - // lookup is unnecessarily involved/fragile for our in-process JIT use case - // (with the llvm_orc_registerJITLoaderGDBAllocAction symbol being in either - // libjulia-codegen or yet another shared library for LLVM depending on the build - // flags, etc.). - const auto Addr = ExecutorAddr::fromPtr(&llvm_orc_registerJITLoaderGDBAllocAction); - ObjectLayer.addPlugin(std::make_unique(Addr)); + orc::SymbolMap GDBFunctions; + GDBFunctions[mangle("llvm_orc_registerJITLoaderGDBAllocAction")] = JITEvaluatedSymbol::fromPointer(&llvm_orc_registerJITLoaderGDBAllocAction, JITSymbolFlags::Exported | JITSymbolFlags::Callable); + GDBFunctions[mangle("llvm_orc_registerJITLoaderGDBWrapper")] = JITEvaluatedSymbol::fromPointer(&llvm_orc_registerJITLoaderGDBWrapper, JITSymbolFlags::Exported | JITSymbolFlags::Callable); + cantFail(JD.define(orc::absoluteSymbols(GDBFunctions))); + if (TM->getTargetTriple().isOSBinFormatMachO()) + ObjectLayer.addPlugin(cantFail(orc::GDBJITDebugInfoRegistrationPlugin::Create(ES, JD, TM->getTargetTriple()))); + 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() { @@ -1049,11 +1567,6 @@ const DataLayout& JuliaOJIT::getDataLayout() const return DL; } -const Triple& JuliaOJIT::getTargetTriple() const -{ - return TM.getTargetTriple(); -} - std::string JuliaOJIT::getMangledName(StringRef Name) { SmallString<128> FullName; @@ -1069,11 +1582,10 @@ std::string JuliaOJIT::getMangledName(const GlobalValue *GV) #ifdef JL_USE_JITLINK size_t JuliaOJIT::getTotalBytes() const { - // TODO: Implement in future custom JITLink memory manager. - return 0; + return total_size.load(std::memory_order_relaxed); } #else -size_t getRTDyldMemoryManagerTotalBytes(RTDyldMemoryManager *mm); +size_t getRTDyldMemoryManagerTotalBytes(RTDyldMemoryManager *mm) JL_NOTSAFEPOINT; size_t JuliaOJIT::getTotalBytes() const { @@ -1081,6 +1593,16 @@ size_t JuliaOJIT::getTotalBytes() const } #endif +void JuliaOJIT::printTimers() +{ +#ifdef JL_USE_NEW_PM + for (auto &printer : PrintLLVMTimers) { + printer(); + } +#endif + reportAndResetTimings(); +} + JuliaOJIT *jl_ExecutionEngine; // destructively move the contents of src into dest @@ -1088,113 +1610,123 @@ JuliaOJIT *jl_ExecutionEngine; // including the DataLayout and ModuleFlags (for example) // and that there is no module-level assembly // Comdat is also removed, since the JIT doesn't need it -void jl_merge_module(Module *dest, std::unique_ptr src) +void jl_merge_module(orc::ThreadSafeModule &destTSM, orc::ThreadSafeModule srcTSM) { - assert(dest != src.get()); - for (Module::global_iterator I = src->global_begin(), E = src->global_end(); I != E;) { - GlobalVariable *sG = &*I; - GlobalVariable *dG = cast_or_null(dest->getNamedValue(sG->getName())); - ++I; - // Replace a declaration with the definition: - if (dG) { - if (sG->isDeclaration()) { - sG->replaceAllUsesWith(dG); - sG->eraseFromParent(); - continue; - } - //// If we start using llvm.used, we need to enable and test this - //else if (!dG->isDeclaration() && dG->hasAppendingLinkage() && sG->hasAppendingLinkage()) { - // auto *dCA = cast(dG->getInitializer()); - // auto *sCA = cast(sG->getInitializer()); - // SmallVector Init; - // for (auto &Op : dCA->operands()) - // Init.push_back(cast_or_null(Op)); - // for (auto &Op : sCA->operands()) - // Init.push_back(cast_or_null(Op)); - // Type *Int8PtrTy = Type::getInt8PtrTy(dest.getContext()); - // ArrayType *ATy = ArrayType::get(Int8PtrTy, Init.size()); - // GlobalVariable *GV = new GlobalVariable(dest, ATy, dG->isConstant(), - // GlobalValue::AppendingLinkage, ConstantArray::get(ATy, Init), "", - // dG->getThreadLocalMode(), dG->getType()->getAddressSpace()); - // GV->copyAttributesFrom(dG); - // sG->replaceAllUsesWith(GV); - // dG->replaceAllUsesWith(GV); - // GV->takeName(sG); - // sG->eraseFromParent(); - // dG->eraseFromParent(); - // continue; - //} - else { - assert(dG->isDeclaration() || dG->getInitializer() == sG->getInitializer()); - dG->replaceAllUsesWith(sG); - dG->eraseFromParent(); + ++ModulesMerged; + destTSM.withModuleDo([&](Module &dest) JL_NOTSAFEPOINT { + srcTSM.withModuleDo([&](Module &src) JL_NOTSAFEPOINT { + assert(&dest != &src && "Cannot merge module with itself!"); + assert(&dest.getContext() == &src.getContext() && "Cannot merge modules with different contexts!"); + assert(dest.getDataLayout() == src.getDataLayout() && "Cannot merge modules with different data layouts!"); + assert(dest.getTargetTriple() == src.getTargetTriple() && "Cannot merge modules with different target triples!"); + + for (Module::global_iterator I = src.global_begin(), E = src.global_end(); I != E;) { + GlobalVariable *sG = &*I; + GlobalVariable *dG = cast_or_null(dest.getNamedValue(sG->getName())); + ++I; + // Replace a declaration with the definition: + if (dG) { + if (sG->isDeclaration()) { + sG->replaceAllUsesWith(dG); + sG->eraseFromParent(); + continue; + } + //// If we start using llvm.used, we need to enable and test this + //else if (!dG->isDeclaration() && dG->hasAppendingLinkage() && sG->hasAppendingLinkage()) { + // auto *dCA = cast(dG->getInitializer()); + // auto *sCA = cast(sG->getInitializer()); + // SmallVector Init; + // for (auto &Op : dCA->operands()) + // Init.push_back(cast_or_null(Op)); + // for (auto &Op : sCA->operands()) + // Init.push_back(cast_or_null(Op)); + // Type *Int8PtrTy = Type::getInt8PtrTy(dest.getContext()); + // ArrayType *ATy = ArrayType::get(Int8PtrTy, Init.size()); + // GlobalVariable *GV = new GlobalVariable(dest, ATy, dG->isConstant(), + // GlobalValue::AppendingLinkage, ConstantArray::get(ATy, Init), "", + // dG->getThreadLocalMode(), dG->getType()->getAddressSpace()); + // GV->copyAttributesFrom(dG); + // sG->replaceAllUsesWith(GV); + // dG->replaceAllUsesWith(GV); + // GV->takeName(sG); + // sG->eraseFromParent(); + // dG->eraseFromParent(); + // continue; + //} + else { + assert(dG->isDeclaration() || dG->getInitializer() == sG->getInitializer()); + dG->replaceAllUsesWith(sG); + dG->eraseFromParent(); + } + } + // Reparent the global variable: + sG->removeFromParent(); + dest.getGlobalList().push_back(sG); + // Comdat is owned by the Module + sG->setComdat(nullptr); } - } - // Reparent the global variable: - sG->removeFromParent(); - dest->getGlobalList().push_back(sG); - // Comdat is owned by the Module - sG->setComdat(nullptr); - } - for (Module::iterator I = src->begin(), E = src->end(); I != E;) { - Function *sG = &*I; - Function *dG = cast_or_null(dest->getNamedValue(sG->getName())); - ++I; - // Replace a declaration with the definition: - if (dG) { - if (sG->isDeclaration()) { - sG->replaceAllUsesWith(dG); - sG->eraseFromParent(); - continue; - } - else { - assert(dG->isDeclaration()); - dG->replaceAllUsesWith(sG); - dG->eraseFromParent(); + for (Module::iterator I = src.begin(), E = src.end(); I != E;) { + Function *sG = &*I; + Function *dG = cast_or_null(dest.getNamedValue(sG->getName())); + ++I; + // Replace a declaration with the definition: + if (dG) { + if (sG->isDeclaration()) { + sG->replaceAllUsesWith(dG); + sG->eraseFromParent(); + continue; + } + else { + assert(dG->isDeclaration()); + dG->replaceAllUsesWith(sG); + dG->eraseFromParent(); + } + } + // Reparent the global variable: + sG->removeFromParent(); + dest.getFunctionList().push_back(sG); + // Comdat is owned by the Module + sG->setComdat(nullptr); } - } - // Reparent the global variable: - sG->removeFromParent(); - dest->getFunctionList().push_back(sG); - // Comdat is owned by the Module - sG->setComdat(nullptr); - } - for (Module::alias_iterator I = src->alias_begin(), E = src->alias_end(); I != E;) { - GlobalAlias *sG = &*I; - GlobalAlias *dG = cast_or_null(dest->getNamedValue(sG->getName())); - ++I; - if (dG) { - if (!dG->isDeclaration()) { // aliases are always definitions, so this test is reversed from the above two - sG->replaceAllUsesWith(dG); - sG->eraseFromParent(); - continue; - } - else { - dG->replaceAllUsesWith(sG); - dG->eraseFromParent(); + for (Module::alias_iterator I = src.alias_begin(), E = src.alias_end(); I != E;) { + GlobalAlias *sG = &*I; + GlobalAlias *dG = cast_or_null(dest.getNamedValue(sG->getName())); + ++I; + if (dG) { + if (!dG->isDeclaration()) { // aliases are always definitions, so this test is reversed from the above two + sG->replaceAllUsesWith(dG); + sG->eraseFromParent(); + continue; + } + else { + dG->replaceAllUsesWith(sG); + dG->eraseFromParent(); + } + } + sG->removeFromParent(); + dest.getAliasList().push_back(sG); } - } - sG->removeFromParent(); - dest->getAliasList().push_back(sG); - } - // metadata nodes need to be explicitly merged not just copied - // so there are special passes here for each known type of metadata - NamedMDNode *sNMD = src->getNamedMetadata("llvm.dbg.cu"); - if (sNMD) { - NamedMDNode *dNMD = dest->getOrInsertNamedMetadata("llvm.dbg.cu"); - for (NamedMDNode::op_iterator I = sNMD->op_begin(), E = sNMD->op_end(); I != E; ++I) { - dNMD->addOperand(*I); - } - } + // metadata nodes need to be explicitly merged not just copied + // so there are special passes here for each known type of metadata + NamedMDNode *sNMD = src.getNamedMetadata("llvm.dbg.cu"); + if (sNMD) { + NamedMDNode *dNMD = dest.getOrInsertNamedMetadata("llvm.dbg.cu"); + for (MDNode *I : sNMD->operands()) { + dNMD->addOperand(I); + } + } + }); + }); } // optimize memory by turning long strings into memoized copies, instead of // making a copy per object file of output. -void jl_jit_share_data(Module &M) +void JuliaOJIT::shareStrings(Module &M) { + ++InternedGlobals; std::vector erase; for (auto &GV : M.globals()) { if (!GV.hasInitializer() || !GV.isConstant()) @@ -1206,7 +1738,7 @@ void jl_jit_share_data(Module &M) if (data.size() > 16) { // only for long strings: keep short ones as values Type *T_size = Type::getIntNTy(GV.getContext(), sizeof(void*) * 8); Constant *v = ConstantExpr::getIntToPtr( - ConstantInt::get(T_size, (uintptr_t)data.data()), + ConstantInt::get(T_size, (uintptr_t)(*ES.intern(data)).data()), GV.getType()); GV.replaceAllUsesWith(v); erase.push_back(&GV); @@ -1216,96 +1748,128 @@ void jl_jit_share_data(Module &M) GV->eraseFromParent(); } -static void jl_add_to_ee(std::unique_ptr m) +//TargetMachine pass-through methods + +std::unique_ptr JuliaOJIT::cloneTargetMachine() const { -#if defined(_CPU_X86_64_) && defined(_OS_WINDOWS_) - // Add special values used by debuginfo to build the UnwindData table registration for Win64 - Type *T_uint32 = Type::getInt32Ty(m->getContext()); - ArrayType *atype = ArrayType::get(T_uint32, 3); // want 4-byte alignment of 12-bytes of data - GlobalVariable *gvs[2] = { - new GlobalVariable(*m, atype, - false, GlobalVariable::InternalLinkage, - ConstantAggregateZero::get(atype), "__UnwindData"), - new GlobalVariable(*m, atype, - false, GlobalVariable::InternalLinkage, - ConstantAggregateZero::get(atype), "__catchjmp") }; - gvs[0]->setSection(".text"); - gvs[1]->setSection(".text"); - appendToCompilerUsed(*m, makeArrayRef((GlobalValue**)gvs, 2)); -#endif - jl_jit_share_data(*m); - assert(jl_ExecutionEngine); - jl_ExecutionEngine->addModule(std::move(m)); + return std::unique_ptr(getTarget() + .createTargetMachine( + getTargetTriple().str(), + getTargetCPU(), + getTargetFeatureString(), + getTargetOptions(), + TM->getRelocationModel(), + TM->getCodeModel(), + TM->getOptLevel())); +} + +const Triple& JuliaOJIT::getTargetTriple() const { + return TM->getTargetTriple(); +} +StringRef JuliaOJIT::getTargetFeatureString() const { + return TM->getTargetFeatureString(); +} +StringRef JuliaOJIT::getTargetCPU() const { + return TM->getTargetCPU(); +} +const TargetOptions &JuliaOJIT::getTargetOptions() const { + return TM->Options; +} +const Target &JuliaOJIT::getTarget() const { + return TM->getTarget(); +} +TargetIRAnalysis JuliaOJIT::getTargetIRAnalysis() const { + return TM->getTargetIRAnalysis(); +} + +static void jl_decorate_module(Module &M) { + auto TT = Triple(M.getTargetTriple()); + if (TT.isOSWindows() && TT.getArch() == Triple::x86_64) { + // Add special values used by debuginfo to build the UnwindData table registration for Win64 + // This used to be GV, but with https://reviews.llvm.org/D100944 we no longer can emit GV into `.text` + // TODO: The data is set in debuginfo.cpp but it should be okay to actually emit it here. + M.appendModuleInlineAsm("\ + .section .text \n\ + .type __UnwindData,@object \n\ + .p2align 2, 0x90 \n\ + __UnwindData: \n\ + .zero 12 \n\ + .size __UnwindData, 12 \n\ + \n\ + .type __catchjmp,@object \n\ + .p2align 2, 0x90 \n\ + __catchjmp: \n\ + .zero 12 \n\ + .size __catchjmp, 12"); + } } +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable static int jl_add_to_ee( - std::unique_ptr &M, - StringMap*> &NewExports, - DenseMap &Queued, - std::vector*>> &ToMerge, - int depth) + orc::ThreadSafeModule &M, + const StringMap &NewExports, + DenseMap &Queued, + std::vector &Stack) { - // DAG-sort (post-dominator) the compile to compute the minimum - // merge-module sets for linkage + // First check if the TSM is empty (already compiled) if (!M) return 0; - // First check and record if it's on the stack somewhere + // Next check and record if it is on the stack somewhere { - auto &Cycle = Queued[M.get()]; - if (Cycle) - return Cycle; - ToMerge.push_back({}); - Cycle = depth; + auto &Id = Queued[&M]; + if (Id) + return Id; + Stack.push_back(&M); + Id = Stack.size(); } + // Finally work out the SCC + int depth = Stack.size(); int MergeUp = depth; - // Compute the cycle-id - for (auto &F : M->global_objects()) { - if (F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { - auto Callee = NewExports.find(F.getName()); - if (Callee != NewExports.end()) { - auto &CM = Callee->second; - int Down = jl_add_to_ee(*CM, NewExports, Queued, ToMerge, depth + 1); - assert(Down <= depth); - if (Down && Down < MergeUp) - MergeUp = Down; + std::vector Children; + M.withModuleDo([&](Module &m) JL_NOTSAFEPOINT { + for (auto &F : m.global_objects()) { + if (F.isDeclaration() && F.getLinkage() == GlobalValue::ExternalLinkage) { + auto Callee = NewExports.find(F.getName()); + if (Callee != NewExports.end()) { + auto *CM = Callee->second; + if (*CM && CM != &M) { + auto Down = Queued.find(CM); + if (Down != Queued.end()) + MergeUp = std::min(MergeUp, Down->second); + else + Children.push_back(CM); + } + } } } + }); + assert(MergeUp > 0); + for (auto *CM : Children) { + int Down = jl_add_to_ee(*CM, NewExports, Queued, Stack); + assert(Down <= (int)Stack.size()); + if (Down) + MergeUp = std::min(MergeUp, Down); } - if (MergeUp == depth) { + if (MergeUp < depth) + return MergeUp; + while (1) { // Not in a cycle (or at the top of it) - Queued.erase(M.get()); - for (auto &CM : ToMerge.at(depth - 1)) { - assert(Queued.find(CM->get())->second == depth); - Queued.erase(CM->get()); - jl_merge_module(M.get(), std::move(*CM)); + // remove SCC state and merge every CM from the cycle into M + orc::ThreadSafeModule *CM = Stack.back(); + auto it = Queued.find(CM); + assert(it->second == (int)Stack.size()); + Queued.erase(it); + Stack.pop_back(); + if ((int)Stack.size() < depth) { + assert(&M == CM); + break; } - jl_add_to_ee(std::move(M)); - MergeUp = 0; + jl_merge_module(M, std::move(*CM)); } - else { - // Add our frame(s) to the top of the cycle - Queued[M.get()] = MergeUp; - auto &Top = ToMerge.at(MergeUp - 1); - Top.push_back(&M); - for (auto &CM : ToMerge.at(depth - 1)) { - assert(Queued.find(CM->get())->second == depth); - Queued[CM->get()] = MergeUp; - Top.push_back(CM); - } - } - ToMerge.pop_back(); - return MergeUp; -} - -static void jl_add_to_ee(std::unique_ptr &M, StringMap*> &NewExports) -{ - DenseMap Queued; - std::vector*>> ToMerge; - jl_add_to_ee(M, NewExports, Queued, ToMerge, 1); - assert(!M); + jl_ExecutionEngine->addModule(std::move(M)); + return 0; } - static uint64_t getAddressForFunction(StringRef fname) { auto addr = jl_ExecutionEngine->getFunctionAddress(fname); @@ -1319,7 +1883,7 @@ void add_named_global(StringRef name, void *addr) jl_ExecutionEngine->addGlobalMapping(name, (uint64_t)(uintptr_t)addr); } -extern "C" JL_DLLEXPORT +extern "C" JL_DLLEXPORT_CODEGEN size_t jl_jit_total_bytes_impl(void) { return jl_ExecutionEngine->getTotalBytes(); diff --git a/src/jitlayers.h b/src/jitlayers.h index ba3f81fa66997..c056a6b3418a3 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -1,16 +1,30 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license +#include + #include #include #include #include -#include "llvm/IR/LegacyPassManager.h" +#include +#include #include +#include #include +#include +#include +#include + #include #include "julia_assert.h" +#include "julia.h" +#include "julia_internal.h" +#include "platform.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 @@ -28,10 +42,14 @@ // and feature support (e.g. Windows, JITEventListeners for various profilers, // etc.). Thus, we currently only use JITLink where absolutely required, that is, // for Mac/aarch64. -#if defined(_OS_DARWIN_) && defined(_CPU_AARCH64_) -# if JL_LLVM_VERSION < 130000 -# warning "On aarch64-darwin, LLVM version >= 13 is required for JITLink; fallback suffers from occasional segfaults" -# endif +// #define JL_FORCE_JITLINK + +#if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_MSAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_) +# define HAS_SANITIZER +#endif +// The sanitizers don't play well with our memory manager + +#if defined(_OS_DARWIN_) && defined(_CPU_AARCH64_) || defined(JL_FORCE_JITLINK) || JL_LLVM_VERSION >= 150000 && defined(HAS_SANITIZER) # define JL_USE_JITLINK #endif @@ -46,16 +64,108 @@ using namespace llvm; extern "C" jl_cgparams_t jl_default_cgparams; -extern TargetMachine *jl_TargetMachine; -extern bool imaging_mode; +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(orc::ThreadSafeContext, LLVMOrcThreadSafeContextRef) +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(orc::ThreadSafeModule, LLVMOrcThreadSafeModuleRef) + +void addTargetPasses(legacy::PassManagerBase *PM, const Triple &triple, TargetIRAnalysis analysis) JL_NOTSAFEPOINT; +void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, bool lower_intrinsics=true, bool dump_native=false, bool external_use=false) JL_NOTSAFEPOINT; +void addMachinePasses(legacy::PassManagerBase *PM, int optlevel) JL_NOTSAFEPOINT; +void jl_merge_module(orc::ThreadSafeModule &dest, orc::ThreadSafeModule src) JL_NOTSAFEPOINT; +GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M) JL_NOTSAFEPOINT; +DataLayout jl_create_datalayout(TargetMachine &TM) JL_NOTSAFEPOINT; + +static inline bool imaging_default() JL_NOTSAFEPOINT { + return jl_options.image_codegen || (jl_generating_output() && (!jl_options.incremental || jl_options.use_pkgimages)); +} + +struct OptimizationOptions { + bool lower_intrinsics; + bool dump_native; + bool external_use; + bool llvm_only; + + 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}; + } +}; + +// LLVM's new pass manager is scheduled to replace the legacy pass manager +// for middle-end IR optimizations. +#if JL_LLVM_VERSION >= 150000 +#define JL_USE_NEW_PM +#endif + +struct NewPM { + std::unique_ptr TM; + StandardInstrumentations SI; + std::unique_ptr PIC; + PassBuilder PB; + ModulePassManager MPM; + OptimizationLevel O; + + NewPM(std::unique_ptr TM, OptimizationLevel O, OptimizationOptions options = OptimizationOptions::defaults()) JL_NOTSAFEPOINT; + ~NewPM() JL_NOTSAFEPOINT; + + void run(Module &M) JL_NOTSAFEPOINT; + + void printTimers() JL_NOTSAFEPOINT; +}; + +struct AnalysisManagers { + LoopAnalysisManager LAM; + FunctionAnalysisManager FAM; + CGSCCAnalysisManager CGAM; + ModuleAnalysisManager MAM; + + AnalysisManagers(PassBuilder &PB) JL_NOTSAFEPOINT; + AnalysisManagers(TargetMachine &TM, PassBuilder &PB, OptimizationLevel O) JL_NOTSAFEPOINT; + ~AnalysisManagers() JL_NOTSAFEPOINT; +}; + +OptimizationLevel getOptLevel(int optlevel) JL_NOTSAFEPOINT; -void addTargetPasses(legacy::PassManagerBase *PM, TargetMachine *TM); -void addOptimizationPasses(legacy::PassManagerBase *PM, int opt_level, bool lower_intrinsics=true, bool dump_native=false); -void addMachinePasses(legacy::PassManagerBase *PM, TargetMachine *TM, int optlevel); -void jl_finalize_module(std::unique_ptr m); -void jl_merge_module(Module *dest, std::unique_ptr src); -Module *jl_create_llvm_module(StringRef name); -GlobalVariable *jl_emit_RTLD_DEFAULT_var(Module *M); +struct jl_locked_stream { + ios_t *stream = nullptr; + std::mutex mutex; + + struct lock { + std::unique_lock lck; + ios_t *&stream; + + lock(std::mutex &mutex, ios_t *&stream) JL_NOTSAFEPOINT + : lck(mutex), stream(stream) {} + lock(lock&) = delete; + lock(lock&&) JL_NOTSAFEPOINT = default; + ~lock() JL_NOTSAFEPOINT = default; + + ios_t *&operator*() JL_NOTSAFEPOINT { + return stream; + } + + explicit operator bool() JL_NOTSAFEPOINT { + return !!stream; + } + + operator ios_t *() JL_NOTSAFEPOINT { + return stream; + } + + operator JL_STREAM *() JL_NOTSAFEPOINT { + return (JL_STREAM*)stream; + } + }; + + jl_locked_stream() JL_NOTSAFEPOINT = default; + ~jl_locked_stream() JL_NOTSAFEPOINT = default; + + lock operator*() JL_NOTSAFEPOINT { + return lock(mutex, stream); + } +}; typedef struct _jl_llvm_functions_t { std::string functionObject; // jlcall llvm Function name @@ -63,7 +173,8 @@ typedef struct _jl_llvm_functions_t { } jl_llvm_functions_t; struct jl_returninfo_t { - llvm::Function *decl; + llvm::FunctionCallee decl; + llvm::AttributeList attrs; enum CallingConv { Boxed = 0, Register, @@ -77,26 +188,33 @@ struct jl_returninfo_t { unsigned return_roots; }; -typedef std::vector> jl_codegen_call_targets_t; -typedef std::tuple, jl_llvm_functions_t> jl_compile_result_t; +typedef std::tuple jl_codegen_call_target_t; typedef struct _jl_codegen_params_t { + orc::ThreadSafeContext tsctx; + orc::ThreadSafeContext::Lock tsctx_lock; + DataLayout DL; + Triple TargetTriple; + + inline LLVMContext &getContext() { + return *tsctx.getContext(); + } typedef StringMap SymMapGV; // outputs - jl_codegen_call_targets_t workqueue; + std::vector> workqueue; std::map globals; + std::map, GlobalVariable*> external_fns; std::map ditypes; std::map llvmtypes; DenseMap mergedConstants; // Map from symbol name (in a certain library) to its GV in sysimg and the // DL handle address in the current session. StringMap> libMapGV; -#ifdef _OS_WINDOWS_ + SymMapGV symMapDefault; + // These symMaps are Windows-only SymMapGV symMapExe; SymMapGV symMapDll; SymMapGV symMapDlli; -#endif - SymMapGV symMapDefault; // Map from distinct callee's to its GOT entry. // In principle the attribute, function type and calling convention // don't need to be part of the key but it seems impossible to forward @@ -106,25 +224,28 @@ typedef struct _jl_codegen_params_t { DenseMap, GlobalVariable*>> allPltMap; - Module *_shared_module = NULL; - Module *shared_module(LLVMContext &context) { - if (!_shared_module) - _shared_module = jl_create_llvm_module("globals"); - return _shared_module; - } + 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; + bool imaging; + _jl_codegen_params_t(orc::ThreadSafeContext ctx, DataLayout DL, Triple triple) + : tsctx(std::move(ctx)), tsctx_lock(tsctx.getLock()), + DL(std::move(DL)), TargetTriple(std::move(triple)), imaging(imaging_default()) {} } jl_codegen_params_t; -jl_compile_result_t jl_emit_code( +jl_llvm_functions_t jl_emit_code( + orc::ThreadSafeModule &M, jl_method_instance_t *mi, jl_code_info_t *src, jl_value_t *jlrettype, jl_codegen_params_t ¶ms); -jl_compile_result_t jl_emit_codeinst( +jl_llvm_functions_t jl_emit_codeinst( + orc::ThreadSafeModule &M, jl_code_instance_t *codeinst, jl_code_info_t *src, jl_codegen_params_t ¶ms); @@ -132,20 +253,22 @@ jl_compile_result_t jl_emit_codeinst( enum CompilationPolicy { Default = 0, Extern = 1, - ImagingMode = 2 }; +typedef std::map> jl_workqueue_t; + void jl_compile_workqueue( - std::map &emitted, + jl_workqueue_t &emitted, + Module &original, jl_codegen_params_t ¶ms, CompilationPolicy policy); Function *jl_cfunction_object(jl_function_t *f, jl_value_t *rt, jl_tupletype_t *argt, jl_codegen_params_t ¶ms); -void add_named_global(StringRef name, void *addr); +void add_named_global(StringRef name, void *addr) JL_NOTSAFEPOINT; -static inline Constant *literal_static_pointer_val(const void *p, Type *T) +static inline Constant *literal_static_pointer_val(const void *p, Type *T) JL_NOTSAFEPOINT { // this function will emit a static pointer into the generated code // the generated code will only be valid during the current session, @@ -157,14 +280,11 @@ static inline Constant *literal_static_pointer_val(const void *p, Type *T) #endif } -static const inline char *name_from_method_instance(jl_method_instance_t *li) +static const inline char *name_from_method_instance(jl_method_instance_t *li) JL_NOTSAFEPOINT { return jl_is_method(li->def.method) ? jl_symbol_name(li->def.method->name) : "top-level scope"; } - -void jl_init_jit(void); - 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 @@ -172,98 +292,300 @@ typedef JITSymbol JL_JITSymbol; typedef JITSymbol JL_SymbolInfo; using CompilerResultT = Expected>; +using OptimizerResultT = Expected; class JuliaOJIT { - struct CompilerT : public orc::IRCompileLayer::IRCompiler { - CompilerT(JuliaOJIT *pjit) - : IRCompiler(orc::IRSymbolMapper::ManglingOptions{}), - jit(*pjit) {} - virtual CompilerResultT operator()(Module &M) override; - private: - JuliaOJIT &jit; - }; - // Custom object emission notification handler for the JuliaOJIT - template - void registerObject(const ObjT &Obj, const LoadResult &LO); - public: #ifdef JL_USE_JITLINK typedef orc::ObjectLinkingLayer ObjLayerT; #else typedef orc::RTDyldObjectLinkingLayer ObjLayerT; #endif + struct LockLayerT : public orc::ObjectLayer { + + LockLayerT(orc::ObjectLayer &BaseLayer) JL_NOTSAFEPOINT : orc::ObjectLayer(BaseLayer.getExecutionSession()), BaseLayer(BaseLayer) {} + ~LockLayerT() JL_NOTSAFEPOINT = default; + + void emit(std::unique_ptr R, + std::unique_ptr O) override { +#ifndef JL_USE_JITLINK + std::lock_guard lock(EmissionMutex); +#endif + BaseLayer.emit(std::move(R), std::move(O)); + } + private: + orc::ObjectLayer &BaseLayer; + std::mutex EmissionMutex; + }; typedef orc::IRCompileLayer CompileLayerT; + typedef orc::IRTransformLayer OptimizeLayerT; typedef object::OwningBinary OwningObj; + template + , + SmallVector + > + > + > + struct ResourcePool { + public: + ResourcePool(std::function creator) JL_NOTSAFEPOINT : creator(std::move(creator)), mutex(std::make_unique()) {} + ResourcePool(ResourcePool&) = delete; + ResourcePool(ResourcePool&&) JL_NOTSAFEPOINT = default; + ~ResourcePool() JL_NOTSAFEPOINT = default; + class OwningResource { + public: + OwningResource(ResourcePool &pool, ResourceT resource) JL_NOTSAFEPOINT // _ENTER + : pool(pool), resource(std::move(resource)) {} + OwningResource(const OwningResource &) = delete; + OwningResource &operator=(const OwningResource &) = delete; + OwningResource(OwningResource &&) JL_NOTSAFEPOINT = default; + OwningResource &operator=(OwningResource &&) JL_NOTSAFEPOINT = default; + ~OwningResource() JL_NOTSAFEPOINT { // _LEAVE + if (resource) + pool.release(std::move(*resource)); + } + ResourceT release() JL_NOTSAFEPOINT { + ResourceT res(std::move(*resource)); + resource.reset(); + return res; + } + void reset(ResourceT res) JL_NOTSAFEPOINT { + *resource = std::move(res); + } + ResourceT &operator*() JL_NOTSAFEPOINT { + return *resource; + } + ResourceT *operator->() JL_NOTSAFEPOINT { + return get(); + } + ResourceT *get() JL_NOTSAFEPOINT { + return resource.getPointer(); + } + const ResourceT &operator*() const JL_NOTSAFEPOINT { + return *resource; + } + const ResourceT *operator->() const JL_NOTSAFEPOINT { + return get(); + } + const ResourceT *get() const JL_NOTSAFEPOINT { + return resource.getPointer(); + } + explicit operator bool() const JL_NOTSAFEPOINT { + return resource; + } + private: + ResourcePool &pool; + llvm::Optional resource; + }; + + OwningResource operator*() JL_NOTSAFEPOINT { + return OwningResource(*this, acquire()); + } + + OwningResource get() { + return **this; + } + + ResourceT acquire() JL_NOTSAFEPOINT { // _ENTER + std::unique_lock lock(mutex->mutex); + if (!pool.empty()) { + return pop(pool); + } + if (!max || created < max) { + created++; + return creator(); + } + mutex->empty.wait(lock, [&](){ return !pool.empty(); }); + assert(!pool.empty() && "Expected resource pool to have a value!"); + return pop(pool); + } + void release(ResourceT &&resource) JL_NOTSAFEPOINT { // _LEAVE + std::lock_guard lock(mutex->mutex); + pool.push(std::move(resource)); + mutex->empty.notify_one(); + } + private: + template + static ResourceT pop(std::queue &pool) JL_NOTSAFEPOINT { + ResourceT top = std::move(pool.front()); + pool.pop(); + return top; + } + template + static ResourceT pop(PoolT &pool) JL_NOTSAFEPOINT { + ResourceT top = std::move(pool.top()); + pool.pop(); + return top; + } + std::function creator; + size_t created = 0; + BackingT pool; + struct WNMutex { + std::mutex mutex; + std::condition_variable empty; + }; + + std::unique_ptr mutex; + }; + struct PipelineT { + PipelineT(orc::ObjectLayer &BaseLayer, TargetMachine &TM, int optlevel, std::vector> &PrintLLVMTimers); + CompileLayerT CompileLayer; + OptimizeLayerT OptimizeLayer; + }; + + struct OptSelLayerT : orc::IRLayer { + + template + OptSelLayerT(const std::array, N> &optimizers) JL_NOTSAFEPOINT + : orc::IRLayer(optimizers[0]->OptimizeLayer.getExecutionSession(), + optimizers[0]->OptimizeLayer.getManglingOptions()), + optimizers(optimizers.data()), + count(N) { + static_assert(N > 0, "Expected array with at least one optimizer!"); + } + ~OptSelLayerT() JL_NOTSAFEPOINT = default; + + void emit(std::unique_ptr R, orc::ThreadSafeModule TSM) override; + + private: + const std::unique_ptr * const optimizers; + size_t count; + }; + +private: + // Custom object emission notification handler for the JuliaOJIT + template + void registerObject(const ObjT &Obj, const LoadResult &LO); - JuliaOJIT(TargetMachine &TM, LLVMContext *Ctx); +public: + + JuliaOJIT() JL_NOTSAFEPOINT; + ~JuliaOJIT() JL_NOTSAFEPOINT; - void enableJITDebuggingSupport(); + void enableJITDebuggingSupport() JL_NOTSAFEPOINT; #ifndef JL_USE_JITLINK // JITLink doesn't support old JITEventListeners (yet). - void RegisterJITEventListener(JITEventListener *L); + void RegisterJITEventListener(JITEventListener *L) JL_NOTSAFEPOINT; #endif - void addGlobalMapping(StringRef Name, uint64_t Addr); - void addModule(std::unique_ptr M); - - JL_JITSymbol findSymbol(StringRef Name, bool ExportedSymbolsOnly); - JL_JITSymbol findUnmangledSymbol(StringRef Name); - uint64_t getGlobalValueAddress(StringRef Name); - uint64_t getFunctionAddress(StringRef Name); - StringRef getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *codeinst); - const DataLayout& getDataLayout() const; - const Triple& getTargetTriple() const; - size_t getTotalBytes() const; + orc::SymbolStringPtr mangle(StringRef Name) JL_NOTSAFEPOINT; + void addGlobalMapping(StringRef Name, uint64_t Addr) JL_NOTSAFEPOINT; + void addModule(orc::ThreadSafeModule M) JL_NOTSAFEPOINT; + + JL_JITSymbol findSymbol(StringRef Name, bool ExportedSymbolsOnly) JL_NOTSAFEPOINT; + JL_JITSymbol findUnmangledSymbol(StringRef Name) JL_NOTSAFEPOINT; + uint64_t getGlobalValueAddress(StringRef Name) JL_NOTSAFEPOINT; + uint64_t getFunctionAddress(StringRef Name) JL_NOTSAFEPOINT; + StringRef getFunctionAtAddress(uint64_t Addr, jl_code_instance_t *codeinst) JL_NOTSAFEPOINT; + auto getContext() JL_NOTSAFEPOINT { + return *ContextPool; + } + orc::ThreadSafeContext acquireContext() { // JL_NOTSAFEPOINT_ENTER? + return ContextPool.acquire(); + } + void releaseContext(orc::ThreadSafeContext &&ctx) { // JL_NOTSAFEPOINT_LEAVE? + ContextPool.release(std::move(ctx)); + } + const DataLayout& getDataLayout() const JL_NOTSAFEPOINT; + + // TargetMachine pass-through methods + std::unique_ptr cloneTargetMachine() const JL_NOTSAFEPOINT; + const Triple& getTargetTriple() const JL_NOTSAFEPOINT; + StringRef getTargetFeatureString() const JL_NOTSAFEPOINT; + StringRef getTargetCPU() const JL_NOTSAFEPOINT; + const TargetOptions &getTargetOptions() const JL_NOTSAFEPOINT; + const Target &getTarget() const JL_NOTSAFEPOINT; + TargetIRAnalysis getTargetIRAnalysis() const JL_NOTSAFEPOINT; + + size_t getTotalBytes() const JL_NOTSAFEPOINT; + void printTimers() JL_NOTSAFEPOINT; + + jl_locked_stream &get_dump_emitted_mi_name_stream() JL_NOTSAFEPOINT { + return dump_emitted_mi_name_stream; + } + jl_locked_stream &get_dump_compiles_stream() JL_NOTSAFEPOINT { + return dump_compiles_stream; + } + jl_locked_stream &get_dump_llvm_opt_stream() JL_NOTSAFEPOINT { + return dump_llvm_opt_stream; + } private: - std::string getMangledName(StringRef Name); - std::string getMangledName(const GlobalValue *GV); + std::string getMangledName(StringRef Name) JL_NOTSAFEPOINT; + std::string getMangledName(const GlobalValue *GV) JL_NOTSAFEPOINT; + void shareStrings(Module &M) JL_NOTSAFEPOINT; - TargetMachine &TM; + const std::unique_ptr TM; const DataLayout DL; - // Should be big enough that in the common case, The - // object fits in its entirety - SmallVector ObjBufferSV; - raw_svector_ostream ObjStream; - legacy::PassManager PM0; // per-optlevel pass managers - legacy::PassManager PM1; - legacy::PassManager PM2; - legacy::PassManager PM3; - TargetMachine *TMs[4]; - MCContext *Ctx; - - orc::ThreadSafeContext TSCtx; + orc::ExecutionSession ES; orc::JITDylib &GlobalJD; orc::JITDylib &JD; + //Map and inc are guarded by RLST_mutex + std::mutex RLST_mutex{}; + int RLST_inc = 0; + DenseMap ReverseLocalSymbolTable; + + //Compilation streams + jl_locked_stream dump_emitted_mi_name_stream; + jl_locked_stream dump_compiles_stream; + jl_locked_stream dump_llvm_opt_stream; + + std::vector> PrintLLVMTimers; + + ResourcePool> ContextPool; + #ifndef JL_USE_JITLINK - std::shared_ptr MemMgr; + const std::shared_ptr MemMgr; +#else + std::atomic total_size{0}; + const std::unique_ptr MemMgr; #endif ObjLayerT ObjectLayer; - CompileLayerT CompileLayer; - - DenseMap ReverseLocalSymbolTable; + LockLayerT LockLayer; + const std::array, 4> Pipelines; + OptSelLayerT OptSelLayer; }; extern JuliaOJIT *jl_ExecutionEngine; +std::unique_ptr jl_create_llvm_module(StringRef name, LLVMContext &ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()) JL_NOTSAFEPOINT; +inline orc::ThreadSafeModule jl_create_ts_module(StringRef name, orc::ThreadSafeContext ctx, bool imaging_mode, const DataLayout &DL = jl_ExecutionEngine->getDataLayout(), const Triple &triple = jl_ExecutionEngine->getTargetTriple()) JL_NOTSAFEPOINT { + auto lock = ctx.getLock(); + return orc::ThreadSafeModule(jl_create_llvm_module(name, *ctx.getContext(), imaging_mode, DL, triple), ctx); +} + +Module &jl_codegen_params_t::shared_module() JL_NOTSAFEPOINT { + if (!_shared_module) { + _shared_module = jl_create_llvm_module("globals", getContext(), imaging, DL, TargetTriple); + } + return *_shared_module; +} + +Pass *createLowerPTLSPass(bool imaging_mode) JL_NOTSAFEPOINT; +Pass *createCombineMulAddPass() JL_NOTSAFEPOINT; +Pass *createFinalLowerGCPass() JL_NOTSAFEPOINT; +Pass *createLateLowerGCFramePass() JL_NOTSAFEPOINT; +Pass *createLowerExcHandlersPass() JL_NOTSAFEPOINT; +Pass *createGCInvariantVerifierPass(bool Strong) JL_NOTSAFEPOINT; +Pass *createPropagateJuliaAddrspaces() JL_NOTSAFEPOINT; +Pass *createRemoveJuliaAddrspacesPass() JL_NOTSAFEPOINT; +Pass *createRemoveNIPass() JL_NOTSAFEPOINT; +Pass *createJuliaLICMPass() JL_NOTSAFEPOINT; +Pass *createMultiVersioningPass(bool external_use) JL_NOTSAFEPOINT; +Pass *createAllocOptPass() JL_NOTSAFEPOINT; +Pass *createDemoteFloat16Pass() JL_NOTSAFEPOINT; +Pass *createCPUFeaturesPass() JL_NOTSAFEPOINT; +Pass *createLowerSimdLoopPass() JL_NOTSAFEPOINT; + +// NewPM +#include "passes.h" -Pass *createLowerPTLSPass(bool imaging_mode); -Pass *createCombineMulAddPass(); -Pass *createFinalLowerGCPass(); -Pass *createLateLowerGCFramePass(); -Pass *createLowerExcHandlersPass(); -Pass *createGCInvariantVerifierPass(bool Strong); -Pass *createPropagateJuliaAddrspaces(); -Pass *createRemoveJuliaAddrspacesPass(); -Pass *createRemoveNIPass(); -Pass *createJuliaLICMPass(); -Pass *createMultiVersioningPass(); -Pass *createAllocOptPass(); -Pass *createDemoteFloat16Pass(); -Pass *createCPUFeaturesPass(); // Whether the Function is an llvm or julia intrinsic. -static inline bool isIntrinsicFunction(Function *F) +static inline bool isIntrinsicFunction(Function *F) JL_NOTSAFEPOINT { return F->isIntrinsic() || F->getName().startswith("julia."); } -CodeGenOpt::Level CodeGenOptLevelFor(int optlevel); +CodeGenOpt::Level CodeGenOptLevelFor(int optlevel) JL_NOTSAFEPOINT; diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index 09d2949c22489..092a48be81930 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -3,7 +3,6 @@ // Pointers that are exposed through the public libjulia #define JL_EXPORTED_DATA_POINTERS(XX) \ XX(jl_abstractarray_type) \ - XX(jl_abstractslot_type) \ XX(jl_abstractstring_type) \ XX(jl_an_empty_string) \ XX(jl_an_empty_vec_any) \ @@ -44,6 +43,7 @@ XX(jl_float64_type) \ XX(jl_floatingpoint_type) \ XX(jl_function_type) \ + XX(jl_binding_type) \ XX(jl_globalref_type) \ XX(jl_gotoifnot_type) \ XX(jl_gotonode_type) \ @@ -55,6 +55,7 @@ XX(jl_interconditional_type) \ XX(jl_interrupt_exception) \ XX(jl_intrinsic_type) \ + XX(jl_kwcall_func) \ XX(jl_lineinfonode_type) \ XX(jl_linenumbernode_type) \ XX(jl_llvmpointer_type) \ @@ -68,6 +69,7 @@ XX(jl_method_type) \ XX(jl_methtable_type) \ XX(jl_module_type) \ + XX(jl_n_threads_per_pool) \ XX(jl_namedtuple_type) \ XX(jl_namedtuple_typename) \ XX(jl_newvarnode_type) \ @@ -101,7 +103,6 @@ XX(jl_true) \ XX(jl_tuple_typename) \ XX(jl_tvar_type) \ - XX(jl_typedslot_type) \ XX(jl_typeerror_type) \ XX(jl_typemap_entry_type) \ XX(jl_typemap_level_type) \ @@ -124,9 +125,15 @@ XX(jl_vecelement_typename) \ XX(jl_voidpointer_type) \ XX(jl_void_type) \ - XX(jl_weakref_type) + XX(jl_weakref_type) \ // Data symbols that are defined inside the public libjulia #define JL_EXPORTED_DATA_SYMBOLS(XX) \ - XX(jl_n_threads, int) \ - XX(jl_options, jl_options_t) + XX(jl_n_threadpools, int) \ + XX(jl_n_threads, _Atomic(int)) \ + XX(jl_n_gcthreads, int) \ + XX(jl_options, jl_options_t) \ + XX(jl_task_gcstack_offset, int) \ + XX(jl_task_ptls_offset, int) \ + +// end of file diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 7748809dcdf05..f79537d419b90 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -3,6 +3,7 @@ #define JL_RUNTIME_EXPORTED_FUNCS(XX) \ XX(jl_active_task_stack) \ XX(jl_add_standard_imports) \ + XX(jl_adopt_thread) \ XX(jl_alignment) \ XX(jl_alloc_array_1d) \ XX(jl_alloc_array_2d) \ @@ -20,7 +21,10 @@ XX(jl_apply_type1) \ XX(jl_apply_type2) \ XX(jl_argument_datatype) \ - XX(jl_argument_method_table) \ + XX(jl_arraylen) \ + XX(jl_arrayref) \ + XX(jl_arrayset) \ + XX(jl_arrayunset) \ XX(jl_array_cconvert_cstring) \ XX(jl_array_copy) \ XX(jl_array_del_at) \ @@ -31,19 +35,15 @@ XX(jl_array_grow_beg) \ XX(jl_array_grow_end) \ XX(jl_array_isassigned) \ - XX(jl_arraylen) \ XX(jl_array_ptr) \ XX(jl_array_ptr_1d_append) \ XX(jl_array_ptr_1d_push) \ XX(jl_array_ptr_copy) \ XX(jl_array_rank) \ - XX(jl_arrayref) \ - XX(jl_arrayset) \ XX(jl_array_size) \ XX(jl_array_sizehint) \ XX(jl_array_to_string) \ XX(jl_array_typetagdata) \ - XX(jl_arrayunset) \ XX(jl_array_validate_dims) \ XX(jl_atexit_hook) \ XX(jl_atomic_bool_cmpswap_bits) \ @@ -54,7 +54,6 @@ XX(jl_atomic_swap_bits) \ XX(jl_backtrace_from_here) \ XX(jl_base_relative_to) \ - XX(jl_binding_owner) \ XX(jl_binding_resolved_p) \ XX(jl_bitcast) \ XX(jl_boundp) \ @@ -85,8 +84,8 @@ XX(jl_call1) \ XX(jl_call2) \ XX(jl_call3) \ - XX(jl_call_in_typeinf_world) \ XX(jl_calloc) \ + XX(jl_call_in_typeinf_world) \ XX(jl_capture_interp_frame) \ XX(jl_ceil_llvm) \ XX(jl_ceil_llvm_withtype) \ @@ -97,12 +96,14 @@ XX(jl_close_uv) \ XX(jl_code_for_staged) \ XX(jl_compile_hint) \ + XX(jl_compile_method_instance) \ XX(jl_compress_argnames) \ XX(jl_compress_ir) \ XX(jl_compute_fieldtypes) \ XX(jl_copy_ast) \ XX(jl_copy_code_info) \ XX(jl_cpu_threads) \ + XX(jl_effective_threads) \ XX(jl_crc32c_sw) \ XX(jl_create_system_image) \ XX(jl_cstr_to_string) \ @@ -115,31 +116,30 @@ XX(jl_dlopen) \ XX(jl_dlsym) \ XX(jl_dump_host_cpu) \ + XX(jl_check_pkgimage_clones) \ + XX(jl_egal) \ XX(jl_egal__bits) \ - XX(jl_egal__special) \ + XX(jl_egal__bitstag) \ XX(jl_eh_restore_state) \ - XX(jl_enqueue_task) \ XX(jl_enter_handler) \ XX(jl_enter_threaded_region) \ XX(jl_environ) \ XX(jl_eof_error) \ XX(jl_eqtable_get) \ - XX(jl_eqtable_nextind) \ XX(jl_eqtable_pop) \ XX(jl_eqtable_put) \ XX(jl_errno) \ XX(jl_error) \ XX(jl_errorf) \ XX(jl_eval_string) \ - XX(jl_exception_clear) \ XX(jl_exceptionf) \ + XX(jl_exception_clear) \ XX(jl_exception_occurred) \ XX(jl_excstack_state) \ XX(jl_exit) \ XX(jl_exit_on_sigint) \ XX(jl_exit_threaded_region) \ XX(jl_expand) \ - XX(jl_resolve_globals_in_ir) \ XX(jl_expand_and_resolve) \ XX(jl_expand_stmt) \ XX(jl_expand_stmt_with_loc) \ @@ -150,11 +150,12 @@ XX(jl_gc_add_finalizer) \ XX(jl_gc_add_finalizer_th) \ XX(jl_gc_add_ptr_finalizer) \ + XX(jl_gc_add_quiescent) \ + XX(jl_gc_allocobj) \ XX(jl_gc_alloc_0w) \ XX(jl_gc_alloc_1w) \ XX(jl_gc_alloc_2w) \ XX(jl_gc_alloc_3w) \ - XX(jl_gc_allocobj) \ XX(jl_gc_alloc_typed) \ XX(jl_gc_big_alloc) \ XX(jl_gc_collect) \ @@ -170,8 +171,10 @@ XX(jl_gc_external_obj_hdr_size) \ XX(jl_gc_find_taggedvalue_pool) \ XX(jl_gc_get_total_bytes) \ + XX(jl_gc_get_max_memory) \ XX(jl_gc_internal_obj_base_ptr) \ XX(jl_gc_is_enabled) \ + XX(jl_gc_is_in_finalizer) \ XX(jl_gc_live_bytes) \ XX(jl_gc_managed_malloc) \ XX(jl_gc_managed_realloc) \ @@ -184,6 +187,7 @@ XX(jl_gc_pool_alloc) \ XX(jl_gc_queue_multiroot) \ XX(jl_gc_queue_root) \ + XX(jl_gc_safepoint) \ XX(jl_gc_schedule_foreign_sweepfunc) \ XX(jl_gc_set_cb_notify_external_alloc) \ XX(jl_gc_set_cb_notify_external_free) \ @@ -191,6 +195,7 @@ XX(jl_gc_set_cb_pre_gc) \ XX(jl_gc_set_cb_root_scanner) \ XX(jl_gc_set_cb_task_scanner) \ + XX(jl_gc_set_max_memory) \ XX(jl_gc_sync_total_bytes) \ XX(jl_gc_total_hrtime) \ XX(jl_gdblookup) \ @@ -198,6 +203,8 @@ XX(jl_generic_function_def) \ XX(jl_gensym) \ XX(jl_getallocationgranularity) \ + XX(jl_getnameinfo) \ + XX(jl_getpagesize) \ XX(jl_get_ARCH) \ XX(jl_get_backtrace) \ XX(jl_get_binding) \ @@ -210,33 +217,27 @@ XX(jl_get_excstack) \ XX(jl_get_fenv_consts) \ XX(jl_get_field) \ - XX(jl_get_field_offset) \ XX(jl_get_global) \ XX(jl_get_image_file) \ XX(jl_get_JIT) \ XX(jl_get_julia_bin) \ XX(jl_get_julia_bindir) \ - XX(jl_get_keyword_sorter) \ - XX(jl_get_kwsorter) \ XX(jl_get_method_inferred) \ - XX(jl_get_module_binding) \ XX(jl_get_module_compile) \ XX(jl_get_module_infer) \ XX(jl_get_module_of_binding) \ XX(jl_get_module_optlevel) \ - XX(jl_getnameinfo) \ XX(jl_get_next_task) \ XX(jl_get_nth_field) \ XX(jl_get_nth_field_checked) \ XX(jl_get_nth_field_noalloc) \ - XX(jl_getpagesize) \ XX(jl_get_pgcstack) \ - XX(jl_getpid) \ XX(jl_get_ptls_states) \ XX(jl_get_root_symbol) \ XX(jl_get_safe_restore) \ XX(jl_get_size) \ XX(jl_get_task_tid) \ + XX(jl_get_task_threadpoolid) \ XX(jl_get_tls_world_age) \ XX(jl_get_UNAME) \ XX(jl_get_world_counter) \ @@ -255,45 +256,47 @@ XX(jl_idtable_rehash) \ XX(jl_infer_thunk) \ XX(jl_init) \ - XX(jl_init__threading) \ - XX(jl_init_restored_modules) \ + XX(jl_init_options) \ + XX(jl_init_restored_module) \ XX(jl_init_with_image) \ XX(jl_init_with_image__threading) \ - XX(jl_init_options) \ + XX(jl_init__threading) \ XX(jl_install_sigint_handler) \ XX(jl_instantiate_type_in_env) \ XX(jl_instantiate_unionall) \ XX(jl_intersect_types) \ - XX(jl_in_threaded_region) \ XX(jl_intrinsic_name) \ XX(jl_invoke) \ XX(jl_invoke_api) \ + XX(jl_in_threaded_region) \ XX(jl_iolock_begin) \ XX(jl_iolock_end) \ XX(jl_ios_buffer_n) \ XX(jl_ios_fd) \ XX(jl_ios_get_nbyte_int) \ XX(jl_ir_flag_inferred) \ - XX(jl_ir_flag_inlineable) \ - XX(jl_ir_flag_pure) \ + XX(jl_ir_flag_has_fcall) \ + XX(jl_ir_flag_inlining) \ + XX(jl_ir_inlining_cost) \ XX(jl_ir_nslots) \ XX(jl_ir_slotflag) \ XX(jl_isa) \ XX(jl_isa_compileable_sig) \ + XX(jl_islayout_inline) \ + XX(jl_istopmod) \ XX(jl_is_binding_deprecated) \ XX(jl_is_char_signed) \ XX(jl_is_const) \ XX(jl_is_debugbuild) \ + XX(jl_is_foreign_type) \ XX(jl_is_identifier) \ XX(jl_is_imported) \ XX(jl_is_initialized) \ XX(jl_is_in_pure_context) \ - XX(jl_islayout_inline) \ XX(jl_is_memdebug) \ XX(jl_is_not_broken_subtype) \ XX(jl_is_operator) \ XX(jl_is_task_started) \ - XX(jl_istopmod) \ XX(jl_is_unary_and_binary_operator) \ XX(jl_is_unary_operator) \ XX(jl_lazy_load_and_lookup) \ @@ -336,10 +339,11 @@ XX(jl_nb_available) \ XX(jl_new_array) \ XX(jl_new_bits) \ - XX(jl_new_code_info_uninit) \ XX(jl_new_codeinst) \ + XX(jl_new_code_info_uninit) \ XX(jl_new_datatype) \ XX(jl_new_foreign_type) \ + XX(jl_reinit_foreign_type) \ XX(jl_new_method_instance_uninit) \ XX(jl_new_method_table) \ XX(jl_new_method_uninit) \ @@ -347,14 +351,14 @@ XX(jl_new_primitivetype) \ XX(jl_new_struct) \ XX(jl_new_structt) \ - XX(jl_new_struct_uninit) \ XX(jl_new_structv) \ + XX(jl_new_struct_uninit) \ XX(jl_new_task) \ XX(jl_new_typename_in) \ XX(jl_new_typevar) \ XX(jl_next_from_addrinfo) \ - XX(jl_no_exc_handler) \ XX(jl_normalize_to_compilable_sig) \ + XX(jl_no_exc_handler) \ XX(jl_object_id) \ XX(jl_object_id_) \ XX(jl_obvious_subtype) \ @@ -372,8 +376,8 @@ XX(jl_pop_handler) \ XX(jl_preload_sysimg_so) \ XX(jl_prepend_cwd) \ - XX(jl_print_backtrace) \ XX(jl_printf) \ + XX(jl_print_backtrace) \ XX(jl_process_events) \ XX(jl_profile_clear_data) \ XX(jl_profile_delay_nsec) \ @@ -390,22 +394,22 @@ XX(jl_queue_work) \ XX(jl_raise_debugger) \ XX(jl_readuntil) \ + XX(jl_cache_flags) \ + 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) \ - XX(jl_restore_incremental_from_buf) \ + XX(jl_restore_package_image_from_file) \ XX(jl_restore_system_image) \ XX(jl_restore_system_image_data) \ XX(jl_rethrow) \ XX(jl_rethrow_other) \ - XX(jl_rettype_inferred) \ XX(jl_running_on_valgrind) \ XX(jl_safe_printf) \ - XX(jl_save_incremental) \ - XX(jl_save_system_image) \ XX(jl_SC_CLK_TCK) \ XX(jl_set_ARGS) \ XX(jl_set_const) \ @@ -422,6 +426,7 @@ XX(jl_set_safe_restore) \ XX(jl_set_sysimg_so) \ XX(jl_set_task_tid) \ + XX(jl_set_task_threadpoolid) \ XX(jl_set_typeinf_func) \ XX(jl_set_zero_subnormals) \ XX(jl_sigatomic_begin) \ @@ -448,7 +453,6 @@ XX(jl_svec2) \ XX(jl_svec_copy) \ XX(jl_svec_fill) \ - XX(jl_svec_isassigned) \ XX(jl_svec_ref) \ XX(jl_switch) \ XX(jl_switchto) \ @@ -459,8 +463,10 @@ XX(jl_take_buffer) \ XX(jl_task_get_next) \ XX(jl_task_stack_buffer) \ + XX(jl_termios_size) \ XX(jl_test_cpu_feature) \ XX(jl_threadid) \ + XX(jl_threadpoolid) \ XX(jl_throw) \ XX(jl_throw_out_of_memory_error) \ XX(jl_too_few_args) \ @@ -470,20 +476,20 @@ XX(jl_try_substrtod) \ XX(jl_try_substrtof) \ XX(jl_tty_set_mode) \ - XX(jl_tupletype_fill) \ XX(jl_typeassert) \ - XX(jl_type_equality_is_identity) \ + XX(jl_typeinf_lock_begin) \ + XX(jl_typeinf_lock_end) \ + XX(jl_typeinf_timing_begin) \ + XX(jl_typeinf_timing_end) \ + XX(jl_typename_str) \ + XX(jl_typeof_str) \ + XX(jl_types_equal) \ XX(jl_type_error) \ XX(jl_type_error_rt) \ - XX(jl_typeinf_begin) \ - XX(jl_typeinf_end) \ XX(jl_type_intersection) \ XX(jl_type_intersection_with_env) \ XX(jl_type_morespecific) \ XX(jl_type_morespecific_no_subtype) \ - XX(jl_typename_str) \ - XX(jl_typeof_str) \ - XX(jl_types_equal) \ XX(jl_type_union) \ XX(jl_type_unionall) \ XX(jl_unbox_bool) \ @@ -499,8 +505,8 @@ XX(jl_unbox_uint8) \ XX(jl_unbox_uint8pointer) \ XX(jl_unbox_voidpointer) \ - XX(jl_uncompress_argname_n) \ XX(jl_uncompress_argnames) \ + XX(jl_uncompress_argname_n) \ XX(jl_uncompress_ir) \ XX(jl_undefined_var_error) \ XX(jl_value_ptr) \ @@ -512,23 +518,24 @@ XX(jl_vexceptionf) \ 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) + XX(jl_setjmp) \ // use YY instead of XX to avoid jl -> ijl renaming in libjulia-codegen #define JL_CODEGEN_EXPORTED_FUNCS(YY) \ YY(jl_dump_function_ir) \ YY(jl_dump_method_asm) \ YY(jl_extern_c) \ - YY(jl_get_llvm_context) \ YY(jl_get_llvmf_defn) \ YY(jl_get_llvm_function) \ YY(jl_get_llvm_module) \ YY(jl_get_LLVM_VERSION) \ YY(jl_dump_native) \ - YY(jl_get_llvm_gv) \ + YY(jl_get_llvm_gvs) \ + YY(jl_get_llvm_external_fns) \ YY(jl_dump_function_asm) \ YY(jl_LLVMCreateDisasm) \ YY(jl_LLVMDisasmInstruction) \ @@ -537,11 +544,10 @@ YY(jl_register_fptrs) \ YY(jl_generate_fptr) \ YY(jl_generate_fptr_for_unspecialized) \ + YY(jl_generate_fptr_for_oc_wrapper) \ YY(jl_compile_extern_c) \ YY(jl_teardown_codegen) \ YY(jl_jit_total_bytes) \ - YY(jl_lock_profile) \ - YY(jl_unlock_profile) \ YY(jl_create_native) \ YY(jl_dump_compiles) \ YY(jl_dump_emitted_mi_name) \ @@ -568,3 +574,4 @@ YY(LLVMExtraAddDemoteFloat16Pass) \ YY(LLVMExtraAddCPUFeaturesPass) \ +// end of file diff --git a/src/jl_uv.c b/src/jl_uv.c index d2372b59c4670..281dd798dbb36 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -30,6 +30,68 @@ extern "C" { #endif static uv_async_t signal_async; +static uv_timer_t wait_empty_worker; + +static void walk_print_cb(uv_handle_t *h, void *arg) +{ + if (!uv_is_active(h) || !uv_has_ref(h)) + return; + const char *type = uv_handle_type_name(h->type); + if (!type) + type = ""; + 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; + const char *pad = " "; // 16 spaces + int npad = fd == -1 ? 0 : snprintf(NULL, 0, "%zd", (size_t)fd); + 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); + else + jl_safe_printf(" %s[%zd] %s@%p->%p\n", type, (size_t)fd, pad, (void*)h, (void*)h->data); +} + +static void wait_empty_func(uv_timer_t *t) +{ + // make sure this is hidden now, since we would auto-unref it later + uv_unref((uv_handle_t*)&signal_async); + 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", + (size_t)uv_os_getpid()); + uv_walk(jl_io_loop, walk_print_cb, NULL); + 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); + 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(); +} + + static void jl_signal_async_cb(uv_async_t *hdl) { @@ -49,7 +111,8 @@ jl_mutex_t jl_uv_mutex; void jl_init_uv(void) { uv_async_init(jl_io_loop, &signal_async, jl_signal_async_cb); - JL_MUTEX_INIT(&jl_uv_mutex); // a file-scope initializer can be used instead + uv_unref((uv_handle_t*)&signal_async); + JL_MUTEX_INIT(&jl_uv_mutex, "jl_uv_mutex"); // a file-scope initializer can be used instead } _Atomic(int) jl_uv_n_waiters = 0; @@ -60,6 +123,7 @@ void JL_UV_LOCK(void) } else { jl_atomic_fetch_add_relaxed(&jl_uv_n_waiters, 1); + jl_fence(); // [^store_buffering_2] jl_wake_libuv(); JL_LOCK(&jl_uv_mutex); jl_atomic_fetch_add_relaxed(&jl_uv_n_waiters, -1); @@ -77,14 +141,16 @@ JL_DLLEXPORT void jl_iolock_end(void) } -void jl_uv_call_close_callback(jl_value_t *val) +static void jl_uv_call_close_callback(jl_value_t *val) { - jl_value_t *args[2]; + jl_value_t **args; + JL_GC_PUSHARGS(args, 2); // val is "rooted" in the finalizer list only right now args[0] = jl_get_global(jl_base_relative_to(((jl_datatype_t*)jl_typeof(val))->name->module), jl_symbol("_uv_hook_close")); // topmod(typeof(val))._uv_hook_close args[1] = val; assert(args[0]); jl_apply(args, 2); // TODO: wrap in try-catch? + JL_GC_POP(); } static void jl_uv_closeHandle(uv_handle_t *handle) @@ -105,8 +171,9 @@ static void jl_uv_closeHandle(uv_handle_t *handle) ct->world_age = jl_atomic_load_acquire(&jl_world_counter); jl_uv_call_close_callback((jl_value_t*)handle->data); ct->world_age = last_age; + return; } - if (handle == (uv_handle_t*)&signal_async) + if (handle == (uv_handle_t*)&signal_async || handle == (uv_handle_t*)&wait_empty_worker) return; free(handle); } @@ -125,6 +192,10 @@ static void jl_uv_flush_close_callback(uv_write_t *req, int status) free(req); return; } + if (uv_is_closing((uv_handle_t*)stream)) { // avoid double-close on the stream + free(req); + return; + } if (status == 0 && uv_is_writable(stream) && stream->write_queue_size != 0) { // new data was written, wait for it to flush too uv_buf_t buf; @@ -132,14 +203,12 @@ static void jl_uv_flush_close_callback(uv_write_t *req, int status) buf.len = 0; req->data = NULL; if (uv_write(req, stream, &buf, 1, (uv_write_cb)jl_uv_flush_close_callback) == 0) - return; - } - if (!uv_is_closing((uv_handle_t*)stream)) { // avoid double-close on the stream - if (stream->type == UV_TTY) - uv_tty_set_mode((uv_tty_t*)stream, UV_TTY_MODE_NORMAL); - uv_close((uv_handle_t*)stream, &jl_uv_closeHandle); + return; // success } free(req); + if (stream->type == UV_TTY) + uv_tty_set_mode((uv_tty_t*)stream, UV_TTY_MODE_NORMAL); + uv_close((uv_handle_t*)stream, &jl_uv_closeHandle); } static void uv_flush_callback(uv_write_t *req, int status) @@ -207,7 +276,9 @@ JL_DLLEXPORT int jl_process_events(void) 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; + uv_ref((uv_handle_t*)&signal_async); // force the loop alive int r = uv_run(loop, UV_RUN_NOWAIT); + uv_unref((uv_handle_t*)&signal_async); JL_PROBE_RT_FINISH_PROCESS_EVENTS(ct); JL_UV_UNLOCK(); return r; @@ -224,15 +295,15 @@ static void jl_proc_exit_cleanup_cb(uv_process_t *process, int64_t exit_status, JL_DLLEXPORT void jl_close_uv(uv_handle_t *handle) { + JL_UV_LOCK(); if (handle->type == UV_PROCESS && ((uv_process_t*)handle)->pid != 0) { // take ownership of this handle, // so we can waitpid for the resource to exit and avoid leaving zombies assert(handle->data == NULL); // make sure Julia has forgotten about it already ((uv_process_t*)handle)->exit_cb = jl_proc_exit_cleanup_cb; - return; + uv_unref(handle); } - JL_UV_LOCK(); - if (handle->type == UV_FILE) { + else if (handle->type == UV_FILE) { uv_fs_t req; jl_uv_file_t *fd = (jl_uv_file_t*)handle; if ((ssize_t)fd->file != -1) { @@ -240,31 +311,26 @@ JL_DLLEXPORT void jl_close_uv(uv_handle_t *handle) fd->file = (uv_os_fd_t)(ssize_t)-1; } jl_uv_closeHandle(handle); // synchronous (ok since the callback is known to not interact with any global state) - JL_UV_UNLOCK(); - return; - } - - if (handle->type == UV_NAMED_PIPE || handle->type == UV_TCP || handle->type == UV_TTY) { - uv_write_t *req = (uv_write_t*)malloc_s(sizeof(uv_write_t)); - req->handle = (uv_stream_t*)handle; - jl_uv_flush_close_callback(req, 0); - JL_UV_UNLOCK(); - return; } - - // avoid double-closing the stream - if (!uv_is_closing(handle)) { - uv_close(handle, &jl_uv_closeHandle); + else if (!uv_is_closing(handle)) { // avoid double-closing the stream + if (handle->type == UV_NAMED_PIPE || handle->type == UV_TCP || handle->type == UV_TTY) { + // flush the stream write-queue first + uv_write_t *req = (uv_write_t*)malloc_s(sizeof(uv_write_t)); + req->handle = (uv_stream_t*)handle; + jl_uv_flush_close_callback(req, 0); + } + else { + uv_close(handle, &jl_uv_closeHandle); + } } JL_UV_UNLOCK(); } JL_DLLEXPORT void jl_forceclose_uv(uv_handle_t *handle) { - // avoid double-closing the stream - if (!uv_is_closing(handle)) { + if (!uv_is_closing(handle)) { // avoid double-closing the stream JL_UV_LOCK(); - if (!uv_is_closing(handle)) { + if (!uv_is_closing(handle)) { // double-check uv_close(handle, &jl_uv_closeHandle); } JL_UV_UNLOCK(); @@ -282,7 +348,7 @@ JL_DLLEXPORT void jl_uv_disassociate_julia_struct(uv_handle_t *handle) handle->data = NULL; } -#define UV_CLOSED 0x02 // UV_HANDLE_CLOSED on Windows (same value) +#define UV_HANDLE_CLOSED 0x02 JL_DLLEXPORT int jl_spawn(char *name, char **argv, uv_loop_t *loop, uv_process_t *proc, @@ -308,7 +374,7 @@ JL_DLLEXPORT int jl_spawn(char *name, char **argv, if (!(flags == UV_INHERIT_FD || flags == UV_INHERIT_STREAM || flags == UV_IGNORE)) { proc->type = UV_PROCESS; proc->loop = loop; - proc->flags = UV_CLOSED; + proc->flags = UV_HANDLE_CLOSED; return UV_EINVAL; } } @@ -481,7 +547,7 @@ JL_DLLEXPORT int jl_uv_write(uv_stream_t *stream, const char *data, size_t n, return err; } -JL_DLLEXPORT void jl_uv_writecb(uv_write_t *req, int status) +static void jl_uv_writecb(uv_write_t *req, int status) JL_NOTSAFEPOINT { free(req); if (status < 0) { @@ -608,7 +674,7 @@ JL_DLLEXPORT int jl_printf(uv_stream_t *s, const char *format, ...) return c; } -JL_DLLEXPORT void jl_safe_printf(const char *fmt, ...) JL_NOTSAFEPOINT +JL_DLLEXPORT void jl_safe_printf(const char *fmt, ...) { static char buf[1000]; buf[0] = '\0'; @@ -633,22 +699,6 @@ JL_DLLEXPORT void jl_safe_printf(const char *fmt, ...) JL_NOTSAFEPOINT errno = last_errno; } -JL_DLLEXPORT void jl_exit(int exitcode) -{ - uv_tty_reset_mode(); - jl_atexit_hook(exitcode); - exit(exitcode); -} - -JL_DLLEXPORT int jl_getpid(void) JL_NOTSAFEPOINT -{ -#ifdef _OS_WINDOWS_ - return GetCurrentProcessId(); -#else - return getpid(); -#endif -} - typedef union { struct sockaddr in; struct sockaddr_in v4; diff --git a/src/jlapi.c b/src/jlapi.c index 3ab01c5def7f4..001253fed71a8 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -15,6 +15,10 @@ #include "julia_assert.h" #include "julia_internal.h" +#ifdef USE_TRACY +#include "tracy/TracyC.h" +#endif + #ifdef __cplusplus #include extern "C" { @@ -22,15 +26,6 @@ extern "C" { #include #endif -#if defined(_OS_WINDOWS_) && !defined(_COMPILER_GCC_) -JL_DLLEXPORT char * __cdecl dirname(char *); -#else -#include -#endif -#ifndef _OS_WINDOWS_ -#include -#endif - JL_DLLEXPORT int jl_is_initialized(void) { return jl_main_module != NULL; @@ -57,18 +52,19 @@ JL_DLLEXPORT void jl_set_ARGS(int argc, char **argv) } // 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 (*.ji) relative to the -// first argument path, or relative to the default julia home dir. -// The default is something like ../lib/julia/sys.ji +// 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 JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, - const char *image_relative_path) + const char *image_path) { if (jl_is_initialized()) return; libsupport_init(); jl_options.julia_bindir = julia_bindir; - if (image_relative_path != NULL) - jl_options.image_file = image_relative_path; + if (image_path != NULL) + jl_options.image_file = image_path; else jl_options.image_file = jl_get_default_sysimg_path(); julia_init(JL_IMAGE_JULIA_HOME); @@ -104,9 +100,15 @@ JL_DLLEXPORT void jl_init_with_image__threading(const char *julia_bindir, jl_init_with_image(julia_bindir, image_relative_path); } +static void _jl_exception_clear(jl_task_t *ct) JL_NOTSAFEPOINT +{ + ct->ptls->previous_exception = NULL; +} + JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) { jl_value_t *r; + jl_task_t *ct = jl_current_task; JL_TRY { const char filename[] = "none"; jl_value_t *ast = jl_parse_all(str, strlen(str), @@ -114,10 +116,10 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) JL_GC_PUSH1(&ast); r = jl_toplevel_eval_in(jl_main_module, ast); JL_GC_POP(); - jl_exception_clear(); + _jl_exception_clear(ct); } JL_CATCH { - jl_current_task->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(); r = NULL; } return r; @@ -136,7 +138,7 @@ JL_DLLEXPORT jl_value_t *jl_exception_occurred(void) JL_DLLEXPORT void jl_exception_clear(void) { - jl_current_task->ptls->previous_exception = NULL; + _jl_exception_clear(jl_current_task); } // get the name of a type as a string @@ -189,7 +191,7 @@ JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, uint32_t n v = jl_apply(argv, nargs); ct->world_age = last_age; JL_GC_POP(); - jl_exception_clear(); + _jl_exception_clear(ct); } JL_CATCH { ct->ptls->previous_exception = jl_current_exception(); @@ -209,7 +211,7 @@ JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f) v = jl_apply_generic(f, NULL, 0); ct->world_age = last_age; JL_GC_POP(); - jl_exception_clear(); + _jl_exception_clear(ct); } JL_CATCH { ct->ptls->previous_exception = jl_current_exception(); @@ -232,7 +234,7 @@ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f, jl_value_t *a) v = jl_apply(argv, 2); ct->world_age = last_age; JL_GC_POP(); - jl_exception_clear(); + _jl_exception_clear(ct); } JL_CATCH { ct->ptls->previous_exception = jl_current_exception(); @@ -256,7 +258,7 @@ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b v = jl_apply(argv, 3); ct->world_age = last_age; JL_GC_POP(); - jl_exception_clear(); + _jl_exception_clear(ct); } JL_CATCH { ct->ptls->previous_exception = jl_current_exception(); @@ -269,6 +271,7 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, jl_value_t *b, jl_value_t *c) { jl_value_t *v; + jl_task_t *ct = jl_current_task; JL_TRY { jl_value_t **argv; JL_GC_PUSHARGS(argv, 4); @@ -276,16 +279,15 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, argv[1] = a; argv[2] = b; argv[3] = c; - jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; ct->world_age = jl_get_world_counter(); v = jl_apply(argv, 4); ct->world_age = last_age; JL_GC_POP(); - jl_exception_clear(); + _jl_exception_clear(ct); } JL_CATCH { - jl_current_task->ptls->previous_exception = jl_current_exception(); + ct->ptls->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -411,7 +413,7 @@ JL_DLLEXPORT const char *jl_git_commit(void) return commit; } -// Create function versions of some useful macros +// Create function versions of some useful macros for GDB or FFI use JL_DLLEXPORT jl_taggedvalue_t *(jl_astaggedvalue)(jl_value_t *v) { return jl_astaggedvalue(v); @@ -432,6 +434,11 @@ JL_DLLEXPORT jl_value_t *(jl_get_fieldtypes)(jl_value_t *v) return (jl_value_t*)jl_get_fieldtypes((jl_datatype_t*)v); } +JL_DLLEXPORT int ijl_egal(jl_value_t *a, jl_value_t *b) +{ + return jl_egal(a, b); +} + #ifndef __clang_gcanalyzer__ JL_DLLEXPORT int8_t (jl_gc_unsafe_enter)(void) @@ -459,7 +466,7 @@ JL_DLLEXPORT void (jl_gc_safe_leave)(int8_t state) } #endif -JL_DLLEXPORT void (jl_gc_safepoint)(void) +JL_DLLEXPORT void jl_gc_safepoint(void) { jl_task_t *ct = jl_current_task; jl_gc_safepoint_(ct->ptls); @@ -470,25 +477,38 @@ JL_DLLEXPORT void (jl_cpu_pause)(void) jl_cpu_pause(); } +JL_DLLEXPORT void (jl_cpu_suspend)(void) +{ + jl_cpu_suspend(); +} + JL_DLLEXPORT void (jl_cpu_wake)(void) { jl_cpu_wake(); } -JL_DLLEXPORT uint64_t jl_cumulative_compile_time_ns_before(void) +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); - return jl_atomic_load_relaxed(&jl_cumulative_compile_time); } -JL_DLLEXPORT uint64_t jl_cumulative_compile_time_ns_after(void) +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); +} + +JL_DLLEXPORT uint64_t jl_cumulative_compile_time_ns(void) +{ return jl_atomic_load_relaxed(&jl_cumulative_compile_time); } +JL_DLLEXPORT uint64_t jl_cumulative_recompile_time_ns(void) +{ + return jl_atomic_load_relaxed(&jl_cumulative_recompile_time); +} + JL_DLLEXPORT void jl_get_fenv_consts(int *ret) { ret[0] = FE_INEXACT; @@ -555,15 +575,15 @@ static NOINLINE int true_main(int argc, char *argv[]) (jl_function_t*)jl_get_global(jl_base_module, jl_symbol("_start")) : NULL; if (start_client) { + jl_task_t *ct = jl_current_task; JL_TRY { - jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; ct->world_age = jl_get_world_counter(); jl_apply(&start_client, 1); ct->world_age = last_age; } JL_CATCH { - jl_no_exc_handler(jl_current_exception()); + jl_no_exc_handler(jl_current_exception(), ct); } return 0; } @@ -669,6 +689,14 @@ static void rr_detach_teleport(void) { JL_DLLEXPORT int jl_repl_entrypoint(int argc, char *argv[]) { +#ifdef USE_TRACY + // Apply e.g. JULIA_TIMING_SUBSYSTEMS="+GC,-INFERENCE" and + // JULIA_TIMING_METADATA_PRINT_LIMIT=20 + jl_timing_apply_env(); + if (getenv("JULIA_WAIT_FOR_TRACY")) + while (!TracyCIsConnected) jl_cpu_pause(); // Wait for connection +#endif + // no-op on Windows, note that the caller must have already converted // from `wchar_t` to `UTF-8` already if we're running on Windows. uv_setup_args(argc, argv); diff --git a/src/jloptions.c b/src/jloptions.c index 1ff4da7c5c10b..4c0b59f811643 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -4,6 +4,7 @@ #include #include "julia.h" +#include "julia_internal.h" #include #include @@ -37,7 +38,10 @@ JL_DLLEXPORT void jl_init_options(void) NULL, // cmds NULL, // image_file (will be filled in below) NULL, // cpu_target ("native", "core2", etc...) + 0, // nthreadpools 0, // nthreads + 0, // ngcthreads + NULL, // nthreads_per_pool 0, // nprocs NULL, // machine_file NULL, // project @@ -48,6 +52,7 @@ JL_DLLEXPORT void jl_init_options(void) JL_OPTIONS_COMPILE_DEFAULT, // compile_enabled 0, // code_coverage 0, // malloc_log + NULL, // tracked_path 2, // opt_level 0, // opt_level_min #ifdef JL_DEBUG_BUILD @@ -67,13 +72,14 @@ JL_DLLEXPORT void jl_init_options(void) JL_OPTIONS_HANDLE_SIGNALS_ON, JL_OPTIONS_USE_SYSIMAGE_NATIVE_CODE_YES, JL_OPTIONS_USE_COMPILED_MODULES_YES, + JL_OPTIONS_USE_PKGIMAGES_YES, NULL, // bind-to NULL, // output-bc NULL, // output-unopt-bc NULL, // output-o NULL, // output-asm NULL, // output-ji - NULL, // output-code_coverage + NULL, // output-code_coverage 0, // incremental 0, // image_file_specified JL_OPTIONS_WARN_SCOPE_ON, // ambiguous scope warning @@ -81,101 +87,128 @@ JL_DLLEXPORT void jl_init_options(void) 0, // rr-detach 0, // strip-metadata 0, // strip-ir + 0, // heap-size-hint }; jl_options_initialized = 1; } -static const char usage[] = "julia [switches] -- [programfile] [args...]\n"; +static const char usage[] = "\n julia [switches] -- [programfile] [args...]\n\n"; static const char opts[] = - " -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 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" // startup options - " --project[={|@.}] Set as the home 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/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}\n" - " Enable or disable incremental precompilation of modules\n\n" + " --project[={|@.}] Set as the home 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}\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" // 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" + " -L, --load Load immediately on all processors\n\n" // parallel options - " -t, --threads {N|auto} Enable N threads; \"auto\" currently sets N to the number of local\n" - " CPU threads but this might change in the future\n" + " -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 Use N threads for GC, set to half of the number of compute threads 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" // interactive options - " -i Interactive mode; REPL runs and isinteractive() is true\n" - " -q, --quiet Quiet startup: no banner, suppress REPL warnings\n" - " --banner={yes|no|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" + " -i, --interactive Interactive mode; REPL runs and `isinteractive()` is true\n" + " -q, --quiet Quiet startup: no banner, suppress REPL warnings\n" + " --banner={yes|no|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 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" // 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 (default level is 2 if unspecified or 3 if used without a level)\n" - " --min-optlevel={0,1,2,3} Set a lower bound on the optimization level (default is 0)\n" - " -g, -g Enable or set the level of debug info generation" + " -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" #ifdef JL_DEBUG_BUILD - " (default level for julia-debug is 2 if unspecified or if used without a level)\n" + " -g, --debug-info=[{0,1,2*}] Set the level of debug info generation in the julia-debug build ($)\n" #else - " (default level is 1 if unspecified or 2 if used without a level)\n" + " -g, --debug-info=[{0,1*,2}] Set the level of debug info generation (level 2 if `-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 overriding @inline declarations\n" + " --check-bounds={yes|no|auto*}\n" + " Emit bounds checks always, never, or respect @inbounds declarations ($)\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 (overrides @polly declaration)\n" #endif - " --math-mode={ieee,fast} Disallow or enable unsafe floating point optimizations (overrides @fastmath declaration)\n\n" // instrumentation options - " --code-coverage={none|user|all}, --code-coverage\n" - " Count executions of source lines (omitting setting is equivalent to \"user\")\n" + " --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=tracefile.info\n" - " Append coverage information to the LCOV tracefile (filename supports format tokens).\n" + " Append coverage information to the LCOV tracefile (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}, --track-allocation\n" - " Count bytes allocated by each source line (omitting setting is equivalent to \"user\")\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" + " --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" ; static const char opts_hidden[] = + "Switches (a '*' marks the default value, if applicable):\n\n" // code generation options - " --compile={yes|no|all|min}Enable or disable JIT compiler, or request exhaustive or minimal compilation\n" + " --compile={yes*|no|all|min}\n" + " Enable or disable JIT compiler, or request exhaustive or minimal compilation\n\n" // compiler output options - " --output-o name Generate an object file (including system image data)\n" - " --output-ji name 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" + " --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" // compiler debugging (see the devdocs for tips on using these options) - " --output-unopt-bc name Generate unoptimized LLVM bitcode (.bc)\n" - " --output-bc name Generate LLVM bitcode (.bc)\n" - " --output-asm name Generate an assembly file (.s)\n" - " --output-incremental=no Generate an incremental output file (rather than complete)\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*}\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\n" - " --image-codegen Force generate code in imaging mode\n" + " Print precompile statements for methods compiled during execution or save to a path\n" + " --image-codegen Force generate code in imaging mode\n" ; JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) @@ -211,6 +244,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) opt_banner, opt_sysimage_native_code, opt_compiled_modules, + opt_pkgimages, opt_machine_file, opt_project, opt_bug_report, @@ -218,15 +252,18 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) opt_rr_detach, opt_strip_metadata, opt_strip_ir, + opt_heap_size_hint, + opt_gc_threads, }; static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:"; static const struct option longopts[] = { // exposed command line options // NOTE: This set of required arguments need to be kept in sync - // with the required arguments defined in base/client.jl `process_options()` + // with the required arguments defined in base/options.jl `struct JLOptions` { "version", no_argument, 0, 'v' }, { "help", no_argument, 0, 'h' }, { "help-hidden", no_argument, 0, opt_help_hidden }, + { "interactive", no_argument, 0, 'i' }, { "quiet", no_argument, 0, 'q' }, { "banner", required_argument, 0, opt_banner }, { "home", required_argument, 0, 'H' }, @@ -236,10 +273,12 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) { "bug-report", required_argument, 0, opt_bug_report }, { "sysimage", required_argument, 0, 'J' }, { "sysimage-native-code", required_argument, 0, opt_sysimage_native_code }, - { "compiled-modules", required_argument, 0, opt_compiled_modules }, + { "compiled-modules",required_argument, 0, opt_compiled_modules }, + { "pkgimages", required_argument, 0, opt_pkgimages }, { "cpu-target", required_argument, 0, 'C' }, { "procs", required_argument, 0, 'p' }, { "threads", required_argument, 0, 't' }, + { "gcthreads", required_argument, 0, opt_gc_threads }, { "machine-file", required_argument, 0, opt_machine_file }, { "project", optional_argument, 0, opt_project }, { "color", required_argument, 0, opt_color }, @@ -250,6 +289,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) { "track-allocation",optional_argument, 0, opt_track_allocation }, { "optimize", optional_argument, 0, 'O' }, { "min-optlevel", optional_argument, 0, opt_optlevel_min }, + { "debug-info", optional_argument, 0, 'g' }, { "check-bounds", required_argument, 0, opt_check_bounds }, { "output-bc", required_argument, 0, opt_output_bc }, { "output-unopt-bc", required_argument, 0, opt_output_unopt_bc }, @@ -273,6 +313,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) { "rr-detach", no_argument, 0, opt_rr_detach }, { "strip-metadata", no_argument, 0, opt_strip_metadata }, { "strip-ir", no_argument, 0, opt_strip_ir }, + { "heap-size-hint", required_argument, 0, opt_heap_size_hint }, { 0, 0, 0, 0 } }; @@ -285,17 +326,9 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) const char **cmds = NULL; int codecov = JL_LOG_NONE; int malloclog = JL_LOG_NONE; - // getopt handles argument parsing up to -- delineator + int pkgimage_explicit = 0; int argc = *argcp; char **argv = *argvp; - if (argc > 0) { - for (int i = 0; i < argc; i++) { - if (!strcmp(argv[i], "--")) { - argc = i; - break; - } - } - } char *endptr; opterr = 0; // suppress getopt warning messages while (1) { @@ -322,11 +355,14 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) c = o->val; goto restart_switch; } - else if (o->val <= 0xff && strchr(shortopts, o->val)) { - jl_errorf("option `-%c/--%s` is missing an argument", o->val, o->name); - } else { - jl_errorf("option `--%s` is missing an argument", o->name); + const char *problem = o->has_arg ? "is missing an argument" : "does not accept an argument"; + if (o->val <= 0xff && strchr(shortopts, o->val)) { + jl_errorf("option `-%c/--%s` %s", o->val, o->name, problem); + } + else { + jl_errorf("option `--%s` %s", o->name, problem); + } } } } @@ -421,6 +457,15 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) else jl_errorf("julia: invalid argument to --compiled-modules={yes|no} (%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 + jl_errorf("julia: invalid argument to --pkgimage={yes|no} (%s)", optarg); + break; case 'C': // cpu-target jl_options.cpu_target = strdup(optarg); if (!jl_options.cpu_target) @@ -428,24 +473,54 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case 't': // threads errno = 0; - if (!strcmp(optarg,"auto")) { + jl_options.nthreadpools = 1; + long nthreads = -1, nthreadsi = 0; + if (!strncmp(optarg, "auto", 4)) { jl_options.nthreads = -1; + if (optarg[4] == ',') { + if (!strncmp(&optarg[5], "auto", 4)) + nthreadsi = 1; + else { + errno = 0; + nthreadsi = strtol(&optarg[5], &endptr, 10); + if (errno != 0 || endptr == &optarg[5] || *endptr != 0 || nthreadsi < 1 || nthreadsi >= INT16_MAX) + jl_errorf("julia: -t,--threads=auto,; m must be an integer >= 1"); + } + jl_options.nthreadpools++; + } } else { - long nthreads = strtol(optarg, &endptr, 10); - if (errno != 0 || optarg == endptr || *endptr != 0 || nthreads < 1 || nthreads >= INT_MAX) - jl_errorf("julia: -t,--threads= must be an integer >= 1"); - jl_options.nthreads = (int)nthreads; + nthreads = strtol(optarg, &endptr, 10); + if (errno != 0 || optarg == endptr || nthreads < 1 || nthreads >= INT16_MAX) + jl_errorf("julia: -t,--threads=[,auto|]; n must be an integer >= 1"); + if (*endptr == ',') { + if (!strncmp(&endptr[1], "auto", 4)) + nthreadsi = 1; + else { + errno = 0; + char *endptri; + nthreadsi = strtol(&endptr[1], &endptri, 10); + if (errno != 0 || endptri == &endptr[1] || *endptri != 0 || nthreadsi < 1 || nthreadsi >= INT16_MAX) + jl_errorf("julia: -t,--threads=,; n and m must be integers >= 1"); + } + jl_options.nthreadpools++; + } + jl_options.nthreads = nthreads + nthreadsi; } + int16_t *ntpp = (int16_t *)malloc_s(jl_options.nthreadpools * sizeof(int16_t)); + ntpp[0] = (int16_t)nthreads; + if (jl_options.nthreadpools == 2) + ntpp[1] = (int16_t)nthreadsi; + jl_options.nthreads_per_pool = ntpp; break; case 'p': // procs errno = 0; if (!strcmp(optarg,"auto")) { - jl_options.nprocs = jl_cpu_threads(); + jl_options.nprocs = jl_effective_threads(); } else { long nprocs = strtol(optarg, &endptr, 10); - if (errno != 0 || optarg == endptr || *endptr != 0 || nprocs < 1 || nprocs >= INT_MAX) + if (errno != 0 || optarg == endptr || *endptr != 0 || nprocs < 1 || nprocs >= INT16_MAX) jl_errorf("julia: -p,--procs= must be an integer >= 1"); jl_options.nprocs = (int)nprocs; } @@ -510,6 +585,10 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) codecov = JL_LOG_ALL; jl_options.output_code_coverage = optarg; } + else if (!strncmp(optarg, "@", 1)) { + codecov = JL_LOG_PATH; + jl_options.tracked_path = optarg + 1; // skip `@` + } else jl_errorf("julia: invalid argument to --code-coverage (%s)", optarg); break; @@ -526,6 +605,10 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) malloclog = JL_LOG_ALL; else if (!strcmp(optarg,"none")) malloclog = JL_LOG_NONE; + else if (!strncmp(optarg, "@", 1)) { + malloclog = JL_LOG_PATH; + jl_options.tracked_path = optarg + 1; // skip `@` + } else jl_errorf("julia: invalid argument to --track-allocation (%s)", optarg); break; @@ -664,7 +747,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) if (!strcmp(optarg,"ieee")) jl_options.fast_math = JL_OPTIONS_FAST_MATH_OFF; else if (!strcmp(optarg,"fast")) - jl_options.fast_math = JL_OPTIONS_FAST_MATH_ON; + jl_options.fast_math = JL_OPTIONS_FAST_MATH_DEFAULT; else if (!strcmp(optarg,"user")) jl_options.fast_math = JL_OPTIONS_FAST_MATH_DEFAULT; else @@ -703,11 +786,59 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) case opt_strip_ir: jl_options.strip_ir = 1; 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); + } + } + if (jl_options.heap_size_hint == 0) + jl_errorf("julia: invalid argument to --heap-size-hint without memory size specified"); + + break; + case opt_gc_threads: + errno = 0; + long ngcthreads = strtol(optarg, &endptr, 10); + if (errno != 0 || optarg == endptr || *endptr != 0 || ngcthreads < 1 || ngcthreads >= INT16_MAX) + jl_errorf("julia: --gcthreads=; n must be an integer >= 1"); + jl_options.ngcthreads = (int16_t)ngcthreads; + break; default: jl_errorf("julia: unhandled option -- %c\n" "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; + } jl_options.code_coverage = codecov; jl_options.malloc_log = malloclog; int proc_args = *argcp < optind ? *argcp : optind; diff --git a/src/jloptions.h b/src/jloptions.h index 0f53bc0f8a4de..c44a8cfe05770 100644 --- a/src/jloptions.h +++ b/src/jloptions.h @@ -13,7 +13,10 @@ typedef struct { const char **cmds; const char *image_file; const char *cpu_target; - int32_t nthreads; + int8_t nthreadpools; + int16_t nthreads; + int16_t ngcthreads; + const int16_t *nthreads_per_pool; int32_t nprocs; const char *machine_file; const char *project; @@ -24,6 +27,7 @@ typedef struct { int8_t compile_enabled; int8_t code_coverage; int8_t malloc_log; + const char *tracked_path; int8_t opt_level; int8_t opt_level_min; int8_t debug_level; @@ -39,6 +43,7 @@ typedef struct { int8_t handle_signals; int8_t use_sysimage_native_code; int8_t use_compiled_modules; + int8_t use_pkgimages; const char *bindto; const char *outputbc; const char *outputunoptbc; @@ -53,6 +58,7 @@ typedef struct { int8_t rr_detach; int8_t strip_metadata; int8_t strip_ir; + uint64_t heap_size_hint; } jl_options_t; #endif diff --git a/src/jltypes.c b/src/jltypes.c index cb9141dd50fd4..1a30df637a706 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -20,6 +20,7 @@ extern "C" { #endif _Atomic(jl_value_t*) cmpswap_names JL_GLOBALLY_ROOTED; +jl_datatype_t *small_typeof[(jl_max_tags << 4) / sizeof(*small_typeof)]; // 16-bit aligned, like the GC // compute empirical max-probe for a given size #define max_probe(size) ((size) <= 1024 ? 16 : (size) >> 6) @@ -37,77 +38,119 @@ static int typeenv_has(jl_typeenv_t *env, jl_tvar_t *v) JL_NOTSAFEPOINT return 0; } -static int layout_uses_free_typevars(jl_value_t *v, jl_typeenv_t *env) +static int typeenv_has_ne(jl_typeenv_t *env, jl_tvar_t *v) JL_NOTSAFEPOINT { - if (jl_typeis(v, jl_tvar_type)) - return !typeenv_has(env, (jl_tvar_t*)v); - if (jl_is_uniontype(v)) - return layout_uses_free_typevars(((jl_uniontype_t*)v)->a, env) || - layout_uses_free_typevars(((jl_uniontype_t*)v)->b, env); - if (jl_is_vararg(v)) { - jl_vararg_t *vm = (jl_vararg_t*)v; - if (vm->T && layout_uses_free_typevars(vm->T, env)) - return 1; - if (vm->N && layout_uses_free_typevars(vm->N, env)) - return 1; - return 0; - } - if (jl_is_unionall(v)) { - jl_unionall_t *ua = (jl_unionall_t*)v; - jl_typeenv_t newenv = { ua->var, NULL, env }; - return layout_uses_free_typevars(ua->body, &newenv); + while (env != NULL) { + if (env->var == v) + return env->val != (jl_value_t*)v; // consider it actually not present if it is bound to itself unchanging + env = env->prev; } - if (jl_is_datatype(v)) { - jl_datatype_t *dt = (jl_datatype_t*)v; - if (dt->layout || dt->isconcretetype || !dt->name->mayinlinealloc) - return 0; - jl_svec_t *types = jl_get_fieldtypes(dt); - size_t i, l = jl_svec_len(types); - for (i = 0; i < l; i++) { - jl_value_t *ft = jl_svecref(types, i); - if (layout_uses_free_typevars(ft, env)) { - // This might be inline-alloc, but we don't know the layout + return 0; +} + + +static int layout_uses_free_typevars(jl_value_t *v, jl_typeenv_t *env) +{ + while (1) { + if (jl_is_typevar(v)) + return !typeenv_has(env, (jl_tvar_t*)v); + while (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + jl_typeenv_t *newenv = (jl_typeenv_t*)alloca(sizeof(jl_typeenv_t)); + newenv->var = ua->var; + newenv->val = NULL; + newenv->prev = env; + env = newenv; + v = ua->body; + } + if (jl_is_datatype(v)) { + jl_datatype_t *dt = (jl_datatype_t*)v; + if (dt->layout || dt->isconcretetype || !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); + if (dt->name == jl_tuple_typename) + // conservative, since we don't want to inline an abstract tuple, + // and we currently declare !has_fixed_layout for these, but that + // means we also won't be able to inline a tuple which is concrete + // except for the use of free type-vars return 1; + jl_svec_t *types = jl_get_fieldtypes(dt); + size_t i, l = jl_svec_len(types); + for (i = 0; i < l; i++) { + jl_value_t *ft = jl_svecref(types, i); + if (layout_uses_free_typevars(ft, env)) + // This might be inline-alloc, but we don't know the layout + return 1; } + return 0; + } + else if (jl_is_uniontype(v)) { + if (layout_uses_free_typevars(((jl_uniontype_t*)v)->a, env)) + return 1; + v = ((jl_uniontype_t*)v)->b; + } + else if (jl_is_vararg(v)) { + jl_vararg_t *vm = (jl_vararg_t*)v; + if (!vm->T) + return 0; + if (vm->N && layout_uses_free_typevars(vm->N, env)) + return 1; + v = vm->T; + } + else { + return 0; } } - return 0; } static int has_free_typevars(jl_value_t *v, jl_typeenv_t *env) JL_NOTSAFEPOINT { - if (jl_typeis(v, jl_tvar_type)) { - return !typeenv_has(env, (jl_tvar_t*)v); - } - if (jl_is_uniontype(v)) - return has_free_typevars(((jl_uniontype_t*)v)->a, env) || - has_free_typevars(((jl_uniontype_t*)v)->b, env); - if (jl_is_vararg(v)) { - jl_vararg_t *vm = (jl_vararg_t*)v; - if (vm->T) { - if (has_free_typevars(vm->T, env)) - return 1; - return vm->N && has_free_typevars(vm->N, env); + while (1) { + if (jl_is_typevar(v)) { + return !typeenv_has(env, (jl_tvar_t*)v); } - } - if (jl_is_unionall(v)) { - jl_unionall_t *ua = (jl_unionall_t*)v; - jl_typeenv_t newenv = { ua->var, NULL, env }; - return has_free_typevars(ua->var->lb, env) || has_free_typevars(ua->var->ub, env) || - has_free_typevars(ua->body, &newenv); - } - if (jl_is_datatype(v)) { - int expect = ((jl_datatype_t*)v)->hasfreetypevars; - if (expect == 0 || env == NULL) - return expect; - size_t i; - for (i = 0; i < jl_nparams(v); i++) { - if (has_free_typevars(jl_tparam(v, i), env)) { + while (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + if (ua->var->lb != jl_bottom_type && has_free_typevars(ua->var->lb, env)) + return 1; + if (ua->var->ub != (jl_value_t*)jl_any_type && has_free_typevars(ua->var->ub, env)) return 1; + jl_typeenv_t *newenv = (jl_typeenv_t*)alloca(sizeof(jl_typeenv_t)); + newenv->var = ua->var; + newenv->val = NULL; + newenv->prev = env; + env = newenv; + v = ua->body; + } + if (jl_is_datatype(v)) { + int expect = ((jl_datatype_t*)v)->hasfreetypevars; + if (expect == 0 || env == NULL) + return expect; + size_t i; + for (i = 0; i < jl_nparams(v); i++) { + if (has_free_typevars(jl_tparam(v, i), env)) + return 1; } + return 0; + } + else if (jl_is_uniontype(v)) { + if (has_free_typevars(((jl_uniontype_t*)v)->a, env)) + return 1; + v = ((jl_uniontype_t*)v)->b; + } + else if (jl_is_vararg(v)) { + jl_vararg_t *vm = (jl_vararg_t*)v; + if (!vm->T) + return 0; + if (vm->N && has_free_typevars(vm->N, env)) + return 1; + v = vm->T; + } + else { + return 0; } } - return 0; } JL_DLLEXPORT int jl_has_free_typevars(jl_value_t *v) JL_NOTSAFEPOINT @@ -117,36 +160,48 @@ JL_DLLEXPORT int jl_has_free_typevars(jl_value_t *v) JL_NOTSAFEPOINT static void find_free_typevars(jl_value_t *v, jl_typeenv_t *env, jl_array_t *out) { - if (jl_typeis(v, jl_tvar_type)) { - if (!typeenv_has(env, (jl_tvar_t*)v)) - jl_array_ptr_1d_push(out, v); - } - else if (jl_is_uniontype(v)) { - find_free_typevars(((jl_uniontype_t*)v)->a, env, out); - find_free_typevars(((jl_uniontype_t*)v)->b, env, out); - } - else if (jl_is_vararg(v)) { - jl_vararg_t *vm = (jl_vararg_t *)v; - if (vm->T) { - find_free_typevars(vm->T, env, out); - if (vm->N) { + while (1) { + if (jl_is_typevar(v)) { + if (!typeenv_has(env, (jl_tvar_t*)v)) + jl_array_ptr_1d_push(out, v); + return; + } + while (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + if (ua->var->lb != jl_bottom_type) + find_free_typevars(ua->var->lb, env, out); + if (ua->var->ub != (jl_value_t*)jl_any_type) + find_free_typevars(ua->var->ub, env, out); + jl_typeenv_t *newenv = (jl_typeenv_t*)alloca(sizeof(jl_typeenv_t)); + newenv->var = ua->var; + newenv->val = NULL; + newenv->prev = env; + env = newenv; + v = ua->body; + } + if (jl_is_datatype(v)) { + if (!((jl_datatype_t*)v)->hasfreetypevars) + return; + size_t i; + for (i = 0; i < jl_nparams(v); i++) + find_free_typevars(jl_tparam(v, i), env, out); + return; + } + else if (jl_is_uniontype(v)) { + find_free_typevars(((jl_uniontype_t*)v)->a, env, out); + v = ((jl_uniontype_t*)v)->b; + } + else if (jl_is_vararg(v)) { + jl_vararg_t *vm = (jl_vararg_t *)v; + if (!vm->T) + return; + if (vm->N) // this swap the visited order, but we don't mind it find_free_typevars(vm->N, env, out); - } + v = vm->T; } - } - else if (jl_is_unionall(v)) { - jl_unionall_t *ua = (jl_unionall_t*)v; - jl_typeenv_t newenv = { ua->var, NULL, env }; - find_free_typevars(ua->var->lb, env, out); - find_free_typevars(ua->var->ub, env, out); - find_free_typevars(ua->body, &newenv, out); - } - else if (jl_is_datatype(v)) { - if (!((jl_datatype_t*)v)->hasfreetypevars) + else { return; - size_t i; - for (i=0; i < jl_nparams(v); i++) - find_free_typevars(jl_tparam(v,i), env, out); + } } } @@ -162,41 +217,55 @@ 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 { - if (jl_typeis(v, jl_tvar_type)) - return typeenv_has(env, (jl_tvar_t*)v); - if (jl_is_uniontype(v)) - return jl_has_bound_typevars(((jl_uniontype_t*)v)->a, env) || - jl_has_bound_typevars(((jl_uniontype_t*)v)->b, env); - if (jl_is_vararg(v)) { - jl_vararg_t *vm = (jl_vararg_t *)v; - return vm->T && (jl_has_bound_typevars(vm->T, env) || - (vm->N && jl_has_bound_typevars(vm->N, env))); - } - if (jl_is_unionall(v)) { - jl_unionall_t *ua = (jl_unionall_t*)v; - if (jl_has_bound_typevars(ua->var->lb, env) || jl_has_bound_typevars(ua->var->ub, env)) - return 1; - jl_typeenv_t *te = env; - while (te != NULL) { - if (te->var == ua->var) - break; - te = te->prev; + while (1) { + if (jl_is_typevar(v)) { + return typeenv_has_ne(env, (jl_tvar_t*)v); } - if (te) te->var = NULL; // temporarily remove this var from env - int ans = jl_has_bound_typevars(ua->body, env); - if (te) te->var = ua->var; - return ans; - } - if (jl_is_datatype(v)) { - if (!((jl_datatype_t*)v)->hasfreetypevars) + while (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + if (ua->var->lb != jl_bottom_type && jl_has_bound_typevars(ua->var->lb, env)) + return 1; + if (ua->var->ub != (jl_value_t*)jl_any_type && jl_has_bound_typevars(ua->var->ub, env)) + return 1; + // Temporarily remove this var from env if necessary + // Note that te might be bound more than once in the env, so + // we remove it by setting it to itself in a new env. + if (typeenv_has_ne(env, ua->var)) { + jl_typeenv_t *newenv = (jl_typeenv_t*)alloca(sizeof(jl_typeenv_t)); + newenv->var = ua->var; + newenv->val = (jl_value_t*)ua->var; + newenv->prev = env; + env = newenv; + } + v = ua->body; + } + if (jl_is_datatype(v)) { + if (!((jl_datatype_t*)v)->hasfreetypevars) + return 0; + size_t i; + for (i = 0; i < jl_nparams(v); i++) { + if (jl_has_bound_typevars(jl_tparam(v, i), env)) + return 1; + } return 0; - size_t i; - for (i=0; i < jl_nparams(v); i++) { - if (jl_has_bound_typevars(jl_tparam(v,i), env)) + } + else if (jl_is_uniontype(v)) { + if (jl_has_bound_typevars(((jl_uniontype_t*)v)->a, env)) return 1; + v = ((jl_uniontype_t*)v)->b; + } + else if (jl_is_vararg(v)) { + jl_vararg_t *vm = (jl_vararg_t *)v; + if (!vm->T) + return 0; + if (vm->N && jl_has_bound_typevars(vm->N, env)) + return 1; + v = vm->T; + } + else { + return 0; } } - return 0; } JL_DLLEXPORT int jl_has_typevar(jl_value_t *t, jl_tvar_t *v) JL_NOTSAFEPOINT @@ -225,8 +294,10 @@ int jl_has_fixed_layout(jl_datatype_t *dt) return 1; if (dt->name->abstract) return 0; - if (jl_is_tuple_type(dt) || jl_is_namedtuple_type(dt)) - return 0; // TODO: relax more? + if (dt->name == jl_namedtuple_typename) + return !layout_uses_free_typevars(jl_tparam0(dt), NULL) && !layout_uses_free_typevars(jl_tparam1(dt), NULL); + if (dt->name == jl_tuple_typename) + return 0; jl_svec_t *types = jl_get_fieldtypes(dt); size_t i, l = jl_svec_len(types); for (i = 0; i < l; i++) { @@ -271,28 +342,15 @@ JL_DLLEXPORT int jl_get_size(jl_value_t *val, size_t *pnt) // --- type union --- -static int count_union_components(jl_value_t **types, size_t n) -{ - size_t i, c=0; - for(i=0; i < n; i++) { - jl_value_t *e = types[i]; - if (jl_is_uniontype(e)) { - jl_uniontype_t *u = (jl_uniontype_t*)e; - c += count_union_components(&u->a, 1); - c += count_union_components(&u->b, 1); - } - else { - c++; - } - } - return c; -} - int jl_count_union_components(jl_value_t *v) { - if (!jl_is_uniontype(v)) return 1; - jl_uniontype_t *u = (jl_uniontype_t*)v; - return jl_count_union_components(u->a) + jl_count_union_components(u->b); + size_t c = 0; + while (jl_is_uniontype(v)) { + jl_uniontype_t *u = (jl_uniontype_t*)v; + c += jl_count_union_components(u->a); + v = u->b; + } + return c + 1; } // Return the `*pi`th element of a nested type union, according to a @@ -300,16 +358,16 @@ int jl_count_union_components(jl_value_t *v) // considered an "element". `*pi` is destroyed in the process. static jl_value_t *nth_union_component(jl_value_t *v, int *pi) JL_NOTSAFEPOINT { - if (!jl_is_uniontype(v)) { - if (*pi == 0) - return v; - (*pi)--; - return NULL; + while (jl_is_uniontype(v)) { + jl_uniontype_t *u = (jl_uniontype_t*)v; + jl_value_t *a = nth_union_component(u->a, pi); + if (a) return a; + v = u->b; } - jl_uniontype_t *u = (jl_uniontype_t*)v; - jl_value_t *a = nth_union_component(u->a, pi); - if (a) return a; - return nth_union_component(u->b, pi); + if (*pi == 0) + return v; + (*pi)--; + return NULL; } jl_value_t *jl_nth_union_component(jl_value_t *v, int i) JL_NOTSAFEPOINT @@ -320,12 +378,11 @@ jl_value_t *jl_nth_union_component(jl_value_t *v, int i) JL_NOTSAFEPOINT // inverse of jl_nth_union_component int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned *nth) JL_NOTSAFEPOINT { - if (jl_is_uniontype(haystack)) { - if (jl_find_union_component(((jl_uniontype_t*)haystack)->a, needle, nth)) - return 1; - if (jl_find_union_component(((jl_uniontype_t*)haystack)->b, needle, nth)) + while (jl_is_uniontype(haystack)) { + jl_uniontype_t *u = (jl_uniontype_t*)haystack; + if (jl_find_union_component(u->a, needle, nth)) return 1; - return 0; + haystack = u->b; } if (needle == haystack) return 1; @@ -333,23 +390,6 @@ int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned * return 0; } -static void flatten_type_union(jl_value_t **types, size_t n, jl_value_t **out, size_t *idx) JL_NOTSAFEPOINT -{ - size_t i; - for(i=0; i < n; i++) { - jl_value_t *e = types[i]; - if (jl_is_uniontype(e)) { - jl_uniontype_t *u = (jl_uniontype_t*)e; - flatten_type_union(&u->a, 1, out, idx); - flatten_type_union(&u->b, 1, out, idx); - } - else { - out[*idx] = e; - (*idx)++; - } - } -} - STATIC_INLINE const char *datatype_module_name(jl_value_t *t) JL_NOTSAFEPOINT { if (((jl_datatype_t*)t)->name->module == NULL) @@ -410,10 +450,8 @@ static int datatype_name_cmp(jl_value_t *a, jl_value_t *b) JL_NOTSAFEPOINT // sort singletons first, then DataTypes, then UnionAlls, // ties broken alphabetically including module name & type parameters -static int union_sort_cmp(const void *ap, const void *bp) JL_NOTSAFEPOINT +static int union_sort_cmp(jl_value_t *a, jl_value_t *b) JL_NOTSAFEPOINT { - jl_value_t *a = *(jl_value_t**)ap; - jl_value_t *b = *(jl_value_t**)bp; if (a == NULL) return b == NULL ? 0 : 1; if (b == NULL) @@ -448,27 +486,91 @@ static int union_sort_cmp(const void *ap, const void *bp) JL_NOTSAFEPOINT } } +static int count_union_components(jl_value_t **types, size_t n, int widen) +{ + size_t i, c = 0; + for (i = 0; i < n; i++) { + jl_value_t *e = types[i]; + while (jl_is_uniontype(e)) { + jl_uniontype_t *u = (jl_uniontype_t*)e; + c += count_union_components(&u->a, 1, widen); + e = u->b; + } + if (widen && jl_is_unionall(e) && jl_is_uniontype(jl_unwrap_unionall(e))) { + jl_uniontype_t *u = (jl_uniontype_t*)jl_unwrap_unionall(e); + c += count_union_components(&u->a, 2, widen); + } + else { + c++; + } + } + return c; +} + +static void flatten_type_union(jl_value_t **types, size_t n, jl_value_t **out, size_t *idx, int widen) +{ + size_t i; + for (i = 0; i < n; i++) { + jl_value_t *e = types[i]; + while (jl_is_uniontype(e)) { + jl_uniontype_t *u = (jl_uniontype_t*)e; + flatten_type_union(&u->a, 1, out, idx, widen); + e = u->b; + } + if (widen && jl_is_unionall(e) && jl_is_uniontype(jl_unwrap_unionall(e))) { + // flatten this UnionAll into place by switching the union and unionall + jl_uniontype_t *u = (jl_uniontype_t*)jl_unwrap_unionall(e); + size_t old_idx = 0; + flatten_type_union(&u->a, 2, out, idx, widen); + for (; old_idx < *idx; old_idx++) + out[old_idx] = jl_rewrap_unionall(out[old_idx], e); + } + else { + out[*idx] = e; + (*idx)++; + } + } +} + + +static void isort_union(jl_value_t **a, size_t len) JL_NOTSAFEPOINT +{ + size_t i, j; + for (i = 1; i < len; i++) { + jl_value_t *x = a[i]; + for (j = i; j > 0; j--) { + jl_value_t *y = a[j - 1]; + if (!(union_sort_cmp(x, y) < 0)) + break; + a[j] = y; + } + a[j] = x; + } +} + JL_DLLEXPORT jl_value_t *jl_type_union(jl_value_t **ts, size_t n) { - if (n == 0) return (jl_value_t*)jl_bottom_type; + if (n == 0) + return (jl_value_t*)jl_bottom_type; size_t i; - for(i=0; i < n; i++) { + for (i = 0; i < n; i++) { jl_value_t *pi = ts[i]; if (!(jl_is_type(pi) || jl_is_typevar(pi))) jl_type_error("Union", (jl_value_t*)jl_type_type, pi); } - if (n == 1) return ts[0]; + if (n == 1) + return ts[0]; - size_t nt = count_union_components(ts, n); + size_t nt = count_union_components(ts, n, 1); jl_value_t **temp; JL_GC_PUSHARGS(temp, nt+1); size_t count = 0; - flatten_type_union(ts, n, temp, &count); + flatten_type_union(ts, n, temp, &count, 1); assert(count == nt); size_t j; - for(i=0; i < nt; i++) { - int has_free = temp[i]!=NULL && jl_has_free_typevars(temp[i]); - for(j=0; j < nt; j++) { + for (i = 0; i < nt; i++) { + 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 || @@ -480,7 +582,7 @@ JL_DLLEXPORT jl_value_t *jl_type_union(jl_value_t **ts, size_t n) } } } - qsort(temp, nt, sizeof(jl_value_t*), union_sort_cmp); + isort_union(temp, nt); jl_value_t **ptu = &temp[nt]; *ptu = jl_bottom_type; int k; @@ -498,6 +600,231 @@ 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) +{ + int subab = 0, subba = 0; + if (jl_egal(a, b)) { + subab = subba = 1; + } + else if (a == jl_bottom_type || b == (jl_value_t*)jl_any_type) { + subab = 1; + } + 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 (jl_is_type_type(a) && jl_is_type_type(b) && + jl_typeof(jl_tparam0(a)) != jl_typeof(jl_tparam0(b))) { + // issue #24521: don't merge Type{T} where typeof(T) varies + } + else if (jl_typeof(a) == jl_typeof(b) && jl_types_egal(a, b)) { + subab = subba = 1; + } + else { + subab = jl_subtype(a, b); + subba = jl_subtype(b, a); + } + return subab | (subba<<1); +} + +jl_value_t *simple_union(jl_value_t *a, jl_value_t *b) +{ + size_t nta = count_union_components(&a, 1, 1); + size_t ntb = count_union_components(&b, 1, 1); + size_t nt = nta + ntb; + jl_value_t **temp; + JL_GC_PUSHARGS(temp, nt+1); + size_t count = 0; + flatten_type_union(&a, 1, temp, &count, 1); + flatten_type_union(&b, 1, temp, &count, 1); + assert(count == nt); + size_t i, j; + size_t ra = nta, rb = ntb; + // 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]); + 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 subab = subs & 1, subba = subs >> 1; + if (subab) { + temp[i] = NULL; + if (!subba) ra = 0; + count--; + break; + } + else if (subba) { + temp[j] = NULL; + rb = 0; + count--; + } + } + } + if (count == ra) { + JL_GC_POP(); + return a; + } + if (count == rb) { + JL_GC_POP(); + return b; + } + // then remove self-redundancy + for (i = 0; i < nt; i++) { + int has_free = temp[i] != NULL && jl_has_free_typevars(temp[i]); + size_t jmin = i < nta ? 0 : nta; + 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]))) { + temp[i] = NULL; + } + } + } + } + isort_union(temp, nt); + temp[nt] = jl_bottom_type; + size_t k; + for (k = nt; k-- > 0; ) { + if (temp[k] != NULL) { + if (temp[nt] == jl_bottom_type) + temp[nt] = temp[k]; + else + temp[nt] = jl_new_struct(jl_uniontype_type, temp[k], temp[nt]); + } + } + assert(temp[nt] != NULL); + jl_value_t *tu = temp[nt]; + JL_GC_POP(); + return tu; +} + +int obviously_disjoint(jl_value_t *a, jl_value_t *b, int specificity); + +jl_value_t *simple_intersect(jl_value_t *a, jl_value_t *b, int overesi) +{ + // Unlike `Union`, we don't unwrap `UnionAll` here to avoid possible widening. + size_t nta = count_union_components(&a, 1, 0); + size_t ntb = count_union_components(&b, 1, 0); + size_t nt = nta + ntb; + jl_value_t **temp; + JL_GC_PUSHARGS(temp, nt+1); + size_t count = 0; + flatten_type_union(&a, 1, temp, &count, 0); + flatten_type_union(&b, 1, temp, &count, 0); + assert(count == nt); + size_t i, j; + int8_t *stemp = (int8_t *)alloca(count); + // first remove disjoint elements. + memset(stemp, 0, count); + for (i = 0; i < nta; i++) { + int hasfree = jl_has_free_typevars(temp[i]); + for (j = nta; j < nt; j++) { + if (!stemp[i] || !stemp[j]) { + int intersect = !hasfree && !jl_has_free_typevars(temp[j]); + if (!(intersect ? jl_has_empty_intersection(temp[i], temp[j]) : obviously_disjoint(temp[i], temp[j], 0))) + stemp[i] = stemp[j] = 1; + } + } + } + for (i = 0; i < nt; i++) { + temp[i] = stemp[i] ? temp[i] : NULL; + } + // then check subtyping. + // stemp[k] == -1 : ∃i temp[k] >:ₛ temp[i] + // stemp[k] == 1 : ∃i temp[k] == temp[i] + // stemp[k] == 2 : ∃i temp[k] <:ₛ temp[i] + memset(stemp, 0, count); + int all_disjoint = 1, subs[2] = {1, 1}, rs[2] = {1, 1}; + for (i = 0; i < nta; i++) { + if (temp[i] == NULL) continue; + all_disjoint = 0; + int hasfree = 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 subab = subs & 1, subba = subs >> 1; + if (subba && !subab) { + stemp[i] = -1; + if (stemp[j] >= 0) stemp[j] = 2; + } + else if (subab && !subba) { + stemp[j] = -1; + if (stemp[i] >= 0) stemp[i] = 2; + } + else if (subs) { + if (stemp[i] == 0) stemp[i] = 1; + if (stemp[j] == 0) stemp[j] = 1; + } + } + } + if (!all_disjoint) { + for (i = 0; i < nt; i++) { + subs[i >= nta] &= (temp[i] == NULL || stemp[i] > 0); + rs[i >= nta] &= (temp[i] != NULL && stemp[i] > 0); + } + // return a(b) if a(b) <: b(a) + if (rs[0]) { + JL_GC_POP(); + return a; + } + if (rs[1]) { + JL_GC_POP(); + return b; + } + } + // return `Union{}` for `merge_env` if we can't prove `<:` or `>:` + if (all_disjoint || (!overesi && !subs[0] && !subs[1])) { + JL_GC_POP(); + return jl_bottom_type; + } + nt = subs[0] ? nta : subs[1] ? nt : nt; + i = subs[0] ? 0 : subs[1] ? nta : 0; + count = nt - i; + if (!subs[0] && !subs[1]) { + // prepare for over estimation + // only preserve `a` with strict <:, but preserve `b` without strict >: + for (j = 0; j < nt; j++) { + if (stemp[j] < (j < nta ? 2 : 0)) + temp[j] = NULL; + } + } + isort_union(&temp[i], count); + temp[nt] = jl_bottom_type; + size_t k; + for (k = nt; k-- > i; ) { + if (temp[k] != NULL) { + if (temp[nt] == jl_bottom_type) + temp[nt] = temp[k]; + else + temp[nt] = jl_new_struct(jl_uniontype_type, temp[k], temp[nt]); + } + } + assert(temp[nt] != NULL); + jl_value_t *tu = temp[nt]; + JL_GC_POP(); + return tu; +} + // unionall types ------------------------------------------------------------- JL_DLLEXPORT jl_value_t *jl_type_unionall(jl_tvar_t *v, jl_value_t *body) @@ -505,8 +832,8 @@ JL_DLLEXPORT jl_value_t *jl_type_unionall(jl_tvar_t *v, jl_value_t *body) if (jl_is_vararg(body)) { if (jl_options.depwarn) { if (jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) - jl_error("Wrapping `Vararg` directly in UnionAll is deprecated (wrap the tuple instead)."); - jl_printf(JL_STDERR, "WARNING: Wrapping `Vararg` directly in UnionAll is deprecated (wrap the tuple instead).\n"); + jl_error("Wrapping `Vararg` directly in UnionAll is deprecated (wrap the tuple instead).\nYou may need to write `f(x::Vararg{T})` rather than `f(x::Vararg{<:T})` or `f(x::Vararg{T}) where T` instead of `f(x::Vararg{T} where T)`."); + jl_printf(JL_STDERR, "WARNING: Wrapping `Vararg` directly in UnionAll is deprecated (wrap the tuple instead).\nYou may need to write `f(x::Vararg{T})` rather than `f(x::Vararg{<:T})` or `f(x::Vararg{T}) where T` instead of `f(x::Vararg{T} where T)`.\n"); } jl_vararg_t *vm = (jl_vararg_t*)body; int T_has_tv = vm->T && jl_has_typevar(vm->T, v); @@ -548,7 +875,7 @@ JL_DLLEXPORT jl_value_t *jl_type_unionall(jl_tvar_t *v, jl_value_t *body) static int typekey_eq(jl_datatype_t *tt, jl_value_t **key, size_t n) { size_t j; - // TOOD: This shouldn't be necessary + // TODO: This shouldn't be necessary JL_GC_PROMISE_ROOTED(tt); size_t tnp = jl_nparams(tt); if (n != tnp) @@ -556,8 +883,8 @@ static int typekey_eq(jl_datatype_t *tt, jl_value_t **key, size_t n) if (tt->name == jl_type_typename) { // for Type{T}, require `typeof(T)` to match also, to avoid incorrect // dispatch from changing the type of something. - // this should work because `Type`s don't have uids, and aren't the - // direct tags of values so we don't rely on pointer equality. + // this should work because `Type`s don't need unique pointers, and aren't the + // direct tags of values (concrete) so we don't rely on pointer equality. jl_value_t *kj = key[0]; jl_value_t *tj = jl_tparam0(tt); return (kj == tj || (jl_typeof(tj) == jl_typeof(kj) && jl_types_equal(tj, kj))); @@ -566,11 +893,14 @@ static int typekey_eq(jl_datatype_t *tt, jl_value_t **key, size_t n) jl_value_t *kj = key[j]; jl_value_t *tj = jl_svecref(tt->parameters, j); if (tj != kj) { - // require exact same Type{T}. see e.g. issue #22842 - if (jl_is_type_type(tj) || jl_is_type_type(kj)) - return 0; - if ((jl_is_concrete_type(tj) || jl_is_concrete_type(kj)) && - jl_type_equality_is_identity(tj, kj)) + if (tt->name == jl_tuple_typename) { + // require exact same Type{T} in covariant context. see e.g. issue #22842 + // this should work because `Tuple{Type}`s don't need unique pointers, and aren't the + // direct tags of values (concrete) so we don't rely on pointer equality. + if (jl_is_type_type(tj) || jl_is_type_type(kj)) + return 0; + } + if (jl_type_equality_is_identity(tj, kj)) return 0; if (!jl_types_equal(tj, kj)) return 0; @@ -584,7 +914,7 @@ static int typekey_eq(jl_datatype_t *tt, jl_value_t **key, size_t n) static int typekeyvalue_eq(jl_datatype_t *tt, jl_value_t *key1, jl_value_t **key, size_t n, int leaf) { size_t j; - // TOOD: This shouldn't be necessary + // TODO: This shouldn't be necessary JL_GC_PROMISE_ROOTED(tt); size_t tnp = jl_nparams(tt); if (n != tnp) @@ -632,9 +962,9 @@ static jl_datatype_t *lookup_type_set(jl_svec_t *cache, jl_value_t **key, size_t size_t iter = 0; do { jl_datatype_t *val = jl_atomic_load_relaxed(&tab[index]); - if (val == NULL) + if ((jl_value_t*)val == jl_nothing) return NULL; - if ((jl_value_t*)val != jl_nothing && val->hash == hv && typekey_eq(val, key, n)) + if (val->hash == hv && typekey_eq(val, key, n)) return val; index = (index + 1) & (sz - 1); iter++; @@ -655,9 +985,9 @@ static jl_datatype_t *lookup_type_setvalue(jl_svec_t *cache, jl_value_t *key1, j size_t iter = 0; do { jl_datatype_t *val = jl_atomic_load_relaxed(&tab[index]); - if (val == NULL) + if ((jl_value_t*)val == jl_nothing) return NULL; - if ((jl_value_t*)val != jl_nothing && val->hash == hv && typekeyvalue_eq(val, key1, key, n, leaf)) + if (val->hash == hv && typekeyvalue_eq(val, key1, key, n, leaf)) return val; index = (index + 1) & (sz - 1); iter++; @@ -677,7 +1007,7 @@ static ssize_t lookup_type_idx_linear(jl_svec_t *cache, jl_value_t **key, size_t ssize_t i; for (i = 0; i < cl; i++) { jl_datatype_t *tt = jl_atomic_load_relaxed(&data[i]); - if (tt == NULL) + if ((jl_value_t*)tt == jl_nothing) return ~i; if (typekey_eq(tt, key, n)) return i; @@ -694,7 +1024,7 @@ static ssize_t lookup_type_idx_linearvalue(jl_svec_t *cache, jl_value_t *key1, j ssize_t i; for (i = 0; i < cl; i++) { jl_datatype_t *tt = jl_atomic_load_relaxed(&data[i]); - if (tt == NULL) + if ((jl_value_t*)tt == jl_nothing) return ~i; if (typekeyvalue_eq(tt, key1, key, n, 1)) return i; @@ -704,7 +1034,13 @@ static ssize_t lookup_type_idx_linearvalue(jl_svec_t *cache, jl_value_t *key1, j static jl_value_t *lookup_type(jl_typename_t *tn JL_PROPAGATES_ROOT, jl_value_t **key, size_t n) { - JL_TIMING(TYPE_CACHE_LOOKUP); + JL_TIMING(TYPE_CACHE_LOOKUP, TYPE_CACHE_LOOKUP); + if (tn == jl_type_typename) { + assert(n == 1); + jl_value_t *uw = jl_unwrap_unionall(key[0]); + if (jl_is_datatype(uw) && key[0] == ((jl_datatype_t*)uw)->name->wrapper) + return jl_atomic_load_acquire(&((jl_datatype_t*)uw)->name->Typeofwrapper); + } unsigned hv = typekey_hash(tn, key, n, 0); if (hv) { jl_svec_t *cache = jl_atomic_load_relaxed(&tn->cache); @@ -719,7 +1055,7 @@ static jl_value_t *lookup_type(jl_typename_t *tn JL_PROPAGATES_ROOT, jl_value_t static jl_value_t *lookup_typevalue(jl_typename_t *tn, jl_value_t *key1, jl_value_t **key, size_t n, int leaf) { - JL_TIMING(TYPE_CACHE_LOOKUP); + JL_TIMING(TYPE_CACHE_LOOKUP, TYPE_CACHE_LOOKUP); unsigned hv = typekeyvalue_hash(tn, key1, key, n, leaf); if (hv) { jl_svec_t *cache = jl_atomic_load_relaxed(&tn->cache); @@ -746,7 +1082,7 @@ static int cache_insert_type_set_(jl_svec_t *a, jl_datatype_t *val, uint_t hv, i size_t maxprobe = max_probe(sz); do { jl_value_t *tab_i = jl_atomic_load_relaxed(&tab[index]); - if (tab_i == NULL || tab_i == jl_nothing) { + if (tab_i == jl_nothing) { if (atomic) jl_atomic_store_release(&tab[index], (jl_value_t*)val); else @@ -761,8 +1097,6 @@ static int cache_insert_type_set_(jl_svec_t *a, jl_datatype_t *val, uint_t hv, i return 0; } -static jl_svec_t *cache_rehash_set(jl_svec_t *a, size_t newsz); - static void cache_insert_type_set(jl_datatype_t *val, uint_t hv) { jl_svec_t *a = jl_atomic_load_relaxed(&val->name->cache); @@ -789,17 +1123,17 @@ static void cache_insert_type_set(jl_datatype_t *val, uint_t hv) } } -static jl_svec_t *cache_rehash_set(jl_svec_t *a, size_t newsz) +jl_svec_t *cache_rehash_set(jl_svec_t *a, size_t newsz) { jl_value_t **ol = jl_svec_data(a); size_t sz = jl_svec_len(a); while (1) { size_t i; - jl_svec_t *newa = jl_alloc_svec(newsz); + jl_svec_t *newa = jl_svec_fill(newsz, jl_nothing); JL_GC_PUSH1(&newa); for (i = 0; i < sz; i += 1) { jl_value_t *val = ol[i]; - if (val != NULL && val != jl_nothing) { + if (val != jl_nothing) { uint_t hv = ((jl_datatype_t*)val)->hash; if (!cache_insert_type_set_(newa, (jl_datatype_t*)val, hv, 0)) { break; @@ -818,15 +1152,15 @@ static void cache_insert_type_linear(jl_datatype_t *type, ssize_t insert_at) jl_svec_t *cache = jl_atomic_load_relaxed(&type->name->linearcache); assert(jl_is_svec(cache)); size_t n = jl_svec_len(cache); - if (n == 0 || jl_svecref(cache, n - 1) != NULL) { - jl_svec_t *nc = jl_alloc_svec(n < 8 ? 8 : (n*3)>>1); + if (n == 0 || jl_svecref(cache, n - 1) != jl_nothing) { + jl_svec_t *nc = jl_svec_fill(n < 4 ? 4 : n * 2, jl_nothing); memcpy(jl_svec_data(nc), jl_svec_data(cache), sizeof(void*) * n); 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) == NULL); + assert(jl_svecref(cache, insert_at) == jl_nothing); jl_svecset(cache, insert_at, (jl_value_t*)type); // todo: make this an atomic-store } @@ -842,10 +1176,20 @@ static int is_cacheable(jl_datatype_t *type) void jl_cache_type_(jl_datatype_t *type) { - JL_TIMING(TYPE_CACHE_INSERT); + JL_TIMING(TYPE_CACHE_INSERT, TYPE_CACHE_INSERT); assert(is_cacheable(type)); jl_value_t **key = jl_svec_data(type->parameters); int n = jl_svec_len(type->parameters); + if (type->name == jl_type_typename) { + assert(n == 1); + jl_value_t *uw = jl_unwrap_unionall(key[0]); + if (jl_is_datatype(uw) && key[0] == ((jl_datatype_t*)uw)->name->wrapper) { + jl_typename_t *tn2 = ((jl_datatype_t*)uw)->name; + jl_atomic_store_release(&tn2->Typeofwrapper, (jl_value_t*)type); + jl_gc_wb(tn2, type); + return; + } + } unsigned hv = typekey_hash(type->name, key, n, 0); if (hv) { assert(hv == type->hash); @@ -866,16 +1210,88 @@ jl_datatype_t *jl_lookup_cache_type_(jl_datatype_t *type) return (jl_datatype_t*)lookup_type(type->name, key, n); } -JL_DLLEXPORT int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) +// compute whether kj might actually be a subtype of something in the cache +// (which otherwise would normally be comparable with pointer-egal) +static int maybe_subtype_of_cache(jl_value_t *kj, int covariant) JL_NOTSAFEPOINT { - if (t1 == t2) + jl_value_t *uw = jl_is_unionall(kj) ? jl_unwrap_unionall(kj) : kj; + if (jl_is_datatype(uw)) { + jl_datatype_t *dt = (jl_datatype_t*)uw; + return dt->maybe_subtype_of_cache; + } + else if (jl_is_uniontype(uw)) { + int ca = maybe_subtype_of_cache(((jl_uniontype_t*)uw)->a, covariant); + int cb = maybe_subtype_of_cache(((jl_uniontype_t*)uw)->b, covariant); + return ca && cb; + } + else if (uw == jl_bottom_type) { return 1; - if (!jl_is_datatype(t1) || !jl_is_datatype(t2)) - return 0; - jl_datatype_t *dt1 = (jl_datatype_t *) t1; - jl_datatype_t *dt2 = (jl_datatype_t *) t2; + } + else if (jl_is_typevar(uw) && !covariant) { // assume Tuple's bounds are always degenerate + // TODO: improve this bound if we can prove that typeintersect(lb,ub) is a leaftype + jl_tvar_t *tv = (jl_tvar_t*)uw; + return tv->lb == tv->ub || + tv->lb != jl_bottom_type; + } + return 1; +} + +// compute whether kj might have a supertype which is actually concrete +static int has_concrete_supertype(jl_value_t *kj) JL_NOTSAFEPOINT +{ + jl_value_t *uw = jl_is_unionall(kj) ? jl_unwrap_unionall(kj) : kj; + if (jl_is_datatype(uw)) { + jl_datatype_t *dt = (jl_datatype_t*)uw; + if (dt->name->abstract && dt->name != jl_type_typename) + return 0; + if (!dt->maybe_subtype_of_cache) + return 0; + if (dt->name == jl_tuple_typename) { + // check tuple parameters recursively for has_concrete_supertype + size_t i, n = jl_nparams(dt); + for (i = 0; i < n; i++) { + jl_value_t *p = jl_tparam(dt, i); + if (jl_is_vararg(p)) + p = jl_unwrap_vararg(p); + if (!has_concrete_supertype(p)) + return 0; + } + } + return 1; + } + else if (jl_is_uniontype(uw)) { + int ca = has_concrete_supertype(((jl_uniontype_t*)uw)->a); + int cb = has_concrete_supertype(((jl_uniontype_t*)uw)->b); + return ca && cb; + } + else if (uw == jl_bottom_type) { + return 1; + } + else if (jl_is_typevar(uw)) { + jl_tvar_t *tv = (jl_tvar_t*)uw; + return has_concrete_supertype(tv->ub); + } + return 0; +} - return dt1->cached_by_hash == dt2->cached_by_hash; +int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL_NOTSAFEPOINT +{ + int c1 = jl_is_concrete_type(t1); + int c2 = jl_is_concrete_type(t2); + if (c1 && c2) { + if (((jl_datatype_t*)t1)->name != jl_tuple_typename) + return 1; + if (((jl_datatype_t*)t2)->name != jl_tuple_typename) + return 1; + if (((jl_datatype_t*)t1)->has_concrete_subtype && ((jl_datatype_t*)t2)->has_concrete_subtype) + return 1; + // e.g. Tuple{Union{}} and Tuple{Int} are both concrete! + } + if (c1 && !has_concrete_supertype(t2)) + return 1; + if (c2 && !has_concrete_supertype(t1)) + return 1; + return 0; } // type instantiation @@ -901,7 +1317,7 @@ struct _jl_typestack_t; typedef struct _jl_typestack_t jl_typestack_t; static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value_t **iparams, size_t ntp, - jl_typestack_t *stack, jl_typeenv_t *env); + jl_typestack_t *stack, jl_typeenv_t *env, int check); // Build an environment mapping a TypeName's parameters to parameter values. // This is the environment needed for instantiating a type's supertype and field types. @@ -909,7 +1325,7 @@ static jl_value_t *inst_datatype_env(jl_value_t *dt, jl_svec_t *p, jl_value_t ** jl_typestack_t *stack, jl_typeenv_t *env, int c) { if (jl_is_datatype(dt)) - return inst_datatype_inner((jl_datatype_t*)dt, p, iparams, ntp, stack, env); + return inst_datatype_inner((jl_datatype_t*)dt, p, iparams, ntp, stack, env, 1); assert(jl_is_unionall(dt)); jl_unionall_t *ua = (jl_unionall_t*)dt; jl_typeenv_t e = { ua->var, iparams[c], env }; @@ -919,7 +1335,7 @@ static jl_value_t *inst_datatype_env(jl_value_t *dt, jl_svec_t *p, jl_value_t ** jl_value_t *jl_apply_type(jl_value_t *tc, jl_value_t **params, size_t n) { if (tc == (jl_value_t*)jl_anytuple_type) - return (jl_value_t*)jl_apply_tuple_type_v(params, n); + return jl_apply_tuple_type_v(params, n); if (tc == (jl_value_t*)jl_uniontype_type) return (jl_value_t*)jl_type_union(params, n); size_t i; @@ -1008,20 +1424,20 @@ jl_datatype_t *jl_apply_cmpswap_type(jl_value_t *dt) } params[0] = dt; params[1] = (jl_value_t*)jl_bool_type; - jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); + jl_datatype_t *tuptyp = (jl_datatype_t*)jl_apply_tuple_type_v(params, 2); JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) jl_datatype_t *rettyp = (jl_datatype_t*)jl_apply_type2((jl_value_t*)jl_namedtuple_type, names, (jl_value_t*)tuptyp); JL_GC_PROMISE_ROOTED(rettyp); // (JL_ALWAYS_LEAFTYPE) return rettyp; } -JL_DLLEXPORT jl_value_t *jl_tupletype_fill(size_t n, jl_value_t *v) +// used to expand an NTuple to a flat representation +static jl_value_t *jl_tupletype_fill(size_t n, jl_value_t *v) { - // TODO: replace with just using NTuple jl_value_t *p = NULL; JL_GC_PUSH1(&p); p = (jl_value_t*)jl_svec_fill(n, v); - p = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)p); + p = jl_apply_tuple_type((jl_svec_t*)p); JL_GC_POP(); return p; } @@ -1040,8 +1456,22 @@ JL_DLLEXPORT jl_value_t *jl_instantiate_unionall(jl_unionall_t *u, jl_value_t *p return inst_type_w_(u->body, &env, NULL, 1); } +jl_unionall_t *jl_rename_unionall(jl_unionall_t *u) +{ + jl_tvar_t *v = jl_new_typevar(u->var->name, u->var->lb, u->var->ub); + jl_value_t *t = NULL; + JL_GC_PUSH2(&v, &t); + jl_typeenv_t env = { u->var, (jl_value_t *)v, NULL }; + t = inst_type_w_(u->body, &env, NULL, 0); + t = jl_new_struct(jl_unionall_type, v, t); + JL_GC_POP(); + return (jl_unionall_t*)t; +} + jl_value_t *jl_substitute_var(jl_value_t *t, jl_tvar_t *var, jl_value_t *val) { + if (val == (jl_value_t*)var) + return t; jl_typeenv_t env = { var, val, NULL }; return inst_type_w_(t, &env, NULL, 1); } @@ -1054,12 +1484,36 @@ jl_value_t *jl_unwrap_unionall(jl_value_t *v) } // wrap `t` in the same unionalls that surround `u` +// where `t` is derived from `u`, so the error checks in jl_type_unionall are unnecessary jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u) { if (!jl_is_unionall(u)) return t; - JL_GC_PUSH1(&t); t = jl_rewrap_unionall(t, ((jl_unionall_t*)u)->body); + jl_tvar_t *v = ((jl_unionall_t*)u)->var; + // normalize `T where T<:S` => S + if (t == (jl_value_t*)v) + return v->ub; + // where var doesn't occur in body just return body + if (!jl_has_typevar(t, v)) + return t; + JL_GC_PUSH1(&t); + //if (v->lb == v->ub) // TODO maybe + // t = jl_substitute_var(body, v, v->ub); + //else + t = jl_new_struct(jl_unionall_type, v, t); + JL_GC_POP(); + return t; +} + +// wrap `t` in the same unionalls that surround `u` +// where `t` is extended from `u`, so the checks in jl_rewrap_unionall are unnecessary +jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u) +{ + if (!jl_is_unionall(u)) + return t; + t = jl_rewrap_unionall_(t, ((jl_unionall_t*)u)->body); + JL_GC_PUSH1(&t); t = jl_new_struct(jl_unionall_type, ((jl_unionall_t*)u)->var, t); JL_GC_POP(); return t; @@ -1084,7 +1538,7 @@ static jl_value_t *lookup_type_stack(jl_typestack_t *stack, jl_datatype_t *tt, s } // stable numbering for types--starts with name->hash, then falls back to objectid -// sets failed if the hash value isn't stable (if not set on entry) +// sets *failed if the hash value isn't stable (if this param not set on entry) static unsigned type_hash(jl_value_t *kj, int *failed) JL_NOTSAFEPOINT { jl_value_t *uw = jl_is_unionall(kj) ? jl_unwrap_unionall(kj) : kj; @@ -1096,32 +1550,21 @@ static unsigned type_hash(jl_value_t *kj, int *failed) JL_NOTSAFEPOINT *failed = 1; return 0; } + // compute a hash now, only for the parent object we are putting in the cache hash = typekey_hash(dt->name, jl_svec_data(dt->parameters), jl_svec_len(dt->parameters), *failed); } return hash; } else if (jl_is_typevar(uw)) { - if (!*failed) { - *failed = 1; - return 0; - } // ignore var and lb, since those might get normalized out in equality testing return type_hash(((jl_tvar_t*)uw)->ub, failed); } - else if (jl_is_vararg(uw)) { - if (!*failed) { - *failed = 1; - return 0; - } - jl_vararg_t *vm = (jl_vararg_t *)uw; - // 0x064eeaab is just a randomly chosen constant - return bitmix(type_hash(vm->T ? vm->T : (jl_value_t*)jl_any_type, failed), vm->N ? type_hash(vm->N, failed) : 0x064eeaab); - } else if (jl_is_uniontype(uw)) { if (!*failed) { *failed = 1; return 0; } + // compute a hash now, only for the parent object we are putting in the cache unsigned hasha = type_hash(((jl_uniontype_t*)uw)->a, failed); unsigned hashb = type_hash(((jl_uniontype_t*)uw)->b, failed); // use a associative mixing function, with well-defined overflow @@ -1133,6 +1576,14 @@ static unsigned type_hash(jl_value_t *kj, int *failed) JL_NOTSAFEPOINT } } +JL_DLLEXPORT uintptr_t jl_type_hash(jl_value_t *v) JL_NOTSAFEPOINT +{ + // NOTE: The value of `failed` is purposefully ignored here. The parameter is relevant + // for other parts of the internal algorithm but not for exposing to the Julia side. + int failed = 0; + return type_hash(v, &failed); +} + static unsigned typekey_hash(jl_typename_t *tn, jl_value_t **key, size_t n, int nofail) JL_NOTSAFEPOINT { if (tn == jl_type_typename && key[0] == jl_bottom_type) @@ -1141,7 +1592,18 @@ static unsigned typekey_hash(jl_typename_t *tn, jl_value_t **key, size_t n, int unsigned hash = 3; int failed = nofail; for (j = 0; j < n; j++) { - hash = bitmix(type_hash(key[j], &failed), hash); + jl_value_t *p = key[j]; + if (jl_is_vararg(p)) { + jl_vararg_t *vm = (jl_vararg_t*)p; + if (!nofail && vm->N) + return 0; + // 0x064eeaab is just a randomly chosen constant + hash = bitmix(vm->N ? type_hash(vm->N, &failed) : 0x064eeaab, hash); + if (failed && !nofail) + return 0; + p = vm->T ? vm->T : (jl_value_t*)jl_any_type; + } + hash = bitmix(type_hash(p, &failed), hash); if (failed && !nofail) return 0; } @@ -1174,6 +1636,7 @@ void jl_precompute_memoized_dt(jl_datatype_t *dt, int cacheable) { int istuple = (dt->name == jl_tuple_typename); dt->hasfreetypevars = 0; + dt->maybe_subtype_of_cache = 1; dt->isconcretetype = !dt->name->abstract; dt->isdispatchtuple = istuple; size_t i, l = jl_nparams(dt); @@ -1184,29 +1647,38 @@ void jl_precompute_memoized_dt(jl_datatype_t *dt, int cacheable) if (dt->hasfreetypevars) dt->isconcretetype = 0; } - if (istuple && dt->isconcretetype) - dt->isconcretetype = (jl_is_datatype(p) && ((jl_datatype_t*)p)->isconcretetype) || p == jl_bottom_type; - if (dt->isdispatchtuple) { - dt->isdispatchtuple = jl_is_datatype(p) && - ((!jl_is_kind(p) && ((jl_datatype_t*)p)->isconcretetype) || - (((jl_datatype_t*)p)->name == jl_type_typename && !((jl_datatype_t*)p)->hasfreetypevars)); + if (istuple) { + if (dt->isconcretetype) + dt->isconcretetype = (jl_is_datatype(p) && ((jl_datatype_t*)p)->isconcretetype) || p == jl_bottom_type; + if (dt->isdispatchtuple) { + dt->isdispatchtuple = jl_is_datatype(p) && + ((!jl_is_kind(p) && ((jl_datatype_t*)p)->isconcretetype) || + (p == (jl_value_t*)jl_typeofbottom_type) || // == Type{Union{}}, so needs to be consistent + (((jl_datatype_t*)p)->name == jl_type_typename && !((jl_datatype_t*)p)->hasfreetypevars)); + } } + if (jl_is_vararg(p)) + p = ((jl_vararg_t*)p)->T; if (istuple && dt->has_concrete_subtype) { - if (jl_is_vararg(p)) - p = ((jl_vararg_t*)p)->T; - // tuple types like Tuple{:x} cannot have instances + // tuple types like Tuple{:x} and Tuple{Union{}} cannot have instances if (p && !jl_is_type(p) && !jl_is_typevar(p)) dt->has_concrete_subtype = 0; + if (p == jl_bottom_type) + dt->has_concrete_subtype = 0; + } + if (dt->maybe_subtype_of_cache) { + dt->maybe_subtype_of_cache = !p || maybe_subtype_of_cache(p, istuple) || !jl_has_free_typevars(p); } } + assert(dt->isconcretetype || dt->isdispatchtuple ? dt->maybe_subtype_of_cache : 1); if (dt->name == jl_type_typename) { - cacheable = 0; // the cache for Type ignores parameter normalization, so it can't be used as a regular hash + cacheable = 0; // n.b. the cache for Type ignores parameter normalization, so it can't be used to make a stable hash value jl_value_t *p = jl_tparam(dt, 0); if (!jl_is_type(p) && !jl_is_typevar(p)) // Type{v} has no subtypes, if v is not a Type dt->has_concrete_subtype = 0; + dt->maybe_subtype_of_cache = 1; } dt->hash = typekey_hash(dt->name, jl_svec_data(dt->parameters), l, cacheable); - dt->cached_by_hash = cacheable ? (typekey_hash(dt->name, jl_svec_data(dt->parameters), l, 0) != 0) : (dt->hash != 0); } static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, size_t np) @@ -1244,7 +1716,7 @@ static void check_datatype_parameters(jl_typename_t *tn, jl_value_t **params, si JL_GC_POP(); } -static jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_GLOBALLY_ROOTED +jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_GLOBALLY_ROOTED { t = jl_unwrap_unionall(t); if (jl_is_datatype(t)) @@ -1261,45 +1733,54 @@ static jl_value_t *extract_wrapper(jl_value_t *t JL_PROPAGATES_ROOT) JL_GLOBALLY int _may_substitute_ub(jl_value_t *v, jl_tvar_t *var, int inside_inv, int *cov_count) JL_NOTSAFEPOINT { - if (v == (jl_value_t*)var) { - if (inside_inv) { - return 0; + while (1) { + if (v == (jl_value_t*)var) { + if (inside_inv) { + return 0; + } + else { + (*cov_count)++; + return *cov_count <= 1 || jl_is_concrete_type(var->ub); + } } - else { - (*cov_count)++; - return *cov_count <= 1 || jl_is_concrete_type(var->ub); + while (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + if (ua->var == var) + return 1; + if (ua->var->lb != jl_bottom_type && !_may_substitute_ub(ua->var->lb, var, inside_inv, cov_count)) + return 0; + if (ua->var->ub != (jl_value_t*)jl_any_type && !_may_substitute_ub(ua->var->ub, var, inside_inv, cov_count)) + return 0; + v = ua->body; } - } - else if (jl_is_uniontype(v)) { - return _may_substitute_ub(((jl_uniontype_t*)v)->a, var, inside_inv, cov_count) && - _may_substitute_ub(((jl_uniontype_t*)v)->b, var, inside_inv, cov_count); - } - else if (jl_is_unionall(v)) { - jl_unionall_t *ua = (jl_unionall_t*)v; - if (ua->var == var) + if (jl_is_datatype(v)) { + int invar = inside_inv || !jl_is_tuple_type(v); + for (size_t i = 0; i < jl_nparams(v); i++) { + if (!_may_substitute_ub(jl_tparam(v, i), var, invar, cov_count)) + return 0; + } return 1; - return _may_substitute_ub(ua->var->lb, var, inside_inv, cov_count) && - _may_substitute_ub(ua->var->ub, var, inside_inv, cov_count) && - _may_substitute_ub(ua->body, var, inside_inv, cov_count); - } - else if (jl_is_datatype(v)) { - int invar = inside_inv || !jl_is_tuple_type(v); - for (size_t i = 0; i < jl_nparams(v); i++) { - if (!_may_substitute_ub(jl_tparam(v,i), var, invar, cov_count)) + } + else if (jl_is_uniontype(v)) { + // TODO: is !inside_inv, these don't have to share the changes to cov_count + if (!_may_substitute_ub(((jl_uniontype_t*)v)->a, var, inside_inv, cov_count)) return 0; + v = ((jl_uniontype_t*)v)->b; + } + else if (jl_is_vararg(v)) { + jl_vararg_t *va = (jl_vararg_t*)v; + if (!va->T) + return 1; + if (va->N && !_may_substitute_ub(va->N, var, 1, cov_count)) + return 0; + if (!jl_is_concrete_type(var->ub)) + inside_inv = 1; // treat as invariant inside vararg, for the sake of this algorithm + v = va->T; + } + else { + return 1; } } - else if (jl_is_vararg(v)) { - jl_vararg_t *va = (jl_vararg_t*)v; - int old_count = *cov_count; - if (va->T && !_may_substitute_ub(va->T, var, inside_inv, cov_count)) - return 0; - if (*cov_count > old_count && !jl_is_concrete_type(var->ub)) - return 0; - if (va->N && !_may_substitute_ub(va->N, var, 1, cov_count)) - return 0; - } - return 1; } // Check whether `var` may be replaced with its upper bound `ub` in `v where var<:ub` @@ -1315,7 +1796,6 @@ int may_substitute_ub(jl_value_t *v, jl_tvar_t *var) JL_NOTSAFEPOINT jl_value_t *normalize_unionalls(jl_value_t *t) { - JL_GC_PUSH1(&t); if (jl_is_uniontype(t)) { jl_uniontype_t *u = (jl_uniontype_t*)t; jl_value_t *a = NULL; @@ -1331,14 +1811,14 @@ 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); if (body != u->body) { - JL_GC_PUSH1(&body); t = jl_new_struct(jl_unionall_type, u->var, body); - JL_GC_POP(); u = (jl_unionall_t*)t; } if (u->var->lb == u->var->ub || may_substitute_ub(body, u->var)) { + body = (jl_value_t*)u; JL_TRY { t = jl_instantiate_unionall(u, u->var->ub); } @@ -1347,21 +1827,21 @@ jl_value_t *normalize_unionalls(jl_value_t *t) // (may happen for bounds inconsistent with the wrapper's bounds) } } + JL_GC_POP(); } - JL_GC_POP(); return t; } static jl_value_t *_jl_instantiate_type_in_env(jl_value_t *ty, jl_unionall_t *env, jl_value_t **vals, jl_typeenv_t *prev, jl_typestack_t *stack); static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value_t **iparams, size_t ntp, - jl_typestack_t *stack, jl_typeenv_t *env) + jl_typestack_t *stack, jl_typeenv_t *env, int check) { jl_typestack_t top; jl_typename_t *tn = dt->name; int istuple = (tn == jl_tuple_typename); int isnamedtuple = (tn == jl_namedtuple_typename); - if (dt->name != jl_type_typename) { + if (check && tn != jl_type_typename) { size_t i; for (i = 0; i < ntp; i++) iparams[i] = normalize_unionalls(iparams[i]); @@ -1371,9 +1851,31 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value int cacheable = 1; if (istuple) { size_t i; - for (i = 0; cacheable && i < ntp; i++) - if (!jl_is_concrete_type(iparams[i]) && iparams[i] != jl_bottom_type) + for (i = 0; i < ntp; i++) { + jl_value_t *pi = iparams[i]; + if (jl_is_vararg(pi) && jl_unwrap_vararg(pi) == jl_bottom_type) { + jl_value_t *va1 = jl_unwrap_vararg_num(pi); + if (va1 && jl_is_long(va1)) { + ssize_t nt = jl_unbox_long(va1); + if (nt == 0) + va1 = NULL; + else + pi = jl_bottom_type; // trigger errorf below + } + // This imposes an implicit constraint that va1==0, + // so we keep the Vararg if it has a TypeVar + if (va1 == NULL) { + p = NULL; + ntp -= 1; + assert(i == ntp); + break; + } + } + if (pi == jl_bottom_type) + jl_errorf("Tuple field type cannot be Union{}"); + if (cacheable && !jl_is_concrete_type(pi)) cacheable = 0; + } } else { size_t i; @@ -1405,6 +1907,11 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value if (p) jl_gc_wb(p, tw); } } + if (tn == jl_type_typename && jl_is_datatype(iparams[0]) && ((jl_datatype_t*)iparams[0])->name == jl_type_typename && + jl_tparam0(iparams[0]) == jl_bottom_type) { + // normalize Type{Type{Union{}}} to Type{TypeofBottom} + iparams[0] = (jl_value_t*)jl_typeofbottom_type; + } jl_value_t *lkup = (jl_value_t*)lookup_type(tn, iparams, ntp); if (lkup != NULL) return lkup; @@ -1413,7 +1920,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value if (stack_lkup) return stack_lkup; - if (!istuple) { + if (check && !istuple) { // check parameters against bounds in type definition check_datatype_parameters(tn, iparams, ntp); } @@ -1423,9 +1930,9 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value } jl_datatype_t *ndt = NULL; - jl_value_t *last = iparams[ntp - 1]; - JL_GC_PUSH3(&p, &ndt, &last); + JL_GC_PUSH2(&p, &ndt); + jl_value_t *last = iparams[ntp - 1]; if (istuple && ntp > 0 && jl_is_vararg(last)) { // normalize Tuple{..., Vararg{Int, 3}} to Tuple{..., Int, Int, Int} jl_value_t *va = jl_unwrap_unionall(last); @@ -1450,7 +1957,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value l = ntp - 1 + nt; for (; i < l; i++) jl_svecset(p, i, va0); - jl_value_t *ndt = (jl_value_t*)jl_apply_tuple_type(p); + jl_value_t *ndt = jl_apply_tuple_type(p); JL_GC_POP(); return ndt; } @@ -1478,6 +1985,10 @@ 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 + // overriden for `Type`, which we handle here. + ndt->ismutationfree = dt->ismutationfree; // associate these parameters with the new type on // the stack, in case one of its field types references it. top.tt = (jl_datatype_t*)ndt; @@ -1522,7 +2033,6 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value jl_datatype_t *primarydt = ((jl_datatype_t*)jl_unwrap_unionall(tn->wrapper)); jl_precompute_memoized_dt(ndt, cacheable); - ndt->size = 0; if (primarydt->layout) jl_compute_field_offsets(ndt); @@ -1530,7 +2040,7 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value ndt->super = jl_any_type; } else if (dt->super) { - ndt->super = (jl_datatype_t*)inst_type_w_((jl_value_t*)dt->super, env, stack, 1); + ndt->super = (jl_datatype_t*)inst_type_w_((jl_value_t*)dt->super, env, stack, check); jl_gc_wb(ndt, ndt->super); } jl_svec_t *ftypes = dt->types; @@ -1576,17 +2086,17 @@ static jl_value_t *inst_datatype_inner(jl_datatype_t *dt, jl_svec_t *p, jl_value return (jl_value_t*)ndt; } -static jl_tupletype_t *jl_apply_tuple_type_v_(jl_value_t **p, size_t np, jl_svec_t *params) +static jl_value_t *jl_apply_tuple_type_v_(jl_value_t **p, size_t np, jl_svec_t *params) { - return (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, p, np, NULL, NULL); + return inst_datatype_inner(jl_anytuple_type, params, p, np, NULL, NULL, 1); } -JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type(jl_svec_t *params) +JL_DLLEXPORT jl_value_t *jl_apply_tuple_type(jl_svec_t *params) { return jl_apply_tuple_type_v_(jl_svec_data(params), jl_svec_len(params), params); } -JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type_v(jl_value_t **p, size_t np) +JL_DLLEXPORT jl_value_t *jl_apply_tuple_type_v(jl_value_t **p, size_t np) { return jl_apply_tuple_type_v_(p, np, NULL); } @@ -1617,7 +2127,7 @@ jl_tupletype_t *jl_inst_arg_tuple_type(jl_value_t *arg1, jl_value_t **args, size } jl_svecset(params, i, ai); } - tt = (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, jl_svec_data(params), nargs, NULL, NULL); + tt = (jl_datatype_t*)inst_datatype_inner(jl_anytuple_type, params, jl_svec_data(params), nargs, NULL, NULL, 1); JL_GC_POP(); } return tt; @@ -1672,7 +2182,7 @@ static jl_value_t *inst_tuple_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_ ssize_t nt = jl_unbox_long(N); if (nt < 0) jl_errorf("size or dimension is negative: %zd", nt); - return (jl_value_t*)jl_tupletype_fill(nt, T); + return jl_tupletype_fill(nt, T); } } jl_value_t **iparams; @@ -1688,14 +2198,14 @@ static jl_value_t *inst_tuple_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_ int i; for (i = 0; i < ntp; i++) { jl_value_t *elt = jl_svecref(tp, i); - jl_value_t *pi = inst_type_w_(elt, env, stack, 0); + jl_value_t *pi = inst_type_w_(elt, env, stack, check); iparams[i] = pi; if (ip_heap) jl_gc_wb(ip_heap, pi); bound |= (pi != elt); } if (bound) - t = inst_datatype_inner(tt, ip_heap, iparams, ntp, stack, env); + t = inst_datatype_inner(tt, ip_heap, iparams, ntp, stack, env, check); JL_GC_POP(); return t; } @@ -1748,8 +2258,14 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t JL_GC_PUSH2(&a, &b); b = inst_type_w_(u->b, env, stack, check); if (a != u->a || b != u->b) { - jl_value_t *uargs[2] = {a, b}; - t = jl_type_union(uargs, 2); + if (check) { + jl_value_t *uargs[2] = {a, b}; + t = jl_type_union(uargs, 2); + } + else { + // fast path for `jl_rename_unionall`. + t = jl_new_struct(jl_uniontype_type, a, b); + } } JL_GC_POP(); return t; @@ -1791,7 +2307,7 @@ static jl_value_t *inst_type_w_(jl_value_t *t, jl_typeenv_t *env, jl_typestack_t } // if t's parameters are not bound in the environment, return it uncopied (#9378) if (bound) - t = inst_datatype_inner(tt, NULL, iparams, ntp, stack, env); + t = inst_datatype_inner(tt, NULL, iparams, ntp, stack, env, check); JL_GC_POP(); return t; } @@ -1841,7 +2357,7 @@ jl_datatype_t *jl_wrap_Type(jl_value_t *t) jl_vararg_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n) { if (n) { - if (jl_is_typevar(n)) { + if (jl_is_typevar(n) || jl_is_uniontype(jl_unwrap_unionall(n))) { // TODO: this is disabled due to #39698; it is also inconsistent // with other similar checks, where we usually only check substituted // values and not the bounds of variables. @@ -1865,6 +2381,7 @@ jl_vararg_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n) } jl_task_t *ct = jl_current_task; jl_vararg_t *vm = (jl_vararg_t *)jl_gc_alloc(ct->ptls, sizeof(jl_vararg_t), jl_vararg_type); + jl_set_typetagof(vm, jl_vararg_tag, 0); vm->T = t; vm->N = n; return vm; @@ -1924,7 +2441,7 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! for (i = 0; i < n; i++) env[i].val = jl_svecref(ndt->parameters, i); - ndt->super = (jl_datatype_t*)inst_type_w_((jl_value_t*)t->super, env, &top, 1); + ndt->super = (jl_datatype_t*)inst_type_w_((jl_value_t*)t->super, &env[n - 1], &top, 1); jl_gc_wb(ndt, ndt->super); } @@ -1934,7 +2451,7 @@ void jl_reinstantiate_inner_types(jl_datatype_t *t) // can throw! 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, &top); + ndt->types = inst_ftypes(t->types, &env[n - 1], &top); jl_gc_wb(ndt, ndt->types); if (ndt->isconcretetype) { // cacheable jl_compute_field_offsets(ndt); @@ -1954,19 +2471,36 @@ static jl_tvar_t *tvar(const char *name) (jl_value_t*)jl_any_type); } +void export_small_typeof(void) +{ + void *copy; +#ifdef _OS_WINDOWS_ + jl_dlsym(jl_libjulia_handle, "small_typeof", ©, 1); +#else + jl_dlsym(jl_libjulia_internal_handle, "small_typeof", ©, 1); +#endif + memcpy(copy, &small_typeof, sizeof(small_typeof)); +} + +#define XX(name) \ + small_typeof[(jl_##name##_tag << 4) / sizeof(*small_typeof)] = jl_##name##_type; \ + jl_##name##_type->smalltag = jl_##name##_tag; void jl_init_types(void) JL_GC_DISABLED { jl_module_t *core = NULL; // will need to be assigned later // create base objects jl_datatype_type = jl_new_uninitialized_datatype(); - jl_set_typeof(jl_datatype_type, jl_datatype_type); + XX(datatype); jl_typename_type = jl_new_uninitialized_datatype(); jl_symbol_type = jl_new_uninitialized_datatype(); + XX(symbol); jl_simplevector_type = jl_new_uninitialized_datatype(); + XX(simplevector); jl_methtable_type = jl_new_uninitialized_datatype(); jl_emptysvec = (jl_svec_t*)jl_gc_permobj(sizeof(void*), jl_simplevector_type); + jl_set_typetagof(jl_emptysvec, jl_simplevector_tag, GC_OLD_MARKED); jl_svec_set_len_unsafe(jl_emptysvec, 0); jl_any_type = (jl_datatype_t*)jl_new_abstracttype((jl_value_t*)jl_symbol("Any"), core, NULL, jl_emptysvec); @@ -1974,8 +2508,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_nonfunction_mt = jl_any_type->name->mt; jl_any_type->name->mt = NULL; - jl_type_type = (jl_unionall_t*)jl_new_abstracttype((jl_value_t*)jl_symbol("Type"), core, jl_any_type, jl_emptysvec); - jl_type_typename = ((jl_datatype_t*)jl_type_type)->name; + jl_datatype_t *type_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Type"), core, jl_any_type, jl_emptysvec); + jl_type_type = (jl_unionall_t*)type_type; + jl_type_typename = type_type->name; jl_type_type_mt = jl_new_method_table(jl_type_typename->name, core); jl_type_typename->mt = jl_type_type_mt; @@ -1983,20 +2518,19 @@ void jl_init_types(void) JL_GC_DISABLED // NOTE: types are not actually mutable, but we want to ensure they are heap-allocated with stable addresses jl_datatype_type->name = jl_new_typename_in(jl_symbol("DataType"), core, 0, 1); jl_datatype_type->name->wrapper = (jl_value_t*)jl_datatype_type; - jl_datatype_type->super = (jl_datatype_t*)jl_type_type; + jl_datatype_type->super = type_type; jl_datatype_type->parameters = jl_emptysvec; - jl_datatype_type->name->n_uninitialized = 9 - 3; - jl_datatype_type->name->names = jl_perm_symsvec(9, + jl_datatype_type->name->n_uninitialized = 8 - 3; + jl_datatype_type->name->names = jl_perm_symsvec(8, "name", "super", "parameters", "types", "instance", "layout", - "size", "hash", - "flags"); // "hasfreetypevars", "isconcretetype", "isdispatchtuple", "isbitstype", "zeroinit", "has_concrete_subtype", "cached_by_hash" - jl_datatype_type->types = jl_svec(9, + "flags"); // "hasfreetypevars", "isconcretetype", "isdispatchtuple", "isbitstype", "zeroinit", "has_concrete_subtype", "maybe_subtype_of_cache" + jl_datatype_type->types = jl_svec(8, jl_typename_type, jl_datatype_type, jl_simplevector_type, @@ -2004,10 +2538,11 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, // instance jl_any_type /*jl_voidpointer_type*/, jl_any_type /*jl_int32_type*/, - jl_any_type /*jl_int32_type*/, - jl_any_type /*jl_uint8_type*/); - const static uint32_t datatype_constfields[1] = { 0x00000097 }; // (1<<0)|(1<<1)|(1<<2)|(1<<4)|(1<<7) + jl_any_type /*jl_uint16_type*/); + const static uint32_t datatype_constfields[1] = { 0x00000057 }; // (1<<0)|(1<<1)|(1<<2)|(1<<4)|(1<<6) + const static uint32_t datatype_atomicfields[1] = { 0x00000028 }; // (1<<3)|(1<<5) jl_datatype_type->name->constfields = datatype_constfields; + jl_datatype_type->name->atomicfields = datatype_atomicfields; jl_precompute_memoized_dt(jl_datatype_type, 1); jl_typename_type->name = jl_new_typename_in(jl_symbol("TypeName"), core, 0, 1); @@ -2015,41 +2550,47 @@ void jl_init_types(void) JL_GC_DISABLED jl_typename_type->name->mt = jl_nonfunction_mt; jl_typename_type->super = jl_any_type; jl_typename_type->parameters = jl_emptysvec; - jl_typename_type->name->n_uninitialized = 13 - 2; - jl_typename_type->name->names = jl_perm_symsvec(13, "name", "module", + jl_typename_type->name->n_uninitialized = 15 - 2; + jl_typename_type->name->names = jl_perm_symsvec(15, "name", "module", "names", "atomicfields", "constfields", - "wrapper", "cache", "linearcache", + "wrapper", "Typeofwrapper", "cache", "linearcache", "mt", "partial", "hash", "n_uninitialized", - "flags"); // "abstract", "mutable", "mayinlinealloc", - jl_typename_type->types = jl_svec(13, jl_symbol_type, jl_any_type /*jl_module_type*/, + "flags", // "abstract", "mutable", "mayinlinealloc", + "max_methods"); + const static uint32_t typename_constfields[1] = { 0x00003a3f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<9)|(1<<11)|(1<<12)|(1<<13) + const static uint32_t typename_atomicfields[1] = { 0x00000180 }; // (1<<7)|(1<<8) + jl_typename_type->name->constfields = typename_constfields; + jl_typename_type->name->atomicfields = typename_atomicfields; + jl_precompute_memoized_dt(jl_typename_type, 1); + jl_typename_type->types = jl_svec(15, jl_symbol_type, jl_any_type /*jl_module_type*/, jl_simplevector_type, jl_any_type/*jl_voidpointer_type*/, jl_any_type/*jl_voidpointer_type*/, - jl_type_type, jl_simplevector_type, jl_simplevector_type, + jl_type_type, jl_type_type, jl_simplevector_type, jl_simplevector_type, jl_methtable_type, jl_any_type, jl_any_type /*jl_long_type*/, jl_any_type /*jl_int32_type*/, + jl_any_type /*jl_uint8_type*/, jl_any_type /*jl_uint8_type*/); - const static uint32_t typename_constfields[1] = { 0x00001d3f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<8)|(1<<10)|(1<<11)|(1<<12) - jl_typename_type->name->constfields = typename_constfields; - jl_precompute_memoized_dt(jl_typename_type, 1); jl_methtable_type->name = jl_new_typename_in(jl_symbol("MethodTable"), core, 0, 1); jl_methtable_type->name->wrapper = (jl_value_t*)jl_methtable_type; jl_methtable_type->name->mt = jl_nonfunction_mt; jl_methtable_type->super = jl_any_type; jl_methtable_type->parameters = jl_emptysvec; - jl_methtable_type->name->n_uninitialized = 12 - 5; - jl_methtable_type->name->names = jl_perm_symsvec(12, "name", "defs", + jl_methtable_type->name->n_uninitialized = 11 - 6; + jl_methtable_type->name->names = jl_perm_symsvec(11, "name", "defs", "leafcache", "cache", "max_args", - "kwsorter", "module", - "backedges", "", "", "offs", ""); - jl_methtable_type->types = jl_svec(12, jl_symbol_type, jl_any_type, jl_any_type, - jl_any_type, jl_any_type/*jl_long*/, - jl_any_type, jl_any_type/*module*/, - jl_any_type/*any vector*/, jl_any_type/*voidpointer*/, jl_any_type/*int32*/, - jl_any_type/*uint8*/, jl_any_type/*uint8*/); - const static uint32_t methtable_constfields[1] = { 0x00000040 }; // (1<<6); + "module", "backedges", + "", "", "offs", ""); + const static uint32_t methtable_constfields[1] = { 0x00000020 }; // (1<<5); + const static uint32_t methtable_atomicfields[1] = { 0x0000001e }; // (1<<1)|(1<<2)|(1<<3)|(1<<4); jl_methtable_type->name->constfields = methtable_constfields; + jl_methtable_type->name->atomicfields = methtable_atomicfields; jl_precompute_memoized_dt(jl_methtable_type, 1); + jl_methtable_type->types = jl_svec(11, jl_symbol_type, jl_any_type, jl_any_type, + jl_any_type, jl_any_type/*jl_long*/, + jl_any_type/*module*/, jl_any_type/*any vector*/, + jl_any_type/*voidpointer*/, jl_any_type/*int32*/, + jl_any_type/*uint8*/, jl_any_type/*uint8*/); jl_symbol_type->name = jl_new_typename_in(jl_symbol("Symbol"), core, 0, 1); jl_symbol_type->name->wrapper = (jl_value_t*)jl_symbol_type; @@ -2059,7 +2600,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_symbol_type->name->n_uninitialized = 0; jl_symbol_type->name->names = jl_emptysvec; jl_symbol_type->types = jl_emptysvec; - jl_symbol_type->size = 0; jl_precompute_memoized_dt(jl_symbol_type, 1); jl_simplevector_type->name = jl_new_typename_in(jl_symbol("SimpleVector"), core, 0, 1); @@ -2079,31 +2619,52 @@ void jl_init_types(void) JL_GC_DISABLED jl_astaggedvalue(jl_nothing)->header = ((uintptr_t)jl_nothing_type) | GC_OLD_MARKED; jl_nothing_type->instance = jl_nothing; - jl_datatype_t *type_type = (jl_datatype_t*)jl_type_type; - jl_typeofbottom_type = jl_new_datatype(jl_symbol("TypeofBottom"), core, type_type, jl_emptysvec, - jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); - jl_bottom_type = jl_new_struct(jl_typeofbottom_type); - jl_typeofbottom_type->instance = jl_bottom_type; - - jl_uniontype_type = jl_new_datatype(jl_symbol("Union"), core, type_type, jl_emptysvec, - jl_perm_symsvec(2, "a", "b"), - jl_svec(2, jl_any_type, jl_any_type), - jl_emptysvec, 0, 0, 2); - jl_tvar_type = jl_new_datatype(jl_symbol("TypeVar"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "name", "lb", "ub"), jl_svec(3, jl_symbol_type, jl_any_type, jl_any_type), jl_emptysvec, 0, 1, 3); + XX(tvar); + const static uint32_t tvar_constfields[1] = { 0x00000007 }; // all fields are constant, even though TypeVar itself has identity + jl_tvar_type->name->constfields = tvar_constfields; + + jl_typeofbottom_type = jl_new_datatype(jl_symbol("TypeofBottom"), core, type_type, jl_emptysvec, + jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 0, 0); + XX(typeofbottom); + jl_bottom_type = jl_gc_permobj(0, jl_typeofbottom_type); + jl_set_typetagof(jl_bottom_type, jl_typeofbottom_tag, GC_OLD_MARKED); + jl_typeofbottom_type->instance = jl_bottom_type; jl_unionall_type = jl_new_datatype(jl_symbol("UnionAll"), core, type_type, jl_emptysvec, jl_perm_symsvec(2, "var", "body"), jl_svec(2, jl_tvar_type, jl_any_type), jl_emptysvec, 0, 0, 2); + XX(unionall); + // 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_unionall_type->name->mayinlinealloc = 0; + + jl_uniontype_type = jl_new_datatype(jl_symbol("Union"), core, type_type, jl_emptysvec, + jl_perm_symsvec(2, "a", "b"), + jl_svec(2, jl_any_type, jl_any_type), + jl_emptysvec, 0, 0, 2); + XX(uniontype); + // 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_uniontype_type->name->mayinlinealloc = 0; + + jl_tvar_t *tttvar = tvar("T"); + type_type->parameters = jl_svec(1, tttvar); + jl_precompute_memoized_dt(type_type, 0); // update the hash value ASAP + type_type->hasfreetypevars = 1; + type_type->ismutationfree = 1; + jl_type_typename->wrapper = jl_new_struct(jl_unionall_type, tttvar, (jl_value_t*)jl_type_type); + jl_type_type = (jl_unionall_t*)jl_type_typename->wrapper; jl_vararg_type = jl_new_datatype(jl_symbol("TypeofVararg"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(2, "T", "N"), jl_svec(2, jl_any_type, jl_any_type), jl_emptysvec, 0, 0, 0); + 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_svec_t *anytuple_params = jl_svec(1, jl_wrap_vararg((jl_value_t*)jl_any_type, (jl_value_t*)NULL)); jl_anytuple_type = jl_new_datatype(jl_symbol("Tuple"), core, jl_any_type, anytuple_params, @@ -2112,53 +2673,44 @@ void jl_init_types(void) JL_GC_DISABLED // fix some miscomputed values, since we didn't know this was going to be a Tuple in jl_precompute_memoized_dt jl_tuple_typename->wrapper = (jl_value_t*)jl_anytuple_type; // remove UnionAll wrappers jl_anytuple_type->isconcretetype = 0; + jl_anytuple_type->maybe_subtype_of_cache = 0; jl_anytuple_type->layout = NULL; - jl_anytuple_type->size = 0; - jl_anytuple_type->cached_by_hash = 0; - - jl_tvar_t *tttvar = tvar("T"); - ((jl_datatype_t*)jl_type_type)->parameters = jl_svec(1, tttvar); - ((jl_datatype_t*)jl_type_type)->hasfreetypevars = 1; - ((jl_datatype_t*)jl_type_type)->cached_by_hash = 0; - jl_type_typename->wrapper = jl_new_struct(jl_unionall_type, tttvar, (jl_value_t*)jl_type_type); - jl_type_type = (jl_unionall_t*)jl_type_typename->wrapper; jl_typeofbottom_type->super = jl_wrap_Type(jl_bottom_type); - - jl_emptytuple_type = jl_apply_tuple_type(jl_emptysvec); + jl_emptytuple_type = (jl_datatype_t*)jl_apply_tuple_type(jl_emptysvec); jl_emptytuple = jl_gc_permobj(0, jl_emptytuple_type); jl_emptytuple_type->instance = jl_emptytuple; // non-primitive definitions follow jl_int32_type = jl_new_primitivetype((jl_value_t*)jl_symbol("Int32"), core, jl_any_type, jl_emptysvec, 32); + XX(int32); jl_int64_type = jl_new_primitivetype((jl_value_t*)jl_symbol("Int64"), core, jl_any_type, jl_emptysvec, 64); + XX(int64); jl_uint32_type = jl_new_primitivetype((jl_value_t*)jl_symbol("UInt32"), core, jl_any_type, jl_emptysvec, 32); + XX(uint32); jl_uint64_type = jl_new_primitivetype((jl_value_t*)jl_symbol("UInt64"), core, jl_any_type, jl_emptysvec, 64); + XX(uint64); jl_uint8_type = jl_new_primitivetype((jl_value_t*)jl_symbol("UInt8"), core, jl_any_type, jl_emptysvec, 8); + XX(uint8); + jl_uint16_type = jl_new_primitivetype((jl_value_t*)jl_symbol("UInt16"), core, + jl_any_type, jl_emptysvec, 16); + XX(uint16); jl_ssavalue_type = jl_new_datatype(jl_symbol("SSAValue"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "id"), jl_svec1(jl_long_type), jl_emptysvec, 0, 0, 1); - jl_abstractslot_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Slot"), core, jl_any_type, - jl_emptysvec); - - jl_slotnumber_type = jl_new_datatype(jl_symbol("SlotNumber"), core, jl_abstractslot_type, jl_emptysvec, + jl_slotnumber_type = jl_new_datatype(jl_symbol("SlotNumber"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "id"), jl_svec1(jl_long_type), jl_emptysvec, 0, 0, 1); - jl_typedslot_type = jl_new_datatype(jl_symbol("TypedSlot"), core, jl_abstractslot_type, jl_emptysvec, - jl_perm_symsvec(2, "id", "typ"), - jl_svec(2, jl_long_type, jl_any_type), - jl_emptysvec, 0, 0, 2); - jl_argument_type = jl_new_datatype(jl_symbol("Argument"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "n"), jl_svec1(jl_long_type), @@ -2169,14 +2721,16 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type = NULL; jl_bool_type = jl_new_primitivetype((jl_value_t*)jl_symbol("Bool"), core, jl_any_type, jl_emptysvec, 8); - jl_false = jl_permbox8(jl_bool_type, 0); - jl_true = jl_permbox8(jl_bool_type, 1); + XX(bool); + jl_false = jl_permbox8(jl_bool_type, jl_bool_tag, 0); + jl_true = jl_permbox8(jl_bool_type, jl_bool_tag, 1); jl_abstractstring_type = jl_new_abstracttype((jl_value_t*)jl_symbol("AbstractString"), core, jl_any_type, jl_emptysvec); jl_string_type = jl_new_datatype(jl_symbol("String"), core, jl_abstractstring_type, jl_emptysvec, jl_emptysvec, jl_emptysvec, jl_emptysvec, 0, 1, 0); + XX(string); jl_string_type->instance = NULL; - jl_compute_field_offsets(jl_string_type); + jl_compute_field_offsets(jl_string_type); // re-compute now that we assigned jl_string_type jl_an_empty_string = jl_pchar_to_string("\0", 1); *(size_t*)jl_an_empty_string = 0; @@ -2198,6 +2752,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type), jl_emptysvec, 0, 1, 6); + const static uint32_t typemap_level_atomicfields[1] = { 0x0000003f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<5) + jl_typemap_level_type->name->atomicfields = typemap_level_atomicfields; jl_typemap_entry_type = jl_new_datatype(jl_symbol("TypeMapEntry"), core, jl_any_type, jl_emptysvec, @@ -2225,8 +2781,10 @@ 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_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) jl_typemap_entry_type->name->constfields = typemap_entry_constfields; + jl_typemap_entry_type->name->atomicfields = typemap_entry_atomicfields; jl_function_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Function"), core, jl_any_type, jl_emptysvec); jl_builtin_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Builtin"), core, jl_function_type, jl_emptysvec); @@ -2271,6 +2829,7 @@ void jl_init_types(void) JL_GC_DISABLED 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); @@ -2284,7 +2843,7 @@ void jl_init_types(void) JL_GC_DISABLED 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_long_type, jl_long_type), + jl_svec(5, jl_module_type, jl_any_type, jl_symbol_type, jl_int32_type, jl_int32_type), jl_emptysvec, 0, 0, 5); jl_gotonode_type = @@ -2341,16 +2900,10 @@ void jl_init_types(void) JL_GC_DISABLED jl_svec(1, jl_slotnumber_type), jl_emptysvec, 0, 0, 1); - jl_globalref_type = - jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(2, "mod", "name"), - jl_svec(2, jl_module_type, jl_symbol_type), - jl_emptysvec, 0, 0, 2); - jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(20, + jl_perm_symsvec(21, "code", "codelocs", "ssavaluetypes", @@ -2366,12 +2919,13 @@ void jl_init_types(void) JL_GC_DISABLED "min_world", "max_world", "inferred", - "inlineable", "propagate_inbounds", - "pure", + "has_fcall", + "inlining", "constprop", - "purity"), - jl_svec(20, + "purity", + "inlining_cost"), + jl_svec(21, jl_array_any_type, jl_array_int32_type, jl_any_type, @@ -2389,9 +2943,10 @@ 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_uint8_type, + jl_uint16_type), jl_emptysvec, 0, 1, 20); @@ -2424,9 +2979,9 @@ void jl_init_types(void) JL_GC_DISABLED "nospecialize", "nkw", "isva", - "pure", "is_for_opaque_closure", "constprop", + "max_varargs", "purity"), jl_svec(29, jl_symbol_type, @@ -2436,7 +2991,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_ulong_type, jl_ulong_type, jl_type_type, - jl_simplevector_type, + jl_any_type, // union(jl_simplevector_type, jl_method_instance_type), jl_array_type, jl_string_type, jl_any_type, @@ -2455,7 +3010,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_int32_type, jl_bool_type, jl_bool_type, - jl_bool_type, + jl_uint8_type, jl_uint8_type, jl_uint8_type), jl_emptysvec, @@ -2466,7 +3021,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_method_instance_type = jl_new_datatype(jl_symbol("MethodInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(8, + jl_perm_symsvec(10, "def", "specTypes", "sparam_vals", @@ -2474,20 +3029,28 @@ void jl_init_types(void) JL_GC_DISABLED "backedges", "callbacks", "cache", - "inInference"), - jl_svec(8, + "inInference", + "cache_with_orig", + "precompiled"), + jl_svec(10, 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_bool_type, + jl_bool_type, jl_bool_type), jl_emptysvec, 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); + //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; jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, @@ -2502,10 +3065,10 @@ void jl_init_types(void) JL_GC_DISABLED "inferred", //"edges", //"absolute_max", - "ipo_purity_bits", "purity_bits", + "ipo_purity_bits", "purity_bits", "argescapes", - "isspecsig", "precompile", "invoke", "specptr", // function object decls - "relocatability"), + "isspecsig", "precompile", "relocatability", + "invoke", "specptr"), // function object decls jl_svec(15, jl_method_instance_type, jl_any_type, @@ -2516,17 +3079,21 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, //jl_any_type, //jl_bool_type, - jl_uint8_type, jl_uint8_type, + jl_uint32_type, jl_uint32_type, jl_any_type, jl_bool_type, jl_bool_type, - jl_any_type, jl_any_type, // fptrs - jl_uint8_type), + jl_uint8_type, + 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] = { 0x00000001 }; // (1<<1); + 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_code_instance_type->name->constfields = code_instance_constfields; + jl_code_instance_type->name->atomicfields = code_instance_atomicfields; jl_const_type = jl_new_datatype(jl_symbol("Const"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(1, "val"), @@ -2539,7 +3106,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 0, 2); jl_interconditional_type = jl_new_datatype(jl_symbol("InterConditional"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(3, "slot", "vtype", "elsetype"), + jl_perm_symsvec(3, "slot", "thentype", "elsetype"), jl_svec(3, jl_long_type, jl_any_type, jl_any_type), jl_emptysvec, 0, 0, 3); @@ -2567,8 +3134,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_pointer_typename = ((jl_datatype_t*)jl_unwrap_unionall((jl_value_t*)jl_pointer_type))->name; // LLVMPtr{T, AS} where {T, AS} - tv = jl_svec2(tvar("T"), tvar("AS")); - jl_svec_t *tv_base = jl_svec1(tvar("T")); + jl_tvar_t *elvar = tvar("T"); + tv = jl_svec2(elvar, tvar("AS")); + jl_svec_t *tv_base = jl_svec1(elvar); jl_llvmpointer_type = (jl_unionall_t*) jl_new_primitivetype((jl_value_t*)jl_symbol("LLVMPtr"), core, (jl_datatype_t*)jl_apply_type((jl_value_t*)jl_ref_type, jl_svec_data(tv_base), 1), tv, @@ -2596,7 +3164,7 @@ void jl_init_types(void) JL_GC_DISABLED NULL, jl_any_type, jl_emptysvec, - jl_perm_symsvec(14, + jl_perm_symsvec(16, "next", "queue", "storage", @@ -2608,10 +3176,12 @@ void jl_init_types(void) JL_GC_DISABLED "rngState1", "rngState2", "rngState3", + "rngState4", "_state", "sticky", - "_isexception"), - jl_svec(14, + "_isexception", + "priority"), + jl_svec(16, jl_any_type, jl_any_type, jl_any_type, @@ -2623,19 +3193,39 @@ void jl_init_types(void) JL_GC_DISABLED jl_uint64_type, jl_uint64_type, jl_uint64_type, + jl_uint64_type, jl_uint8_type, jl_bool_type, - jl_bool_type), + jl_bool_type, + jl_uint16_type), jl_emptysvec, 0, 1, 6); + XX(task); 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_astaggedvalue(jl_current_task)->header = (uintptr_t)jl_task_type | jl_astaggedvalue(jl_current_task)->header; - jl_value_t *pointer_void = jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_nothing_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); + + 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. + // Update that code if you change this. jl_perm_symsvec(5, "captures", "world", "source", "invoke", "specptr"), jl_svec(5, jl_any_type, jl_long_type, jl_any_type, pointer_void, pointer_void), jl_emptysvec, 0, 0, 5)->name->wrapper; @@ -2644,34 +3234,36 @@ void jl_init_types(void) JL_GC_DISABLED jl_partial_opaque_type = jl_new_datatype(jl_symbol("PartialOpaque"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(4, "typ", "env", "parent", "source"), - jl_svec(4, jl_type_type, jl_any_type, jl_method_instance_type, jl_method_type), + jl_svec(4, jl_type_type, jl_any_type, jl_method_instance_type, jl_any_type), jl_emptysvec, 0, 0, 4); // complete builtin type metadata - jl_voidpointer_type = (jl_datatype_t*)pointer_void; jl_uint8pointer_type = (jl_datatype_t*)jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_uint8_type); jl_svecset(jl_datatype_type->types, 5, jl_voidpointer_type); jl_svecset(jl_datatype_type->types, 6, jl_int32_type); - jl_svecset(jl_datatype_type->types, 7, jl_int32_type); - jl_svecset(jl_datatype_type->types, 8, jl_uint8_type); + jl_svecset(jl_datatype_type->types, 7, jl_uint16_type); jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 3, jl_voidpointer_type); jl_svecset(jl_typename_type->types, 4, jl_voidpointer_type); jl_svecset(jl_typename_type->types, 5, jl_type_type); - jl_svecset(jl_typename_type->types, 10, jl_long_type); - jl_svecset(jl_typename_type->types, 11, jl_int32_type); - jl_svecset(jl_typename_type->types, 12, jl_uint8_type); + jl_svecset(jl_typename_type->types, 6, jl_type_type); + jl_svecset(jl_typename_type->types, 11, jl_long_type); + jl_svecset(jl_typename_type->types, 12, jl_int32_type); + jl_svecset(jl_typename_type->types, 13, jl_uint8_type); + jl_svecset(jl_typename_type->types, 14, jl_uint8_type); jl_svecset(jl_methtable_type->types, 4, jl_long_type); - jl_svecset(jl_methtable_type->types, 6, jl_module_type); - jl_svecset(jl_methtable_type->types, 7, jl_array_any_type); - jl_svecset(jl_methtable_type->types, 8, jl_long_type); // voidpointer - jl_svecset(jl_methtable_type->types, 9, jl_long_type); // uint32_t plus alignment + jl_svecset(jl_methtable_type->types, 5, jl_module_type); + jl_svecset(jl_methtable_type->types, 6, jl_array_any_type); + jl_svecset(jl_methtable_type->types, 7, jl_long_type); // voidpointer + 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_methtable_type->types, 11, 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, 12, jl_voidpointer_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_binding_type->types, 1, jl_globalref_type); + jl_svecset(jl_binding_type->types, 2, jl_binding_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); @@ -2685,12 +3277,112 @@ void jl_init_types(void) JL_GC_DISABLED jl_compute_field_offsets(jl_simplevector_type); jl_compute_field_offsets(jl_symbol_type); + // override ismutationfree for builtin types that are mutable for identity + jl_string_type->ismutationfree = jl_string_type->isidentityfree = 1; + jl_symbol_type->ismutationfree = jl_symbol_type->isidentityfree = 1; + jl_simplevector_type->ismutationfree = jl_simplevector_type->isidentityfree = 1; + jl_datatype_type->ismutationfree = 1; + + // Technically not ismutationfree, but there's a separate system to deal + // with mutations for global state. + jl_module_type->ismutationfree = 1; + // 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_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 - // It seems like we probably usually end up needing the box for kinds (used in an Any context)--but is that true? - jl_uniontype_type->name->mayinlinealloc = 0; - jl_unionall_type->name->mayinlinealloc = 0; + export_small_typeof(); +} + +static jl_value_t *core(const char *name) +{ + return jl_get_global(jl_core_module, jl_symbol(name)); +} + +// fetch references to things defined in boot.jl +void post_boot_hooks(void) +{ + jl_char_type = (jl_datatype_t*)core("Char"); + XX(char); + jl_int8_type = (jl_datatype_t*)core("Int8"); + XX(int8); + jl_int16_type = (jl_datatype_t*)core("Int16"); + XX(int16); + jl_float16_type = (jl_datatype_t*)core("Float16"); + //XX(float16); + jl_float32_type = (jl_datatype_t*)core("Float32"); + //XX(float32); + jl_float64_type = (jl_datatype_t*)core("Float64"); + //XX(float64); + jl_floatingpoint_type = (jl_datatype_t*)core("AbstractFloat"); + jl_number_type = (jl_datatype_t*)core("Number"); + jl_signed_type = (jl_datatype_t*)core("Signed"); + jl_datatype_t *jl_unsigned_type = (jl_datatype_t*)core("Unsigned"); + jl_datatype_t *jl_integer_type = (jl_datatype_t*)core("Integer"); + + jl_bool_type->super = jl_integer_type; + jl_uint8_type->super = jl_unsigned_type; + jl_uint16_type->super = jl_unsigned_type; + jl_uint32_type->super = jl_unsigned_type; + jl_uint64_type->super = jl_unsigned_type; + jl_int32_type->super = jl_signed_type; + jl_int64_type->super = jl_signed_type; + + jl_errorexception_type = (jl_datatype_t*)core("ErrorException"); + jl_stackovf_exception = jl_new_struct_uninit((jl_datatype_t*)core("StackOverflowError")); + jl_diverror_exception = jl_new_struct_uninit((jl_datatype_t*)core("DivideError")); + jl_undefref_exception = jl_new_struct_uninit((jl_datatype_t*)core("UndefRefError")); + jl_undefvarerror_type = (jl_datatype_t*)core("UndefVarError"); + jl_atomicerror_type = (jl_datatype_t*)core("ConcurrencyViolationError"); + jl_interrupt_exception = jl_new_struct_uninit((jl_datatype_t*)core("InterruptException")); + jl_boundserror_type = (jl_datatype_t*)core("BoundsError"); + jl_memory_exception = jl_new_struct_uninit((jl_datatype_t*)core("OutOfMemoryError")); + jl_readonlymemory_exception = jl_new_struct_uninit((jl_datatype_t*)core("ReadOnlyMemoryError")); + jl_typeerror_type = (jl_datatype_t*)core("TypeError"); + jl_argumenterror_type = (jl_datatype_t*)core("ArgumentError"); + jl_methoderror_type = (jl_datatype_t*)core("MethodError"); + jl_loaderror_type = (jl_datatype_t*)core("LoadError"); + jl_initerror_type = (jl_datatype_t*)core("InitError"); + jl_pair_type = core("Pair"); + jl_kwcall_func = core("kwcall"); + jl_kwcall_mt = ((jl_datatype_t*)jl_typeof(jl_kwcall_func))->name->mt; + jl_atomic_store_relaxed(&jl_kwcall_mt->max_args, 0); + + jl_weakref_type = (jl_datatype_t*)core("WeakRef"); + jl_vecelement_typename = ((jl_datatype_t*)jl_unwrap_unionall(core("VecElement")))->name; + + jl_init_box_caches(); + + // set module field of primitive types + jl_svec_t *bindings = jl_atomic_load_relaxed(&jl_core_module->bindings); + jl_value_t **table = jl_svec_data(bindings); + for (size_t i = 0; i < jl_svec_len(bindings); i++) { + if (table[i] != jl_nothing) { + jl_binding_t *b = (jl_binding_t*)table[i]; + jl_value_t *v = jl_atomic_load_relaxed(&b->value); + if (v) { + if (jl_is_unionall(v)) + v = jl_unwrap_unionall(v); + if (jl_is_datatype(v)) { + jl_datatype_t *tt = (jl_datatype_t*)v; + tt->name->module = jl_core_module; + if (tt->name->mt) + tt->name->mt->module = jl_core_module; + } + } + } + } + export_small_typeof(); } +#undef XX #ifdef __cplusplus } diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 97a11df701a37..210ba8f0ae07b 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -10,7 +10,7 @@ ;; comma - higher than assignment outside parentheses, lower when inside (define prec-pair (add-dots '(=>))) (define prec-conditional '(?)) -(define prec-arrow (add-dots '(← → ↔ ↚ ↛ ↞ ↠ ↢ ↣ ↦ ↤ ↮ ⇎ ⇍ ⇏ ⇐ ⇒ ⇔ ⇴ ⇶ ⇷ ⇸ ⇹ ⇺ ⇻ ⇼ ⇽ ⇾ ⇿ ⟵ ⟶ ⟷ ⟹ ⟺ ⟻ ⟼ ⟽ ⟾ ⟿ ⤀ ⤁ ⤂ ⤃ ⤄ ⤅ ⤆ ⤇ ⤌ ⤍ ⤎ ⤏ ⤐ ⤑ ⤔ ⤕ ⤖ ⤗ ⤘ ⤝ ⤞ ⤟ ⤠ ⥄ ⥅ ⥆ ⥇ ⥈ ⥊ ⥋ ⥎ ⥐ ⥒ ⥓ ⥖ ⥗ ⥚ ⥛ ⥞ ⥟ ⥢ ⥤ ⥦ ⥧ ⥨ ⥩ ⥪ ⥫ ⥬ ⥭ ⥰ ⧴ ⬱ ⬰ ⬲ ⬳ ⬴ ⬵ ⬶ ⬷ ⬸ ⬹ ⬺ ⬻ ⬼ ⬽ ⬾ ⬿ ⭀ ⭁ ⭂ ⭃ ⭄ ⭇ ⭈ ⭉ ⭊ ⭋ ⭌ ← → ⇜ ⇝ ↜ ↝ ↩ ↪ ↫ ↬ ↼ ↽ ⇀ ⇁ ⇄ ⇆ ⇇ ⇉ ⇋ ⇌ ⇚ ⇛ ⇠ ⇢ ↷ ↶ ↺ ↻ --> <-- <-->))) +(define prec-arrow (add-dots '(← → ↔ ↚ ↛ ↞ ↠ ↢ ↣ ↦ ↤ ↮ ⇎ ⇍ ⇏ ⇐ ⇒ ⇔ ⇴ ⇶ ⇷ ⇸ ⇹ ⇺ ⇻ ⇼ ⇽ ⇾ ⇿ ⟵ ⟶ ⟷ ⟹ ⟺ ⟻ ⟼ ⟽ ⟾ ⟿ ⤀ ⤁ ⤂ ⤃ ⤄ ⤅ ⤆ ⤇ ⤌ ⤍ ⤎ ⤏ ⤐ ⤑ ⤔ ⤕ ⤖ ⤗ ⤘ ⤝ ⤞ ⤟ ⤠ ⥄ ⥅ ⥆ ⥇ ⥈ ⥊ ⥋ ⥎ ⥐ ⥒ ⥓ ⥖ ⥗ ⥚ ⥛ ⥞ ⥟ ⥢ ⥤ ⥦ ⥧ ⥨ ⥩ ⥪ ⥫ ⥬ ⥭ ⥰ ⧴ ⬱ ⬰ ⬲ ⬳ ⬴ ⬵ ⬶ ⬷ ⬸ ⬹ ⬺ ⬻ ⬼ ⬽ ⬾ ⬿ ⭀ ⭁ ⭂ ⭃ ⥷ ⭄ ⥺ ⭇ ⭈ ⭉ ⭊ ⭋ ⭌ ← → ⇜ ⇝ ↜ ↝ ↩ ↪ ↫ ↬ ↼ ↽ ⇀ ⇁ ⇄ ⇆ ⇇ ⇉ ⇋ ⇌ ⇚ ⇛ ⇠ ⇢ ↷ ↶ ↺ ↻ --> <-- <-->))) (define prec-lazy-or (add-dots '(|\|\||))) (define prec-lazy-and (add-dots '(&&))) (define prec-comparison @@ -20,7 +20,7 @@ (define prec-pipe> '(|.\|>| |\|>|)) (define prec-colon (append! '(: |..|) (add-dots '(… ⁝ ⋮ ⋱ ⋰ ⋯)))) (define prec-plus (append! '($) - (add-dots '(+ - − ¦ |\|| ⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ ⩛ ⩝ ⩡ ⩢ ⩣)))) + (add-dots '(+ - − ¦ |\|| ⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⟇ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ ⩛ ⩝ ⩡ ⩢ ⩣)))) (define prec-times (add-dots '(* / ⌿ ÷ % & · · ⋅ ∘ × |\\| ∩ ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛ ⊍ ▷ ⨝ ⟕ ⟖ ⟗ ⨟))) (define prec-rational (add-dots '(//))) (define prec-bitshift (add-dots '(<< >> >>>))) @@ -49,7 +49,7 @@ ((not (length> l 8)) (eval `(lambda (x) (not (not (,(if (every symbol? l) 'memq 'memv) x (quote ,l))))))) - ((and (every symbol? l) (not (length> l 20))) + ((and (not (length> l 20)) (every symbol? l)) (eval `(lambda (x) (not (not (memq x (quote ,l))))))) (else @@ -1352,14 +1352,22 @@ (define (rewrap-where x w) (if (and (pair? w) (eq? (car w) 'where)) - (list 'where (rewrap-where x (cadr w)) (caddr w)) + (list* 'where (rewrap-where x (cadr w)) (cddr w)) x)) +(define (parse-struct-field s) + (let ((tok (peek-token s))) + ;; allow `const x` only as a struct field + (if (eq? tok 'const) + (begin (take-token s) + `(const ,(parse-eq s))) + (parse-eq s)))) + (define (parse-struct-def s mut? word) (if (reserved-word? (peek-token s)) (error (string "invalid type name \"" (take-token s) "\""))) (let ((sig (parse-subtype-spec s))) - (begin0 (list 'struct (if mut? '(true) '(false)) sig (parse-block s)) + (begin0 (list 'struct (if mut? '(true) '(false)) sig (parse-block s parse-struct-field)) (expect-end s word)))) ;; consume any number of line endings from a token stream @@ -1456,7 +1464,13 @@ `(const ,expr) expr))) ((const) - `(const ,(parse-eq s))) + (let ((assgn (parse-eq s))) + (if (not (and (pair? assgn) + (or (eq? (car assgn) '=) + (eq? (car assgn) 'global) + (eq? (car assgn) 'local)))) + (error "expected assignment after \"const\"") + `(const ,assgn)))) ((function macro) (let* ((loc (line-number-node s)) @@ -2481,13 +2495,12 @@ (write-char (not-eof-1 (read-char (ts:port s))) b)) (loop (read-char (ts:port s)))))) - (let ((str (unescape-string (io.tostring! b)))) - (let ((len (string-length str))) - (if (= len 1) - (string.char str 0) - (if (= len 0) - (error "invalid empty character literal") - (error "character literal contains multiple characters"))))))))) + (let* ((str (unescape-string (io.tostring! b))) + (c (string.only-julia-char str))) + (or c + (if (= (string-length str) 0) + (error "invalid empty character literal") + (error "character literal contains multiple characters")))))))) ;; symbol/expression quote ((eq? t ':) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index caeca92f75803..cac8c7b5228b9 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -120,6 +120,10 @@ ;; inside ref only replace within the first argument (list* 'ref (replace-beginend (cadr ex) a n tuples last) (cddr ex))) + ;; TODO: this probably should not be allowed since keyword args aren't + ;; positional, but in this context we have just used their positions anyway + ((eq? (car ex) 'kw) + (list 'kw (cadr ex) (replace-beginend (caddr ex) a n tuples last))) (else (cons (car ex) (map (lambda (x) (replace-beginend x a n tuples last)) @@ -142,16 +146,20 @@ (idx (if (vararg? idx0) (cadr idx0) idx0)) (last (null? (cdr lst))) (replaced (replace-beginend idx a n tuples last)) - (idx (if (or (not has-va?) (simple-atom? replaced)) replaced (make-ssavalue)))) + (val (if (kwarg? replaced) (caddr replaced) replaced)) + (idx (if (or (not has-va?) (simple-atom? val)) + val (make-ssavalue)))) (loop (cdr lst) (+ n 1) - (if (eq? idx replaced) + (if (eq? idx val) stmts - (cons `(= ,idx ,replaced) + (cons `(= ,idx ,val) stmts)) (if (vararg? idx0) (cons idx tuples) tuples) (cons (if (vararg? idx0) `(... ,idx) - idx) + (if (eq? val replaced) + idx + (list 'kw (cadr replaced) idx))) ret))))))) ;; GF method does not need to keep decl expressions on lambda args @@ -358,9 +366,9 @@ (if (has-dups unused_anames) (error (string "function argument name not unique: \"" (car (has-dups unused_anames)) "\""))) (if (has-dups names) - (error "function static parameter names not unique")) + (error (string "function static parameter name not unique: \"" (car (has-dups names)) "\""))) (if (any (lambda (x) (and (not (eq? x UNUSED)) (memq x names))) anames) - (error "function argument and static parameter names must be distinct")) + (error (string "function argument and static parameter name not distinct: \"" (car (intersect names unused_anames)) "\""))) (if (or (and name (not (sym-ref-or-overlay? name))) (not (valid-name? name))) (error (string "invalid function name \"" (deparse name) "\""))) (let* ((loc (maybe-remove-functionloc! body)) @@ -374,13 +382,8 @@ `((meta generated (new (core GeneratedFunctionStub) ,gname - ,(cons 'list anames) - ,(if (null? sparams) - 'nothing - (cons 'list (map car sparams))) - ,(cadr loc) - (inert ,(caddr loc)) - (false)))))) + (call (core svec) ,@(map quotify anames)) + (call (core svec) ,@(map quotify names))))))) (list gf)) '())) (types (llist-types argl)) @@ -433,6 +436,11 @@ (block ,(scopenest (cdr names) (cdr vals) expr))))) +(define (make-assignments names vals expr) + `(block + ,@(map make-assignment names vals) + ,expr)) + (define (keywords-method-def-expr name sparams argl body rett) (let* ((kargl (cdar argl)) ;; keyword expressions (= k v) (annotations (map (lambda (a) `(meta ,(cadr a) ,(arg-name (cadr (caddr a))))) @@ -479,6 +487,15 @@ (lambda (x) (eq? x v)) vals)) keynames)) + ;; if keyword args don't depend on each other and the default + ;; values don't have embedded assignments (ick) then we can use + ;; ssavalues instead of slots in the sorter method. + (ssa-keyvars? (and (not ordered-defaults) + (not (contains assignment? vals)))) + (keyvars (if ssa-keyvars? + (map (lambda (x) (make-ssavalue)) keynames) + keynames)) + (tempslot (gensy)) ;; list of function's initial line number and meta nodes (empty if none) (prologue (extract-method-prologue body)) ;; body statements @@ -490,6 +507,7 @@ positional-sparams))) sparams)) (kw (gensy)) + (kwdecl `(|::| ,kw (core NamedTuple))) (rkw (if (null? restkw) (make-ssavalue) (symbol (string (car restkw) "...")))) (restkw (map (lambda (v) `(|::| ,v (call (top pairs) (core NamedTuple)))) restkw)) (mangled (let ((und (and name (undot-name name)))) @@ -521,7 +539,7 @@ ,(method-def-expr- name positional-sparams pargl-all `(block - ,@(without-generated prologue) + ,@(keep-first linenum? (without-generated prologue)) ,(let (;; call mangled(vals..., [rest_kw,] pargs..., [vararg]...) (ret `(return (call ,mangled ,@(if ordered-defaults keynames vals) @@ -538,19 +556,30 @@ `((|::| ;; if there are optional positional args, we need to be able to reference the function name ,(if (any kwarg? pargl) (gensy) UNUSED) - (call (core kwftype) ,ftype)) ,kw ,@pargl ,@vararg) + (call (core kwftype) ,ftype)) ,kwdecl ,@pargl ,@vararg) `(block - ,@(filter linenum? prologue) + ;; propagate method metadata to keyword sorter + ,@(map propagate-method-meta (filter meta? prologue)) + ,@(filter argwide-nospecialize-meta? prologue) + ,@(let ((lnns (filter linenum? prologue))) + (if (pair? lnns) + (list (car lnns)) + '())) ;; nospecialize meta for just positional args ,@(map (lambda (m) `(meta ,(cadr m) ,@(filter (lambda (v) (not (memq v keynames))) (cddr m)))) (filter nospecialize-meta? prologue)) - ,(scopenest - keynames + ;; If not using slots for the keyword argument values, still declare them + ;; for reflection purposes. + ,@(if ssa-keyvars? + (map (lambda (v) `(local ,v)) (reverse keynames)) + '()) + ,((if ssa-keyvars? make-assignments scopenest) + keyvars (map (lambda (v dflt) (let* ((k (decl-var v)) - (rval0 `(call (top getindex) ,kw (inert ,k))) + (rval0 `(call (core getfield) ,kw (inert ,k))) ;; note: if the "declared" type of a KW arg includes something ;; from keyword-sparams then don't assert it here, since those ;; static parameters don't have values yet. instead, the type @@ -572,9 +601,10 @@ ,temp))) ,temp)) rval0))) - `(if (call (top haskey) ,kw (quote ,k)) - ,rval - ,dflt))) + `(block (if (call (core isdefined) ,kw (quote ,k)) + (= ,tempslot ,rval) + (= ,tempslot ,dflt)) + ,tempslot))) vars vals) `(block (= ,rkw (call (top pairs) @@ -588,7 +618,7 @@ (call (top kwerr) ,kw ,@(map arg-name pargl) ,@splatted-vararg))) '()) (return (call ,mangled ;; finally, call the core function - ,@keynames + ,@keyvars ,@(if (null? restkw) '() (list rkw)) ,@(map arg-name pargl) ,@splatted-vararg)))))) @@ -722,33 +752,39 @@ (define (default-inner-ctors name field-names field-types params bounds locs) (let* ((field-names (safe-field-names field-names field-types)) - (any-ctor + (all-ctor (if (null? params) + ;; definition with exact types for all arguments + `(function (call ,name + ,@(map make-decl field-names field-types)) + (block + ,@locs + (new (outerref ,name) ,@field-names))) + #f)) + (any-ctor (if (or (not all-ctor) (any (lambda (t) (not (equal? t '(core Any)))) + field-types)) ;; definition with Any for all arguments - `(function ,(with-wheres - `(call ,(if (pair? params) - `(curly ,name ,@params) - name) - ,@field-names) - (map (lambda (b) (cons 'var-bounds b)) bounds)) + ;; only if any field type is not Any, checked at runtime + `(function (call (|::| |#ctor-self#| + ,(with-wheres + `(curly (core Type) ,(if (pair? params) + `(curly ,name ,@params) + name)) + (map (lambda (b) (cons 'var-bounds b)) bounds))) + ,@field-names) (block ,@locs - (call new ,@field-names))))) - (if (and (null? params) (any (lambda (t) (not (equal? t '(core Any)))) - field-types)) - (list - ;; definition with field types for all arguments - ;; only if any field type is not Any, checked at runtime - `(if ,(foldl (lambda (t u) - `(&& ,u (call (core ===) (core Any) ,t))) - `(call (core ===) (core Any) ,(car field-types)) - (cdr field-types)) - (block) - (function (call ,name - ,@(map make-decl field-names field-types)) - (block - ,@locs - (new (outerref ,name) ,@field-names)))) - any-ctor) + (call new ,@field-names))) ; this will add convert calls later + #f))) + (if all-ctor + (if any-ctor + (list all-ctor + `(if ,(foldl (lambda (t u) + `(&& ,u (call (core ===) (core Any) ,t))) + `(call (core ===) (core Any) ,(car field-types)) + (cdr field-types)) + '(block) + ,any-ctor)) + (list all-ctor)) (list any-ctor)))) (define (default-outer-ctor name field-names field-types params bounds locs) @@ -758,12 +794,14 @@ (map (lambda (b) (cons 'var-bounds b)) bounds)) (block ,@locs - (call (curly ,name ,@params) ,@field-names))))) + (new (curly ,name ,@params) ,@field-names))))) (define (num-non-varargs args) (count (lambda (a) (not (vararg? a))) args)) -(define (new-call Tname type-params sparams params args field-names field-types) +;; selftype?: tells us whether the called object is the type being constructed, +;; i.e. `new()` and not `new{...}()`. +(define (new-call Tname type-params sparams params args field-names field-types selftype?) (if (any kwarg? args) (error "\"new\" does not accept keyword arguments")) (let ((nnv (num-non-varargs type-params))) @@ -773,18 +811,22 @@ (error "too many type parameters specified in \"new{...}\""))) (let* ((Texpr (if (null? type-params) `(outerref ,Tname) - `(curly (outerref ,Tname) - ,@type-params))) - (tn (make-ssavalue)) + (if selftype? + '|#ctor-self#| + `(curly (outerref ,Tname) + ,@type-params)))) + (tn (if (symbol? Texpr) Texpr (make-ssavalue))) (field-convert (lambda (fld fty val) (if (equal? fty '(core Any)) val - `(call (top convert) - ,(if (and (equal? type-params params) (memq fty params) (memq fty sparams)) - fty ; the field type is a simple parameter, the usage here is of a - ; local variable (currently just handles sparam) for the bijection of params to type-params - `(call (core fieldtype) ,tn ,(+ fld 1))) - ,val))))) + (convert-for-type-decl val + ; for ty, usually use the fieldtype, not the fty expression + (if (and (not selftype?) (equal? type-params params) (memq fty params) (memq fty sparams)) + fty ; the field type is a simple parameter, the usage here is of a + ; local variable (currently just handles sparam) for the bijection of params to type-params + `(call (core fieldtype) ,tn ,(+ fld 1))) + #f + #f))))) (cond ((> (num-non-varargs args) (length field-names)) `(call (core throw) (call (top ArgumentError) ,(string "new: too many arguments (expected " (length field-names) ")")))) @@ -795,7 +837,7 @@ (let ((argt (make-ssavalue)) (nf (make-ssavalue))) `(block - (= ,tn ,Texpr) + ,@(if (symbol? tn) '() `((= ,tn ,Texpr))) (= ,argt (call (core tuple) ,@args)) (= ,nf (call (core nfields) ,argt)) (if (call (top ult_int) ,nf ,(length field-names)) @@ -807,9 +849,9 @@ (new ,tn ,@(map (lambda (fld fty) (field-convert fld fty `(call (core getfield) ,argt ,(+ fld 1) (false)))) (iota (length field-names)) (list-head field-types (length field-names)))))))) (else - `(block - (= ,tn ,Texpr) - (new ,tn ,@(map field-convert (iota (length args)) (list-head field-types (length args)) args))))))) + `(block + ,@(if (symbol? tn) '() `((= ,tn ,Texpr))) + (new ,tn ,@(map field-convert (iota (length args)) (list-head field-types (length args)) args))))))) ;; insert item at start of arglist (define (arglist-unshift sig item) @@ -822,56 +864,69 @@ ((length= lno 3) (string " around " (caddr lno) ":" (cadr lno))) (else ""))) +;; convert constructor signature from X(...) to (|#ctor-self#|::Type{X})(...), +;; or return #f if we can't +(define (ctor-sig sig) + (cond ((or (eq? (car sig) '|::|) (eq? (car sig) 'where)) + (let ((s2 (ctor-sig (cadr sig)))) + (and s2 `(,(car sig) ,s2 ,@(cddr sig))))) + ((eq? (car sig) 'call) + (let ((head (cadr sig))) + (if (decl? head) + (if (eq? (cadr head) '|#ctor-self#|) + sig ;; already in the required form + #f) + `(call (|::| |#ctor-self#| (curly (core Type) ,head)) ,@(cddr sig))))) + (else #f))) + (define (ctor-def name Tname ctor-body sig body wheres) (let* ((curly? (and (pair? name) (eq? (car name) 'curly))) (curlyargs (if curly? (cddr name) '())) (name (if curly? (cadr name) name)) (sparams (map car (map analyze-typevar wheres)))) (cond ((not (eq? name Tname)) - `(function ,(with-wheres `(call ,(if curly? - `(curly ,name ,@curlyargs) - name) - ,@sig) - wheres) + `(function ,sig ;; pass '() in order to require user-specified parameters with ;; new{...} inside a non-ctor inner definition. - ,(ctor-body body '() sparams))) + ,(ctor-body body '() sparams #f))) (else - `(function ,(with-wheres `(call ,(if curly? - `(curly ,name ,@curlyargs) - name) - ,@sig) - wheres) - ,(ctor-body body curlyargs sparams)))))) + (let ((newsig (ctor-sig sig))) + `(function ,(or newsig sig) + ,(ctor-body body curlyargs sparams (not (not newsig))))))))) ;; rewrite calls to `new( ... )` to `new` expressions on the appropriate ;; type, determined by the containing constructor definition. (define (rewrite-ctor ctor Tname params field-names field-types) - (define (ctor-body body type-params sparams) + (define (ctor-body body type-params sparams selftype?) (pattern-replace (pattern-set (pattern-lambda (call (-/ new) . args) (new-call Tname type-params sparams params - (map (lambda (a) (ctor-body a type-params sparams)) args) - field-names field-types)) + (map (lambda (a) (ctor-body a type-params sparams selftype?)) args) + field-names field-types selftype?)) (pattern-lambda (call (curly (-/ new) . p) . args) (new-call Tname p sparams params - (map (lambda (a) (ctor-body a type-params sparams)) args) - field-names field-types))) + (map (lambda (a) (ctor-body a type-params sparams selftype?)) args) + field-names field-types #f))) body)) (pattern-replace (pattern-set + ;; recognize `(t::(Type{X{T}} where T))(...)` as an inner-style constructor for X + (pattern-lambda (function (-$ (call (|::| self (where (curly (core (-/ Type)) name) . wheres)) . sig) + (|::| (call (|::| self (where (curly (core (-/ Type)) name) . wheres)) . sig) _t)) + body) + (ctor-def name Tname ctor-body (cadr __) body wheres)) ;; definitions without `where` (pattern-lambda (function (-$ (call name . sig) (|::| (call name . sig) _t)) body) - (ctor-def name Tname ctor-body sig body #f)) + (ctor-def name Tname ctor-body (cadr __) body #f)) (pattern-lambda (= (-$ (call name . sig) (|::| (call name . sig) _t)) body) - (ctor-def name Tname ctor-body sig body #f)) + (ctor-def name Tname ctor-body (cadr __) body #f)) ;; definitions with `where` (pattern-lambda (function (where (-$ (call name . sig) (|::| (call name . sig) _t)) . wheres) body) - (ctor-def name Tname ctor-body sig body wheres)) + (ctor-def name Tname ctor-body (cadr __) body wheres)) (pattern-lambda (= (where (-$ (call name . sig) (|::| (call name . sig) _t)) . wheres) body) - (ctor-def name Tname ctor-body sig body wheres))) + (ctor-def name Tname ctor-body (cadr __) body wheres))) ;; flatten `where`s first (pattern-replace @@ -1478,6 +1533,8 @@ after (cons R elts))) ((vararg? L) + (if (any vararg? (cdr lhss)) + (error "multiple \"...\" on lhs of assignment")) (if (null? (cdr lhss)) (let ((temp (if (eventually-call? (cadr L)) (gensy) (make-ssavalue)))) `(block ,@(reverse stmts) @@ -1485,8 +1542,50 @@ ,@(reverse after) (= ,(cadr L) ,temp) (unnecessary (tuple ,@(reverse elts) (... ,temp))))) - (error (string "invalid \"...\" on non-final assignment location \"" - (cadr L) "\"")))) + (let ((lhss- (reverse lhss)) + (rhss- (reverse rhss)) + (lhs-tail '()) + (rhs-tail '())) + (define (extract-tail) + (if (not (or (null? lhss-) (null? rhss-) + (vararg? (car lhss-)) (vararg? (car rhss-)))) + (begin + (set! lhs-tail (cons (car lhss-) lhs-tail)) + (set! rhs-tail (cons (car rhss-) rhs-tail)) + (set! lhss- (cdr lhss-)) + (set! rhss- (cdr rhss-)) + (extract-tail)))) + (extract-tail) + (let* ((temp (if (any (lambda (x) + (or (eventually-call? x) + (and (vararg? x) (eventually-call? (cadr x))))) + lhss-) + (gensy) + (make-ssavalue))) + (assigns (make-assignment temp `(tuple ,@(reverse rhss-)))) + (assigns (if (symbol? temp) + `((local-def ,temp) ,assigns) + (list assigns))) + (n (length lhss-)) + (st (gensy)) + (end (list after)) + (assigns (if (and (length= lhss- 1) (vararg? (car lhss-))) + (begin + (set-car! end + (cons `(= ,(cadar lhss-) ,temp) (car end))) + assigns) + (append (if (> n 0) + `(,@assigns (local ,st)) + assigns) + (destructure- 1 (reverse lhss-) temp + n st end))))) + (loop lhs-tail + (append (map (lambda (x) (if (vararg? x) (cadr x) x)) lhss-) assigned) + rhs-tail + (append (reverse assigns) stmts) + (car end) + (cons `(... ,temp) elts)))))) + ((vararg? R) (let ((temp (make-ssavalue))) `(block ,@(reverse stmts) @@ -1532,7 +1631,7 @@ ;; for example a[f(x)] => (temp=f(x); a[temp]) ;; returns a pair (expr . assignments) ;; where 'assignments' is a list of needed assignment statements -(define (remove-argument-side-effects e (tup #f)) +(define (remove-argument-side-effects e) (if (not (pair? e)) (cons e '()) (let ((a '())) @@ -1540,14 +1639,8 @@ (cond ((effect-free? x) x) ((or (eq? (car x) '...) (eq? (car x) '&)) `(,(car x) ,(arg-to-temp (cadr x)))) - ((or (eq? (car x) 'kw) (and tup (eq? (car x) '=))) + ((eq? (car x) 'kw) `(,(car x) ,(cadr x) ,(arg-to-temp (caddr x)))) - ((eq? (car x) 'parameters) - `(parameters ,@(map arg-to-temp (cdr x)))) - ((eq? (car x) 'tuple) - (let ((tmp (remove-argument-side-effects x #t))) - (set! a (revappend (cdr tmp) a)) - (car tmp))) (else (let ((g (make-ssavalue))) (begin (set! a (cons `(= ,g ,x) a)) @@ -1572,7 +1665,7 @@ (define (kwcall-unless-empty f pa kw-container-test kw-container) `(if (call (top isempty) ,kw-container-test) (call ,f ,@pa) - (call (call (core kwfunc) ,f) ,kw-container ,f ,@pa))) + (call (core kwcall) ,kw-container ,f ,@pa))) (let ((f (if (sym-ref? fexpr) fexpr (make-ssavalue))) (kw-container (make-ssavalue))) @@ -1586,7 +1679,7 @@ #t)) ,(if (every vararg? kw) (kwcall-unless-empty f pa kw-container kw-container) - `(call (call (core kwfunc) ,f) ,kw-container ,f ,@pa))))) + `(call (core kwcall) ,kw-container ,f ,@pa))))) ;; convert `a+=b` to `a=a+b` (define (expand-update-operator- op op= lhs rhs declT) @@ -2165,6 +2258,59 @@ lhss) (unnecessary ,xx)))) +;; implement tuple destructuring, possibly with slurping +;; +;; `i`: index of the current lhs arg +;; `lhss`: remaining lhs args +;; `xx`: the rhs, already either an ssavalue or something simple +;; `st`: empty list if i=1, otherwise contains the iteration state +;; `n`: total nr of lhs args +;; `end`: car collects statements to be executed afterwards. +;; In general, actual assignments should only happen after +;; the whole iterator is desctructured (https://github.com/JuliaLang/julia/issues/40574) +(define (destructure- i lhss xx n st end) + (if (null? lhss) + '() + (let* ((lhs (car lhss)) + (lhs- (cond ((or (symbol? lhs) (ssavalue? lhs)) + lhs) + ((vararg? lhs) + (let ((lhs- (cadr lhs))) + (if (or (symbol? lhs-) (ssavalue? lhs-)) + lhs + `(|...| ,(if (eventually-call? lhs-) + (gensy) + (make-ssavalue)))))) + ;; can't use ssavalues if it's a function definition + ((eventually-call? lhs) (gensy)) + (else (make-ssavalue))))) + (if (and (vararg? lhs) (any vararg? (cdr lhss))) + (error "multiple \"...\" on lhs of assignment")) + (if (not (eq? lhs lhs-)) + (if (vararg? lhs) + (set-car! end (cons (expand-forms `(= ,(cadr lhs) ,(cadr lhs-))) (car end))) + (set-car! end (cons (expand-forms `(= ,lhs ,lhs-)) (car end))))) + (if (vararg? lhs-) + (if (= i n) + (if (underscore-symbol? (cadr lhs-)) + '() + (list (expand-forms + `(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 1) '() `(,st))))))) + (let ((tail (if (eventually-call? lhs) (gensy) (make-ssavalue)))) + (cons (expand-forms + (lower-tuple-assignment + (list (cadr lhs-) tail) + `(call (top split_rest) ,xx ,(- n i) ,@(if (eq? i 1) '() `(,st))))) + (destructure- 1 (cdr lhss) tail (- n i) st end)))) + (cons (expand-forms + (lower-tuple-assignment + (if (= i n) + (list lhs-) + (list lhs- st)) + `(call (top indexed_iterate) + ,xx ,i ,@(if (eq? i 1) '() `(,st))))) + (destructure- (+ i 1) (cdr lhss) xx n st end)))))) + (define (expand-tuple-destruct lhss x) (define (sides-match? l r) ;; l and r either have equal lengths, or r has a trailing ... @@ -2181,64 +2327,26 @@ (tuple-to-assignments lhss x)) ;; (a, b, ...) = other (begin - ;; like memq, but if last element of lhss is (... sym), - ;; check against sym instead + ;; like memq, but if lhs is (... sym), check against sym instead (define (in-lhs? x lhss) (if (null? lhss) #f (let ((l (car lhss))) (cond ((and (pair? l) (eq? (car l) '|...|)) - (if (null? (cdr lhss)) - (eq? (cadr l) x) - (error (string "invalid \"...\" on non-final assignment location \"" - (cadr l) "\"")))) + (eq? (cadr l) x)) ((eq? l x) #t) (else (in-lhs? x (cdr lhss))))))) ;; in-lhs? also checks for invalid syntax, so always call it first (let* ((xx (maybe-ssavalue lhss x in-lhs?)) (ini (if (eq? x xx) '() (list (sink-assignment xx (expand-forms x))))) (n (length lhss)) - ;; skip last assignment if it is an all-underscore vararg - (n (if (> n 0) - (let ((l (last lhss))) - (if (and (vararg? l) (underscore-symbol? (cadr l))) - (- n 1) - n)) - n)) (st (gensy)) - (end '())) + (end (list (list)))) `(block ,@(if (> n 0) `((local ,st)) '()) ,@ini - ,@(map (lambda (i lhs) - (let ((lhs- (cond ((or (symbol? lhs) (ssavalue? lhs)) - lhs) - ((vararg? lhs) - (let ((lhs- (cadr lhs))) - (if (or (symbol? lhs-) (ssavalue? lhs-)) - lhs - `(|...| ,(if (eventually-call? lhs-) - (gensy) - (make-ssavalue)))))) - ;; can't use ssavalues if it's a function definition - ((eventually-call? lhs) (gensy)) - (else (make-ssavalue))))) - (if (not (eq? lhs lhs-)) - (if (vararg? lhs) - (set! end (cons (expand-forms `(= ,(cadr lhs) ,(cadr lhs-))) end)) - (set! end (cons (expand-forms `(= ,lhs ,lhs-)) end)))) - (expand-forms - (if (vararg? lhs-) - `(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 0) '() `(,st)))) - (lower-tuple-assignment - (if (= i (- n 1)) - (list lhs-) - (list lhs- st)) - `(call (top indexed_iterate) - ,xx ,(+ i 1) ,@(if (eq? i 0) '() `(,st)))))))) - (iota n) - lhss) - ,@(reverse end) + ,@(destructure- 1 lhss xx n st end) + ,@(reverse (car end)) (unnecessary ,xx)))))) ;; move an assignment into the last statement of a block to keep more statements at top level @@ -2805,18 +2913,17 @@ ,(construct-loops (reverse itrs) (reverse iv)) ,result))))) -(define (lhs-vars e) - (cond ((symdecl? e) (list (decl-var e))) - ((and (pair? e) (eq? (car e) 'tuple)) - (apply append (map lhs-vars (cdr e)))) - (else '()))) - (define (lhs-decls e) (cond ((symdecl? e) (list e)) - ((and (pair? e) (eq? (car e) 'tuple)) + ((and (pair? e) + (or (eq? (car e) 'tuple) + (eq? (car e) 'parameters))) (apply append (map lhs-decls (cdr e)))) (else '()))) +(define (lhs-vars e) + (map decl-var (lhs-decls e))) + (define (all-decl-vars e) ;; map decl-var over every level of an assignment LHS (cond ((eventually-call? e) e) ((decl? e) (decl-var e)) @@ -3149,8 +3256,9 @@ ((and (pair? e) (eq? (car e) 'with-static-parameters)) (free-vars- (cadr e) tab)) ((or (atom? e) (quoted? e)) tab) ((eq? (car e) 'lambda) - (let ((bound (lambda-all-vars e))) - (for-each (lambda (v) (if (not (memq v bound)) (put! tab v #t))) + (let ((bound (table))) + (for-each (lambda (b) (put! bound b #t)) (lambda-all-vars e)) + (for-each (lambda (v) (if (not (has? bound v)) (put! tab v #t))) (free-vars (lam:body e)))) tab) (else @@ -3243,6 +3351,11 @@ (let ((vi (get tab (cadr e) #f))) (if vi (vinfo:set-called! vi #t)) + ;; calls to functions with keyword args have head of `kwcall` first + (if (and (length> e 3) (equal? (cadr e) '(core kwcall))) + (let ((vi2 (get tab (cadddr e) #f))) + (if vi2 + (vinfo:set-called! vi2 #t)))) (for-each (lambda (x) (analyze-vars x env captvars sp tab)) (cdr e)))) ((decl) @@ -3310,9 +3423,9 @@ f(x) = yt(x) (call (core svec) ,@(map quotify fields)) (call (core svec)) (false) ,(length fields))) + (call (core _setsuper!) ,s ,super) (= (outerref ,name) ,s) - (call (core _setsuper!) ,name ,super) - (call (core _typebody!) ,name (call (core svec) ,@types)) + (call (core _typebody!) ,s (call (core svec) ,@types)) (return (null)))))))) (define (type-for-closure name fields super) @@ -3324,9 +3437,9 @@ f(x) = yt(x) (call (core svec) ,@(map quotify fields)) (call (core svec)) (false) ,(length fields))) + (call (core _setsuper!) ,s ,super) (= (outerref ,name) ,s) - (call (core _setsuper!) ,name ,super) - (call (core _typebody!) ,name + (call (core _typebody!) ,s (call (core svec) ,@(map (lambda (v) '(core Box)) fields))) (return (null)))))))) @@ -3363,13 +3476,13 @@ f(x) = yt(x) (define (convert-lambda lam fname interp capt-sp opaq) (let ((body (add-box-inits-to-body - lam (cl-convert (cadddr lam) fname lam (table) (table) #f interp opaq)))) + lam (cl-convert (cadddr lam) fname lam (table) (table) #f interp opaq (table) (vinfo-to-table (car (lam:vinfo lam))))))) `(lambda ,(lam:args lam) (,(clear-capture-bits (car (lam:vinfo lam))) () ,(caddr (lam:vinfo lam)) ,(delete-duplicates (append (lam:sp lam) capt-sp))) - ,body))) + ,body))) ;; renumber ssavalues assigned in an expr, allowing it to be repeated (define (renumber-assigned-ssavalues e) @@ -3389,26 +3502,34 @@ f(x) = yt(x) (cons (car x) (map do-replace (cdr x)))))))))) -(define (convert-for-type-decl rhs t) +(define (convert-for-type-decl rhs t assert lam) (if (equal? t '(core Any)) rhs - (let* ((temp (if (or (atom? t) (ssavalue? t) (quoted? t)) + (let* ((new-mutable-var + (lambda () (let ((g (gensy))) + (if lam (set-car! (lam:vinfo lam) (append (car (lam:vinfo lam)) `((,g Any 10))))) + g))) + (left (if (or (atom? t) (ssavalue? t) (quoted? t)) #f (make-ssavalue))) - (ty (or temp t)) - (ex `(call (core typeassert) - (call (top convert) ,ty ,rhs) - ,ty))) - (if temp - `(block (= ,temp ,(renumber-assigned-ssavalues t)) ,ex) - ex)))) + (temp (new-mutable-var)) ; use a slot to permit union-splitting this in inference + (ty (or left t)) + (ex `(call (top convert) ,ty ,temp)) + (ex (if assert `(call (core typeassert) ,ex ,ty) ex)) + (ex `(= ,temp ,ex)) + (ex `(if (call (core isa) ,temp ,ty) (null) ,ex)) + (t (if left (renumber-assigned-ssavalues t) t)) + (ex `((= ,temp ,rhs) ,ex ,temp)) + (ex (if left (cons `(= ,left ,t) ex) ex)) + (ex (if lam ex (cons `(local-def ,temp) ex)))) + (cons 'block ex)))) (define (capt-var-access var fname opaq) (if opaq `(call (core getfield) ,fname ,(get opaq var)) `(call (core getfield) ,fname (inert ,var)))) -(define (convert-global-assignment var rhs0 globals) +(define (convert-global-assignment var rhs0 globals lam) (let* ((rhs1 (if (or (simple-atom? rhs0) (equal? rhs0 '(the_exception))) rhs0 @@ -3416,7 +3537,7 @@ f(x) = yt(x) (ref (binding-to-globalref var)) (ty `(call (core get_binding_type) ,(cadr ref) (inert ,(caddr ref)))) (rhs (if (get globals ref #t) ;; no type declaration for constants - (convert-for-type-decl rhs1 ty) + (convert-for-type-decl rhs1 ty #f lam) rhs1)) (ex `(= ,var ,rhs))) (if (eq? rhs1 rhs0) @@ -3430,10 +3551,10 @@ f(x) = yt(x) ;; declared types. ;; when doing this, the original value needs to be preserved, to ;; ensure the expression `a=b` always returns exactly `b`. -(define (convert-assignment var rhs0 fname lam interp opaq globals) +(define (convert-assignment var rhs0 fname lam interp opaq globals locals) (cond ((symbol? var) - (let* ((vi (assq var (car (lam:vinfo lam)))) + (let* ((vi (get locals var #f)) (cv (assq var (cadr (lam:vinfo lam)))) (vt (or (and vi (vinfo:type vi)) (and cv (vinfo:type cv)) @@ -3443,14 +3564,12 @@ f(x) = yt(x) (if (and (not closed) (not capt) (equal? vt '(core Any))) (if (or (local-in? var lam) (underscore-symbol? var)) `(= ,var ,rhs0) - (convert-global-assignment var rhs0 globals)) + (convert-global-assignment var rhs0 globals lam)) (let* ((rhs1 (if (or (simple-atom? rhs0) (equal? rhs0 '(the_exception))) rhs0 (make-ssavalue))) - (rhs (if (equal? vt '(core Any)) - rhs1 - (convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f #f interp opaq)))) + (rhs (convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f #f interp opaq (table) locals) #t lam)) (ex (cond (closed `(call (core setfield!) ,(if interp `($ ,var) @@ -3465,7 +3584,7 @@ f(x) = yt(x) ,ex ,rhs1)))))) ((or (outerref? var) (globalref? var)) - (convert-global-assignment var rhs0 globals)) + (convert-global-assignment var rhs0 globals lam)) ((ssavalue? var) `(= ,var ,rhs0)) (else @@ -3569,8 +3688,9 @@ f(x) = yt(x) const atomic null true false ssavalue isdefined toplevel module lambda error gc_preserve_begin gc_preserve_end import using export inline noinline))) -(define (local-in? s lam) - (or (assq s (car (lam:vinfo lam))) +(define (local-in? s lam (tab #f)) + (or (and tab (has? tab s)) + (assq s (car (lam:vinfo lam))) (assq s (cadr (lam:vinfo lam))))) ;; Try to identify never-undef variables, and then clear the `captured` flag for single-assigned, @@ -3725,17 +3845,17 @@ f(x) = yt(x) (define (toplevel-preserving? e) (and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse)))) -(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq (globals (table))) +(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq (globals (table)) (locals (table))) (if toplevel (map (lambda (x) (let ((tl (lift-toplevel (cl-convert x fname lam namemap defined (and toplevel (toplevel-preserving? x)) - interp opaq globals)))) + interp opaq globals locals)))) (if (null? (cdr tl)) (car tl) `(block ,@(cdr tl) ,(car tl))))) exprs) - (map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq globals)) exprs))) + (map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq globals locals)) exprs))) (define (prepare-lambda! lam) ;; mark all non-arguments as assigned, since locals that are never assigned @@ -3744,11 +3864,11 @@ f(x) = yt(x) (list-tail (car (lam:vinfo lam)) (length (lam:args lam)))) (lambda-optimize-vars! lam)) -(define (cl-convert e fname lam namemap defined toplevel interp opaq (globals (table))) +(define (cl-convert e fname lam namemap defined toplevel interp opaq (globals (table)) (locals (table))) (if (and (not lam) (not (and (pair? e) (memq (car e) '(lambda method macro opaque_closure))))) (if (atom? e) e - (cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals))) + (cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals locals))) (cond ((symbol? e) (define (new-undef-var name) @@ -3767,12 +3887,12 @@ f(x) = yt(x) (val (if (equal? typ '(core Any)) val `(call (core typeassert) ,val - ,(cl-convert typ fname lam namemap defined toplevel interp opaq globals))))) + ,(cl-convert typ fname lam namemap defined toplevel interp opaq globals locals))))) `(block ,@(if (eq? box access) '() `((= ,access ,box))) ,undefcheck ,val))) - (let ((vi (assq e (car (lam:vinfo lam)))) + (let ((vi (get locals e #f)) (cv (assq e (cadr (lam:vinfo lam))))) (cond ((eq? e fname) e) ((memq e (lam:sp lam)) e) @@ -3799,15 +3919,15 @@ f(x) = yt(x) e) ((=) (let ((var (cadr e)) - (rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq globals))) - (convert-assignment var rhs fname lam interp opaq globals))) + (rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq globals locals))) + (convert-assignment var rhs fname lam interp opaq globals locals))) ((local-def) ;; make new Box for local declaration of defined variable - (let ((vi (assq (cadr e) (car (lam:vinfo lam))))) + (let ((vi (get locals (cadr e) #f))) (if (and vi (vinfo:asgn vi) (vinfo:capt vi)) `(= ,(cadr e) (call (core Box))) '(null)))) ((local) ;; convert local declarations to newvar statements - (let ((vi (assq (cadr e) (car (lam:vinfo lam))))) + (let ((vi (get locals (cadr e) #f))) (if (and vi (vinfo:asgn vi) (vinfo:capt vi)) `(= ,(cadr e) (call (core Box))) (if (vinfo:never-undef vi) @@ -3818,12 +3938,12 @@ f(x) = yt(x) e) ((atomic) e) ((const-if-global) - (if (local-in? (cadr e) lam) + (if (local-in? (cadr e) lam locals) '(null) `(const ,(cadr e)))) ((isdefined) ;; convert isdefined expr to function for closure converted variables (let* ((sym (cadr e)) - (vi (and (symbol? sym) (assq sym (car (lam:vinfo lam))))) + (vi (and (symbol? sym) (get locals sym #f))) (cv (and (symbol? sym) (assq sym (cadr (lam:vinfo lam)))))) (cond ((eq? sym fname) e) ((memq sym (lam:sp lam)) e) @@ -3854,7 +3974,7 @@ f(x) = yt(x) v))) cvs))) `(new_opaque_closure - ,(cadr e) (call (core apply_type) Union) (core Any) + ,(cadr e) (call (core apply_type) (core Union)) (core Any) (opaque_closure_method (null) ,nargs ,isva ,functionloc ,(convert-lambda lam2 (car (lam:args lam2)) #f '() (symbol-to-idx-map cvs))) ,@var-exprs)))) ((method) @@ -3863,13 +3983,13 @@ f(x) = yt(x) (lam2 (if short #f (cadddr e))) (vis (if short '(() () ()) (lam:vinfo lam2))) (cvs (map car (cadr vis))) - (local? (lambda (s) (and lam (symbol? s) (local-in? s lam)))) + (local? (lambda (s) (and lam (symbol? s) (local-in? s lam locals)))) (local (and (not (outerref? (cadr e))) (local? name))) (sig (and (not short) (caddr e))) (sp-inits (if (or short (not (eq? (car sig) 'block))) '() (map-cl-convert (butlast (cdr sig)) - fname lam namemap defined toplevel interp opaq globals))) + fname lam namemap defined toplevel interp opaq globals locals))) (sig (and sig (if (eq? (car sig) 'block) (last sig) sig)))) @@ -3896,10 +4016,11 @@ f(x) = yt(x) ;; anonymous functions with keyword args generate global ;; functions that refer to the type of a local function (rename-sig-types sig namemap) - fname lam namemap defined toplevel interp opaq globals) + fname lam namemap defined toplevel interp opaq globals locals) ,(let ((body (add-box-inits-to-body lam2 - (cl-convert (cadddr lam2) 'anon lam2 (table) (table) #f interp opaq)))) + (cl-convert (cadddr lam2) 'anon lam2 (table) (table) #f interp opaq (table) + (vinfo-to-table (car (lam:vinfo lam2))))))) `(lambda ,(cadr lam2) (,(clear-capture-bits (car vis)) ,@(cdr vis)) @@ -3910,7 +4031,7 @@ f(x) = yt(x) (newlam (compact-and-renumber (linearize (car exprs)) 'none 0))) `(toplevel-butfirst (block ,@sp-inits - (method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq globals) + (method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq globals locals) ,(julia-bq-macro newlam))) ,@top-stmts)))) @@ -4013,7 +4134,7 @@ f(x) = yt(x) (append (map (lambda (gs tvar) (make-assignment gs `(call (core TypeVar) ',tvar (core Any)))) closure-param-syms closure-param-names) - `((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq globals) + `((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq globals locals) ,(convert-lambda lam2 (if iskw (caddr (lam:args lam2)) @@ -4052,7 +4173,7 @@ f(x) = yt(x) (begin (put! defined name #t) `(toplevel-butfirst - ,(convert-assignment name mk-closure fname lam interp opaq globals) + ,(convert-assignment name mk-closure fname lam interp opaq globals locals) ,@typedef ,@(map (lambda (v) `(moved-local ,v)) moved-vars) ,@sp-inits @@ -4060,44 +4181,45 @@ f(x) = yt(x) ((lambda) ;; happens inside (thunk ...) and generated function bodies (for-each (lambda (vi) (vinfo:set-asgn! vi #t)) (list-tail (car (lam:vinfo e)) (length (lam:args e)))) + (lambda-optimize-vars! e) (let ((body (map-cl-convert (cdr (lam:body e)) 'anon - (lambda-optimize-vars! e) + e (table) (table) (null? (cadr e)) ;; only toplevel thunks have 0 args - interp opaq globals))) + interp opaq globals (vinfo-to-table (car (lam:vinfo e)))))) `(lambda ,(cadr e) (,(clear-capture-bits (car (lam:vinfo e))) () ,@(cddr (lam:vinfo e))) (block ,@body)))) ;; remaining `::` expressions are type assertions ((|::|) - (cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq globals)) + (cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq globals locals)) ;; remaining `decl` expressions are only type assertions if the ;; argument is global or a non-symbol. ((decl) (cond ((and (symbol? (cadr e)) - (local-in? (cadr e) lam)) + (local-in? (cadr e) lam locals)) '(null)) (else (cl-convert - (let ((ref (binding-to-globalref (cadr e)))) - (if ref - (begin - (put! globals ref #t) - `(block - (toplevel-only set_binding_type! ,(cadr e)) - (call (core set_binding_type!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e)))) - `(call (core typeassert) ,@(cdr e)))) - fname lam namemap defined toplevel interp opaq globals)))) + (let ((ref (binding-to-globalref (cadr e)))) + (if ref + (begin + (put! globals ref #t) + `(block + (toplevel-only set_binding_type! ,(cadr e)) + (call (core set_binding_type!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e)))) + `(call (core typeassert) ,@(cdr e)))) + fname lam namemap defined toplevel interp opaq globals locals)))) ;; `with-static-parameters` expressions can be removed now; used only by analyze-vars ((with-static-parameters) - (cl-convert (cadr e) fname lam namemap defined toplevel interp opaq globals)) + (cl-convert (cadr e) fname lam namemap defined toplevel interp opaq globals locals)) (else (cons (car e) - (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals)))))))) + (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals locals)))))))) -(define (closure-convert e) (cl-convert e #f #f #f #f #f #f #f)) +(define (closure-convert e) (cl-convert e #f #f (table) (table) #f #f #f)) ;; pass 5: convert to linear IR @@ -4139,6 +4261,7 @@ f(x) = yt(x) (current-loc #f) (rett #f) (global-const-error #f) + (vinfo-table (vinfo-to-table (car (lam:vinfo lam)))) (arg-map #f) ;; map arguments to new names if they are assigned (label-counter 0) ;; counter for generating label addresses (label-map (table)) ;; maps label names to generated addresses @@ -4151,6 +4274,7 @@ f(x) = yt(x) (handler-level 0) ;; exception handler nesting depth (catch-token-stack '())) ;; tokens identifying handler enter for current catch blocks (define (emit c) + (or c (raise "missing value in IR")) (set! code (cons c code)) c) (define (make-label) @@ -4197,17 +4321,21 @@ f(x) = yt(x) (loop (cdr s)))))) `(pop_exception ,restore-token)))) (define (emit-return x) - (define (actually-return x) - (let* ((x (if rett - (compile (convert-for-type-decl x rett) '() #t #f) - x)) - (tmp (if ((if (null? catch-token-stack) valid-ir-return? simple-atom?) x) + (define (emit- x) + (let* ((tmp (if ((if (null? catch-token-stack) valid-ir-return? simple-atom?) x) #f (make-ssavalue)))) - (if tmp (emit `(= ,tmp ,x))) + (if tmp + (begin (emit `(= ,tmp ,x)) tmp) + x))) + (define (actually-return x) + (let* ((x (if rett + (compile (convert-for-type-decl (emit- x) rett #t lam) '() #t #f) + x)) + (x (emit- x))) (let ((pexc (pop-exc-expr catch-token-stack '()))) (if pexc (emit pexc))) - (emit `(return ,(or tmp x))))) + (emit `(return ,x)))) (if x (if (> handler-level 0) (let ((tmp (cond ((and (simple-atom? x) (or (not (ssavalue? x)) (not finally-handler))) #f) @@ -4267,10 +4395,12 @@ f(x) = yt(x) (not (simple-atom? arg)) (not (simple-atom? aval)) (not (and (pair? arg) - (memq (car arg) '(quote inert top core globalref outerref boundscheck)))) + (memq (car arg) '(quote inert top core boundscheck)))) (not (and (symbol? aval) ;; function args are immutable and always assigned (memq aval (lam:args lam)))) - (not (and (symbol? arg) + (not (and (or (symbol? arg) + (and (pair? arg) + (memq (car arg) '(globalref outerref)))) (or (null? (cdr lst)) (null? vals))))) (let ((tmp (make-ssavalue))) @@ -4289,6 +4419,9 @@ f(x) = yt(x) cnd))) (define (emit-cond cnd break-labels endl) (let* ((cnd (if (and (pair? cnd) (eq? (car cnd) 'block)) + (flatten-ex 'block cnd) + cnd)) + (cnd (if (and (pair? cnd) (eq? (car cnd) 'block)) (begin (if (length> cnd 2) (compile (butlast cnd) break-labels #f #f)) (last cnd)) cnd)) @@ -4316,7 +4449,8 @@ f(x) = yt(x) (emit `(= ,lhs ,rhs)) (let ((rr (make-ssavalue))) (emit `(= ,rr ,rhs)) - (emit `(= ,lhs ,rr))))) + (emit `(= ,lhs ,rr)))) + (emit `(= ,lhs (null)))) ; in unreachable code (such as after return), still emit the assignment so that the structure of those uses is preserved #f) ;; the interpreter loop. `break-labels` keeps track of the labels to jump to ;; for all currently closing break-blocks. @@ -4414,32 +4548,36 @@ f(x) = yt(x) (not (eq? e (lam:body lam)))))) (if file-diff (set! filename fname)) (if need-meta (emit `(meta push_loc ,fname))) - (begin0 - (let loop ((xs (cdr e))) + (let ((v (let loop ((xs (cdr e))) (if (null? (cdr xs)) (compile (car xs) break-labels value tail) (begin (compile (car xs) break-labels #f #f) - (loop (cdr xs))))) - (if need-meta - (if (or (not tail) - (and (pair? (car code)) - (or (eq? (cdar code) 'meta) - (eq? (cdar code) 'line)))) - (emit '(meta pop_loc)) - ;; If we need to return the last non-meta expression - ;; splice the pop before the result - (let ((retv (car code)) - (body (cdr code))) - (set! code body) - (if (complex-return? retv) - (let ((tmp (make-ssavalue))) - (emit `(= ,tmp ,(cadr retv))) - (emit '(meta pop_loc)) - (emit `(return ,tmp))) - (begin - (emit '(meta pop_loc)) - (emit retv)))))) - (if file-diff (set! filename last-fname))))) + (loop (cdr xs))))))) + (if need-meta + (cond (tail + ;; If we need to return the last non-meta expression + ;; attempt to splice the pop_loc before the return + ;; so that the return location always gets + ;; attributed to the right level of macro + (if (and (pair? code) (return? (car code))) + (let ((retv (cadr (car code)))) + (set! code (cdr code)) + (if (not (simple-atom? retv)) + (let ((tmp (make-ssavalue))) + (emit `(= ,tmp ,retv)) + (set! retv tmp))) + (emit '(meta pop_loc)) + (emit `(return ,retv))) + (emit '(meta pop_loc)))) + ((and v value (not (simple-atom? v))) + (let ((tmp (make-ssavalue))) + (emit `(= ,tmp ,v)) + (set! v tmp) + (emit `(meta pop_loc)))) + (else + (emit `(meta pop_loc))))) + (if file-diff (set! filename last-fname)) + v))) ((return) (compile (cadr e) break-labels #t #t) #f) @@ -4560,7 +4698,7 @@ f(x) = yt(x) (begin (mark-label els) (let ((v3 (compile (cadddr e) break-labels value tail))) ;; emit else block code (if val (emit-assignment val v3))) - (emit `(goto ,endl)))) + (if endl (emit `(goto ,endl))))) ;; emit either catch or finally block (mark-label catch) (emit `(leave 1)) @@ -4601,7 +4739,7 @@ f(x) = yt(x) ;; avoid duplicate newvar nodes (if (and (not (and (pair? code) (equal? (car code) e))) ;; exclude deleted vars - (assq (cadr e) (car (lam:vinfo lam)))) + (has? vinfo-table (cadr e))) (emit e) #f)) ((global) ; keep global declarations as statements @@ -4816,22 +4954,20 @@ f(x) = yt(x) (linetable '(list)) (labltable (table)) (ssavtable (table)) - (reachable #t) (current-loc 0) (current-file file) (current-line line) (locstack '()) (i 1)) (define (emit e) + (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)) (set! current-loc 1))) - (if (or reachable - (and (pair? e) (memq (car e) '(meta inbounds gc_preserve_begin gc_preserve_end aliasscope popaliasscope inline noinline)))) - (begin (set! code (cons e code)) - (set! i (+ i 1)) - (set! locs (cons current-loc locs))))) + (set! code (cons e code)) + (set! i (+ i 1)) + (set! locs (cons current-loc locs))) (let loop ((stmts (cdr body))) (if (pair? stmts) (let ((e (car stmts))) @@ -4863,7 +4999,6 @@ f(x) = yt(x) (set! current-line (cadr l)) (set! current-file (caddr l)))) ((eq? (car e) 'label) - (set! reachable #t) (put! labltable (cadr e) i)) ((and (assignment? e) (ssavalue? (cadr e))) (let ((idx (and (ssavalue? (caddr e)) (get ssavtable (cadr (caddr e)) #f)))) @@ -4874,9 +5009,7 @@ f(x) = yt(x) (put! ssavtable (cadr (cadr e)) i) (emit (caddr e)))))) (else - (emit e) - (if (or (eq? (car e) 'goto) (eq? (car e) 'return)) - (set! reachable #f)))) + (emit e))) (loop (cdr stmts))))) (vector (reverse code) (reverse locs) (reverse linetable) ssavtable labltable))) @@ -4910,8 +5043,8 @@ f(x) = yt(x) ((or (atom? e) (quoted? e) (eq? (car e) 'global)) e) ((ssavalue? e) - (let ((idx (or (get ssavalue-table (cadr e) #f) - (error "ssavalue with no def")))) + (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))) diff --git a/src/julia.expmap b/src/julia.expmap index 558dfec6bd260..94b955e95981f 100644 --- a/src/julia.expmap +++ b/src/julia.expmap @@ -1,15 +1,16 @@ { global: - __asan*; - __tsan*; pthread*; __stack_chk_guard; asprintf; bitvector_*; ios_*; + arraylist_grow; small_arraylist_grow; + small_typeof; jl_*; ijl_*; + _jl_mutex_*; rec_backtrace; julia_*; libsupport_init; @@ -18,10 +19,7 @@ memhash32; memhash32_seed; memhash_seed; - restore_arg_area_loc; restore_signals; - rl_clear_input; - save_arg_area_loc; u8_*; uv_*; add_library_mapping; @@ -29,22 +27,19 @@ jlbacktrace; jlbacktracet; _IO_stdin_used; - __ZN4llvm23createLowerSimdLoopPassEv; _Z24jl_coverage_data_pointerN4llvm9StringRefEi; _Z22jl_coverage_alloc_lineN4llvm9StringRefEi; _Z22jl_malloc_data_pointerN4llvm9StringRefEi; LLVMExtra*; + llvmGetPassPluginInfo; + + /* Make visible so that linker will merge duplicate definitions across DSO boundaries */ + _ZN4llvm3Any6TypeId*; /* freebsd */ environ; __progname; - /* compiler run-time intrinsics */ - __gnu_h2f_ieee; - __extendhfsf2; - __gnu_f2h_ieee; - __truncdfhf2; - local: *; }; diff --git a/src/julia.h b/src/julia.h index d726162b88213..286bef615c92d 100644 --- a/src/julia.h +++ b/src/julia.h @@ -3,10 +3,16 @@ #ifndef JULIA_H #define JULIA_H -#ifdef LIBRARY_EXPORTS -#include "jl_internal_funcs.inc" +#if defined(JL_LIBRARY_EXPORTS_INTERNAL) || defined(JL_LIBRARY_EXPORTS_CODEGEN) +#define JL_LIBRARY_EXPORTS +#endif +#ifdef JL_LIBRARY_EXPORTS +// Generated file, needs to be searched in include paths so that the builddir +// retains priority +#include #undef jl_setjmp #undef jl_longjmp +#undef jl_egal #endif #include "julia_fasttls.h" @@ -55,20 +61,21 @@ # define JL_NORETURN __attribute__ ((noreturn)) # define JL_CONST_FUNC __attribute__((const)) # define JL_USED_FUNC __attribute__((used)) -# define JL_SECTION(name) __attribute__((section(name))) -# define JL_THREAD_LOCAL __thread #else # define JL_NORETURN # define JL_CONST_FUNC # define JL_USED_FUNC -# define JL_THREAD_LOCAL #endif #define container_of(ptr, type, member) \ ((type *) ((char *)(ptr) - offsetof(type, member))) typedef struct _jl_taggedvalue_t jl_taggedvalue_t; +typedef struct _jl_tls_states_t *jl_ptls_t; +#ifdef JL_LIBRARY_EXPORTS +#include "uv.h" +#endif #include "julia_atomics.h" #include "julia_threads.h" #include "julia_assert.h" @@ -87,6 +94,13 @@ typedef struct _jl_value_t jl_value_t; struct _jl_taggedvalue_bits { uintptr_t gc:2; + uintptr_t in_image:1; + uintptr_t unused:1; +#ifdef _P64 + uintptr_t tag:60; +#else + uintptr_t tag:28; +#endif }; JL_EXTENSION struct _jl_taggedvalue_t { @@ -99,6 +113,7 @@ JL_EXTENSION struct _jl_taggedvalue_t { // jl_value_t value; }; +static inline jl_value_t *jl_to_typeof(uintptr_t t) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; #ifdef __clang_gcanalyzer__ JL_DLLEXPORT jl_taggedvalue_t *_jl_astaggedvalue(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; #define jl_astaggedvalue(v) _jl_astaggedvalue((jl_value_t*)(v)) @@ -109,10 +124,10 @@ JL_DLLEXPORT jl_value_t *_jl_typeof(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFE #else #define jl_astaggedvalue(v) \ ((jl_taggedvalue_t*)((char*)(v) - sizeof(jl_taggedvalue_t))) -#define jl_valueof(v) \ +#define jl_valueof(v) \ ((jl_value_t*)((char*)(v) + sizeof(jl_taggedvalue_t))) #define jl_typeof(v) \ - ((jl_value_t*)(jl_astaggedvalue(v)->header & ~(uintptr_t)15)) + jl_to_typeof(jl_typetagof(v)) #endif static inline void jl_set_typeof(void *v, void *t) JL_NOTSAFEPOINT { @@ -120,7 +135,11 @@ 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)))) // Symbols are interned strings (hash-consed) stored as an invasive binary tree. // The string data is nul-terminated and hangs off the end of the struct. @@ -192,6 +211,9 @@ STATIC_INLINE int jl_array_ndimwords(uint32_t ndims) JL_NOTSAFEPOINT typedef struct _jl_datatype_t jl_tupletype_t; struct _jl_code_instance_t; +typedef struct _jl_method_instance_t jl_method_instance_t; +typedef struct _jl_globalref_t jl_globalref_t; + // TypeMap is an implicitly defined type // that can consist of any of the following nodes: @@ -209,40 +231,43 @@ typedef jl_call_t *jl_callptr_t; // "speccall" calling convention signatures. // This describes some of the special ABI used by compiled julia functions. extern jl_call_t jl_fptr_args; -JL_DLLEXPORT extern jl_callptr_t jl_fptr_args_addr; +JL_DLLEXPORT extern const jl_callptr_t jl_fptr_args_addr; typedef jl_value_t *(*jl_fptr_args_t)(jl_value_t*, jl_value_t**, uint32_t); extern jl_call_t jl_fptr_const_return; -JL_DLLEXPORT extern jl_callptr_t jl_fptr_const_return_addr; +JL_DLLEXPORT extern const jl_callptr_t jl_fptr_const_return_addr; extern jl_call_t jl_fptr_sparam; -JL_DLLEXPORT extern jl_callptr_t jl_fptr_sparam_addr; +JL_DLLEXPORT extern const jl_callptr_t jl_fptr_sparam_addr; typedef jl_value_t *(*jl_fptr_sparam_t)(jl_value_t*, jl_value_t**, uint32_t, jl_svec_t*); extern jl_call_t jl_fptr_interpret_call; -JL_DLLEXPORT extern jl_callptr_t jl_fptr_interpret_call_addr; +JL_DLLEXPORT extern const jl_callptr_t jl_fptr_interpret_call_addr; -typedef struct _jl_method_instance_t jl_method_instance_t; +JL_DLLEXPORT extern const jl_callptr_t jl_f_opaque_closure_call_addr; typedef struct _jl_line_info_node_t { struct _jl_module_t *module; - jl_value_t *method; + jl_value_t *method; // may contain a jl_symbol, jl_method_t, or jl_method_instance_t jl_sym_t *file; - intptr_t line; - intptr_t inlined_at; + int32_t line; + int32_t inlined_at; } jl_line_info_node_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 : 1; + uint8_t ipo_consistent : 1; + uint8_t ipo_effect_free : 1; + uint8_t ipo_nothrow : 1; + uint8_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_terminates_locally : 1; + uint8_t ipo_notaskstate : 1; + uint8_t ipo_inaccessiblememonly : 1; } overrides; uint8_t bits; } _jl_purity_overrides_t; @@ -275,12 +300,14 @@ typedef struct _jl_code_info_t { size_t max_world; // various boolean properties: uint8_t inferred; - uint8_t inlineable; uint8_t propagate_inbounds; - uint8_t pure; + uint8_t has_fcall; // uint8 settings + uint8_t inlining; // 0 = default; 1 = @inline; 2 = @noinline uint8_t constprop; // 0 = use heuristic; 1 = aggressive; 2 = none _jl_purity_overrides_t purity; + // uint16 settings + uint16_t inlining_cost; } jl_code_info_t; // This type describes a single method definition, and stores data @@ -298,18 +325,18 @@ typedef struct _jl_method_t { jl_value_t *sig; // table of all jl_method_instance_t specializations we have - _Atomic(jl_svec_t*) specializations; // allocated as [hashable, ..., NULL, linear, ....] + _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 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 - _Atomic(struct _jl_method_instance_t*) unspecialized; // unspecialized executable method instance, or null + _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 // Identify roots by module-of-origin. We only track the module for roots added during incremental compilation. // May be NULL if no external roots have been added, otherwise it's a Vector{UInt64} - jl_array_t *root_blocks; // RLE (build_id, offset) pairs (even/odd indexing) + jl_array_t *root_blocks; // RLE (build_id.lo, offset) pairs (even/odd indexing) int32_t nroots_sysimg; // # of roots stored in the system image jl_svec_t *ccallable; // svec(rettype, sig) if a ccallable entry point is requested for this @@ -321,7 +348,7 @@ typedef struct _jl_method_t { // A function that compares two specializations of this method, returning // `true` if the first signature is to be considered "smaller" than the // second for purposes of recursion analysis. Set to NULL to use - // the default recusion relation. + // the default recursion relation. jl_value_t *recursion_relation; uint32_t nargs; @@ -329,11 +356,13 @@ typedef struct _jl_method_t { uint32_t nospecialize; // bit flags: which arguments should not be specialized uint32_t nkw; // # of leading arguments that are actually keyword arguments // of another method. + // various boolean properties uint8_t isva; - uint8_t pure; uint8_t is_for_opaque_closure; // uint8 settings - uint8_t constprop; // 0x00 = use heuristic; 0x01 = aggressive; 0x02 = none + uint8_t constprop; // 0x00 = use heuristic; 0x01 = aggressive; 0x02 = none + uint8_t max_varargs; // 0xFF = use heuristic; otherwise, max # of args to expand + // varargs when specializing. // Override the conclusions of inter-procedural effect analysis, // forcing the conclusion to always true. @@ -356,15 +385,17 @@ struct _jl_method_instance_t { } 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_value_t *uninferred; // cached uncompressed code, for generated functions, top-level thunks, or the interpreter - jl_array_t *backedges; // list of method-instances which contain a call into this method-instance + _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 + _Atomic(uint8_t) precompiled; // true if this instance was generated by an explicit `precompile(...)` call }; // OpaqueClosure -typedef struct jl_opaque_closure_t { +typedef struct _jl_opaque_closure_t { JL_DATA_TYPE jl_value_t *captures; size_t world; @@ -386,34 +417,38 @@ typedef struct _jl_code_instance_t { // inference state cache jl_value_t *rettype; // return type for fptr jl_value_t *rettype_const; // inferred constant return value, or null - jl_value_t *inferred; // inferred jl_code_info_t, or jl_nothing, 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 //TODO: uint8_t absolute_max; // whether true max world is unknown // purity results - union { - uint8_t ipo_purity_bits; - struct { - uint8_t ipo_consistent:2; - uint8_t ipo_effect_free:2; - uint8_t ipo_nothrow:2; - uint8_t ipo_terminates:2; - } ipo_purity_flags; - }; - union { - uint8_t purity_bits; - struct { - uint8_t consistent:2; - uint8_t effect_free:2; - uint8_t nothrow:2; - uint8_t terminates:2; - } purity_flags; - }; + // see also encode_effects() and decode_effects() in `base/compiler/effects.jl`, + uint32_t ipo_purity_bits; + // ipo_purity_flags: + // uint8_t ipo_consistent : 2; + // uint8_t ipo_effect_free : 2; + // uint8_t ipo_nothrow : 2; + // uint8_t ipo_terminates : 2; + // uint8_t ipo_nonoverlayed : 1; + // uint8_t ipo_notaskstate : 2; + // uint8_t ipo_inaccessiblememonly : 2; + _Atomic(uint32_t) purity_bits; + // purity_flags: + // uint8_t consistent : 2; + // uint8_t effect_free : 2; + // uint8_t nothrow : 2; + // uint8_t terminates : 2; + // uint8_t nonoverlayed : 1; + // uint8_t notaskstate : 2; + // uint8_t inaccessiblememonly : 2; jl_value_t *argescapes; // escape information of call arguments // compilation state cache - uint8_t isspecsig; // if specptr is a specialized function signature for specTypes->rettype + _Atomic(uint8_t) specsigflags; // & 0b001 == specptr is a specialized function signature for specTypes->rettype + // & 0b010 == invokeptr matches specptr + // & 0b100 == From image _Atomic(uint8_t) precompile; // if set, this will be added to the output system image + uint8_t relocatability; // nonzero if all roots are built into sysimg or tagged by module key _Atomic(jl_callptr_t) invoke; // jlcall entry point union _jl_generic_specptr_t { _Atomic(void*) fptr; @@ -422,7 +457,6 @@ typedef struct _jl_code_instance_t { _Atomic(jl_fptr_sparam_t) fptr3; // 4 interpreter } specptr; // private data for `jlcall entry point - uint8_t relocatability; // nonzero if all roots are built into sysimg or tagged by module key } jl_code_instance_t; // all values are callable as Functions @@ -456,6 +490,7 @@ typedef struct { // `wrapper` is either the only instantiation of the type (if no parameters) // or a UnionAll accepting parameters to make an instantiation. jl_value_t *wrapper; + _Atomic(jl_value_t*) Typeofwrapper; // cache for Type{wrapper} _Atomic(jl_svec_t*) cache; // sorted array _Atomic(jl_svec_t*) linearcache; // unsorted array struct _jl_methtable_t *mt; @@ -466,6 +501,8 @@ typedef struct { uint8_t abstract:1; uint8_t mutabl:1; uint8_t mayinlinealloc:1; + uint8_t _reserved:5; + uint8_t max_methods; // override for inference's max_methods setting (0 = no additional limit or relaxation) } jl_typename_t; typedef struct { @@ -494,12 +531,14 @@ typedef struct { } jl_fielddesc32_t; typedef struct { + uint32_t size; uint32_t nfields; 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; // union { // jl_fielddesc8_t field8[nfields]; // jl_fielddesc16_t field16[nfields]; @@ -520,16 +559,19 @@ typedef struct _jl_datatype_t { jl_svec_t *types; jl_value_t *instance; // for singletons const jl_datatype_layout_t *layout; - int32_t size; // TODO: move to _jl_datatype_layout_t - // memoized properties + // memoized properties (set on construction) uint32_t hash; - uint8_t hasfreetypevars:1; // majority part of isconcrete computation - uint8_t isconcretetype:1; // whether this type can have instances - uint8_t isdispatchtuple:1; // aka isleaftupletype - uint8_t isbitstype:1; // relevant query for C-api and type-parameters - uint8_t zeroinit:1; // if one or more fields requires zero-initialization - uint8_t has_concrete_subtype:1; // If clear, no value will have this datatype - uint8_t cached_by_hash:1; // stored in hash-based set cache (instead of linear cache) + uint16_t hasfreetypevars:1; // majority part of isconcrete computation + uint16_t isconcretetype:1; // whether this type can have instances + uint16_t isdispatchtuple:1; // aka isleaftupletype + uint16_t isbitstype:1; // relevant query for C-api and type-parameters + uint16_t zeroinit:1; // if one or more fields requires zero-initialization + uint16_t has_concrete_subtype:1; // If clear, no value will have this datatype + uint16_t maybe_subtype_of_cache:1; // Computational bit for has_concrete_supertype. See description in jltypes.c. + uint16_t isprimitivetype:1; // whether this is declared with 'primitive type' keyword (sized, no fields, and immutable) + uint16_t ismutationfree:1; // whether any mutable memory is reachable through this type (in the type or via fields) + uint16_t isidentityfree:1; // whether this type or any object reachable through its fields has non-content-based identity + uint16_t smalltag:6; // whether this type has a small-tag optimization } jl_datatype_t; typedef struct _jl_vararg_t { @@ -538,22 +580,23 @@ typedef struct _jl_vararg_t { jl_value_t *N; } jl_vararg_t; -typedef struct { +typedef struct _jl_weakref_t { JL_DATA_TYPE jl_value_t *value; } jl_weakref_t; -typedef struct { - // not first-class - jl_sym_t *name; +typedef struct _jl_binding_t { + JL_DATA_TYPE _Atomic(jl_value_t*) value; - _Atomic(jl_value_t*) globalref; // cached GlobalRef for this binding - struct _jl_module_t* owner; // for individual imported bindings -- TODO: make _Atomic + jl_globalref_t *globalref; // cached GlobalRef for this binding + _Atomic(struct _jl_binding_t*) owner; // for individual imported bindings (NULL until 'resolved') _Atomic(jl_value_t*) ty; // binding type uint8_t constp:1; uint8_t exportp:1; uint8_t imported:1; + uint8_t usingfailed:1; uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package + uint8_t padding:2; } jl_binding_t; typedef struct { @@ -565,10 +608,11 @@ typedef struct _jl_module_t { JL_DATA_TYPE 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 // hidden fields: - htable_t bindings; arraylist_t usings; // modules with all bindings potentially imported - uint64_t build_id; + jl_uuid_t build_id; jl_uuid_t uuid; size_t primary_world; _Atomic(uint32_t) counter; @@ -579,8 +623,15 @@ typedef struct _jl_module_t { uint8_t istopmod; int8_t max_methods; jl_mutex_t lock; + intptr_t hash; } jl_module_t; +typedef 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 { JL_DATA_TYPE @@ -609,10 +660,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 - _Atomic(jl_array_t*) targ; // contains Type{LeafType} - _Atomic(jl_array_t*) name1; // contains non-abstract TypeName, for parents up to (excluding) Any - _Atomic(jl_array_t*) tname; // contains a dict of Type{TypeName}, for parents up to Any + _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 // 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 @@ -622,14 +673,13 @@ typedef struct _jl_typemap_level_t { // contains the TypeMap for one Type typedef struct _jl_methtable_t { JL_DATA_TYPE - jl_sym_t *name; // sometimes a hack used by serialization to handle kwsorter + jl_sym_t *name; // sometimes used for debug printing _Atomic(jl_typemap_t*) defs; _Atomic(jl_array_t*) leafcache; _Atomic(jl_typemap_t*) cache; - intptr_t max_args; // max # of non-vararg arguments in a signature - jl_value_t *kwsorter; // keyword argument sorter function - jl_module_t *module; // used for incremental serialization to locate original binding - jl_array_t *backedges; + _Atomic(intptr_t) max_args; // max # of non-vararg arguments in a signature + jl_module_t *module; // sometimes used for debug printing + jl_array_t *backedges; // (sig, caller::MethodInstance) pairs jl_mutex_t writelock; uint8_t offs; // 0, or 1 to skip splitting typemap on first (function) argument uint8_t frozen; // whether this accepts adding new methods @@ -653,6 +703,59 @@ typedef struct { // constants and type objects ------------------------------------------------- +#define JL_SMALL_TYPEOF(XX) \ + /* kinds */ \ + XX(typeofbottom) \ + XX(datatype) \ + XX(unionall) \ + XX(uniontype) \ + /* type parameter objects */ \ + XX(vararg) \ + XX(tvar) \ + XX(symbol) \ + XX(module) \ + /* special GC objects */ \ + XX(simplevector) \ + XX(string) \ + XX(task) \ + /* bits types with special allocators */ \ + XX(bool) \ + XX(char) \ + /*XX(float16)*/ \ + /*XX(float32)*/ \ + /*XX(float64)*/ \ + XX(int16) \ + XX(int32) \ + XX(int64) \ + XX(int8) \ + XX(uint16) \ + XX(uint32) \ + XX(uint64) \ + XX(uint8) \ + /* AST objects */ \ + /* XX(argument) */ \ + /* XX(newvarnode) */ \ + /* XX(slotnumber) */ \ + /* XX(ssavalue) */ \ + /* end of JL_SMALL_TYPEOF */ +enum jlsmall_typeof_tags { + jl_null_tag = 0, +#define XX(name) jl_##name##_tag, + JL_SMALL_TYPEOF(XX) +#undef XX + jl_tags_count, + jl_bitstags_first = jl_char_tag, // n.b. bool is not considered a bitstype, since it can be compared by pointer + jl_max_tags = 64 +}; +extern jl_datatype_t *small_typeof[(jl_max_tags << 4) / sizeof(jl_datatype_t*)]; +static inline jl_value_t *jl_to_typeof(uintptr_t t) +{ + if (t < (jl_max_tags << 4)) + return (jl_value_t*)small_typeof[t / sizeof(*small_typeof)]; + return (jl_value_t*)t; +} + + // kinds extern JL_DLLIMPORT jl_datatype_t *jl_typeofbottom_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_datatype_type JL_GLOBALLY_ROOTED; @@ -666,9 +769,7 @@ extern JL_DLLIMPORT jl_datatype_t *jl_typename_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_typename_t *jl_type_typename JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_symbol_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_ssavalue_type JL_GLOBALLY_ROOTED; -extern JL_DLLIMPORT jl_datatype_t *jl_abstractslot_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_slotnumber_type JL_GLOBALLY_ROOTED; -extern JL_DLLIMPORT jl_datatype_t *jl_typedslot_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_argument_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_const_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_partial_struct_type JL_GLOBALLY_ROOTED; @@ -756,6 +857,7 @@ 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_uint64_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; @@ -777,6 +879,7 @@ extern JL_DLLIMPORT jl_value_t *jl_emptytuple JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_true JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_false JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_nothing JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_value_t *jl_kwcall_func JL_GLOBALLY_ROOTED; // gc ------------------------------------------------------------------------- @@ -810,6 +913,7 @@ 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_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_PUSHARGS(jl_value_t **, size_t) JL_NOTSAFEPOINT; // This is necessary, because otherwise the analyzer considers this undefined // behavior and terminates the exploration @@ -849,6 +953,9 @@ 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_PUSHARGS(rts_var,n) \ @@ -875,6 +982,7 @@ JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t); JL_DLLEXPORT void jl_gc_add_finalizer(jl_value_t *v, jl_function_t *f) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gc_add_ptr_finalizer(jl_ptls_t ptls, jl_value_t *v, void *f) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_gc_add_quiescent(jl_ptls_t ptls, void **v, void *f) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_finalize(jl_value_t *o); JL_DLLEXPORT jl_weakref_t *jl_gc_new_weakref(jl_value_t *value); JL_DLLEXPORT jl_value_t *jl_gc_alloc_0w(void); @@ -885,6 +993,9 @@ JL_DLLEXPORT jl_value_t *jl_gc_allocobj(size_t sz); JL_DLLEXPORT void *jl_malloc_stack(size_t *bufsz, struct _jl_task_t *owner) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_free_stack(void *stkbuf, size_t bufsz); JL_DLLEXPORT void jl_gc_use(jl_value_t *a); +// Set GC memory trigger in bytes for greedy memory collecting +JL_DLLEXPORT void jl_gc_set_max_memory(uint64_t max_mem); +JL_DLLEXPORT uint64_t jl_gc_get_max_memory(void); JL_DLLEXPORT void jl_clear_malloc_data(void); @@ -924,6 +1035,7 @@ STATIC_INLINE void jl_gc_multi_wb(const void *parent, const jl_value_t *ptr) JL_ JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz); JL_DLLEXPORT void *jl_gc_managed_realloc(void *d, size_t sz, size_t oldsz, int isaligned, jl_value_t *owner); +JL_DLLEXPORT void jl_gc_safepoint(void); // object accessors ----------------------------------------------------------- @@ -939,7 +1051,7 @@ STATIC_INLINE jl_value_t *jl_svecset( #else STATIC_INLINE jl_value_t *jl_svecref(void *t JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT { - assert(jl_typeis(t,jl_simplevector_type)); + assert(jl_typetagis(t,jl_simplevector_tag << 4)); assert(i < jl_svec_len(t)); // while svec is supposedly immutable, in practice we sometimes publish it first // and set the values lazily @@ -949,11 +1061,12 @@ STATIC_INLINE jl_value_t *jl_svecset( void *t JL_ROOTING_ARGUMENT JL_PROPAGATES_ROOT, size_t i, void *x JL_ROOTED_ARGUMENT) JL_NOTSAFEPOINT { - assert(jl_typeis(t,jl_simplevector_type)); + assert(jl_typetagis(t,jl_simplevector_tag << 4)); assert(i < jl_svec_len(t)); - // TODO: while svec is supposedly immutable, in practice we sometimes publish it first - // and set the values lazily. Those users should be using jl_atomic_store_release here. - jl_svec_data(t)[i] = (jl_value_t*)x; + // while svec is supposedly immutable, in practice we sometimes publish it + // first and set the values lazily. Those users occasionally might need to + // instead use jl_atomic_store_release here. + jl_atomic_store_relaxed((_Atomic(jl_value_t*)*)jl_svec_data(t) + i, (jl_value_t*)x); jl_gc_wb(t, x); return (jl_value_t*)x; } @@ -990,7 +1103,7 @@ STATIC_INLINE jl_value_t *jl_array_ptr_set( { assert(((jl_array_t*)a)->flags.ptrarray); assert(i < jl_array_len(a)); - jl_atomic_store_relaxed(((_Atomic(jl_value_t*)*)(jl_array_data(a))) + i, (jl_value_t*)x); + jl_atomic_store_release(((_Atomic(jl_value_t*)*)(jl_array_data(a))) + i, (jl_value_t*)x); if (x) { if (((jl_array_t*)a)->flags.how == 3) { a = jl_array_data_owner(a); @@ -1004,13 +1117,13 @@ 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_typeis(a, jl_array_uint8_type)); + assert(jl_typetagis(a, jl_array_uint8_type)); return ((uint8_t*)(jl_array_data(a)))[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_typeis(a, jl_array_uint8_type)); + assert(jl_typetagis(a, jl_array_uint8_type)); ((uint8_t*)(jl_array_data(a)))[i] = x; } @@ -1066,9 +1179,9 @@ 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)->size) +#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)->size)*8) +#define jl_datatype_nbits(t) ((((jl_datatype_t*)t)->layout->size)*8) #define jl_datatype_nfields(t) (((jl_datatype_t*)(t))->layout->nfields) JL_DLLEXPORT void *jl_symbol_name(jl_sym_t *s); @@ -1179,55 +1292,57 @@ static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEP #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) #define jl_is_namedtuple(v) (((jl_datatype_t*)jl_typeof(v))->name == jl_namedtuple_typename) -#define jl_is_svec(v) jl_typeis(v,jl_simplevector_type) +#define jl_is_svec(v) jl_typetagis(v,jl_simplevector_tag<<4) #define jl_is_simplevector(v) jl_is_svec(v) -#define jl_is_datatype(v) jl_typeis(v,jl_datatype_type) +#define jl_is_datatype(v) jl_typetagis(v,jl_datatype_tag<<4) #define jl_is_mutable(t) (((jl_datatype_t*)t)->name->mutabl) #define jl_is_mutable_datatype(t) (jl_is_datatype(t) && (((jl_datatype_t*)t)->name->mutabl)) #define jl_is_immutable(t) (!((jl_datatype_t*)t)->name->mutabl) #define jl_is_immutable_datatype(t) (jl_is_datatype(t) && (!((jl_datatype_t*)t)->name->mutabl)) -#define jl_is_uniontype(v) jl_typeis(v,jl_uniontype_type) -#define jl_is_typevar(v) jl_typeis(v,jl_tvar_type) -#define jl_is_unionall(v) jl_typeis(v,jl_unionall_type) -#define jl_is_typename(v) jl_typeis(v,jl_typename_type) -#define jl_is_int8(v) jl_typeis(v,jl_int8_type) -#define jl_is_int16(v) jl_typeis(v,jl_int16_type) -#define jl_is_int32(v) jl_typeis(v,jl_int32_type) -#define jl_is_int64(v) jl_typeis(v,jl_int64_type) -#define jl_is_uint8(v) jl_typeis(v,jl_uint8_type) -#define jl_is_uint16(v) jl_typeis(v,jl_uint16_type) -#define jl_is_uint32(v) jl_typeis(v,jl_uint32_type) -#define jl_is_uint64(v) jl_typeis(v,jl_uint64_type) -#define jl_is_bool(v) jl_typeis(v,jl_bool_type) -#define jl_is_symbol(v) jl_typeis(v,jl_symbol_type) -#define jl_is_ssavalue(v) jl_typeis(v,jl_ssavalue_type) -#define jl_is_slot(v) (jl_typeis(v,jl_slotnumber_type) || jl_typeis(v,jl_typedslot_type)) -#define jl_is_expr(v) jl_typeis(v,jl_expr_type) -#define jl_is_globalref(v) jl_typeis(v,jl_globalref_type) -#define jl_is_gotonode(v) jl_typeis(v,jl_gotonode_type) -#define jl_is_gotoifnot(v) jl_typeis(v,jl_gotoifnot_type) -#define jl_is_returnnode(v) jl_typeis(v,jl_returnnode_type) -#define jl_is_argument(v) jl_typeis(v,jl_argument_type) -#define jl_is_pinode(v) jl_typeis(v,jl_pinode_type) -#define jl_is_phinode(v) jl_typeis(v,jl_phinode_type) -#define jl_is_phicnode(v) jl_typeis(v,jl_phicnode_type) -#define jl_is_upsilonnode(v) jl_typeis(v,jl_upsilonnode_type) -#define jl_is_quotenode(v) jl_typeis(v,jl_quotenode_type) -#define jl_is_newvarnode(v) jl_typeis(v,jl_newvarnode_type) -#define jl_is_linenode(v) jl_typeis(v,jl_linenumbernode_type) -#define jl_is_method_instance(v) jl_typeis(v,jl_method_instance_type) -#define jl_is_code_instance(v) jl_typeis(v,jl_code_instance_type) -#define jl_is_code_info(v) jl_typeis(v,jl_code_info_type) -#define jl_is_method(v) jl_typeis(v,jl_method_type) -#define jl_is_module(v) jl_typeis(v,jl_module_type) -#define jl_is_mtable(v) jl_typeis(v,jl_methtable_type) -#define jl_is_task(v) jl_typeis(v,jl_task_type) -#define jl_is_string(v) jl_typeis(v,jl_string_type) +#define jl_is_uniontype(v) jl_typetagis(v,jl_uniontype_tag<<4) +#define jl_is_typevar(v) jl_typetagis(v,jl_tvar_tag<<4) +#define jl_is_unionall(v) jl_typetagis(v,jl_unionall_tag<<4) +#define jl_is_vararg(v) jl_typetagis(v,jl_vararg_tag<<4) +#define jl_is_typename(v) jl_typetagis(v,jl_typename_type) +#define jl_is_int8(v) jl_typetagis(v,jl_int8_tag<<4) +#define jl_is_int16(v) jl_typetagis(v,jl_int16_tag<<4) +#define jl_is_int32(v) jl_typetagis(v,jl_int32_tag<<4) +#define jl_is_int64(v) jl_typetagis(v,jl_int64_tag<<4) +#define jl_is_uint8(v) jl_typetagis(v,jl_uint8_tag<<4) +#define jl_is_uint16(v) jl_typetagis(v,jl_uint16_tag<<4) +#define jl_is_uint32(v) jl_typetagis(v,jl_uint32_tag<<4) +#define jl_is_uint64(v) jl_typetagis(v,jl_uint64_tag<<4) +#define jl_is_bool(v) jl_typetagis(v,jl_bool_tag<<4) +#define jl_is_symbol(v) jl_typetagis(v,jl_symbol_tag<<4) +#define jl_is_ssavalue(v) jl_typetagis(v,jl_ssavalue_type) +#define jl_is_slotnumber(v) jl_typetagis(v,jl_slotnumber_type) +#define jl_is_expr(v) jl_typetagis(v,jl_expr_type) +#define jl_is_binding(v) jl_typetagis(v,jl_binding_type) +#define jl_is_globalref(v) jl_typetagis(v,jl_globalref_type) +#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_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) +#define jl_is_phicnode(v) jl_typetagis(v,jl_phicnode_type) +#define jl_is_upsilonnode(v) jl_typetagis(v,jl_upsilonnode_type) +#define jl_is_quotenode(v) jl_typetagis(v,jl_quotenode_type) +#define jl_is_newvarnode(v) jl_typetagis(v,jl_newvarnode_type) +#define jl_is_linenode(v) jl_typetagis(v,jl_linenumbernode_type) +#define jl_is_method_instance(v) jl_typetagis(v,jl_method_instance_type) +#define jl_is_code_instance(v) jl_typetagis(v,jl_code_instance_type) +#define jl_is_code_info(v) jl_typetagis(v,jl_code_info_type) +#define jl_is_method(v) jl_typetagis(v,jl_method_type) +#define jl_is_module(v) jl_typetagis(v,jl_module_tag<<4) +#define jl_is_mtable(v) jl_typetagis(v,jl_methtable_type) +#define jl_is_task(v) jl_typetagis(v,jl_task_tag<<4) +#define jl_is_string(v) jl_typetagis(v,jl_string_tag<<4) #define jl_is_cpointer(v) jl_is_cpointer_type(jl_typeof(v)) #define jl_is_pointer(v) jl_is_cpointer_type(jl_typeof(v)) -#define jl_is_uint8pointer(v)jl_typeis(v,jl_uint8pointer_type) +#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_typeis(v,jl_intrinsic_type) +#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)))) JL_DLLEXPORT int jl_subtype(jl_value_t *a, jl_value_t *b); @@ -1238,27 +1353,31 @@ STATIC_INLINE int jl_is_kind(jl_value_t *v) JL_NOTSAFEPOINT v==(jl_value_t*)jl_unionall_type || v==(jl_value_t*)jl_typeofbottom_type); } +STATIC_INLINE int jl_is_kindtag(uintptr_t t) JL_NOTSAFEPOINT +{ + t >>= 4; + return (t==(uintptr_t)jl_uniontype_tag || t==(uintptr_t)jl_datatype_tag || + t==(uintptr_t)jl_unionall_tag || t==(uintptr_t)jl_typeofbottom_tag); +} + STATIC_INLINE int jl_is_type(jl_value_t *v) JL_NOTSAFEPOINT { - return jl_is_kind(jl_typeof(v)); + return jl_is_kindtag(jl_typetagof(v)); } STATIC_INLINE int jl_is_primitivetype(void *v) JL_NOTSAFEPOINT { - return (jl_is_datatype(v) && jl_is_immutable(v) && - ((jl_datatype_t*)(v))->layout && - jl_datatype_nfields(v) == 0 && - jl_datatype_size(v) > 0); + return (jl_is_datatype(v) && ((jl_datatype_t*)(v))->isprimitivetype); } STATIC_INLINE int jl_is_structtype(void *v) JL_NOTSAFEPOINT { return (jl_is_datatype(v) && !((jl_datatype_t*)(v))->name->abstract && - !jl_is_primitivetype(v)); + !((jl_datatype_t*)(v))->isprimitivetype); } -STATIC_INLINE int jl_isbits(void *t) JL_NOTSAFEPOINT // corresponding to isbits() in julia +STATIC_INLINE int jl_isbits(void *t) JL_NOTSAFEPOINT // corresponding to isbitstype() in julia { return (jl_is_datatype(t) && ((jl_datatype_t*)t)->isbitstype); } @@ -1351,37 +1470,38 @@ STATIC_INLINE int jl_is_array_zeroinit(jl_array_t *a) JL_NOTSAFEPOINT // object identity JL_DLLEXPORT int jl_egal(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; -JL_DLLEXPORT int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; -JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_egal__bitstag(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, uintptr_t dtag) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, uintptr_t dtag) JL_NOTSAFEPOINT; JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT; +JL_DLLEXPORT uintptr_t jl_type_hash(jl_value_t *v) JL_NOTSAFEPOINT; -STATIC_INLINE int jl_egal__unboxed_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT +STATIC_INLINE int jl_egal__unboxed_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, uintptr_t dtag) JL_NOTSAFEPOINT { - if (dt->name->mutabl) { - if (dt == jl_simplevector_type || dt == jl_string_type || dt == jl_datatype_type) - return jl_egal__special(a, b, dt); - return 0; + if (dtag < jl_max_tags << 4) { + if (dtag == jl_symbol_tag << 4 || dtag == jl_bool_tag << 4) + return 0; } - return jl_egal__bits(a, b, dt); + else if (((jl_datatype_t*)dtag)->name->mutabl) + return 0; + return jl_egal__bitstag(a, b, dtag); } STATIC_INLINE int jl_egal_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT { if (a == b) return 1; - jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(a); - if (dt != (jl_datatype_t*)jl_typeof(b)) + uintptr_t dtag = jl_typetagof(a); + if (dtag != jl_typetagof(b)) return 0; - return jl_egal__unboxed_(a, b, dt); + return jl_egal__unboxed_(a, b, dtag); } #define jl_egal(a, b) jl_egal_((a), (b)) // type predicates and basic operations -JL_DLLEXPORT int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_has_free_typevars(jl_value_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_has_typevar(jl_value_t *t, jl_tvar_t *v) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_has_typevar_from_unionall(jl_value_t *t, jl_unionall_t *ua); -JL_DLLEXPORT int jl_subtype_env_size(jl_value_t *t); +JL_DLLEXPORT int jl_subtype_env_size(jl_value_t *t) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_subtype_env(jl_value_t *x, jl_value_t *y, jl_value_t **env, int envsz); JL_DLLEXPORT int jl_isa(jl_value_t *a, jl_value_t *t); JL_DLLEXPORT int jl_types_equal(jl_value_t *a, jl_value_t *b); @@ -1404,7 +1524,7 @@ STATIC_INLINE int jl_is_concrete_type(jl_value_t *v) JL_NOTSAFEPOINT return jl_is_datatype(v) && ((jl_datatype_t*)v)->isconcretetype; } -JL_DLLEXPORT int jl_isa_compileable_sig(jl_tupletype_t *type, jl_method_t *definition); +JL_DLLEXPORT int jl_isa_compileable_sig(jl_tupletype_t *type, jl_svec_t *sparams, jl_method_t *definition); // type constructors JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *inmodule, int abstract, int mutabl); @@ -1415,8 +1535,8 @@ JL_DLLEXPORT jl_value_t *jl_apply_type1(jl_value_t *tc, jl_value_t *p1); JL_DLLEXPORT jl_value_t *jl_apply_type2(jl_value_t *tc, jl_value_t *p1, jl_value_t *p2); JL_DLLEXPORT jl_datatype_t *jl_apply_modify_type(jl_value_t *dt); JL_DLLEXPORT jl_datatype_t *jl_apply_cmpswap_type(jl_value_t *dt); -JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type(jl_svec_t *params); -JL_DLLEXPORT jl_tupletype_t *jl_apply_tuple_type_v(jl_value_t **p, size_t np); +JL_DLLEXPORT jl_value_t *jl_apply_tuple_type(jl_svec_t *params); +JL_DLLEXPORT jl_value_t *jl_apply_tuple_type_v(jl_value_t **p, size_t np); JL_DLLEXPORT jl_datatype_t *jl_new_datatype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *super, @@ -1450,7 +1570,6 @@ JL_DLLEXPORT jl_svec_t *jl_alloc_svec(size_t n); JL_DLLEXPORT jl_svec_t *jl_alloc_svec_uninit(size_t n); JL_DLLEXPORT jl_svec_t *jl_svec_copy(jl_svec_t *a); JL_DLLEXPORT jl_svec_t *jl_svec_fill(size_t n, jl_value_t *x); -JL_DLLEXPORT jl_value_t *jl_tupletype_fill(size_t n, jl_value_t *v); JL_DLLEXPORT jl_sym_t *jl_symbol(const char *str) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_sym_t *jl_symbol_lookup(const char *str) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, size_t len) JL_NOTSAFEPOINT; @@ -1459,13 +1578,12 @@ JL_DLLEXPORT jl_sym_t *jl_tagged_gensym(const char *str, size_t len); JL_DLLEXPORT jl_sym_t *jl_get_root_symbol(void); JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_module_t *module, - _Atomic(jl_value_t*) *bp, jl_value_t *bp_owner, + _Atomic(jl_value_t*) *bp, jl_binding_t *bnd); JL_DLLEXPORT jl_method_t *jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt, jl_code_info_t *f, jl_module_t *module); -JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo); +JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, size_t world); JL_DLLEXPORT jl_code_info_t *jl_copy_code_info(jl_code_info_t *src); JL_DLLEXPORT size_t jl_get_world_counter(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_function_t *jl_get_kwsorter(jl_value_t *ty); JL_DLLEXPORT jl_value_t *jl_box_bool(int8_t x) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_box_int8(int8_t x) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_box_uint8(uint8_t x) JL_NOTSAFEPOINT; @@ -1524,8 +1642,9 @@ JL_DLLEXPORT jl_value_t *jl_get_nth_field(jl_value_t *v, size_t i); // Like jl_get_nth_field above, but asserts if it needs to allocate JL_DLLEXPORT jl_value_t *jl_get_nth_field_noalloc(jl_value_t *v JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_get_nth_field_checked(jl_value_t *v, size_t i); -JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t i, jl_value_t *rhs) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_set_nth_field(jl_value_t *v, size_t i, jl_value_t *rhs); JL_DLLEXPORT int jl_field_isdefined(jl_value_t *v, size_t i) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_field_isdefined_checked(jl_value_t *v, size_t i); JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld); JL_DLLEXPORT jl_value_t *jl_value_ptr(jl_value_t *a); int jl_uniontype_size(jl_value_t *ty, size_t *sz); @@ -1575,11 +1694,11 @@ JL_DLLEXPORT size_t jl_array_size(jl_value_t *a, int d); JL_DLLEXPORT const char *jl_string_ptr(jl_value_t *s); // modules and global variables -extern JL_DLLEXPORT jl_module_t *jl_main_module JL_GLOBALLY_ROOTED; -extern JL_DLLEXPORT jl_module_t *jl_core_module JL_GLOBALLY_ROOTED; -extern JL_DLLEXPORT jl_module_t *jl_base_module JL_GLOBALLY_ROOTED; -extern JL_DLLEXPORT jl_module_t *jl_top_module JL_GLOBALLY_ROOTED; -JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name); +extern JL_DLLIMPORT jl_module_t *jl_main_module JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_module_t *jl_core_module JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_module_t *jl_base_module JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_module_t *jl_top_module JL_GLOBALLY_ROOTED; +JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent); JL_DLLEXPORT void jl_set_module_nospecialize(jl_module_t *self, int on); JL_DLLEXPORT void jl_set_module_optlevel(jl_module_t *self, int lvl); JL_DLLEXPORT int jl_get_module_optlevel(jl_module_t *m); @@ -1592,20 +1711,24 @@ 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_binding_type(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 -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int error); +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var); +JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr); +JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr); +JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr); JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_value_t *rhs JL_ROOTED_ARGUMENT); -JL_DLLEXPORT void jl_declare_constant(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_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); JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); @@ -1621,21 +1744,27 @@ 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_value_t *key, jl_value_t *val, int *inserted); -JL_DLLEXPORT jl_value_t *jl_eqtable_get(jl_array_t *h, jl_value_t *key, jl_value_t *deflt) JL_NOTSAFEPOINT; +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; // system information JL_DLLEXPORT int jl_errno(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_set_errno(int e) JL_NOTSAFEPOINT; JL_DLLEXPORT int32_t jl_stat(const char *path, char *statbuf) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_cpu_threads(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_effective_threads(void) JL_NOTSAFEPOINT; JL_DLLEXPORT long jl_getpagesize(void) JL_NOTSAFEPOINT; JL_DLLEXPORT long jl_getallocationgranularity(void) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_is_debugbuild(void) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_sym_t *jl_get_UNAME(void) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_sym_t *jl_get_ARCH(void) JL_NOTSAFEPOINT; -JL_DLLEXPORT jl_value_t *jl_get_libllvm(void) JL_NOTSAFEPOINT; -extern JL_DLLIMPORT int jl_n_threads; +JL_DLLIMPORT jl_value_t *jl_get_libllvm(void) JL_NOTSAFEPOINT; +extern JL_DLLIMPORT int jl_n_threadpools; +extern JL_DLLIMPORT _Atomic(int) jl_n_threads; +extern JL_DLLIMPORT int jl_n_gcthreads; +extern JL_DLLIMPORT int *jl_n_threads_per_pool; // environment entries JL_DLLEXPORT jl_value_t *jl_environ(int i); @@ -1705,27 +1834,32 @@ typedef enum { //JL_IMAGE_LIBJULIA = 2, } JL_IMAGE_SEARCH; -JL_DLLEXPORT const char *jl_get_libdir(void); +JL_DLLIMPORT const char *jl_get_libdir(void); JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel); JL_DLLEXPORT void jl_init(void); JL_DLLEXPORT void jl_init_with_image(const char *julia_bindir, - const char *image_relative_path); + const char *image_path); JL_DLLEXPORT const char *jl_get_default_sysimg_path(void); JL_DLLEXPORT int jl_is_initialized(void); JL_DLLEXPORT void jl_atexit_hook(int status); +JL_DLLEXPORT void jl_task_wait_empty(void); +JL_DLLEXPORT void jl_postoutput_hook(void); JL_DLLEXPORT void JL_NORETURN jl_exit(int status); +JL_DLLEXPORT void JL_NORETURN jl_raise(int signo); JL_DLLEXPORT const char *jl_pathname_for_handle(void *handle); +JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void); JL_DLLEXPORT int jl_deserialize_verify_header(ios_t *s); JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname); JL_DLLEXPORT void jl_set_sysimg_so(void *handle); -JL_DLLEXPORT ios_t *jl_create_system_image(void *); -JL_DLLEXPORT void jl_save_system_image(const char *fname); +JL_DLLEXPORT void jl_create_system_image(void **, jl_array_t *worklist, bool_t emit_split, ios_t **s, ios_t **z, jl_array_t **udeps, int64_t *srctextpos); 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 int jl_save_incremental(const char *fname, jl_array_t *worklist); -JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods); -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, size_t sz, jl_array_t *depmods); +JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int complete, const char *pkgimage); + +JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t *newly_inferred); +JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t *ci); +JL_DLLEXPORT void jl_write_compiler_output(void); // parsing JL_DLLEXPORT jl_value_t *jl_parse_all(const char *text, size_t text_len, @@ -1787,17 +1921,19 @@ JL_DLLEXPORT void jl_register_newmeth_tracer(void (*callback)(jl_method_t *trace JL_DLLEXPORT jl_value_t *jl_copy_ast(jl_value_t *expr JL_MAYBE_UNROOTED); // IR representation -JL_DLLEXPORT jl_array_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_array_t *data); -JL_DLLEXPORT uint8_t jl_ir_flag_inferred(jl_array_t *data) JL_NOTSAFEPOINT; -JL_DLLEXPORT uint8_t jl_ir_flag_inlineable(jl_array_t *data) JL_NOTSAFEPOINT; -JL_DLLEXPORT uint8_t jl_ir_flag_pure(jl_array_t *data) JL_NOTSAFEPOINT; -JL_DLLEXPORT ssize_t jl_ir_nslots(jl_array_t *data) JL_NOTSAFEPOINT; -JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_array_t *data, size_t i) JL_NOTSAFEPOINT; +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; +JL_DLLEXPORT ssize_t jl_ir_nslots(jl_value_t *data) JL_NOTSAFEPOINT; +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 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); @@ -1858,6 +1994,8 @@ typedef struct _jl_handler_t { size_t world_age; } jl_handler_t; +#define JL_RNG_SIZE 5 // xoshiro 4 + splitmix 1 + typedef struct _jl_task_t { JL_DATA_TYPE jl_value_t *next; // invasive linked list for scheduler @@ -1867,19 +2005,34 @@ typedef struct _jl_task_t { jl_value_t *result; jl_value_t *logstate; jl_function_t *start; - uint64_t rngState0; // really rngState[4], but more convenient to split - uint64_t rngState1; - uint64_t rngState2; - uint64_t rngState3; + // 4 byte padding on 32-bit systems + // uint32_t padding0; + uint64_t rngState[JL_RNG_SIZE]; _Atomic(uint8_t) _state; uint8_t sticky; // record whether this Task can be migrated to a new thread _Atomic(uint8_t) _isexception; // set if `result` is an exception to throw or that we exited with + // 1 byte padding + // uint8_t padding1; + // multiqueue priority + uint16_t priority; // hidden state: + +#ifdef USE_TRACY + const char *name; +#endif // id of owning thread - does not need to be defined until the task runs _Atomic(int16_t) tid; - // multiqueue priority - int16_t prio; + // threadpool id + int8_t threadpoolid; + // Reentrancy bits + // Bit 0: 1 if we are currently running inference/codegen + // Bit 1-2: 0-3 counter of how many times we've reentered inference + // Bit 3: 1 if we are writing the image and inference is illegal + uint8_t reentrant_timing; + // 2 bytes of padding on 32-bit, 6 bytes on 64-bit + // uint16_t padding2_32; + // uint48_t padding2_64; // saved gc stack top for context switches jl_gcframe_t *gcstack; size_t world_age; @@ -1903,15 +2056,19 @@ typedef struct _jl_task_t { JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t*, jl_value_t*, size_t); JL_DLLEXPORT void jl_switchto(jl_task_t **pt); -JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int tid) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int16_t tid) JL_NOTSAFEPOINT; +JL_DLLEXPORT int jl_set_task_threadpoolid(jl_task_t *task, int8_t tpid) JL_NOTSAFEPOINT; JL_DLLEXPORT void JL_NORETURN jl_throw(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_rethrow(void); JL_DLLEXPORT void JL_NORETURN jl_sig_throw(void); JL_DLLEXPORT void JL_NORETURN jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED); -JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e); +JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e, jl_task_t *ct); JL_DLLEXPORT JL_CONST_FUNC jl_gcframe_t **(jl_get_pgcstack)(void) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; #define jl_current_task (container_of(jl_get_pgcstack(), jl_task_t, gcstack)) +extern JL_DLLIMPORT int jl_task_gcstack_offset; +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); @@ -1932,7 +2089,7 @@ void (jl_longjmp)(jmp_buf _Buf, int _Value); JL_DLLEXPORT int (ijl_setjmp)(jmp_buf _Buf); void (ijl_longjmp)(jmp_buf _Buf, int _Value); #endif -#ifdef LIBRARY_EXPORTS +#ifdef JL_LIBRARY_EXPORTS #define jl_setjmp_f ijl_setjmp #define jl_setjmp_name "ijl_setjmp" #define jl_setjmp(a,b) ijl_setjmp(a) @@ -1958,8 +2115,14 @@ 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) +#else #define jl_longjmp(a,b) siglongjmp(a,b) #endif +#endif #ifdef __clang_gcanalyzer__ @@ -2002,7 +2165,7 @@ typedef int jl_uv_os_fd_t; JL_DLLEXPORT int jl_process_events(void); -JL_DLLEXPORT struct uv_loop_s *jl_global_event_loop(void); +JL_DLLEXPORT struct uv_loop_s *jl_global_event_loop(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_close_uv(struct uv_handle_s *handle); @@ -2037,6 +2200,7 @@ extern JL_DLLEXPORT JL_STREAM *JL_STDERR; JL_DLLEXPORT JL_STREAM *jl_stdout_stream(void); JL_DLLEXPORT JL_STREAM *jl_stdin_stream(void); JL_DLLEXPORT JL_STREAM *jl_stderr_stream(void); +JL_DLLEXPORT int jl_termios_size(void); // showing and std streams JL_DLLEXPORT void jl_flush_cstdio(void) JL_NOTSAFEPOINT; @@ -2073,6 +2237,7 @@ JL_DLLEXPORT int jl_generating_output(void) JL_NOTSAFEPOINT; #define JL_LOG_NONE 0 #define JL_LOG_USER 1 #define JL_LOG_ALL 2 +#define JL_LOG_PATH 3 #define JL_OPTIONS_CHECK_BOUNDS_DEFAULT 0 #define JL_OPTIONS_CHECK_BOUNDS_ON 1 @@ -2127,8 +2292,11 @@ JL_DLLEXPORT int jl_generating_output(void) JL_NOTSAFEPOINT; #define JL_OPTIONS_USE_COMPILED_MODULES_YES 1 #define JL_OPTIONS_USE_COMPILED_MODULES_NO 0 +#define JL_OPTIONS_USE_PKGIMAGES_YES 1 +#define JL_OPTIONS_USE_PKGIMAGES_NO 0 + // Version information -#include "julia_version.h" +#include // Generated file JL_DLLEXPORT extern int jl_ver_major(void); JL_DLLEXPORT extern int jl_ver_minor(void); @@ -2151,7 +2319,7 @@ typedef struct { #define jl_root_task (jl_current_task->ptls->root_task) -JL_DLLEXPORT jl_task_t *jl_get_current_task(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT jl_task_t *jl_get_current_task(void) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; // TODO: we need to pin the task while using this (set pure bit) JL_DLLEXPORT jl_jmp_buf *jl_get_safe_restore(void) JL_NOTSAFEPOINT; @@ -2169,9 +2337,11 @@ typedef struct { // controls the emission of debug-info. mirrors the clang options int gnu_pubnames; // can we emit the gnu pubnames debuginfo - int debug_info_kind; // Enum for line-table-only, line-directives-only, + int debug_info_kind; // Enum for line-table-only, line-directives-only, // limited, standalone + int safepoint_on_entry; // Emit a safepoint on entry to each function + // Cache access. Default: jl_rettype_inferred. jl_codeinstance_lookup_t lookup; diff --git a/src/julia_atomics.h b/src/julia_atomics.h index 1f1a7a46cc9b6..c4488f774c987 100644 --- a/src/julia_atomics.h +++ b/src/julia_atomics.h @@ -73,7 +73,18 @@ enum jl_memory_order { * are). We also need to access these atomic variables from the LLVM JIT code * which is very hard unless the layout of the object is fully specified. */ -#define jl_fence() atomic_thread_fence(memory_order_seq_cst) + +/** + * On modern Intel and AMD platforms `lock orq` on the SP is faster than + * `mfence`. GCC 11 did switch to this representation. See #48123 + */ +#if defined(_CPU_X86_64_) && \ + ((defined(__GNUC__) && __GNUC__ < 11) || \ + (defined(__clang__))) + #define jl_fence() __asm__ volatile("lock orq $0 , (%rsp)") +#else + #define jl_fence() atomic_thread_fence(memory_order_seq_cst) +#endif #define jl_fence_release() atomic_thread_fence(memory_order_release) #define jl_signal_fence() atomic_signal_fence(memory_order_seq_cst) @@ -149,7 +160,12 @@ bool jl_atomic_cmpswap_explicit(std::atomic *ptr, T *expected, S val, std::me { return std::atomic_compare_exchange_strong_explicit(ptr, expected, val, order, order); } -#define jl_atomic_cmpswap_relaxed(ptr, val) jl_atomic_cmpswap_explicit(ptr, val, memory_order_relaxed) +template +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); +} +#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) { @@ -180,6 +196,8 @@ 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) \ + 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) @@ -240,6 +258,7 @@ extern "C" { #define jl_atomic_exchange_relaxed jl_atomic_exchange #undef jl_atomic_cmpswap +#undef jl_atomic_cmpswap_acqrel #undef jl_atomic_cmpswap_relaxed #define jl_atomic_cmpswap(obj, expected, desired) \ (__extension__({ \ @@ -253,6 +272,7 @@ extern "C" { *x__analyzer__ = temp__analyzer__; \ eq__analyzer__; \ })) +#define jl_atomic_cmpswap_acqrel 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 6787dafb4b7ee..27f0a6b5ec11c 100644 --- a/src/julia_gcext.h +++ b/src/julia_gcext.h @@ -49,6 +49,13 @@ JL_DLLEXPORT jl_datatype_t *jl_new_foreign_type( int haspointers, int large); + +#define HAVE_JL_REINIT_FOREIGN_TYPE 1 +JL_DLLEXPORT int jl_reinit_foreign_type( + jl_datatype_t *dt, + jl_markfunc_t markfunc, + jl_sweepfunc_t sweepfunc); + JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt); JL_DLLEXPORT size_t jl_gc_max_internal_obj_size(void); @@ -76,10 +83,10 @@ JL_DLLEXPORT void jl_gc_mark_queue_objarray(jl_ptls_t ptls, jl_value_t *parent, // Sweep functions will not automatically be called for objects of // foreign types, as that may not always be desired. Only calling // jl_gc_schedule_foreign_sweepfunc() on an object of a foreign type -// will result in the custome sweep function actually being called. +// will result in the custom sweep function actually being called. // This must be done at most once per object and should usually be // done right after allocating the object. -JL_DLLEXPORT void jl_gc_schedule_foreign_sweepfunc(jl_ptls_t ptls, jl_value_t * bj); +JL_DLLEXPORT void jl_gc_schedule_foreign_sweepfunc(jl_ptls_t ptls, jl_value_t *bj); // The following functions enable support for conservative marking. This // functionality allows the user to determine if a machine word can be @@ -120,6 +127,8 @@ JL_DLLEXPORT int jl_gc_conservative_gc_support_enabled(void); // external allocations may not all be valid objects and that for those, // the user *must* validate that they have a proper type, i.e. that // jl_typeof(obj) is an actual type object. +// +// NOTE: Only valid to call from within a GC context. JL_DLLEXPORT jl_value_t *jl_gc_internal_obj_base_ptr(void *p); // Return a non-null pointer to the start of the stack area if the task diff --git a/src/julia_internal.h b/src/julia_internal.h index 3d43584faf0c4..49f0b19ec4209 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -4,7 +4,9 @@ #define JL_INTERNAL_H #include "options.h" +#include "julia_assert.h" #include "julia_locks.h" +#include "julia_threads.h" #include "support/utils.h" #include "support/hashing.h" #include "support/ptrhash.h" @@ -12,24 +14,87 @@ #include "gc-alloc-profiler.h" #include "support/rle.h" #include +#include +#include #if !defined(_WIN32) #include #else #define sleep(x) Sleep(1000*x) #endif +#if defined(_CPU_ARM_) +#include +#endif + +// pragma visibility is more useful than -fvisibility +#pragma GCC visibility push(hidden) #ifdef __cplusplus extern "C" { #endif #ifdef _COMPILER_ASAN_ENABLED_ -void __sanitizer_start_switch_fiber(void**, const void*, size_t); -void __sanitizer_finish_switch_fiber(void*, const void**, size_t*); +#if defined(__GLIBC__) && defined(_CPU_X86_64_) +/* TODO: This is terrible - we're reaching deep into glibc internals here. + We should probably just switch to our own setjmp/longjmp implementation. */ +#define JB_RSP 6 +static inline uintptr_t demangle_ptr(uintptr_t var) +{ + asm ("ror $17, %0\n\t" + "xor %%fs:0x30, %0\n\t" + : "=r" (var) + : "0" (var)); + return var; +} +static inline uintptr_t jmpbuf_sp(jl_jmp_buf *buf) +{ + return demangle_ptr((uintptr_t)(*buf)[0].__jmpbuf[JB_RSP]); +} +#else +#error Need to implement jmpbuf_sp for this architecture +#endif +JL_DLLIMPORT void __sanitizer_start_switch_fiber(void**, const void*, size_t); +JL_DLLIMPORT void __sanitizer_finish_switch_fiber(void*, const void**, size_t*); +JL_DLLIMPORT void __asan_unpoison_stack_memory(uintptr_t addr, size_t size); +static inline void asan_unpoison_task_stack(jl_task_t *ct, jl_jmp_buf *buf) +{ + if (!ct) + return; + /* Unpoison everything from the base of the stack allocation to the address + that we're resetting to. The idea is to remove the poison from the frames + that we're skipping over, since they won't be unwound. */ + uintptr_t top = jmpbuf_sp(buf); + uintptr_t bottom = (uintptr_t)ct->stkbuf; + __asan_unpoison_stack_memory(bottom, top - bottom); +} +static inline void asan_unpoison_stack_memory(uintptr_t addr, size_t size) { + __asan_unpoison_stack_memory(addr, size); +} +#else +static inline void asan_unpoison_task_stack(jl_task_t *ct, jl_jmp_buf *buf) JL_NOTSAFEPOINT {} +static inline void asan_unpoison_stack_memory(uintptr_t addr, size_t size) JL_NOTSAFEPOINT {} +#endif +#ifdef _COMPILER_MSAN_ENABLED_ +JL_DLLIMPORT void __msan_unpoison(const volatile void *a, size_t size) JL_NOTSAFEPOINT; +JL_DLLIMPORT void __msan_allocated_memory(const volatile void *a, size_t size) JL_NOTSAFEPOINT; +JL_DLLIMPORT void __msan_unpoison_string(const volatile char *a) JL_NOTSAFEPOINT; +static inline void msan_allocated_memory(const volatile void *a, size_t size) JL_NOTSAFEPOINT { + __msan_allocated_memory(a, size); +} +static inline void msan_unpoison(const volatile void *a, size_t size) JL_NOTSAFEPOINT { + __msan_unpoison(a, size); +} +static inline void msan_unpoison_string(const volatile char *a) JL_NOTSAFEPOINT { + __msan_unpoison_string(a); +} +#else +static inline void msan_unpoison(const volatile void *a, size_t size) JL_NOTSAFEPOINT {} +static inline void msan_allocated_memory(const volatile void *a, size_t size) JL_NOTSAFEPOINT {} +static inline void msan_unpoison_string(const volatile char *a) JL_NOTSAFEPOINT {} #endif #ifdef _COMPILER_TSAN_ENABLED_ -void *__tsan_create_fiber(unsigned flags); -void *__tsan_get_current_fiber(void); -void __tsan_destroy_fiber(void *fiber); -void __tsan_switch_to_fiber(void *fiber, unsigned flags); +JL_DLLIMPORT void *__tsan_create_fiber(unsigned flags); +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 } @@ -132,6 +197,13 @@ 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); +JL_DLLEXPORT void jl_init_profile_lock(void); +JL_DLLEXPORT uintptr_t jl_lock_profile_rd_held(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_lock_profile(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +JL_DLLEXPORT void jl_unlock_profile(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; +JL_DLLEXPORT void jl_lock_profile_wr(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +JL_DLLEXPORT void jl_unlock_profile_wr(void) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; + // number of cycles since power-on static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT { @@ -151,6 +223,26 @@ static inline uint64_t cycleclock(void) JL_NOTSAFEPOINT int64_t virtual_timer_value; __asm__ volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); return virtual_timer_value; +#elif defined(_CPU_ARM_) + // V6 is the earliest arch that has a standard cyclecount +#if (__ARM_ARCH >= 6) + uint32_t pmccntr; + uint32_t pmuseren; + uint32_t pmcntenset; + // Read the user mode perf monitor counter access permissions. + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren)); + if (pmuseren & 1) { // Allows reading perfmon counters for user mode code. + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset)); + if (pmcntenset & 0x80000000ul) { // Is it counting? + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr)); + // The counter is set up to count every 64th cycle + return (int64_t)(pmccntr) * 64; // Should optimize to << 6 + } + } +#endif + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)(tv.tv_sec) * 1000000 + tv.tv_usec; #elif defined(_CPU_PPC64_) // This returns a time-base, which is not always precisely a cycle-count. // https://reviews.llvm.org/D78084 @@ -169,6 +261,7 @@ static inline uint64_t cycleclock(void) 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; +extern JL_DLLEXPORT _Atomic(uint64_t) jl_cumulative_recompile_time; #define jl_return_address() ((uintptr_t)__builtin_return_address(0)) @@ -196,12 +289,12 @@ static inline void memmove_refs(void **dstp, void *const *srcp, size_t n) JL_NOT _Atomic(void*) *dstpa = (_Atomic(void*)*)dstp; if (dstp < srcp || dstp > srcp + n) { for (i = 0; i < n; i++) { - jl_atomic_store_relaxed(dstpa + i, jl_atomic_load_relaxed(srcpa + i)); + jl_atomic_store_release(dstpa + i, jl_atomic_load_relaxed(srcpa + i)); } } else { for (i = 0; i < n; i++) { - jl_atomic_store_relaxed(dstpa + n - i - 1, jl_atomic_load_relaxed(srcpa + n - i - 1)); + jl_atomic_store_release(dstpa + n - i - 1, jl_atomic_load_relaxed(srcpa + n - i - 1)); } } } @@ -212,10 +305,13 @@ static inline void memmove_refs(void **dstp, void *const *srcp, size_t n) JL_NOT #define GC_MARKED 1 // reachable and young #define GC_OLD 2 // if it is reachable it will be marked as old #define GC_OLD_MARKED (GC_OLD | GC_MARKED) // reachable and old +#define GC_IN_IMAGE 4 // useful constants -extern jl_methtable_t *jl_type_type_mt JL_GLOBALLY_ROOTED; -extern jl_methtable_t *jl_nonfunction_mt JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_methtable_t *jl_type_type_mt JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_methtable_t *jl_nonfunction_mt JL_GLOBALLY_ROOTED; +extern jl_methtable_t *jl_kwcall_mt JL_GLOBALLY_ROOTED; +extern JL_DLLEXPORT jl_method_t *jl_opaque_closure_method JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT _Atomic(size_t) jl_world_counter; typedef void (*tracer_cb)(jl_value_t *tracee); @@ -223,9 +319,13 @@ extern tracer_cb jl_newmeth_tracer; void jl_call_tracer(tracer_cb callback, jl_value_t *tracee); 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 +extern arraylist_t eytzinger_image_tree; +extern arraylist_t eytzinger_idxs; extern JL_DLLEXPORT size_t jl_page_size; -extern jl_function_t *jl_typeinf_func; +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; @@ -236,7 +336,7 @@ JL_DLLEXPORT extern const char *jl_filename; jl_value_t *jl_gc_pool_alloc_noinline(jl_ptls_t ptls, int pool_offset, int osize); jl_value_t *jl_gc_big_alloc_noinline(jl_ptls_t ptls, size_t allocsz); -JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize); +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; @@ -285,7 +385,7 @@ static const int jl_gc_sizeclasses[] = { }; static_assert(sizeof(jl_gc_sizeclasses) / sizeof(jl_gc_sizeclasses[0]) == JL_GC_N_POOLS, ""); -STATIC_INLINE int jl_gc_alignment(size_t sz) +STATIC_INLINE int jl_gc_alignment(size_t sz) JL_NOTSAFEPOINT { if (sz == 0) return sizeof(void*); @@ -305,14 +405,14 @@ STATIC_INLINE int jl_gc_alignment(size_t sz) return 16; #endif } -JL_DLLEXPORT int jl_alignment(size_t sz); +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_assert(sizeof(szclass_table) == 128, ""); -STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) +STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) JL_NOTSAFEPOINT { assert(sz <= 2032); #ifdef _P64 @@ -332,7 +432,7 @@ STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass(unsigned sz) return klass + N; } -STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass_align8(unsigned sz) +STATIC_INLINE uint8_t JL_CONST_FUNC jl_gc_szclass_align8(unsigned sz) JL_NOTSAFEPOINT { if (sz >= 16 && sz <= 152) { #ifdef _P64 @@ -393,9 +493,12 @@ JL_DLLEXPORT jl_value_t *jl_gc_alloc(jl_ptls_t ptls, size_t sz, void *ty); # define jl_gc_alloc(ptls, sz, ty) jl_gc_alloc_(ptls, sz, ty) #endif -// jl_buff_tag must be a multiple of GC_PAGE_SZ so that it can't be -// confused for an actual type reference. -#define jl_buff_tag ((uintptr_t)0x4eadc000) +// jl_buff_tag must be an actual pointer here, so it cannot be confused for an actual type reference. +// defined as uint64_t[3] so that we can get the right alignment of this and a "type tag" on it +const extern uint64_t _jl_buff_tag[3]; +#define jl_buff_tag ((uintptr_t)LLT_ALIGN((uintptr_t)&_jl_buff_tag[1],16)) +JL_DLLEXPORT uintptr_t jl_get_buff_tag(void); + typedef void jl_gc_tracked_buffer_t; // For the benefit of the static analyzer STATIC_INLINE jl_gc_tracked_buffer_t *jl_gc_alloc_buf(jl_ptls_t ptls, size_t sz) { @@ -413,15 +516,13 @@ STATIC_INLINE jl_value_t *jl_gc_permobj(size_t sz, void *ty) JL_NOTSAFEPOINT o->header = tag | GC_OLD_MARKED; return jl_valueof(o); } -jl_value_t *jl_permbox8(jl_datatype_t *t, int8_t x); -jl_value_t *jl_permbox16(jl_datatype_t *t, int16_t x); -jl_value_t *jl_permbox32(jl_datatype_t *t, int32_t x); -jl_value_t *jl_permbox64(jl_datatype_t *t, int64_t x); +jl_value_t *jl_permbox8(jl_datatype_t *t, uintptr_t tag, uint8_t x); +jl_value_t *jl_permbox32(jl_datatype_t *t, uintptr_t tag, uint32_t x); jl_svec_t *jl_perm_symsvec(size_t n, ...); // this sizeof(__VA_ARGS__) trick can't be computed until C11, but that only matters to Clang in some situations #if !defined(__clang_analyzer__) && !(defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_TSAN_ENABLED_)) -#ifdef __GNUC__ +#ifdef _COMPILER_GCC_ #define jl_perm_symsvec(n, ...) \ (jl_perm_symsvec)(__extension__({ \ static_assert( \ @@ -462,15 +563,13 @@ void jl_gc_track_malloced_array(jl_ptls_t ptls, jl_array_t *a) 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_queue_binding(jl_binding_t *bnd) 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* { - if (__unlikely(jl_astaggedvalue(bnd)->bits.gc == 3 && - (jl_astaggedvalue(val)->bits.gc & 1) == 0)) - gc_queue_binding(bnd); + 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* @@ -482,13 +581,16 @@ STATIC_INLINE void jl_gc_wb_buf(void *parent, void *bufptr, size_t minsz) JL_NOT } } -void jl_gc_debug_print_status(void); -JL_DLLEXPORT void jl_gc_debug_critical_error(void); +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); void jl_gc_reset_alloc_count(void); uint32_t jl_get_gs_ctr(void); void jl_set_gs_ctr(uint32_t ctr); +typedef struct _jl_static_show_config_t { uint8_t quiet; } jl_static_show_config_t; +size_t jl_static_show_func_sig_(JL_STREAM *s, jl_value_t *type, jl_static_show_config_t ctx) JL_NOTSAFEPOINT; + STATIC_INLINE jl_value_t *undefref_check(jl_datatype_t *dt, jl_value_t *v) JL_NOTSAFEPOINT { if (dt->layout->first_ptr >= 0) { @@ -502,10 +604,10 @@ STATIC_INLINE jl_value_t *undefref_check(jl_datatype_t *dt, jl_value_t *v) JL_NO // -- helper types -- // typedef struct { - uint8_t pure:1; - uint8_t propagate_inbounds:1; - uint8_t inlineable:1; uint8_t inferred:1; + uint8_t propagate_inbounds:1; + uint8_t has_fcall:1; + uint8_t inlining:2; // 0 = use heuristic; 1 = aggressive; 2 = none uint8_t constprop:2; // 0 = use heuristic; 1 = aggressive; 2 = none } jl_code_info_flags_bitfield_t; @@ -516,27 +618,32 @@ typedef union { // -- functions -- // -// jl_code_info_flag_t code_info_flags(uint8_t pure, uint8_t propagate_inbounds, uint8_t inlineable, uint8_t inferred, uint8_t constprop); JL_DLLEXPORT jl_code_info_t *jl_type_infer(jl_method_instance_t *li, size_t world, int force); JL_DLLEXPORT jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *meth JL_PROPAGATES_ROOT, size_t world); -jl_code_instance_t *jl_generate_fptr(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world); -void jl_generate_fptr_for_unspecialized(jl_code_instance_t *unspec); 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(jl_method_instance_t *method JL_PROPAGATES_ROOT); +jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT); +jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); +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); -int jl_code_requires_compiler(jl_code_info_t *src); +jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *lam JL_PROPAGATES_ROOT, size_t world); +int jl_code_requires_compiler(jl_code_info_t *src, int include_force_compile); jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ast); JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void); -void jl_resolve_globals_in_ir(jl_array_t *stmts, jl_module_t *m, jl_svec_t *sparam_vals, - int binding_effects); +JL_DLLEXPORT void jl_resolve_globals_in_ir(jl_array_t *stmts, jl_module_t *m, jl_svec_t *sparam_vals, + int binding_effects); + +int get_next_edge(jl_array_t *list, int i, jl_value_t** invokesig, jl_method_instance_t **caller) JL_NOTSAFEPOINT; +int set_next_edge(jl_array_t *list, int i, jl_value_t *invokesig, jl_method_instance_t *caller); +void push_edge(jl_array_t *list, jl_value_t *invokesig, jl_method_instance_t *caller); JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_t* root); -int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i); -jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index); +void jl_append_method_roots(jl_method_t *m, uint64_t modid, jl_array_t* roots); +int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i) JL_NOTSAFEPOINT; +jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index) JL_NOTSAFEPOINT; +int nroots_with_key(jl_method_t *m, uint64_t key) JL_NOTSAFEPOINT; int jl_valid_type_param(jl_value_t *v); @@ -545,7 +652,6 @@ JL_DLLEXPORT jl_value_t *jl_apply_2va(jl_value_t *f, jl_value_t **args, uint32_t void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na, size_t world); JL_DLLEXPORT jl_value_t *jl_get_exceptionf(jl_datatype_t *exception_type, const char *fmt, ...); -JL_DLLEXPORT jl_value_t *jl_get_keyword_sorter(jl_value_t *f); JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t); #define JL_CALLABLE(name) \ @@ -561,8 +667,7 @@ void jl_install_thread_signal_handler(jl_ptls_t ptls); JL_DLLEXPORT jl_fptr_args_t jl_get_builtin_fptr(jl_value_t *b); extern uv_loop_t *jl_io_loop; -void jl_uv_flush(uv_stream_t *stream); -void jl_uv_call_close_callback(jl_value_t *val); +JL_DLLEXPORT void jl_uv_flush(uv_stream_t *stream); typedef struct jl_typeenv_t { jl_tvar_t *var; @@ -595,8 +700,10 @@ 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); JL_DLLEXPORT jl_value_t *jl_instantiate_type_in_env(jl_value_t *ty, jl_unionall_t *env, jl_value_t **vals); jl_value_t *jl_substitute_var(jl_value_t *t, jl_tvar_t *var, jl_value_t *val); +jl_unionall_t *jl_rename_unionall(jl_unionall_t *u); JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u); +JL_DLLEXPORT jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u); int jl_count_union_components(jl_value_t *v); JL_DLLEXPORT jl_value_t *jl_nth_union_component(jl_value_t *v JL_PROPAGATES_ROOT, int i) JL_NOTSAFEPOINT; int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned *nth) JL_NOTSAFEPOINT; @@ -609,6 +716,7 @@ jl_vararg_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n); void jl_reinstantiate_inner_types(jl_datatype_t *t); jl_datatype_t *jl_lookup_cache_type_(jl_datatype_t *type); void jl_cache_type_(jl_datatype_t *type); +jl_svec_t *cache_rehash_set(jl_svec_t *a, size_t newsz); void set_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_value_t *rhs, int isatomic) JL_NOTSAFEPOINT; 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); @@ -617,10 +725,12 @@ 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); int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), void *env); +int foreach_mtable_in_module(jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env); void jl_init_main_module(void); JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); 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); @@ -638,16 +748,20 @@ jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t w 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); +JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup_worlds(jl_value_t *types, jl_value_t *mt, size_t world, size_t *min_world, size_t *max_world); JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, jl_value_t *mt, int lim, int include_ambiguous, size_t world, size_t *min_valid, size_t *max_valid, int *ambig); +JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup_worlds(jl_value_t *types, jl_value_t *mt, size_t world, size_t *min_world, size_t *max_world); -JL_DLLEXPORT jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; + +jl_datatype_t *jl_nth_argument_datatype(jl_value_t *argtypes JL_PROPAGATES_ROOT, int n) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_argument_datatype(jl_value_t *argt JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_methtable_t *jl_method_table_for( jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; +jl_methtable_t *jl_kwmethod_table_for( + jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_methtable_t *jl_method_get_table( - jl_method_t *method) JL_NOTSAFEPOINT; -jl_methtable_t *jl_argument_method_table(jl_value_t *argt JL_PROPAGATES_ROOT); + jl_method_t *method JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; 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; @@ -655,15 +769,17 @@ 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_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); -JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b); +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; -int jl_compile_extern_c(void *llvmmod, void *params, void *sysimg, jl_value_t *declrt, jl_value_t *sigt); +extern jl_array_t *jl_global_roots_table 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_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); + jl_value_t *source, jl_value_t **env, size_t nenv, int do_compile); JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source); // Each tuple can exist in one of 4 Vararg states: @@ -678,11 +794,6 @@ typedef enum { JL_VARARG_UNBOUND = 3 } jl_vararg_kind_t; -STATIC_INLINE int jl_is_vararg(jl_value_t *v) JL_NOTSAFEPOINT -{ - return jl_typeof(v) == (jl_value_t*)jl_vararg_type; -} - STATIC_INLINE jl_value_t *jl_unwrap_vararg(jl_vararg_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { assert(jl_is_vararg((jl_value_t*)v)); @@ -743,22 +854,19 @@ void jl_init_flisp(void); void jl_init_common_symbols(void); void jl_init_primitives(void) JL_GC_DISABLED; void jl_init_llvm(void); -void jl_init_codegen(void); void jl_init_runtime_ccall(void); void jl_init_intrinsic_functions(void); void jl_init_intrinsic_properties(void); void jl_init_tasks(void) JL_GC_DISABLED; -void jl_init_stack_limits(int ismaster, void **stack_hi, void **stack_lo); +void jl_init_stack_limits(int ismaster, void **stack_hi, void **stack_lo) JL_NOTSAFEPOINT; jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi); void jl_init_serializer(void); void jl_gc_init(void); void jl_init_uv(void); -void jl_init_thread_heap(jl_ptls_t ptls); +void jl_init_thread_heap(jl_ptls_t ptls) JL_NOTSAFEPOINT; void jl_init_int32_int64_cache(void); JL_DLLEXPORT void jl_init_options(void); -void jl_teardown_codegen(void); - void jl_set_base_ctx(char *__stk); extern JL_DLLEXPORT ssize_t jl_tls_offset; @@ -808,7 +916,7 @@ void jl_safepoint_defer_sigint(void); // Return `1` if the sigint should be delivered and `0` if there's no sigint // to be delivered. int jl_safepoint_consume_sigint(void); -void jl_wake_libuv(void); +void jl_wake_libuv(void) JL_NOTSAFEPOINT; void jl_set_pgcstack(jl_gcframe_t **) JL_NOTSAFEPOINT; #if defined(_OS_DARWIN_) @@ -818,7 +926,11 @@ typedef DWORD jl_pgcstack_key_t; #else typedef jl_gcframe_t ***(*jl_pgcstack_key_t)(void) JL_NOTSAFEPOINT; #endif -JL_DLLEXPORT void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k); +JL_DLLEXPORT void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k) JL_NOTSAFEPOINT; + +#if !defined(_OS_WINDOWS_) && !defined(__APPLE__) && !defined(JL_DISABLE_LIBUNWIND) +extern pthread_mutex_t in_signal_lock; +#endif #if !defined(__clang_gcanalyzer__) && !defined(_OS_DARWIN_) static inline void jl_set_gc_and_wait(void) @@ -832,22 +944,16 @@ static inline void jl_set_gc_and_wait(void) jl_atomic_store_release(&ct->ptls->gc_state, state); } #endif -void jl_gc_set_permalloc_region(void *start, void *end); -JL_DLLEXPORT jl_value_t *jl_dump_method_asm(jl_method_instance_t *linfo, size_t world, - char raw_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary); -JL_DLLEXPORT void *jl_get_llvmf_defn(jl_method_instance_t *linfo, size_t world, char getwrapper, char optimize, const jl_cgparams_t params); -JL_DLLEXPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, char raw_mc, const char* asm_variant, const char *debuginfo, char binary); -JL_DLLEXPORT jl_value_t *jl_dump_function_ir(void *f, char strip_ir_metadata, char dump_module, const char *debuginfo); -JL_DLLEXPORT jl_value_t *jl_dump_function_asm(void *F, char raw_mc, const char* asm_variant, const char *debuginfo, char binary); +// 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 +{ + return jl_image_relocs.len; +} -void *jl_create_native(jl_array_t *methods, const jl_cgparams_t *cgparams, int policy); -void jl_dump_native(void *native_code, - const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, - const char *sysimg_data, size_t sysimg_len); -int32_t jl_get_llvm_gv(void *native_code, jl_value_t *p) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_get_function_id(void *native_code, jl_code_instance_t *ncode, - int32_t *func_idx, int32_t *specfunc_idx); +size_t external_blob_index(jl_value_t *v) JL_NOTSAFEPOINT; + +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 @@ -860,20 +966,24 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo 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_code_instance_t *jl_method_compiled(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t world); -JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *type, size_t world); +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_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); -JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_method_instance_t *caller); +jl_method_instance_t *jl_specializations_get_or_insert(jl_method_instance_t *mi_ins); +JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_value_t *invokesig, jl_method_instance_t *caller); 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 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; jl_tupletype_t *arg_type_tuple(jl_value_t *arg1, jl_value_t **args, size_t nargs); JL_DLLEXPORT int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT; -jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, - size_t lineno, size_t offset, jl_value_t *options); +JL_DLLEXPORT jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, + size_t lineno, size_t offset, jl_value_t *options); //-------------------------------------------------- // Backtraces @@ -899,7 +1009,7 @@ jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, // 2. An "extended entry": a mixture of raw data and pointers to julia objects // which must be treated as GC roots. // -// A single extended entry is seralized using multiple elements from the raw +// A single extended entry is serialized using multiple elements from the raw // buffer; if `e` is the pointer to the first slot we have: // // e[0] JL_BT_NON_PTR_ENTRY - Special marker to distinguish extended entries @@ -990,8 +1100,6 @@ typedef struct { int inlined; } jl_frame_t; -// Might be called from unmanaged thread -uint64_t jl_getUnwindInfo(uint64_t dwBase); #ifdef _OS_WINDOWS_ #include JL_DLLEXPORT EXCEPTION_DISPOSITION NTAPI __julia_personality( @@ -1010,7 +1118,9 @@ extern JL_DLLEXPORT uv_mutex_t jl_in_stackwalk; #elif !defined(JL_DISABLE_LIBUNWIND) // This gives unwind only local unwinding options ==> faster code # define UNW_LOCAL_ONLY +#pragma GCC visibility push(default) # include +#pragma GCC visibility pop typedef unw_context_t bt_context_t; typedef unw_cursor_t bt_cursor_t; # if (!defined(SYSTEM_LIBUNWIND) || UNW_VERSION_MAJOR > 1 || \ @@ -1033,9 +1143,8 @@ size_t rec_backtrace_ctx(jl_bt_element_t *bt_data, size_t maxsize, bt_context_t size_t rec_backtrace_ctx_dwarf(jl_bt_element_t *bt_data, size_t maxsize, bt_context_t *ctx, jl_gcframe_t *pgcstack) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT jl_value_t *jl_get_backtrace(void); -void jl_critical_error(int sig, bt_context_t *context, jl_task_t *ct); -JL_DLLEXPORT void jl_raise_debugger(void); -int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT; +void jl_critical_error(int sig, int si_code, bt_context_t *context, jl_task_t *ct); +JL_DLLEXPORT void jl_raise_debugger(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_gdblookup(void* ip) JL_NOTSAFEPOINT; void jl_print_native_codeloc(uintptr_t ip) JL_NOTSAFEPOINT; void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_data) JL_NOTSAFEPOINT; @@ -1105,35 +1214,36 @@ void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_AR //-------------------------------------------------- // congruential random number generator // for a small amount of thread-local randomness -// we could just use libc:`rand()`, but we want to ensure this is fast -STATIC_INLINE void seed_cong(uint64_t *seed) -{ - *seed = rand(); -} -STATIC_INLINE void unbias_cong(uint64_t max, uint64_t *unbias) +STATIC_INLINE void unbias_cong(uint64_t max, uint64_t *unbias) JL_NOTSAFEPOINT { *unbias = UINT64_MAX - ((UINT64_MAX % max) + 1); } -STATIC_INLINE uint64_t cong(uint64_t max, uint64_t unbias, uint64_t *seed) +STATIC_INLINE uint64_t cong(uint64_t max, uint64_t unbias, uint64_t *seed) JL_NOTSAFEPOINT { while ((*seed = 69069 * (*seed) + 362437) > unbias) ; return *seed % max; } +JL_DLLEXPORT uint64_t jl_rand(void) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_srand(uint64_t) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_init_rand(void); +JL_DLLEXPORT extern void *jl_exe_handle; +JL_DLLEXPORT extern void *jl_libjulia_handle; JL_DLLEXPORT extern void *jl_libjulia_internal_handle; JL_DLLEXPORT extern void *jl_RTLD_DEFAULT_handle; + #if defined(_OS_WINDOWS_) -JL_DLLEXPORT extern void *jl_exe_handle; -JL_DLLEXPORT extern void *jl_libjulia_handle; JL_DLLEXPORT extern const char *jl_crtdll_basename; extern void *jl_ntdll_handle; extern void *jl_kernel32_handle; extern void *jl_crtdll_handle; extern void *jl_winsock_handle; +void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT; #endif JL_DLLEXPORT void *jl_get_library_(const char *f_lib, int throw_err); +void *jl_find_dynamic_library_by_addr(void *symbol); #define jl_get_library(f_lib) jl_get_library_(f_lib, 1) JL_DLLEXPORT void *jl_load_and_lookup(const char *f_lib, const char *f_name, _Atomic(void*) *hnd); JL_DLLEXPORT void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name); @@ -1143,19 +1253,14 @@ JL_DLLEXPORT jl_value_t *jl_get_cfunction_trampoline( jl_unionall_t *env, jl_value_t **vals); -// Windows only +// Special filenames used to refer to internal julia libraries #define JL_EXE_LIBNAME ((const char*)1) #define JL_LIBJULIA_DL_LIBNAME ((const char*)2) #define JL_LIBJULIA_INTERNAL_DL_LIBNAME ((const char*)3) -JL_DLLEXPORT const char *jl_dlfind_win32(const char *name); +JL_DLLEXPORT const char *jl_dlfind(const char *name); // libuv wrappers: JL_DLLEXPORT int jl_fs_rename(const char *src_path, const char *dst_path); -int jl_getpid(void) JL_NOTSAFEPOINT; - -#ifdef SEGV_EXCEPTION -extern JL_DLLEXPORT jl_value_t *jl_segv_exception; -#endif // -- Runtime intrinsics -- // JL_DLLEXPORT const char *jl_intrinsic_name(int f) JL_NOTSAFEPOINT; @@ -1194,7 +1299,6 @@ JL_DLLEXPORT jl_value_t *jl_add_float(jl_value_t *a, jl_value_t *b); JL_DLLEXPORT jl_value_t *jl_sub_float(jl_value_t *a, jl_value_t *b); JL_DLLEXPORT jl_value_t *jl_mul_float(jl_value_t *a, jl_value_t *b); JL_DLLEXPORT jl_value_t *jl_div_float(jl_value_t *a, jl_value_t *b); -JL_DLLEXPORT jl_value_t *jl_rem_float(jl_value_t *a, jl_value_t *b); JL_DLLEXPORT jl_value_t *jl_fma_float(jl_value_t *a, jl_value_t *b, jl_value_t *c); JL_DLLEXPORT jl_value_t *jl_muladd_float(jl_value_t *a, jl_value_t *b, jl_value_t *c); @@ -1269,7 +1373,6 @@ JL_DLLEXPORT void jl_set_next_task(jl_task_t *task) JL_NOTSAFEPOINT; extern jl_mutex_t typecache_lock; extern JL_DLLEXPORT jl_mutex_t jl_codegen_lock; -extern uv_mutex_t safepoint_lock; #if defined(__APPLE__) void jl_mach_gc_end(void); @@ -1332,20 +1435,29 @@ struct typemap_intersection_env { jl_typemap_intersection_visitor_fptr const fptr; // fptr to call on a match jl_value_t *const type; // type to match jl_value_t *const va; // the tparam0 for the vararg in type, if applicable (or NULL) + size_t search_slurp; // output values + size_t min_valid; + size_t max_valid; jl_value_t *ti; // intersection type jl_svec_t *env; // intersection env (initialize to null to perform intersection without an environment) int issubty; // if `a <: b` is true in `intersect(a,b)` }; int jl_typemap_intersection_visitor(jl_typemap_t *a, int offs, struct typemap_intersection_env *closure); +void typemap_slurp_search(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure); // -- simplevector.c -- // // For codegen only. JL_DLLEXPORT size_t (jl_svec_len)(jl_svec_t *t) JL_NOTSAFEPOINT; -JL_DLLEXPORT int8_t jl_svec_isassigned(jl_svec_t *t JL_PROPAGATES_ROOT, ssize_t i) 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; + +JL_DLLEXPORT jl_value_t *jl_argtype_with_function(jl_value_t *f, jl_value_t *types0); +JL_DLLEXPORT jl_value_t *jl_argtype_with_function_type(jl_value_t *ft JL_MAYBE_UNROOTED, jl_value_t *types0); JL_DLLEXPORT unsigned jl_special_vector_alignment(size_t nfields, jl_value_t *field_type); @@ -1395,6 +1507,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_return_sym; extern JL_DLLEXPORT jl_sym_t *jl_lineinfo_sym; extern JL_DLLEXPORT jl_sym_t *jl_lambda_sym; extern JL_DLLEXPORT jl_sym_t *jl_assign_sym; +extern JL_DLLEXPORT jl_sym_t *jl_binding_sym; extern JL_DLLEXPORT jl_sym_t *jl_globalref_sym; extern JL_DLLEXPORT jl_sym_t *jl_do_sym; extern JL_DLLEXPORT jl_sym_t *jl_method_sym; @@ -1422,7 +1535,6 @@ extern JL_DLLEXPORT jl_sym_t *jl_boundscheck_sym; extern JL_DLLEXPORT jl_sym_t *jl_inbounds_sym; extern JL_DLLEXPORT jl_sym_t *jl_copyast_sym; extern JL_DLLEXPORT jl_sym_t *jl_cfunction_sym; -extern JL_DLLEXPORT jl_sym_t *jl_pure_sym; extern JL_DLLEXPORT jl_sym_t *jl_loopinfo_sym; extern JL_DLLEXPORT jl_sym_t *jl_meta_sym; extern JL_DLLEXPORT jl_sym_t *jl_inert_sym; @@ -1472,13 +1584,10 @@ extern JL_DLLEXPORT jl_sym_t *jl_sequentially_consistent_sym; JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order(jl_sym_t *order, char loading, char storing); JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, char loading, char storing); -struct _jl_sysimg_fptrs_t; +struct _jl_image_fptrs_t; -void jl_register_fptrs(uint64_t sysimage_base, const struct _jl_sysimg_fptrs_t *fptrs, - jl_method_instance_t **linfos, size_t n); void jl_write_coverage_data(const char*); void jl_write_malloc_log(void); -void jl_write_compiler_output(void); #if jl_has_builtin(__builtin_unreachable) || defined(_COMPILER_GCC_) || defined(_COMPILER_INTEL_) # define jl_unreachable() __builtin_unreachable() @@ -1509,7 +1618,7 @@ jl_sym_t *_jl_symbol(const char *str, size_t len) JL_NOTSAFEPOINT; #endif // _COMPILER_GCC_ #ifdef __clang_gcanalyzer__ - // Not a safepoint (so it dosn't free other values), but an artificial use. + // Not a safepoint (so it doesn't free other values), but an artificial use. // Usually this is unnecessary because the analyzer can see all real uses, // but sometimes real uses are harder for the analyzer to see, or it may // give up before it sees it, so this can be helpful to be explicit. @@ -1518,15 +1627,68 @@ jl_sym_t *_jl_symbol(const char *str, size_t len) JL_NOTSAFEPOINT; #define JL_GC_ASSERT_LIVE(x) (void)(x) #endif -float __gnu_h2f_ieee(uint16_t param) JL_NOTSAFEPOINT; -uint16_t __gnu_f2h_ieee(float param) JL_NOTSAFEPOINT; +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 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 uint32_t jl_crc32c(uint32_t crc, const char *buf, size_t len); + +// -- exports from codegen -- // + +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_extern_c(LLVMOrcThreadSafeModuleRef llvmmod, void *params, void *sysimg, jl_value_t *declrt, jl_value_t *sigt); + +typedef struct { + LLVMOrcThreadSafeModuleRef TSM; + LLVMValueRef F; +} jl_llvmf_dump_t; + +JL_DLLIMPORT jl_value_t *jl_dump_method_asm(jl_method_instance_t *linfo, size_t world, + char raw_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary); +JL_DLLIMPORT void jl_get_llvmf_defn(jl_llvmf_dump_t* dump, jl_method_instance_t *linfo, size_t world, char getwrapper, char optimize, const jl_cgparams_t params); +JL_DLLIMPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, char raw_mc, const char* asm_variant, const char *debuginfo, char binary); +JL_DLLIMPORT jl_value_t *jl_dump_function_ir(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo); +JL_DLLIMPORT jl_value_t *jl_dump_function_asm(jl_llvmf_dump_t *dump, char raw_mc, const char* asm_variant, const char *debuginfo, char binary); + +JL_DLLIMPORT void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy, int imaging_mode, int cache, size_t world); +JL_DLLIMPORT void jl_dump_native(void *native_code, + const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, + const char *sysimg_data, size_t sysimg_len, ios_t *s); +JL_DLLIMPORT void jl_get_llvm_gvs(void *native_code, arraylist_t *gvs); +JL_DLLIMPORT void jl_get_llvm_external_fns(void *native_code, arraylist_t *gvs); +JL_DLLIMPORT void jl_get_function_id(void *native_code, jl_code_instance_t *ncode, + int32_t *func_idx, int32_t *specfunc_idx); +JL_DLLIMPORT void jl_register_fptrs(uint64_t image_base, const struct _jl_image_fptrs_t *fptrs, + jl_method_instance_t **linfos, size_t n); + +JL_DLLIMPORT void jl_init_codegen(void); +JL_DLLIMPORT void jl_teardown_codegen(void) JL_NOTSAFEPOINT; +JL_DLLIMPORT int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline) JL_NOTSAFEPOINT; +// n.b. this might be called from unmanaged thread: +JL_DLLIMPORT uint64_t jl_getUnwindInfo(uint64_t dwBase); #ifdef __cplusplus } #endif +#pragma GCC visibility pop + + #ifdef USE_DTRACE -#include "uprobes.h.gen" +// Generated file, needs to be searched in include paths so that the builddir +// retains priority +#include // uprobes.h.gen on systems with DTrace, is auto-generated to include // `JL_PROBE_{PROBE}` and `JL_PROBE_{PROBE}_ENABLED()` macros for every probe diff --git a/src/julia_locks.h b/src/julia_locks.h index 8da0fc8ac9537..47e258f69aab2 100644 --- a/src/julia_locks.h +++ b/src/julia_locks.h @@ -3,8 +3,6 @@ #ifndef JL_LOCKS_H #define JL_LOCKS_H -#include "julia_assert.h" - #ifdef __cplusplus extern "C" { #endif @@ -17,28 +15,20 @@ extern "C" { // The JL_LOCK* and JL_UNLOCK* macros are no-op for non-threading build // while the jl_mutex_* functions are always locking and unlocking the locks. +JL_DLLEXPORT void _jl_mutex_init(jl_mutex_t *lock, const char *name) JL_NOTSAFEPOINT; +JL_DLLEXPORT void _jl_mutex_wait(jl_task_t *self, jl_mutex_t *lock, int safepoint); +JL_DLLEXPORT void _jl_mutex_lock(jl_task_t *self, jl_mutex_t *lock); +JL_DLLEXPORT int _jl_mutex_trylock_nogc(jl_task_t *self, jl_mutex_t *lock) JL_NOTSAFEPOINT; +JL_DLLEXPORT int _jl_mutex_trylock(jl_task_t *self, jl_mutex_t *lock); +JL_DLLEXPORT void _jl_mutex_unlock(jl_task_t *self, jl_mutex_t *lock); +JL_DLLEXPORT void _jl_mutex_unlock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT; + static inline void jl_mutex_wait(jl_mutex_t *lock, int safepoint) { - jl_task_t *self = jl_current_task; - jl_task_t *owner = jl_atomic_load_relaxed(&lock->owner); - if (owner == self) { - lock->count++; - return; - } - while (1) { - if (owner == NULL && jl_atomic_cmpswap(&lock->owner, &owner, self)) { - lock->count = 1; - return; - } - if (safepoint) { - jl_gc_safepoint_(self->ptls); - } - jl_cpu_pause(); - owner = jl_atomic_load_relaxed(&lock->owner); - } + _jl_mutex_wait(jl_current_task, lock, safepoint); } -static inline void jl_mutex_lock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT +static inline void jl_mutex_lock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER { #ifndef __clang_gcanalyzer__ // Hide this body from the analyzer, otherwise it complains that we're calling @@ -48,26 +38,6 @@ static inline void jl_mutex_lock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT #endif } -static inline void jl_lock_frame_push(jl_mutex_t *lock) -{ - jl_ptls_t ptls = jl_current_task->ptls; - small_arraylist_t *locks = &ptls->locks; - uint32_t len = locks->len; - if (__unlikely(len >= locks->max)) { - small_arraylist_grow(locks, 1); - } - else { - locks->len = len + 1; - } - locks->items[len] = (void*)lock; -} -static inline void jl_lock_frame_pop(void) -{ - jl_ptls_t ptls = jl_current_task->ptls; - assert(ptls->locks.len > 0); - ptls->locks.len--; -} - #define JL_SIGATOMIC_BEGIN() do { \ jl_current_task->ptls->defer_signal++; \ jl_signal_fence(); \ @@ -79,66 +49,48 @@ static inline void jl_lock_frame_pop(void) } \ } while (0) +#define JL_SIGATOMIC_BEGIN_self() do { \ + self->ptls->defer_signal++; \ + jl_signal_fence(); \ + } while (0) +#define JL_SIGATOMIC_END_self() do { \ + jl_signal_fence(); \ + if (--self->ptls->defer_signal == 0) { \ + jl_sigint_safepoint(self->ptls); \ + } \ + } while (0) + static inline void jl_mutex_lock(jl_mutex_t *lock) { - JL_SIGATOMIC_BEGIN(); - jl_mutex_wait(lock, 1); - jl_lock_frame_push(lock); + _jl_mutex_lock(jl_current_task, lock); } -static inline int jl_mutex_trylock_nogc(jl_mutex_t *lock) +static inline int jl_mutex_trylock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER { - jl_task_t *self = jl_current_task; - jl_task_t *owner = jl_atomic_load_acquire(&lock->owner); - if (owner == self) { - lock->count++; - return 1; - } - if (owner == NULL && jl_atomic_cmpswap(&lock->owner, &owner, self)) { - lock->count = 1; - return 1; - } - return 0; + return _jl_mutex_trylock_nogc(jl_current_task, lock); } static inline int jl_mutex_trylock(jl_mutex_t *lock) { - int got = jl_mutex_trylock_nogc(lock); - if (got) { - JL_SIGATOMIC_BEGIN(); - jl_lock_frame_push(lock); - } - return got; + return _jl_mutex_trylock(jl_current_task, lock); } -static inline void jl_mutex_unlock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT + +static inline void jl_mutex_unlock(jl_mutex_t *lock) { -#ifndef __clang_gcanalyzer__ - assert(jl_atomic_load_relaxed(&lock->owner) == jl_current_task && - "Unlocking a lock in a different thread."); - if (--lock->count == 0) { - jl_atomic_store_release(&lock->owner, (jl_task_t*)NULL); - jl_cpu_wake(); - } -#endif + _jl_mutex_unlock(jl_current_task, lock); } -static inline void jl_mutex_unlock(jl_mutex_t *lock) +static inline void jl_mutex_unlock_nogc(jl_mutex_t *lock) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE { - jl_mutex_unlock_nogc(lock); - jl_lock_frame_pop(); - JL_SIGATOMIC_END(); - if (jl_atomic_load_relaxed(&jl_gc_have_pending_finalizers)) { - jl_gc_run_pending_finalizers(jl_current_task); // may GC - } + _jl_mutex_unlock_nogc(lock); } -static inline void jl_mutex_init(jl_mutex_t *lock) JL_NOTSAFEPOINT +static inline void jl_mutex_init(jl_mutex_t *lock, const char *name) JL_NOTSAFEPOINT { - jl_atomic_store_relaxed(&lock->owner, (jl_task_t*)NULL); - lock->count = 0; + _jl_mutex_init(lock, name); } -#define JL_MUTEX_INIT(m) jl_mutex_init(m) +#define JL_MUTEX_INIT(m, name) jl_mutex_init(m, name) #define JL_LOCK(m) jl_mutex_lock(m) #define JL_UNLOCK(m) jl_mutex_unlock(m) #define JL_LOCK_NOGC(m) jl_mutex_lock_nogc(m) diff --git a/src/julia_threads.h b/src/julia_threads.h index 371eb51250115..c8242d6d6eb0f 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -4,6 +4,7 @@ #ifndef JL_THREADS_H #define JL_THREADS_H +#include "work-stealing-queue.h" #include "julia_atomics.h" #ifndef _OS_WINDOWS_ #include "pthread.h" @@ -16,7 +17,7 @@ extern "C" { JL_DLLEXPORT int16_t jl_threadid(void); -JL_DLLEXPORT void jl_threading_profile(void); +JL_DLLEXPORT int8_t jl_threadpoolid(int16_t tid) JL_NOTSAFEPOINT; // JULIA_ENABLE_THREADING may be controlled by altering JULIA_THREADS in Make.user @@ -79,6 +80,7 @@ typedef struct { void *stacktop; } _jl_ucontext_t; #endif +#pragma GCC visibility push(default) #if defined(JL_HAVE_UNW_CONTEXT) #define UNW_LOCAL_ONLY #include @@ -88,6 +90,7 @@ typedef unw_context_t _jl_ucontext_t; #include typedef ucontext_t _jl_ucontext_t; #endif +#pragma GCC visibility pop #endif typedef struct { @@ -98,6 +101,9 @@ typedef struct { #if defined(_COMPILER_TSAN_ENABLED_) void *tsan_state; #endif +#if defined(_COMPILER_ASAN_ENABLED_) + void *asan_fake_stack; +#endif } jl_ucontext_t; @@ -147,7 +153,6 @@ typedef struct { struct _bigval_t *big_objects; // variables for tracking "remembered set" - arraylist_t rem_bindings; arraylist_t _remset[2]; // contains jl_value_t* // lower bound of the number of pointers inside remembered values int remset_nptr; @@ -168,16 +173,11 @@ typedef struct { arraylist_t free_stacks[JL_N_STACK_POOLS]; } jl_thread_heap_t; -// Cache of thread local change to global metadata during GC -// This is sync'd after marking. -typedef union _jl_gc_mark_data jl_gc_mark_data_t; - typedef struct { - void **pc; // Current stack address for the pc (up growing) - jl_gc_mark_data_t *data; // Current stack address for the data (up growing) - void **pc_start; // Cached value of `gc_cache->pc_stack` - void **pc_end; // Cached value of `gc_cache->pc_stack_end` -} jl_gc_mark_sp_t; + ws_queue_t chunk_queue; + ws_queue_t ptr_queue; + arraylist_t reclaim_set; +} jl_gc_markqueue_t; typedef struct { // thread local increment of `perm_scanned_bytes` @@ -195,17 +195,16 @@ typedef struct { // this makes sure that a single objects can only appear once in // the lists (the mark bit cannot be flipped to `0` without sweeping) void *big_obj[1024]; - void **pc_stack; - void **pc_stack_end; - jl_gc_mark_data_t *data_stack; } jl_gc_mark_cache_t; struct _jl_bt_element_t; + // This includes all the thread local states we care about for a thread. // Changes to TLS field types must be reflected in codegen. #define JL_MAX_BT_SIZE 80000 typedef struct _jl_tls_states_t { int16_t tid; + int8_t threadpoolid; uint64_t rngseed; volatile size_t *safepoint; _Atomic(int8_t) sleep_check_state; // read/write from foreign threads @@ -246,6 +245,8 @@ typedef struct _jl_tls_states_t { // Temporary backtrace buffer. Scanned for gc roots when bt_size > 0. struct _jl_bt_element_t *bt_data; // JL_MAX_BT_SIZE + 1 elements long size_t bt_size; // Size for backtrace in transit in bt_data + // Temporary backtrace buffer used only for allocations profiler. + struct _jl_bt_element_t *profiling_bt_buffer; // Atomically set by the sender, reset by the handler. volatile _Atomic(sig_atomic_t) signal_request; // TODO: no actual reason for this to be _Atomic // Allow the sigint to be raised asynchronously @@ -259,9 +260,9 @@ typedef struct _jl_tls_states_t { #endif jl_thread_t system_id; arraylist_t finalizers; + jl_gc_markqueue_t mark_queue; jl_gc_mark_cache_t gc_cache; arraylist_t sweep_objs; - jl_gc_mark_sp_t gc_mark_sp; // Saved exception for previous *external* API call or NULL if cleared. // Access via jl_exception_occurred(). struct _jl_value_t *previous_exception; @@ -275,11 +276,15 @@ typedef struct _jl_tls_states_t { uint64_t sleep_enter; uint64_t sleep_leave; ) -} jl_tls_states_t; -typedef jl_tls_states_t *jl_ptls_t; + // some hidden state (usually just because we don't have the type's size declaration) +#ifdef JL_LIBRARY_EXPORTS + uv_mutex_t sleep_lock; + uv_cond_t wake_signal; +#endif +} jl_tls_states_t; -#ifndef LIBRARY_EXPORTS +#ifndef JL_LIBRARY_EXPORTS // deprecated (only for external consumers) JL_DLLEXPORT void *jl_get_ptls_states(void); #endif @@ -287,23 +292,28 @@ JL_DLLEXPORT void *jl_get_ptls_states(void); // Update codegen version in `ccall.cpp` after changing either `pause` or `wake` #ifdef __MIC__ # define jl_cpu_pause() _mm_delay_64(100) +# define jl_cpu_suspend() _mm_delay_64(100) # define jl_cpu_wake() ((void)0) # define JL_CPU_WAKE_NOOP 1 #elif defined(_CPU_X86_64_) || defined(_CPU_X86_) /* !__MIC__ */ # define jl_cpu_pause() _mm_pause() +# define jl_cpu_suspend() _mm_pause() # define jl_cpu_wake() ((void)0) # define JL_CPU_WAKE_NOOP 1 #elif defined(_CPU_AARCH64_) || (defined(_CPU_ARM_) && __ARM_ARCH >= 7) -# define jl_cpu_pause() __asm__ volatile ("wfe" ::: "memory") +# define jl_cpu_pause() __asm__ volatile ("isb" ::: "memory") +# define jl_cpu_suspend() __asm__ volatile ("wfe" ::: "memory") # define jl_cpu_wake() __asm__ volatile ("sev" ::: "memory") # define JL_CPU_WAKE_NOOP 0 #else # define jl_cpu_pause() ((void)0) +# define jl_cpu_suspend() ((void)0) # define jl_cpu_wake() ((void)0) # define JL_CPU_WAKE_NOOP 1 #endif JL_DLLEXPORT void (jl_cpu_pause)(void); +JL_DLLEXPORT void (jl_cpu_suspend)(void); JL_DLLEXPORT void (jl_cpu_wake)(void); #ifdef __clang_gcanalyzer__ @@ -344,23 +354,23 @@ STATIC_INLINE int8_t jl_gc_state_save_and_set(jl_ptls_t ptls, return jl_gc_state_set(ptls, state, jl_atomic_load_relaxed(&ptls->gc_state)); } #ifdef __clang_gcanalyzer__ -int8_t jl_gc_unsafe_enter(jl_ptls_t ptls); // Can be a safepoint -int8_t jl_gc_unsafe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT; -int8_t jl_gc_safe_enter(jl_ptls_t ptls) JL_NOTSAFEPOINT; -int8_t jl_gc_safe_leave(jl_ptls_t ptls, int8_t state); // Can be a safepoint +int8_t jl_gc_unsafe_enter(jl_ptls_t ptls) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_LEAVE; // this could be a safepoint, but we will assume it is not +void jl_gc_unsafe_leave(jl_ptls_t ptls, int8_t state) JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER; +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_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_safepoint)(void); 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_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 void jl_wakeup_thread(int16_t tid); diff --git a/src/llvm-alloc-helpers.cpp b/src/llvm-alloc-helpers.cpp index 55a93ea5179b5..d24c08b4b4930 100644 --- a/src/llvm-alloc-helpers.cpp +++ b/src/llvm-alloc-helpers.cpp @@ -2,9 +2,13 @@ #include "llvm-version.h" #include "llvm-alloc-helpers.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" +#include + +#define DEBUG_TYPE "escape-analysis" + using namespace llvm; using namespace jl_alloc; @@ -108,40 +112,58 @@ bool AllocUseInfo::addMemOp(Instruction *inst, unsigned opno, uint32_t offset, return true; } -JL_USED_FUNC void AllocUseInfo::dump() +JL_USED_FUNC void AllocUseInfo::dump(llvm::raw_ostream &OS) { - jl_safe_printf("escaped: %d\n", escaped); - jl_safe_printf("addrescaped: %d\n", addrescaped); - jl_safe_printf("returned: %d\n", returned); - jl_safe_printf("haserror: %d\n", haserror); - jl_safe_printf("hasload: %d\n", hasload); - jl_safe_printf("haspreserve: %d\n", haspreserve); - jl_safe_printf("hasunknownmem: %d\n", hasunknownmem); - jl_safe_printf("hastypeof: %d\n", hastypeof); - jl_safe_printf("refload: %d\n", refload); - jl_safe_printf("refstore: %d\n", refstore); - jl_safe_printf("Uses: %d\n", (unsigned)uses.size()); + OS << "AllocUseInfo:\n"; + OS << "escaped: " << escaped << '\n'; + OS << "addrescaped: " << addrescaped << '\n'; + OS << "returned: " << returned << '\n'; + OS << "haserror: " << haserror << '\n'; + OS << "hasload: " << hasload << '\n'; + OS << "haspreserve: " << haspreserve << '\n'; + OS << "hasunknownmem: " << hasunknownmem << '\n'; + OS << "hastypeof: " << hastypeof << '\n'; + OS << "refload: " << refload << '\n'; + OS << "refstore: " << refstore << '\n'; + OS << "Uses: " << uses.size() << '\n'; for (auto inst: uses) - llvm_dump(inst); + inst->print(OS); if (!preserves.empty()) { - jl_safe_printf("Preserves: %d\n", (unsigned)preserves.size()); - for (auto inst: preserves) { - llvm_dump(inst); - } + OS << "Preserves: " << preserves.size() << '\n'; + for (auto inst: preserves) + inst->print(OS); } - if (!memops.empty()) { - jl_safe_printf("Memops: %d\n", (unsigned)memops.size()); - for (auto &field: memops) { - jl_safe_printf(" Field %d @ %d\n", field.second.size, field.first); - jl_safe_printf(" Accesses:\n"); - for (auto memop: field.second.accesses) { - jl_safe_printf(" "); - llvm_dump(memop.inst); - } + OS << "MemOps: " << memops.size() << '\n'; + for (auto &field: memops) { + OS << " offset: " << field.first << '\n'; + OS << " size: " << field.second.size << '\n'; + OS << " hasobjref: " << field.second.hasobjref << '\n'; + OS << " hasload: " << field.second.hasload << '\n'; + OS << " hasaggr: " << field.second.hasaggr << '\n'; + OS << " accesses: " << field.second.accesses.size() << '\n'; + for (auto &memop: field.second.accesses) { + OS << " "; + memop.inst->print(OS); + OS << '\n'; + OS << " " << (memop.isaggr ? "aggr" : "scalar") << '\n'; + OS << " " << (memop.isobjref ? "objref" : "bits") << '\n'; + OS << " " << memop.offset << '\n'; + OS << " " << memop.size << '\n'; } } } +JL_USED_FUNC void AllocUseInfo::dump() +{ + dump(dbgs()); +} + +#ifndef __clang_gcanalyzer__ +#define REMARK(remark) if (options.ORE) options.ORE->emit(remark) +#else +#define REMARK(remark) +#endif + void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options) { required.use_info.reset(); if (I->use_empty()) @@ -159,9 +181,16 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg }; auto check_inst = [&] (Instruction *inst, Use *use) { + LLVM_DEBUG(dbgs() << "Checking: " << *inst << "\n"); if (isa(inst)) { required.use_info.hasload = true; - if (cur.offset == UINT32_MAX || !required.use_info.addMemOp(inst, 0, cur.offset, + if (cur.offset == UINT32_MAX) { + LLVM_DEBUG(dbgs() << "Load inst has unknown offset\n"); + auto elty = inst->getType(); + required.use_info.has_unknown_objref |= hasObjref(elty); + required.use_info.has_unknown_objrefaggr |= hasObjref(elty) && !isa(elty); + required.use_info.hasunknownmem = true; + } else if (!required.use_info.addMemOp(inst, 0, cur.offset, inst->getType(), false, required.DL)) required.use_info.hasunknownmem = true; @@ -179,13 +208,16 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg !isa(call->getArgOperand(2)) || !isa(call->getArgOperand(1)) || (cast(call->getArgOperand(2))->getLimitedValue() >= - UINT32_MAX - cur.offset)) + UINT32_MAX - cur.offset)) { + LLVM_DEBUG(dbgs() << "Memset inst has unknown offset\n"); required.use_info.hasunknownmem = true; + } return true; } if (id == Intrinsic::lifetime_start || id == Intrinsic::lifetime_end || isa(II)) return true; + LLVM_DEBUG(dbgs() << "Unknown intrinsic, marking addrescape\n"); required.use_info.addrescaped = true; return true; } @@ -213,23 +245,43 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg if (!call->isBundleOperand(opno) || call->getOperandBundleForOperand(opno).getTagName() != "jl_roots") { if (isa(call->getParent()->getTerminator())) { + LLVM_DEBUG(dbgs() << "Detected use of allocation in block terminating with unreachable, likely error function\n"); required.use_info.haserror = true; return true; } + LLVM_DEBUG(dbgs() << "Unknown call, marking escape\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "UnknownCall", + inst) + << "Unknown call, marking escape (" << ore::NV("Call", inst) << ")"; + }); required.use_info.escaped = true; return false; } + LLVM_DEBUG(dbgs() << "Call is in jl_roots bundle, marking haspreserve\n"); required.use_info.haspreserve = true; return true; } if (auto store = dyn_cast(inst)) { // Only store value count if (use->getOperandNo() != StoreInst::getPointerOperandIndex()) { + LLVM_DEBUG(dbgs() << "Object address is stored somewhere, marking escape\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "StoreObjAddr", + inst) + << "Object address is stored somewhere, marking escape (" << ore::NV("Store", inst) << ")"; + }); required.use_info.escaped = true; return false; } auto storev = store->getValueOperand(); - if (cur.offset == UINT32_MAX || !required.use_info.addMemOp(inst, use->getOperandNo(), + if (cur.offset == UINT32_MAX) { + LLVM_DEBUG(dbgs() << "Store inst has unknown offset\n"); + auto elty = storev->getType(); + required.use_info.has_unknown_objref |= hasObjref(elty); + required.use_info.has_unknown_objrefaggr |= hasObjref(elty) && !isa(elty); + required.use_info.hasunknownmem = true; + } else if (!required.use_info.addMemOp(inst, use->getOperandNo(), cur.offset, storev->getType(), true, required.DL)) required.use_info.hasunknownmem = true; @@ -238,6 +290,12 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg if (isa(inst) || isa(inst)) { // Only store value count if (use->getOperandNo() != isa(inst) ? AtomicCmpXchgInst::getPointerOperandIndex() : AtomicRMWInst::getPointerOperandIndex()) { + LLVM_DEBUG(dbgs() << "Object address is cmpxchg/rmw-ed somewhere, marking escape\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "StoreObjAddr", + inst) + << "Object address is cmpxchg/rmw-ed somewhere, marking escape (" << ore::NV("Store", inst) << ")"; + }); required.use_info.escaped = true; return false; } @@ -245,8 +303,10 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg auto storev = isa(inst) ? cast(inst)->getNewValOperand() : cast(inst)->getValOperand(); if (cur.offset == UINT32_MAX || !required.use_info.addMemOp(inst, use->getOperandNo(), cur.offset, storev->getType(), - true, required.DL)) + true, required.DL)) { + LLVM_DEBUG(dbgs() << "Atomic inst has unknown offset\n"); required.use_info.hasunknownmem = true; + } required.use_info.refload = true; return true; } @@ -260,10 +320,12 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg APInt apoffset(sizeof(void*) * 8, cur.offset, true); if (!gep->accumulateConstantOffset(required.DL, apoffset) || apoffset.isNegative()) { next_offset = UINT32_MAX; + LLVM_DEBUG(dbgs() << "GEP inst has unknown offset\n"); } else { next_offset = apoffset.getLimitedValue(); if (next_offset > UINT32_MAX) { + LLVM_DEBUG(dbgs() << "GEP inst exceeeds 32-bit offset\n"); next_offset = UINT32_MAX; } } @@ -273,9 +335,16 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg return true; } if (isa(inst)) { + LLVM_DEBUG(dbgs() << "Allocation is returned\n"); required.use_info.returned = true; return true; } + LLVM_DEBUG(dbgs() << "Unknown instruction, marking escape\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "UnknownInst", + inst) + << "Unknown instruction, marking escape (" << ore::NV("Inst", inst) << ")"; + }); required.use_info.escaped = true; return false; }; diff --git a/src/llvm-alloc-helpers.h b/src/llvm-alloc-helpers.h index 3f06baddfcff6..3bd80704a0888 100644 --- a/src/llvm-alloc-helpers.h +++ b/src/llvm-alloc-helpers.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -87,6 +88,11 @@ namespace jl_alloc { // The object is used in an error function bool haserror:1; + // The alloc has a Julia object reference not in an explicit field. + bool has_unknown_objref:1; + // The alloc has an aggregate Julia object reference not in an explicit field. + bool has_unknown_objrefaggr:1; + void reset() { escaped = false; @@ -99,10 +105,13 @@ namespace jl_alloc { hasunknownmem = false; returned = false; haserror = false; + has_unknown_objref = false; + has_unknown_objrefaggr = false; uses.clear(); preserves.clear(); memops.clear(); } + void dump(llvm::raw_ostream &OS); void dump(); bool addMemOp(llvm::Instruction *inst, unsigned opno, uint32_t offset, llvm::Type *elty, bool isstore, const llvm::DataLayout &DL); @@ -129,6 +138,7 @@ namespace jl_alloc { //will not be considered. Defaults to nullptr, which means all uses of the allocation //are considered const llvm::SmallPtrSetImpl *valid_set; + llvm::OptimizationRemarkEmitter *ORE = nullptr; EscapeAnalysisOptionalArgs() = default; @@ -136,10 +146,15 @@ namespace jl_alloc { this->valid_set = valid_set; return *this; } + + EscapeAnalysisOptionalArgs &with_optimization_remark_emitter(decltype(ORE) ORE) { + this->ORE = ORE; + return *this; + } }; void runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options=EscapeAnalysisOptionalArgs()); } -#endif \ No newline at end of file +#endif diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index 3f270cde8d96d..acb2d673d6760 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -1,6 +1,5 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -#define DEBUG_TYPE "alloc_opt" #undef DEBUG #include "llvm-version.h" @@ -10,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -20,13 +21,15 @@ #include #include #include +#include #include #include #include #include -#include "codegen_shared.h" +#include "passes.h" +#include "llvm-codegen-shared.h" #include "julia.h" #include "julia_internal.h" #include "llvm-pass-helpers.h" @@ -35,17 +38,28 @@ #include #include +#define DEBUG_TYPE "alloc-opt" #include "julia_assert.h" using namespace llvm; using namespace jl_alloc; +STATISTIC(RemovedAllocs, "Total number of heap allocations elided"); +STATISTIC(DeletedAllocs, "Total number of heap allocations fully deleted"); +STATISTIC(SplitAllocs, "Total number of allocations split into registers"); +STATISTIC(StackAllocs, "Total number of allocations moved to the stack"); +STATISTIC(RemovedTypeofs, "Total number of typeofs removed"); +STATISTIC(RemovedWriteBarriers, "Total number of write barriers removed"); +STATISTIC(RemovedGCPreserve, "Total number of GC preserve instructions removed"); + namespace { static void removeGCPreserve(CallInst *call, Instruction *val) { + ++RemovedGCPreserve; auto replace = Constant::getNullValue(val->getType()); call->replaceUsesOfWith(val, replace); + call->setAttributes(AttributeList()); for (auto &arg: call->args()) { if (!isa(arg.get())) { return; @@ -85,37 +99,28 @@ static void removeGCPreserve(CallInst *call, Instruction *val) * * Handle jl_box* */ -struct AllocOpt : public FunctionPass, public JuliaPassContext { - static char ID; - AllocOpt() - : FunctionPass(ID) - { - llvm::initializeDominatorTreeWrapperPassPass(*PassRegistry::getPassRegistry()); - } +#ifndef __clang_gcanalyzer__ +#define REMARK(remark) ORE.emit(remark) +#else +#define REMARK(remark) (void) 0; +#endif +struct AllocOpt : public JuliaPassContext { const DataLayout *DL; Function *lifetime_start; Function *lifetime_end; - Type *T_int64; - -private: - bool doInitialization(Module &m) override; - bool runOnFunction(Function &F) override; - void getAnalysisUsage(AnalysisUsage &AU) const override - { - FunctionPass::getAnalysisUsage(AU); - AU.addRequired(); - AU.addPreserved(); - AU.setPreservesCFG(); - } + bool doInitialization(Module &m); + bool runOnFunction(Function &F, function_ref GetDT); }; struct Optimizer { - Optimizer(Function &F, AllocOpt &pass) + Optimizer(Function &F, AllocOpt &pass, function_ref GetDT) : F(F), - pass(pass) + ORE(&F), + pass(pass), + GetDT(std::move(GetDT)) {} void initialize(); @@ -141,13 +146,15 @@ struct Optimizer { void optimizeTag(CallInst *orig_inst); Function &F; + OptimizationRemarkEmitter ORE; AllocOpt &pass; DominatorTree *_DT = nullptr; + function_ref GetDT; DominatorTree &getDomTree() { if (!_DT) - _DT = &pass.getAnalysis().getDomTree(); + _DT = &GetDT(); return *_DT; } struct Lifetime { @@ -216,25 +223,37 @@ void Optimizer::optimizeAll() size_t sz = item.second; checkInst(orig); if (use_info.escaped) { + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "Escaped", orig) + << "GC allocation escaped " << ore::NV("GC Allocation", orig); + }); if (use_info.hastypeof) optimizeTag(orig); continue; } if (use_info.haserror || use_info.returned) { + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "Escaped", orig) + << "GC allocation has error or was returned " << ore::NV("GC Allocation", orig); + }); if (use_info.hastypeof) optimizeTag(orig); continue; } if (!use_info.addrescaped && !use_info.hasload && (!use_info.haspreserve || !use_info.refstore)) { + REMARK([&]() { + return OptimizationRemark(DEBUG_TYPE, "Dead Allocation", orig) + << "GC allocation removed " << ore::NV("GC Allocation", orig); + }); // No one took the address, no one reads anything and there's no meaningful // preserve of fields (either no preserve/ccall or no object reference fields) // We can just delete all the uses. removeAlloc(orig); continue; } - bool has_ref = false; - bool has_refaggr = false; + bool has_ref = use_info.has_unknown_objref; + bool has_refaggr = use_info.has_unknown_objrefaggr; for (auto memop: use_info.memops) { auto &field = memop.second; if (field.hasobjref) { @@ -247,16 +266,28 @@ void Optimizer::optimizeAll() } } } - if (!use_info.hasunknownmem && !use_info.addrescaped && !has_refaggr) { - // No one actually care about the memory layout of this object, split it. - splitOnStack(orig); - continue; - } if (has_refaggr) { + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "Escaped", orig) + << "GC allocation has unusual object reference, unable to move to stack " << ore::NV("GC Allocation", orig); + }); if (use_info.hastypeof) optimizeTag(orig); continue; } + if (!use_info.hasunknownmem && !use_info.addrescaped) { + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Stack Split Allocation", orig) + << "GC allocation split on stack " << ore::NV("GC Allocation", orig); + }); + // No one actually care about the memory layout of this object, split it. + splitOnStack(orig); + continue; + } + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Stack Move Allocation", orig) + << "GC allocation moved to stack " << ore::NV("GC Allocation", orig); + }); // The object has no fields with mix reference access moveToStack(orig, sz, has_ref); } @@ -314,7 +345,10 @@ ssize_t Optimizer::getGCAllocSize(Instruction *I) if (call->getCalledOperand() != pass.alloc_obj_func) return -1; assert(call->arg_size() == 3); - size_t sz = (size_t)cast(call->getArgOperand(1))->getZExtValue(); + auto CI = dyn_cast(call->getArgOperand(1)); + if (!CI) + return -1; + size_t sz = (size_t)CI->getZExtValue(); if (sz < IntegerType::MAX_INT_BITS / 8 && sz < INT32_MAX) return sz; return -1; @@ -322,8 +356,15 @@ ssize_t Optimizer::getGCAllocSize(Instruction *I) void Optimizer::checkInst(Instruction *I) { + LLVM_DEBUG(dbgs() << "Running escape analysis on " << *I << "\n"); jl_alloc::EscapeAnalysisRequiredArgs required{use_info, check_stack, pass, *pass.DL}; - jl_alloc::runEscapeAnalysis(I, required); + jl_alloc::runEscapeAnalysis(I, required, jl_alloc::EscapeAnalysisOptionalArgs().with_optimization_remark_emitter(&ORE)); + REMARK([&](){ + std::string suse_info; + llvm::raw_string_ostream osuse_info(suse_info); + use_info.dump(osuse_info); + return OptimizationRemarkAnalysis(DEBUG_TYPE, "EscapeAnalysis", I) << "escape analysis for " << ore::NV("GC Allocation", I) << "\n" << ore::NV("UseInfo", osuse_info.str()); + }); } void Optimizer::insertLifetimeEnd(Value *ptr, Constant *sz, Instruction *insert) @@ -530,8 +571,8 @@ void Optimizer::replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, auto res = Intrinsic::matchIntrinsicSignature(newfType, TableRef, overloadTys); assert(res == Intrinsic::MatchIntrinsicTypes_Match); (void)res; - bool matchvararg = Intrinsic::matchIntrinsicVarArg(newfType->isVarArg(), TableRef); - assert(!matchvararg); + bool matchvararg = !Intrinsic::matchIntrinsicVarArg(newfType->isVarArg(), TableRef); + assert(matchvararg); (void)matchvararg; } auto newF = Intrinsic::getDeclaration(call->getModule(), ID, overloadTys); @@ -551,6 +592,8 @@ void Optimizer::replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, // all the original safepoints. void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) { + ++RemovedAllocs; + ++StackAllocs; auto tag = orig_inst->getArgOperand(2); removed.push_back(orig_inst); // The allocation does not escape or get used in a phi node so none of the derived @@ -566,17 +609,18 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) AllocaInst *buff; Instruction *ptr; if (sz == 0) { - buff = prolog_builder.CreateAlloca(pass.T_int8, ConstantInt::get(pass.T_int64, 0)); - ptr = buff; + ptr = buff = prolog_builder.CreateAlloca(Type::getInt8Ty(prolog_builder.getContext()), ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), 0)); } else if (has_ref) { // Allocate with the correct type so that the GC frame lowering pass will // treat this as a non-mem2reg'd alloca // The ccall root and GC preserve handling below makes sure that // the alloca isn't optimized out. - buff = prolog_builder.CreateAlloca(pass.T_prjlvalue); + const DataLayout &DL = F.getParent()->getDataLayout(); + auto asize = ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), sz / DL.getTypeAllocSize(pass.T_prjlvalue)); + buff = prolog_builder.CreateAlloca(pass.T_prjlvalue, asize); buff->setAlignment(Align(align)); - ptr = cast(prolog_builder.CreateBitCast(buff, pass.T_pint8)); + ptr = cast(prolog_builder.CreateBitCast(buff, Type::getInt8PtrTy(prolog_builder.getContext()))); } else { Type *buffty; @@ -586,10 +630,12 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) buffty = ArrayType::get(Type::getInt8Ty(pass.getLLVMContext()), sz); buff = prolog_builder.CreateAlloca(buffty); buff->setAlignment(Align(align)); - ptr = cast(prolog_builder.CreateBitCast(buff, pass.T_pint8)); + ptr = cast(prolog_builder.CreateBitCast(buff, Type::getInt8PtrTy(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); } - insertLifetime(ptr, ConstantInt::get(pass.T_int64, sz), orig_inst); - auto new_inst = cast(prolog_builder.CreateBitCast(ptr, pass.T_pjlvalue)); + insertLifetime(ptr, ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), sz), orig_inst); + Instruction *new_inst = cast(prolog_builder.CreateBitCast(ptr, JuliaType::get_pjlvalue_ty(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); + if (orig_inst->getModule()->getDataLayout().getAllocaAddrSpace() != 0) + new_inst = cast(prolog_builder.CreateAddrSpaceCast(new_inst, JuliaType::get_pjlvalue_ty(prolog_builder.getContext(), orig_inst->getType()->getPointerAddressSpace()))); new_inst->takeName(orig_inst); auto simple_replace = [&] (Instruction *orig_i, Instruction *new_i) { @@ -608,8 +654,10 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) } return false; }; - if (simple_replace(orig_inst, new_inst)) + if (simple_replace(orig_inst, new_inst)) { + LLVM_DEBUG(dbgs() << "Simple replace of allocation was successful in stack move\n"); return; + } assert(replace_stack.empty()); ReplaceUses::Frame cur{orig_inst, new_inst}; auto finish_cur = [&] () { @@ -640,6 +688,7 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) return; } if (pass.typeof_func == callee) { + ++RemovedTypeofs; call->replaceAllUsesWith(tag); call->eraseFromParent(); return; @@ -655,6 +704,7 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) return; } if (pass.write_barrier_func == callee) { + ++RemovedWriteBarriers; call->eraseFromParent(); return; } @@ -669,8 +719,7 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) user->replaceUsesOfWith(orig_i, replace); } else if (isa(user) || isa(user)) { - auto cast_t = PointerType::get(cast(user->getType())->getElementType(), - 0); + auto cast_t = PointerType::getWithSamePointeeType(cast(user->getType()), new_i->getType()->getPointerAddressSpace()); auto replace_i = new_i; Type *new_t = new_i->getType(); if (cast_t != new_t) { @@ -711,6 +760,8 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) // all the original safepoints. void Optimizer::removeAlloc(CallInst *orig_inst) { + ++RemovedAllocs; + ++DeletedAllocs; auto tag = orig_inst->getArgOperand(2); removed.push_back(orig_inst); auto simple_remove = [&] (Instruction *orig_i) { @@ -721,8 +772,10 @@ void Optimizer::removeAlloc(CallInst *orig_inst) } return false; }; - if (simple_remove(orig_inst)) + if (simple_remove(orig_inst)) { + LLVM_DEBUG(dbgs() << "Simple remove of allocation was successful in removeAlloc\n"); return; + } assert(replace_stack.empty()); ReplaceUses::Frame cur{orig_inst, nullptr}; auto finish_cur = [&] () { @@ -755,11 +808,13 @@ void Optimizer::removeAlloc(CallInst *orig_inst) return; } if (pass.typeof_func == callee) { + ++RemovedTypeofs; call->replaceAllUsesWith(tag); call->eraseFromParent(); return; } if (pass.write_barrier_func == callee) { + ++RemovedWriteBarriers; call->eraseFromParent(); return; } @@ -805,6 +860,11 @@ void Optimizer::optimizeTag(CallInst *orig_inst) if (auto call = dyn_cast(user)) { auto callee = call->getCalledOperand(); if (pass.typeof_func == callee) { + ++RemovedTypeofs; + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "typeof", call) + << "removed typeof call for GC allocation " << ore::NV("Alloc", orig_inst); + }); call->replaceAllUsesWith(tag); // Push to the removed instructions to trigger `finalize` to // return the correct result. @@ -820,6 +880,8 @@ void Optimizer::optimizeTag(CallInst *orig_inst) void Optimizer::splitOnStack(CallInst *orig_inst) { auto tag = orig_inst->getArgOperand(2); + ++RemovedAllocs; + ++SplitAllocs; removed.push_back(orig_inst); IRBuilder<> prolog_builder(&F.getEntryBlock().front()); struct SplitSlot { @@ -850,8 +912,8 @@ void Optimizer::splitOnStack(CallInst *orig_inst) allocty = ArrayType::get(Type::getInt8Ty(pass.getLLVMContext()), field.size); } slot.slot = prolog_builder.CreateAlloca(allocty); - insertLifetime(prolog_builder.CreateBitCast(slot.slot, pass.T_pint8), - ConstantInt::get(pass.T_int64, field.size), orig_inst); + insertLifetime(prolog_builder.CreateBitCast(slot.slot, Type::getInt8PtrTy(prolog_builder.getContext())), + ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), field.size), orig_inst); slots.push_back(std::move(slot)); } const auto nslots = slots.size(); @@ -879,8 +941,10 @@ void Optimizer::splitOnStack(CallInst *orig_inst) } return false; }; - if (simple_replace(orig_inst)) + if (simple_replace(orig_inst)) { + LLVM_DEBUG(dbgs() << "Simple replace of allocation was successful in stack split\n"); return; + } assert(replace_stack.empty()); ReplaceUses::Frame cur{orig_inst, uint32_t(0)}; auto finish_cur = [&] () { @@ -907,8 +971,8 @@ void Optimizer::splitOnStack(CallInst *orig_inst) } } else { - addr = builder.CreateBitCast(slot.slot, pass.T_pint8); - addr = builder.CreateConstInBoundsGEP1_32(pass.T_int8, addr, offset); + addr = builder.CreateBitCast(slot.slot, Type::getInt8PtrTy(builder.getContext())); + addr = builder.CreateConstInBoundsGEP1_32(Type::getInt8Ty(builder.getContext()), addr, offset); addr = builder.CreateBitCast(addr, elty->getPointerTo()); } return addr; @@ -958,14 +1022,14 @@ void Optimizer::splitOnStack(CallInst *orig_inst) StoreInst *newstore; if (slot.isref) { assert(slot.offset == offset); + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(builder.getContext()); if (!isa(store_ty)) { - store_val = builder.CreateBitCast(store_val, pass.T_size); - store_val = builder.CreateIntToPtr(store_val, pass.T_pjlvalue); - store_ty = pass.T_pjlvalue; + store_val = builder.CreateBitCast(store_val, pass.DL->getIntPtrType(builder.getContext(), T_pjlvalue->getAddressSpace())); + store_val = builder.CreateIntToPtr(store_val, T_pjlvalue); + store_ty = T_pjlvalue; } else { - store_ty = cast(pass.T_pjlvalue)->getElementType() - ->getPointerTo(cast(store_ty)->getAddressSpace()); + store_ty = PointerType::getWithSamePointeeType(T_pjlvalue, cast(store_ty)->getAddressSpace()); store_val = builder.CreateBitCast(store_val, store_ty); } if (cast(store_ty)->getAddressSpace() != AddressSpace::Tracked) @@ -1023,17 +1087,17 @@ void Optimizer::splitOnStack(CallInst *orig_inst) else { uint64_t intval; memset(&intval, val, 8); - Constant *val = ConstantInt::get(pass.T_size, intval); - val = ConstantExpr::getIntToPtr(val, pass.T_pjlvalue); + Constant *val = ConstantInt::get(pass.DL->getIntPtrType(builder.getContext(), pass.T_prjlvalue->getAddressSpace()), intval); + val = ConstantExpr::getIntToPtr(val, JuliaType::get_pjlvalue_ty(builder.getContext())); ptr = ConstantExpr::getAddrSpaceCast(val, pass.T_prjlvalue); } StoreInst *store = builder.CreateAlignedStore(ptr, slot.slot, Align(sizeof(void*))); store->setOrdering(AtomicOrdering::NotAtomic); continue; } - auto ptr8 = builder.CreateBitCast(slot.slot, pass.T_pint8); + auto ptr8 = builder.CreateBitCast(slot.slot, Type::getInt8PtrTy(builder.getContext())); if (offset > slot.offset) - ptr8 = builder.CreateConstInBoundsGEP1_32(pass.T_int8, ptr8, + ptr8 = builder.CreateConstInBoundsGEP1_32(Type::getInt8Ty(builder.getContext()), ptr8, offset - slot.offset); auto sub_size = std::min(slot.offset + slot.size, offset + size) - std::max(offset, slot.offset); @@ -1048,11 +1112,13 @@ void Optimizer::splitOnStack(CallInst *orig_inst) } } if (pass.typeof_func == callee) { + ++RemovedTypeofs; call->replaceAllUsesWith(tag); call->eraseFromParent(); return; } if (pass.write_barrier_func == callee) { + ++RemovedWriteBarriers; call->eraseFromParent(); return; } @@ -1072,10 +1138,12 @@ void Optimizer::splitOnStack(CallInst *orig_inst) ref->setOrdering(AtomicOrdering::NotAtomic); operands.push_back(ref); } +#ifndef __clang_analyzer__ + // FIXME: SA finds "Called C++ object pointer is null" inside the LLVM code. auto new_call = builder.CreateCall(pass.gc_preserve_begin_func, operands); new_call->takeName(call); - new_call->setAttributes(call->getAttributes()); call->replaceAllUsesWith(new_call); +#endif call->eraseFromParent(); return; } @@ -1151,26 +1219,51 @@ bool AllocOpt::doInitialization(Module &M) DL = &M.getDataLayout(); - T_int64 = Type::getInt64Ty(getLLVMContext()); - - lifetime_start = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_start, { T_pint8 }); - lifetime_end = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_end, { T_pint8 }); + lifetime_start = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_start, { Type::getInt8PtrTy(M.getContext(), DL->getAllocaAddrSpace()) }); + lifetime_end = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_end, { Type::getInt8PtrTy(M.getContext(), DL->getAllocaAddrSpace()) }); return true; } -bool AllocOpt::runOnFunction(Function &F) +bool AllocOpt::runOnFunction(Function &F, function_ref GetDT) { - if (!alloc_obj_func) + if (!alloc_obj_func) { + LLVM_DEBUG(dbgs() << "AllocOpt: no alloc_obj function found, skipping pass\n"); return false; - Optimizer optimizer(F, *this); + } + Optimizer optimizer(F, *this, std::move(GetDT)); optimizer.initialize(); optimizer.optimizeAll(); - return optimizer.finalize(); + bool modified = optimizer.finalize(); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + return modified; } -char AllocOpt::ID = 0; -static RegisterPass X("AllocOpt", "Promote heap allocation to stack", +struct AllocOptLegacy : public FunctionPass { + static char ID; + AllocOpt opt; + AllocOptLegacy() : FunctionPass(ID) { + llvm::initializeDominatorTreeWrapperPassPass(*PassRegistry::getPassRegistry()); + } + bool doInitialization(Module &m) override { + return opt.doInitialization(m); + } + bool runOnFunction(Function &F) override { + return opt.runOnFunction(F, [this]() -> DominatorTree & {return getAnalysis().getDomTree();}); + } + void getAnalysisUsage(AnalysisUsage &AU) const override + { + FunctionPass::getAnalysisUsage(AU); + AU.addRequired(); + AU.addPreserved(); + AU.setPreservesCFG(); + } +}; + +char AllocOptLegacy::ID = 0; +static RegisterPass X("AllocOpt", "Promote heap allocation to stack", false /* Only looks at CFG */, false /* Analysis Pass */); @@ -1178,10 +1271,26 @@ static RegisterPass X("AllocOpt", "Promote heap allocation to stack", Pass *createAllocOptPass() { - return new AllocOpt(); + return new AllocOptLegacy(); +} + +PreservedAnalyses AllocOptPass::run(Function &F, FunctionAnalysisManager &AM) { + AllocOpt opt; + bool modified = opt.doInitialization(*F.getParent()); + if (opt.runOnFunction(F, [&]()->DominatorTree &{ return AM.getResult(F); })) { + modified = true; + } + if (modified) { + auto preserved = PreservedAnalyses::allInSet(); + preserved.preserve(); + return preserved; + } else { + return PreservedAnalyses::all(); + } } -extern "C" JL_DLLEXPORT void LLVMExtraAddAllocOptPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddAllocOptPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createAllocOptPass()); } diff --git a/src/llvm-codegen-shared.h b/src/llvm-codegen-shared.h new file mode 100644 index 0000000000000..0ab140b42b8b7 --- /dev/null +++ b/src/llvm-codegen-shared.h @@ -0,0 +1,539 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include +#include +#include +#include +#include +#include +#include +#include "julia.h" + +#define STR(csym) #csym +#define XSTR(csym) STR(csym) + +enum AddressSpace { + Generic = 0, + Tracked = 10, + Derived = 11, + CalleeRooted = 12, + Loaded = 13, + FirstSpecial = Tracked, + LastSpecial = Loaded, +}; + +namespace JuliaType { + static inline llvm::StructType* get_jlvalue_ty(llvm::LLVMContext &C) { + return llvm::StructType::get(C); + } + + static inline llvm::PointerType* get_pjlvalue_ty(llvm::LLVMContext &C, unsigned addressSpace=0) { + return llvm::PointerType::get(get_jlvalue_ty(C), addressSpace); + } + + static inline llvm::PointerType* get_prjlvalue_ty(llvm::LLVMContext &C) { + return llvm::PointerType::get(get_jlvalue_ty(C), AddressSpace::Tracked); + } + + static inline llvm::PointerType* get_ppjlvalue_ty(llvm::LLVMContext &C) { + return llvm::PointerType::get(get_pjlvalue_ty(C), 0); + } + + static inline llvm::PointerType* get_pprjlvalue_ty(llvm::LLVMContext &C) { + return llvm::PointerType::get(get_prjlvalue_ty(C), 0); + } + + static inline auto get_jlfunc_ty(llvm::LLVMContext &C) { + auto T_prjlvalue = get_prjlvalue_ty(C); + auto T_pprjlvalue = llvm::PointerType::get(T_prjlvalue, 0); + return llvm::FunctionType::get(T_prjlvalue, { + T_prjlvalue, // function + T_pprjlvalue, // args[] + llvm::Type::getInt32Ty(C)}, // nargs + false); + } + + static inline auto get_jlfunc2_ty(llvm::LLVMContext &C) { + auto T_prjlvalue = get_prjlvalue_ty(C); + auto T_pprjlvalue = llvm::PointerType::get(T_prjlvalue, 0); + return llvm::FunctionType::get(T_prjlvalue, { + T_prjlvalue, // function + T_pprjlvalue, // args[] + llvm::Type::getInt32Ty(C), + T_prjlvalue, // linfo + }, // nargs + false); + } + + static inline auto get_jlfuncparams_ty(llvm::LLVMContext &C) { + auto T_prjlvalue = get_prjlvalue_ty(C); + auto T_pprjlvalue = llvm::PointerType::get(T_prjlvalue, 0); + return llvm::FunctionType::get(T_prjlvalue, { + T_prjlvalue, // function + T_pprjlvalue, // args[] + llvm::Type::getInt32Ty(C), + T_pprjlvalue, // linfo->sparam_vals + }, // nargs + false); + } + + static inline auto get_voidfunc_ty(llvm::LLVMContext &C) { + return llvm::FunctionType::get(llvm::Type::getVoidTy(C), /*isVarArg*/false); + } + + static inline auto get_pvoidfunc_ty(llvm::LLVMContext &C) { + return get_voidfunc_ty(C)->getPointerTo(); + } +} + +// return how many Tracked pointers are in T (count > 0), +// and if there is anything else in T (all == false) +struct CountTrackedPointers { + unsigned count = 0; + bool all = true; + bool derived = false; + CountTrackedPointers(llvm::Type *T); +}; + +unsigned TrackWithShadow(llvm::Value *Src, llvm::Type *T, bool isptr, llvm::Value *Dst, llvm::Type *DTy, llvm::IRBuilder<> &irbuilder); +std::vector ExtractTrackedValues(llvm::Value *Src, llvm::Type *STy, bool isptr, llvm::IRBuilder<> &irbuilder, llvm::ArrayRef perm_offsets={}); + +static inline void llvm_dump(llvm::Value *v) +{ + v->print(llvm::dbgs(), true); + llvm::dbgs() << "\n"; +} + +static inline void llvm_dump(llvm::Type *v) +{ + v->print(llvm::dbgs(), true); + llvm::dbgs() << "\n"; +} + +static inline void llvm_dump(llvm::Function *f) +{ + f->print(llvm::dbgs(), nullptr, false, true); +} + +static inline void llvm_dump(llvm::Module *m) +{ + m->print(llvm::dbgs(), nullptr); +} + +static inline void llvm_dump(llvm::Metadata *m) +{ + m->print(llvm::dbgs()); + llvm::dbgs() << "\n"; +} + +static inline void llvm_dump(llvm::DebugLoc *dbg) +{ + dbg->print(llvm::dbgs()); + llvm::dbgs() << "\n"; +} + +static inline std::pair tbaa_make_child_with_context(llvm::LLVMContext &ctxt, const char *name, llvm::MDNode *parent=nullptr, bool isConstant=false) +{ + llvm::MDBuilder mbuilder(ctxt); + llvm::MDNode *jtbaa = mbuilder.createTBAARoot("jtbaa"); + llvm::MDNode *tbaa_root = mbuilder.createTBAAScalarTypeNode("jtbaa", jtbaa); + llvm::MDNode *scalar = mbuilder.createTBAAScalarTypeNode(name, parent ? parent : tbaa_root); + llvm::MDNode *n = mbuilder.createTBAAStructTagNode(scalar, scalar, 0, isConstant); + return std::make_pair(n, scalar); +} + +static inline llvm::MDNode *get_tbaa_const(llvm::LLVMContext &ctxt) { + return tbaa_make_child_with_context(ctxt, "jtbaa_const", nullptr, true).first; +} + +static inline llvm::Instruction *tbaa_decorate(llvm::MDNode *md, llvm::Instruction *inst) +{ + 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)); + return inst; +} + +// bitcast a value, but preserve its address space when dealing with pointer types +static inline llvm::Value *emit_bitcast_with_builder(llvm::IRBuilder<> &builder, llvm::Value *v, llvm::Type *jl_value) +{ + using namespace llvm; + if (isa(jl_value) && + v->getType()->getPointerAddressSpace() != jl_value->getPointerAddressSpace()) { + // Cast to the proper address space + Type *jl_value_addr = PointerType::getWithSamePointeeType(cast(jl_value), v->getType()->getPointerAddressSpace()); + return builder.CreateBitCast(v, jl_value_addr); + } + else { + return builder.CreateBitCast(v, jl_value); + } +} + +// Get PTLS through current task. +static inline llvm::Value *get_current_task_from_pgcstack(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *pgcstack) +{ + using namespace llvm; + auto T_ppjlvalue = JuliaType::get_ppjlvalue_ty(builder.getContext()); + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(builder.getContext()); + const int pgcstack_offset = offsetof(jl_task_t, gcstack); + return builder.CreateInBoundsGEP( + T_pjlvalue, emit_bitcast_with_builder(builder, pgcstack, T_ppjlvalue), + ConstantInt::get(T_size, -(pgcstack_offset / sizeof(void *))), + "current_task"); +} + +// Get PTLS through current task. +static inline llvm::Value *get_current_ptls_from_task(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *current_task, llvm::MDNode *tbaa) +{ + using namespace llvm; + auto T_ppjlvalue = JuliaType::get_ppjlvalue_ty(builder.getContext()); + 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, + ConstantInt::get(T_size, ptls_offset / sizeof(void *)), + "ptls_field"); + LoadInst *ptls_load = builder.CreateAlignedLoad(T_pjlvalue, + emit_bitcast_with_builder(builder, pptls, T_ppjlvalue), Align(sizeof(void *)), "ptls_load"); + // Note: Corresponding store (`t->ptls = ptls`) happens in `ctx_switch` of tasks.c. + tbaa_decorate(tbaa, ptls_load); + return builder.CreateBitCast(ptls_load, T_ppjlvalue, "ptls"); +} + +// Get signal page through current task. +static inline llvm::Value *get_current_signal_page_from_ptls(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, llvm::MDNode *tbaa) +{ + using namespace llvm; + // return builder.CreateCall(prepare_call(reuse_signal_page_func)); + auto T_psize = T_size->getPointerTo(); + auto T_ppsize = T_psize->getPointerTo(); + int nthfield = offsetof(jl_tls_states_t, safepoint) / sizeof(void *); + ptls = emit_bitcast_with_builder(builder, ptls, T_ppsize); + llvm::Value *psafepoint = builder.CreateInBoundsGEP( + T_psize, ptls, ConstantInt::get(T_size, nthfield)); + LoadInst *ptls_load = builder.CreateAlignedLoad( + T_psize, psafepoint, Align(sizeof(void *)), "safepoint"); + tbaa_decorate(tbaa, ptls_load); + return ptls_load; +} + +static inline void emit_signal_fence(llvm::IRBuilder<> &builder) +{ + using namespace llvm; + builder.CreateFence(AtomicOrdering::SequentiallyConsistent, SyncScope::SingleThread); +} + +static inline void emit_gc_safepoint(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, llvm::MDNode *tbaa, bool final = false) +{ + using namespace llvm; + llvm::Value *signal_page = get_current_signal_page_from_ptls(builder, T_size, ptls, tbaa); + emit_signal_fence(builder); + Module *M = builder.GetInsertBlock()->getModule(); + LLVMContext &C = builder.getContext(); + // inline jlsafepoint_func->realize(M) + if (final) { + builder.CreateLoad(T_size, signal_page, true); + } + else { + Function *F = M->getFunction("julia.safepoint"); + if (!F) { + 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); + F->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); + } + builder.CreateCall(F, {signal_page}); + } + emit_signal_fence(builder); +} + +static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, llvm::Value *state, llvm::Value *old_state, bool final) +{ + using namespace llvm; + Type *T_int8 = state->getType(); + llvm::Value *ptls_i8 = emit_bitcast_with_builder(builder, ptls, builder.getInt8PtrTy()); + Constant *offset = ConstantInt::getSigned(builder.getInt32Ty(), offsetof(jl_tls_states_t, gc_state)); + Value *gc_state = builder.CreateInBoundsGEP(T_int8, ptls_i8, ArrayRef(offset), "gc_state"); + if (old_state == nullptr) { + old_state = builder.CreateLoad(T_int8, gc_state); + cast(old_state)->setOrdering(AtomicOrdering::Monotonic); + } + builder.CreateAlignedStore(state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); + if (auto *C = dyn_cast(old_state)) + if (C->isZero()) + return old_state; + if (auto *C = dyn_cast(state)) + if (!C->isZero()) + return old_state; + 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)), + passBB, exitBB); + builder.SetInsertPoint(passBB); + MDNode *tbaa = get_tbaa_const(builder.getContext()); + emit_gc_safepoint(builder, T_size, ptls, tbaa, final); + builder.CreateBr(exitBB); + builder.SetInsertPoint(exitBB); + return old_state; +} + +static inline llvm::Value *emit_gc_unsafe_enter(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, bool final) +{ + using namespace llvm; + Value *state = builder.getInt8(0); + return emit_gc_state_set(builder, T_size, ptls, state, nullptr, final); +} + +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); + return emit_gc_state_set(builder, T_size, ptls, state, old_state, final); +} + +static inline llvm::Value *emit_gc_safe_enter(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, bool final) +{ + using namespace llvm; + Value *state = builder.getInt8(JL_GC_STATE_SAFE); + return emit_gc_state_set(builder, T_size, ptls, state, nullptr, final); +} + +static inline llvm::Value *emit_gc_safe_leave(llvm::IRBuilder<> &builder, llvm::Type *T_size, llvm::Value *ptls, llvm::Value *state, bool final) +{ + using namespace llvm; + Value *old_state = builder.getInt8(JL_GC_STATE_SAFE); + return emit_gc_state_set(builder, T_size, ptls, state, old_state, final); +} + +// Compatibility shims for LLVM attribute APIs that were renamed in LLVM 14. +// +// Once we no longer support LLVM < 14, these can be mechanically removed by +// translating foo(Bar, …) into Bar->foo(…) resp. Bar.foo(…). +namespace { +using namespace llvm; + +inline void addFnAttr(CallInst *Target, Attribute::AttrKind Attr) +{ + Target->addFnAttr(Attr); +} + +template +inline void addRetAttr(T *Target, A Attr) +{ + Target->addRetAttr(Attr); +} + +inline void addAttributeAtIndex(Function *F, unsigned Index, Attribute Attr) +{ + F->addAttributeAtIndex(Index, Attr); +} + +inline AttributeSet getFnAttrs(const AttributeList &Attrs) +{ + return Attrs.getFnAttrs(); +} + +inline AttributeSet getRetAttrs(const AttributeList &Attrs) +{ + return Attrs.getRetAttrs(); +} + +inline bool hasFnAttr(const AttributeList &L, Attribute::AttrKind Kind) +{ + return L.hasFnAttr(Kind); +} + +inline AttributeList addAttributeAtIndex(const AttributeList &L, LLVMContext &C, + unsigned Index, Attribute::AttrKind Kind) +{ + return L.addAttributeAtIndex(C, Index, Kind); +} + +inline AttributeList addAttributeAtIndex(const AttributeList &L, LLVMContext &C, + unsigned Index, Attribute Attr) +{ + return L.addAttributeAtIndex(C, Index, Attr); +} + +inline AttributeList addAttributesAtIndex(const AttributeList &L, LLVMContext &C, + unsigned Index, const AttrBuilder &Builder) +{ + return L.addAttributesAtIndex(C, Index, Builder); +} + +inline AttributeList addFnAttribute(const AttributeList &L, LLVMContext &C, + Attribute::AttrKind Kind) +{ + return L.addFnAttribute(C, Kind); +} + +inline AttributeList addRetAttribute(const AttributeList &L, LLVMContext &C, + Attribute::AttrKind Kind) +{ + return L.addRetAttribute(C, Kind); +} + +inline bool hasAttributesAtIndex(const AttributeList &L, unsigned Index) +{ + return L.hasAttributesAtIndex(Index); +} + +inline Attribute getAttributeAtIndex(const AttributeList &L, unsigned Index, Attribute::AttrKind Kind) +{ + return L.getAttributeAtIndex(Index, Kind); +} + +// Iterate through uses of a particular type. +// Recursively scan through `ConstantExpr` and `ConstantAggregate` use. +template +struct ConstantUses { + template + struct Info { + llvm::Use *use; + T *val; + // If `samebits == true`, the offset the original value appears in the constant. + size_t offset; + // This specify whether the original value appears in the current value in exactly + // the same bit pattern (with possibly an offset determined by `offset`). + bool samebits; + Info(llvm::Use *use, T *val, size_t offset, bool samebits) : + use(use), + val(val), + offset(offset), + samebits(samebits) + { + } + Info(llvm::Use *use, size_t offset, bool samebits) : + use(use), + val(cast(use->getUser())), + offset(offset), + samebits(samebits) + { + } + }; + using UseInfo = Info; + struct Frame : Info { + template + Frame(Args &&... args) : + Info(std::forward(args)...), + cur(this->val->use_empty() ? nullptr : &*this->val->use_begin()), + _next(cur ? cur->getNext() : nullptr) + { + } + private: + void next() + { + cur = _next; + if (!cur) + return; + _next = cur->getNext(); + } + llvm::Use *cur; + llvm::Use *_next; + friend struct ConstantUses; + }; + ConstantUses(llvm::Constant *c, llvm::Module &M) + : stack{Frame(nullptr, c, 0u, true)}, + M(M) + { + forward(); + } + UseInfo get_info() const + { + auto &top = stack.back(); + return UseInfo(top.cur, top.offset, top.samebits); + } + const auto &get_stack() const + { + return stack; + } + void next() + { + stack.back().next(); + forward(); + } + bool done() + { + return stack.empty(); + } +private: + void forward(); + llvm::SmallVector stack; + llvm::Module &M; +}; + +template +void ConstantUses::forward() +{ + assert(!stack.empty()); + auto frame = &stack.back(); + const auto &DL = M.getDataLayout(); + auto pop = [&] { + stack.pop_back(); + if (stack.empty()) { + return false; + } + frame = &stack.back(); + return true; + }; + auto push = [&] (llvm::Use *use, llvm::Constant *c, size_t offset, bool samebits) { + stack.emplace_back(use, c, offset, samebits); + frame = &stack.back(); + }; + auto handle_constaggr = [&] (llvm::Use *use, llvm::ConstantAggregate *aggr) { + if (!frame->samebits) { + push(use, aggr, 0, false); + return; + } + if (auto strct = dyn_cast(aggr)) { + auto layout = DL.getStructLayout(strct->getType()); + push(use, strct, frame->offset + layout->getElementOffset(use->getOperandNo()), true); + } + else if (auto ary = dyn_cast(aggr)) { + auto elty = ary->getType()->getElementType(); + push(use, ary, frame->offset + DL.getTypeAllocSize(elty) * use->getOperandNo(), true); + } + else if (auto vec = dyn_cast(aggr)) { + auto elty = vec->getType()->getElementType(); + push(use, vec, frame->offset + DL.getTypeAllocSize(elty) * use->getOperandNo(), true); + } + else { + abort(); + } + }; + auto handle_constexpr = [&] (llvm::Use *use, llvm::ConstantExpr *expr) { + if (!frame->samebits) { + push(use, expr, 0, false); + return; + } + auto opcode = expr->getOpcode(); + if (opcode == llvm::Instruction::PtrToInt || opcode == llvm::Instruction::IntToPtr || + opcode == llvm::Instruction::AddrSpaceCast || opcode == llvm::Instruction::BitCast) { + push(use, expr, frame->offset, true); + } + else { + push(use, expr, 0, false); + } + }; + while (true) { + auto use = frame->cur; + if (!use) { + if (!pop()) + return; + continue; + } + auto user = use->getUser(); + if (isa(user)) + return; + frame->next(); + if (auto aggr = dyn_cast(user)) { + handle_constaggr(use, aggr); + } + else if (auto expr = dyn_cast(user)) { + handle_constexpr(use, expr); + } + } +} +} diff --git a/src/llvm-cpufeatures.cpp b/src/llvm-cpufeatures.cpp index 8accd399371ae..45637a4c567f6 100644 --- a/src/llvm-cpufeatures.cpp +++ b/src/llvm-cpufeatures.cpp @@ -14,38 +14,42 @@ // instead of using the global target machine? #include "llvm-version.h" +#include "passes.h" +#include #include #include #include #include #include +#include #include #include -#include "julia.h" +#include "jitlayers.h" #define DEBUG_TYPE "cpufeatures" using namespace llvm; -extern TargetMachine *jl_TargetMachine; +STATISTIC(LoweredWithFMA, "Number of have_fma's that were lowered to true"); +STATISTIC(LoweredWithoutFMA, "Number of have_fma's that were lowered to false"); -// whether this platform unconditionally (i.e. without needing multiversioning) supports FMA -Optional always_have_fma(Function &intr) { - auto intr_name = intr.getName(); - auto typ = intr_name.substr(strlen("julia.cpu.have_fma.")); +extern JuliaOJIT *jl_ExecutionEngine; -#if defined(_CPU_AARCH64_) - return typ == "f32" || typ == "f64"; -#else - (void)typ; - return {}; -#endif +// whether this platform unconditionally (i.e. without needing multiversioning) supports FMA +Optional always_have_fma(Function &intr, const Triple &TT) JL_NOTSAFEPOINT { + if (TT.isAArch64()) { + auto intr_name = intr.getName(); + auto typ = intr_name.substr(strlen("julia.cpu.have_fma.")); + return typ == "f32" || typ == "f64"; + } else { + return {}; + } } -bool have_fma(Function &intr, Function &caller) { - auto unconditional = always_have_fma(intr); +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(); @@ -54,35 +58,38 @@ bool have_fma(Function &intr, Function &caller) { Attribute FSAttr = caller.getFnAttribute("target-features"); StringRef FS = - FSAttr.isValid() ? FSAttr.getValueAsString() : jl_TargetMachine->getTargetFeatureString(); + FSAttr.isValid() ? FSAttr.getValueAsString() : jl_ExecutionEngine->getTargetFeatureString(); SmallVector Features; FS.split(Features, ','); for (StringRef Feature : Features) -#if defined _CPU_ARM_ + if (TT.isARM()) { if (Feature == "+vfp4") return typ == "f32" || typ == "f64"; else if (Feature == "+vfp4sp") return typ == "f32"; -#else + } else { if (Feature == "+fma" || Feature == "+fma4") return typ == "f32" || typ == "f64"; -#endif + } return false; } -void lowerHaveFMA(Function &intr, Function &caller, CallInst *I) { - if (have_fma(intr, caller)) +void lowerHaveFMA(Function &intr, Function &caller, const Triple &TT, CallInst *I) JL_NOTSAFEPOINT { + if (have_fma(intr, caller, TT)) { + ++LoweredWithFMA; I->replaceAllUsesWith(ConstantInt::get(I->getType(), 1)); - else + } else { + ++LoweredWithoutFMA; I->replaceAllUsesWith(ConstantInt::get(I->getType(), 0)); - + } return; } -bool lowerCPUFeatures(Module &M) +bool lowerCPUFeatures(Module &M) JL_NOTSAFEPOINT { + auto TT = Triple(M.getTargetTriple()); SmallVector Materialized; for (auto &F: M.functions()) { @@ -92,7 +99,7 @@ bool lowerCPUFeatures(Module &M) for (Use &U: F.uses()) { User *RU = U.getUser(); CallInst *I = cast(RU); - lowerHaveFMA(F, *I->getParent()->getParent(), I); + lowerHaveFMA(F, *I->getParent()->getParent(), TT, I); Materialized.push_back(I); } } @@ -102,26 +109,27 @@ bool lowerCPUFeatures(Module &M) for (auto I: Materialized) { I->eraseFromParent(); } +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif return true; } else { return false; } } -struct CPUFeatures : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); -}; - PreservedAnalyses CPUFeatures::run(Module &M, ModuleAnalysisManager &AM) { - lowerCPUFeatures(M); + if (lowerCPUFeatures(M)) { + return PreservedAnalyses::allInSet(); + } return PreservedAnalyses::all(); } namespace { struct CPUFeaturesLegacy : public ModulePass { static char ID; - CPUFeaturesLegacy() : ModulePass(ID) {}; + CPUFeaturesLegacy() JL_NOTSAFEPOINT : ModulePass(ID) {}; bool runOnModule(Module &M) { @@ -142,7 +150,8 @@ Pass *createCPUFeaturesPass() return new CPUFeaturesLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddCPUFeaturesPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddCPUFeaturesPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createCPUFeaturesPass()); } diff --git a/src/llvm-demote-float16.cpp b/src/llvm-demote-float16.cpp index 3e328424e26d2..6ff7feaa550c8 100644 --- a/src/llvm-demote-float16.cpp +++ b/src/llvm-demote-float16.cpp @@ -14,29 +14,78 @@ #include "llvm-version.h" -#define DEBUG_TYPE "demote_float16" - #include "support/dtypes.h" +#include "passes.h" +#include +#include #include #include #include #include +#include #include +#include "julia.h" +#include "jitlayers.h" + +#define DEBUG_TYPE "demote_float16" using namespace llvm; +STATISTIC(TotalChanged, "Total number of instructions changed"); +STATISTIC(TotalExt, "Total number of FPExt instructions inserted"); +STATISTIC(TotalTrunc, "Total number of FPTrunc instructions inserted"); +#define INST_STATISTIC(Opcode) STATISTIC(Opcode##Changed, "Number of " #Opcode " instructions changed") +INST_STATISTIC(FNeg); +INST_STATISTIC(FAdd); +INST_STATISTIC(FSub); +INST_STATISTIC(FMul); +INST_STATISTIC(FDiv); +INST_STATISTIC(FRem); +INST_STATISTIC(FCmp); +#undef INST_STATISTIC + +extern JuliaOJIT *jl_ExecutionEngine; + namespace { +static bool have_fp16(Function &caller, const Triple &TT) { + Attribute FSAttr = caller.getFnAttribute("target-features"); + StringRef FS = + FSAttr.isValid() ? FSAttr.getValueAsString() : jl_ExecutionEngine->getTargetFeatureString(); + if (TT.isAArch64()) { + if (FS.find("+fp16fml") != llvm::StringRef::npos || FS.find("+fullfp16") != llvm::StringRef::npos){ + return true; + } + } else if (TT.getArch() == Triple::x86_64) { + if (FS.find("+avx512fp16") != llvm::StringRef::npos){ + return true; + } + } + return false; +} + static bool demoteFloat16(Function &F) { + auto TT = Triple(F.getParent()->getTargetTriple()); + if (have_fp16(F, TT)) + return false; + auto &ctx = F.getContext(); - auto T_float16 = Type::getHalfTy(ctx); auto T_float32 = Type::getFloatTy(ctx); - SmallVector erase; for (auto &BB : F) { for (auto &I : BB) { + // extend Float16 operands to Float32 + bool Float16 = I.getType()->getScalarType()->isHalfTy(); + for (size_t i = 0; !Float16 && i < I.getNumOperands(); i++) { + Value *Op = I.getOperand(i); + if (Op->getType()->getScalarType()->isHalfTy()) + Float16 = true; + } + if (!Float16) + continue; + switch (I.getOpcode()) { case Instruction::FNeg: case Instruction::FAdd: @@ -47,6 +96,7 @@ static bool demoteFloat16(Function &F) case Instruction::FCmp: break; default: + // TODO: Do calls to llvm.fma.f16 may need to go to f64 to be correct? continue; } @@ -58,67 +108,77 @@ static bool demoteFloat16(Function &F) IRBuilder<> builder(&I); // extend Float16 operands to Float32 - bool OperandsChanged = false; SmallVector Operands(I.getNumOperands()); for (size_t i = 0; i < I.getNumOperands(); i++) { Value *Op = I.getOperand(i); - if (Op->getType() == T_float16) { - Op = builder.CreateFPExt(Op, T_float32); - OperandsChanged = true; + if (Op->getType()->getScalarType()->isHalfTy()) { + ++TotalExt; + Op = builder.CreateFPExt(Op, Op->getType()->getWithNewType(T_float32)); } - Operands[i] = (Op); + Operands[i] = Op; } // recreate the instruction if any operands changed, // truncating the result back to Float16 - if (OperandsChanged) { - Value *NewI; - switch (I.getOpcode()) { - case Instruction::FNeg: - assert(Operands.size() == 1); - NewI = builder.CreateFNeg(Operands[0]); - break; - case Instruction::FAdd: - assert(Operands.size() == 2); - NewI = builder.CreateFAdd(Operands[0], Operands[1]); - break; - case Instruction::FSub: - assert(Operands.size() == 2); - NewI = builder.CreateFSub(Operands[0], Operands[1]); - break; - case Instruction::FMul: - assert(Operands.size() == 2); - NewI = builder.CreateFMul(Operands[0], Operands[1]); - break; - case Instruction::FDiv: - assert(Operands.size() == 2); - NewI = builder.CreateFDiv(Operands[0], Operands[1]); - break; - case Instruction::FRem: - assert(Operands.size() == 2); - NewI = builder.CreateFRem(Operands[0], Operands[1]); - break; - case Instruction::FCmp: - assert(Operands.size() == 2); - NewI = builder.CreateFCmp(cast(&I)->getPredicate(), - Operands[0], Operands[1]); - break; - default: - abort(); - } - cast(NewI)->copyMetadata(I); - cast(NewI)->copyFastMathFlags(&I); - if (NewI->getType() != I.getType()) - NewI = builder.CreateFPTrunc(NewI, I.getType()); - I.replaceAllUsesWith(NewI); - erase.push_back(&I); + Value *NewI; + ++TotalChanged; + switch (I.getOpcode()) { + case Instruction::FNeg: + assert(Operands.size() == 1); + ++FNegChanged; + NewI = builder.CreateFNeg(Operands[0]); + break; + case Instruction::FAdd: + assert(Operands.size() == 2); + ++FAddChanged; + NewI = builder.CreateFAdd(Operands[0], Operands[1]); + break; + case Instruction::FSub: + assert(Operands.size() == 2); + ++FSubChanged; + NewI = builder.CreateFSub(Operands[0], Operands[1]); + break; + case Instruction::FMul: + assert(Operands.size() == 2); + ++FMulChanged; + NewI = builder.CreateFMul(Operands[0], Operands[1]); + break; + case Instruction::FDiv: + assert(Operands.size() == 2); + ++FDivChanged; + NewI = builder.CreateFDiv(Operands[0], Operands[1]); + break; + case Instruction::FRem: + assert(Operands.size() == 2); + ++FRemChanged; + NewI = builder.CreateFRem(Operands[0], Operands[1]); + break; + case Instruction::FCmp: + assert(Operands.size() == 2); + ++FCmpChanged; + NewI = builder.CreateFCmp(cast(&I)->getPredicate(), + Operands[0], Operands[1]); + break; + default: + abort(); } + cast(NewI)->copyMetadata(I); + cast(NewI)->copyFastMathFlags(&I); + if (NewI->getType() != I.getType()) { + ++TotalTrunc; + NewI = builder.CreateFPTrunc(NewI, I.getType()); + } + I.replaceAllUsesWith(NewI); + erase.push_back(&I); } } if (erase.size() > 0) { for (auto V : erase) V->eraseFromParent(); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif return true; } else @@ -127,13 +187,11 @@ static bool demoteFloat16(Function &F) } // end anonymous namespace -struct DemoteFloat16 : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); -}; - PreservedAnalyses DemoteFloat16::run(Function &F, FunctionAnalysisManager &AM) { - demoteFloat16(F); + if (demoteFloat16(F)) { + return PreservedAnalyses::allInSet(); + } return PreservedAnalyses::all(); } @@ -162,7 +220,8 @@ Pass *createDemoteFloat16Pass() return new DemoteFloat16Legacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddDemoteFloat16Pass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddDemoteFloat16Pass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createDemoteFloat16Pass()); } diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 2f1ae2be32080..ac7d67cddd6f3 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -1,22 +1,32 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "llvm-version.h" +#include "passes.h" +#include #include #include #include #include #include +#include #include #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #include "julia_internal.h" #include "llvm-pass-helpers.h" #define DEBUG_TYPE "final_gc_lowering" +STATISTIC(NewGCFrameCount, "Number of lowered newGCFrameFunc intrinsics"); +STATISTIC(PushGCFrameCount, "Number of lowered pushGCFrameFunc intrinsics"); +STATISTIC(PopGCFrameCount, "Number of lowered popGCFrameFunc intrinsics"); +STATISTIC(GetGCFrameSlotCount, "Number of lowered getGCFrameSlotFunc intrinsics"); +STATISTIC(GCAllocBytesCount, "Number of lowered GCAllocBytesFunc intrinsics"); +STATISTIC(QueueGCRootCount, "Number of lowered queueGCRootFunc intrinsics"); +STATISTIC(SafepointCount, "Number of lowered safepoint intrinsics"); using namespace llvm; @@ -28,20 +38,19 @@ using namespace llvm; // This pass targets typical back-ends for which the standard Julia // runtime library is available. Atypical back-ends should supply // their own lowering pass. -struct FinalLowerGC: public FunctionPass, private JuliaPassContext { - static char ID; - FinalLowerGC() : FunctionPass(ID) - { } + +struct FinalLowerGC: private JuliaPassContext { + bool runOnFunction(Function &F); + bool doInitialization(Module &M); + bool doFinalization(Module &M); private: Function *queueRootFunc; Function *poolAllocFunc; Function *bigAllocFunc; + Function *allocTypedFunc; Instruction *pgcstack; - - bool doInitialization(Module &M) override; - bool doFinalization(Module &M) override; - bool runOnFunction(Function &F) override; + Type *T_size; // Lowers a `julia.new_gc_frame` intrinsic. Value *lowerNewGCFrame(CallInst *target, Function &F); @@ -60,34 +69,47 @@ struct FinalLowerGC: public FunctionPass, private JuliaPassContext { // Lowers a `julia.queue_gc_root` intrinsic. Value *lowerQueueGCRoot(CallInst *target, Function &F); + + // Lowers a `julia.safepoint` intrinsic. + Value *lowerSafepoint(CallInst *target, Function &F); }; Value *FinalLowerGC::lowerNewGCFrame(CallInst *target, Function &F) { + ++NewGCFrameCount; assert(target->arg_size() == 1); unsigned nRoots = cast(target->getArgOperand(0))->getLimitedValue(INT_MAX); // Create the GC frame. - AllocaInst *gcframe = new AllocaInst( + unsigned allocaAddressSpace = F.getParent()->getDataLayout().getAllocaAddrSpace(); + AllocaInst *gcframe_alloca = new AllocaInst( T_prjlvalue, - 0, - ConstantInt::get(T_int32, nRoots + 2), + allocaAddressSpace, + ConstantInt::get(Type::getInt32Ty(F.getContext()), nRoots + 2), Align(16)); - gcframe->insertAfter(target); + gcframe_alloca->insertAfter(target); + Instruction *gcframe; + if (allocaAddressSpace) { + // addrspacecast as needed for non-0 alloca addrspace + gcframe = new AddrSpaceCastInst(gcframe_alloca, T_prjlvalue->getPointerTo(0)); + gcframe->insertAfter(gcframe_alloca); + } else { + gcframe = gcframe_alloca; + } gcframe->takeName(target); // Zero out the GC frame. BitCastInst *tempSlot_i8 = new BitCastInst(gcframe, Type::getInt8PtrTy(F.getContext()), ""); tempSlot_i8->insertAfter(gcframe); - Type *argsT[2] = {tempSlot_i8->getType(), T_int32}; + Type *argsT[2] = {tempSlot_i8->getType(), Type::getInt32Ty(F.getContext())}; Function *memset = Intrinsic::getDeclaration(F.getParent(), Intrinsic::memset, makeArrayRef(argsT)); Value *args[4] = { tempSlot_i8, // dest ConstantInt::get(Type::getInt8Ty(F.getContext()), 0), // val - ConstantInt::get(T_int32, sizeof(jl_value_t*) * (nRoots + 2)), // len + ConstantInt::get(Type::getInt32Ty(F.getContext()), sizeof(jl_value_t*) * (nRoots + 2)), // len ConstantInt::get(Type::getInt1Ty(F.getContext()), 0)}; // volatile CallInst *zeroing = CallInst::Create(memset, makeArrayRef(args)); - cast(zeroing)->setDestAlignment(16); + cast(zeroing)->setDestAlignment(Align(16)); zeroing->setMetadata(LLVMContext::MD_tbaa, tbaa_gcframe); zeroing->insertAfter(tempSlot_i8); @@ -96,6 +118,7 @@ Value *FinalLowerGC::lowerNewGCFrame(CallInst *target, Function &F) void FinalLowerGC::lowerPushGCFrame(CallInst *target, Function &F) { + ++PushGCFrameCount; assert(target->arg_size() == 2); auto gcframe = target->getArgOperand(0); unsigned nRoots = cast(target->getArgOperand(1))->getLimitedValue(INT_MAX); @@ -109,6 +132,7 @@ void FinalLowerGC::lowerPushGCFrame(CallInst *target, Function &F) T_size->getPointerTo()), Align(sizeof(void*))); inst->setMetadata(LLVMContext::MD_tbaa, tbaa_gcframe); + auto T_ppjlvalue = JuliaType::get_ppjlvalue_ty(F.getContext()); inst = builder.CreateAlignedStore( builder.CreateAlignedLoad(T_ppjlvalue, pgcstack, Align(sizeof(void*))), builder.CreatePointerCast( @@ -124,6 +148,7 @@ void FinalLowerGC::lowerPushGCFrame(CallInst *target, Function &F) void FinalLowerGC::lowerPopGCFrame(CallInst *target, Function &F) { + ++PopGCFrameCount; assert(target->arg_size() == 1); auto gcframe = target->getArgOperand(0); @@ -143,6 +168,7 @@ void FinalLowerGC::lowerPopGCFrame(CallInst *target, Function &F) Value *FinalLowerGC::lowerGetGCFrameSlot(CallInst *target, Function &F) { + ++GetGCFrameSlotCount; assert(target->arg_size() == 2); auto gcframe = target->getArgOperand(0); auto index = target->getArgOperand(1); @@ -152,7 +178,7 @@ Value *FinalLowerGC::lowerGetGCFrameSlot(CallInst *target, Function &F) builder.SetInsertPoint(target); // The first two slots are reserved, so we'll add two to the index. - index = builder.CreateAdd(index, ConstantInt::get(T_int32, 2)); + index = builder.CreateAdd(index, ConstantInt::get(Type::getInt32Ty(F.getContext()), 2)); // Lower the intrinsic as a GEP. auto gep = builder.CreateInBoundsGEP(T_prjlvalue, gcframe, index); @@ -162,33 +188,59 @@ Value *FinalLowerGC::lowerGetGCFrameSlot(CallInst *target, Function &F) Value *FinalLowerGC::lowerQueueGCRoot(CallInst *target, Function &F) { + ++QueueGCRootCount; assert(target->arg_size() == 1); target->setCalledFunction(queueRootFunc); return target; } +Value *FinalLowerGC::lowerSafepoint(CallInst *target, Function &F) +{ + ++SafepointCount; + assert(target->arg_size() == 1); + IRBuilder<> builder(target->getContext()); + builder.SetInsertPoint(target); + Value* signal_page = target->getOperand(0); + Value* load = builder.CreateLoad(T_size, signal_page, true); + return load; +} + Value *FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) { + ++GCAllocBytesCount; assert(target->arg_size() == 2); - auto sz = (size_t)cast(target->getArgOperand(1))->getZExtValue(); - // This is strongly architecture and OS dependent - int osize; - int offset = jl_gc_classify_pools(sz, &osize); + CallInst *newI; + IRBuilder<> builder(target); builder.SetCurrentDebugLocation(target->getDebugLoc()); auto ptls = target->getArgOperand(0); - CallInst *newI; - if (offset < 0) { - newI = builder.CreateCall( - bigAllocFunc, - { ptls, ConstantInt::get(T_size, sz + sizeof(void*)) }); - } - else { - auto pool_offs = ConstantInt::get(T_int32, offset); - auto pool_osize = ConstantInt::get(T_int32, osize); - newI = builder.CreateCall(poolAllocFunc, { ptls, pool_offs, pool_osize }); + Attribute derefAttr; + + if (auto CI = dyn_cast(target->getArgOperand(1))) { + size_t sz = (size_t)CI->getZExtValue(); + // This is strongly architecture and OS dependent + int osize; + int offset = jl_gc_classify_pools(sz, &osize); + if (offset < 0) { + newI = builder.CreateCall( + bigAllocFunc, + { ptls, ConstantInt::get(T_size, sz + sizeof(void*)) }); + derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), sz + sizeof(void*)); + } + 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 }); + derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), osize); + } + } 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, ConstantPointerNull::get(Type::getInt8PtrTy(F.getContext())) }); + derefAttr = Attribute::getWithDereferenceableBytes(F.getContext(), sizeof(void*)); } newI->setAttributes(newI->getCalledFunction()->getAttributes()); + newI->addRetAttr(derefAttr); newI->takeName(target); return newI; } @@ -201,8 +253,10 @@ bool FinalLowerGC::doInitialization(Module &M) { queueRootFunc = getOrDeclare(jl_well_known::GCQueueRoot); poolAllocFunc = getOrDeclare(jl_well_known::GCPoolAlloc); bigAllocFunc = getOrDeclare(jl_well_known::GCBigAlloc); + allocTypedFunc = getOrDeclare(jl_well_known::GCAllocTyped); + T_size = M.getDataLayout().getIntPtrType(M.getContext()); - GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc}; + GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc, allocTypedFunc}; unsigned j = 0; for (unsigned i = 0; i < sizeof(functionList) / sizeof(void*); i++) { if (!functionList[i]) @@ -218,8 +272,8 @@ bool FinalLowerGC::doInitialization(Module &M) { bool FinalLowerGC::doFinalization(Module &M) { - GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc}; - queueRootFunc = poolAllocFunc = bigAllocFunc = nullptr; + GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc, allocTypedFunc}; + queueRootFunc = poolAllocFunc = bigAllocFunc = allocTypedFunc = nullptr; auto used = M.getGlobalVariable("llvm.compiler.used"); if (!used) return false; @@ -242,7 +296,7 @@ bool FinalLowerGC::doFinalization(Module &M) used->eraseFromParent(); if (init.empty()) return true; - ArrayType *ATy = ArrayType::get(T_pint8, init.size()); + ArrayType *ATy = ArrayType::get(Type::getInt8PtrTy(M.getContext()), init.size()); used = new GlobalVariable(M, ATy, false, GlobalValue::AppendingLinkage, ConstantArray::get(ATy, init), "llvm.compiler.used"); used->setSection("llvm.metadata"); @@ -266,16 +320,20 @@ static void replaceInstruction( bool FinalLowerGC::runOnFunction(Function &F) { - LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Processing function " << F.getName() << "\n"); // Check availability of functions again since they might have been deleted. initFunctions(*F.getParent()); - if (!pgcstack_getter) + if (!pgcstack_getter && !adoptthread_func) { + LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Skipping function " << F.getName() << "\n"); return false; + } // Look for a call to 'julia.get_pgcstack'. pgcstack = getPGCstack(F); - if (!pgcstack) + if (!pgcstack) { + LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Skipping function " << F.getName() << " no pgcstack\n"); return false; + } + LLVM_DEBUG(dbgs() << "FINAL GC LOWERING: Processing function " << F.getName() << "\n"); // Acquire intrinsic functions. auto newGCFrameFunc = getOrNull(jl_intrinsics::newGCFrame); @@ -284,6 +342,7 @@ bool FinalLowerGC::runOnFunction(Function &F) auto getGCFrameSlotFunc = getOrNull(jl_intrinsics::getGCFrameSlot); auto GCAllocBytesFunc = getOrNull(jl_intrinsics::GCAllocBytes); auto queueGCRootFunc = getOrNull(jl_intrinsics::queueGCRoot); + auto safepointFunc = getOrNull(jl_intrinsics::safepoint); // Lower all calls to supported intrinsics. for (BasicBlock &BB : F) { @@ -295,6 +354,7 @@ bool FinalLowerGC::runOnFunction(Function &F) } Value *callee = CI->getCalledOperand(); + assert(callee); if (callee == newGCFrameFunc) { replaceInstruction(CI, lowerNewGCFrame(CI, F), it); @@ -316,6 +376,10 @@ bool FinalLowerGC::runOnFunction(Function &F) else if (callee == queueGCRootFunc) { replaceInstruction(CI, lowerQueueGCRoot(CI, F), it); } + else if (callee == safepointFunc) { + lowerSafepoint(CI, F); + it = CI->eraseFromParent(); + } else { ++it; } @@ -325,15 +389,70 @@ bool FinalLowerGC::runOnFunction(Function &F) return true; } -char FinalLowerGC::ID = 0; -static RegisterPass X("FinalLowerGC", "Final GC intrinsic lowering pass", false, false); +struct FinalLowerGCLegacy: public FunctionPass { + static char ID; + FinalLowerGCLegacy() : FunctionPass(ID), finalLowerGC(FinalLowerGC()) {} + +protected: + void getAnalysisUsage(AnalysisUsage &AU) const override { + FunctionPass::getAnalysisUsage(AU); + } + +private: + bool runOnFunction(Function &F) override; + bool doInitialization(Module &M) override; + bool doFinalization(Module &M) override; + + FinalLowerGC finalLowerGC; +}; + +bool FinalLowerGCLegacy::runOnFunction(Function &F) { + return finalLowerGC.runOnFunction(F); +} + +bool FinalLowerGCLegacy::doInitialization(Module &M) { + return finalLowerGC.doInitialization(M); +} + +bool FinalLowerGCLegacy::doFinalization(Module &M) { + auto ret = finalLowerGC.doFinalization(M); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif + return ret; +} + + +PreservedAnalyses FinalLowerGCPass::run(Module &M, ModuleAnalysisManager &AM) +{ + auto finalLowerGC = FinalLowerGC(); + bool modified = false; + modified |= finalLowerGC.doInitialization(M); + for (auto &F : M.functions()) { + if (F.isDeclaration()) + continue; + modified |= finalLowerGC.runOnFunction(F); + } + modified |= finalLowerGC.doFinalization(M); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif + if (modified) { + return PreservedAnalyses::allInSet(); + } + return PreservedAnalyses::all(); +} + +char FinalLowerGCLegacy::ID = 0; +static RegisterPass X("FinalLowerGC", "Final GC intrinsic lowering pass", false, false); Pass *createFinalLowerGCPass() { - return new FinalLowerGC(); + return new FinalLowerGCLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddFinalLowerGCPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddFinalLowerGCPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createFinalLowerGCPass()); } diff --git a/src/llvm-gc-invariant-verifier.cpp b/src/llvm-gc-invariant-verifier.cpp index 4302f9021ec2c..26288dc09379d 100644 --- a/src/llvm-gc-invariant-verifier.cpp +++ b/src/llvm-gc-invariant-verifier.cpp @@ -4,6 +4,7 @@ // See the devdocs for a description of these invariants. #include "llvm-version.h" +#include "passes.h" #include #include @@ -25,7 +26,7 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #define DEBUG_TYPE "verify_gc_invariants" @@ -33,11 +34,10 @@ using namespace llvm; -struct GCInvariantVerifier : public FunctionPass, public InstVisitor { - static char ID; +struct GCInvariantVerifier : public InstVisitor { bool Broken = false; bool Strong; - GCInvariantVerifier(bool Strong = false) : FunctionPass(ID), Strong(Strong) {} + GCInvariantVerifier(bool Strong = false) : Strong(Strong) {} private: void Check(bool Cond, const char *message, Value *Val) { @@ -48,12 +48,6 @@ struct GCInvariantVerifier : public FunctionPass, public InstVisitorisPointerTy()) { unsigned AS = cast(Ty)->getAddressSpace(); Check(AS != AddressSpace::CalleeRooted, - "Illegal store of callee rooted value", &LI); + "Illegal load of callee rooted value", &LI); } } @@ -166,12 +160,15 @@ void GCInvariantVerifier::visitGetElementPtrInst(GetElementPtrInst &GEP) { } void GCInvariantVerifier::visitCallInst(CallInst &CI) { - CallingConv::ID CC = CI.getCallingConv(); - if (CC == JLCALL_F_CC || CC == JLCALL_F2_CC) { + Function *Callee = CI.getCalledFunction(); + if (Callee && (Callee->getName() == "julia.call" || + Callee->getName() == "julia.call2")) { + bool First = true; for (Value *Arg : CI.args()) { Type *Ty = Arg->getType(); - Check(Ty->isPointerTy() && cast(Ty)->getAddressSpace() == AddressSpace::Tracked, + Check(Ty->isPointerTy() && cast(Ty)->getAddressSpace() == (First ? 0 : AddressSpace::Tracked), "Invalid derived pointer in jlcall", &CI); + First = false; } } } @@ -188,22 +185,45 @@ void GCInvariantVerifier::visitPtrToIntInst(PtrToIntInst &PII) { "Illegal inttoptr", &PII); } -bool GCInvariantVerifier::runOnFunction(Function &F) { - visit(F); - if (Broken) { +PreservedAnalyses GCInvariantVerifierPass::run(Function &F, FunctionAnalysisManager &AM) { + GCInvariantVerifier GIV(Strong); + GIV.visit(F); + if (GIV.Broken) { abort(); } - return false; + return PreservedAnalyses::all(); } -char GCInvariantVerifier::ID = 0; -static RegisterPass X("GCInvariantVerifier", "GC Invariant Verification Pass", false, false); +struct GCInvariantVerifierLegacy : public FunctionPass { + static char ID; + bool Strong; + GCInvariantVerifierLegacy(bool Strong=false) : FunctionPass(ID), Strong(Strong) {} + +public: + void getAnalysisUsage(AnalysisUsage &AU) const override { + FunctionPass::getAnalysisUsage(AU); + AU.setPreservesAll(); + } + + bool runOnFunction(Function &F) override { + GCInvariantVerifier GIV(Strong); + GIV.visit(F); + if (GIV.Broken) { + abort(); + } + return false; + } +}; + +char GCInvariantVerifierLegacy::ID = 0; +static RegisterPass X("GCInvariantVerifier", "GC Invariant Verification Pass", false, false); Pass *createGCInvariantVerifierPass(bool Strong) { - return new GCInvariantVerifier(Strong); + return new GCInvariantVerifierLegacy(Strong); } -extern "C" JL_DLLEXPORT void LLVMExtraAddGCInvariantVerifierPass_impl(LLVMPassManagerRef PM, LLVMBool Strong) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddGCInvariantVerifierPass_impl(LLVMPassManagerRef PM, LLVMBool Strong) { unwrap(PM)->add(createGCInvariantVerifierPass(Strong)); } diff --git a/src/llvm-julia-licm.cpp b/src/llvm-julia-licm.cpp index 02d06696330d9..fc867252318c5 100644 --- a/src/llvm-julia-licm.cpp +++ b/src/llvm-julia-licm.cpp @@ -1,24 +1,37 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "llvm-version.h" +#include "passes.h" #include #include -#include "llvm/Analysis/LoopIterator.h" +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include -#include #include "llvm-pass-helpers.h" #include "julia.h" #include "llvm-alloc-helpers.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "julia-licm" using namespace llvm; +STATISTIC(HoistedPreserveBegin, "Number of gc_preserve_begin instructions hoisted out of a loop"); +STATISTIC(SunkPreserveEnd, "Number of gc_preserve_end instructions sunk out of a loop"); +STATISTIC(ErasedPreserveEnd, "Number of gc_preserve_end instructions removed from nonterminating loops"); +STATISTIC(HoistedWriteBarrier, "Number of write barriers hoisted out of a loop"); +STATISTIC(HoistedAllocation, "Number of allocations hoisted out of a loop"); + /* * Julia LICM pass. * This takes care of some julia intrinsics that is safe to move around/out of loops but @@ -26,13 +39,116 @@ using namespace llvm; * loop context as well but it is inside a loop where they matter the most. */ +#ifndef __clang_gcanalyzer__ +#define REMARK(remark) ORE.emit(remark) +#else +#define REMARK(remark) (void) 0; +#endif + namespace { -struct JuliaLICMPass : public LoopPass, public JuliaPassContext { +//Stolen and modified from LICM.cpp +static void eraseInstruction(Instruction &I, + MemorySSAUpdater &MSSAU) { + if (MSSAU.getMemorySSA()) + MSSAU.removeMemoryAccess(&I); + I.eraseFromParent(); +} + +//Stolen and modified from LICM.cpp +static void moveInstructionBefore(Instruction &I, Instruction &Dest, + MemorySSAUpdater &MSSAU, + ScalarEvolution *SE, + MemorySSA::InsertionPlace Place = MemorySSA::BeforeTerminator) { + I.moveBefore(&Dest); + if (MSSAU.getMemorySSA()) + if (MemoryUseOrDef *OldMemAcc = cast_or_null( + MSSAU.getMemorySSA()->getMemoryAccess(&I))) + MSSAU.moveToPlace(OldMemAcc, Dest.getParent(), Place); + if (SE) + SE->forgetValue(&I); +} + +static void createNewInstruction(Instruction *New, Instruction *Ref, MemorySSAUpdater &MSSAU) { + if (MSSAU.getMemorySSA() && MSSAU.getMemorySSA()->getMemoryAccess(Ref)) { + // Create a new MemoryAccess and let MemorySSA set its defining access. + MemoryAccess *NewMemAcc = MSSAU.createMemoryAccessInBB( + New, nullptr, New->getParent(), MemorySSA::Beginning); + if (NewMemAcc) { + if (auto *MemDef = dyn_cast(NewMemAcc)) + MSSAU.insertDef(MemDef, /*RenameUses=*/true); + else { + auto *MemUse = cast(NewMemAcc); + MSSAU.insertUse(MemUse, /*RenameUses=*/true); + } + } + } +} + +//Stolen and modified to update SE from LoopInfo.cpp +static bool makeLoopInvariant(Loop *L, Value *V, bool &Changed, Instruction *InsertPt, MemorySSAUpdater &MSSAU, ScalarEvolution *SE); + +static bool makeLoopInvariant(Loop *L, Instruction *I, bool &Changed, Instruction *InsertPt, MemorySSAUpdater &MSSAU, ScalarEvolution *SE) { + // Test if the value is already loop-invariant. + if (L->isLoopInvariant(I)) + return true; + if (!isSafeToSpeculativelyExecute(I)) + return false; + if (I->mayReadFromMemory()) + return false; + // EH block instructions are immobile. + if (I->isEHPad()) + return false; + // Don't hoist instructions with loop-variant operands. + for (Value *Operand : I->operands()) + if (!makeLoopInvariant(L, Operand, Changed, InsertPt, MSSAU, SE)) + return false; + + // Hoist. + moveInstructionBefore(*I, *InsertPt, MSSAU, SE); + + // There is possibility of hoisting this instruction above some arbitrary + // condition. Any metadata defined on it can be control dependent on this + // condition. Conservatively strip it here so that we don't give any wrong + // information to the optimizer. + I->dropUnknownNonDebugMetadata(); + + Changed = true; + return true; +} + +static bool makeLoopInvariant(Loop *L, Value *V, bool &Changed, Instruction *InsertPt, MemorySSAUpdater &MSSAU, ScalarEvolution *SE) { + if (Instruction *I = dyn_cast(V)) + return makeLoopInvariant(L, I, Changed, InsertPt, MSSAU, SE); + return true; // All non-instructions are loop-invariant. +} + +struct JuliaLICMPassLegacy : public LoopPass { static char ID; - JuliaLICMPass() : LoopPass(ID) {}; + JuliaLICMPassLegacy() : LoopPass(ID) {}; - bool runOnLoop(Loop *L, LPPassManager &LPM) override + bool runOnLoop(Loop *L, LPPassManager &LPM) override; + + protected: + void getAnalysisUsage(AnalysisUsage &AU) const override { + getLoopAnalysisUsage(AU); + } +}; +struct JuliaLICM : public JuliaPassContext { + function_ref GetDT; + function_ref GetLI; + function_ref GetMSSA; + function_ref GetSE; + JuliaLICM(function_ref GetDT, + function_ref GetLI, + function_ref GetMSSA, + function_ref GetSE) : + GetDT(GetDT), + GetLI(GetLI), + GetMSSA(GetMSSA), + GetSE(GetSE) {} + + bool runOnLoop(Loop *L, OptimizationRemarkEmitter &ORE) { // Get the preheader block to move instructions into, // required to run this pass. @@ -46,10 +162,16 @@ struct JuliaLICMPass : public LoopPass, public JuliaPassContext { // `gc_preserve_end_func` is optional since the input to // `gc_preserve_end_func` must be from `gc_preserve_begin_func`. // We also hoist write barriers here, so we don't exit if write_barrier_func exists - if (!gc_preserve_begin_func && !write_barrier_func && !alloc_obj_func) + if (!gc_preserve_begin_func && !write_barrier_func && + !alloc_obj_func) { + LLVM_DEBUG(dbgs() << "No gc_preserve_begin_func or write_barrier_func or alloc_obj_func found, skipping JuliaLICM\n"); return false; - auto LI = &getAnalysis().getLoopInfo(); - auto DT = &getAnalysis().getDomTree(); + } + auto LI = &GetLI(); + auto DT = &GetDT(); + auto MSSA = GetMSSA(); + auto SE = GetSE(); + MemorySSAUpdater MSSAU(MSSA); // Lazy initialization of exit blocks insertion points. bool exit_pts_init = false; @@ -98,7 +220,13 @@ struct JuliaLICMPass : public LoopPass, public JuliaPassContext { } if (!canhoist) continue; - call->moveBefore(preheader->getTerminator()); + ++HoistedPreserveBegin; + moveInstructionBefore(*call, *preheader->getTerminator(), MSSAU, SE); + LLVM_DEBUG(dbgs() << "Hoisted gc_preserve_begin: " << *call << "\n"); + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Hoisted", call) + << "hoisting preserve begin " << ore::NV("PreserveBegin", call); + }); changed = true; } else if (callee == gc_preserve_end_func) { @@ -108,76 +236,196 @@ struct JuliaLICMPass : public LoopPass, public JuliaPassContext { changed = true; auto exit_pts = get_exit_pts(); if (exit_pts.empty()) { - call->eraseFromParent(); + ++ErasedPreserveEnd; + eraseInstruction(*call, MSSAU); continue; } - call->moveBefore(exit_pts[0]); + ++SunkPreserveEnd; + moveInstructionBefore(*call, *exit_pts[0], MSSAU, SE, MemorySSA::Beginning); + LLVM_DEBUG(dbgs() << "Sunk gc_preserve_end: " << *call << "\n"); + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Sunk", call) + << "sinking preserve end " << ore::NV("PreserveEnd", call); + }); for (unsigned i = 1; i < exit_pts.size(); i++) { // Clone exit - CallInst::Create(call, {}, exit_pts[i]); + auto CI = CallInst::Create(call, {}, exit_pts[i]); + createNewInstruction(CI, call, MSSAU); + LLVM_DEBUG(dbgs() << "Cloned and sunk gc_preserve_end: " << *CI << "\n"); + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Sunk", call) + << "cloning and sinking preserve end" << ore::NV("PreserveEnd", call); + }); } } else if (callee == write_barrier_func) { bool valid = true; for (std::size_t i = 0; i < call->arg_size(); i++) { - if (!L->makeLoopInvariant(call->getArgOperand(i), changed)) { + if (!makeLoopInvariant(L, call->getArgOperand(i), + changed, preheader->getTerminator(), + MSSAU, SE)) { valid = false; + LLVM_DEBUG(dbgs() << "Failed to hoist write barrier argument: " << *call->getArgOperand(i) << "\n"); break; } } - if (valid) { - call->moveBefore(preheader->getTerminator()); - changed = true; + if (!valid) { + LLVM_DEBUG(dbgs() << "Failed to hoist write barrier: " << *call << "\n"); + continue; } + ++HoistedWriteBarrier; + moveInstructionBefore(*call, *preheader->getTerminator(), MSSAU, SE); + changed = true; + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Hoist", call) + << "hoisting write barrier " << ore::NV("GC Write Barrier", call); + }); } else if (callee == alloc_obj_func) { - jl_alloc::AllocUseInfo use_info; - jl_alloc::CheckInst::Stack check_stack; - jl_alloc::EscapeAnalysisRequiredArgs required{use_info, check_stack, *this, DL}; - jl_alloc::runEscapeAnalysis(call, required, jl_alloc::EscapeAnalysisOptionalArgs().with_valid_set(&L->getBlocksSet())); - if (use_info.escaped || use_info.addrescaped) { - continue; - } bool valid = true; for (std::size_t i = 0; i < call->arg_size(); i++) { - if (!L->makeLoopInvariant(call->getArgOperand(i), changed)) { + if (!makeLoopInvariant(L, call->getArgOperand(i), changed, + preheader->getTerminator(), MSSAU, SE)) { valid = false; + LLVM_DEBUG(dbgs() << "Failed to hoist alloc_obj argument: " << *call->getArgOperand(i) << "\n"); break; } } + if (!valid) { + LLVM_DEBUG(dbgs() << "Failed to hoist alloc_obj: " << *call << "\n"); + continue; + } + LLVM_DEBUG(dbgs() << "Running escape analysis for " << *call << "\n"); + jl_alloc::AllocUseInfo use_info; + jl_alloc::CheckInst::Stack check_stack; + jl_alloc::EscapeAnalysisRequiredArgs required{use_info, check_stack, *this, DL}; + jl_alloc::runEscapeAnalysis(call, required, jl_alloc::EscapeAnalysisOptionalArgs().with_valid_set(&L->getBlocksSet()).with_optimization_remark_emitter(&ORE)); + REMARK([&](){ + std::string suse_info; + llvm::raw_string_ostream osuse_info(suse_info); + use_info.dump(osuse_info); + return OptimizationRemarkAnalysis(DEBUG_TYPE, "EscapeAnalysis", call) << "escape analysis for " << ore::NV("GC Allocation", call) << "\n" << ore::NV("UseInfo", osuse_info.str()); + }); + if (use_info.escaped) { + REMARK([&](){ + return OptimizationRemarkMissed(DEBUG_TYPE, "Escape", call) + << "not hoisting gc allocation " << ore::NV("GC Allocation", call) + << " because it may escape"; + }); + continue; + } + if (use_info.addrescaped) { + REMARK([&](){ + return OptimizationRemarkMissed(DEBUG_TYPE, "Escape", call) + << "not hoisting gc allocation " << ore::NV("GC Allocation", call) + << " because its address may escape"; + }); + continue; + } if (use_info.refstore) { // We need to add write barriers to any stores // that may start crossing generations + REMARK([&](){ + return OptimizationRemarkMissed(DEBUG_TYPE, "Escape", call) + << "not hoisting gc allocation " << ore::NV("GC Allocation", call) + << " because it may have an object stored to it"; + }); continue; } - if (valid) { - call->moveBefore(preheader->getTerminator()); - changed = true; + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Hoist", call) + << "hoisting gc allocation " << ore::NV("GC Allocation", call); + }); + ++HoistedAllocation; + moveInstructionBefore(*call, *preheader->getTerminator(), MSSAU, SE); + IRBuilder<> builder(preheader->getTerminator()); + builder.SetCurrentDebugLocation(call->getDebugLoc()); + auto obj_i8 = builder.CreateBitCast(call, Type::getInt8PtrTy(call->getContext(), call->getType()->getPointerAddressSpace())); + // Note that this alignment is assuming the GC allocates at least pointer-aligned memory + auto align = Align(DL.getPointerSize(0)); + auto clear_obj = builder.CreateMemSet(obj_i8, ConstantInt::get(Type::getInt8Ty(call->getContext()), 0), call->getArgOperand(1), align); + if (MSSAU.getMemorySSA()) { + auto alloc_mdef = MSSAU.getMemorySSA()->getMemoryAccess(call); + assert(isa(alloc_mdef) && "Expected alloc to be associated with a memory def!"); + auto clear_mdef = MSSAU.createMemoryAccessAfter(clear_obj, nullptr, alloc_mdef); + assert(isa(clear_mdef) && "Expected memset to be associated with a memory def!"); + (void) clear_mdef; } + changed = true; } } } + if (changed && SE) { + SE->forgetLoopDispositions(L); + } +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(*L->getHeader()->getParent(), &errs())); +#endif return changed; } - - void getAnalysisUsage(AnalysisUsage &AU) const override - { - getLoopAnalysisUsage(AU); - } }; -char JuliaLICMPass::ID = 0; -static RegisterPass +bool JuliaLICMPassLegacy::runOnLoop(Loop *L, LPPassManager &LPM) { + OptimizationRemarkEmitter ORE(L->getHeader()->getParent()); + auto GetDT = [this]() -> DominatorTree & { + return getAnalysis().getDomTree(); + }; + auto GetLI = [this]() -> LoopInfo & { + return getAnalysis().getLoopInfo(); + }; + auto GetMSSA = []() { + return nullptr; + }; + auto GetSE = []() { + return nullptr; + }; + auto juliaLICM = JuliaLICM(GetDT, GetLI, GetMSSA, GetSE); + return juliaLICM.runOnLoop(L, ORE); +} + +char JuliaLICMPassLegacy::ID = 0; +static RegisterPass Y("JuliaLICM", "LICM for julia specific intrinsics.", false, false); +} //namespace + +PreservedAnalyses JuliaLICMPass::run(Loop &L, LoopAnalysisManager &AM, + LoopStandardAnalysisResults &AR, LPMUpdater &U) +{ + OptimizationRemarkEmitter ORE(L.getHeader()->getParent()); + auto GetDT = [&AR]() -> DominatorTree & { + return AR.DT; + }; + auto GetLI = [&AR]() -> LoopInfo & { + return AR.LI; + }; + auto GetMSSA = [&AR]() { + return AR.MSSA; + }; + auto GetSE = [&AR]() { + return &AR.SE; + }; + auto juliaLICM = JuliaLICM(GetDT, GetLI, GetMSSA, GetSE); + if (juliaLICM.runOnLoop(&L, ORE)) { +#ifdef JL_DEBUG_BUILD + if (AR.MSSA) + AR.MSSA->verifyMemorySSA(); +#endif + auto preserved = getLoopPassPreservedAnalyses(); + preserved.preserveSet(); + preserved.preserve(); + return preserved; + } + return PreservedAnalyses::all(); } Pass *createJuliaLICMPass() { - return new JuliaLICMPass(); + return new JuliaLICMPassLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraJuliaLICMPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraJuliaLICMPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createJuliaLICMPass()); } diff --git a/src/llvm-julia-passes.inc b/src/llvm-julia-passes.inc new file mode 100644 index 0000000000000..18e0f645d5445 --- /dev/null +++ b/src/llvm-julia-passes.inc @@ -0,0 +1,27 @@ +//Module passes +#ifdef MODULE_PASS +MODULE_PASS("CPUFeatures", CPUFeatures()) +MODULE_PASS("RemoveNI", RemoveNI()) +MODULE_PASS("LowerSIMDLoop", LowerSIMDLoop()) +MODULE_PASS("FinalLowerGC", FinalLowerGCPass()) +MODULE_PASS("JuliaMultiVersioning", MultiVersioning()) +MODULE_PASS("RemoveJuliaAddrspaces", RemoveJuliaAddrspacesPass()) +MODULE_PASS("RemoveAddrspaces", RemoveAddrspacesPass()) +MODULE_PASS("LowerPTLSPass", LowerPTLSPass()) +#endif + +//Function passes +#ifdef FUNCTION_PASS +FUNCTION_PASS("DemoteFloat16", DemoteFloat16()) +FUNCTION_PASS("CombineMulAdd", CombineMulAdd()) +FUNCTION_PASS("LateLowerGCFrame", LateLowerGC()) +FUNCTION_PASS("AllocOpt", AllocOptPass()) +FUNCTION_PASS("PropagateJuliaAddrspaces", PropagateJuliaAddrspacesPass()) +FUNCTION_PASS("LowerExcHandlers", LowerExcHandlers()) +FUNCTION_PASS("GCInvariantVerifier", GCInvariantVerifierPass()) +#endif + +//Loop passes +#ifdef LOOP_PASS +LOOP_PASS("JuliaLICM", JuliaLICMPass()) +#endif diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 3586527668135..038948839f725 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1,14 +1,17 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "llvm-version.h" +#include "passes.h" #include #include #include +#include #include #include #include +#include #include "llvm/Analysis/CFG.h" #include #include @@ -19,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -28,11 +32,12 @@ #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia.h" #include "julia_internal.h" #include "julia_assert.h" #include "llvm-pass-helpers.h" +#include #define DEBUG_TYPE "late_lower_gcroot" @@ -222,15 +227,20 @@ using namespace llvm; simply sink the alloca into the GCFrame. */ +// 4096 bits == 64 words (64 bit words). Larger bit numbers are faster and doing something +// substantially smaller here doesn't actually save much memory because of malloc overhead. +// Too large is bad also though - 4096 was found to be a reasonable middle ground. +using LargeSparseBitVector = SparseBitVector<4096>; + struct BBState { // Uses in this BB // These do not get updated after local analysis - BitVector Defs; - BitVector PhiOuts; - BitVector UpExposedUses; + LargeSparseBitVector Defs; + LargeSparseBitVector PhiOuts; + LargeSparseBitVector UpExposedUses; // These get updated during dataflow - BitVector LiveIn; - BitVector LiveOut; + LargeSparseBitVector LiveIn; + LargeSparseBitVector LiveOut; std::vector Safepoints; int TopmostSafepoint = -1; bool HasSafepoint = false; @@ -254,9 +264,9 @@ struct State { std::map ReversePtrNumbering; // Neighbors in the coloring interference graph. I.e. for each value, the // indices of other values that are used simultaneously at some safe point. - std::vector> Neighbors; + std::vector Neighbors; // The result of the local analysis - std::map BBStates; + std::map BBStates; // Refinement map. If all of the values are rooted // (-1 means an externally rooted value and -2 means a globally/permanently rooted value), @@ -286,7 +296,7 @@ struct State { std::vector ReturnsTwice; // The set of values live at a particular safepoint - std::vector LiveSets; + std::vector< LargeSparseBitVector > LiveSets; // Those values that - if live out from our parent basic block - are live // at this safepoint. std::vector> LiveIfLiveOut; @@ -301,16 +311,11 @@ struct State { State(Function &F) : F(&F), DT(nullptr), MaxPtrNumber(-1), MaxSafepointNumber(-1) {} }; -namespace llvm { - void initializeLateLowerGCFramePass(PassRegistry &Registry); -} -struct LateLowerGCFrame: public FunctionPass, private JuliaPassContext { + +struct LateLowerGCFrameLegacy: public FunctionPass { static char ID; - LateLowerGCFrame() : FunctionPass(ID) - { - llvm::initializeDominatorTreeWrapperPassPass(*PassRegistry::getPassRegistry()); - } + LateLowerGCFrameLegacy() : FunctionPass(ID) {} protected: void getAnalysisUsage(AnalysisUsage &AU) const override { @@ -320,11 +325,22 @@ struct LateLowerGCFrame: public FunctionPass, private JuliaPassContext { AU.setPreservesCFG(); } +private: + bool runOnFunction(Function &F) override; +}; + +struct LateLowerGCFrame: private JuliaPassContext { + function_ref GetDT; + LateLowerGCFrame(function_ref GetDT) : GetDT(GetDT) {} + +public: + bool runOnFunction(Function &F, bool *CFGModified = nullptr); + private: CallInst *pgcstack; void MaybeNoteDef(State &S, BBState &BBS, Value *Def, const std::vector &SafepointsSoFar, SmallVector &&RefinedPtr = SmallVector()); - void NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses); + void NoteUse(State &S, BBState &BBS, Value *V, LargeSparseBitVector &Uses); void NoteUse(State &S, BBState &BBS, Value *V) { NoteUse(S, BBS, V, BBS.UpExposedUses); } @@ -350,15 +366,13 @@ struct LateLowerGCFrame: public FunctionPass, private JuliaPassContext { void PlaceGCFrameStore(State &S, unsigned R, unsigned MinColorRoot, const std::vector &Colors, Value *GCFrame, Instruction *InsertBefore); void PlaceGCFrameStores(State &S, unsigned MinColorRoot, const std::vector &Colors, Value *GCFrame); void PlaceRootsAndUpdateCalls(std::vector &Colors, State &S, std::map>); - bool doInitialization(Module &M) override; - bool runOnFunction(Function &F) override; - bool CleanupIR(Function &F, State *S=nullptr); + bool CleanupIR(Function &F, State *S, bool *CFGModified); void NoteUseChain(State &S, BBState &BBS, User *TheUser); SmallVector GetPHIRefinements(PHINode *phi, State &S); void FixUpRefinements(ArrayRef PHINumbers, State &S); - void RefineLiveSet(BitVector &LS, State &S, const std::vector &CalleeRoots); - Value *EmitTagPtr(IRBuilder<> &builder, Type *T, Value *V); - Value *EmitLoadTag(IRBuilder<> &builder, Value *V); + void RefineLiveSet(LargeSparseBitVector &LS, State &S, const std::vector &CalleeRoots); + Value *EmitTagPtr(IRBuilder<> &builder, Type *T, Type *T_size, Value *V); + Value *EmitLoadTag(IRBuilder<> &builder, Type *T_size, Value *V); }; static unsigned getValueAddrSpace(Value *V) { @@ -419,7 +433,7 @@ unsigned getCompositeNumElements(Type *T) { // Walk through a Type, and record the element path to every tracked value inside void TrackCompositeType(Type *T, std::vector &Idxs, std::vector> &Numberings) { if (isa(T)) { - if (T->getPointerAddressSpace() == AddressSpace::Tracked) + if (isSpecialPtr(T)) Numberings.push_back(Idxs); } else if (isa(T) || isa(T) || isa(T)) { @@ -743,6 +757,7 @@ void LateLowerGCFrame::LiftPhi(State &S, PHINode *Phi) { } if (!isa(Phi->getType())) S.AllCompositeNumbering[Phi] = Numbers; + SmallVector, 4> CastedRoots(NumRoots); for (unsigned i = 0; i < Phi->getNumIncomingValues(); ++i) { Value *Incoming = Phi->getIncomingValue(i); BasicBlock *IncomingBB = Phi->getIncomingBlock(i); @@ -760,8 +775,27 @@ void LateLowerGCFrame::LiftPhi(State &S, PHINode *Phi) { BaseElem = Base; else BaseElem = IncomingBases[i]; - if (BaseElem->getType() != T_prjlvalue) - BaseElem = new BitCastInst(BaseElem, T_prjlvalue, "", Terminator); + if (BaseElem->getType() != T_prjlvalue) { + auto &remap = CastedRoots[i][BaseElem]; + if (!remap) { + if (auto constant = dyn_cast(BaseElem)) { + remap = ConstantExpr::getBitCast(constant, T_prjlvalue, ""); + } else { + Instruction *InsertBefore; + if (auto arg = dyn_cast(BaseElem)) { + InsertBefore = &*arg->getParent()->getEntryBlock().getFirstInsertionPt(); + } else { + assert(isa(BaseElem) && "Unknown value type detected!"); + InsertBefore = cast(BaseElem)->getNextNonDebugInstruction(); + } + while (isa(InsertBefore)) { + InsertBefore = InsertBefore->getNextNonDebugInstruction(); + } + remap = new BitCastInst(BaseElem, T_prjlvalue, "", InsertBefore); + } + } + BaseElem = remap; + } lift->addIncoming(BaseElem, IncomingBB); } } @@ -969,11 +1003,17 @@ std::vector LateLowerGCFrame::NumberAll(State &S, Value *V) { static void MaybeResize(BBState &BBS, unsigned Idx) { + /* if (BBS.Defs.size() <= Idx) { BBS.Defs.resize(Idx + 1); BBS.UpExposedUses.resize(Idx + 1); BBS.PhiOuts.resize(Idx + 1); } + */ +} + +static bool HasBitSet(const LargeSparseBitVector &BV, unsigned Bit) { + return BV.test(Bit); } static bool HasBitSet(const BitVector &BV, unsigned Bit) { @@ -983,9 +1023,9 @@ static bool HasBitSet(const BitVector &BV, unsigned Bit) { static void NoteDef(State &S, BBState &BBS, int Num, const std::vector &SafepointsSoFar) { assert(Num >= 0); MaybeResize(BBS, Num); - assert(BBS.Defs[Num] == 0 && "SSA Violation or misnumbering?"); - BBS.Defs[Num] = 1; - BBS.UpExposedUses[Num] = 0; + assert(!BBS.Defs.test(Num) && "SSA Violation or misnumbering?"); + BBS.Defs.set(Num); + BBS.UpExposedUses.reset(Num); // This value could potentially be live at any following safe point // if it ends up live out, so add it to the LiveIfLiveOut lists for all // following safepoints. @@ -1029,7 +1069,7 @@ static int NoteSafepoint(State &S, BBState &BBS, CallInst *CI, std::vector return Number; } -void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses) { +void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, LargeSparseBitVector &Uses) { // Short circuit to avoid having to deal with vectors of constants, etc. if (isa(V)) return; @@ -1039,7 +1079,7 @@ void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses if (Num < 0) return; MaybeResize(BBS, Num); - Uses[Num] = 1; + Uses.set(Num); } } else { std::vector Nums = NumberAll(S, V); @@ -1047,7 +1087,7 @@ void LateLowerGCFrame::NoteUse(State &S, BBState &BBS, Value *V, BitVector &Uses if (Num < 0) continue; MaybeResize(BBS, Num); - Uses[Num] = 1; + Uses.set(Num); } } } @@ -1065,8 +1105,9 @@ void RecursivelyVisit(callback f, Value *V) { if (isa(TheUser)) f(VU); if (isa(TheUser) || isa(TheUser) || - isa(TheUser) || isa(TheUser) || + isa(TheUser) || isa(TheUser) || // TODO: should these be removed from this list? isa(TheUser) || isa(TheUser) || + isa(TheUser) || // ICmpEQ/ICmpNE can be used with ptr types isa(TheUser) || isa(TheUser)) continue; if (isa(TheUser) || isa(TheUser) || isa(TheUser)) { @@ -1079,31 +1120,44 @@ void RecursivelyVisit(callback f, Value *V) { } } -static void dumpBitVectorValues(State &S, BitVector &BV) { +static void dumpBitVectorValues(State &S, LargeSparseBitVector &BV, ModuleSlotTracker &MST) { bool first = true; - for (int Idx = BV.find_first(); Idx >= 0; Idx = BV.find_next(Idx)) { + for (auto Idx : BV) { if (!first) dbgs() << ", "; first = false; - S.ReversePtrNumbering[Idx]->printAsOperand(dbgs()); + S.ReversePtrNumbering[Idx]->printAsOperand(dbgs(), false, MST); } } +static void dumpBBState(const BasicBlock &BB, State &S, ModuleSlotTracker &MST) +{ + dbgs() << "Liveness analysis for BB " << BB.getName(); + dbgs() << "\n\tDefs: "; + dumpBitVectorValues(S, S.BBStates[&BB].Defs, MST); + dbgs() << "\n\tPhiOuts: "; + dumpBitVectorValues(S, S.BBStates[&BB].PhiOuts, MST); + dbgs() << "\n\tUpExposedUses: "; + dumpBitVectorValues(S, S.BBStates[&BB].UpExposedUses, MST); + dbgs() << "\n\tLiveIn: "; + dumpBitVectorValues(S, S.BBStates[&BB].LiveIn, MST); + dbgs() << "\n\tLiveOut: "; + dumpBitVectorValues(S, S.BBStates[&BB].LiveOut, MST); + dbgs() << "\n"; +} + +JL_USED_FUNC static void dumpBBState(const BasicBlock &BB, State &S) +{ + ModuleSlotTracker MST(BB.getParent()->getParent()); + dumpBBState(BB, S, MST); +} + + /* Debugging utility to dump liveness information */ JL_USED_FUNC static void dumpLivenessState(Function &F, State &S) { + ModuleSlotTracker MST(F.getParent()); for (auto &BB : F) { - dbgs() << "Liveness analysis for BB " << BB.getName(); - dbgs() << "\n\tDefs: "; - dumpBitVectorValues(S, S.BBStates[&BB].Defs); - dbgs() << "\n\tPhiOuts: "; - dumpBitVectorValues(S, S.BBStates[&BB].PhiOuts); - dbgs() << "\n\tUpExposedUses: "; - dumpBitVectorValues(S, S.BBStates[&BB].UpExposedUses); - dbgs() << "\n\tLiveIn: "; - dumpBitVectorValues(S, S.BBStates[&BB].LiveIn); - dbgs() << "\n\tLiveOut: "; - dumpBitVectorValues(S, S.BBStates[&BB].LiveOut); - dbgs() << "\n"; + return dumpBBState(BB, S, MST); } } @@ -1208,6 +1262,7 @@ static bool isLoadFromConstGV(LoadInst *LI, bool &task_local, PhiSet *seen) // 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 (isTBAA(LI->getMetadata(LLVMContext::MD_tbaa), {"jtbaa_immut", "jtbaa_const", "jtbaa_datatype"})) { @@ -1231,8 +1286,9 @@ static uint64_t getLoadValueAlign(LoadInst *LI) static bool LooksLikeFrameRef(Value *V) { if (isSpecialPtr(V->getType())) return false; - if (isa(V)) - return LooksLikeFrameRef(cast(V)->getOperand(0)); + V = V->stripInBoundsOffsets(); + if (isSpecialPtr(V->getType())) + return false; return isa(V); } @@ -1280,7 +1336,7 @@ void LateLowerGCFrame::FixUpRefinements(ArrayRef PHINumbers, State &S) // value of -1 or -2 in the refinement map), or may be externally rooted by refinement to other // values. Thus a value is not externally rooted if it either: // either: - // - Has no refinements (all obiviously externally rooted values are annotated by -1/-2 in the + // - Has no refinements (all obviously externally rooted values are annotated by -1/-2 in the // refinement map). // - Recursively reaches a not-externally rooted value through its refinements // @@ -1385,7 +1441,7 @@ void LateLowerGCFrame::FixUpRefinements(ArrayRef PHINumbers, State &S) j++; if (auto inst = dyn_cast(S.ReversePtrNumbering[refine])) { if (!S.DT) - S.DT = &getAnalysis().getDomTree(); + S.DT = &GetDT(); if (S.DT->dominates(inst, Phi)) continue; // Decrement `j` so we'll overwrite/ignore it. @@ -1415,6 +1471,8 @@ void LateLowerGCFrame::FixUpRefinements(ArrayRef PHINumbers, State &S) // This should have been handled by the first loop above. assert(j != 0 && j <= RefinedPtr.size()); RefinedPtr.resize(j); + } else { + S.Refinements.erase(Num); } visited.reset(); } @@ -1467,8 +1525,8 @@ State LateLowerGCFrame::LocalScan(Function &F) { MaybeNoteDef(S, BBS, CI, BBS.Safepoints); } if (CI->hasStructRetAttr()) { - // TODO: get ElT from SRet attribute - Type *ElT = (CI->arg_begin()[0])->getType()->getPointerElementType(); + 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); if (tracked.count) { AllocaInst *SRet = dyn_cast((CI->arg_begin()[0])->stripInBoundsOffsets()); @@ -1550,7 +1608,8 @@ 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() == "memcmp") { + callee == write_barrier_func || + callee->getName() == "memcmp") { continue; } if (callee->hasFnAttribute(Attribute::ReadNone) || @@ -1782,12 +1841,12 @@ std::vector ExtractTrackedValues(Value *Src, Type *STy, bool isptr, IRBu return Ptrs; } -unsigned TrackWithShadow(Value *Src, Type *STy, bool isptr, Value *Dst, IRBuilder<> &irbuilder) { +unsigned TrackWithShadow(Value *Src, Type *STy, bool isptr, Value *Dst, Type *DTy, IRBuilder<> &irbuilder) { auto Ptrs = ExtractTrackedValues(Src, STy, isptr, irbuilder); for (unsigned i = 0; i < Ptrs.size(); ++i) { - Value *Elem = Ptrs[i]; - Type *ET = Dst->getType()->getPointerElementType(); // Dst has type `[n x {}*]*` - Value *Slot = irbuilder.CreateConstInBoundsGEP2_32(ET, Dst, 0, i); + Value *Elem = Ptrs[i];// Dst has type `[n x {}*]*` + Value *Slot = irbuilder.CreateConstInBoundsGEP2_32(DTy, Dst, 0, i); + assert(cast(Dst->getType())->isOpaqueOrPointeeTypeMatches(DTy)); StoreInst *shadowStore = irbuilder.CreateAlignedStore(Elem, Slot, Align(sizeof(void*))); shadowStore->setOrdering(AtomicOrdering::NotAtomic); // TODO: shadowStore->setMetadata(LLVMContext::MD_tbaa, tbaa_gcframe); @@ -1868,33 +1927,27 @@ void LateLowerGCFrame::MaybeTrackStore(State &S, StoreInst *I) { */ void LateLowerGCFrame::ComputeLiveness(State &S) { bool Converged = false; - /* Liveness is a reverse problem, so RPOT is a good way to - * perform this iteration. - */ - ReversePostOrderTraversal RPOT(S.F); + /* Liveness is a reverse problem, so post-order is a good way to perform this iteration. */ + LargeSparseBitVector NewLive; while (!Converged) { bool AnyChanged = false; - for (BasicBlock *BB : RPOT) { + for (BasicBlock *BB : post_order(S.F)) { // This could all be done more efficiently, by only updating what // changed - Let's get it working first though. BBState &BBS = S.BBStates[BB]; - BitVector NewLiveOut = BBS.PhiOuts; + NewLive = BBS.PhiOuts; for (BasicBlock *Succ : successors(BB)) { - NewLiveOut |= S.BBStates[Succ].LiveIn; + NewLive |= S.BBStates[Succ].LiveIn; } - if (NewLiveOut != BBS.LiveOut) { + if (NewLive != BBS.LiveOut) { AnyChanged = true; - BBS.LiveOut = NewLiveOut; - MaybeResize(BBS, BBS.LiveOut.size() - 1); + BBS.LiveOut = NewLive; } - BitVector NewLiveIn = BBS.LiveOut; - BitVector FlippedDefs = BBS.Defs; - FlippedDefs.flip(); - NewLiveIn &= FlippedDefs; - NewLiveIn |= BBS.UpExposedUses; - if (NewLiveIn != BBS.LiveIn) { + NewLive.intersectWithComplement(BBS.Defs); + NewLive |= BBS.UpExposedUses; + if (NewLive != BBS.LiveIn) { AnyChanged = true; - BBS.LiveIn = NewLiveIn; + std::swap(BBS.LiveIn, NewLive); } } Converged = !AnyChanged; @@ -1907,8 +1960,8 @@ JL_USED_FUNC static void dumpSafepointsForBBName(Function &F, State &S, const ch for (auto it : S.SafepointNumbering) { if (it.first->getParent()->getName() == BBName) { dbgs() << "Live at " << *it.first << "\n"; - BitVector &LS = S.LiveSets[it.second]; - for (int Idx = LS.find_first(); Idx >= 0; Idx = LS.find_next(Idx)) { + LargeSparseBitVector &LS = S.LiveSets[it.second]; + for (auto Idx : LS) { dbgs() << "\t"; S.ReversePtrNumbering[Idx]->printAsOperand(dbgs()); dbgs() << "\n"; @@ -1917,63 +1970,70 @@ JL_USED_FUNC static void dumpSafepointsForBBName(Function &F, State &S, const ch } } -void LateLowerGCFrame::RefineLiveSet(BitVector &LS, State &S, const std::vector &CalleeRoots) +static bool IsIndirectlyRooted(const State &S, LargeSparseBitVector &Visited, LargeSparseBitVector &IndirectlyRootedLS, const LargeSparseBitVector &LS, int RefPtr) { + if (HasBitSet(IndirectlyRootedLS, RefPtr)) + return true; + if (HasBitSet(Visited, RefPtr)) + return false; + const auto it = S.Refinements.find(RefPtr); + if (it == S.Refinements.end()) { + Visited.set(RefPtr); + return false; + } + const auto &RefinedPtr = it->second; + assert(!RefinedPtr.empty()); + bool rooted = true; + for (auto NRefPtr: RefinedPtr) { + if (NRefPtr < 0 || IsIndirectlyRooted(S, Visited, IndirectlyRootedLS, LS, NRefPtr)) { + continue; + } + // Not indirectly rooted, but in LS - can be used to establish a root + if (HasBitSet(LS, NRefPtr)) + continue; + rooted = false; + break; + } + if (rooted) + IndirectlyRootedLS.set(RefPtr); + Visited.set(RefPtr); + return rooted; +} + +void LateLowerGCFrame::RefineLiveSet(LargeSparseBitVector &LS, State &S, const std::vector &CalleeRoots) { - BitVector FullLS(S.MaxPtrNumber + 1, false); - FullLS |= LS; - // First expand the live set according to the refinement map - // so that we can see all the values that are effectively live. + // It is possible that a value is not directly rooted by the refinements in the live set, but rather + // indirectly by following the edges of the refinement graph to all the values that root it. + // For example, suppose we have: + // LS: 1 4 5 + // Refinements: 1 -> {2,3} + // 2 -> 4 + // 3 -> 5 + // Even though {2,3} is not in the LiveSet, we can still refine, because we can follow the edges to + // the roots {4, 5} which are in the live set. The two bit vectors here cache the lookup for efficiency. + LargeSparseBitVector Visited; + LargeSparseBitVector IndirectlyRootedLS; for (auto Num: CalleeRoots) { // For callee rooted values, they are all kept alive at the safepoint. // Make sure they are marked (even though they probably are already) // so that other values can be refined to them. - FullLS[Num] = 1; + IndirectlyRootedLS.set(Num); + // Now unmark all values that are rooted by the callee after + // refining other values to them. + LS.reset(Num); } - bool changed; - do { - changed = false; - for (auto &kv: S.Refinements) { - int Num = kv.first; - if (Num < 0 || HasBitSet(FullLS, Num) || kv.second.empty()) - continue; - bool live = true; - for (auto &refine: kv.second) { - if (refine < 0 || HasBitSet(FullLS, refine)) - continue; - live = false; - break; - } - if (live) { - changed = true; - FullLS[Num] = 1; - } - } - } while (changed); + // Now remove all values from the LiveSet that's kept alive by other objects // This loop only mutate `LS` which isn't read from in the loop body so // a single pass is enough. - for (int Idx = LS.find_first(); Idx >= 0; Idx = LS.find_next(Idx)) { - if (!S.Refinements.count(Idx)) - continue; - const auto &RefinedPtr = S.Refinements[Idx]; - if (RefinedPtr.empty()) - continue; - bool rooted = true; - for (auto RefPtr: RefinedPtr) { - if (RefPtr < 0 || HasBitSet(FullLS, RefPtr)) - continue; - rooted = false; - break; - } + auto it = LS.begin(); + while (it != LS.end()) { + int Idx = *it; + bool rooted = IsIndirectlyRooted(S, Visited, IndirectlyRootedLS, LS, Idx); + ++it; if (rooted) { - LS[Idx] = 0; + LS.reset(Idx); } } - for (auto Num: CalleeRoots) { - // Now unmark all values that are rooted by the callee after - // refining other values to them. - LS[Num] = 0; - } } void LateLowerGCFrame::ComputeLiveSets(State &S) { @@ -1984,20 +2044,20 @@ void LateLowerGCFrame::ComputeLiveSets(State &S) { Instruction *Safepoint = it.first; BasicBlock *BB = Safepoint->getParent(); BBState &BBS = S.BBStates[BB]; - BitVector LiveAcross = BBS.LiveIn; + LargeSparseBitVector LiveAcross = BBS.LiveIn; LiveAcross &= BBS.LiveOut; - BitVector &LS = S.LiveSets[idx]; + LargeSparseBitVector &LS = S.LiveSets[idx]; LS |= LiveAcross; for (int Live : S.LiveIfLiveOut[idx]) { if (HasBitSet(BBS.LiveOut, Live)) - LS[Live] = 1; + LS.set(Live); } RefineLiveSet(LS, S, S.CalleeRoots[idx]); // If the function has GC preserves, figure out whether we need to // add in any extra live values. if (!S.GCPreserves.empty()) { if (!S.DT) { - S.DT = &getAnalysis().getDomTree(); + S.DT = &GetDT(); } for (auto it2 : S.GCPreserves) { if (!S.DT->dominates(it2.first, Safepoint)) @@ -2014,30 +2074,18 @@ void LateLowerGCFrame::ComputeLiveSets(State &S) { if (OutsideRange) continue; for (unsigned Num : it2.second) { - if (Num >= LS.size()) - LS.resize(Num + 1); - LS[Num] = 1; + LS.set(Num); } } } } // Compute the interference graph - for (int i = 0; i <= S.MaxPtrNumber; ++i) { - SetVector Neighbors; - BitVector NeighborBits(S.MaxPtrNumber); - for (auto it : S.SafepointNumbering) { - const BitVector &LS = S.LiveSets[it.second]; - if ((unsigned)i >= LS.size() || !LS[i]) - continue; - NeighborBits |= LS; - } - for (int Idx = NeighborBits.find_first(); Idx >= 0; Idx = NeighborBits.find_next(Idx)) { - // We explicitly let i be a neighbor of itself, to distinguish - // between being the only value live at a safepoint, vs not - // being live at any safepoint. - Neighbors.insert(Idx); + S.Neighbors.resize(S.MaxPtrNumber+1); + for (auto it : S.SafepointNumbering) { + const LargeSparseBitVector &LS = S.LiveSets[it.second]; + for (int idx : LS) { + S.Neighbors[idx] |= LS; } - S.Neighbors.push_back(Neighbors); } } @@ -2053,8 +2101,8 @@ struct PEOIterator { }; std::vector Elements; std::vector> Levels; - const std::vector> &Neighbors; - PEOIterator(const std::vector> &Neighbors) : Neighbors(Neighbors) { + const std::vector &Neighbors; + PEOIterator(const std::vector &Neighbors) : Neighbors(Neighbors) { // Initialize State std::vector FirstLevel; for (unsigned i = 0; i < Neighbors.size(); ++i) { @@ -2123,8 +2171,8 @@ std::vector LateLowerGCFrame::ColorRoots(const State &S) { to returns_twice */ for (auto it : S.ReturnsTwice) { int Num = S.SafepointNumbering.at(it); - const BitVector &LS = S.LiveSets[Num]; - for (int Idx = LS.find_first(); Idx >= 0; Idx = LS.find_next(Idx)) { + const LargeSparseBitVector &LS = S.LiveSets[Num]; + for (int Idx : LS) { if (Colors[Idx] == -1) Colors[Idx] = PreAssignedColors++; } @@ -2160,7 +2208,7 @@ std::vector LateLowerGCFrame::ColorRoots(const State &S) { } // Size of T is assumed to be `sizeof(void*)` -Value *LateLowerGCFrame::EmitTagPtr(IRBuilder<> &builder, Type *T, Value *V) +Value *LateLowerGCFrame::EmitTagPtr(IRBuilder<> &builder, Type *T, Type *T_size, Value *V) { assert(T == T_size || isa(T)); auto TV = cast(V->getType()); @@ -2168,18 +2216,19 @@ Value *LateLowerGCFrame::EmitTagPtr(IRBuilder<> &builder, Type *T, Value *V) return builder.CreateInBoundsGEP(T, cast, ConstantInt::get(T_size, -1)); } -Value *LateLowerGCFrame::EmitLoadTag(IRBuilder<> &builder, Value *V) +Value *LateLowerGCFrame::EmitLoadTag(IRBuilder<> &builder, Type *T_size, Value *V) { - auto addr = EmitTagPtr(builder, T_size, V); - LoadInst *load = builder.CreateAlignedLoad(T_size, addr, Align(sizeof(size_t))); + auto addr = EmitTagPtr(builder, T_size, T_size, V); + auto &M = *builder.GetInsertBlock()->getModule(); + LoadInst *load = builder.CreateAlignedLoad(T_size, addr, M.getDataLayout().getPointerABIAlignment(0)); load->setOrdering(AtomicOrdering::Unordered); load->setMetadata(LLVMContext::MD_tbaa, tbaa_tag); MDBuilder MDB(load->getContext()); auto *NullInt = ConstantInt::get(T_size, 0); - // We can be sure that the tag is larger than page size. + // We can be sure that the tag is at least 16 (1<<4) // Hopefully this is enough to convince LLVM that the value is still not NULL // after masking off the tag bits - auto *NonNullInt = ConstantExpr::getAdd(NullInt, ConstantInt::get(T_size, 4096)); + auto *NonNullInt = ConstantExpr::getAdd(NullInt, ConstantInt::get(T_size, 16)); load->setMetadata(LLVMContext::MD_range, MDB.createRange(NonNullInt, NullInt)); return load; } @@ -2227,8 +2276,9 @@ MDNode *createMutableTBAAAccessTag(MDNode *Tag) { return MDBuilder(Tag->getContext()).createMutableTBAAAccessTag(Tag); } - -bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { +bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { + auto T_int32 = Type::getInt32Ty(F.getContext()); + auto T_size = F.getParent()->getDataLayout().getIntPtrType(F.getContext()); bool ChangesMade = false; // We create one alloca for all the jlcall frames that haven't been processed // yet. LLVM would merge them anyway later, so might as well save it a bit @@ -2237,9 +2287,10 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { Instruction *StartOff = &*(F.getEntryBlock().begin()); PointerType *T_pprjlvalue = nullptr; AllocaInst *Frame = nullptr; + unsigned allocaAddressSpace = F.getParent()->getDataLayout().getAllocaAddrSpace(); if (T_prjlvalue) { T_pprjlvalue = T_prjlvalue->getPointerTo(); - Frame = new AllocaInst(T_prjlvalue, 0, + Frame = new AllocaInst(T_prjlvalue, allocaAddressSpace, ConstantInt::get(T_int32, maxframeargs), "", StartOff); } std::vector write_barriers; @@ -2275,14 +2326,13 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { ++it; continue; } - CallingConv::ID CC = CI->getCallingConv(); Value *callee = CI->getCalledOperand(); if (callee && (callee == gc_flush_func || callee == gc_preserve_begin_func || callee == gc_preserve_end_func)) { /* No replacement */ } else if (pointer_from_objref_func != nullptr && callee == pointer_from_objref_func) { auto *obj = CI->getOperand(0); - auto *ASCI = new AddrSpaceCastInst(obj, T_pjlvalue, "", CI); + auto *ASCI = new AddrSpaceCastInst(obj, JuliaType::get_pjlvalue_ty(obj->getContext()), "", CI); ASCI->takeName(CI); CI->replaceAllUsesWith(ASCI); UpdatePtrNumbering(CI, ASCI, S); @@ -2296,7 +2346,7 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { // Create a call to the `julia.gc_alloc_bytes` intrinsic, which is like // `julia.gc_alloc_obj` except it doesn't set the tag. auto allocBytesIntrinsic = getOrDeclare(jl_intrinsics::GCAllocBytes); - auto ptlsLoad = get_current_ptls_from_task(builder, CI->getArgOperand(0), tbaa_gcframe); + auto ptlsLoad = get_current_ptls_from_task(builder, T_size, CI->getArgOperand(0), tbaa_gcframe); auto ptls = builder.CreateBitCast(ptlsLoad, Type::getInt8PtrTy(builder.getContext())); auto newI = builder.CreateCall( allocBytesIntrinsic, @@ -2348,8 +2398,9 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { } } // Set the tag. + auto &M = *builder.GetInsertBlock()->getModule(); StoreInst *store = builder.CreateAlignedStore( - tag, EmitTagPtr(builder, tag_type, newI), Align(sizeof(size_t))); + tag, EmitTagPtr(builder, tag_type, T_size, newI), M.getDataLayout().getPointerABIAlignment(0)); store->setOrdering(AtomicOrdering::Unordered); store->setMetadata(LLVMContext::MD_tbaa, tbaa_tag); @@ -2363,9 +2414,9 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { assert(CI->arg_size() == 1); IRBuilder<> builder(CI); builder.SetCurrentDebugLocation(CI->getDebugLoc()); - auto tag = EmitLoadTag(builder, CI->getArgOperand(0)); + auto tag = EmitLoadTag(builder, T_size, CI->getArgOperand(0)); auto masked = builder.CreateAnd(tag, ConstantInt::get(T_size, ~(uintptr_t)15)); - auto typ = builder.CreateAddrSpaceCast(builder.CreateIntToPtr(masked, T_pjlvalue), + auto typ = builder.CreateAddrSpaceCast(builder.CreateIntToPtr(masked, JuliaType::get_pjlvalue_ty(masked->getContext())), T_prjlvalue); typ->takeName(CI); CI->replaceAllUsesWith(typ); @@ -2378,20 +2429,22 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { ChangesMade = true; ++it; continue; - } else if (CC == JLCALL_F_CC || - CC == JLCALL_F2_CC) { + } else if ((call_func && callee == call_func) || + (call2_func && callee == call2_func)) { assert(T_prjlvalue); size_t nargs = CI->arg_size(); - size_t nframeargs = nargs; - if (CC == JLCALL_F_CC) + size_t nframeargs = nargs-1; + if (callee == call_func) nframeargs -= 1; - else if (CC == JLCALL_F2_CC) + else if (callee == call2_func) nframeargs -= 2; SmallVector ReplacementArgs; auto arg_it = CI->arg_begin(); assert(arg_it != CI->arg_end()); + Value *new_callee = *(arg_it++); + assert(arg_it != CI->arg_end()); ReplacementArgs.push_back(*(arg_it++)); - if (CC != JLCALL_F_CC) { + if (callee == call2_func) { assert(arg_it != CI->arg_end()); ReplacementArgs.push_back(*(arg_it++)); } @@ -2399,32 +2452,33 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { int slot = 0; IRBuilder<> Builder (CI); for (; arg_it != CI->arg_end(); ++arg_it) { - Builder.CreateAlignedStore(*arg_it, + // Julia emits IR with proper pointer types here, but because + // the julia.call signature is varargs, the optimizer is allowed + // to rewrite pointee types. It'll go away with opaque pointer + // types anyway. + Builder.CreateAlignedStore(Builder.CreateBitCast(*arg_it, T_prjlvalue), Builder.CreateInBoundsGEP(T_prjlvalue, Frame, ConstantInt::get(T_int32, slot++)), Align(sizeof(void*))); } ReplacementArgs.push_back(nframeargs == 0 ? (llvm::Value*)ConstantPointerNull::get(T_pprjlvalue) : - (llvm::Value*)Frame); + (allocaAddressSpace ? Builder.CreateAddrSpaceCast(Frame, T_prjlvalue->getPointerTo(0)) : Frame)); ReplacementArgs.push_back(ConstantInt::get(T_int32, nframeargs)); - if (CC == JLCALL_F2_CC) { + if (callee == call2_func) { // move trailing arg to the end now Value *front = ReplacementArgs.front(); ReplacementArgs.erase(ReplacementArgs.begin()); ReplacementArgs.push_back(front); } - FunctionType *FTy; - if (CC == JLCALL_F_CC) // jl_fptr_args - FTy = FunctionType::get(T_prjlvalue, {T_prjlvalue, T_pprjlvalue, T_int32}, false); - else // CC == JLCALL_F2_CC // jl_invoke - FTy = FunctionType::get(T_prjlvalue, {T_prjlvalue, T_pprjlvalue, T_int32, T_prjlvalue}, false); - Value *newFptr = Builder.CreateBitCast(callee, FTy->getPointerTo()); - CallInst *NewCall = CallInst::Create(FTy, newFptr, ReplacementArgs, "", CI); + FunctionType *FTy = 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 old_attrs = CI->getAttributes(); - NewCall->setAttributes(AttributeList::get(CI->getContext(), - getFnAttrs(old_attrs), - getRetAttrs(old_attrs), {})); + auto callattrs = CI->getAttributes(); + callattrs = AttributeList::get(CI->getContext(), getFnAttrs(callattrs), getRetAttrs(callattrs), {}); + if (auto new_callee = CI->getCalledFunction()) // get the parameter attributes from the function target (if possible) + callattrs = AttributeList::get(CI->getContext(), {callattrs, new_callee->getAttributes()}); + NewCall->setAttributes(callattrs); + NewCall->takeName(CI); NewCall->copyMetadata(*CI); CI->replaceAllUsesWith(NewCall); UpdatePtrNumbering(CI, NewCall, S); @@ -2454,16 +2508,19 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { CI->eraseFromParent(); continue; } + if (CFGModified) { + *CFGModified = true; + } IRBuilder<> builder(CI); builder.SetCurrentDebugLocation(CI->getDebugLoc()); - auto parBits = builder.CreateAnd(EmitLoadTag(builder, parent), 3); + auto parBits = builder.CreateAnd(EmitLoadTag(builder, T_size, parent), 3); auto parOldMarked = builder.CreateICmpEQ(parBits, ConstantInt::get(T_size, 3)); auto mayTrigTerm = SplitBlockAndInsertIfThen(parOldMarked, CI, false); builder.SetInsertPoint(mayTrigTerm); Value *anyChldNotMarked = NULL; for (unsigned i = 1; i < CI->arg_size(); i++) { Value *child = CI->getArgOperand(i); - Value *chldBit = builder.CreateAnd(EmitLoadTag(builder, child), 1); + Value *chldBit = builder.CreateAnd(EmitLoadTag(builder, T_size, child), 1); Value *chldNotMarked = builder.CreateICmpEQ(chldBit, ConstantInt::get(T_size, 0)); anyChldNotMarked = anyChldNotMarked ? builder.CreateOr(anyChldNotMarked, chldNotMarked) : chldNotMarked; } @@ -2473,7 +2530,12 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { auto trigTerm = SplitBlockAndInsertIfThen(anyChldNotMarked, mayTrigTerm, false, MDB.createBranchWeights(Weights)); builder.SetInsertPoint(trigTerm); - builder.CreateCall(getOrDeclare(jl_intrinsics::queueGCRoot), parent); + if (CI->getCalledOperand() == write_barrier_func) { + builder.CreateCall(getOrDeclare(jl_intrinsics::queueGCRoot), parent); + } + else { + assert(false); + } CI->eraseFromParent(); } if (maxframeargs == 0 && Frame) { @@ -2485,7 +2547,7 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S) { return ChangesMade; } -static void AddInPredLiveOuts(BasicBlock *BB, BitVector &LiveIn, State &S) +static void AddInPredLiveOuts(BasicBlock *BB, LargeSparseBitVector &LiveIn, State &S) { bool First = true; std::set Visited; @@ -2496,7 +2558,7 @@ static void AddInPredLiveOuts(BasicBlock *BB, BitVector &LiveIn, State &S) WorkList.pop_back(); // Nothing is live at function entry if (BB == &S.F->getEntryBlock()) { - LiveIn.reset(); + LiveIn.clear(); return; } for (BasicBlock *Pred : predecessors(BB)) { @@ -2526,7 +2588,7 @@ void LateLowerGCFrame::PlaceGCFrameStore(State &S, unsigned R, unsigned MinColor // Get the slot address. auto slotAddress = CallInst::Create( getOrDeclare(jl_intrinsics::getGCFrameSlot), - {GCFrame, ConstantInt::get(T_int32, Colors[R] + MinColorRoot)}, + {GCFrame, ConstantInt::get(Type::getInt32Ty(InsertBefore->getContext()), Colors[R] + MinColorRoot)}, "", InsertBefore); Value *Val = GetPtrForNumber(S, R, InsertBefore); @@ -2546,13 +2608,13 @@ void LateLowerGCFrame::PlaceGCFrameStores(State &S, unsigned MinColorRoot, if (!BBS.HasSafepoint) { continue; } - BitVector LiveIn; + LargeSparseBitVector LiveIn; AddInPredLiveOuts(&BB, LiveIn, S); - const BitVector *LastLive = &LiveIn; + const LargeSparseBitVector *LastLive = &LiveIn; for(auto rit = BBS.Safepoints.rbegin(); rit != BBS.Safepoints.rend(); ++rit ) { - const BitVector &NowLive = S.LiveSets[*rit]; - for (int Idx = NowLive.find_first(); Idx >= 0; Idx = NowLive.find_next(Idx)) { + const LargeSparseBitVector &NowLive = S.LiveSets[*rit]; + for (int Idx : NowLive) { if (!HasBitSet(*LastLive, Idx)) { PlaceGCFrameStore(S, Idx, MinColorRoot, Colors, GCFrame, S.ReverseSafepointNumbering[*rit]); @@ -2565,6 +2627,7 @@ void LateLowerGCFrame::PlaceGCFrameStores(State &S, unsigned MinColorRoot, void LateLowerGCFrame::PlaceRootsAndUpdateCalls(std::vector &Colors, State &S, std::map>) { auto F = S.F; + auto T_int32 = Type::getInt32Ty(F->getContext()); int MaxColor = -1; for (auto C : Colors) if (C > MaxColor) @@ -2586,9 +2649,10 @@ void LateLowerGCFrame::PlaceRootsAndUpdateCalls(std::vector &Colors, State // Replace Allocas unsigned AllocaSlot = 2; // first two words are metadata - auto replace_alloca = [this, gcframe, &AllocaSlot](AllocaInst *&AI) { + auto replace_alloca = [this, gcframe, &AllocaSlot, T_int32](AllocaInst *&AI) { // Pick a slot for the alloca. - unsigned align = AI->getAlignment() / sizeof(void*); // TODO: use DataLayout pointer size + AI->getAlign(); + unsigned align = AI->getAlign().value() / sizeof(void*); // TODO: use DataLayout pointer size assert(align <= 16 / sizeof(void*) && "Alignment exceeds llvm-final-gc-lowering abilities"); if (align > 1) AllocaSlot = LLT_ALIGN(AllocaSlot, align); @@ -2658,51 +2722,79 @@ void LateLowerGCFrame::PlaceRootsAndUpdateCalls(std::vector &Colors, State // Insert GC frame stores PlaceGCFrameStores(S, AllocaSlot - 2, Colors, gcframe); // Insert GCFrame pops - for(Function::iterator I = F->begin(), E = F->end(); I != E; ++I) { - if (isa(I->getTerminator())) { + for (auto &BB : *F) { + if (isa(BB.getTerminator())) { auto popGcframe = CallInst::Create( getOrDeclare(jl_intrinsics::popGCFrame), {gcframe}); - popGcframe->insertBefore(I->getTerminator()); + popGcframe->insertBefore(BB.getTerminator()); } } } } -bool LateLowerGCFrame::doInitialization(Module &M) { - // Initialize platform-agnostic references. - initAll(M); - return true; -} - -bool LateLowerGCFrame::runOnFunction(Function &F) { +bool LateLowerGCFrame::runOnFunction(Function &F, bool *CFGModified) { + initAll(*F.getParent()); LLVM_DEBUG(dbgs() << "GC ROOT PLACEMENT: Processing function " << F.getName() << "\n"); - // Check availability of functions again since they might have been deleted. - initFunctions(*F.getParent()); - if (!pgcstack_getter) - return CleanupIR(F); + if (!pgcstack_getter && !adoptthread_func) + return CleanupIR(F, nullptr, CFGModified); pgcstack = getPGCstack(F); if (!pgcstack) - return CleanupIR(F); + return CleanupIR(F, nullptr, CFGModified); State S = LocalScan(F); ComputeLiveness(S); std::vector Colors = ColorRoots(S); std::map> CallFrames; // = OptimizeCallFrames(S, Ordering); PlaceRootsAndUpdateCalls(Colors, S, CallFrames); - CleanupIR(F, &S); + CleanupIR(F, &S, CFGModified); return true; } -char LateLowerGCFrame::ID = 0; -static RegisterPass X("LateLowerGCFrame", "Late Lower GCFrame Pass", false, false); +bool LateLowerGCFrameLegacy::runOnFunction(Function &F) { + auto GetDT = [this]() -> DominatorTree & { + return getAnalysis().getDomTree(); + }; + auto lateLowerGCFrame = LateLowerGCFrame(GetDT); + bool modified = lateLowerGCFrame.runOnFunction(F); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + return modified; +} + +PreservedAnalyses LateLowerGC::run(Function &F, FunctionAnalysisManager &AM) +{ + auto GetDT = [&AM, &F]() -> DominatorTree & { + return AM.getResult(F); + }; + auto lateLowerGCFrame = LateLowerGCFrame(GetDT); + bool CFGModified = false; + bool modified = lateLowerGCFrame.runOnFunction(F, &CFGModified); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + if (modified) { + if (CFGModified) { + return PreservedAnalyses::none(); + } else { + return PreservedAnalyses::allInSet(); + } + } + return PreservedAnalyses::all(); +} + + +char LateLowerGCFrameLegacy::ID = 0; +static RegisterPass X("LateLowerGCFrame", "Late Lower GCFrame Pass", false, false); Pass *createLateLowerGCFramePass() { - return new LateLowerGCFrame(); + return new LateLowerGCFrameLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddLateLowerGCFramePass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddLateLowerGCFramePass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createLateLowerGCFramePass()); } diff --git a/src/llvm-lower-handlers.cpp b/src/llvm-lower-handlers.cpp index 324c591f77be8..57fb6ab1c7ed6 100644 --- a/src/llvm-lower-handlers.cpp +++ b/src/llvm-lower-handlers.cpp @@ -1,11 +1,14 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "llvm-version.h" +#include "passes.h" #include #include #include +#include +#include #include #include #include @@ -15,16 +18,20 @@ #include #include #include +#include #include #include #include #include "julia.h" #include "julia_assert.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" +#include #define DEBUG_TYPE "lower_handlers" #undef DEBUG +STATISTIC(MaxExceptionHandlerDepth, "Maximum nesting of exception handlers"); +STATISTIC(ExceptionHandlerBuffers, "Number of exception handler buffers inserted"); using namespace llvm; @@ -69,28 +76,13 @@ using namespace llvm; * handler structures to tell LLVM that it is free to re-use the stack slot * while the handler is not being used. */ -struct LowerExcHandlers : public FunctionPass { - static char ID; - LowerExcHandlers() : FunctionPass(ID) - {} - -private: - Function *except_enter_func; - Function *leave_func; - Function *jlenter_func; - Function *setjmp_func; - Function *lifetime_start; - Function *lifetime_end; - - bool doInitialization(Module &M) override; - bool runOnFunction(Function &F) override; -}; +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) +static void ensure_enter_function(Module &M, const Triple &TT) { auto T_int8 = Type::getInt8Ty(M.getContext()); auto T_pint8 = PointerType::get(T_int8, 0); @@ -105,33 +97,29 @@ static void ensure_enter_function(Module &M) if (!M.getNamedValue(jl_setjmp_name)) { std::vector args2(0); args2.push_back(T_pint8); -#ifndef _OS_WINDOWS_ - args2.push_back(T_int32); -#endif + if (!TT.isOSWindows()) { + args2.push_back(T_int32); + } Function::Create(FunctionType::get(T_int32, args2, false), Function::ExternalLinkage, jl_setjmp_name, &M) ->addFnAttr(Attribute::ReturnsTwice); } } -bool LowerExcHandlers::doInitialization(Module &M) { - except_enter_func = M.getFunction("julia.except_enter"); +static bool lowerExcHandlers(Function &F) { + Module &M = *F.getParent(); + Triple TT(M.getTargetTriple()); + Function *except_enter_func = M.getFunction("julia.except_enter"); if (!except_enter_func) - return false; - ensure_enter_function(M); - leave_func = M.getFunction(XSTR(jl_pop_handler)); - jlenter_func = M.getFunction(XSTR(jl_enter_handler)); - setjmp_func = M.getFunction(jl_setjmp_name); + return false; // No EH frames in this module + ensure_enter_function(M, TT); + Function *leave_func = M.getFunction(XSTR(jl_pop_handler)); + Function *jlenter_func = M.getFunction(XSTR(jl_enter_handler)); + Function *setjmp_func = M.getFunction(jl_setjmp_name); auto T_pint8 = Type::getInt8PtrTy(M.getContext(), 0); - lifetime_start = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_start, { T_pint8 }); - lifetime_end = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_end, { T_pint8 }); - return true; -} - -bool LowerExcHandlers::runOnFunction(Function &F) { - if (!except_enter_func) - return false; // No EH frames in this module + Function *lifetime_start = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_start, { T_pint8 }); + Function *lifetime_end = Intrinsic::getDeclaration(&M, Intrinsic::lifetime_end, { T_pint8 }); /* Step 1: EH Depth Numbering */ std::map EnterDepth; @@ -174,6 +162,8 @@ bool LowerExcHandlers::runOnFunction(Function &F) { /* Remember the depth at the BB boundary */ ExitDepth[BB] = Depth; } + MaxExceptionHandlerDepth.updateMax(MaxDepth); + ExceptionHandlerBuffers += MaxDepth; /* Step 2: EH Frame lowering */ // Allocate stack space for each handler. We allocate these as separate @@ -184,17 +174,24 @@ bool LowerExcHandlers::runOnFunction(Function &F) { Value *handler_sz64 = ConstantInt::get(Type::getInt64Ty(F.getContext()), sizeof(jl_handler_t)); Instruction *firstInst = &F.getEntryBlock().front(); - std::vector buffs; + std::vector buffs; + unsigned allocaAddressSpace = F.getParent()->getDataLayout().getAllocaAddrSpace(); for (int i = 0; i < MaxDepth; ++i) { - auto *buff = new AllocaInst(Type::getInt8Ty(F.getContext()), 0, + auto *buff = new AllocaInst(Type::getInt8Ty(F.getContext()), allocaAddressSpace, handler_sz, Align(16), "", firstInst); - buffs.push_back(buff); + if (allocaAddressSpace) { + AddrSpaceCastInst *buff_casted = new AddrSpaceCastInst(buff, Type::getInt8PtrTy(F.getContext(), AddressSpace::Generic)); + buff_casted->insertAfter(buff); + buffs.push_back(buff_casted); + } else { + buffs.push_back(buff); + } } // Lower enter funcs for (auto it : EnterDepth) { assert(it.second >= 0); - AllocaInst *buff = buffs[it.second]; + Instruction *buff = buffs[it.second]; CallInst *enter = it.first; auto new_enter = CallInst::Create(jlenter_func, buff, "", enter); Value *lifetime_args[] = { @@ -202,14 +199,15 @@ bool LowerExcHandlers::runOnFunction(Function &F) { buff }; CallInst::Create(lifetime_start, lifetime_args, "", new_enter); -#ifndef _OS_WINDOWS_ - // For LLVM 3.3 compatibility - Value *args[] = {buff, - ConstantInt::get(Type::getInt32Ty(F.getContext()), 0)}; - auto sj = CallInst::Create(setjmp_func, args, "", enter); -#else - auto sj = CallInst::Create(setjmp_func, buff, "", enter); -#endif + 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); + } else { + sj = CallInst::Create(setjmp_func, buff, "", enter); + } // We need to mark this on the call site as well. See issue #6757 sj->setCanReturnTwice(); if (auto dbg = enter->getMetadata(LLVMContext::MD_dbg)) { @@ -236,17 +234,46 @@ bool LowerExcHandlers::runOnFunction(Function &F) { return true; } -char LowerExcHandlers::ID = 0; -static RegisterPass X("LowerExcHandlers", "Lower Julia Exception Handlers", +} // anonymous namespace + +PreservedAnalyses LowerExcHandlers::run(Function &F, FunctionAnalysisManager &AM) +{ + bool modified = lowerExcHandlers(F); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + if (modified) { + return PreservedAnalyses::allInSet(); + } + return PreservedAnalyses::all(); +} + + +struct LowerExcHandlersLegacy : public FunctionPass { + static char ID; + LowerExcHandlersLegacy() : FunctionPass(ID) + {} + bool runOnFunction(Function &F) { + bool modified = lowerExcHandlers(F); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + return modified; + } +}; + +char LowerExcHandlersLegacy::ID = 0; +static RegisterPass X("LowerExcHandlers", "Lower Julia Exception Handlers", false /* Only looks at CFG */, false /* Analysis Pass */); Pass *createLowerExcHandlersPass() { - return new LowerExcHandlers(); + return new LowerExcHandlersLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddLowerExcHandlersPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddLowerExcHandlersPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createLowerExcHandlersPass()); } diff --git a/src/llvm-muladd.cpp b/src/llvm-muladd.cpp index 7166698db356f..efe0acb36f1fc 100644 --- a/src/llvm-muladd.cpp +++ b/src/llvm-muladd.cpp @@ -1,12 +1,13 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -#define DEBUG_TYPE "combine_muladd" -#undef DEBUG #include "llvm-version.h" +#include "passes.h" #include #include +#include +#include #include #include #include @@ -16,13 +17,24 @@ #include #include #include +#include #include #include #include "julia.h" #include "julia_assert.h" +#define DEBUG_TYPE "combine-muladd" +#undef DEBUG + using namespace llvm; +STATISTIC(TotalContracted, "Total number of multiplies marked for FMA"); + +#ifndef __clang_gcanalyzer__ +#define REMARK(remark) ORE.emit(remark) +#else +#define REMARK(remark) (void) 0; +#endif /** * Combine @@ -35,26 +47,40 @@ using namespace llvm; * when `%v0` has no other use */ -// Return true if this function shouldn't be called again on the other operand -// This will always return false on LLVM 5.0+ -static bool checkCombine(Module *m, Instruction *addOp, Value *maybeMul, Value *addend, - bool negadd, bool negres) +// Return true if we changed the mulOp +static bool checkCombine(Value *maybeMul, OptimizationRemarkEmitter &ORE) JL_NOTSAFEPOINT { auto mulOp = dyn_cast(maybeMul); if (!mulOp || mulOp->getOpcode() != Instruction::FMul) return false; - if (!mulOp->hasOneUse()) + if (!mulOp->hasOneUse()) { + LLVM_DEBUG(dbgs() << "mulOp has multiple uses: " << *maybeMul << "\n"); + REMARK([&](){ + return OptimizationRemarkMissed(DEBUG_TYPE, "Multiuse FMul", mulOp) + << "fmul had multiple uses " << ore::NV("fmul", mulOp); + }); return false; + } // On 5.0+ we only need to mark the mulOp as contract and the backend will do the work for us. auto fmf = mulOp->getFastMathFlags(); - fmf.setAllowContract(true); - mulOp->copyFastMathFlags(fmf); + if (!fmf.allowContract()) { + LLVM_DEBUG(dbgs() << "Marking mulOp for FMA: " << *maybeMul << "\n"); + REMARK([&](){ + return OptimizationRemark(DEBUG_TYPE, "Marked for FMA", mulOp) + << "marked for fma " << ore::NV("fmul", mulOp); + }); + ++TotalContracted; + fmf.setAllowContract(true); + mulOp->copyFastMathFlags(fmf); + return true; + } return false; } -static bool combineMulAdd(Function &F) +static bool combineMulAdd(Function &F) JL_NOTSAFEPOINT { - Module *m = F.getParent(); + OptimizationRemarkEmitter ORE(&F); + bool modified = false; for (auto &BB: F) { for (auto it = BB.begin(); it != BB.end();) { auto &I = *it; @@ -63,15 +89,13 @@ static bool combineMulAdd(Function &F) case Instruction::FAdd: { if (!I.isFast()) continue; - checkCombine(m, &I, I.getOperand(0), I.getOperand(1), false, false) || - checkCombine(m, &I, I.getOperand(1), I.getOperand(0), false, false); + modified |= checkCombine(I.getOperand(0), ORE) || checkCombine(I.getOperand(1), ORE); break; } case Instruction::FSub: { if (!I.isFast()) continue; - checkCombine(m, &I, I.getOperand(0), I.getOperand(1), true, false) || - checkCombine(m, &I, I.getOperand(1), I.getOperand(0), true, true); + modified |= checkCombine(I.getOperand(0), ORE) || checkCombine(I.getOperand(1), ORE); break; } default: @@ -79,16 +103,17 @@ static bool combineMulAdd(Function &F) } } } - return true; +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + return modified; } -struct CombineMulAdd : PassInfoMixin { - PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); -}; - -PreservedAnalyses CombineMulAdd::run(Function &F, FunctionAnalysisManager &AM) +PreservedAnalyses CombineMulAdd::run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT { - combineMulAdd(F); + if (combineMulAdd(F)) { + return PreservedAnalyses::allInSet(); + } return PreservedAnalyses::all(); } @@ -114,7 +139,8 @@ Pass *createCombineMulAddPass() return new CombineMulAddLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddCombineMulAddPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddCombineMulAddPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createCombineMulAddPass()); } diff --git a/src/llvm-multiversioning.cpp b/src/llvm-multiversioning.cpp index 57e90a9aa8056..814b13554358c 100644 --- a/src/llvm-multiversioning.cpp +++ b/src/llvm-multiversioning.cpp @@ -1,28 +1,33 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license // Function multi-versioning -#define DEBUG_TYPE "julia_multiversioning" -#undef DEBUG - // LLVM pass to clone function for different archs +//see src/processor.h for documentation of the relevant globals inserted here + #include "llvm-version.h" +#include "passes.h" #include #include #include +#include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include "julia.h" @@ -35,18 +40,19 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" +#define DEBUG_TYPE "julia_multiversioning" +#undef DEBUG + using namespace llvm; -extern Optional always_have_fma(Function&); +extern Optional always_have_fma(Function&, const Triple &TT); namespace { constexpr uint32_t clone_mask = - JL_TARGET_CLONE_LOOP | JL_TARGET_CLONE_SIMD | JL_TARGET_CLONE_MATH | JL_TARGET_CLONE_CPU; - -struct MultiVersioning; + JL_TARGET_CLONE_LOOP | JL_TARGET_CLONE_SIMD | JL_TARGET_CLONE_MATH | JL_TARGET_CLONE_CPU | JL_TARGET_CLONE_FLOAT16; // Treat identical mapping as missing and return `def` in that case. // We mainly need this to identify cloned function using value map after LLVM cloning @@ -60,181 +66,284 @@ Value *map_get(T &&vmap, Value *key, Value *def=nullptr) return val; } -// Iterate through uses of a particular type. -// Recursively scan through `ConstantExpr` and `ConstantAggregate` use. -template -struct ConstantUses { - template - struct Info { - Use *use; - T *val; - // If `samebits == true`, the offset the original value appears in the constant. - size_t offset; - // This specify whether the original value appears in the current value in exactly - // the same bit pattern (with possibly an offset determined by `offset`). - bool samebits; - Info(Use *use, T *val, size_t offset, bool samebits) : - use(use), - val(val), - offset(offset), - samebits(samebits) - { - } - Info(Use *use, size_t offset, bool samebits) : - use(use), - val(cast(use->getUser())), - offset(offset), - samebits(samebits) - { - } - }; - using UseInfo = Info; - struct Frame : Info { - template - Frame(Args &&... args) : - Info(std::forward(args)...), - cur(this->val->use_empty() ? nullptr : &*this->val->use_begin()), - _next(cur ? cur->getNext() : nullptr) - { - } - private: - void next() - { - cur = _next; - if (!cur) - return; - _next = cur->getNext(); +static bool is_vector(FunctionType *ty) +{ + if (ty->getReturnType()->isVectorTy()) + return true; + for (auto arg: ty->params()) { + if (arg->isVectorTy()) { + return true; } - Use *cur; - Use *_next; - friend struct ConstantUses; - }; - ConstantUses(Constant *c, Module &M) - : stack{Frame(nullptr, c, 0u, true)}, - M(M) - { - forward(); } - UseInfo get_info() const - { - auto &top = stack.back(); - return UseInfo(top.cur, top.offset, top.samebits); + return false; +} + +static uint32_t collect_func_info(Function &F, const Triple &TT, bool &has_veccall) +{ + DominatorTree DT(F); + LoopInfo LI(DT); + uint32_t flag = 0; + if (!LI.empty()) + flag |= JL_TARGET_CLONE_LOOP; + if (is_vector(F.getFunctionType())) { + flag |= JL_TARGET_CLONE_SIMD; + has_veccall = true; } - const SmallVector &get_stack() const - { - return stack; + for (auto &bb: F) { + for (auto &I: bb) { + if (auto call = dyn_cast(&I)) { + if (is_vector(call->getFunctionType())) { + has_veccall = true; + flag |= JL_TARGET_CLONE_SIMD; + } + if (auto callee = call->getCalledFunction()) { + auto name = callee->getName(); + if (name.startswith("llvm.muladd.") || name.startswith("llvm.fma.")) { + flag |= JL_TARGET_CLONE_MATH; + } + else if (name.startswith("julia.cpu.")) { + 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()) + flag |= JL_TARGET_CLONE_CPU; + } else { + flag |= JL_TARGET_CLONE_CPU; + } + } + } + } + else if (auto store = dyn_cast(&I)) { + if (store->getValueOperand()->getType()->isVectorTy()) { + flag |= JL_TARGET_CLONE_SIMD; + } + } + else if (I.getType()->isVectorTy()) { + flag |= JL_TARGET_CLONE_SIMD; + } + if (auto mathOp = dyn_cast(&I)) { + if (mathOp->getFastMathFlags().any()) { + flag |= JL_TARGET_CLONE_MATH; + } + } + + for (size_t i = 0; i < I.getNumOperands(); i++) { + if(I.getOperand(i)->getType()->isHalfTy()){ + flag |= JL_TARGET_CLONE_FLOAT16; + } + // Check for BFloat16 when they are added to julia can be done here + } + uint32_t veccall_flags = JL_TARGET_CLONE_SIMD | JL_TARGET_CLONE_MATH | JL_TARGET_CLONE_CPU | JL_TARGET_CLONE_FLOAT16; + if (has_veccall && (flag & veccall_flags) == veccall_flags) { + return flag; + } + } } - void next() - { - stack.back().next(); - forward(); + return flag; +} + +struct TargetSpec { + std::string cpu_name; + std::string cpu_features; + uint32_t base; + uint32_t flags; + + TargetSpec() = default; + + static TargetSpec fromSpec(jl_target_spec_t &spec) { + TargetSpec out; + out.cpu_name = spec.cpu_name; + out.cpu_features = spec.cpu_features; + out.base = spec.base; + out.flags = spec.flags; + return out; } - bool done() - { - return stack.empty(); + + static TargetSpec fromMD(MDTuple *tup) { + TargetSpec out; + assert(tup->getNumOperands() == 4); + out.cpu_name = cast(tup->getOperand(0))->getString().str(); + out.cpu_features = cast(tup->getOperand(1))->getString().str(); + out.base = cast(cast(tup->getOperand(2))->getValue())->getZExtValue(); + out.flags = cast(cast(tup->getOperand(3))->getValue())->getZExtValue(); + return out; + } + + MDNode *toMD(LLVMContext &ctx) const { + return MDTuple::get(ctx, { + MDString::get(ctx, cpu_name), + MDString::get(ctx, cpu_features), + ConstantAsMetadata::get(ConstantInt::get(Type::getInt32Ty(ctx), base)), + ConstantAsMetadata::get(ConstantInt::get(Type::getInt32Ty(ctx), flags)) + }); } -private: - void forward(); - SmallVector stack; - Module &M; }; -template -void ConstantUses::forward() -{ - assert(!stack.empty()); - auto frame = &stack.back(); - const DataLayout &DL = M.getDataLayout(); - auto pop = [&] { - stack.pop_back(); - if (stack.empty()) { - return false; - } - frame = &stack.back(); - return true; - }; - auto push = [&] (Use *use, Constant *c, size_t offset, bool samebits) { - stack.emplace_back(use, c, offset, samebits); - frame = &stack.back(); - }; - auto handle_constaggr = [&] (Use *use, ConstantAggregate *aggr) { - if (!frame->samebits) { - push(use, aggr, 0, false); - return; - } - if (auto strct = dyn_cast(aggr)) { - auto layout = DL.getStructLayout(strct->getType()); - push(use, strct, frame->offset + layout->getElementOffset(use->getOperandNo()), true); - } - else if (auto ary = dyn_cast(aggr)) { - auto elty = ary->getType()->getElementType(); - push(use, ary, frame->offset + DL.getTypeAllocSize(elty) * use->getOperandNo(), true); - } - else if (auto vec = dyn_cast(aggr)) { - auto elty = vec->getType()->getElementType(); - push(use, vec, frame->offset + DL.getTypeAllocSize(elty) * use->getOperandNo(), true); - } - else { - jl_safe_printf("Unknown ConstantAggregate:\n"); - llvm_dump(aggr); - abort(); - } - }; - auto handle_constexpr = [&] (Use *use, ConstantExpr *expr) { - if (!frame->samebits) { - push(use, expr, 0, false); - return; - } - auto opcode = expr->getOpcode(); - if (opcode == Instruction::PtrToInt || opcode == Instruction::IntToPtr || - opcode == Instruction::AddrSpaceCast || opcode == Instruction::BitCast) { - push(use, expr, frame->offset, true); - } - else { - push(use, expr, 0, false); - } - }; - while (true) { - auto use = frame->cur; - if (!use) { - if (!pop()) - return; +static Optional> get_target_specs(Module &M) { + auto md = M.getModuleFlag("julia.mv.specs"); + if (!md) + return None; + auto tup = cast(md); + std::vector out(tup->getNumOperands()); + for (unsigned i = 0; i < tup->getNumOperands(); i++) { + out[i] = TargetSpec::fromMD(cast(tup->getOperand(i).get())); + } + return out; +} + +static void set_target_specs(Module &M, ArrayRef specs) { + std::vector md; + md.reserve(specs.size()); + for (auto &spec: specs) { + md.push_back(spec.toMD(M.getContext())); + } + M.addModuleFlag(Module::Error, "julia.mv.specs", MDTuple::get(M.getContext(), md)); +} + +static void annotate_module_clones(Module &M) { + auto TT = Triple(M.getTargetTriple()); + CallGraph CG(M); + std::vector orig_funcs; + for (auto &F: M) { + if (F.isDeclaration()) continue; + orig_funcs.push_back(&F); + } + bool has_veccall = false; + std::vector specs; + if (auto maybe_specs = get_target_specs(M)) { + specs = std::move(*maybe_specs); + } else { + auto full_specs = jl_get_llvm_clone_targets(); + specs.reserve(full_specs.size()); + for (auto &spec: full_specs) { + specs.push_back(TargetSpec::fromSpec(spec)); } - auto user = use->getUser(); - if (isa(user)) - return; - frame->next(); - if (auto aggr = dyn_cast(user)) { - handle_constaggr(use, aggr); + set_target_specs(M, specs); + } + std::vector clones(orig_funcs.size(), APInt(specs.size(), 0)); + BitVector subtarget_cloned(orig_funcs.size()); + + std::vector func_infos(orig_funcs.size()); + for (unsigned i = 0; i < orig_funcs.size(); i++) { + func_infos[i] = collect_func_info(*orig_funcs[i], TT, has_veccall); + } + for (unsigned i = 1; i < specs.size(); i++) { + if (specs[i].flags & JL_TARGET_CLONE_ALL) { + for (unsigned j = 0; j < orig_funcs.size(); j++) { + clones[j].setBit(i); + } + } else { + unsigned flag = specs[i].flags & clone_mask; + std::set sets[2]; + for (unsigned j = 0; j < orig_funcs.size(); j++) { + if (!(func_infos[j] & flag)) { + continue; + } + sets[0].insert(orig_funcs[j]); + } + std::set all_origs(sets[0]); + auto *cur_set = &sets[0]; + auto *next_set = &sets[1]; + // Reduce dispatch by expand the cloning set to functions that are directly called by + // and calling cloned functions. + while (!cur_set->empty()) { + for (auto orig_f: *cur_set) { + // Use the uncloned function since it's already in the call graph + auto node = CG[orig_f]; + for (const auto &I: *node) { + auto child_node = I.second; + auto orig_child_f = child_node->getFunction(); + if (!orig_child_f) + continue; + // Already cloned + if (all_origs.count(orig_child_f)) + continue; + bool calling_clone = false; + for (const auto &I2: *child_node) { + auto orig_child_f2 = I2.second->getFunction(); + if (!orig_child_f2) + continue; + if (all_origs.count(orig_child_f2)) { + calling_clone = true; + break; + } + } + if (!calling_clone) + continue; + next_set->insert(orig_child_f); + all_origs.insert(orig_child_f); + } + } + std::swap(cur_set, next_set); + next_set->clear(); + } + for (unsigned j = 0; j < orig_funcs.size(); j++) { + if (all_origs.count(orig_funcs[j])) { + clones[j].setBit(i); + subtarget_cloned.set(j); + } + } + } + } + // if there's only one target, we won't need any relocation slots + // but even if there is one clone_all and one non-clone_all, we still need + // to check for relocation slots because we must fixup instruction uses to + // point at the right function. + if (specs.size() > 1) { + for (unsigned i = 0; i < orig_funcs.size(); i++) { + auto &F = *orig_funcs[i]; + if (subtarget_cloned[i] && !ConstantUses(orig_funcs[i], M).done()) { + F.addFnAttr("julia.mv.reloc", ""); + } else { + auto uses = ConstantUses(orig_funcs[i], M); + if (!uses.done()) { + bool slot = false; + for (; !uses.done(); uses.next()) { + if (isa(uses.get_info().val)) { + slot = true; + break; + } + } + if (slot) { + F.addFnAttr("julia.mv.reloc", ""); + } else { + F.addFnAttr("julia.mv.fvar", ""); + } + } + } } - else if (auto expr = dyn_cast(user)) { - handle_constexpr(use, expr); + } + SmallString<128> cloneset; + for (unsigned i = 0; i < orig_funcs.size(); i++) { + if (!clones[i].isZero()) { + auto &F = *orig_funcs[i]; + cloneset.clear(); + clones[i].toStringUnsigned(cloneset, 16); + F.addFnAttr("julia.mv.clones", cloneset); } } + if (has_veccall) { + M.addModuleFlag(Module::Max, "julia.mv.veccall", 1); + } + M.addModuleFlag(Module::Error, "julia.mv.annotated", 1); } struct CloneCtx { struct Target { int idx; - uint32_t flags; std::unique_ptr vmap; // ValueToValueMapTy is not movable.... - // function ids that needs relocation to be initialized - std::set relocs{}; - Target(int idx, const jl_target_spec_t &spec) : + explicit Target(int idx) : idx(idx), - flags(spec.flags), vmap(new ValueToValueMapTy) { } }; struct Group : Target { std::vector clones; - std::set clone_fs; - Group(int base, const jl_target_spec_t &spec) : - Target(base, spec), - clones{}, - clone_fs{} + explicit Group(int base) : + Target(base), + clones{} {} Function *base_func(Function *orig_f) const { @@ -242,138 +351,128 @@ struct CloneCtx { return orig_f; return cast(vmap->lookup(orig_f)); } + + bool has_subtarget_clone(Function *orig_f) const + { + auto base = base_func(orig_f); + for (auto &clone: clones) { + if (map_get(*clone.vmap, base)) + return true; + } + return false; + } }; - CloneCtx(MultiVersioning *pass, Module &M); - void clone_bases(); - void collect_func_infos(); - void clone_all_partials(); + CloneCtx(Module &M, bool allow_bad_fvars); + void prepare_slots(); + void clone_decls(); + void clone_bodies(); void fix_gv_uses(); + void finalize_orig_clone_attr(); void fix_inst_uses(); + void finalize_orig_features(); void emit_metadata(); private: void prepare_vmap(ValueToValueMapTy &vmap); - bool is_vector(FunctionType *ty) const; - void clone_function(Function *F, Function *new_f, ValueToValueMapTy &vmap); - uint32_t collect_func_info(Function &F); - void check_partial(Group &grp, Target &tgt); void clone_partial(Group &grp, Target &tgt); - void add_features(Function *F, StringRef name, StringRef features, uint32_t flags) const; - template - T *add_comdat(T *G) const; - uint32_t get_func_id(Function *F); - template - Constant *rewrite_gv_init(const Stack& stack); - template - Value *rewrite_inst_use(const Stack& stack, Value *replace, Instruction *insert_before); - std::pair get_reloc_slot(Function *F); - Constant *get_ptrdiff32(Constant *ptr, Constant *base) const; - template - Constant *emit_offset_table(const std::vector &vars, StringRef name) const; + uint32_t get_func_id(Function *F) const; + std::pair get_reloc_slot(Function *F) const; void rewrite_alias(GlobalAlias *alias, Function* F); - LLVMContext &ctx; - Type *T_size; - Type *T_int32; - Type *T_void; - PointerType *T_psize; MDNode *tbaa_const; - MultiVersioning *pass; - std::vector specs; + std::vector specs; std::vector groups{}; + std::vector linearized; std::vector fvars; std::vector gvars; Module &M; - // Map from original functiton to one based index in `fvars` + Type *T_size; + Triple TT; + + // Map from original function to one based index in `fvars` std::map func_ids{}; std::vector orig_funcs{}; - std::vector func_infos{}; - std::set cloned{}; // GV addresses and their corresponding function id (i.e. 0-based index in `fvars`) std::vector> gv_relocs{}; // Mapping from function id (i.e. 0-based index in `fvars`) to GVs to be initialized. std::map const_relocs; - // Functions that were referred to by a global alias, and might not have other uses. - std::set alias_relocs; - bool has_veccall{false}; - bool has_cloneall{false}; -}; - -struct MultiVersioning: public ModulePass { - static char ID; - MultiVersioning() - : ModulePass(ID) - {} - -private: - bool runOnModule(Module &M) override; - void getAnalysisUsage(AnalysisUsage &AU) const override - { - AU.addRequired(); - AU.addRequired(); - AU.addPreserved(); - } - friend struct CloneCtx; + std::map extern_relocs; + bool allow_bad_fvars{false}; }; template -static inline std::vector consume_gv(Module &M, const char *name) +static inline std::vector 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()); - auto *ary = cast(gv->getInitializer()); - unsigned nele = ary->getNumOperands(); + ArrayType *Ty = cast(gv->getInitializer()->getType()); + unsigned nele = Ty->getArrayNumElements(); std::vector res(nele); - for (unsigned i = 0; i < nele; i++) - res[i] = cast(ary->getOperand(i)->stripPointerCasts()); + 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->use_empty()) + if (ary && ary->use_empty()) ary->destroyConstant(); return res; } // Collect basic information about targets and functions. -CloneCtx::CloneCtx(MultiVersioning *pass, Module &M) - : ctx(M.getContext()), - T_size(M.getDataLayout().getIntPtrType(ctx, 0)), - T_int32(Type::getInt32Ty(ctx)), - T_void(Type::getVoidTy(ctx)), - T_psize(PointerType::get(T_size, 0)), - tbaa_const(tbaa_make_child_with_context(ctx, "jtbaa_const", nullptr, true).first), - pass(pass), - specs(jl_get_llvm_clone_targets()), - fvars(consume_gv(M, "jl_sysimg_fvars")), - gvars(consume_gv(M, "jl_sysimg_gvars")), - M(M) +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()), + allow_bad_fvars(allow_bad_fvars) { - groups.emplace_back(0, specs[0]); + groups.emplace_back(0); + linearized.resize(specs.size()); + linearized[0] = &groups[0]; + std::vector group_ids(specs.size(), 0); uint32_t ntargets = specs.size(); for (uint32_t i = 1; i < ntargets; i++) { auto &spec = specs[i]; if (spec.flags & JL_TARGET_CLONE_ALL) { - has_cloneall = true; - groups.emplace_back(i, spec); + group_ids[i] = groups.size(); + groups.emplace_back(i); } else { - auto base = spec.base; - bool found = false; - for (auto &grp: groups) { - if (grp.idx == base) { - found = true; - grp.clones.emplace_back(i, spec); - break; - } - } - (void)found; + assert(0 <= spec.base && (unsigned) spec.base < i); + group_ids[i] = group_ids[spec.base]; + groups[group_ids[i]].clones.emplace_back(i); } } + for (auto &grp: groups) { + for (auto &tgt: grp.clones) + linearized[tgt.idx] = &tgt; + linearized[grp.idx] = &grp; + } uint32_t nfvars = fvars.size(); for (uint32_t i = 0; i < nfvars; i++) func_ids[fvars[i]] = i + 1; for (auto &F: M) { - if (F.empty()) + if (F.empty() && !F.hasFnAttribute("julia.mv.clones")) continue; orig_funcs.push_back(&F); } @@ -393,282 +492,132 @@ void CloneCtx::prepare_vmap(ValueToValueMapTy &vmap) } } -void CloneCtx::clone_function(Function *F, Function *new_f, ValueToValueMapTy &vmap) -{ - Function::arg_iterator DestI = new_f->arg_begin(); - for (Function::const_arg_iterator J = F->arg_begin(); J != F->arg_end(); ++J) { - DestI->setName(J->getName()); - vmap[&*J] = &*DestI++; - } - SmallVector Returns; -#if JL_LLVM_VERSION >= 130000 - // We are cloning into the same module - CloneFunctionInto(new_f, F, vmap, CloneFunctionChangeType::GlobalChanges, Returns); -#else - CloneFunctionInto(new_f, F, vmap, true, Returns); -#endif -} - -// Clone all clone_all targets. Makes sure that the base targets are all available. -void CloneCtx::clone_bases() -{ - if (!has_cloneall) - return; - uint32_t ngrps = groups.size(); - for (uint32_t gid = 1; gid < ngrps; gid++) { - auto &grp = groups[gid]; - auto suffix = ".clone_" + std::to_string(grp.idx); - auto &vmap = *grp.vmap; - // Fill in old->new mapping. We need to do this before cloning the function so that - // the intra target calls are automatically fixed up on cloning. - for (auto F: orig_funcs) { - Function *new_f = Function::Create(F->getFunctionType(), F->getLinkage(), - F->getName() + suffix, &M); - new_f->copyAttributesFrom(F); - vmap[F] = new_f; - } - prepare_vmap(vmap); - for (auto F: orig_funcs) { - clone_function(F, cast(vmap.lookup(F)), vmap); - } - } -} - -bool CloneCtx::is_vector(FunctionType *ty) const +void CloneCtx::prepare_slots() { - if (ty->getReturnType()->isVectorTy()) - return true; - for (auto arg: ty->params()) { - if (arg->isVectorTy()) { - return true; - } - } - return false; -} - -uint32_t CloneCtx::collect_func_info(Function &F) -{ - uint32_t flag = 0; - if (!pass->getAnalysis(F).getLoopInfo().empty()) - flag |= JL_TARGET_CLONE_LOOP; - if (is_vector(F.getFunctionType())) { - flag |= JL_TARGET_CLONE_SIMD; - has_veccall = true; - } - for (auto &bb: F) { - for (auto &I: bb) { - if (auto call = dyn_cast(&I)) { - if (is_vector(call->getFunctionType())) { - has_veccall = true; - flag |= JL_TARGET_CLONE_SIMD; - } - if (auto callee = call->getCalledFunction()) { - auto name = callee->getName(); - if (name.startswith("llvm.muladd.") || name.startswith("llvm.fma.")) { - flag |= JL_TARGET_CLONE_MATH; - } - else if (name.startswith("julia.cpu.")) { - 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).hasValue()) - flag |= JL_TARGET_CLONE_CPU; - } else { - flag |= JL_TARGET_CLONE_CPU; - } - } - } - } - else if (auto store = dyn_cast(&I)) { - if (store->getValueOperand()->getType()->isVectorTy()) { - flag |= JL_TARGET_CLONE_SIMD; - } - } - else if (I.getType()->isVectorTy()) { - flag |= JL_TARGET_CLONE_SIMD; - } - if (auto mathOp = dyn_cast(&I)) { - if (mathOp->getFastMathFlags().any()) { - flag |= JL_TARGET_CLONE_MATH; - } + for (auto &F : orig_funcs) { + if (F->hasFnAttribute("julia.mv.reloc")) { + assert(F->hasFnAttribute("julia.mv.clones")); + GlobalVariable *GV = new GlobalVariable(M, F->getType(), false, GlobalValue::ExternalLinkage, nullptr, F->getName() + ".reloc_slot"); + GV->setVisibility(GlobalValue::HiddenVisibility); + GV->setDSOLocal(true); + if (F->isDeclaration()) { + extern_relocs[F] = GV; } - if (has_veccall && (flag & JL_TARGET_CLONE_SIMD) && (flag & JL_TARGET_CLONE_MATH)) { - return flag; + else { + auto id = get_func_id(F); + const_relocs[id] = GV; + GV->setInitializer(Constant::getNullValue(F->getType())); } } } - return flag; } -void CloneCtx::collect_func_infos() +void CloneCtx::clone_decls() { - uint32_t nfuncs = orig_funcs.size(); - func_infos.resize(nfuncs); - for (uint32_t i = 0; i < nfuncs; i++) { - func_infos[i] = collect_func_info(*orig_funcs[i]); - } -} - -void CloneCtx::clone_all_partials() -{ - // First decide what to clone - // Do this before actually cloning the functions - // so that the call graph is easier to understand - for (auto &grp: groups) { - for (auto &tgt: grp.clones) { - check_partial(grp, tgt); - } - } - for (auto &grp: groups) { - for (auto &tgt: grp.clones) - clone_partial(grp, tgt); - // Also set feature strings for base target functions - // now that all the actual cloning is done. - auto &base_spec = specs[grp.idx]; - for (auto orig_f: orig_funcs) { - add_features(grp.base_func(orig_f), base_spec.cpu_name, - base_spec.cpu_features, base_spec.flags); - } + std::vector suffixes(specs.size()); + for (unsigned i = 1; i < specs.size(); i++) { + suffixes[i] = "." + std::to_string(i); } - func_infos.clear(); // We don't need this anymore -} - -void CloneCtx::check_partial(Group &grp, Target &tgt) -{ - auto flag = specs[tgt.idx].flags & clone_mask; - auto suffix = ".clone_" + std::to_string(tgt.idx); - auto &vmap = *tgt.vmap; - uint32_t nfuncs = func_infos.size(); - - std::set all_origs; - // Use a simple heuristic to decide which function we need to clone. - for (uint32_t i = 0; i < nfuncs; i++) { - if (!(func_infos[i] & flag)) + for (auto &F : orig_funcs) { + if (!F->hasFnAttribute("julia.mv.clones")) continue; - auto orig_f = orig_funcs[i]; - // Fill in old->new mapping. We need to do this before cloning the function so that - // the intra target calls are automatically fixed up on cloning. - auto F = grp.base_func(orig_f); - Function *new_f = Function::Create(F->getFunctionType(), F->getLinkage(), - F->getName() + suffix, &M); - new_f->copyAttributesFrom(F); - vmap[F] = new_f; - if (!has_cloneall) - cloned.insert(orig_f); - grp.clone_fs.insert(i); - all_origs.insert(orig_f); - } - std::set sets[2]{all_origs, std::set{}}; - auto *cur_set = &sets[0]; - auto *next_set = &sets[1]; - // Reduce dispatch by expand the cloning set to functions that are directly called by - // and calling cloned functions. - auto &graph = pass->getAnalysis().getCallGraph(); - while (!cur_set->empty()) { - for (auto orig_f: *cur_set) { - // Use the uncloned function since it's already in the call graph - auto node = graph[orig_f]; - for (const auto &I: *node) { - auto child_node = I.second; - auto orig_child_f = child_node->getFunction(); - if (!orig_child_f) - continue; - // Already cloned - if (all_origs.count(orig_child_f)) - continue; - bool calling_clone = false; - for (const auto &I2: *child_node) { - auto orig_child_f2 = I2.second->getFunction(); - if (!orig_child_f2) - continue; - if (all_origs.count(orig_child_f2)) { - calling_clone = true; - break; - } - } - if (!calling_clone) - continue; - next_set->insert(orig_child_f); - all_origs.insert(orig_child_f); - auto child_f = grp.base_func(orig_child_f); - Function *new_f = Function::Create(child_f->getFunctionType(), - child_f->getLinkage(), - child_f->getName() + suffix, &M); - new_f->copyAttributesFrom(child_f); - vmap[child_f] = new_f; + APInt clones(specs.size(), F->getFnAttribute("julia.mv.clones").getValueAsString(), 16); + for (unsigned i = 1; i < specs.size(); i++) { + if (!clones[i]) { + continue; } - } - std::swap(cur_set, next_set); - next_set->clear(); - } - for (uint32_t i = 0; i < nfuncs; i++) { - // Only need to handle expanded functions - if (func_infos[i] & flag) - continue; - auto orig_f = orig_funcs[i]; - if (all_origs.count(orig_f)) { - if (!has_cloneall) - cloned.insert(orig_f); - grp.clone_fs.insert(i); + auto new_F = Function::Create(F->getFunctionType(), F->getLinkage(), F->getName() + suffixes[i], &M); + new_F->copyAttributesFrom(F); + new_F->setVisibility(F->getVisibility()); + new_F->setDSOLocal(true); + auto base_func = F; + 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; } } } -void CloneCtx::clone_partial(Group &grp, Target &tgt) +static void clone_function(Function *F, Function *new_f, ValueToValueMapTy &vmap) { - auto &spec = specs[tgt.idx]; - auto &vmap = *tgt.vmap; - uint32_t nfuncs = orig_funcs.size(); - prepare_vmap(vmap); - for (uint32_t i = 0; i < nfuncs; i++) { - auto orig_f = orig_funcs[i]; - auto F = grp.base_func(orig_f); - if (auto new_v = map_get(vmap, F)) { - auto new_f = cast(new_v); - assert(new_f != F); - clone_function(F, new_f, vmap); - // We can set the feature strings now since no one is going to - // clone these functions again. - add_features(new_f, spec.cpu_name, spec.cpu_features, spec.flags); - } + Function::arg_iterator DestI = new_f->arg_begin(); + for (Function::const_arg_iterator J = F->arg_begin(); J != F->arg_end(); ++J) { + DestI->setName(J->getName()); + vmap[&*J] = &*DestI++; } + SmallVector Returns; + // We are cloning into the same module + CloneFunctionInto(new_f, F, vmap, CloneFunctionChangeType::GlobalChanges, Returns); } -void CloneCtx::add_features(Function *F, StringRef name, StringRef features, uint32_t flags) const +static void add_features(Function *F, TargetSpec &spec) { auto attr = F->getFnAttribute("target-features"); if (attr.isStringAttribute()) { std::string new_features(attr.getValueAsString()); new_features += ","; - new_features += features; + new_features += spec.cpu_features; F->addFnAttr("target-features", new_features); } else { - F->addFnAttr("target-features", features); + F->addFnAttr("target-features", spec.cpu_features); } - F->addFnAttr("target-cpu", name); + F->addFnAttr("target-cpu", spec.cpu_name); if (!F->hasFnAttribute(Attribute::OptimizeNone)) { - if (flags & JL_TARGET_OPTSIZE) { + if (spec.flags & JL_TARGET_OPTSIZE) { F->addFnAttr(Attribute::OptimizeForSize); } - else if (flags & JL_TARGET_MINSIZE) { + else if (spec.flags & JL_TARGET_MINSIZE) { F->addFnAttr(Attribute::MinSize); } } } -uint32_t CloneCtx::get_func_id(Function *F) +void CloneCtx::clone_bodies() { - auto &ref = func_ids[F]; - if (!ref) { - fvars.push_back(F); - ref = fvars.size(); + for (auto F : orig_funcs) { + for (unsigned i = 0; i < groups.size(); i++) { + Function *group_F = F; + if (i != 0) { + group_F = groups[i].base_func(F); + if (!F->isDeclaration()) { + clone_function(F, group_F, *groups[i].vmap); + } + } + for (auto &target : groups[i].clones) { + prepare_vmap(*target.vmap); + auto target_F = cast_or_null(map_get(*target.vmap, F)); + if (target_F) { + if (!F->isDeclaration()) { + clone_function(group_F, target_F, *target.vmap); + } + add_features(target_F, specs[target.idx]); + target_F->addFnAttr("julia.mv.clone", std::to_string(target.idx)); + } + } + // don't set the original function's features yet, + // since we may clone it for later groups + if (i != 0) { + add_features(group_F, specs[groups[i].idx]); + group_F->addFnAttr("julia.mv.clone", std::to_string(groups[i].idx)); + } + } + // still don't set the original function's features yet, + // since we'll copy function attributes if we need to rewrite + // the alias, and target specific attributes are illegal on + // alias trampolines unless the user explicitly specifies them } - return ref - 1; +} + +uint32_t CloneCtx::get_func_id(Function *F) const +{ + auto ref = func_ids.find(F); + assert(ref != func_ids.end() && "Requesting id of non-fvar!"); + return ref->second - 1; } template -Constant *CloneCtx::rewrite_gv_init(const Stack& stack) +static Constant *rewrite_gv_init(const Stack& stack) { // Null initialize so that LLVM put it in the correct section. SmallVector args; @@ -719,40 +668,37 @@ void CloneCtx::rewrite_alias(GlobalAlias *alias, Function *F) Function::Create(F->getFunctionType(), alias->getLinkage(), "", &M); trampoline->copyAttributesFrom(F); trampoline->takeName(alias); + trampoline->setVisibility(alias->getVisibility()); + trampoline->setDSOLocal(alias->isDSOLocal()); + // drop multiversioning attributes, add alias attribute for testing purposes + trampoline->removeFnAttr("julia.mv.reloc"); + trampoline->removeFnAttr("julia.mv.clones"); + trampoline->addFnAttr("julia.mv.alias"); alias->eraseFromParent(); uint32_t id; GlobalVariable *slot; std::tie(id, slot) = get_reloc_slot(F); - for (auto &grp: groups) { - grp.relocs.insert(id); - for (auto &tgt: grp.clones) { - tgt.relocs.insert(id); - } - } - alias_relocs.insert(id); - auto BB = BasicBlock::Create(ctx, "top", trampoline); + auto BB = BasicBlock::Create(F->getContext(), "top", trampoline); IRBuilder<> irbuilder(BB); auto ptr = irbuilder.CreateLoad(F->getType(), slot); ptr->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - ptr->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(ctx, None)); + ptr->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(F->getContext(), None)); std::vector Args; for (auto &arg : trampoline->args()) Args.push_back(&arg); auto call = irbuilder.CreateCall(F->getFunctionType(), ptr, makeArrayRef(Args)); - if (F->isVarArg()) -#if (defined(_CPU_ARM_) || defined(_CPU_PPC_) || defined(_CPU_PPC64_)) - abort(); // musttail support is very bad on ARM, PPC, PPC64 (as of LLVM 3.9) -#else + if (F->isVarArg()) { + assert(!TT.isARM() && !TT.isPPC() && "musttail not supported on ARM/PPC!"); call->setTailCallKind(CallInst::TCK_MustTail); -#endif - else + } else { call->setTailCallKind(CallInst::TCK_Tail); + } - if (F->getReturnType() == T_void) + if (F->getReturnType() == Type::getVoidTy(F->getContext())) irbuilder.CreateRetVoid(); else irbuilder.CreateRet(call); @@ -788,27 +734,39 @@ void CloneCtx::fix_gv_uses() return changed; }; for (auto orig_f: orig_funcs) { - if (!has_cloneall && !cloned.count(orig_f)) + if (!orig_f->hasFnAttribute("julia.mv.clones")) continue; while (single_pass(orig_f)) { } } } -std::pair CloneCtx::get_reloc_slot(Function *F) +void CloneCtx::finalize_orig_clone_attr() { - // Null initialize so that LLVM put it in the correct section. - auto id = get_func_id(F); - auto &slot = const_relocs[id]; - if (!slot) - slot = new GlobalVariable(M, F->getType(), false, GlobalVariable::InternalLinkage, - ConstantPointerNull::get(F->getType()), - F->getName() + ".reloc_slot"); - return std::make_pair(id, slot); + for (auto orig_f: orig_funcs) { + if (!orig_f->hasFnAttribute("julia.mv.clones")) + continue; + orig_f->addFnAttr("julia.mv.clone", "0"); + } +} + +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}; + } + else { + auto id = get_func_id(F); + auto slot = const_relocs.find(id); + assert(slot != const_relocs.end() && "Missing relocation slot!"); + return {id, slot->second}; + } } template -Value *CloneCtx::rewrite_inst_use(const Stack& stack, Value *replace, Instruction *insert_before) +static Value *rewrite_inst_use(const Stack& stack, Type *T_size, Value *replace, Instruction *insert_before) { SmallVector args; uint32_t nlevel = stack.size(); @@ -824,6 +782,7 @@ Value *CloneCtx::rewrite_inst_use(const Stack& stack, Value *replace, Instructio replace = inst; continue; } + assert(val); unsigned nargs = val->getNumOperands(); args.resize(nargs); for (unsigned j = 0; j < nargs; j++) { @@ -857,138 +816,132 @@ Value *CloneCtx::rewrite_inst_use(const Stack& stack, Value *replace, Instructio return replace; } +template +static void replaceUsesWithLoad(Function &F, Type *T_size, I2GV should_replace, MDNode *tbaa_const) { + bool changed; + do { + changed = false; + for (auto uses = ConstantUses(&F, *F.getParent()); !uses.done(); uses.next()) { + auto info = uses.get_info(); + auto use_i = info.val; + GlobalVariable *slot = should_replace(*use_i); + if (!slot) + continue; + Instruction *insert_before = use_i; + if (auto phi = dyn_cast(use_i)) + insert_before = phi->getIncomingBlock(*info.use)->getTerminator(); + Instruction *ptr = new LoadInst(F.getType(), slot, "", false, insert_before); + ptr->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); + ptr->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(ptr->getContext(), None)); + use_i->setOperand(info.use->getOperandNo(), + rewrite_inst_use(uses.get_stack(), T_size, ptr, + insert_before)); + changed = true; + } + } while (changed); +} + void CloneCtx::fix_inst_uses() { uint32_t nfuncs = orig_funcs.size(); for (auto &grp: groups) { - auto suffix = ".clone_" + std::to_string(grp.idx); for (uint32_t i = 0; i < nfuncs; i++) { - if (!grp.clone_fs.count(i)) - continue; auto orig_f = orig_funcs[i]; + if (!grp.has_subtarget_clone(orig_f)) + continue; auto F = grp.base_func(orig_f); - bool changed; - do { - changed = false; - for (auto uses = ConstantUses(F, M); !uses.done(); uses.next()) { - auto info = uses.get_info(); - auto use_i = info.val; - auto use_f = use_i->getFunction(); - if (!use_f->getName().endswith(suffix)) - continue; - Instruction *insert_before = use_i; - if (auto phi = dyn_cast(use_i)) - insert_before = phi->getIncomingBlock(*info.use)->getTerminator(); - uint32_t id; - GlobalVariable *slot; - std::tie(id, slot) = get_reloc_slot(orig_f); - Instruction *ptr = new LoadInst(orig_f->getType(), slot, "", false, insert_before); - ptr->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - ptr->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(ctx, None)); - use_i->setOperand(info.use->getOperandNo(), - rewrite_inst_use(uses.get_stack(), ptr, - insert_before)); - - grp.relocs.insert(id); - for (auto &tgt: grp.clones) { - // The enclosing function of the use is cloned, - // no need to deal with this use on this target. - if (map_get(*tgt.vmap, use_f)) - continue; - tgt.relocs.insert(id); - } - - changed = true; - } - } while (changed); + auto grpidx = std::to_string(grp.idx); + replaceUsesWithLoad(*F, T_size, [&](Instruction &I) -> GlobalVariable * { + uint32_t id; + GlobalVariable *slot; + auto use_f = I.getFunction(); + if (!use_f->hasFnAttribute("julia.mv.clone") || use_f->getFnAttribute("julia.mv.clone").getValueAsString() != grpidx) + return nullptr; + std::tie(id, slot) = get_reloc_slot(orig_f); + return slot; + }, tbaa_const); } } } -template -inline T *CloneCtx::add_comdat(T *G) const -{ -#if defined(_OS_WINDOWS_) - // add __declspec(dllexport) to everything marked for export - if (G->getLinkage() == GlobalValue::ExternalLinkage) - G->setDLLStorageClass(GlobalValue::DLLExportStorageClass); - else - G->setDLLStorageClass(GlobalValue::DefaultStorageClass); -#endif - return G; +void CloneCtx::finalize_orig_features() { + for (auto F : orig_funcs) { + add_features(F, specs[0]); + } } -Constant *CloneCtx::get_ptrdiff32(Constant *ptr, Constant *base) const +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, T_int32) : ptrdiff; + return sizeof(void*) == 8 ? ConstantExpr::getTrunc(ptrdiff, Type::getInt32Ty(ptr->getContext())) : ptrdiff; } template -Constant *CloneCtx::emit_offset_table(const std::vector &vars, StringRef name) const +static Constant *emit_offset_table(Module &M, Type *T_size, const std::vector &vars, StringRef name, StringRef suffix) { - assert(!vars.empty()); - add_comdat(GlobalAlias::create(T_size, 0, GlobalVariable::ExternalLinkage, - name + "_base", - ConstantExpr::getBitCast(vars[0], T_psize), &M)); - auto vbase = ConstantExpr::getPtrToInt(vars[0], T_size); + 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); std::vector offsets(nvars + 1); offsets[0] = ConstantInt::get(T_int32, nvars); - offsets[1] = ConstantInt::get(T_int32, 0); - for (uint32_t i = 1; i < nvars; i++) - offsets[i + 1] = get_ptrdiff32(vars[i], vbase); + 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); - add_comdat(new GlobalVariable(M, vars_type, true, + auto gv = new GlobalVariable(M, vars_type, true, GlobalVariable::ExternalLinkage, ConstantArray::get(vars_type, offsets), - name + "_offsets")); + name + "_offsets" + suffix); + gv->setVisibility(GlobalValue::HiddenVisibility); + gv->setDSOLocal(true); return vbase; } void CloneCtx::emit_metadata() { - // Store back the information about exported functions. - auto fbase = emit_offset_table(fvars, "jl_sysimg_fvars"); - auto gbase = emit_offset_table(gvars, "jl_sysimg_gvars"); uint32_t nfvars = fvars.size(); - - uint32_t ntargets = specs.size(); - SmallVector targets(ntargets); - for (auto &grp: groups) { - targets[grp.idx] = &grp; - for (auto &tgt: grp.clones) { - targets[tgt.idx] = &tgt; - } + if (allow_bad_fvars && nfvars == 0) { + // Will result in a non-loadable sysimg, but `allow_bad_fvars` is for bugpoint only + return; } - // Generate `jl_dispatch_target_ids` - { - const uint32_t base_flags = has_veccall ? JL_TARGET_VEC_CALL : 0; - std::vector data; - auto push_i32 = [&] (uint32_t v) { - uint8_t buff[4]; - memcpy(buff, &v, 4); - data.insert(data.end(), buff, buff + 4); - }; - push_i32(ntargets); - for (uint32_t i = 0; i < ntargets; i++) { - push_i32(base_flags | (specs[i].flags & JL_TARGET_UNKNOWN_NAME)); - auto &specdata = specs[i].data; - data.insert(data.end(), specdata.begin(), specdata.end()); - } - auto value = ConstantDataArray::get(ctx, data); - add_comdat(new GlobalVariable(M, value->getType(), true, - GlobalVariable::ExternalLinkage, - value, "jl_dispatch_target_ids")); + StringRef suffix; + if (auto suffix_md = M.getModuleFlag("julia.mv.suffix")) { + suffix = cast(suffix_md)->getString(); } + // 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); + + 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(); + // Generate `jl_dispatch_reloc_slots` std::set shared_relocs; { - std::stable_sort(gv_relocs.begin(), gv_relocs.end(), + auto T_int32 = Type::getInt32Ty(M.getContext()); + std::sort(gv_relocs.begin(), gv_relocs.end(), [] (const std::pair &lhs, const std::pair &rhs) { return lhs.second < rhs.second; @@ -1005,22 +958,22 @@ void CloneCtx::emit_metadata() gv_reloc_idx++) { shared_relocs.insert(id); values.push_back(id_v); - values.push_back(get_ptrdiff32(gv_relocs[gv_reloc_idx].first, gbase)); + values.push_back(get_ptrdiff32(T_size, gv_relocs[gv_reloc_idx].first, gbase)); } auto it = const_relocs.find(id); if (it != const_relocs.end()) { - values.push_back(id_v); - values.push_back(get_ptrdiff32(it->second, gbase)); - } - if (alias_relocs.find(id) != alias_relocs.end()) { shared_relocs.insert(id); + values.push_back(id_v); + values.push_back(get_ptrdiff32(T_size, it->second, gbase)); } } values[0] = ConstantInt::get(T_int32, values.size() / 2); ArrayType *vars_type = ArrayType::get(T_int32, values.size()); - add_comdat(new GlobalVariable(M, vars_type, true, GlobalVariable::ExternalLinkage, + auto gv = new GlobalVariable(M, vars_type, true, GlobalVariable::ExternalLinkage, ConstantArray::get(vars_type, values), - "jl_dispatch_reloc_slots")); + "jl_clone_slots" + suffix); + gv->setVisibility(GlobalValue::HiddenVisibility); + gv->setDSOLocal(true); } // Generate `jl_dispatch_fvars_idxs` and `jl_dispatch_fvars_offsets` @@ -1028,7 +981,7 @@ void CloneCtx::emit_metadata() std::vector idxs; std::vector offsets; for (uint32_t i = 0; i < ntargets; i++) { - auto tgt = targets[i]; + auto tgt = linearized[i]; auto &spec = specs[i]; uint32_t len_idx = idxs.size(); idxs.push_back(0); // We will fill in the real value later. @@ -1037,18 +990,18 @@ void CloneCtx::emit_metadata() auto grp = static_cast(tgt); count = jl_sysimg_tag_mask; for (uint32_t j = 0; j < nfvars; j++) { - if (shared_relocs.count(j) || tgt->relocs.count(j)) { + if (shared_relocs.count(j)) { count++; idxs.push_back(j); } if (i != 0) { - offsets.push_back(get_ptrdiff32(grp->base_func(fvars[j]), fbase)); + offsets.push_back(get_ptrdiff32(T_size, grp->base_func(fvars[j]), fbase)); } } } else { auto baseidx = spec.base; - auto grp = static_cast(targets[baseidx]); + auto grp = static_cast(linearized[baseidx]); idxs.push_back(baseidx); for (uint32_t j = 0; j < nfvars; j++) { auto base_f = grp->base_func(fvars[j]); @@ -1056,30 +1009,34 @@ 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(cast(f), fbase)); + offsets.push_back(get_ptrdiff32(T_size, cast(f), fbase)); } else if (auto f = map_get(*tgt->vmap, base_f)) { count++; - idxs.push_back(tgt->relocs.count(j) ? (jl_sysimg_tag_mask | j) : j); - offsets.push_back(get_ptrdiff32(cast(f), fbase)); + idxs.push_back(j); + offsets.push_back(get_ptrdiff32(T_size, cast(f), fbase)); } } } idxs[len_idx] = count; } - auto idxval = ConstantDataArray::get(ctx, idxs); - add_comdat(new GlobalVariable(M, idxval->getType(), true, + auto idxval = ConstantDataArray::get(M.getContext(), idxs); + auto gv1 = new GlobalVariable(M, idxval->getType(), true, GlobalVariable::ExternalLinkage, - idxval, "jl_dispatch_fvars_idxs")); - ArrayType *offsets_type = ArrayType::get(T_int32, offsets.size()); - add_comdat(new GlobalVariable(M, offsets_type, true, + idxval, "jl_clone_idxs" + suffix); + gv1->setVisibility(GlobalValue::HiddenVisibility); + gv1->setDSOLocal(true); + ArrayType *offsets_type = ArrayType::get(Type::getInt32Ty(M.getContext()), offsets.size()); + auto gv2 = new GlobalVariable(M, offsets_type, true, GlobalVariable::ExternalLinkage, ConstantArray::get(offsets_type, offsets), - "jl_dispatch_fvars_offsets")); + "jl_clone_offsets" + suffix); + gv2->setVisibility(GlobalValue::HiddenVisibility); + gv2->setDSOLocal(true); } } -bool MultiVersioning::runOnModule(Module &M) +static bool runMultiVersioning(Module &M, bool allow_bad_fvars) { // Group targets and identify cloning bases. // Also initialize function info maps (we'll update these maps as we go) @@ -1089,22 +1046,35 @@ bool MultiVersioning::runOnModule(Module &M) // * Cloned function -> Original function (add as we clone functions) // * Original function -> Base function (target specific and updated by LLVM) // * ID -> relocation slots (const). - if (M.getName() == "sysimage") + if (!M.getModuleFlag("julia.mv.enable")) { + return false; + } + + // for opt testing purposes + bool annotated = !!M.getModuleFlag("julia.mv.annotated"); + if (!annotated) { + annotate_module_clones(M); + } + + // also for opt testing purposes + if (M.getModuleFlag("julia.mv.skipcloning")) { + assert(!annotated && "Multiversioning was enabled and annotations were added, but cloning was skipped!"); + return true; + } + + 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()))) return false; - CloneCtx clone(this, M); + CloneCtx clone(M, allow_bad_fvars); - // Collect a list of original functions and clone base functions - clone.clone_bases(); + clone.prepare_slots(); - // Collect function info (type of instruction used) - clone.collect_func_infos(); + clone.clone_decls(); - // If any partially cloned target exist decide which functions to clone for these targets. - // Clone functions for each group and collect a list of them. - // We can also add feature strings for cloned functions - // now that no additional cloning needs to be done. - clone.clone_all_partials(); + clone.clone_bodies(); // Scan **ALL** cloned functions (including full cloning for base target) // for global variables initialization use. @@ -1112,6 +1082,10 @@ bool MultiVersioning::runOnModule(Module &M) // These relocations must be initialized for **ALL** targets. clone.fix_gv_uses(); + // Now we have all the cloned functions, we can set the original functions' + // clone attribute to be 0 + clone.finalize_orig_clone_attr(); + // For each group, scan all functions cloned by **PARTIALLY** cloned targets for // instruction use. // A function needs a const relocation slot if it is cloned and is called by a @@ -1122,27 +1096,67 @@ bool MultiVersioning::runOnModule(Module &M) // A target needs a slot to be initialized iff at least one caller is not initialized. clone.fix_inst_uses(); + //Now set the original functions' target-specific attributes, since nobody will look at those again + clone.finalize_orig_features(); + // Store back sysimg information with the correct format. // At this point, we should have fixed up all the uses of the cloned functions // and collected all the shared/target-specific relocations. clone.emit_metadata(); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif return true; } -char MultiVersioning::ID = 0; -static RegisterPass X("JuliaMultiVersioning", "JuliaMultiVersioning Pass", +struct MultiVersioningLegacy: public ModulePass { + static char ID; + MultiVersioningLegacy(bool allow_bad_fvars=false) + : ModulePass(ID), allow_bad_fvars(allow_bad_fvars) + {} + +private: + bool runOnModule(Module &M) override; + bool allow_bad_fvars; +}; + +bool MultiVersioningLegacy::runOnModule(Module &M) +{ + return runMultiVersioning(M, allow_bad_fvars); +} + + +char MultiVersioningLegacy::ID = 0; +static RegisterPass X("JuliaMultiVersioning", "JuliaMultiVersioning Pass", false /* Only looks at CFG */, false /* Analysis Pass */); +} // anonymous namespace + +void multiversioning_preannotate(Module &M) +{ + annotate_module_clones(M); + M.addModuleFlag(Module::ModFlagBehavior::Error, "julia.mv.enable", 1); +} + +PreservedAnalyses MultiVersioning::run(Module &M, ModuleAnalysisManager &AM) +{ + if (runMultiVersioning(M, external_use)) { + auto preserved = PreservedAnalyses::allInSet(); + preserved.preserve(); + return preserved; + } + return PreservedAnalyses::all(); } -Pass *createMultiVersioningPass() +Pass *createMultiVersioningPass(bool allow_bad_fvars) { - return new MultiVersioning(); + return new MultiVersioningLegacy(allow_bad_fvars); } -extern "C" JL_DLLEXPORT void LLVMExtraAddMultiVersioningPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddMultiVersioningPass_impl(LLVMPassManagerRef PM) { - unwrap(PM)->add(createMultiVersioningPass()); + unwrap(PM)->add(createMultiVersioningPass(false)); } diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index 2821f9838a0a7..b006f191937f5 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -12,24 +12,22 @@ #include #include -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" #include "llvm-pass-helpers.h" using namespace llvm; JuliaPassContext::JuliaPassContext() - : T_size(nullptr), T_int8(nullptr), T_int32(nullptr), - T_pint8(nullptr), T_jlvalue(nullptr), T_prjlvalue(nullptr), - T_ppjlvalue(nullptr), T_pjlvalue(nullptr), T_pjlvalue_der(nullptr), - T_ppjlvalue_der(nullptr), + : T_prjlvalue(nullptr), tbaa_gcframe(nullptr), tbaa_tag(nullptr), - pgcstack_getter(nullptr), gc_flush_func(nullptr), + 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), module(nullptr) + typeof_func(nullptr), write_barrier_func(nullptr), + call_func(nullptr), call2_func(nullptr), module(nullptr) { } @@ -45,6 +43,7 @@ void JuliaPassContext::initFunctions(Module &M) tbaa_tag = tbaa_make_child_with_context(llvmctx, "jtbaa_tag", tbaa_data_scalar).first; pgcstack_getter = M.getFunction("julia.get_pgcstack"); + adoptthread_func = M.getFunction("julia.get_pgcstack_or_new"); gc_flush_func = M.getFunction("julia.gcroot_flush"); gc_preserve_begin_func = M.getFunction("llvm.julia.gc_preserve_begin"); gc_preserve_end_func = M.getFunction("llvm.julia.gc_preserve_end"); @@ -52,6 +51,8 @@ void JuliaPassContext::initFunctions(Module &M) typeof_func = M.getFunction("julia.typeof"); write_barrier_func = M.getFunction("julia.write_barrier"); alloc_obj_func = M.getFunction("julia.gc_alloc_obj"); + call_func = M.getFunction("julia.call"); + call2_func = M.getFunction("julia.call2"); } void JuliaPassContext::initAll(Module &M) @@ -61,27 +62,20 @@ void JuliaPassContext::initAll(Module &M) // Then initialize types and metadata nodes. auto &ctx = M.getContext(); - T_size = M.getDataLayout().getIntPtrType(ctx); - T_int8 = Type::getInt8Ty(ctx); - T_pint8 = PointerType::get(T_int8, 0); - T_int32 = Type::getInt32Ty(ctx); // Construct derived types. - T_jlvalue = StructType::get(ctx); - T_pjlvalue = PointerType::get(T_jlvalue, 0); - T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked); - T_ppjlvalue = PointerType::get(T_pjlvalue, 0); - T_pjlvalue_der = PointerType::get(T_jlvalue, AddressSpace::Derived); - T_ppjlvalue_der = PointerType::get(T_prjlvalue, AddressSpace::Derived); - T_pppjlvalue = PointerType::get(T_ppjlvalue, 0); + T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); } llvm::CallInst *JuliaPassContext::getPGCstack(llvm::Function &F) const { - for (auto I = F.getEntryBlock().begin(), E = F.getEntryBlock().end(); - pgcstack_getter && I != E; ++I) { - if (CallInst *callInst = dyn_cast(&*I)) { - if (callInst->getCalledOperand() == pgcstack_getter) { + if (!pgcstack_getter && !adoptthread_func) + return nullptr; + for (auto &I : F.getEntryBlock()) { + if (CallInst *callInst = dyn_cast(&I)) { + Value *callee = callInst->getCalledOperand(); + if ((pgcstack_getter && callee == pgcstack_getter) || + (adoptthread_func && callee == adoptthread_func)) { return callInst; } } @@ -107,7 +101,8 @@ llvm::Function *JuliaPassContext::getOrDeclare( else { // Otherwise, we'll declare it and add it to the module. // Declare the function. - auto func = desc.declare(*this); + auto T_size = module->getDataLayout().getIntPtrType(module->getContext()); + auto func = desc.declare(T_size); // Add it to the function list. module->getFunctionList().push_back(func); // Return the newly created function. @@ -122,25 +117,27 @@ namespace jl_intrinsics { static const char *PUSH_GC_FRAME_NAME = "julia.push_gc_frame"; static const char *POP_GC_FRAME_NAME = "julia.pop_gc_frame"; static const char *QUEUE_GC_ROOT_NAME = "julia.queue_gc_root"; + static const char *SAFEPOINT_NAME = "julia.safepoint"; // 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, LLVMContext &context) + static Function *addGCAllocAttributes(Function *target) { addRetAttr(target, Attribute::NoAlias); addRetAttr(target, Attribute::NonNull); - target->addFnAttr(Attribute::getWithAllocSizeArgs(context, 1, None)); // returns %1 bytes return target; } const IntrinsicDescription getGCFrameSlot( GET_GC_FRAME_SLOT_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_pprjlvalue = JuliaType::get_pprjlvalue_ty(ctx); return Function::Create( FunctionType::get( - PointerType::get(context.T_prjlvalue, 0), - {PointerType::get(context.T_prjlvalue, 0), context.T_int32}, + T_pprjlvalue, + {T_pprjlvalue, Type::getInt32Ty(ctx)}, false), Function::ExternalLinkage, GET_GC_FRAME_SLOT_NAME); @@ -148,23 +145,27 @@ namespace jl_intrinsics { const IntrinsicDescription GCAllocBytes( GC_ALLOC_BYTES_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); auto intrinsic = Function::Create( FunctionType::get( - context.T_prjlvalue, - { context.T_pint8, context.T_size }, + T_prjlvalue, + { Type::getInt8PtrTy(ctx), T_size }, false), Function::ExternalLinkage, GC_ALLOC_BYTES_NAME); - - return addGCAllocAttributes(intrinsic, context.getLLVMContext()); + intrinsic->addFnAttr(Attribute::getWithAllocSizeArgs(ctx, 1, None)); + return addGCAllocAttributes(intrinsic); }); const IntrinsicDescription newGCFrame( NEW_GC_FRAME_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_pprjlvalue = JuliaType::get_pprjlvalue_ty(ctx); auto intrinsic = Function::Create( - FunctionType::get(PointerType::get(context.T_prjlvalue, 0), {context.T_int32}, false), + FunctionType::get(T_pprjlvalue, {Type::getInt32Ty(ctx)}, false), Function::ExternalLinkage, NEW_GC_FRAME_NAME); addRetAttr(intrinsic, Attribute::NoAlias); @@ -175,11 +176,13 @@ namespace jl_intrinsics { const IntrinsicDescription pushGCFrame( PUSH_GC_FRAME_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_pprjlvalue = JuliaType::get_pprjlvalue_ty(ctx); return Function::Create( FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - {PointerType::get(context.T_prjlvalue, 0), context.T_int32}, + Type::getVoidTy(ctx), + {T_pprjlvalue, Type::getInt32Ty(ctx)}, false), Function::ExternalLinkage, PUSH_GC_FRAME_NAME); @@ -187,11 +190,13 @@ namespace jl_intrinsics { const IntrinsicDescription popGCFrame( POP_GC_FRAME_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_pprjlvalue = JuliaType::get_pprjlvalue_ty(ctx); return Function::Create( FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - {PointerType::get(context.T_prjlvalue, 0)}, + Type::getVoidTy(ctx), + {T_pprjlvalue}, false), Function::ExternalLinkage, POP_GC_FRAME_NAME); @@ -199,65 +204,108 @@ namespace jl_intrinsics { const IntrinsicDescription queueGCRoot( QUEUE_GC_ROOT_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); auto intrinsic = Function::Create( FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - { context.T_prjlvalue }, + Type::getVoidTy(ctx), + { T_prjlvalue }, false), Function::ExternalLinkage, QUEUE_GC_ROOT_NAME); intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); return intrinsic; }); + + const IntrinsicDescription safepoint( + SAFEPOINT_NAME, + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_psize = T_size->getPointerTo(); + auto intrinsic = Function::Create( + FunctionType::get( + Type::getVoidTy(ctx), + {T_psize}, + false), + Function::ExternalLinkage, + SAFEPOINT_NAME); + intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); + return intrinsic; + }); } namespace jl_well_known { static const char *GC_BIG_ALLOC_NAME = XSTR(jl_gc_big_alloc); static const char *GC_POOL_ALLOC_NAME = XSTR(jl_gc_pool_alloc); static const char *GC_QUEUE_ROOT_NAME = XSTR(jl_gc_queue_root); + static const char *GC_ALLOC_TYPED_NAME = XSTR(jl_gc_alloc_typed); using jl_intrinsics::addGCAllocAttributes; const WellKnownFunctionDescription GCBigAlloc( GC_BIG_ALLOC_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); auto bigAllocFunc = Function::Create( FunctionType::get( - context.T_prjlvalue, - { context.T_pint8, context.T_size }, + T_prjlvalue, + { Type::getInt8PtrTy(ctx), T_size }, false), Function::ExternalLinkage, GC_BIG_ALLOC_NAME); - - return addGCAllocAttributes(bigAllocFunc, context.getLLVMContext()); + bigAllocFunc->addFnAttr(Attribute::getWithAllocSizeArgs(ctx, 1, None)); + return addGCAllocAttributes(bigAllocFunc); }); const WellKnownFunctionDescription GCPoolAlloc( GC_POOL_ALLOC_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); auto poolAllocFunc = Function::Create( FunctionType::get( - context.T_prjlvalue, - { context.T_pint8, context.T_int32, context.T_int32 }, + T_prjlvalue, + { Type::getInt8PtrTy(ctx), Type::getInt32Ty(ctx), Type::getInt32Ty(ctx) }, false), Function::ExternalLinkage, GC_POOL_ALLOC_NAME); - - return addGCAllocAttributes(poolAllocFunc, context.getLLVMContext()); + poolAllocFunc->addFnAttr(Attribute::getWithAllocSizeArgs(ctx, 2, None)); + return addGCAllocAttributes(poolAllocFunc); }); const WellKnownFunctionDescription GCQueueRoot( GC_QUEUE_ROOT_NAME, - [](const JuliaPassContext &context) { + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); auto func = Function::Create( FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - { context.T_prjlvalue }, + Type::getVoidTy(ctx), + { T_prjlvalue }, false), Function::ExternalLinkage, GC_QUEUE_ROOT_NAME); func->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); return func; }); + + const WellKnownFunctionDescription GCAllocTyped( + GC_ALLOC_TYPED_NAME, + [](Type *T_size) { + auto &ctx = T_size->getContext(); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(ctx); + auto allocTypedFunc = Function::Create( + FunctionType::get( + T_prjlvalue, + { Type::getInt8PtrTy(ctx), + T_size, + Type::getInt8PtrTy(ctx) }, + false), + Function::ExternalLinkage, + GC_ALLOC_TYPED_NAME); + allocTypedFunc->addFnAttr(Attribute::getWithAllocSizeArgs(ctx, 1, None)); + return addGCAllocAttributes(allocTypedFunc); + }); } diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index f80786d1e7149..727f463dc50ef 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -10,6 +10,7 @@ #include #include #include +#include "analyzer_annotations.h" struct JuliaPassContext; @@ -19,7 +20,7 @@ namespace jl_intrinsics { // intrinsics and declare new intrinsics if necessary. struct IntrinsicDescription final { // The type of function that declares an intrinsic. - typedef llvm::Function *(*DeclarationFunction)(const JuliaPassContext&); + typedef llvm::Function *(*DeclarationFunction)(llvm::Type *T_size) JL_NOTSAFEPOINT; // Creates an intrinsic description with a particular // name and declaration function. @@ -40,20 +41,9 @@ namespace jl_intrinsics { // from modules or add them if they're not available yet. // Mainly useful for building Julia-specific LLVM passes. struct JuliaPassContext { - // Standard types. - llvm::Type *T_size; - llvm::Type *T_int8; - llvm::Type *T_int32; - llvm::PointerType *T_pint8; // Types derived from 'jl_value_t'. - llvm::Type *T_jlvalue; llvm::PointerType *T_prjlvalue; - llvm::PointerType *T_pppjlvalue; - llvm::PointerType *T_ppjlvalue; - llvm::PointerType *T_pjlvalue; - llvm::PointerType *T_pjlvalue_der; - llvm::PointerType *T_ppjlvalue_der; // TBAA metadata nodes. llvm::MDNode *tbaa_gcframe; @@ -61,6 +51,7 @@ struct JuliaPassContext { // Intrinsics. llvm::Function *pgcstack_getter; + llvm::Function *adoptthread_func; llvm::Function *gc_flush_func; llvm::Function *gc_preserve_begin_func; llvm::Function *gc_preserve_end_func; @@ -68,6 +59,8 @@ struct JuliaPassContext { llvm::Function *alloc_obj_func; llvm::Function *typeof_func; llvm::Function *write_barrier_func; + llvm::Function *call_func; + llvm::Function *call2_func; // Creates a pass context. Type and function pointers // are set to `nullptr`. Metadata nodes are initialized. @@ -133,6 +126,9 @@ namespace jl_intrinsics { // `julia.queue_gc_root`: an intrinsic that queues a GC root. extern const IntrinsicDescription queueGCRoot; + + // `julia.safepoint`: an intrinsic that triggers a GC safepoint. + extern const IntrinsicDescription safepoint; } // A namespace for well-known Julia runtime function descriptions. @@ -153,6 +149,9 @@ namespace jl_well_known { // `jl_gc_queue_root`: queues a GC root. extern const WellKnownFunctionDescription GCQueueRoot; + + // `jl_gc_alloc_typed`: allocates bytes. + extern const WellKnownFunctionDescription GCAllocTyped; } #endif diff --git a/src/llvm-propagate-addrspaces.cpp b/src/llvm-propagate-addrspaces.cpp index e41c85afbf31e..2158109cea120 100644 --- a/src/llvm-propagate-addrspaces.cpp +++ b/src/llvm-propagate-addrspaces.cpp @@ -22,8 +22,8 @@ #include #include -#include "codegen_shared.h" -#include "julia.h" +#include "passes.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "propagate_julia_addrspaces" @@ -40,17 +40,14 @@ using namespace llvm; optimizations. */ -struct PropagateJuliaAddrspaces : public FunctionPass, public InstVisitor { - static char ID; +struct PropagateJuliaAddrspacesVisitor : public InstVisitor { DenseMap LiftingMap; SmallPtrSet Visited; std::vector ToDelete; std::vector> ToInsert; - PropagateJuliaAddrspaces() : FunctionPass(ID) {}; public: - bool runOnFunction(Function &F) override; - Value *LiftPointer(Value *V, Type *LocTy = nullptr, Instruction *InsertPt=nullptr); + Value *LiftPointer(Module *M, Value *V, Instruction *InsertPt=nullptr); void visitMemop(Instruction &I, Type *T, unsigned OpIndex); void visitLoadInst(LoadInst &LI); void visitStoreInst(StoreInst &SI); @@ -63,19 +60,6 @@ struct PropagateJuliaAddrspaces : public FunctionPass, public InstVisitor &Worklist); }; -bool PropagateJuliaAddrspaces::runOnFunction(Function &F) { - visit(F); - for (auto it : ToInsert) - it.first->insertBefore(it.second); - for (Instruction *I : ToDelete) - I->eraseFromParent(); - ToInsert.clear(); - ToDelete.clear(); - LiftingMap.clear(); - Visited.clear(); - return true; -} - static unsigned getValueAddrSpace(Value *V) { return cast(V->getType())->getAddressSpace(); } @@ -84,7 +68,7 @@ static bool isSpecialAS(unsigned AS) { return AddressSpace::FirstSpecial <= AS && AS <= AddressSpace::LastSpecial; } -void PropagateJuliaAddrspaces::PoisonValues(std::vector &Worklist) { +void PropagateJuliaAddrspacesVisitor::PoisonValues(std::vector &Worklist) { while (!Worklist.empty()) { Value *CurrentV = Worklist.back(); Worklist.pop_back(); @@ -97,10 +81,11 @@ void PropagateJuliaAddrspaces::PoisonValues(std::vector &Worklist) { } } -Value *PropagateJuliaAddrspaces::LiftPointer(Value *V, Type *LocTy, Instruction *InsertPt) { +Value *PropagateJuliaAddrspacesVisitor::LiftPointer(Module *M, Value *V, Instruction *InsertPt) { SmallVector Stack; std::vector Worklist; std::set LocalVisited; + unsigned allocaAddressSpace = M->getDataLayout().getAllocaAddrSpace(); Worklist.push_back(V); // Follow pointer casts back, see if we're based on a pointer in // an untracked address space, in which case we're allowed to drop @@ -180,15 +165,14 @@ Value *PropagateJuliaAddrspaces::LiftPointer(Value *V, Type *LocTy, Instruction Instruction *InstV = cast(V); Instruction *NewV = InstV->clone(); ToInsert.push_back(std::make_pair(NewV, InstV)); - Type *NewRetTy = cast(InstV->getType())->getElementType()->getPointerTo(0); + Type *NewRetTy = PointerType::getWithSamePointeeType(cast(InstV->getType()), allocaAddressSpace); NewV->mutateType(NewRetTy); LiftingMap[InstV] = NewV; ToRevisit.push_back(NewV); } } - auto CollapseCastsAndLift = [&](Value *CurrentV, Instruction *InsertPt) -> Value * { - PointerType *TargetType = cast(CurrentV->getType())->getElementType()->getPointerTo(0); + PointerType *TargetType = PointerType::getWithSamePointeeType(cast(CurrentV->getType()), allocaAddressSpace); while (!LiftingMap.count(CurrentV)) { if (isa(CurrentV)) CurrentV = cast(CurrentV)->getOperand(0); @@ -232,38 +216,38 @@ Value *PropagateJuliaAddrspaces::LiftPointer(Value *V, Type *LocTy, Instruction return CollapseCastsAndLift(V, InsertPt); } -void PropagateJuliaAddrspaces::visitMemop(Instruction &I, Type *T, unsigned OpIndex) { +void PropagateJuliaAddrspacesVisitor::visitMemop(Instruction &I, Type *T, unsigned OpIndex) { Value *Original = I.getOperand(OpIndex); unsigned AS = Original->getType()->getPointerAddressSpace(); if (!isSpecialAS(AS)) return; - Value *Replacement = LiftPointer(Original, T, &I); + Value *Replacement = LiftPointer(I.getModule(), Original, &I); if (!Replacement) return; I.setOperand(OpIndex, Replacement); } -void PropagateJuliaAddrspaces::visitLoadInst(LoadInst &LI) { +void PropagateJuliaAddrspacesVisitor::visitLoadInst(LoadInst &LI) { visitMemop(LI, LI.getType(), LoadInst::getPointerOperandIndex()); } -void PropagateJuliaAddrspaces::visitStoreInst(StoreInst &SI) { +void PropagateJuliaAddrspacesVisitor::visitStoreInst(StoreInst &SI) { visitMemop(SI, SI.getValueOperand()->getType(), StoreInst::getPointerOperandIndex()); } -void PropagateJuliaAddrspaces::visitAtomicCmpXchgInst(AtomicCmpXchgInst &SI) { +void PropagateJuliaAddrspacesVisitor::visitAtomicCmpXchgInst(AtomicCmpXchgInst &SI) { visitMemop(SI, SI.getNewValOperand()->getType(), AtomicCmpXchgInst::getPointerOperandIndex()); } -void PropagateJuliaAddrspaces::visitAtomicRMWInst(AtomicRMWInst &SI) { +void PropagateJuliaAddrspacesVisitor::visitAtomicRMWInst(AtomicRMWInst &SI) { visitMemop(SI, SI.getType(), AtomicRMWInst::getPointerOperandIndex()); } -void PropagateJuliaAddrspaces::visitMemSetInst(MemSetInst &MI) { +void PropagateJuliaAddrspacesVisitor::visitMemSetInst(MemSetInst &MI) { unsigned AS = MI.getDestAddressSpace(); if (!isSpecialAS(AS)) return; - Value *Replacement = LiftPointer(MI.getRawDest()); + Value *Replacement = LiftPointer(MI.getModule(), MI.getRawDest()); if (!Replacement) return; Function *TheFn = Intrinsic::getDeclaration(MI.getModule(), Intrinsic::memset, @@ -272,20 +256,20 @@ void PropagateJuliaAddrspaces::visitMemSetInst(MemSetInst &MI) { MI.setArgOperand(0, Replacement); } -void PropagateJuliaAddrspaces::visitMemTransferInst(MemTransferInst &MTI) { +void PropagateJuliaAddrspacesVisitor::visitMemTransferInst(MemTransferInst &MTI) { unsigned DestAS = MTI.getDestAddressSpace(); unsigned SrcAS = MTI.getSourceAddressSpace(); if (!isSpecialAS(DestAS) && !isSpecialAS(SrcAS)) return; Value *Dest = MTI.getRawDest(); if (isSpecialAS(DestAS)) { - Value *Replacement = LiftPointer(Dest, cast(Dest->getType())->getElementType(), &MTI); + Value *Replacement = LiftPointer(MTI.getModule(), Dest, &MTI); if (Replacement) Dest = Replacement; } Value *Src = MTI.getRawSource(); if (isSpecialAS(SrcAS)) { - Value *Replacement = LiftPointer(Src, cast(Src->getType())->getElementType(), &MTI); + Value *Replacement = LiftPointer(MTI.getModule(), Src, &MTI); if (Replacement) Src = Replacement; } @@ -299,14 +283,55 @@ void PropagateJuliaAddrspaces::visitMemTransferInst(MemTransferInst &MTI) { MTI.setArgOperand(1, Src); } -char PropagateJuliaAddrspaces::ID = 0; -static RegisterPass X("PropagateJuliaAddrspaces", "Propagate (non-)rootedness information", false, false); +bool propagateJuliaAddrspaces(Function &F) { + PropagateJuliaAddrspacesVisitor visitor; + visitor.visit(F); + for (auto it : visitor.ToInsert) + it.first->insertBefore(it.second); + for (Instruction *I : visitor.ToDelete) + I->eraseFromParent(); + visitor.ToInsert.clear(); + visitor.ToDelete.clear(); + visitor.LiftingMap.clear(); + visitor.Visited.clear(); + return true; +} + +struct PropagateJuliaAddrspacesLegacy : FunctionPass { + static char ID; + + PropagateJuliaAddrspacesLegacy() : FunctionPass(ID) {} + bool runOnFunction(Function &F) override { + bool modified = propagateJuliaAddrspaces(F); +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + return modified; + } +}; + +char PropagateJuliaAddrspacesLegacy::ID = 0; +static RegisterPass X("PropagateJuliaAddrspaces", "Propagate (non-)rootedness information", false, false); Pass *createPropagateJuliaAddrspaces() { - return new PropagateJuliaAddrspaces(); + return new PropagateJuliaAddrspacesLegacy(); +} + +PreservedAnalyses PropagateJuliaAddrspacesPass::run(Function &F, FunctionAnalysisManager &AM) { + bool modified = propagateJuliaAddrspaces(F); + +#ifdef JL_VERIFY_PASSES + assert(!verifyFunction(F, &errs())); +#endif + if (modified) { + return PreservedAnalyses::allInSet(); + } else { + return PreservedAnalyses::all(); + } } -extern "C" JL_DLLEXPORT void LLVMExtraAddPropagateJuliaAddrspaces_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddPropagateJuliaAddrspaces_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createPropagateJuliaAddrspaces()); } diff --git a/src/llvm-ptls.cpp b/src/llvm-ptls.cpp index 297cbda53d747..84f8d7121ff03 100644 --- a/src/llvm-ptls.cpp +++ b/src/llvm-ptls.cpp @@ -1,17 +1,15 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license - -#define DEBUG_TYPE "lower_ptls" -#undef DEBUG - // LLVM pass to lower TLS access and remove references to julia intrinsics #include "llvm-version.h" #include "support/dtypes.h" +#include "passes.h" #include #include #include +#include #include #include #include @@ -19,50 +17,49 @@ #include #include #include +#include #include #include #include "julia.h" #include "julia_internal.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" #include "julia_assert.h" +#define DEBUG_TYPE "lower_ptls" +#undef DEBUG + using namespace llvm; typedef Instruction TerminatorInst; namespace { -struct LowerPTLS: public ModulePass { - static char ID; - LowerPTLS(bool imaging_mode=false) - : ModulePass(ID), - imaging_mode(imaging_mode) +struct LowerPTLS { + LowerPTLS(Module &M, bool imaging_mode=false) + : imaging_mode(imaging_mode), M(&M), TargetTriple(M.getTargetTriple()) {} + bool run(bool *CFGModified); private: const bool imaging_mode; Module *M; - Function *pgcstack_getter; - LLVMContext *ctx; - MDNode *tbaa_const; - FunctionType *FT_pgcstack_getter; - PointerType *T_pgcstack_getter; - PointerType *T_ppjlvalue; - PointerType *T_pppjlvalue; - Type *T_int8; - Type *T_size; - PointerType *T_pint8; + Triple TargetTriple; + MDNode *tbaa_const{nullptr}; + MDNode *tbaa_gcframe{nullptr}; + FunctionType *FT_pgcstack_getter{nullptr}; + PointerType *T_pgcstack_getter{nullptr}; + PointerType *T_pppjlvalue{nullptr}; + Type *T_size{nullptr}; GlobalVariable *pgcstack_func_slot{nullptr}; GlobalVariable *pgcstack_key_slot{nullptr}; GlobalVariable *pgcstack_offset{nullptr}; void set_pgcstack_attrs(CallInst *pgcstack) const; Instruction *emit_pgcstack_tp(Value *offset, Instruction *insertBefore) const; template T *add_comdat(T *G) const; - GlobalVariable *create_aliased_global(Type *T, StringRef name) const; - void fix_pgcstack_use(CallInst *pgcstack); - bool runOnModule(Module &M) override; + GlobalVariable *create_hidden_global(Type *T, StringRef name) const; + void fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter, bool or_new, bool *CFGModified); }; void LowerPTLS::set_pgcstack_attrs(CallInst *pgcstack) const @@ -74,122 +71,155 @@ void LowerPTLS::set_pgcstack_attrs(CallInst *pgcstack) const Instruction *LowerPTLS::emit_pgcstack_tp(Value *offset, Instruction *insertBefore) const { Value *tls; -#if defined(_CPU_X86_64_) || defined(_CPU_X86_) - if (insertBefore->getFunction()->callsFunctionThatReturnsTwice()) { + if (TargetTriple.isX86() && insertBefore->getFunction()->callsFunctionThatReturnsTwice()) { // Workaround LLVM bug by hiding the offset computation // (and therefore the optimization opportunity) from LLVM. // Ref https://github.com/JuliaLang/julia/issues/17288 - static const std::string const_asm_str = [&] () { - std::string stm; -# if defined(_CPU_X86_64_) - raw_string_ostream(stm) << "movq %fs:0, $0;\naddq $$" << jl_tls_offset << ", $0"; -# else - raw_string_ostream(stm) << "movl %gs:0, $0;\naddl $$" << jl_tls_offset << ", $0"; -# endif - return stm; - }(); -# if defined(_CPU_X86_64_) - const char *dyn_asm_str = "movq %fs:0, $0;\naddq $1, $0"; -# else - const char *dyn_asm_str = "movl %gs:0, $0;\naddl $1, $0"; -# endif + std::string const_asm_str; + raw_string_ostream(const_asm_str) << (TargetTriple.getArch() == Triple::x86_64 ? + "movq %fs:0, $0;\naddq $$" : "movl %gs:0, $0;\naddl $$") + << jl_tls_offset << ", $0"; + const char *dyn_asm_str = TargetTriple.getArch() == Triple::x86_64 ? + "movq %fs:0, $0;\naddq $1, $0" : + "movl %gs:0, $0;\naddl $1, $0"; // The add instruction clobbers flags if (offset) { std::vector args(0); args.push_back(offset->getType()); - auto tp = InlineAsm::get(FunctionType::get(T_pint8, args, false), + auto tp = InlineAsm::get(FunctionType::get(Type::getInt8PtrTy(insertBefore->getContext()), args, false), dyn_asm_str, "=&r,r,~{dirflag},~{fpsr},~{flags}", false); tls = CallInst::Create(tp, offset, "pgcstack_i8", insertBefore); } else { - auto tp = InlineAsm::get(FunctionType::get(T_pint8, false), + auto tp = InlineAsm::get(FunctionType::get(Type::getInt8PtrTy(insertBefore->getContext()), false), const_asm_str.c_str(), "=r,~{dirflag},~{fpsr},~{flags}", false); tls = CallInst::Create(tp, "pgcstack_i8", insertBefore); } - } - else -#endif - { + } else { // AArch64/ARM doesn't seem to have this issue. // (Possibly because there are many more registers and the offset is // positive and small) // It's also harder to emit the offset in a generic way on ARM/AArch64 // (need to generate one or two `add` with shift) so let llvm emit // the add for now. -#if defined(_CPU_AARCH64_) - const char *asm_str = "mrs $0, tpidr_el0"; -#elif defined(__ARM_ARCH) && __ARM_ARCH >= 7 - const char *asm_str = "mrc p15, 0, $0, c13, c0, 3"; -#elif defined(_CPU_X86_64_) - const char *asm_str = "movq %fs:0, $0"; -#elif defined(_CPU_X86_) - const char *asm_str = "movl %gs:0, $0"; -#else - const char *asm_str = nullptr; - assert(0 && "Cannot emit thread pointer for this architecture."); -#endif + const char *asm_str; + if (TargetTriple.isAArch64()) { + asm_str = "mrs $0, tpidr_el0"; + } else if (TargetTriple.isARM()) { + asm_str = "mrc p15, 0, $0, c13, c0, 3"; + } else if (TargetTriple.getArch() == Triple::x86_64) { + asm_str = "movq %fs:0, $0"; + } else if (TargetTriple.getArch() == Triple::x86) { + asm_str = "movl %gs:0, $0"; + } else { + llvm_unreachable("Cannot emit thread pointer for this architecture."); + } if (!offset) offset = ConstantInt::getSigned(T_size, jl_tls_offset); - auto tp = InlineAsm::get(FunctionType::get(T_pint8, false), asm_str, "=r", false); + auto tp = InlineAsm::get(FunctionType::get(Type::getInt8PtrTy(insertBefore->getContext()), false), asm_str, "=r", false); tls = CallInst::Create(tp, "thread_ptr", insertBefore); - tls = GetElementPtrInst::Create(T_int8, tls, {offset}, "ppgcstack_i8", insertBefore); + tls = GetElementPtrInst::Create(Type::getInt8Ty(insertBefore->getContext()), tls, {offset}, "ppgcstack_i8", insertBefore); } tls = new BitCastInst(tls, T_pppjlvalue->getPointerTo(), "ppgcstack", insertBefore); return new LoadInst(T_pppjlvalue, tls, "pgcstack", false, insertBefore); } -GlobalVariable *LowerPTLS::create_aliased_global(Type *T, StringRef name) const +GlobalVariable *LowerPTLS::create_hidden_global(Type *T, StringRef name) const { - // Create a static global variable and points a global alias to it so that - // the address is visible externally but LLVM can still assume that the - // address of this variable doesn't need dynamic relocation - // (can be accessed with a single PC-rel load). - auto GV = new GlobalVariable(*M, T, false, GlobalVariable::InternalLinkage, - Constant::getNullValue(T), name + ".real"); - add_comdat(GlobalAlias::create(T, 0, GlobalVariable::ExternalLinkage, - name, GV, M)); + auto GV = new GlobalVariable(*M, T, false, GlobalVariable::ExternalLinkage, + nullptr, name); + GV->setVisibility(GlobalValue::HiddenVisibility); + GV->setDSOLocal(true); return GV; } -template -inline T *LowerPTLS::add_comdat(T *G) const -{ -#if defined(_OS_WINDOWS_) - // add __declspec(dllexport) to everything marked for export - if (G->getLinkage() == GlobalValue::ExternalLinkage) - G->setDLLStorageClass(GlobalValue::DLLExportStorageClass); - else - G->setDLLStorageClass(GlobalValue::DefaultStorageClass); -#endif - return G; -} - -void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack) +void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter, bool or_new, bool *CFGModified) { if (pgcstack->use_empty()) { pgcstack->eraseFromParent(); return; } + if (or_new) { + // pgcstack(); + // if (pgcstack != nullptr) + // last_gc_state = emit_gc_unsafe_enter(ctx); + // phi = pgcstack; // fast + // else + // last_gc_state = gc_safe; + // phi = adopt(); // slow + // use phi; + // if (!retboxed) + // foreach(retinst) + // emit_gc_unsafe_leave(ctx, last_gc_state); + auto phi = PHINode::Create(pgcstack->getType(), 2, ""); + phi->insertAfter(pgcstack); + pgcstack->replaceAllUsesWith(phi); + MDBuilder MDB(pgcstack->getContext()); + SmallVector Weights{9, 1}; + TerminatorInst *fastTerm; + TerminatorInst *slowTerm; + assert(pgcstack->getType()); // Static analyzer + auto cmp = new ICmpInst(phi, CmpInst::ICMP_NE, pgcstack, Constant::getNullValue(pgcstack->getType())); + SplitBlockAndInsertIfThenElse(cmp, phi, &fastTerm, &slowTerm, + MDB.createBranchWeights(Weights)); + if (CFGModified) + *CFGModified = true; + // emit slow branch code + CallInst *adopt = cast(pgcstack->clone()); + Function *adoptFunc = M->getFunction(XSTR(jl_adopt_thread)); + if (adoptFunc == NULL) { + adoptFunc = Function::Create(pgcstack_getter->getFunctionType(), + pgcstack_getter->getLinkage(), pgcstack_getter->getAddressSpace(), + XSTR(jl_adopt_thread), M); + adoptFunc->copyAttributesFrom(pgcstack_getter); + adoptFunc->copyMetadata(pgcstack_getter, 0); + } + adopt->setCalledFunction(adoptFunc); + adopt->insertBefore(slowTerm); + phi->addIncoming(adopt, slowTerm->getParent()); + // emit fast branch code + IRBuilder<> builder(fastTerm->getParent()); + fastTerm->removeFromParent(); + MDNode *tbaa = tbaa_gcframe; + Value *prior = emit_gc_unsafe_enter(builder, T_size, get_current_ptls_from_task(builder, T_size, get_current_task_from_pgcstack(builder, T_size, pgcstack), tbaa), true); + builder.Insert(fastTerm); + phi->addIncoming(pgcstack, fastTerm->getParent()); + // emit pre-return cleanup + if (CountTrackedPointers(pgcstack->getParent()->getParent()->getReturnType()).count == 0) { + auto last_gc_state = PHINode::Create(Type::getInt8Ty(pgcstack->getContext()), 2, "", phi); + // if we called jl_adopt_thread, we must end this cfunction back in the safe-state + last_gc_state->addIncoming(ConstantInt::get(Type::getInt8Ty(M->getContext()), JL_GC_STATE_SAFE), slowTerm->getParent()); + last_gc_state->addIncoming(prior, fastTerm->getParent()); + for (auto &BB : *pgcstack->getParent()->getParent()) { + if (isa(BB.getTerminator())) { + IRBuilder<> builder(BB.getTerminator()); + emit_gc_unsafe_leave(builder, T_size, get_current_ptls_from_task(builder, T_size, get_current_task_from_pgcstack(builder, T_size, phi), tbaa), last_gc_state, true); + } + } + } + } if (imaging_mode) { if (jl_tls_elf_support) { // if (offset != 0) - // pgcstack = tp + offset; + // pgcstack = tp + offset; // fast // else - // pgcstack = getter(); + // pgcstack = getter(); // slow auto offset = new LoadInst(T_size, pgcstack_offset, "", false, pgcstack); offset->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - offset->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(*ctx, None)); + offset->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(pgcstack->getContext(), None)); auto cmp = new ICmpInst(pgcstack, CmpInst::ICMP_NE, offset, Constant::getNullValue(offset->getType())); - MDBuilder MDB(*ctx); + MDBuilder MDB(pgcstack->getContext()); SmallVector Weights{9, 1}; TerminatorInst *fastTerm; TerminatorInst *slowTerm; SplitBlockAndInsertIfThenElse(cmp, pgcstack, &fastTerm, &slowTerm, MDB.createBranchWeights(Weights)); + if (CFGModified) + *CFGModified = true; auto fastTLS = emit_pgcstack_tp(offset, fastTerm); auto phi = PHINode::Create(T_pppjlvalue, 2, "", pgcstack); @@ -197,7 +227,7 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack) pgcstack->moveBefore(slowTerm); auto getter = new LoadInst(T_pgcstack_getter, pgcstack_func_slot, "", false, pgcstack); getter->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - getter->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(*ctx, None)); + getter->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(pgcstack->getContext(), None)); pgcstack->setCalledFunction(pgcstack->getFunctionType(), getter); set_pgcstack_attrs(pgcstack); @@ -212,19 +242,19 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack) // since we may not know which getter function to use ahead of time. auto getter = new LoadInst(T_pgcstack_getter, pgcstack_func_slot, "", false, pgcstack); getter->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - getter->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(*ctx, None)); -#if defined(_OS_DARWIN_) - auto key = new LoadInst(T_size, pgcstack_key_slot, "", false, pgcstack); - key->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); - key->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(*ctx, None)); - auto new_pgcstack = CallInst::Create(FT_pgcstack_getter, getter, {key}, "", pgcstack); - new_pgcstack->takeName(pgcstack); - pgcstack->replaceAllUsesWith(new_pgcstack); - pgcstack->eraseFromParent(); - pgcstack = new_pgcstack; -#else - pgcstack->setCalledFunction(pgcstack->getFunctionType(), getter); -#endif + getter->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(pgcstack->getContext(), None)); + if (TargetTriple.isOSDarwin()) { + auto key = new LoadInst(T_size, pgcstack_key_slot, "", false, pgcstack); + key->setMetadata(llvm::LLVMContext::MD_tbaa, tbaa_const); + key->setMetadata(llvm::LLVMContext::MD_invariant_load, MDNode::get(pgcstack->getContext(), None)); + auto new_pgcstack = CallInst::Create(FT_pgcstack_getter, getter, {key}, "", pgcstack); + new_pgcstack->takeName(pgcstack); + pgcstack->replaceAllUsesWith(new_pgcstack); + pgcstack->eraseFromParent(); + pgcstack = new_pgcstack; + } else { + pgcstack->setCalledFunction(pgcstack->getFunctionType(), getter); + } set_pgcstack_attrs(pgcstack); } else if (jl_tls_offset != -1) { @@ -238,73 +268,112 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack) jl_pgcstack_getkey(&f, &k); Constant *val = ConstantInt::get(T_size, (uintptr_t)f); val = ConstantExpr::getIntToPtr(val, T_pgcstack_getter); -#if defined(_OS_DARWIN_) - assert(sizeof(k) == sizeof(uintptr_t)); - Constant *key = ConstantInt::get(T_size, (uintptr_t)k); - auto new_pgcstack = CallInst::Create(FT_pgcstack_getter, val, {key}, "", pgcstack); - new_pgcstack->takeName(pgcstack); - pgcstack->replaceAllUsesWith(new_pgcstack); - pgcstack->eraseFromParent(); - pgcstack = new_pgcstack; -#else - pgcstack->setCalledFunction(pgcstack->getFunctionType(), val); -#endif + if (TargetTriple.isOSDarwin()) { + assert(sizeof(k) == sizeof(uintptr_t)); + Constant *key = ConstantInt::get(T_size, (uintptr_t)k); + auto new_pgcstack = CallInst::Create(FT_pgcstack_getter, val, {key}, "", pgcstack); + new_pgcstack->takeName(pgcstack); + pgcstack->replaceAllUsesWith(new_pgcstack); + pgcstack->eraseFromParent(); + pgcstack = new_pgcstack; + } else { + pgcstack->setCalledFunction(pgcstack->getFunctionType(), val); + } set_pgcstack_attrs(pgcstack); } } -bool LowerPTLS::runOnModule(Module &_M) +bool LowerPTLS::run(bool *CFGModified) { - M = &_M; - pgcstack_getter = M->getFunction("julia.get_pgcstack"); - if (!pgcstack_getter) - return false; - - ctx = &M->getContext(); - tbaa_const = tbaa_make_child_with_context(*ctx, "jtbaa_const", nullptr, true).first; - - T_int8 = Type::getInt8Ty(*ctx); - T_size = sizeof(size_t) == 8 ? Type::getInt64Ty(*ctx) : Type::getInt32Ty(*ctx); - T_pint8 = T_int8->getPointerTo(); - FT_pgcstack_getter = pgcstack_getter->getFunctionType(); -#if defined(_OS_DARWIN_) - assert(sizeof(jl_pgcstack_key_t) == sizeof(uintptr_t)); - FT_pgcstack_getter = FunctionType::get(FT_pgcstack_getter->getReturnType(), {T_size}, false); -#endif - T_pgcstack_getter = FT_pgcstack_getter->getPointerTo(); - T_pppjlvalue = cast(FT_pgcstack_getter->getReturnType()); - T_ppjlvalue = JuliaType::get_ppjlvalue_ty(*ctx); - if (imaging_mode) { - pgcstack_func_slot = create_aliased_global(T_pgcstack_getter, "jl_pgcstack_func_slot"); - pgcstack_key_slot = create_aliased_global(T_size, "jl_pgcstack_key_slot"); // >= sizeof(jl_pgcstack_key_t) - pgcstack_offset = create_aliased_global(T_size, "jl_tls_offset"); - } + bool need_init = true; + auto runOnGetter = [&](bool or_new) { + Function *pgcstack_getter = M->getFunction(or_new ? "julia.get_pgcstack_or_new" : "julia.get_pgcstack"); + if (!pgcstack_getter) + return false; - for (auto it = pgcstack_getter->user_begin(); it != pgcstack_getter->user_end();) { - auto call = cast(*it); - ++it; - assert(call->getCalledOperand() == pgcstack_getter); - fix_pgcstack_use(call); - } - assert(pgcstack_getter->use_empty()); - pgcstack_getter->eraseFromParent(); - return true; + if (need_init) { + tbaa_const = tbaa_make_child_with_context(M->getContext(), "jtbaa_const", nullptr, true).first; + tbaa_gcframe = tbaa_make_child_with_context(M->getContext(), "jtbaa_gcframe").first; + T_size = M->getDataLayout().getIntPtrType(M->getContext()); + + FT_pgcstack_getter = pgcstack_getter->getFunctionType(); + if (TargetTriple.isOSDarwin()) { + assert(sizeof(jl_pgcstack_key_t) == sizeof(uintptr_t)); + FT_pgcstack_getter = FunctionType::get(FT_pgcstack_getter->getReturnType(), {T_size}, false); + } + T_pgcstack_getter = FT_pgcstack_getter->getPointerTo(); + T_pppjlvalue = cast(FT_pgcstack_getter->getReturnType()); + if (imaging_mode) { + pgcstack_func_slot = create_hidden_global(T_pgcstack_getter, "jl_pgcstack_func_slot"); + pgcstack_key_slot = create_hidden_global(T_size, "jl_pgcstack_key_slot"); // >= sizeof(jl_pgcstack_key_t) + pgcstack_offset = create_hidden_global(T_size, "jl_tls_offset"); + } + need_init = false; + } + + for (auto it = pgcstack_getter->user_begin(); it != pgcstack_getter->user_end();) { + auto call = cast(*it); + ++it; + assert(call->getCalledOperand() == pgcstack_getter); + fix_pgcstack_use(call, pgcstack_getter, or_new, CFGModified); + } + assert(pgcstack_getter->use_empty()); + pgcstack_getter->eraseFromParent(); + return true; + }; + return runOnGetter(false) + runOnGetter(true); } -char LowerPTLS::ID = 0; +struct LowerPTLSLegacy: public ModulePass { + static char ID; + LowerPTLSLegacy(bool imaging_mode=false) + : ModulePass(ID), + imaging_mode(imaging_mode) + {} + + bool imaging_mode; + bool runOnModule(Module &M) override { + LowerPTLS lower(M, imaging_mode); + bool modified = lower.run(nullptr); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif + return modified; + } +}; -static RegisterPass X("LowerPTLS", "LowerPTLS Pass", +char LowerPTLSLegacy::ID = 0; + +static RegisterPass X("LowerPTLS", "LowerPTLS Pass", false /* Only looks at CFG */, false /* Analysis Pass */); } // anonymous namespace +PreservedAnalyses LowerPTLSPass::run(Module &M, ModuleAnalysisManager &AM) { + LowerPTLS lower(M, imaging_mode); + bool CFGModified = false; + bool modified = lower.run(&CFGModified); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif + if (modified) { + if (CFGModified) { + return PreservedAnalyses::none(); + } else { + return PreservedAnalyses::allInSet(); + } + } + return PreservedAnalyses::all(); +} + Pass *createLowerPTLSPass(bool imaging_mode) { - return new LowerPTLS(imaging_mode); + return new LowerPTLSLegacy(imaging_mode); } -extern "C" JL_DLLEXPORT void LLVMExtraAddLowerPTLSPass_impl(LLVMPassManagerRef PM, LLVMBool imaging_mode) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddLowerPTLSPass_impl(LLVMPassManagerRef PM, LLVMBool imaging_mode) { unwrap(PM)->add(createLowerPTLSPass(imaging_mode)); } diff --git a/src/llvm-remove-addrspaces.cpp b/src/llvm-remove-addrspaces.cpp index 9b3631e264124..b964c20e3353e 100644 --- a/src/llvm-remove-addrspaces.cpp +++ b/src/llvm-remove-addrspaces.cpp @@ -8,12 +8,13 @@ #include #include #include +#include #include #include #include -#include "codegen_shared.h" -#include "julia.h" +#include "passes.h" +#include "llvm-codegen-shared.h" #define DEBUG_TYPE "remove_addrspaces" @@ -43,10 +44,17 @@ class AddrspaceRemoveTypeRemapper : public ValueMapTypeRemapper { return DstTy; DstTy = SrcTy; - if (auto Ty = dyn_cast(SrcTy)) - DstTy = PointerType::get( - remapType(Ty->getElementType()), - ASRemapper(Ty->getAddressSpace())); + if (auto Ty = dyn_cast(SrcTy)) { + if (Ty->isOpaque()) { + DstTy = PointerType::get(Ty->getContext(), ASRemapper(Ty->getAddressSpace())); + } + else { + //Remove once opaque pointer transition is complete + DstTy = PointerType::get( + remapType(Ty->getNonOpaquePointerElementType()), + ASRemapper(Ty->getAddressSpace())); + } + } else if (auto Ty = dyn_cast(SrcTy)) { SmallVector Params; for (unsigned Index = 0; Index < Ty->getNumParams(); ++Index) @@ -105,10 +113,9 @@ class AddrspaceRemoveTypeRemapper : public ValueMapTypeRemapper { } private: - static DenseMap MappedTypes; + DenseMap MappedTypes; }; -DenseMap AddrspaceRemoveTypeRemapper::MappedTypes; class AddrspaceRemoveValueMaterializer : public ValueMaterializer { ValueToValueMapTy &VM; @@ -151,10 +158,12 @@ class AddrspaceRemoveValueMaterializer : public ValueMaterializer { // GEP const exprs need to know the type of the source. // asserts remapType(typeof arg0) == typeof mapValue(arg0). Constant *Src = CE->getOperand(0); - Type *SrcTy = remapType( - cast(Src->getType()->getScalarType()) - ->getElementType()); - DstV = CE->getWithOperands(Ops, Ty, false, SrcTy); + auto ptrty = cast(Src->getType()->getScalarType()); + //Remove once opaque pointer transition is complete + if (!ptrty->isOpaque()) { + Type *SrcTy = remapType(ptrty->getNonOpaquePointerElementType()); + DstV = CE->getWithOperands(Ops, Ty, false, SrcTy); + } } else DstV = CE->getWithOperands(Ops, Ty); @@ -231,18 +240,7 @@ unsigned removeAllAddrspaces(unsigned AS) return AddressSpace::Generic; } -struct RemoveAddrspacesPass : public ModulePass { - static char ID; - AddrspaceRemapFunction ASRemapper; - RemoveAddrspacesPass( - AddrspaceRemapFunction ASRemapper = removeAllAddrspaces) - : ModulePass(ID), ASRemapper(ASRemapper){}; - -public: - bool runOnModule(Module &M) override; -}; - -bool RemoveAddrspacesPass::runOnModule(Module &M) +bool removeAddrspaces(Module &M, AddrspaceRemapFunction ASRemapper) { ValueToValueMapTy VMap; AddrspaceRemoveTypeRemapper TypeRemapper(ASRemapper); @@ -345,11 +343,7 @@ bool RemoveAddrspacesPass::runOnModule(Module &M) for (auto MD : MDs) NGV->addMetadata( MD.first, -#if JL_LLVM_VERSION >= 130000 *MapMetadata(MD.second, VMap)); -#else - *MapMetadata(MD.second, VMap, RF_MoveDistinctMDs)); -#endif copyComdat(NGV, GV); @@ -358,11 +352,9 @@ bool RemoveAddrspacesPass::runOnModule(Module &M) // Similarly, copy over and rewrite function bodies for (Function *F : Functions) { - if (F->isDeclaration()) - continue; - Function *NF = cast(VMap[F]); LLVM_DEBUG(dbgs() << "Processing function " << NF->getName() << "\n"); + // we also need this to run for declarations, or attributes won't be copied Function::arg_iterator DestI = NF->arg_begin(); for (Function::const_arg_iterator I = F->arg_begin(); I != F->arg_end(); @@ -376,46 +368,29 @@ bool RemoveAddrspacesPass::runOnModule(Module &M) NF, F, VMap, -#if JL_LLVM_VERSION >= 130000 CloneFunctionChangeType::GlobalChanges, -#else - /*ModuleLevelChanges=*/true, -#endif Returns, "", nullptr, &TypeRemapper, &Materializer); - // CloneFunctionInto unconditionally copies the attributes from F to NF, - // without considering e.g. the byval attribute type. + // Update function attributes that contain types AttributeList Attrs = F->getAttributes(); LLVMContext &C = F->getContext(); for (unsigned i = 0; i < Attrs.getNumAttrSets(); ++i) { for (Attribute::AttrKind TypedAttr : {Attribute::ByVal, Attribute::StructRet, Attribute::ByRef}) { -#if JL_LLVM_VERSION >= 140000 auto Attr = Attrs.getAttributeAtIndex(i, TypedAttr); -#else - auto Attr = Attrs.getAttribute(i, TypedAttr); -#endif if (Type *Ty = Attr.getValueAsType()) { -#if JL_LLVM_VERSION >= 140000 Attrs = Attrs.replaceAttributeTypeAtIndex( C, i, TypedAttr, TypeRemapper.remapType(Ty)); -#else - Attrs = Attrs.replaceAttributeType( - C, i, TypedAttr, TypeRemapper.remapType(Ty)); -#endif break; } } } NF->setAttributes(Attrs); - if (F->hasPersonalityFn()) - NF->setPersonalityFn(MapValue(F->getPersonalityFn(), VMap)); - copyComdat(NF, F); RemoveNoopAddrSpaceCasts(NF); @@ -457,8 +432,26 @@ bool RemoveAddrspacesPass::runOnModule(Module &M) return true; } -char RemoveAddrspacesPass::ID = 0; -static RegisterPass + +struct RemoveAddrspacesPassLegacy : public ModulePass { + static char ID; + AddrspaceRemapFunction ASRemapper; + RemoveAddrspacesPassLegacy( + AddrspaceRemapFunction ASRemapper = removeAllAddrspaces) + : ModulePass(ID), ASRemapper(ASRemapper){}; + +public: + bool runOnModule(Module &M) override { + bool modified = removeAddrspaces(M, ASRemapper); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif + return modified; + } +}; + +char RemoveAddrspacesPassLegacy::ID = 0; +static RegisterPass X("RemoveAddrspaces", "Remove IR address space information.", false, @@ -467,7 +460,21 @@ static RegisterPass Pass *createRemoveAddrspacesPass( AddrspaceRemapFunction ASRemapper = removeAllAddrspaces) { - return new RemoveAddrspacesPass(ASRemapper); + return new RemoveAddrspacesPassLegacy(ASRemapper); +} + +RemoveAddrspacesPass::RemoveAddrspacesPass() : RemoveAddrspacesPass(removeAllAddrspaces) {} + +PreservedAnalyses RemoveAddrspacesPass::run(Module &M, ModuleAnalysisManager &AM) { + bool modified = removeAddrspaces(M, ASRemapper); +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif + if (modified) { + return PreservedAnalyses::allInSet(); + } else { + return PreservedAnalyses::all(); + } } @@ -483,16 +490,16 @@ unsigned removeJuliaAddrspaces(unsigned AS) return AS; } -struct RemoveJuliaAddrspacesPass : public ModulePass { +struct RemoveJuliaAddrspacesPassLegacy : public ModulePass { static char ID; - RemoveAddrspacesPass Pass; - RemoveJuliaAddrspacesPass() : ModulePass(ID), Pass(removeJuliaAddrspaces){}; + RemoveAddrspacesPassLegacy Pass; + RemoveJuliaAddrspacesPassLegacy() : ModulePass(ID), Pass(removeJuliaAddrspaces){}; - bool runOnModule(Module &M) { return Pass.runOnModule(M); } + bool runOnModule(Module &M) override { return Pass.runOnModule(M); } }; -char RemoveJuliaAddrspacesPass::ID = 0; -static RegisterPass +char RemoveJuliaAddrspacesPassLegacy::ID = 0; +static RegisterPass Y("RemoveJuliaAddrspaces", "Remove IR address space information.", false, @@ -500,10 +507,15 @@ static RegisterPass Pass *createRemoveJuliaAddrspacesPass() { - return new RemoveJuliaAddrspacesPass(); + return new RemoveJuliaAddrspacesPassLegacy(); +} + +PreservedAnalyses RemoveJuliaAddrspacesPass::run(Module &M, ModuleAnalysisManager &AM) { + return RemoveAddrspacesPass(removeJuliaAddrspaces).run(M, AM); } -extern "C" JL_DLLEXPORT void LLVMExtraAddRemoveJuliaAddrspacesPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddRemoveJuliaAddrspacesPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createRemoveJuliaAddrspacesPass()); } diff --git a/src/llvm-remove-ni.cpp b/src/llvm-remove-ni.cpp index 40b0ecd735b13..b767074202eb2 100644 --- a/src/llvm-remove-ni.cpp +++ b/src/llvm-remove-ni.cpp @@ -1,7 +1,9 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "llvm-version.h" +#include "passes.h" +#include #include #include #include @@ -15,7 +17,7 @@ using namespace llvm; namespace { -static bool removeNI(Module &M) +static bool removeNI(Module &M) JL_NOTSAFEPOINT { auto dlstr = M.getDataLayoutStr(); auto nistart = dlstr.find("-ni:"); @@ -34,13 +36,11 @@ static bool removeNI(Module &M) } } -struct RemoveNI : PassInfoMixin { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); -}; - PreservedAnalyses RemoveNI::run(Module &M, ModuleAnalysisManager &AM) { - removeNI(M); + if (removeNI(M)) { + return PreservedAnalyses::allInSet(); + } return PreservedAnalyses::all(); } @@ -68,7 +68,8 @@ Pass *createRemoveNIPass() return new RemoveNILegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddRemoveNIPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddRemoveNIPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createRemoveNIPass()); } diff --git a/src/llvm-simdloop.cpp b/src/llvm-simdloop.cpp index 8d80a535b2319..fcb05ba7c6805 100644 --- a/src/llvm-simdloop.cpp +++ b/src/llvm-simdloop.cpp @@ -1,8 +1,7 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include "llvm-version.h" - -#define DEBUG_TYPE "lower_simd_loop" +#include "passes.h" // This file defines a LLVM pass that: // 1. Set's loop information in form of metadata @@ -19,20 +18,39 @@ #include #include +#include #include +#include #include #include -#include #include +#include #include #include "julia_assert.h" -namespace llvm { +#define DEBUG_TYPE "lower_simd_loop" +using namespace llvm; + +STATISTIC(TotalMarkedLoops, "Total number of loops marked with simdloop"); +STATISTIC(IVDepLoops, "Number of loops with no loop-carried dependencies"); +STATISTIC(SimdLoops, "Number of loops with SIMD instructions"); +STATISTIC(IVDepInstructions, "Number of instructions marked ivdep"); +STATISTIC(ReductionChains, "Number of reduction chains folded"); +STATISTIC(ReductionChainLength, "Total sum of instructions folded from reduction chain"); +STATISTIC(MaxChainLength, "Max length of reduction chain"); +STATISTIC(AddChains, "Addition reduction chains"); +STATISTIC(MulChains, "Multiply reduction chains"); + +#ifndef __clang_gcanalyzer__ +#define REMARK(remark) ORE.emit(remark) +#else +#define REMARK(remark) (void) 0; +#endif namespace { -static unsigned getReduceOpcode(Instruction *J, Instruction *operand) +static unsigned getReduceOpcode(Instruction *J, Instruction *operand) JL_NOTSAFEPOINT { switch (J->getOpcode()) { case Instruction::FSub: @@ -55,7 +73,7 @@ static unsigned getReduceOpcode(Instruction *J, Instruction *operand) /// If Phi is part of a reduction cycle of FAdd, FSub, FMul or FDiv, /// mark the ops as permitting reassociation/commuting. /// As of LLVM 4.0, FDiv is not handled by the loop vectorizer -static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) +static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L, OptimizationRemarkEmitter &ORE) JL_NOTSAFEPOINT { typedef SmallVector chainVector; chainVector chain; @@ -69,6 +87,10 @@ static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) if (L->contains(U)) { if (J) { LLVM_DEBUG(dbgs() << "LSL: not a reduction var because op has two internal uses: " << *I << "\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "NotReductionVar", U) + << "not a reduction variable because operation has two internal uses"; + }); return; } J = U; @@ -76,6 +98,10 @@ static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) } if (!J) { LLVM_DEBUG(dbgs() << "LSL: chain prematurely terminated at " << *I << "\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "ChainPrematurelyTerminated", I) + << "chain prematurely terminated at " << ore::NV("Instruction", I); + }); return; } if (J == Phi) { @@ -86,6 +112,10 @@ static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) // Check that arithmetic op matches prior arithmetic ops in the chain. if (getReduceOpcode(J, I) != opcode) { LLVM_DEBUG(dbgs() << "LSL: chain broke at " << *J << " because of wrong opcode\n"); + REMARK([&](){ + return OptimizationRemarkMissed(DEBUG_TYPE, "ChainBroke", J) + << "chain broke at " << ore::NV("Instruction", J) << " because of wrong opcode"; + }); return; } } @@ -94,30 +124,55 @@ static void enableUnsafeAlgebraIfReduction(PHINode *Phi, Loop *L) opcode = getReduceOpcode(J, I); if (!opcode) { LLVM_DEBUG(dbgs() << "LSL: first arithmetic op in chain is uninteresting" << *J << "\n"); + REMARK([&]() { + return OptimizationRemarkMissed(DEBUG_TYPE, "FirstArithmeticOpInChainIsUninteresting", J) + << "first arithmetic op in chain is uninteresting"; + }); return; } } chain.push_back(J); } + switch (opcode) { + case Instruction::FAdd: + ++AddChains; + break; + case Instruction::FMul: + ++MulChains; + break; + } + ++ReductionChains; + int length = 0; for (chainVector::const_iterator K=chain.begin(); K!=chain.end(); ++K) { LLVM_DEBUG(dbgs() << "LSL: marking " << **K << "\n"); + REMARK([&]() { + return OptimizationRemark(DEBUG_TYPE, "MarkedUnsafeAlgebra", *K) + << "marked unsafe algebra on " << ore::NV("Instruction", *K); + }); (*K)->setFast(true); + ++length; } + ReductionChainLength += length; + MaxChainLength.updateMax(length); } -static bool markLoopInfo(Module &M, Function *marker, function_ref GetLI) +static bool markLoopInfo(Module &M, Function *marker, function_ref GetLI) JL_NOTSAFEPOINT { bool Changed = false; std::vector ToDelete; for (User *U : marker->users()) { + ++TotalMarkedLoops; Instruction *I = cast(U); ToDelete.push_back(I); - LoopInfo &LI = GetLI(*I->getParent()->getParent()); - Loop *L = LI.getLoopFor(I->getParent()); - I->removeFromParent(); - if (!L) + BasicBlock *B = I->getParent(); + OptimizationRemarkEmitter ORE(B->getParent()); + LoopInfo &LI = GetLI(*B->getParent()); + Loop *L = LI.getLoopFor(B); + if (!L) { + I->removeFromParent(); continue; + } LLVM_DEBUG(dbgs() << "LSL: loopinfo marker found\n"); bool simd = false; @@ -156,6 +211,11 @@ static bool markLoopInfo(Module &M, Function *marker, function_refgetDebugLoc(), B) + << "Loop marked for SIMD vectorization with flags { \"simd\": " << (simd ? "true" : "false") << ", \"ivdep\": " << (ivdep ? "true" : "false") << " }"; + }); + MDNode *n = L->getLoopID(); if (n) { // Loop already has a LoopID so copy over Metadata @@ -176,10 +236,12 @@ static bool markLoopInfo(Module &M, Function *marker, function_refblocks()) { for (Instruction &I : *BB) { if (I.mayReadOrWriteMemory()) { + ++IVDepInstructions; I.setMetadata(LLVMContext::MD_mem_parallel_loop_access, m); } } @@ -188,22 +250,27 @@ static bool markLoopInfo(Module &M, Function *marker, function_refbegin(), E = Lh->end(); I != E; ++I) { if (PHINode *Phi = dyn_cast(I)) - enableUnsafeAlgebraIfReduction(Phi, L); + enableUnsafeAlgebraIfReduction(Phi, L, ORE); else break; } } + I->removeFromParent(); + Changed = true; } for (Instruction *I : ToDelete) I->deleteValue(); marker->eraseFromParent(); - +#ifdef JL_VERIFY_PASSES + assert(!verifyModule(M, &errs())); +#endif return Changed; } @@ -213,9 +280,6 @@ static bool markLoopInfo(Module &M, Function *marker, function_ref { - PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); -}; PreservedAnalyses LowerSIMDLoop::run(Module &M, ModuleAnalysisManager &AM) @@ -232,7 +296,11 @@ PreservedAnalyses LowerSIMDLoop::run(Module &M, ModuleAnalysisManager &AM) return FAM.getResult(F); }; - markLoopInfo(M, loopinfo_marker, GetLI); + if (markLoopInfo(M, loopinfo_marker, GetLI)) { + auto preserved = PreservedAnalyses::allInSet(); + preserved.preserve(); + return preserved; + } return PreservedAnalyses::all(); } @@ -252,7 +320,7 @@ class LowerSIMDLoopLegacy : public ModulePass { Function *loopinfo_marker = M.getFunction("julia.loopinfo_marker"); - auto GetLI = [this](Function &F) -> LoopInfo & { + auto GetLI = [this](Function &F) JL_NOTSAFEPOINT -> LoopInfo & { return getAnalysis(F).getLoopInfo(); }; @@ -279,14 +347,13 @@ static RegisterPass X("LowerSIMDLoop", "LowerSIMDLoop Pass" false /* Only looks at CFG */, false /* Analysis Pass */); -JL_DLLEXPORT Pass *createLowerSimdLoopPass() +Pass *createLowerSimdLoopPass() { return new LowerSIMDLoopLegacy(); } -extern "C" JL_DLLEXPORT void LLVMExtraAddLowerSimdLoopPass_impl(LLVMPassManagerRef PM) +extern "C" JL_DLLEXPORT_CODEGEN +void LLVMExtraAddLowerSimdLoopPass_impl(LLVMPassManagerRef PM) { unwrap(PM)->add(createLowerSimdLoopPass()); } - -} // namespace llvm diff --git a/src/llvm-version.h b/src/llvm-version.h index 6d79abdf271f1..01638b8d44a6e 100644 --- a/src/llvm-version.h +++ b/src/llvm-version.h @@ -2,6 +2,7 @@ #include #include "julia_assert.h" +#include "platform.h" // The LLVM version used, JL_LLVM_VERSION, is represented as a 5-digit integer // of the form ABBCC, where A is the major version, B is minor, and C is patch. @@ -9,8 +10,21 @@ #define JL_LLVM_VERSION (LLVM_VERSION_MAJOR * 10000 + LLVM_VERSION_MINOR * 100 \ + LLVM_VERSION_PATCH) -#if JL_LLVM_VERSION < 120000 - #error Only LLVM versions >= 12.0.0 are supported by Julia +#if JL_LLVM_VERSION < 140000 + #error Only LLVM versions >= 14.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 diff --git a/src/llvmcalltest.cpp b/src/llvmcalltest.cpp index 1ce8e9fe55bef..93c442445d79a 100644 --- a/src/llvmcalltest.cpp +++ b/src/llvmcalltest.cpp @@ -9,7 +9,7 @@ #include #include "julia.h" -#include "codegen_shared.h" +#include "llvm-codegen-shared.h" using namespace llvm; @@ -17,21 +17,15 @@ using namespace llvm; #ifdef _OS_WINDOWS_ # define DLLEXPORT __declspec(dllexport) #else -# if defined(_OS_LINUX_) -# define DLLEXPORT __attribute__ ((visibility("protected"))) -# else # define DLLEXPORT __attribute__ ((visibility("default"))) -# endif #endif extern "C" { DLLEXPORT const char *MakeIdentityFunction(jl_value_t* jl_AnyTy) { LLVMContext Ctx; - PointerType *AnyTy = PointerType::get(StructType::get(Ctx), 0); - // FIXME: get AnyTy via jl_type_to_llvm(Ctx, jl_AnyTy) - - Type *TrackedTy = PointerType::get(AnyTy->getElementType(), AddressSpace::Tracked); + // FIXME: get TrackedTy via jl_type_to_llvm(Ctx, jl_AnyTy) + Type *TrackedTy = PointerType::get(StructType::get(Ctx), AddressSpace::Tracked); Module *M = new llvm::Module("shadow", Ctx); Function *F = Function::Create( FunctionType::get( diff --git a/src/mach_dyld_atfork.tbd b/src/mach_dyld_atfork.tbd index 9a5d18099dbcf..c2cda4417ec38 100644 --- a/src/mach_dyld_atfork.tbd +++ b/src/mach_dyld_atfork.tbd @@ -21,5 +21,6 @@ install-name: '/usr/lib/libSystem.B.dylib' exports: - targets: [ arm64-macos, arm64e-macos, x86_64-macos, x86_64-maccatalyst, arm64-maccatalyst, arm64e-maccatalyst ] - symbols: [ __dyld_atfork_parent, __dyld_atfork_prepare ] + symbols: [ __dyld_atfork_parent, __dyld_atfork_prepare, + __dyld_dlopen_atfork_parent, __dyld_dlopen_atfork_prepare ] ... diff --git a/src/mach_excServer.c b/src/mach_excServer.c new file mode 100644 index 0000000000000..7e99331fa8554 --- /dev/null +++ b/src/mach_excServer.c @@ -0,0 +1,863 @@ +/* + * IDENTIFICATION: + * stub generated Fri Apr 1 18:55:39 2022 + * with a MiG generated by bootstrap_cmds-122 + * from mach/mach_exc.defs + * OPTIONS: + */ +/* + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * 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. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ + +/* Module mach_exc */ + +#define __MIG_check__Request__mach_exc_subsystem__ 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* BEGIN VOUCHER CODE */ + +#ifndef KERNEL +#if defined(__has_include) +#if __has_include() +#ifndef USING_VOUCHERS +#define USING_VOUCHERS +#endif +#ifndef __VOUCHER_FORWARD_TYPE_DECLS__ +#define __VOUCHER_FORWARD_TYPE_DECLS__ +#ifdef __cplusplus +extern "C" { +#endif + extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import)); +#ifdef __cplusplus +} +#endif +#endif // __VOUCHER_FORWARD_TYPE_DECLS__ +#endif // __has_include() +#endif // __has_include +#endif // !KERNEL + +/* END VOUCHER CODE */ + + +/* BEGIN MIG_STRNCPY_ZEROFILL CODE */ + +#if defined(__has_include) +#if __has_include() +#ifndef USING_MIG_STRNCPY_ZEROFILL +#define USING_MIG_STRNCPY_ZEROFILL +#endif +#ifndef __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ +#define __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ +#ifdef __cplusplus +extern "C" { +#endif + extern int mig_strncpy_zerofill(char *dest, const char *src, int len) __attribute__((weak_import)); +#ifdef __cplusplus +} +#endif +#endif /* __MIG_STRNCPY_ZEROFILL_FORWARD_TYPE_DECLS__ */ +#endif /* __has_include() */ +#endif /* __has_include */ + +/* END MIG_STRNCPY_ZEROFILL CODE */ + + +#include +#include +#include +#include +#include + +#ifndef mig_internal +#define mig_internal static __inline__ +#endif /* mig_internal */ + +#ifndef mig_external +#define mig_external +#endif /* mig_external */ + +#if !defined(__MigTypeCheck) && defined(TypeCheck) +#define __MigTypeCheck TypeCheck /* Legacy setting */ +#endif /* !defined(__MigTypeCheck) */ + +#if !defined(__MigKernelSpecificCode) && defined(_MIG_KERNEL_SPECIFIC_CODE_) +#define __MigKernelSpecificCode _MIG_KERNEL_SPECIFIC_CODE_ /* Legacy setting */ +#endif /* !defined(__MigKernelSpecificCode) */ + +#ifndef LimitCheck +#define LimitCheck 0 +#endif /* LimitCheck */ + +#ifndef min +#define min(a,b) ( ((a) < (b))? (a): (b) ) +#endif /* min */ + +#if !defined(_WALIGN_) +#define _WALIGN_(x) (((x) + 3) & ~3) +#endif /* !defined(_WALIGN_) */ + +#if !defined(_WALIGNSZ_) +#define _WALIGNSZ_(x) _WALIGN_(sizeof(x)) +#endif /* !defined(_WALIGNSZ_) */ + +#ifndef UseStaticTemplates +#define UseStaticTemplates 0 +#endif /* UseStaticTemplates */ + +#ifndef MIG_SERVER_ROUTINE +#define MIG_SERVER_ROUTINE +#endif + +#ifndef __DeclareRcvRpc +#define __DeclareRcvRpc(_NUM_, _NAME_) +#endif /* __DeclareRcvRpc */ + +#ifndef __BeforeRcvRpc +#define __BeforeRcvRpc(_NUM_, _NAME_) +#endif /* __BeforeRcvRpc */ + +#ifndef __AfterRcvRpc +#define __AfterRcvRpc(_NUM_, _NAME_) +#endif /* __AfterRcvRpc */ + +#ifndef __DeclareRcvSimple +#define __DeclareRcvSimple(_NUM_, _NAME_) +#endif /* __DeclareRcvSimple */ + +#ifndef __BeforeRcvSimple +#define __BeforeRcvSimple(_NUM_, _NAME_) +#endif /* __BeforeRcvSimple */ + +#ifndef __AfterRcvSimple +#define __AfterRcvSimple(_NUM_, _NAME_) +#endif /* __AfterRcvSimple */ + +#define novalue void + +#define msgh_request_port msgh_local_port +#define MACH_MSGH_BITS_REQUEST(bits) MACH_MSGH_BITS_LOCAL(bits) +#define msgh_reply_port msgh_remote_port +#define MACH_MSGH_BITS_REPLY(bits) MACH_MSGH_BITS_REMOTE(bits) + +#define MIG_RETURN_ERROR(X, code) {\ + ((mig_reply_error_t *)X)->RetCode = code;\ + ((mig_reply_error_t *)X)->NDR = NDR_record;\ + return;\ + } + +/* typedefs for all requests */ + +#ifndef __Request__mach_exc_subsystem__defined +#define __Request__mach_exc_subsystem__defined + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + } __Request__mach_exception_raise_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; + } __Request__mach_exception_raise_state_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; + } __Request__mach_exception_raise_state_identity_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif +#endif /* !__Request__mach_exc_subsystem__defined */ + +/* typedefs for all replies */ + +#ifndef __Reply__mach_exc_subsystem__defined +#define __Reply__mach_exc_subsystem__defined + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + } __Reply__mach_exception_raise_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + int flavor; + mach_msg_type_number_t new_stateCnt; + natural_t new_state[1296]; + } __Reply__mach_exception_raise_state_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + int flavor; + mach_msg_type_number_t new_stateCnt; + natural_t new_state[1296]; + } __Reply__mach_exception_raise_state_identity_t __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif +#endif /* !__Reply__mach_exc_subsystem__defined */ + + +/* union of all replies */ + +#ifndef __ReplyUnion__catch_mach_exc_subsystem__defined +#define __ReplyUnion__catch_mach_exc_subsystem__defined +union __ReplyUnion__catch_mach_exc_subsystem { + __Reply__mach_exception_raise_t Reply_mach_exception_raise; + __Reply__mach_exception_raise_state_t Reply_mach_exception_raise_state; + __Reply__mach_exception_raise_state_identity_t Reply_mach_exception_raise_state_identity; +}; +#endif /* __ReplyUnion__catch_mach_exc_subsystem__defined */ +/* Forward Declarations */ + + +mig_internal novalue _Xmach_exception_raise + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); + +mig_internal novalue _Xmach_exception_raise_state + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); + +mig_internal novalue _Xmach_exception_raise_state_identity + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); + + +#if ( __MigTypeCheck ) +#if __MIG_check__Request__mach_exc_subsystem__ +#if !defined(__MIG_check__Request__mach_exception_raise_t__defined) +#define __MIG_check__Request__mach_exception_raise_t__defined + +mig_internal kern_return_t __MIG_check__Request__mach_exception_raise_t(__attribute__((__unused__)) __Request__mach_exception_raise_t *In0P) +{ + + typedef __Request__mach_exception_raise_t __Request; +#if __MigTypeCheck + unsigned int msgh_size; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + msgh_size = In0P->Head.msgh_size; + if (!(In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + (In0P->msgh_body.msgh_descriptor_count != 2) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 16)) || (msgh_size > (mach_msg_size_t)sizeof(__Request))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->thread.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->thread.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->task.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->task.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if defined(__NDR_convert__int_rep__Request__mach_exception_raise_t__codeCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_t__codeCnt(&In0P->codeCnt, In0P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_t__codeCnt__defined */ +#if __MigTypeCheck + if ( In0P->codeCnt > 2 ) + return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 16)) / 8 < In0P->codeCnt) || + (msgh_size != (mach_msg_size_t)(sizeof(__Request) - 16) + (8 * In0P->codeCnt))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + + return MACH_MSG_SUCCESS; +} +#endif /* !defined(__MIG_check__Request__mach_exception_raise_t__defined) */ +#endif /* __MIG_check__Request__mach_exc_subsystem__ */ +#endif /* ( __MigTypeCheck ) */ + + +/* Routine mach_exception_raise */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ +MIG_SERVER_ROUTINE +kern_return_t catch_mach_exception_raise +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt +); + +/* Routine mach_exception_raise */ +mig_internal novalue _Xmach_exception_raise + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) +{ + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + mach_msg_trailer_t trailer; + } Request __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + typedef __Request__mach_exception_raise_t __Request; + typedef __Reply__mach_exception_raise_t Reply __attribute__((unused)); + + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + Request *In0P = (Request *) InHeadP; + Reply *OutP = (Reply *) OutHeadP; +#ifdef __MIG_check__Request__mach_exception_raise_t__defined + kern_return_t check_result; +#endif /* __MIG_check__Request__mach_exception_raise_t__defined */ + + __DeclareRcvRpc(2405, "mach_exception_raise") + __BeforeRcvRpc(2405, "mach_exception_raise") + +#if defined(__MIG_check__Request__mach_exception_raise_t__defined) + check_result = __MIG_check__Request__mach_exception_raise_t((__Request *)In0P); + if (check_result != MACH_MSG_SUCCESS) + { MIG_RETURN_ERROR(OutP, check_result); } +#endif /* defined(__MIG_check__Request__mach_exception_raise_t__defined) */ + + OutP->RetCode = catch_mach_exception_raise(In0P->Head.msgh_request_port, In0P->thread.name, In0P->task.name, In0P->exception, In0P->code, In0P->codeCnt); + + OutP->NDR = NDR_record; + + + __AfterRcvRpc(2405, "mach_exception_raise") +} + +#if ( __MigTypeCheck ) +#if __MIG_check__Request__mach_exc_subsystem__ +#if !defined(__MIG_check__Request__mach_exception_raise_state_t__defined) +#define __MIG_check__Request__mach_exception_raise_state_t__defined + +mig_internal kern_return_t __MIG_check__Request__mach_exception_raise_state_t(__attribute__((__unused__)) __Request__mach_exception_raise_state_t *In0P, __attribute__((__unused__)) __Request__mach_exception_raise_state_t **In1PP) +{ + + typedef __Request__mach_exception_raise_state_t __Request; + __Request *In1P; +#if __MigTypeCheck + unsigned int msgh_size; +#endif /* __MigTypeCheck */ + unsigned int msgh_size_delta; + +#if __MigTypeCheck + msgh_size = In0P->Head.msgh_size; + if ((In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 5200)) || (msgh_size > (mach_msg_size_t)sizeof(__Request))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + +#if defined(__NDR_convert__int_rep__Request__mach_exception_raise_state_t__codeCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_t__codeCnt(&In0P->codeCnt, In0P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_t__codeCnt__defined */ + msgh_size_delta = (8 * In0P->codeCnt); +#if __MigTypeCheck + if ( In0P->codeCnt > 2 ) + return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 8 < In0P->codeCnt) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 5200) + (8 * In0P->codeCnt))) + return MIG_BAD_ARGUMENTS; + msgh_size -= msgh_size_delta; +#endif /* __MigTypeCheck */ + + *In1PP = In1P = (__Request *) ((pointer_t) In0P + msgh_size_delta - 16); + +#if defined(__NDR_convert__int_rep__Request__mach_exception_raise_state_t__old_stateCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_t__old_stateCnt(&In1P->old_stateCnt, In1P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_t__old_stateCnt__defined */ +#if __MigTypeCheck + if ( In1P->old_stateCnt > 1296 ) + return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 4 < In1P->old_stateCnt) || + (msgh_size != (mach_msg_size_t)(sizeof(__Request) - 5200) + (4 * In1P->old_stateCnt))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + + return MACH_MSG_SUCCESS; +} +#endif /* !defined(__MIG_check__Request__mach_exception_raise_state_t__defined) */ +#endif /* __MIG_check__Request__mach_exc_subsystem__ */ +#endif /* ( __MigTypeCheck ) */ + + +/* Routine mach_exception_raise_state */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ +MIG_SERVER_ROUTINE +kern_return_t catch_mach_exception_raise_state +( + mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +/* Routine mach_exception_raise_state */ +mig_internal novalue _Xmach_exception_raise_state + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) +{ + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; + mach_msg_trailer_t trailer; + } Request __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + typedef __Request__mach_exception_raise_state_t __Request; + typedef __Reply__mach_exception_raise_state_t Reply __attribute__((unused)); + + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + Request *In0P = (Request *) InHeadP; + Request *In1P; + Reply *OutP = (Reply *) OutHeadP; +#ifdef __MIG_check__Request__mach_exception_raise_state_t__defined + kern_return_t check_result; +#endif /* __MIG_check__Request__mach_exception_raise_state_t__defined */ + + __DeclareRcvRpc(2406, "mach_exception_raise_state") + __BeforeRcvRpc(2406, "mach_exception_raise_state") + +#if defined(__MIG_check__Request__mach_exception_raise_state_t__defined) + check_result = __MIG_check__Request__mach_exception_raise_state_t((__Request *)In0P, (__Request **)&In1P); + if (check_result != MACH_MSG_SUCCESS) + { MIG_RETURN_ERROR(OutP, check_result); } +#endif /* defined(__MIG_check__Request__mach_exception_raise_state_t__defined) */ + + OutP->new_stateCnt = 1296; + + OutP->RetCode = catch_mach_exception_raise_state(In0P->Head.msgh_request_port, In0P->exception, In0P->code, In0P->codeCnt, &In1P->flavor, In1P->old_state, In1P->old_stateCnt, OutP->new_state, &OutP->new_stateCnt); + if (OutP->RetCode != KERN_SUCCESS) { + MIG_RETURN_ERROR(OutP, OutP->RetCode); + } + + OutP->NDR = NDR_record; + + + OutP->flavor = In1P->flavor; + OutP->Head.msgh_size = (mach_msg_size_t)(sizeof(Reply) - 5184) + (((4 * OutP->new_stateCnt))); + + __AfterRcvRpc(2406, "mach_exception_raise_state") +} + +#if ( __MigTypeCheck ) +#if __MIG_check__Request__mach_exc_subsystem__ +#if !defined(__MIG_check__Request__mach_exception_raise_state_identity_t__defined) +#define __MIG_check__Request__mach_exception_raise_state_identity_t__defined + +mig_internal kern_return_t __MIG_check__Request__mach_exception_raise_state_identity_t(__attribute__((__unused__)) __Request__mach_exception_raise_state_identity_t *In0P, __attribute__((__unused__)) __Request__mach_exception_raise_state_identity_t **In1PP) +{ + + typedef __Request__mach_exception_raise_state_identity_t __Request; + __Request *In1P; +#if __MigTypeCheck + unsigned int msgh_size; +#endif /* __MigTypeCheck */ + unsigned int msgh_size_delta; + +#if __MigTypeCheck + msgh_size = In0P->Head.msgh_size; + if (!(In0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + (In0P->msgh_body.msgh_descriptor_count != 2) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 5200)) || (msgh_size > (mach_msg_size_t)sizeof(__Request))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->thread.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->thread.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if __MigTypeCheck + if (In0P->task.type != MACH_MSG_PORT_DESCRIPTOR || + In0P->task.disposition != 17) + return MIG_TYPE_ERROR; +#endif /* __MigTypeCheck */ + +#if defined(__NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__codeCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__codeCnt(&In0P->codeCnt, In0P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__codeCnt__defined */ + msgh_size_delta = (8 * In0P->codeCnt); +#if __MigTypeCheck + if ( In0P->codeCnt > 2 ) + return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 8 < In0P->codeCnt) || + (msgh_size < (mach_msg_size_t)(sizeof(__Request) - 5200) + (8 * In0P->codeCnt))) + return MIG_BAD_ARGUMENTS; + msgh_size -= msgh_size_delta; +#endif /* __MigTypeCheck */ + + *In1PP = In1P = (__Request *) ((pointer_t) In0P + msgh_size_delta - 16); + +#if defined(__NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__old_stateCnt__defined) + if (In0P->NDR.int_rep != NDR_record.int_rep) + __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__old_stateCnt(&In1P->old_stateCnt, In1P->NDR.int_rep); +#endif /* __NDR_convert__int_rep__Request__mach_exception_raise_state_identity_t__old_stateCnt__defined */ +#if __MigTypeCheck + if ( In1P->old_stateCnt > 1296 ) + return MIG_BAD_ARGUMENTS; + if (((msgh_size - (mach_msg_size_t)(sizeof(__Request) - 5200)) / 4 < In1P->old_stateCnt) || + (msgh_size != (mach_msg_size_t)(sizeof(__Request) - 5200) + (4 * In1P->old_stateCnt))) + return MIG_BAD_ARGUMENTS; +#endif /* __MigTypeCheck */ + + return MACH_MSG_SUCCESS; +} +#endif /* !defined(__MIG_check__Request__mach_exception_raise_state_identity_t__defined) */ +#endif /* __MIG_check__Request__mach_exc_subsystem__ */ +#endif /* ( __MigTypeCheck ) */ + + +/* Routine mach_exception_raise_state_identity */ +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ +MIG_SERVER_ROUTINE +kern_return_t catch_mach_exception_raise_state_identity +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +/* Routine mach_exception_raise_state_identity */ +mig_internal novalue _Xmach_exception_raise_state_identity + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) +{ + +#ifdef __MigPackStructs +#pragma pack(push, 4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; + int flavor; + mach_msg_type_number_t old_stateCnt; + natural_t old_state[1296]; + mach_msg_trailer_t trailer; + } Request __attribute__((unused)); +#ifdef __MigPackStructs +#pragma pack(pop) +#endif + typedef __Request__mach_exception_raise_state_identity_t __Request; + typedef __Reply__mach_exception_raise_state_identity_t Reply __attribute__((unused)); + + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + Request *In0P = (Request *) InHeadP; + Request *In1P; + Reply *OutP = (Reply *) OutHeadP; +#ifdef __MIG_check__Request__mach_exception_raise_state_identity_t__defined + kern_return_t check_result; +#endif /* __MIG_check__Request__mach_exception_raise_state_identity_t__defined */ + + __DeclareRcvRpc(2407, "mach_exception_raise_state_identity") + __BeforeRcvRpc(2407, "mach_exception_raise_state_identity") + +#if defined(__MIG_check__Request__mach_exception_raise_state_identity_t__defined) + check_result = __MIG_check__Request__mach_exception_raise_state_identity_t((__Request *)In0P, (__Request **)&In1P); + if (check_result != MACH_MSG_SUCCESS) + { MIG_RETURN_ERROR(OutP, check_result); } +#endif /* defined(__MIG_check__Request__mach_exception_raise_state_identity_t__defined) */ + + OutP->new_stateCnt = 1296; + + OutP->RetCode = catch_mach_exception_raise_state_identity(In0P->Head.msgh_request_port, In0P->thread.name, In0P->task.name, In0P->exception, In0P->code, In0P->codeCnt, &In1P->flavor, In1P->old_state, In1P->old_stateCnt, OutP->new_state, &OutP->new_stateCnt); + if (OutP->RetCode != KERN_SUCCESS) { + MIG_RETURN_ERROR(OutP, OutP->RetCode); + } + + OutP->NDR = NDR_record; + + + OutP->flavor = In1P->flavor; + OutP->Head.msgh_size = (mach_msg_size_t)(sizeof(Reply) - 5184) + (((4 * OutP->new_stateCnt))); + + __AfterRcvRpc(2407, "mach_exception_raise_state_identity") +} + + +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ +boolean_t mach_exc_server( + mach_msg_header_t *InHeadP, + mach_msg_header_t *OutHeadP); + +#ifdef mig_external +mig_external +#else +extern +#endif /* mig_external */ +mig_routine_t mach_exc_server_routine( + mach_msg_header_t *InHeadP); + + +/* Description of this subsystem, for use in direct RPC */ +const struct catch_mach_exc_subsystem { + mig_server_routine_t server; /* Server routine */ + mach_msg_id_t start; /* Min routine number */ + mach_msg_id_t end; /* Max routine number + 1 */ + unsigned int maxsize; /* Max msg size */ + vm_address_t reserved; /* Reserved */ + struct routine_descriptor /*Array of routine descriptors */ + routine[4]; +} catch_mach_exc_subsystem = { + mach_exc_server_routine, + 2405, + 2409, + (mach_msg_size_t)sizeof(union __ReplyUnion__catch_mach_exc_subsystem), + (vm_address_t)0, + { + { (mig_impl_routine_t) 0, + (mig_stub_routine_t) _Xmach_exception_raise, 6, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__mach_exception_raise_t)}, + { (mig_impl_routine_t) 0, + (mig_stub_routine_t) _Xmach_exception_raise_state, 9, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__mach_exception_raise_state_t)}, + { (mig_impl_routine_t) 0, + (mig_stub_routine_t) _Xmach_exception_raise_state_identity, 11, 0, (routine_arg_descriptor_t)0, (mach_msg_size_t)sizeof(__Reply__mach_exception_raise_state_identity_t)}, + {0, 0, 0, 0, 0, 0}, + } +}; + +mig_external boolean_t mach_exc_server + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP) +{ + /* + * typedef struct { + * mach_msg_header_t Head; + * NDR_record_t NDR; + * kern_return_t RetCode; + * } mig_reply_error_t; + */ + + mig_routine_t routine; + + OutHeadP->msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REPLY(InHeadP->msgh_bits), 0); + OutHeadP->msgh_remote_port = InHeadP->msgh_reply_port; + /* Minimal size: routine() will update it if different */ + OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); + OutHeadP->msgh_local_port = MACH_PORT_NULL; + OutHeadP->msgh_id = InHeadP->msgh_id + 100; + OutHeadP->msgh_reserved = 0; + + if ((InHeadP->msgh_id > 2408) || (InHeadP->msgh_id < 2405) || + ((routine = catch_mach_exc_subsystem.routine[InHeadP->msgh_id - 2405].stub_routine) == 0)) { + ((mig_reply_error_t *)OutHeadP)->NDR = NDR_record; + ((mig_reply_error_t *)OutHeadP)->RetCode = MIG_BAD_ID; + return FALSE; + } + (*routine) (InHeadP, OutHeadP); + return TRUE; +} + +mig_external mig_routine_t mach_exc_server_routine + (mach_msg_header_t *InHeadP) +{ + int msgh_id; + + msgh_id = InHeadP->msgh_id - 2405; + + if ((msgh_id > 3) || (msgh_id < 0)) + return 0; + + return catch_mach_exc_subsystem.routine[msgh_id].stub_routine; +} diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 516dd9b29f354..2933ca4888c4e 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -183,6 +183,19 @@ (cadr e) e)) +(define (unescape-global-lhs e env m parent-scope inarg) + (cond ((not (pair? e)) e) + ((eq? (car e) 'escape) (cadr e)) + ((memq (car e) '(parameters tuple)) + (list* (car e) (map (lambda (e) + (unescape-global-lhs e env m 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))) + (else + (resolve-expansion-vars-with-new-env e env m parent-scope inarg)))) + (define (typedef-expr-name e) (cond ((atom? e) e) ((or (eq? (car e) 'curly) (eq? (car e) '<:)) (typedef-expr-name (cadr e))) @@ -344,14 +357,14 @@ (m (cadr scope)) (parent-scope (cdr parent-scope))) (resolve-expansion-vars-with-new-env (cadr e) env m parent-scope inarg)))) - ((global) (let ((arg (cadr e))) - (cond ((symbol? arg) e) - ((assignment? arg) - `(global - (= ,(unescape (cadr arg)) - ,(resolve-expansion-vars-with-new-env (caddr arg) env m parent-scope inarg)))) - (else - `(global ,(resolve-expansion-vars-with-new-env arg env m 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))) + (cdr e)))) ((using import export meta line inbounds boundscheck loopinfo inline noinline) (map unescape e)) ((macrocall) e) ; invalid syntax anyways, so just act like it's quoted. ((symboliclabel) e) diff --git a/src/method.c b/src/method.c index f71bb8803caf0..c207149032fb9 100644 --- a/src/method.c +++ b/src/method.c @@ -17,9 +17,11 @@ extern "C" { extern jl_value_t *jl_builtin_getfield; 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, - jl_value_t *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); static void check_c_types(const char *where, jl_value_t *rt, jl_value_t *at) { @@ -51,11 +53,14 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve return jl_module_globalref(module, (jl_sym_t*)expr); } else if (jl_is_returnnode(expr)) { - jl_value_t *val = resolve_globals(jl_returnnode_value(expr), module, sparam_vals, binding_effects, eager_resolve); - if (val != jl_returnnode_value(expr)) { - JL_GC_PUSH1(&val); - expr = jl_new_struct(jl_returnnode_type, val); - JL_GC_POP(); + jl_value_t *retval = jl_returnnode_value(expr); + if (retval) { + jl_value_t *val = resolve_globals(retval, module, sparam_vals, binding_effects, eager_resolve); + if (val != retval) { + JL_GC_PUSH1(&val); + expr = jl_new_struct(jl_returnnode_type, val); + JL_GC_POP(); + } } return expr; } @@ -95,14 +100,16 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve jl_error("opaque_closure_method: invalid syntax"); } jl_value_t *name = jl_exprarg(e, 0); - jl_value_t *nargs = jl_exprarg(e, 1); + jl_value_t *oc_nargs = jl_exprarg(e, 1); int isva = jl_exprarg(e, 2) == jl_true; jl_value_t *functionloc = jl_exprarg(e, 3); jl_value_t *ci = jl_exprarg(e, 4); if (!jl_is_code_info(ci)) { jl_error("opaque_closure_method: lambda should be a CodeInfo"); + } 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, 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); return (jl_value_t*)m; } if (e->head == jl_cfunction_sym) { @@ -123,7 +130,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_typeis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate cfunction return type (it might depend on a local variable)"); else jl_rethrow(); @@ -135,7 +142,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_typeis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate cfunction argument type (it might depend on a local variable)"); else jl_rethrow(); @@ -156,7 +163,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_typeis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate ccall return type (it might depend on a local variable)"); else jl_rethrow(); @@ -168,7 +175,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_typeis(jl_current_exception(), jl_errorexception_type)) + if (jl_typetagis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate ccall argument type (it might depend on a local variable)"); else jl_rethrow(); @@ -308,10 +315,10 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) jl_array_t *meta = ((jl_expr_t*)st)->args; for (k = 0; k < na; k++) { jl_value_t *ma = jl_array_ptr_ref(meta, k); - if (ma == (jl_value_t*)jl_pure_sym) - li->pure = 1; - else if (ma == (jl_value_t*)jl_inline_sym) - li->inlineable = 1; + if (ma == (jl_value_t*)jl_inline_sym) + li->inlining = 1; + else if (ma == (jl_value_t*)jl_noinline_sym) + li->inlining = 2; else if (ma == (jl_value_t*)jl_propagate_inbounds_sym) li->propagate_inbounds = 1; else if (ma == (jl_value_t*)jl_aggressive_constprop_sym) @@ -319,12 +326,14 @@ 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) == 5) { + if (jl_expr_nargs(ma) == 7) { 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)); - li->purity.overrides.ipo_terminates = jl_unbox_bool(jl_exprarg(ma, 3)); + li->purity.overrides.ipo_terminates_globally = jl_unbox_bool(jl_exprarg(ma, 3)); li->purity.overrides.ipo_terminates_locally = jl_unbox_bool(jl_exprarg(ma, 4)); + li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5)); + li->purity.overrides.ipo_inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6)); } } else @@ -372,7 +381,9 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) 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)) { + li->has_fcall = 1; + } if (is_flag_stmt) jl_array_uint8_set(li->ssaflags, j, 0); else { @@ -428,18 +439,20 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) { jl_task_t *ct = jl_current_task; - jl_method_instance_t *li = + jl_method_instance_t *mi = (jl_method_instance_t*)jl_gc_alloc(ct->ptls, sizeof(jl_method_instance_t), jl_method_instance_type); - li->def.value = NULL; - li->specTypes = NULL; - li->sparam_vals = jl_emptysvec; - li->uninferred = NULL; - li->backedges = NULL; - li->callbacks = NULL; - jl_atomic_store_relaxed(&li->cache, NULL); - li->inInference = 0; - return li; + mi->def.value = NULL; + mi->specTypes = NULL; + 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; + jl_atomic_store_relaxed(&mi->precompiled, 0); + return mi; } JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) @@ -462,12 +475,13 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->min_world = 1; src->max_world = ~(size_t)0; src->inferred = 0; - src->inlineable = 0; src->propagate_inbounds = 0; - src->pure = 0; + src->has_fcall = 0; src->edges = jl_nothing; src->constprop = 0; + src->inlining = 0; src->purity.bits = 0; + src->inlining_cost = UINT16_MAX; return src; } @@ -481,42 +495,46 @@ jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir) return src; } -void jl_add_function_name_to_lineinfo(jl_code_info_t *ci, jl_value_t *name) +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_typeis(ln, jl_lineinfonode_type)); + 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); - jl_value_t *ln_name = (jl_is_long(inl) && jl_unbox_long(inl) == 0) ? name : jl_fieldref_noalloc(ln, 1); - rt = jl_new_struct(jl_lineinfonode_type, mod, ln_name, file, lno, inl); + // 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_INLINE jl_value_t *jl_call_staged(jl_method_t *def, jl_value_t *generator, jl_svec_t *sparam_vals, - jl_value_t **args, uint32_t nargs) +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) { size_t n_sparams = jl_svec_len(sparam_vals); jl_value_t **gargs; - size_t totargs = 1 + n_sparams + nargs + def->isva; + size_t totargs = 2 + n_sparams + def->nargs; JL_GC_PUSHARGS(gargs, totargs); - gargs[0] = generator; - memcpy(&gargs[1], jl_svec_data(sparam_vals), n_sparams * sizeof(void*)); - memcpy(&gargs[1 + n_sparams], args, nargs * sizeof(void*)); - if (def->isva) { - gargs[totargs-1] = jl_f_tuple(NULL, &gargs[1 + n_sparams + def->nargs - 1], nargs - (def->nargs - 1)); - gargs[1 + n_sparams + def->nargs - 1] = gargs[totargs - 1]; - } - jl_value_t *code = jl_apply(gargs, 1 + n_sparams + def->nargs); + gargs[0] = jl_box_ulong(world); + gargs[1] = jl_box_long(def->line); + gargs[1] = jl_new_struct(jl_linenumbernode_type, gargs[1], def->file); + memcpy(&gargs[2], jl_svec_data(sparam_vals), n_sparams * sizeof(void*)); + memcpy(&gargs[2 + n_sparams], args, (def->nargs - def->isva) * sizeof(void*)); + if (def->isva) + gargs[totargs - 1] = jl_f_tuple(NULL, &args[def->nargs - 1], nargs - def->nargs + 1); + jl_value_t *code = jl_apply_generic(generator, gargs, totargs); JL_GC_POP(); return code; } @@ -540,15 +558,18 @@ JL_DLLEXPORT jl_code_info_t *jl_expand_and_resolve(jl_value_t *ex, jl_module_t * // Return a newly allocated CodeInfo for the function signature // effectively described by the tuple (specTypes, env, Method) inside linfo -JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) +JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, size_t world) { - if (linfo->uninferred) { - return (jl_code_info_t*)jl_copy_ast((jl_value_t*)linfo->uninferred); + jl_value_t *uninferred = jl_atomic_load_relaxed(&linfo->uninferred); + if (uninferred) { + assert(jl_is_code_info(uninferred)); // make sure this did not get `nothing` put here + return (jl_code_info_t*)jl_copy_ast((jl_value_t*)uninferred); } - JL_TIMING(STAGED_FUNCTION); + JL_TIMING(STAGED_FUNCTION, STAGED_FUNCTION); jl_value_t *tt = linfo->specTypes; jl_method_t *def = linfo->def.method; + jl_timing_show_method_instance(linfo, JL_TIMING_CURRENT_BLOCK); jl_value_t *generator = def->generator; assert(generator != NULL); assert(jl_is_method(def)); @@ -562,13 +583,13 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) JL_TRY { ct->ptls->in_pure_callback = 1; - // and the right world ct->world_age = def->primary_world; // invoke code generator jl_tupletype_t *ttdt = (jl_tupletype_t*)jl_unwrap_unionall(tt); - ex = jl_call_staged(def, generator, linfo->sparam_vals, jl_svec_data(ttdt->parameters), jl_nparams(ttdt)); + ex = jl_call_staged(def, generator, world, linfo->sparam_vals, jl_svec_data(ttdt->parameters), jl_nparams(ttdt)); + // do some post-processing if (jl_is_code_info(ex)) { func = (jl_code_info_t*)ex; jl_array_t *stmts = (jl_array_t*)func->code; @@ -577,7 +598,6 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) else { // Lower the user's expression and resolve references to the type parameters func = jl_expand_and_resolve(ex, def->module, linfo->sparam_vals); - if (!jl_is_code_info(func)) { if (jl_is_expr(func) && ((jl_expr_t*)func)->head == jl_error_sym) { ct->ptls->in_pure_callback = 0; @@ -586,14 +606,24 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) 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) { 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) { - linfo->uninferred = jl_copy_ast((jl_value_t*)func); - jl_gc_wb(linfo, linfo->uninferred); + if (jl_options.incremental && jl_generating_output()) + jl_error("Impossible to correctly handle OpaqueClosure inside @generated returned during precompile process."); + jl_value_t *uninferred = jl_copy_ast((jl_value_t*)func); + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&linfo->uninferred, &old, uninferred)) { + jl_gc_wb(linfo, uninferred); + } + else { + assert(jl_is_code_info(old)); + func = (jl_code_info_t*)old; + } break; } } @@ -601,7 +631,6 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) ct->ptls->in_pure_callback = last_in; jl_lineno = last_lineno; ct->world_age = last_age; - jl_add_function_name_to_lineinfo(func, (jl_value_t*)def->name); } JL_CATCH { ct->ptls->in_pure_callback = last_in; @@ -653,15 +682,14 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) } } m->called = called; - m->pure = src->pure; m->constprop = src->constprop; m->purity.bits = src->purity.bits; - jl_add_function_name_to_lineinfo(src, (jl_value_t*)m->name); + 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_typeis(src->code, jl_array_any_type)); + 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); copy = jl_alloc_vec_any(n); @@ -675,7 +703,7 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) size_t j; for (j = 1; j < nargs; j++) { jl_value_t *aj = jl_exprarg(st, j); - if (!jl_is_slot(aj) && !jl_is_argument(aj)) + if (!jl_is_slotnumber(aj) && !jl_is_argument(aj)) continue; int sn = (int)jl_slot_number(aj) - 2; if (sn < 0) // @nospecialize on self is valid but currently ignored @@ -696,23 +724,33 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) else if (nargs >= 1 && jl_exprarg(st, 0) == (jl_value_t*)jl_specialize_sym) { if (nargs == 1) // bare `@specialize` is special: it causes specialization on all args m->nospecialize = 0; + for (j = 1; j < nargs; j++) { + jl_value_t *aj = jl_exprarg(st, j); + if (!jl_is_slotnumber(aj) && !jl_is_argument(aj)) + continue; + int sn = (int)jl_slot_number(aj) - 2; + if (sn < 0) // @specialize on self is valid but currently ignored + continue; + if (sn > (m->nargs - 2)) { + jl_error("@specialize annotation applied to a non-argument"); + } + if (sn >= sizeof(m->nospecialize) * 8) { + jl_printf(JL_STDERR, + "WARNING: @specialize annotation only supported on the first %d arguments.\n", + (int)(sizeof(m->nospecialize) * 8)); + continue; + } + m->nospecialize &= ~(1 << sn); + } st = jl_nothing; } else if (nargs == 2 && jl_exprarg(st, 0) == (jl_value_t*)jl_generated_sym) { - m->generator = NULL; + if (m->generator != NULL) + jl_error("duplicate @generated function body"); jl_value_t *gexpr = jl_exprarg(st, 1); - if (jl_expr_nargs(gexpr) == 7) { - // expects (new (core GeneratedFunctionStub) funcname argnames sp line file expandearly) - jl_value_t *funcname = jl_exprarg(gexpr, 1); - assert(jl_is_symbol(funcname)); - if (jl_get_global(m->module, (jl_sym_t*)funcname) != NULL) { - m->generator = jl_toplevel_eval(m->module, gexpr); - jl_gc_wb(m, m->generator); - } - } - if (m->generator == NULL) { - jl_error("invalid @generated function; try placing it in global scope"); - } + // the frontend would put (new (core GeneratedFunctionStub) funcname argnames sp) here, for example + m->generator = jl_toplevel_eval(m->module, gexpr); + jl_gc_wb(m, m->generator); st = jl_nothing; } else if (nargs == 1 && jl_exprarg(st, 0) == (jl_value_t*)jl_generated_only_sym) { @@ -747,7 +785,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) jl_task_t *ct = jl_current_task; 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_emptysvec); + 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); m->sig = NULL; m->slot_syms = NULL; @@ -766,7 +804,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->called = 0xff; m->nospecialize = module->nospecialize; m->nkw = 0; - jl_atomic_store_relaxed(&m->invokes, NULL); + jl_atomic_store_relaxed(&m->invokes, jl_nothing); m->recursion_relation = NULL; m->isva = 0; m->nargs = 0; @@ -774,14 +812,59 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->deleted_world = ~(size_t)0; m->is_for_opaque_closure = 0; m->constprop = 0; - JL_MUTEX_INIT(&m->writelock); + m->purity.bits = 0; + m->max_varargs = UINT8_MAX; + JL_MUTEX_INIT(&m->writelock, "method->writelock"); return m; } +// backedges ------------------------------------------------------------------ + +// Use this in a `while` loop to iterate over the backedges in a MethodInstance. +// `*invokesig` will be NULL if the call was made by ordinary dispatch, otherwise +// it will be the signature supplied in an `invoke` call. +// If you don't need `invokesig`, you can set it to NULL on input. +// Initialize iteration with `i = 0`. Returns `i` for the next backedge to be extracted. +int get_next_edge(jl_array_t *list, int i, jl_value_t** invokesig, jl_method_instance_t **caller) JL_NOTSAFEPOINT +{ + jl_value_t *item = jl_array_ptr_ref(list, i); + if (jl_is_method_instance(item)) { + // Not an `invoke` call, it's just the MethodInstance + if (invokesig != NULL) + *invokesig = NULL; + *caller = (jl_method_instance_t*)item; + return i + 1; + } + assert(jl_is_type(item)); + // An `invoke` call, it's a (sig, MethodInstance) pair + if (invokesig != NULL) + *invokesig = item; + *caller = (jl_method_instance_t*)jl_array_ptr_ref(list, i + 1); + if (*caller) + assert(jl_is_method_instance(*caller)); + return i + 2; +} + +int set_next_edge(jl_array_t *list, int i, jl_value_t *invokesig, jl_method_instance_t *caller) +{ + if (invokesig) + jl_array_ptr_set(list, i++, invokesig); + jl_array_ptr_set(list, i++, caller); + return i; +} + +void push_edge(jl_array_t *list, jl_value_t *invokesig, jl_method_instance_t *caller) +{ + if (invokesig) + jl_array_ptr_1d_push(list, invokesig); + jl_array_ptr_1d_push(list, (jl_value_t*)caller); + return; +} + // method definition ---------------------------------------------------------- jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, - jl_value_t *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) { jl_method_t *m = jl_new_method_uninit(module); JL_GC_PUSH1(&m); @@ -795,7 +878,7 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name assert(jl_is_symbol(name)); m->name = (jl_sym_t*)name; } - m->nargs = jl_unbox_long(nargs) + 1; + m->nargs = nargs + 1; assert(jl_is_linenode(functionloc)); jl_value_t *file = jl_linenode_file(functionloc); m->file = jl_is_symbol(file) ? (jl_sym_t*)file : jl_empty_sym; @@ -809,53 +892,52 @@ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_module_t *module, _Atomic(jl_value_t*) *bp, - jl_value_t *bp_owner, jl_binding_t *bnd) { jl_value_t *gf = NULL; assert(name && bp); if (bnd && jl_atomic_load_relaxed(&bnd->value) != NULL && !bnd->constp) - jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(bnd->name)); + jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); gf = jl_atomic_load_relaxed(bp); if (gf != NULL) { if (!jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(gf)) && !jl_is_type(gf)) jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name)); } if (bnd) - bnd->constp = 1; + bnd->constp = 1; // XXX: use jl_declare_constant and jl_checked_assignment if (gf == NULL) { gf = (jl_value_t*)jl_new_generic_function(name, module); jl_atomic_store(bp, gf); // TODO: fix constp assignment data race - if (bp_owner) jl_gc_wb(bp_owner, gf); + if (bnd) jl_gc_wb(bnd, gf); } return gf; } -static jl_methtable_t *first_methtable(jl_value_t *a JL_PROPAGATES_ROOT, int got_tuple1) JL_NOTSAFEPOINT +static jl_methtable_t *nth_methtable(jl_value_t *a JL_PROPAGATES_ROOT, int n) JL_NOTSAFEPOINT { if (jl_is_datatype(a)) { - if (got_tuple1) { + if (n == 0) { jl_methtable_t *mt = ((jl_datatype_t*)a)->name->mt; if (mt != NULL) return mt; } - if (jl_is_tuple_type(a)) { - if (jl_nparams(a) >= 1) - return first_methtable(jl_tparam0(a), 1); + else if (jl_is_tuple_type(a)) { + if (jl_nparams(a) >= n) + return nth_methtable(jl_tparam(a, n - 1), 0); } } else if (jl_is_typevar(a)) { - return first_methtable(((jl_tvar_t*)a)->ub, got_tuple1); + return nth_methtable(((jl_tvar_t*)a)->ub, n); } else if (jl_is_unionall(a)) { - return first_methtable(((jl_unionall_t*)a)->body, got_tuple1); + return nth_methtable(((jl_unionall_t*)a)->body, n); } else if (jl_is_uniontype(a)) { jl_uniontype_t *u = (jl_uniontype_t*)a; - jl_methtable_t *m1 = first_methtable(u->a, got_tuple1); + jl_methtable_t *m1 = nth_methtable(u->a, n); if ((jl_value_t*)m1 != jl_nothing) { - jl_methtable_t *m2 = first_methtable(u->b, got_tuple1); + jl_methtable_t *m2 = nth_methtable(u->b, n); if (m1 == m2) return m1; } @@ -866,18 +948,20 @@ static jl_methtable_t *first_methtable(jl_value_t *a JL_PROPAGATES_ROOT, int got // get the MethodTable for dispatch, or `nothing` if cannot be determined JL_DLLEXPORT jl_methtable_t *jl_method_table_for(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { - return first_methtable(argtypes, 0); + return nth_methtable(argtypes, 1); } -JL_DLLEXPORT jl_methtable_t *jl_method_get_table(jl_method_t *method JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +jl_methtable_t *jl_kwmethod_table_for(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { - return method->external_mt ? (jl_methtable_t*)method->external_mt : jl_method_table_for(method->sig); + jl_methtable_t *kwmt = nth_methtable(argtypes, 3); + if ((jl_value_t*)kwmt == jl_nothing) + return NULL; + return kwmt; } -// get the MethodTable implied by a single given type, or `nothing` -JL_DLLEXPORT jl_methtable_t *jl_argument_method_table(jl_value_t *argt JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +JL_DLLEXPORT jl_methtable_t *jl_method_get_table(jl_method_t *method JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { - return first_methtable(argt, 1); + 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; @@ -891,11 +975,11 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, jl_svec_t *atypes = (jl_svec_t*)jl_svecref(argdata, 0); jl_svec_t *tvars = (jl_svec_t*)jl_svecref(argdata, 1); jl_value_t *functionloc = jl_svecref(argdata, 2); - size_t nargs = jl_svec_len(atypes); - int isva = jl_is_vararg(jl_svecref(atypes, nargs - 1)); assert(jl_is_svec(atypes)); - assert(nargs > 0); assert(jl_is_svec(tvars)); + size_t nargs = jl_svec_len(atypes); + assert(nargs > 0); + int isva = jl_is_vararg(jl_svecref(atypes, nargs - 1)); if (!jl_is_type(jl_svecref(atypes, 0)) || (isva && nargs == 1)) jl_error("function type in method definition is not a type"); jl_sym_t *name; @@ -904,13 +988,9 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, JL_GC_PUSH3(&f, &m, &argtype); size_t i, na = jl_svec_len(atypes); - argtype = (jl_value_t*)jl_apply_tuple_type(atypes); - for (i = jl_svec_len(tvars); i > 0; i--) { - jl_value_t *tv = jl_svecref(tvars, i - 1); - if (!jl_is_typevar(tv)) - jl_type_error("method signature", (jl_value_t*)jl_tvar_type, tv); - argtype = jl_new_struct(jl_unionall_type, tv, argtype); - } + argtype = jl_apply_tuple_type(atypes); + if (!jl_is_datatype(argtype)) + jl_error("invalid type in method definition (Union{})"); jl_methtable_t *external_mt = mt; if (!mt) @@ -920,12 +1000,20 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, if (mt->frozen) jl_error("cannot add methods to a builtin function"); + assert(jl_is_linenode(functionloc)); + jl_sym_t *file = (jl_sym_t*)jl_linenode_file(functionloc); + if (!jl_is_symbol(file)) + file = jl_empty_sym; + int32_t line = jl_linenode_line(functionloc); + // TODO: derive our debug name from the syntax instead of the type - name = mt->name; - if (mt == jl_type_type_mt || mt == jl_nonfunction_mt || external_mt) { + jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(argtype) : mt; + // if we have a kwcall, try to derive the name from the callee argument method table + name = (kwmt ? kwmt : mt)->name; + if (kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || external_mt) { // our value for `name` is bad, try to guess what the syntax might have had, // like `jl_static_show_func_sig` might have come up with - jl_datatype_t *dt = jl_first_argument_datatype(argtype); + jl_datatype_t *dt = jl_nth_argument_datatype(argtype, mt == jl_kwcall_mt ? 3 : 1); if (dt != NULL) { name = dt->name->name; if (jl_is_type_type((jl_value_t*)dt)) { @@ -936,61 +1024,76 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, } } } + if (!jl_is_code_info(f)) { // this occurs when there is a closure being added to an out-of-scope function // the user should only do this at the toplevel // the result is that the closure variables get interpolated directly into the IR f = jl_new_code_info_from_ir((jl_expr_t*)f); } - m = jl_new_method_uninit(module); - m->external_mt = (jl_value_t*)external_mt; - if (external_mt) - jl_gc_wb(m, external_mt); - m->sig = argtype; - m->name = name; - m->isva = isva; - m->nargs = nargs; - assert(jl_is_linenode(functionloc)); - 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, f); - - if (jl_has_free_typevars(argtype)) { - jl_exceptionf(jl_argumenterror_type, - "method definition for %s at %s:%d has free type variables", - jl_symbol_name(name), - jl_symbol_name(m->file), - m->line); - } for (i = 0; i < na; i++) { jl_value_t *elt = jl_svecref(atypes, i); - if (!jl_is_type(elt) && !jl_is_typevar(elt) && !jl_is_vararg(elt)) { + int isvalid = jl_is_type(elt) || jl_is_typevar(elt) || jl_is_vararg(elt); + if (elt == jl_bottom_type || (jl_is_vararg(elt) && jl_unwrap_vararg(elt) == jl_bottom_type)) + isvalid = 0; + if (!isvalid) { jl_sym_t *argname = (jl_sym_t*)jl_array_ptr_ref(f->slotnames, i); if (argname == jl_unused_sym) jl_exceptionf(jl_argumenterror_type, "invalid type for argument number %d in method definition for %s at %s:%d", i, jl_symbol_name(name), - jl_symbol_name(m->file), - m->line); + jl_symbol_name(file), + line); else jl_exceptionf(jl_argumenterror_type, "invalid type for argument %s in method definition for %s at %s:%d", jl_symbol_name(argname), jl_symbol_name(name), - jl_symbol_name(m->file), - m->line); + jl_symbol_name(file), + line); } if (jl_is_vararg(elt) && i < na-1) jl_exceptionf(jl_argumenterror_type, "Vararg on non-final argument in method definition for %s at %s:%d", jl_symbol_name(name), - jl_symbol_name(m->file), - m->line); + jl_symbol_name(file), + line); + } + for (i = jl_svec_len(tvars); i > 0; i--) { + jl_value_t *tv = jl_svecref(tvars, i - 1); + if (!jl_is_typevar(tv)) + jl_type_error("method signature", (jl_value_t*)jl_tvar_type, tv); + if (!jl_has_typevar(argtype, (jl_tvar_t*)tv)) // deprecate this to an error in v2 + jl_printf(JL_STDERR, + "WARNING: method definition for %s at %s:%d declares type variable %s but does not use it.\n", + jl_symbol_name(name), + jl_symbol_name(file), + line, + jl_symbol_name(((jl_tvar_t*)tv)->name)); + argtype = jl_new_struct(jl_unionall_type, tv, argtype); + } + if (jl_has_free_typevars(argtype)) { + jl_exceptionf(jl_argumenterror_type, + "method definition for %s at %s:%d has free type variables", + jl_symbol_name(name), + jl_symbol_name(file), + line); } + m = jl_new_method_uninit(module); + m->external_mt = (jl_value_t*)external_mt; + if (external_mt) + jl_gc_wb(m, external_mt); + m->sig = argtype; + m->name = name; + m->isva = isva; + m->nargs = nargs; + m->file = file; + 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); @@ -1011,6 +1114,46 @@ JL_DLLEXPORT jl_method_t* jl_method_def(jl_svec_t *argdata, // root blocks +// This section handles method roots. Roots are GC-preserved items needed to +// represent lowered, type-inferred, and/or compiled code. These items are +// stored in a flat list (`m.roots`), and during serialization and +// deserialization of code we replace C-pointers to these items with a +// relocatable reference. We use a bipartite reference, `(key, index)` pair, +// where `key` identifies the module that added the root and `index` numbers +// just those roots with the same `key`. +// +// During precompilation (serialization), we save roots that were added to +// methods that are tagged with this package's module-key, even for "external" +// methods not owned by a module currently being precompiled. During +// deserialization, we load the new roots and append them to the method. When +// code is deserialized (see ircode.c), we replace the bipartite reference with +// the pointer to the memory address in the current session. The bipartite +// reference allows us to cache both roots and references in precompilation .ji +// files using a naming scheme that is independent of which packages are loaded +// in arbitrary order. +// +// To track the module-of-origin for each root, methods also have a +// `root_blocks` field that uses run-length encoding (RLE) storing `key` and the +// (absolute) integer index within `roots` at which a block of roots with that +// key begins. This makes it possible to look up an individual `(key, index)` +// pair fairly efficiently. A given `key` may possess more than one block; the +// `index` continues to increment regardless of block boundaries. +// +// Roots with `key = 0` are considered to be of unknown origin, and +// CodeInstances referencing such roots will remain unserializable unless all +// such roots were added at the time of system image creation. To track this +// additional data, we use two fields: +// +// - methods have an `nroots_sysimg` field to count the number of roots defined +// at the time of writing the system image (such occur first in the list of +// roots). These are the cases with `key = 0` that do not prevent +// serialization. +// - CodeInstances have a `relocatability` field which when 1 indicates that +// every root is "safe," meaning it was either added at sysimg creation or is +// tagged with a non-zero `key`. Even a single unsafe root will cause this to +// have value 0. + +// Get the key of the current (final) block of roots static uint64_t current_root_id(jl_array_t *root_blocks) { if (!root_blocks) @@ -1023,6 +1166,7 @@ static uint64_t current_root_id(jl_array_t *root_blocks) return blocks[nx2-2]; } +// Add a new block of `len` roots with key `modid` (module id) static void add_root_block(jl_array_t *root_blocks, uint64_t modid, size_t len) { assert(jl_is_array(root_blocks)); @@ -1033,15 +1177,9 @@ static void add_root_block(jl_array_t *root_blocks, uint64_t modid, size_t len) blocks[nx2-1] = len; } -JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_t* root) +// Allocate storage for roots +static void prepare_method_for_roots(jl_method_t *m, uint64_t modid) { - JL_GC_PUSH2(&m, &root); - uint64_t modid = 0; - if (mod) { - assert(jl_is_module(mod)); - modid = mod->build_id; - } - assert(jl_is_method(m)); if (!m->roots) { m->roots = jl_alloc_vec_any(0); jl_gc_wb(m, m->roots); @@ -1050,12 +1188,37 @@ JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_ m->root_blocks = jl_alloc_array_1d(jl_array_uint64_type, 0); jl_gc_wb(m, m->root_blocks); } +} + +// Add a single root with owner `mod` to a method +JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_t* root) +{ + JL_GC_PUSH2(&m, &root); + uint64_t modid = 0; + if (mod) { + assert(jl_is_module(mod)); + modid = mod->build_id.lo; + } + 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)); jl_array_ptr_1d_push(m->roots, root); JL_GC_POP(); } +// Add a list of roots with key `modid` to a method +void jl_append_method_roots(jl_method_t *m, uint64_t modid, jl_array_t* roots) +{ + JL_GC_PUSH2(&m, &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)); + jl_array_ptr_1d_append(m->roots, roots); + JL_GC_POP(); +} + // given the absolute index i of a root, retrieve its relocatable reference // returns 1 if the root is relocatable int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i) @@ -1084,6 +1247,24 @@ jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index) return jl_array_ptr_ref(m->roots, i); } +// Count the number of roots added by module with id `key` +int nroots_with_key(jl_method_t *m, uint64_t key) +{ + size_t nroots = 0; + if (m->roots) + nroots = jl_array_len(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); + int nwithkey = 0; + for (j = 0; j < nblocks2; j+=2) { + if (rletable[j] == key) + nwithkey += (j+3 < nblocks2 ? rletable[j+3] : nroots) - rletable[j+1]; + } + return nwithkey; +} + #ifdef __cplusplus } #endif diff --git a/src/module.c b/src/module.c index 8f37cc00b1bd6..04d3970f9b460 100644 --- a/src/module.c +++ b/src/module.c @@ -11,30 +11,35 @@ extern "C" { #endif -JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) +JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_names) { jl_task_t *ct = jl_current_task; const jl_uuid_t uuid_zero = {0, 0}; jl_module_t *m = (jl_module_t*)jl_gc_alloc(ct->ptls, sizeof(jl_module_t), jl_module_type); + jl_set_typetagof(m, jl_module_tag, 0); assert(jl_is_symbol(name)); m->name = name; - m->parent = NULL; + m->parent = parent; m->istopmod = 0; m->uuid = uuid_zero; static unsigned int mcounter; // simple counter backup, in case hrtime is not incrementing - m->build_id = jl_hrtime() + (++mcounter); - if (!m->build_id) - m->build_id++; // build id 0 is invalid + m->build_id.lo = jl_hrtime() + (++mcounter); + 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; - m->counter = 1; + jl_atomic_store_relaxed(&m->counter, 1); m->nospecialize = 0; m->optlevel = -1; m->compile = -1; m->infer = -1; m->max_methods = -1; - JL_MUTEX_INIT(&m->lock); - htable_new(&m->bindings, 0); + m->hash = parent == NULL ? bitmix(name->hash, jl_module_type->hash) : + 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); arraylist_new(&m->usings, 0); JL_GC_PUSH1(&m); if (jl_core_module && default_names) { @@ -49,9 +54,9 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, uint8_t default_names) return m; } -JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name) +JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent) { - return jl_new_module_(name, 1); + return jl_new_module_(name, parent, 1); } uint32_t jl_module_next_counter(jl_module_t *m) @@ -62,10 +67,9 @@ uint32_t jl_module_next_counter(jl_module_t *m) JL_DLLEXPORT jl_value_t *jl_f_new_module(jl_sym_t *name, uint8_t std_imports, uint8_t default_names) { // TODO: should we prohibit this during incremental compilation? - jl_module_t *m = jl_new_module_(name, default_names); + // TODO: the parent module is a lie + jl_module_t *m = jl_new_module_(name, jl_main_module, default_names); JL_GC_PUSH1(&m); - m->parent = jl_main_module; // TODO: this is a lie - jl_gc_wb(m, m->parent); if (std_imports) jl_add_standard_imports(m); JL_GC_POP(); @@ -154,130 +158,104 @@ JL_DLLEXPORT uint8_t jl_istopmod(jl_module_t *mod) return mod->istopmod; } -static jl_binding_t *new_binding(jl_sym_t *name) +static jl_globalref_t *jl_new_globalref(jl_module_t *mod, jl_sym_t *name, jl_binding_t *b) { jl_task_t *ct = jl_current_task; - assert(jl_is_symbol(name)); - jl_binding_t *b = (jl_binding_t*)jl_gc_alloc_buf(ct->ptls, sizeof(jl_binding_t)); - b->name = name; - b->value = NULL; - b->owner = NULL; - b->ty = NULL; + jl_globalref_t *g = (jl_globalref_t*)jl_gc_alloc(ct->ptls, sizeof(jl_globalref_t), jl_globalref_type); + g->mod = mod; + jl_gc_wb(g, g->mod); + g->name = name; + g->binding = b; + return g; +} + +static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) +{ + jl_task_t *ct = jl_current_task; + assert(jl_is_module(mod) && jl_is_symbol(name)); + jl_binding_t *b = (jl_binding_t*)jl_gc_alloc(ct->ptls, sizeof(jl_binding_t), jl_binding_type); + jl_atomic_store_relaxed(&b->value, NULL); + jl_atomic_store_relaxed(&b->owner, NULL); + jl_atomic_store_relaxed(&b->ty, NULL); b->globalref = NULL; b->constp = 0; b->exportp = 0; b->imported = 0; b->deprecated = 0; + b->usingfailed = 0; + b->padding = 0; + JL_GC_PUSH1(&b); + b->globalref = jl_new_globalref(mod, name, b); + JL_GC_POP(); return b; } +static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) JL_GLOBALLY_ROOTED; + // get binding for assignment -JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int error) +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&m->bindings, var); - jl_binding_t *b = *bp; + jl_binding_t *b = jl_get_module_binding(m, var, 1); - if (b != HT_NOTFOUND) { - if (b->owner != m) { - if (b->owner == NULL) { - b->owner = m; - } - else if (error) { - JL_UNLOCK(&m->lock); - jl_errorf("cannot assign a value to variable %s.%s from module %s", - jl_symbol_name(b->owner->name), jl_symbol_name(var), jl_symbol_name(m->name)); - } + if (b) { + jl_binding_t *b2 = NULL; + if (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b) { + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + if (from == m) + jl_errorf("cannot assign a value to imported variable %s.%s", + jl_symbol_name(from->name), jl_symbol_name(var)); + else + jl_errorf("cannot assign a value to imported variable %s.%s from module %s", + jl_symbol_name(from->name), jl_symbol_name(var), jl_symbol_name(m->name)); } } - else { - b = new_binding(var); - b->owner = m; - *bp = b; - JL_GC_PROMISE_ROOTED(b); - jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); - } - JL_UNLOCK(&m->lock); return b; } -// Hash tables don't generically root their contents, but they do for bindings. -// Express this to the analyzer. -// NOTE: Must hold m->lock while calling these. -#ifdef __clang_gcanalyzer__ -jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; -jl_binding_t **_jl_get_module_binding_bp(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT; -#else -static inline jl_binding_t *_jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT -{ - return (jl_binding_t*)ptrhash_get(&m->bindings, var); -} -static inline jl_binding_t **_jl_get_module_binding_bp(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) JL_NOTSAFEPOINT -{ - return (jl_binding_t**)ptrhash_bp(&m->bindings, var); -} -#endif - - // return module of binding JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); if (b == NULL) return NULL; - return b->owner; + return b->globalref->mod; // TODO: deprecate this? } // get binding for adding a method // like jl_get_binding_wr, but has different error paths JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t **bp = _jl_get_module_binding_bp(m, var); - jl_binding_t *b = *bp; + jl_binding_t *b = jl_get_module_binding(m, var, 1); - if (b != HT_NOTFOUND) { - if (b->owner != m) { - if (b->owner == NULL) { - b->owner = m; - } - else { - JL_UNLOCK(&m->lock); - jl_binding_t *b2 = jl_get_binding(b->owner, b->name); - if (b2 == NULL || b2->value == NULL) - jl_errorf("invalid method definition: imported function %s.%s does not exist", - jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); - // TODO: we might want to require explicitly importing types to add constructors - if (!b->imported && !jl_is_type(b2->value)) { - jl_errorf("error in method definition: function %s.%s must be explicitly imported to be extended", - jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); - } - return b2; - } + jl_binding_t *b2 = NULL; + if (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b) { + jl_value_t *f = jl_atomic_load_relaxed(&b2->value); + jl_module_t *from = jl_binding_dbgmodule(b, m, var); + if (f == NULL) { + // we must have implicitly imported this with using, so call jl_binding_dbgmodule to try to get the name of the module we got this from + jl_errorf("invalid method definition in %s: exported function %s.%s does not exist", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); } - } - else { - b = new_binding(var); - b->owner = m; - *bp = b; - jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); + // TODO: we might want to require explicitly importing types to add constructors + // or we might want to drop this error entirely + if (!b->imported && (!b2->constp || !jl_is_type(f))) { + jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended", + jl_symbol_name(m->name), jl_symbol_name(from->name), jl_symbol_name(var)); + } + return b2; } - JL_UNLOCK(&m->lock); return b; } -static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname, - int explici); - typedef struct _modstack_t { jl_module_t *m; jl_sym_t *var; struct _modstack_t *prev; } modstack_t; -static jl_binding_t *jl_get_binding_(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st); +static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st); static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; @@ -289,36 +267,49 @@ static inline jl_module_t *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROO } #endif +static int eq_bindings(jl_binding_t *owner, jl_binding_t *alias) +{ + assert(owner == jl_atomic_load_relaxed(&owner->owner)); + if (owner == alias) + return 1; + alias = jl_atomic_load_relaxed(&alias->owner); + if (owner == alias) + return 1; + if (owner->constp && alias->constp && jl_atomic_load_relaxed(&owner->value) && jl_atomic_load_relaxed(&alias->value) == jl_atomic_load_relaxed(&owner->value)) + return 1; + return 0; +} + // find a binding from a module's `usings` list -// called while holding m->lock -static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, modstack_t *st, int warn) +static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, jl_module_t **from, modstack_t *st, int warn) { jl_binding_t *b = NULL; jl_module_t *owner = NULL; - for(int i=(int)m->usings.len-1; i >= 0; --i) { + JL_LOCK(&m->lock); + int i = (int)m->usings.len - 1; + JL_UNLOCK(&m->lock); + for (; i >= 0; --i) { + JL_LOCK(&m->lock); jl_module_t *imp = module_usings_getidx(m, i); - // TODO: make sure this can't deadlock - JL_LOCK(&imp->lock); - jl_binding_t *tempb = _jl_get_module_binding(imp, var); - JL_UNLOCK(&imp->lock); - if (tempb != HT_NOTFOUND && tempb->exportp) { - tempb = jl_get_binding_(imp, var, st); - if (tempb == NULL || tempb->owner == NULL) + JL_UNLOCK(&m->lock); + jl_binding_t *tempb = jl_get_module_binding(imp, var, 0); + if (tempb != NULL && tempb->exportp) { + tempb = jl_resolve_owner(NULL, imp, var, st); // find the owner for tempb + if (tempb == NULL) // couldn't resolve; try next using (see issue #6105) continue; - if (owner != NULL && tempb->owner != b->owner && - !tempb->deprecated && !b->deprecated && - !(tempb->constp && tempb->value && b->constp && b->value == tempb->value)) { + assert(jl_atomic_load_relaxed(&tempb->owner) == tempb); + if (b != NULL && !tempb->deprecated && !b->deprecated && !eq_bindings(tempb, b)) { if (warn) { - JL_UNLOCK(&m->lock); + // set usingfailed=1 to avoid repeating this warning + // the owner will still be NULL, so it can be later imported or defined + tempb = jl_get_module_binding(m, var, 1); + tempb->usingfailed = 1; jl_printf(JL_STDERR, "WARNING: both %s and %s export \"%s\"; uses of it in module %s must be qualified\n", jl_symbol_name(owner->name), jl_symbol_name(imp->name), jl_symbol_name(var), jl_symbol_name(m->name)); - // mark this binding resolved, to avoid repeating the warning - (void)jl_get_binding_wr(m, var, 0); - JL_LOCK(&m->lock); } return NULL; } @@ -328,62 +319,111 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl } } } + *from = owner; return b; } +// for error message printing: look up the module that exported a binding to m as var +// this might not be the same as the owner of the binding, since the binding itself may itself have been imported from elsewhere +static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) +{ + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b && !b->imported) { + // for implicitly imported globals, try to re-resolve it to find the module we got it from most directly + jl_module_t *from = NULL; + b = using_resolve_binding(m, var, &from, NULL, 0); + if (b) { + if (b2 == NULL || jl_atomic_load_relaxed(&b->owner) == jl_atomic_load_relaxed(&b2->owner)) + return from; + // if we did not find it (or accidentally found a different one), ignore this + } + } + return m; +} + +static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t *b); + // get binding for reading. might return NULL for unbound. -static jl_binding_t *jl_get_binding_(jl_module_t *m, jl_sym_t *var, modstack_t *st) +static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st) { - modstack_t top = { m, var, st }; - modstack_t *tmp = st; - while (tmp != NULL) { - if (tmp->m == m && tmp->var == var) { - // import cycle without finding actual location + if (b == NULL) + b = jl_get_module_binding(m, var, 1); + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 == NULL) { + if (b->usingfailed) return NULL; + modstack_t top = { m, var, st }; + modstack_t *tmp = st; + for (; tmp != NULL; tmp = tmp->prev) { + if (tmp->m == m && tmp->var == var) { + // import cycle without finding actual location + return NULL; + } } - tmp = tmp->prev; - } - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - if (b == HT_NOTFOUND || b->owner == NULL) { - b = using_resolve_binding(m, var, &top, 1); - JL_UNLOCK(&m->lock); - if (b != NULL) { - // do a full import to prevent the result of this lookup - // from changing, for example if this var is assigned to - // later. - module_import_(m, b->owner, var, var, 0); - return b; + jl_module_t *from = NULL; // for error message printing + b2 = using_resolve_binding(m, var, &from, &top, 1); + if (b2 == NULL) + return NULL; + assert(from); + JL_GC_PROMISE_ROOTED(from); // gc-analysis does not understand output parameters + if (b2->deprecated) { + if (jl_atomic_load_relaxed(&b2->value) == jl_nothing) { + // silently skip importing deprecated values assigned to nothing (to allow later mutation) + return NULL; + } + } + // do a full import to prevent the result of this lookup from + // changing, for example if this var is assigned to later. + jl_binding_t *owner = NULL; + if (!jl_atomic_cmpswap(&b->owner, &owner, b2)) { + // concurrent import + return owner; + } + if (b2->deprecated) { + b->deprecated = 1; // we will warn about this below, but we might want to warn at the use sites too + if (m != jl_main_module && m != jl_base_module && + jl_options.depwarn != JL_OPTIONS_DEPWARN_OFF) { + /* with #22763, external packages wanting to replace + deprecated Base bindings should simply export the new + binding */ + jl_printf(JL_STDERR, + "WARNING: using deprecated binding %s.%s in %s.\n", + jl_symbol_name(from->name), jl_symbol_name(var), + jl_symbol_name(m->name)); + jl_binding_dep_message(from, var, b2); + } } - return NULL; } - JL_UNLOCK(&m->lock); - if (b->owner != m || b->name != var) - return jl_get_binding_(b->owner, b->name, &top); - return b; + assert(jl_atomic_load_relaxed(&b2->owner) == b2); + return b2; } -// get owner of binding when accessing m.var, without resolving the binding -JL_DLLEXPORT jl_value_t *jl_binding_owner(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_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); - if (b == HT_NOTFOUND || b->owner == NULL) - b = using_resolve_binding(m, var, NULL, 0); - JL_UNLOCK(&m->lock); - if (b == NULL || b->owner == NULL) - return jl_nothing; - return (jl_value_t*)b->owner; + 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) +{ + jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_module_t *from = m; + if (b == NULL || (!b->usingfailed && jl_atomic_load_relaxed(&b->owner) == NULL)) + b = using_resolve_binding(m, var, &from, NULL, 0); + else + b = jl_atomic_load_relaxed(&b->owner); + return b; } // get type of binding m.var, without resolving the binding -JL_DLLEXPORT jl_value_t *jl_binding_type(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) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); - if (b == HT_NOTFOUND || b->owner == NULL) - b = using_resolve_binding(m, var, NULL, 0); - JL_UNLOCK(&m->lock); + jl_binding_t *b = jl_get_module_binding(m, var, 0); + if (b == NULL) + return jl_nothing; + b = jl_atomic_load_relaxed(&b->owner); if (b == NULL) return jl_nothing; jl_value_t *ty = jl_atomic_load_relaxed(&b->ty); @@ -392,7 +432,7 @@ JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var) JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var) { - return jl_get_binding_(m, var, NULL); + return jl_resolve_owner(NULL, m, var, NULL); } JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var) @@ -400,51 +440,82 @@ 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); + // XXX: this only considers if the original is deprecated, not the binding in m if (b->deprecated) - jl_binding_deprecation_warning(m, b); + jl_binding_deprecation_warning(m, var, b); return b; } JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); - if (b == HT_NOTFOUND) { - JL_UNLOCK(&m->lock); - return jl_new_struct(jl_globalref_type, m, var); - } - jl_value_t *globalref = jl_atomic_load_relaxed(&b->globalref); - if (globalref == NULL) { - jl_value_t *newref = jl_new_struct(jl_globalref_type, m, var); - if (jl_atomic_cmpswap_relaxed(&b->globalref, &globalref, newref)) { - JL_GC_PROMISE_ROOTED(newref); - globalref = newref; - jl_gc_wb(m, globalref); - } - } - JL_UNLOCK(&m->lock); // may GC - return globalref; + jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_globalref_t *globalref = b->globalref; + assert(globalref != NULL); + return (jl_value_t*)globalref; } -static int eq_bindings(jl_binding_t *a, jl_binding_t *b) +// does module m explicitly import s? +JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *var) { - if (a==b) return 1; - if (a->name == b->name && a->owner == b->owner) return 1; - if (a->constp && a->value && b->constp && b->value == a->value) return 1; - return 0; + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && b->imported; } -// does module m explicitly import s? -JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s) +extern const char *jl_filename; +extern int jl_lineno; + +static char const dep_message_prefix[] = "_dep_message_"; + +static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t *b) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, s); - JL_UNLOCK(&m->lock); - return (b != HT_NOTFOUND && b->imported); + size_t prefix_len = strlen(dep_message_prefix); + size_t name_len = strlen(jl_symbol_name(name)); + char *dep_binding_name = (char*)alloca(prefix_len+name_len+1); + memcpy(dep_binding_name, dep_message_prefix, prefix_len); + memcpy(dep_binding_name + prefix_len, jl_symbol_name(name), name_len); + dep_binding_name[prefix_len+name_len] = '\0'; + jl_binding_t *dep_message_binding = jl_get_binding(m, jl_symbol(dep_binding_name)); + jl_value_t *dep_message = NULL; + if (dep_message_binding != NULL) + dep_message = jl_atomic_load_relaxed(&dep_message_binding->value); + JL_GC_PUSH1(&dep_message); + if (dep_message != NULL) { + if (jl_is_string(dep_message)) { + jl_uv_puts(JL_STDERR, jl_string_data(dep_message), jl_string_len(dep_message)); + } + else { + jl_static_show(JL_STDERR, dep_message); + } + } + else { + jl_value_t *v = jl_atomic_load_relaxed(&b->value); + dep_message = v; // use as gc-root + if (v) { + if (jl_is_type(v) || jl_is_module(v)) { + jl_printf(JL_STDERR, ", use "); + jl_static_show(JL_STDERR, v); + jl_printf(JL_STDERR, " instead."); + } + else { + jl_methtable_t *mt = jl_gf_mtable(v); + if (mt != NULL) { + jl_printf(JL_STDERR, ", use "); + if (mt->module != jl_core_module) { + jl_static_show(JL_STDERR, (jl_value_t*)mt->module); + jl_printf(JL_STDERR, "."); + } + jl_printf(JL_STDERR, "%s", jl_symbol_name(mt->name)); + jl_printf(JL_STDERR, " instead."); + } + } + } + } + jl_printf(JL_STDERR, "\n"); + JL_GC_POP(); } // NOTE: we use explici since explicit is a C++ keyword -static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname, int explici) +static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici) { jl_binding_t *b = jl_get_binding(from, s); if (b == NULL) { @@ -454,8 +525,10 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s jl_symbol_name(to->name)); } else { + assert(jl_atomic_load_relaxed(&b->owner) == b); if (b->deprecated) { - if (b->value == jl_nothing) { + if (jl_atomic_load_relaxed(&b->value) == jl_nothing) { + // silently skip importing deprecated values assigned to nothing (to allow later mutation) return; } else if (to != jl_main_module && to != jl_base_module && @@ -464,78 +537,45 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s deprecated Base bindings should simply export the new binding */ jl_printf(JL_STDERR, - "WARNING: importing deprecated binding %s.%s into %s.\n", + "WARNING: importing deprecated binding %s.%s into %s%s%s.\n", jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); + jl_symbol_name(to->name), + asname == s ? "" : " as ", + asname == s ? "" : jl_symbol_name(asname)); + jl_binding_dep_message(from, s, b); } } - JL_LOCK(&to->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&to->bindings, asname); - jl_binding_t *bto = *bp; - if (bto != HT_NOTFOUND) { - if (bto == b) { - // importing a binding on top of itself. harmless. + jl_binding_t *bto = jl_get_module_binding(to, asname, 1); + if (bto == b) { + // importing a binding on top of itself. harmless. + return; + } + jl_binding_t *ownerto = NULL; + if (jl_atomic_cmpswap(&bto->owner, &ownerto, b)) { + bto->imported |= (explici != 0); + bto->deprecated |= b->deprecated; // we already warned about this above, but we might want to warn at the use sites too + } + else { + if (eq_bindings(b, bto)) { + // already imported + bto->imported |= (explici != 0); } - else if (bto->name != s) { - JL_UNLOCK(&to->lock); + else if (ownerto != bto) { + // already imported from somewhere else jl_printf(JL_STDERR, "WARNING: ignoring conflicting import of %s.%s into %s\n", jl_symbol_name(from->name), jl_symbol_name(s), jl_symbol_name(to->name)); - return; - } - else if (bto->owner == b->owner) { - // already imported - bto->imported = (explici!=0); - } - else if (bto->owner != to && bto->owner != NULL) { - // already imported from somewhere else - jl_binding_t *bval = jl_get_binding(to, asname); - if (bval->constp && bval->value && b->constp && b->value == bval->value) { - // equivalent binding - bto->imported = (explici!=0); - JL_UNLOCK(&to->lock); - } - else { - JL_UNLOCK(&to->lock); - jl_printf(JL_STDERR, - "WARNING: ignoring conflicting import of %s.%s into %s\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); - } - return; - } - else if (bto->constp || bto->value) { - // conflict with name owned by destination module - assert(bto->owner == to); - if (bto->constp && bto->value && b->constp && b->value == bto->value) { - // equivalent binding - JL_UNLOCK(&to->lock); - } - else { - JL_UNLOCK(&to->lock); - jl_printf(JL_STDERR, - "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", - jl_symbol_name(from->name), jl_symbol_name(s), - jl_symbol_name(to->name)); - } - return; } else { - bto->owner = b->owner; - bto->imported = (explici!=0); + // conflict with name owned by destination module + jl_printf(JL_STDERR, + "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n", + jl_symbol_name(from->name), jl_symbol_name(s), + jl_symbol_name(to->name)); } } - else { - jl_binding_t *nb = new_binding(s); - nb->owner = b->owner; - nb->imported = (explici!=0); - nb->deprecated = b->deprecated; - *bp = nb; - jl_gc_wb_buf(to, nb, sizeof(jl_binding_t)); - } - JL_UNLOCK(&to->lock); } } @@ -546,7 +586,7 @@ JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t JL_DLLEXPORT void jl_module_import_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) { - module_import_(to, from, s, asname, 1); + module_import_(to, from, asname, s, 1); } JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s) @@ -556,127 +596,175 @@ JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s) JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) { - module_import_(to, from, s, asname, 0); + module_import_(to, from, asname, s, 0); } + JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) { if (to == from) return; JL_LOCK(&to->lock); - for(size_t i=0; i < to->usings.len; i++) { + for (size_t i = 0; i < to->usings.len; i++) { if (from == to->usings.items[i]) { JL_UNLOCK(&to->lock); return; } } - // TODO: make sure this can't deadlock - JL_LOCK(&from->lock); + arraylist_push(&to->usings, from); + jl_gc_wb(to, from); + JL_UNLOCK(&to->lock); + // print a warning if something visible via this "using" conflicts with // an existing identifier. note that an identifier added later may still // silently override a "using" name. see issue #2054. - void **table = from->bindings.table; - for(size_t i=1; i < from->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->exportp && (b->owner==from || b->imported)) { - jl_sym_t *var = (jl_sym_t*)table[i-1]; - jl_binding_t **tobp = (jl_binding_t**)ptrhash_bp(&to->bindings, var); - if (*tobp != HT_NOTFOUND && (*tobp)->owner != NULL && - // don't warn for conflicts with the module name itself. - // see issue #4715 - var != to->name && - !eq_bindings(jl_get_binding(to,var), b)) { - // TODO: not ideal to print this while holding module locks - jl_printf(JL_STDERR, - "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", - jl_symbol_name(from->name), jl_symbol_name(var), - jl_symbol_name(to->name)); - } + 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); + if ((void*)b == jl_nothing) + break; + if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { + jl_sym_t *var = b->globalref->name; + jl_binding_t *tob = jl_get_module_binding(to, var, 0); + if (tob && jl_atomic_load_relaxed(&tob->owner) != NULL && + // don't warn for conflicts with the module name itself. + // see issue #4715 + var != to->name && + !eq_bindings(jl_atomic_load_relaxed(&tob->owner), b)) { + jl_printf(JL_STDERR, + "WARNING: using %s.%s in module %s conflicts with an existing identifier.\n", + jl_symbol_name(from->name), jl_symbol_name(var), + jl_symbol_name(to->name)); } } + table = jl_atomic_load_relaxed(&from->bindings); } - JL_UNLOCK(&from->lock); - - arraylist_push(&to->usings, from); - jl_gc_wb(to, from); - JL_UNLOCK(&to->lock); } JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) { - JL_LOCK(&from->lock); - jl_binding_t **bp = (jl_binding_t**)ptrhash_bp(&from->bindings, s); - if (*bp == HT_NOTFOUND) { - jl_binding_t *b = new_binding(s); - // don't yet know who the owner is - b->owner = NULL; - *bp = b; - jl_gc_wb_buf(from, b, sizeof(jl_binding_t)); - } - assert(*bp != HT_NOTFOUND); - (*bp)->exportp = 1; - JL_UNLOCK(&from->lock); + jl_binding_t *b = jl_get_module_binding(from, s, 1); + b->exportp = 1; } JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); - return b && (b->value != NULL); + return b && (jl_atomic_load_relaxed(&b->value) != NULL); } JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = (jl_binding_t*)ptrhash_get(&m->bindings, var); - JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && (b->exportp || b->owner==m); + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && (b->exportp || jl_atomic_load_relaxed(&b->owner) == b); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && b->exportp; + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && b->exportp; } JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - return b != HT_NOTFOUND && b->owner != NULL; + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && jl_atomic_load_relaxed(&b->owner) != NULL; } -JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) +static uint_t bindingkey_hash(size_t idx, jl_svec_t *data) { - JL_LOCK(&m->lock); - jl_binding_t *b = _jl_get_module_binding(m, var); - JL_UNLOCK(&m->lock); - return b == HT_NOTFOUND ? NULL : b; + 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) +{ + jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); + jl_sym_t *name = b->globalref->name; + return var == name; +} + +JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, int alloc) +{ + uint_t hv = var->hash; + for (int locked = 0; ; locked++) { + jl_array_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 + if (idx != -1) { + jl_binding_t *b = (jl_binding_t*)jl_svecref(bindings, idx); // relaxed + if (locked) + JL_UNLOCK(&m->lock); + return b; + } + if (!alloc) { + return NULL; + } + else if (!locked) { + JL_LOCK(&m->lock); + } + else { + size_t i, cl = jl_svec_len(bindings); + for (i = cl; i > 0; i--) { + jl_value_t *b = jl_svecref(bindings, i - 1); + if (b != jl_nothing) + break; + } + if (i == cl) { + size_t ncl = cl < 8 ? 8 : (cl*3)>>1; // grow 50% + jl_svec_t *nc = jl_alloc_svec_uninit(ncl); + if (i > 0) + memcpy((char*)jl_svec_data(nc), jl_svec_data(bindings), sizeof(void*) * i); + for (size_t j = i; j < ncl; j++) + jl_svec_data(nc)[j] = jl_nothing; + jl_atomic_store_release(&m->bindings, nc); + jl_gc_wb(m, nc); + bindings = nc; + } + 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_UNLOCK(&m->lock); + return b; + } + } +} + + +JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr) +{ + jl_binding_t *b = gr->binding; + b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + // ignores b->deprecated + return b == NULL ? NULL : jl_atomic_load_relaxed(&b->value); } JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_binding(m, var); - if (b == NULL) return NULL; - if (b->deprecated) jl_binding_deprecation_warning(m, b); - return b->value; + if (b == NULL) + return NULL; + // XXX: this only considers if the original is deprecated, not the binding in m + if (b->deprecated) + jl_binding_deprecation_warning(m, var, b); + return jl_atomic_load_relaxed(&b->value); } JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { - JL_TYPECHK(jl_set_global, module, (jl_value_t*)m); - JL_TYPECHK(jl_set_global, symbol, (jl_value_t*)var); - jl_binding_t *bp = jl_get_binding_wr(m, var, 1); - jl_checked_assignment(bp, val); + jl_binding_t *bp = jl_get_binding_wr(m, var); + jl_checked_assignment(bp, m, var, val); } JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { - jl_binding_t *bp = jl_get_binding_wr(m, var, 1); - if (bp->value == NULL) { + // this function is mostly only used during initialization, so the data races here are not too important to us + jl_binding_t *bp = jl_get_binding_wr(m, var); + if (jl_atomic_load_relaxed(&bp->value) == NULL) { + jl_value_t *old_ty = NULL; + jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); uint8_t constp = 0; // if (jl_atomic_cmpswap(&bp->constp, &constp, 1)) { if (constp = bp->constp, bp->constp = 1, constp == 0) { @@ -686,11 +774,22 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var return; } } - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); } - jl_errorf("invalid redefinition of constant %s", - jl_symbol_name(bp->name)); + jl_errorf("invalid redefinition of constant %s", jl_symbol_name(var)); +} + +JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) +{ + jl_binding_t *b = gr->binding; + b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + return b && b->constp; +} + +JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr) +{ + jl_binding_t *b = gr->binding; + b = jl_resolve_owner(b, gr->mod, gr->name, NULL); + return b && jl_atomic_load_relaxed(&b->value) != NULL; } JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) @@ -703,6 +802,7 @@ JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var) // 0=not deprecated, 1=renamed, 2=moved to another package JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag) { + // XXX: this deprecates the original value, which might be imported from elsewhere jl_binding_t *b = jl_get_binding(m, var); if (b) b->deprecated = flag; } @@ -710,29 +810,14 @@ JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag) JL_DLLEXPORT int jl_is_binding_deprecated(jl_module_t *m, jl_sym_t *var) { if (jl_binding_resolved_p(m, var)) { + // XXX: this only considers if the original is deprecated, not this precise binding jl_binding_t *b = jl_get_binding(m, var); return b && b->deprecated; } return 0; } -extern const char *jl_filename; -extern int jl_lineno; - -static char const dep_message_prefix[] = "_dep_message_"; - -static jl_binding_t *jl_get_dep_message_binding(jl_module_t *m, jl_binding_t *deprecated_binding) -{ - size_t prefix_len = strlen(dep_message_prefix); - size_t name_len = strlen(jl_symbol_name(deprecated_binding->name)); - char *dep_binding_name = (char*)alloca(prefix_len+name_len+1); - memcpy(dep_binding_name, dep_message_prefix, prefix_len); - memcpy(dep_binding_name + prefix_len, jl_symbol_name(deprecated_binding->name), name_len); - dep_binding_name[prefix_len+name_len] = '\0'; - return jl_get_binding(m, jl_symbol(dep_binding_name)); -} - -void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) +void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b) { // Only print a warning for deprecated == 1 (renamed). // For deprecated == 2 (moved to a package) the binding is to a function @@ -740,76 +825,36 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) if (b->deprecated == 1 && jl_options.depwarn) { if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) jl_printf(JL_STDERR, "WARNING: "); - jl_binding_t *dep_message_binding = NULL; - if (b->owner) { - jl_printf(JL_STDERR, "%s.%s is deprecated", - jl_symbol_name(b->owner->name), jl_symbol_name(b->name)); - dep_message_binding = jl_get_dep_message_binding(b->owner, b); - } - else { - jl_printf(JL_STDERR, "%s is deprecated", jl_symbol_name(b->name)); - } - - if (dep_message_binding && dep_message_binding->value) { - if (jl_isa(dep_message_binding->value, (jl_value_t*)jl_string_type)) { - jl_uv_puts(JL_STDERR, jl_string_data(dep_message_binding->value), - jl_string_len(dep_message_binding->value)); - } - else { - jl_static_show(JL_STDERR, dep_message_binding->value); - } - } - else { - jl_value_t *v = b->value; - if (v) { - if (jl_is_type(v) || jl_is_module(v)) { - jl_printf(JL_STDERR, ", use "); - jl_static_show(JL_STDERR, v); - jl_printf(JL_STDERR, " instead."); - } - else { - jl_methtable_t *mt = jl_gf_mtable(v); - if (mt != NULL && (mt->defs != jl_nothing || - jl_isa(v, (jl_value_t*)jl_builtin_type))) { - jl_printf(JL_STDERR, ", use "); - if (mt->module != jl_core_module) { - jl_static_show(JL_STDERR, (jl_value_t*)mt->module); - jl_printf(JL_STDERR, "."); - } - jl_printf(JL_STDERR, "%s", jl_symbol_name(mt->name)); - jl_printf(JL_STDERR, " instead."); - } - } - } - } - jl_printf(JL_STDERR, "\n"); + assert(jl_atomic_load_relaxed(&b->owner) == b); + jl_printf(JL_STDERR, "%s.%s is deprecated", + jl_symbol_name(m->name), jl_symbol_name(s)); + jl_binding_dep_message(m, s, b); if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) { - if (jl_lineno == 0) { - jl_printf(JL_STDERR, " in module %s\n", jl_symbol_name(m->name)); - } - else { + if (jl_lineno != 0) { jl_printf(JL_STDERR, " likely near %s:%d\n", jl_filename, jl_lineno); } } if (jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) { - if (b->owner) - jl_errorf("deprecated binding: %s.%s", - jl_symbol_name(b->owner->name), - jl_symbol_name(b->name)); - else - jl_errorf("deprecated binding: %s", jl_symbol_name(b->name)); + jl_errorf("use of deprecated variable: %s.%s", + jl_symbol_name(m->name), + jl_symbol_name(s)); } } } -JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) +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 *old_ty = NULL; - if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type) && !jl_isa(rhs, old_ty)) { - jl_errorf("cannot assign an incompatible value to the global %s.", - jl_symbol_name(b->name)); + if (!jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type)) { + if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty) { + JL_GC_PUSH1(&rhs); // callee-rooted + if (!jl_isa(rhs, old_ty)) + jl_errorf("cannot assign an incompatible value to the global %s.%s.", + jl_symbol_name(mod->name), jl_symbol_name(var)); + JL_GC_POP(); + } } if (b->constp) { jl_value_t *old = NULL; @@ -820,38 +865,39 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) if (jl_egal(rhs, old)) return; if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) { -#ifndef __clang_gcanalyzer__ - jl_errorf("invalid redefinition of constant %s", - jl_symbol_name(b->name)); -#endif + jl_errorf("invalid redefinition of constant %s.%s", + jl_symbol_name(mod->name), jl_symbol_name(var)); + } - jl_safe_printf("WARNING: redefinition of constant %s. This may fail, cause incorrect answers, or produce other errors.\n", - jl_symbol_name(b->name)); + 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_atomic_store_relaxed(&b->value, rhs); + jl_atomic_store_release(&b->value, rhs); jl_gc_wb_binding(b, rhs); } -JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b) +JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var) { - if (b->value != NULL && !b->constp) { - jl_errorf("cannot declare %s constant; it already has a value", - jl_symbol_name(b->name)); + // n.b. jl_get_binding_wr should have ensured b->owner == b as mod.var + if (jl_atomic_load_relaxed(&b->owner) != b || (jl_atomic_load_relaxed(&b->value) != NULL && !b->constp)) { + jl_errorf("cannot declare %s.%s constant; it already has a value", + jl_symbol_name(mod->name), jl_symbol_name(var)); } b->constp = 1; } JL_DLLEXPORT jl_value_t *jl_module_usings(jl_module_t *m) { - jl_array_t *a = jl_alloc_array_1d(jl_array_any_type, 0); - JL_GC_PUSH1(&a); JL_LOCK(&m->lock); - for(int i=(int)m->usings.len-1; i >= 0; --i) { - jl_array_grow_end(a, 1); + int j = m->usings.len; + jl_array_t *a = jl_alloc_array_1d(jl_array_any_type, j); + JL_GC_PUSH1(&a); + for (int i = 0; j > 0; i++) { + j--; jl_module_t *imp = (jl_module_t*)m->usings.items[i]; - jl_array_ptr_set(a,jl_array_dim0(a)-1, (jl_value_t*)imp); + jl_array_ptr_set(a, j, (jl_value_t*)imp); } - JL_UNLOCK(&m->lock); + JL_UNLOCK(&m->lock); // may gc JL_GC_POP(); return (jl_value_t*)a; } @@ -860,32 +906,39 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported) { jl_array_t *a = jl_alloc_array_1d(jl_array_symbol_type, 0); JL_GC_PUSH1(&a); - size_t i; - JL_LOCK(&m->lock); - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - int hidden = jl_symbol_name(b->name)[0]=='#'; - if ((b->exportp || - (imported && b->imported) || - (b->owner == m && !b->imported && (all || m == jl_main_module))) && - (all || (!b->deprecated && !hidden))) { - jl_sym_t *in_module_name = (jl_sym_t*)table[i-1]; // the name in the module may not be b->name, use the httable key instead - jl_array_grow_end(a, 1); - //XXX: change to jl_arrayset if array storage allocation for Array{Symbols,1} changes: - jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)in_module_name); - } + 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); + if ((void*)b == jl_nothing) + break; + jl_sym_t *asname = b->globalref->name; + int hidden = jl_symbol_name(asname)[0]=='#'; + if ((b->exportp || + (imported && b->imported) || + (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) && + (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: + jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)asname); } + table = jl_atomic_load_relaxed(&m->bindings); } - JL_UNLOCK(&m->lock); JL_GC_POP(); return (jl_value_t*)a; } JL_DLLEXPORT jl_sym_t *jl_module_name(jl_module_t *m) { return m->name; } JL_DLLEXPORT jl_module_t *jl_module_parent(jl_module_t *m) { return m->parent; } -JL_DLLEXPORT uint64_t jl_module_build_id(jl_module_t *m) { return m->build_id; } +jl_module_t *jl_module_root(jl_module_t *m) +{ + while (1) { + if (m->parent == NULL || m->parent == m) + return m; + m = m->parent; + } +} + +JL_DLLEXPORT jl_uuid_t jl_module_build_id(jl_module_t *m) { return m->build_id; } JL_DLLEXPORT jl_uuid_t jl_module_uuid(jl_module_t* m) { return m->uuid; } // TODO: make this part of the module constructor and read-only? @@ -908,19 +961,30 @@ int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT // is to leave `Main` as empty as possible in the default system image. JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) { - size_t i; JL_LOCK(&m->lock); - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner != m && !b->imported) - table[i] = HT_NOTFOUND; - } + 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); + if ((void*)b == jl_nothing) + break; + if (jl_atomic_load_relaxed(&b->owner) && jl_atomic_load_relaxed(&b->owner) != b && !b->imported) + jl_atomic_store_relaxed(&b->owner, NULL); } JL_UNLOCK(&m->lock); } +JL_DLLEXPORT void jl_init_restored_module(jl_value_t *mod) +{ + if (!jl_generating_output() || jl_options.incremental) { + jl_module_run_initializer((jl_module_t*)mod); + } + else { + if (jl_module_init_order == NULL) + jl_module_init_order = jl_alloc_vec_any(0); + jl_array_ptr_1d_push(jl_module_init_order, mod); + } +} + #ifdef __cplusplus } #endif diff --git a/src/opaque_closure.c b/src/opaque_closure.c index 3fceadf67a583..d73beff0f8587 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -8,6 +8,11 @@ jl_value_t *jl_fptr_const_opaque_closure(jl_opaque_closure_t *oc, jl_value_t **a return oc->captures; } +jl_value_t *jl_fptr_const_opaque_closure_typeerror(jl_opaque_closure_t *oc, jl_value_t **args, size_t nargs) +{ + jl_type_error("OpaqueClosure", jl_tparam1(jl_typeof(oc)), oc->captures); +} + // determine whether `argt` is a valid argument type tuple for the given opaque closure method JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *source) { @@ -22,8 +27,8 @@ JL_DLLEXPORT int jl_is_valid_oc_argtype(jl_tupletype_t *argt, jl_method_t *sourc return 1; } -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) +static jl_opaque_closure_t *new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, + jl_value_t *source_, jl_value_t *captures, int do_compile) { if (!jl_is_tuple_type((jl_value_t*)argt)) { jl_error("OpaqueClosure argument tuple must be a tuple type"); @@ -40,45 +45,124 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ } if (jl_nparams(argt) + 1 - jl_is_va_tuple(argt) < source->nargs - source->isva) jl_error("Argument type tuple has too few required arguments for method"); + jl_value_t *sigtype = NULL; + jl_value_t *selected_rt = rt_ub; + JL_GC_PUSH2(&sigtype, &selected_rt); + sigtype = jl_argtype_with_function(captures, (jl_value_t*)argt); + + jl_method_instance_t *mi = jl_specializations_get_linfo(source, sigtype, jl_emptysvec); jl_task_t *ct = jl_current_task; - jl_value_t *oc_type JL_ALWAYS_LEAFTYPE; - oc_type = jl_apply_type2((jl_value_t*)jl_opaque_closure_type, (jl_value_t*)argt, rt_ub); - JL_GC_PROMISE_ROOTED(oc_type); - jl_value_t *captures = NULL, *sigtype = NULL; - jl_svec_t *sig_args = NULL; - JL_GC_PUSH3(&captures, &sigtype, &sig_args); - captures = jl_f_tuple(NULL, env, nenv); - - size_t nsig = 1 + jl_svec_len(argt->parameters); - sig_args = jl_alloc_svec_uninit(nsig); - jl_svecset(sig_args, 0, jl_typeof(captures)); - for (size_t i = 0; i < nsig-1; ++i) { - jl_svecset(sig_args, 1+i, jl_tparam(argt, i)); + size_t world = ct->world_age; + jl_code_instance_t *ci = NULL; + if (do_compile) { + ci = jl_compile_method_internal(mi, world); } - sigtype = (jl_value_t*)jl_apply_tuple_type_v(jl_svec_data(sig_args), nsig); - jl_method_instance_t *mi = jl_specializations_get_linfo(source, sigtype, jl_emptysvec); - size_t world = jl_atomic_load_acquire(&jl_world_counter); - jl_code_instance_t *ci = jl_compile_method_internal(mi, world); + jl_fptr_args_t invoke = (jl_fptr_args_t)jl_interpret_opaque_closure; + void *specptr = NULL; + + if (ci) { + invoke = (jl_fptr_args_t)jl_atomic_load_relaxed(&ci->invoke); + specptr = jl_atomic_load_relaxed(&ci->specptr.fptr); + + selected_rt = ci->rettype; + // If we're not allowed to generate a specsig with this, rt, fall + // back to the invoke wrapper. We could instead generate a specsig->specsig + // wrapper, but lets leave that for later. + if (!jl_subtype(rt_lb, selected_rt)) { + // TODO: It would be better to try to get a specialization with the + // correct rt check here (or we could codegen a wrapper). + specptr = NULL; invoke = (jl_fptr_args_t)jl_interpret_opaque_closure; + jl_value_t *ts[2] = {rt_lb, (jl_value_t*)ci->rettype}; + selected_rt = jl_type_union(ts, 2); + } + if (!jl_subtype(ci->rettype, rt_ub)) { + // TODO: It would be better to try to get a specialization with the + // correct rt check here (or we could codegen a wrapper). + specptr = NULL; invoke = (jl_fptr_args_t)jl_interpret_opaque_closure; + selected_rt = jl_type_intersection(rt_ub, selected_rt); + } + + if (invoke == (jl_fptr_args_t) jl_fptr_interpret_call) { + invoke = (jl_fptr_args_t)jl_interpret_opaque_closure; + } + else if (invoke == (jl_fptr_args_t)jl_fptr_args && specptr) { + invoke = (jl_fptr_args_t)specptr; + } + else if (invoke == (jl_fptr_args_t)jl_fptr_const_return) { + invoke = jl_isa(ci->rettype_const, selected_rt) ? + (jl_fptr_args_t)jl_fptr_const_opaque_closure : + (jl_fptr_args_t)jl_fptr_const_opaque_closure_typeerror; + captures = ci->rettype_const; + } + } + + jl_value_t *oc_type JL_ALWAYS_LEAFTYPE = jl_apply_type2((jl_value_t*)jl_opaque_closure_type, (jl_value_t*)argt, selected_rt); + JL_GC_PROMISE_ROOTED(oc_type); + + if (!specptr) { + sigtype = jl_argtype_with_function_type((jl_value_t*)oc_type, (jl_value_t*)argt); + 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); + if (!jl_atomic_load_acquire(&ci->invoke)) + jl_generate_fptr_for_oc_wrapper(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); - JL_GC_POP(); oc->source = source; oc->captures = captures; - oc->specptr = NULL; - if (jl_atomic_load_relaxed(&ci->invoke) == jl_fptr_interpret_call) { - oc->invoke = (jl_fptr_args_t)jl_interpret_opaque_closure; - } - else if (jl_atomic_load_relaxed(&ci->invoke) == jl_fptr_args) { - oc->invoke = jl_atomic_load_relaxed(&ci->specptr.fptr1); - } - else if (jl_atomic_load_relaxed(&ci->invoke) == jl_fptr_const_return) { - oc->invoke = (jl_fptr_args_t)jl_fptr_const_opaque_closure; - oc->captures = ci->rettype_const; - } - else { - oc->invoke = (jl_fptr_args_t)jl_atomic_load_relaxed(&ci->invoke); - } oc->world = world; + oc->invoke = invoke; + oc->specptr = specptr; + + JL_GC_POP(); + return oc; +} + +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_value_t *captures = jl_f_tuple(NULL, env, nenv); + JL_GC_PUSH1(&captures); + jl_opaque_closure_t *oc = new_opaque_closure(argt, rt_lb, rt_ub, source_, captures, do_compile); + JL_GC_POP(); + 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) +{ + 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); + 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); + + jl_opaque_closure_t *oc = new_opaque_closure(argt, rt_lb, rt_ub, root, env, do_compile); + JL_GC_POP(); return oc; } @@ -87,13 +171,12 @@ JL_CALLABLE(jl_new_opaque_closure_jlcall) if (nargs < 4) jl_error("new_opaque_closure: Not enough arguments"); return (jl_value_t*)jl_new_opaque_closure((jl_tupletype_t*)args[0], - args[1], args[2], args[3], &args[4], nargs-4); + args[1], args[2], args[3], &args[4], nargs-4, 1); } - // check whether the specified number of arguments is compatible with the // specified number of parameters of the tuple type -STATIC_INLINE int jl_tupletype_length_compat(jl_value_t *v, size_t nargs) JL_NOTSAFEPOINT +int jl_tupletype_length_compat(jl_value_t *v, size_t nargs) { v = jl_unwrap_unionall(v); assert(jl_is_tuple_type(v)); diff --git a/src/options.h b/src/options.h index 36f34654b2bd0..b535d5ad4566f 100644 --- a/src/options.h +++ b/src/options.h @@ -64,11 +64,6 @@ #endif #endif -// SEGV_EXCEPTION turns segmentation faults into catchable julia exceptions. -// This is not recommended, as the memory state after such an exception should -// be considered untrusted, but can be helpful during development -// #define SEGV_EXCEPTION - // profiling options // GC_FINAL_STATS prints total GC stats at exit @@ -83,9 +78,6 @@ // OBJPROFILE counts objects by type // #define OBJPROFILE -// Automatic Instrumenting Profiler -//#define ENABLE_TIMINGS - // method dispatch profiling -------------------------------------------------- @@ -113,7 +105,9 @@ // 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) -#ifdef _P64 +#if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_MSAN_ENABLED_) +#define JL_STACK_SIZE (64*1024*1024) +#elif defined(_P64) #define JL_STACK_SIZE (4*1024*1024) #else #define JL_STACK_SIZE (2*1024*1024) @@ -126,7 +120,7 @@ // controls for when threads sleep #define THREAD_SLEEP_THRESHOLD_NAME "JULIA_THREAD_SLEEP_THRESHOLD" -#define DEFAULT_THREAD_SLEEP_THRESHOLD 16*1000 // nanoseconds (16us) +#define DEFAULT_THREAD_SLEEP_THRESHOLD 100*1000 // nanoseconds (100us) // defaults for # threads #define NUM_THREADS_NAME "JULIA_NUM_THREADS" @@ -134,6 +128,12 @@ # define JULIA_NUM_THREADS 1 #endif +// threadpools specification +#define THREADPOOLS_NAME "JULIA_THREADPOOLS" + +// GC threads +#define NUM_GC_THREADS_NAME "JULIA_NUM_GC_THREADS" + // affinitization behavior #define MACHINE_EXCLUSIVE_NAME "JULIA_EXCLUSIVE" #define DEFAULT_MACHINE_EXCLUSIVE 0 @@ -161,8 +161,12 @@ // sanitizer defaults --------------------------------------------------------- // Automatically enable MEMDEBUG and KEEP_BODIES for the sanitizers -#if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_MSAN_ENABLED_) +#if defined(_COMPILER_ASAN_ENABLED_) +// No MEMDEBUG for msan - we just poison allocated memory directly. #define MEMDEBUG +#endif + +#if defined(_COMPILER_ASAN_ENABLED_) || defined(_COMPILER_MSAN_ENABLED_) #define KEEP_BODIES #endif diff --git a/src/partr.c b/src/partr.c index c8cc3245ebb4c..403f911b1284f 100644 --- a/src/partr.c +++ b/src/partr.c @@ -26,6 +26,9 @@ static const int16_t not_sleeping = 0; // it is acceptable for the thread to be sleeping. static const int16_t sleeping = 1; +// this thread is dead. +static const int16_t sleeping_like_the_dead JL_UNUSED = 2; + // 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. @@ -34,13 +37,18 @@ static const int16_t sleeping = 1; // information: These observations require sequentially-consistent fences to be inserted between each of those operational phases. // [^store_buffering_1]: These fences are used to avoid the cycle 2b -> 1a -> 1b -> 2a -> 2b where // * Dequeuer: -// * 1a: `jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping)` -// * 1b: `multiq_check_empty` returns true +// * 1: `jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping)` // * Enqueuer: -// * 2a: `multiq_insert` -// * 2b: `jl_atomic_load_relaxed(&ptls->sleep_check_state)` in `jl_wakeup_thread` returns `not_sleeping` +// * 2: `jl_atomic_load_relaxed(&ptls->sleep_check_state)` in `jl_wakeup_thread` returns `not_sleeping` // i.e., the dequeuer misses the enqueue and enqueuer misses the sleep state transition. - +// [^store_buffering_2]: and also +// * Enqueuer: +// * 1a: `jl_atomic_store_relaxed(jl_uv_n_waiters, 1)` in `JL_UV_LOCK` +// * 1b: "cheap read" of `handle->pending` in `uv_async_send` (via `JL_UV_LOCK`) loads `0` +// * Dequeuer: +// * 2a: store `2` to `handle->pending` in `uv_async_send` (via `JL_UV_LOCK` in `jl_task_get_next`) +// * 2b: `jl_atomic_load_relaxed(jl_uv_n_waiters)` in `jl_task_get_next` returns `0` +// i.e., the dequeuer misses the `n_waiters` is set and enqueuer misses the `uv_stop` flag (in `signal_async`) transition to cleared JULIA_DEBUG_SLEEPWAKE( uint64_t wakeup_enter; @@ -49,10 +57,7 @@ uint64_t io_wakeup_enter; uint64_t io_wakeup_leave; ); -uv_mutex_t *sleep_locks; -uv_cond_t *wake_signals; - -JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int tid) JL_NOTSAFEPOINT +JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int16_t tid) JL_NOTSAFEPOINT { // Try to acquire the lock on this task. int16_t was = jl_atomic_load_relaxed(&task->tid); @@ -63,192 +68,33 @@ JL_DLLEXPORT int jl_set_task_tid(jl_task_t *task, int tid) JL_NOTSAFEPOINT return 0; } -// GC functions used -extern int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, - jl_gc_mark_sp_t *sp, jl_value_t *obj) JL_NOTSAFEPOINT; - -// multiq -// --- - -/* a task heap */ -typedef struct taskheap_tag { - uv_mutex_t lock; - jl_task_t **tasks; - _Atomic(int32_t) ntasks; - _Atomic(int16_t) prio; -} taskheap_t; - -/* multiqueue parameters */ -static const int32_t heap_d = 8; -static const int heap_c = 2; - -/* size of each heap */ -static const int tasks_per_heap = 65536; // TODO: this should be smaller by default, but growable! - -/* the multiqueue's heaps */ -static taskheap_t *heaps; -static int32_t heap_p; - -/* unbias state for the RNG */ -static uint64_t cong_unbias; - - -static inline void multiq_init(void) -{ - heap_p = heap_c * jl_n_threads; - heaps = (taskheap_t *)calloc(heap_p, sizeof(taskheap_t)); - for (int32_t i = 0; i < heap_p; ++i) { - uv_mutex_init(&heaps[i].lock); - heaps[i].tasks = (jl_task_t **)calloc(tasks_per_heap, sizeof(jl_task_t*)); - jl_atomic_store_relaxed(&heaps[i].ntasks, 0); - jl_atomic_store_relaxed(&heaps[i].prio, INT16_MAX); - } - unbias_cong(heap_p, &cong_unbias); -} - - -static inline void sift_up(taskheap_t *heap, int32_t idx) -{ - if (idx > 0) { - int32_t parent = (idx-1)/heap_d; - if (heap->tasks[idx]->prio < heap->tasks[parent]->prio) { - jl_task_t *t = heap->tasks[parent]; - heap->tasks[parent] = heap->tasks[idx]; - heap->tasks[idx] = t; - sift_up(heap, parent); - } - } -} - - -static inline void sift_down(taskheap_t *heap, int32_t idx) +JL_DLLEXPORT int jl_set_task_threadpoolid(jl_task_t *task, int8_t tpid) JL_NOTSAFEPOINT { - if (idx < jl_atomic_load_relaxed(&heap->ntasks)) { - for (int32_t child = heap_d*idx + 1; - child < tasks_per_heap && child <= heap_d*idx + heap_d; - ++child) { - if (heap->tasks[child] - && heap->tasks[child]->prio < heap->tasks[idx]->prio) { - jl_task_t *t = heap->tasks[idx]; - heap->tasks[idx] = heap->tasks[child]; - heap->tasks[child] = t; - sift_down(heap, child); - } - } - } + if (tpid < 0 || tpid >= jl_n_threadpools) + return 0; + task->threadpoolid = tpid; + return 1; } +// GC functions used +extern int jl_gc_mark_queue_obj_explicit(jl_gc_mark_cache_t *gc_cache, + jl_gc_markqueue_t *mq, jl_value_t *obj) JL_NOTSAFEPOINT; -static inline int multiq_insert(jl_task_t *task, int16_t priority) -{ - jl_ptls_t ptls = jl_current_task->ptls; - uint64_t rn; - - task->prio = priority; - do { - rn = cong(heap_p, cong_unbias, &ptls->rngseed); - } while (uv_mutex_trylock(&heaps[rn].lock) != 0); - - if (jl_atomic_load_relaxed(&heaps[rn].ntasks) >= tasks_per_heap) { - uv_mutex_unlock(&heaps[rn].lock); - // multiq insertion failed, increase #tasks per heap - return -1; - } - - int32_t ntasks = jl_atomic_load_relaxed(&heaps[rn].ntasks); - jl_atomic_store_relaxed(&heaps[rn].ntasks, ntasks + 1); - heaps[rn].tasks[ntasks] = task; - sift_up(&heaps[rn], ntasks); - int16_t prio = jl_atomic_load_relaxed(&heaps[rn].prio); - if (task->prio < prio) - jl_atomic_store_relaxed(&heaps[rn].prio, task->prio); - uv_mutex_unlock(&heaps[rn].lock); - - return 0; -} - +// parallel task runtime +// --- -static inline jl_task_t *multiq_deletemin(void) +JL_DLLEXPORT uint32_t jl_rand_ptls(uint32_t max, uint32_t unbias) { jl_ptls_t ptls = jl_current_task->ptls; - uint64_t rn1 = 0, rn2; - int32_t i; - int16_t prio1, prio2; - jl_task_t *task; - retry: - jl_gc_safepoint(); - for (i = 0; i < heap_p; ++i) { - rn1 = cong(heap_p, cong_unbias, &ptls->rngseed); - rn2 = cong(heap_p, cong_unbias, &ptls->rngseed); - prio1 = jl_atomic_load_relaxed(&heaps[rn1].prio); - prio2 = jl_atomic_load_relaxed(&heaps[rn2].prio); - if (prio1 > prio2) { - prio1 = prio2; - rn1 = rn2; - } - else if (prio1 == prio2 && prio1 == INT16_MAX) - continue; - if (uv_mutex_trylock(&heaps[rn1].lock) == 0) { - if (prio1 == jl_atomic_load_relaxed(&heaps[rn1].prio)) - break; - uv_mutex_unlock(&heaps[rn1].lock); - } - } - if (i == heap_p) - return NULL; - - task = heaps[rn1].tasks[0]; - if (!jl_set_task_tid(task, ptls->tid)) { - uv_mutex_unlock(&heaps[rn1].lock); - goto retry; - } - int32_t ntasks = jl_atomic_load_relaxed(&heaps[rn1].ntasks) - 1; - jl_atomic_store_relaxed(&heaps[rn1].ntasks, ntasks); - heaps[rn1].tasks[0] = heaps[rn1].tasks[ntasks]; - heaps[rn1].tasks[ntasks] = NULL; - prio1 = INT16_MAX; - if (ntasks > 0) { - sift_down(&heaps[rn1], 0); - prio1 = heaps[rn1].tasks[0]->prio; - } - jl_atomic_store_relaxed(&heaps[rn1].prio, prio1); - uv_mutex_unlock(&heaps[rn1].lock); - - return task; + // one-extend unbias back to 64-bits + return cong(max, -(uint64_t)-unbias, &ptls->rngseed); } - -void jl_gc_mark_enqueued_tasks(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp) -{ - int32_t i, j; - for (i = 0; i < heap_p; ++i) - for (j = 0; j < jl_atomic_load_relaxed(&heaps[i].ntasks); ++j) - jl_gc_mark_queue_obj_explicit(gc_cache, sp, (jl_value_t *)heaps[i].tasks[j]); -} - - -static int multiq_check_empty(void) -{ - int32_t i; - for (i = 0; i < heap_p; ++i) { - if (jl_atomic_load_relaxed(&heaps[i].ntasks) != 0) - return 0; - } - return 1; -} - - - -// parallel task runtime -// --- - // initialize the threading infrastructure -// (used only by the main thread) +// (called only by the main thread) void jl_init_threadinginfra(void) { - /* initialize the synchronization trees pool and the multiqueue */ - multiq_init(); - + /* initialize the synchronization trees pool */ sleep_threshold = DEFAULT_THREAD_SLEEP_THRESHOLD; char *cp = getenv(THREAD_SLEEP_THRESHOLD_NAME); if (cp) { @@ -257,23 +103,37 @@ void jl_init_threadinginfra(void) else sleep_threshold = (uint64_t)strtol(cp, NULL, 10); } - - jl_ptls_t ptls = jl_current_task->ptls; - jl_install_thread_signal_handler(ptls); - - int16_t tid; - sleep_locks = (uv_mutex_t*)calloc(jl_n_threads, sizeof(uv_mutex_t)); - wake_signals = (uv_cond_t*)calloc(jl_n_threads, sizeof(uv_cond_t)); - for (tid = 0; tid < jl_n_threads; tid++) { - uv_mutex_init(&sleep_locks[tid]); - uv_cond_init(&wake_signals[tid]); - } } void JL_NORETURN jl_finish_task(jl_task_t *t); -// thread function: used by all except the main thread +// gc thread function +void jl_gc_threadfun(void *arg) +{ + jl_threadarg_t *targ = (jl_threadarg_t*)arg; + + // initialize this thread (set tid and create heap) + jl_ptls_t ptls = jl_init_threadtls(targ->tid); + + // wait for all threads + jl_gc_state_set(ptls, JL_GC_STATE_WAITING, 0); + uv_barrier_wait(targ->barrier); + + // free the thread argument here + free(targ); + + while (1) { + uv_mutex_lock(&gc_threads_lock); + while (jl_atomic_load(&gc_n_threads_marking) == 0) { + uv_cond_wait(&gc_threads_cond, &gc_threads_lock); + } + uv_mutex_unlock(&gc_threads_lock); + gc_mark_loop_parallel(ptls, 0); + } +} + +// thread function: used by all mutator threads except the main thread void jl_threadfun(void *arg) { jl_threadarg_t *targ = (jl_threadarg_t*)arg; @@ -285,7 +145,6 @@ void jl_threadfun(void *arg) // 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); - jl_install_thread_signal_handler(ptls); // wait for all threads jl_gc_state_set(ptls, JL_GC_STATE_SAFE, 0); @@ -299,18 +158,6 @@ void jl_threadfun(void *arg) } -// enqueue the specified task for execution -JL_DLLEXPORT int jl_enqueue_task(jl_task_t *task) -{ - char failed; - if (multiq_insert(task, task->prio) == -1) - failed = 1; - failed = 0; - JL_PROBE_RT_TASKQ_INSERT(jl_current_task->ptls, task); - return failed; -} - - int jl_running_under_rr(int recheck) { #ifdef _OS_LINUX_ @@ -361,17 +208,17 @@ static int sleep_check_after_threshold(uint64_t *start_cycles) } -static int wake_thread(int16_t tid) +static int wake_thread(int16_t tid) JL_NOTSAFEPOINT { - jl_ptls_t other = jl_all_tls_states[tid]; + 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(&sleep_locks[tid]); - uv_cond_signal(&wake_signals[tid]); - uv_mutex_unlock(&sleep_locks[tid]); + uv_mutex_lock(&other->sleep_lock); + uv_cond_signal(&other->wake_signal); + uv_mutex_unlock(&other->sleep_lock); return 1; } } @@ -379,7 +226,7 @@ static int wake_thread(int16_t tid) } -static void wake_libuv(void) +static void wake_libuv(void) JL_NOTSAFEPOINT { JULIA_DEBUG_SLEEPWAKE( io_wakeup_enter = cycleclock() ); jl_wake_libuv(); @@ -387,7 +234,7 @@ static void wake_libuv(void) } /* ensure thread tid is awake if necessary */ -JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) +JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; int16_t self = jl_atomic_load_relaxed(&ct->tid); @@ -407,14 +254,15 @@ JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) } else { // something added to the sticky-queue: notify that thread - if (wake_thread(tid)) { + if (wake_thread(tid) && uvlock != ct) { // check if we need to notify uv_run too jl_fence(); - jl_task_t *tid_task = jl_atomic_load_relaxed(&jl_all_tls_states[tid]->current_task); + jl_ptls_t other = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; + jl_task_t *tid_task = jl_atomic_load_relaxed(&other->current_task); // now that we have changed the thread to not-sleeping, ensure that // either it has not yet acquired the libuv lock, or that it will // observe the change of state to not_sleeping - if (uvlock != ct && jl_atomic_load_relaxed(&jl_uv_mutex.owner) == tid_task) + if (jl_atomic_load_relaxed(&jl_uv_mutex.owner) == tid_task) wake_libuv(); } } @@ -424,7 +272,8 @@ JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) // in the future, we might want to instead wake some fraction of threads, // and let each of those wake additional threads if they find work int anysleep = 0; - for (tid = 0; tid < jl_n_threads; tid++) { + int nthreads = jl_atomic_load_acquire(&jl_n_threads); + for (tid = 0; tid < nthreads; tid++) { if (tid != self) anysleep |= wake_thread(tid); } @@ -439,21 +288,43 @@ JL_DLLEXPORT void jl_wakeup_thread(int16_t tid) } -// get the next runnable task from the multiq +// get the next runnable task static jl_task_t *get_next_task(jl_value_t *trypoptask, jl_value_t *q) { jl_gc_safepoint(); - jl_value_t *args[2] = { trypoptask, q }; - jl_task_t *task = (jl_task_t*)jl_apply(args, 2); - if (jl_typeis(task, jl_task_type)) { + jl_task_t *task = (jl_task_t*)jl_apply_generic(trypoptask, &q, 1); + if (jl_is_task(task)) { int self = jl_atomic_load_relaxed(&jl_current_task->tid); jl_set_task_tid(task, self); return task; } - task = multiq_deletemin(); - if (task) - JL_PROBE_RT_TASKQ_GET(jl_current_task->ptls, task); - return task; + return NULL; +} + +static int check_empty(jl_value_t *checkempty) +{ + return jl_apply_generic(checkempty, NULL, 0) == jl_true; +} + +jl_task_t *wait_empty JL_GLOBALLY_ROOTED; +void jl_wait_empty_begin(void); +void jl_wait_empty_end(void); + +void jl_task_wait_empty(void) +{ + jl_task_t *ct = jl_current_task; + if (jl_atomic_load_relaxed(&ct->tid) == 0 && jl_base_module) { + jl_wait_empty_begin(); + jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("wait")); + wait_empty = ct; + size_t lastage = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + if (f) + jl_apply_generic(f, NULL, 0); + ct->world_age = lastage; + wait_empty = NULL; + jl_wait_empty_end(); + } } static int may_sleep(jl_ptls_t ptls) JL_NOTSAFEPOINT @@ -462,13 +333,13 @@ static int may_sleep(jl_ptls_t ptls) JL_NOTSAFEPOINT // by the thread itself. As a result, if this returns false, it will // continue returning false. If it returns true, we know the total // modification order of the fences. - jl_fence(); // [^store_buffering_1] + jl_fence(); // [^store_buffering_1] [^store_buffering_2] 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_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q, jl_value_t *checkempty) { jl_task_t *ct = jl_current_task; uint64_t start_cycles = 0; @@ -480,19 +351,19 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) // quick, race-y check to see if there seems to be any stuff in there jl_cpu_pause(); - if (!multiq_check_empty()) { + if (!check_empty(checkempty)) { start_cycles = 0; continue; } jl_cpu_pause(); jl_ptls_t ptls = ct->ptls; - if (sleep_check_after_threshold(&start_cycles) || (!jl_atomic_load_relaxed(&_threadedregion) && ptls->tid == 0)) { + if (sleep_check_after_threshold(&start_cycles) || (ptls->tid == 0 && (!jl_atomic_load_relaxed(&_threadedregion) || wait_empty))) { // acquire sleep-check lock jl_atomic_store_relaxed(&ptls->sleep_check_state, sleeping); jl_fence(); // [^store_buffering_1] JL_PROBE_RT_SLEEP_CHECK_SLEEP(ptls); - if (!multiq_check_empty()) { // uses relaxed loads + if (!check_empty(checkempty)) { // uses relaxed loads if (jl_atomic_load_relaxed(&ptls->sleep_check_state) != not_sleeping) { jl_atomic_store_relaxed(&ptls->sleep_check_state, not_sleeping); // let other threads know they don't need to wake us JL_PROBE_RT_SLEEP_CHECK_TASKQ_WAKE(ptls); @@ -540,17 +411,26 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) } else if (ptls->tid == 0) { uvlock = 1; - JL_UV_LOCK(); // jl_mutex_lock(&jl_uv_mutex); + JL_UV_LOCK(); + } + else { + // Since we might have started some IO work, we might need + // to ensure tid = 0 will go watch that new event source. + // If trylock would have succeeded, that may have been our + // responsibility, so need to make sure thread 0 will take care + // of us. + if (jl_atomic_load_relaxed(&jl_uv_mutex.owner) == NULL) // aka trylock + jl_wakeup_thread(0); } if (uvlock) { - int active = 1; - // otherwise, we block until someone asks us for the lock - uv_loop_t *loop = jl_global_event_loop(); - while (active && may_sleep(ptls)) { - if (jl_atomic_load_relaxed(&jl_uv_n_waiters) != 0) - // but if we won the race against someone who actually needs - // the lock to do real work, we need to let them have it instead - break; + int enter_eventloop = may_sleep(ptls); + int active = 0; + if (jl_atomic_load_relaxed(&jl_uv_n_waiters) != 0) + // if we won the race against someone who actually needs + // the lock to do real work, we need to let them have it instead + enter_eventloop = 0; + if (enter_eventloop) { + uv_loop_t *loop = jl_global_event_loop(); loop->stop_flag = 0; JULIA_DEBUG_SLEEPWAKE( ptls->uv_run_enter = cycleclock() ); active = uv_run(loop, UV_RUN_ONCE); @@ -563,11 +443,11 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) // that just wanted to steal libuv from us. We will just go // right back to sleep on the individual wake signal to let // them take it from us without conflict. - if (!may_sleep(ptls)) { + if (active || !may_sleep(ptls)) { start_cycles = 0; continue; } - if (!jl_atomic_load_relaxed(&_threadedregion) && active && ptls->tid == 0) { + if (!enter_eventloop && !jl_atomic_load_relaxed(&_threadedregion) && ptls->tid == 0) { // 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) { @@ -582,16 +462,28 @@ JL_DLLEXPORT jl_task_t *jl_task_get_next(jl_value_t *trypoptask, jl_value_t *q) // 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(&sleep_locks[ptls->tid]); + uv_mutex_lock(&ptls->sleep_lock); while (may_sleep(ptls)) { - uv_cond_wait(&wake_signals[ptls->tid], &sleep_locks[ptls->tid]); - // TODO: help with gc work here, if applicable + if (ptls->tid == 0 && wait_empty) { + 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 + JL_PROBE_RT_SLEEP_CHECK_TASK_WAKE(ptls); + } + break; + } + uv_cond_wait(&ptls->wake_signal, &ptls->sleep_lock); } assert(jl_atomic_load_relaxed(&ptls->sleep_check_state) == not_sleeping); - uv_mutex_unlock(&sleep_locks[ptls->tid]); + 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; + return task; + } } else { // maybe check the kernel for new messages too diff --git a/src/passes.h b/src/passes.h new file mode 100644 index 0000000000000..acbfcd9538106 --- /dev/null +++ b/src/passes.h @@ -0,0 +1,104 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#ifndef JL_PASSES_H +#define JL_PASSES_H + +#include "analyzer_annotations.h" +#include +#include + +using namespace llvm; + +// Function Passes +struct DemoteFloat16 : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct CombineMulAdd : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; +}; + +struct LateLowerGC : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct AllocOptPass : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; +}; + +struct PropagateJuliaAddrspacesPass : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct LowerExcHandlers : PassInfoMixin { + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct GCInvariantVerifierPass : PassInfoMixin { + bool Strong; + GCInvariantVerifierPass(bool Strong = false) JL_NOTSAFEPOINT : Strong(Strong) {} + + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +// Module Passes +struct CPUFeatures : PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct RemoveNI : PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; +}; + +struct LowerSIMDLoop : PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; +}; + +struct FinalLowerGCPass : PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct MultiVersioning : PassInfoMixin { + bool external_use; + MultiVersioning(bool external_use = false) : external_use(external_use) {} + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct RemoveJuliaAddrspacesPass : PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct RemoveAddrspacesPass : PassInfoMixin { + std::function ASRemapper; + RemoveAddrspacesPass() JL_NOTSAFEPOINT; + RemoveAddrspacesPass(std::function ASRemapper) JL_NOTSAFEPOINT : ASRemapper(std::move(ASRemapper)) {} + ~RemoveAddrspacesPass() JL_NOTSAFEPOINT = default; + + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +struct LowerPTLSPass : PassInfoMixin { + bool imaging_mode; + LowerPTLSPass(bool imaging_mode=false) JL_NOTSAFEPOINT : imaging_mode(imaging_mode) {} + + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) JL_NOTSAFEPOINT; + static bool isRequired() { return true; } +}; + +// Loop Passes +struct JuliaLICMPass : PassInfoMixin { + PreservedAnalyses run(Loop &L, LoopAnalysisManager &AM, + LoopStandardAnalysisResults &AR, LPMUpdater &U) JL_NOTSAFEPOINT; +}; + +#endif diff --git a/src/pipeline.cpp b/src/pipeline.cpp new file mode 100644 index 0000000000000..1007dfd35c1d6 --- /dev/null +++ b/src/pipeline.cpp @@ -0,0 +1,727 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include +#include "platform.h" + +//We don't care about uninitialized variables in LLVM; that's LLVM's problem +#ifdef _COMPILER_GCC_ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +// analysis passes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NewPM needs to manually include all the pass headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _COMPILER_GCC_ +#pragma GCC diagnostic pop +#endif + +#include + +#include "julia.h" +#include "julia_internal.h" +#include "jitlayers.h" +#include "julia_assert.h" +#include "passes.h" + + +using namespace llvm; + +namespace { + //Shamelessly stolen from Clang's approach to sanitizers + //TODO do we want to enable other sanitizers? + static void addSanitizerPasses(ModulePassManager &MPM, OptimizationLevel O) JL_NOTSAFEPOINT { + // Coverage sanitizer + // if (CodeGenOpts.hasSanitizeCoverage()) { + // auto SancovOpts = getSancovOptsFromCGOpts(CodeGenOpts); + // MPM.addPass(ModuleSanitizerCoveragePass( + // SancovOpts, CodeGenOpts.SanitizeCoverageAllowlistFiles, + // CodeGenOpts.SanitizeCoverageIgnorelistFiles)); + // } + + #ifdef _COMPILER_MSAN_ENABLED_ + auto MSanPass = [&](/*SanitizerMask Mask, */bool CompileKernel) JL_NOTSAFEPOINT { + // if (LangOpts.Sanitize.has(Mask)) { + // int TrackOrigins = CodeGenOpts.SanitizeMemoryTrackOrigins; + // bool Recover = CodeGenOpts.SanitizeRecover.has(Mask); + + // MemorySanitizerOptions options(TrackOrigins, Recover, CompileKernel,{ + // CodeGenOpts.SanitizeMemoryParamRetval); + MemorySanitizerOptions options; + MPM.addPass(ModuleMemorySanitizerPass(options)); + FunctionPassManager FPM; + FPM.addPass(MemorySanitizerPass(options)); + if (O != OptimizationLevel::O0) { + // MemorySanitizer inserts complex instrumentation that mostly + // follows the logic of the original code, but operates on + // "shadow" values. It can benefit from re-running some + // general purpose optimization passes. + FPM.addPass(EarlyCSEPass()); + // TODO: Consider add more passes like in + // addGeneralOptsForMemorySanitizer. EarlyCSEPass makes visible + // difference on size. It's not clear if the rest is still + // useful. InstCombinePass breaks + // compiler-rt/test/msan/select_origin.cpp. + } + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + // } + }; + MSanPass(/*SanitizerKind::Memory, */false); + // MSanPass(SanitizerKind::KernelMemory, true); + #endif + + #ifdef _COMPILER_TSAN_ENABLED_ + // if (LangOpts.Sanitize.has(SanitizerKind::Thread)) { + MPM.addPass(ModuleThreadSanitizerPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass())); + // } + #endif + + + #ifdef _COMPILER_ASAN_ENABLED_ + auto ASanPass = [&](/*SanitizerMask Mask, */bool CompileKernel) JL_NOTSAFEPOINT { + // if (LangOpts.Sanitize.has(Mask)) { + // bool UseGlobalGC = asanUseGlobalsGC(TargetTriple, CodeGenOpts); + // bool UseOdrIndicator = CodeGenOpts.SanitizeAddressUseOdrIndicator; + // llvm::AsanDtorKind DestructorKind = + // CodeGenOpts.getSanitizeAddressDtor(); + // AddressSanitizerOptions Opts; + // Opts.CompileKernel = CompileKernel; + // Opts.Recover = CodeGenOpts.SanitizeRecover.has(Mask); + // Opts.UseAfterScope = CodeGenOpts.SanitizeAddressUseAfterScope; + // Opts.UseAfterReturn = CodeGenOpts.getSanitizeAddressUseAfterReturn(); + // MPM.addPass(RequireAnalysisPass()); + // MPM.addPass(ModuleAddressSanitizerPass( + // Opts, UseGlobalGC, UseOdrIndicator, DestructorKind)); + //Let's assume the defaults are actually fine for our purposes + MPM.addPass(ModuleAddressSanitizerPass(AddressSanitizerOptions())); + // } + }; + ASanPass(/*SanitizerKind::Address, */false); + // ASanPass(SanitizerKind::KernelAddress, true); + #endif + + // auto HWASanPass = [&](SanitizerMask Mask, bool CompileKernel) { + // if (LangOpts.Sanitize.has(Mask)) { + // bool Recover = CodeGenOpts.SanitizeRecover.has(Mask); + // MPM.addPass(HWAddressSanitizerPass( + // {CompileKernel, Recover, + // /*DisableOptimization=*/CodeGenOpts.OptimizationLevel == 0})); + // } + // }; + // HWASanPass(/*SanitizerKind::HWAddress, */false); + // // HWASanPass(SanitizerKind::KernelHWAddress, true); + + // if (LangOpts.Sanitize.has(SanitizerKind::DataFlow)) { + // MPM.addPass(DataFlowSanitizerPass(LangOpts.NoSanitizeFiles)); + // } + } + +#ifdef JL_DEBUG_BUILD + static inline void addVerificationPasses(ModulePassManager &MPM, bool llvm_only) JL_NOTSAFEPOINT { + if (!llvm_only) + MPM.addPass(llvm::createModuleToFunctionPassAdaptor(GCInvariantVerifierPass())); + MPM.addPass(VerifierPass()); + } +#endif + + auto basicSimplifyCFGOptions() JL_NOTSAFEPOINT { + return SimplifyCFGOptions() + .convertSwitchRangeToICmp(true) + .convertSwitchToLookupTable(true) + .forwardSwitchCondToPhi(true); + } + + auto aggressiveSimplifyCFGOptions() JL_NOTSAFEPOINT { + return SimplifyCFGOptions() + .convertSwitchRangeToICmp(true) + .convertSwitchToLookupTable(true) + .forwardSwitchCondToPhi(true) + //These mess with loop rotation, so only do them after that + .hoistCommonInsts(true) + // Causes an SRET assertion error in late-gc-lowering + // .sinkCommonInsts(true) + ; + } + + // TODO(vchuravy/maleadt): + // Since we are not using the PassBuilder fully and instead rolling our own, we are missing out on + // TargetMachine::registerPassBuilderCallbacks. We need to find a solution either in working with upstream + // or adapting PassBuilder (or subclassing it) to suite our needs. This is in particular important for + // BPF, NVPTX, and AMDGPU. + //TODO implement these once LLVM exposes + //the PassBuilder extension point callbacks + //For now we'll maintain the insertion points even though they don't do anything + //for the sake of documentation + //If PB is a nullptr, don't invoke anything (this happens when running julia from opt) + void invokePipelineStartCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokePeepholeEPCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeEarlySimplificationCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeCGSCCCallbacks(CGSCCPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeOptimizerEarlyCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeLateLoopOptimizationCallbacks(LoopPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeLoopOptimizerEndCallbacks(LoopPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeScalarOptimizerCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeVectorizerCallbacks(FunctionPassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} + void invokeOptimizerLastCallbacks(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O) JL_NOTSAFEPOINT {} +} + +//The actual pipelines +//TODO Things we might want to consider: +//? annotation2metadata pass +//? force function attributes pass +//? annotation remarks pass +//? infer function attributes pass +//? lower expect intrinsic pass +//? warn missed transformations pass +//* For vectorization +//? loop unroll/jam after loop vectorization +//? optimization remarks pass +//? cse/cvp/instcombine/bdce/sccp/licm/unswitch after loop vectorization ( +// cleanup as much as possible before trying to slp vectorize) +//? vectorcombine pass +//* For optimization +//? float2int pass +//? lower constant intrinsics pass +//? loop sink pass +//? hot-cold splitting pass + +#define JULIA_PASS(ADD_PASS) if (!options.llvm_only) { ADD_PASS; } else do { } while (0) + +//Use for O1 and below +static void buildBasicPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, OptimizationOptions options) JL_NOTSAFEPOINT { +#ifdef JL_DEBUG_BUILD + addVerificationPasses(MPM, options.llvm_only); +#endif + invokePipelineStartCallbacks(MPM, PB, O); + MPM.addPass(ConstantMergePass()); + if (!options.dump_native) { + JULIA_PASS(MPM.addPass(CPUFeatures())); + if (O.getSpeedupLevel() > 0) { + MPM.addPass(createModuleToFunctionPassAdaptor(InstSimplifyPass())); + } + } + { + FunctionPassManager FPM; + FPM.addPass(SimplifyCFGPass(basicSimplifyCFGOptions())); + if (O.getSpeedupLevel() > 0) { + FPM.addPass(SROAPass()); + FPM.addPass(InstCombinePass()); + FPM.addPass(EarlyCSEPass()); + } + FPM.addPass(MemCpyOptPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + invokeEarlySimplificationCallbacks(MPM, PB, O); + MPM.addPass(AlwaysInlinerPass()); + { + CGSCCPassManager CGPM; + invokeCGSCCCallbacks(CGPM, PB, O); + MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); + } + invokeOptimizerEarlyCallbacks(MPM, PB, O); + JULIA_PASS(MPM.addPass(LowerSIMDLoop())); + { + FunctionPassManager FPM; + { + LoopPassManager LPM; + invokeLateLoopOptimizationCallbacks(LPM, PB, O); + invokeLoopOptimizerEndCallbacks(LPM, PB, O); + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM))); + } + invokeScalarOptimizerCallbacks(FPM, PB, O); + invokeVectorizerCallbacks(FPM, PB, O); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + if (options.lower_intrinsics) { + //TODO no barrier pass? + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(LowerExcHandlers())); + JULIA_PASS(FPM.addPass(GCInvariantVerifierPass(false))); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + JULIA_PASS(MPM.addPass(RemoveNI())); + JULIA_PASS(MPM.addPass(createModuleToFunctionPassAdaptor(LateLowerGC()))); + JULIA_PASS(MPM.addPass(FinalLowerGCPass())); + JULIA_PASS(MPM.addPass(LowerPTLSPass(options.dump_native))); + } else { + JULIA_PASS(MPM.addPass(RemoveNI())); + } + JULIA_PASS(MPM.addPass(LowerSIMDLoop())); // TODO why do we do this twice + if (options.dump_native) { + JULIA_PASS(MPM.addPass(MultiVersioning(options.external_use))); + JULIA_PASS(MPM.addPass(CPUFeatures())); + if (O.getSpeedupLevel() > 0) { + FunctionPassManager FPM; + FPM.addPass(InstSimplifyPass()); + FPM.addPass(SimplifyCFGPass(basicSimplifyCFGOptions())); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + } + invokeOptimizerLastCallbacks(MPM, PB, O); + addSanitizerPasses(MPM, O); + JULIA_PASS(MPM.addPass(createModuleToFunctionPassAdaptor(DemoteFloat16()))); +} + +//Use for O2 and above +static void buildFullPipeline(ModulePassManager &MPM, PassBuilder *PB, OptimizationLevel O, OptimizationOptions options) JL_NOTSAFEPOINT { +#ifdef JL_DEBUG_BUILD + addVerificationPasses(MPM, options.llvm_only); +#endif + invokePipelineStartCallbacks(MPM, PB, O); + MPM.addPass(ConstantMergePass()); + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(PropagateJuliaAddrspacesPass())); + //TODO consider not using even basic simplification + //options here, and adding a run of CVP to take advantage + //of the unsimplified codegen information (e.g. known + //zeros or ones) + FPM.addPass(SimplifyCFGPass(basicSimplifyCFGOptions())); + FPM.addPass(DCEPass()); + FPM.addPass(SROAPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + invokeEarlySimplificationCallbacks(MPM, PB, O); + MPM.addPass(AlwaysInlinerPass()); + invokeOptimizerEarlyCallbacks(MPM, PB, O); + { + CGSCCPassManager CGPM; + invokeCGSCCCallbacks(CGPM, PB, O); + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(AllocOptPass())); + FPM.addPass(InstCombinePass()); + FPM.addPass(SimplifyCFGPass(basicSimplifyCFGOptions())); + CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM))); + } + MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); + } + if (options.dump_native) { + JULIA_PASS(MPM.addPass(MultiVersioning(options.external_use))); + } + JULIA_PASS(MPM.addPass(CPUFeatures())); + { + FunctionPassManager FPM; + 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())); + invokePeepholeEPCallbacks(FPM, PB, O); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + MPM.addPass(LowerSIMDLoop()); + { + FunctionPassManager FPM; + { + LoopPassManager LPM1, LPM2; + LPM1.addPass(LoopRotatePass()); + invokeLateLoopOptimizationCallbacks(LPM1, PB, O); + //We don't know if the loop callbacks support MSSA + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM1), /*UseMemorySSA = */false)); +#if JL_LLVM_VERSION < 150000 +#define LICMOptions() +#endif + LPM2.addPass(LICMPass(LICMOptions())); + JULIA_PASS(LPM2.addPass(JuliaLICMPass())); + LPM2.addPass(SimpleLoopUnswitchPass(/*NonTrivial*/true, true)); + LPM2.addPass(LICMPass(LICMOptions())); + JULIA_PASS(LPM2.addPass(JuliaLICMPass())); + //LICM needs MemorySSA now, so we must use it + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM2), /*UseMemorySSA = */true)); + } + FPM.addPass(IRCEPass()); + { + LoopPassManager LPM; + LPM.addPass(LoopInstSimplifyPass()); + LPM.addPass(LoopIdiomRecognizePass()); + LPM.addPass(IndVarSimplifyPass()); + LPM.addPass(LoopDeletionPass()); + LPM.addPass(LoopFullUnrollPass()); + invokeLoopOptimizerEndCallbacks(LPM, PB, O); + //We don't know if the loop end callbacks support MSSA + FPM.addPass(createFunctionToLoopPassAdaptor(std::move(LPM), /*UseMemorySSA = */false)); + } + 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()); + } + 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))); + } + invokeScalarOptimizerCallbacks(FPM, PB, O); + //TODO look into loop vectorize options + FPM.addPass(LoopVectorizePass()); + FPM.addPass(LoopLoadEliminationPass()); + FPM.addPass(InstCombinePass()); + FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); + FPM.addPass(SLPVectorizerPass()); + invokeVectorizerCallbacks(FPM, PB, O); + FPM.addPass(ADCEPass()); + //TODO add BDCEPass here? + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + if (options.lower_intrinsics) { + //TODO barrier pass? + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(LowerExcHandlers())); + JULIA_PASS(FPM.addPass(GCInvariantVerifierPass(false))); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + // Needed **before** LateLowerGCFrame on LLVM < 12 + // due to bug in `CreateAlignmentAssumption`. + JULIA_PASS(MPM.addPass(RemoveNI())); + JULIA_PASS(MPM.addPass(createModuleToFunctionPassAdaptor(LateLowerGC()))); + JULIA_PASS(MPM.addPass(FinalLowerGCPass())); + { + FunctionPassManager FPM; + FPM.addPass(GVNPass()); + FPM.addPass(SCCPPass()); + FPM.addPass(DCEPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + JULIA_PASS(MPM.addPass(LowerPTLSPass(options.dump_native))); + { + FunctionPassManager FPM; + FPM.addPass(InstCombinePass()); + FPM.addPass(SimplifyCFGPass(aggressiveSimplifyCFGOptions())); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + } else { + JULIA_PASS(MPM.addPass(RemoveNI())); + } + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(CombineMulAdd())); + FPM.addPass(DivRemPairsPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } + invokeOptimizerLastCallbacks(MPM, PB, O); + addSanitizerPasses(MPM, O); + { + FunctionPassManager FPM; + JULIA_PASS(FPM.addPass(DemoteFloat16())); + FPM.addPass(GVNPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); + } +} + +#undef JULIA_PASS + +namespace { + auto createPIC(StandardInstrumentations &SI) JL_NOTSAFEPOINT { + auto PIC = std::make_unique(); +//Borrowed from LLVM PassBuilder.cpp:386 +#define MODULE_PASS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define MODULE_PASS_WITH_PARAMS(NAME, CLASS, CREATE_PASS, PARSER, PARAMS) \ +PIC->addClassToPassName(CLASS, NAME); +#define MODULE_ANALYSIS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define FUNCTION_PASS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define FUNCTION_PASS_WITH_PARAMS(NAME, CLASS, CREATE_PASS, PARSER, PARAMS) \ +PIC->addClassToPassName(CLASS, NAME); +#define FUNCTION_ANALYSIS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define LOOPNEST_PASS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define LOOP_PASS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define LOOP_PASS_WITH_PARAMS(NAME, CLASS, CREATE_PASS, PARSER, PARAMS) \ +PIC->addClassToPassName(CLASS, NAME); +#define LOOP_ANALYSIS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define CGSCC_PASS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); +#define CGSCC_PASS_WITH_PARAMS(NAME, CLASS, CREATE_PASS, PARSER, PARAMS) \ +PIC->addClassToPassName(CLASS, NAME); +#define CGSCC_ANALYSIS(NAME, CREATE_PASS) \ +PIC->addClassToPassName(decltype(CREATE_PASS)::name(), NAME); + +#include "llvm-julia-passes.inc" + +#undef MODULE_PASS +#undef MODULE_PASS_WITH_PARAMS +#undef MODULE_ANALYSIS +#undef FUNCTION_PASS +#undef FUNCTION_PASS_WITH_PARAMS +#undef FUNCTION_ANALYSIS +#undef LOOPNEST_PASS +#undef LOOP_PASS +#undef LOOP_PASS_WITH_PARAMS +#undef LOOP_ANALYSIS +#undef CGSCC_PASS +#undef CGSCC_PASS_WITH_PARAMS +#undef CGSCC_ANALYSIS + + SI.registerCallbacks(*PIC); + return PIC; + } + + FunctionAnalysisManager createFAM(OptimizationLevel O, TargetIRAnalysis analysis, const Triple &triple) JL_NOTSAFEPOINT { + + FunctionAnalysisManager FAM; + // Register the AA manager first so that our version is the one used. + FAM.registerPass([&] JL_NOTSAFEPOINT { + AAManager AA; + if (O.getSpeedupLevel() >= 2) { + AA.registerFunctionAnalysis(); + AA.registerFunctionAnalysis(); + AA.registerFunctionAnalysis(); + } + // TM->registerDefaultAliasAnalyses(AA); + return AA; + }); + // Register our TargetLibraryInfoImpl. + FAM.registerPass([&] JL_NOTSAFEPOINT { return llvm::TargetIRAnalysis(analysis); }); + FAM.registerPass([&] JL_NOTSAFEPOINT { return llvm::TargetLibraryAnalysis(llvm::TargetLibraryInfoImpl(triple)); }); + return FAM; + } + + ModulePassManager createMPM(PassBuilder &PB, OptimizationLevel O, OptimizationOptions options) JL_NOTSAFEPOINT { + ModulePassManager MPM; + if (O.getSpeedupLevel() < 2) + buildBasicPipeline(MPM, &PB, O, options); + else + buildFullPipeline(MPM, &PB, O, options); + return MPM; + } +} + +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) {} + +NewPM::~NewPM() = default; + +AnalysisManagers::AnalysisManagers(TargetMachine &TM, PassBuilder &PB, OptimizationLevel O) : LAM(), FAM(createFAM(O, TM.getTargetIRAnalysis(), TM.getTargetTriple())), CGAM(), MAM() { + PB.registerLoopAnalyses(LAM); + PB.registerFunctionAnalyses(FAM); + PB.registerCGSCCAnalyses(CGAM); + PB.registerModuleAnalyses(MAM); + PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); +} + +AnalysisManagers::AnalysisManagers(PassBuilder &PB) : LAM(), FAM(), CGAM(), MAM() { + PB.registerLoopAnalyses(LAM); + PB.registerFunctionAnalyses(FAM); + PB.registerCGSCCAnalyses(CGAM); + PB.registerModuleAnalyses(MAM); + PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); +} + +AnalysisManagers::~AnalysisManagers() = default; + +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}; +#ifndef __clang_gcanalyzer__ /* the analyzer cannot prove we have not added instrumentation callbacks with safepoints */ + MPM.run(M, AM.MAM); +#endif +} + +void NewPM::printTimers() { + SI.getTimePasses().print(); +} + +OptimizationLevel getOptLevel(int optlevel) { + switch (std::min(std::max(optlevel, 0), 3)) { + case 0: + return OptimizationLevel::O0; + case 1: + return OptimizationLevel::O1; + case 2: + return OptimizationLevel::O2; + case 3: + return OptimizationLevel::O3; + } + llvm_unreachable("cannot get here!"); +} + +//This part is also basically stolen from LLVM's PassBuilder.cpp file +static llvm::Optional> parseJuliaPipelineOptions(StringRef name) { + if (name.consume_front("julia")) { + auto O = OptimizationLevel::O2; + auto options = OptimizationOptions::defaults(); + if (!name.empty() && (!name.consume_front("<") || !name.consume_back(">"))) { + assert(false && "Expected pass options to be enclosed in <>!"); + } + std::map option_pointers = { +#define OPTION(name) {#name, &options.name} + OPTION(lower_intrinsics), + OPTION(dump_native), + OPTION(external_use), + OPTION(llvm_only) +#undef OPTION + }; + while (!name.empty()) { + StringRef option; + std::tie(option, name) = name.split(';'); + bool enable = !option.consume_front("no_"); + auto it = option_pointers.find(option); + if (it == option_pointers.end()) { + if (option.consume_front("level=")) { + int level = 2; + if (option.getAsInteger(0, level)) { + assert(false && "Non-integer passed to julia level!"); + } + switch (std::min(std::max(level, 0), 3)) { + case 0: + O = OptimizationLevel::O0; + break; + case 1: + O = OptimizationLevel::O1; + break; + case 2: + O = OptimizationLevel::O2; + break; + case 3: + O = OptimizationLevel::O3; + break; + } + } else { + errs() << "Unable to find julia option '" << option << "'!"; + assert(false && "Invalid option passed to julia pass!"); + } + } else { + *it->second = enable; + } + } + return {{O, options}}; + } + return {}; +} + +// new pass manager plugin + +// NOTE: Instead of exporting all the constructors in passes.h we could +// forward the callbacks to the respective passes. LLVM seems to prefer this, +// and when we add the full pass builder having them directly will be helpful. +void registerCallbacks(PassBuilder &PB) JL_NOTSAFEPOINT { + PB.registerPipelineParsingCallback( + [](StringRef Name, FunctionPassManager &PM, + ArrayRef InnerPipeline) { +#define FUNCTION_PASS(NAME, CREATE_PASS) if (Name == NAME) { PM.addPass(CREATE_PASS); return true; } +#include "llvm-julia-passes.inc" +#undef FUNCTION_PASS + return false; + }); + + PB.registerPipelineParsingCallback( + [](StringRef Name, ModulePassManager &PM, + ArrayRef InnerPipeline) { +#define MODULE_PASS(NAME, CREATE_PASS) if (Name == NAME) { PM.addPass(CREATE_PASS); return true; } +#include "llvm-julia-passes.inc" +#undef MODULE_PASS + //Add full pipelines here + auto julia_options = parseJuliaPipelineOptions(Name); + if (julia_options) { + ModulePassManager pipeline; + if (julia_options->first.getSpeedupLevel() < 2) { + buildBasicPipeline(pipeline, nullptr, julia_options->first, julia_options->second); + } else { + buildFullPipeline(pipeline, nullptr, julia_options->first, julia_options->second); + } + PM.addPass(std::move(pipeline)); + return true; + } + return false; + }); + + PB.registerPipelineParsingCallback( + [](StringRef Name, LoopPassManager &PM, + ArrayRef InnerPipeline) { +#define LOOP_PASS(NAME, CREATE_PASS) if (Name == NAME) { PM.addPass(CREATE_PASS); return true; } +#include "llvm-julia-passes.inc" +#undef LOOP_PASS + return false; + }); +} + +extern "C" JL_DLLEXPORT_CODEGEN +::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() JL_NOTSAFEPOINT { + return {LLVM_PLUGIN_API_VERSION, "Julia", "1", registerCallbacks}; +} diff --git a/src/precompile.c b/src/precompile.c index 5a43dc45f094e..4aac28ff9a790 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -10,6 +10,7 @@ #include "julia.h" #include "julia_internal.h" #include "julia_assert.h" +#include "serialize.h" #ifdef __cplusplus extern "C" { @@ -20,17 +21,61 @@ JL_DLLEXPORT int jl_generating_output(void) return jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputji || jl_options.outputasm; } -static void *jl_precompile(int all); +void write_srctext(ios_t *f, jl_array_t *udeps, int64_t srctextpos) { + // Write the source-text for the dependent files + if (udeps) { + // Go back and update the source-text position to point to the current position + int64_t posfile = ios_pos(f); + ios_seek(f, srctextpos); + write_uint64(f, posfile); + ios_seek_end(f); + // Each source-text file is written as + // int32: length of abspath + // char*: abspath + // 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); + ios_t srctext; + for (size_t i = 0; i < len; i++) { + jl_value_t *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]) + continue; + ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0); + if (!srctp) { + jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n", + jl_string_data(dep)); + continue; + } + size_t slen = jl_string_len(dep); + write_int32(f, slen); + ios_write(f, depstr, 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); + ios_close(&srctext); + ios_seek(f, posfile); + write_uint64(f, filelen); + ios_seek_end(f); + } + } + } + write_int32(f, 0); // mark the end of the source text +} -void jl_write_compiler_output(void) +JL_DLLEXPORT void jl_write_compiler_output(void) { if (!jl_generating_output()) { return; } - void *native_code = NULL; - if (!jl_options.incremental) - native_code = jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); + jl_task_wait_empty(); if (!jl_module_init_order) { jl_printf(JL_STDERR, "WARNING: --output requested, but no modules defined during run\n"); @@ -38,7 +83,8 @@ void jl_write_compiler_output(void) } jl_array_t *worklist = jl_module_init_order; - JL_GC_PUSH1(&worklist); + 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); for (i = 0; i < l; i++) { @@ -53,355 +99,72 @@ void jl_write_compiler_output(void) // since it's a slightly duplication of effort jl_value_t *tt = jl_is_type(f) ? (jl_value_t*)jl_wrap_Type(f) : jl_typeof(f); JL_GC_PUSH1(&tt); - tt = (jl_value_t*)jl_apply_tuple_type_v(&tt, 1); + tt = jl_apply_tuple_type_v(&tt, 1); jl_compile_hint((jl_tupletype_t*)tt); JL_GC_POP(); } } } - if (jl_options.incremental) { - if (jl_options.outputji) - if (jl_save_incremental(jl_options.outputji, worklist)) - jl_exit(1); - if (jl_options.outputbc || jl_options.outputunoptbc) - jl_printf(JL_STDERR, "WARNING: incremental output to a .bc file is not implemented\n"); - if (jl_options.outputo) - jl_printf(JL_STDERR, "WARNING: incremental output to a .o file is not implemented\n"); - if (jl_options.outputasm) - jl_printf(JL_STDERR, "WARNING: incremental output to a .s file is not implemented\n"); - } - else { - ios_t *s = NULL; - if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) - s = jl_create_system_image(native_code); - - if (jl_options.outputji) { - if (s == NULL) { - jl_save_system_image(jl_options.outputji); - } - else { - ios_t f; - if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL) - jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji); - ios_write(&f, (const char*)s->buf, (size_t)s->size); - ios_close(&f); - } - } - - if (jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm) { - assert(s); - jl_dump_native(native_code, - jl_options.outputbc, - jl_options.outputunoptbc, - jl_options.outputo, - jl_options.outputasm, - (const char*)s->buf, (size_t)s->size); - } - } - for (size_t i = 0; i < jl_current_modules.size; i += 2) { - if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { - jl_printf(JL_STDERR, "\nWARNING: detected unclosed module: "); - jl_static_show(JL_STDERR, (jl_value_t*)jl_current_modules.table[i]); - jl_printf(JL_STDERR, "\n ** incremental compilation may be broken for this module **\n\n"); - } - } - JL_GC_POP(); -} - -// f{<:Union{...}}(...) is a common pattern -// and expanding the Union may give a leaf function -static void _compile_all_tvar_union(jl_value_t *methsig) -{ - if (!jl_is_unionall(methsig) && jl_is_dispatch_tupletype(methsig)) { - // usually can create a specialized version of the function, - // if the signature is already a dispatch type - if (jl_compile_hint((jl_tupletype_t*)methsig)) - return; - } - - int tvarslen = jl_subtype_env_size(methsig); - jl_value_t *sigbody = methsig; - jl_value_t **roots; - JL_GC_PUSHARGS(roots, 1 + 2 * tvarslen); - jl_value_t **env = roots + 1; - int *idx = (int*)alloca(sizeof(int) * tvarslen); - int i; - for (i = 0; i < tvarslen; i++) { - assert(jl_is_unionall(sigbody)); - idx[i] = 0; - env[2 * i] = (jl_value_t*)((jl_unionall_t*)sigbody)->var; - env[2 * i + 1] = jl_bottom_type; // initialize the list with Union{}, since T<:Union{} is always a valid option - sigbody = ((jl_unionall_t*)sigbody)->body; - } - - for (i = 0; i < tvarslen; /* incremented by inner loop */) { - jl_value_t **sig = &roots[0]; - JL_TRY { - // TODO: wrap in UnionAll for each tvar in env[2*i + 1] ? - // currently doesn't matter much, since jl_compile_hint doesn't work on abstract types - *sig = (jl_value_t*)jl_instantiate_type_with(sigbody, env, tvarslen); - } - JL_CATCH { - goto getnext; // sigh, we found an invalid type signature. should we warn the user? - } - if (!jl_has_concrete_subtype(*sig)) - goto getnext; // signature wouldn't be callable / is invalid -- skip it - if (jl_is_concrete_type(*sig)) { - if (jl_compile_hint((jl_tupletype_t *)*sig)) - goto getnext; // success - } - - getnext: - for (i = 0; i < tvarslen; i++) { - jl_tvar_t *tv = (jl_tvar_t*)env[2 * i]; - if (jl_is_uniontype(tv->ub)) { - size_t l = jl_count_union_components(tv->ub); - size_t j = idx[i]; - if (j == l) { - env[2 * i + 1] = jl_bottom_type; - idx[i] = 0; - } - else { - jl_value_t *ty = jl_nth_union_component(tv->ub, j); - if (!jl_is_concrete_type(ty)) - ty = (jl_value_t*)jl_new_typevar(tv->name, tv->lb, ty); - env[2 * i + 1] = ty; - idx[i] = j + 1; - break; - } - } - else { - env[2 * i + 1] = (jl_value_t*)tv; - } - } - } - JL_GC_POP(); -} - -// f(::Union{...}, ...) is a common pattern -// and expanding the Union may give a leaf function -static void _compile_all_union(jl_value_t *sig) -{ - jl_tupletype_t *sigbody = (jl_tupletype_t*)jl_unwrap_unionall(sig); - size_t count_unions = 0; - size_t i, l = jl_svec_len(sigbody->parameters); - jl_svec_t *p = NULL; - jl_value_t *methsig = NULL; - - for (i = 0; i < l; i++) { - jl_value_t *ty = jl_svecref(sigbody->parameters, i); - if (jl_is_uniontype(ty)) - ++count_unions; - else if (ty == jl_bottom_type) - return; // why does this method exist? - else if (jl_is_datatype(ty) && !jl_has_free_typevars(ty) && - ((!jl_is_kind(ty) && ((jl_datatype_t*)ty)->isconcretetype) || - ((jl_datatype_t*)ty)->name == jl_type_typename)) - return; // no amount of union splitting will make this a leaftype signature - } - - if (count_unions == 0 || count_unions >= 6) { - _compile_all_tvar_union(sig); - return; - } + assert(jl_precompile_toplevel_module == NULL); + void *native_code = NULL; - int *idx = (int*)alloca(sizeof(int) * count_unions); - for (i = 0; i < count_unions; i++) { - idx[i] = 0; - } + bool_t emit_native = jl_options.outputo || jl_options.outputbc || jl_options.outputunoptbc || jl_options.outputasm; - JL_GC_PUSH2(&p, &methsig); - int idx_ctr = 0, incr = 0; - while (!incr) { - p = jl_alloc_svec_uninit(l); - for (i = 0, idx_ctr = 0, incr = 1; i < l; i++) { - jl_value_t *ty = jl_svecref(sigbody->parameters, i); - if (jl_is_uniontype(ty)) { - assert(idx_ctr < count_unions); - size_t l = jl_count_union_components(ty); - size_t j = idx[idx_ctr]; - jl_svecset(p, i, jl_nth_union_component(ty, j)); - ++j; - if (incr) { - if (j == l) { - idx[idx_ctr] = 0; - } - else { - idx[idx_ctr] = j; - incr = 0; - } - } - ++idx_ctr; - } - else { - jl_svecset(p, i, ty); - } - } - methsig = (jl_value_t*)jl_apply_tuple_type(p); - methsig = jl_rewrap_unionall(methsig, sig); - _compile_all_tvar_union(methsig); - } + bool_t emit_split = jl_options.outputji && emit_native; - JL_GC_POP(); -} + ios_t *s = NULL; + ios_t *z = NULL; + int64_t srctextpos = 0 ; + jl_create_system_image(emit_native ? &native_code : NULL, + jl_options.incremental ? worklist : NULL, + emit_split, &s, &z, &udeps, &srctextpos); -static void _compile_all_deq(jl_array_t *found) -{ - int found_i, found_l = jl_array_len(found); - jl_printf(JL_STDERR, "found %d uncompiled methods for compile-all\n", (int)found_l); - jl_method_instance_t *mi = NULL; - jl_value_t *src = NULL; - JL_GC_PUSH2(&mi, &src); - for (found_i = 0; found_i < found_l; found_i++) { - if (found_i % (1 + found_l / 300) == 0 || found_i == found_l - 1) // show 300 progress steps, to show progress without overwhelming log files - jl_printf(JL_STDERR, " %d / %d\r", found_i + 1, found_l); - jl_typemap_entry_t *ml = (jl_typemap_entry_t*)jl_array_ptr_ref(found, found_i); - jl_method_t *m = ml->func.method; - if (m->source == NULL) // TODO: generic implementations of generated functions - continue; - mi = jl_get_unspecialized(mi); - assert(mi == m->unspecialized); // make sure we didn't get tricked by a generated function, since we can't handle those - jl_code_instance_t *ucache = jl_get_method_inferred(mi, (jl_value_t*)jl_any_type, 1, ~(size_t)0); - if (ucache->invoke != NULL) - continue; - src = m->source; - assert(src); - // TODO: we could now enable storing inferred function pointers in the `unspecialized` cache - //src = jl_type_infer(mi, jl_atomic_load_acquire(&jl_world_counter), 1); - //if (ucache->invoke != NULL) - // continue; + if (!emit_split) + z = s; - // first try to create leaf signatures from the signature declaration and compile those - _compile_all_union((jl_value_t*)ml->sig); - // then also compile the generic fallback - jl_generate_fptr_for_unspecialized(ucache); + // jl_dump_native writes the clone_targets into `s` + // We need to postpone the srctext writing after that. + if (native_code) { + jl_dump_native(native_code, + jl_options.outputbc, + jl_options.outputunoptbc, + jl_options.outputo, + jl_options.outputasm, + (const char*)z->buf, (size_t)z->size, s); + jl_postoutput_hook(); } - JL_GC_POP(); - jl_printf(JL_STDERR, "\n"); -} -static int compile_all_enq__(jl_typemap_entry_t *ml, void *env) -{ - jl_array_t *found = (jl_array_t*)env; - // method definition -- compile template field - jl_method_t *m = ml->func.method; - if (m->source) { - // found a method to compile - jl_array_ptr_1d_push(found, (jl_value_t*)ml); + if ((jl_options.outputji || emit_native) && jl_options.incremental) { + write_srctext(s, udeps, srctextpos); } - return 1; -} - -static int compile_all_enq_(jl_methtable_t *mt, void *env) -{ - jl_typemap_visitor(mt->defs, compile_all_enq__, env); - return 1; -} - -static void jl_compile_all_defs(void) -{ - // this "found" array will contain - // TypeMapEntries for Methods and MethodInstances that need to be compiled - jl_array_t *m = jl_alloc_vec_any(0); - JL_GC_PUSH1(&m); - while (1) { - jl_foreach_reachable_mtable(compile_all_enq_, m); - size_t changes = jl_array_len(m); - if (!changes) - break; - _compile_all_deq(m); - jl_array_del_end(m, changes); + if (jl_options.outputji) { + ios_t f; + if (ios_file(&f, jl_options.outputji, 1, 1, 1, 1) == NULL) + jl_errorf("cannot open system image file \"%s\" for writing", jl_options.outputji); + ios_write(&f, (const char*)s->buf, (size_t)s->size); + ios_close(&f); } - JL_GC_POP(); -} -static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closure) -{ - assert(jl_is_method_instance(mi)); - jl_code_instance_t *codeinst = mi->cache; - while (codeinst) { - int do_compile = 0; - if (codeinst->invoke != jl_fptr_const_return) { - if (codeinst->inferred && codeinst->inferred != jl_nothing && - jl_ir_flag_inferred((jl_array_t*)codeinst->inferred) && - !jl_ir_flag_inlineable((jl_array_t*)codeinst->inferred)) { - do_compile = 1; - } - else if (codeinst->invoke != NULL || codeinst->precompile) { - do_compile = 1; - } - } - if (do_compile) { - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); - return 1; - } - codeinst = jl_atomic_load_relaxed(&codeinst->next); + if (s) { + ios_close(s); + free(s); } - return 1; -} -static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *closure) -{ - jl_method_t *m = def->func.method; - if ((m->name == jl_symbol("__init__") || m->ccallable) && jl_is_dispatch_tupletype(m->sig)) { - // ensure `__init__()` and @ccallables get strongly-hinted, specialized, and compiled - jl_method_instance_t *mi = jl_specializations_get_linfo(m, m->sig, jl_emptysvec); - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); - } - else { - jl_svec_t *specializations = def->func.method->specializations; - size_t i, l = jl_svec_len(specializations); - for (i = 0; i < l; i++) { - jl_value_t *mi = jl_svecref(specializations, i); - if (mi != jl_nothing) - precompile_enq_specialization_((jl_method_instance_t*)mi, closure); - } + if (emit_split) { + ios_close(z); + free(z); } - if (m->ccallable) - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)m->ccallable); - return 1; -} - -static int precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) -{ - return jl_typemap_visitor(mt->defs, precompile_enq_all_specializations__, env); -} -static void *jl_precompile(int all) -{ - if (all) - jl_compile_all_defs(); - // this "found" array will contain function - // type signatures that were inferred but haven't been compiled - jl_array_t *m = jl_alloc_vec_any(0); - jl_array_t *m2 = NULL; - jl_method_instance_t *mi = NULL; - JL_GC_PUSH3(&m, &m2, &mi); - jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); - m2 = jl_alloc_vec_any(0); - for (size_t i = 0; i < jl_array_len(m); i++) { - jl_value_t *item = jl_array_ptr_ref(m, i); - if (jl_is_method_instance(item)) { - mi = (jl_method_instance_t*)item; - size_t min_world = 0; - size_t max_world = ~(size_t)0; - if (!jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->def.method)) - mi = jl_get_specialization1((jl_tupletype_t*)mi->specTypes, jl_atomic_load_acquire(&jl_world_counter), &min_world, &max_world, 0); - if (mi) - jl_array_ptr_1d_push(m2, (jl_value_t*)mi); - } - else { - assert(jl_is_simplevector(item)); - assert(jl_svec_len(item) == 2); - jl_array_ptr_1d_push(m2, item); + for (size_t i = 0; i < jl_current_modules.size; i += 2) { + if (jl_current_modules.table[i + 1] != HT_NOTFOUND) { + jl_printf(JL_STDERR, "\nWARNING: detected unclosed module: "); + jl_static_show(JL_STDERR, (jl_value_t*)jl_current_modules.table[i]); + jl_printf(JL_STDERR, "\n ** incremental compilation may be broken for this module **\n\n"); } } - m = NULL; - void *native_code = jl_create_native(m2, NULL, 0); JL_GC_POP(); - return native_code; } #ifdef __cplusplus diff --git a/src/precompile_utils.c b/src/precompile_utils.c new file mode 100644 index 0000000000000..055ec4b3330f1 --- /dev/null +++ b/src/precompile_utils.c @@ -0,0 +1,321 @@ +// f{<:Union{...}}(...) is a common pattern +// and expanding the Union may give a leaf function +static void _compile_all_tvar_union(jl_value_t *methsig) +{ + int tvarslen = jl_subtype_env_size(methsig); + jl_value_t *sigbody = methsig; + jl_value_t **roots; + JL_GC_PUSHARGS(roots, 1 + 2 * tvarslen); + jl_value_t **env = roots + 1; + int *idx = (int*)alloca(sizeof(int) * tvarslen); + int i; + for (i = 0; i < tvarslen; i++) { + assert(jl_is_unionall(sigbody)); + idx[i] = 0; + env[2 * i] = (jl_value_t*)((jl_unionall_t*)sigbody)->var; + env[2 * i + 1] = jl_bottom_type; // initialize the list with Union{}, since T<:Union{} is always a valid option + sigbody = ((jl_unionall_t*)sigbody)->body; + } + + for (i = 0; i < tvarslen; /* incremented by inner loop */) { + jl_value_t **sig = &roots[0]; + JL_TRY { + // TODO: wrap in UnionAll for each tvar in env[2*i + 1] ? + // currently doesn't matter much, since jl_compile_hint doesn't work on abstract types + *sig = (jl_value_t*)jl_instantiate_type_with(sigbody, env, tvarslen); + } + JL_CATCH { + goto getnext; // sigh, we found an invalid type signature. should we warn the user? + } + if (!jl_has_concrete_subtype(*sig)) + goto getnext; // signature wouldn't be callable / is invalid -- skip it + if (jl_is_concrete_type(*sig)) { + if (jl_compile_hint((jl_tupletype_t *)*sig)) + goto getnext; // success + } + + getnext: + for (i = 0; i < tvarslen; i++) { + jl_tvar_t *tv = (jl_tvar_t*)env[2 * i]; + if (jl_is_uniontype(tv->ub)) { + size_t l = jl_count_union_components(tv->ub); + size_t j = idx[i]; + if (j == l) { + env[2 * i + 1] = jl_bottom_type; + idx[i] = 0; + } + else { + jl_value_t *ty = jl_nth_union_component(tv->ub, j); + if (!jl_is_concrete_type(ty)) + ty = (jl_value_t*)jl_new_typevar(tv->name, tv->lb, ty); + env[2 * i + 1] = ty; + idx[i] = j + 1; + break; + } + } + else { + env[2 * i + 1] = (jl_value_t*)tv; + } + } + } + JL_GC_POP(); +} + +// f(::Union{...}, ...) is a common pattern +// and expanding the Union may give a leaf function +static void _compile_all_union(jl_value_t *sig) +{ + jl_tupletype_t *sigbody = (jl_tupletype_t*)jl_unwrap_unionall(sig); + size_t count_unions = 0; + size_t i, l = jl_svec_len(sigbody->parameters); + jl_svec_t *p = NULL; + jl_value_t *methsig = NULL; + + for (i = 0; i < l; i++) { + jl_value_t *ty = jl_svecref(sigbody->parameters, i); + if (jl_is_uniontype(ty)) + ++count_unions; + else if (ty == jl_bottom_type) + return; // why does this method exist? + else if (jl_is_datatype(ty) && !jl_has_free_typevars(ty) && + ((!jl_is_kind(ty) && ((jl_datatype_t*)ty)->isconcretetype) || + ((jl_datatype_t*)ty)->name == jl_type_typename)) + return; // no amount of union splitting will make this a leaftype signature + } + + if (count_unions == 0 || count_unions >= 6) { + _compile_all_tvar_union(sig); + return; + } + + int *idx = (int*)alloca(sizeof(int) * count_unions); + for (i = 0; i < count_unions; i++) { + idx[i] = 0; + } + + JL_GC_PUSH2(&p, &methsig); + int idx_ctr = 0, incr = 0; + while (!incr) { + p = jl_alloc_svec_uninit(l); + for (i = 0, idx_ctr = 0, incr = 1; i < l; i++) { + jl_value_t *ty = jl_svecref(sigbody->parameters, i); + if (jl_is_uniontype(ty)) { + assert(idx_ctr < count_unions); + size_t l = jl_count_union_components(ty); + size_t j = idx[idx_ctr]; + jl_svecset(p, i, jl_nth_union_component(ty, j)); + ++j; + if (incr) { + if (j == l) { + idx[idx_ctr] = 0; + } + else { + idx[idx_ctr] = j; + incr = 0; + } + } + ++idx_ctr; + } + else { + jl_svecset(p, i, ty); + } + } + methsig = jl_apply_tuple_type(p); + methsig = jl_rewrap_unionall(methsig, sig); + _compile_all_tvar_union(methsig); + } + + JL_GC_POP(); +} + +static int compile_all_collect__(jl_typemap_entry_t *ml, void *env) +{ + jl_array_t *allmeths = (jl_array_t*)env; + jl_method_t *m = ml->func.method; + if (m->external_mt) + return 1; + if (m->source) { + // method has a non-generated definition; can be compiled generically + jl_array_ptr_1d_push(allmeths, (jl_value_t*)m); + } + return 1; +} + +static int compile_all_collect_(jl_methtable_t *mt, void *env) +{ + jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), compile_all_collect__, env); + return 1; +} + +static void jl_compile_all_defs(jl_array_t *mis) +{ + jl_array_t *allmeths = jl_alloc_vec_any(0); + JL_GC_PUSH1(&allmeths); + + jl_foreach_reachable_mtable(compile_all_collect_, allmeths); + + size_t i, l = jl_array_len(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)) { + // method has a single compilable specialization, e.g. its definition + // signature is concrete. in this case we can just hint it. + jl_compile_hint((jl_tupletype_t*)m->sig); + } + else { + // first try to create leaf signatures from the signature declaration and compile those + _compile_all_union(m->sig); + + // finally, compile a fully generic fallback that can work for all arguments + jl_method_instance_t *unspec = jl_get_unspecialized(m); + if (unspec) + jl_array_ptr_1d_push(mis, (jl_value_t*)unspec); + } + } + + JL_GC_POP(); +} + +static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closure) +{ + assert(jl_is_method_instance(mi)); + 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) { + 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)) { + do_compile = 1; + } + else if (jl_atomic_load_relaxed(&codeinst->invoke) != NULL || jl_atomic_load_relaxed(&codeinst->precompile)) { + do_compile = 1; + } + } + if (do_compile) { + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); + return 1; + } + codeinst = jl_atomic_load_relaxed(&codeinst->next); + } + return 1; +} + +static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *closure) +{ + jl_method_t *m = def->func.method; + if (m->external_mt) + return 1; + if ((m->name == jl_symbol("__init__") || m->ccallable) && jl_is_dispatch_tupletype(m->sig)) { + // ensure `__init__()` and @ccallables get strongly-hinted, specialized, and compiled + jl_method_instance_t *mi = jl_specializations_get_linfo(m, m->sig, jl_emptysvec); + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)mi); + } + else { + jl_value_t *specializations = jl_atomic_load_relaxed(&def->func.method->specializations); + if (!jl_is_svec(specializations)) { + precompile_enq_specialization_((jl_method_instance_t*)specializations, closure); + } + else { + size_t i, l = jl_svec_len(specializations); + for (i = 0; i < l; i++) { + jl_value_t *mi = jl_svecref(specializations, i); + if (mi != jl_nothing) + precompile_enq_specialization_((jl_method_instance_t*)mi, closure); + } + } + } + if (m->ccallable) + jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)m->ccallable); + return 1; +} + +static int precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) +{ + return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), precompile_enq_all_specializations__, env); +} + +static void *jl_precompile_(jl_array_t *m, int external_linkage) +{ + jl_array_t *m2 = NULL; + 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++) { + jl_value_t *item = jl_array_ptr_ref(m, i); + if (jl_is_method_instance(item)) { + mi = (jl_method_instance_t*)item; + size_t min_world = 0; + size_t max_world = ~(size_t)0; + if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->sparam_vals, mi->def.method)) + mi = jl_get_specialization1((jl_tupletype_t*)mi->specTypes, jl_atomic_load_acquire(&jl_world_counter), &min_world, &max_world, 0); + if (mi) + jl_array_ptr_1d_push(m2, (jl_value_t*)mi); + } + else { + assert(jl_is_simplevector(item)); + assert(jl_svec_len(item) == 2); + jl_array_ptr_1d_push(m2, item); + } + } + void *native_code = jl_create_native(m2, NULL, NULL, 0, 1, external_linkage, + jl_atomic_load_acquire(&jl_world_counter)); + JL_GC_POP(); + return native_code; +} + +static void *jl_precompile(int all) +{ + // array of MethodInstances and ccallable aliases to include in the output + jl_array_t *m = jl_alloc_vec_any(0); + JL_GC_PUSH1(&m); + if (all) + jl_compile_all_defs(m); + jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); + void *native_code = jl_precompile_(m, 0); + JL_GC_POP(); + return native_code; +} + +static void *jl_precompile_worklist(jl_array_t *worklist, jl_array_t *extext_methods, jl_array_t *new_specializations) +{ + if (!worklist) + return NULL; + // this "found" array will contain function + // 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); + 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); + 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)); + jl_value_t *specializations = jl_atomic_load_relaxed(&method->specializations); + if (!jl_is_svec(specializations)) { + precompile_enq_specialization_((jl_method_instance_t*)specializations, m); + } + else { + size_t j, l = jl_svec_len(specializations); + for (j = 0; j < l; j++) { + jl_value_t *mi = jl_svecref(specializations, j); + if (mi != jl_nothing) + precompile_enq_specialization_((jl_method_instance_t*)mi, m); + } + } + } + n = jl_array_len(new_specializations); + for (i = 0; i < n; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_specializations, i); + precompile_enq_specialization_(ci->def, m); + } + void *native_code = jl_precompile_(m, 1); + JL_GC_POP(); + return native_code; +} diff --git a/src/processor.cpp b/src/processor.cpp index b9dfc2b7f0b4e..24a434af91ad3 100644 --- a/src/processor.cpp +++ b/src/processor.cpp @@ -17,6 +17,12 @@ #include "julia_assert.h" +#ifndef _OS_WINDOWS_ +#include +#endif + +#include + // CPU target string is a list of strings separated by `;` each string starts with a CPU // or architecture name and followed by an optional list of features separated by `,`. // A "generic" or empty CPU name means the basic required feature set of the target ISA @@ -621,107 +627,192 @@ static inline std::vector> &get_cmdline_targets(F &&feature_cb) // Load sysimg, use the `callback` for dispatch and perform all relocations // for the selected target. template -static inline jl_sysimg_fptrs_t parse_sysimg(void *hdl, F &&callback) +static inline jl_image_t parse_sysimg(void *hdl, F &&callback) { - jl_sysimg_fptrs_t res = {nullptr, 0, nullptr, 0, nullptr, nullptr}; - - // .data base - char *data_base; - jl_dlsym(hdl, "jl_sysimg_gvars_base", (void**)&data_base, 1); - // .text base - char *text_base; - jl_dlsym(hdl, "jl_sysimg_fvars_base", (void**)&text_base, 1); - res.base = text_base; + JL_TIMING(LOAD_IMAGE, LOAD_Processor); + jl_image_t res{}; - int32_t *offsets; - jl_dlsym(hdl, "jl_sysimg_fvars_offsets", (void**)&offsets, 1); - uint32_t nfunc = offsets[0]; - res.offsets = offsets + 1; + const jl_image_pointers_t *pointers; + jl_dlsym(hdl, "jl_image_pointers", (void**)&pointers, 1); - void *ids; - jl_dlsym(hdl, "jl_dispatch_target_ids", &ids, 1); + const void *ids = pointers->target_data; uint32_t target_idx = callback(ids); - int32_t *reloc_slots; - jl_dlsym(hdl, "jl_dispatch_reloc_slots", (void **)&reloc_slots, 1); - const uint32_t nreloc = reloc_slots[0]; - reloc_slots += 1; - uint32_t *clone_idxs; - int32_t *clone_offsets; - jl_dlsym(hdl, "jl_dispatch_fvars_idxs", (void**)&clone_idxs, 1); - jl_dlsym(hdl, "jl_dispatch_fvars_offsets", (void**)&clone_offsets, 1); - uint32_t tag_len = clone_idxs[0]; - clone_idxs += 1; - - assert(tag_len & jl_sysimg_tag_mask); - std::vector base_offsets = {res.offsets}; - // Find target - 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; - } - else { - clone_offsets += len; - clone_idxs += len + 2; - } - tag_len = clone_idxs[-1]; - base_offsets.push_back(tag_len & jl_sysimg_tag_mask ? clone_offsets : nullptr); + if (pointers->header->version != 1) { + jl_error("Image file is not compatible with this version of Julia"); } - bool clone_all = (tag_len & jl_sysimg_tag_mask) != 0; - // Fill in return value - if (clone_all) { - // clone_all - if (target_idx != 0) { - res.offsets = clone_offsets; - } - } - else { - uint32_t base_idx = clone_idxs[0]; - assert(base_idx < target_idx); - if (target_idx != 0) { - res.offsets = base_offsets[base_idx]; - assert(res.offsets); + std::vector fvars(pointers->header->nfvars); + std::vector gvars(pointers->header->ngvars); + + std::vector> 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]; + assert(nfunc <= pointers->header->nfvars); + offsets++; + const int32_t *reloc_slots = shard.clone_slots; + const uint32_t nreloc = reloc_slots[0]; + reloc_slots += 1; + const uint32_t *clone_idxs = shard.clone_idxs; + const int32_t *clone_offsets = shard.clone_offsets; + uint32_t tag_len = clone_idxs[0]; + clone_idxs += 1; + + assert(tag_len & jl_sysimg_tag_mask); + std::vector base_offsets = {offsets}; + // Find target + 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; + } + else { + clone_offsets += len; + clone_idxs += len + 2; + } + tag_len = clone_idxs[-1]; + base_offsets.push_back(tag_len & jl_sysimg_tag_mask ? clone_offsets : nullptr); } - clone_idxs++; - res.nclones = tag_len; - res.clone_offsets = clone_offsets; - res.clone_idxs = clone_idxs; - } - // Do relocation - uint32_t reloc_i = 0; - 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; + + bool clone_all = (tag_len & jl_sysimg_tag_mask) != 0; + // Fill in return value if (clone_all) { - offset = res.offsets[idx]; - } - else if (idx & jl_sysimg_tag_mask) { - idx = idx & jl_sysimg_val_mask; - offset = clone_offsets[i]; + // clone_all + if (target_idx != 0) { + offsets = clone_offsets; + } } else { - continue; + uint32_t base_idx = clone_idxs[0]; + assert(base_idx < target_idx); + if (target_idx != 0) { + offsets = base_offsets[base_idx]; + assert(offsets); + } + 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}; + } } - bool found = false; - for (; reloc_i < nreloc; reloc_i++) { - auto reloc_idx = ((const uint32_t*)reloc_slots)[reloc_i * 2]; - if (reloc_idx == idx) { - found = true; - auto slot = (const void**)(data_base + reloc_slots[reloc_i * 2 + 1]); - *slot = offset + res.base; + // Do relocation + uint32_t reloc_i = 0; + 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; + if (clone_all) { + offset = offsets[idx]; + } + else if (idx & jl_sysimg_tag_mask) { + idx = idx & jl_sysimg_val_mask; + offset = clone_offsets[i]; + } + else { + continue; } - else if (reloc_idx > idx) { - break; + bool found = false; + for (; reloc_i < nreloc; reloc_i++) { + auto reloc_idx = ((const uint32_t*)reloc_slots)[reloc_i * 2]; + if (reloc_idx == idx) { + found = true; + auto slot = (const void**)(data_base + reloc_slots[reloc_i * 2 + 1]); + assert(slot); + *slot = offset + text_base; + } + else if (reloc_idx > idx) { + break; + } } + assert(found && "Cannot find GOT entry for cloned function."); + (void)found; + } + + auto fidxs = shard.fvar_idxs; + for (uint32_t i = 0; i < nfunc; i++) { + fvars[fidxs[i]] = text_base + offsets[i]; + } + + auto gidxs = shard.gvar_idxs; + unsigned ngvars = shard.gvar_offsets[0]; + assert(ngvars <= pointers->header->ngvars); + 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]; + for (size_t i = 0; i < fvars.size(); i++) { + assert(fvars[i] && "Missing function pointer!"); + offsets[i] = fvars[i] - res.fptrs.base; + } + res.fptrs.offsets = offsets; + res.fptrs.noffsets = fvars.size(); + } + + if (!gvars.empty()) { + auto offsets = (int32_t *) malloc(sizeof(int32_t) * gvars.size()); + res.gvars_base = (uintptr_t *)gvars[0]; + 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; + } + res.gvars_offsets = offsets; + res.ngvars = gvars.size(); + } + + if (!clones.empty()) { + assert(!fvars.empty()); + std::sort(clones.begin(), clones.end()); + auto clone_offsets = (int32_t *) malloc(sizeof(int32_t) * 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; } - assert(found && "Cannot find GOT entry for cloned function."); - (void)found; + res.fptrs.clone_idxs = clone_idxs; + res.fptrs.clone_offsets = clone_offsets; + res.fptrs.nclones = clones.size(); + } + +#ifdef _OS_WINDOWS_ + res.base = (intptr_t)hdl; +#else + Dl_info dlinfo; + if (dladdr((void*)pointers, &dlinfo) != 0) { + res.base = (intptr_t)dlinfo.dli_fbase; + } + else { + res.base = 0; } +#endif + + { + void *pgcstack_func_slot = pointers->ptls->pgcstack_func_slot; + void *pgcstack_key_slot = pointers->ptls->pgcstack_key_slot; + jl_pgcstack_getkey((jl_get_pgcstack_func**)pgcstack_func_slot, (jl_pgcstack_key_t*)pgcstack_key_slot); + + size_t *tls_offset_idx = pointers->ptls->tls_offset; + *tls_offset_idx = (uintptr_t)(jl_tls_offset == -1 ? 0 : jl_tls_offset); + } + + res.small_typeof = pointers->small_typeof; return res; } @@ -734,20 +825,24 @@ static inline void check_cmdline(T &&cmdline, bool imaging) // sysimg means. Make it an error for now. if (!imaging) { if (cmdline.size() > 1) { - jl_error("More than one command line CPU targets specified " - "without a `--output-` flag specified"); + jl_safe_printf("More than one command line CPU targets specified " + "without a `--output-` flag specified"); + exit(1); } if (cmdline[0].en.flags & JL_TARGET_CLONE_ALL) { - jl_error("\"clone_all\" feature specified " - "without a `--output-` flag specified"); + jl_safe_printf("\"clone_all\" feature specified " + "without a `--output-` flag specified"); + exit(1); } if (cmdline[0].en.flags & JL_TARGET_OPTSIZE) { - jl_error("\"opt_size\" feature specified " - "without a `--output-` flag specified"); + jl_safe_printf("\"opt_size\" feature specified " + "without a `--output-` flag specified"); + exit(1); } if (cmdline[0].en.flags & JL_TARGET_MINSIZE) { - jl_error("\"min_size\" feature specified " - "without a `--output-` flag specified"); + jl_safe_printf("\"min_size\" feature specified " + "without a `--output-` flag specified"); + exit(1); } } } diff --git a/src/processor.h b/src/processor.h index 1d385cfc80b98..3e83bbb2247d6 100644 --- a/src/processor.h +++ b/src/processor.h @@ -1,5 +1,8 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license +#ifndef JL_PROCESSOR_H +#define JL_PROCESSOR_H + #include "support/dtypes.h" #include "julia.h" @@ -11,82 +14,9 @@ extern "C" { #endif -/** - * Related sysimg exported symbols - * - * In the following text, function refers to an abstract entity. - * It corresponds to a `Function` that we emit in the codegen, and there might be multiple copies - * of it in the system image. Only one of those copies will be used in a given session. - * Function pointers refer to a real piece of code in the system image. - * Each function might have multiple function pointers in the system image - * and each function pointer will correspond to only one function. - * - * # Global function and base pointers - * `jl_sysimg_gvars_base`: - * The address of this symbol is the base data pointer - * (all other data pointers are stored as offsets to this address) - * `jl_sysimg_fvars_base`: - * The address of this symbol is the base function pointer - * (all other function pointers are stored as offsets to this address) - * `jl_sysimg_fvars_offsets`: [static data] - * The array of function pointer offsets (`int32_t`) from the base pointer. - * 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 - * 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. - * - * # Target data and dispatch slots (Only needed by runtime during loading) - * `jl_dispatch_target_ids`: [static data] serialize target data. - * This contains the number of targets which is needed to decode `jl_dispatch_fvars_idxs` - * in addition to the name and feature set of each target. - * `jl_dispatch_reloc_slots`: [static data] location and index of relocation slots. - * Stored as pairs of function indices and `int32_t` offsets from `jl_sysimg_gvars_base`. - * The first element is an `uint32_t` giving the number of relocations. - * This is needed for functions whose address is used in a way that requires dispatch. - * We currently only support one type of relocation (i.e. absolute pointer) which is enough - * for all use in functions as well as GOT slot (for "PLT" callback). - * Note that not all functions being cloned are assigned a slot. - * This array is sorted by the function indices. - * There can be more than one slot per-function, - * i.e. there can be duplicated function indices. - * - * # Target functions - * `jl_dispatch_fvars_idxs`: [static data] 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. - * Otherwise, the length is the total number of functions that we are interested in - * for this target, which includes all cloned julia functions and - * 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 - * 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`. - * `jl_dispatch_fvars_offsets`: [static data] Target-specific function pointer offsets. - * This contains all the cloned functions that we are interested in and it needs to be decoded - * and used along with `jl_dispatch_fvars_idxs`. - * For the default target, there's no entries in this variable, if there's any relocations - * needed for the default target, the function pointers are taken from the global offset - * arrays directly. - * For a `clone_all` target (i.e. with the length in `jl_dispatch_fvars_idxs` tagged), this - * variable contains an offset array of the same length as the global one. Only the indices - * appearing in `jl_dispatch_fvars_idxs` need relocation and the dispatch code should return - * 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. - */ +// Image metadata +// Every image exports a `jl_image_pointers_t` as a global symbol `jl_image_pointers`. +// This symbol acts as a root for all other code-related symbols in the image. enum { JL_TARGET_VEC_CALL = 1 << 0, @@ -109,6 +39,8 @@ enum { JL_TARGET_MINSIZE = 1 << 7, // Clone when the function queries CPU features JL_TARGET_CLONE_CPU = 1 << 8, + // Clone when the function uses fp16 + JL_TARGET_CLONE_FLOAT16 = 1 << 9, }; #define JL_FEATURE_DEF_NAME(name, bit, llvmver, str) JL_FEATURE_DEF(name, bit, llvmver) @@ -130,7 +62,7 @@ JL_DLLEXPORT int jl_test_cpu_feature(jl_cpu_feature_t feature); static const uint32_t jl_sysimg_tag_mask = 0x80000000u; static const uint32_t jl_sysimg_val_mask = ~((uint32_t)0x80000000u); -typedef struct _jl_sysimg_fptrs_t { +typedef struct _jl_image_fptrs_t { // base function pointer const char *base; // number of functions @@ -148,7 +80,131 @@ typedef struct _jl_sysimg_fptrs_t { const int32_t *clone_offsets; // sorted indices of the cloned functions (including the tag bit) const uint32_t *clone_idxs; -} jl_sysimg_fptrs_t; +} jl_image_fptrs_t; + +typedef struct { + uint64_t base; + uintptr_t *gvars_base; + const int32_t *gvars_offsets; + uint32_t ngvars; + jl_image_fptrs_t fptrs; + void **small_typeof; +} jl_image_t; + +// The header for each image +// Details important counts about the image +typedef struct { + // The version of the image format + // Most up-to-date version is 1 + uint32_t version; + // The number of shards in this image + uint32_t nshards; + // The total number of fvars in this image among all shards + uint32_t nfvars; + // The total number of gvars in this image among all shards + uint32_t ngvars; +} jl_image_header_t; + +// 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. + // 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 + // 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; + // 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 + 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 + const uint32_t *gvar_idxs; + // location and index of relocation slots. + // Stored as pairs of function indices and `int32_t` offsets from `jl_sysimg_gvars_base`. + // The first element is an `uint32_t` giving the number of relocations. + // This is needed for functions whose address is used in a way that requires dispatch. + // We currently only support one type of relocation (i.e. absolute pointer) which is enough + // for all use in functions as well as GOT slot (for "PLT" callback). + // Note that not all functions being cloned are assigned a slot. + // This array is sorted by the function indices. + // There can be more than one slot per-function, + // i.e. there can be duplicated function indices. + const int32_t *clone_slots; + // Target-specific function pointer offsets. + // This contains all the cloned functions that we are interested in and it needs to be decoded + // and used along with `jl_dispatch_fvars_idxs`. + // For the default target, there's no entries in this variable, if there's any relocations + // needed for the default target, the function pointers are taken from the global offset + // arrays directly. + // For a `clone_all` target (i.e. with the length in `jl_dispatch_fvars_idxs` tagged), this + // variable contains an offset array of the same length as the global one. Only the indices + // appearing in `jl_dispatch_fvars_idxs` need relocation and the dispatch code should return + // 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; + // 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. + // Otherwise, the length is the total number of functions that we are interested in + // for this target, which includes all cloned julia functions and + // 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 + // 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; + +// The TLS data for each image +typedef struct { + void *pgcstack_func_slot; + void *pgcstack_key_slot; + size_t *tls_offset; +} jl_image_ptls_t; + +//The root struct for images, points to all the other globals +typedef struct { + // The image header, contains numerical global data + const jl_image_header_t *header; + // The shard table, contains per-shard data + const jl_image_shard_t *shards; // points to header->nshards length array + // The TLS data pointer + const jl_image_ptls_t *ptls; + // A copy of small_typeof[] + void **small_typeof; + + // serialized target data + // This contains the number of targets + // in addition to the name and feature set of each target. + const void *target_data; +} jl_image_pointers_t; /** * Initialize the processor dispatch system with sysimg `hdl` (also initialize the sysimg itself). @@ -160,13 +216,15 @@ typedef struct _jl_sysimg_fptrs_t { * * Return the data about the function pointers selected. */ -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl); +jl_image_t jl_init_processor_sysimg(void *hdl); +jl_image_t jl_init_processor_pkgimg(void *hdl); // Return the name of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); // Dump the name and feature set of the host CPU // For debugging only JL_DLLEXPORT void jl_dump_host_cpu(void); +JL_DLLEXPORT void jl_check_pkgimage_clones(char* data); JL_DLLEXPORT int32_t jl_set_zero_subnormals(int8_t isZero); JL_DLLEXPORT int32_t jl_get_zero_subnormals(void); @@ -187,14 +245,14 @@ extern JL_DLLEXPORT bool jl_processor_print_help; * If the detected/specified CPU name is not available on the LLVM version specified, * a fallback CPU name will be used. Unsupported features will be ignored. */ -extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags); +extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) JL_NOTSAFEPOINT; /** * Returns the CPU name and feature string to be used by LLVM disassembler. * * This will return a generic CPU name and a full feature string. */ -extern "C" JL_DLLEXPORT const std::pair &jl_get_llvm_disasm_target(void); +extern "C" JL_DLLEXPORT const std::pair &jl_get_llvm_disasm_target(void) JL_NOTSAFEPOINT; struct jl_target_spec_t { // LLVM target name @@ -211,7 +269,9 @@ struct jl_target_spec_t { /** * Return the list of targets to clone */ -extern "C" JL_DLLEXPORT std::vector jl_get_llvm_clone_targets(void); -std::string jl_get_cpu_name_llvm(void); -std::string jl_get_cpu_features_llvm(void); +extern "C" JL_DLLEXPORT std::vector 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; +#endif + #endif diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index f5cc2a42a4870..0797fa4381f9d 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1586,6 +1586,20 @@ static uint32_t sysimg_init_cb(const void *id) return match.best_idx; } +static uint32_t pkgimg_init_cb(const void *id) +{ + TargetData target = jit_targets.front(); + auto pkgimg = deserialize_target_data((const uint8_t*)id); + for (auto &t: pkgimg) { + if (auto nname = normalize_cpu_name(t.name)) { + t.name = nname; + } + } + auto match = match_sysimg_targets(pkgimg, target, max_vector_size); + + return match.best_idx; +} + static void ensure_jit_target(bool imaging) { auto &cmdline = get_cmdline_targets(); @@ -1602,12 +1616,19 @@ static void ensure_jit_target(bool imaging) auto &t = jit_targets[i]; if (t.en.flags & JL_TARGET_CLONE_ALL) continue; + auto &features0 = jit_targets[t.base].en.features; // Always clone when code checks CPU features t.en.flags |= JL_TARGET_CLONE_CPU; + static constexpr uint32_t clone_fp16[] = {Feature::fp16fml,Feature::fullfp16}; + for (auto fe: clone_fp16) { + if (!test_nbit(features0, fe) && test_nbit(t.en.features, fe)) { + t.en.flags |= JL_TARGET_CLONE_FLOAT16; + break; + } + } // The most useful one in general... t.en.flags |= JL_TARGET_CLONE_LOOP; #ifdef _CPU_ARM_ - auto &features0 = jit_targets[t.base].en.features; static constexpr uint32_t clone_math[] = {Feature::vfp3, Feature::vfp4, Feature::neon}; for (auto fe: clone_math) { if (!test_nbit(features0, fe) && test_nbit(t.en.features, fe)) { @@ -1781,13 +1802,27 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) return jl_cstr_to_string(host_cpu_name().c_str()); } -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) +jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); return parse_sysimg(hdl, sysimg_init_cb); } +jl_image_t jl_init_processor_pkgimg(void *hdl) +{ + if (jit_targets.empty()) + jl_error("JIT targets not initialized"); + if (jit_targets.size() > 1) + jl_error("Expected only one JIT target"); + return parse_sysimg(hdl, pkgimg_init_cb); +} + +JL_DLLEXPORT void jl_check_pkgimage_clones(char *data) +{ + pkgimg_init_cb(data); +} + std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); @@ -1843,20 +1878,20 @@ extern "C" int jl_test_cpu_feature(jl_cpu_feature_t feature) #ifdef _CPU_AARCH64_ // FPCR FZ, bit [24] -static constexpr uint32_t fpcr_fz_mask = 1 << 24; +static constexpr uint64_t fpcr_fz_mask = 1 << 24; // FPCR FZ16, bit [19] -static constexpr uint32_t fpcr_fz16_mask = 1 << 19; +static constexpr uint64_t fpcr_fz16_mask = 1 << 19; // FPCR DN, bit [25] -static constexpr uint32_t fpcr_dn_mask = 1 << 25; +static constexpr uint64_t fpcr_dn_mask = 1 << 25; -static inline uint32_t get_fpcr_aarch64(void) +static inline uint64_t get_fpcr_aarch64(void) { - uint32_t fpcr; + uint64_t fpcr; asm volatile("mrs %0, fpcr" : "=r"(fpcr)); return fpcr; } -static inline void set_fpcr_aarch64(uint32_t fpcr) +static inline void set_fpcr_aarch64(uint64_t fpcr) { asm volatile("msr fpcr, %0" :: "r"(fpcr)); } @@ -1868,8 +1903,8 @@ extern "C" JL_DLLEXPORT int32_t jl_get_zero_subnormals(void) extern "C" JL_DLLEXPORT int32_t jl_set_zero_subnormals(int8_t isZero) { - uint32_t fpcr = get_fpcr_aarch64(); - static uint32_t mask = fpcr_fz_mask | (jl_test_cpu_feature(JL_AArch64_fullfp16) ? fpcr_fz16_mask : 0); + uint64_t fpcr = get_fpcr_aarch64(); + static uint64_t mask = fpcr_fz_mask | (jl_test_cpu_feature(JL_AArch64_fullfp16) ? fpcr_fz16_mask : 0); fpcr = isZero ? (fpcr | mask) : (fpcr & ~mask); set_fpcr_aarch64(fpcr); return 0; @@ -1882,7 +1917,7 @@ extern "C" JL_DLLEXPORT int32_t jl_get_default_nans(void) extern "C" JL_DLLEXPORT int32_t jl_set_default_nans(int8_t isDefault) { - uint32_t fpcr = get_fpcr_aarch64(); + uint64_t fpcr = get_fpcr_aarch64(); fpcr = isDefault ? (fpcr | fpcr_dn_mask) : (fpcr & ~fpcr_dn_mask); set_fpcr_aarch64(fpcr); return 0; diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index 1f314eb460f0f..1aebde6dab90a 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -51,6 +51,22 @@ static uint32_t sysimg_init_cb(const void *id) return best_idx; } +static uint32_t pkgimg_init_cb(const void *id) +{ + TargetData<1> target = jit_targets.front(); + // Find the last name match or use the default one. + uint32_t best_idx = 0; + auto pkgimg = deserialize_target_data<1>((const uint8_t*)id); + for (uint32_t i = 0; i < pkgimg.size(); i++) { + auto &imgt = pkgimg[i]; + if (imgt.name == target.name) { + best_idx = i; + } + } + + return best_idx; +} + static void ensure_jit_target(bool imaging) { auto &cmdline = get_cmdline_targets(); @@ -96,13 +112,22 @@ get_llvm_target_str(const TargetData<1> &data) using namespace Fallback; -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) +jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); return parse_sysimg(hdl, sysimg_init_cb); } +jl_image_t jl_init_processor_pkgimg(void *hdl) +{ + if (jit_targets.empty()) + jl_error("JIT targets not initialized"); + if (jit_targets.size() > 1) + jl_error("Expected only one JIT target"); + return parse_sysimg(hdl, pkgimg_init_cb); +} + std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); @@ -145,6 +170,11 @@ JL_DLLEXPORT void jl_dump_host_cpu(void) jl_safe_printf("Features: %s\n", jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT void jl_check_pkgimage_clones(char *data) +{ + pkgimg_init_cb(data); +} + extern "C" int jl_test_cpu_feature(jl_cpu_feature_t) { return 0; diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index f18c7069fa2c2..30a6ff9b3dede 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -71,6 +71,7 @@ enum class CPU : uint32_t { intel_corei7_icelake_client, intel_corei7_icelake_server, intel_corei7_tigerlake, + intel_corei7_alderlake, intel_corei7_sapphirerapids, intel_knights_landing, intel_knights_mill, @@ -92,6 +93,7 @@ enum class CPU : uint32_t { amd_barcelona, amd_znver1, amd_znver2, + amd_znver3, }; static constexpr size_t feature_sz = 11; @@ -136,6 +138,7 @@ static constexpr FeatureDep deps[] = { {vaes, aes}, {vpclmulqdq, avx}, {vpclmulqdq, pclmul}, + {avxvnni, avx2}, {avx512f, avx2}, {avx512dq, avx512f}, {avx512ifma, avx512f}, @@ -151,6 +154,9 @@ static constexpr FeatureDep deps[] = { {avx512vnni, avx512f}, {avx512vp2intersect, avx512f}, {avx512vpopcntdq, avx512f}, + {avx512fp16, avx512bw}, + {avx512fp16, avx512dq}, + {avx512fp16, avx512vl}, {amx_int8, amx_tile}, {amx_bf16, amx_tile}, {sse4a, sse3}, @@ -202,9 +208,11 @@ constexpr auto icelake = cannonlake | get_feature_masks(avx512bitalg, vaes, avx5 constexpr auto icelake_server = icelake | get_feature_masks(pconfig, wbnoinvd); constexpr auto tigerlake = icelake | get_feature_masks(avx512vp2intersect, movdiri, movdir64b, shstk); +constexpr auto alderlake = skylake | get_feature_masks(clwb, sha, waitpkg, shstk, gfni, vaes, vpclmulqdq, pconfig, + rdpid, movdiri, pku, movdir64b, serialize, ptwrite, avxvnni); constexpr auto sapphirerapids = icelake_server | - get_feature_masks(amx_tile, amx_int8, amx_bf16, avx512bf16, serialize, cldemote, waitpkg, - ptwrite, tsxldtrk, enqcmd, shstk, avx512vp2intersect, movdiri, movdir64b); + get_feature_masks(amx_tile, amx_int8, amx_bf16, avx512bf16, avx512fp16, serialize, cldemote, waitpkg, + avxvnni, uintr, ptwrite, tsxldtrk, enqcmd, shstk, avx512vp2intersect, movdiri, movdir64b); constexpr auto k8_sse3 = get_feature_masks(sse3, cx16); constexpr auto amdfam10 = k8_sse3 | get_feature_masks(sse4a, lzcnt, popcnt, sahf); @@ -214,7 +222,7 @@ constexpr auto btver2 = btver1 | get_feature_masks(sse41, sse42, avx, aes, pclmu movbe, xsave, xsaveopt); constexpr auto bdver1 = amdfam10 | get_feature_masks(xop, fma4, avx, ssse3, sse41, sse42, aes, - prfchw, pclmul, xsave, lwp); + prfchw, pclmul, xsave); constexpr auto bdver2 = bdver1 | get_feature_masks(f16c, bmi, tbm, fma); constexpr auto bdver3 = bdver2 | get_feature_masks(xsaveopt, fsgsbase); constexpr auto bdver4 = bdver3 | get_feature_masks(avx2, bmi2, mwaitx, movbe, rdrnd); @@ -222,6 +230,7 @@ constexpr auto bdver4 = bdver3 | get_feature_masks(avx2, bmi2, mwaitx, movbe, rd constexpr auto znver1 = haswell | get_feature_masks(adx, aes, clflushopt, clzero, mwaitx, prfchw, rdseed, sha, sse4a, xsavec, xsaves); constexpr auto znver2 = znver1 | get_feature_masks(clwb, rdpid, wbnoinvd); +constexpr auto znver3 = znver2 | get_feature_masks(shstk, pku, vaes, vpclmulqdq); } @@ -255,6 +264,8 @@ static constexpr CPUSpec cpus[] = { Feature::icelake_server}, {"tigerlake", CPU::intel_corei7_tigerlake, CPU::intel_corei7_icelake_client, 100000, Feature::tigerlake}, + {"alderlake", CPU::intel_corei7_alderlake, CPU::intel_corei7_skylake, 120000, + Feature::alderlake}, {"sapphirerapids", CPU::intel_corei7_sapphirerapids, CPU::intel_corei7_icelake_server, 120000, Feature::sapphirerapids}, @@ -280,6 +291,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}, }; static constexpr size_t ncpu_names = sizeof(cpus) / sizeof(cpus[0]); @@ -411,6 +423,10 @@ static CPU get_intel_processor_name(uint32_t family, uint32_t model, uint32_t br case 0x8c: case 0x8d: return CPU::intel_corei7_tigerlake; + //Alder Lake + case 0x97: + case 0x9a: + return CPU::intel_corei7_alderlake; // Sapphire Rapids case 0x8f: @@ -543,6 +559,10 @@ 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) + return CPU::amd_znver3; // 00h-0Fh, 21h: Zen3 + return CPU::amd_znver3; // fallback } } @@ -861,6 +881,19 @@ static uint32_t sysimg_init_cb(const void *id) return match.best_idx; } +static uint32_t pkgimg_init_cb(const void *id) +{ + TargetData target = jit_targets.front(); + auto pkgimg = deserialize_target_data((const uint8_t*)id); + for (auto &t: pkgimg) { + if (auto nname = normalize_cpu_name(t.name)) { + t.name = nname; + } + } + auto match = match_sysimg_targets(pkgimg, target, max_vector_size); + return match.best_idx; +} + static void ensure_jit_target(bool imaging) { auto &cmdline = get_cmdline_targets(); @@ -900,10 +933,10 @@ static void ensure_jit_target(bool imaging) Feature::avx512pf, Feature::avx512er, Feature::avx512cd, Feature::avx512bw, Feature::avx512vl, Feature::avx512vbmi, - Feature::avx512vpopcntdq, + Feature::avx512vpopcntdq, Feature::avxvnni, Feature::avx512vbmi2, Feature::avx512vnni, Feature::avx512bitalg, Feature::avx512bf16, - Feature::avx512vp2intersect}; + Feature::avx512vp2intersect, Feature::avx512fp16}; for (auto fe: clone_math) { if (!test_nbit(features0, fe) && test_nbit(t.en.features, fe)) { t.en.flags |= JL_TARGET_CLONE_MATH; @@ -916,6 +949,13 @@ static void ensure_jit_target(bool imaging) break; } } + static constexpr uint32_t clone_fp16[] = {Feature::avx512fp16}; + for (auto fe: clone_fp16) { + if (!test_nbit(features0, fe) && test_nbit(t.en.features, fe)) { + t.en.flags |= JL_TARGET_CLONE_FLOAT16; + break; + } + } } } @@ -989,18 +1029,32 @@ JL_DLLEXPORT void jl_dump_host_cpu(void) cpus, ncpu_names); } +JL_DLLEXPORT void jl_check_pkgimage_clones(char *data) +{ + pkgimg_init_cb(data); +} + JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void) { return jl_cstr_to_string(host_cpu_name().c_str()); } -jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl) +jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) jl_error("JIT targets already initialized"); return parse_sysimg(hdl, sysimg_init_cb); } +jl_image_t jl_init_processor_pkgimg(void *hdl) +{ + if (jit_targets.empty()) + jl_error("JIT targets not initialized"); + if (jit_targets.size() > 1) + jl_error("Expected only one JIT target"); + return parse_sysimg(hdl, pkgimg_init_cb); +} + extern "C" JL_DLLEXPORT std::pair> jl_get_llvm_target(bool imaging, uint32_t &flags) { ensure_jit_target(imaging); diff --git a/src/rtutils.c b/src/rtutils.c index b4432d8af3d0c..01ea11014a6db 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -129,6 +129,8 @@ JL_DLLEXPORT void JL_NORETURN jl_type_error(const char *fname, JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var) { + if (!jl_undefvarerror_type) + jl_errorf("UndefVarError(%s)", jl_symbol_name(var)); jl_throw(jl_new_struct(jl_undefvarerror_type, var)); } @@ -202,12 +204,6 @@ JL_DLLEXPORT void JL_NORETURN jl_eof_error(void) jl_throw(jl_new_struct(eof_error)); } -// get kwsorter field, with appropriate error check and message -JL_DLLEXPORT jl_value_t *jl_get_keyword_sorter(jl_value_t *f) -{ - return jl_get_kwsorter(jl_typeof(f)); -} - JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t) { if (!jl_isa(x,t)) @@ -530,28 +526,37 @@ 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")); - return stdout_obj ? stdout_obj->value : 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) return NULL; - jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr")); - return stderr_obj ? stderr_obj->value : NULL; + jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr"), 0); + return stderr_obj ? jl_atomic_load_relaxed(&stderr_obj->value) : NULL; } // toys for debugging --------------------------------------------------------- -static size_t jl_show_svec(JL_STREAM *out, jl_svec_t *t, const char *head, const char *opn, const char *cls) JL_NOTSAFEPOINT +struct recur_list { + struct recur_list *prev; + jl_value_t *v; +}; + +static size_t jl_static_show_x(JL_STREAM *out, jl_value_t *v, struct recur_list *depth, jl_static_show_config_t ctx) JL_NOTSAFEPOINT; +static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt, struct recur_list *depth, jl_static_show_config_t ctx) JL_NOTSAFEPOINT; +static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *prev, struct recur_list *depth, jl_static_show_config_t ctx) JL_NOTSAFEPOINT; + +static size_t jl_show_svec(JL_STREAM *out, jl_svec_t *t, const char *head, const char *opn, const char *cls, jl_static_show_config_t ctx) JL_NOTSAFEPOINT { size_t i, n=0, len = jl_svec_len(t); n += jl_printf(out, "%s", head); n += jl_printf(out, "%s", opn); for (i = 0; i < len; i++) { jl_value_t *v = jl_svecref(t,i); - n += jl_static_show(out, v); + n += jl_static_show_x(out, v, 0, ctx); if (i != len-1) n += jl_printf(out, ", "); } @@ -559,14 +564,6 @@ static size_t jl_show_svec(JL_STREAM *out, jl_svec_t *t, const char *head, const return n; } -struct recur_list { - struct recur_list *prev; - jl_value_t *v; -}; - -static size_t jl_static_show_x(JL_STREAM *out, jl_value_t *v, struct recur_list *depth) JL_NOTSAFEPOINT; -static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *prev, struct recur_list *depth) JL_NOTSAFEPOINT; - JL_DLLEXPORT int jl_id_start_char(uint32_t wc) JL_NOTSAFEPOINT; JL_DLLEXPORT int jl_id_char(uint32_t wc) JL_NOTSAFEPOINT; @@ -583,29 +580,29 @@ JL_DLLEXPORT int jl_is_identifier(char *str) JL_NOTSAFEPOINT return 1; } -static jl_datatype_t *first_arg_datatype(jl_value_t *a JL_PROPAGATES_ROOT, int got_tuple1) JL_NOTSAFEPOINT +static jl_datatype_t *nth_arg_datatype(jl_value_t *a JL_PROPAGATES_ROOT, int n) JL_NOTSAFEPOINT { if (jl_is_datatype(a)) { - if (got_tuple1) + if (n == 0) return (jl_datatype_t*)a; if (jl_is_tuple_type(a)) { - if (jl_nparams(a) < 1) + if (jl_nparams(a) < n) return NULL; - return first_arg_datatype(jl_tparam0(a), 1); + return nth_arg_datatype(jl_tparam(a, n - 1), 0); } return NULL; } else if (jl_is_typevar(a)) { - return first_arg_datatype(((jl_tvar_t*)a)->ub, got_tuple1); + return nth_arg_datatype(((jl_tvar_t*)a)->ub, n); } else if (jl_is_unionall(a)) { - return first_arg_datatype(((jl_unionall_t*)a)->body, got_tuple1); + return nth_arg_datatype(((jl_unionall_t*)a)->body, n); } else if (jl_is_uniontype(a)) { jl_uniontype_t *u = (jl_uniontype_t*)a; - jl_datatype_t *d1 = first_arg_datatype(u->a, got_tuple1); + jl_datatype_t *d1 = nth_arg_datatype(u->a, n); if (d1 == NULL) return NULL; - jl_datatype_t *d2 = first_arg_datatype(u->b, got_tuple1); + jl_datatype_t *d2 = nth_arg_datatype(u->b, n); if (d2 == NULL || d1->name != d2->name) return NULL; return d1; @@ -614,35 +611,43 @@ static jl_datatype_t *first_arg_datatype(jl_value_t *a JL_PROPAGATES_ROOT, int g } // get DataType of first tuple element (if present), or NULL if cannot be determined -JL_DLLEXPORT jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT +jl_datatype_t *jl_nth_argument_datatype(jl_value_t *argtypes JL_PROPAGATES_ROOT, int n) JL_NOTSAFEPOINT { - return first_arg_datatype(argtypes, 0); + return nth_arg_datatype(argtypes, n); } // get DataType implied by a single given type, or `nothing` JL_DLLEXPORT jl_value_t *jl_argument_datatype(jl_value_t *argt JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { - jl_datatype_t *dt = first_arg_datatype(argt, 1); + jl_datatype_t *dt = nth_arg_datatype(argt, 0); if (dt == NULL) return jl_nothing; return (jl_value_t*)dt; } -static int is_globfunction(jl_value_t *v, jl_datatype_t *dv, jl_sym_t **globname_out) +static int is_globname_binding(jl_value_t *v, jl_datatype_t *dv) JL_NOTSAFEPOINT { jl_sym_t *globname = dv->name->mt != NULL ? dv->name->mt->name : NULL; - *globname_out = globname; - int globfunc = 0; - if (globname && !strchr(jl_symbol_name(globname), '#') && - !strchr(jl_symbol_name(globname), '@') && dv->name->module && - jl_binding_resolved_p(dv->name->module, globname)) { - jl_binding_t *b = jl_get_module_binding(dv->name->module, globname); - // The `||` makes this function work for both function instances and function types. - if (b && b->value && (b->value == v || jl_typeof(b->value) == v)) { - globfunc = 1; + if (globname && dv->name->module) { + jl_binding_t *b = jl_get_module_binding(dv->name->module, globname, 0); + if (b && jl_atomic_load_relaxed(&b->owner) && b->constp) { + jl_value_t *bv = jl_atomic_load_relaxed(&b->value); + // The `||` makes this function work for both function instances and function types. + if (bv == v || jl_typeof(bv) == v) + return 1; } } - return globfunc; + return 0; +} + +static int is_globfunction(jl_value_t *v, jl_datatype_t *dv, jl_sym_t **globname_out) JL_NOTSAFEPOINT +{ + jl_sym_t *globname = dv->name->mt != NULL ? dv->name->mt->name : NULL; + *globname_out = globname; + if (globname && !strchr(jl_symbol_name(globname), '#') && !strchr(jl_symbol_name(globname), '@')) { + return 1; + } + return 0; } static size_t jl_static_show_x_sym_escaped(JL_STREAM *out, jl_sym_t *name) JL_NOTSAFEPOINT @@ -695,7 +700,7 @@ static int jl_static_is_function_(jl_datatype_t *vt) JL_NOTSAFEPOINT { // This is necessary to make sure that this function doesn't allocate any // memory through the Julia GC static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt, - struct recur_list *depth) JL_NOTSAFEPOINT + struct recur_list *depth, jl_static_show_config_t ctx) JL_NOTSAFEPOINT { size_t n = 0; if ((uintptr_t)vt < 4096U) { @@ -703,9 +708,15 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if ((uintptr_t)v < 4096U) { n += jl_printf(out, ""); } + else if (vt == (jl_datatype_t*)jl_buff_tag) { + n += jl_printf(out, "", (void*)v); + } + else if (vt == (jl_datatype_t*)(uintptr_t)(0xbabababababababaull & ~15)) { + n += jl_printf(out, "", (void*)v); + } // These need to be special cased because they // exist only by pointer identity in early startup else if (v == (jl_value_t*)jl_simplevector_type) { @@ -738,17 +749,17 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_static_show_func_sig(out, li->def.method->sig); } else { - n += jl_static_show_x(out, (jl_value_t*)li->def.module, depth); + n += jl_static_show_x(out, (jl_value_t*)li->def.module, depth, ctx); n += jl_printf(out, ". -> "); - n += jl_static_show_x(out, li->uninferred, depth); + n += jl_static_show_x(out, jl_atomic_load_relaxed(&li->uninferred), depth, ctx); } } else if (vt == jl_typename_type) { - n += jl_static_show_x(out, jl_unwrap_unionall(((jl_typename_t*)v)->wrapper), depth); + n += jl_static_show_x(out, jl_unwrap_unionall(((jl_typename_t*)v)->wrapper), depth, ctx); n += jl_printf(out, ".name"); } else if (vt == jl_simplevector_type) { - n += jl_show_svec(out, (jl_svec_t*)v, "svec", "(", ")"); + n += jl_show_svec(out, (jl_svec_t*)v, "svec", "(", ")", ctx); } else if (v == (jl_value_t*)jl_unionall_type) { // avoid printing `typeof(Type)` for `UnionAll`. @@ -759,10 +770,10 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, "Vararg"); if (vm->T) { n += jl_printf(out, "{"); - n += jl_static_show_x(out, vm->T, depth); + n += jl_static_show_x(out, vm->T, depth, ctx); if (vm->N) { n += jl_printf(out, ", "); - n += jl_static_show_x(out, vm->N, depth); + n += jl_static_show_x(out, vm->N, depth, ctx); } n += jl_printf(out, "}"); } @@ -773,15 +784,49 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt // `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_globfunction(v, dv, &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"); + int taillen = 1, tlen = jl_nparams(dv), i; + for (i = tlen-2; i >= 0; i--) { + if (jl_tparam(dv, i) == jl_tparam(dv, tlen-1)) + taillen++; + else + break; + } + if (taillen == tlen && taillen > 3) { + n += jl_printf(out, "NTuple{%d, ", tlen); + n += jl_static_show_x(out, jl_tparam0(dv), depth, ctx); + n += jl_printf(out, "}"); + } + else { + n += jl_printf(out, "Tuple{"); + for (i = 0; i < (taillen > 3 ? tlen-taillen : tlen); i++) { + if (i > 0) + n += jl_printf(out, ", "); + n += jl_static_show_x(out, jl_tparam(dv, i), depth, ctx); + } + if (taillen > 3) { + n += jl_printf(out, ", Vararg{"); + n += jl_static_show_x(out, jl_tparam(dv, tlen-1), depth, ctx); + n += jl_printf(out, ", %d}", taillen); + } + n += jl_printf(out, "}"); + } + return n; + } + if (ctx.quiet) { + return jl_printf(out, "%s", jl_symbol_name(dv->name->name)); + } if (globfunc) { n += jl_printf(out, "typeof("); } if (jl_core_module && (dv->name->module != jl_core_module || !jl_module_exports_p(jl_core_module, sym))) { - n += jl_static_show_x(out, (jl_value_t*)dv->name->module, depth); + n += jl_static_show_x(out, (jl_value_t*)dv->name->module, depth, ctx); n += jl_printf(out, "."); size_t i = 0; if (globfunc && !jl_id_start_char(u8_nextchar(sn, &i))) { @@ -796,23 +841,18 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, ")"); } } - if (dv->parameters && (jl_value_t*)dv != dv->name->wrapper && - (jl_has_free_typevars(v) || - (jl_value_t*)dv != (jl_value_t*)jl_tuple_type)) { + if (dv->parameters && (jl_value_t*)dv != dv->name->wrapper) { size_t j, tlen = jl_nparams(dv); if (tlen > 0) { n += jl_printf(out, "{"); for (j = 0; j < tlen; j++) { jl_value_t *p = jl_tparam(dv,j); - n += jl_static_show_x(out, p, depth); + n += jl_static_show_x(out, p, depth, ctx); if (j != tlen-1) n += jl_printf(out, ", "); } n += jl_printf(out, "}"); } - else if (dv->name == jl_tuple_typename) { - n += jl_printf(out, "{}"); - } } } else if (vt == jl_intrinsic_type) { @@ -874,22 +914,22 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, "Union{"); while (jl_is_uniontype(v)) { // tail-recurse on b to flatten the printing of the Union structure in the common case - n += jl_static_show_x(out, ((jl_uniontype_t*)v)->a, depth); + n += jl_static_show_x(out, ((jl_uniontype_t*)v)->a, depth, ctx); n += jl_printf(out, ", "); v = ((jl_uniontype_t*)v)->b; } - n += jl_static_show_x(out, v, depth); + n += jl_static_show_x(out, v, depth, ctx); n += jl_printf(out, "}"); } else if (vt == jl_unionall_type) { jl_unionall_t *ua = (jl_unionall_t*)v; - n += jl_static_show_x(out, ua->body, depth); + n += jl_static_show_x(out, ua->body, depth, ctx); n += jl_printf(out, " where "); - n += jl_static_show_x(out, (jl_value_t*)ua->var, depth->prev); + n += jl_static_show_x(out, (jl_value_t*)ua->var, depth->prev, ctx); } else if (vt == jl_typename_type) { n += jl_printf(out, "typename("); - n += jl_static_show_x(out, jl_unwrap_unionall(((jl_typename_t*)v)->wrapper), depth); + n += jl_static_show_x(out, jl_unwrap_unionall(((jl_typename_t*)v)->wrapper), depth, ctx); n += jl_printf(out, ")"); } else if (vt == jl_tvar_type) { @@ -909,7 +949,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt int ua = jl_is_unionall(lb); if (ua) n += jl_printf(out, "("); - n += jl_static_show_x(out, lb, depth); + n += jl_static_show_x(out, lb, depth, ctx); if (ua) n += jl_printf(out, ")"); n += jl_printf(out, "<:"); @@ -921,7 +961,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, "<:"); if (ua) n += jl_printf(out, "("); - n += jl_static_show_x(out, ub, depth); + n += jl_static_show_x(out, ub, depth, ctx); if (ua) n += jl_printf(out, ")"); } @@ -929,7 +969,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt else if (vt == jl_module_type) { jl_module_t *m = (jl_module_t*)v; if (m->parent != m && m->parent != jl_main_module) { - n += jl_static_show_x(out, (jl_value_t*)m->parent, depth); + 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)); @@ -950,7 +990,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt (uintptr_t)((jl_ssavalue_t*)v)->id); } else if (vt == jl_globalref_type) { - n += jl_static_show_x(out, (jl_value_t*)jl_globalref_mod(v), depth); + 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); } @@ -965,7 +1005,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt else { n += jl_printf(out, ":("); } - n += jl_static_show_x(out, qv, depth); + n += jl_static_show_x(out, qv, depth, ctx); if (!jl_is_symbol(qv)) { n += jl_printf(out, " end"); } @@ -975,20 +1015,20 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if (vt == jl_newvarnode_type) { n += jl_printf(out, ""); } else if (vt == jl_linenumbernode_type) { n += jl_printf(out, "#= "); - n += jl_static_show_x(out, jl_linenode_file(v), depth); + n += jl_static_show_x(out, jl_linenode_file(v), depth, ctx); n += jl_printf(out, ":%" PRIuPTR " =#", jl_linenode_line(v)); } 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); + 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); + n += jl_static_show_x(out, jl_exprarg(e,1), depth, ctx); } else { char sep = ' '; @@ -996,14 +1036,14 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt size_t i, len = jl_array_len(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); + 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); + n += jl_static_show_x(out, (jl_value_t*)jl_tparam0(vt), depth, ctx); n += jl_printf(out, ", ("); size_t i, ndims = jl_array_ndims(v); if (ndims == 1) @@ -1038,13 +1078,13 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt 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); + 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); + depth, ctx); } if (j != tlen - 1) n += jl_printf(out, nlsep ? ",\n " : ", "); @@ -1053,32 +1093,30 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } else if (vt == jl_loaderror_type) { n += jl_printf(out, "LoadError(at "); - n += jl_static_show_x(out, *(jl_value_t**)v, depth); + n += jl_static_show_x(out, *(jl_value_t**)v, depth, ctx); // Access the field directly to avoid allocation n += jl_printf(out, " line %" PRIdPTR, ((intptr_t*)v)[1]); n += jl_printf(out, ": "); - n += jl_static_show_x(out, ((jl_value_t**)v)[2], depth); + n += jl_static_show_x(out, ((jl_value_t**)v)[2], depth, ctx); n += jl_printf(out, ")"); } else if (vt == jl_errorexception_type) { n += jl_printf(out, "ErrorException("); - n += jl_static_show_x(out, *(jl_value_t**)v, depth); + n += jl_static_show_x(out, *(jl_value_t**)v, depth, ctx); n += jl_printf(out, ")"); } - else if (jl_static_is_function_(vt)) { + else if (jl_static_is_function_(vt) && is_globname_binding(v, (jl_datatype_t*)vt)) { // v is function instance (an instance of a Function type). jl_datatype_t *dv = (jl_datatype_t*)vt; - jl_sym_t *sym = dv->name->mt->name; - char *sn = jl_symbol_name(sym); - - jl_sym_t *globname; - int globfunc = is_globfunction(v, dv, &globname); + jl_sym_t *sym; + int globfunc = is_globfunction(v, dv, &sym); int quote = 0; if (jl_core_module && (dv->name->module != jl_core_module || !jl_module_exports_p(jl_core_module, sym))) { - n += jl_static_show_x(out, (jl_value_t*)dv->name->module, depth); + n += jl_static_show_x(out, (jl_value_t*)dv->name->module, depth, ctx); n += jl_printf(out, "."); size_t i = 0; + char *sn = jl_symbol_name(sym); if (globfunc && !jl_id_start_char(u8_nextchar(sn, &i))) { n += jl_printf(out, ":("); quote = 1; @@ -1104,7 +1142,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, "NamedTuple"); } else if (!istuple) { - n += jl_static_show_x(out, (jl_value_t*)vt, depth); + n += jl_static_show_x(out, (jl_value_t*)vt, depth, ctx); } n += jl_printf(out, "("); size_t nb = jl_datatype_size(vt); @@ -1127,7 +1165,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt size_t offs = jl_field_offset(vt, i); char *fld_ptr = (char*)v + offs; if (jl_field_isptr(vt, i)) { - n += jl_static_show_x(out, *(jl_value_t**)fld_ptr, depth); + n += jl_static_show_x(out, *(jl_value_t**)fld_ptr, depth, ctx); } else { jl_datatype_t *ft = (jl_datatype_t*)jl_field_type_concrete(vt, i); @@ -1135,7 +1173,7 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt uint8_t sel = ((uint8_t*)fld_ptr)[jl_field_size(vt, i) - 1]; ft = (jl_datatype_t*)jl_nth_union_component((jl_value_t*)ft, sel); } - n += jl_static_show_x_(out, (jl_value_t*)fld_ptr, ft, depth); + n += jl_static_show_x_(out, (jl_value_t*)fld_ptr, ft, depth, ctx); } if ((istuple || isnamedtuple) && tlen == 1) n += jl_printf(out, ","); @@ -1144,26 +1182,27 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt } if (vt == jl_typemap_entry_type) { n += jl_printf(out, ", next=↩︎\n "); - n += jl_static_show_next_(out, (jl_value_t*)((jl_typemap_entry_t*)v)->next, v, depth); + jl_value_t *next = (jl_value_t*)jl_atomic_load_relaxed(&((jl_typemap_entry_t*)v)->next); + n += jl_static_show_next_(out, next, v, depth, ctx); } } n += jl_printf(out, ")"); } else { n += jl_printf(out, ""); } return n; } -static size_t jl_static_show_x(JL_STREAM *out, jl_value_t *v, struct recur_list *depth) JL_NOTSAFEPOINT +static size_t jl_static_show_x(JL_STREAM *out, jl_value_t *v, struct recur_list *depth, jl_static_show_config_t ctx) JL_NOTSAFEPOINT { // show values without calling a julia method or allocating through the GC - return jl_static_show_next_(out, v, NULL, depth); + return jl_static_show_next_(out, v, NULL, depth, ctx); } -static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *prev, struct recur_list *depth) JL_NOTSAFEPOINT +static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *prev, struct recur_list *depth, jl_static_show_config_t ctx) JL_NOTSAFEPOINT { // helper for showing a typemap list by following the next pointers // while being careful about avoiding any recursion due to malformed (circular) references @@ -1178,13 +1217,13 @@ static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *pr *newdepth = &this_item, *p = depth; while (p) { - if (jl_typeis(v, jl_typemap_entry_type) && newdepth == &this_item) { + if (jl_typetagis(v, jl_typemap_entry_type) && newdepth == &this_item) { jl_value_t *m = p->v; unsigned nid = 1; - while (m && jl_typeis(m, jl_typemap_entry_type)) { + while (m && jl_typetagis(m, jl_typemap_entry_type)) { if (m == v) { return jl_printf(out, "sig, depth) + + jl_static_show_x(out, (jl_value_t*)((jl_typemap_entry_t*)m)->sig, depth, ctx) + jl_printf(out, ">"); } if (m == prev) { @@ -1193,12 +1232,12 @@ static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *pr } // verify that we aren't trying to follow a circular list // by following the list again, and ensuring this is the only link to next - jl_value_t *mnext = (jl_value_t*)((jl_typemap_entry_t*)m)->next; + jl_value_t *mnext = (jl_value_t*)jl_atomic_load_relaxed(&((jl_typemap_entry_t*)m)->next); jl_value_t *m2 = p->v; if (m2 == mnext) break; - while (m2 && jl_typeis(m2, jl_typemap_entry_type)) { - jl_value_t *mnext2 = (jl_value_t*)((jl_typemap_entry_t*)m2)->next; + while (m2 && jl_typetagis(m2, jl_typemap_entry_type)) { + jl_value_t *mnext2 = (jl_value_t*)jl_atomic_load_relaxed(&((jl_typemap_entry_t*)m2)->next); if (mnext2 == mnext) { if (m2 != m) mnext = NULL; @@ -1215,19 +1254,26 @@ static size_t jl_static_show_next_(JL_STREAM *out, jl_value_t *v, jl_value_t *pr dist++; p = p->prev; } - return jl_static_show_x_(out, v, (jl_datatype_t*)jl_typeof(v), newdepth); + return jl_static_show_x_(out, v, (jl_datatype_t*)jl_typeof(v), newdepth, ctx); } JL_DLLEXPORT size_t jl_static_show(JL_STREAM *out, jl_value_t *v) JL_NOTSAFEPOINT { - return jl_static_show_x(out, v, 0); + jl_static_show_config_t ctx = { /* quiet */ 0 }; + return jl_static_show_x(out, v, 0, ctx); } JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) JL_NOTSAFEPOINT +{ + jl_static_show_config_t ctx = { /* quiet */ 0 }; + return jl_static_show_func_sig_(s, type, ctx); +} + +size_t jl_static_show_func_sig_(JL_STREAM *s, jl_value_t *type, jl_static_show_config_t ctx) JL_NOTSAFEPOINT { size_t n = 0; size_t i; - jl_value_t *ftype = (jl_value_t*)jl_first_argument_datatype(type); + jl_value_t *ftype = (jl_value_t*)jl_nth_argument_datatype(type, 1); if (ftype == NULL) return jl_static_show(s, type); jl_unionall_t *tvars = (jl_unionall_t*)type; @@ -1246,12 +1292,14 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) JL_N n += jl_static_show(s, type); return n; } - if (jl_nparams(ftype) == 0 || ftype == ((jl_datatype_t*)ftype)->name->wrapper) { + 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)); } else { n += jl_printf(s, "(::"); - n += jl_static_show_x(s, ftype, depth); + n += jl_static_show_x(s, ftype, depth, ctx); n += jl_printf(s, ")"); } size_t tl = jl_nparams(type); @@ -1259,7 +1307,7 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) JL_N for (i = 1; i < tl; i++) { jl_value_t *tp = jl_tparam(type, i); if (i != tl - 1) { - n += jl_static_show_x(s, tp, depth); + n += jl_static_show_x(s, tp, depth, ctx); n += jl_printf(s, ", "); } else { @@ -1267,13 +1315,13 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) JL_N tp = jl_unwrap_vararg(tp); if (jl_is_unionall(tp)) n += jl_printf(s, "("); - n += jl_static_show_x(s, tp, depth); + n += jl_static_show_x(s, tp, depth, ctx); if (jl_is_unionall(tp)) n += jl_printf(s, ")"); n += jl_printf(s, "..."); } else { - n += jl_static_show_x(s, tp, depth); + n += jl_static_show_x(s, tp, depth, ctx); } } } @@ -1285,7 +1333,7 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) JL_N while (jl_is_unionall(tvars)) { if (!first) n += jl_printf(s, ", "); - n += jl_static_show_x(s, (jl_value_t*)tvars->var, first ? NULL : depth); + n += jl_static_show_x(s, (jl_value_t*)tvars->var, first ? NULL : depth, ctx); tvars = (jl_unionall_t*)tvars->body; if (!first) depth += 1; @@ -1316,6 +1364,11 @@ JL_DLLEXPORT void jl_breakpoint(jl_value_t *v) // put a breakpoint in your debugger here } +JL_DLLEXPORT void jl_test_failure_breakpoint(jl_value_t *v) +{ + // put a breakpoint in your debugger here +} + // logging tools -------------------------------------------------------------- void jl_log(int level, jl_value_t *module, jl_value_t *group, jl_value_t *id, diff --git a/src/runtime_ccall.cpp b/src/runtime_ccall.cpp index 02523abe73479..23793254c205d 100644 --- a/src/runtime_ccall.cpp +++ b/src/runtime_ccall.cpp @@ -31,14 +31,12 @@ void *jl_get_library_(const char *f_lib, int throw_err) { if (f_lib == NULL) return jl_RTLD_DEFAULT_handle; -#ifdef _OS_WINDOWS_ if (f_lib == JL_EXE_LIBNAME) return jl_exe_handle; if (f_lib == JL_LIBJULIA_INTERNAL_DL_LIBNAME) return jl_libjulia_internal_handle; if (f_lib == JL_LIBJULIA_DL_LIBNAME) return jl_libjulia_handle; -#endif JL_LOCK(&libmap_lock); // This is the only operation we do on the map, which doesn't invalidate // any references or iterators. @@ -157,7 +155,7 @@ std::string jl_format_filename(StringRef output_pattern) } switch (c) { case 'p': - outfile << jl_getpid(); + outfile << uv_os_getpid(); break; case 'd': if (got_pwd) @@ -246,13 +244,13 @@ static void *trampoline_alloc() JL_NOTSAFEPOINT // lock taken by caller return tramp; } -static void trampoline_free(void *tramp) // lock taken by caller +static void trampoline_free(void *tramp) JL_NOTSAFEPOINT // lock taken by caller { *(void**)tramp = trampoline_freelist; trampoline_freelist = tramp; } -static void trampoline_deleter(void **f) +static void trampoline_deleter(void **f) JL_NOTSAFEPOINT { void *tramp = f[0]; void *fobj = f[1]; @@ -362,6 +360,6 @@ JL_GCC_IGNORE_STOP void jl_init_runtime_ccall(void) { - JL_MUTEX_INIT(&libmap_lock); + JL_MUTEX_INIT(&libmap_lock, "libmap_lock"); uv_mutex_init(&trampoline_lock); } diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 9525b655dc5e3..9babdf89f098b 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -8,16 +8,13 @@ // // TODO: add half-float support +#include "APInt-C.h" #include "julia.h" #include "julia_internal.h" -#include "APInt-C.h" const unsigned int host_char_bit = 8; // float16 intrinsics -// TODO: use LLVM's compiler-rt on all platforms (Xcode already links compiler-rt) - -#if !defined(_OS_DARWIN_) static inline float half_to_float(uint16_t ival) JL_NOTSAFEPOINT { @@ -188,22 +185,17 @@ static inline uint16_t float_to_half(float param) JL_NOTSAFEPOINT return h; } -JL_DLLEXPORT float __gnu_h2f_ieee(uint16_t param) -{ - return half_to_float(param); -} - -JL_DLLEXPORT float __extendhfsf2(uint16_t param) +JL_DLLEXPORT float julia__gnu_h2f_ieee(uint16_t param) { return half_to_float(param); } -JL_DLLEXPORT uint16_t __gnu_f2h_ieee(float param) +JL_DLLEXPORT uint16_t julia__gnu_f2h_ieee(float param) { return float_to_half(param); } -JL_DLLEXPORT uint16_t __truncdfhf2(double param) +JL_DLLEXPORT uint16_t julia__truncdfhf2(double param) { float res = (float)param; uint32_t resi; @@ -225,7 +217,25 @@ JL_DLLEXPORT uint16_t __truncdfhf2(double param) return float_to_half(res); } -#endif +//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) + // run time version of bitcast intrinsic JL_DLLEXPORT jl_value_t *jl_bitcast(jl_value_t *ty, jl_value_t *v) @@ -480,14 +490,14 @@ JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) char *f_lib = NULL; if (jl_is_tuple(v) && jl_nfields(v) > 1) { - jl_value_t *t1 = jl_fieldref_noalloc(v, 1); - v = jl_fieldref(v, 0); + jl_value_t *t1 = jl_fieldref(v, 1); if (jl_is_symbol(t1)) f_lib = jl_symbol_name((jl_sym_t*)t1); else if (jl_is_string(t1)) f_lib = jl_string_data(t1); else JL_TYPECHK(cglobal, symbol, t1) + v = jl_fieldref(v, 0); } char *f_name = NULL; @@ -498,10 +508,8 @@ JL_DLLEXPORT jl_value_t *jl_cglobal(jl_value_t *v, jl_value_t *ty) else JL_TYPECHK(cglobal, symbol, v) -#ifdef _OS_WINDOWS_ if (!f_lib) - f_lib = (char*)jl_dlfind_win32(f_name); -#endif + f_lib = (char*)jl_dlfind(f_name); void *ptr; jl_dlsym(jl_get_library(f_lib), f_name, &ptr, 1); @@ -551,9 +559,9 @@ static inline unsigned select_by_size(unsigned sz) JL_NOTSAFEPOINT } #define fp_select(a, func) \ - sizeof(a) == sizeof(float) ? func##f((float)a) : func(a) + sizeof(a) <= sizeof(float) ? func##f((float)a) : func(a) #define fp_select2(a, b, func) \ - sizeof(a) == sizeof(float) ? func##f(a, b) : func(a, b) + sizeof(a) <= sizeof(float) ? func##f(a, b) : func(a, b) // fast-function generators // @@ -597,11 +605,11 @@ static inline void name(unsigned osize, void *pa, void *pr) JL_NOTSAFEPOINT \ static inline void name(unsigned osize, void *pa, void *pr) JL_NOTSAFEPOINT \ { \ uint16_t a = *(uint16_t*)pa; \ - float A = __gnu_h2f_ieee(a); \ + float A = julia__gnu_h2f_ieee(a); \ if (osize == 16) { \ float R; \ OP(&R, A); \ - *(uint16_t*)pr = __gnu_f2h_ieee(R); \ + *(uint16_t*)pr = julia__gnu_f2h_ieee(R); \ } else { \ OP((uint16_t*)pr, A); \ } \ @@ -625,11 +633,11 @@ 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 = __gnu_h2f_ieee(a); \ - float B = __gnu_h2f_ieee(b); \ + float A = julia__gnu_h2f_ieee(a); \ + float B = julia__gnu_h2f_ieee(b); \ runtime_nbits = 16; \ float R = OP(A, B); \ - *(uint16_t*)pr = __gnu_f2h_ieee(R); \ + *(uint16_t*)pr = julia__gnu_f2h_ieee(R); \ } // float or integer inputs, bool output @@ -650,8 +658,8 @@ 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 = __gnu_h2f_ieee(a); \ - float B = __gnu_h2f_ieee(b); \ + float A = julia__gnu_h2f_ieee(a); \ + float B = julia__gnu_h2f_ieee(b); \ runtime_nbits = 16; \ return OP(A, B); \ } @@ -691,12 +699,12 @@ 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 = __gnu_h2f_ieee(a); \ - float B = __gnu_h2f_ieee(b); \ - float C = __gnu_h2f_ieee(c); \ + float A = julia__gnu_h2f_ieee(a); \ + float B = julia__gnu_h2f_ieee(b); \ + float C = julia__gnu_h2f_ieee(c); \ runtime_nbits = 16; \ float R = OP(A, B, C); \ - *(uint16_t*)pr = __gnu_f2h_ieee(R); \ + *(uint16_t*)pr = julia__gnu_f2h_ieee(R); \ } @@ -707,7 +715,7 @@ SELECTOR_FUNC(intrinsic_1) #define un_iintrinsic(name, u) \ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a) \ { \ - return jl_iintrinsic_1(jl_typeof(a), a, #name, u##signbitbyte, jl_intrinsiclambda_ty1, name##_list); \ + return jl_iintrinsic_1(a, #name, u##signbitbyte, jl_intrinsiclambda_ty1, name##_list); \ } #define un_iintrinsic_fast(LLVMOP, OP, name, u) \ un_iintrinsic_ctype(OP, name, 8, u##int##8_t) \ @@ -733,7 +741,7 @@ SELECTOR_FUNC(intrinsic_u1) #define uu_iintrinsic(name, u) \ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *a) \ { \ - return jl_iintrinsic_1(jl_typeof(a), a, #name, u##signbitbyte, jl_intrinsiclambda_u1, name##_list); \ + return jl_iintrinsic_1(a, #name, u##signbitbyte, jl_intrinsiclambda_u1, name##_list); \ } #define uu_iintrinsic_fast(LLVMOP, OP, name, u) \ uu_iintrinsic_ctype(OP, name, 8, u##int##8_t) \ @@ -755,14 +763,13 @@ static const select_intrinsic_u1_t name##_list = { \ uu_iintrinsic(name, u) static inline -jl_value_t *jl_iintrinsic_1(jl_value_t *ty, jl_value_t *a, const char *name, +jl_value_t *jl_iintrinsic_1(jl_value_t *a, const char *name, char (*getsign)(void*, unsigned), jl_value_t *(*lambda1)(jl_value_t*, void*, unsigned, unsigned, const void*), const void *list) { - if (!jl_is_primitivetype(jl_typeof(a))) - jl_errorf("%s: value is not a primitive type", name); + jl_value_t *ty = jl_typeof(a); if (!jl_is_primitivetype(ty)) - jl_errorf("%s: type is not a primitive type", name); + jl_errorf("%s: value is not a primitive type", name); void *pa = jl_data_ptr(a); unsigned isize = jl_datatype_size(jl_typeof(a)); unsigned isize2 = next_power_of_two(isize); @@ -823,11 +830,12 @@ JL_DLLEXPORT jl_value_t *jl_##name(jl_value_t *ty, jl_value_t *a) \ static inline jl_value_t *jl_intrinsic_cvt(jl_value_t *ty, jl_value_t *a, const char *name, intrinsic_cvt_t op) { + JL_TYPECHKS(name, datatype, ty); + if (!jl_is_concrete_type(ty) || !jl_is_primitivetype(ty)) + jl_errorf("%s: target type not a leaf primitive type", name); jl_value_t *aty = jl_typeof(a); if (!jl_is_primitivetype(aty)) jl_errorf("%s: value is not a primitive type", name); - if (!jl_is_primitivetype(ty)) - jl_errorf("%s: type is not a primitive type", name); void *pa = jl_data_ptr(a); unsigned isize = jl_datatype_size(aty); unsigned osize = jl_datatype_size(ty); @@ -1025,10 +1033,10 @@ static inline jl_value_t *jl_intrinsiclambda_checked(jl_value_t *ty, void *pa, v jl_value_t *params[2]; params[0] = ty; params[1] = (jl_value_t*)jl_bool_type; - jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); + jl_datatype_t *tuptyp = (jl_datatype_t*)jl_apply_tuple_type_v(params, 2); JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) jl_task_t *ct = jl_current_task; - jl_value_t *newv = jl_gc_alloc(ct->ptls, ((jl_datatype_t*)tuptyp)->size, tuptyp); + jl_value_t *newv = jl_gc_alloc(ct->ptls, jl_datatype_size(tuptyp), tuptyp); intrinsic_checked_t op = select_intrinsic_checked(sz2, (const intrinsic_checked_t*)voidlist); int ovflw = op(sz * host_char_bit, pa, pb, jl_data_ptr(newv)); @@ -1172,7 +1180,6 @@ bi_fintrinsic(add,add_float) bi_fintrinsic(sub,sub_float) bi_fintrinsic(mul,mul_float) bi_fintrinsic(div,div_float) -bi_fintrinsic(frem,rem_float) // ternary operators // // runtime fma is broken on windows, define julia_fma(f) ourself with fma_emulated as reference. @@ -1318,7 +1325,7 @@ static inline int fpiseq##nbits(c_type a, c_type b) JL_NOTSAFEPOINT { \ fpiseq_n(float, 32) fpiseq_n(double, 64) #define fpiseq(a,b) \ - sizeof(a) == sizeof(float) ? fpiseq32(a, b) : fpiseq64(a, b) + sizeof(a) <= sizeof(float) ? fpiseq32(a, b) : fpiseq64(a, b) bool_fintrinsic(eq,eq_float) bool_fintrinsic(ne,ne_float) @@ -1367,7 +1374,7 @@ cvt_iintrinsic(LLVMFPtoUI, fptoui) if (!(osize < 8 * sizeof(a))) \ jl_error("fptrunc: output bitsize must be < input bitsize"); \ else if (osize == 16) \ - *(uint16_t*)pr = __gnu_f2h_ieee(a); \ + *(uint16_t*)pr = julia__gnu_f2h_ieee(a); \ else if (osize == 32) \ *(float*)pr = a; \ else if (osize == 64) \ diff --git a/src/safepoint.c b/src/safepoint.c index 17c37a66c3a16..19eca4bf6f00d 100644 --- a/src/safepoint.c +++ b/src/safepoint.c @@ -43,6 +43,7 @@ uint8_t jl_safepoint_enable_cnt[3] = {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; static void jl_safepoint_enable(int idx) JL_NOTSAFEPOINT { @@ -87,6 +88,7 @@ static void jl_safepoint_disable(int idx) JL_NOTSAFEPOINT void jl_safepoint_init(void) { uv_mutex_init(&safepoint_lock); + uv_cond_init(&safepoint_cond); // jl_page_size isn't available yet. size_t pgsz = jl_getpagesize(); #ifdef _OS_WINDOWS_ @@ -109,10 +111,6 @@ void jl_safepoint_init(void) int jl_safepoint_start_gc(void) { - if (jl_n_threads == 1) { - jl_atomic_store_relaxed(&jl_gc_running, 1); - return 1; - } // The thread should have set this already assert(jl_atomic_load_relaxed(&jl_current_task->ptls->gc_state) == JL_GC_STATE_WAITING); uv_mutex_lock(&safepoint_lock); @@ -135,10 +133,6 @@ int jl_safepoint_start_gc(void) void jl_safepoint_end_gc(void) { assert(jl_atomic_load_relaxed(&jl_gc_running)); - if (jl_n_threads == 1) { - jl_atomic_store_relaxed(&jl_gc_running, 0); - return; - } uv_mutex_lock(&safepoint_lock); // Need to reset the page protection before resetting the flag since // the thread will trigger a segfault immediately after returning from @@ -151,16 +145,25 @@ void jl_safepoint_end_gc(void) jl_mach_gc_end(); # endif uv_mutex_unlock(&safepoint_lock); + uv_cond_broadcast(&safepoint_cond); } void jl_safepoint_wait_gc(void) { + jl_task_t *ct = jl_current_task; (void)ct; + JL_TIMING_SUSPEND(GC_SAFEPOINT, ct); // The thread should have set this is already - assert(jl_atomic_load_relaxed(&jl_current_task->ptls->gc_state) != 0); + assert(jl_atomic_load_relaxed(&ct->ptls->gc_state) != 0); // 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)) { - jl_cpu_pause(); // yield? + // Use system mutexes rather than spin locking to minimize wasted CPU + // time on the idle cores while we wait for the GC to finish. + // 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_mutex_unlock(&safepoint_lock); } } diff --git a/src/serialize.h b/src/serialize.h index 63d7c2d360951..afcdcc31d66c4 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -63,52 +63,78 @@ extern "C" { #define TAG_RETURNNODE 55 #define TAG_ARGUMENT 56 #define TAG_RELOC_METHODROOT 57 +#define TAG_BINDING 58 -#define LAST_TAG 57 +#define LAST_TAG 58 #define write_uint8(s, n) ios_putc((n), (s)) -#define read_uint8(s) ((uint8_t)ios_getc(s)) -#define write_int8(s, n) write_uint8(s, n) -#define read_int8(s) read_uint8(s) +#define read_uint8(s) ((uint8_t)ios_getc((s))) +#define write_int8(s, n) write_uint8((s), (n)) +#define read_int8(s) read_uint8((s)) /* read and write in host byte order */ -static void write_int32(ios_t *s, int32_t i) JL_NOTSAFEPOINT +static inline void write_int32(ios_t *s, int32_t i) JL_NOTSAFEPOINT { ios_write(s, (char*)&i, 4); } -static int32_t read_int32(ios_t *s) JL_NOTSAFEPOINT +static inline int32_t read_int32(ios_t *s) JL_NOTSAFEPOINT { int32_t x = 0; ios_read(s, (char*)&x, 4); return x; } -static uint64_t read_uint64(ios_t *s) JL_NOTSAFEPOINT +static inline uint64_t read_uint64(ios_t *s) JL_NOTSAFEPOINT { uint64_t x = 0; ios_read(s, (char*)&x, 8); return x; } -static void write_int64(ios_t *s, int64_t i) JL_NOTSAFEPOINT +static inline void write_uint64(ios_t *s, uint64_t i) JL_NOTSAFEPOINT { ios_write(s, (char*)&i, 8); } -static void write_uint16(ios_t *s, uint16_t i) JL_NOTSAFEPOINT +static inline void write_uint16(ios_t *s, uint16_t i) JL_NOTSAFEPOINT { ios_write(s, (char*)&i, 2); } -static uint16_t read_uint16(ios_t *s) JL_NOTSAFEPOINT +static inline uint16_t read_uint16(ios_t *s) JL_NOTSAFEPOINT { int16_t x = 0; ios_read(s, (char*)&x, 2); return x; } +static inline void write_uint32(ios_t *s, uint32_t i) JL_NOTSAFEPOINT +{ + ios_write(s, (char*)&i, 4); +} + +static inline uint32_t read_uint32(ios_t *s) JL_NOTSAFEPOINT +{ + uint32_t x = 0; + ios_read(s, (char*)&x, 4); + return x; +} + +#ifdef _P64 +#define write_uint(s, i) write_uint64(s, i) +#else +#define write_uint(s, i) write_uint32(s, i) +#endif + +#ifdef _P64 +#define read_uint(s) read_uint64(s) +#else +#define read_uint(s) read_uint32(s) +#endif + + void *jl_lookup_ser_tag(jl_value_t *v); void *jl_lookup_common_symbol(jl_value_t *v); jl_value_t *jl_deser_tag(uint8_t tag); diff --git a/src/signal-handling.c b/src/signal-handling.c index 142f03b6c899d..e241fd22ecb18 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -6,8 +6,8 @@ #include #include "julia.h" #include "julia_internal.h" -#ifndef _OS_WINDOWS_ #include +#ifndef _OS_WINDOWS_ #include #endif @@ -25,26 +25,167 @@ static volatile size_t bt_size_cur = 0; static volatile uint64_t nsecprof = 0; static volatile int running = 0; static const uint64_t GIGA = 1000000000ULL; -static uint64_t profile_cong_rng_seed = 0; -static uint64_t profile_cong_rng_unbias = 0; -static volatile uint64_t *profile_round_robin_thread_order = NULL; // Timers to take samples at intervals JL_DLLEXPORT void jl_profile_stop_timer(void); JL_DLLEXPORT int jl_profile_start_timer(void); -void jl_lock_profile(void); -void jl_unlock_profile(void); -void jl_shuffle_int_array_inplace(volatile uint64_t *carray, size_t size, uint64_t *seed); + +/////////////////////// +// Utility functions // +/////////////////////// +JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec) +{ + bt_size_max = maxsize; + nsecprof = delay_nsec; + if (bt_data_prof != NULL) + free((void*)bt_data_prof); + bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t)); + if (bt_data_prof == NULL && maxsize > 0) + return -1; + bt_size_cur = 0; + return 0; +} + +JL_DLLEXPORT uint8_t *jl_profile_get_data(void) +{ + return (uint8_t*) bt_data_prof; +} + +JL_DLLEXPORT size_t jl_profile_len_data(void) +{ + return bt_size_cur; +} + +JL_DLLEXPORT size_t jl_profile_maxlen_data(void) +{ + return bt_size_max; +} + +JL_DLLEXPORT uint64_t jl_profile_delay_nsec(void) +{ + return nsecprof; +} + +JL_DLLEXPORT void jl_profile_clear_data(void) +{ + bt_size_cur = 0; +} + +JL_DLLEXPORT int jl_profile_is_running(void) +{ + return running; +} + +// Any function that acquires this lock must be either a unmanaged thread +// or in the GC safe region and must NOT allocate anything through the GC +// while holding this lock. +// Certain functions in this file might be called from an unmanaged thread +// and cannot have any interaction with the julia runtime +// They also may be re-entrant, and operating while threads are paused, so we +// separately manage the re-entrant count behavior for safety across platforms +// Note that we cannot safely upgrade read->write +uv_rwlock_t debuginfo_asyncsafe; +#ifndef _OS_WINDOWS_ +pthread_key_t debuginfo_asyncsafe_held; +#else +DWORD debuginfo_asyncsafe_held; +#endif + +void jl_init_profile_lock(void) +{ + uv_rwlock_init(&debuginfo_asyncsafe); +#ifndef _OS_WINDOWS_ + pthread_key_create(&debuginfo_asyncsafe_held, NULL); +#else + debuginfo_asyncsafe_held = TlsAlloc(); +#endif +} + +uintptr_t jl_lock_profile_rd_held(void) +{ +#ifndef _OS_WINDOWS_ + return (uintptr_t)pthread_getspecific(debuginfo_asyncsafe_held); +#else + return (uintptr_t)TlsGetValue(debuginfo_asyncsafe_held); +#endif +} + +void jl_lock_profile(void) +{ + uintptr_t held = jl_lock_profile_rd_held(); + if (held++ == 0) + uv_rwlock_rdlock(&debuginfo_asyncsafe); +#ifndef _OS_WINDOWS_ + pthread_setspecific(debuginfo_asyncsafe_held, (void*)held); +#else + TlsSetValue(debuginfo_asyncsafe_held, (void*)held); +#endif +} + +JL_DLLEXPORT void jl_unlock_profile(void) +{ + uintptr_t held = jl_lock_profile_rd_held(); + assert(held); + if (--held == 0) + uv_rwlock_rdunlock(&debuginfo_asyncsafe); +#ifndef _OS_WINDOWS_ + pthread_setspecific(debuginfo_asyncsafe_held, (void*)held); +#else + TlsSetValue(debuginfo_asyncsafe_held, (void*)held); +#endif +} + +void jl_lock_profile_wr(void) +{ + uv_rwlock_wrlock(&debuginfo_asyncsafe); +} + +void jl_unlock_profile_wr(void) +{ + uv_rwlock_wrunlock(&debuginfo_asyncsafe); +} + + +#ifndef _OS_WINDOWS_ +static uint64_t profile_cong_rng_seed = 0; +static int *profile_round_robin_thread_order = NULL; +static int profile_round_robin_thread_order_size = 0; + +static void jl_shuffle_int_array_inplace(int *carray, int size, uint64_t *seed) +{ + // The "modern Fisher–Yates shuffle" - O(n) algorithm + // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm + for (int i = size; i-- > 1; ) { + uint64_t unbias = UINT64_MAX; // slightly biased, but i is very small + size_t j = cong(i, unbias, seed); + uint64_t tmp = carray[j]; + carray[j] = carray[i]; + carray[i] = tmp; + } +} + + +static int *profile_get_randperm(int size) +{ + if (profile_round_robin_thread_order_size < size) { + free(profile_round_robin_thread_order); + profile_round_robin_thread_order = (int*)malloc_s(size * sizeof(int)); + for (int i = 0; i < size; i++) + profile_round_robin_thread_order[i] = i; + profile_round_robin_thread_order_size = size; + profile_cong_rng_seed = jl_rand(); + } + jl_shuffle_int_array_inplace(profile_round_robin_thread_order, size, &profile_cong_rng_seed); + return profile_round_robin_thread_order; +} +#endif + JL_DLLEXPORT int jl_profile_is_buffer_full(void) { - // declare buffer full if there isn't enough room to take samples across all threads - #if defined(_OS_WINDOWS_) - uint64_t nthreads = 1; // windows only profiles the main thread - #else - uint64_t nthreads = jl_n_threads; - #endif - // the `+ 6` is for the two block terminators `0` plus 4 metadata entries - return bt_size_cur + (((JL_BT_MAX_ENTRY_SIZE + 1) + 6) * nthreads) > bt_size_max; + // Declare buffer full if there isn't enough room to sample even just the + // thread metadata and one max-sized frame. The `+ 6` is for the two block + // terminator `0`'s plus the 4 metadata entries. + return bt_size_cur + ((JL_BT_MAX_ENTRY_SIZE + 1) + 6) > bt_size_max; } static uint64_t jl_last_sigint_trigger = 0; @@ -114,6 +255,9 @@ JL_DLLEXPORT void jl_exit_on_sigint(int on) static uintptr_t jl_get_pc_from_ctx(const void *_ctx); void jl_show_sigill(void *_ctx); +#if defined(_CPU_X86_64_) || defined(_CPU_X86_) \ + || (defined(_OS_LINUX_) && defined(_CPU_AARCH64_)) \ + || (defined(_OS_LINUX_) && defined(_CPU_ARM_)) static size_t jl_safe_read_mem(const volatile char *ptr, char *out, size_t len) { jl_jmp_buf *old_buf = jl_get_safe_restore(); @@ -128,26 +272,28 @@ static size_t jl_safe_read_mem(const volatile char *ptr, char *out, size_t len) jl_set_safe_restore(old_buf); return i; } +#endif static double profile_autostop_time = -1.0; static double profile_peek_duration = 1.0; // seconds -double jl_get_profile_peek_duration(void) { +double jl_get_profile_peek_duration(void) +{ return profile_peek_duration; } -void jl_set_profile_peek_duration(double t) { +void jl_set_profile_peek_duration(double t) +{ profile_peek_duration = t; - return; } uintptr_t profile_show_peek_cond_loc; JL_DLLEXPORT void jl_set_peek_cond(uintptr_t cond) { profile_show_peek_cond_loc = cond; - return; } -static void jl_check_profile_autostop(void) { +static void jl_check_profile_autostop(void) +{ if ((profile_autostop_time != -1.0) && (jl_hrtime() > profile_autostop_time)) { profile_autostop_time = -1.0; jl_profile_stop_timer(); @@ -269,24 +415,34 @@ void jl_show_sigill(void *_ctx) #endif } +// make it invalid for a task to return from this point to its stack +// this is generally quite an foolish operation, but does free you up to do +// arbitrary things on this stack now without worrying about corrupt state that +// existed already on it +void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT +{ + jl_set_safe_restore(NULL); + if (ct) { + ct->gcstack = NULL; + ct->eh = NULL; + ct->world_age = 1; + ct->ptls->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 + } +} + // what to do on a critical error on a thread -void jl_critical_error(int sig, bt_context_t *context, jl_task_t *ct) +void jl_critical_error(int sig, int si_code, bt_context_t *context, jl_task_t *ct) { jl_bt_element_t *bt_data = ct ? ct->ptls->bt_data : NULL; size_t *bt_size = ct ? &ct->ptls->bt_size : NULL; size_t i, n = ct ? *bt_size : 0; if (sig) { // kill this task, so that we cannot get back to it accidentally (via an untimely ^C or jlbacktrace in jl_exit) - jl_set_safe_restore(NULL); - if (ct) { - ct->gcstack = NULL; - ct->eh = NULL; - ct->excstack = NULL; - ct->ptls->locks.len = 0; - ct->ptls->in_pure_callback = 0; - ct->ptls->in_finalizer = 1; - ct->world_age = 1; - } + jl_task_frame_noreturn(ct); #ifndef _OS_WINDOWS_ sigset_t sset; sigemptyset(&sset); @@ -307,7 +463,10 @@ void jl_critical_error(int sig, bt_context_t *context, jl_task_t *ct) sigaddset(&sset, sig); pthread_sigmask(SIG_UNBLOCK, &sset, NULL); #endif - jl_safe_printf("\nsignal (%d): %s\n", sig, strsignal(sig)); + if (si_code) + 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("in expression starting at %s:%d\n", jl_filename, jl_lineno); if (context && ct) { @@ -322,74 +481,6 @@ void jl_critical_error(int sig, bt_context_t *context, jl_task_t *ct) jl_gc_debug_critical_error(); } -/////////////////////// -// Utility functions // -/////////////////////// -JL_DLLEXPORT int jl_profile_init(size_t maxsize, uint64_t delay_nsec) -{ - bt_size_max = maxsize; - nsecprof = delay_nsec; - if (bt_data_prof != NULL) - free((void*)bt_data_prof); - if (profile_round_robin_thread_order == NULL) { - // NOTE: We currently only allocate this once, since jl_n_threads cannot change - // during execution of a julia process. If/when this invariant changes in the - // future, this will have to be adjusted. - profile_round_robin_thread_order = (uint64_t*) calloc(jl_n_threads, sizeof(uint64_t)); - for (int i = 0; i < jl_n_threads; i++) { - profile_round_robin_thread_order[i] = i; - } - } - seed_cong(&profile_cong_rng_seed); - unbias_cong(jl_n_threads, &profile_cong_rng_unbias); - bt_data_prof = (jl_bt_element_t*) calloc(maxsize, sizeof(jl_bt_element_t)); - if (bt_data_prof == NULL && maxsize > 0) - return -1; - bt_size_cur = 0; - return 0; -} - -void jl_shuffle_int_array_inplace(volatile uint64_t *carray, size_t size, uint64_t *seed) { - // The "modern Fisher–Yates shuffle" - O(n) algorithm - // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm - for (size_t i = size - 1; i >= 1; --i) { - size_t j = cong(i, profile_cong_rng_unbias, seed); - uint64_t tmp = carray[j]; - carray[j] = carray[i]; - carray[i] = tmp; - } -} - -JL_DLLEXPORT uint8_t *jl_profile_get_data(void) -{ - return (uint8_t*) bt_data_prof; -} - -JL_DLLEXPORT size_t jl_profile_len_data(void) -{ - return bt_size_cur; -} - -JL_DLLEXPORT size_t jl_profile_maxlen_data(void) -{ - return bt_size_max; -} - -JL_DLLEXPORT uint64_t jl_profile_delay_nsec(void) -{ - return nsecprof; -} - -JL_DLLEXPORT void jl_profile_clear_data(void) -{ - bt_size_cur = 0; -} - -JL_DLLEXPORT int jl_profile_is_running(void) -{ - return running; -} - #ifdef __cplusplus } #endif diff --git a/src/signals-mach.c b/src/signals-mach.c index 130603931c5aa..073ab2ebc33a6 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -9,6 +9,7 @@ #include #include #include +#include "mach_excServer.c" #ifdef MAC_OS_X_VERSION_10_9 #include @@ -35,11 +36,16 @@ extern int _keymgr_set_lockmode_processwide_ptr(unsigned int key, unsigned int m extern void _dyld_atfork_prepare(void) __attribute__((weak_import)); extern void _dyld_atfork_parent(void) __attribute__((weak_import)); //extern void _dyld_fork_child(void) __attribute__((weak_import)); +extern void _dyld_dlopen_atfork_prepare(void) __attribute__((weak_import)); +extern void _dyld_dlopen_atfork_parent(void) __attribute__((weak_import)); +//extern void _dyld_dlopen_atfork_child(void) __attribute__((weak_import)); 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; void jl_mach_gc_end(void) { // Requires the safepoint lock to be held @@ -47,7 +53,7 @@ void jl_mach_gc_end(void) uintptr_t item = (uintptr_t)suspended_threads.items[i]; int16_t tid = (int16_t)item; int8_t gc_state = (int8_t)(item >> 8); - jl_ptls_t ptls2 = jl_all_tls_states[tid]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; jl_atomic_store_release(&ptls2->gc_state, gc_state); thread_resume(pthread_mach_thread_np(ptls2->system_id)); } @@ -68,6 +74,14 @@ static int jl_mach_gc_wait(jl_ptls_t ptls2, return 0; } // Otherwise, set the gc state of the thread, suspend and record it + // TODO: TSAN will complain that it never saw the faulting task do an + // atomic release (it was in the kernel). And our attempt here does + // nothing, since we are a different thread, and it is not transitive). + // + // This also means we are not making this thread available for GC work. + // 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; jl_atomic_store_release(&ptls2->gc_state, JL_GC_STATE_WAITING); uintptr_t item = tid | (((uintptr_t)gc_state) << 16); @@ -79,25 +93,21 @@ static int jl_mach_gc_wait(jl_ptls_t ptls2, static mach_port_t segv_port = 0; -extern boolean_t exc_server(mach_msg_header_t *, mach_msg_header_t *); - #define STR(x) #x #define XSTR(x) STR(x) #define HANDLE_MACH_ERROR(msg, retval) \ - if (retval != KERN_SUCCESS) { mach_error(msg XSTR(: __FILE__:__LINE__:), (retval)); jl_exit(1); } + if (retval != KERN_SUCCESS) { mach_error(msg XSTR(: __FILE__:__LINE__:), (retval)); abort(); } void *mach_segv_listener(void *arg) { (void)arg; - while (1) { - int ret = mach_msg_server(exc_server, 2048, segv_port, MACH_MSG_TIMEOUT_NONE); - jl_safe_printf("mach_msg_server: %s\n", mach_error_string(ret)); - jl_exit(128 + SIGSEGV); - } + int ret = mach_msg_server(mach_exc_server, 2048, segv_port, MACH_MSG_TIMEOUT_NONE); + mach_error("mach_msg_server" XSTR(: __FILE__:__LINE__:), ret); + abort(); } -static void allocate_mach_handler() +static void allocate_mach_handler(void) { // ensure KEYMGR_GCC3_DW2_OBJ_LIST is initialized, as this requires malloc // and thus can deadlock when used without first initializing it. @@ -110,7 +120,8 @@ static void allocate_mach_handler() if (_keymgr_set_lockmode_processwide_ptr(KEYMGR_GCC3_DW2_OBJ_LIST, NM_ALLOW_RECURSION)) jl_error("_keymgr_set_lockmode_processwide_ptr failed"); - arraylist_new(&suspended_threads, jl_n_threads); + int16_t nthreads = jl_atomic_load_acquire(&jl_n_threads); + arraylist_new(&suspended_threads, nthreads); // we will resize later (inside safepoint_lock), if needed pthread_t thread; pthread_attr_t attr; kern_return_t ret; @@ -128,35 +139,32 @@ static void allocate_mach_handler() jl_error("pthread_create failed"); } pthread_attr_destroy(&attr); - for (int16_t tid = 0; tid < jl_n_threads; tid++) { - attach_exception_port(pthread_mach_thread_np(jl_all_tls_states[tid]->system_id), 0); - } } #ifdef LLVMLIBUNWIND volatile mach_port_t mach_profiler_thread = 0; -static kern_return_t profiler_segv_handler - (mach_port_t exception_port, - mach_port_t thread, - mach_port_t task, - exception_type_t exception, - exception_data_t code, - mach_msg_type_number_t code_count); +static kern_return_t profiler_segv_handler( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt); #endif #if defined(_CPU_X86_64_) typedef x86_thread_state64_t host_thread_state_t; typedef x86_exception_state64_t host_exception_state_t; -#define THREAD_STATE x86_THREAD_STATE64 -#define THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT +#define MACH_THREAD_STATE x86_THREAD_STATE64 +#define MACH_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT #define HOST_EXCEPTION_STATE x86_EXCEPTION_STATE64 #define HOST_EXCEPTION_STATE_COUNT x86_EXCEPTION_STATE64_COUNT #elif defined(_CPU_AARCH64_) typedef arm_thread_state64_t host_thread_state_t; typedef arm_exception_state64_t host_exception_state_t; -#define THREAD_STATE ARM_THREAD_STATE64 -#define THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT +#define MACH_THREAD_STATE ARM_THREAD_STATE64 +#define MACH_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT #define HOST_EXCEPTION_STATE ARM_EXCEPTION_STATE64 #define HOST_EXCEPTION_STATE_COUNT ARM_EXCEPTION_STATE64_COUNT #endif @@ -207,14 +215,13 @@ int is_write_fault(host_exception_state_t exc_state) { } #endif -static void jl_throw_in_thread(int tid, mach_port_t thread, jl_value_t *exception) +static void jl_throw_in_thread(jl_ptls_t ptls2, mach_port_t thread, jl_value_t *exception) { - unsigned int count = THREAD_STATE_COUNT; + unsigned int count = MACH_THREAD_STATE_COUNT; host_thread_state_t state; - kern_return_t ret = thread_get_state(thread, THREAD_STATE, (thread_state_t)&state, &count); + kern_return_t ret = thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, &count); HANDLE_MACH_ERROR("thread_get_state", ret); - jl_ptls_t ptls2 = jl_all_tls_states[tid]; - if (!jl_get_safe_restore()) { + if (1) { // XXX: !jl_has_safe_restore(ptls2) assert(exception); ptls2->bt_size = rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, (bt_context_t *)&state, @@ -222,7 +229,7 @@ static void jl_throw_in_thread(int tid, mach_port_t thread, jl_value_t *exceptio ptls2->sig_exception = exception; } jl_call_in_state(ptls2, &state, &jl_sig_throw); - ret = thread_set_state(thread, THREAD_STATE, (thread_state_t)&state, count); + ret = thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state", ret); } @@ -239,51 +246,70 @@ static void segv_handler(int sig, siginfo_t *info, void *context) } } -//exc_server uses dlsym to find symbol -JL_DLLEXPORT -kern_return_t catch_exception_raise(mach_port_t exception_port, - mach_port_t thread, - mach_port_t task, - exception_type_t exception, - exception_data_t code, - mach_msg_type_number_t code_count) +// n.b. mach_exc_server expects us to define this symbol locally +/* The documentation for catch_exception_raise says: A return value of + * KERN_SUCCESS indicates that the thread is to continue from the point of + * exception. A return value of MIG_NO_REPLY indicates that the exception was + * handled directly and the thread was restarted or terminated by the exception + * handler. A return value of MIG_DESTROY_REQUEST causes the kernel to try + * another exception handler (or terminate the thread). Any other value will + * cause mach_msg_server to remove the task and thread port references. + * + * However MIG_DESTROY_REQUEST does not exist, not does it appear the source + * code for mach_msg_server ever destroy those references (only the message + * itself). + */ +kern_return_t catch_mach_exception_raise( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt) { unsigned int exc_count = HOST_EXCEPTION_STATE_COUNT; host_exception_state_t exc_state; #ifdef LLVMLIBUNWIND if (thread == mach_profiler_thread) { - return profiler_segv_handler(exception_port, thread, task, exception, code, code_count); + return profiler_segv_handler(exception_port, thread, task, exception, code, codeCnt); } #endif int16_t tid; jl_ptls_t ptls2 = NULL; - for (tid = 0; tid < jl_n_threads; tid++) { - jl_ptls_t _ptls2 = jl_all_tls_states[tid]; + int nthreads = jl_atomic_load_acquire(&jl_n_threads); + for (tid = 0; tid < nthreads; tid++) { + jl_ptls_t _ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; if (pthread_mach_thread_np(_ptls2->system_id) == thread) { ptls2 = _ptls2; break; } } - if (!ptls2) { + if (!ptls2 || ptls2->current_task == NULL) { // We don't know about this thread, let the kernel try another handler // instead. This shouldn't actually happen since we only register the // handler for the threads we know about. jl_safe_printf("ERROR: Exception handler triggered on unmanaged thread.\n"); return KERN_INVALID_ARGUMENT; } + // XXX: jl_throw_in_thread or segv_handler will eventually check this, but + // we would like to avoid some of this work if we could detect this earlier + // if (jl_has_safe_restore(ptls2)) { + // jl_throw_in_thread(ptls2, thread, jl_stackovf_exception); + // return KERN_SUCCESS; + // } + if (ptls2->gc_state == JL_GC_STATE_WAITING) + return KERN_FAILURE; if (exception == EXC_ARITHMETIC) { - jl_throw_in_thread(tid, thread, jl_diverror_exception); + jl_throw_in_thread(ptls2, thread, jl_diverror_exception); return KERN_SUCCESS; } - assert(exception == EXC_BAD_ACCESS); + assert(exception == EXC_BAD_ACCESS); // SIGSEGV or SIGBUS + if (codeCnt < 2 || code[0] != KERN_PROTECTION_FAILURE) // SEGV_ACCERR or BUS_ADRERR or BUS_ADRALN + return KERN_FAILURE; + uint64_t fault_addr = code[1]; kern_return_t ret = thread_get_state(thread, HOST_EXCEPTION_STATE, (thread_state_t)&exc_state, &exc_count); HANDLE_MACH_ERROR("thread_get_state", ret); -#ifdef _CPU_X86_64_ - uint64_t fault_addr = exc_state.__faultvaddr; -#else - uint64_t fault_addr = exc_state.__far; -#endif - if (jl_addr_is_safepoint(fault_addr)) { + if (jl_addr_is_safepoint(fault_addr) && !is_write_fault(exc_state)) { if (jl_mach_gc_wait(ptls2, thread, tid)) return KERN_SUCCESS; if (ptls2->tid != 0) @@ -293,42 +319,54 @@ kern_return_t catch_exception_raise(mach_port_t exception_port, } else if (jl_safepoint_consume_sigint()) { jl_clear_force_sigint(); - jl_throw_in_thread(tid, thread, jl_interrupt_exception); + jl_throw_in_thread(ptls2, thread, jl_interrupt_exception); } return KERN_SUCCESS; } - if (jl_get_safe_restore()) { - jl_throw_in_thread(tid, thread, jl_stackovf_exception); - return KERN_SUCCESS; + if (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)) { + excpt = jl_stackovf_exception; } -#ifdef SEGV_EXCEPTION - if (1) { -#else - if (msync((void*)(fault_addr & ~(jl_page_size - 1)), 1, MS_ASYNC) == 0) { // check if this was a valid address -#endif - jl_value_t *excpt; - if (is_addr_on_stack(jl_atomic_load_relaxed(&ptls2->current_task), (void*)fault_addr)) { - excpt = jl_stackovf_exception; - } -#ifdef SEGV_EXCEPTION - else if (msync((void*)(fault_addr & ~(jl_page_size - 1)), 1, MS_ASYNC) != 0) { - // no page mapped at this address - excpt = jl_segv_exception; - } -#endif - else { - if (!is_write_fault(exc_state)) - return KERN_INVALID_ARGUMENT; - excpt = jl_readonlymemory_exception; - } - jl_throw_in_thread(tid, thread, excpt); + else if (is_write_fault(exc_state)) // false for alignment errors + excpt = jl_readonlymemory_exception; + else + return KERN_FAILURE; + jl_throw_in_thread(ptls2, thread, excpt); + return KERN_SUCCESS; +} - return KERN_SUCCESS; - } - else { - jl_exit_thread0(128 + SIGSEGV, NULL, 0); - return KERN_SUCCESS; - } +//mach_exc_server expects us to define this symbol locally +kern_return_t catch_mach_exception_raise_state( + mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt) +{ + return KERN_INVALID_ARGUMENT; // we only use EXCEPTION_DEFAULT +} + +//mach_exc_server expects us to define this symbol locally +kern_return_t catch_mach_exception_raise_state_identity( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt) +{ + return KERN_INVALID_ARGUMENT; // we only use EXCEPTION_DEFAULT } static void attach_exception_port(thread_port_t thread, int segv_only) @@ -338,36 +376,47 @@ static void attach_exception_port(thread_port_t thread, int segv_only) exception_mask_t mask = EXC_MASK_BAD_ACCESS; if (!segv_only) mask |= EXC_MASK_ARITHMETIC; - ret = thread_set_exception_ports(thread, mask, segv_port, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE); + ret = thread_set_exception_ports(thread, mask, segv_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, MACH_THREAD_STATE); HANDLE_MACH_ERROR("thread_set_exception_ports", ret); } -static void jl_thread_suspend_and_get_state2(int tid, host_thread_state_t *ctx) +static int jl_thread_suspend_and_get_state2(int tid, host_thread_state_t *ctx) { - jl_ptls_t ptls2 = jl_all_tls_states[tid]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; + if (ptls2 == NULL) // this thread is not alive + return 0; + jl_task_t *ct2 = ptls2 ? jl_atomic_load_relaxed(&ptls2->current_task) : NULL; + if (ct2 == NULL) // this thread is already dead + return 0; + mach_port_t thread = pthread_mach_thread_np(ptls2->system_id); kern_return_t ret = thread_suspend(thread); HANDLE_MACH_ERROR("thread_suspend", ret); // Do the actual sampling - unsigned int count = THREAD_STATE_COUNT; + unsigned int count = MACH_THREAD_STATE_COUNT; memset(ctx, 0, sizeof(*ctx)); // Get the state of the suspended thread - ret = thread_get_state(thread, THREAD_STATE, (thread_state_t)ctx, &count); + ret = thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)ctx, &count); + return 1; } -static void jl_thread_suspend_and_get_state(int tid, unw_context_t **ctx) +static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t **ctx) { + (void)timeout; static host_thread_state_t state; - jl_thread_suspend_and_get_state2(tid, &state); + if (!jl_thread_suspend_and_get_state2(tid, &state)) { + *ctx = NULL; + return; + } *ctx = (unw_context_t*)&state; } static void jl_thread_resume(int tid, int sig) { - jl_ptls_t ptls2 = jl_all_tls_states[tid]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; mach_port_t thread = pthread_mach_thread_np(ptls2->system_id); kern_return_t ret = thread_resume(thread); HANDLE_MACH_ERROR("thread_resume", ret); @@ -377,7 +426,7 @@ static void jl_thread_resume(int tid, int sig) // or if SIGINT happens too often. static void jl_try_deliver_sigint(void) { - jl_ptls_t ptls2 = jl_all_tls_states[0]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; mach_port_t thread = pthread_mach_thread_np(ptls2->system_id); kern_return_t ret = thread_suspend(thread); @@ -394,7 +443,7 @@ static void jl_try_deliver_sigint(void) if (force) jl_safe_printf("WARNING: Force throwing a SIGINT\n"); jl_clear_force_sigint(); - jl_throw_in_thread(0, thread, jl_interrupt_exception); + jl_throw_in_thread(ptls2, thread, jl_interrupt_exception); } else { jl_wake_libuv(); @@ -404,58 +453,43 @@ static void jl_try_deliver_sigint(void) HANDLE_MACH_ERROR("thread_resume", ret); } -static void JL_NORETURN jl_exit_thread0_cb(int exitstate) +static void JL_NORETURN jl_exit_thread0_cb(int signo) { CFI_NORETURN - jl_critical_error(exitstate - 128, NULL, jl_current_task); - jl_exit(exitstate); + jl_critical_error(signo, 0, NULL, jl_current_task); + jl_atexit_hook(128); + jl_raise(signo); } -static void jl_exit_thread0(int exitstate, jl_bt_element_t *bt_data, size_t bt_size) +static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size) { - jl_ptls_t ptls2 = jl_all_tls_states[0]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; mach_port_t thread = pthread_mach_thread_np(ptls2->system_id); host_thread_state_t state; - jl_thread_suspend_and_get_state2(0, &state); - unw_context_t *uc = (unw_context_t*)&state; + if (!jl_thread_suspend_and_get_state2(0, &state)) { + // thread 0 is gone? just do the signal ourself + jl_raise(signo); + } // This aborts `sleep` and other syscalls. kern_return_t ret = thread_abort(thread); HANDLE_MACH_ERROR("thread_abort", ret); - if (bt_data == NULL) { - // Must avoid extended backtrace frames here unless we're sure bt_data - // is properly rooted. - ptls2->bt_size = rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, uc, NULL); - } - else { - ptls2->bt_size = bt_size; // <= JL_MAX_BT_SIZE - memcpy(ptls2->bt_data, bt_data, ptls2->bt_size * sizeof(bt_data[0])); - } - - void (*exit_func)(int) = &_exit; - if (thread0_exit_count <= 1) { - exit_func = &jl_exit_thread0_cb; - } - else if (thread0_exit_count == 2) { - exit_func = &exit; - } - else { - exit_func = &_exit; - } + ptls2->bt_size = bt_size; // <= JL_MAX_BT_SIZE + memcpy(ptls2->bt_data, bt_data, ptls2->bt_size * sizeof(bt_data[0])); #ifdef _CPU_X86_64_ // First integer argument. Not portable but good enough =) - state.__rdi = exitstate; + state.__rdi = signo; #elif defined(_CPU_AARCH64_) - state.__x[0] = exitstate; + state.__x[0] = signo; #else #error Fill in first integer argument here #endif - jl_call_in_state(ptls2, &state, (void (*)(void))exit_func); - unsigned int count = THREAD_STATE_COUNT; - ret = thread_set_state(thread, THREAD_STATE, (thread_state_t)&state, count); + jl_call_in_state(ptls2, &state, (void (*)(void))&jl_exit_thread0_cb); + unsigned int count = MACH_THREAD_STATE_COUNT; + ret = thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state", ret); ret = thread_resume(thread); @@ -472,29 +506,29 @@ static mach_port_t profile_port = 0; volatile static int forceDwarf = -2; static unw_context_t profiler_uc; -static kern_return_t profiler_segv_handler - (mach_port_t exception_port, - mach_port_t thread, - mach_port_t task, - exception_type_t exception, - exception_data_t code, - mach_msg_type_number_t code_count) +static kern_return_t profiler_segv_handler( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt) { assert(thread == mach_profiler_thread); host_thread_state_t state; // Not currently unwinding. Raise regular segfault if (forceDwarf == -2) - return KERN_INVALID_ARGUMENT; + return KERN_FAILURE; if (forceDwarf == 0) forceDwarf = 1; else forceDwarf = -1; - unsigned int count = THREAD_STATE_COUNT; + unsigned int count = MACH_THREAD_STATE_COUNT; - thread_get_state(thread, THREAD_STATE, (thread_state_t)&state, &count); + thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, &count); #ifdef _CPU_X86_64_ // don't change cs fs gs rflags @@ -519,7 +553,7 @@ static kern_return_t profiler_segv_handler state.__cpsr = cpsr; #endif - kern_return_t ret = thread_set_state(thread, THREAD_STATE, (thread_state_t)&state, count); + kern_return_t ret = thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state", ret); return KERN_SUCCESS; @@ -533,7 +567,12 @@ static int jl_lock_profile_mach(int dlsymlock) // workaround for old keymgr bugs void *unused = NULL; int keymgr_locked = _keymgr_get_and_lock_processwide_ptr_2(KEYMGR_GCC3_DW2_OBJ_LIST, &unused) == 0; - // workaround for new dlsym4 bugs (API and bugs introduced in macOS 12.1) + // workaround for new dlsym4 bugs in the workaround for dlsym bugs: _dyld_atfork_prepare + // acquires its locks in the wrong order, but fortunately we happen to able to guard it + // with this call to force it to prevent that TSAN violation from causing a deadlock + if (dlsymlock && _dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL) + _dyld_dlopen_atfork_prepare(); + // workaround for new dlsym4 bugs (API and bugs introduced circa macOS 12.1) if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL) _dyld_atfork_prepare(); return keymgr_locked; @@ -541,8 +580,10 @@ static int jl_lock_profile_mach(int dlsymlock) static void jl_unlock_profile_mach(int dlsymlock, int keymgr_locked) { - if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL) \ - _dyld_atfork_parent(); \ + if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL) + _dyld_atfork_parent(); + if (dlsymlock && _dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL) + _dyld_dlopen_atfork_parent(); if (keymgr_locked) _keymgr_unlock_processwide_ptr(KEYMGR_GCC3_DW2_OBJ_LIST); jl_unlock_profile(); @@ -568,24 +609,31 @@ void *mach_profile_listener(void *arg) // sample each thread, round-robin style in reverse order // (so that thread zero gets notified last) int keymgr_locked = jl_lock_profile_mach(0); - jl_shuffle_int_array_inplace(profile_round_robin_thread_order, jl_n_threads, &profile_cong_rng_seed); - for (int idx = jl_n_threads; idx-- > 0; ) { - // Stop the threads in the random round-robin order. - int i = profile_round_robin_thread_order[idx]; + + int nthreads = jl_atomic_load_acquire(&jl_n_threads); + int *randperm = profile_get_randperm(nthreads); + for (int idx = nthreads; idx-- > 0; ) { + // Stop the threads in the random or reverse round-robin order. + int i = randperm[idx]; // if there is no space left, break early if (jl_profile_is_buffer_full()) { jl_profile_stop_timer(); break; } + if (_dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL) + _dyld_dlopen_atfork_prepare(); if (_dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL) _dyld_atfork_prepare(); // briefly acquire the dlsym lock host_thread_state_t state; - jl_thread_suspend_and_get_state2(i, &state); + int valid_thread = jl_thread_suspend_and_get_state2(i, &state); unw_context_t *uc = (unw_context_t*)&state; if (_dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL) _dyld_atfork_parent(); // quickly release the dlsym lock - + if (_dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL) + _dyld_dlopen_atfork_parent(); + if (!valid_thread) + continue; if (running) { #ifdef LLVMLIBUNWIND /* @@ -620,12 +668,12 @@ void *mach_profile_listener(void *arg) #else bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, uc, NULL); #endif - jl_ptls_t ptls = jl_all_tls_states[i]; + jl_ptls_t ptls = jl_atomic_load_relaxed(&jl_all_tls_states)[i]; // store threadid but add 1 as 0 is preserved to indicate end of block bt_data_prof[bt_size_cur++].uintptr = ptls->tid + 1; - // store task id + // store task id (never null) bt_data_prof[bt_size_cur++].jlvalue = (jl_value_t*)jl_atomic_load_relaxed(&ptls->current_task); // store cpu cycle clock diff --git a/src/signals-unix.c b/src/signals-unix.c index b2e3ef2952029..4c21d25d3622c 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -27,9 +27,7 @@ #ifdef __APPLE__ // Darwin's mach ports allow signal-free thread management #define HAVE_MACH #define HAVE_KEVENT -#elif defined(__FreeBSD__) // generic bsd -#define HAVE_ITIMER -#else // generic linux +#else // generic Linux or BSD #define HAVE_TIMER #endif @@ -62,7 +60,7 @@ bt_context_t *jl_to_bt_context(void *sigctx) } static int thread0_exit_count = 0; -static void jl_exit_thread0(int exitstate, jl_bt_element_t *bt_data, size_t bt_size); +static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size); static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void *_ctx) { @@ -104,7 +102,7 @@ static int is_addr_on_sigstack(jl_ptls_t ptls, void *ptr) // returns. `fptr` will execute on the signal stack, and must not return. // jl_call_in_ctx is also currently executing on that signal stack, // so be careful not to smash it -static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_ctx) +JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_ctx) { // Modifying the ucontext should work but there is concern that // sigreturn oriented programming mitigation can work against us @@ -117,7 +115,7 @@ static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_c sigset_t sset; sigemptyset(&sset); sigaddset(&sset, sig); - sigprocmask(SIG_UNBLOCK, &sset, NULL); + pthread_sigmask(SIG_UNBLOCK, &sset, NULL); fptr(); return; } @@ -190,12 +188,12 @@ static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_c ctx->uc_mcontext64->__ss.__lr = 0; #endif #else -#warning "julia: throw-in-context not supported on this platform" +#pragma message("julia: throw-in-context not supported on this platform") // TODO Add support for PowerPC(64)? sigset_t sset; sigemptyset(&sset); sigaddset(&sset, sig); - sigprocmask(SIG_UNBLOCK, &sset, NULL); + pthread_sigmask(SIG_UNBLOCK, &sset, NULL); fptr(); #endif } @@ -231,12 +229,29 @@ 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, jl_to_bt_context(context), jl_get_current_task()); - if (sig != SIGSEGV && - sig != SIGBUS && - sig != SIGILL) { + jl_critical_error(sig, info->si_code, jl_to_bt_context(context), jl_get_current_task()); + if (info->si_code == 0 || + info->si_code == SI_USER || +#ifdef SI_KERNEL + info->si_code == SI_KERNEL || +#endif + info->si_code == SI_QUEUE || + info->si_code == SI_MESGQ || + info->si_code == SI_ASYNCIO || +#ifdef SI_SIGIO + info->si_code == SI_SIGIO || +#endif +#ifdef SI_TKILL + info->si_code == SI_TKILL || +#endif + info->si_code == SI_TIMER) + raise(sig); + else if (sig != SIGSEGV && + sig != SIGBUS && + sig != SIGILL && + sig != SIGFPE && + sig != SIGTRAP) raise(sig); - } // fall-through return to re-execute faulting statement (but without the error handler) } @@ -244,7 +259,7 @@ static void sigdie_handler(int sig, siginfo_t *info, void *context) enum x86_trap_flags { USER_MODE = 0x4, WRITE_FAULT = 0x2, - PAGE_PRESENT = 0x1 + PAGE_PRESENT = 0x1 // whether this page is currently mapped into memory }; int exc_reg_is_write_fault(uintptr_t err) { @@ -254,11 +269,21 @@ int exc_reg_is_write_fault(uintptr_t err) { enum aarch64_esr_layout { EC_MASK = ((uint32_t)0b111111) << 26, EC_DATA_ABORT = ((uint32_t)0b100100) << 26, + DFSC_MASK = ((uint32_t)0b111111) << 0, ISR_DA_WnR = ((uint32_t)1) << 6 }; int exc_reg_is_write_fault(uintptr_t esr) { - return (esr & EC_MASK) == EC_DATA_ABORT && (esr & ISR_DA_WnR); + // n.b. we check that DFSC is either a permission fault (page in memory but not writable) or a translation fault (page not in memory) + // but because of info->si_code == SEGV_ACCERR, we know the kernel could have brought the page into memory. + // Access faults happen when trying to write to code or secure memory, which is a more severe violation, so we ignore those. + // AArch64 appears to leaves it up to a given implementer whether atomic update errors are reported as read or write faults. + return (esr & EC_MASK) == EC_DATA_ABORT && + (((esr & DFSC_MASK) >= 0b000100 && // Translation flag fault, level 0. + (esr & DFSC_MASK) <= 0b000111) || // Translation fault, level 3. + ((esr & DFSC_MASK) >= 0b001100 && // Permission flag fault, level 0. + (esr & DFSC_MASK) <= 0b001111)) && // Permission fault, level 3. + (esr & ISR_DA_WnR); // Attempted write } #endif @@ -274,8 +299,8 @@ int is_write_fault(void *context) { } #elif defined(_OS_LINUX_) && defined(_CPU_AARCH64_) struct linux_aarch64_ctx_header { - uint32_t magic; - uint32_t size; + uint32_t magic; + uint32_t size; }; const uint32_t linux_esr_magic = 0x45535201; @@ -298,7 +323,7 @@ int is_write_fault(void *context) { return exc_reg_is_write_fault(ctx->uc_mcontext.mc_err); } #else -#warning Implement this query for consistent PROT_NONE handling +#pragma message("Implement this query for consistent PROT_NONE handling") int is_write_fault(void *context) { return 0; } @@ -310,19 +335,19 @@ static int jl_is_on_sigstack(jl_ptls_t ptls, void *ptr, void *context) is_addr_on_sigstack(ptls, (void*)jl_get_rsp_from_ctx(context))); } -static void segv_handler(int sig, siginfo_t *info, void *context) +JL_NO_ASAN static void segv_handler(int sig, siginfo_t *info, void *context) { + assert(sig == SIGSEGV || sig == SIGBUS); if (jl_get_safe_restore()) { // restarting jl_ or profile jl_call_in_ctx(NULL, &jl_sig_throw, sig, context); return; } jl_task_t *ct = jl_get_current_task(); - if (ct == NULL) { + if (ct == NULL || ct->ptls == NULL || jl_atomic_load_relaxed(&ct->ptls->gc_state) == JL_GC_STATE_WAITING) { sigdie_handler(sig, info, context); return; } - assert(sig == SIGSEGV || sig == SIGBUS); - if (jl_addr_is_safepoint((uintptr_t)info->si_addr)) { + if (sig == SIGSEGV && info->si_code == SEGV_ACCERR && jl_addr_is_safepoint((uintptr_t)info->si_addr) && !is_write_fault(context)) { jl_set_gc_and_wait(); // Do not raise sigint on worker thread if (jl_atomic_load_relaxed(&ct->tid) != 0) @@ -336,7 +361,9 @@ static void segv_handler(int sig, siginfo_t *info, void *context) } return; } - if (is_addr_on_stack(ct, info->si_addr)) { // stack overflow + if (ct->eh == NULL) + sigdie_handler(sig, info, context); + if ((sig != SIGBUS || info->si_code == BUS_ADRERR) && is_addr_on_stack(ct, info->si_addr)) { // stack overflow and not a BUS_ADRALN (alignment error) jl_throw_in_ctx(ct, jl_stackovf_exception, sig, context); } else if (jl_is_on_sigstack(ct->ptls, info->si_addr, context)) { @@ -346,33 +373,36 @@ static void segv_handler(int sig, siginfo_t *info, void *context) // (we are already corrupting that stack running this function) // so just call `_exit` to terminate immediately. jl_safe_printf("ERROR: Signal stack overflow, exit\n"); - _exit(sig + 128); + jl_raise(sig); } else if (sig == SIGSEGV && info->si_code == SEGV_ACCERR && is_write_fault(context)) { // writing to read-only memory (e.g., mmap) jl_throw_in_ctx(ct, jl_readonlymemory_exception, sig, context); } else { -#ifdef SEGV_EXCEPTION - jl_throw_in_ctx(ct, jl_segv_exception, sig, context); -#else sigdie_handler(sig, info, context); -#endif } } #if !defined(JL_DISABLE_LIBUNWIND) -static unw_context_t *volatile signal_context; -static pthread_mutex_t in_signal_lock; +static unw_context_t *signal_context; +pthread_mutex_t in_signal_lock; static pthread_cond_t exit_signal_cond; static pthread_cond_t signal_caught_cond; -static void jl_thread_suspend_and_get_state(int tid, unw_context_t **ctx) +static void jl_thread_suspend_and_get_state(int tid, int timeout, unw_context_t **ctx) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += 1; + ts.tv_sec += timeout; pthread_mutex_lock(&in_signal_lock); - jl_ptls_t ptls2 = jl_all_tls_states[tid]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; + jl_task_t *ct2 = ptls2 ? jl_atomic_load_relaxed(&ptls2->current_task) : NULL; + if (ct2 == NULL) { + // this thread is not alive or already dead + *ctx = NULL; + pthread_mutex_unlock(&in_signal_lock); + return; + } jl_atomic_store_release(&ptls2->signal_request, 1); pthread_kill(ptls2->system_id, SIGUSR2); // wait for thread to acknowledge @@ -384,20 +414,34 @@ static void jl_thread_suspend_and_get_state(int tid, unw_context_t **ctx) pthread_mutex_unlock(&in_signal_lock); return; } - err = pthread_cond_wait(&signal_caught_cond, &in_signal_lock); + // Request is either now 0 (meaning the other thread is waiting for + // exit_signal_cond already), + // Or it is now -1 (meaning the other thread + // is waiting for in_signal_lock, and we need to release that lock + // here for a bit, until the other thread has a chance to get to the + // exit_signal_cond) + if (request == -1) { + err = pthread_cond_wait(&signal_caught_cond, &in_signal_lock); + assert(!err); + } } - assert(!err); - assert(jl_atomic_load_acquire(&ptls2->signal_request) == 0); + // Now the other thread is waiting on exit_signal_cond (verify that here by + // checking it is 0, and add an acquire barrier for good measure) + int request = jl_atomic_load_acquire(&ptls2->signal_request); + assert(request == 0); (void) request; *ctx = signal_context; } static void jl_thread_resume(int tid, int sig) { - jl_ptls_t ptls2 = jl_all_tls_states[tid]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid]; jl_atomic_store_release(&ptls2->signal_request, sig == -1 ? 3 : 1); pthread_cond_broadcast(&exit_signal_cond); pthread_cond_wait(&signal_caught_cond, &in_signal_lock); // wait for thread to acknowledge - assert(jl_atomic_load_acquire(&ptls2->signal_request) == 0); + // The other thread is waiting to leave exit_signal_cond (verify that here by + // checking it is 0, and add an acquire barrier for good measure) + int request = jl_atomic_load_acquire(&ptls2->signal_request); + assert(request == 0); (void) request; pthread_mutex_unlock(&in_signal_lock); } #endif @@ -406,7 +450,7 @@ static void jl_thread_resume(int tid, int sig) // or if SIGINT happens too often. static void jl_try_deliver_sigint(void) { - jl_ptls_t ptls2 = jl_all_tls_states[0]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; jl_safepoint_enable_sigint(); jl_wake_libuv(); jl_atomic_store_release(&ptls2->signal_request, 2); @@ -416,51 +460,40 @@ static void jl_try_deliver_sigint(void) // Write only by signal handling thread, read only by main thread // no sync necessary. -static int thread0_exit_state = 0; +static int thread0_exit_signo = 0; static void JL_NORETURN jl_exit_thread0_cb(void) { CFI_NORETURN - // This can get stuck if it happens at an unfortunate spot - // (unavoidable due to its async nature). - // Try harder to exit each time if we get multiple exit requests. - if (thread0_exit_count <= 1) { - jl_critical_error(thread0_exit_state - 128, NULL, jl_current_task); - jl_exit(thread0_exit_state); - } - else if (thread0_exit_count == 2) { - exit(thread0_exit_state); - } - else { - _exit(thread0_exit_state); - } + jl_critical_error(thread0_exit_signo, 0, NULL, jl_current_task); + jl_atexit_hook(128); + jl_raise(thread0_exit_signo); } -static void jl_exit_thread0(int state, jl_bt_element_t *bt_data, size_t bt_size) +static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size) { - jl_ptls_t ptls2 = jl_all_tls_states[0]; - if (thread0_exit_count <= 1) { - unw_context_t *signal_context; - jl_thread_suspend_and_get_state(0, &signal_context); - if (signal_context != NULL) { - thread0_exit_state = state; - ptls2->bt_size = bt_size; // <= JL_MAX_BT_SIZE - memcpy(ptls2->bt_data, bt_data, ptls2->bt_size * sizeof(bt_data[0])); - jl_thread_resume(0, -1); - return; - } - } - thread0_exit_state = state; - jl_atomic_store_release(&ptls2->signal_request, 3); + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; + unw_context_t *signal_context; // This also makes sure `sleep` is aborted. - pthread_kill(ptls2->system_id, SIGUSR2); + jl_thread_suspend_and_get_state(0, 30, &signal_context); + if (signal_context != NULL) { + thread0_exit_signo = signo; + ptls2->bt_size = bt_size; // <= JL_MAX_BT_SIZE + memcpy(ptls2->bt_data, bt_data, ptls2->bt_size * sizeof(bt_data[0])); + jl_thread_resume(0, -1); // resume with message 3 (call jl_exit_thread0_cb) + } + else { + // thread 0 is gone? just do the exit ourself + jl_raise(signo); + } } // request: -// 0: nothing -// 1: get state -// 2: throw sigint if `!defer_signal && io_wait` or if force throw threshold -// is reached -// 3: exit with `thread0_exit_state` +// -1: beginning processing [invalid outside here] +// 0: nothing [not from here] +// 1: get state +// 2: throw sigint if `!defer_signal && io_wait` or if force throw threshold +// is reached +// 3: raise `thread0_exit_signo` and try to exit void usr2_handler(int sig, siginfo_t *info, void *ctx) { jl_task_t *ct = jl_get_current_task(); @@ -470,20 +503,26 @@ void usr2_handler(int sig, siginfo_t *info, void *ctx) if (ptls == NULL) return; int errno_save = errno; - sig_atomic_t request = jl_atomic_exchange(&ptls->signal_request, 0); + // acknowledge that we saw the signal_request + sig_atomic_t request = jl_atomic_exchange(&ptls->signal_request, -1); #if !defined(JL_DISABLE_LIBUNWIND) if (request == 1) { - signal_context = jl_to_bt_context(ctx); - pthread_mutex_lock(&in_signal_lock); + signal_context = jl_to_bt_context(ctx); + // acknowledge that we set the signal_caught_cond broadcast + request = jl_atomic_exchange(&ptls->signal_request, 0); + assert(request == -1); (void) request; pthread_cond_broadcast(&signal_caught_cond); pthread_cond_wait(&exit_signal_cond, &in_signal_lock); request = jl_atomic_exchange(&ptls->signal_request, 0); assert(request == 1 || request == 3); + // acknowledge that we got the resume signal pthread_cond_broadcast(&signal_caught_cond); pthread_mutex_unlock(&in_signal_lock); } + else #endif + jl_atomic_exchange(&ptls->signal_request, 0); // returns -1 if (request == 2) { int force = jl_check_force_sigint(); if (force || (!ptls->defer_signal && ptls->io_wait)) { @@ -501,6 +540,16 @@ void usr2_handler(int sig, siginfo_t *info, void *ctx) errno = errno_save; } +// Because SIGUSR1 is dual-purpose, and the timer can have trailing signals after being deleted, +// a 2-second grace period is imposed to ignore any trailing timer-created signals so they don't get +// confused for user triggers +uint64_t last_timer_delete_time = 0; + +int timer_graceperiod_elapsed(void) +{ + return jl_hrtime() > (last_timer_delete_time + 2e9); +} + #if defined(HAVE_TIMER) // Linux-style #include @@ -537,37 +586,12 @@ JL_DLLEXPORT int jl_profile_start_timer(void) return 0; } -JL_DLLEXPORT void jl_profile_stop_timer(void) -{ - if (running) - timer_delete(timerprof); - running = 0; -} - -#elif defined(HAVE_ITIMER) -// BSD-style timers -#include -#include -struct itimerval timerprof; - -JL_DLLEXPORT int jl_profile_start_timer(void) -{ - timerprof.it_interval.tv_sec = 0; - timerprof.it_interval.tv_usec = 0; - timerprof.it_value.tv_sec = nsecprof / GIGA; - timerprof.it_value.tv_usec = ((nsecprof % GIGA) + 999) / 1000; - if (setitimer(ITIMER_PROF, &timerprof, NULL) == -1) - return -3; - running = 1; - return 0; -} - JL_DLLEXPORT void jl_profile_stop_timer(void) { if (running) { + timer_delete(timerprof); + last_timer_delete_time = jl_hrtime(); running = 0; - memset(&timerprof, 0, sizeof(timerprof)); - setitimer(ITIMER_PROF, &timerprof, NULL); } } @@ -597,7 +621,7 @@ static void allocate_segv_handler(void) static void *alloc_sigstack(size_t *ssize) { void *stk = jl_malloc_stack(ssize, NULL); - if (stk == MAP_FAILED) + if (stk == NULL) jl_errorf("fatal error allocating signal stack: mmap: %s", strerror(errno)); return stk; } @@ -614,25 +638,30 @@ void jl_install_thread_signal_handler(jl_ptls_t ptls) if (sigaltstack(&ss, NULL) < 0) { jl_errorf("fatal error: sigaltstack: %s", strerror(errno)); } + +#ifdef HAVE_MACH + attach_exception_port(pthread_mach_thread_np(ptls->system_id), 0); +#endif } -static void jl_sigsetset(sigset_t *sset) -{ - sigemptyset(sset); - sigaddset(sset, SIGINT); - sigaddset(sset, SIGTERM); - sigaddset(sset, SIGABRT); - sigaddset(sset, SIGQUIT); +const static int sigwait_sigs[] = { + SIGINT, SIGTERM, SIGQUIT, #ifdef SIGINFO - sigaddset(sset, SIGINFO); + SIGINFO, #else - sigaddset(sset, SIGUSR1); + SIGUSR1, #endif #if defined(HAVE_TIMER) - sigaddset(sset, SIGUSR1); -#elif defined(HAVE_ITIMER) - sigaddset(sset, SIGPROF); + SIGUSR1, #endif + 0 +}; + +static void jl_sigsetset(sigset_t *sset) +{ + sigemptyset(sset); + for (const int *sig = sigwait_sigs; *sig; sig++) + sigaddset(sset, *sig); } #ifdef HAVE_KEVENT @@ -647,6 +676,7 @@ static void kqueue_signal(int *sigqueue, struct kevent *ev, int sig) *sigqueue = -1; } else { + // kqueue gets signals before SIG_IGN, but does not remove them from pending (unlike sigwait) signal(sig, SIG_IGN); } } @@ -657,6 +687,14 @@ void trigger_profile_peek(void) jl_safe_printf("\n======================================================================================\n"); jl_safe_printf("Information request received. A stacktrace will print followed by a %.1f second profile\n", profile_peek_duration); jl_safe_printf("======================================================================================\n"); + if (bt_size_max == 0){ + // If the buffer hasn't been initialized, initialize with default size + // Keep these values synchronized with Profile.default_init() + if (jl_profile_init(10000000, 1000000) == -1) { + jl_safe_printf("ERROR: could not initialize the profile buffer"); + return; + } + } bt_size_cur = 0; // clear profile buffer if (jl_profile_start_timer() < 0) jl_safe_printf("ERROR: Could not start profile timer\n"); @@ -681,20 +719,13 @@ static void *signal_listener(void *arg) perror("signal kqueue"); } else { - kqueue_signal(&sigqueue, &ev, SIGINT); - kqueue_signal(&sigqueue, &ev, SIGTERM); - kqueue_signal(&sigqueue, &ev, SIGABRT); - kqueue_signal(&sigqueue, &ev, SIGQUIT); -#ifdef SIGINFO - kqueue_signal(&sigqueue, &ev, SIGINFO); -#else - kqueue_signal(&sigqueue, &ev, SIGUSR1); -#endif -#if defined(HAVE_TIMER) - kqueue_signal(&sigqueue, &ev, SIGUSR1); -#elif defined(HAVE_ITIMER) - kqueue_signal(&sigqueue, &ev, SIGPROF); -#endif + for (const int *sig = sigwait_sigs; *sig; sig++) + kqueue_signal(&sigqueue, &ev, *sig); + if (sigqueue == -1) { + // re-enable sigwait for these + for (const int *sig = sigwait_sigs; *sig; sig++) + signal(*sig, SIG_DFL); + } } #endif while (1) { @@ -711,6 +742,8 @@ static void *signal_listener(void *arg) if (nevents != 1) { close(sigqueue); sigqueue = -1; + for (const int *sig = sigwait_sigs; *sig; sig++) + signal(*sig, SIG_DFL); continue; } sig = ev.ident; @@ -732,13 +765,11 @@ static void *signal_listener(void *arg) #ifndef HAVE_MACH #if defined(HAVE_TIMER) profile = (sig == SIGUSR1); -#if _POSIX_C_SOURCE >= 199309L +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L if (profile && !(info.si_code == SI_TIMER && - info.si_value.sival_ptr == &timerprof)) + info.si_value.sival_ptr == &timerprof)) profile = 0; #endif -#elif defined(HAVE_ITIMER) - profile = (sig == SIGPROF); #endif #endif @@ -776,12 +807,37 @@ static void *signal_listener(void *arg) } #else if (sig == SIGUSR1) { - if (running != 1) + if (running != 1 && timer_graceperiod_elapsed()) trigger_profile_peek(); doexit = 0; } #endif + if (doexit) { + // The exit can get stuck if it happens at an unfortunate spot in thread 0 + // (unavoidable due to its async nature). + // Try much harder to exit next time, if we get multiple exit requests. + // 1. unblock the signal, so this thread can be killed by it + // 2. reset the tty next, because we might die before we get another chance to do that + // 3. attempt a graceful cleanup of julia, followed by an abrupt end to the C runtime (except for fflush) + // 4. kill this thread with `raise`, to preserve the signo / exit code / and coredump configuration + // Similar to jl_raise, but a slightly different order of operations + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, sig); + pthread_sigmask(SIG_UNBLOCK, &sset, NULL); +#ifdef HAVE_KEVENT + signal(sig, SIG_DFL); +#endif + uv_tty_reset_mode(); + thread0_exit_count++; + fflush(NULL); + if (thread0_exit_count > 1) { + raise(sig); // very unlikely to return + _exit(128 + sig); + } + } + int nthreads = jl_atomic_load_acquire(&jl_n_threads); bt_size = 0; #if !defined(JL_DISABLE_LIBUNWIND) unw_context_t *signal_context; @@ -789,13 +845,14 @@ static void *signal_listener(void *arg) // (so that thread zero gets notified last) if (critical || profile) { jl_lock_profile(); - if (!critical) - jl_shuffle_int_array_inplace(profile_round_robin_thread_order, jl_n_threads, &profile_cong_rng_seed); - for (int idx = jl_n_threads; idx-- > 0; ) { - // Stop the threads in the random round-robin order. - int i = critical ? idx : profile_round_robin_thread_order[idx]; + int *randperm; + if (profile) + randperm = profile_get_randperm(nthreads); + for (int idx = nthreads; idx-- > 0; ) { + // Stop the threads in the random or reverse round-robin order. + int i = profile ? randperm[idx] : idx; // notify thread to stop - jl_thread_suspend_and_get_state(i, &signal_context); + jl_thread_suspend_and_get_state(i, 1, &signal_context); if (signal_context == NULL) continue; @@ -803,7 +860,7 @@ static void *signal_listener(void *arg) // this part must be signal-handler safe if (critical) { bt_size += rec_backtrace_ctx(bt_data + bt_size, - JL_MAX_BT_SIZE / jl_n_threads - 1, + JL_MAX_BT_SIZE / nthreads - 1, signal_context, NULL); bt_data[bt_size++].uintptr = 0; } @@ -830,12 +887,12 @@ static void *signal_listener(void *arg) } jl_set_safe_restore(old_buf); - jl_ptls_t ptls2 = jl_all_tls_states[i]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[i]; // store threadid but add 1 as 0 is preserved to indicate end of block bt_data_prof[bt_size_cur++].uintptr = ptls2->tid + 1; - // store task id + // store task id (never null) bt_data_prof[bt_size_cur++].jlvalue = (jl_value_t*)jl_atomic_load_relaxed(&ptls2->current_task); // store cpu cycle clock @@ -860,8 +917,6 @@ static void *signal_listener(void *arg) jl_check_profile_autostop(); #if defined(HAVE_TIMER) timer_settime(timerprof, 0, &itsprof, NULL); -#elif defined(HAVE_ITIMER) - setitimer(ITIMER_PROF, &timerprof, NULL); #endif } #endif @@ -869,26 +924,29 @@ static void *signal_listener(void *arg) // this part is async with the running of the rest of the program // and must be thread-safe, but not necessarily signal-handler safe - if (critical) { - if (doexit) { - thread0_exit_count++; - jl_exit_thread0(128 + sig, bt_data, bt_size); + if (doexit) { +// // this is probably always SI_USER (0x10001 / 65537), so we suppress it +// int si_code = 0; +//#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L && !HAVE_KEVENT +// si_code = info.si_code; +//#endif + jl_exit_thread0(sig, bt_data, bt_size); + } + else if (critical) { + // critical in this case actually means SIGINFO request +#ifndef SIGINFO // SIGINFO already prints something similar automatically + int nrunning = 0; + for (int idx = nthreads; idx-- > 0; ) { + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[idx]; + nrunning += !jl_atomic_load_relaxed(&ptls2->sleep_check_state); } - else { -#ifndef SIGINFO // SIGINFO already prints this automatically - int nrunning = 0; - for (int idx = jl_n_threads; idx-- > 0; ) { - jl_ptls_t ptls2 = jl_all_tls_states[idx]; - nrunning += !jl_atomic_load_relaxed(&ptls2->sleep_check_state); - } - jl_safe_printf("\ncmd: %s %d running %d of %d\n", jl_options.julia_bin ? jl_options.julia_bin : "julia", jl_getpid(), nrunning, jl_n_threads); + jl_safe_printf("\ncmd: %s %d running %d of %d\n", jl_options.julia_bin ? jl_options.julia_bin : "julia", uv_os_getpid(), nrunning, nthreads); #endif - jl_safe_printf("\nsignal (%d): %s\n", sig, strsignal(sig)); - size_t i; - for (i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { - jl_print_bt_entry_codeloc(bt_data + i); - } + jl_safe_printf("\nsignal (%d): %s\n", sig, strsignal(sig)); + size_t i; + for (i = 0; i < bt_size; i += jl_bt_entry_size(bt_data + i)) { + jl_print_bt_entry_codeloc(bt_data + i); } } } @@ -902,7 +960,7 @@ void restore_signals(void) sigset_t sset; jl_sigsetset(&sset); - sigprocmask(SIG_SETMASK, &sset, 0); + pthread_sigmask(SIG_SETMASK, &sset, 0); #if !defined(HAVE_MACH) && !defined(JL_DISABLE_LIBUNWIND) if (pthread_mutex_init(&in_signal_lock, NULL) != 0 || @@ -925,7 +983,7 @@ static void fpe_handler(int sig, siginfo_t *info, void *context) return; } jl_task_t *ct = jl_get_current_task(); - if (ct == NULL) // exception on foreign thread is fatal + if (ct == NULL || ct->eh == NULL) // exception on foreign thread is fatal sigdie_handler(sig, info, context); else jl_throw_in_ctx(ct, jl_diverror_exception, sig, context); @@ -992,11 +1050,6 @@ void jl_install_default_signal_handlers(void) } // need to ensure the following signals are not SIG_IGN, even though they will be blocked act_die.sa_flags = SA_SIGINFO | SA_RESTART | SA_RESETHAND; -#if defined(HAVE_ITIMER) - if (sigaction(SIGPROF, &act_die, NULL) < 0) { - jl_errorf("fatal error: sigaction: %s", strerror(errno)); - } -#endif #ifdef SIGINFO if (sigaction(SIGINFO, &act_die, NULL) < 0) { jl_errorf("fatal error: sigaction: %s", strerror(errno)); diff --git a/src/signals-win.c b/src/signals-win.c index 178a7463b8d50..5dd6b34558ca6 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -85,14 +85,16 @@ void __cdecl crt_sig_handler(int sig, int num) jl_try_throw_sigint(); } break; - default: // SIGSEGV, (SSIGTERM, IGILL) - if (jl_get_safe_restore()) - jl_rethrow(); + default: // SIGSEGV, SIGTERM, SIGILL, SIGABRT + if (sig == SIGSEGV && jl_get_safe_restore()) { + signal(sig, (void (__cdecl *)(int))crt_sig_handler); + jl_sig_throw(); + } memset(&Context, 0, sizeof(Context)); RtlCaptureContext(&Context); if (sig == SIGILL) jl_show_sigill(&Context); - jl_critical_error(sig, &Context, jl_get_current_task()); + jl_critical_error(sig, 0, &Context, jl_get_current_task()); raise(sig); } } @@ -165,7 +167,7 @@ HANDLE hMainThread = INVALID_HANDLE_VALUE; // Try to throw the exception in the master thread. static void jl_try_deliver_sigint(void) { - jl_ptls_t ptls2 = jl_all_tls_states[0]; + jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; jl_lock_profile(); jl_safepoint_enable_sigint(); jl_wake_libuv(); @@ -226,102 +228,113 @@ static BOOL WINAPI sigint_handler(DWORD wsig) //This needs winapi types to guara LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo) { - jl_task_t *ct = jl_current_task; - jl_ptls_t ptls = ct->ptls; - if (ExceptionInfo->ExceptionRecord->ExceptionFlags == 0) { + if (ExceptionInfo->ExceptionRecord->ExceptionFlags != 0) + return EXCEPTION_CONTINUE_SEARCH; + jl_task_t *ct = jl_get_current_task(); + if (ct != NULL && ct->ptls != NULL && ct->ptls->gc_state != JL_GC_STATE_WAITING) { + jl_ptls_t ptls = ct->ptls; switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { - case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + if (ct->eh != NULL) { fpreset(); jl_throw_in_ctx(jl_diverror_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; - case EXCEPTION_STACK_OVERFLOW: + } + break; + case EXCEPTION_STACK_OVERFLOW: + if (ct->eh != NULL) { ptls->needs_resetstkoflw = 1; jl_throw_in_ctx(jl_stackovf_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; - case EXCEPTION_ACCESS_VIOLATION: - if (jl_addr_is_safepoint(ExceptionInfo->ExceptionRecord->ExceptionInformation[1])) { - jl_set_gc_and_wait(); - // Do not raise sigint on worker thread - if (ptls->tid != 0) - return EXCEPTION_CONTINUE_EXECUTION; - if (ptls->defer_signal) { - jl_safepoint_defer_sigint(); - } - else if (jl_safepoint_consume_sigint()) { - jl_clear_force_sigint(); - jl_throw_in_ctx(jl_interrupt_exception, ExceptionInfo->ContextRecord); - } + } + break; + case EXCEPTION_ACCESS_VIOLATION: + if (jl_addr_is_safepoint(ExceptionInfo->ExceptionRecord->ExceptionInformation[1])) { + jl_set_gc_and_wait(); + // Do not raise sigint on worker thread + if (ptls->tid != 0) return EXCEPTION_CONTINUE_EXECUTION; + if (ptls->defer_signal) { + jl_safepoint_defer_sigint(); } - if (jl_get_safe_restore()) { - jl_throw_in_ctx(NULL, ExceptionInfo->ContextRecord); - return EXCEPTION_CONTINUE_EXECUTION; + else if (jl_safepoint_consume_sigint()) { + jl_clear_force_sigint(); + jl_throw_in_ctx(jl_interrupt_exception, ExceptionInfo->ContextRecord); } + return EXCEPTION_CONTINUE_EXECUTION; + } + if (jl_get_safe_restore()) { + jl_throw_in_ctx(NULL, ExceptionInfo->ContextRecord); + return EXCEPTION_CONTINUE_EXECUTION; + } + if (ct->eh != NULL) { if (ExceptionInfo->ExceptionRecord->ExceptionInformation[0] == 1) { // writing to read-only memory (e.g. mmap) jl_throw_in_ctx(jl_readonlymemory_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; } + } + default: + break; } - if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { - jl_safe_printf("\n"); - jl_show_sigill(ExceptionInfo->ContextRecord); - } - jl_safe_printf("\nPlease submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.\nException: "); - switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { - case EXCEPTION_ACCESS_VIOLATION: - jl_safe_printf("EXCEPTION_ACCESS_VIOLATION"); break; - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - jl_safe_printf("EXCEPTION_ARRAY_BOUNDS_EXCEEDED"); break; - case EXCEPTION_BREAKPOINT: - jl_safe_printf("EXCEPTION_BREAKPOINT"); break; - case EXCEPTION_DATATYPE_MISALIGNMENT: - jl_safe_printf("EXCEPTION_DATATYPE_MISALIGNMENT"); break; - case EXCEPTION_FLT_DENORMAL_OPERAND: - jl_safe_printf("EXCEPTION_FLT_DENORMAL_OPERAND"); break; - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - jl_safe_printf("EXCEPTION_FLT_DIVIDE_BY_ZERO"); break; - case EXCEPTION_FLT_INEXACT_RESULT: - jl_safe_printf("EXCEPTION_FLT_INEXACT_RESULT"); break; - case EXCEPTION_FLT_INVALID_OPERATION: - jl_safe_printf("EXCEPTION_FLT_INVALID_OPERATION"); break; - case EXCEPTION_FLT_OVERFLOW: - jl_safe_printf("EXCEPTION_FLT_OVERFLOW"); break; - case EXCEPTION_FLT_STACK_CHECK: - jl_safe_printf("EXCEPTION_FLT_STACK_CHECK"); break; - case EXCEPTION_FLT_UNDERFLOW: - jl_safe_printf("EXCEPTION_FLT_UNDERFLOW"); break; - case EXCEPTION_ILLEGAL_INSTRUCTION: - jl_safe_printf("EXCEPTION_ILLEGAL_INSTRUCTION"); break; - case EXCEPTION_IN_PAGE_ERROR: - jl_safe_printf("EXCEPTION_IN_PAGE_ERROR"); break; - case EXCEPTION_INT_DIVIDE_BY_ZERO: - jl_safe_printf("EXCEPTION_INT_DIVIDE_BY_ZERO"); break; - case EXCEPTION_INT_OVERFLOW: - jl_safe_printf("EXCEPTION_INT_OVERFLOW"); break; - case EXCEPTION_INVALID_DISPOSITION: - jl_safe_printf("EXCEPTION_INVALID_DISPOSITION"); break; - case EXCEPTION_NONCONTINUABLE_EXCEPTION: - jl_safe_printf("EXCEPTION_NONCONTINUABLE_EXCEPTION"); break; - case EXCEPTION_PRIV_INSTRUCTION: - jl_safe_printf("EXCEPTION_PRIV_INSTRUCTION"); break; - case EXCEPTION_SINGLE_STEP: - jl_safe_printf("EXCEPTION_SINGLE_STEP"); break; - case EXCEPTION_STACK_OVERFLOW: - jl_safe_printf("EXCEPTION_STACK_OVERFLOW"); break; - default: - jl_safe_printf("UNKNOWN"); break; - } - jl_safe_printf(" at 0x%Ix -- ", (size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress); - jl_print_native_codeloc((uintptr_t)ExceptionInfo->ExceptionRecord->ExceptionAddress); - - jl_critical_error(0, ExceptionInfo->ContextRecord, ct); - static int recursion = 0; - if (recursion++) - exit(1); - else - jl_exit(1); } - return EXCEPTION_CONTINUE_SEARCH; + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { + jl_safe_printf("\n"); + jl_show_sigill(ExceptionInfo->ContextRecord); + } + jl_safe_printf("\nPlease submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.\nException: "); + switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: + jl_safe_printf("EXCEPTION_ACCESS_VIOLATION"); break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + jl_safe_printf("EXCEPTION_ARRAY_BOUNDS_EXCEEDED"); break; + case EXCEPTION_BREAKPOINT: + jl_safe_printf("EXCEPTION_BREAKPOINT"); break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + jl_safe_printf("EXCEPTION_DATATYPE_MISALIGNMENT"); break; + case EXCEPTION_FLT_DENORMAL_OPERAND: + jl_safe_printf("EXCEPTION_FLT_DENORMAL_OPERAND"); break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + jl_safe_printf("EXCEPTION_FLT_DIVIDE_BY_ZERO"); break; + case EXCEPTION_FLT_INEXACT_RESULT: + jl_safe_printf("EXCEPTION_FLT_INEXACT_RESULT"); break; + case EXCEPTION_FLT_INVALID_OPERATION: + jl_safe_printf("EXCEPTION_FLT_INVALID_OPERATION"); break; + case EXCEPTION_FLT_OVERFLOW: + jl_safe_printf("EXCEPTION_FLT_OVERFLOW"); break; + case EXCEPTION_FLT_STACK_CHECK: + jl_safe_printf("EXCEPTION_FLT_STACK_CHECK"); break; + case EXCEPTION_FLT_UNDERFLOW: + jl_safe_printf("EXCEPTION_FLT_UNDERFLOW"); break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + jl_safe_printf("EXCEPTION_ILLEGAL_INSTRUCTION"); break; + case EXCEPTION_IN_PAGE_ERROR: + jl_safe_printf("EXCEPTION_IN_PAGE_ERROR"); break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + jl_safe_printf("EXCEPTION_INT_DIVIDE_BY_ZERO"); break; + case EXCEPTION_INT_OVERFLOW: + jl_safe_printf("EXCEPTION_INT_OVERFLOW"); break; + case EXCEPTION_INVALID_DISPOSITION: + jl_safe_printf("EXCEPTION_INVALID_DISPOSITION"); break; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + jl_safe_printf("EXCEPTION_NONCONTINUABLE_EXCEPTION"); break; + case EXCEPTION_PRIV_INSTRUCTION: + jl_safe_printf("EXCEPTION_PRIV_INSTRUCTION"); break; + case EXCEPTION_SINGLE_STEP: + jl_safe_printf("EXCEPTION_SINGLE_STEP"); break; + case EXCEPTION_STACK_OVERFLOW: + jl_safe_printf("EXCEPTION_STACK_OVERFLOW"); break; + default: + jl_safe_printf("UNKNOWN"); break; + } + jl_safe_printf(" at 0x%Ix -- ", (size_t)ExceptionInfo->ExceptionRecord->ExceptionAddress); + jl_print_native_codeloc((uintptr_t)ExceptionInfo->ExceptionRecord->ExceptionAddress); + + jl_critical_error(0, 0, ExceptionInfo->ContextRecord, ct); + static int recursion = 0; + if (recursion++) + exit(1); + else + jl_exit(1); } JL_DLLEXPORT void jl_install_sigint_handler(void) @@ -362,12 +375,12 @@ static DWORD WINAPI profile_bt( LPVOID lparam ) bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)bt_data_prof + bt_size_cur, bt_size_max - bt_size_cur - 1, &ctxThread, NULL); - jl_ptls_t ptls = jl_all_tls_states[0]; // given only profiling hMainThread + jl_ptls_t ptls = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; // given only profiling hMainThread // store threadid but add 1 as 0 is preserved to indicate end of block bt_data_prof[bt_size_cur++].uintptr = ptls->tid + 1; - // store task id + // store task id (never null) bt_data_prof[bt_size_cur++].jlvalue = (jl_value_t*)jl_atomic_load_relaxed(&ptls->current_task); // store cpu cycle clock @@ -470,11 +483,15 @@ void jl_install_default_signal_handlers(void) void jl_install_thread_signal_handler(jl_ptls_t ptls) { - size_t ssize = sig_stack_size; - void *stk = jl_malloc_stack(&ssize, NULL); - collect_backtrace_fiber.uc_stack.ss_sp = (void*)stk; - collect_backtrace_fiber.uc_stack.ss_size = ssize; - jl_makecontext(&collect_backtrace_fiber, start_backtrace_fiber); - uv_mutex_init(&backtrace_lock); - have_backtrace_fiber = 1; + if (!have_backtrace_fiber) { + size_t ssize = sig_stack_size; + void *stk = jl_malloc_stack(&ssize, NULL); + if (stk == NULL) + jl_errorf("fatal error allocating signal stack: mmap: %s", strerror(errno)); + collect_backtrace_fiber.uc_stack.ss_sp = (void*)stk; + collect_backtrace_fiber.uc_stack.ss_size = ssize; + jl_makecontext(&collect_backtrace_fiber, start_backtrace_fiber); + uv_mutex_init(&backtrace_lock); + have_backtrace_fiber = 1; + } } diff --git a/src/simplevector.c b/src/simplevector.c index 988cf18ccc9b6..65217715ae55f 100644 --- a/src/simplevector.c +++ b/src/simplevector.c @@ -23,6 +23,7 @@ jl_svec_t *(jl_perm_symsvec)(size_t n, ...) { if (n == 0) return jl_emptysvec; jl_svec_t *jv = (jl_svec_t*)jl_gc_permobj((n + 1) * sizeof(void*), jl_simplevector_type); + jl_set_typetagof(jv, jl_simplevector_tag, jl_astaggedvalue(jv)->bits.gc); jl_svec_set_len_unsafe(jv, n); va_list args; va_start(args, n); @@ -37,6 +38,7 @@ JL_DLLEXPORT jl_svec_t *jl_svec1(void *a) jl_task_t *ct = jl_current_task; jl_svec_t *v = (jl_svec_t*)jl_gc_alloc(ct->ptls, sizeof(void*) * 2, jl_simplevector_type); + jl_set_typetagof(v, jl_simplevector_tag, 0); jl_svec_set_len_unsafe(v, 1); jl_svec_data(v)[0] = (jl_value_t*)a; return v; @@ -47,6 +49,7 @@ JL_DLLEXPORT jl_svec_t *jl_svec2(void *a, void *b) jl_task_t *ct = jl_current_task; jl_svec_t *v = (jl_svec_t*)jl_gc_alloc(ct->ptls, sizeof(void*) * 3, jl_simplevector_type); + jl_set_typetagof(v, jl_simplevector_tag, 0); jl_svec_set_len_unsafe(v, 2); jl_svec_data(v)[0] = (jl_value_t*)a; jl_svec_data(v)[1] = (jl_value_t*)b; @@ -59,6 +62,7 @@ JL_DLLEXPORT jl_svec_t *jl_alloc_svec_uninit(size_t n) if (n == 0) return jl_emptysvec; jl_svec_t *jv = (jl_svec_t*)jl_gc_alloc(ct->ptls, (n + 1) * sizeof(void*), jl_simplevector_type); + jl_set_typetagof(jv, jl_simplevector_tag, 0); jl_svec_set_len_unsafe(jv, n); return jv; } @@ -93,15 +97,9 @@ JL_DLLEXPORT size_t (jl_svec_len)(jl_svec_t *t) JL_NOTSAFEPOINT return jl_svec_len(t); } -JL_DLLEXPORT int8_t jl_svec_isassigned(jl_svec_t *t JL_PROPAGATES_ROOT, ssize_t i) JL_NOTSAFEPOINT -{ - return jl_svecref(t, (size_t)i) != NULL; -} - 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); - if (__unlikely(v == NULL)) - jl_throw(jl_undefref_exception); + assert(v != NULL); return v; } diff --git a/src/smallintset.c b/src/smallintset.c index 54fdad616a758..fa647b57e7d3e 100644 --- a/src/smallintset.c +++ b/src/smallintset.c @@ -13,6 +13,13 @@ #define max_probe(size) ((size) <= 1024 ? 16 : (size) >> 6) #define h2index(hv, sz) (size_t)((hv) & ((sz)-1)) +// a set of small positive integers representing the indices into another set +// (or dict) where the hash is derived from the keys in the set via the lambdas +// `hash` and `eq` supports concurrent calls to jl_smallintset_lookup (giving +// acquire ordering), provided that a lock is held over calls to +// smallintset_rehash, and the elements of `data` support release-consume +// atomics. + #ifdef __cplusplus extern "C" { #endif @@ -21,24 +28,37 @@ static inline size_t jl_intref(const jl_array_t *arr, size_t idx) JL_NOTSAFEPOIN { jl_value_t *el = jl_tparam0(jl_typeof(arr)); if (el == (jl_value_t*)jl_uint8_type) - return ((uint8_t*)jl_array_data(arr))[idx]; + 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]); + else + abort(); +} + +static inline size_t jl_intref_acquire(const jl_array_t *arr, size_t idx) 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 ((uint16_t*)jl_array_data(arr))[idx]; + return jl_atomic_load_acquire(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx]); else if (el == (jl_value_t*)jl_uint32_type) - return ((uint32_t*)jl_array_data(arr))[idx]; + return jl_atomic_load_acquire(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx]); else abort(); } -static inline void jl_intset(const jl_array_t *arr, size_t idx, size_t val) JL_NOTSAFEPOINT +static inline void jl_intset_release(const jl_array_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) - ((uint8_t*)jl_array_data(arr))[idx] = val; + jl_atomic_store_release(&((_Atomic(uint8_t)*)jl_array_data(arr))[idx], val); else if (el == (jl_value_t*)jl_uint16_type) - ((uint16_t*)jl_array_data(arr))[idx] = val; + jl_atomic_store_release(&((_Atomic(uint16_t)*)jl_array_data(arr))[idx], val); else if (el == (jl_value_t*)jl_uint32_type) - ((uint32_t*)jl_array_data(arr))[idx] = val; + jl_atomic_store_release(&((_Atomic(uint32_t)*)jl_array_data(arr))[idx], val); else abort(); } @@ -93,7 +113,7 @@ ssize_t jl_smallintset_lookup(jl_array_t *cache, smallintset_eq eq, const void * size_t orig = index; size_t iter = 0; do { - size_t val1 = jl_intref(cache, index); + size_t val1 = jl_intref_acquire(cache, index); if (val1 == 0) { JL_GC_POP(); return -1; @@ -121,7 +141,7 @@ static int smallintset_insert_(jl_array_t *a, uint_t hv, size_t val1) size_t maxprobe = max_probe(sz); do { if (jl_intref(a, index) == 0) { - jl_intset(a, index, val1); + jl_intset_release(a, index, val1); return 1; } index = (index + 1) & (sz - 1); diff --git a/src/stackwalk.c b/src/stackwalk.c index 766e318a46b7b..18bf4b2126938 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -214,10 +214,10 @@ NOINLINE size_t rec_backtrace(jl_bt_element_t *bt_data, size_t maxsize, int skip int r = jl_unw_get(&context); if (r < 0) return 0; - jl_gcframe_t *pgcstack = jl_pgcstack; bt_cursor_t cursor; - if (!jl_unw_init(&cursor, &context)) + if (!jl_unw_init(&cursor, &context) || maxsize == 0) return 0; + jl_gcframe_t *pgcstack = jl_pgcstack; size_t bt_size = 0; jl_unw_stepn(&cursor, bt_data, &bt_size, NULL, maxsize, skip + 1, &pgcstack, 0); return bt_size; @@ -321,6 +321,7 @@ static void decode_backtrace(jl_bt_element_t *bt_data, size_t bt_size, JL_DLLEXPORT jl_value_t *jl_get_backtrace(void) { + JL_TIMING(STACKWALK, STACKWALK_Backtrace); jl_excstack_t *s = jl_current_task->excstack; jl_bt_element_t *bt_data = NULL; size_t bt_size = 0; @@ -343,6 +344,7 @@ JL_DLLEXPORT jl_value_t *jl_get_backtrace(void) JL_DLLEXPORT jl_value_t *jl_get_excstack(jl_task_t* task, int include_bt, int max_entries) { JL_TYPECHK(current_exceptions, task, (jl_value_t*)task); + JL_TIMING(STACKWALK, STACKWALK_Excstack); jl_task_t *ct = jl_current_task; if (task != ct && jl_atomic_load_relaxed(&task->_state) == JL_TASK_STATE_RUNNABLE) { jl_error("Inspecting the exception stack of a task which might " @@ -661,7 +663,7 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT jl_value_t *code = jl_bt_entry_jlvalue(bt_entry, 0); if (jl_is_method_instance(code)) { // When interpreting a method instance, need to unwrap to find the code info - code = ((jl_method_instance_t*)code)->uninferred; + 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; @@ -671,7 +673,7 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT while (debuginfoloc != 0) { jl_line_info_node_t *locinfo = (jl_line_info_node_t*) jl_array_ptr_ref(src->linetable, debuginfoloc - 1); - assert(jl_typeis(locinfo, jl_lineinfonode_type)); + assert(jl_typetagis(locinfo, jl_lineinfonode_type)); const char *func_name = "Unknown"; jl_value_t *method = locinfo->method; if (jl_is_method_instance(method)) @@ -697,9 +699,164 @@ void jl_print_bt_entry_codeloc(jl_bt_element_t *bt_entry) JL_NOTSAFEPOINT } } + +#ifdef _OS_LINUX_ +#if defined(__GLIBC__) && defined(_CPU_AARCH64_) +#define LONG_JMP_SP_ENV_SLOT 13 +static uintptr_t julia_longjmp_xor_key; +// GLIBC mangles the function pointers in jmp_buf (used in {set,long}*jmp +// functions) by XORing them with a random key. For AArch64 it is a global +// variable rather than a TCB one (as for x86_64/powerpc). We obtain the key by +// issuing a setjmp and XORing the SP pointer values to derive the key. +static void JuliaInitializeLongjmpXorKey(void) +{ + // 1. Call REAL(setjmp), which stores the mangled SP in env. + jmp_buf env; + _setjmp(env); + + // 2. Retrieve vanilla/mangled SP. + uintptr_t sp; + asm("mov %0, sp" : "=r" (sp)); + uintptr_t mangled_sp = ((uintptr_t*)&env)[LONG_JMP_SP_ENV_SLOT]; + + // 3. xor SPs to obtain key. + julia_longjmp_xor_key = mangled_sp ^ sp; +} +#endif + +JL_UNUSED static uintptr_t ptr_demangle(uintptr_t p) JL_NOTSAFEPOINT +{ +#if defined(__GLIBC__) +#if defined(_CPU_X86_) +// from https://github.com/bminor/glibc/blame/master/sysdeps/unix/sysv/linux/i386/sysdep.h +// last changed for GLIBC_2.6 on 2007-02-01 + asm(" rorl $9, %0\n" + " xorl %%gs:0x18, %0" + : "=r"(p) : "0"(p) : ); +#elif defined(_CPU_X86_64_) +// from https://github.com/bminor/glibc/blame/master/sysdeps/unix/sysv/linux/i386/sysdep.h + asm(" rorq $17, %0\n" + " xorq %%fs:0x30, %0" + : "=r"(p) : "0"(p) : ); +#elif defined(_CPU_AARCH64_) +// from https://github.com/bminor/glibc/blame/master/sysdeps/unix/sysv/linux/aarch64/sysdep.h +// We need to use a trick like this (from GCC/LLVM TSAN) to get access to it: +// https://github.com/llvm/llvm-project/commit/daa3ebce283a753f280c549cdb103fbb2972f08e + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, &JuliaInitializeLongjmpXorKey); + p ^= julia_longjmp_xor_key; +#elif defined(_CPU_ARM_) +// from https://github.com/bminor/glibc/blame/master/sysdeps/unix/sysv/linux/arm/sysdep.h + ; // nothing to do +#endif +#endif + return p; +} +#endif + +// n.b. musl does not mangle pointers, but intentionally makes that impossible +// to determine (https://www.openwall.com/lists/musl/2013/03/29/13) so we do +// not support musl here. + +// n.b. We have not looked at other libc (e.g. ulibc), though they are probably +// often compatible with glibc (perhaps with or without pointer mangling). + + +#ifdef _OS_DARWIN_ +// from https://github.com/apple/darwin-xnu/blame/main/libsyscall/os/tsd.h +#define __TSD_PTR_MUNGE 7 + +#if defined(__i386__) || defined(__x86_64__) + +#if defined(__has_attribute) +#if __has_attribute(address_space) +#define OS_GS_RELATIVE __attribute__((address_space(256))) +#endif +#endif + +#ifdef OS_GS_RELATIVE +#define _os_tsd_get_base() ((void * OS_GS_RELATIVE *)0) +#else +__attribute__((always_inline)) +static __inline__ void* +_os_tsd_get_direct(unsigned long slot) +{ + void *ret; + __asm__("mov %%gs:%1, %0" : "=r" (ret) : "m" (*(void **)(slot * sizeof(void *)))); + return ret; +} +#endif + +#elif defined(__arm__) || defined(__arm64__) +// 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) { + // 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, + // so will do nothing when it is not enabled or not available. + uint64_t ret; + asm volatile( + "mov x30, %1\n\t" + "hint #7\n\t" // xpaclri + "mov %0, x30\n\t" + : "=r"(ret) + : "r"(__value) + : "x30"); + return ret; +} + +__attribute__((always_inline, pure)) +static __inline__ void** +_os_tsd_get_base(void) +{ +#if defined(__arm__) + uintptr_t tsd; + __asm__("mrc p15, 0, %0, c13, c0, 3\n" + "bic %0, %0, #0x3\n" : "=r" (tsd)); + /* lower 2-bits contain CPU number */ +#elif defined(__arm64__) + uint64_t tsd; + __asm__("mrs %0, TPIDRRO_EL0\n" + "bic %0, %0, #0x7\n" : "=r" (tsd)); + /* lower 3-bits contain CPU number */ +#endif + + return (void**)(uintptr_t)tsd; +} +#define _os_tsd_get_base() _os_tsd_get_base() +#endif + +#ifdef _os_tsd_get_base +__attribute__((always_inline)) +static __inline__ void* +_os_tsd_get_direct(unsigned long slot) +{ + return _os_tsd_get_base()[slot]; +} +#endif + +__attribute__((always_inline, pure)) +static __inline__ uintptr_t +_os_ptr_munge_token(void) +{ + 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) +{ + return ptr ^ _os_ptr_munge_token(); +} +#define _OS_PTR_UNMUNGE(_ptr) _os_ptr_munge((uintptr_t)(_ptr)) +#endif + + extern bt_context_t *jl_to_bt_context(void *sigctx); -void jl_rec_backtrace(jl_task_t *t) +void jl_rec_backtrace(jl_task_t *t) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; @@ -740,7 +897,185 @@ void jl_rec_backtrace(jl_task_t *t) context = &t->ctx.ctx; #elif defined(JL_HAVE_UCONTEXT) context = jl_to_bt_context(&t->ctx.ctx); +#elif defined(JL_HAVE_ASM) + bt_context_t c; + memset(&c, 0, sizeof(c)); + #if defined(_OS_LINUX_) && defined(__GLIBC__) + __jmp_buf *mctx = &t->ctx.ctx.uc_mcontext->__jmpbuf; + mcontext_t *mc = &c.uc_mcontext; + #if defined(_CPU_X86_) + // https://github.com/bminor/glibc/blame/master/sysdeps/i386/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/i386/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/i386/longjmp.s + mc->gregs[REG_EBX] = (*mctx)[0]; + mc->gregs[REG_ESI] = (*mctx)[1]; + mc->gregs[REG_EDI] = (*mctx)[2]; + mc->gregs[REG_EBP] = (*mctx)[3]; + mc->gregs[REG_ESP] = (*mctx)[4]; + mc->gregs[REG_EIP] = (*mctx)[5]; + // ifdef PTR_DEMANGLE ? + mc->gregs[REG_ESP] = ptr_demangle(mc->gregs[REG_ESP]); + mc->gregs[REG_EIP] = ptr_demangle(mc->gregs[REG_EIP]); + context = &c; + #elif defined(_CPU_X86_64_) + // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/x86_64/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/x86_64/setjmp.s + mc->gregs[REG_RBX] = (*mctx)[0]; + mc->gregs[REG_RBP] = (*mctx)[1]; + mc->gregs[REG_R12] = (*mctx)[2]; + mc->gregs[REG_R13] = (*mctx)[3]; + mc->gregs[REG_R14] = (*mctx)[4]; + mc->gregs[REG_R15] = (*mctx)[5]; + mc->gregs[REG_RSP] = (*mctx)[6]; + mc->gregs[REG_RIP] = (*mctx)[7]; + // ifdef PTR_DEMANGLE ? + mc->gregs[REG_RBP] = ptr_demangle(mc->gregs[REG_RBP]); + mc->gregs[REG_RSP] = ptr_demangle(mc->gregs[REG_RSP]); + mc->gregs[REG_RIP] = ptr_demangle(mc->gregs[REG_RIP]); + context = &c; + #elif defined(_CPU_ARM_) + // https://github.com/bminor/glibc/blame/master/sysdeps/arm/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/arm/include/bits/setjmp.h + // https://github.com/bminor/musl/blame/master/src/setjmp/arm/longjmp.S + mc->arm_sp = (*mctx)[0]; + mc->arm_lr = (*mctx)[1]; + mc->arm_r4 = (*mctx)[2]; // aka v1 + mc->arm_r5 = (*mctx)[3]; // aka v2 + mc->arm_r6 = (*mctx)[4]; // aka v3 + mc->arm_r7 = (*mctx)[5]; // aka v4 + mc->arm_r8 = (*mctx)[6]; // aka v5 + mc->arm_r9 = (*mctx)[7]; // aka v6 aka sb + mc->arm_r10 = (*mctx)[8]; // aka v7 aka sl + mc->arm_fp = (*mctx)[10]; // aka v8 aka r11 + // ifdef PTR_DEMANGLE ? + mc->arm_sp = ptr_demangle(mc->arm_sp); + mc->arm_lr = ptr_demangle(mc->arm_lr); + mc->arm_pc = mc->arm_lr; + context = &c; + #elif defined(_CPU_AARCH64_) + // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/__longjmp.S + // https://github.com/bminor/glibc/blame/master/sysdeps/aarch64/jmpbuf-offsets.h + // https://github.com/bminor/musl/blame/master/src/setjmp/aarch64/longjmp.s + // https://github.com/libunwind/libunwind/blob/ec171c9ba7ea3abb2a1383cee2988a7abd483a1f/src/aarch64/unwind_i.h#L62 + unw_fpsimd_context_t *mcfp = (unw_fpsimd_context_t*)&mc->__reserved; + mc->regs[19] = (*mctx)[0]; + mc->regs[20] = (*mctx)[1]; + mc->regs[21] = (*mctx)[2]; + mc->regs[22] = (*mctx)[3]; + mc->regs[23] = (*mctx)[4]; + mc->regs[24] = (*mctx)[5]; + mc->regs[25] = (*mctx)[6]; + mc->regs[26] = (*mctx)[7]; + mc->regs[27] = (*mctx)[8]; + mc->regs[28] = (*mctx)[9]; + mc->regs[29] = (*mctx)[10]; // aka fp + mc->regs[30] = (*mctx)[11]; // aka lr + // Yes, they did skip 12 why writing the code originally; and, no, I do not know why. + mc->sp = (*mctx)[13]; + mcfp->vregs[7] = (*mctx)[14]; // aka d8 + mcfp->vregs[8] = (*mctx)[15]; // aka d9 + mcfp->vregs[9] = (*mctx)[16]; // aka d10 + mcfp->vregs[10] = (*mctx)[17]; // aka d11 + mcfp->vregs[11] = (*mctx)[18]; // aka d12 + mcfp->vregs[12] = (*mctx)[19]; // aka d13 + mcfp->vregs[13] = (*mctx)[20]; // aka d14 + mcfp->vregs[14] = (*mctx)[21]; // aka d15 + // ifdef PTR_DEMANGLE ? + mc->sp = ptr_demangle(mc->sp); + mc->regs[30] = ptr_demangle(mc->regs[30]); + mc->pc = mc->regs[30]; + context = &c; + #else + #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown linux") + (void)mc; + (void)c; + (void)mctx; + #endif + #elif defined(_OS_DARWIN_) + sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; + #if defined(_CPU_X86_64_) + // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/x86_64/_setjmp.s + x86_thread_state64_t *mc = (x86_thread_state64_t*)&c; + mc->__rbx = ((uint64_t*)mctx)[0]; + mc->__rbp = ((uint64_t*)mctx)[1]; + mc->__rsp = ((uint64_t*)mctx)[2]; + mc->__r12 = ((uint64_t*)mctx)[3]; + mc->__r13 = ((uint64_t*)mctx)[4]; + 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) + // 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); + mc->__rsp = _OS_PTR_UNMUNGE(mc->__rsp); + mc->__rip = _OS_PTR_UNMUNGE(mc->__rip); + context = &c; + #elif defined(_CPU_AARCH64_) + // from https://github.com/apple/darwin-libplatform/blob/main/src/setjmp/arm64/setjmp.s + // https://github.com/apple/darwin-xnu/blob/main/osfmk/mach/arm/_structs.h + // https://github.com/llvm/llvm-project/blob/7714e0317520207572168388f22012dd9e152e9e/libunwind/src/Registers.hpp -> Registers_arm64 + arm_thread_state64_t *mc = (arm_thread_state64_t*)&c; + mc->__x[19] = ((uint64_t*)mctx)[0]; + mc->__x[20] = ((uint64_t*)mctx)[1]; + mc->__x[21] = ((uint64_t*)mctx)[2]; + mc->__x[22] = ((uint64_t*)mctx)[3]; + mc->__x[23] = ((uint64_t*)mctx)[4]; + mc->__x[24] = ((uint64_t*)mctx)[5]; + mc->__x[25] = ((uint64_t*)mctx)[6]; + mc->__x[26] = ((uint64_t*)mctx)[7]; + mc->__x[27] = ((uint64_t*)mctx)[8]; + mc->__x[28] = ((uint64_t*)mctx)[9]; + mc->__x[10] = ((uint64_t*)mctx)[10]; + mc->__x[11] = ((uint64_t*)mctx)[11]; + mc->__x[12] = ((uint64_t*)mctx)[12]; + // 13 is reserved/unused + double *mcfp = (double*)&mc[1]; + mcfp[7] = ((uint64_t*)mctx)[14]; // aka d8 + mcfp[8] = ((uint64_t*)mctx)[15]; // aka d9 + mcfp[9] = ((uint64_t*)mctx)[16]; // aka d10 + mcfp[10] = ((uint64_t*)mctx)[17]; // aka d11 + mcfp[11] = ((uint64_t*)mctx)[18]; // aka d12 + mcfp[12] = ((uint64_t*)mctx)[19]; // aka d13 + mcfp[13] = ((uint64_t*)mctx)[20]; // aka d14 + mcfp[14] = ((uint64_t*)mctx)[21]; // aka d15 + mc->__fp = _OS_PTR_UNMUNGE(mc->__x[10]); + mc->__lr = _OS_PTR_UNMUNGE(mc->__x[11]); + mc->__x[12] = _OS_PTR_UNMUNGE(mc->__x[12]); + mc->__sp = mc->__x[12]; + // libunwind is broken for signed-pointers, but perhaps best not to leave the signed pointer lying around either + mc->__pc = ptrauth_strip(mc->__lr, 0); + mc->__pad = 0; // aka __ra_sign_state = not signed + context = &c; + #else + #pragma message("jl_rec_backtrace not defined for ASM/SETJMP on unknown darwin") + (void)mctx; + (void)c; + #endif + #elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_) + sigjmp_buf *mctx = &t->ctx.ctx.uc_mcontext; + mcontext_t *mc = &c.uc_mcontext; + // https://github.com/freebsd/freebsd-src/blob/releng/13.1/lib/libc/amd64/gen/_setjmp.S + mc->mc_rip = ((long*)mctx)[0]; + mc->mc_rbx = ((long*)mctx)[1]; + mc->mc_rsp = ((long*)mctx)[2]; + mc->mc_rbp = ((long*)mctx)[3]; + mc->mc_r12 = ((long*)mctx)[4]; + mc->mc_r13 = ((long*)mctx)[5]; + mc->mc_r14 = ((long*)mctx)[6]; + mc->mc_r15 = ((long*)mctx)[7]; + context = &c; + #else + #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 + #pragma message("jl_rec_backtrace not defined for unknown task system") #endif if (context) ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, context, t->gcstack); @@ -771,7 +1106,9 @@ JL_DLLEXPORT void jlbacktrace(void) JL_NOTSAFEPOINT jl_print_bt_entry_codeloc(bt_data + i); } } -JL_DLLEXPORT void jlbacktracet(jl_task_t *t) + +// Print backtrace for specified task +JL_DLLEXPORT void jlbacktracet(jl_task_t *t) JL_NOTSAFEPOINT { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; @@ -788,6 +1125,48 @@ JL_DLLEXPORT void jl_print_backtrace(void) JL_NOTSAFEPOINT jlbacktrace(); } +// Print backtraces for all live tasks, for all threads. +// WARNING: this is dangerous and can crash if used outside of gdb, if +// all of Julia's threads are not stopped! +JL_DLLEXPORT void jl_print_task_backtraces(int show_done) JL_NOTSAFEPOINT +{ + size_t nthreads = jl_atomic_load_acquire(&jl_n_threads); + jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); + for (size_t i = 0; i < nthreads; i++) { + jl_ptls_t ptls2 = allstates[i]; + arraylist_t *live_tasks = &ptls2->heap.live_tasks; + size_t n = live_tasks->len; + jl_safe_printf("==== Thread %d created %zu live tasks\n", + ptls2->tid + 1, n + 1); + jl_safe_printf(" ---- Root task (%p)\n", ptls2->root_task); + jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", + ptls2->root_task->sticky, ptls2->root_task->started, + jl_atomic_load_relaxed(&ptls2->root_task->_state), + jl_atomic_load_relaxed(&ptls2->root_task->tid) + 1); + jlbacktracet(ptls2->root_task); + + void **lst = live_tasks->items; + for (size_t j = 0; j < live_tasks->len; j++) { + jl_task_t *t = (jl_task_t *)lst[j]; + int t_state = jl_atomic_load_relaxed(&t->_state); + if (!show_done && t_state == JL_TASK_STATE_DONE) { + continue; + } + jl_safe_printf(" ---- Task %zu (%p)\n", j + 1, t); + jl_safe_printf(" (sticky: %d, started: %d, state: %d, tid: %d)\n", + t->sticky, t->started, t_state, + jl_atomic_load_relaxed(&t->tid) + 1); + if (t->stkbuf != NULL) + jlbacktracet(t); + else + jl_safe_printf(" no stack\n"); + jl_safe_printf(" ---- End task %zu\n", j + 1); + } + jl_safe_printf("==== End thread %d\n", ptls2->tid + 1); + } + jl_safe_printf("==== Done\n"); +} + #ifdef __cplusplus } #endif diff --git a/src/staticdata.c b/src/staticdata.c index 28a21e9ea7c2b..1728e0642551b 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -3,33 +3,24 @@ /* saving and restoring system images - This performs serialization and deserialization of in-memory data. The dump.c file is similar, but has less complete coverage: - dump.c has no knowledge of native code (and simply discards it), whereas this supports native code caching in .o files. - Duplication is avoided by elevating the .o-serialized versions of global variables and native-compiled functions to become - the authoritative source for such entities in the system image, with references to these objects appropriately inserted into - the (de)serialized version of Julia's internal data. This makes deserialization simple and fast: we only need to deal with - pointer relocation, registering with the garbage collector, and making note of special internal types. During serialization, - we also need to pay special attention to things like builtin functions, C-implemented types (those in jltypes.c), the metadata - for documentation, optimal layouts, integration with native system image generation, and preparing other preprocessing - directives. - - dump.c has capabilities missing from this serializer, most notably the ability to handle external references. This is not needed - for system images as they are self-contained. However, it would be needed to support incremental compilation of packages. + This performs serialization and deserialization of system and package images. It creates and saves a compact binary + blob, making deserialization "simple" and fast: we "only" need to deal with uniquing, pointer relocation, + method root insertion, registering with the garbage collector, making note of special internal types, and + backedges/invalidation. Special objects include things like builtin functions, C-implemented types (those in jltypes.c), + the metadata for documentation, optimal layouts, integration with native system image generation, and preparing other + preprocessing directives. During serialization, the flow has several steps: - - step 1 inserts relevant items into `backref_table`, an `obj` => `id::Int` mapping. `id` is assigned by - order of insertion. This is effectively a recursive traversal, singling out items like pointers and symbols - that need restoration when the system image is loaded. This stage is implemented by `jl_serialize_value` - and its callees; while it would be simplest to use recursion, this risks stack overflow, so recursion is mimicked + - step 1 inserts relevant items into `serialization_order`, an `obj` => `id::Int` mapping. `id` is assigned by + order of insertion. This stage is implemented by `jl_queue_for_serialization` and its callees; + while it would be simplest to use recursion, this risks stack overflow, so recursion is mimicked using a work-queue managed by `jl_serialize_reachable`. - It's worth emphasizing that despite the name `jl_serialize_value`, the only goal of this stage is to - insert objects into `backref_table`. The entire system gets inserted, either directly or indirectly via - fields of other objects. Objects requiring pointer relocation or gc registration must be inserted directly. - In later stages, such objects get referenced by their `id`. + It's worth emphasizing that the only goal of this stage is to insert objects into `serialization_order`. + In later stages, such objects get written in order of `id`. - - step 2 (the biggest of four steps) takes all items in `backref_table` and actually serializes them ordered + - step 2 (the biggest of four steps) takes all items in `serialization_order` and actually serializes them ordered by `id`. The system is serialized into several distinct streams (see `jl_serializer_state`), a "main stream" (the `s` field) as well as parallel streams for writing specific categories of additional internal data (e.g., global data invisible to codegen, as well as deserialization "touch-up" tables, see below). These different streams @@ -46,24 +37,49 @@ one of the corresponding categorical list, then `index = t << RELOC_TAG_OFFSET + i`. The simplest source for the details of this encoding can be found in the pair of functions `get_reloc_for_item` and `get_item_for_reloc`. + `uniquing` also holds the serialized location of external DataTypes, MethodInstances, and singletons + in the serialized blob (i.e., new-at-the-time-of-serialization specializations). + Most of step 2 is handled by `jl_write_values`, followed by special handling of the dedicated parallel streams. - step 3 combines the different sections (fields of `jl_serializer_state`) into one - - step 4 writes the values of the hard-coded tagged items and `reinit_list`/`ccallable_list` - -The tables written to the serializer stream make deserialization fairly straightforward. Much of the "real work" is -done by `get_item_for_reloc`. + - step 4 writes the values of the hard-coded tagged items and `ccallable_list` + +Much of the "real work" during deserialization is done by `get_item_for_reloc`. But a few items require specific +attention: +- uniquing: during deserialization, the target item (an "external" type or MethodInstance) must be checked against + the running system to see whether such an object already exists (i.e., whether some other previously-loaded package + or workload has created such types/MethodInstances previously) or whether it needs to be created de-novo. + In either case, all references at `location` must be updated to the one in the running system. + `new_dt_objs` is a hash set of newly allocated datatype-reachable objects +- method root insertion: when new specializations generate new roots, these roots must be inserted into + method root tables +- backedges & invalidation: external edges have to be checked against the running system and any invalidations executed. + +Encoding of a pointer: +- in the location of the pointer, we initially write zero padding +- for both relocs_list and gctags_list, we write loc/backrefid (for gctags_list this is handled by the caller of write_gctaggedfield, + for relocs_list it's handled by write_pointerfield) +- when writing to disk, both call get_reloc_for_item, and its return value (subject to modification by gc bits) + ends up being written into the data stream (s->s), and the data stream's position written to s->relocs + +External links: +- location holds the offset +- loc/0 in relocs_list */ #include #include #include // printf +#include // PRIxPTR #include "julia.h" #include "julia_internal.h" +#include "julia_gcext.h" #include "builtin_proto.h" #include "processor.h" +#include "serialize.h" #ifndef _OS_WINDOWS_ #include @@ -72,6 +88,9 @@ done by `get_item_for_reloc`. #include "valgrind.h" #include "julia_assert.h" +#include "staticdata_utils.c" +#include "precompile_utils.c" + #ifdef __cplusplus extern "C" { #endif @@ -79,7 +98,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 153 +#define NUM_TAGS 158 // 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. @@ -99,8 +118,8 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_slotnumber_type); INSERT_TAG(jl_simplevector_type); INSERT_TAG(jl_array_type); - INSERT_TAG(jl_typedslot_type); INSERT_TAG(jl_expr_type); + INSERT_TAG(jl_binding_type); INSERT_TAG(jl_globalref_type); INSERT_TAG(jl_string_type); INSERT_TAG(jl_module_type); @@ -145,7 +164,6 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_abstractstring_type); INSERT_TAG(jl_array_any_type); INSERT_TAG(jl_intrinsic_type); - INSERT_TAG(jl_abstractslot_type); INSERT_TAG(jl_methtable_type); INSERT_TAG(jl_typemap_level_type); INSERT_TAG(jl_typemap_entry_type); @@ -164,13 +182,13 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_int64_type); INSERT_TAG(jl_bool_type); INSERT_TAG(jl_uint8_type); + INSERT_TAG(jl_uint16_type); INSERT_TAG(jl_uint32_type); INSERT_TAG(jl_uint64_type); INSERT_TAG(jl_char_type); INSERT_TAG(jl_weakref_type); INSERT_TAG(jl_int8_type); INSERT_TAG(jl_int16_type); - INSERT_TAG(jl_uint16_type); INSERT_TAG(jl_float16_type); INSERT_TAG(jl_float32_type); INSERT_TAG(jl_float64_type); @@ -221,6 +239,9 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_typeinf_func); INSERT_TAG(jl_type_type_mt); INSERT_TAG(jl_nonfunction_mt); + INSERT_TAG(jl_kwcall_mt); + INSERT_TAG(jl_kwcall_func); + INSERT_TAG(jl_opaque_closure_method); // some Core.Builtin Functions that we want to be able to reference: INSERT_TAG(jl_builtin_throw); @@ -252,14 +273,12 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_builtin_ifelse); INSERT_TAG(jl_builtin__typebody); INSERT_TAG(jl_builtin_donotdelete); - - // All optional tags must be placed at the end, so that we - // don't accidentally have a `NULL` in the middle -#ifdef SEGV_EXCEPTION - INSERT_TAG(jl_segv_exception); -#endif + INSERT_TAG(jl_builtin_compilerbarrier); + INSERT_TAG(jl_builtin_getglobal); + INSERT_TAG(jl_builtin_setglobal); + // n.b. must update NUM_TAGS when you add something here #undef INSERT_TAG - assert(i >= (NUM_TAGS-2) && i < NUM_TAGS); + assert(i == NUM_TAGS - 1); } return (jl_value_t**const*const) _tags; } @@ -270,23 +289,142 @@ static uintptr_t nsym_tag; // array of definitions for the predefined tagged object types // (reverse of symbol_table) static arraylist_t deser_sym; - -// table of all objects that are serialized -static htable_t backref_table; -static int backref_table_numel; -static arraylist_t layout_table; // cache of `position(s)` for each `id` in `backref_table` +// 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 +// before the "wrapper" type so they can be properly recached against the running system. +static arraylist_t serialization_queue; +static arraylist_t layout_table; // cache of `position(s)` for each `id` in `serialization_order` static arraylist_t object_worklist; // used to mimic recursion by jl_serialize_reachable -// Both `reinit_list` and `ccallable_list` are lists of (size_t pos, code) entries -// for the serializer to mark values in need of rework during deserialization -// codes: -// 1: typename (reinit_list) -// 2: module (reinit_list) -// 3: method (ccallable_list) -static arraylist_t reinit_list; +// Permanent list of void* (begin, end+1) pairs of system/package images we've loaded previously +// together with their module build_ids (used for external linkage) +// 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; + +// Eytzinger tree of images. Used for very fast jl_object_in_image queries +// See https://algorithmica.org/en/eytzinger +arraylist_t eytzinger_image_tree; +arraylist_t eytzinger_idxs; +static uintptr_t img_min; +static uintptr_t img_max; + +static int ptr_cmp(const void *l, const void *r) +{ + uintptr_t left = *(const uintptr_t*)l; + uintptr_t right = *(const uintptr_t*)r; + return (left > right) - (left < right); +} + +// Build an eytzinger tree from a sorted array +static int eytzinger(uintptr_t *src, uintptr_t *dest, size_t i, size_t k, size_t n) +{ + if (k <= n) { + i = eytzinger(src, dest, i, 2 * k, n); + dest[k-1] = src[i]; + i++; + i = eytzinger(src, dest, i, 2 * k + 1, n); + } + return i; +} + +static size_t eyt_obj_idx(jl_value_t *obj) JL_NOTSAFEPOINT +{ + size_t n = eytzinger_image_tree.len - 1; + if (n == 0) + return n; + assert(n % 2 == 0 && "Eytzinger tree not even length!"); + uintptr_t cmp = (uintptr_t) obj; + if (cmp <= img_min || cmp > img_max) + return n; + uintptr_t *tree = (uintptr_t*)eytzinger_image_tree.items; + size_t k = 1; + // note that k preserves the history of how we got to the current node + while (k <= n) { + int greater = (cmp > tree[k - 1]); + k <<= 1; + k |= greater; + } + // Free to assume k is nonzero, since we start with k = 1 + // and cmp > gc_img_min + // This shift does a fast revert of the path until we get + // to a node that evaluated less than cmp. + k >>= (__builtin_ctzll(k) + 1); + assert(k != 0); + assert(k <= n && "Eytzinger tree index out of bounds!"); + assert(tree[k - 1] < cmp && "Failed to find lower bound for object!"); + return k - 1; +} + +//used in staticdata.c after we add an image +void rebuild_image_blob_tree(void) +{ + size_t inc = 1 + jl_linkage_blobs.len - eytzinger_image_tree.len; + assert(eytzinger_idxs.len == eytzinger_image_tree.len); + assert(eytzinger_idxs.max == eytzinger_image_tree.max); + arraylist_grow(&eytzinger_idxs, inc); + arraylist_grow(&eytzinger_image_tree, inc); + eytzinger_idxs.items[eytzinger_idxs.len - 1] = (void*)jl_linkage_blobs.len; + eytzinger_image_tree.items[eytzinger_image_tree.len - 1] = (void*)1; // outside image + for (size_t i = 0; i < jl_linkage_blobs.len; i++) { + assert((uintptr_t) jl_linkage_blobs.items[i] % 4 == 0 && "Linkage blob not 4-byte aligned!"); + // We abuse the pointer here a little so that a couple of properties are true: + // 1. a start and an end are never the same value. This simplifies the binary search. + // 2. ends are always after starts. This also simplifies the binary search. + // We assume that there exist no 0-size blobs, but that's a safe assumption + // since it means nothing could be there anyways + uintptr_t val = (uintptr_t) jl_linkage_blobs.items[i]; + eytzinger_idxs.items[i] = (void*)(val + (i & 1)); + } + qsort(eytzinger_idxs.items, eytzinger_idxs.len - 1, sizeof(void*), ptr_cmp); + img_min = (uintptr_t) eytzinger_idxs.items[0]; + img_max = (uintptr_t) eytzinger_idxs.items[eytzinger_idxs.len - 2] + 1; + eytzinger((uintptr_t*)eytzinger_idxs.items, (uintptr_t*)eytzinger_image_tree.items, 0, 1, eytzinger_idxs.len - 1); + // Reuse the scratch memory to store the indices + // Still O(nlogn) because binary search + for (size_t i = 0; i < jl_linkage_blobs.len; i ++) { + uintptr_t val = (uintptr_t) jl_linkage_blobs.items[i]; + // This is the same computation as in the prior for loop + uintptr_t eyt_val = val + (i & 1); + size_t eyt_idx = eyt_obj_idx((jl_value_t*)(eyt_val + 1)); assert(eyt_idx < eytzinger_idxs.len - 1); + assert(eytzinger_image_tree.items[eyt_idx] == (void*)eyt_val && "Eytzinger tree failed to find object!"); + if (i & 1) + eytzinger_idxs.items[eyt_idx] = (void*)n_linkage_blobs(); + else + eytzinger_idxs.items[eyt_idx] = (void*)(i / 2); + } +} + +static int eyt_obj_in_img(jl_value_t *obj) JL_NOTSAFEPOINT +{ + assert((uintptr_t) obj % 4 == 0 && "Object not 4-byte aligned!"); + int idx = eyt_obj_idx(obj); + // Now we use a tiny trick: tree[idx] & 1 is whether or not tree[idx] is a + // start (0) or an end (1) of a blob. If it's a start, then the object is + // in the image, otherwise it is not. + int in_image = ((uintptr_t)eytzinger_image_tree.items[idx] & 1) == 0; + return in_image; +} + +size_t external_blob_index(jl_value_t *v) JL_NOTSAFEPOINT +{ + assert((uintptr_t) v % 4 == 0 && "Object not 4-byte aligned!"); + int eyt_idx = eyt_obj_idx(v); + // We fill the invalid slots with the length, so we can just return that + size_t idx = (size_t) eytzinger_idxs.items[eyt_idx]; + return idx; +} -// @ccallable entry points to install -static arraylist_t ccallable_list; +uint8_t jl_object_in_image(jl_value_t *obj) JL_NOTSAFEPOINT +{ + return eyt_obj_in_img(obj); +} // hash of definitions for predefined function pointers static htable_t fptr_to_id; @@ -302,45 +440,74 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_throw, &jl_f_is, &jl_f_typeof, &jl_f_issubtype, &jl_f_isa, &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_invoke_kwsorter, + &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_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_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, NULL }; typedef struct { ios_t *s; // the main stream - ios_t *const_data; // codegen-invisible internal data (e.g., datatype layouts, list-like typename fields, foreign types, internal arrays) + ios_t *const_data; // GC-invisible internal data (e.g., datatype layouts, list-like typename fields, foreign types, internal arrays) ios_t *symbols; // names (char*) of symbols (some may be referenced by pointer in generated code) 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 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_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 + arraylist_t ccallable_list; // @ccallable entry points to install + // mapping from a buildid_idx to a depmods_idx + jl_array_t *buildid_depmods_idxs; + // record of build_ids for all external linkages, in order of serialization for the current sysimg/pkgimg + // conceptually, the base pointer for the jth externally-linked item is determined from + // i = findfirst(==(link_ids[j]), build_ids) + // blob_base = jl_linkage_blobs.items[2i] # 0-offset indexing + // We need separate lists since they are intermingled at creation but split when written. + jl_array_t *link_ids_relocs; + jl_array_t *link_ids_gctags; + jl_array_t *link_ids_gvars; + jl_array_t *link_ids_external_fnvars; jl_ptls_t ptls; + 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; +#ifdef _P64 +#define RELOC_TAG_OFFSET 61 +#define DEPS_IDX_OFFSET 40 // only on 64-bit can we encode the dependency-index as part of the tagged reloc +#else +// this supports up to 8 RefTags, 512MB of pointer data, and 4/2 (64/32-bit) GB of constant data. +#define RELOC_TAG_OFFSET 29 +#define DEPS_IDX_OFFSET RELOC_TAG_OFFSET +#endif + + // Tags of category `t` are located at offsets `t << RELOC_TAG_OFFSET` // Consequently there is room for 2^RELOC_TAG_OFFSET pointers, etc enum RefTags { - DataRef, // mutable data - ConstDataRef, // constant data (e.g., layouts) - TagRef, // items serialized via their tags - SymbolRef, // symbols - BindingRef, // module bindings - FunctionRef, // generic functions - BuiltinFunctionRef // builtin functions + DataRef, // mutable data + ConstDataRef, // constant data (e.g., layouts) + TagRef, // items serialized via their tags + SymbolRef, // symbols + FunctionRef, // functions + SysimageLinkage, // reference to the sysimage (from pkgimage) + ExternalLinkage // reference to some other pkgimage }; // calling conventions for internal entry points. @@ -355,41 +522,44 @@ typedef enum { JL_API_MAX } jl_callingconv_t; +// Sub-divisions of some RefTags +const uintptr_t BuiltinFunctionTag = ((uintptr_t)1 << (RELOC_TAG_OFFSET - 1)); -// this supports up to 8 RefTags, 512MB of pointer data, and 4/2 (64/32-bit) GB of constant data. -// if a larger size is required, will need to add support for writing larger relocations in many cases below -#define RELOC_TAG_OFFSET 29 - - -/* read and write in host byte order */ - -#define write_uint8(s, n) ios_putc((n), (s)) -#define read_uint8(s) ((uint8_t)ios_getc((s))) - -static void write_uint32(ios_t *s, uint32_t i) JL_NOTSAFEPOINT -{ - ios_write(s, (char*)&i, 4); -} -static uint32_t read_uint32(ios_t *s) JL_NOTSAFEPOINT +#if RELOC_TAG_OFFSET <= 32 +typedef uint32_t reloc_t; +#else +typedef uint64_t reloc_t; +#endif +static void write_reloc_t(ios_t *s, uintptr_t reloc_id) JL_NOTSAFEPOINT { - uint32_t x = 0; - ios_read(s, (char*)&x, 4); - return x; + if (sizeof(reloc_t) <= sizeof(uint32_t)) { + assert(reloc_id < UINT32_MAX); + write_uint32(s, reloc_id); + } + else { + write_uint64(s, reloc_id); + } } +// Reporting to PkgCacheInspector +typedef struct { + size_t sysdata; + size_t isbitsdata; + size_t symboldata; + size_t tagslist; + size_t reloclist; + size_t gvarlist; + size_t fptrlist; +} pkgcachesizes; // --- Static Compile --- - static void *jl_sysimg_handle = NULL; -static uint64_t sysimage_base = 0; -static uintptr_t *sysimg_gvars_base = NULL; -static const int32_t *sysimg_gvars_offsets = NULL; -static jl_sysimg_fptrs_t sysimg_fptrs; +static jl_image_t sysimage; -static inline uintptr_t *sysimg_gvars(uintptr_t *base, size_t idx) +static inline uintptr_t *sysimg_gvars(uintptr_t *base, const int32_t *offsets, size_t idx) { - return base + sysimg_gvars_offsets[idx] / sizeof(base[0]); + return base + offsets[idx] / sizeof(base[0]); } JL_DLLEXPORT int jl_running_on_valgrind(void) @@ -402,35 +572,10 @@ 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) { - jl_dlsym(jl_sysimg_handle, "jl_sysimg_gvars_base", (void **)&sysimg_gvars_base, 1); - jl_dlsym(jl_sysimg_handle, "jl_sysimg_gvars_offsets", (void **)&sysimg_gvars_offsets, 1); - sysimg_gvars_offsets += 1; - assert(sysimg_fptrs.base); - - void *pgcstack_func_slot; - jl_dlsym(jl_sysimg_handle, "jl_pgcstack_func_slot", &pgcstack_func_slot, 1); - void *pgcstack_key_slot; - jl_dlsym(jl_sysimg_handle, "jl_pgcstack_key_slot", &pgcstack_key_slot, 1); - jl_pgcstack_getkey((jl_get_pgcstack_func**)pgcstack_func_slot, (jl_pgcstack_key_t*)pgcstack_key_slot); - - size_t *tls_offset_idx; - jl_dlsym(jl_sysimg_handle, "jl_tls_offset", (void **)&tls_offset_idx, 1); - *tls_offset_idx = (uintptr_t)(jl_tls_offset == -1 ? 0 : jl_tls_offset); - -#ifdef _OS_WINDOWS_ - sysimage_base = (intptr_t)jl_sysimg_handle; -#else - Dl_info dlinfo; - if (dladdr((void*)sysimg_gvars_base, &dlinfo) != 0) { - sysimage_base = (intptr_t)dlinfo.dli_fbase; - } - else { - sysimage_base = 0; - } -#endif + assert(sysimage.fptrs.base); } else { - memset(&sysimg_fptrs, 0, sizeof(sysimg_fptrs)); + memset(&sysimage.fptrs, 0, sizeof(sysimage.fptrs)); } const char *sysimg_data; jl_dlsym(jl_sysimg_handle, "jl_system_image_data", (void **)&sysimg_data, 1); @@ -442,122 +587,243 @@ static void jl_load_sysimg_so(void) // --- serializer --- -static uintptr_t jl_fptr_id(void *fptr) +#define NBOX_C 1024 + +static int jl_needs_serialization(jl_serializer_state *s, jl_value_t *v) JL_NOTSAFEPOINT { - void **pbp = ptrhash_bp(&fptr_to_id, fptr); - if (*pbp == HT_NOTFOUND || fptr == NULL) + // ignore items that are given a special relocation representation + if (s->incremental && jl_object_in_image(v)) return 0; - else - return *(uintptr_t*)pbp; -} -#define jl_serialize_value(s, v) jl_serialize_value_(s,(jl_value_t*)(v),1) -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int recursive); + if (v == NULL || jl_is_symbol(v) || v == jl_nothing) { + return 0; + } + else if (jl_typetagis(v, jl_int64_tag << 4)) { + int64_t i64 = *(int64_t*)v + NBOX_C / 2; + if ((uint64_t)i64 < NBOX_C) + return 0; + } + else if (jl_typetagis(v, jl_int32_tag << 4)) { + int32_t i32 = *(int32_t*)v + NBOX_C / 2; + if ((uint32_t)i32 < NBOX_C) + return 0; + } + else if (jl_typetagis(v, jl_uint8_tag << 4)) { + return 0; + } + else if (jl_typetagis(v, jl_task_tag << 4)) { + return 0; + } + + return 1; +} -static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) +static int caching_tag(jl_value_t *v) JL_NOTSAFEPOINT { - jl_serialize_value(s, m->name); - jl_serialize_value(s, m->parent); - size_t i; - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_serialize_value(s, (jl_value_t*)table[i]); - jl_binding_t *b = (jl_binding_t*)table[i+1]; - jl_serialize_value(s, b->name); - if (jl_docmeta_sym && b->name == jl_docmeta_sym && jl_options.strip_metadata) - jl_serialize_value(s, jl_nothing); - else - jl_serialize_value(s, jl_atomic_load_relaxed(&b->value)); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->globalref)); - jl_serialize_value(s, b->owner); - jl_serialize_value(s, jl_atomic_load_relaxed(&b->ty)); - } + if (jl_is_method_instance(v)) { + jl_method_instance_t *mi = (jl_method_instance_t*)v; + jl_value_t *m = mi->def.value; + if (jl_is_method(m) && jl_object_in_image(m)) + return 1 + type_in_worklist(mi->specTypes); } - - for (i = 0; i < m->usings.len; i++) { - jl_serialize_value(s, (jl_value_t*)m->usings.items[i]); + if (jl_is_datatype(v)) { + jl_datatype_t *dt = (jl_datatype_t*)v; + if (jl_is_tuple_type(dt) ? !dt->isconcretetype : dt->hasfreetypevars) + return 0; // aka !is_cacheable from jltypes.c + if (jl_object_in_image((jl_value_t*)dt->name)) + return 1 + type_in_worklist(v); } + jl_value_t *dtv = jl_typeof(v); + if (jl_is_datatype_singleton((jl_datatype_t*)dtv)) { + return 1 - type_in_worklist(dtv); // these are already recached in the datatype in the image + } + return 0; +} + +static int needs_recaching(jl_value_t *v) JL_NOTSAFEPOINT +{ + return caching_tag(v) == 2; +} + +static int needs_uniquing(jl_value_t *v) JL_NOTSAFEPOINT +{ + assert(!jl_object_in_image(v)); + return caching_tag(v) == 1; +} + +static void record_field_change(jl_value_t **addr, jl_value_t *newval) JL_NOTSAFEPOINT +{ + ptrhash_put(&field_replace, (void*)addr, newval); } -static jl_value_t *get_replaceable_field(jl_value_t **addr) +static jl_value_t *get_replaceable_field(jl_value_t **addr, int mutabl) JL_GC_DISABLED { jl_value_t *fld = (jl_value_t*)ptrhash_get(&field_replace, addr); - if (fld == HT_NOTFOUND) - return *addr; + if (fld == HT_NOTFOUND) { + fld = *addr; + if (mutabl && fld && jl_is_cpointer_type(jl_typeof(fld)) && jl_unbox_voidpointer(fld) != NULL && jl_unbox_voidpointer(fld) != (void*)(uintptr_t)-1) { + void **nullval = ptrhash_bp(&nullptrs, (void*)jl_typeof(fld)); + if (*nullval == HT_NOTFOUND) { + void *C_NULL = NULL; + *nullval = (void*)jl_new_bits(jl_typeof(fld), &C_NULL); + } + fld = (jl_value_t*)*nullval; + } + return fld; + } return fld; } -#define NBOX_C 1024 +static uintptr_t jl_fptr_id(void *fptr) +{ + void **pbp = ptrhash_bp(&fptr_to_id, fptr); + if (*pbp == HT_NOTFOUND || fptr == NULL) + return 0; + else + return *(uintptr_t*)pbp; +} + +// `jl_queue_for_serialization` adds items to `serialization_order` +#define jl_queue_for_serialization(s, v) jl_queue_for_serialization_((s), (jl_value_t*)(v), 1, 0) +static void jl_queue_for_serialization_(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate) JL_GC_DISABLED; -static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int recursive) + +static void jl_queue_module_for_serialization(jl_serializer_state *s, jl_module_t *m) JL_GC_DISABLED { - // ignore items that are given a special representation - if (v == NULL || jl_is_symbol(v) || v == jl_nothing) { - return; - } - else if (jl_typeis(v, jl_task_type)) { - if (v == (jl_value_t*)s->ptls->root_task) { - jl_serialize_value(s, ((jl_task_t*)v)->tls); - return; + 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); + 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); + if ((void*)b == jl_nothing) + break; + jl_sym_t *name = b->globalref->name; + if (name == jl_docmeta_sym && jl_atomic_load_relaxed(&b->value)) + record_field_change((jl_value_t**)&b->value, jl_nothing); } } - else if (jl_typeis(v, jl_int64_type)) { - int64_t i64 = *(int64_t*)v + NBOX_C / 2; - if ((uint64_t)i64 < NBOX_C) - return; - } - else if (jl_typeis(v, jl_int32_type)) { - int32_t i32 = *(int32_t*)v + NBOX_C / 2; - if ((uint32_t)i32 < NBOX_C) - return; - } - else if (jl_typeis(v, jl_uint8_type)) { - return; + + for (size_t i = 0; i < m->usings.len; i++) { + jl_queue_for_serialization(s, (jl_value_t*)m->usings.items[i]); } - arraylist_push(&object_worklist, (void*)((uintptr_t)v | recursive)); } -static void jl_serialize_value__(jl_serializer_state *s, jl_value_t *v, int recursive) +// Anything that requires uniquing or fixing during deserialization needs to be "toplevel" +// in serialization (i.e., have its own entry in `serialization_order`). Consequently, +// objects that act as containers for other potentially-"problematic" objects must add such "children" +// to the queue. +// Most objects use preorder traversal. But things that need uniquing require postorder: +// you want to handle uniquing of `Dict{String,Float64}` before you tackle `Vector{Dict{String,Float64}}`. +// Uniquing is done in `serialization_order`, so the very first mention of such an object must +// be the "source" rather than merely a cross-reference. +static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate) JL_GC_DISABLED { - void **bp = ptrhash_bp(&backref_table, v); - if (*bp != HT_NOTFOUND) { - return; + jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); + jl_queue_for_serialization_(s, (jl_value_t*)t, 1, immediate); + + 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 + // 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) + } + if (s->incremental && jl_is_method_instance(v)) { + jl_method_instance_t *mi = (jl_method_instance_t*)v; + jl_value_t *def = mi->def.value; + if (needs_uniquing(v)) { + // we only need 3 specific fields of this (the rest are not used) + 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)) { + // we only need 3 specific fields of this (the rest are restored afterward, if valid) + // in particular, cache is repopulated by jl_mi_cache_insert for all foreign function, + // 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 { + assert(!needs_recaching(v)); + } + // n.b. opaque closures cannot be inspected and relied upon like a + // normal method since they can get improperly introduced by generated + // functions, so if they appeared at all, we will probably serialize + // them wrong and segfault. The jl_code_for_staged function should + // prevent this from happening, so we do not need to detect that user + // error now. + } + if (s->incremental && jl_is_globalref(v)) { + jl_globalref_t *gr = (jl_globalref_t*)v; + if (jl_object_in_image((jl_value_t*)gr->mod)) { + record_field_change((jl_value_t**)&gr->binding, NULL); + } + } + 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); + if (s->incremental) { + assert(!jl_object_in_image((jl_value_t*)tn->module)); + assert(!jl_object_in_image((jl_value_t*)tn->wrapper)); + } + } + 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 + 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); + } } - size_t item = ++backref_table_numel; - assert(item < ((uintptr_t)1 << RELOC_TAG_OFFSET) && "too many items to serialize"); - char *pos = (char*)HT_NOTFOUND + item; - *bp = (void*)pos; - // some values have special representations - jl_datatype_t *t = (jl_datatype_t*)jl_typeof(v); - jl_serialize_value(s, t); + 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)); - if (t->layout->npointers == 0) { - // skip it + const jl_datatype_layout_t *layout = t->layout; + if (layout->npointers == 0) { + // bitstypes do not require recursion } else if (jl_is_svec(v)) { - if (!recursive) - return; size_t i, l = jl_svec_len(v); jl_value_t **data = jl_svec_data(v); for (i = 0; i < l; i++) { - jl_serialize_value(s, data[i]); + jl_queue_for_serialization_(s, data[i], 1, immediate); } } else if (jl_is_array(v)) { jl_array_t *ar = (jl_array_t*)v; - jl_serialize_value(s, jl_typeof(ar)); + const char *data = (const char*)jl_array_data(ar); if (ar->flags.ptrarray) { size_t i, l = jl_array_len(ar); for (i = 0; i < l; i++) { - jl_serialize_value(s, jl_array_ptr_ref(ar, 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) { - const char *data = (const char*)jl_array_data(ar); 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)); @@ -565,43 +831,95 @@ static void jl_serialize_value__(jl_serializer_state *s, jl_value_t *v, int recu for (i = 0; i < l; i++) { for (j = 0; j < np; j++) { uint32_t ptr = jl_ptr_offset(et, j); - jl_value_t *fld = ((jl_value_t**)data)[ptr]; - JL_GC_PROMISE_ROOTED(fld); - jl_serialize_value(s, fld); + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], 1); + jl_queue_for_serialization_(s, fld, 1, immediate); } data += elsz; } } } - else if (jl_typeis(v, jl_module_type)) { - jl_serialize_module(s, (jl_module_t*)v); + else if (jl_typetagis(v, jl_module_tag << 4)) { + jl_queue_module_for_serialization(s, (jl_module_t*)v); } - else if (jl_is_typename(v)) { - jl_typename_t *tn = (jl_typename_t*)v; - jl_serialize_value(s, tn->name); - jl_serialize_value(s, tn->module); - jl_serialize_value(s, tn->names); - jl_serialize_value(s, tn->wrapper); - jl_serialize_value_(s, (jl_value_t*)tn->cache, 0); - jl_serialize_value_(s, (jl_value_t*)tn->linearcache, 0); - jl_serialize_value(s, tn->mt); - jl_serialize_value(s, tn->partial); - } - else if (t->layout->nfields > 0) { + else if (layout->nfields > 0) { char *data = (char*)jl_data_ptr(v); - size_t i, np = t->layout->npointers; + size_t i, np = layout->npointers; for (i = 0; i < np; i++) { uint32_t ptr = jl_ptr_offset(t, i); - jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr]); - jl_serialize_value(s, fld); + int mutabl = t->name->mutabl; + if (jl_is_binding(v) && ((jl_binding_t*)v)->constp && i == 0) // value field depends on constp field + mutabl = 0; + jl_value_t *fld = get_replaceable_field(&((jl_value_t**)data)[ptr], mutabl); + jl_queue_for_serialization_(s, fld, 1, immediate); + } + } + +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); + } + 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); +} + +static void jl_queue_for_serialization_(jl_serializer_state *s, jl_value_t *v, int recursive, int immediate) JL_GC_DISABLED +{ + if (!jl_needs_serialization(s, v)) + return; + + jl_value_t *t = jl_typeof(v); + // Items that require postorder traversal must visit their children prior to insertion into + // the worklist/serialization_order (and also before their first use) + if (s->incremental && !immediate) { + if (jl_is_datatype(t) && needs_uniquing(v)) + immediate = 1; + if (jl_is_datatype_singleton((jl_datatype_t*)t) && needs_uniquing(v)) + immediate = 1; + } + + 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 } + + if (immediate) + jl_insert_into_serialization_queue(s, v, recursive, immediate); + else + arraylist_push(&object_worklist, (void*)v); } // Do a pre-order traversal of the to-serialize worklist, in the identical order -// to the calls to jl_serialize_value would occur in a purely recursive +// to the calls to jl_queue_for_serialization would occur in a purely recursive // implementation, but without potentially running out of stack. -static void jl_serialize_reachable(jl_serializer_state *s) +static void jl_serialize_reachable(jl_serializer_state *s) JL_GC_DISABLED { size_t i, prevlen = 0; while (object_worklist.len) { @@ -614,10 +932,16 @@ static void jl_serialize_reachable(jl_serializer_state *s) object_worklist.items[j] = tmp; } prevlen = --object_worklist.len; - uintptr_t v = (uintptr_t)object_worklist.items[prevlen]; - int recursive = v & 1; - v &= ~(uintptr_t)1; // untag v - jl_serialize_value__(s, (jl_value_t*)v, recursive); + jl_value_t *v = (jl_value_t*)object_worklist.items[prevlen]; + void **bp = ptrhash_bp(&serialization_order, (void*)v); + assert(*bp != HT_NOTFOUND && *bp != (void*)(uintptr_t)-2); + if (*bp == (void*)(uintptr_t)-1) { // might have been eagerly handled for post-order while in the lazy pre-order queue + *bp = (void*)(uintptr_t)-2; + jl_insert_into_serialization_queue(s, v, 1, 0); + } + else { + assert(s->incremental); + } } } @@ -631,20 +955,6 @@ static void ios_ensureroom(ios_t *s, size_t newsize) JL_NOTSAFEPOINT } } -// Maybe encode a global variable. `gid` is the LLVM index, 0 if the object is not serialized -// in the generated code (and thus not a gvar from that standpoint, maybe only stored in the internal-data sysimg). -// `reloc_id` is the RefTags-encoded `target`. -static void record_gvar(jl_serializer_state *s, int gid, uintptr_t reloc_id) JL_NOTSAFEPOINT -{ - if (gid == 0) - return; - ios_ensureroom(s->gvar_record, gid * sizeof(uint32_t)); - ios_seek(s->gvar_record, (gid - 1) * sizeof(uint32_t)); - assert(reloc_id < UINT32_MAX); - write_uint32(s->gvar_record, reloc_id); -} - - static void write_padding(ios_t *s, size_t nb) JL_NOTSAFEPOINT { static const char zeros[16] = {0}; @@ -656,18 +966,42 @@ static void write_padding(ios_t *s, size_t nb) JL_NOTSAFEPOINT ios_write(s, zeros, nb); } - static void write_pointer(ios_t *s) JL_NOTSAFEPOINT { assert((ios_pos(s) & (sizeof(void*) - 1)) == 0 && "stream misaligned for writing a word-sized value"); - write_padding(s, sizeof(void*)); -} - -// Return the integer `id` for `v`. Generically this is looked up in `backref_table`, + write_uint(s, 0); +} + +// Records the buildid holding `v` and returns the tagged offset within the corresponding image +static uintptr_t add_external_linkage(jl_serializer_state *s, jl_value_t *v, jl_array_t *link_ids) { + size_t i = external_blob_index(v); + if (i < n_linkage_blobs()) { + // We found the sysimg/pkg that this item links against + // Compute the relocation code + 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(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 + return ((uintptr_t)SysimageLinkage << RELOC_TAG_OFFSET) + ((uintptr_t)depsidx << DEPS_IDX_OFFSET) + offset; + // 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; + return ((uintptr_t)ExternalLinkage << RELOC_TAG_OFFSET) + offset; + } + return 0; +} + +// Return the integer `id` for `v`. Generically this is looked up in `serialization_order`, // but symbols, small integers, and a couple of special items (`nothing` and the root Task) // have special handling. -#define backref_id(s, v) _backref_id(s, (jl_value_t*)(v)) -static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v) JL_NOTSAFEPOINT +#define backref_id(s, v, link_ids) _backref_id(s, (jl_value_t*)(v), link_ids) +static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v, jl_array_t *link_ids) JL_NOTSAFEPOINT { assert(v != NULL && "cannot get backref to NULL object"); void *idx = HT_NOTFOUND; @@ -690,100 +1024,97 @@ static uintptr_t _backref_id(jl_serializer_state *s, jl_value_t *v) JL_NOTSAFEPO else if (v == jl_nothing) { return ((uintptr_t)TagRef << RELOC_TAG_OFFSET) + 1; } - else if (jl_typeis(v, jl_int64_type)) { + else if (jl_typetagis(v, jl_int64_tag << 4)) { int64_t i64 = *(int64_t*)v + NBOX_C / 2; if ((uint64_t)i64 < NBOX_C) return ((uintptr_t)TagRef << RELOC_TAG_OFFSET) + i64 + 2; } - else if (jl_typeis(v, jl_int32_type)) { + else if (jl_typetagis(v, jl_int32_tag << 4)) { int32_t i32 = *(int32_t*)v + NBOX_C / 2; if ((uint32_t)i32 < NBOX_C) return ((uintptr_t)TagRef << RELOC_TAG_OFFSET) + i32 + 2 + NBOX_C; } - else if (jl_typeis(v, jl_uint8_type)) { + else if (jl_typetagis(v, jl_uint8_tag << 4)) { uint8_t u8 = *(uint8_t*)v; return ((uintptr_t)TagRef << RELOC_TAG_OFFSET) + u8 + 2 + NBOX_C + NBOX_C; } + if (s->incremental && jl_object_in_image(v)) { + assert(link_ids); + uintptr_t item = add_external_linkage(s, v, link_ids); + assert(item && "no external linkage identified"); + return item; + } if (idx == HT_NOTFOUND) { - idx = ptrhash_get(&backref_table, v); - assert(idx != HT_NOTFOUND && "object missed during jl_serialize_value pass"); + idx = ptrhash_get(&serialization_order, v); + if (idx == HT_NOTFOUND) { + jl_(jl_typeof(v)); + jl_(v); + } + assert(idx != HT_NOTFOUND && "object missed during jl_queue_for_serialization pass"); + assert(idx != (void*)(uintptr_t)-1 && "object missed during jl_insert_into_serialization_queue pass"); + assert(idx != (void*)(uintptr_t)-2 && "object missed during jl_insert_into_serialization_queue pass"); } return (char*)idx - 1 - (char*)HT_NOTFOUND; } +static void record_uniquing(jl_serializer_state *s, jl_value_t *fld, uintptr_t offset) JL_NOTSAFEPOINT +{ + 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 + arraylist_push(&s->uniquing_objs, (void*)(uintptr_t)offset); + } +} + // Save blank space in stream `s` for a pointer `fld`, storing both location and target // in `relocs_list`. static void write_pointerfield(jl_serializer_state *s, jl_value_t *fld) JL_NOTSAFEPOINT { if (fld != NULL) { arraylist_push(&s->relocs_list, (void*)(uintptr_t)ios_pos(s->s)); - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld)); + arraylist_push(&s->relocs_list, (void*)backref_id(s, fld, s->link_ids_relocs)); + record_uniquing(s, fld, ios_pos(s->s)); } write_pointer(s->s); } // Save blank space in stream `s` for a pointer `fld`, storing both location and target // in `gctags_list`. -static void write_gctaggedfield(jl_serializer_state *s, uintptr_t ref) JL_NOTSAFEPOINT +static void write_gctaggedfield(jl_serializer_state *s, jl_datatype_t *ref) JL_NOTSAFEPOINT { + // jl_printf(JL_STDOUT, "gctaggedfield: position %p, value 0x%lx\n", (void*)(uintptr_t)ios_pos(s->s), ref); arraylist_push(&s->gctags_list, (void*)(uintptr_t)ios_pos(s->s)); - arraylist_push(&s->gctags_list, (void*)ref); + arraylist_push(&s->gctags_list, (void*)backref_id(s, ref, s->link_ids_gctags)); write_pointer(s->s); } + // Special handling from `jl_write_values` for modules -static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t *m) +static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t *m) JL_GC_DISABLED { size_t reloc_offset = ios_pos(s->s); size_t tot = sizeof(jl_module_t); ios_write(s->s, (char*)m, tot); // raw memory dump of the `jl_module_t` structure + // will need to recreate the binding table for this + arraylist_push(&s->fixup_objs, (void*)reloc_offset); // Handle the fields requiring special attention jl_module_t *newm = (jl_module_t*)&s->s->buf[reloc_offset]; newm->name = NULL; arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, name))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->name)); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->name, s->link_ids_relocs)); 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)); - newm->primary_world = jl_atomic_load_acquire(&jl_world_counter); - - // write out the bindings table as a list - // immediately after jl_module_t - // (the ptrhash will need to be recreated on load) - size_t count = 0; - size_t i; - void **table = m->bindings.table; - for (i = 0; i < m->bindings.size; i += 2) { - if (table[i+1] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i+1]; - write_pointerfield(s, (jl_value_t*)table[i]); - tot += sizeof(void*); - write_gctaggedfield(s, (uintptr_t)BindingRef << RELOC_TAG_OFFSET); - tot += sizeof(void*); - size_t binding_reloc_offset = ios_pos(s->s); - record_gvar(s, jl_get_llvm_gv(native_functions, (jl_value_t*)b), - ((uintptr_t)DataRef << RELOC_TAG_OFFSET) + binding_reloc_offset); - write_pointerfield(s, (jl_value_t*)b->name); - if (jl_docmeta_sym && b->name == jl_docmeta_sym && jl_options.strip_metadata) - write_pointerfield(s, jl_nothing); - else - write_pointerfield(s, jl_atomic_load_relaxed(&b->value)); - write_pointerfield(s, jl_atomic_load_relaxed(&b->globalref)); - write_pointerfield(s, (jl_value_t*)b->owner); - write_pointerfield(s, jl_atomic_load_relaxed(&b->ty)); - size_t flag_offset = offsetof(jl_binding_t, ty) + sizeof(b->ty); - ios_write(s->s, (char*)b + flag_offset, sizeof(*b) - flag_offset); - tot += sizeof(jl_binding_t); - count += 1; - } - } - assert(ios_pos(s->s) - reloc_offset == tot); - newm = (jl_module_t*)&s->s->buf[reloc_offset]; // buf might have been reallocated - newm->bindings.size = count; // stash the count in newm->size - newm->bindings.table = NULL; - memset(&newm->bindings._space, 0, sizeof(newm->bindings._space)); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->parent, s->link_ids_relocs)); + 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*)(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; // write out the usings list memset(&newm->usings._space, 0, sizeof(newm->usings._space)); @@ -794,7 +1125,7 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t size_t i; for (i = 0; i < m->usings.len; i++) { arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_module_t, usings._space[i]))); - arraylist_push(&s->relocs_list, (void*)backref_id(s, m->usings._space[i])); + arraylist_push(&s->relocs_list, (void*)backref_id(s, m->usings._space[i], s->link_ids_relocs)); } } else { @@ -811,94 +1142,103 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t tot += sizeof(void*); } } + assert(ios_pos(s->s) - reloc_offset == tot); } -#if 0 -static size_t jl_sort_size(jl_datatype_t *dt) +static void record_gvars(jl_serializer_state *s, arraylist_t *globals) JL_NOTSAFEPOINT { - if (dt == jl_simplevector_type) - return SIZE_MAX - 5; - if (dt == jl_string_type) - return SIZE_MAX - 4; - if (dt->name == jl_array_typename) - return SIZE_MAX - 3; - if (dt == jl_datatype_type) - return SIZE_MAX - 2; - if (dt == jl_module_type) - return SIZE_MAX - 1; - return jl_datatype_size(dt); + for (size_t i = 0; i < globals->len; i++) + jl_queue_for_serialization(s, globals->items[i]); } -#endif -// Used by `qsort` to order `backref_table` by `id` -static int sysimg_sort_order(const void *pa, const void *pb) -{ - uintptr_t sa = ((uintptr_t*)pa)[1]; - uintptr_t sb = ((uintptr_t*)pb)[1]; - return (sa > sb ? 1 : (sa < sb ? -1 : 0)); -#if 0 - jl_value_t *a = *(jl_value_t**)pa; - jl_datatype_t *tya = (jl_datatype_t*)jl_typeof(a); - size_t sa = jl_sort_size(tya); - jl_value_t *b = *(jl_value_t**)pb; - jl_datatype_t *tyb = (jl_datatype_t*)jl_typeof(b); - size_t sb = jl_sort_size(tyb); - if (sa == sb) { - sa = tya->uid; - sb = tyb->uid; - } - return (sa > sb ? 1 : (sa < sb ? -1 : 0)); +static void record_external_fns(jl_serializer_state *s, arraylist_t *external_fns) JL_NOTSAFEPOINT +{ + if (!s->incremental) { + assert(external_fns->len == 0); + (void) external_fns; + return; + } + + // We could call jl_queue_for_serialization here, but that should + // always be a no-op. +#ifndef JL_NDEBUG + for (size_t i = 0; i < external_fns->len; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)external_fns->items[i]; + assert(jl_atomic_load_relaxed(&ci->specsigflags) & 0b100); + } #endif } jl_value_t *jl_find_ptr = NULL; -// The main function for serializing all the items queued in `backref_table` -static void jl_write_values(jl_serializer_state *s) +// The main function for serializing all the items queued in `serialization_order` +// (They are also stored in `serialization_queue` which is order-preserving, unlike the hash table used +// for `serialization_order`). +static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED { - arraylist_t objects_list; - arraylist_new(&objects_list, backref_table_numel * 2); + size_t l = serialization_queue.len; arraylist_new(&layout_table, 0); - arraylist_grow(&layout_table, backref_table_numel); - memset(layout_table.items, 0, backref_table_numel * sizeof(void*)); - - // Order `backref_table` by `id` - size_t i, len = backref_table.size; - void **p = backref_table.table; - for (i = 0; i < len; i += 2) { - char *reloc_id = (char*)p[i + 1]; - if (reloc_id != HT_NOTFOUND) { - jl_value_t *v = (jl_value_t*)p[i]; - uintptr_t item = reloc_id - 1 - (char*)HT_NOTFOUND; - objects_list.items[objects_list.len++] = (void*)v; - objects_list.items[objects_list.len++] = (void*)item; - } - } - assert(backref_table_numel * 2 == objects_list.len); - qsort(objects_list.items, backref_table_numel, sizeof(void*) * 2, sysimg_sort_order); + arraylist_grow(&layout_table, l * 2); + memset(layout_table.items, 0, l * 2 * sizeof(void*)); // Serialize all entries - for (i = 0, len = backref_table_numel * 2; i < len; i += 2) { - jl_value_t *v = (jl_value_t*)objects_list.items[i]; // the object + for (size_t item = 0; item < l; item++) { + jl_value_t *v = (jl_value_t*)serialization_queue.items[item]; // the object JL_GC_PROMISE_ROOTED(v); - uintptr_t item = (uintptr_t)objects_list.items[i + 1]; // the id + 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"); + 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) { + f = s->const_data; + } + } + } + // realign stream to expected gc alignment (16 bytes) - uintptr_t skip_header_pos = ios_pos(s->s) + sizeof(jl_taggedvalue_t); - write_padding(s->s, LLT_ALIGN(skip_header_pos, 16) - skip_header_pos); + uintptr_t skip_header_pos = ios_pos(f) + sizeof(jl_taggedvalue_t); + write_padding(f, LLT_ALIGN(skip_header_pos, 16) - skip_header_pos); + // write header - write_gctaggedfield(s, backref_id(s, t)); - size_t reloc_offset = ios_pos(s->s); + 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); + else + write_gctaggedfield(s, t); + size_t reloc_offset = ios_pos(f); assert(item < layout_table.len && layout_table.items[item] == NULL); - layout_table.items[item] = (void*)reloc_offset; // store the inverse mapping of `backref_table` (`id` => object) - record_gvar(s, jl_get_llvm_gv(native_functions, v), ((uintptr_t)DataRef << RELOC_TAG_OFFSET) + reloc_offset); + layout_table.items[item] = (void*)(reloc_offset | (f == s->const_data)); // store the inverse mapping of `serialization_order` (`id` => object-as-streampos) + + if (s->incremental) { + if (needs_uniquing(v)) { + if (jl_is_method_instance(v)) { + assert(f == s->s); + jl_method_instance_t *mi = (jl_method_instance_t*)v; + write_pointerfield(s, mi->def.value); + write_pointerfield(s, mi->specTypes); + write_pointerfield(s, (jl_value_t*)mi->sparam_vals); + continue; + } + else if (!jl_is_datatype(v)) { + assert(jl_is_datatype_singleton(t) && "unreachable"); + } + } + else if (needs_recaching(v)) { + arraylist_push(jl_is_datatype(v) ? &s->fixup_types : &s->fixup_objs, (void*)reloc_offset); + } + else if (jl_typetagis(v, jl_binding_type)) { + jl_binding_t *b = (jl_binding_t*)v; + if (b->globalref == NULL || jl_object_in_image((jl_value_t*)b->globalref->mod)) + jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity + } + } // write data - if (jl_is_cpointer(v)) { - write_pointer(s->s); - } - else if (jl_is_array(v)) { + 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; @@ -914,12 +1254,12 @@ static void jl_write_values(jl_serializer_state *s) int ndimwords = jl_array_ndimwords(ar->flags.ndims); size_t headersize = sizeof(jl_array_t) + ndimwords*sizeof(size_t); // copy header - ios_write(s->s, (char*)v, headersize); + 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*)&s->s->buf[reloc_offset]; + jl_array_t *newa = (jl_array_t*)&f->buf[reloc_offset]; if (newa->flags.ndims == 1) newa->maxsize = alen; newa->offset = 0; @@ -939,10 +1279,15 @@ static void jl_write_values(jl_serializer_state *s) 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 elements to C_NULL + // 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++) - write_pointer(s->const_data); + 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) { @@ -956,17 +1301,17 @@ static void jl_write_values(jl_serializer_state *s) } else { // Pointer eltypes are encoded in the mutable data section - size_t data = LLT_ALIGN(ios_pos(s->s), alignment_amt); - size_t padding_amt = data - ios_pos(s->s); - write_padding(s->s, padding_amt); + 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(s->s, data, datasize); + 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; @@ -974,61 +1319,65 @@ static void jl_write_values(jl_serializer_state *s) 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 = *(jl_value_t**)&data[offset]; + 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)(reloc_offset + headersize + offset)); // relocation location - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld)); // relocation target - memset(&s->s->buf[reloc_offset + headersize + offset], 0, sizeof(fld)); // relocation offset (none) - } - else { - assert(*(jl_value_t**)&s->s->buf[reloc_offset + headersize + offset] == 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) } } } 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 = jl_array_ptr_ref(v, i); + jl_value_t *e = get_replaceable_field(&data[i], 1); write_pointerfield(s, e); } } } } else if (jl_typeis(v, jl_module_type)) { + assert(f == s->s); jl_write_module(s, item, (jl_module_t*)v); - // will need to recreate the binding table for this - arraylist_push(&reinit_list, (void*)item); - arraylist_push(&reinit_list, (void*)2); } - else if (jl_typeis(v, jl_task_type)) { + else if (jl_typetagis(v, jl_task_tag << 4)) { jl_error("Task cannot be serialized"); } else if (jl_is_svec(v)) { - ios_write(s->s, (char*)v, sizeof(void*)); - size_t i, l = jl_svec_len(v); + assert(f == s->s); + ios_write(f, (char*)v, sizeof(void*)); + size_t ii, l = jl_svec_len(v); assert(l > 0 || (jl_svec_t*)v == jl_emptysvec); - for (i = 0; i < l; i++) { - write_pointerfield(s, jl_svecref(v, i)); + for (ii = 0; ii < l; ii++) { + write_pointerfield(s, jl_svecref(v, ii)); } } else if (jl_is_string(v)) { - ios_write(s->s, (char*)v, sizeof(void*) + jl_string_len(v)); - write_uint8(s->s, '\0'); // null-terminated strings for easier C-compatibility + ios_write(f, (char*)v, sizeof(void*) + jl_string_len(v)); + write_uint8(f, '\0'); // null-terminated strings for easier C-compatibility + } + else if (jl_is_foreign_type(t) == 1) { + jl_error("Cannot serialize instances of foreign datatypes"); } 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); - if (t->size > 0) - ios_write(s->s, (char*)v, t->size); + ios_write(f, (char*)v, jl_datatype_size(t)); } - else if (jl_bigint_type && jl_typeis(v, jl_bigint_type)) { + else if (jl_bigint_type && jl_typetagis(v, jl_bigint_type)) { // foreign types require special handling + assert(f == s->s); jl_value_t *sizefield = jl_get_nth_field(v, 1); int32_t sz = jl_unbox_int32(sizefield); int32_t nw = (sz == 0 ? 1 : (sz < 0 ? -sz : sz)); size_t nb = nw * gmp_limb_size; - ios_write(s->s, (char*)&nw, sizeof(int32_t)); - ios_write(s->s, (char*)&sz, sizeof(int32_t)); + ios_write(f, (char*)&nw, sizeof(int32_t)); + ios_write(f, (char*)&sz, sizeof(int32_t)); uintptr_t data = LLT_ALIGN(ios_pos(s->const_data), 8); write_padding(s->const_data, data - ios_pos(s->const_data)); data /= sizeof(void*); @@ -1037,7 +1386,7 @@ static void jl_write_values(jl_serializer_state *s) arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + data)); // relocation target void *pdata = jl_unbox_voidpointer(jl_get_nth_field(v, 2)); ios_write(s->const_data, (char*)pdata, nb); - write_pointer(s->s); + write_pointer(f); } else { // Generic object::DataType serialization by field @@ -1047,16 +1396,16 @@ static void jl_write_values(jl_serializer_state *s) for (i = 0; i < nf; i++) { size_t offset = jl_field_offset(t, i); const char *slot = data + offset; - write_padding(s->s, offset - tot); + 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))) { - // reset Ptr fields to C_NULL + if (t->name->mutabl && jl_is_cpointer_type(jl_field_type(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(s->s); + write_pointer(f); } else if (fsz > 0) { - ios_write(s->s, slot, fsz); + ios_write(f, slot, fsz); } tot += fsz; } @@ -1064,29 +1413,83 @@ static void jl_write_values(jl_serializer_state *s) size_t np = t->layout->npointers; for (i = 0; i < np; i++) { size_t offset = jl_ptr_offset(t, i) * sizeof(jl_value_t*); - jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset]); + int mutabl = t->name->mutabl; + if (jl_is_binding(v) && ((jl_binding_t*)v)->constp && i == 0) // value field depends on constp field + mutabl = 0; + jl_value_t *fld = get_replaceable_field((jl_value_t**)&data[offset], mutabl); + size_t fld_pos = offset + reloc_offset; if (fld != NULL) { - arraylist_push(&s->relocs_list, (void*)(uintptr_t)(offset + reloc_offset)); // relocation location - arraylist_push(&s->relocs_list, (void*)backref_id(s, fld)); // relocation target - memset(&s->s->buf[offset + reloc_offset], 0, sizeof(fld)); // relocation offset (none) + 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) } // A few objects need additional handling beyond the generic serialization above - if (jl_is_method(v)) { - write_padding(s->s, sizeof(jl_method_t) - tot); - if (((jl_method_t*)v)->ccallable) { - arraylist_push(&ccallable_list, (void*)item); - arraylist_push(&ccallable_list, (void*)3); + + 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; + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + } } + else { + // garbage newentry - delete it :( + newentry->min_world = 1; + newentry->max_world = 0; + } + } + else if (jl_is_method(v)) { + assert(f == s->s); + write_padding(f, sizeof(jl_method_t) - tot); // hidden fields + 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 { + newm->nroots_sysimg = m->roots ? jl_array_len(m->roots) : 0; + } + if (m->ccallable) + arraylist_push(&s->ccallable_list, (void*)reloc_offset); + } + else if (jl_is_method_instance(v)) { + assert(f == s->s); + jl_method_instance_t *newmi = (jl_method_instance_t*)&f->buf[reloc_offset]; + jl_atomic_store_relaxed(&newmi->precompiled, 0); } 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*)&s->s->buf[reloc_offset]; + jl_code_instance_t *newm = (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 + } + } + } newm->invoke = NULL; - newm->isspecsig = 0; + newm->specsigflags = 0; newm->specptr.fptr = NULL; int8_t fptr_id = JL_API_NULL; int8_t builtin_id = 0; @@ -1115,18 +1518,20 @@ static void jl_write_values(jl_serializer_state *s) assert(invokeptr_id > 0); ios_ensureroom(s->fptr_record, invokeptr_id * sizeof(void*)); ios_seek(s->fptr_record, (invokeptr_id - 1) * sizeof(void*)); - write_uint32(s->fptr_record, ~reloc_offset); + write_reloc_t(s->fptr_record, (reloc_t)~reloc_offset); #ifdef _P64 - write_padding(s->fptr_record, 4); + if (sizeof(reloc_t) < 8) + write_padding(s->fptr_record, 8 - sizeof(reloc_t)); #endif } if (specfptr_id) { assert(specfptr_id > invokeptr_id && specfptr_id > 0); ios_ensureroom(s->fptr_record, specfptr_id * sizeof(void*)); ios_seek(s->fptr_record, (specfptr_id - 1) * sizeof(void*)); - write_uint32(s->fptr_record, reloc_offset); + write_reloc_t(s->fptr_record, reloc_offset); #ifdef _P64 - write_padding(s->fptr_record, 4); + if (sizeof(reloc_t) < 8) + write_padding(s->fptr_record, 8 - sizeof(reloc_t)); #endif } } @@ -1135,24 +1540,31 @@ static void jl_write_values(jl_serializer_state *s) } newm->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 arraylist_push(&s->relocs_list, (void*)(((uintptr_t)FunctionRef << RELOC_TAG_OFFSET) + fptr_id)); // relocation target } if (builtin_id >= 2) { arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_code_instance_t, specptr.fptr))); // relocation location - arraylist_push(&s->relocs_list, (void*)(((uintptr_t)BuiltinFunctionRef << RELOC_TAG_OFFSET) + builtin_id - 2)); // relocation target + arraylist_push(&s->relocs_list, (void*)(((uintptr_t)FunctionRef << RELOC_TAG_OFFSET) + BuiltinFunctionTag + builtin_id - 2)); // relocation target } } else if (jl_is_datatype(v)) { + assert(f == s->s); jl_datatype_t *dt = (jl_datatype_t*)v; - jl_datatype_t *newdt = (jl_datatype_t*)&s->s->buf[reloc_offset]; + jl_datatype_t *newdt = (jl_datatype_t*)&f->buf[reloc_offset]; + if (dt->layout != NULL) { size_t nf = dt->layout->nfields; size_t np = dt->layout->npointers; - size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); + size_t fieldsize = 0; + uint8_t is_foreign_type = dt->layout->fielddesc_type == 3; + if (!is_foreign_type) { + fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type); + } char *flddesc = (char*)dt->layout; size_t fldsize = sizeof(jl_datatype_layout_t) + nf * fieldsize; - if (dt->layout->first_ptr != -1) + if (!is_foreign_type && dt->layout->first_ptr != -1) fldsize += np << dt->layout->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 @@ -1161,11 +1573,19 @@ static void jl_write_values(jl_serializer_state *s) arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_datatype_t, layout))); // relocation location arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target ios_write(s->const_data, flddesc, fldsize); + if (is_foreign_type) { + // make sure we have space for the extra hidden pointers + // zero them since they will need to be re-initialized externally + assert(fldsize == sizeof(jl_datatype_layout_t)); + jl_fielddescdyn_t dyn = {0, 0}; + ios_write(s->const_data, (char*)&dyn, sizeof(jl_fielddescdyn_t)); + } } } else if (jl_is_typename(v)) { + assert(f == s->s); jl_typename_t *tn = (jl_typename_t*)v; - jl_typename_t *newtn = (jl_typename_t*)&s->s->buf[reloc_offset]; + jl_typename_t *newtn = (jl_typename_t*)&f->buf[reloc_offset]; if (tn->atomicfields != NULL) { size_t nb = (jl_svec_len(tn->names) + 31) / 32 * sizeof(uint32_t); uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*)); @@ -1187,85 +1607,36 @@ static void jl_write_values(jl_serializer_state *s) ios_write(s->const_data, (char*)tn->constfields, nb); } } + else if (jl_is_globalref(v)) { + assert(f == s->s); + jl_globalref_t *gr = (jl_globalref_t*)v; + if (s->incremental && jl_object_in_image((jl_value_t*)gr->mod)) { + // will need to populate the binding field later + arraylist_push(&s->fixup_objs, (void*)reloc_offset); + } + } else if (((jl_datatype_t*)(jl_typeof(v)))->name == jl_idtable_typename) { + assert(f == s->s); // will need to rehash this, later (after types are fully constructed) - arraylist_push(&reinit_list, (void*)item); - arraylist_push(&reinit_list, (void*)1); + arraylist_push(&s->fixup_objs, (void*)reloc_offset); } else { - write_padding(s->s, t->size - tot); + write_padding(f, jl_datatype_size(t) - tot); } } } } - -// Record all symbols that get referenced by the generated code -// and queue them for pointer relocation -static void jl_write_gv_syms(jl_serializer_state *s, jl_sym_t *v) -{ - // since symbols are static, they might not have had a - // reference anywhere in the code image other than here - int32_t gv = jl_get_llvm_gv(native_functions, (jl_value_t*)v); - if (gv != 0) { - uintptr_t item = backref_id(s, v); - assert(item >> RELOC_TAG_OFFSET == SymbolRef); - record_gvar(s, gv, item); - } - if (v->left) - jl_write_gv_syms(s, v->left); - if (v->right) - jl_write_gv_syms(s, v->right); -} - -// Record all hardcoded-tagged items that get referenced by -// the generated code and queue them for pointer relocation -static void jl_write_gv_tagref(jl_serializer_state *s, jl_value_t *v) -{ - int32_t gv = jl_get_llvm_gv(native_functions, (jl_value_t*)v); - if (gv != 0) { - uintptr_t item = backref_id(s, v); - assert(item >> RELOC_TAG_OFFSET == TagRef); - record_gvar(s, gv, item); - } -} -static void jl_write_gv_tagrefs(jl_serializer_state *s) -{ - // this also ensures all objects referenced in the code have - // references in the system image to their global variable - // since codegen knows that some integer boxes are static, - // they might not have had a reference anywhere in the code - // image other than here - size_t i; - jl_write_gv_tagref(s, (jl_value_t*)s->ptls->root_task); - jl_write_gv_tagref(s, s->ptls->root_task->tls); - jl_write_gv_tagref(s, jl_nothing); - for (i = 0; i < NBOX_C; i++) { - jl_write_gv_tagref(s, jl_box_int32((int32_t)i - NBOX_C / 2)); - jl_write_gv_tagref(s, jl_box_int64((int64_t)i - NBOX_C / 2)); - } - for (i = 0; i < 256; i++) { - jl_write_gv_tagref(s, jl_box_uint8(i)); - } -} - -static inline uint32_t load_uint32(uintptr_t *base) -{ - uint32_t v = jl_load_unaligned_i32((void*)*base); - *base += 4; - return v; -} - - // In deserialization, create Symbols and set up the // index for backreferencing static void jl_read_symbols(jl_serializer_state *s) { - assert(deser_sym.len == nsym_tag); + assert(deser_sym.len == 0); uintptr_t base = (uintptr_t)&s->symbols->buf[0]; uintptr_t end = base + s->symbols->size; while (base < end) { - uint32_t len = load_uint32(&base); + uint32_t len = jl_load_unaligned_i32((void*)base); + base += 4; const char *str = (const char*)base; base += len + 1; //printf("symbol %3d: %s\n", len, str); @@ -1286,8 +1657,16 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) assert(reloc_item < layout_table.len); uintptr_t reloc_base = (uintptr_t)layout_table.items[reloc_item]; assert(reloc_base != 0 && "layout offset missing for relocation item"); + if (reloc_base & 1) { + // convert to a ConstDataRef + tag = ConstDataRef; + reloc_base &= ~(uintptr_t)1; + assert(LLT_ALIGN(reloc_base, sizeof(void*)) == reloc_base); + reloc_base /= sizeof(void*); + assert(reloc_offset == 0); + } // write reloc_offset into s->s at pos - return reloc_base + reloc_offset; + return ((uintptr_t)tag << RELOC_TAG_OFFSET) + reloc_base + reloc_offset; } else { // just write the item reloc_id directly @@ -1303,16 +1682,19 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) case TagRef: assert(offset < 2 * NBOX_C + 258 && "corrupt relocation item id"); break; - case BindingRef: - assert(offset == 0 && "corrupt relocation offset"); + case FunctionRef: + if (offset & BuiltinFunctionTag) { + offset &= ~BuiltinFunctionTag; + assert(offset < sizeof(id_to_fptrs) / sizeof(*id_to_fptrs) && "unknown function pointer id"); + } + else { + assert(offset < JL_API_MAX && "unknown function pointer id"); + } break; - case BuiltinFunctionRef: - assert(offset < sizeof(id_to_fptrs) / sizeof(*id_to_fptrs) && "unknown function pointer id"); + case SysimageLinkage: break; - case FunctionRef: - assert(offset < JL_API_MAX && "unknown function pointer id"); + case ExternalLinkage: break; - case DataRef: default: assert(0 && "corrupt relocation item id"); abort(); @@ -1323,21 +1705,21 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) } // Compute target location at deserialization -static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t base, size_t size, uint32_t reloc_id) +static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t base, uintptr_t reloc_id, jl_array_t *link_ids, int *link_index) JL_NOTSAFEPOINT { enum RefTags tag = (enum RefTags)(reloc_id >> RELOC_TAG_OFFSET); size_t offset = (reloc_id & (((uintptr_t)1 << RELOC_TAG_OFFSET) - 1)); switch (tag) { case DataRef: - assert(offset <= size); - return base + offset; + assert(offset <= s->s->size); + return (uintptr_t)base + offset; case ConstDataRef: - return (uintptr_t)s->const_data->buf + (offset * sizeof(void*)); + offset *= sizeof(void*); + assert(offset <= s->const_data->size); + return (uintptr_t)s->const_data->buf + offset; case SymbolRef: assert(offset < deser_sym.len && deser_sym.items[offset] && "corrupt relocation item id"); return (uintptr_t)deser_sym.items[offset]; - case BindingRef: - return jl_buff_tag | GC_OLD_MARKED; case TagRef: if (offset == 0) return (uintptr_t)s->ptls->root_task; @@ -1355,17 +1737,19 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas // offset -= 256; assert(0 && "corrupt relocation item id"); jl_unreachable(); // terminate control flow if assertion is disabled. - case BuiltinFunctionRef: - assert(offset < sizeof(id_to_fptrs) / sizeof(*id_to_fptrs) && "unknown function pointer ID"); - return (uintptr_t)id_to_fptrs[offset]; case FunctionRef: + if (offset & BuiltinFunctionTag) { + offset &= ~BuiltinFunctionTag; + assert(offset < sizeof(id_to_fptrs) / sizeof(*id_to_fptrs) && "unknown function pointer ID"); + return (uintptr_t)id_to_fptrs[offset]; + } switch ((jl_callingconv_t)offset) { case JL_API_BOXED: - if (sysimg_fptrs.base) + if (s->image->fptrs.base) return (uintptr_t)jl_fptr_args; JL_FALLTHROUGH; case JL_API_WITH_PARAMETERS: - if (sysimg_fptrs.base) + if (s->image->fptrs.base) return (uintptr_t)jl_fptr_sparam; return (uintptr_t)NULL; case JL_API_CONST: @@ -1379,120 +1763,235 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas //default: assert("corrupt relocation item id"); } + case SysimageLinkage: { +#ifdef _P64 + size_t depsidx = offset >> DEPS_IDX_OFFSET; + offset &= ((size_t)1 << DEPS_IDX_OFFSET) - 1; +#else + 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]; + assert(2*i < jl_linkage_blobs.len); + return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); + } + case ExternalLinkage: { + 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]; + *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]; + assert(2*i < jl_linkage_blobs.len); + return (uintptr_t)jl_linkage_blobs.items[2*i] + offset*sizeof(void*); + } } abort(); } -static void jl_write_skiplist(ios_t *s, char *base, size_t size, arraylist_t *list) +static void jl_finish_relocs(char *base, size_t size, arraylist_t *list) { - size_t i; - for (i = 0; i < list->len; i += 2) { + for (size_t i = 0; i < list->len; i += 2) { size_t pos = (size_t)list->items[i]; - size_t item = (size_t)list->items[i + 1]; + size_t item = (size_t)list->items[i + 1]; // item is tagref-encoded uintptr_t *pv = (uintptr_t*)(base + pos); assert(pos < size && pos != 0); *pv = get_reloc_for_item(item, *pv); - // record pos in relocations list - // TODO: save space by using delta-compression - assert(pos < UINT32_MAX); - write_uint32(s, pos); } - write_uint32(s, 0); } - -static void jl_write_relocations(jl_serializer_state *s) +static void jl_write_offsetlist(ios_t *s, size_t size, arraylist_t *list) { - char *base = &s->s->buf[0]; - jl_write_skiplist(s->relocs, base, s->s->size, &s->gctags_list); - jl_write_skiplist(s->relocs, base, s->s->size, &s->relocs_list); + for (size_t i = 0; i < list->len; i += 2) { + size_t last_pos = i ? (size_t)list->items[i - 2] : 0; + size_t pos = (size_t)list->items[i]; + assert(pos < size && pos != 0); + // write pos as compressed difference. + size_t pos_diff = pos - last_pos; + while (pos_diff) { + assert(pos_diff >= 0); + if (pos_diff <= 127) { + write_int8(s, pos_diff); + break; + } + else { + // Extract the next 7 bits + int8_t ns = pos_diff & (int8_t)0x7F; + pos_diff >>= 7; + // Set the high bit if there's still more + ns |= (!!pos_diff) << 7; + write_int8(s, ns); + } + } + } + write_int8(s, 0); } -static void jl_read_relocations(jl_serializer_state *s, uint8_t bits) +static void jl_write_arraylist(ios_t *s, arraylist_t *list) { - uintptr_t base = (uintptr_t)&s->s->buf[0]; - size_t size = s->s->size; + write_uint(s, list->len); + ios_write(s, (const char*)list->items, list->len * sizeof(void*)); +} + +static void jl_read_reloclist(jl_serializer_state *s, jl_array_t *link_ids, uint8_t bits) +{ + uintptr_t base = (uintptr_t)s->s->buf; + uintptr_t last_pos = 0; + uint8_t *current = (uint8_t *)(s->relocs->buf + s->relocs->bpos); + int link_index = 0; while (1) { - uintptr_t val = (uintptr_t)&s->relocs->buf[s->relocs->bpos]; - uint32_t offset = load_uint32(&val); - s->relocs->bpos += sizeof(uint32_t); - if (offset == 0) + // 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 *pv = (uintptr_t*)(base + offset); + + uintptr_t pos = last_pos + pos_diff; + last_pos = pos; + uintptr_t *pv = (uintptr_t *)(base + pos); uintptr_t v = *pv; - v = get_item_for_reloc(s, base, size, v); + v = get_item_for_reloc(s, base, v, link_ids, &link_index); + if (bits && v && ((jl_datatype_t*)v)->smalltag) + v = (uintptr_t)((jl_datatype_t*)v)->smalltag << 4; // TODO: should we have a representation that supports sweep without a relocation step? *pv = v | bits; } + assert(!link_ids || link_index == jl_array_len(link_ids)); +} + +static void jl_read_arraylist(ios_t *s, arraylist_t *list) +{ + size_t list_len = read_uint(s); + arraylist_new(list, 0); + arraylist_grow(list, list_len); + ios_read(s, (char*)list->items, list_len * sizeof(void*)); } -static char* sysimg_base; -static char* sysimg_relocs; void gc_sweep_sysimg(void) { - uintptr_t base = (uintptr_t)sysimg_base; - uintptr_t relocs = (uintptr_t)sysimg_relocs; - if (relocs == 0) + size_t nblobs = n_linkage_blobs(); + if (nblobs == 0) return; - while (1) { - uint32_t offset = load_uint32(&relocs); - if (offset == 0) - break; - jl_taggedvalue_t *o = (jl_taggedvalue_t*)(base + offset); - o->bits.gc = GC_OLD; + assert(jl_linkage_blobs.len == 2*nblobs); + assert(jl_image_relocs.len == nblobs); + for (size_t i = 0; i < 2*nblobs; i+=2) { + reloc_t *relocs = (reloc_t*)jl_image_relocs.items[i>>1]; + if (!relocs) + continue; + uintptr_t base = (uintptr_t)jl_linkage_blobs.items[i]; + uintptr_t last_pos = 0; + uint8_t *current = (uint8_t *)relocs; + while (1) { + // Read the offset of the next object + size_t pos_diff = 0; + size_t cnt = 0; + while (1) { + int8_t c = *current++; + 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_taggedvalue_t *o = (jl_taggedvalue_t *)(base + pos); + o->bits.gc = GC_OLD; + assert(o->bits.in_image == 1); + } } } +// jl_write_value and jl_read_value are used for storing Julia objects that are adjuncts to +// the image proper. For example, new methods added to external callables require +// insertion into the appropriate method table. #define jl_write_value(s, v) _jl_write_value((s), (jl_value_t*)(v)) static void _jl_write_value(jl_serializer_state *s, jl_value_t *v) { if (v == NULL) { - write_uint32(s->s, 0); + write_reloc_t(s->s, 0); return; } - uintptr_t item = backref_id(s, v); + uintptr_t item = backref_id(s, v, NULL); uintptr_t reloc = get_reloc_for_item(item, 0); - assert(reloc < UINT32_MAX); - write_uint32(s->s, reloc); + write_reloc_t(s->s, reloc); } - static jl_value_t *jl_read_value(jl_serializer_state *s) { - uintptr_t base = (uintptr_t)&s->s->buf[0]; - size_t size = s->s->size; - uintptr_t val = base + s->s->bpos; - uint32_t offset = load_uint32(&val); - s->s->bpos += sizeof(uint32_t); + uintptr_t base = (uintptr_t)s->s->buf; + uintptr_t offset = *(reloc_t*)(base + (uintptr_t)s->s->bpos); + s->s->bpos += sizeof(reloc_t); if (offset == 0) return NULL; - return (jl_value_t*)get_item_for_reloc(s, base, size, offset); + return (jl_value_t*)get_item_for_reloc(s, base, offset, NULL, NULL); +} + +// The next two, `jl_read_offset` and `jl_delayed_reloc`, are essentially a split version +// of `jl_read_value` that allows usage of the relocation data rather than passing NULL +// to `get_item_for_reloc`. +// This works around what would otherwise be an order-dependency conundrum: objects +// that may require relocation data have to be inserted into `serialization_order`, +// and that may include some of the adjunct data that gets serialized via +// `jl_write_value`. But we can't interpret them properly until we read the relocation +// data, and that happens after we pull items out of the serialization stream. +static uintptr_t jl_read_offset(jl_serializer_state *s) +{ + uintptr_t base = (uintptr_t)&s->s->buf[0]; + uintptr_t offset = *(reloc_t*)(base + (uintptr_t)s->s->bpos); + s->s->bpos += sizeof(reloc_t); + return offset; } +static jl_value_t *jl_delayed_reloc(jl_serializer_state *s, uintptr_t offset) JL_GC_DISABLED +{ + if (!offset) + return NULL; + uintptr_t base = (uintptr_t)s->s->buf; + int link_index = 0; + jl_value_t *ret = (jl_value_t*)get_item_for_reloc(s, base, offset, s->link_ids_relocs, &link_index); + assert(!s->link_ids_relocs || link_index < jl_array_len(s->link_ids_relocs)); + return ret; +} -static void jl_update_all_fptrs(jl_serializer_state *s) + +static void jl_update_all_fptrs(jl_serializer_state *s, jl_image_t *image) { - jl_sysimg_fptrs_t fvars = sysimg_fptrs; + jl_image_fptrs_t fvars = image->fptrs; // make these NULL now so we skip trying to restore GlobalVariable pointers later - sysimg_gvars_base = NULL; - sysimg_fptrs.base = NULL; + image->gvars_base = NULL; + image->fptrs.base = NULL; if (fvars.base == NULL) return; - int sysimg_fvars_max = s->fptr_record->size / sizeof(void*); + + memcpy(image->small_typeof, &small_typeof, sizeof(small_typeof)); + + int img_fvars_max = s->fptr_record->size / sizeof(void*); size_t i; uintptr_t base = (uintptr_t)&s->s->buf[0]; // These will become MethodInstance references, but they start out as a list of // offsets into `s` for CodeInstances jl_method_instance_t **linfos = (jl_method_instance_t**)&s->fptr_record->buf[0]; uint32_t clone_idx = 0; - for (i = 0; i < sysimg_fvars_max; i++) { - uintptr_t val = (uintptr_t)&linfos[i]; - uint32_t offset = load_uint32(&val); + for (i = 0; i < img_fvars_max; i++) { + reloc_t offset = *(reloc_t*)&linfos[i]; linfos[i] = NULL; if (offset != 0) { int specfunc = 1; - if (offset & ((uintptr_t)1 << (8 * sizeof(uint32_t) - 1))) { + if (offset & ((uintptr_t)1 << (8 * sizeof(reloc_t) - 1))) { // if high bit is set, this is the func wrapper, not the specfunc specfunc = 0; offset = ~offset; @@ -1514,7 +2013,7 @@ static void jl_update_all_fptrs(jl_serializer_state *s) void *fptr = (void*)(base + offset); if (specfunc) { codeinst->specptr.fptr = fptr; - codeinst->isspecsig = 1; // TODO: set only if confirmed to be true + codeinst->specsigflags = 0b111; // TODO: set only if confirmed to be true } else { codeinst->invoke = (jl_callptr_t)fptr; @@ -1522,141 +2021,124 @@ static void jl_update_all_fptrs(jl_serializer_state *s) } } // Tell LLVM about the native code - jl_register_fptrs(sysimage_base, &fvars, linfos, sysimg_fvars_max); + jl_register_fptrs(image->base, &fvars, linfos, img_fvars_max); } - -// Pointer relocation for native-code referenced global variables -static void jl_update_all_gvars(jl_serializer_state *s) +static uint32_t write_gvars(jl_serializer_state *s, arraylist_t *globals, arraylist_t *external_fns) JL_NOTSAFEPOINT { - if (sysimg_gvars_base == NULL) - return; - size_t gvname_index = 0; - uintptr_t base = (uintptr_t)&s->s->buf[0]; - size_t size = s->s->size; - uintptr_t gvars = (uintptr_t)&s->gvar_record->buf[0]; - uintptr_t end = gvars + s->gvar_record->size; - while (gvars < end) { - uint32_t offset = load_uint32(&gvars); - if (offset) { - uintptr_t v = get_item_for_reloc(s, base, size, offset); - *sysimg_gvars(sysimg_gvars_base, gvname_index) = v; - } - gvname_index += 1; + size_t len = globals->len + external_fns->len; + ios_ensureroom(s->gvar_record, len * sizeof(reloc_t)); + for (size_t i = 0; i < globals->len; i++) { + void *g = globals->items[i]; + uintptr_t item = backref_id(s, g, s->link_ids_gvars); + uintptr_t reloc = get_reloc_for_item(item, 0); + write_reloc_t(s->gvar_record, reloc); + record_uniquing(s, (jl_value_t*)g, ((i << 2) | 2)); // mark as gvar && !tag + } + for (size_t i = 0; i < external_fns->len; i++) { + jl_code_instance_t *ci = (jl_code_instance_t*)external_fns->items[i]; + assert(ci && (jl_atomic_load_relaxed(&ci->specsigflags) & 0b001)); + uintptr_t item = backref_id(s, (void*)ci, s->link_ids_external_fnvars); + uintptr_t reloc = get_reloc_for_item(item, 0); + write_reloc_t(s->gvar_record, reloc); } + return globals->len; } - -// Reinitialization -static void jl_finalize_serializer(jl_serializer_state *s, arraylist_t *list) +// Pointer relocation for native-code referenced global variables +static void jl_update_all_gvars(jl_serializer_state *s, jl_image_t *image, uint32_t external_fns_begin) { - size_t i, l; - - // record list of reinitialization functions - l = list->len; - for (i = 0; i < l; i += 2) { - size_t item = (size_t)list->items[i]; - size_t reloc_offset = (size_t)layout_table.items[item]; - assert(reloc_offset != 0); - write_uint32(s->s, (uint32_t)reloc_offset); - write_uint32(s->s, (uint32_t)((uintptr_t)list->items[i + 1])); + if (image->gvars_base == NULL) + return; + uintptr_t base = (uintptr_t)s->s->buf; + size_t i = 0; + size_t l = s->gvar_record->size / sizeof(reloc_t); + reloc_t *gvars = (reloc_t*)&s->gvar_record->buf[0]; + int gvar_link_index = 0; + int external_fns_link_index = 0; + assert(l == image->ngvars); + for (i = 0; i < l; i++) { + uintptr_t offset = gvars[i]; + uintptr_t v = 0; + if (i < external_fns_begin) { + v = get_item_for_reloc(s, base, offset, s->link_ids_gvars, &gvar_link_index); + } + else { + v = get_item_for_reloc(s, base, offset, s->link_ids_external_fnvars, &external_fns_link_index); + } + uintptr_t *gv = sysimg_gvars(image->gvars_base, image->gvars_offsets, i); + *gv = v; } - write_uint32(s->s, 0); + assert(!s->link_ids_gvars || gvar_link_index == jl_array_len(s->link_ids_gvars)); + assert(!s->link_ids_external_fnvars || external_fns_link_index == jl_array_len(s->link_ids_external_fnvars)); } - -static void jl_reinit_item(jl_value_t *v, int how) JL_GC_DISABLED +static void jl_root_new_gvars(jl_serializer_state *s, jl_image_t *image, uint32_t external_fns_begin) { - switch (how) { - case 1: { // rehash IdDict - jl_array_t **a = (jl_array_t**)v; - assert(jl_is_array(*a)); - // Assume *a don't need a write barrier - *a = jl_idtable_rehash(*a, jl_array_len(*a)); - jl_gc_wb(v, *a); - break; - } - case 2: { // rebuild the binding table for module v - jl_module_t *mod = (jl_module_t*)v; - assert(jl_is_module(mod)); - size_t nbindings = mod->bindings.size; - htable_new(&mod->bindings, nbindings); - struct binding { - jl_sym_t *asname; - uintptr_t tag; - jl_binding_t b; - } *b; - b = (struct binding*)&mod[1]; - while (nbindings > 0) { - ptrhash_put(&mod->bindings, b->asname, &b->b); - b += 1; - nbindings -= 1; - } - if (mod->usings.items != &mod->usings._space[0]) { - void **newitems = (void**)malloc_s(mod->usings.max * sizeof(void*)); - memcpy(newitems, mod->usings.items, mod->usings.len * sizeof(void*)); - mod->usings.items = newitems; - } - break; - } - case 3: { // install ccallable entry point in JIT - jl_svec_t *sv = ((jl_method_t*)v)->ccallable; - int success = jl_compile_extern_c(NULL, NULL, jl_sysimg_handle, jl_svecref(sv, 0), jl_svecref(sv, 1)); - assert(success); (void)success; - break; + if (image->gvars_base == NULL) + return; + size_t i = 0; + size_t l = s->gvar_record->size / sizeof(reloc_t); + for (i = 0; i < l; i++) { + uintptr_t *gv = sysimg_gvars(image->gvars_base, image->gvars_offsets, i); + 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 { + jl_code_instance_t *codeinst = (jl_code_instance_t*) v; + assert(codeinst && (codeinst->specsigflags & 0b01) && codeinst->specptr.fptr); + v = (uintptr_t)codeinst->specptr.fptr; } - default: - assert(0 && "corrupt deserialization state"); - abort(); + *gv = v; } } -static void jl_finalize_deserializer(jl_serializer_state *s) JL_GC_DISABLED +static void jl_compile_extern(jl_method_t *m, void *sysimg_handle) JL_GC_DISABLED { - // run reinitialization functions - uintptr_t base = (uintptr_t)&s->s->buf[0]; - while (1) { - size_t offset = read_uint32(s->s); - if (offset == 0) - break; - jl_value_t *v = (jl_value_t*)(base + offset); - jl_reinit_item(v, read_uint32(s->s)); - } + // install ccallable entry point in JIT + assert(m); // makes clang-sa happy + jl_svec_t *sv = m->ccallable; + int success = jl_compile_extern_c(NULL, NULL, sysimg_handle, jl_svecref(sv, 0), jl_svecref(sv, 1)); + if (!success) + jl_safe_printf("WARNING: @ccallable was already defined for this method name\n"); // enjoy a very bad time + assert(success || !sysimg_handle); } - -// Code below helps slim down the images -static void jl_scan_type_cache_gv(jl_serializer_state *s, jl_svec_t *cache) +static void jl_reinit_ccallable(arraylist_t *ccallable_list, char *base, void *sysimg_handle) { - size_t l = jl_svec_len(cache), i; - for (i = 0; i < l; i++) { - jl_value_t *ti = jl_svecref(cache, i); - if (ti == NULL || ti == jl_nothing) - continue; - if (jl_get_llvm_gv(native_functions, ti)) { - jl_serialize_value(s, ti); - } - else if (jl_is_datatype(ti)) { - jl_value_t *singleton = ((jl_datatype_t*)ti)->instance; - if (singleton && jl_get_llvm_gv(native_functions, singleton)) - jl_serialize_value(s, ti); - } + for (size_t i = 0; i < ccallable_list->len; i++) { + uintptr_t item = (uintptr_t)ccallable_list->items[i]; + jl_method_t *m = (jl_method_t*)(base + item); + jl_compile_extern(m, sysimg_handle); } } -// remove cached types not referenced in the stream -static void jl_prune_type_cache_hash(jl_svec_t *cache) + +// Code below helps slim down the images by +// removing cached types not referenced in the stream +static jl_svec_t *jl_prune_type_cache_hash(jl_svec_t *cache) JL_GC_DISABLED { size_t l = jl_svec_len(cache), i; + if (l == 0) + return cache; for (i = 0; i < l; i++) { jl_value_t *ti = jl_svecref(cache, i); - if (ti == NULL || ti == jl_nothing) + if (ti == jl_nothing) continue; - if (ptrhash_get(&backref_table, ti) == HT_NOTFOUND) + if (ptrhash_get(&serialization_order, ti) == HT_NOTFOUND) jl_svecset(cache, i, jl_nothing); } + 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); + // 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; + return cache; } static void jl_prune_type_cache_linear(jl_svec_t *cache) @@ -1664,14 +2146,13 @@ static void jl_prune_type_cache_linear(jl_svec_t *cache) size_t l = jl_svec_len(cache), ins = 0, i; for (i = 0; i < l; i++) { jl_value_t *ti = jl_svecref(cache, i); - if (ti == NULL) + if (ti == jl_nothing) break; - if (ptrhash_get(&backref_table, ti) != HT_NOTFOUND) + if (ptrhash_get(&serialization_order, ti) != HT_NOTFOUND) jl_svecset(cache, ins++, ti); } - if (i > ins) { - memset(&jl_svec_data(cache)[ins], 0, (i - ins) * sizeof(jl_value_t*)); - } + while (ins < l) + jl_svecset(cache, ins++, jl_nothing); } static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, int orig) @@ -1681,7 +2162,7 @@ static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, int orig int compressed = 0; if (!jl_is_code_info(ci_)) { compressed = 1; - ci = jl_uncompress_ir(m, NULL, (jl_array_t*)ci_); + ci = jl_uncompress_ir(m, NULL, (jl_value_t*)ci_); } else { ci = (jl_code_info_t*)ci_; @@ -1710,29 +2191,29 @@ static jl_value_t *strip_codeinfo_meta(jl_method_t *m, jl_value_t *ci_, int orig return ret; } -static void record_field_change(jl_value_t **addr, jl_value_t *newval) -{ - ptrhash_put(&field_replace, (void*)addr, newval); -} - static void strip_specializations_(jl_method_instance_t *mi) { assert(jl_is_method_instance(mi)); - jl_code_instance_t *codeinst = mi->cache; + jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { - if (codeinst->inferred && codeinst->inferred != jl_nothing) { + jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred); + if (inferred && inferred != jl_nothing) { if (jl_options.strip_ir) { - record_field_change(&codeinst->inferred, jl_nothing); + record_field_change((jl_value_t**)&codeinst->inferred, jl_nothing); } else if (jl_options.strip_metadata) { - codeinst->inferred = strip_codeinfo_meta(mi->def.method, codeinst->inferred, 0); - jl_gc_wb(codeinst, codeinst->inferred); + jl_value_t *stripped = strip_codeinfo_meta(mi->def.method, inferred, 0); + if (jl_atomic_cmpswap_relaxed(&codeinst->inferred, &inferred, stripped)) { + jl_gc_wb(codeinst, stripped); + } } } codeinst = jl_atomic_load_relaxed(&codeinst->next); } if (jl_options.strip_ir) { - record_field_change(&mi->uninferred, NULL); + 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); } } @@ -1764,20 +2245,29 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) jl_gc_wb(m, m->source); } } - jl_svec_t *specializations = m->specializations; - size_t i, l = jl_svec_len(specializations); - for (i = 0; i < l; i++) { - jl_value_t *mi = jl_svecref(specializations, i); - if (mi != jl_nothing) - strip_specializations_((jl_method_instance_t*)mi); + jl_value_t *specializations = m->specializations; + if (!jl_is_svec(specializations)) { + strip_specializations_((jl_method_instance_t*)specializations); + } + else { + size_t i, l = jl_svec_len(specializations); + for (i = 0; i < l; i++) { + jl_value_t *mi = jl_svecref(specializations, i); + if (mi != jl_nothing) + strip_specializations_((jl_method_instance_t*)mi); + } } if (m->unspecialized) strip_specializations_(m->unspecialized); + if (jl_options.strip_ir && m->root_blocks) + record_field_change((jl_value_t**)&m->root_blocks, NULL); return 1; } 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); } @@ -1786,59 +2276,132 @@ static void jl_strip_all_codeinfos(void) jl_foreach_reachable_mtable(strip_all_codeinfos_, NULL); } -// Method roots created during sysimg construction are exempted from -// triggering non-relocatability of compressed CodeInfos. -// Set the number of such roots in each method when the sysimg is -// serialized. -static int set_nroots_sysimg__(jl_typemap_entry_t *def, void *_env) -{ - jl_method_t *m = def->func.method; - m->nroots_sysimg = m->roots ? jl_array_len(m->roots) : 0; - return 1; -} +// --- entry points --- -static int set_nroots_sysimg_(jl_methtable_t *mt, void *_env) +jl_array_t *jl_global_roots_table; +jl_mutex_t global_roots_lock; + +JL_DLLEXPORT int jl_is_globally_rooted(jl_value_t *val JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT { - return jl_typemap_visitor(mt->defs, set_nroots_sysimg__, NULL); + if (jl_is_concrete_type(val) || 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) + return 1; + return 0; } -static void jl_set_nroots_sysimg(void) +JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val JL_MAYBE_UNROOTED) { - jl_foreach_reachable_mtable(set_nroots_sysimg_, NULL); + if (jl_is_globally_rooted(val)) + return val; + if (jl_is_uint8(val)) + return jl_box_uint8(jl_unbox_uint8(val)); + if (jl_is_int32(val)) { + int32_t n = jl_unbox_int32(val); + if ((uint32_t)(n+512) < 1024) + return jl_box_int32(n); + } + else if (jl_is_int64(val)) { + uint64_t n = jl_unbox_uint64(val); + if ((uint64_t)(n+512) < 1024) + return jl_box_int64(n); + } + JL_GC_PUSH1(&val); + JL_LOCK(&global_roots_lock); + jl_value_t *rval = jl_eqtable_getkey(jl_global_roots_table, val, NULL); + if (rval) { + val = rval; + } + else { + jl_global_roots_table = jl_eqtable_put(jl_global_roots_table, val, jl_nothing, NULL); + } + JL_UNLOCK(&global_roots_lock); + JL_GC_POP(); + return val; } -// --- entry points --- +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, + 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 + // ext_targets: [invokesig1, callee1, matches1, ...] non-worklist callees of worklist-owned methods + // ordinary dispatch: invokesig=NULL, callee is MethodInstance + // `invoke` dispatch: invokesig is signature, callee is MethodInstance + // abstract call: callee is signature + // edges: [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods + assert(edges_map == NULL); + + // Save the inferred code from newly inferred, external methods + *new_specializations = 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); + *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); + size_t i, len = jl_array_len(mod_array); + for (i = 0; i < len; 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 + jl_collect_extext_methods_from_mod(*extext_methods, m); + } + + if (edges) { + size_t world = jl_atomic_load_acquire(&jl_world_counter); + jl_collect_missing_backedges(jl_type_type_mt); + jl_collect_missing_backedges(jl_nonfunction_mt); + // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges also accumulate data in callers_with_edges. + // Process this to extract `edges` and `ext_targets`. + *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); + } + assert(edges_map == NULL); // jl_collect_edges clears this when done -static void jl_init_serializer2(int); -static void jl_cleanup_serializer2(void); + JL_GC_POP(); +} -static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED +// 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 *ext_targets, jl_array_t *edges) JL_GC_DISABLED { - jl_gc_collect(JL_GC_FULL); - jl_gc_collect(JL_GC_INCREMENTAL); // sweep finalizers - JL_TIMING(SYSIMG_DUMP); - - htable_new(&field_replace, 10000); + htable_new(&field_replace, 0); // strip metadata and IR when requested if (jl_options.strip_metadata || jl_options.strip_ir) jl_strip_all_codeinfos(); - jl_set_nroots_sysimg(); int en = jl_gc_enable(0); - jl_init_serializer2(1); - htable_reset(&backref_table, 250000); - arraylist_new(&reinit_list, 0); - arraylist_new(&ccallable_list, 0); + nsym_tag = 0; + htable_new(&symbol_table, 0); + htable_new(&fptr_to_id, sizeof(id_to_fptrs) / sizeof(*id_to_fptrs)); + uintptr_t i; + for (i = 0; id_to_fptrs[i] != NULL; i++) { + 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); - backref_table_numel = 0; + arraylist_new(&serialization_queue, 0); ios_t sysimg, const_data, symbols, relocs, gvar_record, fptr_record; - ios_mem(&sysimg, 1000000); - ios_mem(&const_data, 100000); - ios_mem(&symbols, 100000); - ios_mem(&relocs, 100000); - ios_mem(&gvar_record, 100000); - ios_mem(&fptr_record, 100000); - jl_serializer_state s; + ios_mem(&sysimg, 0); + ios_mem(&const_data, 0); + ios_mem(&symbols, 0); + ios_mem(&relocs, 0); + ios_mem(&gvar_record, 0); + ios_mem(&fptr_record, 0); + jl_serializer_state s = {0}; + s.incremental = !(worklist == NULL); s.s = &sysimg; s.const_data = &const_data; s.symbols = &symbols; @@ -1848,16 +2411,37 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED s.ptls = jl_current_task->ptls; arraylist_new(&s.relocs_list, 0); arraylist_new(&s.gctags_list, 0); - jl_value_t **const*const tags = get_tags(); - - // empty!(Core.ARGS) - if (jl_core_module != NULL) { - jl_array_t *args = (jl_array_t*)jl_get_global(jl_core_module, jl_symbol("ARGS")); - if (args != NULL) { - jl_array_del_end(args, jl_array_len(args)); + arraylist_new(&s.uniquing_types, 0); + arraylist_new(&s.uniquing_objs, 0); + arraylist_new(&s.fixup_types, 0); + arraylist_new(&s.fixup_objs, 0); + arraylist_new(&s.ccallable_list, 0); + s.buildid_depmods_idxs = image_to_depmodidx(mod_array); + s.link_ids_relocs = jl_alloc_array_1d(jl_array_int32_type, 0); + s.link_ids_gctags = jl_alloc_array_1d(jl_array_int32_type, 0); + s.link_ids_gvars = jl_alloc_array_1d(jl_array_int32_type, 0); + s.link_ids_external_fnvars = jl_alloc_array_1d(jl_array_int32_type, 0); + htable_new(&s.callers_with_edges, 0); + jl_value_t **const*const tags = get_tags(); // worklist == NULL ? get_tags() : NULL; + + arraylist_t gvars; + arraylist_t external_fns; + arraylist_new(&gvars, 0); + arraylist_new(&external_fns, 0); + if (native_functions) { + jl_get_llvm_gvs(native_functions, &gvars); + jl_get_llvm_external_fns(native_functions, &external_fns); + } + + if (worklist == NULL) { + // empty!(Core.ARGS) + if (jl_core_module != NULL) { + jl_array_t *args = (jl_array_t*)jl_get_global(jl_core_module, jl_symbol("ARGS")); + if (args != NULL) { + jl_array_del_end(args, jl_array_len(args)); + } } } - 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; @@ -1874,133 +2458,342 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED { // step 1: record values (recursively) that need to go in the image size_t i; - for (i = 0; tags[i] != NULL; i++) { - jl_value_t *tag = *tags[i]; - jl_serialize_value(&s, tag); + if (worklist == NULL) { + for (i = 0; tags[i] != NULL; i++) { + 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); } - jl_serialize_reachable(&s); - // step 1.1: check for values only found in the generated code - arraylist_t typenames; - arraylist_new(&typenames, 0); - for (i = 0; i < backref_table.size; i += 2) { - jl_typename_t *tn = (jl_typename_t*)backref_table.table[i]; - if (tn == HT_NOTFOUND || !jl_is_typename(tn)) - continue; - arraylist_push(&typenames, tn); + 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); + // Classify the CodeInstances with respect to their need for validation + classify_callers(&s.callers_with_edges, edges); } - for (i = 0; i < typenames.len; i++) { - jl_typename_t *tn = (jl_typename_t*)typenames.items[i]; - jl_scan_type_cache_gv(&s, tn->cache); - jl_scan_type_cache_gv(&s, tn->linearcache); + // step 1.1: as needed, serialize the data needed for insertion into the running system + if (extext_methods) { + assert(ext_targets); + assert(edges); + // Queue method extensions + jl_queue_for_serialization(&s, extext_methods); + // Queue the new specializations + jl_queue_for_serialization(&s, new_specializations); + // Queue the new roots + jl_queue_for_serialization(&s, method_roots_list); + // Queue the edges + jl_queue_for_serialization(&s, ext_targets); + jl_queue_for_serialization(&s, edges); } jl_serialize_reachable(&s); - // step 1.2: prune (garbage collect) some special weak references from + // step 1.2: ensure all gvars are part of the sysimage too + 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 - for (i = 0; i < typenames.len; i++) { - jl_typename_t *tn = (jl_typename_t*)typenames.items[i]; - jl_prune_type_cache_hash(tn->cache); - jl_prune_type_cache_linear(tn->linearcache); + 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); + } } - arraylist_free(&typenames); } + uint32_t external_fns_begin = 0; { // step 2: build all the sysimg sections - write_padding(&sysimg, sizeof(uint32_t)); + write_padding(&sysimg, sizeof(uintptr_t)); jl_write_values(&s); - jl_write_relocations(&s); - jl_write_gv_syms(&s, jl_get_root_symbol()); - jl_write_gv_tagrefs(&s); + external_fns_begin = write_gvars(&s, &gvars, &external_fns); } - if (sysimg.size > ((uintptr_t)1 << RELOC_TAG_OFFSET) || - const_data.size > ((uintptr_t)1 << RELOC_TAG_OFFSET)*sizeof(void*)) { - jl_printf(JL_STDERR, "ERROR: system image too large\n"); + // This ensures that we can use the low bit of addresses for + // identifying end pointers in gc's eytzinger search. + write_padding(&sysimg, 4 - (sysimg.size % 4)); + write_padding(&const_data, 4 - (const_data.size % 4)); + + if (sysimg.size > ((uintptr_t)1 << RELOC_TAG_OFFSET)) { + jl_printf( + JL_STDERR, + "ERROR: system image too large: sysimg.size is %jd but the limit is %" PRIxPTR "\n", + (intmax_t)sysimg.size, + ((uintptr_t)1 << RELOC_TAG_OFFSET) + ); jl_exit(1); } + if (const_data.size / sizeof(void*) > ((uintptr_t)1 << RELOC_TAG_OFFSET)) { + jl_printf( + JL_STDERR, + "ERROR: system image too large: const_data.size is %jd but the limit is %" PRIxPTR "\n", + (intmax_t)const_data.size, + ((uintptr_t)1 << RELOC_TAG_OFFSET)*sizeof(void*) + ); + jl_exit(1); + } + htable_free(&s.callers_with_edges); // step 3: combine all of the sections into one file - write_uint32(f, sysimg.size - sizeof(uint32_t)); - ios_seek(&sysimg, sizeof(uint32_t)); + assert(ios_pos(f) % JL_CACHE_BYTE_ALIGNMENT == 0); + ssize_t sysimg_offset = ios_pos(f); + write_uint(f, sysimg.size - sizeof(uintptr_t)); + ios_seek(&sysimg, sizeof(uintptr_t)); ios_copyall(f, &sysimg); + size_t sysimg_size = s.s->size; + assert(ios_pos(f) - sysimg_offset == sysimg_size); ios_close(&sysimg); - write_uint32(f, const_data.size); + write_uint(f, const_data.size); // realign stream to max-alignment for data - write_padding(f, LLT_ALIGN(ios_pos(f), 16) - ios_pos(f)); + write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); ios_seek(&const_data, 0); ios_copyall(f, &const_data); ios_close(&const_data); - write_uint32(f, symbols.size); + write_uint(f, symbols.size); + write_padding(f, LLT_ALIGN(ios_pos(f), 8) - ios_pos(f)); ios_seek(&symbols, 0); ios_copyall(f, &symbols); ios_close(&symbols); - write_uint32(f, relocs.size); + // Prepare and write the relocations sections, now that the rest of the image is laid out + char *base = &f->buf[0]; + jl_finish_relocs(base + sysimg_offset, sysimg_size, &s.gctags_list); + 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); + if (s.incremental) { + jl_write_arraylist(s.relocs, &s.uniquing_types); + jl_write_arraylist(s.relocs, &s.uniquing_objs); + jl_write_arraylist(s.relocs, &s.fixup_types); + } + jl_write_arraylist(s.relocs, &s.fixup_objs); + write_uint(f, relocs.size); + write_padding(f, LLT_ALIGN(ios_pos(f), 8) - ios_pos(f)); ios_seek(&relocs, 0); ios_copyall(f, &relocs); ios_close(&relocs); - write_uint32(f, gvar_record.size); + write_uint(f, gvar_record.size); + write_padding(f, LLT_ALIGN(ios_pos(f), 8) - ios_pos(f)); ios_seek(&gvar_record, 0); ios_copyall(f, &gvar_record); ios_close(&gvar_record); - write_uint32(f, fptr_record.size); + write_uint(f, fptr_record.size); + write_padding(f, LLT_ALIGN(ios_pos(f), 8) - ios_pos(f)); ios_seek(&fptr_record, 0); ios_copyall(f, &fptr_record); ios_close(&fptr_record); { // step 4: record locations of special roots + write_padding(f, LLT_ALIGN(ios_pos(f), 8) - ios_pos(f)); s.s = f; - size_t i; - for (i = 0; tags[i] != NULL; i++) { - jl_value_t *tag = *tags[i]; - jl_write_value(&s, tag); + if (worklist == NULL) { + size_t i; + for (i = 0; tags[i] != NULL; i++) { + jl_value_t *tag = *tags[i]; + jl_write_value(&s, tag); + } + jl_write_value(&s, jl_global_roots_table); + 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)); + write_uint(f, jl_typeinf_world); + } + else { + jl_write_value(&s, worklist); + // save module initialization order + if (jl_module_init_order != NULL) { + size_t i, l = jl_array_len(jl_module_init_order); + for (i = 0; i < l; i++) { + // verify that all these modules were saved + assert(ptrhash_get(&serialization_order, jl_array_ptr_ref(jl_module_init_order, i)) != HT_NOTFOUND); + } + } + jl_write_value(&s, jl_module_init_order); + jl_write_value(&s, extext_methods); + jl_write_value(&s, new_specializations); + jl_write_value(&s, method_roots_list); + jl_write_value(&s, ext_targets); + jl_write_value(&s, edges); } - jl_write_value(&s, s.ptls->root_task->tls); - write_uint32(f, jl_get_gs_ctr()); - write_uint32(f, jl_atomic_load_acquire(&jl_world_counter)); - write_uint32(f, jl_typeinf_world); - jl_finalize_serializer(&s, &reinit_list); - jl_finalize_serializer(&s, &ccallable_list); + 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)); + 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)); + 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)); + 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)); + write_uint32(f, external_fns_begin); + jl_write_arraylist(s.s, &s.ccallable_list); } assert(object_worklist.len == 0); arraylist_free(&object_worklist); + arraylist_free(&serialization_queue); arraylist_free(&layout_table); - arraylist_free(&reinit_list); - arraylist_free(&ccallable_list); + arraylist_free(&s.ccallable_list); arraylist_free(&s.relocs_list); arraylist_free(&s.gctags_list); + arraylist_free(&gvars); + arraylist_free(&external_fns); htable_free(&field_replace); - jl_cleanup_serializer2(); + 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); + nsym_tag = 0; jl_gc_enable(en); } -JL_DLLEXPORT ios_t *jl_create_system_image(void *_native_data) +static void jl_write_header_for_incremental(ios_t *f, jl_array_t *worklist, jl_array_t *mod_array, jl_array_t **udeps, int64_t *srctextpos, int64_t *checksumpos) +{ + assert(jl_precompile_toplevel_module == NULL); + jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); + + *checksumpos = write_header(f, 0); + 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. + // 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) + // write description of requirements for loading (modules that must be pre-loaded if initialization is to succeed) + // this can return errors during deserialize, + // best to keep it early (before any actual initialization) + write_mod_list(f, mod_array); +} + +JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *worklist, bool_t emit_split, + ios_t **s, ios_t **z, jl_array_t **udeps, int64_t *srctextpos) { + jl_gc_collect(JL_GC_FULL); + jl_gc_collect(JL_GC_INCREMENTAL); // sweep finalizers + JL_TIMING(SYSIMG_DUMP, SYSIMG_DUMP); + + // iff emit_split + // write header and src_text to one file f/s + // write systemimg to a second file ff/z + jl_task_t *ct = jl_current_task; ios_t *f = (ios_t*)malloc_s(sizeof(ios_t)); ios_mem(f, 0); - native_functions = _native_data; - jl_save_system_image_to_stream(f); - return f; -} -JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src); -JL_DLLEXPORT void jl_save_system_image(const char *fname) -{ - ios_t f; - if (ios_file(&f, fname, 1, 1, 1, 1) == NULL) { - jl_errorf("cannot open system image file \"%s\" for writing", fname); + ios_t *ff = NULL; + if (emit_split) { + ff = (ios_t*)malloc_s(sizeof(ios_t)); + ios_mem(ff, 0); + } else { + ff = f; + } + + jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_specializations = 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); + + 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); + 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); + jl_precompile_toplevel_module = NULL; + extext_methods = NULL; + new_specializations = NULL; + } + jl_write_header_for_incremental(f, worklist, mod_array, udeps, srctextpos, &checksumpos); + if (emit_split) { + checksumpos_ff = write_header(ff, 1); + write_uint8(ff, jl_cache_flags()); + write_mod_list(ff, mod_array); + } + else { + checksumpos_ff = checksumpos; + } } - JL_SIGATOMIC_BEGIN(); - jl_save_system_image_to_stream(&f); - ios_close(&f); - JL_SIGATOMIC_END(); + else if (_native_data != NULL) { + *_native_data = jl_precompile(jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL); + } + + // Make sure we don't run any Julia code concurrently after this point + // since it will invalidate our serialization preparations + jl_gc_enable_finalizers(ct, 0); + assert((ct->reentrant_timing & 0b1110) == 0); + 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); + 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)); + } + else { + write_padding(ff, LLT_ALIGN(ios_pos(ff), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(ff)); + } + datastartpos = ios_pos(ff); + } + 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); + if (_native_data != NULL) + native_functions = NULL; + // make sure we don't run any Julia code concurrently before this point + // Re-enable running julia code for postoutput hooks, atexit, etc. + jl_gc_enable_finalizers(ct, 1); + ct->reentrant_timing &= ~0b1000u; + jl_precompile_toplevel_module = NULL; + + if (worklist) { + // Go back and update the checksum in the header + int64_t dataendpos = ios_pos(ff); + uint32_t checksum = jl_crc32c(0, &ff->buf[datastartpos], dataendpos - datastartpos); + ios_seek(ff, checksumpos_ff); + write_uint64(ff, checksum | ((uint64_t)0xfafbfcfd << 32)); + write_uint64(ff, datastartpos); + write_uint64(ff, dataendpos); + ios_seek(ff, dataendpos); + + // Write the checksum to the split header if necessary + if (emit_split) { + int64_t cur = ios_pos(f); + ios_seek(f, checksumpos); + write_uint64(f, checksum | ((uint64_t)0xfafbfcfd << 32)); + ios_seek(f, cur); + // Next we will write the clone_targets and afterwards the srctext + } + } + + JL_GC_POP(); + *s = f; + if (emit_split) + *z = ff; + return; } +JL_DLLEXPORT size_t ios_write_direct(ios_t *dest, ios_t *src); + // Takes in a path of the form "usr/lib/julia/sys.so" (jl_restore_system_image should be passed the same string) JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname) { @@ -2025,16 +2818,33 @@ JL_DLLEXPORT void jl_set_sysimg_so(void *handle) if (jl_options.cpu_target == NULL) jl_options.cpu_target = "native"; jl_sysimg_handle = handle; - sysimg_fptrs = jl_init_processor_sysimg(handle); + sysimage = jl_init_processor_sysimg(handle); } -static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED +#ifndef JL_NDEBUG +// skip the performance optimizations of jl_types_equal and just use subtyping directly +// one of these types is invalid - that's why we're doing the recache type operation +// static int jl_invalid_types_equal(jl_datatype_t *a, jl_datatype_t *b) +// { +// return jl_subtype((jl_value_t*)a, (jl_value_t*)b) && jl_subtype((jl_value_t*)b, (jl_value_t*)a); +// } +#endif + +extern void rebuild_image_blob_tree(void); +extern void export_small_typeof(void); + +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 **ext_targets, jl_array_t **edges, + char **base, arraylist_t *ccallable_list, pkgcachesizes *cachesizes) JL_GC_DISABLED { - JL_TIMING(SYSIMG_LOAD); int en = jl_gc_enable(0); - jl_init_serializer2(0); ios_t sysimg, const_data, symbols, relocs, gvar_record, fptr_record; - jl_serializer_state s; + jl_serializer_state s = {0}; + s.incremental = restored != NULL; // jl_linkage_blobs.len > 0; + s.image = image; s.s = NULL; s.const_data = &const_data; s.symbols = &symbols; @@ -2042,60 +2852,123 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED s.gvar_record = &gvar_record; s.fptr_record = &fptr_record; s.ptls = jl_current_task->ptls; - arraylist_new(&s.relocs_list, 0); - arraylist_new(&s.gctags_list, 0); jl_value_t **const*const tags = get_tags(); + htable_t new_dt_objs; + htable_new(&new_dt_objs, 0); + arraylist_new(&deser_sym, 0); // step 1: read section map assert(ios_pos(f) == 0 && f->bm == bm_mem); - size_t sizeof_sysimg = read_uint32(f); - ios_static_buffer(&sysimg, f->buf, sizeof_sysimg + sizeof(uint32_t)); - ios_skip(f, sizeof_sysimg); + size_t sizeof_sysdata = read_uint(f); + ios_static_buffer(&sysimg, f->buf, sizeof_sysdata + sizeof(uintptr_t)); + ios_skip(f, sizeof_sysdata); - size_t sizeof_constdata = read_uint32(f); + size_t sizeof_constdata = read_uint(f); // realign stream to max-alignment for data - ios_seek(f, LLT_ALIGN(ios_pos(f), 16)); + ios_seek(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT)); ios_static_buffer(&const_data, f->buf + f->bpos, sizeof_constdata); ios_skip(f, sizeof_constdata); - size_t sizeof_symbols = read_uint32(f); + size_t sizeof_sysimg = f->bpos; + + size_t sizeof_symbols = read_uint(f); + ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); ios_static_buffer(&symbols, f->buf + f->bpos, sizeof_symbols); ios_skip(f, sizeof_symbols); - size_t sizeof_relocations = read_uint32(f); + size_t sizeof_relocations = read_uint(f); + ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); assert(!ios_eof(f)); ios_static_buffer(&relocs, f->buf + f->bpos, sizeof_relocations); ios_skip(f, sizeof_relocations); - size_t sizeof_gvar_record = read_uint32(f); + size_t sizeof_gvar_record = read_uint(f); + ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); assert(!ios_eof(f)); ios_static_buffer(&gvar_record, f->buf + f->bpos, sizeof_gvar_record); ios_skip(f, sizeof_gvar_record); - size_t sizeof_fptr_record = read_uint32(f); + size_t sizeof_fptr_record = read_uint(f); + ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); assert(!ios_eof(f)); ios_static_buffer(&fptr_record, f->buf + f->bpos, sizeof_fptr_record); ios_skip(f, sizeof_fptr_record); // step 2: get references to special values + ios_seek(f, LLT_ALIGN(ios_pos(f), 8)); + assert(!ios_eof(f)); s.s = f; - size_t i; - for (i = 0; tags[i] != NULL; i++) { - jl_value_t **tag = tags[i]; - *tag = jl_read_value(&s); - } - // set typeof extra-special values now that we have the type set by tags above - jl_astaggedvalue(jl_current_task)->header = (uintptr_t)jl_task_type | jl_astaggedvalue(jl_current_task)->header; - jl_astaggedvalue(jl_nothing)->header = (uintptr_t)jl_nothing_type | jl_astaggedvalue(jl_nothing)->header; - s.ptls->root_task->tls = jl_read_value(&s); - jl_gc_wb(s.ptls->root_task, s.ptls->root_task->tls); - jl_init_int32_int64_cache(); - jl_init_box_caches(); - - uint32_t gs_ctr = read_uint32(f); - jl_atomic_store_release(&jl_world_counter, read_uint32(f)); - jl_typeinf_world = read_uint32(f); - jl_set_gs_ctr(gs_ctr); + 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_ext_targets = 0, offset_edges = 0; + if (!s.incremental) { + size_t i; + for (i = 0; tags[i] != NULL; i++) { + jl_value_t **tag = tags[i]; + *tag = jl_read_value(&s); + } +#define XX(name) \ + small_typeof[(jl_##name##_tag << 4) / sizeof(*small_typeof)] = jl_##name##_type; + JL_SMALL_TYPEOF(XX) +#undef XX + export_small_typeof(); + jl_global_roots_table = (jl_array_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); + jl_gc_wb(s.ptls->root_task, s.ptls->root_task->tls); + jl_init_int32_int64_cache(); + jl_init_box_caches(); + + uint32_t gs_ctr = read_uint32(f); + jl_atomic_store_release(&jl_world_counter, read_uint(f)); + 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_method_roots_list = jl_read_offset(&s); + offset_ext_targets = jl_read_offset(&s); + offset_edges = jl_read_offset(&s); + } + s.buildid_depmods_idxs = depmod_to_imageidx(depmods); + 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)); + } + 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)); + } + 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)); + } + 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)); + } + 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); + *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); + *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); + } s.s = NULL; // step 3: apply relocations @@ -2103,24 +2976,310 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED jl_read_symbols(&s); ios_close(&symbols); - sysimg_base = &sysimg.buf[0]; - sysimg_relocs = &relocs.buf[0]; - jl_gc_set_permalloc_region((void*)sysimg_base, (void*)(sysimg_base + sysimg.size)); + char *image_base = (char*)&sysimg.buf[0]; + reloc_t *relocs_base = (reloc_t*)&relocs.buf[0]; + if (base) + *base = image_base; s.s = &sysimg; - jl_read_relocations(&s, GC_OLD_MARKED); // gctags + jl_read_reloclist(&s, s.link_ids_gctags, GC_OLD | GC_IN_IMAGE); // gctags size_t sizeof_tags = ios_pos(&relocs); (void)sizeof_tags; - jl_read_relocations(&s, 0); // general relocs + jl_read_reloclist(&s, s.link_ids_relocs, 0); // general 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 + if (s.incremental) { + jl_read_arraylist(s.relocs, &s.uniquing_types); + jl_read_arraylist(s.relocs, &s.uniquing_objs); + jl_read_arraylist(s.relocs, &s.fixup_types); + } + else { + arraylist_new(&s.uniquing_types, 0); + arraylist_new(&s.uniquing_objs, 0); + arraylist_new(&s.fixup_types, 0); + } + jl_read_arraylist(s.relocs, &s.fixup_objs); + // Perform the uniquing of objects that we don't "own" and consequently can't promise + // weren't created by some other package before this one got loaded: + // - iterate through all objects that need to be uniqued. The first encounter has to be the + // "reconstructable blob". We either look up the object (if something has created it previously) + // or construct it for the first time, crucially outside the pointer range of any pkgimage. + // This ensures it stays unique-worthy. + // - after we've stored the address of the "real" object (which for convenience we do among the data + // written to allow lookup/reconstruction), then we have to update references to that "reconstructable blob": + // instead of performing the relocation within the package image, we instead (re)direct all references + // to the external object. + arraylist_t cleanup_list; + arraylist_new(&cleanup_list, 0); + arraylist_t delay_list; + arraylist_new(&delay_list, 0); + for (size_t i = 0; i < s.uniquing_types.len; i++) { + 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; + item &= ~(uintptr_t)3; + uintptr_t *pfld; + jl_value_t **obj, *newobj; + if (gvar) { + 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) + obj = (jl_value_t**)jl_typeof(jl_valueof(pfld)); + else + obj = *(jl_value_t***)pfld; + if ((char*)obj > (char*)pfld) { + assert(tag == 0); + arraylist_push(&delay_list, pfld); + arraylist_push(&delay_list, obj); + ptrhash_put(&new_dt_objs, (void*)obj, obj); // mark obj as invalid + *pfld = (uintptr_t)NULL; + continue; + } + } + uintptr_t otyp = jl_typetagof(obj); // the original type of the object that was written here + assert(image_base < (char*)obj && (char*)obj <= image_base + sizeof_sysimg); + if (otyp == jl_datatype_tag << 4) { + jl_datatype_t *dt = (jl_datatype_t*)obj[0], *newdt; + if (jl_is_datatype(dt)) { + newdt = dt; // already done + } + else { + dt = (jl_datatype_t*)obj; + arraylist_push(&cleanup_list, (void*)obj); + ptrhash_remove(&new_dt_objs, (void*)obj); // unmark obj as invalid before must_be_new_dt + if (must_be_new_dt((jl_value_t*)dt, &new_dt_objs, image_base, sizeof_sysimg)) + newdt = NULL; + else + newdt = jl_lookup_cache_type_(dt); + if (newdt == NULL) { + // make a non-owned copy of obj so we don't accidentally + // assume this is the unique copy later + newdt = jl_new_uninitialized_datatype(); + jl_astaggedvalue(newdt)->bits.gc = GC_OLD; + // 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); + } + static_assert(offsetof(jl_datatype_t, name) == 0, ""); + newdt->name = dt->name; + ptrhash_put(&new_dt_objs, (void*)newdt, dt); + } + else { + assert(newdt->hash == dt->hash); + } + obj[0] = (jl_value_t*)newdt; + } + newobj = (jl_value_t*)newdt; + } + 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); + arraylist_push(&cleanup_list, (void*)obj); + } + if (tag) + *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)); + } + 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 + // think this pkg image has the singular unique copy of it + void **table = new_dt_objs.table; + for (size_t i = 0; i < new_dt_objs.size; i += 2) { + void *dt = table[i + 1]; + if (dt != HT_NOTFOUND) { + jl_datatype_t *newdt = (jl_datatype_t*)table[i]; + jl_typename_t *name = newdt->name; + static_assert(offsetof(jl_datatype_t, name) == 0, ""); + assert(*(void**)dt == (void*)newdt); + *newdt = *(jl_datatype_t*)dt; // copy the datatype fields (except field 1, which we corrupt above) + newdt->name = name; + } + } + // we should never see these pointers again, so scramble their memory, so any attempt to look at them crashes + for (size_t i = 0; i < cleanup_list.len; i++) { + void *item = cleanup_list.items[i]; + jl_taggedvalue_t *o = jl_astaggedvalue(item); + jl_value_t *t = jl_typeof(item); // n.b. might be 0xbabababa already + if (t == (jl_value_t*)jl_datatype_type) + memset(o, 0xba, sizeof(jl_value_t*) + sizeof(jl_datatype_t)); + else + memset(o, 0xba, sizeof(jl_value_t*) + 0); // singleton + o->bits.in_image = 1; + } + arraylist_grow(&cleanup_list, -cleanup_list.len); + // finally cache all our new types now + for (size_t i = 0; i < new_dt_objs.size; i += 2) { + void *dt = table[i + 1]; + if (dt != HT_NOTFOUND) { + jl_datatype_t *newdt = (jl_datatype_t*)table[i]; + jl_cache_type_(newdt); + } + } + for (size_t i = 0; i < s.fixup_types.len; i++) { + uintptr_t item = (uintptr_t)s.fixup_types.items[i]; + jl_value_t *obj = (jl_value_t*)(image_base + item); + assert(jl_is_datatype(obj)); + 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; + item &= ~(uintptr_t)3; + uintptr_t *pfld; + jl_value_t **obj, *newobj; + if (gvar) { + 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; + } + else { + pfld = (uintptr_t*)(image_base + item); + obj = *(jl_value_t***)pfld; + } + uintptr_t otyp = jl_typetagof(obj); // the original type of the object that was written here + if (otyp == (uintptr_t)jl_method_instance_type) { + assert(image_base < (char*)obj && (char*)obj <= image_base + sizeof_sysimg); + jl_value_t *m = obj[0]; + if (jl_is_method_instance(m)) { + newobj = m; // already done + } + else { + arraylist_push(&cleanup_list, (void*)obj); + jl_value_t *specTypes = obj[1]; + jl_value_t *sparams = obj[2]; + newobj = (jl_value_t*)jl_specializations_get_linfo((jl_method_t*)m, specTypes, (jl_svec_t*)sparams); + obj[0] = newobj; + } + } + else { + abort(); // should be unreachable + } + *pfld = (uintptr_t)newobj; + assert(!(image_base < (char*)newobj && (char*)newobj <= image_base + sizeof_sysimg)); + assert(jl_typetagis(obj, otyp)); + } + arraylist_free(&s.uniquing_types); + arraylist_free(&s.uniquing_objs); + for (size_t i = 0; i < cleanup_list.len; i++) { + void *item = cleanup_list.items[i]; + jl_taggedvalue_t *o = jl_astaggedvalue(item); + jl_value_t *t = jl_typeof(item); + if (t == (jl_value_t*)jl_method_instance_type) + memset(o, 0xba, sizeof(jl_value_t*) * 3); // only specTypes and sparams fields stored + o->bits.in_image = 1; + } + arraylist_free(&cleanup_list); + 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; + } + 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 + } + else if (jl_is_module(obj)) { + // rebuild the binding 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*)); + memcpy(newitems, mod->usings.items, mod->usings.len * sizeof(void*)); + mod->usings.items = newitems; + } + } + 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); + } + } + } + arraylist_free(&s.fixup_types); + arraylist_free(&s.fixup_objs); + + if (s.incremental) + jl_root_new_gvars(&s, image, external_fns_begin); ios_close(&relocs); ios_close(&const_data); - jl_update_all_gvars(&s); // gvars relocs ios_close(&gvar_record); - s.s = NULL; - s.s = f; - // reinit items except ccallables - jl_finalize_deserializer(&s); + htable_free(&new_dt_objs); + s.s = NULL; if (0) { @@ -2132,7 +3291,7 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED " reloc list: %8u\n" " gvar list: %8u\n" " fptr list: %8u\n", - (unsigned)sizeof_sysimg, + (unsigned)sizeof_sysdata, (unsigned)sizeof_constdata, (unsigned)sizeof_symbols, (unsigned)sizeof_tags, @@ -2140,21 +3299,168 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED (unsigned)sizeof_gvar_record, (unsigned)sizeof_fptr_record); } + if (cachesizes) { + cachesizes->sysdata = sizeof_sysdata; + cachesizes->isbitsdata = sizeof_constdata; + cachesizes->symboldata = sizeof_symbols; + cachesizes->tagslist = sizeof_tags; + cachesizes->reloclist = sizeof_relocations - sizeof_tags; + cachesizes->gvarlist = sizeof_gvar_record; + cachesizes->fptrlist = sizeof_fptr_record; + } s.s = &sysimg; - jl_init_codegen(); - jl_update_all_fptrs(&s); // fptr relocs and registration - // reinit ccallables, which require codegen to be initialized - s.s = f; - jl_finalize_deserializer(&s); + jl_update_all_fptrs(&s, image); // fptr relocs and registration + if (!ccallable_list) { + // TODO: jl_sysimg_handle or img_handle? + jl_reinit_ccallable(&s.ccallable_list, image_base, jl_sysimg_handle); + arraylist_free(&s.ccallable_list); + } + s.s = NULL; ios_close(&fptr_record); ios_close(&sysimg); - s.s = NULL; - jl_gc_reset_alloc_count(); + if (!s.incremental) + jl_gc_reset_alloc_count(); + arraylist_free(&deser_sym); + + // Prepare for later external linkage against the sysimg + // Also sets up images for protection against garbage collection + 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); + rebuild_image_blob_tree(); + + // jl_printf(JL_STDOUT, "%ld blobs to link against\n", jl_linkage_blobs.len >> 1); jl_gc_enable(en); - jl_cleanup_serializer2(); +} + +static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_t *checksum, int64_t *dataendpos, int64_t *datastartpos) +{ + uint8_t pkgimage = 0; + if (ios_eof(f) || 0 == (*checksum = jl_read_verify_header(f, &pkgimage, dataendpos, datastartpos)) || (*checksum >> 32 != 0xfafbfcfd)) { + return jl_get_exceptionf(jl_errorexception_type, + "Precompile file header verification checks failed."); + } + uint8_t flags = read_uint8(f); + if (pkgimage && !jl_match_cache_flags(flags)) { + return jl_get_exceptionf(jl_errorexception_type, "Pkgimage flags mismatch"); + } + if (!pkgimage) { + // skip past the worklist + size_t len; + while ((len = read_int32(f))) + ios_skip(f, len + 3 * sizeof(uint64_t)); + // skip past the dependency list + size_t deplen = read_uint64(f); + ios_skip(f, deplen - sizeof(uint64_t)); + read_uint64(f); // where is this write coming from? + } + + // verify that the system state is valid + return read_verify_mod_list(f, depmods); +} + +// TODO?: refactor to make it easier to create the "package inspector" +static jl_value_t *jl_restore_package_image_from_stream(ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname) +{ + JL_TIMING(LOAD_IMAGE, LOAD_Pkgimg); + jl_timing_printf(JL_TIMING_CURRENT_BLOCK, pkgname); + uint64_t checksum = 0; + int64_t dataendpos = 0; + int64_t datastartpos = 0; + jl_value_t *verify_fail = jl_validate_cache_file(f, depmods, &checksum, &dataendpos, &datastartpos); + + if (verify_fail) + return verify_fail; + + assert(datastartpos > 0 && datastartpos < dataendpos); + + 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); + + { // 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 = (char*)jl_gc_perm_alloc(len, 0, 64, 0); + ios_seek(f, datastartpos); + if (ios_readall(f, sysimg, len) != len || jl_crc32c(0, sysimg, len) != (uint32_t)checksum) { + restored = jl_get_exceptionf(jl_errorexception_type, "Error reading system image file."); + JL_SIGATOMIC_END(); + } + else { + 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_SIGATOMIC_END(); + + // Insert method extensions + jl_insert_methods(extext_methods); + // No special processing of `new_specializations` 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) + // reinit ccallables + jl_reinit_ccallable(&ccallable_list, base, NULL); + arraylist_free(&ccallable_list); + + if (completeinfo) { + cachesizes_sv = jl_alloc_svec(7); + jl_svecset(cachesizes_sv, 0, jl_box_long(cachesizes.sysdata)); + jl_svecset(cachesizes_sv, 1, jl_box_long(cachesizes.isbitsdata)); + jl_svecset(cachesizes_sv, 2, jl_box_long(cachesizes.symboldata)); + jl_svecset(cachesizes_sv, 3, jl_box_long(cachesizes.tagslist)); + 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); + } + else { + restored = (jl_value_t*)jl_svec(2, restored, init_order); + } + } + } + + JL_GC_POP(); + return restored; +} + +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_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname) +{ + ios_t f; + ios_static_buffer(&f, (char*)buf, sz); + jl_value_t *ret = jl_restore_package_image_from_stream(&f, image, depmods, completeinfo, pkgname); + ios_close(&f); + return ret; +} + +JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *depmods, int completeinfo, const char *pkgname) +{ + ios_t f; + if (ios_file(&f, fname, 1, 0, 0, 0) == NULL) { + return jl_get_exceptionf(jl_errorexception_type, + "Cache file \"%s\" not found.\n", fname); + } + jl_image_t pkgimage = {}; + jl_value_t *ret = jl_restore_package_image_from_stream(&f, &pkgimage, depmods, completeinfo, pkgname); + ios_close(&f); + return ret; } // TODO: need to enforce that the alignment of the buffer is suitable for vectors @@ -2183,8 +3489,9 @@ JL_DLLEXPORT void jl_restore_system_image(const char *fname) if (ios_readall(&f, sysimg, len) != len) jl_errorf("Error reading system image file."); ios_close(&f); + uint32_t checksum = jl_crc32c(0, sysimg, len); ios_static_buffer(&f, sysimg, len); - jl_restore_system_image_from_stream(&f); + jl_restore_system_image_from_stream(&f, &sysimage, checksum); ios_close(&f); JL_SIGATOMIC_END(); } @@ -2195,36 +3502,36 @@ JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len) ios_t f; JL_SIGATOMIC_BEGIN(); ios_static_buffer(&f, (char*)buf, len); - jl_restore_system_image_from_stream(&f); + uint32_t checksum = jl_crc32c(0, buf, len); + jl_restore_system_image_from_stream(&f, &sysimage, checksum); ios_close(&f); JL_SIGATOMIC_END(); } -// --- init --- - -static void jl_init_serializer2(int for_serialize) +JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods, int completeinfo, const char *pkgname) { - if (for_serialize) { - htable_new(&symbol_table, 0); - htable_new(&fptr_to_id, sizeof(id_to_fptrs) / sizeof(*id_to_fptrs)); - htable_new(&backref_table, 0); - uintptr_t i; - for (i = 0; id_to_fptrs[i] != NULL; i++) { - ptrhash_put(&fptr_to_id, (void*)(uintptr_t)id_to_fptrs[i], (void*)(i + 2)); - } - } - else { - arraylist_new(&deser_sym, 0); + void *pkgimg_handle = jl_dlopen(fname, JL_RTLD_LAZY); + if (!pkgimg_handle) { +#ifdef _OS_WINDOWS_ + int err; + char reason[256]; + err = GetLastError(); + win32_formatmessage(err, reason, sizeof(reason)); +#else + const char *reason = dlerror(); +#endif + jl_errorf("Error opening package file %s: %s\n", fname, reason); } - nsym_tag = 0; -} + const char *pkgimg_data; + jl_dlsym(pkgimg_handle, "jl_system_image_data", (void **)&pkgimg_data, 1); + size_t *plen; + jl_dlsym(pkgimg_handle, "jl_system_image_size", (void **)&plen, 1); -static void jl_cleanup_serializer2(void) -{ - htable_reset(&symbol_table, 0); - htable_reset(&fptr_to_id, 0); - htable_reset(&backref_table, 0); - arraylist_free(&deser_sym); + jl_image_t pkgimage = jl_init_processor_pkgimg(pkgimg_handle); + + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname); + + return mod; } #ifdef __cplusplus diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c new file mode 100644 index 0000000000000..bf1a830b608de --- /dev/null +++ b/src/staticdata_utils.c @@ -0,0 +1,1265 @@ +// 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 + +static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT +{ + write_uint64(s, *((uint64_t*)&x)); +} + +// Decide if `t` must be new, because it points to something new. +// If it is new, the object (in particular, the super field) might not be entirely +// valid for the cache, so we want to finish transforming it before attempting +// to look in the cache for it +int must_be_new_dt(jl_value_t *t, htable_t *news, char *image_base, size_t sizeof_sysimg) +{ + //if (jl_object_in_image(t)) + // return 0; // fast-path for rejection + assert(ptrhash_get(news, (void*)t) != (void*)t); + if (ptrhash_has(news, (void*)t) || ptrhash_has(news, (void*)jl_typeof(t))) + return 1; + if (!(image_base < (char*)t && (char*)t <= image_base + sizeof_sysimg)) + return 0; // fast-path for rejection + if (jl_is_uniontype(t)) { + jl_uniontype_t *u = (jl_uniontype_t*)t; + return must_be_new_dt(u->a, news, image_base, sizeof_sysimg) || + must_be_new_dt(u->b, news, image_base, sizeof_sysimg); + } + else if (jl_is_unionall(t)) { + jl_unionall_t *ua = (jl_unionall_t*)t; + return must_be_new_dt((jl_value_t*)ua->var, news, image_base, sizeof_sysimg) || + must_be_new_dt(ua->body, news, image_base, sizeof_sysimg); + } + else if (jl_is_typevar(t)) { + jl_tvar_t *tv = (jl_tvar_t*)t; + return must_be_new_dt(tv->lb, news, image_base, sizeof_sysimg) || + must_be_new_dt(tv->ub, news, image_base, sizeof_sysimg); + } + else if (jl_is_vararg(t)) { + jl_vararg_t *tv = (jl_vararg_t*)t; + if (tv->T && must_be_new_dt(tv->T, news, image_base, sizeof_sysimg)) + return 1; + if (tv->N && must_be_new_dt(tv->N, news, image_base, sizeof_sysimg)) + return 1; + } + else if (jl_is_datatype(t)) { + 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); + 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 + // 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; + } + jl_svec_t *tt = dt->parameters; + size_t i, l = jl_svec_len(tt); + for (i = 0; i < l; i++) + if (must_be_new_dt(jl_tparam(dt, i), news, image_base, sizeof_sysimg)) + return 1; + } + else { + return must_be_new_dt(jl_typeof(t), news, image_base, sizeof_sysimg); + } + return 0; +} + +static uint64_t jl_worklist_key(jl_array_t *worklist) JL_NOTSAFEPOINT +{ + assert(jl_is_array(worklist)); + size_t len = jl_array_len(worklist); + if (len > 0) { + jl_module_t *topmod = (jl_module_t*)jl_array_ptr_ref(worklist, len-1); + assert(jl_is_module(topmod)); + return topmod->build_id.lo; + } + return 0; +} + +static jl_array_t *newly_inferred JL_GLOBALLY_ROOTED /*FIXME*/; +// Mutex for newly_inferred +jl_mutex_t newly_inferred_mutex; + +// Register array of newly-inferred MethodInstances +// This gets called as the first step of Base.include_package_for_output +JL_DLLEXPORT void jl_set_newly_inferred(jl_value_t* _newly_inferred) +{ + assert(_newly_inferred == NULL || jl_is_array(_newly_inferred)); + newly_inferred = (jl_array_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); + jl_array_grow_end(newly_inferred, 1); + jl_arrayset(newly_inferred, ci, end); + JL_UNLOCK(&newly_inferred_mutex); +} + + +// compute whether a type references something internal to worklist +// and thus could not have existed before deserialize +// and thus does not need delayed unique-ing +static int type_in_worklist(jl_value_t *v) JL_NOTSAFEPOINT +{ + if (jl_object_in_image(v)) + return 0; // fast-path for rejection + if (jl_is_uniontype(v)) { + jl_uniontype_t *u = (jl_uniontype_t*)v; + return type_in_worklist(u->a) || + type_in_worklist(u->b); + } + else if (jl_is_unionall(v)) { + jl_unionall_t *ua = (jl_unionall_t*)v; + return type_in_worklist((jl_value_t*)ua->var) || + type_in_worklist(ua->body); + } + else if (jl_is_typevar(v)) { + jl_tvar_t *tv = (jl_tvar_t*)v; + return type_in_worklist(tv->lb) || + type_in_worklist(tv->ub); + } + else if (jl_is_vararg(v)) { + jl_vararg_t *tv = (jl_vararg_t*)v; + if (tv->T && type_in_worklist(tv->T)) + return 1; + if (tv->N && type_in_worklist(tv->N)) + return 1; + } + else if (jl_is_datatype(v)) { + jl_datatype_t *dt = (jl_datatype_t*)v; + if (!jl_object_in_image((jl_value_t*)dt->name)) + return 1; + jl_svec_t *tt = dt->parameters; + size_t i, l = jl_svec_len(tt); + for (i = 0; i < l; i++) + if (type_in_worklist(jl_tparam(dt, i))) + return 1; + } + else { + return type_in_worklist(jl_typeof(v)); + } + return 0; +} + +// When we infer external method instances, ensure they link back to the +// package. Otherwise they might be, e.g., for external macros. +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable +static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited, arraylist_t *stack) +{ + jl_module_t *mod = mi->def.module; + 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)) { + return 1; + } + if (!mi->backedges) { + return 0; + } + void **bp = ptrhash_bp(visited, mi); + // 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 + 4 + depth: in-progress + int found = (char*)*bp - (char*)HT_NOTFOUND; + if (found) + return found - 1; + 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); + int cycle = depth; + while (i < n) { + jl_method_instance_t *be; + i = get_next_edge(mi->backedges, i, NULL, &be); + int child_found = has_backedge_to_worklist(be, visited, stack); + if (child_found == 1 || child_found == 2) { + // found what we were looking for, so terminate early + found = 1; + break; + } + else if (child_found >= 3 && child_found - 3 < cycle) { + // record the cycle will resolve at depth "cycle" + cycle = child_found - 3; + assert(cycle); + } + } + if (!found && cycle != depth) + return cycle + 3; + // If we are the top of the current cycle, now mark all other parts of + // our cycle with what we found. + // Or if we found a backedge, also mark all of the other parts of the + // cycle as also having an backedge. + while (stack->len >= depth) { + void *mi = arraylist_pop(stack); + bp = ptrhash_bp(visited, mi); + assert((char*)*bp - (char*)HT_NOTFOUND == 5 + stack->len); + *bp = (void*)((char*)HT_NOTFOUND + 1 + found); + } + return found; +} + +// Given the list of CodeInstances that were inferred during the build, select +// those that are (1) external, (2) still valid, (3) are inferred to be called +// from the worklist or explicitly added by a `precompile` statement, and +// (4) are the most recently computed result for that method. +// These will be preserved in the image. +static jl_array_t *queue_external_cis(jl_array_t *list) +{ + if (list == NULL) + return NULL; + size_t i; + htable_t visited; + arraylist_t stack; + assert(jl_is_array(list)); + size_t n0 = jl_array_len(list); + htable_new(&visited, n0); + arraylist_new(&stack, 0); + jl_array_t *new_specializations = jl_alloc_vec_any(0); + JL_GC_PUSH1(&new_specializations); + 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)); + if (!ci->relocatability) + 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)) { + 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); + } + } + } + } + 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); + 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; +} + +// New roots for external methods +static void jl_collect_new_roots(jl_array_t *roots, jl_array_t *new_specializations, uint64_t key) +{ + htable_t mset; + htable_new(&mset, 0); + size_t l = new_specializations ? jl_array_len(new_specializations) : 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); + assert(jl_is_code_instance(ci)); + jl_method_t *m = ci->def->def.method; + assert(jl_is_method(m)); + ptrhash_put(&mset, (void*)m, (void*)m); + } + int nwithkey; + void *const *table = mset.table; + jl_array_t *newroots = NULL; + JL_GC_PUSH1(&newroots); + for (size_t i = 0; i < mset.size; i += 2) { + if (table[i+1] != HT_NOTFOUND) { + jl_method_t *m = (jl_method_t*)table[i]; + assert(jl_is_method(m)); + nwithkey = nroots_with_key(m, key); + if (nwithkey) { + jl_array_ptr_1d_push(roots, (jl_value_t*)m); + newroots = jl_alloc_vec_any(nwithkey); + 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; + if (m->root_blocks) { + rletable = (uint64_t*)jl_array_data(m->root_blocks); + nblocks2 = jl_array_len(m->root_blocks); + } + while (rle_iter_increment(&rootiter, nroots, rletable, nblocks2)) + if (rootiter.key == key) + jl_array_ptr_set(newroots, k++, jl_array_ptr_ref(m->roots, rootiter.i)); + assert(k == nwithkey); + } + } + } + JL_GC_POP(); + htable_free(&mset); +} + +// Create the forward-edge map (caller => callees) +// the intent of these functions is to invert the backedges tree +// for anything that points to a method not part of the worklist +// +// from MethodTables +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); + 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 + jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); + if (edges == NULL) { + edges = jl_alloc_vec_any(0); + JL_GC_PUSH1(&edges); + edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); + JL_GC_POP(); + } + jl_array_ptr_1d_push(edges, NULL); + jl_array_ptr_1d_push(edges, missing_callee); + } + } +} + + +// from MethodInstances +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); + while (i < l) { + jl_value_t *invokeTypes; + jl_method_instance_t *caller; + i = get_next_edge(backedges, i, &invokeTypes, &caller); + jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); + if (edges == NULL) { + edges = jl_alloc_vec_any(0); + JL_GC_PUSH1(&edges); + edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); + JL_GC_POP(); + } + jl_array_ptr_1d_push(edges, invokeTypes); + jl_array_ptr_1d_push(edges, (jl_value_t*)callee); + } + } +} + + +// For functions owned by modules not on the worklist, call this on each method. +// - if the method is owned by a worklist module, add it to the list of things to be +// fully serialized +// - Collect all backedges (may be needed later when we invert this list). +static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) +{ + jl_array_t *s = (jl_array_t*)closure; + jl_method_t *m = ml->func.method; + if (s && !jl_object_in_image((jl_value_t*)m->module)) { + jl_array_ptr_1d_push(s, (jl_value_t*)m); + } + if (edges_map == NULL) + return 1; + jl_value_t *specializations = jl_atomic_load_relaxed(&m->specializations); + if (!jl_is_svec(specializations)) { + jl_method_instance_t *callee = (jl_method_instance_t*)specializations; + collect_backedges(callee, !s); + } + else { + size_t i, l = jl_svec_len(specializations); + for (i = 0; i < l; i++) { + jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(specializations, i); + if ((jl_value_t*)callee != jl_nothing) + collect_backedges(callee, !s); + } + } + return 1; +} + +static int jl_collect_methtable_from_mod(jl_methtable_t *mt, void *env) +{ + if (!jl_object_in_image((jl_value_t*)mt)) + env = NULL; // do not collect any methods from here + jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), jl_collect_methcache_from_mod, env); + if (env && edges_map) + jl_collect_missing_backedges(mt); + return 1; +} + +// Collect methods of external functions defined by modules in the worklist +// "extext" = "extending external" +// Also collect relevant backedges +static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) +{ + foreach_mtable_in_module(m, jl_collect_methtable_from_mod, s); +} + +static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_array_t *edges) +{ + jl_array_t *callees = NULL; + JL_GC_PUSH2(&caller, &callees); + callees = (jl_array_t*)jl_eqtable_pop(edges_map, (jl_value_t*)caller, NULL, NULL); + 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); + 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)) { + arraylist_push(wq, c); + } + } + } + JL_GC_POP(); +} + + +// Extract `edges` and `ext_targets` from `edges_map` +// `edges` = [caller1, targets_indexes1, ...], the list of methods and their edges +// `ext_targets` is [invokesig1, callee1, matches1, ...], the edges for each target +static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *external_cis, size_t world) +{ + htable_t external_mis; + htable_new(&external_mis, 0); + if (external_cis) { + for (size_t i = 0; i < jl_array_len(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); + } + } + 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); + for (size_t i = 0; i < table_size; i += 2) { + assert(table == jl_array_data(edges_map) && table_size == jl_array_len(edges_map) && + "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]; + if (callees == NULL) + continue; + assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); + if (!jl_object_in_image((jl_value_t*)caller->def.method->module) || + ptrhash_get(&external_mis, caller) != HT_NOTFOUND) { + jl_record_edges(caller, &wq, edges); + } + } + htable_free(&external_mis); + while (wq.len) { + jl_method_instance_t *caller = (jl_method_instance_t*)arraylist_pop(&wq); + jl_record_edges(caller, &wq, edges); + } + arraylist_free(&wq); + edges_map = NULL; + htable_t edges_map2; + htable_new(&edges_map2, 0); + htable_t edges_ids; + size_t l = edges ? jl_array_len(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); + void *target = (void*)((char*)HT_NOTFOUND + i + 1); + ptrhash_put(&edges_ids, (void*)caller, target); + } + // process target list to turn it into a memoized validity table + // and compute the old methods list, ready for serialization + jl_value_t *matches = NULL; + jl_array_t *callee_ids = NULL; + jl_value_t *sig = NULL; + 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); + callee_ids = jl_alloc_array_1d(jl_array_int32_type, l + 1); + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + idxs[0] = 0; + size_t nt = 0; + for (size_t j = 0; j < l; j += 2) { + jl_value_t *invokeTypes = jl_array_ptr_ref(callees, j); + jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); + assert(callee && "unsupported edge"); + + if (jl_is_method_instance(callee)) { + jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); + if (!jl_object_in_image((jl_value_t*)mt)) + continue; + } + + // (nullptr, c) => call + // (invokeTypes, c) => invoke + // (nullptr, invokeTypes) => missing call + // (invokeTypes, nullptr) => missing invoke (unused--inferred as Any) + void *target = ptrhash_get(&edges_map2, invokeTypes ? (void*)invokeTypes : (void*)callee); + if (target == HT_NOTFOUND) { + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + if (invokeTypes) { + assert(jl_is_method_instance(callee)); + jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); + if ((jl_value_t*)mt == jl_nothing) { + callee_ids = NULL; // invalid + break; + } + else { + matches = jl_gf_invoke_lookup_worlds(invokeTypes, (jl_value_t*)mt, world, &min_valid, &max_valid); + if (matches == jl_nothing) { + callee_ids = NULL; // invalid + break; + } + matches = (jl_value_t*)((jl_method_match_t*)matches)->method; + } + } + else { + if (jl_is_method_instance(callee)) { + jl_method_instance_t *mi = (jl_method_instance_t*)callee; + sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); + } + else { + sig = callee; + } + int ambig = 0; + matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, + INT32_MAX, 0, world, &min_valid, &max_valid, &ambig); + sig = NULL; + if (matches == jl_nothing) { + callee_ids = NULL; // invalid + break; + } + size_t k; + for (k = 0; k < jl_array_len(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); + } + } + 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); + ptrhash_put(&edges_map2, (void*)callee, target); + } + idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; + } + jl_array_ptr_set(edges, i + 1, callee_ids); // swap callees for ids + if (!callee_ids) + continue; + idxs[0] = nt; + // record place of every method in edges + // add method edges to the callee_ids list + for (size_t j = 0; j < l; j += 2) { + jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); + if (callee && jl_is_method_instance(callee)) { + void *target = ptrhash_get(&edges_ids, (void*)callee); + if (target != HT_NOTFOUND) { + idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; + } + } + } + jl_array_del_end(callee_ids, l - nt); + } + JL_GC_POP(); + htable_free(&edges_map2); +} + +// Headers + +// serialize information about all loaded modules +static void write_mod_list(ios_t *s, jl_array_t *a) +{ + size_t i; + size_t len = jl_array_len(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)); + if (jl_object_in_image((jl_value_t*)m)) { + const char *modname = jl_symbol_name(m->name); + size_t l = strlen(modname); + write_int32(s, l); + ios_write(s, modname, l); + write_uint64(s, m->uuid.hi); + write_uint64(s, m->uuid.lo); + write_uint64(s, m->build_id.hi); + write_uint64(s, m->build_id.lo); + } + } + write_int32(s, 0); +} + +// OPT_LEVEL should always be the upper bits +#define OPT_LEVEL 6 + +JL_DLLEXPORT uint8_t jl_cache_flags(void) +{ + // OOICCDDP + uint8_t flags = 0; + flags |= (jl_options.use_pkgimages & 1); // 0-bit + flags |= (jl_options.debug_level & 3) << 1; // 1-2 bit + flags |= (jl_options.check_bounds & 3) << 3; // 3-4 bit + flags |= (jl_options.can_inline & 1) << 5; // 5-bit + flags |= (jl_options.opt_level & 3) << OPT_LEVEL; // 6-7 bit + return flags; +} + +JL_DLLEXPORT uint8_t jl_match_cache_flags(uint8_t 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); + + // For .ji packages ignore other flags + if (!supports_pkgimage && !is_pkgimage) { + return 1; + } + + // 2. Check all flags, execept opt level must be exact + uint8_t mask = (1 << OPT_LEVEL)-1; + if ((flags & mask) != (current_flags & mask)) + return 0; + // 3. allow for higher optimization flags in cache + flags >>= OPT_LEVEL; + current_flags >>= OPT_LEVEL; + return flags >= current_flags; +} + +// "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 +static const uint16_t BOM = 0xFEFF; // byte-order marker +static int64_t write_header(ios_t *s, uint8_t pkgimage) +{ + ios_write(s, JI_MAGIC, strlen(JI_MAGIC)); + write_uint16(s, JI_FORMAT_VERSION); + ios_write(s, (char *) &BOM, 2); + write_uint8(s, sizeof(void*)); + ios_write(s, JL_BUILD_UNAME, strlen(JL_BUILD_UNAME)+1); + ios_write(s, JL_BUILD_ARCH, strlen(JL_BUILD_ARCH)+1); + ios_write(s, JULIA_VERSION_STRING, strlen(JULIA_VERSION_STRING)+1); + const char *branch = jl_git_branch(), *commit = jl_git_commit(); + ios_write(s, branch, strlen(branch)+1); + ios_write(s, commit, strlen(commit)+1); + write_uint8(s, pkgimage); + int64_t checksumpos = ios_pos(s); + write_uint64(s, 0); // eventually will hold checksum for the content portion of this (build_id.hi) + write_uint64(s, 0); // eventually will hold dataendpos + write_uint64(s, 0); // eventually will hold datastartpos + return checksumpos; +} + +// 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); + 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) { + size_t l = strlen(jl_symbol_name(workmod->name)); + write_int32(s, l); + ios_write(s, jl_symbol_name(workmod->name), l); + write_uint64(s, workmod->uuid.hi); + write_uint64(s, workmod->uuid.lo); + write_uint64(s, workmod->build_id.lo); + } + } + write_int32(s, 0); +} + +static void write_module_path(ios_t *s, jl_module_t *depmod) JL_NOTSAFEPOINT +{ + if (depmod->parent == jl_main_module || depmod->parent == depmod) + return; + const char *mname = jl_symbol_name(depmod->name); + size_t slen = strlen(mname); + write_module_path(s, depmod->parent); + write_int32(s, slen); + ios_write(s, mname, slen); +} + +// Cache file header +// Serialize the global Base._require_dependencies array of pathnames that +// are include dependencies. Also write Preferences and return +// the location of the srctext "pointer" in the header index. +static int64_t write_dependency_list(ios_t *s, jl_array_t* worklist, jl_array_t **udepsp) +{ + int64_t initial_pos = 0; + int64_t pos = 0; + static jl_array_t *deps = NULL; + if (!deps) + deps = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("_require_dependencies")); + + // unique(deps) to eliminate duplicates while preserving order: + // we preserve order so that the topmost included .jl file comes first + static jl_value_t *unique_func = NULL; + if (!unique_func) + unique_func = jl_get_global(jl_base_module, jl_symbol("unique")); + jl_value_t *uniqargs[2] = {unique_func, (jl_value_t*)deps}; + 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_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL); + ct->world_age = last_age; + + // 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; + 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); + write_int32(s, slen); + ios_write(s, jl_string_data(dep), slen); + write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2))); // 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); + 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) { + ++provides; + if (workmod == depmod_top) { + write_int32(s, provides); + write_module_path(s, depmod); + break; + } + } + } + write_int32(s, 0); + } + write_int32(s, 0); // terminator, for ease of reading + + // Calculate Preferences hash for current package. + jl_value_t *prefs_hash = NULL; + jl_value_t *prefs_list = NULL; + JL_GC_PUSH1(&prefs_list); + if (jl_base_module) { + // Toplevel module is the module we're currently compiling, use it to get our preferences hash + jl_value_t * toplevel = (jl_value_t*)jl_get_global(jl_base_module, jl_symbol("__toplevel__")); + jl_value_t * prefs_hash_func = jl_get_global(jl_base_module, jl_symbol("get_preferences_hash")); + jl_value_t * get_compiletime_prefs_func = jl_get_global(jl_base_module, jl_symbol("get_compiletime_preferences")); + + if (toplevel && prefs_hash_func && get_compiletime_prefs_func) { + // Temporary invoke in newest world age + size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + + // call get_compiletime_prefs(__toplevel__) + jl_value_t *args[3] = {get_compiletime_prefs_func, (jl_value_t*)toplevel, NULL}; + prefs_list = (jl_value_t*)jl_apply(args, 2); + + // Call get_preferences_hash(__toplevel__, prefs_list) + args[0] = prefs_hash_func; + args[2] = prefs_list; + prefs_hash = (jl_value_t*)jl_apply(args, 3); + + // Reset world age to normal + ct->world_age = last_age; + } + } + + // 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); + 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); + write_int32(s, slen); + ios_write(s, jl_string_data(pref_name), slen); + } + write_int32(s, 0); // terminator + write_uint64(s, jl_unbox_uint64(prefs_hash)); + } + else { + // This is an error path, but let's at least generate a valid `.ji` file. + // We declare an empty list of preference names, followed by a zero-hash. + // The zero-hash is not what would be generated for an empty set of preferences, + // and so this `.ji` file will be invalidated by a future non-erroring pass + // through this function. + write_int32(s, 0); + write_uint64(s, 0); + } + JL_GC_POP(); // for prefs_list + + // write a dummy file position to indicate the beginning of the source-text + pos = ios_pos(s); + ios_seek(s, initial_pos); + write_uint64(s, pos - initial_pos); + ios_seek(s, pos); + write_uint64(s, 0); + return pos; +} + + +// Deserialization + +// Add methods to external (non-worklist-owned) functions +static void jl_insert_methods(jl_array_t *list) +{ + size_t i, l = jl_array_len(list); + for (i = 0; i < l; i++) { + jl_method_t *meth = (jl_method_t*)jl_array_ptr_ref(list, 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); + } +} + +static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) +{ + size_t i, l = jl_array_len(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); + if (roots) { + assert(jl_is_array(roots)); + jl_append_method_roots(m, key, roots); + } + } +} + + +// verify that these edges intersect with the same methods as before +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; + 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)); + jl_value_t *loctag = NULL; + jl_value_t *matches = NULL; + jl_value_t *sig = NULL; + JL_GC_PUSH4(&maxvalids, &matches, &sig, &loctag); + for (i = 0; i < l; i++) { + jl_value_t *invokesig = jl_array_ptr_ref(targets, i * 3); + jl_value_t *callee = jl_array_ptr_ref(targets, i * 3 + 1); + jl_value_t *expected = jl_array_ptr_ref(targets, i * 3 + 2); + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + if (invokesig) { + assert(callee && "unsupported edge"); + jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); + if ((jl_value_t*)mt == jl_nothing) { + max_valid = 0; + } + else { + matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, minworld, &min_valid, &max_valid); + if (matches == jl_nothing) { + max_valid = 0; + } + else { + matches = (jl_value_t*)((jl_method_match_t*)matches)->method; + if (matches != expected) { + max_valid = 0; + } + } + } + } + else { + if (jl_is_method_instance(callee)) { + jl_method_instance_t *mi = (jl_method_instance_t*)callee; + sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); + } + else { + sig = callee; + } + assert(jl_is_array(expected)); + int ambig = 0; + // 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), + 0, minworld, &min_valid, &max_valid, &ambig); + sig = NULL; + if (matches == jl_nothing) { + max_valid = 0; + } + else { + // setdiff!(matches, expected) + size_t j, k, ins = 0; + if (jl_array_len(matches) != jl_array_len(expected)) { + max_valid = 0; + } + for (k = 0; k < jl_array_len(matches); k++) { + jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(matches, k))->method; + size_t l = jl_array_len(expected); + for (j = 0; j < l; j++) + if (match == (jl_method_t*)jl_array_ptr_ref(expected, j)) + break; + if (j == l) { + // intersection has a new method or a method was + // deleted--this is now probably no good, just invalidate + // everything about it now + max_valid = 0; + if (!_jl_debug_method_invalidation) + break; + jl_array_ptr_set(matches, ins++, match); + } + } + if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) + jl_array_del_end((jl_array_t*)matches, jl_array_len(matches) - ins); + } + } + ((size_t*)(jl_array_data(maxvalids)))[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"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + loctag = jl_box_int32((int32_t)i); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); + } + //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); + } + JL_GC_POP(); + return maxvalids; +} + +// Combine all edges relevant to a method to initialize the maxvalids list +static jl_array_t *jl_verify_methods(jl_array_t *edges, jl_array_t *maxvalids) +{ + JL_TIMING(VERIFY_IMAGE, VERIFY_Methods); + jl_value_t *loctag = NULL; + jl_array_t *maxvalids2 = NULL; + JL_GC_PUSH2(&loctag, &maxvalids2); + size_t i, l = jl_array_len(edges) / 2; + maxvalids2 = jl_alloc_array_1d(jl_typeof(maxvalids), l); + size_t *maxvalids2_data = (size_t*)jl_array_data(maxvalids2); + 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); + assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); + jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); + assert(jl_typetagis((jl_value_t*)callee_ids, jl_array_int32_type)); + if (callee_ids == NULL) { + // serializing the edges had failed + maxvalids2_data[i] = 0; + } + else { + int32_t *idxs = (int32_t*)jl_array_data(callee_ids); + 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]; + 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"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + loctag = jl_box_int32((int32_t)idx); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + } + if (max_valid < maxvalids2_data[i]) + maxvalids2_data[i] = max_valid; + if (max_valid == 0) + break; + } + } + //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); + } + JL_GC_POP(); + return maxvalids2; +} + + +// Visit the entire call graph, starting from edges[idx] to determine if that method is valid +// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable +// and slightly modified with an early termination option once the computation reaches its minimum +static int jl_verify_graph_edge(size_t *maxvalids2_data, jl_array_t *edges, size_t idx, arraylist_t *visited, arraylist_t *stack) +{ + if (maxvalids2_data[idx] == 0) { + visited->items[idx] = (void*)1; + return 0; + } + size_t cycle = (size_t)visited->items[idx]; + if (cycle != 0) + return cycle - 1; // depth remaining + jl_value_t *cause = NULL; + arraylist_push(stack, (void*)idx); + size_t depth = stack->len; + 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); + cycle = depth; + for (i = idxs[0] + 1; i < n; i++) { + int32_t childidx = idxs[i]; + int child_cycle = jl_verify_graph_edge(maxvalids2_data, edges, childidx, visited, stack); + size_t child_max_valid = maxvalids2_data[childidx]; + if (child_max_valid < maxvalids2_data[idx]) { + maxvalids2_data[idx] = child_max_valid; + cause = jl_array_ptr_ref(edges, childidx * 2); + } + if (child_max_valid == 0) { + // found what we were looking for, so terminate early + break; + } + else if (child_cycle && child_cycle < cycle) { + // record the cycle will resolve at depth "cycle" + cycle = child_cycle; + } + } + size_t max_valid = maxvalids2_data[idx]; + if (max_valid != 0 && cycle != depth) + return cycle; + // If we are the top of the current cycle, now mark all other parts of + // our cycle with what we found. + // Or if we found a failed edge, also mark all of the other parts of the + // cycle as also having an failed edge. + while (stack->len >= depth) { + size_t childidx = (size_t)arraylist_pop(stack); + assert(visited->items[childidx] == (void*)(2 + stack->len)); + if (idx != childidx) { + if (max_valid < maxvalids2_data[childidx]) + maxvalids2_data[childidx] = max_valid; + } + visited->items[childidx] = (void*)1; + if (_jl_debug_method_invalidation && max_valid != ~(size_t)0) { + jl_method_instance_t *mi = (jl_method_instance_t*)jl_array_ptr_ref(edges, childidx * 2); + jl_value_t *loctag = NULL; + JL_GC_PUSH1(&loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi); + loctag = jl_cstr_to_string("verify_methods"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)cause); + JL_GC_POP(); + } + } + return 0; +} + +// Visit all entries in edges, verify if they are valid +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; + arraylist_new(&visited, n); + memset(visited.items, 0, n * sizeof(size_t)); + size_t *maxvalids2_data = (size_t*)jl_array_data(maxvalids2); + 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); + assert(child_cycle == 0); (void)child_cycle; + assert(stack.len == 0); + assert(visited.items[i] == (void*)1); + } + arraylist_free(&stack); + arraylist_free(&visited); +} + +// 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) +{ + // 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; + + // 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); + } + else { + assert(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) { + jl_mi_cache_insert(caller, ci); + } + //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++) { + 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]; + 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); + 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); + jl_value_t *callee = jl_array_ptr_ref(ext_targets, idx * 3 + 1); + if (callee && jl_is_method_instance(callee)) { + jl_method_instance_add_backedge((jl_method_instance_t*)callee, invokesig, caller); + } + else { + jl_value_t *sig = callee == NULL ? invokesig : callee; + jl_methtable_t *mt = jl_method_table_for(sig); + // FIXME: rarely, `callee` has an unexpected `Union` signature, + // see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1030329344 + // Fix the issue and turn this back into an `assert((jl_value_t*)mt != jl_nothing)` + // This workaround exposes us to (rare) 265-violations. + if ((jl_value_t*)mt != jl_nothing) + jl_method_table_add_backedge(mt, sig, (jl_value_t*)caller); + } + } + } + // then enable any methods associated with it + void *ci = ptrhash_get(&visited, (void*)caller); + //assert(ci != HT_NOTFOUND); + if (ci != HT_NOTFOUND) { + // have some new external code to use + 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); + } + } + } + + 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; + 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); + } +} + +static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *depmods) +{ + if (!jl_main_module->build_id.lo) { + return jl_get_exceptionf(jl_errorexception_type, + "Main module uuid state is invalid for module deserialization."); + } + size_t i, l = jl_array_len(depmods); + for (i = 0; ; i++) { + size_t len = read_int32(s); + if (len == 0 && i == l) + return NULL; // success + if (len == 0 || i == l) + return jl_get_exceptionf(jl_errorexception_type, "Wrong number of entries in module list."); + char *name = (char*)alloca(len + 1); + ios_readall(s, name, len); + name[len] = '\0'; + jl_uuid_t uuid; + uuid.hi = read_uint64(s); + uuid.lo = read_uint64(s); + jl_uuid_t build_id; + build_id.hi = read_uint64(s); + build_id.lo = read_uint64(s); + jl_sym_t *sym = _jl_symbol(name, len); + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(depmods, i); + if (!m || !jl_is_module(m) || m->uuid.hi != uuid.hi || m->uuid.lo != uuid.lo || m->name != sym || + m->build_id.hi != build_id.hi || m->build_id.lo != build_id.lo) { + return jl_get_exceptionf(jl_errorexception_type, + "Invalid input in module list: expected %s.", name); + } + } +} + +static int readstr_verify(ios_t *s, const char *str, int include_null) +{ + size_t i, len = strlen(str) + include_null; + for (i = 0; i < len; ++i) + if ((char)read_uint8(s) != str[i]) + return 0; + return 1; +} + +JL_DLLEXPORT uint64_t jl_read_verify_header(ios_t *s, uint8_t *pkgimage, int64_t *dataendpos, int64_t *datastartpos) +{ + uint16_t bom; + uint64_t checksum = 0; + if (readstr_verify(s, JI_MAGIC, 0) && + read_uint16(s) == JI_FORMAT_VERSION && + ios_read(s, (char *) &bom, 2) == 2 && bom == BOM && + read_uint8(s) == sizeof(void*) && + readstr_verify(s, JL_BUILD_UNAME, 1) && + readstr_verify(s, JL_BUILD_ARCH, 1) && + readstr_verify(s, JULIA_VERSION_STRING, 1) && + readstr_verify(s, jl_git_branch(), 1) && + readstr_verify(s, jl_git_commit(), 1)) + { + *pkgimage = read_uint8(s); + checksum = read_uint64(s); + *datastartpos = (int64_t)read_uint64(s); + *dataendpos = (int64_t)read_uint64(s); + } + return checksum; +} + +// Returns `depmodidxs` where `j = depmodidxs[i]` corresponds to the blob `depmods[j]` in `write_mod_list` +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"); + size_t lbids = n_linkage_blobs(); + size_t ldeps = jl_array_len(depmods); + jl_array_t *depmodidxs = jl_alloc_array_1d(jl_array_int32_type, lbids); + int32_t *dmidxs = (int32_t*)jl_array_data(depmodidxs); + 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++) { + jl_value_t *depmod = jl_array_ptr_ref(depmods, i); + size_t idx = external_blob_index(depmod); + if (idx < lbids) { // jl_object_in_image + j++; + if (dmidxs[idx] == -1) + dmidxs[idx] = j; + } + } + return depmodidxs; +} + +// Returns `imageidxs` where `j = imageidxs[i]` is the blob corresponding to `depmods[j]` +static jl_array_t *depmod_to_imageidx(jl_array_t *depmods) +{ + if (!depmods) + return NULL; + size_t ldeps = jl_array_len(depmods); + jl_array_t *imageidxs = jl_alloc_array_1d(jl_array_int32_type, ldeps + 1); + int32_t *imgidxs = (int32_t*)jl_array_data(imageidxs); + imgidxs[0] = 0; + for (size_t i = 0; i < ldeps; i++) { + jl_value_t *depmod = jl_array_ptr_ref(depmods, i); + size_t j = external_blob_index(depmod); + assert(j < INT32_MAX); + imgidxs[i + 1] = (int32_t)j; + } + return imageidxs; +} diff --git a/src/subtype.c b/src/subtype.c index eb668645552d7..fd9bd3e8be00f 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -65,6 +65,7 @@ typedef struct jl_varbinding_t { jl_value_t *lb; jl_value_t *ub; int8_t right; // whether this variable came from the right side of `A <: B` + int8_t occurs; // occurs in any position 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 @@ -72,14 +73,11 @@ typedef struct jl_varbinding_t { // let ub = var.ub ∩ type // 0 - var.ub <: type ? var : ub // 1 - var.ub = ub; return var - // 2 - either (var.ub = ub; return var), or return ub + // 2 - var.lb = lb; return ub int8_t constraintkind; - int8_t intvalued; // must be integer-valued; i.e. occurs as N in Vararg{_,N} + int8_t intvalued; // intvalued: must be integer-valued; i.e. occurs as N in Vararg{_,N} int8_t limited; int16_t depth0; // # of invariant constructors nested around the UnionAll type for this var - // when this variable's integer value is compared to that of another, - // it equals `other + offset`. used by vararg length parameters. - int16_t offset; // array of typevars that our bounds depend on, whose UnionAlls need to be // moved outside ours. jl_array_t *innervars; @@ -96,12 +94,14 @@ typedef struct jl_stenv_t { jl_value_t **envout; // for passing caller the computed bounds of right-side variables int envsz; // length of envout int envidx; // current index in envout - int invdepth; // # of invariant constructors we're nested in on the left - int Rinvdepth; // # of invariant constructors we're nested in on the right + int invdepth; // current number of invariant constructors we're nested in int ignore_free; // treat free vars as black boxes; used during intersection int intersection; // true iff subtype is being called from intersection int emptiness_only; // true iff intersection only needs to test for emptiness int triangular; // when intersecting Ref{X} with Ref{<:Y} + // Used to represent the length difference between 2 vararg. + // intersect(X, Y) ==> X = Y + Loffset + int Loffset; } jl_stenv_t; // state manipulation utilities @@ -154,71 +154,156 @@ static void statestack_set(jl_unionstate_t *st, int i, int val) JL_NOTSAFEPOINT memcpy(&(dst)->stack, (saved)->stack, ((saved)->used+7)/8); \ } while (0); +static int current_env_length(jl_stenv_t *e) +{ + jl_varbinding_t *v = e->vars; + int len = 0; + while (v) { + len++; + v = v->prev; + } + return len; +} + typedef struct { int8_t *buf; int rdepth; - int8_t _space[16]; + int8_t _space[24]; // == 8 * 3 + jl_gcframe_t gcframe; + jl_value_t *roots[24]; } jl_savedenv_t; -static void save_env(jl_stenv_t *e, jl_value_t **root, jl_savedenv_t *se) +static void re_save_env(jl_stenv_t *e, jl_savedenv_t *se, int root) { - jl_varbinding_t *v = e->vars; - int len=0; - while (v != NULL) { - len++; - v = v->prev; + jl_value_t **roots = NULL; + int nroots = 0; + if (root) { + if (se->gcframe.nroots == JL_GC_ENCODE_PUSHARGS(1)) { + jl_svec_t *sv = (jl_svec_t*)se->roots[0]; + assert(jl_is_svec(sv)); + roots = jl_svec_data(sv); + nroots = jl_svec_len(sv); + } + else { + roots = se->roots; + nroots = se->gcframe.nroots >> 2; + } } - if (root) - *root = (jl_value_t*)jl_alloc_svec(len * 3); - se->buf = (int8_t*)(len > 8 ? malloc_s(len * 2) : &se->_space); -#ifdef __clang_gcanalyzer__ - memset(se->buf, 0, len * 2); -#endif - int i=0, j=0; v = e->vars; + jl_varbinding_t *v = e->vars; + int i = 0, j = 0; while (v != NULL) { if (root) { - jl_svecset(*root, i++, v->lb); - jl_svecset(*root, i++, v->ub); - jl_svecset(*root, i++, (jl_value_t*)v->innervars); + roots[i++] = v->lb; + roots[i++] = v->ub; + roots[i++] = (jl_value_t*)v->innervars; } + se->buf[j++] = v->occurs; se->buf[j++] = v->occurs_inv; se->buf[j++] = v->occurs_cov; v = v->prev; } + assert(i == nroots); (void)nroots; se->rdepth = e->Runions.depth; } +static void alloc_env(jl_stenv_t *e, jl_savedenv_t *se, int root) +{ + jl_task_t *ct = jl_current_task; + int len = current_env_length(e); + se->gcframe.nroots = 0; + se->gcframe.prev = NULL; + se->roots[0] = NULL; + if (len > 8) { + if (root) { + se->gcframe.nroots = JL_GC_ENCODE_PUSHARGS(1); + se->gcframe.prev = ct->gcstack; + ct->gcstack = &se->gcframe; + jl_svec_t *sv = jl_alloc_svec(len * 3); + se->roots[0] = (jl_value_t*)sv; + } + } + else { + if (root && len) { + for (int i = 0; i < len * 3; i++) + se->roots[i] = NULL; + se->gcframe.nroots = JL_GC_ENCODE_PUSHARGS(len * 3); + se->gcframe.prev = ct->gcstack; + ct->gcstack = &se->gcframe; + } + } + se->buf = (len > 8 ? (int8_t*)malloc_s(len * 3) : se->_space); +#ifdef __clang_gcanalyzer__ + memset(se->buf, 0, len * 3); +#endif +} + +static void save_env(jl_stenv_t *e, jl_savedenv_t *se, int root) +{ + alloc_env(e, se, root); + re_save_env(e, se, root); +} + static void free_env(jl_savedenv_t *se) JL_NOTSAFEPOINT { + if (se->gcframe.nroots) { + assert(jl_current_task->gcstack == &se->gcframe); + JL_GC_POP(); + } if (se->buf != se->_space) free(se->buf); se->buf = NULL; } -static void restore_env(jl_stenv_t *e, jl_value_t *root, jl_savedenv_t *se) JL_NOTSAFEPOINT +static void restore_env(jl_stenv_t *e, jl_savedenv_t *se, int root) JL_NOTSAFEPOINT { + jl_value_t **roots = NULL; + int nroots = 0; + if (root) { + if (se->gcframe.nroots == JL_GC_ENCODE_PUSHARGS(1)) { + jl_svec_t *sv = (jl_svec_t*)se->roots[0]; + assert(jl_is_svec(sv)); + roots = jl_svec_data(sv); + nroots = jl_svec_len(sv); + } + else { + roots = se->roots; + nroots = se->gcframe.nroots >> 2; + } + } jl_varbinding_t *v = e->vars; int i = 0, j = 0; while (v != NULL) { - if (root) v->lb = jl_svecref(root, i); - i++; - if (root) v->ub = jl_svecref(root, i); - i++; - if (root) v->innervars = (jl_array_t*)jl_svecref(root, i); - i++; + if (root) { + v->lb = roots[i++]; + v->ub = roots[i++]; + v->innervars = (jl_array_t*)roots[i++]; + } + v->occurs = se->buf[j++]; v->occurs_inv = se->buf[j++]; v->occurs_cov = se->buf[j++]; v = v->prev; } + assert(i == nroots); (void)nroots; e->Runions.depth = se->rdepth; if (e->envout && e->envidx < e->envsz) memset(&e->envout[e->envidx], 0, (e->envsz - e->envidx)*sizeof(void*)); } +static void clean_occurs(jl_stenv_t *e) +{ + jl_varbinding_t *v = e->vars; + while (v) { + v->occurs = 0; + v = v->prev; + } +} + +#define flip_offset(e) ((e)->Loffset *= -1) + // type utilities // quickly test that two types are identical -static int obviously_egal(jl_value_t *a, jl_value_t *b) +static int obviously_egal(jl_value_t *a, jl_value_t *b) JL_NOTSAFEPOINT { if (a == (jl_value_t*)jl_typeofbottom_type->super) a = (jl_value_t*)jl_typeofbottom_type; // supertype(typeof(Union{})) is equal to, although distinct from, itself @@ -282,11 +367,8 @@ static int obviously_unequal(jl_value_t *a, jl_value_t *b) if (ad->name != bd->name) return 1; int istuple = (ad->name == jl_tuple_typename); - if ((jl_is_concrete_type(a) || jl_is_concrete_type(b)) && - jl_type_equality_is_identity(a, b)) { - if (!istuple && ad->name != jl_type_typename) // HACK: can't properly normalize Tuple{Float64} == Tuple{<:Float64} like types or Type{T} types - return 1; - } + if (jl_type_equality_is_identity(a, b)) + return 1; size_t i, np; if (istuple) { size_t na = jl_nparams(ad), nb = jl_nparams(bd); @@ -343,19 +425,44 @@ static int in_union(jl_value_t *u, jl_value_t *x) JL_NOTSAFEPOINT return in_union(((jl_uniontype_t*)u)->a, x) || in_union(((jl_uniontype_t*)u)->b, x); } -static int obviously_disjoint(jl_value_t *a, jl_value_t *b, int specificity) +static int obviously_in_union(jl_value_t *u, jl_value_t *x) +{ + jl_value_t *a = NULL, *b = NULL; + if (jl_is_uniontype(x)) { + a = ((jl_uniontype_t*)x)->a; + b = ((jl_uniontype_t*)x)->b; + JL_GC_PUSH2(&a, &b); + int res = obviously_in_union(u, a) && obviously_in_union(u, b); + JL_GC_POP(); + return res; + } + if (jl_is_uniontype(u)) { + a = ((jl_uniontype_t*)u)->a; + b = ((jl_uniontype_t*)u)->b; + JL_GC_PUSH2(&a, &b); + int res = obviously_in_union(a, x) || obviously_in_union(b, x); + JL_GC_POP(); + return res; + } + return obviously_egal(u, x); +} + +int obviously_disjoint(jl_value_t *a, jl_value_t *b, int specificity) { if (a == b || a == (jl_value_t*)jl_any_type || b == (jl_value_t*)jl_any_type) return 0; if (specificity && a == (jl_value_t*)jl_typeofbottom_type) return 0; - if (jl_is_concrete_type(a) && jl_is_concrete_type(b) && - jl_type_equality_is_identity(a, b) && - (((jl_datatype_t*)a)->name != jl_tuple_typename || - ((jl_datatype_t*)b)->name != jl_tuple_typename)) + if (jl_is_concrete_type(a) && jl_is_concrete_type(b) && jl_type_equality_is_identity(a, b)) return 1; if (jl_is_unionall(a)) a = jl_unwrap_unionall(a); if (jl_is_unionall(b)) b = jl_unwrap_unionall(b); + if (jl_is_uniontype(a)) + return obviously_disjoint(((jl_uniontype_t *)a)->a, b, specificity) && + obviously_disjoint(((jl_uniontype_t *)a)->b, b, specificity); + if (jl_is_uniontype(b)) + return obviously_disjoint(a, ((jl_uniontype_t *)b)->a, specificity) && + obviously_disjoint(a, ((jl_uniontype_t *)b)->b, specificity); if (jl_is_datatype(a) && jl_is_datatype(b)) { jl_datatype_t *ad = (jl_datatype_t*)a, *bd = (jl_datatype_t*)b; if (ad->name != bd->name) { @@ -431,19 +538,16 @@ static int obviously_disjoint(jl_value_t *a, jl_value_t *b, int specificity) return 0; } +jl_value_t *simple_union(jl_value_t *a, jl_value_t *b); // compute a least upper bound of `a` and `b` static jl_value_t *simple_join(jl_value_t *a, jl_value_t *b) { - if (a == jl_bottom_type || b == (jl_value_t*)jl_any_type || obviously_egal(a,b)) + if (a == jl_bottom_type || b == (jl_value_t*)jl_any_type || obviously_egal(a, b)) return b; if (b == jl_bottom_type || a == (jl_value_t*)jl_any_type) return a; if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return (jl_value_t*)jl_any_type; - if (jl_is_uniontype(a) && in_union(a, b)) - return a; - if (jl_is_uniontype(b) && in_union(b, a)) - return b; if (jl_is_kind(a) && jl_is_type_type(b) && jl_typeof(jl_tparam0(b)) == a) return a; if (jl_is_kind(b) && jl_is_type_type(a) && jl_typeof(jl_tparam0(a)) == b) @@ -452,18 +556,14 @@ static jl_value_t *simple_join(jl_value_t *a, jl_value_t *b) return a; if (jl_is_typevar(b) && obviously_egal(a, ((jl_tvar_t*)b)->lb)) return b; - if (!jl_has_free_typevars(a) && !jl_has_free_typevars(b) && - // issue #24521: don't merge Type{T} where typeof(T) varies - !(jl_is_type_type(a) && jl_is_type_type(b) && jl_typeof(jl_tparam0(a)) != jl_typeof(jl_tparam0(b)))) { - if (jl_subtype(a, b)) return b; - if (jl_subtype(b, a)) return a; - } - return jl_new_struct(jl_uniontype_type, a, b); + return simple_union(a, b); } -// compute a greatest lower bound of `a` and `b` -// in many cases, we need to over-estimate this by returning `b`. -static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b) +jl_value_t *simple_intersect(jl_value_t *a, jl_value_t *b, int overesi); +// Compute a greatest lower bound of `a` and `b` +// For the subtype path, we need to over-estimate this by returning `b` in many cases. +// But for `merge_env`, we'd better under-estimate and return a `Union{}` +static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b, int overesi) { if (a == (jl_value_t*)jl_any_type || b == jl_bottom_type || obviously_egal(a,b)) return b; @@ -471,10 +571,6 @@ static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b) return a; if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return jl_bottom_type; - if (jl_is_uniontype(a) && in_union(a, b)) - return b; - if (jl_is_uniontype(b) && in_union(b, a)) - return a; if (jl_is_kind(a) && jl_is_type_type(b) && jl_typeof(jl_tparam0(b)) == a) return b; if (jl_is_kind(b) && jl_is_type_type(a) && jl_typeof(jl_tparam0(a)) == b) @@ -483,56 +579,56 @@ static jl_value_t *simple_meet(jl_value_t *a, jl_value_t *b) return a; if (jl_is_typevar(b) && obviously_egal(a, ((jl_tvar_t*)b)->ub)) return b; - if (obviously_disjoint(a, b, 0)) - return jl_bottom_type; - if (!jl_has_free_typevars(a) && !jl_has_free_typevars(b)) { - if (jl_subtype(a, b)) return a; - if (jl_subtype(b, a)) return b; - } - return b; -} - -static jl_unionall_t *rename_unionall(jl_unionall_t *u) -{ - jl_tvar_t *v = jl_new_typevar(u->var->name, u->var->lb, u->var->ub); - jl_value_t *t = NULL; - JL_GC_PUSH2(&v, &t); - t = jl_instantiate_unionall(u, (jl_value_t*)v); - t = jl_new_struct(jl_unionall_type, v, t); - JL_GC_POP(); - return (jl_unionall_t*)t; + return simple_intersect(a, b, overesi); } // main subtyping algorithm static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param); -static jl_value_t *pick_union_element(jl_value_t *u JL_PROPAGATES_ROOT, jl_stenv_t *e, int8_t R) JL_NOTSAFEPOINT +static int next_union_state(jl_stenv_t *e, int8_t R) JL_NOTSAFEPOINT +{ + jl_unionstate_t *state = R ? &e->Runions : &e->Lunions; + if (state->more == 0) + return 0; + // reset `used` and let `pick_union_decision` clean the stack. + state->used = state->more; + statestack_set(state, state->used - 1, 1); + return 1; +} + +static int pick_union_decision(jl_stenv_t *e, int8_t R) JL_NOTSAFEPOINT { jl_unionstate_t *state = R ? &e->Runions : &e->Lunions; + if (state->depth >= state->used) { + statestack_set(state, state->used, 0); + state->used++; + } + int ui = statestack_get(state, state->depth); + state->depth++; + if (ui == 0) + state->more = state->depth; // memorize that this was the deepest available choice + return ui; +} + +static jl_value_t *pick_union_element(jl_value_t *u JL_PROPAGATES_ROOT, jl_stenv_t *e, int8_t R) JL_NOTSAFEPOINT +{ do { - if (state->depth >= state->used) { - statestack_set(state, state->used, 0); - state->used++; - } - int ui = statestack_get(state, state->depth); - state->depth++; - if (ui == 0) { - state->more = state->depth; // memorize that this was the deepest available choice - u = ((jl_uniontype_t*)u)->a; - } - else { + if (pick_union_decision(e, R)) u = ((jl_uniontype_t*)u)->b; - } + else + u = ((jl_uniontype_t*)u)->a; } while (jl_is_uniontype(u)); return u; } -static int forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param); +static int local_forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param, int limit_slow); // subtype for variable bounds consistency check. needs its own forall/exists environment. static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { + if (jl_is_long(x) && jl_is_long(y)) + return jl_unbox_long(x) == jl_unbox_long(y) + e->Loffset; if (x == y) return 1; if (x == jl_bottom_type && jl_is_type(y)) @@ -544,24 +640,16 @@ static int subtype_ccheck(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) if (x == (jl_value_t*)jl_any_type && jl_is_datatype(y)) return 0; jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); - jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); - int sub; - e->Lunions.used = e->Runions.used = 0; - e->Runions.depth = 0; - e->Runions.more = 0; - e->Lunions.depth = 0; - e->Lunions.more = 0; - - sub = forall_exists_subtype(x, y, e, 0); - - pop_unionstate(&e->Runions, &oldRunions); + int sub = local_forall_exists_subtype(x, y, e, 0, 1); pop_unionstate(&e->Lunions, &oldLunions); return sub; } static int subtype_left_var(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) { - if (x == y) + if (jl_is_long(x) && jl_is_long(y)) + return jl_unbox_long(x) == jl_unbox_long(y) + e->Loffset; + if (x == y && !(jl_is_unionall(y))) return 1; if (x == jl_bottom_type && jl_is_type(y)) return 1; @@ -578,9 +666,11 @@ static int subtype_left_var(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int par // of determining whether the variable is concrete. static void record_var_occurrence(jl_varbinding_t *vb, jl_stenv_t *e, int param) JL_NOTSAFEPOINT { + if (vb != NULL) + vb->occurs = 1; if (vb != NULL && param) { // saturate counters at 2; we don't need values bigger than that - if (param == 2 && (vb->right ? e->Rinvdepth : e->invdepth) > vb->depth0) { + if (param == 2 && e->invdepth > vb->depth0) { if (vb->occurs_inv < 2) vb->occurs_inv++; } @@ -602,7 +692,9 @@ static int var_outside(jl_stenv_t *e, jl_tvar_t *x, jl_tvar_t *y) return 0; } -static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int R, int d); +static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int depth); + +static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e); // check that type var `b` is <: `a`, and update b's upper bound. static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) @@ -611,6 +703,10 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) if (bb == NULL) return e->ignore_free || subtype_left_var(b->ub, a, e, param); record_var_occurrence(bb, e, param); + assert(!jl_is_long(a) || e->Loffset == 0); + if (e->Loffset != 0 && !jl_is_typevar(a) && + a != jl_bottom_type && a != (jl_value_t *)jl_any_type) + return 0; if (!bb->right) // check ∀b . b<:a return subtype_left_var(bb->ub, a, e, param); if (bb->ub == a) @@ -620,12 +716,14 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) // for this to work we need to compute issub(left,right) before issub(right,left), // since otherwise the issub(a, bb.ub) check in var_gt becomes vacuous. if (e->intersection) { - jl_value_t *ub = intersect_aside(bb->ub, a, e, 0, bb->depth0); - if (ub != (jl_value_t*)b) + jl_value_t *ub = intersect_aside(a, bb->ub, e, bb->depth0); + JL_GC_PUSH1(&ub); + if (ub != (jl_value_t*)b && (!jl_is_typevar(ub) || !reachable_var(ub, b, e))) bb->ub = ub; + JL_GC_POP(); } else { - bb->ub = simple_meet(bb->ub, a); + bb->ub = simple_meet(bb->ub, a, 1); } assert(bb->ub != (jl_value_t*)b); if (jl_is_typevar(a)) { @@ -639,8 +737,6 @@ static int var_lt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) return 1; } -static int subtype_by_bounds(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) JL_NOTSAFEPOINT; - // check that type var `b` is >: `a`, and update b's lower bound. static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) { @@ -648,19 +744,21 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) if (bb == NULL) return e->ignore_free || subtype_left_var(a, b->lb, e, param); record_var_occurrence(bb, e, param); + assert(!jl_is_long(a) || e->Loffset == 0); + if (e->Loffset != 0 && !jl_is_typevar(a) && + a != jl_bottom_type && a != (jl_value_t *)jl_any_type) + return 0; if (!bb->right) // check ∀b . b>:a return subtype_left_var(a, bb->lb, e, param); - if (bb->lb == bb->ub) { - if (jl_is_typevar(bb->lb) && !jl_is_type(a) && !jl_is_typevar(a)) - return var_gt((jl_tvar_t*)bb->lb, a, e, param); - if (jl_is_typevar(a) && !jl_is_type(bb->lb) && !jl_is_typevar(bb->lb)) - return var_lt((jl_tvar_t*)a, bb->lb, e, param); - } + if (bb->lb == a) + return 1; if (!((bb->ub == (jl_value_t*)jl_any_type && !jl_is_type(a) && !jl_is_typevar(a)) || subtype_ccheck(a, bb->ub, e))) return 0; jl_value_t *lb = simple_join(bb->lb, a); - if (!e->intersection || !subtype_by_bounds(lb, (jl_value_t*)b, e)) + JL_GC_PUSH1(&lb); + if (!e->intersection || !jl_is_typevar(lb) || !reachable_var(lb, b, e)) bb->lb = lb; + JL_GC_POP(); // this bound should not be directly circular assert(bb->lb != (jl_value_t*)b); if (jl_is_typevar(a)) { @@ -671,6 +769,30 @@ static int var_gt(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int param) return 1; } +static int subtype_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int R, int param) +{ + if (e->intersection) { + jl_varbinding_t *bb = lookup(e, (jl_tvar_t*)b); + jl_value_t *bub = bb ? bb->ub : ((jl_tvar_t*)b)->ub; + jl_value_t *blb = bb ? bb->lb : ((jl_tvar_t*)b)->lb; + if (bub == blb && jl_is_typevar(bub)) { + int sub = subtype_var((jl_tvar_t *)bub, a, e, R, param); + return sub; + } + } + if (e->Loffset != 0 && jl_is_long(a)) { + int old_offset = R ? -e->Loffset : e->Loffset; + jl_value_t *na = jl_box_long(jl_unbox_long(a) + old_offset); + JL_GC_PUSH1(&na); + e->Loffset = 0; + int sub = R ? var_gt(b, na, e, param) : var_lt(b, na, e, param); + e->Loffset = R ? -old_offset : old_offset; + JL_GC_POP(); + return sub; + } + return R ? var_gt(b, a, e, param) : var_lt(b, a, e, param); +} + // 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. @@ -716,6 +838,8 @@ static jl_value_t *widen_Type(jl_value_t *t JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT // when a static parameter value is not known exactly. static jl_value_t *fix_inferred_var_bound(jl_tvar_t *var, jl_value_t *ty JL_MAYBE_UNROOTED) { + if (ty == NULL) // may happen if the user is intersecting with an incomplete type + return (jl_value_t*)var; if (!jl_is_typevar(ty) && jl_has_free_typevars(ty)) { jl_value_t *ans = ty; jl_array_t *vs = NULL; @@ -752,7 +876,7 @@ static jl_unionall_t *unalias_unionall(jl_unionall_t *u, jl_stenv_t *e) // outer var can only refer to inner var if bounds changed (btemp->lb != btemp->var->lb && jl_has_typevar(btemp->lb, u->var)) || (btemp->ub != btemp->var->ub && jl_has_typevar(btemp->ub, u->var))) { - u = rename_unionall(u); + u = jl_rename_unionall(u); break; } btemp = btemp->prev; @@ -764,8 +888,8 @@ static jl_unionall_t *unalias_unionall(jl_unionall_t *u, jl_stenv_t *e) static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param) { u = unalias_unionall(u, e); - jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 0, 0, 0, 0, 0, 0, - R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars }; + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 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; int ans; @@ -776,37 +900,9 @@ static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8 // widen Type{x} to typeof(x) in argument position if (!vb.occurs_inv) vb.lb = widen_Type(vb.lb); - // fill variable values into `envout` up to `envsz` - if (e->envidx < e->envsz) { - jl_value_t *val; - if (vb.intvalued && vb.lb == (jl_value_t*)jl_any_type) - val = (jl_value_t*)jl_wrap_vararg(NULL, NULL); - else if (!vb.occurs_inv && vb.lb != jl_bottom_type) - val = is_leaf_bound(vb.lb) ? vb.lb : (jl_value_t*)jl_new_typevar(u->var->name, jl_bottom_type, vb.lb); - else if (vb.lb == vb.ub) - val = vb.lb; - else if (vb.lb != jl_bottom_type) - // TODO: for now return the least solution, which is what - // method parameters expect. - val = vb.lb; - else if (vb.lb == u->var->lb && vb.ub == u->var->ub) - val = (jl_value_t*)u->var; - else - val = (jl_value_t*)jl_new_typevar(u->var->name, vb.lb, vb.ub); - jl_value_t *oldval = e->envout[e->envidx]; - // if we try to assign different variable values (due to checking - // multiple union members), consider the value unknown. - if (oldval && !jl_egal(oldval, val)) - e->envout[e->envidx] = (jl_value_t*)u->var; - else - e->envout[e->envidx] = fix_inferred_var_bound(u->var, val); - // TODO: substitute the value (if any) of this variable into previous envout entries } - } - else { - ans = R ? subtype(t, u->body, e, param) : - subtype(u->body, t, e, param); - } + else + ans = subtype(u->body, t, e, param); // handle the "diagonal dispatch" rule, which says that a type var occurring more // than once, and only in covariant position, is constrained to concrete types. E.g. @@ -853,6 +949,33 @@ static int subtype_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8 } } + // fill variable values into `envout` up to `envsz` + if (R && ans && e->envidx < e->envsz) { + jl_value_t *val; + if (vb.intvalued && vb.lb == (jl_value_t*)jl_any_type) + val = (jl_value_t*)jl_wrap_vararg(NULL, NULL); // special token result that represents N::Int in the envout + else if (!vb.occurs_inv && vb.lb != jl_bottom_type) + val = is_leaf_bound(vb.lb) ? vb.lb : (jl_value_t*)jl_new_typevar(u->var->name, jl_bottom_type, vb.lb); + else if (vb.lb == vb.ub) + val = vb.lb; + else if (vb.lb != jl_bottom_type) + // TODO: for now return the least solution, which is what + // method parameters expect. + val = vb.lb; + else if (vb.lb == u->var->lb && vb.ub == u->var->ub) + val = (jl_value_t*)u->var; + else + val = (jl_value_t*)jl_new_typevar(u->var->name, vb.lb, vb.ub); + jl_value_t *oldval = e->envout[e->envidx]; + // if we try to assign different variable values (due to checking + // multiple union members), consider the value unknown. + if (oldval && !jl_egal(oldval, val)) + e->envout[e->envidx] = (jl_value_t*)u->var; + else + e->envout[e->envidx] = val; + // TODO: substitute the value (if any) of this variable into previous envout entries + } + JL_GC_POP(); return ans; } @@ -866,10 +989,8 @@ static int check_vararg_length(jl_value_t *v, ssize_t n, jl_stenv_t *e) jl_value_t *nn = jl_box_long(n); JL_GC_PUSH1(&nn); e->invdepth++; - e->Rinvdepth++; int ans = subtype(nn, N, e, 2) && subtype(N, nn, e, 0); e->invdepth--; - e->Rinvdepth--; JL_GC_POP(); if (!ans) return 0; @@ -879,17 +1000,6 @@ static int check_vararg_length(jl_value_t *v, ssize_t n, jl_stenv_t *e) static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e); -struct subtype_tuple_env { - jl_datatype_t *xd, *yd; - jl_value_t *lastx, *lasty; - size_t lx, ly; - size_t i, j; - int vx, vy; - jl_value_t *vtx; - jl_value_t *vty; - jl_vararg_kind_t vvx, vvy; -} JL_ROOTED_VALUE_COLLECTION; - static int subtype_tuple_varargs( jl_vararg_t *vtx, jl_vararg_t *vty, size_t vx, size_t vy, @@ -970,32 +1080,57 @@ static int subtype_tuple_varargs( } if (ylv) { - if (ylv->depth0 != e->invdepth || ylv->occurs_inv) + if (ylv->depth0 != e->invdepth || + ylv->lb != jl_bottom_type || + ylv->ub != (jl_value_t *)jl_any_type) return 0; ylv->intvalued = 1; } // set lb to Any. Since `intvalued` is set, we'll interpret that // appropriately. e->invdepth++; - e->Rinvdepth++; int ans = subtype((jl_value_t*)jl_any_type, yp1, e, 2); e->invdepth--; - e->Rinvdepth--; return ans; } // Vararg{T,N} <: Vararg{T2,N2}; equate N and N2 e->invdepth++; - e->Rinvdepth++; JL_GC_PUSH2(&xp1, &yp1); - if (xp1 && jl_is_long(xp1) && vx != 1) - xp1 = jl_box_long(jl_unbox_long(xp1) - vx + 1); - if (jl_is_long(yp1) && vy != 1) - yp1 = jl_box_long(jl_unbox_long(yp1) - vy + 1); - int ans = forall_exists_equal(xp1, yp1, e); + int ans; + jl_varbinding_t *bxp1 = jl_is_typevar(xp1) ? lookup(e, (jl_tvar_t *)xp1) : NULL; + jl_varbinding_t *byp1 = jl_is_typevar(yp1) ? lookup(e, (jl_tvar_t *)yp1) : NULL; + if (bxp1) { + if (bxp1->intvalued == 0) + bxp1->intvalued = 1; + if (jl_is_long(bxp1->lb)) + xp1 = bxp1->lb; + } + if (byp1) { + if (byp1->intvalued == 0) + byp1->intvalued = 1; + if (jl_is_long(byp1->lb)) + yp1 = byp1->lb; + } + if (jl_is_long(xp1) && jl_is_long(yp1)) + ans = jl_unbox_long(xp1) - vx == jl_unbox_long(yp1) - vy; + else { + if (jl_is_long(xp1) && vx != vy) { + xp1 = jl_box_long(jl_unbox_long(xp1) + vy - vx); + vx = vy; + } + if (jl_is_long(yp1) && vy != vx) { + yp1 = jl_box_long(jl_unbox_long(yp1) + vx - vy); + vy = vx; + } + assert(e->Loffset == 0); + e->Loffset = vx - vy; + ans = forall_exists_equal(xp1, yp1, e); + assert(e->Loffset == vx - vy); + e->Loffset = 0; + } JL_GC_POP(); e->invdepth--; - e->Rinvdepth--; return ans; } @@ -1033,7 +1168,8 @@ static int subtype_tuple_tail(jl_datatype_t *xd, jl_datatype_t *yd, int8_t R, jl xi = jl_tparam(xd, lx-1); if (jl_is_vararg(xi)) { all_varargs = 1; - vy += lx - i; + // count up to lx-2 rather than lx-1. + vy += lx - i - 1; vx = 1; } else { break; @@ -1053,25 +1189,21 @@ static int subtype_tuple_tail(jl_datatype_t *xd, jl_datatype_t *yd, int8_t R, jl return !!vx; xi = vx ? jl_unwrap_vararg(xi) : xi; - int x_same = lastx && jl_egal(xi, lastx); - if (vy) { - yi = jl_unwrap_vararg(yi); - // keep track of number of consecutive identical types compared to Vararg - if (x_same) - x_reps++; - else - x_reps = 1; - } + yi = vy ? jl_unwrap_vararg(yi) : yi; + int x_same = vx > 1 || (lastx && obviously_egal(xi, lastx)); + int y_same = vy > 1 || (lasty && obviously_egal(yi, lasty)); + // keep track of number of consecutive identical subtyping + x_reps = y_same && x_same ? x_reps + 1 : 1; if (x_reps > 2) { - // an identical type on the left doesn't need to be compared to a Vararg + // an identical type on the left doesn't need to be compared to the same // element type on the right more than twice. } else if (x_same && e->Runions.depth == 0 && - ((yi == lasty && !jl_has_free_typevars(xi) && !jl_has_free_typevars(yi)) || + ((y_same && !jl_has_free_typevars(xi) && !jl_has_free_typevars(yi)) || (yi == lastx && !vx && vy && jl_is_concrete_type(xi)))) { // fast path for repeated elements } - else if (e->Runions.depth == 0 && e->Lunions.depth == 0 && !jl_has_free_typevars(xi) && !jl_has_free_typevars(yi)) { + else if (e->Runions.depth == 0 && !jl_has_free_typevars(xi) && !jl_has_free_typevars(yi)) { // fast path for separable sub-formulas if (!jl_subtype(xi, yi)) return 0; @@ -1180,15 +1312,9 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // of unions and vars: if matching `typevar <: union`, first try to match the whole // union against the variable before trying to take it apart to see if there are any // variables lurking inside. - jl_unionstate_t *state = &e->Runions; - if (state->depth >= state->used) { - statestack_set(state, state->used, 0); - state->used++; - } - ui = statestack_get(state, state->depth); - state->depth++; - if (ui == 0) - state->more = state->depth; // memorize that this was the deepest available choice + // note: for forall var, there's no need to split y if it has no free typevars. + jl_varbinding_t *xx = lookup(e, (jl_tvar_t *)x); + ui = ((xx && xx->right) || jl_has_free_typevars(y)) && pick_union_decision(e, 1); } if (ui == 1) y = pick_union_element(y, e, 1); @@ -1228,10 +1354,20 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // to other left-side variables, so using || here is safe. return subtype(xub, y, e, param) || subtype(x, ylb, e, param); } - return var_lt((jl_tvar_t*)x, y, e, param); + if (jl_is_unionall(y)) { + jl_varbinding_t *xb = lookup(e, (jl_tvar_t*)x); + if (xb == NULL ? !e->ignore_free : !xb->right) { + // We'd better unwrap `y::UnionAll` eagerly if `x` isa ∀-var. + // This makes sure the following cases work correct: + // 1) `∀T <: Union{∃S, SomeType{P}} where {P}`: `S == Any` ==> `S >: T` + // 2) `∀T <: Union{∀T, SomeType{P}} where {P}`: + return subtype_unionall(x, (jl_unionall_t*)y, e, 1, param); + } + } + return subtype_var((jl_tvar_t*)x, y, e, 0, param); } if (jl_is_typevar(y)) - return var_gt((jl_tvar_t*)y, x, e, param); + return subtype_var((jl_tvar_t*)y, x, e, 1, param); if (y == (jl_value_t*)jl_any_type && !jl_has_free_typevars(x)) return 1; if (x == jl_bottom_type && !jl_has_free_typevars(y)) @@ -1258,7 +1394,6 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) } if (jl_is_unionall(y)) return subtype_unionall(x, (jl_unionall_t*)y, e, 1, param); - assert(!jl_is_vararg(x) && !jl_is_vararg(y)); if (jl_is_datatype(x) && jl_is_datatype(y)) { if (x == y) return 1; if (y == (jl_value_t*)jl_any_type) return 1; @@ -1283,15 +1418,14 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) // The answer is true iff `T` has full bounds (as in `Type`), but this needs to // be checked at the same depth where `Type{T}` occurs --- the depth of the LHS // doesn't matter because it (e.g. `DataType`) doesn't actually contain the variable. - int saved = e->invdepth; - e->invdepth = e->Rinvdepth; int issub = subtype((jl_value_t*)jl_type_type, y, e, param); - e->invdepth = saved; return issub; } while (xd != jl_any_type && xd->name != yd->name) { - if (xd->super == NULL) + if (xd->super == NULL) { + assert(xd->parameters && jl_is_typename(xd->name)); jl_errorf("circular type parameter constraint in definition of %s", jl_symbol_name(xd->name->name)); + } xd = xd->super; } if (xd == jl_any_type) return 0; @@ -1300,7 +1434,6 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) size_t i, np = jl_nparams(xd); int ans = 1; e->invdepth++; - e->Rinvdepth++; for (i=0; i < np; i++) { jl_value_t *xi = jl_tparam(xd, i), *yi = jl_tparam(yd, i); if (!(xi == yi || forall_exists_equal(xi, yi, e))) { @@ -1308,11 +1441,12 @@ static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) } } e->invdepth--; - e->Rinvdepth--; return ans; } if (jl_is_type(y)) return x == jl_bottom_type; + if (jl_is_long(x) && jl_is_long(y)) + return jl_unbox_long(x) == jl_unbox_long(y) + e->Loffset; return jl_egal(x, y); } @@ -1339,54 +1473,123 @@ static int is_definite_length_tuple_type(jl_value_t *x) return k == JL_VARARG_NONE || k == JL_VARARG_INT; } -static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) -{ - if (obviously_egal(x, y)) return 1; +static int _forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param, int *count, int *noRmore); - if ((is_indefinite_length_tuple_type(x) && is_definite_length_tuple_type(y)) || - (is_definite_length_tuple_type(x) && is_indefinite_length_tuple_type(y))) +static int may_contain_union_decision(jl_value_t *x, jl_stenv_t *e, jl_typeenv_t *log) JL_NOTSAFEPOINT +{ + if (x == NULL || x == (jl_value_t*)jl_any_type || x == jl_bottom_type) + return 0; + if (jl_is_unionall(x)) + return may_contain_union_decision(((jl_unionall_t *)x)->body, e, log); + if (jl_is_datatype(x)) { + jl_datatype_t *xd = (jl_datatype_t *)x; + for (int i = 0; i < jl_nparams(xd); i++) { + jl_value_t *param = jl_tparam(xd, i); + if (jl_is_vararg(param)) + param = jl_unwrap_vararg(param); + if (may_contain_union_decision(param, e, log)) + return 1; + } return 0; + } + if (!jl_is_typevar(x)) + return jl_is_type(x); + jl_typeenv_t *t = log; + while (t != NULL) { + if (x == (jl_value_t *)t->var) + return 1; + t = t->prev; + } + jl_typeenv_t newlog = { (jl_tvar_t*)x, NULL, log }; + jl_varbinding_t *xb = lookup(e, (jl_tvar_t *)x); + return may_contain_union_decision(xb ? xb->lb : ((jl_tvar_t *)x)->lb, e, &newlog) || + may_contain_union_decision(xb ? xb->ub : ((jl_tvar_t *)x)->ub, e, &newlog); +} - jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); - e->Lunions.used = 0; +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; int sub; - - if (!jl_has_free_typevars(x) || !jl_has_free_typevars(y)) { + int kindx = !jl_has_free_typevars(x); + int kindy = !jl_has_free_typevars(y); + if (kindx && kindy) + return jl_subtype(x, y); + if (may_contain_union_decision(y, e, NULL) && pick_union_decision(e, 1) == 0) { jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); - e->Runions.used = 0; - e->Runions.depth = 0; - e->Runions.more = 0; - e->Lunions.depth = 0; - e->Lunions.more = 0; - - sub = forall_exists_subtype(x, y, e, 2); - + e->Lunions.used = e->Runions.used = 0; + e->Lunions.depth = e->Runions.depth = 0; + e->Lunions.more = e->Runions.more = 0; + 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 + // 2) the left `Union` looks big + if (limit_slow == -1) + limit_slow = kindx || kindy; + if (noRmore || (limit_slow && (count > 3 || !sub))) + e->Runions.more = oldRmore; } else { - int lastset = 0; + // slow path + e->Lunions.used = 0; while (1) { e->Lunions.more = 0; e->Lunions.depth = 0; - sub = subtype(x, y, e, 2); - int set = e->Lunions.more; - if (!sub || !set) + sub = subtype(x, y, e, param); + if (!sub || !next_union_state(e, 0)) break; - for (int i = set; i <= lastset; i++) - statestack_set(&e->Lunions, i, 0); - lastset = set - 1; - statestack_set(&e->Lunions, lastset, 1); } } + return sub; +} + +static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) +{ + if (obviously_egal(x, y)) return 1; + + if ((is_indefinite_length_tuple_type(x) && is_definite_length_tuple_type(y)) || + (is_definite_length_tuple_type(x) && is_indefinite_length_tuple_type(y))) + return 0; + + if (jl_is_datatype(x) && jl_is_datatype(y)) { + // Fastpath for nested constructor. Skip the unneeded `>:` check. + // Note: since there is no changes to the environment or union stack implied by `x` or `y`, this will simply forward to calling + // `forall_exists_equal(xi, yi, e)` on each parameter `(xi, yi)` of `(x, y)`, + // which means this subtype call will give the same result for `subtype(x, y)` and `subtype(y, x)`. + jl_datatype_t *xd = (jl_datatype_t*)x, *yd = (jl_datatype_t*)y; + if (xd->name != yd->name) + return 0; + if (xd->name != jl_tuple_typename) + return subtype(x, y, e, 2); + } + + if ((jl_is_uniontype(x) && jl_is_uniontype(y))) { + // For 2 unions, first try a more efficient greedy algorithm that compares the unions + // componentwise. If failed, `exists_subtype` would memorize that this branch should be skipped. + // Note: this is valid because the normal path checks `>:` locally. + if (pick_union_decision(e, 1) == 0) { + return forall_exists_equal(((jl_uniontype_t *)x)->a, ((jl_uniontype_t *)y)->a, e) && + forall_exists_equal(((jl_uniontype_t *)x)->b, ((jl_uniontype_t *)y)->b, e); + } + } + + jl_saved_unionstate_t oldLunions; push_unionstate(&oldLunions, &e->Lunions); + int sub = local_forall_exists_subtype(x, y, e, 2, -1); + if (sub) { + flip_offset(e); + sub = local_forall_exists_subtype(y, x, e, 0, 0); + flip_offset(e); + } pop_unionstate(&e->Lunions, &oldLunions); - return sub && subtype(y, x, e, 0); + return sub; } -static int exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, jl_value_t *saved, jl_savedenv_t *se, int param) +static int exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, jl_savedenv_t *se, int param) { e->Runions.used = 0; - int lastset = 0; while (1) { e->Runions.depth = 0; e->Runions.more = 0; @@ -1394,63 +1597,68 @@ static int exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, jl_value_ e->Lunions.more = 0; if (subtype(x, y, e, param)) return 1; - restore_env(e, saved, se); - int set = e->Runions.more; - if (!set) + if (next_union_state(e, 1)) { + // We preserve `envout` here as `subtype_unionall` needs previous assigned env values. + int oldidx = e->envidx; + e->envidx = e->envsz; + restore_env(e, se, 1); + e->envidx = oldidx; + } + else { + restore_env(e, se, 1); return 0; - for (int i = set; i <= lastset; i++) - statestack_set(&e->Runions, i, 0); - lastset = set - 1; - statestack_set(&e->Runions, lastset, 1); + } } } -static int forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) +static int _forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param, int *count, int *noRmore) { // The depth recursion has the following shape, after simplification: // ∀₁ // ∃₁ assert(e->Runions.depth == 0); assert(e->Lunions.depth == 0); - jl_value_t *saved=NULL; jl_savedenv_t se; - JL_GC_PUSH1(&saved); - save_env(e, &saved, &se); + jl_savedenv_t se; + save_env(e, &se, 1); e->Lunions.used = 0; - int lastset = 0; int sub; + if (count) *count = 0; + if (noRmore) *noRmore = 1; while (1) { - sub = exists_subtype(x, y, e, saved, &se, param); - int set = e->Lunions.more; - if (!sub || !set) + sub = exists_subtype(x, y, e, &se, param); + if (count) *count = (*count < 4) ? *count + 1 : 4; + if (noRmore) *noRmore = *noRmore && e->Runions.more == 0; + if (!sub || !next_union_state(e, 0)) break; - free_env(&se); - save_env(e, &saved, &se); - for (int i = set; i <= lastset; i++) - statestack_set(&e->Lunions, i, 0); - lastset = set - 1; - statestack_set(&e->Lunions, lastset, 1); + re_save_env(e, &se, 1); } free_env(&se); - JL_GC_POP(); return sub; } +static int forall_exists_subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) +{ + return _forall_exists_subtype(x, y, e, param, NULL, NULL); +} + static void init_stenv(jl_stenv_t *e, jl_value_t **env, int envsz) { e->vars = NULL; - assert(env != NULL || envsz == 0); e->envsz = envsz; e->envout = env; - if (envsz) + if (envsz) { + assert(env != NULL); memset(env, 0, envsz*sizeof(void*)); + } e->envidx = 0; - e->invdepth = e->Rinvdepth = 0; + e->invdepth = 0; e->ignore_free = 0; e->intersection = 0; e->emptiness_only = 0; e->triangular = 0; + e->Loffset = 0; e->Lunions.depth = 0; e->Runions.depth = 0; e->Lunions.more = 0; e->Runions.more = 0; e->Lunions.used = 0; e->Runions.used = 0; @@ -1543,8 +1751,15 @@ static int obvious_subtype(jl_value_t *x, jl_value_t *y, jl_value_t *y0, int *su *subtype = 1; return 1; } - if (jl_is_unionall(x)) - x = jl_unwrap_unionall(x); + while (jl_is_unionall(x)) { + if (!jl_is_unionall(y)) { + if (obvious_subtype(jl_unwrap_unionall(x), y, y0, subtype) && !*subtype) + return 1; + return 0; + } + x = ((jl_unionall_t*)x)->body; + y = ((jl_unionall_t*)y)->body; + } if (jl_is_unionall(y)) y = jl_unwrap_unionall(y); if (x == (jl_value_t*)jl_typeofbottom_type->super) @@ -1626,7 +1841,7 @@ static int obvious_subtype(jl_value_t *x, jl_value_t *y, jl_value_t *y0, int *su if (jl_is_datatype(y)) { int istuple = (((jl_datatype_t*)y)->name == jl_tuple_typename); int iscov = istuple; - // TODO: this would be a nice fast-path to have, unfortuanately, + // TODO: this would be a nice fast-path to have, unfortunately, // datatype allocation fails to correctly hash-cons them // and the subtyping tests include tests for this case //if (!iscov && ((jl_datatype_t*)y)->isconcretetype && !jl_is_type_type(x)) { @@ -1881,34 +2096,34 @@ JL_DLLEXPORT int jl_subtype_env(jl_value_t *x, jl_value_t *y, jl_value_t **env, if (obvious_subtype == 0 || (obvious_subtype == 1 && envsz == 0)) subtype = obvious_subtype; // this ensures that running in a debugger doesn't change the result #endif + if (env) { + jl_unionall_t *ub = (jl_unionall_t*)y; + int i; + for (i = 0; i < envsz; i++) { + assert(jl_is_unionall(ub)); + jl_tvar_t *var = ub->var; + env[i] = fix_inferred_var_bound(var, env[i]); + ub = (jl_unionall_t*)ub->body; + } + } return subtype; } -static int subtype_in_env_(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int invdepth, int Rinvdepth) +static int subtype_in_env(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { jl_stenv_t e2; init_stenv(&e2, NULL, 0); e2.vars = e->vars; e2.intersection = e->intersection; e2.ignore_free = e->ignore_free; - e2.invdepth = invdepth; - e2.Rinvdepth = Rinvdepth; + e2.invdepth = e->invdepth; e2.envsz = e->envsz; e2.envout = e->envout; e2.envidx = e->envidx; + e2.Loffset = e->Loffset; return forall_exists_subtype(x, y, &e2, 0); } -static int subtype_in_env(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) -{ - return subtype_in_env_(x, y, e, e->invdepth, e->Rinvdepth); -} - -static int subtype_bounds_in_env(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int R, int d) -{ - return subtype_in_env_(x, y, e, R ? e->invdepth : d, R ? d : e->Rinvdepth); -} - JL_DLLEXPORT int jl_subtype(jl_value_t *x, jl_value_t *y) { return jl_subtype_env(x, y, NULL, 0); @@ -2042,23 +2257,39 @@ int jl_has_intersect_type_not_kind(jl_value_t *t) t = jl_unwrap_unionall(t); if (t == (jl_value_t*)jl_any_type) return 1; - if (jl_is_uniontype(t)) { + assert(!jl_is_vararg(t)); + if (jl_is_uniontype(t)) return jl_has_intersect_type_not_kind(((jl_uniontype_t*)t)->a) || jl_has_intersect_type_not_kind(((jl_uniontype_t*)t)->b); - } - if (jl_is_typevar(t)) { + if (jl_is_typevar(t)) return jl_has_intersect_type_not_kind(((jl_tvar_t*)t)->ub); - } - if (jl_is_datatype(t)) { + if (jl_is_datatype(t)) if (((jl_datatype_t*)t)->name == jl_type_typename) return 1; - } return 0; } +// compute if DataType<:t || Union<:t || UnionAll<:t etc. +int jl_has_intersect_kind_not_type(jl_value_t *t) +{ + t = jl_unwrap_unionall(t); + if (t == (jl_value_t*)jl_any_type || jl_is_kind(t)) + return 1; + assert(!jl_is_vararg(t)); + if (jl_is_uniontype(t)) + return jl_has_intersect_kind_not_type(((jl_uniontype_t*)t)->a) || + jl_has_intersect_kind_not_type(((jl_uniontype_t*)t)->b); + if (jl_is_typevar(t)) + return jl_has_intersect_kind_not_type(((jl_tvar_t*)t)->ub); + return 0; +} + + JL_DLLEXPORT int jl_isa(jl_value_t *x, jl_value_t *t) { - if (jl_typeis(x,t) || t == (jl_value_t*)jl_any_type) + if (t == (jl_value_t*)jl_any_type || jl_typetagis(x,t)) + return 1; + if (jl_typetagof(x) < (jl_max_tags << 4) && jl_is_datatype(t) && jl_typetagis(x,((jl_datatype_t*)t)->smalltag << 4)) return 1; if (jl_is_type(x)) { if (t == (jl_value_t*)jl_type_type) @@ -2103,7 +2334,7 @@ JL_DLLEXPORT int jl_isa(jl_value_t *x, jl_value_t *t) return 0; } } - if (jl_is_concrete_type(t) && jl_type_equality_is_identity(jl_typeof(x), t)) + if (jl_is_concrete_type(t)) return 0; return jl_subtype(jl_typeof(x), t); } @@ -2115,24 +2346,23 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e); // intersect in nested union environment, similar to subtype_ccheck -static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int R, int d) +static jl_value_t *intersect_aside(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int depth) { // band-aid for #30335 if (x == (jl_value_t*)jl_any_type && !jl_is_typevar(y)) return y; if (y == (jl_value_t*)jl_any_type && !jl_is_typevar(x)) return x; + // band-aid for #46736 + if (obviously_egal(x, y)) + return x; jl_saved_unionstate_t oldRunions; push_unionstate(&oldRunions, &e->Runions); - int savedepth = e->invdepth, Rsavedepth = e->Rinvdepth; - // TODO: this doesn't quite make sense - e->invdepth = e->Rinvdepth = d; - + int savedepth = e->invdepth; + e->invdepth = depth; jl_value_t *res = intersect_all(x, y, e); - - pop_unionstate(&e->Runions, &oldRunions); e->invdepth = savedepth; - e->Rinvdepth = Rsavedepth; + pop_unionstate(&e->Runions, &oldRunions); return res; } @@ -2155,20 +2385,28 @@ static jl_value_t *intersect_union(jl_value_t *x, jl_uniontype_t *u, jl_stenv_t } // set a variable to a non-type constant -static jl_value_t *set_var_to_const(jl_varbinding_t *bb, jl_value_t *v JL_MAYBE_UNROOTED, jl_varbinding_t *othervar) +static jl_value_t *set_var_to_const(jl_varbinding_t *bb, jl_value_t *v JL_MAYBE_UNROOTED, jl_stenv_t *e, int R) { - int offset = bb->offset; - if (othervar && offset == 0) - offset = -othervar->offset; - assert(!othervar || othervar->offset == -offset); + int offset = R ? -e->Loffset : e->Loffset; if (bb->lb == jl_bottom_type && bb->ub == (jl_value_t*)jl_any_type) { - if (jl_is_long(v)) - v = jl_box_long(jl_unbox_long(v) + offset); - bb->lb = bb->ub = v; + if (offset == 0) + bb->lb = bb->ub = v; + else if (jl_is_long(v)) { + size_t iv = jl_unbox_long(v); + v = jl_box_long(iv + offset); + bb->lb = bb->ub = v; + // Here we always return the shorter `Vararg`'s length. + if (offset > 0) + return jl_box_long(iv); + } + else + return jl_bottom_type; } else if (jl_is_long(v) && jl_is_long(bb->lb)) { - if (jl_unbox_long(v) != jl_unbox_long(bb->lb)) + if (jl_unbox_long(v) + offset != jl_unbox_long(bb->lb)) return jl_bottom_type; + // Here we always return the shorter `Vararg`'s length. + if (offset < 0) return bb->lb; } else if (!jl_egal(v, bb->lb)) { return jl_bottom_type; @@ -2176,38 +2414,77 @@ static jl_value_t *set_var_to_const(jl_varbinding_t *bb, jl_value_t *v JL_MAYBE_ return v; } -static jl_value_t *bound_var_below(jl_tvar_t *tv, jl_varbinding_t *bb, jl_stenv_t *e) { +static jl_value_t *bound_var_below(jl_tvar_t *tv, jl_varbinding_t *bb, jl_stenv_t *e, int R) { if (!bb) return (jl_value_t*)tv; if (bb->depth0 != e->invdepth) return jl_bottom_type; + e->invdepth++; record_var_occurrence(bb, e, 2); + e->invdepth--; + int offset = R ? -e->Loffset : e->Loffset; if (jl_is_long(bb->lb)) { - if (bb->offset == 0) - return bb->lb; - if (jl_unbox_long(bb->lb) < bb->offset) + ssize_t blb = jl_unbox_long(bb->lb); + if (blb < offset || blb < 0) return jl_bottom_type; - return jl_box_long(jl_unbox_long(bb->lb) - bb->offset); + // Here we always return the shorter `Vararg`'s length. + if (offset <= 0) + return bb->lb; + return jl_box_long(blb - offset); + } + if (offset > 0) { + if (bb->innervars == NULL) + bb->innervars = jl_alloc_array_1d(jl_array_any_type, 0); + jl_value_t *ntv = NULL; + JL_GC_PUSH1(&ntv); + ntv = (jl_value_t *)jl_new_typevar(tv->name, jl_bottom_type, (jl_value_t *)jl_any_type); + jl_array_ptr_1d_push(bb->innervars, ntv); + JL_GC_POP(); + return ntv; } return (jl_value_t*)tv; } -static int try_subtype_in_env(jl_value_t *a, jl_value_t *b, jl_stenv_t *e, int R, int d) -{ - jl_value_t *root=NULL; jl_savedenv_t se; - JL_GC_PUSH1(&root); - save_env(e, &root, &se); - int ret = subtype_bounds_in_env(a, b, e, R, d); - restore_env(e, root, &se); - free_env(&se); - JL_GC_POP(); - return ret; -} +static int subtype_by_bounds(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) JL_NOTSAFEPOINT; -static void set_bound(jl_value_t **bound, jl_value_t *val, jl_tvar_t *v, jl_stenv_t *e) JL_NOTSAFEPOINT +// similar to `subtype_by_bounds`, used to avoid stack-overflow caused by circulation constraints. +static int try_subtype_by_bounds(jl_value_t *a, jl_value_t *b, jl_stenv_t *e) { - if (in_union(val, (jl_value_t*)v)) - return; + if (jl_is_uniontype(a)) + return try_subtype_by_bounds(((jl_uniontype_t *)a)->a, b, e) && + try_subtype_by_bounds(((jl_uniontype_t *)a)->b, b, e); + else if (jl_is_uniontype(b)) + return try_subtype_by_bounds(a, ((jl_uniontype_t *)b)->a, e) || + try_subtype_by_bounds(a, ((jl_uniontype_t *)b)->b, e); + else if (jl_egal(a, b)) + return 1; + else if (!jl_is_typevar(b)) + return 0; + jl_varbinding_t *vb = e->vars; + while (vb != NULL) { + if (subtype_by_bounds(b, (jl_value_t *)vb->var, e) && obviously_in_union(a, vb->ub)) + return 1; + vb = vb->prev; + } + return 0; +} + +static int try_subtype_in_env(jl_value_t *a, jl_value_t *b, jl_stenv_t *e) +{ + if (a == jl_bottom_type || b == (jl_value_t *)jl_any_type || try_subtype_by_bounds(a, b, e)) + return 1; + jl_savedenv_t se; + save_env(e, &se, 1); + int ret = subtype_in_env(a, b, e); + restore_env(e, &se, 1); + free_env(&se); + return ret; +} + +static void set_bound(jl_value_t **bound, jl_value_t *val, jl_tvar_t *v, jl_stenv_t *e) JL_NOTSAFEPOINT +{ + if (in_union(val, (jl_value_t*)v)) + return; jl_varbinding_t *btemp = e->vars; while (btemp != NULL) { if ((btemp->lb == (jl_value_t*)v || btemp->ub == (jl_value_t*)v) && @@ -2219,7 +2496,7 @@ static void set_bound(jl_value_t **bound, jl_value_t *val, jl_tvar_t *v, jl_sten } // subtype, treating all vars as existential -static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int R, int d) +static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { jl_varbinding_t *v = e->vars; int len = 0; @@ -2238,7 +2515,7 @@ static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t * v->right = 1; v = v->prev; } - int issub = subtype_bounds_in_env(x, y, e, R, d); + int issub = subtype_in_env(x, y, e); n = 0; v = e->vars; while (n < len) { assert(v != NULL); @@ -2250,16 +2527,31 @@ static int subtype_in_env_existential(jl_value_t *x, jl_value_t *y, jl_stenv_t * } // See if var y is reachable from x via bounds; used to avoid cycles. -static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) +static int _reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e, jl_typeenv_t *log) { if (in_union(x, (jl_value_t*)y)) return 1; + if (jl_is_uniontype(x)) + return _reachable_var(((jl_uniontype_t *)x)->a, y, e, log) || + _reachable_var(((jl_uniontype_t *)x)->b, y, e, log); if (!jl_is_typevar(x)) return 0; + jl_typeenv_t *t = log; + while (t != NULL) { + if (x == (jl_value_t *)t->var) + return 0; + t = t->prev; + } jl_varbinding_t *xv = lookup(e, (jl_tvar_t*)x); - if (xv == NULL) - return 0; - return reachable_var(xv->ub, y, e) || reachable_var(xv->lb, y, e); + jl_value_t *lb = xv == NULL ? ((jl_tvar_t*)x)->lb : xv->lb; + jl_value_t *ub = xv == NULL ? ((jl_tvar_t*)x)->ub : xv->ub; + jl_typeenv_t newlog = { (jl_tvar_t*)x, NULL, log }; + return _reachable_var(ub, y, e, &newlog) || _reachable_var(lb, y, e, &newlog); +} + +static int reachable_var(jl_value_t *x, jl_tvar_t *y, jl_stenv_t *e) +{ + return _reachable_var(x, y, e, NULL); } // check whether setting v == t implies v == SomeType{v}, which is unsatisfiable. @@ -2277,27 +2569,30 @@ static int check_unsat_bound(jl_value_t *t, jl_tvar_t *v, jl_stenv_t *e) JL_NOTS return 0; } + static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int8_t R, int param) { jl_varbinding_t *bb = lookup(e, b); if (bb == NULL) - return R ? intersect_aside(a, b->ub, e, 1, 0) : intersect_aside(b->ub, a, e, 0, 0); + return R ? intersect_aside(a, b->ub, e, 0) : intersect_aside(b->ub, a, e, 0); if (reachable_var(bb->lb, b, e) || reachable_var(bb->ub, b, e)) return a; - if (bb->lb == bb->ub && jl_is_typevar(bb->lb)) { - return intersect(a, bb->lb, e, param); - } + if (bb->lb == bb->ub && jl_is_typevar(bb->lb)) + return R ? intersect(a, bb->lb, e, param) : intersect(bb->lb, a, e, param); if (!jl_is_type(a) && !jl_is_typevar(a)) - return set_var_to_const(bb, a, NULL); - int d = bb->depth0; - jl_value_t *root=NULL; jl_savedenv_t se; + return set_var_to_const(bb, a, e, R); + jl_savedenv_t se; if (param == 2) { jl_value_t *ub = NULL; - JL_GC_PUSH2(&ub, &root); + JL_GC_PUSH1(&ub); if (!jl_has_free_typevars(a)) { - save_env(e, &root, &se); - int issub = subtype_in_env_existential(bb->lb, a, e, 0, d) && subtype_in_env_existential(a, bb->ub, e, 1, d); - restore_env(e, root, &se); + save_env(e, &se, 1); + int issub = subtype_in_env_existential(bb->lb, a, e); + restore_env(e, &se, 1); + if (issub) { + issub = subtype_in_env_existential(a, bb->ub, e); + restore_env(e, &se, 1); + } free_env(&se); if (!issub) { JL_GC_POP(); @@ -2307,11 +2602,11 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int } else { e->triangular++; - ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d); + ub = R ? intersect_aside(a, bb->ub, e, bb->depth0) : intersect_aside(bb->ub, a, e, bb->depth0); e->triangular--; - save_env(e, &root, &se); - int issub = subtype_in_env_existential(bb->lb, ub, e, 0, d); - restore_env(e, root, &se); + save_env(e, &se, 1); + int issub = subtype_in_env_existential(bb->lb, ub, e); + restore_env(e, &se, 1); free_env(&se); if (!issub) { JL_GC_POP(); @@ -2326,12 +2621,16 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int } } bb->ub = ub; - bb->lb = ub; + if ((jl_is_uniontype(ub) && !jl_is_uniontype(a)) || + (jl_is_unionall(ub) && !jl_is_unionall(a))) + ub = (jl_value_t*)b; + else + bb->lb = ub; } JL_GC_POP(); return ub; } - jl_value_t *ub = R ? intersect_aside(a, bb->ub, e, 1, d) : intersect_aside(bb->ub, a, e, 0, d); + jl_value_t *ub = R ? intersect_aside(a, bb->ub, e, bb->depth0) : intersect_aside(bb->ub, a, e, bb->depth0); if (ub == jl_bottom_type) return jl_bottom_type; if (bb->constraintkind == 1 || e->triangular) { @@ -2342,7 +2641,7 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int } else if (bb->constraintkind == 0) { JL_GC_PUSH1(&ub); - if (!jl_is_typevar(a) && try_subtype_in_env(bb->ub, a, e, 0, d)) { + if (!jl_is_typevar(a) && try_subtype_in_env(bb->ub, a, e)) { JL_GC_POP(); return (jl_value_t*)b; } @@ -2350,14 +2649,24 @@ static jl_value_t *intersect_var(jl_tvar_t *b, jl_value_t *a, jl_stenv_t *e, int return ub; } assert(bb->constraintkind == 2); - if (!jl_is_typevar(a)) { - if (ub == a && bb->lb != jl_bottom_type) - return ub; - else if (jl_egal(bb->ub, bb->lb)) - return ub; - set_bound(&bb->ub, ub, b, e); - } - return (jl_value_t*)b; + if (ub == a && bb->lb != jl_bottom_type) + return ub; + if (jl_egal(bb->ub, bb->lb)) + return ub; + if (is_leaf_bound(ub)) + set_bound(&bb->lb, ub, b, e); + // TODO: can we improve this bound by pushing a new variable into the environment + // and adding that to the lower bound of our variable? + //jl_value_t *ntv = NULL; + //JL_GC_PUSH2(&ntv, &ub); + //if (bb->innervars == NULL) + // bb->innervars = jl_alloc_array_1d(jl_array_any_type, 0); + //ntv = (jl_value_t*)jl_new_typevar(b->name, bb->lb, ub); + //jl_array_ptr_1d_push(bb->innervars, ntv); + //jl_value_t *lb = simple_join(b->lb, ntv); + //JL_GC_POP(); + //bb->lb = lb; + return ub; } // test whether `var` occurs inside constructors. `want_inv` tests only inside @@ -2400,6 +2709,61 @@ static int var_occurs_inside(jl_value_t *v, jl_tvar_t *var, int inside, int want return 0; } +static jl_value_t *omit_bad_union(jl_value_t *u, jl_tvar_t *t) +{ + if (!jl_has_typevar(u, t)) + return u; // return u if possible as many checks use `==`. + jl_value_t *res = NULL; + if (jl_is_unionall(u)) { + jl_tvar_t *var = ((jl_unionall_t *)u)->var; + jl_value_t *ub = var->ub, *body = ((jl_unionall_t *)u)->body; + assert(var != t); + if (!jl_has_typevar(var->lb, t)) { + JL_GC_PUSH3(&ub, &body, &var); + body = omit_bad_union(body, t); + if (!jl_has_typevar(body, var)) { + res = body; + } + else { + ub = omit_bad_union(ub, t); + if (ub == jl_bottom_type && var->lb != ub) { + res = jl_bottom_type; + } + else if (obviously_egal(var->lb, ub)) { + JL_TRY { + res = jl_substitute_var(body, var, ub); + } + JL_CATCH { + res = jl_bottom_type; + } + } + else { + if (ub != var->ub) { + var = jl_new_typevar(var->name, var->lb, ub); + body = jl_substitute_var(body, ((jl_unionall_t *)u)->var, (jl_value_t *)var); + } + res = jl_new_struct(jl_unionall_type, var, body); + } + } + } + JL_GC_POP(); + } + else if (jl_is_uniontype(u)) { + jl_value_t *a = ((jl_uniontype_t *)u)->a; + jl_value_t *b = ((jl_uniontype_t *)u)->b; + JL_GC_PUSH2(&a, &b); + a = omit_bad_union(a, t); + b = omit_bad_union(b, t); + res = simple_join(a, b); + JL_GC_POP(); + } + else { + res = jl_bottom_type; + } + assert(res != NULL); + return res; +} + // Caller might not have rooted `res` 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) { @@ -2414,10 +2778,9 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind // given x<:T<:x, substitute x for T varval = vb->ub; } - // TODO: `vb.occurs_cov == 1` here allows substituting Tuple{<:X} => Tuple{X}, - // which is valid but changes some ambiguity errors so we don't need to do it yet. - else if ((/*vb->occurs_cov == 1 || */is_leaf_bound(vb->ub)) && - !var_occurs_invariant(u->body, u->var, 0)) { + // TODO: `vb.occurs_cov == 1`, we could also substitute Tuple{<:X} => Tuple{X}, + // but it may change some ambiguity errors so we don't need to do it yet. + else if (vb->occurs_cov && is_leaf_bound(vb->ub) && !jl_has_free_typevars(vb->ub)) { // replace T<:x with x in covariant position when possible varval = vb->ub; } @@ -2440,9 +2803,8 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind newvar = jl_new_typevar(vb->var->name, vb->lb, vb->ub); // remove/replace/rewrap free occurrences of this var in the environment - jl_varbinding_t *btemp = e->vars; - int wrap = 1; - while (btemp != NULL) { + jl_varbinding_t *wrap = NULL; + for (jl_varbinding_t *btemp = e->vars; btemp != NULL; btemp = btemp->prev) { if (jl_has_typevar(btemp->lb, vb->var)) { if (vb->lb == (jl_value_t*)btemp->var) { JL_GC_POP(); @@ -2456,31 +2818,28 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind res = jl_bottom_type; } } - else if (btemp->lb == (jl_value_t*)vb->var) + else if (btemp->lb == (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) && jl_has_typevar(btemp->ub, vb->var)) { + } + else if (btemp->depth0 == vb->depth0 && !jl_has_typevar(vb->lb, btemp->var) && !jl_has_typevar(vb->ub, btemp->var)) { // 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 (btemp->innervars == NULL) - btemp->innervars = jl_alloc_array_1d(jl_array_any_type, 0); - if (newvar != vb->var) { + if (newvar != vb->var) btemp->lb = jl_substitute_var(btemp->lb, vb->var, (jl_value_t*)newvar); - btemp->ub = jl_substitute_var(btemp->ub, vb->var, (jl_value_t*)newvar); - } - jl_array_ptr_1d_push(btemp->innervars, (jl_value_t*)newvar); - wrap = 0; - btemp = btemp->prev; - continue; + wrap = btemp; } - else + else { btemp->lb = jl_new_struct(jl_unionall_type, vb->var, btemp->lb); + } assert((jl_value_t*)btemp->var != btemp->lb); } if (jl_has_typevar(btemp->ub, vb->var)) { if (vb->ub == (jl_value_t*)btemp->var) { - JL_GC_POP(); - return jl_bottom_type; + btemp->ub = omit_bad_union(btemp->ub, vb->var); + if (btemp->ub == jl_bottom_type && btemp->ub != btemp->lb) { + JL_GC_POP(); + return jl_bottom_type; + } } if (varval) { JL_TRY { @@ -2490,13 +2849,31 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind res = jl_bottom_type; } } - else if (btemp->ub == (jl_value_t*)vb->var) + else if (btemp->ub == (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; + } + else if (btemp->depth0 == vb->depth0 && !jl_has_typevar(vb->lb, btemp->var) && !jl_has_typevar(vb->ub, btemp->var)) { + if (newvar != vb->var) + btemp->ub = jl_substitute_var(btemp->ub, vb->var, (jl_value_t*)newvar); + wrap = btemp; + } else btemp->ub = jl_new_struct(jl_unionall_type, vb->var, btemp->ub); assert((jl_value_t*)btemp->var != btemp->ub); } - btemp = btemp->prev; + } + + 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? } // if `v` still occurs, re-wrap body in `UnionAll v` or eliminate the UnionAll @@ -2519,17 +2896,32 @@ 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 (!wrap) res = jl_type_unionall((jl_tvar_t*)newvar, res); } } - if (res != jl_bottom_type && vb->innervars != NULL) { - int i; - for(i=0; i < jl_array_len(vb->innervars); i++) { + if (vb->innervars != NULL) { + for (size_t i = 0; i < jl_array_len(vb->innervars); i++) { jl_tvar_t *var = (jl_tvar_t*)jl_array_ptr_ref(vb->innervars, i); - if (jl_has_typevar(res, var)) - res = jl_type_unionall((jl_tvar_t*)var, res); + // 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); + } } } @@ -2538,7 +2930,7 @@ static jl_value_t *finish_unionall(jl_value_t *res JL_MAYBE_UNROOTED, jl_varbind if (!varval || (!is_leaf_bound(varval) && !vb->occurs_inv)) e->envout[e->envidx] = (jl_value_t*)vb->var; else if (!(oldval && jl_is_typevar(oldval) && jl_is_long(varval))) - e->envout[e->envidx] = fix_inferred_var_bound(vb->var, varval); + e->envout[e->envidx] = varval; } JL_GC_POP(); @@ -2560,7 +2952,7 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv } if (btemp->var == u->var || btemp->lb == (jl_value_t*)u->var || btemp->ub == (jl_value_t*)u->var) { - u = rename_unionall(u); + u = jl_rename_unionall(u); break; } btemp = btemp->prev; @@ -2600,10 +2992,17 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv // T=Bottom in covariant position res = jl_bottom_type; } - else if (jl_has_typevar(vb->lb, u->var) || jl_has_typevar(vb->ub, u->var)) { + else if (jl_has_typevar(vb->lb, u->var)) { // fail on circular constraints res = jl_bottom_type; } + else { + JL_GC_PUSH1(&res); + vb->ub = omit_bad_union(vb->ub, u->var); + JL_GC_POP(); + if (vb->ub == jl_bottom_type && vb->ub != vb->lb) + res = jl_bottom_type; + } } if (res != jl_bottom_type) // res is rooted by callee @@ -2614,12 +3013,14 @@ static jl_value_t *intersect_unionall_(jl_value_t *t, jl_unionall_t *u, jl_stenv static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_t *e, int8_t R, int param) { - jl_value_t *res=NULL, *save=NULL; + 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, - R ? e->Rinvdepth : e->invdepth, 0, NULL, e->vars }; - JL_GC_PUSH5(&res, &vb.lb, &vb.ub, &save, &vb.innervars); - save_env(e, &save, &se); + jl_varbinding_t vb = { u->var, u->var->lb, u->var->ub, R, 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); + if (is_leaf_typevar(u->var) && !var_occurs_invariant(u->body, u->var, 0)) + vb.constraintkind = 1; res = intersect_unionall_(t, u, e, R, param, &vb); if (vb.limited) { // if the environment got too big, avoid tree recursion and propagate the flag @@ -2627,16 +3028,21 @@ static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_ e->vars->limited = 1; } else if (res != jl_bottom_type) { - if (vb.concrete || vb.occurs_inv>1 || u->var->lb != jl_bottom_type || (vb.occurs_inv && vb.occurs_cov)) { - restore_env(e, NULL, &se); - vb.occurs_cov = vb.occurs_inv = 0; + int constraint1 = vb.constraintkind; + if (vb.concrete || vb.occurs_inv>1 || (vb.occurs_inv && vb.occurs_cov)) vb.constraintkind = vb.concrete ? 1 : 2; - res = intersect_unionall_(t, u, e, R, param, &vb); - } - else if (vb.occurs_cov && !var_occurs_invariant(u->body, u->var, 0)) { - restore_env(e, save, &se); - vb.occurs_cov = vb.occurs_inv = 0; + else if (u->var->lb != jl_bottom_type) + vb.constraintkind = 2; + else if (vb.occurs_cov && !var_occurs_invariant(u->body, u->var, 0)) vb.constraintkind = 1; + int reintersection = constraint1 != vb.constraintkind || vb.concrete; + if (reintersection) { + if (constraint1 == 1) { + vb.lb = vb.var->lb; + vb.ub = vb.var->ub; + } + restore_env(e, &se, vb.constraintkind == 1 ? 1 : 0); + vb.occurs = vb.occurs_cov = vb.occurs_inv = 0; res = intersect_unionall_(t, u, e, R, param, &vb); } } @@ -2645,6 +3051,8 @@ static jl_value_t *intersect_unionall(jl_value_t *t, jl_unionall_t *u, jl_stenv_ return res; } +static jl_value_t *intersect_invariant(jl_value_t *x, jl_value_t *y, jl_stenv_t *e); + // check n = (length of vararg type v) static int intersect_vararg_length(jl_value_t *v, ssize_t n, jl_stenv_t *e, int8_t R) { @@ -2653,16 +3061,15 @@ static int intersect_vararg_length(jl_value_t *v, ssize_t n, jl_stenv_t *e, int8 if (N && jl_is_typevar(N)) { jl_value_t *len = jl_box_long(n); JL_GC_PUSH1(&len); - jl_value_t *il = R ? intersect(len, N, e, 2) : intersect(N, len, e, 2); + jl_value_t *il = R ? intersect_invariant(len, N, e) : intersect_invariant(N, len, e); JL_GC_POP(); - if (il == jl_bottom_type) + if (il == NULL || il == jl_bottom_type) return 0; } return 1; } -static jl_value_t *intersect_invariant(jl_value_t *x, jl_value_t *y, jl_stenv_t *e); -static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, jl_stenv_t *e, int param) +static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, ssize_t offset, jl_stenv_t *e, int param) { // Vararg: covariant in first parameter, invariant in second jl_value_t *xp1=jl_unwrap_vararg(vmx), *xp2=jl_unwrap_vararg_num(vmx), @@ -2673,26 +3080,37 @@ static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, jl_sten if (intersect(xp1, yp1, e, param==0 ? 1 : param) == jl_bottom_type) return jl_bottom_type; jl_value_t *i2=NULL, *ii = intersect(xp1, yp1, e, 1); - if (ii == jl_bottom_type) return jl_bottom_type; - JL_GC_PUSH2(&ii, &i2); + if (ii == jl_bottom_type) + return jl_bottom_type; if (!xp2 && !yp2) { - ii = (jl_value_t*)jl_wrap_vararg(ii, NULL); - JL_GC_POP(); + if (obviously_egal(xp1, ii)) + ii = (jl_value_t*)vmx; + else if (obviously_egal(yp1, ii)) + ii = (jl_value_t*)vmy; + else { + JL_GC_PUSH1(&ii); + ii = (jl_value_t*)jl_wrap_vararg(ii, NULL); + JL_GC_POP(); + } return ii; } - if (xp2 && jl_is_typevar(xp2)) { - jl_varbinding_t *xb = lookup(e, (jl_tvar_t*)xp2); + JL_GC_PUSH2(&ii, &i2); + assert(e->Loffset == 0); + e->Loffset = offset; + jl_varbinding_t *xb = NULL, *yb = NULL; + if (xp2) { + assert(jl_is_typevar(xp2)); + xb = lookup(e, (jl_tvar_t*)xp2); if (xb) xb->intvalued = 1; - if (!yp2) { - i2 = bound_var_below((jl_tvar_t*)xp2, xb, e); - } + if (!yp2) + i2 = bound_var_below((jl_tvar_t*)xp2, xb, e, 0); } - if (yp2 && jl_is_typevar(yp2)) { - jl_varbinding_t *yb = lookup(e, (jl_tvar_t*)yp2); + if (yp2) { + assert(jl_is_typevar(yp2)); + yb = lookup(e, (jl_tvar_t*)yp2); if (yb) yb->intvalued = 1; - if (!xp2) { - i2 = bound_var_below((jl_tvar_t*)yp2, yb, e); - } + if (!xp2) + i2 = bound_var_below((jl_tvar_t*)yp2, yb, e, 1); } if (xp2 && yp2) { // Vararg{T,N} <: Vararg{T2,N2}; equate N and N2 @@ -2703,7 +3121,16 @@ static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, jl_sten i2 = jl_bottom_type; } } - ii = i2 == jl_bottom_type ? (jl_value_t*)jl_bottom_type : (jl_value_t*)jl_wrap_vararg(ii, i2); + assert(e->Loffset == offset); + e->Loffset = 0; + 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); JL_GC_POP(); return ii; } @@ -2712,77 +3139,99 @@ static jl_value_t *intersect_varargs(jl_vararg_t *vmx, jl_vararg_t *vmy, jl_sten static jl_value_t *intersect_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_stenv_t *e, int param) { size_t lx = jl_nparams(xd), ly = jl_nparams(yd); + size_t llx = lx, lly = ly; if (lx == 0 && ly == 0) return (jl_value_t*)yd; - int vx=0, vy=0, vvx = (lx > 0 && jl_is_vararg(jl_tparam(xd, lx-1))); - int vvy = (ly > 0 && jl_is_vararg(jl_tparam(yd, ly-1))); - if (!vvx && !vvy && lx != ly) - return jl_bottom_type; - jl_svec_t *params = jl_alloc_svec(lx > ly ? lx : ly); - jl_value_t *res=NULL; - JL_GC_PUSH1(¶ms); + int vx=0, vy=0; + jl_vararg_kind_t vvx = lx > 0 ? jl_vararg_kind(jl_tparam(xd, lx-1)) : JL_VARARG_NONE; + jl_vararg_kind_t vvy = ly > 0 ? jl_vararg_kind(jl_tparam(yd, ly-1)) : JL_VARARG_NONE; + if (vvx == JL_VARARG_INT) + 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_NONE || vvx == JL_VARARG_INT) && + (vvy == JL_VARARG_NONE || vvy == JL_VARARG_INT)) { + if (llx != lly) + return jl_bottom_type; + } + + size_t np = llx > lly ? llx : lly; + jl_value_t *res = NULL; + jl_svec_t *p = NULL; + jl_value_t **params; + jl_value_t **roots; + JL_GC_PUSHARGS(roots, np < 64 ? np : 1); + if (np < 64) { + params = roots; + } + else { + p = jl_alloc_svec(np); + roots[0] = (jl_value_t*)p; + params = jl_svec_data(p); + } size_t i=0, j=0; jl_value_t *xi, *yi; + int isx = 1, isy = 1; // try to reuse the object x or y as res whenever we can (e.g. when it is the supertype) instead of allocating a copy while (1) { vx = vy = 0; - xi = i < lx ? jl_tparam(xd, i) : NULL; - yi = j < ly ? jl_tparam(yd, j) : NULL; + xi = i < llx ? jl_tparam(xd, i < lx ? i : lx - 1) : NULL; + yi = j < lly ? jl_tparam(yd, j < ly ? j : ly - 1) : NULL; if (xi == NULL && yi == NULL) { - assert(i == j && i == jl_svec_len(params)); + assert(i == j && i == np); break; } - if (xi && jl_is_vararg(xi)) vx = 1; - if (yi && jl_is_vararg(yi)) vy = 1; + if (xi && jl_is_vararg(xi)) vx = vvx != JL_VARARG_INT; + if (yi && jl_is_vararg(yi)) vy = vvy != JL_VARARG_INT; if (xi == NULL || yi == NULL) { - res = jl_bottom_type; - if (vx && intersect_vararg_length(xi, ly+1-lx, e, 0)) - res = (jl_value_t*)jl_apply_tuple_type_v(jl_svec_data(params), j); - if (vy && intersect_vararg_length(yi, lx+1-ly, e, 1)) - res = (jl_value_t*)jl_apply_tuple_type_v(jl_svec_data(params), i); + if (vx && intersect_vararg_length(xi, lly+1-llx, e, 0)) { + np = j; + p = NULL; + } + else if (vy && intersect_vararg_length(yi, llx+1-lly, e, 1)) { + np = i; + p = NULL; + } + else { + res = jl_bottom_type; + } break; } - jl_varbinding_t *xb=NULL, *yb=NULL; jl_value_t *ii = NULL; if (vx && vy) { - // {A^n...,Vararg{T,N}} ∩ {Vararg{S,M}} = {(A∩S)^n...,Vararg{T∩S,N}} plus N = M-n - jl_value_t *xlen = jl_unwrap_vararg_num(xi); - if (xlen && jl_is_typevar(xlen)) { - xb = lookup(e, (jl_tvar_t*)xlen); - if (xb) - xb->offset = ly-lx; - } - jl_value_t *ylen = jl_unwrap_vararg_num(yi); - if (ylen && jl_is_typevar(ylen)) { - yb = lookup(e, (jl_tvar_t*)ylen); - if (yb) - yb->offset = lx-ly; - } ii = intersect_varargs((jl_vararg_t*)xi, (jl_vararg_t*)yi, - e, param); - if (xb) xb->offset = 0; - if (yb) yb->offset = 0; - } else { - if (vx) - xi = jl_unwrap_vararg(xi); - if (vy) - yi = jl_unwrap_vararg(yi); - ii = intersect(xi, yi, e, param == 0 ? 1 : param); + lly - llx, // xi's offset: {A^n...,Vararg{T,N}} ∩ {Vararg{S,M}} + // {(A∩S)^n...,Vararg{T∩S,N}} plus N = M-n + e, + param); + } + else { + ii = intersect(jl_is_vararg(xi) ? jl_unwrap_vararg(xi) : xi, + jl_is_vararg(yi) ? jl_unwrap_vararg(yi) : yi, + e, + param == 0 ? 1 : param); } if (ii == jl_bottom_type) { if (vx && vy) { + jl_varbinding_t *xb=NULL, *yb=NULL; + jl_value_t *xlen = jl_unwrap_vararg_num(xi); + assert(xlen == NULL || jl_is_typevar(xlen)); + if (xlen) xb = lookup(e, (jl_tvar_t*)xlen); + jl_value_t *ylen = jl_unwrap_vararg_num(yi); + assert(ylen == NULL || jl_is_typevar(ylen)); + if (ylen) yb = lookup(e, (jl_tvar_t*)ylen); int len = i > j ? i : j; - if ((xb && jl_is_long(xb->lb) && lx-1+jl_unbox_long(xb->lb) != len) || - (yb && jl_is_long(yb->lb) && ly-1+jl_unbox_long(yb->lb) != len)) { - res = jl_bottom_type; - } - else if (param == 2 && jl_is_unionall(xi) != jl_is_unionall(yi)) { + if ((xb && jl_is_long(xb->lb) && llx-1+jl_unbox_long(xb->lb) != len) || + (yb && jl_is_long(yb->lb) && lly-1+jl_unbox_long(yb->lb) != len)) { res = jl_bottom_type; } else { - if (xb) set_var_to_const(xb, jl_box_long(len-lx+1), yb); - if (yb) set_var_to_const(yb, jl_box_long(len-ly+1), xb); - res = (jl_value_t*)jl_apply_tuple_type_v(jl_svec_data(params), len); + assert(e->Loffset == 0); + if (xb) set_var_to_const(xb, jl_box_long(len-llx+1), e, 0); + if (yb) set_var_to_const(yb, jl_box_long(len-lly+1), e, 1); + np = len; + p = NULL; } } else { @@ -2790,15 +3239,44 @@ static jl_value_t *intersect_tuple(jl_datatype_t *xd, jl_datatype_t *yd, jl_sten } break; } - jl_svecset(params, (i > j ? i : j), ii); + isx = isx && ii == xi; + isy = isy && ii == yi; + if (p) + jl_svecset(p, (i > j ? i : j), ii); + else + params[i > j ? i : j] = ii; if (vx && vy) break; - if (i < lx-1 || !vx) i++; - if (j < ly-1 || !vy) j++; + if (!vx) i++; + if (!vy) j++; } // TODO: handle Vararg with explicit integer length parameter - if (res == NULL) - res = (jl_value_t*)jl_apply_tuple_type(params); + if (res == NULL) { + assert(!p || np == jl_svec_len(p)); + isx = isx && lx == np; + isy = isy && ly == np; + if (!isx && !isy) { + // do a more careful check now for equivalence + if (lx == np) { + isx = 1; + for (i = 0; i < np; i++) + isx = isx && obviously_egal(params[i], jl_tparam(xd, i)); + } + if (!isx && ly == np) { + isy = 1; + for (i = 0; i < np; i++) + isy = isy && obviously_egal(params[i], jl_tparam(yd, i)); + } + } + if (isx) + res = (jl_value_t*)xd; + else if (isy) + res = (jl_value_t*)yd; + else if (p) + res = jl_apply_tuple_type(p); + else + res = jl_apply_tuple_type_v(params, np); + } JL_GC_POP(); return res; } @@ -2815,85 +3293,51 @@ static void flip_vars(jl_stenv_t *e) // intersection where xd nominally inherits from yd static jl_value_t *intersect_sub_datatype(jl_datatype_t *xd, jl_datatype_t *yd, jl_stenv_t *e, int R, int param) { + // attempt to populate additional constraints into `e` + // if that attempt fails, then return bottom + // otherwise return xd (finish_unionall will later handle propagating those constraints) + assert(e->Loffset == 0); jl_value_t *isuper = R ? intersect((jl_value_t*)yd, (jl_value_t*)xd->super, e, param) : intersect((jl_value_t*)xd->super, (jl_value_t*)yd, e, param); - if (isuper == jl_bottom_type) return jl_bottom_type; - if (jl_nparams(xd) == 0 || jl_nparams(xd->super) == 0 || !jl_has_free_typevars((jl_value_t*)xd)) - return (jl_value_t*)xd; - jl_value_t *super_pattern=NULL; - JL_GC_PUSH2(&isuper, &super_pattern); - jl_value_t *wrapper = xd->name->wrapper; - super_pattern = jl_rewrap_unionall((jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(wrapper))->super, - wrapper); - int envsz = jl_subtype_env_size(super_pattern); - jl_value_t *ii = jl_bottom_type; - { - jl_value_t **env; - JL_GC_PUSHARGS(env, envsz); - jl_stenv_t tempe; - init_stenv(&tempe, env, envsz); - tempe.ignore_free = 1; - if (subtype_in_env(isuper, super_pattern, &tempe)) { - jl_value_t *wr = wrapper; - int i; - for(i=0; ivar || - (jl_is_typevar(ei) && lookup(e, (jl_tvar_t*)ei) == NULL)) - env[i] = jl_tparam(xd,i); - wr = ((jl_unionall_t*)wr)->body; - } - JL_TRY { - ii = jl_apply_type(wrapper, env, envsz); - } - JL_CATCH { - ii = jl_bottom_type; - } - } - JL_GC_POP(); - } - JL_GC_POP(); - return ii; + if (isuper == jl_bottom_type) + return jl_bottom_type; + return (jl_value_t*)xd; } static jl_value_t *intersect_invariant(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { - if (!jl_has_free_typevars(x) && !jl_has_free_typevars(y)) { + if (e->Loffset == 0 && !jl_has_free_typevars(x) && !jl_has_free_typevars(y)) { return (jl_subtype(x,y) && jl_subtype(y,x)) ? y : NULL; } e->invdepth++; - e->Rinvdepth++; jl_value_t *ii = intersect(x, y, e, 2); e->invdepth--; - e->Rinvdepth--; - if (jl_is_typevar(x) && jl_is_typevar(y) && (jl_is_typevar(ii) || !jl_is_type(ii))) - return ii; + if (jl_is_typevar(x) && jl_is_typevar(y) && jl_is_typevar(ii)) + return ii; // skip the following check due to possible circular constraints. if (ii == jl_bottom_type) { if (!subtype_in_env(x, jl_bottom_type, e)) return NULL; - flip_vars(e); + flip_vars(e); flip_offset(e); if (!subtype_in_env(y, jl_bottom_type, e)) { - flip_vars(e); + flip_vars(e); flip_offset(e); return NULL; } - flip_vars(e); + flip_vars(e); flip_offset(e); return jl_bottom_type; } - jl_value_t *root=NULL; jl_savedenv_t se; - JL_GC_PUSH2(&ii, &root); - save_env(e, &root, &se); - if (!subtype_in_env_existential(x, y, e, 0, e->invdepth)) { + JL_GC_PUSH1(&ii); + save_env(e, &se, 1); + if (!subtype_in_env_existential(x, y, e)) ii = NULL; - } else { - if (!subtype_in_env_existential(y, x, e, 0, e->invdepth)) + restore_env(e, &se, 1); + flip_offset(e); + if (!subtype_in_env_existential(y, x, e)) ii = NULL; + flip_offset(e); } - restore_env(e, root, &se); + restore_env(e, &se, 1); free_env(&se); JL_GC_POP(); return ii; @@ -2902,6 +3346,7 @@ static jl_value_t *intersect_invariant(jl_value_t *x, jl_value_t *y, jl_stenv_t // intersection where x == Type{...} and y is not static jl_value_t *intersect_type_type(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int8_t R) { + assert(e->Loffset == 0); jl_value_t *p0 = jl_tparam0(x); if (!jl_is_typevar(p0)) return (jl_typeof(p0) == y) ? x : jl_bottom_type; @@ -2950,6 +3395,23 @@ static int subtype_by_bounds(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) JL_NOT return compareto_var(x, (jl_tvar_t*)y, e, -1) || compareto_var(y, (jl_tvar_t*)x, e, 1); } +static int has_typevar_via_env(jl_value_t *x, jl_tvar_t *t, jl_stenv_t *e) +{ + if (e->Loffset == 0) { + jl_varbinding_t *temp = e->vars; + while (temp != NULL) { + if (temp->var == t) + break; + if (temp->lb == temp->ub && + temp->lb == (jl_value_t *)t && + jl_has_typevar(x, temp->var)) + return 1; + temp = temp->prev; + } + } + return jl_has_typevar(x, t); +} + // `param` means we are currently looking at a parameter of a type constructor // (as opposed to being outside any type constructor, or comparing variable bounds). // this is used to record the positions where type variables occur for the @@ -2976,72 +3438,139 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa jl_value_t *xub = xx ? xx->ub : ((jl_tvar_t*)x)->ub; jl_value_t *ylb = yy ? yy->lb : ((jl_tvar_t*)y)->lb; jl_value_t *yub = yy ? yy->ub : ((jl_tvar_t*)y)->ub; - record_var_occurrence(xx, e, param); if (xx && yy && xx->depth0 != yy->depth0) { + record_var_occurrence(xx, e, param); record_var_occurrence(yy, e, param); return subtype_in_env(yy->ub, yy->lb, e) ? y : jl_bottom_type; } if (xub == xlb && jl_is_typevar(xub)) { + record_var_occurrence(xx, e, param); if (y == xub) { record_var_occurrence(yy, e, param); return y; } - return intersect(y, xub, e, param); + if (R) flip_offset(e); + jl_value_t *res = intersect(xub, y, e, param); + if (R) flip_offset(e); + return res; + } + if (yub == ylb && jl_is_typevar(yub)) { + record_var_occurrence(yy, e, param); + if (R) flip_offset(e); + jl_value_t *res = intersect(x, yub, e, param); + if (R) flip_offset(e); + return res; } + if (xub == xlb && jl_is_typevar(xub)) { + record_var_occurrence(xx, e, param); + if (y == xub) { + record_var_occurrence(yy, e, param); + return y; + } + if (R) flip_offset(e); + jl_value_t *res = intersect(xub, y, e, param); + if (R) flip_offset(e); + return res; + } + if (yub == ylb && jl_is_typevar(yub)) { + record_var_occurrence(yy, e, param); + if (R) flip_offset(e); + jl_value_t *res = intersect(x, yub, e, param); + if (R) flip_offset(e); + return res; + } + record_var_occurrence(xx, e, param); record_var_occurrence(yy, e, param); + int xoffset = R ? -e->Loffset : e->Loffset; if (!jl_is_type(ylb) && !jl_is_typevar(ylb)) { if (xx) - return set_var_to_const(xx, ylb, yy); - if ((xlb == jl_bottom_type && xub == (jl_value_t*)jl_any_type) || jl_egal(xlb, ylb)) - return ylb; + return set_var_to_const(xx, ylb, e, R); + if ((xlb == jl_bottom_type && xub == (jl_value_t*)jl_any_type) || jl_egal(xlb, ylb)) { + if (xoffset == 0) + return ylb; + else if (jl_is_long(ylb)) { + if (xoffset > 0) + return ylb; + else + return jl_box_long(jl_unbox_long(ylb) + xoffset); + } + } return jl_bottom_type; } if (!jl_is_type(xlb) && !jl_is_typevar(xlb)) { if (yy) - return set_var_to_const(yy, xlb, xx); - if (ylb == jl_bottom_type && yub == (jl_value_t*)jl_any_type) - return xlb; + return set_var_to_const(yy, xlb, e, !R); + if (ylb == jl_bottom_type && yub == (jl_value_t*)jl_any_type) { + if (xoffset == 0) + return xlb; + else if (jl_is_long(xlb)) { + if (xoffset < 0) + return xlb; + else + return jl_box_long(jl_unbox_long(ylb) - xoffset); + } + } return jl_bottom_type; } int ccheck; - if (yub == xub || + if (R) flip_offset(e); + 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. + // 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; + } + else if (yub == xub || (subtype_by_bounds(xlb, yub, e) && subtype_by_bounds(ylb, xub, e))) { ccheck = 1; } else { if (R) flip_vars(e); - ccheck = subtype_in_env(xlb, yub, e) && subtype_in_env(ylb, xub, e); + ccheck = subtype_in_env(xlb, yub, e); + if (ccheck) { + flip_offset(e); + ccheck = subtype_in_env(ylb, xub, e); + flip_offset(e); + } if (R) flip_vars(e); } + if (R) flip_offset(e); if (!ccheck) return jl_bottom_type; - if (var_occurs_inside(xub, (jl_tvar_t*)y, 0, 0) && var_occurs_inside(yub, (jl_tvar_t*)x, 0, 0)) { + if ((has_typevar_via_env(xub, (jl_tvar_t*)y, e) || has_typevar_via_env(xub, (jl_tvar_t*)x, e)) && + (has_typevar_via_env(yub, (jl_tvar_t*)x, e) || has_typevar_via_env(yub, (jl_tvar_t*)y, e))) { + // TODO: This doesn't make much sense. // circular constraint. the result will be Bottom, but in the meantime // we need to avoid computing intersect(xub, yub) since it won't terminate. return y; } jl_value_t *ub=NULL, *lb=NULL; JL_GC_PUSH2(&lb, &ub); - ub = intersect_aside(xub, yub, e, 0, xx ? xx->depth0 : 0); + int d = xx ? xx->depth0 : yy ? yy->depth0 : 0; + ub = R ? intersect_aside(yub, xub, e, d) : intersect_aside(xub, yub, e, d); if (reachable_var(xlb, (jl_tvar_t*)y, e)) lb = ylb; else lb = simple_join(xlb, ylb); - if (yy) { + if (yy && xoffset == 0) { yy->lb = lb; if (!reachable_var(ub, (jl_tvar_t*)y, e)) yy->ub = ub; assert(yy->ub != y); assert(yy->lb != y); } - if (xx && !reachable_var(y, (jl_tvar_t*)x, e)) { + if (xx && xoffset == 0 && !reachable_var(y, (jl_tvar_t*)x, e)) { xx->lb = y; xx->ub = y; assert(xx->ub != x); } JL_GC_POP(); - return y; + // Here we always return the shorter `Vararg`'s length. + return xoffset < 0 ? x : y; } + assert(e->Loffset == 0); record_var_occurrence(xx, e, param); record_var_occurrence(yy, e, param); if (xx && yy && xx->concrete && !yy->concrete) { @@ -3056,7 +3585,7 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa record_var_occurrence(lookup(e, (jl_tvar_t*)y), e, param); return intersect_var((jl_tvar_t*)y, x, e, 1, param); } - if (!jl_has_free_typevars(x) && !jl_has_free_typevars(y)) { + if (e->Loffset == 0 && !jl_has_free_typevars(x) && !jl_has_free_typevars(y)) { if (jl_subtype(x, y)) return x; if (jl_subtype(y, x)) return y; } @@ -3077,9 +3606,9 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa if (jl_is_unionall(x)) { if (jl_is_unionall(y)) { jl_value_t *a=NULL, *b=jl_bottom_type, *res=NULL; - JL_GC_PUSH2(&a,&b); + JL_GC_PUSH2(&a, &b); jl_savedenv_t se; - save_env(e, NULL, &se); + save_env(e, &se, 0); a = intersect_unionall(y, (jl_unionall_t*)x, e, 0, param); if (jl_is_unionall(a)) { jl_unionall_t *ua = (jl_unionall_t*)a; @@ -3087,7 +3616,7 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa jl_unionall_t *ub = (jl_unionall_t*)ua->body; if (jl_has_typevar(ub->var->ub, ua->var) || jl_has_typevar(ub->var->lb, ua->var)) { - restore_env(e, NULL, &se); // restore counts + restore_env(e, &se, 0); // restore counts b = intersect_unionall(x, (jl_unionall_t*)y, e, 1, param); } } @@ -3107,7 +3636,6 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa } if (jl_is_unionall(y)) return intersect_unionall(x, (jl_unionall_t*)y, e, 1, param); - assert(!jl_is_vararg(x) && !jl_is_vararg(y)); if (jl_is_datatype(x) && jl_is_datatype(y)) { jl_datatype_t *xd = (jl_datatype_t*)x, *yd = (jl_datatype_t*)y; if (param < 2) { @@ -3125,20 +3653,40 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa size_t i, np = jl_nparams(xd); jl_value_t **newparams; JL_GC_PUSHARGS(newparams, np); - for (i=0; i < np; i++) { + int isx = 1, isy = 1; // try to reuse the object x or y as res whenever we can (e.g. when it is the supertype) instead of allocating a copy + for (i = 0; i < np; i++) { jl_value_t *xi = jl_tparam(xd, i), *yi = jl_tparam(yd, i); jl_value_t *ii = intersect_invariant(xi, yi, e); if (ii == NULL) break; + isx = isx && ii == xi; + isy = isy && ii == yi; newparams[i] = ii; } jl_value_t *res = jl_bottom_type; - if (i >= np) { - JL_TRY { - res = jl_apply_type(xd->name->wrapper, newparams, np); + if (i == np) { + if (!isx && !isy) { + // do a more careful check now for equivalence + isx = 1; + for (i = 0; i < np; i++) + isx = isx && obviously_egal(newparams[i], jl_tparam(xd, i)); + if (!isx) { + isy = 1; + for (i = 0; i < np; i++) + isy = isy && obviously_egal(newparams[i], jl_tparam(yd, i)); + } } - JL_CATCH { - res = jl_bottom_type; + if (isx) + res = x; + else if (isy) + res = y; + else { + JL_TRY { + res = jl_apply_type(xd->name->wrapper, newparams, np); + } + JL_CATCH { + res = jl_bottom_type; + } } } JL_GC_POP(); @@ -3161,64 +3709,187 @@ static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int pa return jl_bottom_type; } +static int merge_env(jl_stenv_t *e, jl_savedenv_t *se, int count) +{ + if (count == 0) + alloc_env(e, se, 1); + jl_value_t **roots = NULL; + int nroots = 0; + if (se->gcframe.nroots == JL_GC_ENCODE_PUSHARGS(1)) { + jl_svec_t *sv = (jl_svec_t*)se->roots[0]; + assert(jl_is_svec(sv)); + roots = jl_svec_data(sv); + nroots = jl_svec_len(sv); + } + else { + roots = se->roots; + nroots = se->gcframe.nroots >> 2; + } + int 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; + } + if (v->occurs) { + // only merge lb/ub/innervars if this var occurs. + jl_value_t *b1, *b2; + b1 = roots[n]; + JL_GC_PROMISE_ROOTED(b1); // clang-sagc doesn't know this came from our GC frame + b2 = v->lb; + JL_GC_PROMISE_ROOTED(b2); // clang-sagc doesn't know the fields of this are stack GC roots + roots[n] = b1 ? simple_meet(b1, b2, 0) : b2; + b1 = roots[n+1]; + JL_GC_PROMISE_ROOTED(b1); // clang-sagc doesn't know this came from our GC frame + 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; + } + // 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; + n = n + 3; + v = v->prev; + } + assert(n == nroots); (void)nroots; + return count + 1; +} + +// merge untouched vars' info. +static void final_merge_env(jl_stenv_t *e, jl_savedenv_t *me, jl_savedenv_t *se) +{ + jl_value_t **merged = NULL; + jl_value_t **saved = NULL; + int nroots = 0; + assert(se->gcframe.nroots == me->gcframe.nroots); + if (se->gcframe.nroots == JL_GC_ENCODE_PUSHARGS(1)) { + jl_svec_t *sv = (jl_svec_t*)se->roots[0]; + assert(jl_is_svec(sv)); + saved = jl_svec_data(sv); + nroots = jl_svec_len(sv); + sv = (jl_svec_t*)me->roots[0]; + assert(jl_is_svec(sv)); + merged = jl_svec_data(sv); + assert(nroots == jl_svec_len(sv)); + } + else { + saved = se->roots; + merged = me->roots; + nroots = se->gcframe.nroots >> 2; + } + assert(nroots == current_env_length(e) * 3); + assert(nroots % 3 == 0); + for (int n = 0; n < nroots; n = n + 3) { + if (merged[n] == NULL) + merged[n] = saved[n]; + if (merged[n+1] == NULL) + merged[n+1] = saved[n+1]; + jl_value_t *b1, *b2; + b1 = merged[n+2]; + JL_GC_PROMISE_ROOTED(b1); // clang-sagc doesn't know this came from our GC frame + b2 = saved[n+2]; + JL_GC_PROMISE_ROOTED(b2); // clang-sagc doesn't know this came from our GC frame + if (b2 && b1 != b2) { + if (b1) + jl_array_ptr_1d_append((jl_array_t*)b1, (jl_array_t*)b2); + else + merged[n+2] = b2; + } + me->buf[n] |= se->buf[n]; + } +} + +static void expand_local_env(jl_stenv_t *e, jl_value_t *res) +{ + jl_varbinding_t *v = e->vars; + // Here we pull in some typevar missed in fastpath. + while (v != NULL) { + v->occurs = v->occurs || jl_has_typevar(res, v->var); + assert(v->occurs == 0 || v->occurs == 1); + v = v->prev; + } + v = e->vars; + while (v != NULL) { + if (v->occurs == 1) { + jl_varbinding_t *v2 = e->vars; + while (v2 != NULL) { + if (v2 != v && v2->occurs == 0) + v2->occurs = -(jl_has_typevar(v->lb, v2->var) || jl_has_typevar(v->ub, v2->var)); + v2 = v2->prev; + } + } + v = v->prev; + } +} + static jl_value_t *intersect_all(jl_value_t *x, jl_value_t *y, jl_stenv_t *e) { e->Runions.depth = 0; e->Runions.more = 0; e->Runions.used = 0; jl_value_t **is; - JL_GC_PUSHARGS(is, 3); - jl_value_t **saved = &is[2]; - jl_savedenv_t se; - save_env(e, saved, &se); - int lastset = 0, niter = 0, total_iter = 0; - jl_value_t *ii = intersect(x, y, e, 0); - is[0] = ii; // root - if (ii == jl_bottom_type) { - restore_env(e, *saved, &se); - } - else { - free_env(&se); - save_env(e, saved, &se); - } - while (e->Runions.more) { - if (e->emptiness_only && ii != jl_bottom_type) + JL_GC_PUSHARGS(is, 2); + jl_savedenv_t se, me; + save_env(e, &se, 1); + int niter = 0, total_iter = 0; + clean_occurs(e); + is[0] = intersect(x, y, e, 0); // root + if (is[0] != jl_bottom_type) { + expand_local_env(e, is[0]); + niter = merge_env(e, &me, niter); + } + restore_env(e, &se, 1); + while (next_union_state(e, 1)) { + if (e->emptiness_only && is[0] != jl_bottom_type) break; e->Runions.depth = 0; - int set = e->Runions.more - 1; e->Runions.more = 0; - statestack_set(&e->Runions, set, 1); - for (int i = set + 1; i <= lastset; i++) - statestack_set(&e->Runions, i, 0); - lastset = set; - is[0] = ii; + clean_occurs(e); is[1] = intersect(x, y, e, 0); - if (is[1] == jl_bottom_type) { - restore_env(e, *saved, &se); - } - else { - free_env(&se); - save_env(e, saved, &se); + if (is[1] != jl_bottom_type) { + expand_local_env(e, is[1]); + niter = merge_env(e, &me, niter); } + restore_env(e, &se, 1); if (is[0] == jl_bottom_type) - ii = is[1]; - else if (is[1] == jl_bottom_type) - ii = is[0]; - else { + is[0] = is[1]; + else if (is[1] != jl_bottom_type) { // TODO: the repeated subtype checks in here can get expensive - ii = jl_type_union(is, 2); - niter++; + is[0] = jl_type_union(is, 2); } total_iter++; - if (niter > 3 || total_iter > 400000) { - ii = y; + if (niter > 4 || total_iter > 400000) { + is[0] = y; break; } } + if (niter) { + final_merge_env(e, &me, &se); + restore_env(e, &me, 1); + free_env(&me); + } free_env(&se); JL_GC_POP(); - return ii; + return is[0]; } // type intersection entry points @@ -3339,13 +4010,14 @@ static jl_value_t *switch_union_tuple(jl_value_t *a, jl_value_t *b) ts[1] = jl_tparam(b, i); jl_svecset(vec, i, jl_type_union(ts, 2)); } - jl_value_t *ans = (jl_value_t*)jl_apply_tuple_type(vec); + jl_value_t *ans = jl_apply_tuple_type(vec); JL_GC_POP(); return ans; } // `a` might have a non-empty intersection with some concrete type b even if !(a<:b) and !(b<:a) // For example a=`Tuple{Type{<:Vector}}` and b=`Tuple{DataType}` +// TODO: this query is partly available memoized as jl_type_equality_is_identity static int might_intersect_concrete(jl_value_t *a) { if (jl_is_unionall(a)) @@ -3395,9 +4067,9 @@ jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t * *ans = a; sz = szb; if (issubty) *issubty = 1; } - else if (lta && ltb) { - goto bot; - } + // else if (lta && ltb) { // !jl_type_equality_is_identity known in this case because obviously_disjoint returned false + // goto bot; + // } else if (jl_subtype(b, a)) { *ans = b; } @@ -3427,7 +4099,7 @@ jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t * if (jl_is_uniontype(ans_unwrapped)) { ans_unwrapped = switch_union_tuple(((jl_uniontype_t*)ans_unwrapped)->a, ((jl_uniontype_t*)ans_unwrapped)->b); if (ans_unwrapped != NULL) { - *ans = jl_rewrap_unionall(ans_unwrapped, *ans); + *ans = jl_rewrap_unionall_(ans_unwrapped, *ans); } } JL_GC_POP(); @@ -3448,17 +4120,20 @@ jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t * } } if (sz == 0 && szb > 0) { - while (jl_is_unionall(b)) { - env[i++] = (jl_value_t*)((jl_unionall_t*)b)->var; - b = ((jl_unionall_t*)b)->body; + jl_unionall_t *ub = (jl_unionall_t*)b; + while (jl_is_unionall(ub)) { + env[i++] = (jl_value_t*)ub->var; + ub = (jl_unionall_t*)ub->body; } sz = szb; } if (penv) { jl_svec_t *e = jl_alloc_svec(sz); - *penv = e; - for(i=0; i < sz; i++) + for (i = 0; i < sz; i++) { + assert(env[i]); jl_svecset(e, i, env[i]); + } + *penv = e; } bot: JL_GC_POP(); @@ -3499,9 +4174,11 @@ int jl_subtype_matching(jl_value_t *a, jl_value_t *b, jl_svec_t **penv) // copy env to svec for return int i = 0; jl_svec_t *e = jl_alloc_svec(szb); - *penv = e; - for (i = 0; i < szb; i++) + for (i = 0; i < szb; i++) { + assert(env[i]); jl_svecset(e, i, env[i]); + } + *penv = e; } JL_GC_POP(); return sub; @@ -3510,39 +4187,139 @@ int jl_subtype_matching(jl_value_t *a, jl_value_t *b, jl_svec_t **penv) // specificity comparison -static int eq_msp(jl_value_t *a, jl_value_t *b, jl_typeenv_t *env) +static int eq_msp(jl_value_t *a, jl_value_t *b, jl_value_t *a0, jl_value_t *b0, jl_typeenv_t *env) { if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return jl_egal(a, b); + if (a == b) // assume the TypeVar env is the same?? + return 1; + if (jl_typeof(a) == jl_typeof(b) && jl_types_egal(a, b)) + return 1; + if (obviously_unequal(a, b)) + return 0; + // the following is an interleaved version of: + // return jl_type_equal(a, b) + // where we try to do the fast checks before the expensive ones + if (jl_is_datatype(a) && !jl_is_concrete_type(b)) { + // if one type looks simpler, check it on the right + // first in order to reject more quickly. + jl_value_t *temp = a; + a = b; + b = temp; + } + // first check if a <: b has an obvious answer + int subtype_ab = 2; + if (b == (jl_value_t*)jl_any_type || a == jl_bottom_type) { + subtype_ab = 1; + } + else if (obvious_subtype(a, b, b0, &subtype_ab)) { +#ifdef NDEBUG + if (subtype_ab == 0) + return 0; +#endif + } + else { + subtype_ab = 3; + } + // next check if b <: a has an obvious answer + int subtype_ba = 2; + if (a == (jl_value_t*)jl_any_type || b == jl_bottom_type) { + subtype_ba = 1; + } + else if (obvious_subtype(b, a, a0, &subtype_ba)) { +#ifdef NDEBUG + if (subtype_ba == 0) + return 0; +#endif + } + else { + subtype_ba = 3; + } + // finally, do full subtyping for any inconclusive test JL_GC_PUSH2(&a, &b); - jl_typeenv_t *e = env; - while (e != NULL) { - a = jl_type_unionall(e->var, a); - b = jl_type_unionall(e->var, b); - e = e->prev; + jl_typeenv_t *env2 = env; + while (env2 != NULL) { + a = jl_type_unionall(env2->var, a); + b = jl_type_unionall(env2->var, b); + env2 = env2->prev; + } + jl_stenv_t e; +#ifdef NDEBUG + if (subtype_ab != 1) +#endif + { + init_stenv(&e, NULL, 0); + int subtype = forall_exists_subtype(a, b, &e, 0); + assert(subtype_ab == 3 || subtype_ab == subtype || jl_has_free_typevars(a) || jl_has_free_typevars(b)); +#ifndef NDEBUG + if (subtype_ab != 0 && subtype_ab != 1) // ensures that running in a debugger doesn't change the result +#endif + subtype_ab = subtype; +#ifdef NDEBUG + if (subtype_ab == 0) { + JL_GC_POP(); + return 0; + } +#endif + } +#ifdef NDEBUG + if (subtype_ba != 1) +#endif + { + init_stenv(&e, NULL, 0); + int subtype = forall_exists_subtype(b, a, &e, 0); + assert(subtype_ba == 3 || subtype_ba == subtype || jl_has_free_typevars(a) || jl_has_free_typevars(b)); +#ifndef NDEBUG + if (subtype_ba != 0 && subtype_ba != 1) // ensures that running in a debugger doesn't change the result +#endif + subtype_ba = subtype; } - int eq = jl_types_equal(a, b); JL_GC_POP(); - return eq; + // all tests successful + return subtype_ab && subtype_ba; } -static int sub_msp(jl_value_t *a, jl_value_t *b, jl_typeenv_t *env) +static int sub_msp(jl_value_t *x, jl_value_t *y, jl_value_t *y0, jl_typeenv_t *env) { - JL_GC_PUSH2(&a, &b); + jl_stenv_t e; + if (y == (jl_value_t*)jl_any_type || x == jl_bottom_type) + return 1; + if (x == y || + (jl_typeof(x) == jl_typeof(y) && + (jl_is_unionall(y) || jl_is_uniontype(y)) && + jl_types_egal(x, y))) { + return 1; + } + int obvious_sub = 2; + if (obvious_subtype(x, y, y0, &obvious_sub)) { +#ifdef NDEBUG + return obvious_sub; +#endif + } + else { + obvious_sub = 3; + } + JL_GC_PUSH2(&x, &y); while (env != NULL) { - if (jl_is_type(a) || jl_is_typevar(a)) - a = jl_type_unionall(env->var, a); - if (jl_is_type(b) || jl_is_typevar(b)) - b = jl_type_unionall(env->var, b); + if (jl_is_type(x) || jl_is_typevar(x)) + x = jl_type_unionall(env->var, x); + if (jl_is_type(y) || jl_is_typevar(y)) + y = jl_type_unionall(env->var, y); env = env->prev; } - int sub = jl_subtype(a, b); + init_stenv(&e, NULL, 0); + int subtype = forall_exists_subtype(x, y, &e, 0); + assert(obvious_sub == 3 || obvious_sub == subtype || jl_has_free_typevars(x) || jl_has_free_typevars(y)); +#ifndef NDEBUG + if (obvious_sub == 0 || obvious_sub == 1) + subtype = obvious_sub; // this ensures that running in a debugger doesn't change the result +#endif JL_GC_POP(); - return sub; + return subtype; } -static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_typeenv_t *env); +static int type_morespecific_(jl_value_t *a, jl_value_t *b, jl_value_t *a0, jl_value_t *b0, int invariant, jl_typeenv_t *env); static int num_occurs(jl_tvar_t *v, jl_typeenv_t *env); @@ -3565,7 +4342,7 @@ static jl_value_t *nth_tuple_elt(jl_datatype_t *t JL_PROPAGATES_ROOT, size_t i) return NULL; } -static int tuple_morespecific(jl_datatype_t *cdt, jl_datatype_t *pdt, int invariant, jl_typeenv_t *env) +static int tuple_morespecific(jl_datatype_t *cdt, jl_datatype_t *pdt, jl_value_t *c0, jl_value_t *p0, int invariant, jl_typeenv_t *env) { size_t plen = jl_nparams(pdt); if (plen == 0) return 0; @@ -3595,8 +4372,8 @@ static int tuple_morespecific(jl_datatype_t *cdt, jl_datatype_t *pdt, int invari break; } - if (type_morespecific_(pe, ce, invariant, env)) { - assert(!type_morespecific_(ce, pe, invariant, env)); + if (type_morespecific_(pe, ce, p0, c0, invariant, env)) { + assert(!type_morespecific_(ce, pe, c0, p0, invariant, env)); return 0; } @@ -3609,9 +4386,9 @@ static int tuple_morespecific(jl_datatype_t *cdt, jl_datatype_t *pdt, int invari if (cva && pva && i >= clen-1 && i >= plen-1 && (some_morespecific || (cdiag && !pdiag))) return 1; - int cms = type_morespecific_(ce, pe, invariant, env); + int cms = type_morespecific_(ce, pe, c0, p0, invariant, env); - if (!cms && !sub_msp(ce, pe, env)) { + if (!cms && !sub_msp(ce, pe, p0, env)) { /* A bound vararg tuple can be more specific despite disjoint elements in order to preserve transitivity. For example in @@ -3624,7 +4401,7 @@ static int tuple_morespecific(jl_datatype_t *cdt, jl_datatype_t *pdt, int invari } // Tuple{..., T} not more specific than Tuple{..., Vararg{S}} if S is diagonal - if (!cms && i == clen-1 && clen == plen && !cva && pva && eq_msp(ce, pe, env) && + if (!cms && i == clen-1 && clen == plen && !cva && pva && eq_msp(ce, pe, c0, p0, env) && jl_is_typevar(ce) && jl_is_typevar(pe) && !cdiag && pdiag) return 0; @@ -3653,7 +4430,7 @@ static size_t tuple_full_length(jl_value_t *t) // Called when a is a bound-vararg and b is not a vararg. Sets the vararg length // in a to match b, as long as this makes some earlier argument more specific. -static int args_morespecific_fix1(jl_value_t *a, jl_value_t *b, int swap, jl_typeenv_t *env) +static int args_morespecific_fix1(jl_value_t *a, jl_value_t *b, jl_value_t *a0, jl_value_t *b0, int swap, jl_typeenv_t *env) { size_t n = jl_nparams(a); int taillen = tuple_full_length(b)-n+1; @@ -3673,12 +4450,12 @@ static int args_morespecific_fix1(jl_value_t *a, jl_value_t *b, int swap, jl_typ } int ret = -1; if (changed) { - if (eq_msp(b, (jl_value_t*)new_a, env)) + if (eq_msp(b, (jl_value_t*)new_a, b0, a0, env)) ret = swap; else if (swap) - ret = type_morespecific_(b, (jl_value_t*)new_a, 0, env); + ret = type_morespecific_(b, (jl_value_t*)new_a, b0, a0, 0, env); else - ret = type_morespecific_((jl_value_t*)new_a, b, 0, env); + ret = type_morespecific_((jl_value_t*)new_a, b, a0, b0, 0, env); } JL_GC_POP(); return ret; @@ -3723,38 +4500,61 @@ static int num_occurs(jl_tvar_t *v, jl_typeenv_t *env) return 0; } +int tuple_cmp_typeofbottom(jl_datatype_t *a, jl_datatype_t *b) +{ + size_t i, la = jl_nparams(a), lb = jl_nparams(b); + for (i = 0; i < la || i < lb; i++) { + jl_value_t *pa = i < la ? jl_tparam(a, i) : NULL; + jl_value_t *pb = i < lb ? jl_tparam(b, i) : NULL; + assert(jl_typeofbottom_type); // for clang-sa + int xa = pa == (jl_value_t*)jl_typeofbottom_type || pa == (jl_value_t*)jl_typeofbottom_type->super; + int xb = pb == (jl_value_t*)jl_typeofbottom_type || pb == (jl_value_t*)jl_typeofbottom_type->super; + if (xa != xb) + return xa - xb; + } + return 0; +} + + #define HANDLE_UNIONALL_A \ jl_unionall_t *ua = (jl_unionall_t*)a; \ jl_typeenv_t newenv = { ua->var, 0x0, env }; \ newenv.val = (jl_value_t*)(intptr_t)count_occurs(ua->body, ua->var); \ - return type_morespecific_(ua->body, b, invariant, &newenv) + return type_morespecific_(ua->body, b, a0, b0, invariant, &newenv) #define HANDLE_UNIONALL_B \ jl_unionall_t *ub = (jl_unionall_t*)b; \ jl_typeenv_t newenv = { ub->var, 0x0, env }; \ newenv.val = (jl_value_t*)(intptr_t)count_occurs(ub->body, ub->var); \ - return type_morespecific_(a, ub->body, invariant, &newenv) + return type_morespecific_(a, ub->body, a0, b0, invariant, &newenv) -static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_typeenv_t *env) +static int type_morespecific_(jl_value_t *a, jl_value_t *b, jl_value_t *a0, jl_value_t *b0, int invariant, jl_typeenv_t *env) { if (a == b) return 0; if (jl_is_tuple_type(a) && jl_is_tuple_type(b)) { + // compare whether a and b have Type{Union{}} included, + // which makes them instantly the most specific, regardless of all else, + // for whichever is left most (the left-to-right behavior here ensures + // we do not need to keep track of conflicts with multiple methods). + int msp = tuple_cmp_typeofbottom((jl_datatype_t*)a, (jl_datatype_t*)b); + if (msp) + return msp > 0; // When one is JL_VARARG_BOUND and the other has fixed length, // allow the argument length to fix the tvar jl_vararg_kind_t akind = jl_va_tuple_kind((jl_datatype_t*)a); jl_vararg_kind_t bkind = jl_va_tuple_kind((jl_datatype_t*)b); int ans = -1; if (akind == JL_VARARG_BOUND && bkind < JL_VARARG_BOUND) { - ans = args_morespecific_fix1(a, b, 0, env); + ans = args_morespecific_fix1(a, b, a0, b0, 0, env); if (ans == 1) return 1; } if (bkind == JL_VARARG_BOUND && akind < JL_VARARG_BOUND) { - ans = args_morespecific_fix1(b, a, 1, env); + ans = args_morespecific_fix1(b, a, b0, a0, 1, env); if (ans == 0) return 0; } - return tuple_morespecific((jl_datatype_t*)a, (jl_datatype_t*)b, invariant, env); + return tuple_morespecific((jl_datatype_t*)a, (jl_datatype_t*)b, a0, b0, invariant, env); } if (!invariant) { @@ -3768,13 +4568,13 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty } // Union a is more specific than b if some element of a is more specific than b, but // not vice-versa. - if (sub_msp(b, a, env)) + if (sub_msp(b, a, a0, env)) return 0; jl_uniontype_t *u = (jl_uniontype_t*)a; - if (type_morespecific_(u->a, b, invariant, env) || type_morespecific_(u->b, b, invariant, env)) { + if (type_morespecific_(u->a, b, a0, b0, invariant, env) || type_morespecific_(u->b, b, a0, b0, invariant, env)) { if (jl_is_uniontype(b)) { jl_uniontype_t *v = (jl_uniontype_t*)b; - if (type_morespecific_(v->a, a, invariant, env) || type_morespecific_(v->b, a, invariant, env)) + if (type_morespecific_(v->a, a, b0, a0, invariant, env) || type_morespecific_(v->b, a, b0, a0, invariant, env)) return 0; } return 1; @@ -3788,11 +4588,11 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty jl_value_t *tp0a = jl_tparam0(a); if (jl_is_typevar(tp0a)) { jl_value_t *ub = ((jl_tvar_t*)tp0a)->ub; - if (jl_is_kind(b) && !sub_msp((jl_value_t*)jl_any_type, ub, env)) + if (jl_is_kind(b) && !sub_msp((jl_value_t*)jl_any_type, ub, b0, env)) return 1; } else if (tp0a == jl_bottom_type) { - if (sub_msp(b, (jl_value_t*)jl_type_type, env)) + if (sub_msp(b, (jl_value_t*)jl_type_type, (jl_value_t*)jl_type_type, env)) return 1; } else if (b == (jl_value_t*)jl_datatype_type || b == (jl_value_t*)jl_unionall_type || @@ -3806,8 +4606,8 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty HANDLE_UNIONALL_A; } jl_uniontype_t *u = (jl_uniontype_t*)b; - if (type_morespecific_(a, u->a, invariant, env) || type_morespecific_(a, u->b, invariant, env)) - return !type_morespecific_(b, a, invariant, env); + if (type_morespecific_(a, u->a, a0, b0, invariant, env) || type_morespecific_(a, u->b, a0, b0, invariant, env)) + return !type_morespecific_(b, a, b0, a0, invariant, env); return 0; } @@ -3823,7 +4623,7 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty if (tta->name != jl_type_typename) return 1; jl_value_t *tp0 = jl_tparam0(b); if (jl_is_typevar(tp0)) { - if (sub_msp((jl_value_t*)jl_any_type, ((jl_tvar_t*)tp0)->ub, env)) + if (sub_msp((jl_value_t*)jl_any_type, ((jl_tvar_t*)tp0)->ub, b0, env)) return 1; } } @@ -3836,11 +4636,11 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty int bfree = jl_has_free_typevars(bpara); if (!afree && !bfree && !jl_types_equal(apara, bpara)) return 0; - if (type_morespecific_(apara, bpara, 1, env) && (jl_is_typevar(apara) || !afree || bfree)) + if (type_morespecific_(apara, bpara, a0, b0, 1, env) && (jl_is_typevar(apara) || !afree || bfree)) ascore += 1; - else if (type_morespecific_(bpara, apara, 1, env) && (jl_is_typevar(bpara) || !bfree || afree)) + else if (type_morespecific_(bpara, apara, b0, a0, 1, env) && (jl_is_typevar(bpara) || !bfree || afree)) bscore += 1; - else if (eq_msp(apara, bpara, env)) { + else if (eq_msp(apara, bpara, a0, b0, env)) { if (!afree && bfree) ascore += 1; else if (afree && !bfree) @@ -3879,13 +4679,13 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty if (jl_is_typevar(a)) { if (jl_is_typevar(b)) { return (( type_morespecific_((jl_value_t*)((jl_tvar_t*)a)->ub, - (jl_value_t*)((jl_tvar_t*)b)->ub, 0, env) && + (jl_value_t*)((jl_tvar_t*)b)->ub, a0, b0, 0, env) && !type_morespecific_((jl_value_t*)((jl_tvar_t*)a)->lb, - (jl_value_t*)((jl_tvar_t*)b)->lb, 0, env)) || + (jl_value_t*)((jl_tvar_t*)b)->lb, a0, b0, 0, env)) || ( type_morespecific_((jl_value_t*)((jl_tvar_t*)b)->lb, - (jl_value_t*)((jl_tvar_t*)a)->lb, 0, env) && + (jl_value_t*)((jl_tvar_t*)a)->lb, b0, a0, 0, env) && !type_morespecific_((jl_value_t*)((jl_tvar_t*)b)->ub, - (jl_value_t*)((jl_tvar_t*)a)->ub, 0, env))); + (jl_value_t*)((jl_tvar_t*)a)->ub, b0, a0, 0, env))); } if (!jl_is_type(b)) return 0; @@ -3894,7 +4694,7 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty return 1; if (!jl_has_free_typevars(b)) return 0; - if (eq_msp(((jl_tvar_t*)a)->ub, b, env)) + if (eq_msp(((jl_tvar_t*)a)->ub, b, a0, b0, env)) return num_occurs((jl_tvar_t*)a, env) >= 2; } else { @@ -3903,7 +4703,7 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty num_occurs((jl_tvar_t*)a, env) >= 2) return 1; } - return type_morespecific_(((jl_tvar_t*)a)->ub, b, 0, env); + return type_morespecific_(((jl_tvar_t*)a)->ub, b, a0, b0, 0, env); } if (jl_is_typevar(b)) { if (!jl_is_type(a)) @@ -3912,21 +4712,21 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty if (((jl_tvar_t*)b)->ub == jl_bottom_type) return 0; if (jl_has_free_typevars(a)) { - if (type_morespecific_(a, ((jl_tvar_t*)b)->ub, 0, env)) + if (type_morespecific_(a, ((jl_tvar_t*)b)->ub, a0, b0, 0, env)) return 1; - if (eq_msp(a, ((jl_tvar_t*)b)->ub, env)) + if (eq_msp(a, ((jl_tvar_t*)b)->ub, a0, b0, env)) return num_occurs((jl_tvar_t*)b, env) < 2; return 0; } else { if (obviously_disjoint(a, ((jl_tvar_t*)b)->ub, 1)) return 0; - if (type_morespecific_(((jl_tvar_t*)b)->ub, a, 0, env)) + if (type_morespecific_(((jl_tvar_t*)b)->ub, a, b0, a0, 0, env)) return 0; return 1; } } - return type_morespecific_(a, ((jl_tvar_t*)b)->ub, 0, env); + return type_morespecific_(a, ((jl_tvar_t*)b)->ub, a0, b0, 0, env); } if (jl_is_unionall(a)) { @@ -3949,12 +4749,12 @@ JL_DLLEXPORT int jl_type_morespecific(jl_value_t *a, jl_value_t *b) return 0; if (jl_subtype(a, b)) return 1; - return type_morespecific_(a, b, 0, NULL); + return type_morespecific_(a, b, a, b, 0, NULL); } JL_DLLEXPORT int jl_type_morespecific_no_subtype(jl_value_t *a, jl_value_t *b) { - return type_morespecific_(a, b, 0, NULL); + return type_morespecific_(a, b, a, b, 0, NULL); } #ifdef __cplusplus diff --git a/src/support/Makefile b/src/support/Makefile index a884aa5fd47e0..1ee98a4eabdee 100644 --- a/src/support/Makefile +++ b/src/support/Makefile @@ -24,7 +24,7 @@ HEADERS := $(wildcard *.h) $(LIBUV_INC)/uv.h OBJS := $(SRCS:%=$(BUILDDIR)/%.o) DOBJS := $(SRCS:%=$(BUILDDIR)/%.dbg.obj) -FLAGS := $(HFILEDIRS:%=-I%) -I$(LIBUV_INC) -I$(UTF8PROC_INC) -DLIBRARY_EXPORTS -DUTF8PROC_EXPORTS +FLAGS := $(HFILEDIRS:%=-I%) -I$(LIBUV_INC) -I$(UTF8PROC_INC) -DJL_LIBRARY_EXPORTS_INTERNAL -DUTF8PROC_EXPORTS FLAGS += -Wall -Wno-strict-aliasing -fvisibility=hidden -Wpointer-arith -Wundef JCFLAGS += -Wold-style-definition -Wstrict-prototypes -Wc++-compat diff --git a/src/support/MurmurHash3.c b/src/support/MurmurHash3.c index fce7351f90ffe..7eaded17c379f 100644 --- a/src/support/MurmurHash3.c +++ b/src/support/MurmurHash3.c @@ -8,12 +8,11 @@ // non-native version will be less than optimal. #include "MurmurHash3.h" +#include "dtypes.h" //----------------------------------------------------------------------------- // Platform-specific functions and macros -#define FORCE_INLINE inline __attribute__((always_inline)) - static inline uint32_t rotl32 ( uint32_t x, int8_t r ) { return (x << r) | (x >> (32 - r)); @@ -58,11 +57,11 @@ FORCE_INLINE uint64_t fmix64 ( uint64_t k ) //----------------------------------------------------------------------------- -void MurmurHash3_x86_32 ( const void * key, int len, +void MurmurHash3_x86_32 ( const void * key, size_t len, uint32_t seed, void * out ) { const uint8_t * data = (const uint8_t*)key; - const int nblocks = len / 4; + const size_t nblocks = len / 4; uint32_t h1 = seed; @@ -74,7 +73,7 @@ void MurmurHash3_x86_32 ( const void * key, int len, const uint8_t * tail = data + nblocks*4; - for(int i = -nblocks; i; i++) + for(size_t i = -nblocks; i; i++) { uint32_t k1 = jl_load_unaligned_i32(tail + sizeof(uint32_t)*i); @@ -112,11 +111,11 @@ void MurmurHash3_x86_32 ( const void * key, int len, //----------------------------------------------------------------------------- -void MurmurHash3_x86_128 ( const void * key, const int len, +void MurmurHash3_x86_128 ( const void * key, const size_t len, uint32_t seed, void * out ) { const uint8_t * data = (const uint8_t*)key; - const int nblocks = len / 16; + const size_t nblocks = len / 16; uint32_t h1 = seed; uint32_t h2 = seed; @@ -133,7 +132,7 @@ void MurmurHash3_x86_128 ( const void * key, const int len, const uint8_t *tail = data + nblocks*16; - for(int i = -nblocks; i; i++) + for(size_t i = -nblocks; i; i++) { uint32_t k1 = jl_load_unaligned_i32(tail + sizeof(uint32_t)*(i*4 + 0)); uint32_t k2 = jl_load_unaligned_i32(tail + sizeof(uint32_t)*(i*4 + 1)); @@ -218,11 +217,11 @@ void MurmurHash3_x86_128 ( const void * key, const int len, //----------------------------------------------------------------------------- -void MurmurHash3_x64_128 ( const void * key, const int len, +void MurmurHash3_x64_128 ( const void * key, const size_t len, const uint32_t seed, void * out ) { const uint8_t * data = (const uint8_t*)key; - const int nblocks = len / 16; + const size_t nblocks = len / 16; uint64_t h1 = seed; uint64_t h2 = seed; @@ -233,7 +232,7 @@ void MurmurHash3_x64_128 ( const void * key, const int len, //---------- // body - for(int i = 0; i < nblocks; i++) + for(size_t i = 0; i < nblocks; i++) { uint64_t k1 = jl_load_unaligned_i64(data + sizeof(uint64_t)*(i*2 + 0)); uint64_t k2 = jl_load_unaligned_i64(data + sizeof(uint64_t)*(i*2 + 1)); diff --git a/src/support/MurmurHash3.h b/src/support/MurmurHash3.h index e3e7da9df62fa..6137098d6828c 100644 --- a/src/support/MurmurHash3.h +++ b/src/support/MurmurHash3.h @@ -8,14 +8,14 @@ //----------------------------------------------------------------------------- // Platform-specific functions and macros #include - +#include //----------------------------------------------------------------------------- -void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out ); +void MurmurHash3_x86_32 ( const void * key, size_t len, uint32_t seed, void * out ); -void MurmurHash3_x86_128 ( const void * key, int len, uint32_t seed, void * out ); +void MurmurHash3_x86_128 ( const void * key, size_t len, uint32_t seed, void * out ); -void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out ); +void MurmurHash3_x64_128 ( const void * key, size_t len, uint32_t seed, void * out ); //----------------------------------------------------------------------------- diff --git a/src/support/analyzer_annotations.h b/src/support/analyzer_annotations.h index 70b5a273953f1..69827e4d77f37 100644 --- a/src/support/analyzer_annotations.h +++ b/src/support/analyzer_annotations.h @@ -12,6 +12,8 @@ #define JL_PROPAGATES_ROOT __attribute__((annotate("julia_propagates_root"))) #define JL_NOTSAFEPOINT __attribute__((annotate("julia_not_safepoint"))) +#define JL_NOTSAFEPOINT_ENTER __attribute__((annotate("julia_notsafepoint_enter"))) +#define JL_NOTSAFEPOINT_LEAVE __attribute__((annotate("julia_notsafepoint_leave"))) #define JL_MAYBE_UNROOTED __attribute__((annotate("julia_maybe_unrooted"))) #define JL_GLOBALLY_ROOTED __attribute__((annotate("julia_globally_rooted"))) #define JL_ROOTING_ARGUMENT __attribute__((annotate("julia_rooting_argument"))) @@ -20,12 +22,11 @@ #define JL_ALWAYS_LEAFTYPE JL_GLOBALLY_ROOTED #define JL_ROOTS_TEMPORARILY __attribute__((annotate("julia_temporarily_roots"))) #define JL_REQUIRE_ROOTED_SLOT __attribute__((annotate("julia_require_rooted_slot"))) -#define JL_ROOTED_VALUE_COLLECTION __attribute__((annotate("julia_rooted_value_collection"))) #ifdef __cplusplus extern "C" { #endif void JL_GC_PROMISE_ROOTED(void *v) JL_NOTSAFEPOINT; - void jl_may_leak(uintptr_t) JL_NOTSAFEPOINT; + void jl_may_leak(void *v) JL_NOTSAFEPOINT; #ifdef __cplusplus } #endif @@ -34,6 +35,8 @@ extern "C" { #define JL_PROPAGATES_ROOT #define JL_NOTSAFEPOINT +#define JL_NOTSAFEPOINT_ENTER +#define JL_NOTSAFEPOINT_LEAVE #define JL_MAYBE_UNROOTED #define JL_GLOBALLY_ROOTED #define JL_ROOTING_ARGUMENT @@ -42,7 +45,6 @@ extern "C" { #define JL_ALWAYS_LEAFTYPE #define JL_ROOTS_TEMPORARILY #define JL_REQUIRE_ROOTED_SLOT -#define JL_ROOTED_VALUE_COLLECTION #define JL_GC_PROMISE_ROOTED(x) (void)(x) #define jl_may_leak(x) (void)(x) diff --git a/src/support/arraylist.h b/src/support/arraylist.h index 03bfd45f8f525..6ad2f0e2f28c9 100644 --- a/src/support/arraylist.h +++ b/src/support/arraylist.h @@ -25,7 +25,7 @@ void arraylist_free(arraylist_t *a) JL_NOTSAFEPOINT; void arraylist_push(arraylist_t *a, void *elt) JL_NOTSAFEPOINT; void *arraylist_pop(arraylist_t *a) JL_NOTSAFEPOINT; -void arraylist_grow(arraylist_t *a, size_t n) JL_NOTSAFEPOINT; +JL_DLLEXPORT void arraylist_grow(arraylist_t *a, size_t n) JL_NOTSAFEPOINT; typedef struct { uint32_t len; diff --git a/src/support/dirname.c b/src/support/dirname.c deleted file mode 100644 index e023b842ce13d..0000000000000 --- a/src/support/dirname.c +++ /dev/null @@ -1,249 +0,0 @@ -/** - * @file dirname.c - * Copyright 2012, 2013 MinGW.org project - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -/* - * Provides an implementation of the "dirname" function, conforming - * to SUSv3, with extensions to accommodate Win32 drive designators, - * and suitable for use on native Microsoft(R) Win32 platforms. - */ - -#include -#include -#include -#include -#include -#include "dtypes.h" - -#ifdef __cplusplus -extern "C" { -#endif - -JL_DLLEXPORT char *dirname( char *path ) -{ - size_t len; - static JL_THREAD_LOCAL char *retfail = NULL; - - /* to handle path names for files in multibyte character locales, - * we need to set up LC_CTYPE to match the host file system locale. - */ - - char *locale = setlocale( LC_CTYPE, NULL ); - if( locale != NULL ) locale = strdup( locale ); - setlocale( LC_CTYPE, "" ); - - if( path && *path ) - { - /* allocate sufficient local storage space, - * in which to create a wide character reference copy of path - */ - - wchar_t* refcopy = (wchar_t*)alloca((1 + (len = mbstowcs(NULL, path, 0)))*sizeof(wchar_t)); - - /* create the wide character reference copy of path */ - - wchar_t *refpath = refcopy; - len = mbstowcs( refpath, path, len ); - refcopy[ len ] = L'\0'; - - /* SUSv3 identifies a special case, where path is exactly equal to "//"; - * (we will also accept "\\" in the Win32 context, but not "/\" or "\/", - * and neither will we consider paths with an initial drive designator). - * For this special case, SUSv3 allows the implementation to choose to - * return "/" or "//", (or "\" or "\\", since this is Win32); we will - * simply return the path unchanged, (i.e. "//" or "\\"). - */ - - if( (len > 1) && ((refpath[0] == L'/') || (refpath[0] == L'\\')) ) - { - if( (refpath[1] == refpath[0]) && (refpath[2] == L'\0') ) - { - setlocale( LC_CTYPE, locale ); - free( locale ); - return path; - } - } - - /* For all other cases ... - * step over the drive designator, if present ... - */ - - else if( (len > 1) && (refpath[1] == L':') ) - { - /* FIXME: maybe should confirm *refpath is a valid drive designator */ - - refpath += 2; - } - - /* check again, just to ensure we still have a non-empty path name ... */ - - if( *refpath ) - { - /* reproduce the scanning logic of the "basename" function - * to locate the basename component of the current path string, - * (but also remember where the dirname component starts). - */ - - wchar_t *refname, *basename; - for( refname = basename = refpath ; *refpath ; ++refpath ) - { - if( (*refpath == L'/') || (*refpath == L'\\') ) - { - /* we found a dir separator ... - * step over it, and any others which immediately follow it - */ - - while( (*refpath == L'/') || (*refpath == L'\\') ) - ++refpath; - - /* if we didn't reach the end of the path string ... */ - - if( *refpath ) - - /* then we have a new candidate for the base name */ - - basename = refpath; - - else - - /* we struck an early termination of the path string, - * with trailing dir separators following the base name, - * so break out of the for loop, to avoid overrun. - */ - - break; - } - } - - /* now check, - * to confirm that we have distinct dirname and basename components - */ - - if( basename > refname ) - { - /* and, when we do ... - * backtrack over all trailing separators on the dirname component, - * (but preserve exactly two initial dirname separators, if identical), - * and add a NUL terminator in their place. - */ - - do --basename; - while( (basename > refname) && ((*basename == L'/') || (*basename == L'\\')) ); - if( (basename == refname) && ((refname[0] == L'/') || (refname[0] == L'\\')) - && (refname[1] == refname[0]) && (refname[2] != L'/') && (refname[2] != L'\\') ) - ++basename; - *++basename = L'\0'; - - /* if the resultant dirname begins with EXACTLY two dir separators, - * AND both are identical, then we preserve them. - */ - - refpath = refcopy; - while( ((*refpath == L'/') || (*refpath == L'\\')) ) - ++refpath; - if( ((refpath - refcopy) > 2) || (refcopy[1] != refcopy[0]) ) - refpath = refcopy; - - /* and finally ... - * we remove any residual, redundantly duplicated separators from the dirname, - * reterminate, and return it. - */ - - refname = refpath; - while( *refpath ) - { - if( ((*refname++ = *refpath) == L'/') || (*refpath++ == L'\\') ) - { - while( (*refpath == L'/') || (*refpath == L'\\') ) - ++refpath; - } - } - *refname = L'\0'; - - /* finally ... - * transform the resolved dirname back into the multibyte char domain, - * restore the caller's locale, and return the resultant dirname - */ - - if( (len = wcstombs( path, refcopy, len )) != (size_t)(-1) ) - path[ len ] = '\0'; - } - - else - { - /* either there were no dirname separators in the path name, - * or there was nothing else ... - */ - - if( (*refname == L'/') || (*refname == L'\\') ) - { - /* it was all separators, so return one */ - - ++refname; - } - - else - { - /* there were no separators, so return '.' */ - - *refname++ = L'.'; - } - - /* add a NUL terminator, in either case, - * then transform to the multibyte char domain, - * using our own buffer - */ - - *refname = L'\0'; - retfail = (char*)realloc( retfail, len = 1 + wcstombs( NULL, refcopy, 0 )); - wcstombs( path = retfail, refcopy, len ); - } - - /* restore caller's locale, clean up, and return the resolved dirname */ - - setlocale( LC_CTYPE, locale ); - free( locale ); - return path; - } - } - - /* path is NULL, or an empty string; default return value is "." ... - * return this in our own buffer, regenerated by wide char transform, - * in case the caller trashed it after a previous call. - */ - - retfail = (char*)realloc( retfail, len = 1 + wcstombs( NULL, L".", 0 )); - wcstombs( retfail, L".", len ); - - /* restore caller's locale, clean up, and return the default dirname */ - - setlocale( LC_CTYPE, locale ); - free( locale ); - return retfail; -} - -#ifdef __cplusplus -} -#endif - -/* $RCSfile: dirname.c,v $$Revision: 1.2 $: end of file */ diff --git a/src/support/dirpath.h b/src/support/dirpath.h index 57c7927f14d55..b2314d571c649 100644 --- a/src/support/dirpath.h +++ b/src/support/dirpath.h @@ -6,14 +6,17 @@ #ifdef _OS_WINDOWS_ #define PATHSEPSTRING "\\" #define PATHLISTSEPSTRING ";" +#if defined(PATH_MAX) #define JL_PATH_MAX PATH_MAX -#if defined(_COMPILER_CLANG_) +#else // _COMPILER_CLANG_ may have the name reversed #define JL_PATH_MAX MAX_PATH #endif #else #define PATHSEPSTRING "/" #define PATHLISTSEPSTRING ":" -#ifndef JL_PATH_MAX // many platforms don't have a max path, we define one anyways +#if defined(PATH_MAX) +#define JL_PATH_MAX PATH_MAX +#else // many platforms don't have a max path, we define one anyways #define JL_PATH_MAX 1024 #endif #endif diff --git a/src/support/dtypes.h b/src/support/dtypes.h index 2df897c7ba554..a30fe85ccc0d0 100644 --- a/src/support/dtypes.h +++ b/src/support/dtypes.h @@ -27,6 +27,16 @@ #define WIN32_LEAN_AND_MEAN #include +#if defined(_COMPILER_MICROSOFT_) && !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) + +/* See https://github.com/JuliaLang/julia/pull/44587 */ +typedef intptr_t ssize_t; +#define SSIZE_MAX INTPTR_MAX +#define _SSIZE_T_ +#define _SSIZE_T_DEFINED + +#endif /* defined(_COMPILER_MICROSOFT_) && !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) */ + #if !defined(_COMPILER_GCC_) #define strtoull _strtoui64 @@ -62,16 +72,24 @@ #ifdef _OS_WINDOWS_ #define STDCALL __stdcall -# ifdef LIBRARY_EXPORTS +# ifdef JL_LIBRARY_EXPORTS_INTERNAL # define JL_DLLEXPORT __declspec(dllexport) -# else -# define JL_DLLEXPORT __declspec(dllimport) # endif +# ifdef JL_LIBRARY_EXPORTS_CODEGEN +# define JL_DLLEXPORT_CODEGEN __declspec(dllexport) +# endif +#define JL_HIDDEN #define JL_DLLIMPORT __declspec(dllimport) #else #define STDCALL -# define JL_DLLEXPORT __attribute__ ((visibility("default"))) -#define JL_DLLIMPORT +#define JL_DLLIMPORT __attribute__ ((visibility("default"))) +#define JL_HIDDEN __attribute__ ((visibility("hidden"))) +#endif +#ifndef JL_DLLEXPORT +# define JL_DLLEXPORT JL_DLLIMPORT +#endif +#ifndef JL_DLLEXPORT_CODEGEN +# define JL_DLLEXPORT_CODEGEN JL_DLLIMPORT #endif #ifdef _OS_LINUX_ @@ -107,6 +125,7 @@ #define LLT_FREE(x) free(x) #define STATIC_INLINE static inline +#define FORCE_INLINE static inline __attribute__((always_inline)) #if defined(_OS_WINDOWS_) && !defined(_COMPILER_GCC_) # define NOINLINE __declspec(noinline) @@ -116,7 +135,13 @@ # define NOINLINE_DECL(f) f __attribute__((noinline)) #endif -#if defined(__GNUC__) +#ifdef _COMPILER_MICROSOFT_ +# ifdef _P64 +# define JL_ATTRIBUTE_ALIGN_PTRSIZE(x) __declspec(align(8)) x +# else +# define JL_ATTRIBUTE_ALIGN_PTRSIZE(x) __declspec(align(4)) x +# endif +#elif defined(__GNUC__) # define JL_ATTRIBUTE_ALIGN_PTRSIZE(x) x __attribute__ ((aligned (sizeof(void*)))) #else # define JL_ATTRIBUTE_ALIGN_PTRSIZE(x) diff --git a/src/support/htable.h b/src/support/htable.h index 0b5196374e2b6..4f821493beee8 100644 --- a/src/support/htable.h +++ b/src/support/htable.h @@ -47,13 +47,13 @@ int HTNAME##_has(htable_t *h, void *key) JL_NOTSAFEPOINT; \ int HTNAME##_remove(htable_t *h, void *key) JL_NOTSAFEPOINT; \ void **HTNAME##_bp(htable_t *h, void *key) JL_NOTSAFEPOINT; -#define HTPROT_R(HTNAME) \ -void *HTNAME##_get_r(htable_t *h, void *key, void *ctx); \ -void HTNAME##_put_r(htable_t *h, void *key, void *val, void *ctx); \ -void HTNAME##_adjoin_r(htable_t *h, void *key, void *val, void *ctx); \ -int HTNAME##_has_r(htable_t *h, void *key, void *ctx); \ -int HTNAME##_remove_r(htable_t *h, void *key, void *ctx); \ -void **HTNAME##_bp_r(htable_t *h, void *key, void *ctx); +#define HTPROT_R(HTNAME) \ +void *HTNAME##_get_r(htable_t *h, void *key, void *ctx) JL_NOTSAFEPOINT; \ +void HTNAME##_put_r(htable_t *h, void *key, void *val, void *ctx) JL_NOTSAFEPOINT; \ +void HTNAME##_adjoin_r(htable_t *h, void *key, void *val, void *ctx) JL_NOTSAFEPOINT; \ +int HTNAME##_has_r(htable_t *h, void *key, void *ctx) JL_NOTSAFEPOINT; \ +int HTNAME##_remove_r(htable_t *h, void *key, void *ctx) JL_NOTSAFEPOINT; \ +void **HTNAME##_bp_r(htable_t *h, void *key, void *ctx) JL_NOTSAFEPOINT; #ifdef __cplusplus } diff --git a/src/support/ios.c b/src/support/ios.c index c0f1c92572b78..b5a168f705603 100644 --- a/src/support/ios.c +++ b/src/support/ios.c @@ -196,6 +196,9 @@ static char *_buf_realloc(ios_t *s, size_t sz) if (sz <= s->maxsize) return s->buf; + if (!s->growable) + return NULL; + if (s->ownbuf && s->buf != &s->local[0]) { // if we own the buffer we're free to resize it temp = (char*)LLT_REALLOC(s->buf, sz); @@ -892,6 +895,7 @@ static void _ios_init(ios_t *s) s->readable = 1; s->writable = 1; s->rereadable = 0; + s->growable = 1; } /* stream object initializers. we do no allocation. */ @@ -935,9 +939,11 @@ ios_t *ios_file(ios_t *s, const char *fname, int rd, int wr, int create, int tru { int flags; int fd; - if (!(rd || wr)) + if (!(rd || wr)) { // must specify read and/or write + errno = EINVAL; goto open_file_err; + } flags = wr ? (rd ? O_RDWR : O_WRONLY) : O_RDONLY; if (create) flags |= O_CREAT; if (trunc) flags |= O_TRUNC; @@ -1078,7 +1084,7 @@ int ios_putc(int c, ios_t *s) int ios_getc(ios_t *s) { - char ch; + char ch = 0; if (s->state == bst_rd && s->bpos < s->size) { ch = s->buf[s->bpos++]; } diff --git a/src/support/ios.h b/src/support/ios.h index e5d83ec974a2b..2547555b5585d 100644 --- a/src/support/ios.h +++ b/src/support/ios.h @@ -4,6 +4,7 @@ #define JL_IOS_H #include +#include #include "analyzer_annotations.h" #ifdef __cplusplus @@ -18,13 +19,13 @@ extern "C" { typedef enum { bm_none=1000, bm_line, bm_block, bm_mem } bufmode_t; typedef enum { bst_none, bst_rd, bst_wr } bufstate_t; -#define IOS_INLSIZE 54 +#define IOS_INLSIZE 83 #define IOS_BUFSIZE 32768 #ifdef _P64 -#define ON_P64(x) x +#define IF_P64(x,y) x #else -#define ON_P64(x) +#define IF_P64(x,y) y #endif // We allow ios_t as a cvalue in flisp, which only guarantees pointer @@ -35,10 +36,8 @@ JL_ATTRIBUTE_ALIGN_PTRSIZE(typedef struct { // in general, you can do any operation in any state. char *buf; // start of buffer - int errcode; - - ON_P64(int _pad_bm;) // put bm at same offset as type field of uv_stream_s - bufmode_t bm; // + IF_P64(int64_t userdata;, int errcode;) + bufmode_t bm; // bm must be at same offset as type field of uv_stream_s bufstate_t state; int64_t maxsize; // space allocated to buffer @@ -50,6 +49,8 @@ JL_ATTRIBUTE_ALIGN_PTRSIZE(typedef struct { size_t lineno; // current line number size_t u_colno; // current column number (in Unicode charwidths) + IF_P64(int errcode;, int64_t userdata;) + // pointer-size integer to support platforms where it might have // to be a pointer long fd; @@ -73,11 +74,14 @@ JL_ATTRIBUTE_ALIGN_PTRSIZE(typedef struct { // request durable writes (fsync) // unsigned char durable:1; - int64_t userdata; + // this declares that the buffer should not be (re-)alloc'd when + // attempting to write beyond its current maxsize. + unsigned char growable:1; + char local[IOS_INLSIZE]; } ios_t); -#undef ON_P64 +#undef IF_P64 extern void (*ios_set_io_wait_func)(int); /* low-level interface functions */ @@ -127,8 +131,8 @@ void ios_init_stdstreams(void); /* high-level functions - output */ JL_DLLEXPORT int ios_pututf8(ios_t *s, uint32_t wc); -JL_DLLEXPORT int ios_printf(ios_t *s, const char *format, ...); -JL_DLLEXPORT int ios_vprintf(ios_t *s, const char *format, va_list args); +JL_DLLEXPORT int ios_printf(ios_t *s, const char *format, ...) JL_NOTSAFEPOINT; +JL_DLLEXPORT int ios_vprintf(ios_t *s, const char *format, va_list args) JL_NOTSAFEPOINT; /* high-level stream functions - input */ JL_DLLEXPORT int ios_getutf8(ios_t *s, uint32_t *pwc); diff --git a/src/support/libsupportinit.c b/src/support/libsupportinit.c index 897aea944237e..c1afde186e6cd 100644 --- a/src/support/libsupportinit.c +++ b/src/support/libsupportinit.c @@ -3,7 +3,7 @@ #include #include "libsupport.h" -#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L +#ifndef _OS_WINDOWS_ #include #endif @@ -25,7 +25,7 @@ void libsupport_init(void) if (!isInitialized) { ios_init_stdstreams(); isInitialized = 1; -#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L +#ifndef _OS_WINDOWS_ // Raise the open file descriptor limit. { struct rlimit rl; diff --git a/src/support/platform.h b/src/support/platform.h index bb960f54d3c4e..56f8cafbc89fa 100644 --- a/src/support/platform.h +++ b/src/support/platform.h @@ -37,28 +37,43 @@ #define _COMPILER_CLANG_ #elif defined(__GNUC__) #define _COMPILER_GCC_ +#elif defined(_MSC_VER) +#define _COMPILER_MICROSOFT_ #else #error Unsupported compiler #endif + +#define JL_NO_ASAN +#define JL_NO_MSAN +#define JL_NO_TSAN #if defined(__has_feature) // Clang flavor #if __has_feature(address_sanitizer) #define _COMPILER_ASAN_ENABLED_ +#undef JL_NO_ASAN +#define JL_NO_ASAN __attribute__((no_sanitize("address"))) #endif #if __has_feature(memory_sanitizer) #define _COMPILER_MSAN_ENABLED_ +#undef JL_NO_MSAN +#define JL_NO_MSAN __attribute__((no_sanitize("memory"))) #endif #if __has_feature(thread_sanitizer) #if __clang_major__ < 11 #error Thread sanitizer runtime libraries in clang < 11 leak memory and cannot be used #endif #define _COMPILER_TSAN_ENABLED_ +#undef JL_NO_TSAN +#define JL_NO_TSAN __attribute__((no_sanitize("thread"))) #endif #else // GCC flavor #if defined(__SANITIZE_ADDRESS__) #define _COMPILER_ASAN_ENABLED_ +#undef JL_NO_ASAN +#define JL_NO_ASAN __attribute__((no_sanitize("address"))) #endif #endif // __has_feature +#define JL_NO_SANITIZE JL_NO_ASAN JL_NO_MSAN JL_NO_TSAN /******************************************************************************* * OS * diff --git a/src/support/rle.h b/src/support/rle.h index f85d9f35c4b80..bd2fdafc0f79f 100644 --- a/src/support/rle.h +++ b/src/support/rle.h @@ -10,6 +10,7 @@ extern "C" { #include #include #include +#include "analyzer_annotations.h" /* Run-length encoding (RLE) utilities */ /* In the RLE table, even indexes encode the key (the item classification), odd indexes encode the item index */ @@ -28,8 +29,8 @@ typedef struct _rle_iter_state_t { uint64_t key; // current identifier } rle_iter_state; -rle_iter_state rle_iter_init(/* implicit value of key for indexes prior to first explicit rle pair */ uint64_t key0); -int rle_iter_increment(rle_iter_state *state, /* number of items */ size_t len, uint64_t *rletable, /*length of rletable */ size_t npairs); +rle_iter_state rle_iter_init(/* implicit value of key for indexes prior to first explicit rle pair */ uint64_t key0) JL_NOTSAFEPOINT; +int rle_iter_increment(rle_iter_state *state, /* number of items */ size_t len, uint64_t *rletable, /*length of rletable */ size_t npairs) JL_NOTSAFEPOINT; /* indexing */ typedef struct { @@ -37,8 +38,8 @@ typedef struct { int index; // number of preceding items in the list with the same key } rle_reference; -void rle_index_to_reference(rle_reference *rr, /* item index */ size_t i, uint64_t *rletable, size_t npairs, uint64_t key0); -size_t rle_reference_to_index(rle_reference *rr, uint64_t *rletable, size_t npairs, uint64_t key0); +void rle_index_to_reference(rle_reference *rr, /* item index */ size_t i, uint64_t *rletable, size_t npairs, uint64_t key0) JL_NOTSAFEPOINT; +size_t rle_reference_to_index(rle_reference *rr, uint64_t *rletable, size_t npairs, uint64_t key0) JL_NOTSAFEPOINT; #ifdef __cplusplus diff --git a/src/symbol.c b/src/symbol.c index 14606c82b9778..c9c0c0e533924 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -35,12 +35,10 @@ static jl_sym_t *mk_symbol(const char *str, size_t len) JL_NOTSAFEPOINT { jl_sym_t *sym; size_t nb = symbol_nbytes(len); - assert(jl_symbol_type && "not initialized"); - jl_taggedvalue_t *tag = (jl_taggedvalue_t*)jl_gc_perm_alloc_nolock(nb, 0, sizeof(void*), 0); sym = (jl_sym_t*)jl_valueof(tag); // set to old marked so that we won't look at it in the GC or write barrier. - tag->header = ((uintptr_t)jl_symbol_type) | GC_OLD_MARKED; + jl_set_typetagof(sym, jl_symbol_tag, GC_OLD_MARKED); jl_atomic_store_relaxed(&sym->left, NULL); jl_atomic_store_relaxed(&sym->right, NULL); sym->hash = hash_symbol(str, len); diff --git a/src/sys.c b/src/sys.c index 2538eaf62163c..2de4bc61a20b8 100644 --- a/src/sys.c +++ b/src/sys.c @@ -27,6 +27,9 @@ #include #include #include + +// For `struct termios` +#include #endif #ifndef _OS_WINDOWS_ @@ -58,12 +61,6 @@ extern "C" { #endif -#if defined(_OS_WINDOWS_) && !defined(_COMPILER_GCC_) -JL_DLLEXPORT char *dirname(char *); -#else -#include -#endif - JL_DLLEXPORT int jl_sizeof_off_t(void) { return sizeof(off_t); } #ifndef _OS_WINDOWS_ JL_DLLEXPORT int jl_sizeof_mode_t(void) { return sizeof(mode_t); } @@ -242,231 +239,6 @@ JL_DLLEXPORT unsigned long jl_geteuid(void) #endif } -JL_DLLEXPORT int jl_os_get_passwd(uv_passwd_t *pwd, unsigned long uid) -{ -#ifdef _OS_WINDOWS_ - return UV_ENOTSUP; -#else - // taken directly from libuv - struct passwd pw; - struct passwd* result; - char* buf; - size_t bufsize; - size_t name_size; - size_t homedir_size; - size_t shell_size; - size_t gecos_size; - long initsize; - int r; - - if (pwd == NULL) - return UV_EINVAL; - - initsize = sysconf(_SC_GETPW_R_SIZE_MAX); - - if (initsize <= 0) - bufsize = 4096; - else - bufsize = (size_t) initsize; - - buf = NULL; - - for (;;) { - free(buf); - buf = (char*)malloc(bufsize); - - if (buf == NULL) - return UV_ENOMEM; - - r = getpwuid_r(uid, &pw, buf, bufsize, &result); - - if (r != ERANGE) - break; - - bufsize *= 2; - } - - if (r != 0) { - free(buf); - return -r; - } - - if (result == NULL) { - free(buf); - return UV_ENOENT; - } - - /* Allocate memory for the username, gecos, shell, and home directory. */ - name_size = strlen(pw.pw_name) + 1; - homedir_size = strlen(pw.pw_dir) + 1; - shell_size = strlen(pw.pw_shell) + 1; - -#ifdef __MVS__ - gecos_size = 0; /* pw_gecos does not exist on zOS. */ -#else - if (pw.pw_gecos != NULL) - gecos_size = strlen(pw.pw_gecos) + 1; - else - gecos_size = 0; -#endif - - pwd->username = (char*)malloc(name_size + - homedir_size + - shell_size + - gecos_size); - - if (pwd->username == NULL) { - free(buf); - return UV_ENOMEM; - } - - /* Copy the username */ - memcpy(pwd->username, pw.pw_name, name_size); - - /* Copy the home directory */ - pwd->homedir = pwd->username + name_size; - memcpy(pwd->homedir, pw.pw_dir, homedir_size); - - /* Copy the shell */ - pwd->shell = pwd->homedir + homedir_size; - memcpy(pwd->shell, pw.pw_shell, shell_size); - - /* Copy the gecos field */ -#ifdef __MVS__ - pwd->gecos = NULL; /* pw_gecos does not exist on zOS. */ -#else - if (pw.pw_gecos == NULL) { - pwd->gecos = NULL; - } else { - pwd->gecos = pwd->shell + shell_size; - memcpy(pwd->gecos, pw.pw_gecos, gecos_size); - } -#endif - - /* Copy the uid and gid */ - pwd->uid = pw.pw_uid; - pwd->gid = pw.pw_gid; - - free(buf); - - return 0; -#endif -} - -typedef struct jl_group_s { - char* groupname; - unsigned long gid; - char** members; -} jl_group_t; - -JL_DLLEXPORT int jl_os_get_group(jl_group_t *grp, unsigned long gid) -{ -#ifdef _OS_WINDOWS_ - return UV_ENOTSUP; -#else - // modified directly from uv_os_get_password - struct group gp; - struct group* result; - char* buf; - char* gr_mem; - size_t bufsize; - size_t name_size; - long members; - size_t mem_size; - long initsize; - int r; - - if (grp == NULL) - return UV_EINVAL; - - initsize = sysconf(_SC_GETGR_R_SIZE_MAX); - - if (initsize <= 0) - bufsize = 4096; - else - bufsize = (size_t) initsize; - - buf = NULL; - - for (;;) { - free(buf); - buf = (char*)malloc(bufsize); - - if (buf == NULL) - return UV_ENOMEM; - - r = getgrgid_r(gid, &gp, buf, bufsize, &result); - - if (r != ERANGE) - break; - - bufsize *= 2; - } - - if (r != 0) { - free(buf); - return -r; - } - - if (result == NULL) { - free(buf); - return UV_ENOENT; - } - - /* Allocate memory for the groupname and members. */ - name_size = strlen(gp.gr_name) + 1; - members = 0; - mem_size = sizeof(char*); - for (r = 0; gp.gr_mem[r] != NULL; r++) { - mem_size += strlen(gp.gr_mem[r]) + 1 + sizeof(char*); - members++; - } - - gr_mem = (char*)malloc(name_size + mem_size); - if (gr_mem == NULL) { - free(buf); - return UV_ENOMEM; - } - - /* Copy the members */ - grp->members = (char**) gr_mem; - grp->members[members] = NULL; - gr_mem = (char*) ((char**) gr_mem + members + 1); - for (r = 0; r < members; r++) { - grp->members[r] = gr_mem; - gr_mem = stpcpy(gr_mem, gp.gr_mem[r]) + 1; - } - assert(gr_mem == (char*)grp->members + mem_size); - - /* Copy the groupname */ - grp->groupname = gr_mem; - memcpy(grp->groupname, gp.gr_name, name_size); - gr_mem += name_size; - - /* Copy the gid */ - grp->gid = gp.gr_gid; - - free(buf); - - return 0; -#endif -} - -JL_DLLEXPORT void jl_os_free_group(jl_group_t *grp) -{ - if (grp == NULL) - return; - - /* - The memory for is allocated in a single uv__malloc() call. The base of the - pointer is stored in grp->members, so that is the only field that needs - to be freed. - */ - free(grp->members); - grp->members = NULL; - grp->groupname = NULL; -} - // --- buffer manipulation --- JL_DLLEXPORT jl_array_t *jl_take_buffer(ios_t *s) @@ -661,6 +433,29 @@ JL_DLLEXPORT int jl_cpu_threads(void) JL_NOTSAFEPOINT #endif } +JL_DLLEXPORT int jl_effective_threads(void) JL_NOTSAFEPOINT +{ + int cpu = jl_cpu_threads(); + int masksize = uv_cpumask_size(); + if (masksize < 0 || jl_running_under_rr(0)) + return cpu; + uv_thread_t tid = uv_thread_self(); + char *cpumask = (char *)calloc(masksize, sizeof(char)); + int err = uv_thread_getaffinity(&tid, cpumask, masksize); + if (err) { + free(cpumask); + jl_safe_printf("WARNING: failed to get thread affinity (%s %d)\n", uv_err_name(err), + err); + return cpu; + } + int n = 0; + for (size_t i = 0; i < masksize; i++) { + n += cpumask[i]; + } + free(cpumask); + return n < cpu ? n : cpu; +} + // -- high resolution timers -- // Returns time in nanosec @@ -722,6 +517,14 @@ JL_DLLEXPORT JL_STREAM *jl_stdin_stream(void) { return JL_STDIN; } JL_DLLEXPORT JL_STREAM *jl_stdout_stream(void) { return JL_STDOUT; } JL_DLLEXPORT JL_STREAM *jl_stderr_stream(void) { return JL_STDERR; } +JL_DLLEXPORT int jl_termios_size(void) { +#if defined(_OS_WINDOWS_) + return 0; +#else + return sizeof(struct termios); +#endif +} + // -- processor native alignment information -- JL_DLLEXPORT void jl_native_alignment(uint_t *int8align, uint_t *int16align, uint_t *int32align, @@ -923,6 +726,40 @@ JL_DLLEXPORT size_t jl_maxrss(void) #endif } +// Simple `rand()` like function, with global seed and added thread-safety +// (but slow and insecure) +static _Atomic(uint64_t) g_rngseed; +JL_DLLEXPORT uint64_t jl_rand(void) JL_NOTSAFEPOINT +{ + uint64_t max = UINT64_MAX; + uint64_t unbias = UINT64_MAX; + uint64_t rngseed0 = jl_atomic_load_relaxed(&g_rngseed); + uint64_t rngseed; + uint64_t rnd; + do { + rngseed = rngseed0; + rnd = cong(max, unbias, &rngseed); + } while (!jl_atomic_cmpswap_relaxed(&g_rngseed, &rngseed0, rngseed)); + return rnd; +} + +JL_DLLEXPORT void jl_srand(uint64_t rngseed) JL_NOTSAFEPOINT +{ + jl_atomic_store_relaxed(&g_rngseed, rngseed); +} + +void jl_init_rand(void) JL_NOTSAFEPOINT +{ + uint64_t rngseed; + if (uv_random(NULL, NULL, &rngseed, sizeof(rngseed), 0, NULL)) { + ios_puts("WARNING: Entropy pool not available to seed RNG; using ad-hoc entropy sources.\n", ios_stderr); + rngseed = uv_hrtime(); + rngseed ^= int64hash(uv_os_getpid()); + } + jl_srand(rngseed); + srand(rngseed); +} + #ifdef __cplusplus } #endif diff --git a/src/task.c b/src/task.c index 1dd4e76b8ba1c..477ae481071a0 100644 --- a/src/task.c +++ b/src/task.c @@ -42,15 +42,27 @@ extern "C" { #endif #if defined(_COMPILER_ASAN_ENABLED_) -static inline void sanitizer_start_switch_fiber(const void* bottom, size_t size) { - __sanitizer_start_switch_fiber(NULL, bottom, size); +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); + else + __sanitizer_start_switch_fiber(&from->ctx.asan_fake_stack, to->stkbuf, to->bufsz); +} +static inline void sanitizer_start_switch_fiber_killed(jl_ptls_t ptls, jl_task_t *to) { + if (to->copy_stack) + __sanitizer_start_switch_fiber(NULL, (char*)ptls->stackbase-ptls->stacksize, ptls->stacksize); + else + __sanitizer_start_switch_fiber(NULL, to->stkbuf, to->bufsz); } -static inline void sanitizer_finish_switch_fiber(void) { - __sanitizer_finish_switch_fiber(NULL, NULL, NULL); +static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *current) { + __sanitizer_finish_switch_fiber(current->ctx.asan_fake_stack, NULL, NULL); + //(const void**)&last->stkbuf, + //&last->bufsz); } #else -static inline void sanitizer_start_switch_fiber(const void* bottom, size_t size) {} -static inline void sanitizer_finish_switch_fiber(void) {} +static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_task_t *from, jl_task_t *to) JL_NOTSAFEPOINT {} +static inline void sanitizer_start_switch_fiber_killed(jl_ptls_t ptls, jl_task_t *to) JL_NOTSAFEPOINT {} +static inline void sanitizer_finish_switch_fiber(jl_task_t *last, jl_task_t *current) JL_NOTSAFEPOINT {} #endif #if defined(_COMPILER_TSAN_ENABLED_) @@ -109,7 +121,11 @@ static inline void sanitizer_finish_switch_fiber(void) {} #define MINSTKSZ 131072 #endif +#ifdef _COMPILER_ASAN_ENABLED_ +#define ROOT_TASK_STACK_ADJUSTMENT 0 +#else #define ROOT_TASK_STACK_ADJUSTMENT 3000000 +#endif #ifdef JL_HAVE_ASYNCIFY // Switching logic is implemented in JavaScript @@ -133,13 +149,62 @@ static int always_copy_stacks = 1; static int always_copy_stacks = 0; #endif +#if defined(_COMPILER_ASAN_ENABLED_) +extern void __asan_get_shadow_mapping(size_t *shadow_scale, size_t *shadow_offset); + +JL_NO_ASAN void *memcpy_noasan(void *dest, const void *src, size_t n) { + char *d = (char*)dest; + const char *s = (const char *)src; + for (size_t i = 0; i < n; ++i) + d[i] = s[i]; + return dest; +} + +JL_NO_ASAN void *memcpy_a16_noasan(uint64_t *dest, const uint64_t *src, size_t nb) { + uint64_t *end = (uint64_t*)((char*)src + nb); + while (src < end) + *(dest++) = *(src++); + return dest; +} + +/* Copy stack are allocated as regular bigval objects and do no go through free_stack, + which would otherwise unpoison it before returning to the GC pool */ +static void asan_free_copy_stack(void *stkbuf, size_t bufsz) { + __asan_unpoison_stack_memory((uintptr_t)stkbuf, bufsz); +} +#else +static void asan_free_copy_stack(void *stkbuf, size_t bufsz) {} +#endif + #ifdef COPY_STACKS -static void memcpy_a16(uint64_t *to, uint64_t *from, size_t nb) +static void JL_NO_ASAN JL_NO_MSAN memcpy_stack_a16(uint64_t *to, uint64_t *from, size_t nb) { +#if defined(_COMPILER_ASAN_ENABLED_) + /* Asan keeps shadow memory for everything on the stack. However, in general, + this function may touch invalid portions of the stack, since it just moves + the stack around. To keep ASAN's stack tracking capability intact, we need + to move the shadow memory along with the stack memory itself. */ + size_t shadow_offset; + size_t shadow_scale; + __asan_get_shadow_mapping(&shadow_scale, &shadow_offset); + uintptr_t from_addr = (((uintptr_t)from) >> shadow_scale) + shadow_offset; + uintptr_t to_addr = (((uintptr_t)to) >> shadow_scale) + shadow_offset; + // Make sure that the shadow scale is compatible with the alignment, so + // we can copy whole bytes. + assert(shadow_scale <= 4); + size_t shadow_nb = nb >> shadow_scale; + // Copy over the shadow memory + 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). + 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); //uint64_t *end = (uint64_t*)((char*)from + nb); //while (from < end) // *(to++) = *(from++); +#endif } static void NOINLINE save_stack(jl_ptls_t ptls, jl_task_t *lastt, jl_task_t **pt) @@ -150,6 +215,7 @@ static void NOINLINE save_stack(jl_ptls_t ptls, jl_task_t *lastt, jl_task_t **pt size_t nb = stackbase - frame_addr; void *buf; if (lastt->bufsz < nb) { + asan_free_copy_stack(lastt->stkbuf, lastt->bufsz); buf = (void*)jl_gc_alloc_buf(ptls, nb); lastt->stkbuf = buf; lastt->bufsz = nb; @@ -160,14 +226,14 @@ static void NOINLINE save_stack(jl_ptls_t ptls, jl_task_t *lastt, jl_task_t **pt *pt = NULL; // clear the gc-root for the target task before copying the stack for saving lastt->copy_stack = nb; lastt->sticky = 1; - memcpy_a16((uint64_t*)buf, (uint64_t*)frame_addr, nb); + memcpy_stack_a16((uint64_t*)buf, (uint64_t*)frame_addr, nb); // this task's stack could have been modified after // it was marked by an incremental collection // move the barrier back instead of walking it again here jl_gc_wb_back(lastt); } -static void NOINLINE JL_NORETURN restore_stack(jl_task_t *t, jl_ptls_t ptls, char *p) +JL_NO_ASAN static void NOINLINE JL_NORETURN restore_stack(jl_task_t *t, jl_ptls_t ptls, char *p) { size_t nb = t->copy_stack; char *_x = (char*)ptls->stackbase - nb; @@ -181,9 +247,8 @@ static void NOINLINE JL_NORETURN restore_stack(jl_task_t *t, jl_ptls_t ptls, cha } void *_y = t->stkbuf; assert(_x != NULL && _y != NULL); - memcpy_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe + memcpy_stack_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe - sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); #if defined(_OS_WINDOWS_) jl_setcontext(&t->ctx.copy_ctx); #else @@ -192,14 +257,14 @@ static void NOINLINE JL_NORETURN restore_stack(jl_task_t *t, jl_ptls_t ptls, cha abort(); // unreachable } -static void restore_stack2(jl_task_t *t, jl_ptls_t ptls, jl_task_t *lastt) +JL_NO_ASAN static void restore_stack2(jl_task_t *t, jl_ptls_t ptls, jl_task_t *lastt) { assert(t->copy_stack && !lastt->copy_stack); size_t nb = t->copy_stack; char *_x = (char*)ptls->stackbase - nb; void *_y = t->stkbuf; assert(_x != NULL && _y != NULL); - memcpy_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe + memcpy_stack_a16((uint64_t*)_x, (uint64_t*)_y, nb); // destroys all but the current stackframe #if defined(JL_HAVE_UNW_CONTEXT) volatile int returns = 0; int r = unw_getcontext(&lastt->ctx.ctx); @@ -213,7 +278,6 @@ static void restore_stack2(jl_task_t *t, jl_ptls_t ptls, jl_task_t *lastt) #else #error COPY_STACKS is incompatible with this platform #endif - sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); tsan_switch_to_copyctx(&t->ctx); #if defined(_OS_WINDOWS_) jl_setcontext(&t->ctx.copy_ctx); @@ -235,8 +299,10 @@ void JL_NORETURN jl_finish_task(jl_task_t *t) jl_atomic_store_release(&t->_state, JL_TASK_STATE_FAILED); else jl_atomic_store_release(&t->_state, JL_TASK_STATE_DONE); - if (t->copy_stack) // early free of stkbuf + if (t->copy_stack) { // early free of stkbuf + asan_free_copy_stack(t->stkbuf, t->bufsz); t->stkbuf = NULL; + } // ensure that state is cleared ct->ptls->in_finalizer = 0; ct->ptls->in_pure_callback = 0; @@ -254,7 +320,7 @@ void JL_NORETURN jl_finish_task(jl_task_t *t) jl_apply(args, 2); } JL_CATCH { - jl_no_exc_handler(jl_current_exception()); + jl_no_exc_handler(jl_current_exception(), ct); } } jl_gc_debug_critical_error(); @@ -265,7 +331,8 @@ JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *ptid { size_t off = 0; #ifndef _OS_WINDOWS_ - if (jl_all_tls_states[0]->root_task == task) { + jl_ptls_t ptls0 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; + if (ptls0->root_task == task) { // See jl_init_root_task(). The root task of the main thread // has its buffer enlarged by an artificial 3000000 bytes, but // that means that the start of the buffer usually points to @@ -306,7 +373,8 @@ JL_DLLEXPORT void jl_active_task_stack(jl_task_t *task, else if (task->stkbuf) { *total_start = *active_start = (char*)task->stkbuf; #ifndef _OS_WINDOWS_ - if (jl_all_tls_states[0]->root_task == task) { + jl_ptls_t ptls0 = jl_atomic_load_relaxed(&jl_all_tls_states)[0]; + if (ptls0->root_task == task) { // See jl_init_root_task(). The root task of the main thread // has its buffer enlarged by an artificial 3000000 bytes, but // that means that the start of the buffer usually points to @@ -362,7 +430,7 @@ JL_DLLEXPORT jl_task_t *jl_get_next_task(void) JL_NOTSAFEPOINT const char tsan_state_corruption[] = "TSAN state corrupted. Exiting HARD!\n"; #endif -static void ctx_switch(jl_task_t *lastt) +JL_NO_ASAN static void ctx_switch(jl_task_t *lastt) { jl_ptls_t ptls = lastt->ptls; jl_task_t **pt = &ptls->next_task; @@ -406,6 +474,7 @@ static void ctx_switch(jl_task_t *lastt) if (killed) { *pt = NULL; // can't fail after here: clear the gc-root for the target task now lastt->gcstack = NULL; + lastt->eh = NULL; if (!lastt->copy_stack && lastt->stkbuf) { // early free of stkbuf back to the pool jl_release_task_stack(ptls, lastt); @@ -416,7 +485,7 @@ static void ctx_switch(jl_task_t *lastt) if (lastt->copy_stack) { // save the old copy-stack save_stack(ptls, lastt, pt); // allocates (gc-safepoint, and can also fail) if (jl_setjmp(lastt->ctx.copy_ctx.uc_mcontext, 0)) { - sanitizer_finish_switch_fiber(); + sanitizer_finish_switch_fiber(ptls->previous_task, jl_atomic_load_relaxed(&ptls->current_task)); // TODO: mutex unlock the thread we just switched from return; } @@ -441,12 +510,25 @@ static void ctx_switch(jl_task_t *lastt) if (t->started) { #ifdef COPY_STACKS if (t->copy_stack) { - if (!killed && !lastt->copy_stack) + if (lastt->copy_stack) { + // Switching from copystack to copystack. Clear any shadow stack + // memory above the saved shadow stack. + uintptr_t stacktop = (uintptr_t)ptls->stackbase - t->copy_stack; + uintptr_t stackbottom = ((uintptr_t)jl_get_frame_addr() & ~15); + if (stackbottom < stacktop) + asan_unpoison_stack_memory(stackbottom, stacktop-stackbottom); + } + if (!killed && !lastt->copy_stack) { + sanitizer_start_switch_fiber(ptls, lastt, t); restore_stack2(t, ptls, lastt); - else { + } else { tsan_switch_to_copyctx(&t->ctx); - if (killed) + if (killed) { + sanitizer_start_switch_fiber_killed(ptls, t); tsan_destroy_copyctx(ptls, &lastt->ctx); + } else { + sanitizer_start_switch_fiber(ptls, lastt, t); + } if (lastt->copy_stack) { restore_stack(t, ptls, NULL); // (doesn't return) @@ -459,14 +541,26 @@ static void ctx_switch(jl_task_t *lastt) else #endif { - sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); + if (lastt->copy_stack) { + // Switching away from a copystack to a non-copystack. Clear + // the whole shadow stack now, because otherwise we won't know + // how much stack memory to clear the next time we switch to + // a copystack. + uintptr_t stacktop = (uintptr_t)ptls->stackbase; + uintptr_t stackbottom = ((uintptr_t)jl_get_frame_addr() & ~15); + // We're not restoring the stack, but we still need to unpoison the + // stack, so it starts with a pristine stack. + asan_unpoison_stack_memory(stackbottom, stacktop-stackbottom); + } if (killed) { + sanitizer_start_switch_fiber_killed(ptls, t); tsan_switch_to_ctx(&t->ctx); tsan_destroy_ctx(ptls, &lastt->ctx); jl_set_fiber(&t->ctx); // (doesn't return) abort(); // unreachable } else { + sanitizer_start_switch_fiber(ptls, lastt, t); if (lastt->copy_stack) { // Resume at the jl_setjmp earlier in this function, // don't do a full task swap @@ -480,11 +574,20 @@ static void ctx_switch(jl_task_t *lastt) } } else { - sanitizer_start_switch_fiber(t->stkbuf, t->bufsz); + if (lastt->copy_stack) { + uintptr_t stacktop = (uintptr_t)ptls->stackbase; + uintptr_t stackbottom = ((uintptr_t)jl_get_frame_addr() & ~15); + // We're not restoring the stack, but we still need to unpoison the + // stack, so it starts with a pristine stack. + asan_unpoison_stack_memory(stackbottom, stacktop-stackbottom); + } if (t->copy_stack && always_copy_stacks) { tsan_switch_to_ctx(&t->ctx); if (killed) { + sanitizer_start_switch_fiber_killed(ptls, t); tsan_destroy_ctx(ptls, &lastt->ctx); + } else { + sanitizer_start_switch_fiber(ptls, lastt, t); } #ifdef COPY_STACKS #if defined(_OS_WINDOWS_) @@ -497,12 +600,14 @@ static void ctx_switch(jl_task_t *lastt) } else { if (killed) { + sanitizer_start_switch_fiber_killed(ptls, t); tsan_switch_to_ctx(&t->ctx); tsan_destroy_ctx(ptls, &lastt->ctx); jl_start_fiber_set(&t->ctx); // (doesn't return) abort(); } - else if (lastt->copy_stack) { + sanitizer_start_switch_fiber(ptls, lastt, t); + if (lastt->copy_stack) { // Resume at the jl_setjmp earlier in this function tsan_switch_to_ctx(&t->ctx); jl_start_fiber_set(&t->ctx); // (doesn't return) @@ -513,10 +618,10 @@ static void ctx_switch(jl_task_t *lastt) } } } - sanitizer_finish_switch_fiber(); + sanitizer_finish_switch_fiber(ptls->previous_task, jl_atomic_load_relaxed(&ptls->current_task)); } -JL_DLLEXPORT void jl_switch(void) +JL_DLLEXPORT void jl_switch(void) JL_NOTSAFEPOINT_LEAVE JL_NOTSAFEPOINT_ENTER { jl_task_t *ct = jl_current_task; jl_ptls_t ptls = ct->ptls; @@ -524,6 +629,7 @@ JL_DLLEXPORT void jl_switch(void) if (t == ct) { return; } + int8_t gc_state = jl_gc_unsafe_enter(ptls); if (t->started && t->stkbuf == NULL) jl_error("attempt to switch to exited task"); if (ptls->in_finalizer) @@ -537,17 +643,10 @@ JL_DLLEXPORT void jl_switch(void) // Store old values on the stack and reset sig_atomic_t defer_signal = ptls->defer_signal; - int8_t gc_state = jl_gc_unsafe_enter(ptls); int finalizers_inhibited = ptls->finalizers_inhibited; ptls->finalizers_inhibited = 0; -#ifdef ENABLE_TIMINGS - jl_timing_block_t *blk = ptls->timing_stack; - if (blk) - jl_timing_block_stop(blk); - ptls->timing_stack = NULL; -#endif - + jl_timing_block_t *blk = jl_timing_block_exit_task(ct, ptls); ctx_switch(ct); #ifdef MIGRATE_TASKS @@ -567,32 +666,24 @@ JL_DLLEXPORT void jl_switch(void) 0 != ct->ptls && 0 == ptls->finalizers_inhibited); ptls->finalizers_inhibited = finalizers_inhibited; + jl_timing_block_enter_task(ct, ptls, blk); (void)blk; -#ifdef ENABLE_TIMINGS - assert(ptls->timing_stack == NULL); - ptls->timing_stack = blk; - if (blk) - jl_timing_block_start(blk); -#else - (void)ct; -#endif - - jl_gc_unsafe_leave(ptls, gc_state); sig_atomic_t other_defer_signal = ptls->defer_signal; ptls->defer_signal = defer_signal; if (other_defer_signal && !defer_signal) jl_sigint_safepoint(ptls); JL_PROBE_RT_RUN_TASK(ct); + jl_gc_unsafe_leave(ptls, gc_state); } -JL_DLLEXPORT void jl_switchto(jl_task_t **pt) +JL_DLLEXPORT void jl_switchto(jl_task_t **pt) JL_NOTSAFEPOINT_ENTER // n.b. this does not actually enter a safepoint { jl_set_next_task(*pt); jl_switch(); } -JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e) +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. @@ -603,41 +694,67 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e) jl_static_show((JL_STREAM*)STDERR_FILENO, e); jl_printf((JL_STREAM*)STDERR_FILENO, "\n"); jlbacktrace(); // written to STDERR_FILENO + if (ct == NULL) + jl_raise(6); jl_exit(1); } -// yield to exception handler -static void JL_NORETURN throw_internal(jl_task_t *ct, jl_value_t *exception JL_MAYBE_UNROOTED) -{ - assert(!jl_get_safe_restore()); - jl_ptls_t ptls = ct->ptls; - ptls->io_wait = 0; - JL_GC_PUSH1(&exception); - jl_gc_unsafe_enter(ptls); - if (exception) { - // 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->excstack, exception, - ptls->bt_data, ptls->bt_size); - ptls->bt_size = 0; - } - assert(ct->excstack && ct->excstack->top); - jl_handler_t *eh = ct->eh; - if (eh != NULL) { +/* throw_internal - yield to exception handler */ + #ifdef ENABLE_TIMINGS - jl_timing_block_t *cur_block = ptls->timing_stack; - while (cur_block && eh->timing_stack != cur_block) { - cur_block = jl_pop_timing_block(cur_block); - } +#define pop_timings_stack() \ + jl_timing_block_t *cur_block = ptls->timing_stack; \ + while (cur_block && eh->timing_stack != cur_block) { \ + cur_block = jl_pop_timing_block(cur_block); \ + } \ assert(cur_block == eh->timing_stack); +#else +#define pop_timings_stack() /* Nothing */ #endif - jl_longjmp(eh->eh_ctx, 1); - } - else { - jl_no_exc_handler(exception); - } + +#define throw_internal_body(altstack) \ + assert(!jl_get_safe_restore()); \ + jl_ptls_t ptls = ct->ptls; \ + ptls->io_wait = 0; \ + jl_gc_unsafe_enter(ptls); \ + if (exception) { \ + /* 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->excstack, exception, \ + ptls->bt_data, ptls->bt_size); \ + ptls->bt_size = 0; \ + } \ + assert(ct->excstack && ct->excstack->top); \ + jl_handler_t *eh = ct->eh; \ + if (eh != NULL) { \ + if (altstack) ptls->sig_exception = NULL; \ + pop_timings_stack() \ + asan_unpoison_task_stack(ct, &eh->eh_ctx); \ + jl_longjmp(eh->eh_ctx, 1); \ + } \ + else { \ + jl_no_exc_handler(exception, ct); \ + } \ assert(0); + +static void JL_NORETURN throw_internal(jl_task_t *ct, jl_value_t *exception JL_MAYBE_UNROOTED) +{ +CFI_NORETURN + JL_GC_PUSH1(&exception); + throw_internal_body(0); + jl_unreachable(); +} + +/* On the signal stack, we don't want to create any asan frames, but we do on the + normal, stack, so we split this function in two, depending on which context + we're calling it in. This also lets us avoid making a GC frame on the altstack, + which might end up getting corrupted if we recur here through another signal. */ +JL_NO_ASAN static void JL_NORETURN throw_internal_altstack(jl_task_t *ct, jl_value_t *exception) +{ +CFI_NORETURN + throw_internal_body(1); + jl_unreachable(); } // record backtrace and raise an error @@ -645,12 +762,13 @@ JL_DLLEXPORT void jl_throw(jl_value_t *e JL_MAYBE_UNROOTED) { assert(e != NULL); jl_jmp_buf *safe_restore = jl_get_safe_restore(); - if (safe_restore) - jl_longjmp(*safe_restore, 1); jl_task_t *ct = jl_get_current_task(); - if (ct == NULL) // During startup - jl_no_exc_handler(e); - JL_GC_PROMISE_ROOTED(ct); + if (safe_restore) { + asan_unpoison_task_stack(ct, safe_restore); + jl_longjmp(*safe_restore, 1); + } + if (ct == NULL) // During startup, or on other threads + jl_no_exc_handler(e, ct); record_backtrace(ct->ptls, 1); throw_internal(ct, e); } @@ -668,17 +786,19 @@ JL_DLLEXPORT void jl_rethrow(void) // Special case throw for errors detected inside signal handlers. This is not // (cannot be) called directly in the signal handler itself, but is returned to // after the signal handler exits. -JL_DLLEXPORT void JL_NORETURN jl_sig_throw(void) +JL_DLLEXPORT JL_NO_ASAN void JL_NORETURN jl_sig_throw(void) { CFI_NORETURN jl_jmp_buf *safe_restore = jl_get_safe_restore(); - if (safe_restore) - jl_longjmp(*safe_restore, 1); jl_task_t *ct = jl_current_task; + if (safe_restore) { + asan_unpoison_task_stack(ct, safe_restore); + jl_longjmp(*safe_restore, 1); + } jl_ptls_t ptls = ct->ptls; jl_value_t *e = ptls->sig_exception; - ptls->sig_exception = NULL; - throw_internal(ct, e); + JL_GC_PROMISE_ROOTED(e); + throw_internal_altstack(ct, e); } JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) @@ -704,14 +824,14 @@ JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) ACM Trans. Math. Softw., 2021. There is a pure Julia implementation in stdlib that tends to be faster when used from - within Julia, due to inlining and more agressive architecture-specific optimizations. + within Julia, due to inlining and more aggressive architecture-specific optimizations. */ -JL_DLLEXPORT uint64_t jl_tasklocal_genrandom(jl_task_t *task) JL_NOTSAFEPOINT +uint64_t jl_genrandom(uint64_t rngState[4]) JL_NOTSAFEPOINT { - uint64_t s0 = task->rngState0; - uint64_t s1 = task->rngState1; - uint64_t s2 = task->rngState2; - uint64_t s3 = task->rngState3; + uint64_t s0 = rngState[0]; + uint64_t s1 = rngState[1]; + uint64_t s2 = rngState[2]; + uint64_t s3 = rngState[3]; uint64_t t = s1 << 17; uint64_t tmp = s0 + s3; @@ -723,41 +843,201 @@ JL_DLLEXPORT uint64_t jl_tasklocal_genrandom(jl_task_t *task) JL_NOTSAFEPOINT s2 ^= t; s3 = (s3 << 45) | (s3 >> 19); - task->rngState0 = s0; - task->rngState1 = s1; - task->rngState2 = s2; - task->rngState3 = s3; + rngState[0] = s0; + rngState[1] = s1; + rngState[2] = s2; + rngState[3] = s3; return res; } -void rng_split(jl_task_t *from, jl_task_t *to) JL_NOTSAFEPOINT +/* +The jl_rng_split function forks a task's RNG state in a way that is essentially +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 LCG (linear congruential generator), 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; which is one of the reasons it's not +recommended for general purpose RNGs unless space is at a premium, but in our +usage invertibility is actually a benefit, as is explained below. + +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 -- 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 common vector +of random weights. 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 must be invertible, which is guaranteed by N being prime. +We take a different approach: we instead limit pedigree coordinates to being +binary instead -- when a task spawns a child, both tasks share the same pedigree +prefix, with the parent appending a zero and the child appending a one. This way +a binary pedigree vector uniquely identifies each task. Moreover, since the +coordinates are binary, the difference between coordinates is always one which +is its own inverse regardless of whether N is prime or not. This allows us to +compute the dot product modulo 2^64 using native machine arithmetic, which is +considerably more efficient and simpler to implement than arithmetic in 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 use the LCG in rngState[4] to derive 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 subsequence +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 binary task 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/SplitMix 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. 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. From that perspective, it's easier to believe +that adding an arbitrary constant to each seed doesn't worsen its randomness. + +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. + +2. It's easier to compute four PCG output variants in parallel. Iterating the + LCG is inherently sequential. Each PCG variant can be computed independently + from the LCG state. All four can even be computed at once with SIMD vector + instructions, but the compiler doesn't currently choose to do that. + +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, which is actually better scaling of collision +resistance than we expect in theory. In theory, with one byte of resistance we +have a 50% chance of some collision at 20, which matches, but four bytes gives a +50% chance of collision at 2^17 and our (reduced size analogue) construction is +still collision free at 2^19. This may be due to the next observation, which guarantees collision avoidance for certain shapes of task trees as a result of using an +invertible RNG to generate weights. + +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 this part of each 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. + +[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 +*/ +void jl_rng_split(uint64_t dst[JL_RNG_SIZE], uint64_t src[JL_RNG_SIZE]) JL_NOTSAFEPOINT { - /* TODO: consider a less ad-hoc construction - Ideally we could just use the output of the random stream to seed the initial - state of the child. Out of an overabundance of caution we multiply with - effectively random coefficients, to break possible self-interactions. - - It is not the goal to mix bits -- we work under the assumption that the - source is well-seeded, and its output looks effectively random. - However, xoshiro has never been studied in the mode where we seed the - initial state with the output of another xoshiro instance. - - Constants have nothing up their sleeve: - 0x02011ce34bce797f == hash(UInt(1))|0x01 - 0x5a94851fb48a6e05 == hash(UInt(2))|0x01 - 0x3688cf5d48899fa7 == hash(UInt(3))|0x01 - 0x867b4bb4c42e5661 == hash(UInt(4))|0x01 - */ - to->rngState0 = 0x02011ce34bce797f * jl_tasklocal_genrandom(from); - to->rngState1 = 0x5a94851fb48a6e05 * jl_tasklocal_genrandom(from); - to->rngState2 = 0x3688cf5d48899fa7 * jl_tasklocal_genrandom(from); - to->rngState3 = 0x867b4bb4c42e5661 * jl_tasklocal_genrandom(from); + // load and advance the internal LCG state + uint64_t x = src[4]; + src[4] = dst[4] = x * 0xd1342543de82ef95 + 1; + // high spectrum multiplier from https://arxiv.org/abs/2001.05304 + + static const uint64_t a[4] = { + 0xe5f8fa077b92a8a8, // random additive offsets... + 0x7a0cd918958c124d, + 0x86222f7d388588d4, + 0xd30cbd35f2b64f52 + }; + static const uint64_t m[4] = { + 0xaef17502108ef2d9, // standard PCG multiplier + 0xf34026eeb86766af, // random odd multipliers... + 0x38fd70ad58dd9fbb, + 0x6677f9b93ab0c04d + }; + + // PCG-RXS-M-XS 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 + } } JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion_future, size_t ssize) { jl_task_t *ct = jl_current_task; jl_task_t *t = (jl_task_t*)jl_gc_alloc(ct->ptls, sizeof(jl_task_t), jl_task_type); + jl_set_typetagof(t, jl_task_tag, 0); JL_PROBE_RT_NEW_TASK(ct, t); t->copy_stack = 0; if (ssize == 0) { @@ -791,17 +1071,20 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion // Inherit logger state from parent task t->logstate = ct->logstate; // Fork task-local random state from parent - rng_split(ct, t); + jl_rng_split(t->rngState, ct->rngState); // there is no active exception handler available on this stack yet t->eh = NULL; t->sticky = 1; t->gcstack = NULL; t->excstack = NULL; t->started = 0; - t->prio = -1; + t->priority = 0; jl_atomic_store_relaxed(&t->tid, t->copy_stack ? jl_atomic_load_relaxed(&ct->tid) : -1); // copy_stacks are always pinned since they can't be moved + t->threadpoolid = ct->threadpoolid; t->ptls = NULL; t->world_age = ct->world_age; + t->reentrant_timing = 0; + jl_timing_init_task(t); #ifdef COPY_STACKS if (!t->copy_stack) { @@ -818,6 +1101,9 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, jl_value_t *completion #endif #ifdef _COMPILER_TSAN_ENABLED_ t->ctx.tsan_state = __tsan_create_fiber(0); +#endif +#ifdef _COMPILER_ASAN_ENABLED_ + t->ctx.asan_fake_stack = NULL; #endif return t; } @@ -829,6 +1115,7 @@ 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) { @@ -891,14 +1178,33 @@ void jl_init_tasks(void) JL_GC_DISABLED #endif } -STATIC_OR_JS void NOINLINE JL_NORETURN start_task(void) +#if defined(_COMPILER_ASAN_ENABLED_) +STATIC_OR_JS void NOINLINE JL_NORETURN _start_task(void); +#endif + +STATIC_OR_JS void NOINLINE JL_NORETURN JL_NO_ASAN start_task(void) +{ +CFI_NORETURN +#if defined(_COMPILER_ASAN_ENABLED_) + // First complete the fiber switch, otherwise ASAN will be confused + // when it unpoisons the stack in _start_task +#ifdef __clang_gcanalyzer__ + jl_task_t *ct = jl_get_current_task(); +#else + jl_task_t *ct = jl_current_task; +#endif + jl_ptls_t ptls = ct->ptls; + sanitizer_finish_switch_fiber(ptls->previous_task, ct); + _start_task(); +} + +STATIC_OR_JS void NOINLINE JL_NORETURN _start_task(void) { CFI_NORETURN +#endif // this runs the first time we switch to a task - sanitizer_finish_switch_fiber(); #ifdef __clang_gcanalyzer__ jl_task_t *ct = jl_get_current_task(); - JL_GC_PROMISE_ROOTED(ct); #else jl_task_t *ct = jl_current_task; #endif @@ -915,6 +1221,7 @@ CFI_NORETURN ct->started = 1; JL_PROBE_RT_START_TASK(ct); + jl_timing_block_enter_task(ct, ptls, NULL); if (jl_atomic_load_relaxed(&ct->_isexception)) { record_backtrace(ptls, 0); jl_push_excstack(&ct->excstack, ct->result, @@ -927,7 +1234,7 @@ CFI_NORETURN ptls->defer_signal = 0; jl_sigint_safepoint(ptls); } - JL_TIMING(ROOT); + JL_TIMING(ROOT, ROOT); res = jl_apply(&ct->start, 1); } JL_CATCH { @@ -1112,7 +1419,7 @@ static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) #endif #if defined(JL_HAVE_ASM) -static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) +JL_NO_ASAN static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) { assert(lastt); #ifdef JL_HAVE_UNW_CONTEXT @@ -1129,7 +1436,7 @@ static void jl_start_fiber_swap(jl_ucontext_t *lastt, jl_ucontext_t *t) tsan_switch_to_ctx(t); jl_start_fiber_set(t); // doesn't return } -static void jl_start_fiber_set(jl_ucontext_t *t) +JL_NO_ASAN static void jl_start_fiber_set(jl_ucontext_t *t) { char *stk = ((char**)&t->ctx)[0]; size_t ssize = ((size_t*)&t->ctx)[1]; @@ -1225,9 +1532,9 @@ static char *jl_alloc_fiber(_jl_ucontext_t *t, size_t *ssize, jl_task_t *owner) _jl_ucontext_t base_ctx; memcpy(&base_ctx, &ptls->base_ctx, sizeof(base_ctx)); sigfillset(&set); - if (sigprocmask(SIG_BLOCK, &set, &oset) != 0) { + if (pthread_sigmask(SIG_BLOCK, &set, &oset) != 0) { jl_free_stack(stk, *ssize); - jl_error("sigprocmask failed"); + jl_error("pthread_sigmask failed"); } uc_stack.ss_sp = stk; uc_stack.ss_size = *ssize; @@ -1259,9 +1566,9 @@ static char *jl_alloc_fiber(_jl_ucontext_t *t, size_t *ssize, jl_task_t *owner) jl_free_stack(stk, *ssize); jl_error("sigaltstack failed"); } - if (sigprocmask(SIG_SETMASK, &oset, NULL) != 0) { + if (pthread_sigmask(SIG_SETMASK, &oset, NULL) != 0) { jl_free_stack(stk, *ssize); - jl_error("sigprocmask failed"); + jl_error("pthread_sigmask failed"); } if (&ptls->base_ctx != t) { memcpy(&t, &ptls->base_ctx, sizeof(base_ctx)); @@ -1328,6 +1635,7 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) if (jl_nothing == NULL) // make a placeholder jl_nothing = jl_gc_permobj(0, jl_nothing_type); jl_task_t *ct = (jl_task_t*)jl_gc_alloc(ptls, sizeof(jl_task_t), jl_task_type); + jl_set_typetagof(ct, jl_task_tag, 0); memset(ct, 0, sizeof(jl_task_t)); void *stack = stack_lo; size_t ssize = (char*)stack_hi - (char*)stack_lo; @@ -1347,6 +1655,12 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) ct->stkbuf = stack; ct->bufsz = ssize; } + +#ifdef USE_TRACY + char *unique_string = (char *)malloc(strlen("Root") + 1); + strcpy(unique_string, "Root"); + ct->name = unique_string; +#endif ct->started = 1; ct->next = jl_nothing; ct->queue = jl_nothing; @@ -1361,9 +1675,11 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) ct->gcstack = NULL; ct->excstack = NULL; jl_atomic_store_relaxed(&ct->tid, ptls->tid); + ct->threadpoolid = jl_threadpoolid(ptls->tid); ct->sticky = 1; ct->ptls = ptls; ct->world_age = 1; // OK to run Julia code on this task + ct->reentrant_timing = 0; ptls->root_task = ct; jl_atomic_store_relaxed(&ptls->current_task, ct); JL_GC_PROMISE_ROOTED(ct); @@ -1373,6 +1689,11 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) #ifdef _COMPILER_TSAN_ENABLED_ ct->ctx.tsan_state = __tsan_get_current_fiber(); #endif +#ifdef _COMPILER_ASAN_ENABLED_ + ct->ctx.asan_fake_stack = NULL; +#endif + + jl_timing_block_enter_task(ct, ptls, NULL); #ifdef COPY_STACKS // initialize the base_ctx from which all future copy_stacks will be copies @@ -1387,13 +1708,20 @@ jl_task_t *jl_init_root_task(jl_ptls_t ptls, void *stack_lo, void *stack_hi) #endif if (jl_setjmp(ptls->copy_stack_ctx.uc_mcontext, 0)) start_task(); // sanitizer_finish_switch_fiber is part of start_task - return ct; } - ssize = JL_STACK_SIZE; - char *stkbuf = jl_alloc_fiber(&ptls->base_ctx, &ssize, NULL); - ptls->stackbase = stkbuf + ssize; - ptls->stacksize = ssize; + else { + ssize = JL_STACK_SIZE; + char *stkbuf = jl_alloc_fiber(&ptls->base_ctx, &ssize, NULL); + if (stkbuf != NULL) { + ptls->stackbase = stkbuf + ssize; + ptls->stacksize = ssize; + } + } #endif + + if (jl_options.handle_signals == JL_OPTIONS_HANDLE_SIGNALS_ON) + jl_install_thread_signal_handler(ptls); + return ct; } @@ -1407,6 +1735,11 @@ JL_DLLEXPORT int16_t jl_get_task_tid(jl_task_t *t) JL_NOTSAFEPOINT return jl_atomic_load_relaxed(&t->tid); } +JL_DLLEXPORT int8_t jl_get_task_threadpoolid(jl_task_t *t) +{ + return t->threadpoolid; +} + #ifdef _OS_WINDOWS_ #if defined(_CPU_X86_) diff --git a/src/threading.c b/src/threading.c index 2f50783dafaf0..83d2e942e960f 100644 --- a/src/threading.c +++ b/src/threading.c @@ -10,10 +10,14 @@ #include "julia_internal.h" #include "julia_assert.h" +#ifdef USE_ITTAPI +#include "ittapi/ittnotify.h" +#endif + // Ref https://www.uclibc.org/docs/tls.pdf // For variant 1 JL_ELF_TLS_INIT_SIZE is the size of the thread control block (TCB) // For variant 2 JL_ELF_TLS_INIT_SIZE is 0 -#ifdef _OS_LINUX_ +#if defined(_OS_LINUX_) || defined(_OS_FREEBSD_) # if defined(_CPU_X86_64_) || defined(_CPU_X86_) # define JL_ELF_TLS_VARIANT 2 # define JL_ELF_TLS_INIT_SIZE 0 @@ -30,24 +34,37 @@ # include #endif +// `ElfW` was added to FreeBSD in 12.3 but we still support 12.2 +#if defined(_OS_FREEBSD_) && !defined(ElfW) +# define ElfW(x) __ElfN(x) +#endif + #ifdef __cplusplus extern "C" { #endif #include "threading.h" +JL_DLLEXPORT _Atomic(uint8_t) jl_measure_compile_time_enabled = 0; +JL_DLLEXPORT _Atomic(uint64_t) jl_cumulative_compile_time = 0; +JL_DLLEXPORT _Atomic(uint64_t) jl_cumulative_recompile_time = 0; + JL_DLLEXPORT void *jl_get_ptls_states(void) { // mostly deprecated: use current_task instead return jl_current_task->ptls; } +static void jl_delete_thread(void*); + #if !defined(_OS_WINDOWS_) +static pthread_key_t jl_task_exit_key; static pthread_key_t jl_safe_restore_key; __attribute__((constructor)) void _jl_init_safe_restore(void) { pthread_key_create(&jl_safe_restore_key, NULL); + pthread_key_create(&jl_task_exit_key, jl_delete_thread); } JL_DLLEXPORT jl_jmp_buf *jl_get_safe_restore(void) @@ -120,21 +137,26 @@ static DWORD jl_safe_restore_key; BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle, IN DWORD nReason, IN LPVOID Reserved) { + jl_task_t *ct; switch (nReason) { case DLL_PROCESS_ATTACH: jl_pgcstack_key = TlsAlloc(); assert(jl_pgcstack_key != TLS_OUT_OF_INDEXES); jl_safe_restore_key = TlsAlloc(); assert(jl_safe_restore_key != TLS_OUT_OF_INDEXES); - // Fall through - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: TlsFree(jl_pgcstack_key); TlsFree(jl_safe_restore_key); break; + case DLL_THREAD_ATTACH: + // will call jl_adopt_thread lazily on-demand + break; + case DLL_THREAD_DETACH: + ct = jl_get_current_task(); + if (ct != NULL) + jl_delete_thread((void*)ct->ptls); + break; } return 1; // success } @@ -241,7 +263,7 @@ static jl_gcframe_t **jl_get_pgcstack_init(void) // are used. Since the address of TLS variables should be constant, // changing the getter address can result in weird crashes. - // This is clearly not thread safe but should be fine since we + // This is clearly not thread-safe but should be fine since we // make sure the tls states callback is finalized before adding // multiple threads # if JL_USE_IFUNC @@ -278,31 +300,57 @@ JL_DLLEXPORT jl_gcframe_t **jl_get_pgcstack(void) JL_GLOBALLY_ROOTED void jl_pgcstack_getkey(jl_get_pgcstack_func **f, jl_pgcstack_key_t *k) { +#ifndef __clang_gcanalyzer__ if (jl_get_pgcstack_cb == jl_get_pgcstack_init) jl_get_pgcstack_init(); +#endif // for codegen *f = jl_get_pgcstack_cb; *k = jl_pgcstack_key; } #endif -jl_ptls_t *jl_all_tls_states JL_GLOBALLY_ROOTED; -JL_DLLEXPORT _Atomic(uint8_t) jl_measure_compile_time_enabled = 0; -JL_DLLEXPORT _Atomic(uint64_t) jl_cumulative_compile_time = 0; +static uv_mutex_t tls_lock; // controls write-access to these variables: +_Atomic(jl_ptls_t*) jl_all_tls_states JL_GLOBALLY_ROOTED; +int jl_all_tls_states_size; +static uv_cond_t cond; // return calling thread's ID -// Also update the suspended_threads list in signals-mach when changing the -// type of the thread id. JL_DLLEXPORT int16_t jl_threadid(void) { return jl_atomic_load_relaxed(&jl_current_task->tid); } +JL_DLLEXPORT int8_t jl_threadpoolid(int16_t tid) JL_NOTSAFEPOINT +{ + int nthreads = jl_atomic_load_acquire(&jl_n_threads); + if (tid < 0 || tid >= nthreads) + jl_error("invalid tid"); + int n = 0; + for (int i = 0; i < jl_n_threadpools; i++) { + n += jl_n_threads_per_pool[i]; + if (tid < n) + return (int8_t)i; + } + return 0; // everything else uses threadpool 0 (though does not become part of any threadpool) +} + jl_ptls_t jl_init_threadtls(int16_t tid) { +#ifndef _OS_WINDOWS_ + if (pthread_getspecific(jl_task_exit_key)) + abort(); +#endif + if (jl_get_pgcstack() != NULL) + abort(); jl_ptls_t ptls = (jl_ptls_t)calloc(1, sizeof(jl_tls_states_t)); +#ifndef _OS_WINDOWS_ + pthread_setspecific(jl_task_exit_key, (void*)ptls); +#endif ptls->system_id = (jl_thread_t)(uintptr_t)uv_thread_self(); - seed_cong(&ptls->rngseed); + ptls->rngseed = jl_rand(); + if (tid == 0) + ptls->disable_gc = 1; #ifdef _OS_WINDOWS_ if (tid == 0) { if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), @@ -313,7 +361,6 @@ jl_ptls_t jl_init_threadtls(int16_t tid) } } #endif - ptls->tid = tid; jl_atomic_store_relaxed(&ptls->gc_state, 0); // GC unsafe // Conditionally initialize the safepoint address. See comment in // `safepoint.c` @@ -331,11 +378,111 @@ jl_ptls_t jl_init_threadtls(int16_t tid) small_arraylist_new(&ptls->locks, 0); jl_init_thread_heap(ptls); - jl_all_tls_states[tid] = ptls; + uv_mutex_init(&ptls->sleep_lock); + uv_cond_init(&ptls->wake_signal); + + uv_mutex_lock(&tls_lock); + jl_ptls_t *allstates = jl_atomic_load_relaxed(&jl_all_tls_states); + if (tid == -1) + tid = jl_atomic_load_relaxed(&jl_n_threads); + ptls->tid = tid; + if (jl_all_tls_states_size <= tid) { + int i, newsize = jl_all_tls_states_size + tid + 2; + jl_ptls_t *newpptls = (jl_ptls_t*)calloc(newsize, sizeof(jl_ptls_t)); + for (i = 0; i < jl_all_tls_states_size; i++) { + newpptls[i] = allstates[i]; + } + jl_atomic_store_release(&jl_all_tls_states, newpptls); + jl_all_tls_states_size = newsize; + jl_gc_add_quiescent(ptls, (void**)allstates, free); + allstates = newpptls; + } + allstates[tid] = ptls; + if (jl_atomic_load_relaxed(&jl_n_threads) < tid + 1) + jl_atomic_store_release(&jl_n_threads, tid + 1); + jl_fence(); + uv_mutex_unlock(&tls_lock); return ptls; } +JL_DLLEXPORT jl_gcframe_t **jl_adopt_thread(void) JL_NOTSAFEPOINT_LEAVE +{ + // initialize this thread (assign tid, create heap, set up root task) + jl_ptls_t ptls = jl_init_threadtls(-1); + void *stack_lo, *stack_hi; + jl_init_stack_limits(0, &stack_lo, &stack_hi); + + (void)jl_gc_unsafe_enter(ptls); + // 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); + uv_random(NULL, NULL, &ct->rngState, sizeof(ct->rngState), 0, NULL); + return &ct->gcstack; +} + +void jl_task_frame_noreturn(jl_task_t *ct) JL_NOTSAFEPOINT; + +static void jl_delete_thread(void *value) JL_NOTSAFEPOINT_ENTER +{ +#ifndef _OS_WINDOWS_ + pthread_setspecific(jl_task_exit_key, NULL); +#endif + jl_ptls_t ptls = (jl_ptls_t)value; + // 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) + // 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 + // this here by blocking. This also synchronizes our read of `current_task` + // (which is the flag we currently use to check the liveness state of a thread). +#ifdef _OS_WINDOWS_ + jl_lock_profile_wr(); +#elif defined(JL_DISABLE_LIBUNWIND) + // nothing +#elif defined(__APPLE__) + jl_lock_profile_wr(); +#else + pthread_mutex_lock(&in_signal_lock); +#endif + // need to clear pgcstack and eh, but we can clear everything now too + jl_task_frame_noreturn(jl_atomic_load_relaxed(&ptls->current_task)); + if (jl_set_task_tid(ptls->root_task, ptls->tid)) { + // the system will probably free this stack memory soon + // so prevent any other thread from accessing it later + jl_task_frame_noreturn(ptls->root_task); + } + else { + // Uh oh. The user cleared the sticky bit so it started running + // elsewhere, then called pthread_exit on this thread. This is not + // recoverable. Though we could just hang here, a fatal message is better. + jl_safe_printf("fatal: thread exited from wrong Task.\n"); + abort(); + } + jl_atomic_store_relaxed(&ptls->current_task, NULL); // dead + // finally, release all of the locks we had grabbed +#ifdef _OS_WINDOWS_ + jl_unlock_profile_wr(); +#elif defined(JL_DISABLE_LIBUNWIND) + // nothing +#elif defined(__APPLE__) + jl_unlock_profile_wr(); +#else + pthread_mutex_unlock(&in_signal_lock); +#endif + // then park in safe-region + (void)jl_gc_safe_enter(ptls); +} + +//// debugging hack: if we are exiting too fast for error message printing on threads, +//// enabling this will stall that first thread just before exiting, to give +//// the other threads time to fail and emit their failure message +//__attribute__((destructor)) static void _waitthreaddeath(void) { sleep(1); } + JL_DLLEXPORT jl_mutex_t jl_codegen_lock; jl_mutex_t typecache_lock; @@ -442,46 +589,100 @@ static void jl_check_tls(void) JL_DLLEXPORT const int jl_tls_elf_support = 0; #endif +extern int gc_first_tid; + // interface to Julia; sets up to make the runtime thread-safe void jl_init_threading(void) { char *cp; + uv_mutex_init(&tls_lock); + uv_cond_init(&cond); #ifdef JL_ELF_TLS_VARIANT jl_check_tls(); #endif - // how many threads available, usable - jl_n_threads = JULIA_NUM_THREADS; - if (jl_options.nthreads < 0) { // --threads=auto - jl_n_threads = jl_cpu_threads(); + // Determine how many threads and pools are requested. This may have been + // specified on the command line (and so are in `jl_options`) or by the + // environment variable. Set the globals `jl_n_threadpools`, `jl_n_threads` + // and `jl_n_threads_per_pool`. + jl_n_threadpools = 2; + int16_t nthreads = JULIA_NUM_THREADS; + int16_t nthreadsi = 0; + char *endptr, *endptri; + + if (jl_options.nthreads != 0) { // --threads specified + nthreads = jl_options.nthreads_per_pool[0]; + if (nthreads < 0) + nthreads = jl_effective_threads(); + if (jl_options.nthreadpools == 2) + nthreadsi = jl_options.nthreads_per_pool[1]; } - else if (jl_options.nthreads > 0) { // --threads=N - jl_n_threads = jl_options.nthreads; + else if ((cp = getenv(NUM_THREADS_NAME))) { // ENV[NUM_THREADS_NAME] specified + if (!strncmp(cp, "auto", 4)) { + nthreads = jl_effective_threads(); + cp += 4; + } + else { + errno = 0; + nthreads = strtol(cp, &endptr, 10); + if (errno != 0 || endptr == cp || nthreads <= 0) + nthreads = 1; + cp = endptr; + } + if (*cp == ',') { + cp++; + if (!strncmp(cp, "auto", 4)) + nthreadsi = 1; + else { + errno = 0; + nthreadsi = strtol(cp, &endptri, 10); + if (errno != 0 || endptri == cp || nthreadsi < 0) + nthreadsi = 0; + } + } } - else if ((cp = getenv(NUM_THREADS_NAME))) { - if (strcmp(cp, "auto")) - jl_n_threads = (uint64_t)strtol(cp, NULL, 10); // ENV[NUM_THREADS_NAME] == "N" - else - jl_n_threads = jl_cpu_threads(); // ENV[NUM_THREADS_NAME] == "auto" + + int16_t ngcthreads = jl_options.ngcthreads - 1; + if (ngcthreads == -1 && + (cp = getenv(NUM_GC_THREADS_NAME))) { // ENV[NUM_GC_THREADS_NAME] specified + + ngcthreads = (uint64_t)strtol(cp, NULL, 10) - 1; } - if (jl_n_threads <= 0) - jl_n_threads = 1; -#ifndef __clang_gcanalyzer__ - jl_all_tls_states = (jl_ptls_t*)calloc(jl_n_threads, sizeof(void*)); -#endif + if (ngcthreads == -1) { + // if `--gcthreads` was not specified, set the number of GC threads + // to half of compute threads + if (nthreads <= 1) { + ngcthreads = 0; + } + else { + ngcthreads = (nthreads / 2) - 1; + } + } + + 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; + + 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; + gc_first_tid = nthreads; } static uv_barrier_t thread_init_done; void jl_start_threads(void) { + int nthreads = jl_atomic_load_relaxed(&jl_n_threads); + int ngcthreads = jl_n_gcthreads; int cpumasksize = uv_cpumask_size(); char *cp; int i, exclusive; uv_thread_t uvtid; - if (cpumasksize < jl_n_threads) // also handles error case - cpumasksize = jl_n_threads; + if (cpumasksize < nthreads) // also handles error case + cpumasksize = nthreads; char *mask = (char*)alloca(cpumasksize); // do we have exclusive use of the machine? default is no @@ -494,7 +695,7 @@ void jl_start_threads(void) // according to a 'compact' policy // non-exclusive: no affinity settings; let the kernel move threads about if (exclusive) { - if (jl_n_threads > jl_cpu_threads()) { + if (nthreads > jl_cpu_threads()) { jl_printf(JL_STDERR, "ERROR: Too many threads requested for %s option.\n", MACHINE_EXCLUSIVE_NAME); exit(1); } @@ -505,21 +706,26 @@ void jl_start_threads(void) mask[0] = 0; } - // The analyzer doesn't know jl_n_threads doesn't change, help it - size_t nthreads = jl_n_threads; - // create threads uv_barrier_init(&thread_init_done, nthreads); + // GC/System threads need to be after the worker threads. + int nworker_threads = nthreads - ngcthreads; + for (i = 1; i < nthreads; ++i) { - jl_threadarg_t *t = (jl_threadarg_t*)malloc_s(sizeof(jl_threadarg_t)); // ownership will be passed to the thread + jl_threadarg_t *t = (jl_threadarg_t *)malloc_s(sizeof(jl_threadarg_t)); // ownership will be passed to the thread t->tid = i; t->barrier = &thread_init_done; - uv_thread_create(&uvtid, jl_threadfun, t); - if (exclusive) { - mask[i] = 1; - uv_thread_setaffinity(&uvtid, mask, NULL, cpumasksize); - mask[i] = 0; + if (i < nworker_threads) { + uv_thread_create(&uvtid, jl_threadfun, t); + if (exclusive) { + mask[i] = 1; + uv_thread_setaffinity(&uvtid, mask, NULL, cpumasksize); + mask[i] = 0; + } + } + else { + uv_thread_create(&uvtid, jl_gc_threadfun, t); } uv_thread_detach(&uvtid); } @@ -552,6 +758,133 @@ JL_DLLEXPORT void jl_exit_threaded_region(void) } } +// Profiling stubs + +void _jl_mutex_init(jl_mutex_t *lock, const char *name) JL_NOTSAFEPOINT +{ + jl_atomic_store_relaxed(&lock->owner, (jl_task_t*)NULL); + lock->count = 0; + jl_profile_lock_init(lock, name); +} + +void _jl_mutex_wait(jl_task_t *self, jl_mutex_t *lock, int safepoint) +{ + jl_task_t *owner = jl_atomic_load_relaxed(&lock->owner); + if (owner == self) { + lock->count++; + return; + } + // Don't use JL_TIMING for instant acquires, results in large blowup of events + jl_profile_lock_start_wait(lock); + if (owner == NULL && jl_atomic_cmpswap(&lock->owner, &owner, self)) { + lock->count = 1; + jl_profile_lock_acquired(lock); + return; + } + JL_TIMING(LOCK_SPIN, LOCK_SPIN); + while (1) { + if (owner == NULL && jl_atomic_cmpswap(&lock->owner, &owner, self)) { + lock->count = 1; + jl_profile_lock_acquired(lock); + return; + } + if (safepoint) { + jl_gc_safepoint_(self->ptls); + } + if (jl_running_under_rr(0)) { + // when running under `rr`, use system mutexes rather than spin locking + uv_mutex_lock(&tls_lock); + if (jl_atomic_load_relaxed(&lock->owner)) + uv_cond_wait(&cond, &tls_lock); + uv_mutex_unlock(&tls_lock); + } + jl_cpu_suspend(); + owner = jl_atomic_load_relaxed(&lock->owner); + } +} + +static void jl_lock_frame_push(jl_task_t *self, jl_mutex_t *lock) +{ + jl_ptls_t ptls = self->ptls; + small_arraylist_t *locks = &ptls->locks; + uint32_t len = locks->len; + if (__unlikely(len >= locks->max)) { + small_arraylist_grow(locks, 1); + } + else { + locks->len = len + 1; + } + locks->items[len] = (void*)lock; +} + +static void jl_lock_frame_pop(jl_task_t *self) +{ + jl_ptls_t ptls = self->ptls; + assert(ptls->locks.len > 0); + ptls->locks.len--; +} + +void _jl_mutex_lock(jl_task_t *self, jl_mutex_t *lock) +{ + JL_SIGATOMIC_BEGIN_self(); + _jl_mutex_wait(self, lock, 1); + jl_lock_frame_push(self, lock); +} + +int _jl_mutex_trylock_nogc(jl_task_t *self, jl_mutex_t *lock) +{ + jl_task_t *owner = jl_atomic_load_acquire(&lock->owner); + if (owner == self) { + lock->count++; + return 1; + } + if (owner == NULL && jl_atomic_cmpswap(&lock->owner, &owner, self)) { + lock->count = 1; + return 1; + } + return 0; +} + +int _jl_mutex_trylock(jl_task_t *self, jl_mutex_t *lock) +{ + int got = _jl_mutex_trylock_nogc(self, lock); + if (got) { + JL_SIGATOMIC_BEGIN_self(); + jl_lock_frame_push(self, lock); + } + return got; +} + +void _jl_mutex_unlock_nogc(jl_mutex_t *lock) +{ +#ifndef __clang_gcanalyzer__ + assert(jl_atomic_load_relaxed(&lock->owner) == jl_current_task && + "Unlocking a lock in a different thread."); + if (--lock->count == 0) { + jl_profile_lock_release_start(lock); + jl_atomic_store_release(&lock->owner, (jl_task_t*)NULL); + jl_cpu_wake(); + if (jl_running_under_rr(0)) { + // when running under `rr`, use system mutexes rather than spin locking + uv_mutex_lock(&tls_lock); + uv_cond_broadcast(&cond); + uv_mutex_unlock(&tls_lock); + } + jl_profile_lock_release_end(lock); + } +#endif +} + +void _jl_mutex_unlock(jl_task_t *self, jl_mutex_t *lock) +{ + _jl_mutex_unlock_nogc(lock); + jl_lock_frame_pop(self); + JL_SIGATOMIC_END_self(); + if (jl_atomic_load_relaxed(&jl_gc_have_pending_finalizers)) { + jl_gc_run_pending_finalizers(self); // may GC + } +} + // Make gc alignment available for threading // see threads.jl alignment diff --git a/src/threading.h b/src/threading.h index 4c6f1e19881f5..40792a2889e44 100644 --- a/src/threading.h +++ b/src/threading.h @@ -12,7 +12,7 @@ extern "C" { #define PROFILE_JL_THREADING 0 -extern jl_ptls_t *jl_all_tls_states JL_GLOBALLY_ROOTED; /* thread local storage */ +extern _Atomic(jl_ptls_t*) jl_all_tls_states JL_GLOBALLY_ROOTED; /* thread local storage */ typedef struct _jl_threadarg_t { int16_t tid; @@ -21,10 +21,11 @@ typedef struct _jl_threadarg_t { } jl_threadarg_t; // each thread must initialize its TLS -jl_ptls_t jl_init_threadtls(int16_t tid); +jl_ptls_t jl_init_threadtls(int16_t tid) JL_NOTSAFEPOINT; // provided by a threading infrastructure void jl_init_threadinginfra(void); +void jl_gc_threadfun(void *arg); void jl_threadfun(void *arg); #ifdef __cplusplus diff --git a/src/timing.c b/src/timing.c index 929a09305f993..2e0dba7c025bc 100644 --- a/src/timing.c +++ b/src/timing.c @@ -6,44 +6,80 @@ #include "options.h" #include "stdio.h" +jl_module_t *jl_module_root(jl_module_t *m); + #ifdef __cplusplus extern "C" { #endif #ifdef ENABLE_TIMINGS -#include "timing.h" #ifndef HAVE_TIMING_SUPPORT #error Timings are not supported on your compiler #endif static uint64_t t0; -JL_DLLEXPORT uint64_t jl_timing_data[(int)JL_TIMING_LAST] = {0}; +#if defined(USE_TRACY) || defined(USE_ITTAPI) +/** + * These sources often generate millions of events / minute. Although Tracy + * can generally keep up with that, those events also bloat the saved ".tracy" + * files, so we disable them by default. + **/ +JL_DLLEXPORT uint64_t jl_timing_enable_mask = ~((1ull << JL_TIMING_ROOT) | + (1ull << JL_TIMING_TYPE_CACHE_LOOKUP) | + (1ull << JL_TIMING_METHOD_MATCH) | + (1ull << JL_TIMING_METHOD_LOOKUP_FAST) | + (1ull << JL_TIMING_AST_COMPRESS) | + (1ull << JL_TIMING_AST_UNCOMPRESS)); +#else +JL_DLLEXPORT uint64_t jl_timing_enable_mask = ~0ull; +#endif + +JL_DLLEXPORT uint64_t jl_timing_counts[(int)JL_TIMING_LAST] = {0}; + +// Used to as an item limit when several strings of metadata can +// potentially be associated with a single timing zone. +JL_DLLEXPORT uint32_t jl_timing_print_limit = 10; + const char *jl_timing_names[(int)JL_TIMING_LAST] = { -#define X(name) #name +#define X(name) #name, JL_TIMING_OWNERS #undef X }; +#ifdef USE_ITTAPI +JL_DLLEXPORT __itt_event jl_timing_ittapi_events[(int)JL_TIMING_EVENT_LAST]; +#endif + void jl_print_timings(void) { uint64_t total_time = cycleclock() - t0; uint64_t root_time = total_time; for (int i = 0; i < JL_TIMING_LAST; i++) { - root_time -= jl_timing_data[i]; + root_time -= jl_timing_counts[i]; } - jl_timing_data[0] = root_time; + jl_timing_counts[0] = root_time; for (int i = 0; i < JL_TIMING_LAST; i++) { - if (jl_timing_data[i] != 0) + if (jl_timing_counts[i] != 0) fprintf(stderr, "%-25s : %5.2f %% %" PRIu64 "\n", jl_timing_names[i], - 100 * (((double)jl_timing_data[i]) / total_time), jl_timing_data[i]); + 100 * (((double)jl_timing_counts[i]) / total_time), jl_timing_counts[i]); } } void jl_init_timing(void) { t0 = cycleclock(); + + _Static_assert(JL_TIMING_EVENT_LAST < sizeof(uint64_t) * CHAR_BIT, "Too many timing events!"); + _Static_assert((int)JL_TIMING_LAST <= (int)JL_TIMING_EVENT_LAST, "More owners than events!"); + + int i __attribute__((unused)) = 0; +#ifdef USE_ITTAPI +#define X(name) jl_timing_ittapi_events[i++] = __itt_event_create(#name, strlen(#name)); + JL_TIMING_EVENTS +#undef X +#endif } void jl_destroy_timing(void) @@ -62,20 +98,265 @@ jl_timing_block_t *jl_pop_timing_block(jl_timing_block_t *cur_block) return cur_block->prev; } -void jl_timing_block_start(jl_timing_block_t *cur_block) +void jl_timing_block_enter_task(jl_task_t *ct, jl_ptls_t ptls, jl_timing_block_t *prev_blk) +{ + if (prev_blk != NULL) { + assert(ptls->timing_stack == NULL); + + ptls->timing_stack = prev_blk; + if (prev_blk != NULL) { + _COUNTS_START(&prev_blk->counts_ctx, cycleclock()); + } + } + +#ifdef USE_TRACY + TracyCFiberEnter(ct->name); +#else + (void)ct; +#endif +} + +jl_timing_block_t *jl_timing_block_exit_task(jl_task_t *ct, jl_ptls_t ptls) +{ +#ifdef USE_TRACY + // Tracy is fairly strict about not leaving a fiber that hasn't + // been entered, which happens often when connecting to a running + // Julia session. + // + // Eventually, Tracy will support telling the server which fibers + // are active upon connection, but until then we work around the + // problem by not explicitly leaving the fiber at all. + // + // Later when we enter the new fiber directly, that will cause the + // the active fiber to be left implicitly. + + //TracyCFiberLeave; +#endif + (void)ct; + + jl_timing_block_t *blk = ptls->timing_stack; + ptls->timing_stack = NULL; + + if (blk != NULL) { + _COUNTS_STOP(&blk->counts_ctx, cycleclock()); + } + return blk; +} + +JL_DLLEXPORT void jl_timing_show(jl_value_t *v, jl_timing_block_t *cur_block) +{ +#ifdef USE_TRACY + ios_t buf; + ios_mem(&buf, IOS_INLSIZE); + buf.growable = 0; // Restrict to inline buffer to avoid allocation + + jl_static_show((JL_STREAM*)&buf, v); + if (buf.size == buf.maxsize) + memset(&buf.buf[IOS_INLSIZE - 3], '.', 3); + + TracyCZoneText(*(cur_block->tracy_ctx), buf.buf, buf.size); +#endif +} + +JL_DLLEXPORT void jl_timing_show_module(jl_module_t *m, jl_timing_block_t *cur_block) +{ +#ifdef USE_TRACY + jl_module_t *root = jl_module_root(m); + if (root == m || root == jl_main_module) { + const char *module_name = jl_symbol_name(m->name); + TracyCZoneText(*(cur_block->tracy_ctx), module_name, strlen(module_name)); + } else { + jl_timing_printf(cur_block, "%s.%s", jl_symbol_name(root->name), jl_symbol_name(m->name)); + } +#endif +} + +JL_DLLEXPORT void jl_timing_show_filename(const char *path, jl_timing_block_t *cur_block) +{ +#ifdef USE_TRACY + const char *filename = gnu_basename(path); + TracyCZoneText(*(cur_block->tracy_ctx), filename, strlen(filename)); +#endif +} + +JL_DLLEXPORT void jl_timing_show_method_instance(jl_method_instance_t *mi, jl_timing_block_t *cur_block) +{ + jl_timing_show_func_sig(mi->specTypes, cur_block); + jl_method_t *def = mi->def.method; + jl_timing_printf(cur_block, "%s:%d in %s", + gnu_basename(jl_symbol_name(def->file)), + def->line, + jl_symbol_name(def->module->name)); +} + +JL_DLLEXPORT void jl_timing_show_method(jl_method_t *method, jl_timing_block_t *cur_block) { - _jl_timing_block_start(cur_block, cycleclock()); + jl_timing_show((jl_value_t *)method, cur_block); + jl_timing_printf(cur_block, "%s:%d in %s", + gnu_basename(jl_symbol_name(method->file)), + method->line, + jl_symbol_name(method->module->name)); } -void jl_timing_block_stop(jl_timing_block_t *cur_block) +JL_DLLEXPORT void jl_timing_show_func_sig(jl_value_t *v, jl_timing_block_t *cur_block) { - _jl_timing_block_stop(cur_block, cycleclock()); +#ifdef USE_TRACY + ios_t buf; + ios_mem(&buf, IOS_INLSIZE); + buf.growable = 0; // Restrict to inline buffer to avoid allocation + + jl_static_show_config_t config = { /* quiet */ 1 }; + jl_static_show_func_sig_((JL_STREAM*)&buf, v, config); + if (buf.size == buf.maxsize) + memset(&buf.buf[IOS_INLSIZE - 3], '.', 3); + + TracyCZoneText(*(cur_block->tracy_ctx), buf.buf, buf.size); +#endif +} + +JL_DLLEXPORT void jl_timing_printf(jl_timing_block_t *cur_block, const char *format, ...) +{ + va_list args; + va_start(args, format); + +#ifdef USE_TRACY + ios_t buf; + ios_mem(&buf, IOS_INLSIZE); + buf.growable = 0; // Restrict to inline buffer to avoid allocation + + jl_vprintf((JL_STREAM*)&buf, format, args); + if (buf.size == buf.maxsize) + memset(&buf.buf[IOS_INLSIZE - 3], '.', 3); + + TracyCZoneText(*(cur_block->tracy_ctx), buf.buf, buf.size); +#endif + va_end(args); +} + +JL_DLLEXPORT void jl_timing_puts(jl_timing_block_t *cur_block, const char *str) +{ +#ifdef USE_TRACY + TracyCZoneText(*(cur_block->tracy_ctx), str, strlen(str)); +#endif +} + +void jl_timing_init_task(jl_task_t *t) +{ +#ifdef USE_TRACY + jl_value_t *start_type = jl_typeof(t->start); + const char *start_name = ""; + if (jl_is_datatype(start_type)) + start_name = jl_symbol_name(((jl_datatype_t *) start_type)->name->name); + + static uint16_t task_id = 1; + + // XXX: Tracy uses this as a handle internally and requires that this + // string live forever, so this allocation is intentionally leaked. + char *fiber_name; + if (start_name[0] == '#') { + jl_method_instance_t *mi = jl_method_lookup(&t->start, 1, jl_get_world_counter()); + const char *filename = gnu_basename(jl_symbol_name(mi->def.method->file)); + const char *module_name = jl_symbol_name(mi->def.method->module->name); + + // 26 characters in "Task 65535 (:0000000 in )\0" + size_t fiber_name_len = strlen(filename) + strlen(module_name) + 26; + fiber_name = (char *)malloc(fiber_name_len); + snprintf(fiber_name, fiber_name_len, "Task %d (%s:%d in %s)", + task_id++, filename, mi->def.method->line, module_name); + } else { + + // 16 characters in "Task 65535 (\"\")\0" + size_t fiber_name_len = strlen(start_name) + 16; + fiber_name = (char *)malloc(fiber_name_len); + snprintf(fiber_name, fiber_name_len, "Task %d (\"%s\")", + task_id++, start_name); + } + + t->name = fiber_name; +#endif +} + +JL_DLLEXPORT int jl_timing_set_enable(const char *subsystem, uint8_t enabled) +{ + for (int i = 0; i < JL_TIMING_LAST; i++) { + if (strcmp(subsystem, jl_timing_names[i]) == 0) { + uint64_t subsystem_bit = (1ul << i); + if (enabled) { + jl_timing_enable_mask |= subsystem_bit; + } else { + jl_timing_enable_mask &= ~subsystem_bit; + } + return 0; + } + } + return -1; +} + +static void jl_timing_set_enable_from_env(void) +{ + const char *env = getenv("JULIA_TIMING_SUBSYSTEMS"); + if (!env) + return; + + // Copy `env`, so that we can modify it + size_t sz = strlen(env) + 1; + char *env_copy = (char *)malloc(sz); + memcpy(env_copy, env, sz); + + char *subsystem = env_copy; + char *ch = subsystem; + uint8_t enable = 1; + while (1) { + // +SUBSYSTEM means enable, -SUBSYSTEM means disable + if (*subsystem == '+' || *subsystem == '-') + enable = (*subsystem++ == '+'); + + if (*ch == ',') { + *ch++ = '\0'; + if ((*subsystem != '\0') && jl_timing_set_enable(subsystem, enable)) + fprintf(stderr, "warning: unable to configure timing for non-existent subsystem \"%s\"\n", subsystem); + + subsystem = ch; + enable = 1; + } + else if (*ch == '\0') { + if ((*subsystem != '\0') && jl_timing_set_enable(subsystem, enable)) + fprintf(stderr, "warning: unable to configure timing for non-existent subsystem \"%s\"\n", subsystem); + + break; + } + else ch++; + } + free(env_copy); +} + +static void jl_timing_set_print_limit_from_env(void) +{ + const char *const env = getenv("JULIA_TIMING_METADATA_PRINT_LIMIT"); + if (!env) + return; + + char *endp; + long value = strtol(env, &endp, 10); + if (*endp == '\0' && value >= 0 && value <= UINT32_MAX) + jl_timing_print_limit = (uint32_t)value; +} + +void jl_timing_apply_env(void) +{ + // JULIA_TIMING_SUBSYSTEMS + jl_timing_set_enable_from_env(); + + // JULIA_TIMING_METADATA_PRINT_LIMIT + jl_timing_set_print_limit_from_env(); } #else void jl_init_timing(void) { } void jl_destroy_timing(void) { } +JL_DLLEXPORT int jl_timing_set_enable(const char *subsystem, uint8_t enabled) { return -1; } +JL_DLLEXPORT uint32_t jl_timing_print_limit = 0; #endif diff --git a/src/timing.h b/src/timing.h index fd84707ad5d2c..2e5147608fb22 100644 --- a/src/timing.h +++ b/src/timing.h @@ -3,91 +3,263 @@ #ifndef JL_TIMING_H #define JL_TIMING_H +#include "julia.h" + +static inline const char *gnu_basename(const char *path) +{ + const char *base = strrchr(path, '/'); +#ifdef _WIN32 + const char *backslash = strrchr(path, '\\'); + if (backslash > base) + base = backslash; +#endif + return base ? base+1 : path; +} + #ifdef __cplusplus extern "C" { #endif + void jl_init_timing(void); -void jl_destroy_timing(void); +void jl_destroy_timing(void) JL_NOTSAFEPOINT; + +// Update the enable bit-mask to enable/disable tracing events for +// the subsystem in `jl_timing_names` matching the provided string. +// +// Returns -1 if no matching sub-system was found. +JL_DLLEXPORT int jl_timing_set_enable(const char *subsystem, uint8_t enabled); + +// Check for environment vars "JULIA_TIMING_METADATA_PRINT_LIMIT" and +// "JULIA_TIMING_SUBSYSTEMS" and if present apply these to the metadata +// print limit and the timings enable mask, respectively. +// +// For example, to enable INFERENCE and METHOD_MATCH and disable GC: +// JULIA_TIMING_SUBSYSTEMS="+INFERENCE,-GC,+METHOD_MATCH" +// +// For example, to increase the metadata item print limit from 10 to 20: +// JULIA_TIMING_METADATA_PRINT_LIMIT=20 +void jl_timing_apply_env(void); + +// Configurable item limit, runtime code should use this to limit printing +// when adding potentially many items of metadata to a single timing zone. +extern JL_DLLEXPORT uint32_t jl_timing_print_limit; + #ifdef __cplusplus } #endif -#ifndef ENABLE_TIMINGS -#define JL_TIMING(owner) +#ifdef __cplusplus +#define HAVE_TIMING_SUPPORT +#elif defined(_COMPILER_CLANG_) +#define HAVE_TIMING_SUPPORT +#elif defined(_COMPILER_GCC_) +#define HAVE_TIMING_SUPPORT +#endif + +#if defined( USE_TRACY ) || defined( USE_ITTAPI ) || defined( USE_TIMING_COUNTS ) +#define ENABLE_TIMINGS +#endif + +#if !defined( ENABLE_TIMINGS ) || !defined( HAVE_TIMING_SUPPORT ) + +#define JL_TIMING(subsystem, event) +#define JL_TIMING_SUSPEND(subsystem, ct) + +#define jl_timing_show(v, b) +#define jl_timing_show_module(m, b) +#define jl_timing_show_filename(f, b) +#define jl_timing_show_method_instance(mi, b) +#define jl_timing_show_method(mi, b) +#define jl_timing_show_func_sig(tt, b) +#define jl_timing_printf(b, f, ...) +#define jl_timing_puts(b, s) +#define jl_timing_init_task(t) +#define jl_timing_block_enter_task(ct, ptls, blk) +#define jl_timing_block_exit_task(ct, ptls) ((jl_timing_block_t *)NULL) +#define jl_pop_timing_block(blk) + +#define jl_profile_lock_init(lock, name) +#define jl_profile_lock_start_wait(lock) +#define jl_profile_lock_acquired(lock) +#define jl_profile_lock_release_start(lock) +#define jl_profile_lock_release_end(lock) + #else #include "julia_assert.h" +#ifdef USE_TRACY +#include "tracy/TracyC.h" +#endif + +#ifdef USE_ITTAPI +#include +#endif #ifdef __cplusplus extern "C" { #endif void jl_print_timings(void); jl_timing_block_t *jl_pop_timing_block(jl_timing_block_t *cur_block); -void jl_timing_block_start(jl_timing_block_t *cur_block); -void jl_timing_block_stop(jl_timing_block_t *cur_block); + +void jl_timing_init_task(jl_task_t *t); +void jl_timing_block_enter_task(jl_task_t *ct, jl_ptls_t ptls, jl_timing_block_t *prev_blk); +jl_timing_block_t *jl_timing_block_exit_task(jl_task_t *ct, jl_ptls_t ptls); + +// Add the output of `jl_static_show(x)` as a text annotation to the +// profiling region corresponding to `cur_block`. +// +// If larger than IOS_INLSIZE (~80 characters), text is truncated. +JL_DLLEXPORT void jl_timing_show(jl_value_t *v, jl_timing_block_t *cur_block); +JL_DLLEXPORT void jl_timing_show_module(jl_module_t *m, jl_timing_block_t *cur_block); +JL_DLLEXPORT void jl_timing_show_filename(const char *path, jl_timing_block_t *cur_block); +JL_DLLEXPORT void jl_timing_show_method_instance(jl_method_instance_t *mi, jl_timing_block_t *cur_block); +JL_DLLEXPORT void jl_timing_show_method(jl_method_t *method, jl_timing_block_t *cur_block); +JL_DLLEXPORT void jl_timing_show_func_sig(jl_value_t *v, jl_timing_block_t *cur_block); +JL_DLLEXPORT void jl_timing_printf(jl_timing_block_t *cur_block, const char *format, ...); +JL_DLLEXPORT void jl_timing_puts(jl_timing_block_t *cur_block, const char *str); #ifdef __cplusplus } #endif #ifdef __cplusplus -#define HAVE_TIMING_SUPPORT -#elif defined(_COMPILER_CLANG_) -#define HAVE_TIMING_SUPPORT -#elif defined(_COMPILER_GCC_) -#define HAVE_TIMING_SUPPORT +#define JL_TIMING_CURRENT_BLOCK (&__timing_block.block) +#else +#define JL_TIMING_CURRENT_BLOCK (&__timing_block) #endif -#ifndef HAVE_TIMING_SUPPORT -#define JL_TIMING(owner) -#else +#define JL_TIMING_OWNERS \ + X(ROOT) \ + X(GC) \ + X(LOWERING) \ + X(PARSING) \ + X(INFERENCE) \ + X(CODEGEN) \ + X(METHOD_LOOKUP_SLOW) \ + X(METHOD_LOOKUP_FAST) \ + X(CODEINST_COMPILE) \ + X(LLVM_OPT) \ + X(LLVM_ORC) \ + X(METHOD_MATCH) \ + X(TYPE_CACHE_LOOKUP) \ + X(TYPE_CACHE_INSERT) \ + X(STAGED_FUNCTION) \ + X(MACRO_INVOCATION) \ + X(AST_COMPRESS) \ + X(AST_UNCOMPRESS) \ + X(SYSIMG_DUMP) \ + X(NATIVE_AOT) \ + X(ADD_METHOD) \ + X(LOAD_MODULE) \ + X(LOAD_IMAGE) \ + X(VERIFY_IMAGE) \ + X(SAVE_MODULE) \ + X(INIT_MODULE) \ + X(LOCK_SPIN) \ + X(STACKWALK) \ + X(DL_OPEN) \ + X(JULIA_INIT) \ + + +#define JL_TIMING_EVENTS \ + JL_TIMING_OWNERS \ + X(GC_Stop) \ + X(GC_Mark) \ + X(GC_Sweep) \ + X(GC_Finalizers) \ + X(CODEGEN_LLVM) \ + X(CODEGEN_Codeinst) \ + X(CODEGEN_Workqueue) \ + X(LOAD_Sysimg) \ + X(LOAD_Pkgimg) \ + X(LOAD_Processor) \ + X(VERIFY_Edges) \ + X(VERIFY_Methods) \ + X(VERIFY_Graph) \ + X(STACKWALK_Backtrace) \ + X(STACKWALK_Excstack) \ + X(NATIVE_Dump) \ + X(NATIVE_Create) \ -#define JL_TIMING_OWNERS \ - X(ROOT), \ - X(GC), \ - X(LOWERING), \ - X(PARSING), \ - X(INFERENCE), \ - X(CODEGEN), \ - X(METHOD_LOOKUP_SLOW), \ - X(METHOD_LOOKUP_FAST), \ - X(LLVM_OPT), \ - X(LLVM_MODULE_FINISH), \ - X(METHOD_MATCH), \ - X(TYPE_CACHE_LOOKUP), \ - X(TYPE_CACHE_INSERT), \ - X(STAGED_FUNCTION), \ - X(MACRO_INVOCATION), \ - X(AST_COMPRESS), \ - X(AST_UNCOMPRESS), \ - X(SYSIMG_LOAD), \ - X(SYSIMG_DUMP), \ - X(NATIVE_DUMP), \ - X(ADD_METHOD), \ - X(LOAD_MODULE), \ - X(SAVE_MODULE), \ - X(INIT_MODULE), enum jl_timing_owners { -#define X(name) JL_TIMING_ ## name +#define X(name) JL_TIMING_ ## name, JL_TIMING_OWNERS #undef X JL_TIMING_LAST }; -extern uint64_t jl_timing_data[(int)JL_TIMING_LAST]; -extern const char *jl_timing_names[(int)JL_TIMING_LAST]; +enum jl_timing_events { +#define X(name) JL_TIMING_EVENT_ ## name, + JL_TIMING_EVENTS +#undef X + JL_TIMING_EVENT_LAST +}; -struct _jl_timing_block_t { // typedef in julia.h - jl_timing_block_t *prev; +/** + * Timing back-ends differ in terms of whether they support nested + * and asynchronous events. + **/ + +/** + * Timing Backend: Aggregated timing counts (implemented in timing.c) + **/ + +#ifdef USE_TIMING_COUNTS +#define _COUNTS_CTX_MEMBER jl_timing_counts_t counts_ctx; +#define _COUNTS_CTOR(block, owner) _jl_timing_counts_ctor(block, owner) +#define _COUNTS_DESTROY(block) _jl_timing_counts_destroy(block) +#define _COUNTS_START(block, t) _jl_timing_counts_start(block, t) +#define _COUNTS_STOP(block, t) _jl_timing_counts_stop(block, t) +#else +#define _COUNTS_CTX_MEMBER +#define _COUNTS_CTOR(block, owner) +#define _COUNTS_DESTROY(block) +#define _COUNTS_START(block, t) +#define _COUNTS_STOP(block, t) +#endif + +/** + * Timing Backend: Tracy + **/ + +#ifdef USE_TRACY +#define _TRACY_CTX_MEMBER TracyCZoneCtx *tracy_ctx; +#define _TRACY_CTOR(context, name, enable) TracyCZoneN(__tracy_ctx, name, (enable)); \ + (context) = &__tracy_ctx +#define _TRACY_DESTROY(ctx) TracyCZoneEnd(*ctx) +#else +#define _TRACY_CTX_MEMBER +#define _TRACY_CTOR(context, name, enable) +#define _TRACY_DESTROY(block) +#endif + +#ifdef USE_ITTAPI +#define _ITTAPI_CTX_MEMBER int owner; int event; +#define _ITTAPI_CTOR(block, owner, event) block->owner = owner; block->event = event +#define _ITTAPI_START(block) if (_jl_timing_enabled(block->owner)) __itt_event_start(jl_timing_ittapi_events[block->event]) +#define _ITTAPI_STOP(block) if (_jl_timing_enabled(block->owner)) __itt_event_end(jl_timing_ittapi_events[block->event]) +#else +#define _ITTAPI_CTX_MEMBER +#define _ITTAPI_CTOR(block, owner, event) +#define _ITTAPI_START(block) +#define _ITTAPI_STOP(block) +#endif + +/** + * Implementation: Aggregated counts back-end + **/ + +extern JL_DLLEXPORT uint64_t jl_timing_counts[(int)JL_TIMING_LAST]; +typedef struct _jl_timing_counts_t { uint64_t total; uint64_t t0; int owner; #ifdef JL_DEBUG_BUILD uint8_t running; #endif -}; +} jl_timing_counts_t; -STATIC_INLINE void _jl_timing_block_stop(jl_timing_block_t *block, uint64_t t) { +STATIC_INLINE void _jl_timing_counts_stop(jl_timing_counts_t *block, uint64_t t) JL_NOTSAFEPOINT { #ifdef JL_DEBUG_BUILD assert(block->running); block->running = 0; @@ -95,7 +267,7 @@ STATIC_INLINE void _jl_timing_block_stop(jl_timing_block_t *block, uint64_t t) { block->total += t - block->t0; } -STATIC_INLINE void _jl_timing_block_start(jl_timing_block_t *block, uint64_t t) { +STATIC_INLINE void _jl_timing_counts_start(jl_timing_counts_t *block, uint64_t t) JL_NOTSAFEPOINT { #ifdef JL_DEBUG_BUILD assert(!block->running); block->running = 1; @@ -103,46 +275,96 @@ STATIC_INLINE void _jl_timing_block_start(jl_timing_block_t *block, uint64_t t) block->t0 = t; } -STATIC_INLINE uint64_t _jl_timing_block_init(jl_timing_block_t *block, int owner) { - uint64_t t = cycleclock(); +STATIC_INLINE void _jl_timing_counts_ctor(jl_timing_counts_t *block, int owner) JL_NOTSAFEPOINT { block->owner = owner; block->total = 0; #ifdef JL_DEBUG_BUILD block->running = 0; #endif - _jl_timing_block_start(block, t); - return t; } -STATIC_INLINE void _jl_timing_block_ctor(jl_timing_block_t *block, int owner) { - uint64_t t = _jl_timing_block_init(block, owner); +STATIC_INLINE void _jl_timing_counts_destroy(jl_timing_counts_t *block) JL_NOTSAFEPOINT { + jl_timing_counts[block->owner] += block->total; +} + +/** + * Top-level jl_timing implementation + **/ + +extern JL_DLLEXPORT uint64_t jl_timing_enable_mask; +extern const char *jl_timing_names[(int)JL_TIMING_LAST]; +#ifdef USE_ITTAPI +extern JL_DLLEXPORT __itt_event jl_timing_ittapi_events[(int)JL_TIMING_EVENT_LAST]; +#endif + +struct _jl_timing_block_t { // typedef in julia.h + struct _jl_timing_block_t *prev; + _TRACY_CTX_MEMBER + _ITTAPI_CTX_MEMBER + _COUNTS_CTX_MEMBER +}; + +STATIC_INLINE int _jl_timing_enabled(int event) JL_NOTSAFEPOINT { + return !!(jl_timing_enable_mask & (1 << event)); +} + +STATIC_INLINE void _jl_timing_block_ctor(jl_timing_block_t *block, int owner, int event) JL_NOTSAFEPOINT { + uint64_t t = cycleclock(); (void)t; + _COUNTS_CTOR(&block->counts_ctx, owner); + _COUNTS_START(&block->counts_ctx, t); + _ITTAPI_CTOR(block, owner, event); + _ITTAPI_START(block); + jl_task_t *ct = jl_current_task; jl_timing_block_t **prevp = &ct->ptls->timing_stack; block->prev = *prevp; - if (block->prev) - _jl_timing_block_stop(block->prev, t); + if (block->prev) { + _COUNTS_STOP(&block->prev->counts_ctx, t); + } *prevp = block; } -STATIC_INLINE void _jl_timing_block_destroy(jl_timing_block_t *block) { - uint64_t t = cycleclock(); +STATIC_INLINE void _jl_timing_block_destroy(jl_timing_block_t *block) JL_NOTSAFEPOINT { + uint64_t t = cycleclock(); (void)t; + + _ITTAPI_STOP(block); + _COUNTS_STOP(&block->counts_ctx, t); + _COUNTS_DESTROY(&block->counts_ctx); + _TRACY_DESTROY(block->tracy_ctx); + jl_task_t *ct = jl_current_task; - _jl_timing_block_stop(block, t); - jl_timing_data[block->owner] += block->total; jl_timing_block_t **pcur = &ct->ptls->timing_stack; assert(*pcur == block); *pcur = block->prev; - if (block->prev) - _jl_timing_block_start(block->prev, t); + if (block->prev) { + _COUNTS_START(&block->prev->counts_ctx, t); + } +} + +typedef struct _jl_timing_suspend_t { + jl_task_t *ct; +} jl_timing_suspend_t; + +STATIC_INLINE void _jl_timing_suspend_ctor(jl_timing_suspend_t *suspend, const char *subsystem, jl_task_t *ct) JL_NOTSAFEPOINT { + suspend->ct = ct; +#ifdef USE_TRACY + TracyCFiberEnter(subsystem); +#endif +} + +STATIC_INLINE void _jl_timing_suspend_destroy(jl_timing_suspend_t *suspend) JL_NOTSAFEPOINT { +#ifdef USE_TRACY + TracyCFiberEnter(suspend->ct->name); +#endif } #ifdef __cplusplus struct jl_timing_block_cpp_t { jl_timing_block_t block; - jl_timing_block_cpp_t(int owner) { - _jl_timing_block_ctor(&block, owner); + jl_timing_block_cpp_t(int owner, int event) JL_NOTSAFEPOINT { + _jl_timing_block_ctor(&block, owner, event); } - ~jl_timing_block_cpp_t() { + ~jl_timing_block_cpp_t() JL_NOTSAFEPOINT { _jl_timing_block_destroy(&block); } jl_timing_block_cpp_t(const jl_timing_block_cpp_t&) = delete; @@ -150,15 +372,61 @@ struct jl_timing_block_cpp_t { jl_timing_block_cpp_t& operator=(const jl_timing_block_cpp_t &) = delete; jl_timing_block_cpp_t& operator=(const jl_timing_block_cpp_t &&) = delete; }; -#define JL_TIMING(owner) jl_timing_block_cpp_t __timing_block(JL_TIMING_ ## owner) +#define JL_TIMING(subsystem, event) jl_timing_block_cpp_t __timing_block(JL_TIMING_ ## subsystem, JL_TIMING_EVENT_ ## event); \ + _TRACY_CTOR(__timing_block.block.tracy_ctx, #event, (jl_timing_enable_mask >> (JL_TIMING_ ## subsystem)) & 1) #else -#define JL_TIMING(owner) \ +#define JL_TIMING(subsystem, event) \ __attribute__((cleanup(_jl_timing_block_destroy))) \ jl_timing_block_t __timing_block; \ - _jl_timing_block_ctor(&__timing_block, JL_TIMING_ ## owner) + _jl_timing_block_ctor(&__timing_block, JL_TIMING_ ## subsystem, JL_TIMING_EVENT_ ## event); \ + _TRACY_CTOR(__timing_block.tracy_ctx, #event, (jl_timing_enable_mask >> (JL_TIMING_ ## subsystem)) & 1) #endif +#ifdef __cplusplus +struct jl_timing_suspend_cpp_t { + jl_timing_suspend_t suspend; + jl_timing_suspend_cpp_t(const char *subsystem, jl_task_t *ct) JL_NOTSAFEPOINT { + _jl_timing_suspend_ctor(&suspend, subsystem, ct); + } + ~jl_timing_suspend_cpp_t() JL_NOTSAFEPOINT { + _jl_timing_suspend_destroy(&suspend); + } + jl_timing_suspend_cpp_t(const jl_timing_suspend_cpp_t &) = delete; + jl_timing_suspend_cpp_t(jl_timing_suspend_cpp_t &&) = delete; + jl_timing_suspend_cpp_t& operator=(const jl_timing_suspend_cpp_t &) = delete; + jl_timing_suspend_cpp_t& operator=(jl_timing_suspend_cpp_t &&) = delete; +}; +#define JL_TIMING_SUSPEND(subsystem, ct) jl_timing_suspend_cpp_t __suspend_block(#subsystem, ct) +#else +#define JL_TIMING_SUSPEND(subsystem, ct) \ + __attribute__((cleanup(_jl_timing_suspend_destroy))) \ + jl_timing_suspend_t __timing_suspend; \ + _jl_timing_suspend_ctor(&__timing_suspend, #subsystem, ct) +#endif + +// Locking profiling +static inline void jl_profile_lock_init(jl_mutex_t *lock, const char *name) { +#ifdef USE_ITTAPI + __itt_sync_create(lock, "jl_mutex_t", name, __itt_attr_mutex); #endif +} +static inline void jl_profile_lock_start_wait(jl_mutex_t *lock) { +#ifdef USE_ITTAPI + __itt_sync_prepare(lock); +#endif +} +static inline void jl_profile_lock_acquired(jl_mutex_t *lock) { +#ifdef USE_ITTAPI + __itt_sync_acquired(lock); +#endif +} +static inline void jl_profile_lock_release_start(jl_mutex_t *lock) { +#ifdef USE_ITTAPI + __itt_sync_releasing(lock); +#endif +} +static inline void jl_profile_lock_release_end(jl_mutex_t *lock) {} + #endif #endif diff --git a/src/toplevel.c b/src/toplevel.c index 1f60a1b57c19c..200d0ad220231 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -48,7 +48,7 @@ JL_DLLEXPORT void jl_add_standard_imports(jl_module_t *m) void jl_init_main_module(void) { assert(jl_main_module == NULL); - jl_main_module = jl_new_module(jl_symbol("Main")); + jl_main_module = jl_new_module(jl_symbol("Main"), NULL); jl_main_module->parent = jl_main_module; jl_set_const(jl_main_module, jl_symbol("Core"), (jl_value_t*)jl_core_module); @@ -64,7 +64,8 @@ static jl_function_t *jl_module_get_initializer(jl_module_t *m JL_PROPAGATES_ROO void jl_module_run_initializer(jl_module_t *m) { - JL_TIMING(INIT_MODULE); + JL_TIMING(INIT_MODULE, INIT_MODULE); + jl_timing_show_module(m, JL_TIMING_CURRENT_BLOCK); jl_function_t *f = jl_module_get_initializer(m); if (f == NULL) return; @@ -134,7 +135,8 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex jl_type_error("module", (jl_value_t*)jl_symbol_type, (jl_value_t*)name); } - jl_module_t *newm = jl_new_module(name); + int is_parent__toplevel__ = jl_is__toplevel__mod(parent_module); + jl_module_t *newm = jl_new_module(name, is_parent__toplevel__ ? NULL : parent_module); jl_value_t *form = (jl_value_t*)newm; JL_GC_PUSH1(&form); JL_LOCK(&jl_modules_mutex); @@ -145,7 +147,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex // copy parent environment info into submodule newm->uuid = parent_module->uuid; - if (jl_is__toplevel__mod(parent_module)) { + if (is_parent__toplevel__) { newm->parent = newm; jl_register_root_module(newm); if (jl_options.incremental) { @@ -153,9 +155,8 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex } } else { - newm->parent = parent_module; - jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); - jl_declare_constant(b); + jl_binding_t *b = jl_get_binding_wr(parent_module, name); + jl_declare_constant(b, parent_module, name); jl_value_t *old = NULL; if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) { if (!jl_is_module(old)) { @@ -208,17 +209,17 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex #if 0 // some optional post-processing steps size_t i; - void **table = newm->bindings.table; - for(i=1; i < newm->bindings.size; i+=2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[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); + if ((void*)b != jl_nothing) { // remove non-exported macros if (jl_symbol_name(b->name)[0]=='@' && - !b->exportp && b->owner == newm) + !b->exportp && b->owner == b) b->value = NULL; // error for unassigned exports /* - if (b->exportp && b->owner==newm && b->value==NULL) + if (b->exportp && b->owner==b && b->value==NULL) jl_errorf("identifier %s exported from %s is not initialized", jl_symbol_name(b->name), jl_symbol_name(newm->name)); */ @@ -283,7 +284,7 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f args[1] = jl_toplevel_eval_flex(m, x, fast, 0); args[2] = jl_toplevel_eval_flex(m, f, fast, 0); if (jl_is_module(args[1])) { - JL_TYPECHK(getfield, symbol, args[2]); + JL_TYPECHK(getglobal, symbol, args[2]); args[0] = jl_eval_global_var((jl_module_t*)args[1], (jl_sym_t*)args[2]); } else { @@ -298,7 +299,7 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f } void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { - // create uninitialized mutable binding for "global x" decl + // create uninitialized mutable binding for "global x" decl sometimes or probably size_t i, l = jl_array_len(ex->args); for (i = 0; i < l; i++) { jl_value_t *arg = jl_exprarg(ex, i); @@ -313,10 +314,13 @@ void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { gm = m; gs = (jl_sym_t*)arg; } - jl_binding_t *b = jl_get_binding_wr(gm, gs, 0); - if (set_type && b) { - jl_value_t *old_ty = NULL; - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + if (!jl_binding_resolved_p(gm, gs)) { + jl_binding_t *b = jl_get_binding_wr(gm, gs); + if (set_type) { + jl_value_t *old_ty = NULL; + // maybe set the type too, perhaps + jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); + } } } } @@ -337,7 +341,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_intrinsics, int *has_defs, int *has_opaque) +static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *has_opaque) { if (!jl_is_expr(v)) return; @@ -361,11 +365,11 @@ static void expr_attributes(jl_value_t *v, int *has_intrinsics, int *has_defs, i *has_defs = 1; } else if (head == jl_cfunction_sym) { - *has_intrinsics = 1; + *has_ccall = 1; return; } else if (head == jl_foreigncall_sym) { - *has_intrinsics = 1; + *has_ccall = 1; return; } else if (head == jl_new_opaque_closure_sym) { @@ -380,16 +384,17 @@ static void expr_attributes(jl_value_t *v, int *has_intrinsics, int *has_defs, i jl_sym_t *name = jl_globalref_name(f); if (jl_binding_resolved_p(mod, name)) { jl_binding_t *b = jl_get_binding(mod, name); - if (b && b->value && b->constp) - called = b->value; + if (b && b->constp) { + called = jl_atomic_load_relaxed(&b->value); + } } } else if (jl_is_quotenode(f)) { called = jl_quotenode_value(f); } - if (called) { + if (called != NULL) { if (jl_is_intrinsic(called) && jl_unbox_int32(called) == (int)llvmcall) { - *has_intrinsics = 1; + *has_ccall = 1; } if (called == jl_builtin__typebody) { *has_defs = 1; @@ -401,28 +406,28 @@ static void expr_attributes(jl_value_t *v, int *has_intrinsics, int *has_defs, i for (i = 0; i < jl_array_len(e->args); i++) { jl_value_t *a = jl_exprarg(e, i); if (jl_is_expr(a)) - expr_attributes(a, has_intrinsics, has_defs, has_opaque); + expr_attributes(a, has_ccall, has_defs, has_opaque); } } -int jl_code_requires_compiler(jl_code_info_t *src) +int jl_code_requires_compiler(jl_code_info_t *src, int include_force_compile) { jl_array_t *body = src->code; - assert(jl_typeis(body, jl_array_any_type)); + assert(jl_typetagis(body, jl_array_any_type)); size_t i; - int has_intrinsics = 0, has_defs = 0, has_opaque = 0; - if (jl_has_meta(body, jl_force_compile_sym)) + 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++) { jl_value_t *stmt = jl_array_ptr_ref(body,i); - expr_attributes(stmt, &has_intrinsics, &has_defs, &has_opaque); - if (has_intrinsics) + expr_attributes(stmt, &has_ccall, &has_defs, &has_opaque); + if (has_ccall) return 1; } return 0; } -static void body_attributes(jl_array_t *body, int *has_intrinsics, int *has_defs, int *has_loops, int *has_opaque, int *forced_compile) +static void body_attributes(jl_array_t *body, int *has_ccall, int *has_defs, int *has_loops, int *has_opaque, int *forced_compile) { size_t i; *has_loops = 0; @@ -438,7 +443,7 @@ static void body_attributes(jl_array_t *body, int *has_intrinsics, int *has_defs *has_loops = 1; } } - expr_attributes(stmt, has_intrinsics, has_defs, has_opaque); + expr_attributes(stmt, has_ccall, has_defs, has_opaque); } *forced_compile = jl_has_meta(body, jl_force_compile_sym); } @@ -575,7 +580,7 @@ int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT static jl_method_instance_t *method_instance_for_thunk(jl_code_info_t *src, jl_module_t *module) { jl_method_instance_t *li = jl_new_method_instance_uninit(); - li->uninferred = (jl_value_t*)src; + jl_atomic_store_relaxed(&li->uninferred, (jl_value_t*)src); li->specTypes = (jl_value_t*)jl_emptytuple_type; li->def.module = module; return li; @@ -585,23 +590,22 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym { assert(m); jl_sym_t *name = asname ? asname : import->name; - jl_binding_t *b; - if (jl_binding_resolved_p(m, name)) { - b = jl_get_binding(m, name); - if ((!b->constp && b->owner != m) || (b->value && b->value != (jl_value_t*)import)) { - jl_errorf("importing %s into %s conflicts with an existing identifier", + // TODO: this is a bit race-y with what error message we might print + jl_binding_t *b = jl_get_module_binding(m, name, 0); + jl_binding_t *b2; + if (b != NULL && (b2 = jl_atomic_load_relaxed(&b->owner)) != NULL) { + if (b2->constp && jl_atomic_load_relaxed(&b2->value) == (jl_value_t*)import) + return; + if (b2 != b) + jl_errorf("importing %s into %s conflicts with an existing global", jl_symbol_name(name), jl_symbol_name(m->name)); - } } else { - b = jl_get_binding_wr(m, name, 1); - b->imported = 1; - } - if (!b->constp) { - b->value = (jl_value_t*)import; - b->constp = 1; - jl_gc_wb(m, (jl_value_t*)import); + b = jl_get_binding_wr(m, name); } + jl_declare_constant(b, m, name); + jl_checked_assignment(b, m, name, (jl_value_t*)import); + b->imported = 1; } // in `import A.B: x, y, ...`, evaluate the `A.B` part if it exists @@ -838,8 +842,8 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int gm = m; gs = (jl_sym_t*)arg; } - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); - jl_declare_constant(b); + jl_binding_t *b = jl_get_binding_wr(gm, gs); + jl_declare_constant(b, gm, gs); JL_GC_POP(); return jl_nothing; } @@ -868,16 +872,17 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int return (jl_value_t*)ex; } - int has_intrinsics = 0, has_defs = 0, has_loops = 0, has_opaque = 0, forced_compile = 0; + int has_ccall = 0, has_defs = 0, has_loops = 0, has_opaque = 0, forced_compile = 0; assert(head == jl_thunk_sym); thk = (jl_code_info_t*)jl_exprarg(ex, 0); - assert(jl_is_code_info(thk)); - assert(jl_typeis(thk->code, jl_array_any_type)); - body_attributes((jl_array_t*)thk->code, &has_intrinsics, &has_defs, &has_loops, &has_opaque, &forced_compile); + if (!jl_is_code_info(thk) || !jl_typetagis(thk->code, jl_array_any_type)) { + jl_eval_errorf(m, "malformed \"thunk\" statement"); + } + body_attributes((jl_array_t*)thk->code, &has_ccall, &has_defs, &has_loops, &has_opaque, &forced_compile); jl_value_t *result; - if (forced_compile || has_intrinsics || - (!has_defs && fast && has_loops && + if (has_ccall || + ((forced_compile || (!has_defs && fast && has_loops)) && jl_options.compile_enabled != JL_OPTIONS_COMPILE_OFF && jl_options.compile_enabled != JL_OPTIONS_COMPILE_MIN && jl_get_module_compile(m) != JL_OPTIONS_COMPILE_OFF && diff --git a/src/typemap.c b/src/typemap.c index dfa8ac67f6abc..1bdbe52a974dd 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -9,7 +9,7 @@ #endif #include "julia_assert.h" -#define MAX_METHLIST_COUNT 12 // this can strongly affect the sysimg size and speed! +#define MAX_METHLIST_COUNT 6 // this helps configure the sysimg size and speed. #ifdef __cplusplus extern "C" { @@ -23,7 +23,7 @@ static int jl_is_any(jl_value_t *t1) return t1 == (jl_value_t*)jl_any_type; } -static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) +static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { if (jl_is_unionall(t1)) t1 = jl_unwrap_unionall(t1); @@ -33,6 +33,9 @@ static jl_value_t *jl_type_extract_name(jl_value_t *t1 JL_PROPAGATES_ROOT) else if (jl_is_typevar(t1)) { return jl_type_extract_name(((jl_tvar_t*)t1)->ub); } + else if (t1 == jl_bottom_type || t1 == (jl_value_t*)jl_typeofbottom_type || t1 == (jl_value_t*)jl_typeofbottom_type->super) { + return (jl_value_t*)jl_typeofbottom_type->name; // put Union{} and typeof(Union{}) and Type{Union{}} together for convenience + } else if (jl_is_datatype(t1)) { jl_datatype_t *dt = (jl_datatype_t*)t1; if (!jl_is_kind(t1)) @@ -63,6 +66,9 @@ static int jl_type_extract_name_precise(jl_value_t *t1, int invariant) else if (jl_is_typevar(t1)) { return jl_type_extract_name_precise(((jl_tvar_t*)t1)->ub, 0); } + else if (t1 == jl_bottom_type || t1 == (jl_value_t*)jl_typeofbottom_type || t1 == (jl_value_t*)jl_typeofbottom_type->super) { + return 1; + } else if (jl_is_datatype(t1)) { jl_datatype_t *dt = (jl_datatype_t*)t1; if ((invariant || !dt->name->abstract) && !jl_is_kind(t1)) @@ -84,6 +90,18 @@ static int jl_type_extract_name_precise(jl_value_t *t1, int invariant) return 1; } +// return whether Type{Union{}} is a subtype of Type{t1} (which may have free typevars) +static int jl_parameter_includes_bottom(jl_value_t *t1) +{ + if (jl_is_typevar(t1) || t1 == jl_bottom_type) + return 1; + else if (jl_is_uniontype(t1)) { + jl_uniontype_t *u1 = (jl_uniontype_t*)t1; + return jl_parameter_includes_bottom(u1->a) && jl_parameter_includes_bottom(u1->b); + } + return 0; +} + // ----- Type Signature Subtype Testing ----- // @@ -249,7 +267,7 @@ static inline int sig_match_simple(jl_value_t *arg1, jl_value_t **args, size_t n // predicate to fast-test if this type is a leaf type that can exist in the cache // and does not need a more expensive linear scan to find all intersections -// be careful not to put non-leaf types or DataType/UnionAll/Union in the +// we try not to put non-leaf types or DataType/UnionAll/Union in the // argument cache, since they should have a lower priority and so will go in some // later list static int is_cache_leaf(jl_value_t *ty, int tparam) @@ -259,11 +277,11 @@ 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_typemap_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_array_t *cache JL_PROPAGATES_ROOT, jl_value_t *ty) JL_NOTSAFEPOINT { if (cache == (jl_array_t*)jl_an_empty_vec_any) return NULL; - _Atomic(jl_typemap_t*) *pml = jl_table_peek_bp(cache, ty); + _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; } @@ -275,13 +293,15 @@ static void mtcache_hash_insert(_Atomic(jl_array_t*) *cache, jl_value_t *parent, if (a == (jl_array_t*)jl_an_empty_vec_any) { a = jl_alloc_vec_any(16); jl_atomic_store_release(cache, a); - jl_gc_wb(parent, a); + if (parent) + jl_gc_wb(parent, a); } a = jl_eqtable_put(a, key, val, &inserted); assert(inserted); if (a != jl_atomic_load_relaxed(cache)) { jl_atomic_store_release(cache, a); - jl_gc_wb(parent, a); + if (parent) + jl_gc_wb(parent, a); } } @@ -290,7 +310,6 @@ static jl_typemap_t *mtcache_hash_lookup(jl_array_t *cache JL_PROPAGATES_ROOT, j if (cache == (jl_array_t*)jl_an_empty_vec_any) return (jl_typemap_t*)jl_nothing; jl_typemap_t *ml = (jl_typemap_t*)jl_eqtable_get(cache, ty, jl_nothing); - JL_GC_PROMISE_ROOTED(ml); // clang-sa doesn't trust our JL_PROPAGATES_ROOT claim return ml; } @@ -303,8 +322,16 @@ static int jl_typemap_array_visitor(jl_array_t *a, jl_typemap_visitor_fptr fptr, for (i = 1; i < l; i += 2) { jl_value_t *d = jl_atomic_load_relaxed(&data[i]); JL_GC_PROMISE_ROOTED(d); - if (d && !jl_typemap_visitor(d, fptr, closure)) - return 0; + if (d == NULL) + continue; + if (jl_is_array(d)) { + if (!jl_typemap_array_visitor((jl_array_t*)d, fptr, closure)) + return 0; + } + else { + if (!jl_typemap_visitor(d, fptr, closure)) + return 0; + } } return 1; } @@ -349,17 +376,16 @@ int jl_typemap_visitor(jl_typemap_t *cache, jl_typemap_visitor_fptr fptr, void * goto exit; JL_GC_POP(); return 1; +exit: + JL_GC_POP(); + return 0; } else { return jl_typemap_node_visitor((jl_typemap_entry_t*)cache, fptr, closure); } - -exit: - JL_GC_POP(); - return 0; } -static unsigned jl_supertype_height(jl_datatype_t *dt) +static unsigned jl_supertype_height(jl_datatype_t *dt) JL_NOTSAFEPOINT { unsigned height = 1; while (dt != jl_any_type) { @@ -370,8 +396,10 @@ static unsigned jl_supertype_height(jl_datatype_t *dt) } // return true if a and b might intersect in the type domain (over just their type-names) -static int tname_intersection(jl_datatype_t *a, jl_typename_t *bname, unsigned ha) +static int tname_intersection_dt(jl_datatype_t *a, jl_typename_t *bname, unsigned ha) JL_NOTSAFEPOINT { + if (a == jl_any_type) + return 1; jl_datatype_t *b = (jl_datatype_t*)jl_unwrap_unionall(bname->wrapper); unsigned hb = 1; while (b != jl_any_type) { @@ -387,15 +415,70 @@ static int tname_intersection(jl_datatype_t *a, jl_typename_t *bname, unsigned h return a->name == bname; } -// tparam bit 1 is ::Type{T} (vs. T) -// tparam bit 2 is typename(T) (vs. T) -static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, int tparam, - int offs, struct typemap_intersection_env *closure) +static int tname_intersection(jl_value_t *a, jl_typename_t *bname, int8_t tparam) JL_NOTSAFEPOINT +{ + if (a == (jl_value_t*)jl_any_type) + return 1; + a = jl_unwrap_unionall(a); + assert(!jl_is_vararg(a)); + if (jl_is_uniontype(a)) + return tname_intersection(((jl_uniontype_t*)a)->a, bname, tparam) || + tname_intersection(((jl_uniontype_t*)a)->b, bname, tparam); + if (jl_is_typevar(a)) + return tname_intersection(((jl_tvar_t*)a)->ub, bname, tparam); + if (jl_is_datatype(a)) { + if (tparam) { + if (!jl_is_type_type(a)) + return 0; + a = jl_unwrap_unionall(jl_tparam0(a)); + if (!jl_is_datatype(a)) + return tname_intersection(a, bname, 0); + } + return tname_intersection_dt((jl_datatype_t*)a, bname, jl_supertype_height((jl_datatype_t*)a)); + } + return 0; +} + +static int concrete_intersects(jl_value_t *t, jl_value_t *ty, int8_t tparam) +{ + if (ty == (jl_value_t*)jl_any_type) // easy case: Any always matches + return 1; + if (tparam & 1) + return jl_isa(t, ty); // (Type{t} <: ty), where is_leaf_type(t) => isa(t, ty) + else + return t == ty || jl_subtype(t, ty); +} + +// 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, + 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); - unsigned height = tparam & 2 ? jl_supertype_height((jl_datatype_t*)ty) : 0; + unsigned height = 0; + jl_datatype_t *tydt = jl_any_type; + if (tparam & 2) { + // try to extract a description of ty for intersections, but since we + jl_value_t *ttype = jl_unwrap_unionall(ty); + if (tparam & 1) + // extract T from Type{T} (if possible) + ttype = jl_is_type_type(ttype) ? jl_tparam0(ttype) : NULL; + if (ttype && jl_is_datatype(ttype)) { + tydt = (jl_datatype_t*)ttype; + } + else if (ttype) { + ttype = jl_type_extract_name(ttype); + tydt = ttype ? (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)ttype)->wrapper) : NULL; + } + if (tydt == jl_any_type) + ty = (jl_value_t*)jl_any_type; + else if (tydt == NULL) + tydt = jl_any_type; + else + height = jl_supertype_height(tydt); + } for (i = 0; i < l; i += 2) { jl_value_t *t = jl_atomic_load_relaxed(&data[i]); JL_GC_PROMISE_ROOTED(t); @@ -404,18 +487,24 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, if (tparam & 2) { jl_typemap_t *ml = jl_atomic_load_relaxed(&data[i + 1]); JL_GC_PROMISE_ROOTED(ml); - if (ty == (jl_value_t*)jl_any_type || // easy case: Any always matches - tname_intersection((jl_datatype_t*)ty, (jl_typename_t*)t, height)) { - if (!jl_typemap_intersection_visitor(ml, offs + 1, closure)) - goto exit; + if (tydt == jl_any_type ? + tname_intersection(ty, (jl_typename_t*)t, tparam & 1) : + 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)) + goto exit; + } + else { + if (!jl_typemap_intersection_visitor(ml, offs + 1, closure)) + goto exit; + } } } else { - // `t` is a leaftype, so intersection test becomes subtype - if (ty == (jl_value_t*)jl_any_type || // easy case: Any always matches - (tparam & 1 - ? (jl_typeof(t) == ty || jl_isa(t, ty)) // (Type{t} <: ty), where is_leaf_type(t) => isa(t, ty) - : (t == ty || jl_subtype(t, ty)))) { + // `t` is a leaftype, so intersection test becomes subtype (after excluding kinds) + if (concrete_intersects(t, ty, tparam)) { jl_typemap_t *ml = jl_atomic_load_relaxed(&data[i + 1]); JL_GC_PROMISE_ROOTED(ml); // NOTE: ml might be NULL if we're racing with the thread that's inserting the item @@ -432,6 +521,7 @@ static int jl_typemap_intersection_array_visitor(jl_array_t *a, jl_value_t *ty, return 0; } + // calls fptr on each jl_typemap_entry_t in cache in sort order // for which type ∩ ml->type != Union{}, until fptr return false static int jl_typemap_intersection_node_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure) @@ -440,38 +530,64 @@ static int jl_typemap_intersection_node_visitor(jl_typemap_entry_t *ml, struct t // mark this `register` because (for branch prediction) // that can be absolutely critical for speed register jl_typemap_intersection_visitor_fptr fptr = closure->fptr; - while (ml != (void*)jl_nothing) { - if (closure->type == (jl_value_t*)ml->sig) { - // fast-path for the intersection of a type with itself - if (closure->env) - closure->env = jl_outer_unionall_vars((jl_value_t*)ml->sig); - closure->ti = closure->type; - closure->issubty = 1; - if (!fptr(ml, closure)) - return 0; + for (; ml != (void*)jl_nothing; ml = jl_atomic_load_relaxed(&ml->next)) { + if (closure->max_valid < ml->min_world) + continue; + if (closure->min_valid > ml->max_world) + continue; + jl_svec_t **penv = NULL; + if (closure->env) { + closure->env = jl_emptysvec; + penv = &closure->env; } - else { - jl_svec_t **penv = NULL; - if (closure->env) { - closure->env = jl_emptysvec; - penv = &closure->env; - } - closure->ti = jl_type_intersection_env_s(closure->type, (jl_value_t*)ml->sig, penv, &closure->issubty); - if (closure->ti != (jl_value_t*)jl_bottom_type) { - // In some corner cases type intersection is conservative and returns something - // for intersect(A, B) even though A is a dispatch tuple and !(A <: B). - // For dispatch purposes in such a case we know there's no match. This check - // fixes issue #30394. - if (closure->issubty || !jl_is_dispatch_tupletype(closure->type)) - if (!fptr(ml, closure)) - return 0; - } + closure->ti = jl_type_intersection_env_s(closure->type, (jl_value_t*)ml->sig, penv, &closure->issubty); + if (closure->ti != (jl_value_t*)jl_bottom_type) { + // In some corner cases type intersection is conservative and returns something + // for intersect(A, B) even though A is a dispatch tuple and !(A <: B). + // For dispatch purposes in such a case we know there's no match. This check + // fixes issue #30394. + if (closure->issubty || !jl_is_dispatch_tupletype(closure->type)) + if (!fptr(ml, closure)) + return 0; } - ml = jl_atomic_load_relaxed(&ml->next); } return 1; } +int jl_has_intersect_type_not_kind(jl_value_t *t); +int jl_has_intersect_kind_not_type(jl_value_t *t); + +// if TypeVar tv is used covariantly, it cannot be Union{} +int has_covariant_var(jl_datatype_t *ttypes, jl_tvar_t *tv) +{ + size_t i, l = jl_nparams(ttypes); + for (i = 0; i < l; i++) + if (jl_tparam(ttypes, i) == (jl_value_t*)tv) + return 1; + return 0; +} + +void typemap_slurp_search(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure) +{ + // n.b. we could consider mt->max_args here too, so this optimization + // usually works even if the user forgets the `slurp...` argument, but + // there is discussion that parameter may be going away? (and it is + // already not accurately up-to-date for all tables currently anyways) + if (closure->search_slurp && ml->va) { + jl_value_t *sig = jl_unwrap_unionall((jl_value_t*)ml->sig); + size_t nargs = jl_nparams(sig); + if (nargs > 1 && nargs - 1 == closure->search_slurp) { + jl_vararg_t *va = (jl_vararg_t*)jl_tparam(sig, nargs - 1); + assert(jl_is_vararg((jl_value_t*)va)); + if (va->T == (jl_value_t*)jl_any_type && va->N == NULL) { + // instruct typemap it can set exclude_typeofbottom on parameter nargs + // since we found the necessary slurp argument + closure->search_slurp = 0; + } + } + } +} + int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, struct typemap_intersection_env *closure) { @@ -480,13 +596,12 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, //TODO: fast-path for leaf-type tuples? //if (ttypes->isdispatchtuple) { // register jl_typemap_intersection_visitor_fptr fptr = closure->fptr; - // struct jl_typemap_assoc search = {(jl_value_t*)closure->type, world, closure->env, 0, ~(size_t)0}; - // jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(map, search, offs, /*subtype*/1); - // if (ml) { - // closure->env = search->env; - // if (!fptr(ml, closure)) - // return 0; - // } + // struct jl_typemap_assoc search = {(jl_value_t*)closure->type, world, closure->env, 0, ~(size_t)0}; + // jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(map, search, offs, /*subtype*/1); + // if (ml) { + // closure->env = search->env; + // if (!fptr(ml, closure)) + // return 0; // } // return 1; //} @@ -508,115 +623,186 @@ int jl_typemap_intersection_visitor(jl_typemap_t *map, int offs, if (ty) { while (jl_is_typevar(ty)) ty = ((jl_tvar_t*)ty)->ub; - jl_value_t *typetype = jl_unwrap_unionall(ty); - typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; // approxify the tparam until we have a valid type - if (jl_has_free_typevars(ty)) { - ty = jl_unwrap_unionall(ty); - if (jl_is_datatype(ty)) - ty = ((jl_datatype_t*)ty)->name->wrapper; - else - ty = (jl_value_t*)jl_any_type; - } + 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); - if (targ != (jl_array_t*)jl_an_empty_vec_any) { - if (typetype && !jl_has_free_typevars(typetype)) { - if (is_cache_leaf(typetype, 1)) { - // direct lookup of leaf types - jl_typemap_t *ml = mtcache_hash_lookup(targ, typetype); - if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; + jl_array_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) { + 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) { + typetype = jl_unwrap_unionall(ty); + typetype = jl_is_type_type(typetype) ? jl_tparam0(typetype) : NULL; + name = typetype ? jl_type_extract_name(typetype) : NULL; + if (!typetype) + exclude_typeofbottom = !jl_subtype((jl_value_t*)jl_typeofbottom_type, ty); + else if (jl_is_typevar(typetype)) + exclude_typeofbottom = has_covariant_var((jl_datatype_t*)ttypes, (jl_tvar_t*)typetype); + else + exclude_typeofbottom = !jl_parameter_includes_bottom(typetype); + } + } + // 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 && + (!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 + // otherwise the possibility of encountering `Type{Union{}}` in this intersection may + // be forcing us to do some extra work here whenever we see a typevar, even though + // the likelihood of that value actually occurring is frequently likely to be + // zero (or result in an ambiguous match) + targ = jl_atomic_load_relaxed(&cache->targ); // may be GC'd during type-intersection + jl_value_t *ml = mtcache_hash_lookup(targ, (jl_value_t*)jl_typeofbottom_type->name); + if (ml != jl_nothing) { + size_t search_slurp = closure->search_slurp; + closure->search_slurp = offs + 1; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { + closure->search_slurp = search_slurp; + JL_GC_POP(); + return 0; } + if (closure->search_slurp == 0) + exclude_typeofbottom = 1; + closure->search_slurp = search_slurp; } } - else { - // else an array scan is required to check subtypes - // first, fast-path: optimized pre-intersection test to see if `ty` could intersect with any Type - if (typetype || !jl_has_empty_intersection((jl_value_t*)jl_type_type, ty)) { - targ = jl_atomic_load_relaxed(&cache->targ); // may be GC'd during type-intersection - if (!jl_typemap_intersection_array_visitor(targ, ty, 1, offs, closure)) return 0; + if (name != (jl_value_t*)jl_typeofbottom_type->name) { + targ = jl_atomic_load_relaxed(&cache->targ); // may be GC'd earlier + if (exclude_typeofbottom && name && jl_type_extract_name_precise(typetype, 1)) { + // 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 (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); + 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; } + } + } + else if (ml != jl_nothing) { + 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 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; } } } } jl_array_t *cachearg1 = jl_atomic_load_relaxed(&cache->arg1); if (cachearg1 != (jl_array_t*)jl_an_empty_vec_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_typemap_t *ml = mtcache_hash_lookup(cachearg1, ty); + 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 (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; + if (!jl_typemap_intersection_visitor(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, 0, offs, closure)) return 0; + jl_value_t *name = jl_type_extract_name(ty); + 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; } + } + 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; } + } } } - jl_array_t *tname = jl_atomic_load_relaxed(&cache->tname); - if (tname != (jl_array_t*)jl_an_empty_vec_any) { - jl_value_t *name = typetype ? jl_type_extract_name(typetype) : NULL; - if (name && !jl_is_typevar(typetype)) { - // semi-direct lookup of types - // TODO: the possibility of encountering `Type{Union{}}` in this intersection may + // 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 (!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 // be forcing us to do some extra work here whenever we see a typevar, even though - // the likelyhood of that value actually occurring is frequently likely to be + // the likelihood of that value actually occurring is frequently likely to be // zero (or result in an ambiguous match) - jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); - if (jl_type_extract_name_precise(typetype, 1)) { - // just consider the type and its direct super types - while (1) { - tname = jl_atomic_load_relaxed(&cache->tname); // reload after callback - jl_typemap_t *ml = mtcache_hash_lookup(tname, (jl_value_t*)super->name); - if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; - } - if (super == jl_any_type) - break; - super = super->super; + tname = jl_atomic_load_relaxed(&cache->tname); // may be GC'd earlier + jl_value_t *ml = mtcache_hash_lookup(tname, (jl_value_t*)jl_typeofbottom_type->name); + if (ml != jl_nothing) { + size_t search_slurp = closure->search_slurp; + closure->search_slurp = offs + 1; + if (!jl_typemap_intersection_visitor((jl_typemap_t*)ml, offs+1, closure)) { + closure->search_slurp = search_slurp; + JL_GC_POP(); + return 0; } + if (closure->search_slurp == 0) + exclude_typeofbottom = 1; + closure->search_slurp = search_slurp; } - else { - // consider all of the possible subtypes - if (!jl_typemap_intersection_array_visitor(tname, (jl_value_t*)super, 3, offs, closure)) return 0; + } + if (exclude_typeofbottom && name && jl_type_extract_name_precise(typetype, 1)) { + // semi-direct lookup of types + // just consider the type and its direct super types + jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); + if (super->name == jl_typeofbottom_type->name) + super = super->super; // this was handled above + while (1) { + tname = jl_atomic_load_relaxed(&cache->tname); // reload after callback + jl_typemap_t *ml = mtcache_hash_lookup(tname, (jl_value_t*)super->name); + if (ml != jl_nothing) { + if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } + } + if (super == jl_any_type) + break; + super = super->super; } } else { - // else an array scan is required to check subtypes - // first, fast-path: optimized pre-intersection test to see if `ty` could intersect with any Type - if (name || !jl_has_empty_intersection((jl_value_t*)jl_type_type, ty)) { - tname = jl_atomic_load_relaxed(&cache->tname); // may be GC'd during type-intersection - if (!jl_typemap_intersection_array_visitor(tname, (jl_value_t*)jl_any_type, 3, offs, closure)) return 0; - } + // else an array 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; } } } jl_array_t *name1 = jl_atomic_load_relaxed(&cache->name1); if (name1 != (jl_array_t*)jl_an_empty_vec_any) { jl_value_t *name = jl_type_extract_name(ty); - if (name) { + if (name && jl_type_extract_name_precise(ty, 0)) { jl_datatype_t *super = (jl_datatype_t*)jl_unwrap_unionall(((jl_typename_t*)name)->wrapper); - if (jl_type_extract_name_precise(ty, 0)) { - // direct lookup of concrete types - while (1) { - name1 = jl_atomic_load_relaxed(&cache->name1); // reload after callback - jl_typemap_t *ml = mtcache_hash_lookup(name1, (jl_value_t*)super->name); - if (ml != jl_nothing) { - if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) return 0; - } - if (super == jl_any_type) - break; - super = super->super; + // direct lookup of concrete types + while (1) { + name1 = jl_atomic_load_relaxed(&cache->name1); // reload after callback + jl_typemap_t *ml = mtcache_hash_lookup(name1, (jl_value_t*)super->name); + if (ml != jl_nothing) { + if (!jl_typemap_intersection_visitor(ml, offs+1, closure)) { JL_GC_POP(); return 0; } } - } - else { - // consider all of the possible subtypes too - if (!jl_typemap_intersection_array_visitor(name1, (jl_value_t*)super, 2, offs, closure)) return 0; + if (super == jl_any_type) + break; + super = super->super; } } else { // else an array scan is required to check subtypes - if (!jl_typemap_intersection_array_visitor(name1, (jl_value_t*)jl_any_type, 2, offs, closure)) return 0; + if (!jl_typemap_intersection_array_visitor(name1, ty, 2, offs, closure)) { JL_GC_POP(); return 0; } } } + JL_GC_POP(); } if (!jl_typemap_intersection_node_visitor(jl_atomic_load_relaxed(&cache->linear), closure)) return 0; @@ -650,6 +836,10 @@ 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) + continue; size_t lensig = jl_nparams(jl_unwrap_unionall((jl_value_t*)ml->sig)); if (lensig == n || (ml->va && lensig <= n+1)) { int resetenv = 0, ismatch = 1; @@ -801,9 +991,12 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type( 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_typemap_t *ml = mtcache_hash_lookup(targ, a0); + 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 (ml != jl_nothing) { - jl_typemap_entry_t *li = jl_typemap_assoc_by_type(ml, search, offs + 1, subtype); + jl_typemap_entry_t *li = jl_typemap_assoc_by_type((jl_typemap_t*)ml, search, offs + 1, subtype); if (li) return li; } } @@ -813,9 +1006,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_typemap_t *ml = mtcache_hash_lookup(cachearg1, ty); + 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 (ml != jl_nothing) { - jl_typemap_entry_t *li = jl_typemap_assoc_by_type(ml, search, offs + 1, subtype); + jl_typemap_entry_t *li = jl_typemap_assoc_by_type((jl_typemap_t*)ml, search, offs + 1, subtype); if (li) return li; } } @@ -1003,15 +1199,21 @@ jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_v jl_value_t *ty = jl_typeof(a1); assert(jl_is_datatype(ty)); jl_array_t *targ = jl_atomic_load_relaxed(&cache->targ); - if (ty == (jl_value_t*)jl_datatype_type && targ != (jl_array_t*)jl_an_empty_vec_any && is_cache_leaf(a1, 1)) { - jl_typemap_t *ml_or_cache = mtcache_hash_lookup(targ, a1); + if (targ != (jl_array_t*)jl_an_empty_vec_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); 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_typemap_t *ml_or_cache = mtcache_hash_lookup(cachearg1, ty); - jl_typemap_entry_t *ml = jl_typemap_assoc_exact(ml_or_cache, arg1, args, n, offs+1, world); + 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); + 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); @@ -1104,10 +1306,14 @@ static jl_typemap_level_t *jl_new_typemap_level(void) return cache; } -static jl_typemap_level_t *jl_method_convert_list_to_cache( - jl_typemap_t *map, jl_typemap_entry_t *ml, int8_t offs) +static void jl_typemap_array_insert_( + jl_typemap_t *map, _Atomic(jl_array_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_typemap_level_t *cache = jl_new_typemap_level(); + jl_value_t *cache = doublesplit ? jl_an_empty_vec_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) { @@ -1115,7 +1321,25 @@ static jl_typemap_level_t *jl_method_convert_list_to_cache( jl_atomic_store_relaxed(&ml->next, (jl_typemap_entry_t*)jl_nothing); // n.b. this is being done concurrently with lookups! // TODO: is it safe to be doing this concurrently with lookups? - jl_typemap_level_insert_(map, cache, ml, offs); + if (doublesplit) { + jl_value_t *key = jl_unwrap_unionall((jl_value_t*)ml->sig); + size_t len = jl_nparams(key); + if (offs < len-1) + key = jl_tparam(key, offs); + else + key = jl_tparam(key, len-1); + if (jl_is_vararg(key)) + key = jl_unwrap_vararg(key); + if (key == (jl_value_t*)jl_typeofbottom_type) + key = (jl_value_t*)jl_assume(jl_typeofbottom_type)->super; + if (tparam) { + 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); + } + else + jl_typemap_level_insert_(map, (jl_typemap_level_t*)cache, ml, offs); ml = next; } JL_GC_POP(); @@ -1141,23 +1365,33 @@ static void jl_typemap_list_insert_( jl_gc_wb(parent, newrec); } +// n.b. tparam value only needed if doublesplit is set (for jl_method_convert_list_to_cache) static void jl_typemap_insert_generic( - jl_typemap_t *map, _Atomic(jl_typemap_t*) *pml, jl_value_t *parent, - jl_typemap_entry_t *newrec, int8_t offs) + jl_typemap_t *map, _Atomic(jl_value_t*) *pml, jl_value_t *parent, + jl_typemap_entry_t *newrec, int8_t tparam, int8_t offs, jl_value_t *doublesplit) { - jl_typemap_t *ml = jl_atomic_load_relaxed(pml); + jl_value_t *ml = jl_atomic_load_relaxed(pml); + if (jl_is_array(ml)) { + assert(doublesplit); + jl_typemap_array_insert_(map, (_Atomic(jl_array_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); + return; + } if (jl_typeof(ml) == (jl_value_t*)jl_typemap_level_type) { + assert(!doublesplit); jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs); return; } unsigned count = jl_typemap_list_count_locked((jl_typemap_entry_t*)ml); if (count > MAX_METHLIST_COUNT) { - ml = (jl_typemap_t*)jl_method_convert_list_to_cache( - map, (jl_typemap_entry_t*)ml, offs); + ml = jl_method_convert_list_to_cache( + map, (jl_typemap_entry_t*)ml, tparam, offs, doublesplit != NULL); jl_atomic_store_release(pml, ml); jl_gc_wb(parent, ml); - jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs); + if (doublesplit) + jl_typemap_array_insert_(map, (_Atomic(jl_array_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL); + else + jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs); return; } @@ -1167,14 +1401,14 @@ static void jl_typemap_insert_generic( static void jl_typemap_array_insert_( jl_typemap_t *map, _Atomic(jl_array_t*) *pcache, jl_value_t *key, jl_typemap_entry_t *newrec, - jl_value_t *parent, int8_t offs) + jl_value_t *parent, int8_t tparam, int8_t offs, jl_value_t *doublesplit) { jl_array_t *cache = jl_atomic_load_relaxed(pcache); - _Atomic(jl_typemap_t*) *pml = mtcache_hash_lookup_bp(cache, key); - if (pml != NULL) - jl_typemap_insert_generic(map, pml, (jl_value_t*)cache, newrec, offs+1); - else + _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); } static void jl_typemap_level_insert_( @@ -1205,7 +1439,7 @@ static void jl_typemap_level_insert_( t1 = (jl_value_t*)jl_assume(jl_typeofbottom_type)->super; // If the type at `offs` is Any, put it in the Any list if (t1 && jl_is_any(t1)) { - jl_typemap_insert_generic(map, &cache->any, (jl_value_t*)cache, newrec, offs+1); + jl_typemap_insert_generic(map, &cache->any, (jl_value_t*)cache, newrec, 0, offs+1, NULL); return; } // Don't put Varargs in the optimized caches (too hard to handle in lookup and bp) @@ -1216,12 +1450,14 @@ static void jl_typemap_level_insert_( // and we use the table indexed for that purpose. jl_value_t *a0 = jl_tparam0(t1); if (is_cache_leaf(a0, 1)) { - jl_typemap_array_insert_(map, &cache->targ, a0, newrec, (jl_value_t*)cache, offs); + 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); return; } } if (is_cache_leaf(t1, 0)) { - jl_typemap_array_insert_(map, &cache->arg1, t1, newrec, (jl_value_t*)cache, offs); + 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); return; } @@ -1231,12 +1467,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, offs); + jl_typemap_array_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, offs); + jl_typemap_array_insert_(map, &cache->name1, a0, newrec, (jl_value_t*)cache, 0, offs, NULL); return; } } @@ -1292,7 +1528,7 @@ void jl_typemap_insert(_Atomic(jl_typemap_t *) *pcache, jl_value_t *parent, jl_typemap_entry_t *newrec, int8_t offs) { jl_typemap_t *cache = jl_atomic_load_relaxed(pcache); - jl_typemap_insert_generic(cache, pcache, parent, newrec, offs); + jl_typemap_insert_generic(cache, pcache, parent, newrec, 0, offs, NULL); } #ifdef __cplusplus diff --git a/src/utils.scm b/src/utils.scm index 7be6b2999a90c..97464b9a14e5a 100644 --- a/src/utils.scm +++ b/src/utils.scm @@ -104,3 +104,11 @@ (begin (put! tbl (car xs) i) (loop (cdr xs) (+ i 1))))) tbl)) + +;; keep at most the first element matching a given predicate +(define (keep-first pred lst) + (cond ((null? lst) lst) + ((pred (car lst)) + (cons (car lst) (filter (lambda (x) (not (pred x))) (cdr lst)))) + (else + (cons (car lst) (keep-first pred (cdr lst)))))) diff --git a/src/work-stealing-queue.h b/src/work-stealing-queue.h new file mode 100644 index 0000000000000..38429e02886e9 --- /dev/null +++ b/src/work-stealing-queue.h @@ -0,0 +1,102 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#ifndef WORK_STEALING_QUEUE_H +#define WORK_STEALING_QUEUE_H + +#include "julia_atomics.h" +#include "assert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ======= +// Chase and Lev's work-stealing queue, optimized for +// weak memory models by Le et al. +// +// * Chase D., Lev Y. Dynamic Circular Work-Stealing queue +// * Le N. M. et al. Correct and Efficient Work-Stealing for +// Weak Memory Models +// ======= + +typedef struct { + char *buffer; + int32_t capacity; + int32_t mask; +} ws_array_t; + +static inline ws_array_t *create_ws_array(size_t capacity, int32_t eltsz) JL_NOTSAFEPOINT +{ + ws_array_t *a = (ws_array_t *)malloc_s(sizeof(ws_array_t)); + a->buffer = (char *)malloc_s(capacity * eltsz); + a->capacity = capacity; + a->mask = capacity - 1; + return a; +} + +typedef struct { + _Atomic(int64_t) top; + _Atomic(int64_t) bottom; + _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 +{ + int64_t b = jl_atomic_load_relaxed(&q->bottom); + int64_t t = jl_atomic_load_acquire(&q->top); + ws_array_t *ary = jl_atomic_load_relaxed(&q->array); + ws_array_t *old_ary = NULL; + if (__unlikely(b - t > ary->capacity - 1)) { + ws_array_t *new_ary = create_ws_array(2 * ary->capacity, eltsz); + for (int i = 0; i < ary->capacity; i++) { + memcpy(new_ary->buffer + ((t + i) & new_ary->mask) * eltsz, ary->buffer + ((t + i) & ary->mask) * eltsz, eltsz); + } + jl_atomic_store_release(&q->array, new_ary); + old_ary = ary; + ary = new_ary; + } + memcpy(ary->buffer + (b & ary->mask) * eltsz, elt, eltsz); + jl_fence_release(); + jl_atomic_store_relaxed(&q->bottom, b + 1); + return old_ary; +} + +static inline void ws_queue_pop(ws_queue_t *q, void *dest, int32_t eltsz) JL_NOTSAFEPOINT +{ + int64_t b = jl_atomic_load_relaxed(&q->bottom) - 1; + ws_array_t *ary = jl_atomic_load_relaxed(&q->array); + jl_atomic_store_relaxed(&q->bottom, b); + jl_fence(); + int64_t t = jl_atomic_load_relaxed(&q->top); + if (__likely(t <= b)) { + memcpy(dest, ary->buffer + (b & ary->mask) * eltsz, eltsz); + if (t == b) { + if (!jl_atomic_cmpswap(&q->top, &t, t + 1)) + memset(dest, 0, eltsz); + jl_atomic_store_relaxed(&q->bottom, b + 1); + } + } + else { + memset(dest, 0, eltsz); + jl_atomic_store_relaxed(&q->bottom, b + 1); + } +} + +static inline void ws_queue_steal_from(ws_queue_t *q, void *dest, int32_t eltsz) JL_NOTSAFEPOINT +{ + int64_t t = jl_atomic_load_acquire(&q->top); + jl_fence(); + int64_t b = jl_atomic_load_acquire(&q->bottom); + if (t < b) { + ws_array_t *ary = jl_atomic_load_relaxed(&q->array); + memcpy(dest, ary->buffer + (t & ary->mask) * eltsz, eltsz); + if (!jl_atomic_cmpswap(&q->top, &t, t + 1)) + memset(dest, 0, eltsz); + } +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/stdlib/.gitignore b/stdlib/.gitignore index ffbc2f12f52da..dec1745520d4c 100644 --- a/stdlib/.gitignore +++ b/stdlib/.gitignore @@ -1,4 +1,6 @@ /srccache +/DelimitedFiles-* +/DelimitedFiles /Pkg-* /Pkg /Statistics-* @@ -21,3 +23,4 @@ /SHA /*_jll/StdlibArtifacts.toml /*/Manifest.toml +/*.image diff --git a/stdlib/Artifacts/src/Artifacts.jl b/stdlib/Artifacts/src/Artifacts.jl index 645e77944208b..47812fb993428 100644 --- a/stdlib/Artifacts/src/Artifacts.jl +++ b/stdlib/Artifacts/src/Artifacts.jl @@ -52,16 +52,33 @@ function artifacts_dirs(args...) return String[abspath(depot, "artifacts", args...) for depot in Base.DEPOT_PATH] else # If we've been given an override, use _only_ that directory. - return String[abspath(ARTIFACTS_DIR_OVERRIDE[], args...)] + return String[abspath(ARTIFACTS_DIR_OVERRIDE[]::String, args...)] end end +# Recursive function, let's not make this a closure because it then has to +# be boxed. +function parse_mapping(mapping::String, name::String, override_file::String) + if !isabspath(mapping) && !isempty(mapping) + mapping = tryparse(Base.SHA1, mapping) + if mapping === nothing + @error("Invalid override in '$(override_file)': entry '$(name)' must map to an absolute path or SHA1 hash!") + end + 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) +end +# Fallthrough for invalid Overrides.toml files +parse_mapping(mapping, name::String, override_file::String) = nothing + """ ARTIFACT_OVERRIDES -Artifact locations can be overridden by writing `Override.toml` files within the artifact +Artifact locations can be overridden by writing `Overrides.toml` files within the artifact directories of Pkg depots. For example, in the default depot `~/.julia`, one may create -a `~/.julia/artifacts/Override.toml` file with the following contents: +a `~/.julia/artifacts/Overrides.toml` file with the following contents: 78f35e74ff113f02274ce60dab6e92b4546ef806 = "/path/to/replacement" c76f8cda85f83a06d17de6c57aabf9e294eb2537 = "fb886e813a4aed4147d5979fcdf27457d20aa35d" @@ -88,7 +105,7 @@ 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 - # mechanisticly happen through the "hash" overrides. + # mechanistically happen through the "hash" overrides. overrides = Dict{Symbol,Any}( # Overrides by UUID :UUID => Dict{Base.UUID,Dict{String,Union{String,SHA1}}}(), @@ -103,24 +120,9 @@ function load_overrides(;force::Bool = false)::Dict{Symbol, Any} # Load the toml file depot_override_dict = parse_toml(override_file) - function parse_mapping(mapping::String, name::String) - if !isabspath(mapping) && !isempty(mapping) - mapping = tryparse(Base.SHA1, mapping) - if mapping === nothing - @error("Invalid override in '$(override_file)': entry '$(name)' must map to an absolute path or SHA1 hash!") - end - end - return mapping - end - function parse_mapping(mapping::Dict, name::String) - return Dict(k => parse_mapping(v, name) for (k, v) in mapping) - end - # Fallthrough for invalid Overrides.toml files - parse_mapping(mapping, name::String) = nothing - for (k, mapping) in depot_override_dict # First, parse the mapping. Is it an absolute path, a valid SHA1-hash, or neither? - mapping = parse_mapping(mapping, k) + mapping = parse_mapping(mapping, k, override_file) if mapping === nothing @error("Invalid override in '$(override_file)': failed to parse entry `$(k)`") continue @@ -242,7 +244,7 @@ end """ artifact_exists(hash::SHA1; honor_overrides::Bool=true) -Returns whether or not the given artifact (identified by its sha1 git tree hash) exists +Return whether or not the given artifact (identified by its sha1 git tree hash) exists on-disk. Note that it is possible that the given artifact exists in multiple locations (e.g. within multiple depots). @@ -267,22 +269,22 @@ function unpack_platform(entry::Dict{String,Any}, name::String, end if !haskey(entry, "arch") - @error("Invalid artifacts file at '$(artifacts_toml)': platform-specific artifact entrty '$name' missing 'arch' key") + @error("Invalid artifacts file at '$(artifacts_toml)': platform-specific artifact entry '$name' missing 'arch' key") return nothing end # Collect all String-valued mappings in `entry` and use them as tags - tags = Dict{Symbol, String}() + tags = Dict{String, String}() for (k, v) in entry if v isa String - tags[Symbol(k)] = v + tags[k] = v end end # Removing some known entries that shouldn't be passed through `tags` - delete!(tags, :os) - delete!(tags, :arch) - delete!(tags, Symbol("git-tree-sha1")) - return Platform(entry["arch"], entry["os"]; tags...) + delete!(tags, "os") + delete!(tags, "arch") + delete!(tags, "git-tree-sha1") + return Platform(entry["arch"], entry["os"], tags) end function pack_platform!(meta::Dict, p::AbstractPlatform) @@ -313,7 +315,7 @@ end """ process_overrides(artifact_dict::Dict, pkg_uuid::Base.UUID) -When loading an `Artifacts.toml` file, we must check `Override.toml` files to see if any +When loading an `Artifacts.toml` file, we must check `Overrides.toml` files to see if any of the artifacts within it have been overridden by UUID. If they have, we honor the overrides by inspecting the hashes of the targeted artifacts, then overriding them to point to the given override, punting the actual redirection off to the hash-based @@ -325,7 +327,7 @@ function process_overrides(artifact_dict::Dict, pkg_uuid::Base.UUID) # 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] + pkg_overrides = overrides[:UUID][pkg_uuid]::Dict{String, <:Any} for name in keys(artifact_dict) # Skip names that we're not overriding @@ -455,7 +457,7 @@ end include_lazy = false, pkg_uuid = nothing) -Returns a dictionary where every entry is an artifact from the given `Artifacts.toml` +Return a dictionary where every entry is an artifact from the given `Artifacts.toml` that should be downloaded for the requested platform. Lazy artifacts are included if `include_lazy` is set. """ @@ -524,14 +526,16 @@ function jointail(dir, tail) end function _artifact_str(__module__, artifacts_toml, name, path_tail, artifact_dict, hash, platform, @nospecialize(lazyartifacts)) - if haskey(Base.module_keys, __module__) + moduleroot = Base.moduleroot(__module__) + if haskey(Base.module_keys, moduleroot) # Process overrides for this UUID, if we know what it is - process_overrides(artifact_dict, Base.module_keys[__module__].uuid) + process_overrides(artifact_dict, Base.module_keys[moduleroot].uuid) end # If the artifact exists, we're in the happy path and we can immediately # return the path to the artifact: - for dir in artifact_paths(hash; honor_overrides=true) + dirs = artifact_paths(hash; honor_overrides=true) + for dir in dirs if isdir(dir) return jointail(dir, path_tail) end @@ -548,13 +552,27 @@ function _artifact_str(__module__, artifacts_toml, name, path_tail, artifact_dic end error("Artifact $(repr(name)) is a lazy artifact; package developers must call `using LazyArtifacts` in $(__module__) before using lazy artifacts.") end - error("Artifact $(repr(name)) was not installed correctly. Try `using Pkg; Pkg.instantiate()` to re-install all missing resources.") + + path_str = if length(dirs) == 1 + "path \"$(first(dirs))\". " + else + string("paths:\n", join(" " .* contractuser.(dirs), '\n'), '\n') + end + + suggestion_str = if query_override(hash) !== nothing + "Check that your `Overrides.toml` file is correct (https://pkgdocs.julialang.org/v1/artifacts/#Overriding-artifact-locations)." + else + "Try `using Pkg; Pkg.instantiate()` to re-install all missing resources if the artifact is part of a package \ + or call `Pkg.ensure_artifact_installed` (https://pkgdocs.julialang.org/v1/api/#Pkg.Artifacts.ensure_artifact_installed) if not." + end + + error("Artifact $(repr(name)) was not found by looking in the $(path_str)$suggestion_str") end raw""" split_artifact_slash(name::String) -Splits an artifact indexing string by path deliminters, isolates the first path element, +Splits an artifact indexing string by path delimiters, isolates the first path element, returning that and the `joinpath()` of the remaining arguments. This normalizes all path separators to the native path separator for the current platform. Examples: @@ -595,7 +613,7 @@ end artifact_slash_lookup(name::String, atifact_dict::Dict, artifacts_toml::String, platform::Platform) -Returns `artifact_name`, `artifact_path_tail`, and `hash` by looking the results up in +Return `artifact_name`, `artifact_path_tail`, and `hash` by looking the results up in the given `artifacts_toml`, first extracting the name and path tail from the given `name` to support slash-indexing within the given artifact. """ @@ -636,13 +654,18 @@ access a single file/directory within an artifact. Example: !!! compat "Julia 1.6" Slash-indexing requires at least Julia 1.6. """ -macro artifact_str(name, platform=nothing) +macro artifact_str(name, platform=nothing, artifacts_toml_path=nothing) # Find Artifacts.toml file we're going to load from srcfile = string(__source__.file) if ((isinteractive() && startswith(srcfile, "REPL[")) || (!isinteractive() && srcfile == "none")) && !isfile(srcfile) srcfile = pwd() end - local artifacts_toml = find_artifacts_toml(srcfile) + # Sometimes we know the exact path to the Artifacts.toml file, so we can save some lookups + local artifacts_toml = if artifacts_toml_path === nothing || artifacts_toml_path == :(nothing) + find_artifacts_toml(srcfile) + else + eval(artifacts_toml_path) + end if artifacts_toml === nothing error(string( "Cannot locate '(Julia)Artifacts.toml' file when attempting to use artifact '", @@ -672,7 +695,7 @@ macro artifact_str(name, platform=nothing) # If `name` is a constant, (and we're using the default `Platform`) we can actually load # and parse the `Artifacts.toml` file now, saving the work from runtime. - if isa(name, AbstractString) && platform === nothing + if isa(name, AbstractString) && (platform === nothing || platform == :(nothing)) # To support slash-indexing, we need to split the artifact name from the path tail: platform = HostPlatform() artifact_name, artifact_path_tail, hash = artifact_slash_lookup(name, artifact_dict, artifacts_toml, platform) @@ -696,7 +719,7 @@ end with_artifacts_directory(f::Function, artifacts_dir::AbstractString) = with_artifacts_directory(f, String(artifacts_dir)::String) query_override(pkg::Base.UUID, artifact_name::AbstractString; overrides::Dict=load_overrides()) = - query_override(pkg, String(artifact_name)::String; overrides=convert(Dict{Symbol, Any}(overrides))) + query_override(pkg, String(artifact_name)::String; overrides=convert(Dict{Symbol, Any}, overrides)) unpack_platform(entry::Dict, name::AbstractString, artifacts_toml::AbstractString) = unpack_platform(convert(Dict{String, Any}, entry), String(name)::String, String(artifacts_toml)::String) load_artifacts_toml(artifacts_toml::AbstractString; kwargs...) = @@ -718,4 +741,12 @@ split_artifact_slash(name::AbstractString) = artifact_slash_lookup(name::AbstractString, artifact_dict::Dict, artifacts_toml::AbstractString) = artifact_slash_lookup(String(name)::String, artifact_dict, String(artifacts_toml)::String) +# Precompilation to reduce latency +precompile(load_artifacts_toml, (String,)) +precompile(NamedTuple{(:pkg_uuid,)}, (Tuple{Base.UUID},)) +precompile(Core.kwfunc(load_artifacts_toml), (NamedTuple{(:pkg_uuid,), Tuple{Base.UUID}}, typeof(load_artifacts_toml), String)) +precompile(parse_mapping, (String, String, String)) +precompile(parse_mapping, (Dict{String, Any}, String, String)) + + end # module Artifacts diff --git a/stdlib/Artifacts/test/refresh_artifacts.jl b/stdlib/Artifacts/test/refresh_artifacts.jl index a70e13db1ee93..7078912c00072 100644 --- a/stdlib/Artifacts/test/refresh_artifacts.jl +++ b/stdlib/Artifacts/test/refresh_artifacts.jl @@ -12,11 +12,11 @@ let if meta isa Array for meta in meta get(meta, "lazy", false) && continue - ensure_artifact_installed(name, meta, toml; platform=unused) + ensure_artifact_installed(name, meta, toml; platform=unused, io = devnull) end else; meta::Dict get(meta, "lazy", false) && continue - ensure_artifact_installed(name, meta, toml; platform=unused) + ensure_artifact_installed(name, meta, toml; platform=unused, io = devnull) end end end diff --git a/stdlib/Artifacts/test/runtests.jl b/stdlib/Artifacts/test/runtests.jl index 7527b548061ce..248d851ccad79 100644 --- a/stdlib/Artifacts/test/runtests.jl +++ b/stdlib/Artifacts/test/runtests.jl @@ -91,6 +91,9 @@ end HelloWorldC_exe_path = joinpath(HelloWorldC_dir, "bin", "hello_world$(exeext)") @test isfile(HelloWorldC_exe_path) + HelloWorldC_dir_explicit_artifact = eval(:(@artifact_str "HelloWorldC" nothing joinpath(@__DIR__, "Artifacts.toml"))) + @test isdir(HelloWorldC_dir_explicit_artifact) + # Simple slash-indexed lookup HelloWorldC_bin_path = artifact"HelloWorldC/bin" @test isdir(HelloWorldC_bin_path) @@ -137,7 +140,7 @@ end mktempdir() do tempdir with_artifacts_directory(tempdir) do ex = @test_throws ErrorException artifact"HelloWorldC" - @test startswith(ex.value.msg, "Artifact \"HelloWorldC\" was not installed correctly. ") + @test startswith(ex.value.msg, "Artifact \"HelloWorldC\" was not found ") ex = @test_throws ErrorException artifact"socrates" @test startswith(ex.value.msg, "Artifact \"socrates\" is a lazy artifact; ") diff --git a/stdlib/Base64/src/Base64.jl b/stdlib/Base64/src/Base64.jl index 3f5a8f369130a..f1fef096888ed 100644 --- a/stdlib/Base64/src/Base64.jl +++ b/stdlib/Base64/src/Base64.jl @@ -3,7 +3,8 @@ """ Base64 -Functionality for base-64 encoded strings and IO. +Functionality for [base64 encoding and decoding](https://en.wikipedia.org/wiki/Base64), +a method to represent binary data using text, common on the web. """ module Base64 @@ -32,7 +33,7 @@ include("decode.jl") """ stringmime(mime, x; context=nothing) -Returns an `AbstractString` containing the representation of `x` in the +Return an `AbstractString` containing the representation of `x` in the requested `mime` type. This is similar to [`repr(mime, x)`](@ref) except that binary data is base64-encoded as an ASCII string. diff --git a/stdlib/Base64/src/decode.jl b/stdlib/Base64/src/decode.jl index c66f8ad9904b8..056293528e142 100644 --- a/stdlib/Base64/src/decode.jl +++ b/stdlib/Base64/src/decode.jl @@ -150,7 +150,6 @@ function decode_slow(b1, b2, b3, b4, buffer, i, input, ptr, n, rest) b4 = decode(read(input, UInt8)) else b4 = BASE64_CODE_END - break end end @@ -158,13 +157,13 @@ function decode_slow(b1, b2, b3, b4, buffer, i, input, ptr, n, rest) k = 0 if b1 < 0x40 && b2 < 0x40 && b3 < 0x40 && b4 < 0x40 k = 3 - elseif b1 < 0x40 && b2 < 0x40 && b3 < 0x40 && b4 == BASE64_CODE_PAD + elseif b1 < 0x40 && b2 < 0x40 && b3 < 0x40 && (b4 == BASE64_CODE_PAD || b4 == BASE64_CODE_END) b4 = 0x00 k = 2 - elseif b1 < 0x40 && b2 < 0x40 && b3 == b4 == BASE64_CODE_PAD + elseif b1 < 0x40 && b2 < 0x40 && (b3 == BASE64_CODE_PAD || b3 == BASE64_CODE_END) && (b4 == BASE64_CODE_PAD || b4 == BASE64_CODE_END) b3 = b4 = 0x00 k = 1 - elseif b1 == b2 == b3 == BASE64_CODE_IGN && b4 == BASE64_CODE_END + elseif b1 == b2 == b3 == b4 == BASE64_CODE_END b1 = b2 = b3 = b4 = 0x00 else throw(ArgumentError("malformed base64 sequence")) diff --git a/stdlib/Base64/test/runtests.jl b/stdlib/Base64/test/runtests.jl index ba6e178f2917c..11d0a3cca4348 100644 --- a/stdlib/Base64/test/runtests.jl +++ b/stdlib/Base64/test/runtests.jl @@ -87,6 +87,21 @@ const longDecodedText = "name = \"Genie\"\nuuid = \"c43c736e-a2d1-11e8-161f-af95 # issue #32397 @test String(base64decode(longEncodedText)) == longDecodedText; + + # Optional padding + @test base64decode("AQ==") == base64decode("AQ") + @test base64decode("zzzzAQ==") == base64decode("zzzzAQ") + @test base64decode("AQI=") == base64decode("AQI") + + # Too short, 6 bits do not cover a full byte. + @test_throws ArgumentError base64decode("a") + @test_throws ArgumentError base64decode("a===") + @test_throws ArgumentError base64decode("ZZZZa") + @test_throws ArgumentError base64decode("ZZZZa===") + + # Bit padding should be ignored, which means that `jl` and `jk` should give the same result. + @test base64decode("jl") == base64decode("jk") == base64decode("jk==") == [142] + @test base64decode("Aa") == base64decode("AS") == base64decode("AS==") == [1] end @testset "Random data" begin diff --git a/stdlib/CRC32c/docs/src/index.md b/stdlib/CRC32c/docs/src/index.md index 13047099a7f9d..24a073d1e3938 100644 --- a/stdlib/CRC32c/docs/src/index.md +++ b/stdlib/CRC32c/docs/src/index.md @@ -1,5 +1,7 @@ # CRC32c +Standard library module for computing the CRC-32c checksum. + ```@docs CRC32c.crc32c CRC32c.crc32c(::IO, ::Integer, ::UInt32) diff --git a/stdlib/CRC32c/src/CRC32c.jl b/stdlib/CRC32c/src/CRC32c.jl index 42a5f468a8886..35d2d4cb339d6 100644 --- a/stdlib/CRC32c/src/CRC32c.jl +++ b/stdlib/CRC32c/src/CRC32c.jl @@ -36,7 +36,7 @@ function crc32c end crc32c(a::Union{Array{UInt8},FastContiguousSubArray{UInt8,N,<:Array{UInt8}} where N}, crc::UInt32=0x00000000) = Base._crc32c(a, crc) -crc32c(s::String, crc::UInt32=0x00000000) = Base._crc32c(s, crc) +crc32c(s::Union{String, SubString{String}}, crc::UInt32=0x00000000) = Base._crc32c(s, crc) """ crc32c(io::IO, [nb::Integer,] crc::UInt32=0x00000000) diff --git a/stdlib/CRC32c/test/runtests.jl b/stdlib/CRC32c/test/runtests.jl index b385880850abc..e9e933ee2451c 100644 --- a/stdlib/CRC32c/test/runtests.jl +++ b/stdlib/CRC32c/test/runtests.jl @@ -6,7 +6,9 @@ using CRC32c function test_crc32c(crc32c) # CRC32c checksum (test data generated from @andrewcooke's CRC.jl package) for (n,crc) in [(0,0x00000000),(1,0xa016d052),(2,0x03f89f52),(3,0xf130f21e),(4,0x29308cf4),(5,0x53518fab),(6,0x4f4dfbab),(7,0xbd3a64dc),(8,0x46891f81),(9,0x5a14b9f9),(10,0xb219db69),(11,0xd232a91f),(12,0x51a15563),(13,0x9f92de41),(14,0x4d8ae017),(15,0xc8b74611),(16,0xa0de6714),(17,0x672c992a),(18,0xe8206eb6),(19,0xc52fd285),(20,0x327b0397),(21,0x318263dd),(22,0x08485ccd),(23,0xea44d29e),(24,0xf6c0cb13),(25,0x3969bba2),(26,0x6a8810ec),(27,0x75b3d0df),(28,0x82d535b1),(29,0xbdf7fc12),(30,0x1f836b7d),(31,0xd29f33af),(32,0x8e4acb3e),(33,0x1cbee2d1),(34,0xb25f7132),(35,0xb0fa484c),(36,0xb9d262b4),(37,0x3207fe27),(38,0xa024d7ac),(39,0x49a2e7c5),(40,0x0e2c157f),(41,0x25f7427f),(42,0x368c6adc),(43,0x75efd4a5),(44,0xa84c5c31),(45,0x0fc817b2),(46,0x8d99a881),(47,0x5cc3c078),(48,0x9983d5e2),(49,0x9267c2db),(50,0xc96d4745),(51,0x058d8df3),(52,0x453f9cf3),(53,0xb714ade1),(54,0x55d3c2bc),(55,0x495710d0),(56,0x3bddf494),(57,0x4f2577d0),(58,0xdae0f604),(59,0x3c57c632),(60,0xfe39bbb0),(61,0x6f5d1d41),(62,0x7d996665),(63,0x68c738dc),(64,0x8dfea7ae)] - @test crc32c(UInt8[1:n;]) == crc == crc32c(String(UInt8[1:n;])) + s = String(UInt8[1:n;]) + ss = SubString(String(UInt8[0:(n+1);]), 2:(n+1)) + @test crc32c(UInt8[1:n;]) == crc == crc32c(s) == crc32c(ss) end # test that crc parameter is equivalent to checksum of concatenated data, @@ -48,7 +50,11 @@ unsafe_crc32c_sw(a, n, crc) = ccall(:jl_crc32c_sw, UInt32, (UInt32, Ptr{UInt8}, Csize_t), crc, a, n) crc32c_sw(a::Union{Array{UInt8},Base.FastContiguousSubArray{UInt8,N,<:Array{UInt8}} where N}, crc::UInt32=0x00000000) = unsafe_crc32c_sw(a, length(a), crc) -crc32c_sw(s::String, crc::UInt32=0x00000000) = unsafe_crc32c_sw(s, sizeof(s), crc) + +function crc32c_sw(s::Union{String, SubString{String}}, crc::UInt32=0x00000000) + unsafe_crc32c_sw(s, sizeof(s), crc) +end + function crc32c_sw(io::IO, nb::Integer, crc::UInt32=0x00000000) nb < 0 && throw(ArgumentError("number of bytes to checksum must be ≥ 0")) buf = Vector{UInt8}(undef, min(nb, 24576)) diff --git a/stdlib/CompilerSupportLibraries_jll/Project.toml b/stdlib/CompilerSupportLibraries_jll/Project.toml index 15ca525723c07..fc5883cc79802 100644 --- a/stdlib/CompilerSupportLibraries_jll/Project.toml +++ b/stdlib/CompilerSupportLibraries_jll/Project.toml @@ -4,7 +4,7 @@ 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 # automatic usage of BB-built CSLs on extremely up-to-date systems! -version = "0.5.0+0" +version = "1.0.2+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 af8a679d87e10..bd7a0571f9d5a 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -14,13 +14,13 @@ export libgfortran, libstdcxx, libgomp # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libgfortran_handle = C_NULL -libgfortran_path = "" -libstdcxx_handle = C_NULL -libstdcxx_path = "" -libgomp_handle = C_NULL -libgomp_path = "" +artifact_dir::String = "" +libgfortran_handle::Ptr{Cvoid} = C_NULL +libgfortran_path::String = "" +libstdcxx_handle::Ptr{Cvoid} = C_NULL +libstdcxx_path::String = "" +libgomp_handle::Ptr{Cvoid} = C_NULL +libgomp_path::String = "" if Sys.iswindows() if arch(HostPlatform()) == "x86_64" @@ -31,20 +31,25 @@ if Sys.iswindows() const libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll") const libstdcxx = "libstdc++-6.dll" const libgomp = "libgomp-1.dll" + const libssp = "libssp-0.dll" elseif Sys.isapple() - if arch(HostPlatform()) == "aarch64" - const libgcc_s = "@rpath/libgcc_s.2.dylib" + if arch(HostPlatform()) == "aarch64" || libgfortran_version(HostPlatform()) == v"5" + const libgcc_s = "@rpath/libgcc_s.1.1.dylib" else const libgcc_s = "@rpath/libgcc_s.1.dylib" end const libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib") const libstdcxx = "@rpath/libstdc++.6.dylib" const libgomp = "@rpath/libgomp.1.dylib" + const libssp = "@rpath/libssp.0.dylib" else const libgcc_s = "libgcc_s.so.1" const libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major) const libstdcxx = "libstdc++.so.6" const libgomp = "libgomp.so.1" + if libc(HostPlatform()) != "musl" + const libssp = "libssp.so.0" + end end function __init__() @@ -56,6 +61,9 @@ function __init__() global libstdcxx_path = dlpath(libstdcxx_handle) global libgomp_handle = dlopen(libgomp) global libgomp_path = dlpath(libgomp_handle) + @static if libc(HostPlatform()) != "musl" + dlopen(libssp; throw_error = false) + end global artifact_dir = dirname(Sys.BINDIR) LIBPATH[] = dirname(libgcc_s_path) push!(LIBPATH_list, LIBPATH[]) diff --git a/stdlib/Dates/docs/src/index.md b/stdlib/Dates/docs/src/index.md index 4975f175bbf16..aa46f7b827f10 100644 --- a/stdlib/Dates/docs/src/index.md +++ b/stdlib/Dates/docs/src/index.md @@ -1,3 +1,7 @@ +```@meta +EditURL = "https://github.com/JuliaLang/julia/blob/master/stdlib/Dates/docs/src/index.md" +``` + # Dates ```@meta @@ -96,8 +100,7 @@ missing parts of dates and times so long as the preceding parts are given. The o default values. For example, `Date("1981-03", dateformat"y-m-d")` returns `1981-03-01`, whilst `Date("31/12", dateformat"d/m/y")` gives `0001-12-31`. (Note that the default year is 1 AD/CE.) -Consequently, an empty string will always return `0001-01-01` for `Date`s, -and `0001-01-01T00:00:00.000` for `DateTime`s. +An empty string, however, always throws an `ArgumentError`. Fixed-width slots are specified by repeating the period character the number of times corresponding to the width with no delimiter between characters. So `dateformat"yyyymmdd"` would correspond to a date @@ -153,14 +156,13 @@ an optional third argument of type `DateFormat` specifying the format; for examp `parse(Date, "06.23.2013", dateformat"m.d.y")`, or `tryparse(DateTime, "1999-12-31T23:59:59")` which uses the default format. The notable difference between the functions is that with [`tryparse`](@ref), -an error is not thrown if the string is in an invalid format; -instead `nothing` is returned. Note however that as with the constructors -above, empty date and time parts assume -default values and consequently an empty string (`""`) is valid -for _any_ `DateFormat`, giving for example a `Date` of `0001-01-01`. Code -relying on `parse` or `tryparse` for `Date` and `DateTime` parsing should -therefore also check whether parsed strings are empty before using the -result. +an error is not thrown if the string is empty or in an invalid format; +instead `nothing` is returned. + +!!! compat "Julia 1.9" + Before Julia 1.9, empty strings could be passed to constructors and `parse` + without error, returning as appropriate `DateTime(1)`, `Date(1)` or `Time(0)`. + Likewise, `tryparse` did not return `nothing`. A full suite of parsing and formatting tests and examples is available in [`stdlib/Dates/test/io.jl`](https://github.com/JuliaLang/julia/blob/master/stdlib/Dates/test/io.jl). @@ -363,7 +365,7 @@ julia> Dates.monthabbr(t;locale="french") ``` Since the abbreviated versions of the days are not loaded, trying to use the -function `dayabbr` will error. +function `dayabbr` will throw an error. ```jldoctest tdate2 julia> Dates.dayabbr(t;locale="french") @@ -642,8 +644,8 @@ by 10. As Julia [`Date`](@ref) and [`DateTime`](@ref) values are represented according to the ISO 8601 standard, `0000-01-01T00:00:00` was chosen as base (or "rounding epoch") from which to begin the count of days (and milliseconds) used in rounding calculations. (Note that this differs slightly -from Julia's internal representation of [`Date`](@ref) s using Rata Die notation; but since the -ISO 8601 standard is most visible to the end user, `0000-01-01T00:00:00` was chosen as the rounding +from Julia's internal representation of [`Date`](@ref) s using [Rata Die notation](https://en.wikipedia.org/wiki/Rata_Die); +but since the ISO 8601 standard is most visible to the end user, `0000-01-01T00:00:00` was chosen as the rounding epoch instead of the `0000-12-31T00:00:00` used internally to minimize confusion.) The only exception to the use of `0000-01-01T00:00:00` as the rounding epoch is when rounding diff --git a/stdlib/Dates/src/Dates.jl b/stdlib/Dates/src/Dates.jl index 6164216cbd1af..a111ea24089c4 100644 --- a/stdlib/Dates/src/Dates.jl +++ b/stdlib/Dates/src/Dates.jl @@ -14,13 +14,13 @@ For time zone functionality, see the TimeZones.jl package. julia> dt = DateTime(2017,12,31,23,59,59,999) 2017-12-31T23:59:59.999 -julia> d1 = Date(Dates.Month(12), Dates.Year(2017)) +julia> d1 = Date(Month(12), Year(2017)) 2017-12-01 -julia> d2 = Date("2017-12-31", Dates.DateFormat("y-m-d")) +julia> d2 = Date("2017-12-31", DateFormat("y-m-d")) 2017-12-31 -julia> Dates.yearmonthday(d2) +julia> yearmonthday(d2) (2017, 12, 31) julia> d2-d1 diff --git a/stdlib/Dates/src/accessors.jl b/stdlib/Dates/src/accessors.jl index 10e0142c83f21..05e9017303ef1 100644 --- a/stdlib/Dates/src/accessors.jl +++ b/stdlib/Dates/src/accessors.jl @@ -97,13 +97,13 @@ week of 2004. # Examples ```jldoctest -julia> Dates.week(Date(1989, 6, 22)) +julia> week(Date(1989, 6, 22)) 25 -julia> Dates.week(Date(2005, 1, 1)) +julia> week(Date(2005, 1, 1)) 53 -julia> Dates.week(Date(2004, 12, 31)) +julia> week(Date(2004, 12, 31)) 53 ``` """ diff --git a/stdlib/Dates/src/adjusters.jl b/stdlib/Dates/src/adjusters.jl index d5617ba8cf93c..245e2678a9d77 100644 --- a/stdlib/Dates/src/adjusters.jl +++ b/stdlib/Dates/src/adjusters.jl @@ -29,7 +29,7 @@ Truncates the value of `dt` according to the provided `Period` type. # Examples ```jldoctest -julia> trunc(Dates.DateTime("1996-01-01T12:30:00"), Dates.Day) +julia> trunc(DateTime("1996-01-01T12:30:00"), Day) 1996-01-01T00:00:00 ``` """ @@ -43,7 +43,7 @@ Adjusts `dt` to the Monday of its week. # Examples ```jldoctest -julia> Dates.firstdayofweek(DateTime("1996-01-05T12:30:00")) +julia> firstdayofweek(DateTime("1996-01-05T12:30:00")) 1996-01-01T00:00:00 ``` """ @@ -59,7 +59,7 @@ Adjusts `dt` to the Sunday of its week. # Examples ```jldoctest -julia> Dates.lastdayofweek(DateTime("1996-01-05T12:30:00")) +julia> lastdayofweek(DateTime("1996-01-05T12:30:00")) 1996-01-07T00:00:00 ``` """ @@ -75,7 +75,7 @@ Adjusts `dt` to the first day of its month. # Examples ```jldoctest -julia> Dates.firstdayofmonth(DateTime("1996-05-20")) +julia> firstdayofmonth(DateTime("1996-05-20")) 1996-05-01T00:00:00 ``` """ @@ -91,7 +91,7 @@ Adjusts `dt` to the last day of its month. # Examples ```jldoctest -julia> Dates.lastdayofmonth(DateTime("1996-05-20")) +julia> lastdayofmonth(DateTime("1996-05-20")) 1996-05-31T00:00:00 ``` """ @@ -110,7 +110,7 @@ Adjusts `dt` to the first day of its year. # Examples ```jldoctest -julia> Dates.firstdayofyear(DateTime("1996-05-20")) +julia> firstdayofyear(DateTime("1996-05-20")) 1996-01-01T00:00:00 ``` """ @@ -126,7 +126,7 @@ Adjusts `dt` to the last day of its year. # Examples ```jldoctest -julia> Dates.lastdayofyear(DateTime("1996-05-20")) +julia> lastdayofyear(DateTime("1996-05-20")) 1996-12-31T00:00:00 ``` """ @@ -145,10 +145,10 @@ Adjusts `dt` to the first day of its quarter. # Examples ```jldoctest -julia> Dates.firstdayofquarter(DateTime("1996-05-20")) +julia> firstdayofquarter(DateTime("1996-05-20")) 1996-04-01T00:00:00 -julia> Dates.firstdayofquarter(DateTime("1996-08-20")) +julia> firstdayofquarter(DateTime("1996-08-20")) 1996-07-01T00:00:00 ``` """ @@ -168,10 +168,10 @@ Adjusts `dt` to the last day of its quarter. # Examples ```jldoctest -julia> Dates.lastdayofquarter(DateTime("1996-05-20")) +julia> lastdayofquarter(DateTime("1996-05-20")) 1996-06-30T00:00:00 -julia> Dates.lastdayofquarter(DateTime("1996-08-20")) +julia> lastdayofquarter(DateTime("1996-08-20")) 1996-09-30T00:00:00 ``` """ @@ -221,13 +221,13 @@ pursue before throwing an error (given that `f::Function` is never satisfied). # Examples ```jldoctest -julia> Date(date -> Dates.week(date) == 20, 2010, 01, 01) +julia> Date(date -> week(date) == 20, 2010, 01, 01) 2010-05-17 -julia> Date(date -> Dates.year(date) == 2010, 2000, 01, 01) +julia> Date(date -> year(date) == 2010, 2000, 01, 01) 2010-01-01 -julia> Date(date -> Dates.month(date) == 10, 2000, 01, 01; limit = 5) +julia> Date(date -> month(date) == 10, 2000, 01, 01; limit = 5) ERROR: ArgumentError: Adjustment limit reached: 5 iterations Stacktrace: [...] @@ -248,10 +248,10 @@ pursue before throwing an error (in the case that `f::Function` is never satisfi # Examples ```jldoctest -julia> DateTime(dt -> Dates.second(dt) == 40, 2010, 10, 20, 10; step = Dates.Second(1)) +julia> DateTime(dt -> second(dt) == 40, 2010, 10, 20, 10; step = Second(1)) 2010-10-20T10:00:40 -julia> DateTime(dt -> Dates.hour(dt) == 20, 2010, 10, 20, 10; step = Dates.Hour(1), limit = 5) +julia> DateTime(dt -> hour(dt) == 20, 2010, 10, 20, 10; step = Hour(1), limit = 5) ERROR: ArgumentError: Adjustment limit reached: 5 iterations Stacktrace: [...] @@ -291,13 +291,13 @@ arguments are provided, the default step will be `Millisecond(1)` instead of `Se # Examples ```jldoctest -julia> Dates.Time(t -> Dates.minute(t) == 30, 20) +julia> Time(t -> minute(t) == 30, 20) 20:30:00 -julia> Dates.Time(t -> Dates.minute(t) == 0, 20) +julia> Time(t -> minute(t) == 0, 20) 20:00:00 -julia> Dates.Time(t -> Dates.hour(t) == 10, 3; limit = 5) +julia> Time(t -> hour(t) == 10, 3; limit = 5) ERROR: ArgumentError: Adjustment limit reached: 5 iterations Stacktrace: [...] diff --git a/stdlib/Dates/src/arithmetic.jl b/stdlib/Dates/src/arithmetic.jl index 6537f4e1caa82..a847f749d0154 100644 --- a/stdlib/Dates/src/arithmetic.jl +++ b/stdlib/Dates/src/arithmetic.jl @@ -7,6 +7,7 @@ # TimeType arithmetic (+)(x::TimeType) = x (-)(x::T, y::T) where {T<:TimeType} = x.instant - y.instant +(-)(x::TimeType, y::TimeType) = -(promote(x, y)...) # Date-Time arithmetic """ diff --git a/stdlib/Dates/src/conversions.jl b/stdlib/Dates/src/conversions.jl index 161dc3791afab..30f1f2581d1fa 100644 --- a/stdlib/Dates/src/conversions.jl +++ b/stdlib/Dates/src/conversions.jl @@ -46,9 +46,11 @@ Take the number of seconds since unix epoch `1970-01-01T00:00:00` and convert to corresponding `DateTime`. """ function unix2datetime(x) - rata = UNIXEPOCH + round(Int64, Int64(1000) * x) + # Rounding should match `now` below + rata = UNIXEPOCH + trunc(Int64, Int64(1000) * x) return DateTime(UTM(rata)) end + """ datetime2unix(dt::DateTime) -> Float64 @@ -80,6 +82,13 @@ today() = Date(now()) now(::Type{UTC}) -> DateTime Return a `DateTime` corresponding to the user's system time as UTC/GMT. +For other time zones, see the TimeZones.jl package. + +# Example +```julia +julia> now(UTC) +2023-01-04T10:52:24.864 +``` """ now(::Type{UTC}) = unix2datetime(time()) diff --git a/stdlib/Dates/src/deprecated.jl b/stdlib/Dates/src/deprecated.jl index 3c8a58f6e75e7..b50d8501e7570 100644 --- a/stdlib/Dates/src/deprecated.jl +++ b/stdlib/Dates/src/deprecated.jl @@ -65,3 +65,6 @@ for op in (:+, :-) end end end + +@deprecate argerror(msg::String) ArgumentError(msg) false +@deprecate argerror() nothing false diff --git a/stdlib/Dates/src/io.jl b/stdlib/Dates/src/io.jl index 7e007ced0bbee..257e86064c2fb 100644 --- a/stdlib/Dates/src/io.jl +++ b/stdlib/Dates/src/io.jl @@ -55,7 +55,7 @@ Base.show(io::IO, ::MIME"text/plain", t::Time) = print(io, t) Base.print(io::IO, t::Time) = print(io, string(t)) function Base.show(io::IO, t::Time) - if get(io, :compact, false) + if get(io, :compact, false)::Bool print(io, t) else values = [ @@ -356,23 +356,23 @@ Construct a date formatting object that can be used for parsing date strings or formatting a date object as a string. The following character codes can be used to construct the `format` string: -| Code | Matches | Comment | -|:-----------|:----------|:-------------------------------------------------------------| -| `y` | 1996, 96 | Returns year of 1996, 0096 | -| `Y` | 1996, 96 | Returns year of 1996, 0096. Equivalent to `y` | -| `m` | 1, 01 | Matches 1 or 2-digit months | -| `u` | Jan | Matches abbreviated months according to the `locale` keyword | -| `U` | January | Matches full month names according to the `locale` keyword | -| `d` | 1, 01 | Matches 1 or 2-digit days | -| `H` | 00 | Matches hours (24-hour clock) | -| `I` | 00 | For outputting hours with 12-hour clock | -| `M` | 00 | Matches minutes | -| `S` | 00 | Matches seconds | -| `s` | .500 | Matches milliseconds | -| `e` | Mon, Tues | Matches abbreviated days of the week | -| `E` | Monday | Matches full name days of the week | -| `p` | AM | Matches AM/PM (case-insensitive) | -| `yyyymmdd` | 19960101 | Matches fixed-width year, month, and day | +| Code | Matches | Comment | +|:-----------|:----------|:--------------------------------------------------------------| +| `Y` | 1996, 96 | Returns year of 1996, 0096 | +| `y` | 1996, 96 | Same as `Y` on `parse` but discards excess digits on `format` | +| `m` | 1, 01 | Matches 1 or 2-digit months | +| `u` | Jan | Matches abbreviated months according to the `locale` keyword | +| `U` | January | Matches full month names according to the `locale` keyword | +| `d` | 1, 01 | Matches 1 or 2-digit days | +| `H` | 00 | Matches hours (24-hour clock) | +| `I` | 00 | For outputting hours with 12-hour clock | +| `M` | 00 | Matches minutes | +| `S` | 00 | Matches seconds | +| `s` | .500 | Matches milliseconds | +| `e` | Mon, Tues | Matches abbreviated days of the week | +| `E` | Monday | Matches full name days of the week | +| `p` | AM | Matches AM/PM (case-insensitive) | +| `yyyymmdd` | 19960101 | Matches fixed-width year, month, and day | Characters not listed above are normally treated as delimiters between date and time slots. For example a `dt` string of "1996-01-15T00:00:00.0" would have a `format` string like @@ -414,8 +414,6 @@ function DateFormat(f::AbstractString, locale::DateLocale=ENGLISH) if !isempty(prev) letter, width = prev - typ = CONVERSION_SPECIFIERS[letter] - push!(tokens, DatePart{letter}(width, isempty(tran))) end @@ -434,8 +432,6 @@ function DateFormat(f::AbstractString, locale::DateLocale=ENGLISH) if !isempty(prev) letter, width = prev - typ = CONVERSION_SPECIFIERS[letter] - push!(tokens, DatePart{letter}(width, false)) end @@ -451,12 +447,8 @@ function DateFormat(f::AbstractString, locale::AbstractString) DateFormat(f, LOCALES[locale]) end -function Base.show(io::IO, df::DateFormat) - print(io, "dateformat\"") - for t in df.tokens - _show_content(io, t) - end - print(io, '"') +function Base.show(io::IO, df::DateFormat{S,T}) where {S,T} + print(io, "dateformat\"", S, '"') end Base.Broadcast.broadcastable(x::DateFormat) = Ref(x) diff --git a/stdlib/Dates/src/parse.jl b/stdlib/Dates/src/parse.jl index a5bbc686c955d..62d44177de877 100644 --- a/stdlib/Dates/src/parse.jl +++ b/stdlib/Dates/src/parse.jl @@ -198,6 +198,7 @@ end function Base.parse(::Type{DateTime}, s::AbstractString, df::typeof(ISODateTimeFormat)) i, end_pos = firstindex(s), lastindex(s) + i > end_pos && throw(ArgumentError("Cannot parse an empty string as a DateTime")) local dy dm = dd = Int64(1) @@ -279,6 +280,7 @@ end function Base.parse(::Type{T}, str::AbstractString, df::DateFormat=default_format(T)) where T<:TimeType pos, len = firstindex(str), lastindex(str) + pos > len && throw(ArgumentError("Cannot parse an empty string as a Date or Time")) val = tryparsenext_internal(T, str, pos, len, df, true) @assert val !== nothing values, endpos = val @@ -287,6 +289,7 @@ end function Base.tryparse(::Type{T}, str::AbstractString, df::DateFormat=default_format(T)) where T<:TimeType pos, len = firstindex(str), lastindex(str) + pos > len && return nothing res = tryparsenext_internal(T, str, pos, len, df, false) res === nothing && return nothing values, endpos = res diff --git a/stdlib/Dates/src/periods.jl b/stdlib/Dates/src/periods.jl index 021e91924ce59..9b7e29496e642 100644 --- a/stdlib/Dates/src/periods.jl +++ b/stdlib/Dates/src/periods.jl @@ -17,8 +17,6 @@ for period in (:Year, :Quarter, :Month, :Week, :Day, :Hour, :Minute, :Second, :M accessor_str = lowercase(period_str) # Convenience method for show() @eval _units(x::$period) = " " * $accessor_str * (abs(value(x)) == 1 ? "" : "s") - # periodisless - @eval periodisless(x::$period, y::$period) = value(x) < value(y) # AbstractString parsing (mainly for IO code) @eval $period(x::AbstractString) = $period(Base.parse(Int64, x)) # The period type is printed when output, thus it already implies its own typeinfo @@ -60,7 +58,7 @@ Base.isfinite(::Union{Type{P}, P}) where {P<:Period} = true """ default(p::Period) -> Period -Returns a sensible "default" value for the input Period by returning `T(1)` for Year, +Return a sensible "default" value for the input Period by returning `T(1)` for Year, Month, and Day, and `T(0)` for Hour, Minute, Second, and Millisecond. """ function default end @@ -105,43 +103,6 @@ Base.gcdx(a::T, b::T) where {T<:Period} = ((g, x, y) = gcdx(value(a), value(b)); Base.abs(a::T) where {T<:Period} = T(abs(value(a))) Base.sign(x::Period) = sign(value(x)) -periodisless(::Period,::Year) = true -periodisless(::Period,::Quarter) = true -periodisless(::Year,::Quarter) = false -periodisless(::Period,::Month) = true -periodisless(::Year,::Month) = false -periodisless(::Quarter,::Month) = false -periodisless(::Period,::Week) = true -periodisless(::Year,::Week) = false -periodisless(::Quarter,::Week) = false -periodisless(::Month,::Week) = false -periodisless(::Period,::Day) = true -periodisless(::Year,::Day) = false -periodisless(::Quarter,::Day) = false -periodisless(::Month,::Day) = false -periodisless(::Week,::Day) = false -periodisless(::Period,::Hour) = false -periodisless(::Minute,::Hour) = true -periodisless(::Second,::Hour) = true -periodisless(::Millisecond,::Hour) = true -periodisless(::Microsecond,::Hour) = true -periodisless(::Nanosecond,::Hour) = true -periodisless(::Period,::Minute) = false -periodisless(::Second,::Minute) = true -periodisless(::Millisecond,::Minute) = true -periodisless(::Microsecond,::Minute) = true -periodisless(::Nanosecond,::Minute) = true -periodisless(::Period,::Second) = false -periodisless(::Millisecond,::Second) = true -periodisless(::Microsecond,::Second) = true -periodisless(::Nanosecond,::Second) = true -periodisless(::Period,::Millisecond) = false -periodisless(::Microsecond,::Millisecond) = true -periodisless(::Nanosecond,::Millisecond) = true -periodisless(::Period,::Microsecond) = false -periodisless(::Nanosecond,::Microsecond) = true -periodisless(::Period,::Nanosecond) = false - # return (next coarser period, conversion factor): coarserperiod(::Type{P}) where {P<:Period} = (P, 1) coarserperiod(::Type{Nanosecond}) = (Microsecond, 1000) @@ -160,37 +121,40 @@ coarserperiod(::Type{Month}) = (Year, 12) CompoundPeriod A `CompoundPeriod` is useful for expressing time periods that are not a fixed multiple of -smaller periods. For example, \"a year and a day\" is not a fixed number of days, but can +smaller periods. For example, "a year and a day" is not a fixed number of days, but can be expressed using a `CompoundPeriod`. In fact, a `CompoundPeriod` is automatically generated by addition of different period types, e.g. `Year(1) + Day(1)` produces a `CompoundPeriod` result. """ struct CompoundPeriod <: AbstractTime - periods::Array{Period, 1} + periods::Vector{Period} function CompoundPeriod(p::Vector{Period}) n = length(p) if n > 1 - sort!(p, rev=true, lt=periodisless) + # We sort periods in decreasing order (rev = true) according to the length of + # the period's type (by = tons ∘ oneunit). We sort by type, not value, so that + # we can merge equal types. + # + # This works by computing how many nanoseconds are in a single period, and sorting + # by that. For example, (tons ∘ oneunit)(Week(10)) = tons(oneunit(Week(10))) = + # tons(Week(1)) ≈ 6.0e14, which is less than (tons ∘ oneunit)(Month(-2)) ≈ 2.6e15 + sort!(p, rev = true, by = tons ∘ oneunit) # canonicalize p by merging equal period types and removing zeros i = j = 1 while j <= n k = j + 1 - while k <= n - if typeof(p[j]) == typeof(p[k]) - p[j] += p[k] - k += 1 - else - break - end + while k <= n && typeof(p[j]) == typeof(p[k]) + p[j] += p[k] + k += 1 end - if p[j] != zero(p[j]) + if !iszero(p[j]) p[i] = p[j] i += 1 end j = k end n = i - 1 # new length - p = resize!(p, n) + p = resize!(p, n) elseif n == 1 && value(p[1]) == 0 p = Period[] end @@ -250,16 +214,16 @@ Reduces the `CompoundPeriod` into its canonical form by applying the following r # Examples ```jldoctest -julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Hour(12), Dates.Hour(13))) +julia> canonicalize(Dates.CompoundPeriod(Dates.Hour(12), Dates.Hour(13))) 1 day, 1 hour -julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Hour(-1), Dates.Minute(1))) +julia> canonicalize(Dates.CompoundPeriod(Dates.Hour(-1), Dates.Minute(1))) -59 minutes -julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Month(1), Dates.Week(-2))) +julia> canonicalize(Dates.CompoundPeriod(Dates.Month(1), Dates.Week(-2))) 1 month, -2 weeks -julia> Dates.canonicalize(Dates.CompoundPeriod(Dates.Minute(50000))) +julia> canonicalize(Dates.CompoundPeriod(Dates.Minute(50000))) 4 weeks, 6 days, 17 hours, 20 minutes ``` """ @@ -419,70 +383,44 @@ end # hitting the deprecated construct-to-convert fallback. (::Type{T})(p::Period) where {T<:Period} = convert(T, p)::T -# FixedPeriod conversions and promotion rules -const fixedperiod_conversions = [(:Week, 7), (:Day, 24), (:Hour, 60), (:Minute, 60), (:Second, 1000), - (:Millisecond, 1000), (:Microsecond, 1000), (:Nanosecond, 1)] -for i = 1:length(fixedperiod_conversions) - T, n = fixedperiod_conversions[i] - N = Int64(1) - for j = (i - 1):-1:1 # less-precise periods - Tc, nc = fixedperiod_conversions[j] - N *= nc - vmax = typemax(Int64) ÷ N - vmin = typemin(Int64) ÷ N - @eval function Base.convert(::Type{$T}, x::$Tc) - $vmin ≤ value(x) ≤ $vmax || throw(InexactError(:convert, $T, x)) - return $T(value(x) * $N) +# Conversions and promotion rules +function define_conversions(periods) + for i = eachindex(periods) + T, n = periods[i] + N = Int64(1) + for j = (i - 1):-1:firstindex(periods) # less-precise periods + Tc, nc = periods[j] + N *= nc + vmax = typemax(Int64) ÷ N + vmin = typemin(Int64) ÷ N + @eval function Base.convert(::Type{$T}, x::$Tc) + $vmin ≤ value(x) ≤ $vmax || throw(InexactError(:convert, $T, x)) + return $T(value(x) * $N) + end + end + N = n + for j = (i + 1):lastindex(periods) # more-precise periods + Tc, nc = periods[j] + @eval Base.convert(::Type{$T}, x::$Tc) = $T(divexact(value(x), $N)) + @eval Base.promote_rule(::Type{$T}, ::Type{$Tc}) = $Tc + N *= nc end - end - N = n - for j = (i + 1):length(fixedperiod_conversions) # more-precise periods - Tc, nc = fixedperiod_conversions[j] - @eval Base.convert(::Type{$T}, x::$Tc) = $T(divexact(value(x), $N)) - @eval Base.promote_rule(::Type{$T}, ::Type{$Tc}) = $Tc - N *= nc - end -end - -# other periods with fixed conversions but which aren't fixed time periods -const OtherPeriod = Union{Month, Quarter, Year} -let vmax = typemax(Int64) ÷ 12, vmin = typemin(Int64) ÷ 12 - @eval function Base.convert(::Type{Month}, x::Year) - $vmin ≤ value(x) ≤ $vmax || throw(InexactError(:convert, Month, x)) - Month(value(x) * 12) - end -end -Base.convert(::Type{Year}, x::Month) = Year(divexact(value(x), 12)) -Base.promote_rule(::Type{Year}, ::Type{Month}) = Month - -let vmax = typemax(Int64) ÷ 4, vmin = typemin(Int64) ÷ 4 - @eval function Base.convert(::Type{Quarter}, x::Year) - $vmin ≤ value(x) ≤ $vmax || throw(InexactError(:convert, Quarter, x)) - Quarter(value(x) * 4) - end -end -Base.convert(::Type{Year}, x::Quarter) = Year(divexact(value(x), 4)) -Base.promote_rule(::Type{Year}, ::Type{Quarter}) = Quarter - -let vmax = typemax(Int64) ÷ 3, vmin = typemin(Int64) ÷ 3 - @eval function Base.convert(::Type{Month}, x::Quarter) - $vmin ≤ value(x) ≤ $vmax || throw(InexactError(:convert, Month, x)) - Month(value(x) * 3) end end -Base.convert(::Type{Quarter}, x::Month) = Quarter(divexact(value(x), 3)) -Base.promote_rule(::Type{Quarter}, ::Type{Month}) = Month - +define_conversions([(:Week, 7), (:Day, 24), (:Hour, 60), (:Minute, 60), (:Second, 1000), + (:Millisecond, 1000), (:Microsecond, 1000), (:Nanosecond, 1)]) +define_conversions([(:Year, 4), (:Quarter, 3), (:Month, 1)]) # fixed is not comparable to other periods, except when both are zero (#37459) +const OtherPeriod = Union{Month, Quarter, Year} (==)(x::FixedPeriod, y::OtherPeriod) = iszero(x) & iszero(y) (==)(x::OtherPeriod, y::FixedPeriod) = y == x const zero_or_fixedperiod_seed = UInt === UInt64 ? 0x5b7fc751bba97516 : 0xeae0fdcb const nonzero_otherperiod_seed = UInt === UInt64 ? 0xe1837356ff2d2ac9 : 0x170d1b00 -otherperiod_seed(x::OtherPeriod) = iszero(value(x)) ? zero_or_fixedperiod_seed : nonzero_otherperiod_seed +otherperiod_seed(x) = iszero(value(x)) ? zero_or_fixedperiod_seed : nonzero_otherperiod_seed # tons() will overflow for periods longer than ~300,000 years, implying a hash collision -# which is relatively harmless given how infrequent such periods should appear +# which is relatively harmless given how infrequently such periods should appear Base.hash(x::FixedPeriod, h::UInt) = hash(tons(x), h + zero_or_fixedperiod_seed) # Overflow can also happen here for really long periods (~8e17 years) Base.hash(x::Year, h::UInt) = hash(12 * value(x), h + otherperiod_seed(x)) @@ -511,11 +449,7 @@ toms(c::Millisecond) = value(c) toms(c::Second) = 1000 * value(c) toms(c::Minute) = 60000 * value(c) toms(c::Hour) = 3600000 * value(c) -toms(c::Day) = 86400000 * value(c) -toms(c::Week) = 604800000 * value(c) -toms(c::Month) = 86400000.0 * 30.436875 * value(c) -toms(c::Quarter) = 86400000.0 * 91.310625 * value(c) -toms(c::Year) = 86400000.0 * 365.2425 * value(c) +toms(c::Period) = 86400000 * days(c) toms(c::CompoundPeriod) = isempty(c.periods) ? 0.0 : Float64(sum(toms, c.periods)) tons(x) = toms(x) * 1000000 tons(x::Microsecond) = value(x) * 1000 diff --git a/stdlib/Dates/src/query.jl b/stdlib/Dates/src/query.jl index c204f750f5de2..4f3b5a5c4b095 100644 --- a/stdlib/Dates/src/query.jl +++ b/stdlib/Dates/src/query.jl @@ -93,10 +93,10 @@ Return 366 if the year of `dt` is a leap year, otherwise return 365. # Examples ```jldoctest -julia> Dates.daysinyear(1999) +julia> daysinyear(1999) 365 -julia> Dates.daysinyear(2000) +julia> daysinyear(2000) 366 ``` """ @@ -114,7 +114,7 @@ Return the day of the week as an [`Int64`](@ref) with `1 = Monday, 2 = Tuesday, # Examples ```jldoctest -julia> Dates.dayofweek(Date("2000-01-01")) +julia> dayofweek(Date("2000-01-01")) 6 ``` """ @@ -159,10 +159,10 @@ the given `locale`. Also accepts `Integer`. # Examples ```jldoctest -julia> Dates.dayname(Date("2000-01-01")) +julia> dayname(Date("2000-01-01")) "Saturday" -julia> Dates.dayname(4) +julia> dayname(4) "Thursday" ``` """ @@ -179,10 +179,10 @@ in the given `locale`. Also accepts `Integer`. # Examples ```jldoctest -julia> Dates.dayabbr(Date("2000-01-01")) +julia> dayabbr(Date("2000-01-01")) "Sat" -julia> Dates.dayabbr(3) +julia> dayabbr(3) "Wed" ``` """ @@ -209,13 +209,13 @@ month, etc.` In the range 1:5. # Examples ```jldoctest -julia> Dates.dayofweekofmonth(Date("2000-02-01")) +julia> dayofweekofmonth(Date("2000-02-01")) 1 -julia> Dates.dayofweekofmonth(Date("2000-02-08")) +julia> dayofweekofmonth(Date("2000-02-08")) 2 -julia> Dates.dayofweekofmonth(Date("2000-02-15")) +julia> dayofweekofmonth(Date("2000-02-15")) 3 ``` """ @@ -240,10 +240,10 @@ function. # Examples ```jldoctest -julia> Dates.daysofweekinmonth(Date("2005-01-01")) +julia> daysofweekinmonth(Date("2005-01-01")) 5 -julia> Dates.daysofweekinmonth(Date("2005-01-04")) +julia> daysofweekinmonth(Date("2005-01-04")) 4 ``` """ @@ -569,10 +569,10 @@ Return the full name of the month of the `Date` or `DateTime` or `Integer` in th # Examples ```jldoctest -julia> Dates.monthname(Date("2005-01-04")) +julia> monthname(Date("2005-01-04")) "January" -julia> Dates.monthname(2) +julia> monthname(2) "February" ``` """ @@ -588,7 +588,7 @@ Return the abbreviated month name of the `Date` or `DateTime` or `Integer` in th # Examples ```jldoctest -julia> Dates.monthabbr(Date("2005-01-04")) +julia> monthabbr(Date("2005-01-04")) "Jan" julia> monthabbr(2) @@ -606,13 +606,13 @@ Return the number of days in the month of `dt`. Value will be 28, 29, 30, or 31. # Examples ```jldoctest -julia> Dates.daysinmonth(Date("2000-01")) +julia> daysinmonth(Date("2000-01")) 31 -julia> Dates.daysinmonth(Date("2001-02")) +julia> daysinmonth(Date("2001-02")) 28 -julia> Dates.daysinmonth(Date("2000-02")) +julia> daysinmonth(Date("2000-02")) 29 ``` """ @@ -626,10 +626,10 @@ Return `true` if the year of `dt` is a leap year. # Examples ```jldoctest -julia> Dates.isleapyear(Date("2004")) +julia> isleapyear(Date("2004")) true -julia> Dates.isleapyear(Date("2005")) +julia> isleapyear(Date("2005")) false ``` """ diff --git a/stdlib/Dates/src/ranges.jl b/stdlib/Dates/src/ranges.jl index 3939d3661ec66..c4299c7b02be5 100644 --- a/stdlib/Dates/src/ranges.jl +++ b/stdlib/Dates/src/ranges.jl @@ -4,6 +4,7 @@ StepRange{<:Dates.DatePeriod,<:Real}(start, step, stop) = throw(ArgumentError("must specify step as a Period when constructing Dates ranges")) +Base.:(:)(a::T, b::T) where {T<:Date} = (:)(a, Day(1), b) # Given a start and end date, how many steps/periods are in between guess(a::DateTime, b::DateTime, c) = floor(Int64, (Int128(value(b)) - Int128(value(a))) / toms(c)) @@ -42,7 +43,7 @@ function Base.steprange_last(start::T, step, stop) where T<:TimeType else diff = stop - start if (diff > zero(diff)) != (stop > start) - throw(OverflowError()) + throw(OverflowError("Difference between stop and start overflowed")) end remain = stop - (start + step * len(start, stop, step)) last = stop - remain diff --git a/stdlib/Dates/src/rounding.jl b/stdlib/Dates/src/rounding.jl index 53e680a6bfd1b..b5b6e52decba8 100644 --- a/stdlib/Dates/src/rounding.jl +++ b/stdlib/Dates/src/rounding.jl @@ -94,13 +94,13 @@ For convenience, `precision` may be a type instead of a value: `floor(x, Dates.H shortcut for `floor(x, Dates.Hour(1))`. ```jldoctest -julia> floor(Dates.Day(16), Dates.Week) +julia> floor(Day(16), Week) 2 weeks -julia> floor(Dates.Minute(44), Dates.Minute(15)) +julia> floor(Minute(44), Minute(15)) 30 minutes -julia> floor(Dates.Hour(36), Dates.Day) +julia> floor(Hour(36), Day) 1 day ``` @@ -122,13 +122,13 @@ For convenience, `p` may be a type instead of a value: `floor(dt, Dates.Hour)` i for `floor(dt, Dates.Hour(1))`. ```jldoctest -julia> floor(Date(1985, 8, 16), Dates.Month) +julia> floor(Date(1985, 8, 16), Month) 1985-08-01 -julia> floor(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15)) +julia> floor(DateTime(2013, 2, 13, 0, 31, 20), Minute(15)) 2013-02-13T00:30:00 -julia> floor(DateTime(2016, 8, 6, 12, 0, 0), Dates.Day) +julia> floor(DateTime(2016, 8, 6, 12, 0, 0), Day) 2016-08-06T00:00:00 ``` """ @@ -143,13 +143,13 @@ For convenience, `p` may be a type instead of a value: `ceil(dt, Dates.Hour)` is for `ceil(dt, Dates.Hour(1))`. ```jldoctest -julia> ceil(Date(1985, 8, 16), Dates.Month) +julia> ceil(Date(1985, 8, 16), Month) 1985-09-01 -julia> ceil(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15)) +julia> ceil(DateTime(2013, 2, 13, 0, 31, 20), Minute(15)) 2013-02-13T00:45:00 -julia> ceil(DateTime(2016, 8, 6, 12, 0, 0), Dates.Day) +julia> ceil(DateTime(2016, 8, 6, 12, 0, 0), Day) 2016-08-07T00:00:00 ``` """ @@ -168,13 +168,13 @@ For convenience, `precision` may be a type instead of a value: `ceil(x, Dates.Ho shortcut for `ceil(x, Dates.Hour(1))`. ```jldoctest -julia> ceil(Dates.Day(16), Dates.Week) +julia> ceil(Day(16), Week) 3 weeks -julia> ceil(Dates.Minute(44), Dates.Minute(15)) +julia> ceil(Minute(44), Minute(15)) 45 minutes -julia> ceil(Dates.Hour(36), Dates.Day) +julia> ceil(Hour(36), Day) 2 days ``` @@ -218,13 +218,13 @@ For convenience, `p` may be a type instead of a value: `round(dt, Dates.Hour)` i for `round(dt, Dates.Hour(1))`. ```jldoctest -julia> round(Date(1985, 8, 16), Dates.Month) +julia> round(Date(1985, 8, 16), Month) 1985-08-01 -julia> round(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15)) +julia> round(DateTime(2013, 2, 13, 0, 31, 20), Minute(15)) 2013-02-13T00:30:00 -julia> round(DateTime(2016, 8, 6, 12, 0, 0), Dates.Day) +julia> round(DateTime(2016, 8, 6, 12, 0, 0), Day) 2016-08-07T00:00:00 ``` @@ -248,13 +248,13 @@ For convenience, `precision` may be a type instead of a value: `round(x, Dates.H shortcut for `round(x, Dates.Hour(1))`. ```jldoctest -julia> round(Dates.Day(16), Dates.Week) +julia> round(Day(16), Week) 2 weeks -julia> round(Dates.Minute(44), Dates.Minute(15)) +julia> round(Minute(44), Minute(15)) 45 minutes -julia> round(Dates.Hour(36), Dates.Day) +julia> round(Hour(36), Day) 2 days ``` diff --git a/stdlib/Dates/src/types.jl b/stdlib/Dates/src/types.jl index 551448df12a1b..1d9769a05bd3d 100644 --- a/stdlib/Dates/src/types.jl +++ b/stdlib/Dates/src/types.jl @@ -103,7 +103,7 @@ end UTM(x) = UTInstant(Millisecond(x)) UTD(x) = UTInstant(Day(x)) -# Calendar types provide rules for interpretating instant +# Calendar types provide rules for interpreting instant # timelines in human-readable form. abstract type Calendar <: AbstractTime end @@ -199,20 +199,11 @@ daysinmonth(y,m) = DAYSINMONTH[m] + (m == 2 && isleapyear(y)) """ validargs(::Type{<:TimeType}, args...) -> Union{ArgumentError, Nothing} -Determine whether the given arguments consitute valid inputs for the given type. +Determine whether the given arguments constitute valid inputs for the given type. Returns either an `ArgumentError`, or [`nothing`](@ref) in case of success. """ function validargs end -""" - argerror([msg]) -> Union{ArgumentError, Nothing} - -Return an `ArgumentError` object with the given message, -or [`nothing`](@ref) if no message is provided. For use by `validargs`. -""" -argerror(msg::String) = ArgumentError(msg) -argerror() = nothing - # Julia uses 24-hour clocks internally, but user input can be AM/PM with 12pm == noon and 12am == midnight. @enum AMPM AM PM TWENTYFOURHOUR function adjusthour(h::Int64, ampm::AMPM) @@ -240,18 +231,18 @@ end function validargs(::Type{DateTime}, y::Int64, m::Int64, d::Int64, h::Int64, mi::Int64, s::Int64, ms::Int64, ampm::AMPM=TWENTYFOURHOUR) - 0 < m < 13 || return argerror("Month: $m out of range (1:12)") - 0 < d < daysinmonth(y, m) + 1 || return argerror("Day: $d out of range (1:$(daysinmonth(y, m)))") + 0 < m < 13 || return ArgumentError("Month: $m out of range (1:12)") + 0 < d < daysinmonth(y, m) + 1 || return ArgumentError("Day: $d out of range (1:$(daysinmonth(y, m)))") if ampm == TWENTYFOURHOUR # 24-hour clock -1 < h < 24 || (h == 24 && mi==s==ms==0) || - return argerror("Hour: $h out of range (0:23)") + return ArgumentError("Hour: $h out of range (0:23)") else - 0 < h < 13 || return argerror("Hour: $h out of range (1:12)") + 0 < h < 13 || return ArgumentError("Hour: $h out of range (1:12)") end - -1 < mi < 60 || return argerror("Minute: $mi out of range (0:59)") - -1 < s < 60 || return argerror("Second: $s out of range (0:59)") - -1 < ms < 1000 || return argerror("Millisecond: $ms out of range (0:999)") - return argerror() + -1 < mi < 60 || return ArgumentError("Minute: $mi out of range (0:59)") + -1 < s < 60 || return ArgumentError("Second: $s out of range (0:59)") + -1 < ms < 1000 || return ArgumentError("Millisecond: $ms out of range (0:999)") + return nothing end DateTime(dt::Base.Libc.TmStruct) = DateTime(1900 + dt.year, 1 + dt.month, dt.mday, dt.hour, dt.min, dt.sec) @@ -268,9 +259,9 @@ function Date(y::Int64, m::Int64=1, d::Int64=1) end function validargs(::Type{Date}, y::Int64, m::Int64, d::Int64) - 0 < m < 13 || return argerror("Month: $m out of range (1:12)") - 0 < d < daysinmonth(y, m) + 1 || return argerror("Day: $d out of range (1:$(daysinmonth(y, m)))") - return argerror() + 0 < m < 13 || return ArgumentError("Month: $m out of range (1:12)") + 0 < d < daysinmonth(y, m) + 1 || return ArgumentError("Day: $d out of range (1:$(daysinmonth(y, m)))") + return nothing end Date(dt::Base.Libc.TmStruct) = Date(1900 + dt.year, 1 + dt.month, dt.mday) @@ -289,16 +280,16 @@ end function validargs(::Type{Time}, h::Int64, mi::Int64, s::Int64, ms::Int64, us::Int64, ns::Int64, ampm::AMPM=TWENTYFOURHOUR) if ampm == TWENTYFOURHOUR # 24-hour clock - -1 < h < 24 || return argerror("Hour: $h out of range (0:23)") + -1 < h < 24 || return ArgumentError("Hour: $h out of range (0:23)") else - 0 < h < 13 || return argerror("Hour: $h out of range (1:12)") + 0 < h < 13 || return ArgumentError("Hour: $h out of range (1:12)") end - -1 < mi < 60 || return argerror("Minute: $mi out of range (0:59)") - -1 < s < 60 || return argerror("Second: $s out of range (0:59)") - -1 < ms < 1000 || return argerror("Millisecond: $ms out of range (0:999)") - -1 < us < 1000 || return argerror("Microsecond: $us out of range (0:999)") - -1 < ns < 1000 || return argerror("Nanosecond: $ns out of range (0:999)") - return argerror() + -1 < mi < 60 || return ArgumentError("Minute: $mi out of range (0:59)") + -1 < s < 60 || return ArgumentError("Second: $s out of range (0:59)") + -1 < ms < 1000 || return ArgumentError("Millisecond: $ms out of range (0:999)") + -1 < us < 1000 || return ArgumentError("Microsecond: $us out of range (0:999)") + -1 < ns < 1000 || return ArgumentError("Nanosecond: $ns out of range (0:999)") + return nothing end Time(dt::Base.Libc.TmStruct) = Time(dt.hour, dt.min, dt.sec) @@ -475,7 +466,7 @@ function Base.Timer(delay::Period; interval::Period=Second(0)) Timer(toms(delay) / 1000, interval=toms(interval) / 1000) end -function Base.timedwait(testcb::Function, timeout::Period; pollint::Period=Millisecond(100)) +function Base.timedwait(testcb, timeout::Period; pollint::Period=Millisecond(100)) timedwait(testcb, toms(timeout) / 1000, pollint=toms(pollint) / 1000) end diff --git a/stdlib/Dates/test/accessors.jl b/stdlib/Dates/test/accessors.jl index 819fa8c40ddbc..b690a81d70e49 100644 --- a/stdlib/Dates/test/accessors.jl +++ b/stdlib/Dates/test/accessors.jl @@ -29,7 +29,7 @@ using Test @test Dates.yearmonthday(730120) == (2000, 1, 1) end @testset "year/month/day" begin - # year, month, and day return the indivial components + # year, month, and day return the individual components # of yearmonthday, avoiding additional calculations when possible @test Dates.year(-1) == 0 @test Dates.month(-1) == 12 diff --git a/stdlib/Dates/test/arithmetic.jl b/stdlib/Dates/test/arithmetic.jl index 485fea5624066..2e684815a3c86 100644 --- a/stdlib/Dates/test/arithmetic.jl +++ b/stdlib/Dates/test/arithmetic.jl @@ -10,6 +10,13 @@ using Dates b = Dates.Time(11, 59, 59) @test Dates.CompoundPeriod(a - b) == Dates.Hour(12) end + +@testset "TimeType arithmetic" begin + a = Date(2023, 5, 1) + b = DateTime(2023, 5, 2) + @test b - a == Day(1) +end + @testset "Wrapping arithmetic for Months" begin # This ends up being trickier than expected because # the user might do 2014-01-01 + Month(-14) diff --git a/stdlib/Dates/test/conversions.jl b/stdlib/Dates/test/conversions.jl index 488af4110e884..99572b41b4f90 100644 --- a/stdlib/Dates/test/conversions.jl +++ b/stdlib/Dates/test/conversions.jl @@ -60,10 +60,16 @@ end if Sys.isapple() withenv("TZ" => "UTC") do - @test abs(Dates.now() - now(Dates.UTC)) < Dates.Second(1) + a = Dates.now() + b = Dates.now(Dates.UTC) + c = Dates.now() + @test a <= b <= c end end - @test abs(Dates.now() - now(Dates.UTC)) < Dates.Hour(16) + a = Dates.now() + b = now(Dates.UTC) + c = Dates.now() + @test abs(a - b) < Dates.Hour(16) + abs(c - a) end @testset "Issue #9171, #9169" begin let t = Dates.Period[Dates.Week(2), Dates.Day(14), Dates.Hour(14 * 24), Dates.Minute(14 * 24 * 60), Dates.Second(14 * 24 * 60 * 60), Dates.Millisecond(14 * 24 * 60 * 60 * 1000)] diff --git a/stdlib/Dates/test/io.jl b/stdlib/Dates/test/io.jl index 1c50676eb8346..2c99ac45d0c58 100644 --- a/stdlib/Dates/test/io.jl +++ b/stdlib/Dates/test/io.jl @@ -60,7 +60,9 @@ end end @testset "DateFormat printing" begin - @test sprint(show, DateFormat("yyyzzxmmdd\\MHH:MM:SS\\P")) == "dateformat\"yyyzzxmmdd\\MHH:MM:SSP\"" + @test sprint(show, DateFormat("yyyzzxmmdd\\MHH:MM:SS\\P")) == "dateformat\"yyyzzxmmdd\\MHH:MM:SS\\P\"" + @test sprint(show, dateformat"yyyy-mm-dd\THH:MM:SS.s") == "dateformat\"yyyy-mm-dd\\THH:MM:SS.s\"" + @test sprint(show, dateformat"yyyy-mm-ddTHH:MM:SS.s") == "dateformat\"yyyy-mm-ddTHH:MM:SS.s\"" @test sprint(show, DateFormat("yyy").tokens[1]) == "DatePart(yyy)" @test sprint(show, DateFormat("mmzzdd").tokens[2]) == "Delim(zz)" @test sprint(show, DateFormat("ddxmm").tokens[2]) == "Delim(x)" @@ -548,7 +550,7 @@ end @test Time("$t12", "$HH:MMp") == t end local tmstruct, strftime - withlocales(["C"]) do + withlocales(["C"]) do locale # test am/pm comparison handling tmstruct = Libc.strptime("%I:%M%p", t12) strftime = Libc.strftime("%I:%M%p", tmstruct) @@ -586,4 +588,34 @@ end @test (@inferred Nothing g()) == datetime end +@testset "Issue #43883: parsing empty strings" begin + for (T, name, fmt) in zip( + (DateTime, Date, Time), + ("DateTime", "Date or Time", "Date or Time"), + ("yyyy-mm-ddHHMMSS.s", "yyymmdd", "HHMMSS") + ) + @test_throws ArgumentError T("") + @test_throws ArgumentError T("", fmt) + @test_throws ArgumentError T("", DateFormat(fmt)) + try + T("") + @test false + catch err + @test err.msg == "Cannot parse an empty string as a $name" + end + + @test_throws ArgumentError parse(T, "") + @test_throws ArgumentError parse(T, "", DateFormat(fmt)) + try + parse(T, "") + @test false + catch err + @test err.msg == "Cannot parse an empty string as a $name" + end + + @test tryparse(T, "") === nothing + @test tryparse(T, "", DateFormat(fmt)) === nothing + end +end + end diff --git a/stdlib/Dates/test/periods.jl b/stdlib/Dates/test/periods.jl index 0467841fb6261..7b23ffcb5d4e1 100644 --- a/stdlib/Dates/test/periods.jl +++ b/stdlib/Dates/test/periods.jl @@ -283,7 +283,7 @@ Beat(p::Period) = Beat(Dates.toms(p) ÷ 86400) Dates.toms(b::Beat) = Dates.value(b) * 86400 Dates._units(b::Beat) = " beat" * (abs(Dates.value(b)) == 1 ? "" : "s") Base.promote_rule(::Type{Dates.Day}, ::Type{Beat}) = Dates.Millisecond - Base.convert(::Type{T}, b::Beat) where {T<:Dates.Millisecond} = T(Dates.toms(b)) + Base.convert(::Type{T}, b::Beat) where {T<:Dates.Millisecond} = T(Dates.toms(b))::T @test Beat(1000) == Dates.Day(1) @test Beat(1) < Dates.Day(1) @@ -531,4 +531,3 @@ end end end - diff --git a/stdlib/Dates/test/ranges.jl b/stdlib/Dates/test/ranges.jl index 52416fc95ec0c..d4339dcde51d4 100644 --- a/stdlib/Dates/test/ranges.jl +++ b/stdlib/Dates/test/ranges.jl @@ -596,4 +596,19 @@ a = Dates.Time(23, 1, 1) @test length(utm_typemin:-Millisecond(1):utm_typemin) == 1 end +# Issue #45816 +@testset "default step for date ranges" begin + r = Date(2000, 1, 1):Date(2000, 12, 31) + @test step(r) === Day(1) + @test length(r) == 366 +end + +# Issue #48209 +@testset "steprange_last overflow" begin + epoch = Date(Date(1) - Day(1)) + dmax = epoch + Day(typemax(fieldtype(Day, :value))) + dmin = epoch + Day(typemin(fieldtype(Day, :value))) + @test_throws OverflowError StepRange(dmin, Day(1), dmax) +end + end # RangesTest module diff --git a/stdlib/Dates/test/rounding.jl b/stdlib/Dates/test/rounding.jl index 13e276c4426e8..85c90981423d3 100644 --- a/stdlib/Dates/test/rounding.jl +++ b/stdlib/Dates/test/rounding.jl @@ -189,7 +189,7 @@ end @test round(x, Dates.Nanosecond) == x end -@testset "Rouding DateTime to Date" begin +@testset "Rounding DateTime to Date" begin now_ = DateTime(2020, 9, 1, 13) for p in (Year, Month, Day) for r in (RoundUp, RoundDown) diff --git a/stdlib/DelimitedFiles.version b/stdlib/DelimitedFiles.version new file mode 100644 index 0000000000000..d741690a96838 --- /dev/null +++ b/stdlib/DelimitedFiles.version @@ -0,0 +1,4 @@ +DELIMITEDFILES_BRANCH = main +DELIMITEDFILES_SHA1 = db79c842f95f55b1f8d8037c0d3363ab21cd3b90 +DELIMITEDFILES_GIT_URL := https://github.com/JuliaData/DelimitedFiles.jl.git +DELIMITEDFILES_TAR_URL = https://api.github.com/repos/JuliaData/DelimitedFiles.jl/tarball/$1 diff --git a/stdlib/DelimitedFiles/Project.toml b/stdlib/DelimitedFiles/Project.toml deleted file mode 100644 index 7b774ec3ba035..0000000000000 --- a/stdlib/DelimitedFiles/Project.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "DelimitedFiles" -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - -[deps] -Mmap = "a63ad114-7e13-5084-954f-fe012c677804" - -[extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[targets] -test = ["Test", "Random"] diff --git a/stdlib/DelimitedFiles/docs/src/index.md b/stdlib/DelimitedFiles/docs/src/index.md deleted file mode 100644 index 11e24f12ff3c1..0000000000000 --- a/stdlib/DelimitedFiles/docs/src/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Delimited Files - -```@docs -DelimitedFiles.readdlm(::Any, ::AbstractChar, ::Type, ::AbstractChar) -DelimitedFiles.readdlm(::Any, ::AbstractChar, ::AbstractChar) -DelimitedFiles.readdlm(::Any, ::AbstractChar, ::Type) -DelimitedFiles.readdlm(::Any, ::AbstractChar) -DelimitedFiles.readdlm(::Any, ::Type) -DelimitedFiles.readdlm(::Any) -DelimitedFiles.writedlm -``` diff --git a/stdlib/DelimitedFiles/src/DelimitedFiles.jl b/stdlib/DelimitedFiles/src/DelimitedFiles.jl deleted file mode 100644 index 7c0e3e39b6b86..0000000000000 --- a/stdlib/DelimitedFiles/src/DelimitedFiles.jl +++ /dev/null @@ -1,832 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -""" -Utilities for reading and writing delimited files, for example ".csv". -See [`readdlm`](@ref) and [`writedlm`](@ref). -""" -module DelimitedFiles - -using Mmap - -import Base: tryparse_internal, show - -export readdlm, writedlm - -invalid_dlm(::Type{Char}) = reinterpret(Char, 0xfffffffe) -invalid_dlm(::Type{UInt8}) = 0xfe -invalid_dlm(::Type{UInt16}) = 0xfffe -invalid_dlm(::Type{UInt32}) = 0xfffffffe - -const offs_chunk_size = 5000 - -""" - readdlm(source, T::Type; options...) - -The columns are assumed to be separated by one or more whitespaces. The end of line -delimiter is taken as `\\n`. - -# Examples -```jldoctest -julia> using DelimitedFiles - -julia> x = [1; 2; 3; 4]; - -julia> y = [5; 6; 7; 8]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x y]) - end; - -julia> readdlm("delim_file.txt", Int64) -4×2 Matrix{Int64}: - 1 5 - 2 6 - 3 7 - 4 8 - -julia> readdlm("delim_file.txt", Float64) -4×2 Matrix{Float64}: - 1.0 5.0 - 2.0 6.0 - 3.0 7.0 - 4.0 8.0 - -julia> rm("delim_file.txt") -``` -""" -readdlm(input, T::Type; opts...) = readdlm(input, invalid_dlm(Char), T, '\n'; opts...) - -""" - readdlm(source, delim::AbstractChar, T::Type; options...) - -The end of line delimiter is taken as `\\n`. - -# Examples -```jldoctest -julia> using DelimitedFiles - -julia> x = [1; 2; 3; 4]; - -julia> y = [1.1; 2.2; 3.3; 4.4]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x y], ',') - end; - -julia> readdlm("delim_file.txt", ',', Float64) -4×2 Matrix{Float64}: - 1.0 1.1 - 2.0 2.2 - 3.0 3.3 - 4.0 4.4 - -julia> rm("delim_file.txt") -``` -""" -readdlm(input, dlm::AbstractChar, T::Type; opts...) = readdlm(input, dlm, T, '\n'; opts...) - -""" - readdlm(source; options...) - -The columns are assumed to be separated by one or more whitespaces. The end of line -delimiter is taken as `\\n`. If all data is numeric, the result will be a numeric array. If -some elements cannot be parsed as numbers, a heterogeneous array of numbers and strings -is returned. - -# Examples -```jldoctest -julia> using DelimitedFiles - -julia> x = [1; 2; 3; 4]; - -julia> y = ["a"; "b"; "c"; "d"]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x y]) - end; - -julia> readdlm("delim_file.txt") -4×2 Matrix{Any}: - 1 "a" - 2 "b" - 3 "c" - 4 "d" - -julia> rm("delim_file.txt") -``` -""" -readdlm(input; opts...) = readdlm(input, invalid_dlm(Char), '\n'; opts...) - -""" - readdlm(source, delim::AbstractChar; options...) - -The end of line delimiter is taken as `\\n`. If all data is numeric, the result will be a -numeric array. If some elements cannot be parsed as numbers, a heterogeneous array of -numbers and strings is returned. - -# Examples -```jldoctest -julia> using DelimitedFiles - -julia> x = [1; 2; 3; 4]; - -julia> y = [1.1; 2.2; 3.3; 4.4]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x y], ',') - end; - -julia> readdlm("delim_file.txt", ',') -4×2 Matrix{Float64}: - 1.0 1.1 - 2.0 2.2 - 3.0 3.3 - 4.0 4.4 - -julia> z = ["a"; "b"; "c"; "d"]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x z], ',') - end; - -julia> readdlm("delim_file.txt", ',') -4×2 Matrix{Any}: - 1 "a" - 2 "b" - 3 "c" - 4 "d" - -julia> rm("delim_file.txt") -``` -""" -readdlm(input, dlm::AbstractChar; opts...) = readdlm(input, dlm, '\n'; opts...) - -""" - readdlm(source, delim::AbstractChar, eol::AbstractChar; options...) - -If all data is numeric, the result will be a numeric array. If some elements cannot be -parsed as numbers, a heterogeneous array of numbers and strings is returned. -""" -readdlm(input, dlm::AbstractChar, eol::AbstractChar; opts...) = - readdlm_auto(input, dlm, Float64, eol, true; opts...) - -""" - readdlm(source, delim::AbstractChar, T::Type, eol::AbstractChar; header=false, skipstart=0, skipblanks=true, use_mmap, quotes=true, dims, comments=false, comment_char='#') - -Read a matrix from the source where each line (separated by `eol`) gives one row, with -elements separated by the given delimiter. The source can be a text file, stream or byte -array. Memory mapped files can be used by passing the byte array representation of the -mapped segment as source. - -If `T` is a numeric type, the result is an array of that type, with any non-numeric elements -as `NaN` for floating-point types, or zero. Other useful values of `T` include -`String`, `AbstractString`, and `Any`. - -If `header` is `true`, the first row of data will be read as header and the tuple -`(data_cells, header_cells)` is returned instead of only `data_cells`. - -Specifying `skipstart` will ignore the corresponding number of initial lines from the input. - -If `skipblanks` is `true`, blank lines in the input will be ignored. - -If `use_mmap` is `true`, the file specified by `source` is memory mapped for potential -speedups if the file is large. Default is `false`. On a Windows filesystem, `use_mmap` should not be set -to `true` unless the file is only read once and is also not written to. -Some edge cases exist where an OS is Unix-like but the filesystem is Windows-like. - -If `quotes` is `true`, columns enclosed within double-quote (\") characters are allowed to -contain new lines and column delimiters. Double-quote characters within a quoted field must -be escaped with another double-quote. Specifying `dims` as a tuple of the expected rows and -columns (including header, if any) may speed up reading of large files. If `comments` is -`true`, lines beginning with `comment_char` and text following `comment_char` in any line -are ignored. - -# Examples -```jldoctest -julia> using DelimitedFiles - -julia> x = [1; 2; 3; 4]; - -julia> y = [5; 6; 7; 8]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x y]) - end - -julia> readdlm("delim_file.txt", '\\t', Int, '\\n') -4×2 Matrix{Int64}: - 1 5 - 2 6 - 3 7 - 4 8 - -julia> rm("delim_file.txt") -``` -""" -readdlm(input, dlm::AbstractChar, T::Type, eol::AbstractChar; opts...) = - readdlm_auto(input, dlm, T, eol, false; opts...) - -readdlm_auto(input::Vector{UInt8}, dlm::AbstractChar, T::Type, eol::AbstractChar, auto::Bool; opts...) = - readdlm_string(String(copyto!(Base.StringVector(length(input)), input)), dlm, T, eol, auto, val_opts(opts)) -readdlm_auto(input::IO, dlm::AbstractChar, T::Type, eol::AbstractChar, auto::Bool; opts...) = - readdlm_string(read(input, String), dlm, T, eol, auto, val_opts(opts)) -function readdlm_auto(input::AbstractString, dlm::AbstractChar, T::Type, eol::AbstractChar, auto::Bool; opts...) - isfile(input) || throw(ArgumentError("Cannot open \'$input\': not a file")) - optsd = val_opts(opts) - use_mmap = get(optsd, :use_mmap, false) - fsz = filesize(input) - if use_mmap && fsz > 0 && fsz < typemax(Int) - a = open(input, "r") do f - mmap(f, Vector{UInt8}, (Int(fsz),)) - end - # TODO: It would be nicer to use String(a) without making a copy, - # but because the mmap'ed array is not NUL-terminated this causes - # jl_try_substrtod to segfault below. - return readdlm_string(GC.@preserve(a, unsafe_string(pointer(a),length(a))), dlm, T, eol, auto, optsd) - else - return readdlm_string(read(input, String), dlm, T, eol, auto, optsd) - end -end - -# -# Handlers act on events generated by the parser. -# Parser calls store_cell on the handler to pass events. -# -# DLMOffsets: Keep offsets (when result dimensions are not known) -# DLMStore: Store values directly into a result store (when result dimensions are known) -abstract type DLMHandler end - -mutable struct DLMOffsets <: DLMHandler - oarr::Vector{Vector{Int}} - offidx::Int - thresh::Int - bufflen::Int - - function DLMOffsets(sbuff::String) - offsets = Vector{Vector{Int}}(undef, 1) - offsets[1] = Vector{Int}(undef, offs_chunk_size) - thresh = ceil(min(typemax(UInt), Base.Sys.total_memory()) / sizeof(Int) / 5) - new(offsets, 1, thresh, sizeof(sbuff)) - end -end - -function store_cell(dlmoffsets::DLMOffsets, row::Int, col::Int, - quoted::Bool, startpos::Int, endpos::Int) - offidx = dlmoffsets.offidx - (offidx == 0) && return # offset collection stopped to avoid choking on memory - - oarr = dlmoffsets.oarr - offsets = oarr[end] - if length(offsets) < offidx - offlen = offs_chunk_size * length(oarr) - if (offlen + offs_chunk_size) > dlmoffsets.thresh - est_tot = round(Int, offlen * dlmoffsets.bufflen / endpos) - if (est_tot - offlen) > offs_chunk_size # allow another chunk - # abandon offset collection - dlmoffsets.oarr = Vector{Int}[] - dlmoffsets.offidx = 0 - return - end - end - offsets = Vector{Int}(undef, offs_chunk_size) - push!(oarr, offsets) - offidx = 1 - end - offsets[offidx] = row - offsets[offidx+1] = col - offsets[offidx+2] = Int(quoted) - offsets[offidx+3] = startpos - offsets[offidx+4] = endpos - dlmoffsets.offidx = offidx + 5 - nothing -end - -function result(dlmoffsets::DLMOffsets) - trimsz = (dlmoffsets.offidx-1) % offs_chunk_size - ((trimsz > 0) || (dlmoffsets.offidx == 1)) && resize!(dlmoffsets.oarr[end], trimsz) - dlmoffsets.oarr -end - -mutable struct DLMStore{T} <: DLMHandler - hdr::Array{AbstractString, 2} - data::Array{T, 2} - - nrows::Int - ncols::Int - lastrow::Int - lastcol::Int - hdr_offset::Int - sbuff::String - auto::Bool - eol::Char -end - -function DLMStore(::Type{T}, dims::NTuple{2,Integer}, - has_header::Bool, sbuff::String, auto::Bool, eol::AbstractChar) where T - (nrows,ncols) = dims - nrows <= 0 && throw(ArgumentError("number of rows in dims must be > 0, got $nrows")) - ncols <= 0 && throw(ArgumentError("number of columns in dims must be > 0, got $ncols")) - hdr_offset = has_header ? 1 : 0 - DLMStore{T}(fill(SubString(sbuff,1,0), 1, ncols), Matrix{T}(undef, nrows-hdr_offset, ncols), - nrows, ncols, 0, 0, hdr_offset, sbuff, auto, Char(eol)) -end - -_chrinstr(sbuff::String, chr::UInt8, startpos::Int, endpos::Int) = - GC.@preserve sbuff (endpos >= startpos) && (C_NULL != ccall(:memchr, Ptr{UInt8}, - (Ptr{UInt8}, Int32, Csize_t), pointer(sbuff)+startpos-1, chr, endpos-startpos+1)) - -function store_cell(dlmstore::DLMStore{T}, row::Int, col::Int, - quoted::Bool, startpos::Int, endpos::Int) where T - drow = row - dlmstore.hdr_offset - - ncols = dlmstore.ncols - lastcol = dlmstore.lastcol - lastrow = dlmstore.lastrow - cells::Matrix{T} = dlmstore.data - sbuff = dlmstore.sbuff - - endpos = prevind(sbuff, nextind(sbuff,endpos)) - if (endpos > 0) && ('\n' == dlmstore.eol) && ('\r' == Char(sbuff[endpos])) - endpos = prevind(sbuff, endpos) - end - if quoted - startpos += 1 - endpos = prevind(sbuff, endpos) - end - - if drow > 0 - # fill missing elements - while ((drow - lastrow) > 1) || ((drow > lastrow > 0) && (lastcol < ncols)) - if (lastcol == ncols) || (lastrow == 0) - lastcol = 0 - lastrow += 1 - end - for cidx in (lastcol+1):ncols - if (T <: AbstractString) || (T == Any) - cells[lastrow, cidx] = SubString(sbuff, 1, 0) - elseif ((T <: Number) || (T <: AbstractChar)) && dlmstore.auto - throw(TypeError(:store_cell, "", Any, T)) - else - error("missing value at row $lastrow column $cidx") - end - end - lastcol = ncols - end - - # fill data - if quoted && _chrinstr(sbuff, UInt8('"'), startpos, endpos) - unescaped = replace(SubString(sbuff, startpos, endpos), r"\"\"" => "\"") - fail = colval(unescaped, 1, lastindex(unescaped), cells, drow, col) - else - fail = colval(sbuff, startpos, endpos, cells, drow, col) - end - if fail - sval = SubString(sbuff, startpos, endpos) - if (T <: Number) && dlmstore.auto - throw(TypeError(:store_cell, "", Any, T)) - else - error("file entry \"$(sval)\" cannot be converted to $T") - end - end - - dlmstore.lastrow = drow - dlmstore.lastcol = col - else - # fill header - if quoted && _chrinstr(sbuff, UInt8('"'), startpos, endpos) - unescaped = replace(SubString(sbuff, startpos, endpos), r"\"\"" => "\"") - colval(unescaped, 1, lastindex(unescaped), dlmstore.hdr, 1, col) - else - colval(sbuff, startpos, endpos, dlmstore.hdr, 1, col) - end - end - - nothing -end - -function result(dlmstore::DLMStore{T}) where T - nrows = dlmstore.nrows - dlmstore.hdr_offset - ncols = dlmstore.ncols - lastcol = dlmstore.lastcol - lastrow = dlmstore.lastrow - cells = dlmstore.data - sbuff = dlmstore.sbuff - - if (nrows > 0) && ((lastcol < ncols) || (lastrow < nrows)) - while lastrow <= nrows - (lastcol == ncols) && (lastcol = 0; lastrow += 1) - for cidx in (lastcol+1):ncols - if (T <: AbstractString) || (T == Any) - cells[lastrow, cidx] = SubString(sbuff, 1, 0) - elseif ((T <: Number) || (T <: AbstractChar)) && dlmstore.auto - throw(TypeError(:store_cell, "", Any, T)) - else - error("missing value at row $lastrow column $cidx") - end - end - lastcol = ncols - (lastrow == nrows) && break - end - dlmstore.lastrow = lastrow - dlmstore.lastcol = ncols - end - (dlmstore.hdr_offset > 0) ? (dlmstore.data, dlmstore.hdr) : dlmstore.data -end - - -function readdlm_string(sbuff::String, dlm::AbstractChar, T::Type, eol::AbstractChar, auto::Bool, optsd::Dict) - ign_empty = (dlm == invalid_dlm(Char)) - quotes = get(optsd, :quotes, true) - comments = get(optsd, :comments, false) - comment_char = get(optsd, :comment_char, '#') - dims = get(optsd, :dims, nothing) - - has_header = get(optsd, :header, get(optsd, :has_header, false)) - haskey(optsd, :has_header) && (optsd[:has_header] != has_header) && throw(ArgumentError("conflicting values for header and has_header")) - - skipstart = get(optsd, :skipstart, 0) - (skipstart >= 0) || throw(ArgumentError("skipstart must be ≥ 0, got $skipstart")) - - skipblanks = get(optsd, :skipblanks, true) - - offset_handler = (dims === nothing) ? DLMOffsets(sbuff) : DLMStore(T, dims, has_header, sbuff, auto, eol) - - for retry in 1:2 - try - dims = dlm_parse(sbuff, eol, dlm, '"', comment_char, ign_empty, quotes, comments, skipstart, skipblanks, offset_handler) - break - catch ex - if isa(ex, TypeError) && (ex.func === :store_cell) - T = ex.expected - else - rethrow() - end - offset_handler = (dims === nothing) ? DLMOffsets(sbuff) : DLMStore(T, dims, has_header, sbuff, auto, eol) - end - end - - isa(offset_handler, DLMStore) && (return result(offset_handler)) - - offsets = result(offset_handler) - !isempty(offsets) && (return dlm_fill(T, offsets, dims, has_header, sbuff, auto, eol)) - - optsd[:dims] = dims - return readdlm_string(sbuff, dlm, T, eol, auto, optsd) -end - -const valid_opts = [:header, :has_header, :use_mmap, :quotes, :comments, :dims, :comment_char, :skipstart, :skipblanks] -const valid_opt_types = [Bool, Bool, Bool, Bool, Bool, NTuple{2,Integer}, Char, Integer, Bool] - -function val_opts(opts) - d = Dict{Symbol, Union{Bool, NTuple{2, Integer}, Char, Integer}}() - for (opt_name, opt_val) in opts - in(opt_name, valid_opts) || - throw(ArgumentError("unknown option $opt_name")) - opt_typ = valid_opt_types[findfirst(isequal(opt_name), valid_opts)::Int] - isa(opt_val, opt_typ) || - throw(ArgumentError("$opt_name should be of type $opt_typ, got $(typeof(opt_val))")) - d[opt_name] = opt_val - end - return d -end - -function dlm_fill(T::DataType, offarr::Vector{Vector{Int}}, dims::NTuple{2,Integer}, has_header::Bool, sbuff::String, auto::Bool, eol::AbstractChar) - idx = 1 - offidx = 1 - offsets = offarr[1] - row = 0 - col = 0 - try - dh = DLMStore(T, dims, has_header, sbuff, auto, eol) - while idx <= length(offsets) - row = offsets[idx] - col = offsets[idx+1] - quoted = offsets[idx+2] != 0 - startpos = offsets[idx+3] - endpos = offsets[idx+4] - - ((idx += 5) > offs_chunk_size) && (offidx < length(offarr)) && (idx = 1; offsets = offarr[offidx += 1]) - - store_cell(dh, row, col, quoted, startpos, endpos) - end - return result(dh) - catch ex - isa(ex, TypeError) && (ex.func === :store_cell) && (return dlm_fill(ex.expected, offarr, dims, has_header, sbuff, auto, eol)) - error("at row $row, column $col : $ex") - end -end - -function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{Bool,2}, row::Int, col::Int) - n = tryparse_internal(Bool, sbuff, startpos, endpos, 0, false) - n === nothing || (cells[row, col] = n) - n === nothing -end -function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{T,2}, row::Int, col::Int) where T<:Integer - n = tryparse_internal(T, sbuff, startpos, endpos, 0, false) - n === nothing || (cells[row, col] = n) - n === nothing -end -function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{T,2}, row::Int, col::Int) where T<:Union{Real,Complex} - n = tryparse_internal(T, sbuff, startpos, endpos, false) - n === nothing || (cells[row, col] = n) - n === nothing -end -function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{<:AbstractString,2}, row::Int, col::Int) - cells[row, col] = SubString(sbuff, startpos, endpos) - return false -end -function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{Any,2}, row::Int, col::Int) - # if array is of Any type, attempt parsing only the most common types: Int, Bool, Float64 and fallback to SubString - len = endpos-startpos+1 - if len > 0 - # check Inteter - ni64 = tryparse_internal(Int, sbuff, startpos, endpos, 0, false) - ni64 === nothing || (cells[row, col] = ni64; return false) - - # check Bool - nb = tryparse_internal(Bool, sbuff, startpos, endpos, 0, false) - nb === nothing || (cells[row, col] = nb; return false) - - # check float64 - hasvalue, valf64 = ccall(:jl_try_substrtod, Tuple{Bool, Float64}, - (Ptr{UInt8}, Csize_t, Csize_t), sbuff, startpos-1, endpos-startpos+1) - hasvalue && (cells[row, col] = valf64; return false) - end - cells[row, col] = SubString(sbuff, startpos, endpos) - false -end -function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{<:AbstractChar,2}, row::Int, col::Int) - if startpos == endpos - cells[row, col] = iterate(sbuff, startpos)[1] - return false - else - return true - end -end -colval(sbuff::String, startpos::Int, endpos::Int, cells::Array, row::Int, col::Int) = true - -function dlm_parse(dbuff::String, eol::D, dlm::D, qchar::D, cchar::D, - ign_adj_dlm::Bool, allow_quote::Bool, allow_comments::Bool, - skipstart::Int, skipblanks::Bool, dh::DLMHandler) where D - ncols = nrows = col = 0 - is_default_dlm = (dlm == invalid_dlm(D)) - error_str = "" - # 0: begin field, 1: quoted field, 2: unquoted field, - # 3: second quote (could either be end of field or escape character), - # 4: comment, 5: skipstart - state = (skipstart > 0) ? 5 : 0 - is_eol = is_dlm = is_cr = is_quote = is_comment = expct_col = false - idx = 1 - try - slen = sizeof(dbuff) - col_start_idx = 1 - was_cr = false - while idx <= slen - val,idx = iterate(dbuff, idx) - if (is_eol = (Char(val) == Char(eol))) - is_dlm = is_comment = is_cr = is_quote = false - elseif (is_dlm = (is_default_dlm ? isspace(Char(val)) : (Char(val) == Char(dlm)))) - is_comment = is_cr = is_quote = false - elseif (is_quote = (Char(val) == Char(qchar))) - is_comment = is_cr = false - elseif (is_comment = (Char(val) == Char(cchar))) - is_cr = false - else - is_cr = (Char(eol) == '\n') && (Char(val) == '\r') - end - - if 2 == state # unquoted field - if is_dlm - state = 0 - col += 1 - store_cell(dh, nrows+1, col, false, col_start_idx, idx-2) - col_start_idx = idx - !ign_adj_dlm && (expct_col = true) - elseif is_eol - nrows += 1 - col += 1 - store_cell(dh, nrows, col, false, col_start_idx, idx - (was_cr ? 3 : 2)) - col_start_idx = idx - ncols = max(ncols, col) - col = 0 - state = 0 - elseif (is_comment && allow_comments) - nrows += 1 - col += 1 - store_cell(dh, nrows, col, false, col_start_idx, idx - 2) - ncols = max(ncols, col) - col = 0 - state = 4 - end - elseif 1 == state # quoted field - is_quote && (state = 3) - elseif 4 == state # comment line - if is_eol - col_start_idx = idx - state = 0 - end - elseif 0 == state # begin field - if is_quote - state = (allow_quote && !was_cr) ? 1 : 2 - expct_col = false - elseif is_dlm - if !ign_adj_dlm - expct_col = true - col += 1 - store_cell(dh, nrows+1, col, false, col_start_idx, idx-2) - end - col_start_idx = idx - elseif is_eol - if (col > 0) || !skipblanks - nrows += 1 - if expct_col - col += 1 - store_cell(dh, nrows, col, false, col_start_idx, idx - (was_cr ? 3 : 2)) - end - ncols = max(ncols, col) - col = 0 - end - col_start_idx = idx - expct_col = false - elseif is_comment && allow_comments - if col > 0 - nrows += 1 - if expct_col - col += 1 - store_cell(dh, nrows, col, false, col_start_idx, idx - 2) - end - ncols = max(ncols, col) - col = 0 - end - expct_col = false - state = 4 - elseif !is_cr - state = 2 - expct_col = false - end - elseif 3 == state # second quote - if is_quote && !was_cr - state = 1 - elseif is_dlm && !was_cr - state = 0 - col += 1 - store_cell(dh, nrows+1, col, true, col_start_idx, idx-2) - col_start_idx = idx - !ign_adj_dlm && (expct_col = true) - elseif is_eol - nrows += 1 - col += 1 - store_cell(dh, nrows, col, true, col_start_idx, idx - (was_cr ? 3 : 2)) - col_start_idx = idx - ncols = max(ncols, col) - col = 0 - state = 0 - elseif is_comment && allow_comments && !was_cr - nrows += 1 - col += 1 - store_cell(dh, nrows, col, true, col_start_idx, idx - 2) - ncols = max(ncols, col) - col = 0 - state = 4 - elseif (is_cr && was_cr) || !is_cr - error_str = escape_string("unexpected character '$(Char(val))' after quoted field at row $(nrows+1) column $(col+1)") - break - end - elseif 5 == state # skip start - if is_eol - col_start_idx = idx - skipstart -= 1 - (0 == skipstart) && (state = 0) - end - end - was_cr = is_cr - end - - if isempty(error_str) - if 1 == state # quoted field - error_str = "truncated column at row $(nrows+1) column $(col+1)" - elseif (2 == state) || (3 == state) || ((0 == state) && is_dlm) # unquoted field, second quote, or begin field with last character as delimiter - col += 1 - nrows += 1 - store_cell(dh, nrows, col, (3 == state), col_start_idx, idx-1) - ncols = max(ncols, col) - end - end - catch ex - if isa(ex, TypeError) && (ex.func === :store_cell) - rethrow() - else - error("at row $(nrows+1), column $col : $ex)") - end - end - !isempty(error_str) && error(error_str) - - return (nrows, ncols) -end - -# todo: keyword argument for # of digits to print -writedlm_cell(io::IO, elt::AbstractFloat, dlm, quotes) = print(io, elt) -function writedlm_cell(io::IO, elt::AbstractString, dlm::T, quotes::Bool) where T - if quotes && !isempty(elt) && (('"' in elt) || ('\n' in elt) || ((T <: AbstractChar) ? (dlm in elt) : occursin(dlm, elt))) - print(io, '"', replace(elt, r"\"" => "\"\""), '"') - else - print(io, elt) - end -end -writedlm_cell(io::IO, elt, dlm, quotes) = print(io, elt) -function writedlm(io::IO, a::AbstractMatrix, dlm; opts...) - optsd = val_opts(opts) - quotes = get(optsd, :quotes, true) - pb = PipeBuffer() - lastc = last(axes(a, 2)) - for i = axes(a, 1) - for j = axes(a, 2) - writedlm_cell(pb, a[i, j], dlm, quotes) - j == lastc ? print(pb,'\n') : print(pb,dlm) - end - (bytesavailable(pb) > (16*1024)) && write(io, take!(pb)) - end - write(io, take!(pb)) - nothing -end - -writedlm(io::IO, a::AbstractArray{<:Any,0}, dlm; opts...) = writedlm(io, reshape(a,1), dlm; opts...) - -# write an iterable row as dlm-separated items -function writedlm_row(io::IO, row, dlm, quotes) - y = iterate(row) - while y !== nothing - (x, state) = y - y = iterate(row, state) - writedlm_cell(io, x, dlm, quotes) - y === nothing ? print(io,'\n') : print(io,dlm) - end -end - -# If the row is a single string, write it as a string rather than -# iterating over characters. Also, include the common case of -# a Number (handled correctly by the generic writedlm_row above) -# purely as an optimization. -function writedlm_row(io::IO, row::Union{Number,AbstractString}, dlm, quotes) - writedlm_cell(io, row, dlm, quotes) - print(io, '\n') -end - -# write an iterable collection of iterable rows -function writedlm(io::IO, itr, dlm; opts...) - optsd = val_opts(opts) - quotes = get(optsd, :quotes, true) - pb = PipeBuffer() - for row in itr - writedlm_row(pb, row, dlm, quotes) - (bytesavailable(pb) > (16*1024)) && write(io, take!(pb)) - end - write(io, take!(pb)) - nothing -end - -function writedlm(fname::AbstractString, a, dlm; opts...) - open(fname, "w") do io - writedlm(io, a, dlm; opts...) - end -end - -""" - writedlm(f, A, delim='\\t'; opts) - -Write `A` (a vector, matrix, or an iterable collection of iterable rows) as text to `f` -(either a filename string or an `IO` stream) using the given delimiter -`delim` (which defaults to tab, but can be any printable Julia object, typically a `Char` or -`AbstractString`). - -For example, two vectors `x` and `y` of the same length can be written as two columns of -tab-delimited text to `f` by either `writedlm(f, [x y])` or by `writedlm(f, zip(x, y))`. - -# Examples -```jldoctest -julia> using DelimitedFiles - -julia> x = [1; 2; 3; 4]; - -julia> y = [5; 6; 7; 8]; - -julia> open("delim_file.txt", "w") do io - writedlm(io, [x y]) - end - -julia> readdlm("delim_file.txt", '\\t', Int, '\\n') -4×2 Matrix{Int64}: - 1 5 - 2 6 - 3 7 - 4 8 - -julia> rm("delim_file.txt") -``` -""" -writedlm(io, a; opts...) = writedlm(io, a, '\t'; opts...) - -show(io::IO, ::MIME"text/csv", a) = writedlm(io, a, ',') -show(io::IO, ::MIME"text/tab-separated-values", a) = writedlm(io, a, '\t') - -end # module DelimitedFiles diff --git a/stdlib/DelimitedFiles/test/runtests.jl b/stdlib/DelimitedFiles/test/runtests.jl deleted file mode 100644 index 69285b6c58fb0..0000000000000 --- a/stdlib/DelimitedFiles/test/runtests.jl +++ /dev/null @@ -1,332 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -using Test, Random -using DelimitedFiles - -isequaldlm(m1, m2, t) = isequal(m1, m2) && (eltype(m1) == eltype(m2) == t) - -@testset "readdlm" begin - @test isequaldlm(readdlm(IOBuffer("1\t2\n3\t4\n5\t6\n")), [1. 2; 3 4; 5 6], Float64) - @test isequaldlm(readdlm(IOBuffer("1\t2\n3\t4\n5\t6\n"), Int), [1 2; 3 4; 5 6], Int) - @test isequaldlm(readdlm(IOBuffer("1,22222222222222222222222222222222222222,0x3,10e6\n2000.1,true,false,-10.34"), ',', Any), - reshape(Any[1,2000.1,Float64(22222222222222222222222222222222222222),true,0x3,false,10e6,-10.34], 2, 4), Any) - - @test isequaldlm(readdlm(IOBuffer("-9223355253176920979,9223355253176920979"), ',', Int64), Int64[-9223355253176920979 9223355253176920979], Int64) - - @test size(readdlm(IOBuffer("1,2,3,4"), ',')) == (1,4) - @test size(readdlm(IOBuffer("1,2,3,"), ',')) == (1,4) - @test size(readdlm(IOBuffer("1,2,3,4\n"), ',')) == (1,4) - @test size(readdlm(IOBuffer("1,2,3,\n"), ',')) == (1,4) - @test size(readdlm(IOBuffer("1,2,3,4\n1,2,3,4"), ',')) == (2,4) - @test size(readdlm(IOBuffer("1,2,3,4\n1,2,3,"), ',')) == (2,4) - @test size(readdlm(IOBuffer("1,2,3,4\n1,2,3"), ',')) == (2,4) - - @test size(readdlm(IOBuffer("1,2,3,4\r\n"), ',')) == (1,4) - @test size(readdlm(IOBuffer("1,2,3,4\r\n1,2,3\r\n"), ',')) == (2,4) - @test size(readdlm(IOBuffer("1,2,3,4\r\n1,2,3,4\r\n"), ',')) == (2,4) - @test size(readdlm(IOBuffer("1,2,3,\"4\"\r\n1,2,3,4\r\n"), ',')) == (2,4) - - @test size(readdlm(IOBuffer("1 2 3 4\n1 2 3"))) == (2,4) - @test size(readdlm(IOBuffer("1\t2 3 4\n1 2 3"))) == (2,4) - @test size(readdlm(IOBuffer("1\t 2 3 4\n1 2 3"))) == (2,4) - @test size(readdlm(IOBuffer("1\t 2 3 4\n1 2 3\n"))) == (2,4) - @test size(readdlm(IOBuffer("1,,2,3,4\n1,2,3\n"), ',')) == (2,5) - - let result1 = reshape(Any["", "", "", "", "", "", 1.0, 1.0, "", "", "", "", "", 1.0, 2.0, "", 3.0, "", "", "", "", "", 4.0, "", "", ""], 2, 13), - result2 = reshape(Any[1.0, 1.0, 2.0, 1.0, 3.0, "", 4.0, ""], 2, 4) - - @test isequaldlm(readdlm(IOBuffer(",,,1,,,,2,3,,,4,\n,,,1,,,1\n"), ','), result1, Any) - @test isequaldlm(readdlm(IOBuffer(" 1 2 3 4 \n 1 1\n")), result2, Any) - @test isequaldlm(readdlm(IOBuffer(" 1 2 3 4 \n 1 1\n"), ' '), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1 2\n3 4 \n")), [[1.0, 3.0] [2.0, 4.0]], Float64) - end - - let result1 = reshape(Any["", "", "", "", "", "", "भारत", 1.0, "", "", "", "", "", 1.0, 2.0, "", 3.0, "", "", "", "", "", 4.0, "", "", ""], 2, 13) - @test isequaldlm(readdlm(IOBuffer(",,,भारत,,,,2,3,,,4,\n,,,1,,,1\n"), ',') , result1, Any) - end - - let result1 = reshape(Any[1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, ""], 2, 4) - @test isequaldlm(readdlm(IOBuffer("1\t 2 3 4\n1 2 3")), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1\t 2 3 4\n1 2 3 ")), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1\t 2 3 4\n1 2 3\n")), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1,2,3,4\n1,2,3\n"), ','), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1,2,3,4\n1,2,3"), ','), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1,2,3,4\r\n1,2,3\r\n"), ','), result1, Any) - @test isequaldlm(readdlm(IOBuffer("1,2,3,\"4\"\r\n1,2,3\r\n"), ','), result1, Any) - end - - let result1 = reshape(Any["abc", "hello", "def,ghi", " \"quote\" ", "new\nline", "world"], 2, 3), - result2 = reshape(Any["abc", "line\"", "\"hello\"", "\"def", "", "\" \"\"quote\"\" \"", "ghi\"", "", "world", "\"new", "", ""], 3, 4) - - @test isequaldlm(readdlm(IOBuffer("abc,\"def,ghi\",\"new\nline\"\n\"hello\",\" \"\"quote\"\" \",world"), ','), result1, Any) - @test isequaldlm(readdlm(IOBuffer("abc,\"def,ghi\",\"new\nline\"\n\"hello\",\" \"\"quote\"\" \",world"), ',', quotes=false), result2, Any) - end - - let result1 = reshape(Any["t", "c", "", "c"], 2, 2), - result2 = reshape(Any["t", "\"c", "t", "c"], 2, 2) - @test isequaldlm(readdlm(IOBuffer("t \n\"c\" c")), result1, Any) - @test isequaldlm(readdlm(IOBuffer("t t \n\"\"\"c\" c")), result2, Any) - end - - @test isequaldlm(readdlm(IOBuffer("\n1,2,3\n4,5,6\n\n\n"), ',', skipblanks=false), - reshape(Any["",1.0,4.0,"","","",2.0,5.0,"","","",3.0,6.0,"",""], 5, 3), Any) - @test isequaldlm(readdlm(IOBuffer("\n1,2,3\n4,5,6\n\n\n"), ',', skipblanks=true), reshape([1.0,4.0,2.0,5.0,3.0,6.0], 2, 3), Float64) - @test isequaldlm(readdlm(IOBuffer("1,2\n\n4,5"), ',', skipblanks=false), reshape(Any[1.0,"",4.0,2.0,"",5.0], 3, 2), Any) - @test isequaldlm(readdlm(IOBuffer("1,2\n\n4,5"), ',', skipblanks=true), reshape([1.0,4.0,2.0,5.0], 2, 2), Float64) - - let x = bitrand(5, 10), io = IOBuffer() - writedlm(io, x) - seek(io, 0) - @test readdlm(io, Bool) == x - end - - let x = [1,2,3], y = [4,5,6], io = IOBuffer() - writedlm(io, zip(x,y), ", ") - seek(io, 0) - @test readdlm(io, ',') == [x y] - end - - let x = [0.1 0.3 0.5], io = IOBuffer() - writedlm(io, x, ", ") - seek(io, 0) - @test read(io, String) == "0.1, 0.3, 0.5\n" - end - - let x = [0.1 0.3 0.5], io = IOBuffer() - writedlm(io, x, ", ") - seek(io, 0) - @test readdlm(io, ',') == [0.1 0.3 0.5] - end - - let x = ["abc", "def\"ghi", "jk\nl"], y = [1, ",", "\"quoted\""], io = IOBuffer() - writedlm(io, zip(x,y), ',') - seek(io, 0) - @test readdlm(io, ',') == [x y] - end - - let x = ["a" "b"; "d" ""], io = IOBuffer() - writedlm(io, x) - seek(io, 0) - @test readdlm(io) == x - end - - let x = ["\"hello\"", "world\""], io = IOBuffer() - writedlm(io, x, quotes=false) - @test String(take!(io)) == "\"hello\"\nworld\"\n" - - writedlm(io, x) - @test String(take!(io)) == "\"\"\"hello\"\"\"\n\"world\"\"\"\n" - end -end - -@testset "comments" begin - @test isequaldlm(readdlm(IOBuffer("#this is comment\n1,2,3\n#one more comment\n4,5,6"), ',', comments=true), [1. 2. 3.;4. 5. 6.], Float64) - @test isequaldlm(readdlm(IOBuffer("#this is \n#comment\n1,2,3\n#one more \n#comment\n4,5,6"), ',', comments=true), [1. 2. 3.;4. 5. 6.], Float64) - @test isequaldlm(readdlm(IOBuffer("1,2,#3\n4,5,6"), ',', comments=true), [1. 2. "";4. 5. 6.], Any) - @test isequaldlm(readdlm(IOBuffer("1#,2,3\n4,5,6"), ',', comments=true), [1. "" "";4. 5. 6.], Any) - @test isequaldlm(readdlm(IOBuffer("1,2,\"#3\"\n4,5,6"), ',', comments=true), [1. 2. "#3";4. 5. 6.], Any) - @test isequaldlm(readdlm(IOBuffer("1,2,3\n #with leading whitespace\n4,5,6"), ',', comments=true), [1. 2. 3.;" " "" "";4. 5. 6.], Any) -end - -@testset "without comments" begin - @test isequaldlm(readdlm(IOBuffer("1,2,#3\n4,5,6"), ','), [1. 2. "#3";4. 5. 6.], Any) - @test isequaldlm(readdlm(IOBuffer("1#,2,3\n4,5,6"), ','), ["1#" 2. 3.;4. 5. 6.], Any) - @test isequaldlm(readdlm(IOBuffer("1,2,\"#3\"\n4,5,6"), ','), [1. 2. "#3";4. 5. 6.], Any) -end - -@testset "skipstart" begin - x = ["a" "b" "c"; "d" "e" "f"; "g" "h" "i"; "A" "B" "C"; 1 2 3; 4 5 6; 7 8 9] - io = IOBuffer() - - writedlm(io, x, quotes=false) - seek(io, 0) - (data, hdr) = readdlm(io, header=true, skipstart=3) - @test data == [1 2 3; 4 5 6; 7 8 9] - @test hdr == ["A" "B" "C"] - - x = ["a" "b" "\nc"; "d" "\ne" "f"; "g" "h" "i\n"; "A" "B" "C"; 1 2 3; 4 5 6; 7 8 9] - io = IOBuffer() - - writedlm(io, x, quotes=true) - seek(io, 0) - (data, hdr) = readdlm(io, header=true, skipstart=6) - @test data == [1 2 3; 4 5 6; 7 8 9] - @test hdr == ["A" "B" "C"] - - io = IOBuffer() - writedlm(io, x, quotes=false) - seek(io, 0) - (data, hdr) = readdlm(io, header=true, skipstart=6) - @test data == [1 2 3; 4 5 6; 7 8 9] - @test hdr == ["A" "B" "C"] -end - -@testset "i18n" begin - # source: http://www.i18nguy.com/unicode/unicode-example-utf8.zip - let i18n_data = ["Origin (English)", "Name (English)", "Origin (Native)", "Name (Native)", - "Australia", "Nicole Kidman", "Australia", "Nicole Kidman", - "Austria", "Johann Strauss", "Österreich", "Johann Strauß", - "Belgium (Flemish)", "Rene Magritte", "België", "René Magritte", - "Belgium (French)", "Rene Magritte", "Belgique", "René Magritte", - "Belgium (German)", "Rene Magritte", "Belgien", "René Magritte", - "Bhutan", "Gonpo Dorji", "འབྲུག་ཡུལ།", "མགོན་པོ་རྡོ་རྗེ།", - "Canada", "Celine Dion", "Canada", "Céline Dion", - "Canada - Nunavut (Inuktitut)", "Susan Aglukark", "ᓄᓇᕗᒻᒥᐅᑦ", "ᓱᓴᓐ ᐊᒡᓗᒃᑲᖅ", - "Democratic People's Rep. of Korea", "LEE Sol-Hee", "조선 민주주의 인민 공화국", "이설희", - "Denmark", "Soren Hauch-Fausboll", "Danmark", "Søren Hauch-Fausbøll", - "Denmark", "Soren Kierkegaard", "Danmark", "Søren Kierkegård", - "Egypt", "Abdel Halim Hafez", "ﻣﺼﺮ", "ﻋﺑﺪﺍﻠﺣﻟﻳﻢ ﺤﺎﻓﻅ", - "Egypt", "Om Kolthoum", "ﻣﺼﺮ", "ﺃﻡ ﻛﻟﺛﻭﻡ", - "Eritrea", "Berhane Zeray", "ብርሃነ ዘርኣይ", "ኤርትራ", - "Ethiopia", "Haile Gebreselassie", "ኃይሌ ገብረሥላሴ", "ኢትዮጵያ", - "France", "Gerard Depardieu", "France", "Gérard Depardieu", - "France", "Jean Reno", "France", "Jean Réno", - "France", "Camille Saint-Saens", "France", "Camille Saint-Saëns", - "France", "Mylene Demongeot", "France", "Mylène Demongeot", - "France", "Francois Truffaut", "France", "François Truffaut", - "France (Braille)", "Louis Braille", "⠋⠗⠁⠝⠉⠑", "⠇⠕⠥⠊⠎⠀
⠃⠗⠁⠊⠇⠇⠑", - "Georgia", "Eduard Shevardnadze", "საქართველო", "ედუარდ შევარდნაძე", - "Germany", "Rudi Voeller", "Deutschland", "Rudi Völler", - "Germany", "Walter Schultheiss", "Deutschland", "Walter Schultheiß", - "Greece", "Giorgos Dalaras", "Ελλάς", "Γιώργος Νταλάρας", - "Iceland", "Bjork Gudmundsdottir", "Ísland", "Björk Guðmundsdóttir", - "India (Hindi)", "Madhuri Dixit", "भारत", "माधुरी दिछित", - "Ireland", "Sinead O'Connor", "Éire", "Sinéad O'Connor", - "Israel", "Yehoram Gaon", "ישראל", "יהורם גאון", - "Italy", "Fabrizio DeAndre", "Italia", "Fabrizio De André", - "Japan", "KUBOTA Toshinobu", "日本", "久保田    利伸", - "Japan", "HAYASHIBARA Megumi", "日本", "林原 めぐみ", - "Japan", "Mori Ogai", "日本", "森鷗外", - "Japan", "Tex Texin", "日本", "テクス テクサン", - "Norway", "Tor Age Bringsvaerd", "Noreg", "Tor Åge Bringsværd", - "Pakistan (Urdu)", "Nusrat Fatah Ali Khan", "پاکستان", "نصرت فتح علی خان", - "People's Rep. of China", "ZHANG Ziyi", "中国", "章子怡", - "People's Rep. of China", "WONG Faye", "中国", "王菲", - "Poland", "Lech Walesa", "Polska", "Lech Wałęsa", - "Puerto Rico", "Olga Tanon", "Puerto Rico", "Olga Tañón", - "Rep. of China", "Hsu Chi", "臺灣", "舒淇", - "Rep. of China", "Ang Lee", "臺灣", "李安", - "Rep. of Korea", "AHN Sung-Gi", "대한민국", "안성기", - "Rep. of Korea", "SHIM Eun-Ha", "대한민국", "심은하", - "Russia", "Mikhail Gorbachev", "Россия", "Михаил Горбачёв", - "Russia", "Boris Grebenshchikov", "Россия", "Борис Гребенщиков", - "Slovenia", "\"Frane \"\"Jezek\"\" Milcinski", "Slovenija", "Frane Milčinski - Ježek", - "Syracuse (Sicily)", "Archimedes", "Συρακούσα", "Ἀρχιμήδης", - "Thailand", "Thongchai McIntai", "ประเทศไทย", "ธงไชย แม็คอินไตย์", - "U.S.A.", "Brad Pitt", "U.S.A.", "Brad Pitt", - "Yugoslavia (Cyrillic)", "Djordje Balasevic", "Југославија", "Ђорђе Балашевић", - "Yugoslavia (Latin)", "Djordje Balasevic", "Jugoslavija", "Đorđe Balašević"] - - i18n_arr = permutedims(reshape(i18n_data, 4, Int(floor(length(i18n_data)/4))), [2, 1]) - i18n_buff = PipeBuffer() - writedlm(i18n_buff, i18n_arr, ',') - @test i18n_arr == readdlm(i18n_buff, ',') - - hdr = i18n_arr[1:1, :] - data = i18n_arr[2:end, :] - writedlm(i18n_buff, i18n_arr, ',') - @test (data, hdr) == readdlm(i18n_buff, ',', header=true) - - writedlm(i18n_buff, i18n_arr, '\t') - @test (data, hdr) == readdlm(i18n_buff, '\t', header=true) - end -end - -@testset "issue #13028" begin - for data in ["A B C", "A B C\n"] - data,hdr = readdlm(IOBuffer(data), header=true) - @test hdr == AbstractString["A" "B" "C"] - @test data == Matrix{Float64}(undef, 0, 3) - end -end - -# fix #13179 parsing unicode lines with default delmiters -@test isequaldlm(readdlm(IOBuffer("# Should ignore this π\n1\tα\n2\tβ\n"), comments=true), Any[1 "α"; 2 "β"], Any) - -# BigInt parser -let data = "1 2 3" - readdlm(IOBuffer(data), ' ', BigInt) == BigInt[1 2 3] -end - -@testset "show with MIME types" begin - @test sprint(show, "text/csv", [1 2; 3 4]) == "1,2\n3,4\n" - @test sprint(show, "text/tab-separated-values", [1 2; 3 4]) == "1\t2\n3\t4\n" - - for writefunc in ((io,x) -> show(io, "text/csv", x), - (io,x) -> invoke(writedlm, Tuple{IO,Any,Any}, io, x, ",")) - # iterable collections of iterable rows: - let x = [(1,2), (3,4)], io = IOBuffer() - writefunc(io, x) - seek(io, 0) - @test readdlm(io, ',') == [1 2; 3 4] - end - # vectors of strings: - let x = ["foo", "bar"], io = IOBuffer() - writefunc(io, x) - seek(io, 0) - @test vec(readdlm(io, ',')) == x - end - end - - for writefunc in ((io,x) -> show(io, "text/tab-separated-values", x), - (io,x) -> invoke(writedlm, Tuple{IO,Any,Any}, io, x, "\t")) - # iterable collections of iterable rows: - let x = [(1,2), (3,4)], io = IOBuffer() - writefunc(io, x) - seek(io, 0) - @test readdlm(io, '\t') == [1 2; 3 4] - end - # vectors of strings: - let x = ["foo", "bar"], io = IOBuffer() - writefunc(io, x) - seek(io, 0) - @test vec(readdlm(io, '\t')) == x - end - end -end - -# Test that we can read a write protected file -let fn = tempname() - open(fn, "w") do f - write(f, "Julia") - end - chmod(fn, 0o444) - readdlm(fn)[] == "Julia" - rm(fn) -end - -# test writedlm with a filename instead of io input -let fn = tempname(), x = ["a" "b"; "d" ""] - writedlm(fn, x, ',') - @test readdlm(fn, ',') == x - rm(fn) -end - -# issue #21180 -let data = "\"721\",\"1438\",\"1439\",\"…\",\"1\"" - @test readdlm(IOBuffer(data), ',') == Any[721 1438 1439 "…" 1] -end - -# issue #21207 -let data = "\"1\",\"灣\"\"灣灣灣灣\",\"3\"" - @test readdlm(IOBuffer(data), ',') == Any[1 "灣\"灣灣灣灣" 3] -end - -# reading from a byte array (#16731) -let data = Vector{UInt8}("1,2,3\n4,5,6"), origdata = copy(data) - @test readdlm(data, ',') == [1 2 3; 4 5 6] - @test data == origdata -end - -# issue #11484: useful error message for invalid readdlm filepath arguments -@test_throws ArgumentError readdlm(tempdir()) - -# showing as text/csv -let d = TextDisplay(PipeBuffer()) - show(d.io, "text/csv", [3 1 4]) - @test read(d.io, String) == "3,1,4\n" -end - -@testset "complex" begin - @test readdlm(IOBuffer("3+4im, 4+5im"), ',', Complex{Int}) == [3+4im 4+5im] -end diff --git a/stdlib/Distributed/docs/src/index.md b/stdlib/Distributed/docs/src/index.md index dc8cef5e22d92..00b40de49b396 100644 --- a/stdlib/Distributed/docs/src/index.md +++ b/stdlib/Distributed/docs/src/index.md @@ -1,4 +1,5 @@ # [Distributed Computing](@id man-distributed) +Tools for distributed parallel processing. ```@docs Distributed.addprocs @@ -12,6 +13,7 @@ Distributed.interrupt Distributed.myid Distributed.pmap Distributed.RemoteException +Distributed.ProcessExitedException Distributed.Future Distributed.RemoteChannel Distributed.fetch(::Distributed.Future) diff --git a/stdlib/Distributed/src/Distributed.jl b/stdlib/Distributed/src/Distributed.jl index d428a6df0e683..a7c5b1778b144 100644 --- a/stdlib/Distributed/src/Distributed.jl +++ b/stdlib/Distributed/src/Distributed.jl @@ -7,10 +7,10 @@ module Distributed # imports for extension import Base: getindex, wait, put!, take!, fetch, isready, push!, length, - hash, ==, kill, close, isopen, showerror + hash, ==, kill, close, isopen, showerror, iterate, IteratorSize # imports for use -using Base: Process, Semaphore, JLOptions, buffer_writes, @sync_add, +using Base: Process, Semaphore, JLOptions, buffer_writes, @async_unwrap, VERSION_STRING, binding_module, atexit, julia_exename, julia_cmd, AsyncGenerator, acquire, release, invokelatest, shell_escape_posixly, shell_escape_csh, @@ -76,7 +76,10 @@ function _require_callback(mod::Base.PkgId) # broadcast top-level (e.g. from Main) import/using from node 1 (only) @sync for p in procs() p == 1 && continue - @sync_add remotecall(p) do + # Extensions are already loaded on workers by their triggers being loaded + # so no need to fire the callback upon extension being loaded on master. + Base.loading_extension && continue + @async_unwrap remotecall_wait(p) do Base.require(mod) nothing end @@ -107,6 +110,7 @@ include("macros.jl") # @spawn and friends include("workerpool.jl") include("pmap.jl") include("managers.jl") # LocalManager and SSHManager +include("precompile.jl") function __init__() init_parallel() diff --git a/stdlib/Distributed/src/cluster.jl b/stdlib/Distributed/src/cluster.jl index 5e90f231f59b1..3fd3d63108297 100644 --- a/stdlib/Distributed/src/cluster.jl +++ b/stdlib/Distributed/src/cluster.jl @@ -22,7 +22,7 @@ Some are used by the cluster manager to add workers to an already-initialized ho * `count` -- the number of workers to be launched on the host * `exename` -- the path to the Julia executable on the host, defaults to `"\$(Sys.BINDIR)/julia"` or `"\$(Sys.BINDIR)/julia-debug"` - * `exeflags` -- flags to use when lauching Julia remotely + * `exeflags` -- flags to use when launching Julia remotely The `userdata` field is used to store information for each worker by external managers. @@ -232,7 +232,10 @@ start_worker(cookie::AbstractString=readline(stdin); kwargs...) = start_worker(s function start_worker(out::IO, cookie::AbstractString=readline(stdin); close_stdin::Bool=true, stderr_to_stdout::Bool=true) init_multi() - close_stdin && close(stdin) # workers will not use it + if close_stdin # workers will not use it + redirect_stdin(devnull) + close(stdin) + end stderr_to_stdout && redirect_stderr(stdout) init_worker(cookie) @@ -533,6 +536,7 @@ default_addprocs_params() = Dict{Symbol,Any}( :dir => pwd(), :exename => joinpath(Sys.BINDIR, julia_exename()), :exeflags => ``, + :env => [], :enable_threaded_blas => false, :lazy => true) @@ -611,7 +615,7 @@ function create_worker(manager, wconfig) end end - # set when the new worker has finshed connections with all other workers + # set when the new worker has finished connections with all other workers ntfy_oid = RRID() rr_ntfy_join = lookup_ref(ntfy_oid) rr_ntfy_join.waitingfor = myid() @@ -1327,7 +1331,10 @@ function process_opts(opts) end # Propagate --threads to workers - exeflags = opts.nthreads > 0 ? `--threads=$(opts.nthreads)` : `` + threads = opts.nthreads > 0 ? `--threads=$(opts.nthreads)` : `` + gcthreads = opts.ngcthreads > 0 ? `--gcthreads=$(opts.ngcthreads)` : `` + + exeflags = `$threads $gcthreads` # add processors if opts.nprocs > 0 diff --git a/stdlib/Distributed/src/clusterserialize.jl b/stdlib/Distributed/src/clusterserialize.jl index e37987c5bf875..0acd4ce68c45b 100644 --- a/stdlib/Distributed/src/clusterserialize.jl +++ b/stdlib/Distributed/src/clusterserialize.jl @@ -170,7 +170,7 @@ function deserialize_global_from_main(s::ClusterSerializer, sym) if sym_isconst ccall(:jl_set_const, Cvoid, (Any, Any, Any), Main, sym, v) else - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, sym, v) + setglobal!(Main, sym, v) end return nothing end @@ -243,7 +243,7 @@ An exception is raised if a global constant is requested to be cleared. """ function clear!(syms, pids=workers(); mod=Main) @sync for p in pids - @sync_add remotecall(clear_impl!, p, syms, mod) + @async_unwrap remotecall_wait(clear_impl!, p, syms, mod) end end clear!(sym::Symbol, pid::Int; mod=Main) = clear!([sym], [pid]; mod=mod) diff --git a/stdlib/Distributed/src/macros.jl b/stdlib/Distributed/src/macros.jl index 0a62fdd5439f0..a767c7a40d9c9 100644 --- a/stdlib/Distributed/src/macros.jl +++ b/stdlib/Distributed/src/macros.jl @@ -222,10 +222,10 @@ function remotecall_eval(m::Module, procs, ex) if pid == myid() run_locally += 1 else - @sync_add remotecall(Core.eval, pid, m, ex) + @async_unwrap remotecall_wait(Core.eval, pid, m, ex) end end - yield() # ensure that the remotecall_fetch have had a chance to start + yield() # ensure that the remotecalls have had a chance to start # execute locally last as we do not want local execution to block serialization # of the request to remote nodes. diff --git a/stdlib/Distributed/src/managers.jl b/stdlib/Distributed/src/managers.jl index e3ecb97767ae3..57f58598e85dc 100644 --- a/stdlib/Distributed/src/managers.jl +++ b/stdlib/Distributed/src/managers.jl @@ -51,21 +51,32 @@ end """ addprocs(machines; tunnel=false, sshflags=\`\`, max_parallel=10, kwargs...) -> List of process identifiers -Add processes on remote machines via SSH. See `exename` to set the path to the `julia` installation on remote machines. - -`machines` is a vector of machine specifications. Workers are started for each specification. - -A machine specification is either a string `machine_spec` or a tuple - `(machine_spec, count)`. - -`machine_spec` is a string of the form `[user@]host[:port] [bind_addr[:port]]`. `user` -defaults to current user, `port` to the standard ssh port. If `[bind_addr[:port]]` is -specified, other workers will connect to this worker at the specified `bind_addr` and -`port`. - -`count` is the number of workers to be launched on the specified host. If specified as -`:auto` it will launch as many workers as the number of CPU threads on the specific host. - -Keyword arguments: +Add worker processes on remote machines via SSH. Configuration is done with keyword +arguments (see below). In particular, the `exename` keyword can be used to specify +the path to the `julia` binary on the remote machine(s). + +`machines` is a vector of "machine specifications" which are given as strings of +the form `[user@]host[:port] [bind_addr[:port]]`. `user` defaults to current user and `port` +to the standard SSH port. If `[bind_addr[:port]]` is specified, other workers will connect +to this worker at the specified `bind_addr` and `port`. + +It is possible to launch multiple processes on a remote host by using a tuple in the +`machines` vector or the form `(machine_spec, count)`, where `count` is the number of +workers to be launched on the specified host. Passing `:auto` as the worker count will +launch as many workers as the number of CPU threads on the remote host. + +**Examples**: +```julia +addprocs([ + "remote1", # one worker on 'remote1' logging in with the current username + "user@remote2", # one worker on 'remote2' logging in with the 'user' username + "user@remote3:2222", # specifying SSH port to '2222' for 'remote3' + ("user@remote4", 4), # launch 4 workers on 'remote4' + ("user@remote5", :auto), # launch as many workers as CPU threads on 'remote5' +]) +``` + +**Keyword arguments**: * `tunnel`: if `true` then SSH tunneling will be used to connect to the worker from the master process. Default is `false`. @@ -96,7 +107,9 @@ Keyword arguments: processes. Default is `false`. * `exename`: name of the `julia` executable. Defaults to `"\$(Sys.BINDIR)/julia"` or - `"\$(Sys.BINDIR)/julia-debug"` as the case may be. + `"\$(Sys.BINDIR)/julia-debug"` as the case may be. It is recommended that a common Julia + version is used on all remote machines because serialization and code distribution might + fail otherwise. * `exeflags`: additional flags passed to the worker processes. @@ -184,7 +197,7 @@ function parse_machine(machine::AbstractString) if machine[begin] == '[' # ipv6 bracket notation (RFC 2732) ipv6_end = findlast(']', machine) if ipv6_end === nothing - throw(ArgumentError("invalid machine definition format string: invalid port format \"$machine_def\"")) + throw(ArgumentError("invalid machine definition format string: invalid port format \"$machine\"")) end hoststr = machine[begin+1 : prevind(machine,ipv6_end)] machine_def = split(machine[ipv6_end : end] , ':') @@ -268,7 +281,7 @@ function launch_on_machine(manager::SSHManager, machine::AbstractString, cnt, pa end # Julia process with passed in command line flag arguments - if shell == :posix + if shell === :posix # ssh connects to a POSIX shell cmds = "exec $(shell_escape_posixly(exename)) $(shell_escape_posixly(exeflags))" @@ -284,7 +297,7 @@ function launch_on_machine(manager::SSHManager, machine::AbstractString, cnt, pa # shell login (-l) with string command (-c) to launch julia process remotecmd = shell_escape_posixly(`sh -l -c $cmds`) - elseif shell == :csh + elseif shell === :csh # ssh connects to (t)csh remotecmd = "exec $(shell_escape_csh(exename)) $(shell_escape_csh(exeflags))" @@ -300,7 +313,7 @@ function launch_on_machine(manager::SSHManager, machine::AbstractString, cnt, pa remotecmd = "cd $(shell_escape_csh(dir))\n$remotecmd" end - elseif shell == :wincmd + elseif shell === :wincmd # ssh connects to Windows cmd.exe any(c -> c == '"', exename) && throw(ArgumentError("invalid exename")) @@ -405,7 +418,7 @@ function ssh_tunnel(user, host, bind_addr, port, sshflags, multiplex) else # if we cannot do port forwarding, fail immediately # the -f option backgrounds the ssh session - # `sleep 60` command specifies that an alloted time of 60 seconds is allowed to start the + # `sleep 60` command specifies that an allotted time of 60 seconds is allowed to start the # remote julia process and establish the network connections specified by the process topology. # If no connections are made within 60 seconds, ssh will exit and an error will be printed on the # process that launched the remote process. @@ -430,26 +443,23 @@ struct LocalManager <: ClusterManager end """ - addprocs(; kwargs...) -> List of process identifiers + addprocs(np::Integer=Sys.CPU_THREADS; restrict=true, kwargs...) -> List of process identifiers -Equivalent to `addprocs(Sys.CPU_THREADS; kwargs...)` +Launch `np` workers on the local host using the in-built `LocalManager`. -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. -""" -addprocs(; kwargs...) = addprocs(Sys.CPU_THREADS; kwargs...) +Local workers inherit the current package environment (i.e., active project, +[`LOAD_PATH`](@ref), and [`DEPOT_PATH`](@ref)) from the main process. -""" - addprocs(np::Integer; restrict=true, kwargs...) -> List of process identifiers +**Keyword arguments**: + - `restrict::Bool`: if `true` (default) binding is restricted to `127.0.0.1`. + - `dir`, `exename`, `exeflags`, `env`, `topology`, `lazy`, `enable_threaded_blas`: same effect + as for `SSHManager`, see documentation for [`addprocs(machines::AbstractVector)`](@ref). -Launches workers using the in-built `LocalManager` which only launches workers on the -local host. This can be used to take advantage of multiple cores. `addprocs(4)` will add 4 -processes on the local machine. If `restrict` is `true`, binding is restricted to -`127.0.0.1`. Keyword args `dir`, `exename`, `exeflags`, `topology`, `lazy` and -`enable_threaded_blas` have the same effect as documented for `addprocs(machines)`. +!!! compat "Julia 1.9" + The inheriting of the package environment and the `env` keyword argument were + added in Julia 1.9. """ -function addprocs(np::Integer; restrict=true, kwargs...) +function addprocs(np::Integer=Sys.CPU_THREADS; restrict=true, kwargs...) manager = LocalManager(np, restrict) check_addprocs_args(manager, kwargs) addprocs(manager; kwargs...) @@ -462,10 +472,39 @@ function launch(manager::LocalManager, params::Dict, launched::Array, c::Conditi exename = params[:exename] exeflags = params[:exeflags] bind_to = manager.restrict ? `127.0.0.1` : `$(LPROC.bind_addr)` + env = Dict{String,String}(params[:env]) + + # TODO: Maybe this belongs in base/initdefs.jl as a package_environment() function + # together with load_path() etc. Might be useful to have when spawning julia + # processes outside of Distributed.jl too. + # JULIA_(LOAD|DEPOT)_PATH are used to populate (LOAD|DEPOT)_PATH on startup, + # but since (LOAD|DEPOT)_PATH might have changed they are re-serialized here. + # Users can opt-out of this by passing `env = ...` to addprocs(...). + pathsep = Sys.iswindows() ? ";" : ":" + if get(env, "JULIA_LOAD_PATH", nothing) === nothing + env["JULIA_LOAD_PATH"] = join(LOAD_PATH, pathsep) + end + if get(env, "JULIA_DEPOT_PATH", nothing) === nothing + env["JULIA_DEPOT_PATH"] = join(DEPOT_PATH, pathsep) + end + + # If we haven't explicitly asked for threaded BLAS, prevent OpenBLAS from starting + # up with multiple threads, thereby sucking up a bunch of wasted memory on Windows. + if !params[:enable_threaded_blas] && + get(env, "OPENBLAS_NUM_THREADS", nothing) === nothing + env["OPENBLAS_NUM_THREADS"] = "1" + end + # Set the active project on workers using JULIA_PROJECT. + # Users can opt-out of this by (i) passing `env = ...` or (ii) passing + # `--project=...` as `exeflags` to addprocs(...). + project = Base.ACTIVE_PROJECT[] + if project !== nothing && get(env, "JULIA_PROJECT", nothing) === nothing + env["JULIA_PROJECT"] = project + end for i in 1:manager.np cmd = `$(julia_cmd(exename)) $exeflags --bind-to $bind_to --worker` - io = open(detach(setenv(cmd, dir=dir)), "r+") + io = open(detach(setenv(addenv(cmd, env), dir=dir)), "r+") write_cookie(io) wconfig = WorkerConfig() @@ -693,3 +732,26 @@ function kill(manager::SSHManager, pid::Int, config::WorkerConfig) cancel_ssh_tunnel(config) nothing end + +function kill(manager::LocalManager, pid::Int, config::WorkerConfig; exit_timeout = 15, term_timeout = 15) + # First, try sending `exit()` to the remote over the usual control channels + remote_do(exit, pid) + + timer_task = @async begin + sleep(exit_timeout) + + # Check to see if our child exited, and if not, send an actual kill signal + if !process_exited(config.process) + @warn("Failed to gracefully kill worker $(pid), sending SIGTERM") + kill(config.process, Base.SIGTERM) + + sleep(term_timeout) + if !process_exited(config.process) + @warn("Worker $(pid) ignored SIGTERM, sending SIGKILL") + kill(config.process, Base.SIGKILL) + end + end + end + errormonitor(timer_task) + return nothing +end diff --git a/stdlib/Distributed/src/pmap.jl b/stdlib/Distributed/src/pmap.jl index 603dfa7e031ce..f884d47fff98e 100644 --- a/stdlib/Distributed/src/pmap.jl +++ b/stdlib/Distributed/src/pmap.jl @@ -6,7 +6,7 @@ struct BatchProcessingError <: Exception end """ - pgenerate([::WorkerPool], f, c...) -> iterator + pgenerate([::AbstractWorkerPool], f, c...) -> iterator Apply `f` to each element of `c` in parallel using available workers and tasks. @@ -18,14 +18,14 @@ Note that `f` must be made available to all worker processes; see [Code Availability and Loading Packages](@ref code-availability) for details. """ -function pgenerate(p::WorkerPool, f, c) +function pgenerate(p::AbstractWorkerPool, f, c) if length(p) == 0 return AsyncGenerator(f, c; ntasks=()->nworkers(p)) end batches = batchsplit(c, min_batch_count = length(p) * 3) return Iterators.flatten(AsyncGenerator(remote(p, b -> asyncmap(f, b)), batches)) end -pgenerate(p::WorkerPool, f, c1, c...) = pgenerate(p, a->f(a...), zip(c1, c...)) +pgenerate(p::AbstractWorkerPool, f, c1, c...) = pgenerate(p, a->f(a...), zip(c1, c...)) pgenerate(f, c) = pgenerate(default_worker_pool(), f, c) pgenerate(f, c1, c...) = pgenerate(a->f(a...), zip(c1, c...)) diff --git a/stdlib/Distributed/src/precompile.jl b/stdlib/Distributed/src/precompile.jl new file mode 100644 index 0000000000000..87380f627db7a --- /dev/null +++ b/stdlib/Distributed/src/precompile.jl @@ -0,0 +1,14 @@ +precompile(Tuple{typeof(Distributed.remotecall),Function,Int,Module,Vararg{Any, 100}}) +precompile(Tuple{typeof(Distributed.procs)}) +precompile(Tuple{typeof(Distributed.finalize_ref), Distributed.Future}) +# This is disabled because it doesn't give much benefit +# and the code in Distributed is poorly typed causing many invalidations +# TODO: Maybe reenable now that Distributed is not in sysimage. +#= + precompile_script *= """ + using Distributed + addprocs(2) + pmap(x->iseven(x) ? 1 : 0, 1:4) + @distributed (+) for i = 1:100 Int(rand(Bool)) end + """ +=# diff --git a/stdlib/Distributed/src/remotecall.jl b/stdlib/Distributed/src/remotecall.jl index d4bf767537c1d..0b1143d855510 100644 --- a/stdlib/Distributed/src/remotecall.jl +++ b/stdlib/Distributed/src/remotecall.jl @@ -321,7 +321,7 @@ function process_worker(rr) w = worker_from_id(rr.where)::Worker msg = (remoteref_id(rr), myid()) - # Needs to aquire a lock on the del_msg queue + # Needs to acquire a lock on the del_msg queue T = Threads.@spawn begin publish_del_msg!($w, $msg) end @@ -485,7 +485,7 @@ julia> remotecall_fetch(sqrt, 2, 4) julia> remotecall_fetch(sqrt, 2, -4) ERROR: On worker 2: DomainError with -4.0: -sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). +sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)). ... ``` """ @@ -778,3 +778,23 @@ function getindex(r::RemoteChannel, args...) end return remotecall_fetch(getindex, r.where, r, args...) end + +function iterate(c::RemoteChannel, state=nothing) + if isopen(c) || isready(c) + try + return (take!(c), nothing) + catch e + if isa(e, InvalidStateException) || + (isa(e, RemoteException) && + isa(e.captured.ex, InvalidStateException) && + e.captured.ex.state === :closed) + return nothing + end + rethrow() + end + else + return nothing + end +end + +IteratorSize(::Type{<:RemoteChannel}) = SizeUnknown() diff --git a/stdlib/Distributed/src/workerpool.jl b/stdlib/Distributed/src/workerpool.jl index 354c61c845113..5dd1c07044e09 100644 --- a/stdlib/Distributed/src/workerpool.jl +++ b/stdlib/Distributed/src/workerpool.jl @@ -33,9 +33,9 @@ function WorkerPool() end """ - WorkerPool(workers::Vector{Int}) + WorkerPool(workers::Union{Vector{Int},AbstractRange{Int}}) -Create a `WorkerPool` from a vector of worker ids. +Create a `WorkerPool` from a vector or range of worker ids. # Examples ```julia-repl @@ -43,9 +43,12 @@ Create a `WorkerPool` from a vector of worker ids. julia> WorkerPool([2, 3]) WorkerPool(Channel{Int64}(sz_max:9223372036854775807,sz_curr:2), Set([2, 3]), RemoteChannel{Channel{Any}}(1, 1, 6)) + +julia> WorkerPool(2:4) +WorkerPool(Channel{Int64}(sz_max:9223372036854775807,sz_curr:2), Set([4, 2, 3]), RemoteChannel{Channel{Any}}(1, 1, 7)) ``` """ -function WorkerPool(workers::Vector{Int}) +function WorkerPool(workers::Union{Vector{Int},AbstractRange{Int}}) pool = WorkerPool() foreach(w->push!(pool, w), workers) return pool @@ -70,7 +73,7 @@ wp_local_length(pool::AbstractWorkerPool) = length(pool.workers) wp_local_isready(pool::AbstractWorkerPool) = isready(pool.channel) function wp_local_put!(pool::AbstractWorkerPool, w::Int) - # In case of default_worker_pool, the master is implictly considered a worker, i.e., + # In case of default_worker_pool, the master is implicitly considered a worker, i.e., # it is not present in pool.workers. # Confirm the that the worker is part of a pool before making it available. w in pool.workers && put!(pool.channel, w) @@ -236,12 +239,14 @@ perform a `remote_do` on it. """ remote_do(f, pool::AbstractWorkerPool, args...; kwargs...) = remotecall_pool(remote_do, f, pool, args...; kwargs...) -const _default_worker_pool = Ref{Union{WorkerPool, Nothing}}(nothing) +const _default_worker_pool = Ref{Union{AbstractWorkerPool, Nothing}}(nothing) """ default_worker_pool() -[`WorkerPool`](@ref) containing idle [`workers`](@ref) - used by `remote(f)` and [`pmap`](@ref) (by default). +[`AbstractWorkerPool`](@ref) containing idle [`workers`](@ref) - used by `remote(f)` and [`pmap`](@ref) +(by default). Unless one is explicitly set via `default_worker_pool!(pool)`, the default worker pool is +initialized to a [`WorkerPool`](@ref). # Examples ```julia-repl @@ -264,6 +269,15 @@ function default_worker_pool() return _default_worker_pool[] end +""" + default_worker_pool!(pool::AbstractWorkerPool) + +Set a [`AbstractWorkerPool`](@ref) to be used by `remote(f)` and [`pmap`](@ref) (by default). +""" +function default_worker_pool!(pool::AbstractWorkerPool) + _default_worker_pool[] = pool +end + """ remote([p::AbstractWorkerPool], f) -> Function diff --git a/stdlib/Distributed/test/distributed_exec.jl b/stdlib/Distributed/test/distributed_exec.jl index 69d9fb47eccc5..43e02c92b5a81 100644 --- a/stdlib/Distributed/test/distributed_exec.jl +++ b/stdlib/Distributed/test/distributed_exec.jl @@ -126,7 +126,7 @@ function testf(id) @test_throws ErrorException put!(f, :OK) # Cannot put! to a already set future @test_throws MethodError take!(f) # take! is unsupported on a Future - @test fetch(f) == :OK + @test fetch(f) === :OK end testf(id_me) @@ -152,13 +152,13 @@ function _getenv_include_thread_unsafe() return b end const _env_include_thread_unsafe = _getenv_include_thread_unsafe() -function include_thread_unsafe() - if Threads.nthreads() > 1 +function include_thread_unsafe_tests() + if Threads.maxthreadid() > 1 if _env_include_thread_unsafe return true end - msg = "Skipping a thread-unsafe test because `Threads.nthreads() > 1`" - @warn msg Threads.nthreads() + msg = "Skipping a thread-unsafe test because `Threads.maxthreadid() > 1`" + @warn msg Threads.maxthreadid() Test.@test_broken false return false end @@ -218,7 +218,7 @@ isready(f) @test remotecall_fetch(k->haskey(Distributed.PGRP.refs, k), wid1, fid) == true put!(f, :OK) @test remotecall_fetch(k->haskey(Distributed.PGRP.refs, k), wid1, fid) == false -@test fetch(f) == :OK +@test fetch(f) === :OK # RemoteException should be thrown on a put! when another process has set the value f = Future(wid1) @@ -260,8 +260,7 @@ remotecall_fetch(f25847, id_other, f) finalize(f) yield() # flush gc msgs -@test false == remotecall_fetch(chk_rrid->(yield(); haskey(Distributed.PGRP.refs, chk_rrid)), id_other, rrid) - +@test poll_while(() -> remotecall_fetch(chk_rrid->(yield(); haskey(Distributed.PGRP.refs, chk_rrid)), id_other, rrid)) # Distributed GC tests for RemoteChannels function test_remoteref_dgc(id) @@ -271,7 +270,7 @@ function test_remoteref_dgc(id) # remote value should be deleted after finalizing the ref @test remotecall_fetch(k->(yield();haskey(Distributed.PGRP.refs, k)), id, rrid) == true - @test fetch(rr) == :OK + @test fetch(rr) === :OK @test remotecall_fetch(k->(yield();haskey(Distributed.PGRP.refs, k)), id, rrid) == true finalize(rr) yield(); # flush gc msgs @@ -288,22 +287,22 @@ let wid1 = workers()[1], fstore = RemoteChannel(wid2) put!(fstore, rr) - if include_thread_unsafe() + if include_thread_unsafe_tests() @test remotecall_fetch(k -> haskey(Distributed.PGRP.refs, k), wid1, rrid) == true end finalize(rr) # finalize locally yield() # flush gc msgs - if include_thread_unsafe() + if include_thread_unsafe_tests() @test remotecall_fetch(k -> haskey(Distributed.PGRP.refs, k), wid1, rrid) == true end remotecall_fetch(r -> (finalize(take!(r)); yield(); nothing), wid2, fstore) # finalize remotely sleep(0.5) # to ensure that wid2 messages have been executed on wid1 - @test remotecall_fetch(k -> haskey(Distributed.PGRP.refs, k), wid1, rrid) == false + @test poll_while(() -> remotecall_fetch(k -> haskey(Distributed.PGRP.refs, k), wid1, rrid)) end # Tests for issue #23109 - should not hang. f = @spawnat :any rand(1, 1) -@Base.Experimental.sync begin +Base.Experimental.@sync begin for _ in 1:10 @async fetch(f) end @@ -311,7 +310,7 @@ end wid1, wid2 = workers()[1:2] f = @spawnat wid1 rand(1,1) -@Base.Experimental.sync begin +Base.Experimental.@sync begin @async fetch(f) @async remotecall_fetch(()->fetch(f), wid2) end @@ -350,7 +349,7 @@ function test_regular_io_ser(ref::Distributed.AbstractRemoteRef) v = getfield(ref2, fld) if isa(v, Number) @test v === zero(typeof(v)) - elseif fld == :lock + elseif fld === :lock @test v isa ReentrantLock @test !islocked(v) elseif v !== nothing @@ -457,6 +456,32 @@ function test_iteration(in_c, out_c) end test_iteration(Channel(10), Channel(10)) +test_iteration(RemoteChannel(() -> Channel(10)), RemoteChannel(() -> Channel(10))) + +@everywhere function test_iteration_take(ch) + count = 0 + for x in ch + count += 1 + end + return count +end + +@everywhere function test_iteration_put(ch, total) + for i in 1:total + put!(ch, i) + end + close(ch) +end + +let ch = RemoteChannel(() -> Channel(1)) + @async test_iteration_put(ch, 10) + @test 10 == @fetchfrom id_other test_iteration_take(ch) + # now reverse + ch = RemoteChannel(() -> Channel(1)) + @spawnat id_other test_iteration_put(ch, 10) + @test 10 == test_iteration_take(ch) +end + # make sure exceptions propagate when waiting on Tasks @test_throws CompositeException (@sync (@async error("oops"))) try @@ -529,7 +554,7 @@ let ex bt = ex.captured.processed_bt::Array{Any,1} @test length(bt) > 1 frame, repeated = bt[1]::Tuple{Base.StackTraces.StackFrame, Int} - @test frame.func == :foo + @test frame.func === :foo @test frame.linfo === nothing @test repeated == 1 end @@ -666,7 +691,8 @@ pmap(_->myid(), 1:nworkers()) # priming run wp = WorkerPool(workers()) @test nworkers() == length(unique(pmap(_->myid(), wp, 1:100))) @test nworkers() == length(unique(remotecall_fetch(wp->pmap(_->myid(), wp, 1:100), id_other, wp))) - +wp = WorkerPool(2:3) +@test sort(unique(pmap(_->myid(), wp, 1:100))) == [2,3] # CachingPool tests wp = CachingPool(workers()) @@ -675,13 +701,26 @@ wp = CachingPool(workers()) clear!(wp) @test length(wp.map_obj2ref) == 0 +# default_worker_pool! tests +wp_default = Distributed.default_worker_pool() +try + local wp = CachingPool(workers()) + Distributed.default_worker_pool!(wp) + @test [1:100...] == pmap(x->x, wp, 1:100) + @test !isempty(wp.map_obj2ref) + clear!(wp) + @test isempty(wp.map_obj2ref) +finally + Distributed.default_worker_pool!(wp_default) +end + # The below block of tests are usually run only on local development systems, since: # - tests which print errors # - addprocs tests are memory intensive # - ssh addprocs requires sshd to be running locally with passwordless login enabled. # The test block is enabled by defining env JULIA_TESTFULL=1 -DoFullTest = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) +DoFullTest = Base.get_bool_env("JULIA_TESTFULL", false) if DoFullTest println("Testing exception printing on remote worker from a `remote_do` call") @@ -815,11 +854,11 @@ function f13168(n) return val end let t = schedule(@task f13168(100)) - @test t.state == :runnable + @test t.state === :runnable @test t.queue !== nothing @test_throws ErrorException schedule(t) yield() - @test t.state == :done + @test t.state === :done @test t.queue === nothing @test_throws ErrorException schedule(t) @test isa(fetch(t), Float64) @@ -900,7 +939,7 @@ end take!(rc)[1] != float(i) && error("Failed") end return :OK - end, id_other, rc_unbuffered) == :OK + end, id_other, rc_unbuffered) === :OK # github issue 33972 rc_unbuffered_other = RemoteChannel(()->Channel{Int}(0), id_other) @@ -993,11 +1032,11 @@ end let @test_throws RemoteException remotecall_fetch(()->LocalFoo.foo, 2) - bad_thunk = ()->NonexistantModule.f() + bad_thunk = ()->NonexistentModule.f() @test_throws RemoteException remotecall_fetch(bad_thunk, 2) # Test that the stream is still usable - @test remotecall_fetch(()->:test,2) == :test + @test remotecall_fetch(()->:test,2) === :test ref = remotecall(bad_thunk, 2) @test_throws RemoteException fetch(ref) end @@ -1175,11 +1214,11 @@ function launch(manager::ErrorSimulator, params::Dict, launched::Array, c::Condi dir = params[:dir] cmd = `$(Base.julia_cmd(exename)) --startup-file=no` - if manager.mode == :timeout + if manager.mode === :timeout cmd = `$cmd -e "sleep(10)"` - elseif manager.mode == :ntries + elseif manager.mode === :ntries cmd = `$cmd -e "[println(x) for x in 1:1001]"` - elseif manager.mode == :exit + elseif manager.mode === :exit cmd = `$cmd -e "exit(-1)"` else error("Unknown mode") @@ -1427,7 +1466,7 @@ let thrown = false thrown = true local b = IOBuffer() showerror(b, e) - @test occursin("sqrt will only return", String(take!(b))) + @test occursin("sqrt was called with a negative real argument", String(take!(b))) end @test thrown end @@ -1616,7 +1655,11 @@ cluster_cookie("") for close_stdin in (true, false), stderr_to_stdout in (true, false) local npids = addprocs_with_testenv(RetainStdioTester(close_stdin,stderr_to_stdout)) @test remotecall_fetch(myid, npids[1]) == npids[1] - @test close_stdin != remotecall_fetch(()->isopen(stdin), npids[1]) + if close_stdin + @test remotecall_fetch(()->stdin === devnull && !isreadable(stdin), npids[1]) + else + @test remotecall_fetch(()->stdin !== devnull && isopen(stdin) && isreadable(stdin), npids[1]) + end @test stderr_to_stdout == remotecall_fetch(()->(stderr === stdout), npids[1]) rmprocs(npids) end @@ -1739,8 +1782,139 @@ for p in procs() @test @fetchfrom(p, i27429) == 27429 end +# Propagation of package environments for local workers (#28781) +let julia = `$(Base.julia_cmd()) --startup-file=no`; mktempdir() do tmp + project = mkdir(joinpath(tmp, "project")) + depots = [mkdir(joinpath(tmp, "depot1")), mkdir(joinpath(tmp, "depot2"))] + load_path = [mkdir(joinpath(tmp, "load_path")), "@stdlib", "@"] + pathsep = Sys.iswindows() ? ";" : ":" + env = Dict( + "JULIA_DEPOT_PATH" => join(depots, pathsep), + "JULIA_LOAD_PATH" => join(load_path, pathsep), + # Explicitly propagate `TMPDIR`, in the event that we're running on a + # CI system where `TMPDIR` is special. + "TMPDIR" => dirname(tmp), + ) + setupcode = """ + using Distributed, Test + @everywhere begin + depot_path() = DEPOT_PATH + load_path() = LOAD_PATH + active_project() = Base.ACTIVE_PROJECT[] + end + """ + testcode = setupcode * """ + for w in workers() + @test remotecall_fetch(depot_path, w) == DEPOT_PATH + @test remotecall_fetch(load_path, w) == LOAD_PATH + @test remotecall_fetch(Base.load_path, w) == Base.load_path() + @test remotecall_fetch(active_project, w) == Base.ACTIVE_PROJECT[] + @test remotecall_fetch(Base.active_project, w) == Base.active_project() + end + """ + # No active project + extracode = """ + for w in workers() + @test remotecall_fetch(active_project, w) === Base.ACTIVE_PROJECT[] === nothing + end + """ + cmd = setenv(`$(julia) -p1 -e $(testcode * extracode)`, env) + @test success(cmd) + # --project + extracode = """ + for w in workers() + @test remotecall_fetch(active_project, w) == Base.ACTIVE_PROJECT[] == + $(repr(project)) + end + """ + cmd = setenv(`$(julia) --project=$(project) -p1 -e $(testcode * extracode)`, env) + @test success(cmd) + # JULIA_PROJECT + cmd = setenv(`$(julia) -p1 -e $(testcode * extracode)`, + (env["JULIA_PROJECT"] = project; env)) + @test success(cmd) + # Pkg.activate(...) + activateish = """ + Base.ACTIVE_PROJECT[] = $(repr(project)) + using Distributed + addprocs(1) + """ + cmd = setenv(`$(julia) -e $(activateish * testcode * extracode)`, env) + @test success(cmd) + # JULIA_(LOAD|DEPOT)_PATH + shufflecode = """ + d = reverse(DEPOT_PATH) + append!(empty!(DEPOT_PATH), d) + l = reverse(LOAD_PATH) + append!(empty!(LOAD_PATH), l) + """ + addcode = """ + using Distributed + addprocs(1) # after shuffling + """ + extracode = """ + for w in workers() + @test remotecall_fetch(load_path, w) == $(repr(reverse(load_path))) + @test remotecall_fetch(depot_path, w) == $(repr(reverse(depots))) + end + """ + cmd = setenv(`$(julia) -e $(shufflecode * addcode * testcode * extracode)`, env) + @test success(cmd) + # Mismatch when shuffling after proc addition + failcode = shufflecode * setupcode * """ + for w in workers() + @test remotecall_fetch(load_path, w) == reverse(LOAD_PATH) == $(repr(load_path)) + @test remotecall_fetch(depot_path, w) == reverse(DEPOT_PATH) == $(repr(depots)) + end + """ + cmd = setenv(`$(julia) -p1 -e $(failcode)`, env) + @test success(cmd) + # Passing env or exeflags to addprocs(...) to override defaults + envcode = """ + using Distributed + project = mktempdir() + env = Dict( + "JULIA_LOAD_PATH" => string(LOAD_PATH[1], $(repr(pathsep)), "@stdlib"), + "JULIA_DEPOT_PATH" => DEPOT_PATH[1], + "TMPDIR" => ENV["TMPDIR"], + ) + addprocs(1; env = env, exeflags = `--project=\$(project)`) + env["JULIA_PROJECT"] = project + addprocs(1; env = env) + """ * setupcode * """ + for w in workers() + @test remotecall_fetch(depot_path, w) == [DEPOT_PATH[1]] + @test remotecall_fetch(load_path, w) == [LOAD_PATH[1], "@stdlib"] + @test remotecall_fetch(active_project, w) == project + @test remotecall_fetch(Base.active_project, w) == joinpath(project, "Project.toml") + end + """ + cmd = setenv(`$(julia) -e $(envcode)`, env) + @test success(cmd) +end end + include("splitrange.jl") +# Clear all workers for timeout tests (issue #45785) +rmprocs(workers()) +begin + # First, assert that we get no messages when we close a cooperative worker + w = only(addprocs(1)) + @test_nowarn begin + wait(rmprocs([w])) + end + + # Next, ensure we get a log message when a worker does not cleanly exit + w = only(addprocs(1)) + @test_logs (:warn, r"sending SIGTERM") begin + remote_do(w) do + # Cause the 'exit()' message that `rmprocs()` sends to do nothing + Core.eval(Base, :(exit() = nothing)) + end + wait(rmprocs([w])) + end +end + # Run topology tests last after removing all workers, since a given # cluster at any time only supports a single topology. rmprocs(workers()) diff --git a/stdlib/Distributed/test/managers.jl b/stdlib/Distributed/test/managers.jl index efc354356c618..7971222c7511a 100644 --- a/stdlib/Distributed/test/managers.jl +++ b/stdlib/Distributed/test/managers.jl @@ -13,9 +13,13 @@ using Distributed: parse_machine, SSHManager, LocalManager @test parse_machine("127.0.0.1:90") == ("127.0.0.1", 90) @test parse_machine("127.0.0.1:1") == ("127.0.0.1", 1) @test parse_machine("127.0.0.1:65535") == ("127.0.0.1", 65535) + @test_throws ArgumentError parse_machine("127.0.0.1:-1") @test_throws ArgumentError parse_machine("127.0.0.1:0") @test_throws ArgumentError parse_machine("127.0.0.1:65536") +@test_throws ArgumentError parse_machine("[2001:db8::1]:443:888") +@test_throws ArgumentError parse_machine("[2001:db8::1") +@test_throws ArgumentError parse_machine("[2001:db8::1]:aaa") @test occursin(r"^SSHManager\(machines=.*\)$", sprint((t,x) -> show(t, "text/plain", x), SSHManager("127.0.0.1"))) diff --git a/stdlib/Distributed/test/splitrange.jl b/stdlib/Distributed/test/splitrange.jl index 9f3c9c92a3ffa..1cb12e1952b7d 100644 --- a/stdlib/Distributed/test/splitrange.jl +++ b/stdlib/Distributed/test/splitrange.jl @@ -28,6 +28,8 @@ isdefined(Main, :OffsetArrays) || @eval Main @everywhere include(joinpath($(BASE using .Main.OffsetArrays oa = OffsetArray([123, -345], (-2,)) + +@everywhere using Test @sync @distributed for i in eachindex(oa) @test i ∈ (-1, 0) end diff --git a/stdlib/Distributed/test/topology.jl b/stdlib/Distributed/test/topology.jl index 2a659931ed306..fc969323bc587 100644 --- a/stdlib/Distributed/test/topology.jl +++ b/stdlib/Distributed/test/topology.jl @@ -62,9 +62,9 @@ end const map_pid_ident=Dict() function manage(manager::TopoTestManager, id::Integer, config::WorkerConfig, op::Symbol) - if op == :register + if op === :register map_pid_ident[id] = config.ident - elseif op == :interrupt + elseif op === :interrupt kill(config.process, 2) end end diff --git a/stdlib/Downloads.version b/stdlib/Downloads.version index 6553487f41cbc..c6db08779e947 100644 --- a/stdlib/Downloads.version +++ b/stdlib/Downloads.version @@ -1,4 +1,4 @@ DOWNLOADS_BRANCH = master -DOWNLOADS_SHA1 = 2a21b1536aec0219c6bdb78dbb6570fc31a40983 +DOWNLOADS_SHA1 = f97c72fbd726e208a04c53791b35cc34c747569f 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/docs/src/index.md b/stdlib/FileWatching/docs/src/index.md index 3944f5d3ed1c9..6c332511f578f 100644 --- a/stdlib/FileWatching/docs/src/index.md +++ b/stdlib/FileWatching/docs/src/index.md @@ -7,3 +7,31 @@ FileWatching.watch_file FileWatching.watch_folder FileWatching.unwatch_folder ``` + +# Pidfile + +```@meta +CurrentModule = FileWatching.Pidfile +``` + +A simple utility tool for creating advisory pidfiles (lock files). + +## Primary Functions + +```@docs +mkpidlock +close(lock::LockMonitor) +``` + + +## Helper Functions + +```@docs +Pidfile.open_exclusive +Pidfile.tryopen_exclusive +Pidfile.write_pidfile +Pidfile.parse_pidfile +Pidfile.stale_pidfile +Pidfile.isvalidpid +Base.touch(::Pidfile.LockMonitor) +``` diff --git a/stdlib/FileWatching/src/FileWatching.jl b/stdlib/FileWatching/src/FileWatching.jl index fd26b62132047..17ae24460db6b 100644 --- a/stdlib/FileWatching/src/FileWatching.jl +++ b/stdlib/FileWatching/src/FileWatching.jl @@ -16,7 +16,9 @@ export FileMonitor, FolderMonitor, PollingFileWatcher, - FDWatcher + FDWatcher, + # pidfile: + mkpidlock import Base: @handle_as, wait, close, eventloop, notify_error, IOError, _sizeof_uv_poll, _sizeof_uv_fs_poll, _sizeof_uv_fs_event, _uv_hook_close, uv_error, _UVError, @@ -76,7 +78,7 @@ iswritable(f::FDEvent) = f.writable |(a::FDEvent, b::FDEvent) = FDEvent(getfield(a, :events) | getfield(b, :events)) mutable struct FileMonitor - handle::Ptr{Cvoid} + @atomic handle::Ptr{Cvoid} file::String notify::Base.ThreadSynchronizer events::Int32 @@ -99,12 +101,14 @@ mutable struct FileMonitor end mutable struct FolderMonitor - handle::Ptr{Cvoid} - notify::Channel{Any} # eltype = Union{Pair{String, FileEvent}, IOError} + @atomic handle::Ptr{Cvoid} + # notify::Channel{Any} # eltype = Union{Pair{String, FileEvent}, IOError} + notify::Base.ThreadSynchronizer + channel::Vector{Any} # eltype = Pair{String, FileEvent} FolderMonitor(folder::AbstractString) = FolderMonitor(String(folder)) function FolderMonitor(folder::String) handle = Libc.malloc(_sizeof_uv_fs_event) - this = new(handle, Channel(Inf)) + this = new(handle, Base.ThreadSynchronizer(), []) associate_julia_struct(handle, this) iolock_begin() err = ccall(:uv_fs_event_init, Cint, (Ptr{Cvoid}, Ptr{Cvoid}), eventloop(), handle) @@ -122,7 +126,7 @@ mutable struct FolderMonitor end mutable struct PollingFileWatcher - handle::Ptr{Cvoid} + @atomic handle::Ptr{Cvoid} file::String interval::UInt32 notify::Base.ThreadSynchronizer @@ -147,14 +151,14 @@ mutable struct PollingFileWatcher end mutable struct _FDWatcher - handle::Ptr{Cvoid} + @atomic handle::Ptr{Cvoid} fdnum::Int # this is NOT the file descriptor refcount::Tuple{Int, Int} notify::Base.ThreadSynchronizer events::Int32 active::Tuple{Bool, Bool} - let FDWatchers = Vector{Any}() # XXX: this structure and refcount need thread-safety locks + let FDWatchers = Vector{Any}() # n.b.: this structure and the refcount are protected by the iolock global _FDWatcher, uvfinalize @static if Sys.isunix() _FDWatcher(fd::RawFD, mask::FDEvent) = _FDWatcher(fd, mask.readable, mask.writable) @@ -206,12 +210,12 @@ mutable struct _FDWatcher if t.handle != C_NULL disassociate_julia_struct(t) ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t.handle) - t.handle = C_NULL + @atomic :monotonic t.handle = C_NULL end t.refcount = (0, 0) t.active = (false, false) @static if Sys.isunix() - if FDWatchers[t.fdnum] == t + if FDWatchers[t.fdnum] === t FDWatchers[t.fdnum] = nothing end end @@ -313,19 +317,25 @@ function close(t::FDWatcher) end function uvfinalize(uv::Union{FileMonitor, FolderMonitor, PollingFileWatcher}) - disassociate_julia_struct(uv) - close(uv) + iolock_begin() + if uv.handle != C_NULL + disassociate_julia_struct(uv) # close (and free) without notify + ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), uv.handle) + end + iolock_end() end function close(t::Union{FileMonitor, FolderMonitor, PollingFileWatcher}) + iolock_begin() if t.handle != C_NULL ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), t.handle) end + iolock_end() end function _uv_hook_close(uv::_FDWatcher) # fyi: jl_atexit_hook can cause this to get called too - uv.handle = C_NULL + Libc.free(@atomicswap :monotonic uv.handle = C_NULL) uvfinalize(uv) nothing end @@ -333,8 +343,8 @@ end function _uv_hook_close(uv::PollingFileWatcher) lock(uv.notify) try - uv.handle = C_NULL uv.active = false + Libc.free(@atomicswap :monotonic uv.handle = C_NULL) notify(uv.notify, StatStruct()) finally unlock(uv.notify) @@ -345,8 +355,8 @@ end function _uv_hook_close(uv::FileMonitor) lock(uv.notify) try - uv.handle = C_NULL uv.active = false + Libc.free(@atomicswap :monotonic uv.handle = C_NULL) notify(uv.notify, FileEvent()) finally unlock(uv.notify) @@ -355,8 +365,13 @@ function _uv_hook_close(uv::FileMonitor) end function _uv_hook_close(uv::FolderMonitor) - uv.handle = C_NULL - close(uv.notify) + lock(uv.notify) + try + Libc.free(@atomicswap :monotonic uv.handle = C_NULL) + notify_error(uv.notify, EOFError()) + finally + unlock(uv.notify) + end nothing end @@ -384,11 +399,17 @@ end function uv_fseventscb_folder(handle::Ptr{Cvoid}, filename::Ptr, events::Int32, status::Int32) t = @handle_as handle FolderMonitor - if status != 0 - put!(t.notify, _UVError("FolderMonitor", status)) - else - fname = (filename == C_NULL) ? "" : unsafe_string(convert(Cstring, filename)) - put!(t.notify, fname => FileEvent(events)) + lock(t.notify) + try + if status != 0 + notify_error(t.notify, _UVError("FolderMonitor", status)) + else + fname = (filename == C_NULL) ? "" : unsafe_string(convert(Cstring, filename)) + push!(t.channel, fname => FileEvent(events)) + notify(t.notify) + end + finally + unlock(t.notify) end nothing end @@ -446,7 +467,7 @@ end function start_watching(t::_FDWatcher) iolock_begin() - t.handle == C_NULL && return throw(ArgumentError("FDWatcher is closed")) + t.handle == C_NULL && throw(ArgumentError("FDWatcher is closed")) readable = t.refcount[1] > 0 writable = t.refcount[2] > 0 if t.active[1] != readable || t.active[2] != writable @@ -464,7 +485,7 @@ end function start_watching(t::PollingFileWatcher) iolock_begin() - t.handle == C_NULL && return throw(ArgumentError("PollingFileWatcher is closed")) + t.handle == C_NULL && throw(ArgumentError("PollingFileWatcher is closed")) if !t.active uv_error("PollingFileWatcher (start)", ccall(:uv_fs_poll_start, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, UInt32), @@ -493,7 +514,7 @@ end function start_watching(t::FileMonitor) iolock_begin() - t.handle == C_NULL && return throw(ArgumentError("FileMonitor is closed")) + t.handle == C_NULL && throw(ArgumentError("FileMonitor is closed")) if !t.active uv_error("FileMonitor (start)", ccall(:uv_fs_event_start, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Int32), @@ -647,26 +668,20 @@ function wait(m::FileMonitor) end function wait(m::FolderMonitor) - m.handle == C_NULL && return throw(ArgumentError("FolderMonitor is closed")) - if isready(m.notify) - evt = take!(m.notify) # non-blocking fast-path - else - preserve_handle(m) - evt = try - take!(m.notify) - catch ex - unpreserve_handle(m) - if ex isa InvalidStateException && ex.state === :closed - rethrow(EOFError()) # `wait(::Channel)` throws the wrong exception - end - rethrow() + m.handle == C_NULL && throw(EOFError()) + preserve_handle(m) + lock(m.notify) + evt = try + m.handle == C_NULL && throw(EOFError()) + while isempty(m.channel) + wait(m.notify) end - end - if evt isa Pair{String, FileEvent} - return evt - else - throw(evt) - end + popfirst!(m.channel) + finally + unlock(m.notify) + unpreserve_handle(m) + end + return evt::Pair{String, FileEvent} end @@ -687,6 +702,7 @@ function poll_fd(s::Union{RawFD, Sys.iswindows() ? WindowsRawSocket : Union{}}, mask.timedout && return mask fdw = _FDWatcher(s, mask) local timer + # we need this flag to explicitly track whether we call `close` already, to update the internal refcount correctly timedout = false # TODO: make this atomic try if timeout_s >= 0 @@ -728,9 +744,11 @@ end watch_file(path::AbstractString, timeout_s::Real=-1) Watch file or directory `path` for changes until a change occurs or `timeout_s` seconds have -elapsed. +elapsed. This function does not poll the file system and instead uses platform-specific +functionality to receive notifications from the operating system (e.g. via inotify on Linux). +See the NodeJS documentation linked below for details. -The returned value is an object with boolean fields `changed`, `renamed`, and `timedout`, +The returned value is an object with boolean fields `renamed`, `changed`, and `timedout`, giving the result of watching the file. This behavior of this function varies slightly across platforms. See @@ -757,13 +775,15 @@ watch_file(s::AbstractString, timeout_s::Real=-1) = watch_file(String(s), Float6 watch_folder(path::AbstractString, timeout_s::Real=-1) Watches a file or directory `path` for changes until a change has occurred or `timeout_s` -seconds have elapsed. +seconds have elapsed. This function does not poll the file system and instead uses platform-specific +functionality to receive notifications from the operating system (e.g. via inotify on Linux). +See the NodeJS documentation linked below for details. This will continuing tracking changes for `path` in the background until `unwatch_folder` is called on the same `path`. The returned value is an pair where the first field is the name of the changed file (if available) -and the second field is an object with boolean fields `changed`, `renamed`, and `timedout`, +and the second field is an object with boolean fields `renamed`, `changed`, and `timedout`, giving the event. This behavior of this function varies slightly across platforms. See @@ -774,37 +794,39 @@ function watch_folder(s::String, timeout_s::Real=-1) fm = get!(watched_folders, s) do return FolderMonitor(s) end - if timeout_s >= 0 && !isready(fm.notify) + local timer + if timeout_s >= 0 + @lock fm.notify isempty(fm.channel) || return popfirst!(fm.channel) if timeout_s <= 0.010 # for very small timeouts, we can just sleep for the whole timeout-interval (timeout_s == 0) ? yield() : sleep(timeout_s) - if !isready(fm.notify) - return "" => FileEvent() # timeout - end - # fall-through to a guaranteed non-blocking fast-path call to wait + @lock fm.notify isempty(fm.channel) || return popfirst!(fm.channel) + return "" => FileEvent() # timeout else - # If we may need to be able to cancel via a timeout, - # create a second monitor object just for that purpose. - # We still take the events from the primary stream. - fm2 = FileMonitor(s) timer = Timer(timeout_s) do t - close(fm2) + @lock fm.notify notify(fm.notify) end - try - while isopen(fm.notify) && !isready(fm.notify) - fm2.handle == C_NULL && return "" => FileEvent() # timeout - wait(fm2) + end + end + # inline a copy of `wait` with added support for checking timer + fm.handle == C_NULL && throw(EOFError()) + preserve_handle(fm) + lock(fm.notify) + evt = try + fm.handle == C_NULL && throw(EOFError()) + while isempty(fm.channel) + if @isdefined(timer) + isopen(timer) || return "" => FileEvent() # timeout end - finally - close(fm2) - close(timer) + wait(fm.notify) end - # guaranteed that next call to `wait(fm)` is non-blocking - # since we haven't entered the libuv event loop yet - # or the Base scheduler workqueue since last testing `isready` + popfirst!(fm.channel) + finally + unlock(fm.notify) + unpreserve_handle(fm) + @isdefined(timer) && close(timer) end - end - return wait(fm) + return evt::Pair{String, FileEvent} end """ @@ -862,4 +884,7 @@ function poll_file(s::AbstractString, interval_seconds::Real=5.007, timeout_s::R end end +include("pidfile.jl") +import .Pidfile: mkpidlock + end diff --git a/stdlib/FileWatching/src/pidfile.jl b/stdlib/FileWatching/src/pidfile.jl new file mode 100644 index 0000000000000..8416765a57b97 --- /dev/null +++ b/stdlib/FileWatching/src/pidfile.jl @@ -0,0 +1,317 @@ +module Pidfile + + +export mkpidlock + +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 + +using ..FileWatching: watch_file +using Base.Sys: iswindows + +""" + mkpidlock([f::Function], at::String, [pid::Cint, proc::Process]; kwopts...) + +Create a pidfile lock for the path "at" for the current process +or the process identified by pid or proc. Can take a function to execute once locked, +for usage in `do` blocks, after which the lock will be automatically closed. If the lock fails +and `wait` is false, then an error is thrown. + +The lock will be released by either `close`, a `finalizer`, or shortly after `proc` exits. +Make sure the return value is live through the end of the critical section of +your program, so the `finalizer` does not reclaim it early. + +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 its mtime is older than this. + The file won't be deleted until 25x longer than this if the pid in the file appears that it may be valid. + 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. + By default, this is set to `stale_age/2`, which is the recommended value. + - `wait`: If true, block until we get the lock, if false, raise error if lock fails. +""" +function mkpidlock end + + +# mutable only because we want to add a finalizer +mutable struct LockMonitor + const path::String + const fd::File + const update::Union{Nothing,Timer} + + global function mkpidlock(at::String, pid::Cint; stale_age::Real=0, refresh::Real=stale_age/2, kwopts...) + local lock + atdir, atname = splitdir(at) + isempty(atdir) && (atdir = pwd()) + at = realpath(atdir) * path_separator * atname + fd = open_exclusive(at; stale_age=stale_age, kwopts...) + update = nothing + try + write_pidfile(fd, pid) + if refresh > 0 + # N.b.: to ensure our finalizer works we are careful to capture + # `fd` here instead of `lock`. + update = Timer(t -> isopen(t) && touch(fd), refresh; interval=refresh) + end + lock = new(at, fd, update) + finalizer(close, lock) + catch ex + tryrmopenfile(at) + close(fd) + rethrow(ex) + end + return lock + end +end + +mkpidlock(at::String; kwopts...) = mkpidlock(at, getpid(); kwopts...) +mkpidlock(f::Function, at::String; kwopts...) = mkpidlock(f, at, getpid(); kwopts...) + +function mkpidlock(f::Function, at::String, pid::Cint; kwopts...) + lock = mkpidlock(at, pid; kwopts...) + try + return f() + finally + close(lock) + end +end + +function mkpidlock(at::String, proc::Process; kwopts...) + lock = mkpidlock(at, getpid(proc); kwopts...) + closer = @async begin + wait(proc) + close(lock) + end + isdefined(Base, :errormonitor) && Base.errormonitor(closer) + return lock +end + +""" + Base.touch(::Pidfile.LockMonitor) + +Update the `mtime` on the lock, to indicate it is still fresh. + +See also the `refresh` keyword in the [`mkpidlock`](@ref) constructor. +""" +Base.touch(lock::LockMonitor) = (touch(lock.fd); lock) + +""" + write_pidfile(io, pid) + +Write our pidfile format to an open IO descriptor. +""" +function write_pidfile(io::IO, pid::Cint) + print(io, "$pid $(gethostname())") +end + +""" + parse_pidfile(file::Union{IO, String}) => (pid, hostname, age) + +Attempt to parse our pidfile format, +replaced an element with (0, "", 0.0), respectively, for any read that failed. +""" +function parse_pidfile(io::IO) + fields = split(read(io, String), ' ', limit = 2) + pid = tryparse(Cuint, fields[1]) + pid === nothing && (pid = Cuint(0)) + hostname = (length(fields) == 2) ? fields[2] : "" + when = mtime(io) + age = time() - when + return (pid, hostname, age) +end + +function parse_pidfile(path::String) + try + existing = open(path, JL_O_RDONLY) + try + return parse_pidfile(existing) + finally + close(existing) + end + catch ex + isa(ex, EOFError) || isa(ex, IOError) || rethrow(ex) + return (Cuint(0), "", 0.0) + end +end + +""" + isvalidpid(hostname::String, pid::Cuint) :: Bool + +Attempt to conservatively estimate whether pid is a valid process id. +""" +function isvalidpid(hostname::AbstractString, pid::Cuint) + # can't inspect remote hosts + (hostname == "" || hostname == gethostname()) || return true + # pid < 0 is never valid (must be a parser error or different OS), + # and would have a completely different meaning when passed to kill + !iswindows() && pid > typemax(Cint) && return false + # (similarly for pid 0) + pid == 0 && return false + # see if the process id exists by querying kill without sending a signal + # and checking if it returned ESRCH (no such process) + return ccall(:uv_kill, Cint, (Cuint, Cint), pid, 0) != UV_ESRCH +end + +""" + stale_pidfile(path::String, stale_age::Real) :: Bool + +Helper function for `open_exclusive` for deciding if a pidfile is stale. +""" +function stale_pidfile(path::String, stale_age::Real) + pid, hostname, age = parse_pidfile(path) + age < -stale_age && @warn "filesystem time skew detected" path=path + if age > stale_age + if (age > stale_age * 25) || !isvalidpid(hostname, pid) + return true + end + end + return false +end + +""" + tryopen_exclusive(path::String, mode::Integer = 0o444) :: Union{Void, File} + +Try to create a new file for read-write advisory-exclusive access, +return nothing if it already exists. +""" +function tryopen_exclusive(path::String, mode::Integer = 0o444) + try + return open(path, JL_O_RDWR | JL_O_CREAT | JL_O_EXCL, mode) + catch ex + (isa(ex, IOError) && ex.code == UV_EEXIST) || rethrow(ex) + end + return nothing +end + +""" + open_exclusive(path::String; mode, poll_interval, stale_age) :: File + +Create a new a file for read-write advisory-exclusive access. +If `wait` is `false` then error out if the lock files exist +otherwise block until we get the lock. + +For a description of the keyword arguments, see [`mkpidlock`](@ref). +""" +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 =#) + # 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) + @warn "attempting to remove probably stale pidfile" path=path + tryrmopenfile(path) + end + file = tryopen_exclusive(path, mode) + end + if file === nothing + error("Failed to get pidfile lock for $(repr(path)).") + else + return file + end + end + # fall-back: wait for the lock + + while true + # start the file-watcher prior to checking for the pidfile existence + t = @async try + watch_file(path, poll_interval) + catch ex + isa(ex, IOError) || rethrow(ex) + sleep(poll_interval) # if the watch failed, convert to just doing a sleep + end + # now try again to create it + 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 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 + @warn "attempting to remove probably stale pidfile" path=path + tryrmopenfile(path) + end + end +end + +function _rand_filename(len::Int=4) # modified from Base.Libc + slug = Base.StringVector(len) + chars = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + for i = 1:len + slug[i] = chars[(Libc.rand() % length(chars)) + 1] + end + return String(slug) +end + +function tryrmopenfile(path::String) + # Deleting open file on Windows is a bit hard + # if we want to reuse the name immediately after: + # we need to first rename it, then delete it. + if Sys.iswindows() + try + local rmpath + rmdir, rmname = splitdir(path) + while true + rmpath = string(rmdir, isempty(rmdir) ? "" : path_separator, + "\$", _rand_filename(), rmname, ".deleted") + ispath(rmpath) || break + end + rename(path, rmpath) + path = rmpath + catch ex + isa(ex, IOError) || rethrow(ex) + end + end + return try + rm(path) + true + catch ex + isa(ex, IOError) || rethrow(ex) + ex + end +end + +""" + close(lock::LockMonitor) + +Release a pidfile lock. +""" +function Base.close(lock::LockMonitor) + update = lock.update + update === nothing || close(update) + isopen(lock.fd) || return false + removed = false + path = lock.path + pathstat = try + # Windows sometimes likes to return EACCES here, + # if the path is in the process of being deleted + stat(path) + catch ex + ex isa IOError || rethrow() + removed = ex + nothing + end + if pathstat !== nothing && samefile(stat(lock.fd), pathstat) + # try not to delete someone else's lock + removed = tryrmopenfile(path) + end + close(lock.fd) + havelock = removed === true + havelock || @warn "failed to remove pidfile on close" path=path removed=removed + return havelock +end + +end # module diff --git a/stdlib/FileWatching/test/pidfile.jl b/stdlib/FileWatching/test/pidfile.jl new file mode 100644 index 0000000000000..94621f6af78e3 --- /dev/null +++ b/stdlib/FileWatching/test/pidfile.jl @@ -0,0 +1,372 @@ +using FileWatching.Pidfile + +using Test + +using Base.Filesystem: File +using FileWatching.Pidfile: iswindows, + write_pidfile, parse_pidfile, + isvalidpid, stale_pidfile, + tryopen_exclusive, open_exclusive + +# helper utilities +struct MemoryFile <: Base.AbstractPipe + io::IOBuffer + mtime::Float64 +end +Base.pipe_reader(io::MemoryFile) = io.io +Base.Filesystem.mtime(io::MemoryFile) = io.mtime + +# set the process umask so we can test the behavior of +# open mask without interference from parent's state +# and create a test environment temp directory +umask(new_mask) = ccall((@static iswindows() ? :_umask : :umask), Cint, (Cint,), new_mask) + +# TODO: Use targeted @test_log tests instead of suppressing all logs to hide the expected warnings +Base.CoreLogging.with_logger(Base.CoreLogging.NullLogger()) do + +@testset "Pidfile.jl" begin +old_umask = umask(0o002) +try + mktempdir() do dir + cd(dir) do + +# now start tests definitions: + +@testset "validpid" begin + mypid = getpid() % Cuint + @test isvalidpid(gethostname(), mypid) + @test isvalidpid("", mypid) + @test !isvalidpid("", 0 % Cuint) + @test isvalidpid("NOT" * gethostname(), mypid) + @test isvalidpid("NOT" * gethostname(), 0 % Cuint) + @test isvalidpid("NOT" * gethostname(), -1 % Cuint) + if !iswindows() + @test isvalidpid("", 1 % Cuint) + @test !isvalidpid("", -1 % Cuint) + @test !isvalidpid("", -mypid) + end +end + +@testset "write_pidfile" begin + buf = IOBuffer() + pid, host, age = 0, "", 123 + pid2, host2, age2 = parse_pidfile(MemoryFile(seekstart(buf), time() - age)) + @test pid == pid2 + @test host == host2 + @test age ≈ age2 atol=5 + + host = " host\r\n" + write(buf, "-1 $host") + pid2, host2, age2 = parse_pidfile(MemoryFile(seekstart(buf), time() - age)) + @test pid == pid2 + @test host == host2 + @test age ≈ age2 atol=5 + truncate(seekstart(buf), 0) + + pid, host = getpid(), gethostname() + write_pidfile(buf, pid) + @test read(seekstart(buf), String) == "$pid $host" + pid2, host2, age2 = parse_pidfile(MemoryFile(seekstart(buf), time() - age)) + @test pid == pid2 + @test host == host2 + @test age ≈ age2 atol=5 + truncate(seekstart(buf), 0) + + @testset "parse_pidfile" begin + age = 0 + @test parse_pidfile("nonexist") === (Cuint(0), "", 0.0) + open(io -> write_pidfile(io, pid), "pidfile", "w") + pid2, host2, age2 = parse_pidfile("pidfile") + @test pid == pid2 + @test host == host2 + @test age ≈ age2 atol=10 + rm("pidfile") + end +end + +@assert !ispath("pidfile") +@testset "open_exclusive" begin + f = open_exclusive("pidfile")::File + try + # check that f is open and read-writable + @test isfile("pidfile") + @test filemode("pidfile") & 0o777 == 0o444 + @test filemode(f) & 0o777 == 0o444 + @test filesize(f) == 0 + @test write(f, "a") == 1 + @test filesize(f) == 1 + @test read(seekstart(f), String) == "a" + chmod("pidfile", 0o600) + @test filemode(f) & 0o777 == (iswindows() ? 0o666 : 0o600) + finally + close(f) + end + + # release the pidfile after a short delay + deleted = false + rmtask = @async begin + sleep(3) + rm("pidfile") + deleted = true + end + Base.errormonitor(rmtask) + @test isfile("pidfile") + @test !deleted + + # open the pidfile again (should wait for it to disappear first) + t = @elapsed f2 = open_exclusive(joinpath(dir, "pidfile"))::File + try + @test deleted + @test isfile("pidfile") + @test t > 2 + if t > 6 + println("INFO: watch_file optimization appears to have NOT succeeded") + end + @test filemode(f2) & 0o777 == 0o444 + @test filesize(f2) == 0 + @test write(f2, "bc") == 2 + @test read(seekstart(f2), String) == "bc" + @test filesize(f2) == 2 + finally + close(f2) + end + rm("pidfile") + wait(rmtask) + + # now test with a long delay and other non-default options + f = open_exclusive("pidfile", mode = 0o000)::File + try + @test filemode(f) & 0o777 == (iswindows() ? 0o444 : 0o000) + finally + close(f) + end + deleted = false + rmtask = @async begin + sleep(8) + rm("pidfile") + deleted = true + end + Base.errormonitor(rmtask) + @test isfile("pidfile") + @test !deleted + # open the pidfile again (should wait for it to disappear first) + t = @elapsed f2 = open_exclusive("pidfile", mode = 0o777, poll_interval = 1.0)::File + try + @test deleted + @test isfile("pidfile") + @test filemode(f2) & 0o777 == (iswindows() ? 0o666 : 0o775) + @test write(f2, "def") == 3 + @test read(seekstart(f2), String) == "def" + @test t > 7 + finally + close(f2) + end + rm("pidfile") + wait(rmtask) + + @testset "test for wait == false cases" begin + f = open_exclusive("pidfile", wait=false) + @test isfile("pidfile") + close(f) + rm("pidfile") + + f = open_exclusive("pidfile")::File + deleted = false + rmtask = @async begin + sleep(2) + @test Pidfile.tryrmopenfile("pidfile") + deleted = true + end + Base.errormonitor(rmtask) + + t1 = time() + @test_throws ErrorException open_exclusive("pidfile", wait=false) + @test time()-t1 ≈ 0 atol=1 + + sleep(1) + @test !deleted + + t1 = time() + @test_throws ErrorException open_exclusive("pidfile", wait=false) + @test time()-t1 ≈ 0 atol=1 + + wait(rmtask) + @test deleted + t = @elapsed f2 = open_exclusive("pidfile", wait=false)::File + @test isfile("pidfile") + @test t ≈ 0 atol=1 + close(f) + close(f2) + rm("pidfile") + end +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 + close(f) + 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) + @test t < 2 + t = @elapsed f = open_exclusive("pidfile", poll_interval=3, stale_age=10)::File + close(f) + @test 8 < t < 20 + rm("pidfile") +end + +@testset "open_exclusive: other errors" begin + error = @test_throws(Base.IOError, open_exclusive("nonexist/folder")) + @test error.value.code == Base.UV_ENOENT + + error = @test_throws(Base.IOError, open_exclusive("")) + @test error.value.code == Base.UV_ENOENT +end + +@assert !ispath("pidfile") +@testset "mkpidlock" begin + lockf = mkpidlock("pidfile") + @test lockf.update === nothing + waittask = @async begin + sleep(3) + cd(homedir()) do + return close(lockf) + end + end + Base.errormonitor(waittask) + + # mkpidlock with no waiting + t = @elapsed @test_throws ErrorException mkpidlock("pidfile", wait=false) + @test t ≈ 0 atol=1 + + t = @elapsed lockf1 = mkpidlock(joinpath(dir, "pidfile")) + @test t > 2 + @test istaskdone(waittask) && fetch(waittask) + @test !close(lockf) + finalize(lockf1) + t = @elapsed lockf2 = mkpidlock("pidfile") + @test t < 2 + @test !close(lockf1) + + # test manual breakage of the lock + # is correctly handled + @test Pidfile.tryrmopenfile("pidfile") + t = @elapsed lockf3 = mkpidlock("pidfile") + @test t < 2 + @test isopen(lockf2.fd) + @test !close(lockf2) + @test !isopen(lockf2.fd) + @test isfile("pidfile") + @test close(lockf3) + @test !isfile("pidfile") + + # Just for coverage's sake, run a test with do-block syntax + lock_times = Float64[] + synchronizer = Base.Event() + synchronizer2 = Base.Event() + t_loop = @async begin + for idx in 1:100 + if idx == 1 + wait(synchronizer) + notify(synchronizer2) + end + t = @elapsed mkpidlock("do_block_pidfile") do + # nothing + end + sleep(0.01) + push!(lock_times, t) + end + end + Base.errormonitor(t_loop) + mkpidlock("do_block_pidfile") do + notify(synchronizer) + wait(synchronizer2) + sleep(3) + end + wait(t_loop) + @test lock_times[1] >= 3 + @test minimum(lock_times) < 1 +end + +@assert !ispath("pidfile") +@testset "mkpidlock update" begin + lockf = mkpidlock("pidfile") + @test lockf.update === nothing + new = mtime(lockf.fd) + @test new ≈ time() atol=1 + sleep(1) + @test mtime(lockf.fd) == new + touch(lockf) + old, new = new, mtime(lockf.fd) + @test new != old + @test new ≈ time() atol=1 + close(lockf) + + lockf = mkpidlock("pidfile"; refresh=0.2) + new = mtime(lockf.fd) + @test new ≈ time() atol=1 + for i = 1:10 + sleep(0.5) + old, new = new, mtime(lockf.fd) + @test new != old + @test new ≈ time() atol=1 + end + @test isopen(lockf.update::Timer) + close(lockf) + @test !isopen(lockf.update::Timer) + + lockf = mkpidlock("pidfile"; stale_age=10) + @test lockf.update isa Timer + close(lockf.update) # simulate a finalizer running in an undefined order + close(lockf) +end + +@assert !ispath("pidfile") +@testset "mkpidlock for child" begin + proc = open(`cat`, "w", devnull) + lock = mkpidlock("pidfile", proc) + @test isopen(lock.fd) + @test isfile("pidfile") + close(proc) + @test success(proc) + sleep(1) # give some time for the other task to finish releasing the lock resources + @test !isopen(lock.fd) + @test !isfile("pidfile") + + error = @test_throws Base.IOError mkpidlock("pidfile", proc) + @test error.value.code == Base.UV_ESRCH +end + +@assert !ispath("pidfile-2") +@testset "mkpidlock non-blocking stale lock break" begin + # mkpidlock with no waiting + lockf = mkpidlock("pidfile-2", wait=false) + @test lockf.update === nothing + + sleep(1) + t = @elapsed @test_throws ErrorException mkpidlock("pidfile-2", wait=false, stale_age=1, poll_interval=1, refresh=0) + @test t ≈ 0 atol=1 + + sleep(5) + t = @elapsed (lockf2 = mkpidlock("pidfile-2", wait=false, stale_age=.1, poll_interval=1, refresh=0)) + @test t ≈ 0 atol=1 + close(lockf) + close(lockf2) +end + +end; end # cd(tempdir) +finally + umask(old_umask) +end; end # testset + +end # with_logger diff --git a/stdlib/FileWatching/test/runtests.jl b/stdlib/FileWatching/test/runtests.jl index f302f28295a01..75b17b5f0e511 100644 --- a/stdlib/FileWatching/test/runtests.jl +++ b/stdlib/FileWatching/test/runtests.jl @@ -3,6 +3,8 @@ using Test, FileWatching using Base: uv_error, Experimental +@testset "FileWatching" begin + # This script does the following # Sets up N unix pipes (or WSA sockets) # For the odd pipes, a byte is written to the write end at intervals specified in intvls @@ -13,8 +15,8 @@ using Base: uv_error, Experimental n = 20 intvls = [2, .2, .1, .005, .00001] - pipe_fds = fill((Base.INVALID_OS_HANDLE, Base.INVALID_OS_HANDLE), n) + for i in 1:n if Sys.iswindows() || i > n ÷ 2 uv_error("socketpair", ccall(:uv_socketpair, Cint, (Cint, Cint, Ptr{NTuple{2, Base.OS_HANDLE}}, Cint, Cint), 1, (Sys.iswindows() ? 6 : 0), Ref(pipe_fds, i), 0, 0)) @@ -30,7 +32,9 @@ for i in 1:n if !fd_in_limits && Sys.islinux() run(`ls -la /proc/$(getpid())/fd`) end - @test fd_in_limits + if !Sys.isapple() + @test fd_in_limits + end end function pfd_tst_reads(idx, intvl) @@ -70,7 +74,7 @@ end # Odd numbers trigger reads, even numbers timeout for (i, intvl) in enumerate(intvls) - @Experimental.sync begin + Experimental.@sync begin global ready = 0 global ready_c = Condition() for idx in 1:n @@ -157,8 +161,8 @@ test2_12992() ####################################################################### # This section tests file watchers. # ####################################################################### -const F_GETPATH = Sys.islinux() || Sys.iswindows() || Sys.isapple() # platforms where F_GETPATH is available -const F_PATH = F_GETPATH ? "afile.txt" : "" +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") @@ -181,16 +185,19 @@ function test_init_afile() @test(watch_folder(dir) == (F_PATH => FileWatching.FileEvent(FileWatching.UV_RENAME))) @test close(open(file, "w")) === nothing sleep(3) - let c - c = watch_folder(dir, 0) - if F_GETPATH - @test c.first == F_PATH - @test c.second.changed ⊻ c.second.renamed - @test !c.second.timedout - else # we don't expect to be able to detect file changes in this case - @test c.first == "" - @test !c.second.changed && !c.second.renamed - @test c.second.timedout + if !Sys.isapple() + let c + c = watch_folder(dir, 0) + + if F_GETPATH + @test c.first == F_PATH + @test c.second.changed ⊻ c.second.renamed + @test !c.second.timedout + else # we don't expect to be able to detect file changes in this case + @test c.first == "" + @test !c.second.changed && !c.second.renamed + @test c.second.timedout + end end end @test unwatch_folder(dir) === nothing @@ -322,8 +329,10 @@ function test_dirmonitor_wait2(tval) fname, events = wait(fm) end for i = 1:3 - @test fname == "$F_PATH$i" - @test !events.changed && !events.timedout && events.renamed + @testset let (fname, events) = (fname, events) + @test fname == "$F_PATH$i" + @test !events.changed && !events.timedout && events.renamed + end i == 3 && break fname, events = wait(fm) end @@ -365,60 +374,62 @@ test_monitor_wait_poll() test_monitor_wait_poll() test_watch_file_timeout(0.2) test_watch_file_change(6) -test_dirmonitor_wait2(0.2) -test_dirmonitor_wait2(0.2) -mv(file, file * "~") -mv(file * "~", file) -let changes = [] - while true - let c - Sys.iswindows() && sleep(0.1) - @test @elapsed(c = watch_folder(dir, 0.0)) < 0.5 - push!(changes, c) - (c.second::FileWatching.FileEvent).timedout && break - end - end - if F_GETPATH - @test 12 < length(changes) < 48 - else - @test 5 < length(changes) < 16 - end - @test pop!(changes) == ("" => FileWatching.FileEvent()) - if F_GETPATH - Sys.iswindows() && @test pop!(changes) == (F_PATH => FileWatching.FileEvent(FileWatching.UV_CHANGE)) - p = pop!(changes) - if !Sys.isapple() - @test p == (F_PATH => FileWatching.FileEvent(FileWatching.UV_RENAME)) - end - while changes[end][1] == F_PATH - @test pop!(changes)[2] == FileWatching.FileEvent(FileWatching.UV_RENAME) - end - p = pop!(changes) - if !Sys.isapple() - @test p == (F_PATH * "~" => FileWatching.FileEvent(FileWatching.UV_RENAME)) - end - while changes[end][1] == F_PATH * "~" - @test pop!(changes)[2] == FileWatching.FileEvent(FileWatching.UV_RENAME) +if !Sys.isapple() + test_dirmonitor_wait2(0.2) + test_dirmonitor_wait2(0.2) + + mv(file, file * "~") + mv(file * "~", file) + let changes = [] + while true + let c + Sys.iswindows() && sleep(0.1) + @test @elapsed(c = watch_folder(dir, 0.0)) < 0.5 + push!(changes, c) + (c.second::FileWatching.FileEvent).timedout && break + end end - if changes[end][1] == F_PATH - @test pop!(changes)[2] == FileWatching.FileEvent(FileWatching.UV_RENAME) + if F_GETPATH + @test 12 < length(changes) < 48 + else + @test 5 < length(changes) < 16 end - for j = 1:4 - for i = 3:-1:1 - while changes[end - 1][1] == "$F_PATH$i" - @test let x = pop!(changes)[2]; x.changed ⊻ x.renamed; end - end - p = pop!(changes) - if !Sys.isapple() - @test p == ("$F_PATH$i" => FileWatching.FileEvent(FileWatching.UV_RENAME)) + @test pop!(changes) == ("" => FileWatching.FileEvent()) + if F_GETPATH + Sys.iswindows() && @test pop!(changes) == (F_PATH => FileWatching.FileEvent(FileWatching.UV_CHANGE)) + p = pop!(changes) + if !Sys.isapple() + @test p == (F_PATH => FileWatching.FileEvent(FileWatching.UV_RENAME)) + end + while changes[end][1] == F_PATH + @test pop!(changes)[2] == FileWatching.FileEvent(FileWatching.UV_RENAME) + end + p = pop!(changes) + if !Sys.isapple() + @test p == (F_PATH * "~" => FileWatching.FileEvent(FileWatching.UV_RENAME)) + end + while changes[end][1] == F_PATH * "~" + @test pop!(changes)[2] == FileWatching.FileEvent(FileWatching.UV_RENAME) + end + if changes[end][1] == F_PATH + @test pop!(changes)[2] == FileWatching.FileEvent(FileWatching.UV_RENAME) + end + for j = 1:4 + for i = 3:-1:1 + while changes[end - 1][1] == "$F_PATH$i" + @test let x = pop!(changes)[2]; x.changed ⊻ x.renamed; end + end + p = pop!(changes) + if !Sys.isapple() + @test p == ("$F_PATH$i" => FileWatching.FileEvent(FileWatching.UV_RENAME)) + end end end end + @test all(x -> (isa(x, Pair) && x[1] == F_PATH && (x[2].changed ⊻ x[2].renamed)), changes) || changes end - @test all(x -> (isa(x, Pair) && x[1] == F_PATH && (x[2].changed ⊻ x[2].renamed)), changes) || changes end - @test_throws(Base._UVError("FileMonitor (start)", Base.UV_ENOENT), watch_file("____nonexistent_file", 10)) @test_throws(Base._UVError("FolderMonitor (start)", Base.UV_ENOENT), @@ -431,3 +442,9 @@ unwatch_folder(dir) @test isempty(FileWatching.watched_folders) rm(file) rm(dir) + +@testset "Pidfile" begin + include("pidfile.jl") +end + +end # testset diff --git a/stdlib/GMP_jll/Project.toml b/stdlib/GMP_jll/Project.toml index 0fc262e562da7..510b6f6a49c60 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+1" +version = "6.2.1+2" [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 90daa24b150ed..fde2fc15acf90 100644 --- a/stdlib/GMP_jll/src/GMP_jll.jl +++ b/stdlib/GMP_jll/src/GMP_jll.jl @@ -13,11 +13,11 @@ export libgmp, libgmpxx # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libgmp_handle = C_NULL -libgmp_path = "" -libgmpxx_handle = C_NULL -libgmpxx_path = "" +artifact_dir::String = "" +libgmp_handle::Ptr{Cvoid} = C_NULL +libgmp_path::String = "" +libgmpxx_handle::Ptr{Cvoid} = C_NULL +libgmpxx_path::String = "" if Sys.iswindows() const libgmp = "libgmp-10.dll" diff --git a/stdlib/InteractiveUtils/docs/src/index.md b/stdlib/InteractiveUtils/docs/src/index.md index 9ad4b5a7cea80..5ee8e57adc848 100644 --- a/stdlib/InteractiveUtils/docs/src/index.md +++ b/stdlib/InteractiveUtils/docs/src/index.md @@ -1,6 +1,6 @@ # [Interactive Utilities](@id man-interactive-utils) -This module is intended for interactive work. It is loaded automaticaly in [interactive mode](@ref command-line-options). +This module is intended for interactive work. It 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 6c742660ca73c..b0bf24e0d1379 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -11,7 +11,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, - to_tuple_type, signature_type, format_bytes + signature_type, format_bytes using Markdown @@ -21,7 +21,7 @@ include("macros.jl") include("clipboard.jl") """ - varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported::Bool = false, sortby::Symbol = :name, minsize::Int = 0) + varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported::Bool = false, recursive::Bool = false, sortby::Symbol = :name, minsize::Int = 0) Return a markdown table giving information about exported global variables in a module, optionally restricted to those matching `pattern`. @@ -33,8 +33,11 @@ The memory consumption estimate is an approximate lower bound on the size of the - `recursive` : recursively include objects in sub-modules, observing the same settings in each. - `sortby` : the column to sort results by. Options are `:name` (default), `:size`, and `:summary`. - `minsize` : only includes objects with size at least `minsize` bytes. Defaults to `0`. + +The output of `varinfo` is intended for display purposes only. See also [`names`](@ref) to get an array of symbols defined in +a module, which is suitable for more general manipulations. """ -function varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported::Bool = false, sortby::Symbol = :name, recursive::Bool = false, minsize::Int=0) +function varinfo(m::Module=Base.active_module(), pattern::Regex=r""; all::Bool = false, imported::Bool = false, sortby::Symbol = :name, recursive::Bool = false, minsize::Int=0) sortby in (:name, :size, :summary) || throw(ArgumentError("Unrecognized `sortby` value `:$sortby`. Possible options are `:name`, `:size`, and `:summary`")) rows = Vector{Any}[] workqueue = [(m, ""),] @@ -45,7 +48,7 @@ function varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported continue end value = getfield(m2, v) - isbuiltin = value === Base || value === Main || value === Core + isbuiltin = value === Base || value === Base.active_module() || value === Core if recursive && !isbuiltin && isa(value, Module) && value !== m2 && nameof(value) === v && parentmodule(value) === m2 push!(workqueue, (value, "$prep$v.")) end @@ -60,11 +63,11 @@ function varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported end end end - let (col, rev) = if sortby == :name + let (col, rev) = if sortby === :name 1, false - elseif sortby == :size + elseif sortby === :size 4, true - elseif sortby == :summary + elseif sortby === :summary 3, false else @assert "unreachable" @@ -75,7 +78,7 @@ function varinfo(m::Module=Main, pattern::Regex=r""; all::Bool = false, imported return Markdown.MD(Any[Markdown.Table(map(r->r[1:3], rows), Symbol[:l, :r, :l])]) end -varinfo(pat::Regex; kwargs...) = varinfo(Main, pat, kwargs...) +varinfo(pat::Regex; kwargs...) = varinfo(Base.active_module(), pat; kwargs...) """ versioninfo(io::IO=stdout; verbose::Bool=false) @@ -96,7 +99,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 - if ccall(:jl_is_debugbuild, Cint, ())!=0 + if Base.isdebugbuild() println(io, "DEBUG build") end println(io, "Platform Info:") @@ -141,7 +144,7 @@ function versioninfo(io::IO=stdout; verbose::Bool=false) println(io, " WORD_SIZE: ", Sys.WORD_SIZE) println(io, " LIBM: ",Base.libm_name) println(io, " LLVM: libLLVM-",Base.libllvm_version," (", Sys.JIT, ", ", Sys.CPU_NAME, ")") - println(io, " Threads: ", Threads.nthreads(), " on ", Sys.CPU_THREADS, " virtual cores") + println(io, " Threads: ", Threads.maxthreadid(), " on ", Sys.CPU_THREADS, " virtual cores") function is_nonverbose_env(k::String) return occursin(r"^JULIA_|^DYLD_|^LD_", k) @@ -183,7 +186,7 @@ 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`. """ -function methodswith(t::Type, f::Base.Callable, meths = Method[]; supertypes::Bool=false) +function methodswith(@nospecialize(t::Type), @nospecialize(f::Base.Callable), meths = Method[]; supertypes::Bool=false) for d in methods(f) if any(function (x) let x = rewrap_unionall(x, d.sig) @@ -200,7 +203,7 @@ function methodswith(t::Type, f::Base.Callable, meths = Method[]; supertypes::Bo return meths end -function _methodswith(t::Type, m::Module, supertypes::Bool) +function _methodswith(@nospecialize(t::Type), m::Module, supertypes::Bool) meths = Method[] for nm in names(m) if isdefined(m, nm) @@ -213,9 +216,9 @@ function _methodswith(t::Type, m::Module, supertypes::Bool) return unique(meths) end -methodswith(t::Type, m::Module; supertypes::Bool=false) = _methodswith(t, m, supertypes) +methodswith(@nospecialize(t::Type), m::Module; supertypes::Bool=false) = _methodswith(t, m, supertypes) -function methodswith(t::Type; supertypes::Bool=false) +function methodswith(@nospecialize(t::Type); supertypes::Bool=false) meths = Method[] for mod in Base.loaded_modules_array() append!(meths, _methodswith(t, mod, supertypes)) diff --git a/stdlib/InteractiveUtils/src/clipboard.jl b/stdlib/InteractiveUtils/src/clipboard.jl index 7bc718b91b2bd..a4a5118acf8d7 100644 --- a/stdlib/InteractiveUtils/src/clipboard.jl +++ b/stdlib/InteractiveUtils/src/clipboard.jl @@ -51,7 +51,7 @@ elseif Sys.islinux() || Sys.KERNEL === :FreeBSD _clipboardcmd !== nothing && return _clipboardcmd for cmd in (:xclip, :xsel, :wlclipboard) # wl-clipboard ships wl-copy/paste individually - c = cmd == :wlclipboard ? Symbol("wl-copy") : cmd + c = cmd === :wlclipboard ? Symbol("wl-copy") : cmd success(pipeline(`which $c`, devnull)) && return _clipboardcmd = cmd end pkgs = @static if Sys.KERNEL === :FreeBSD @@ -83,14 +83,14 @@ elseif Sys.iswindows() x_u16 = Base.cwstring(x) pdata = Ptr{UInt16}(C_NULL) function cleanup(cause) - errno = cause == :success ? UInt32(0) : Libc.GetLastError() + errno = cause === :success ? UInt32(0) : Libc.GetLastError() if cause !== :OpenClipboard if cause !== :success && pdata != C_NULL ccall((:GlobalFree, "kernel32"), stdcall, Cint, (Ptr{UInt16},), pdata) end ccall((:CloseClipboard, "user32"), stdcall, Cint, ()) == 0 && Base.windowserror(:CloseClipboard) # this should never fail end - cause == :success || Base.windowserror(cause, errno) + cause === :success || Base.windowserror(cause, errno) nothing end ccall((:OpenClipboard, "user32"), stdcall, Cint, (Ptr{Cvoid},), C_NULL) == 0 && return Base.windowserror(:OpenClipboard) @@ -103,25 +103,25 @@ elseif Sys.iswindows() ccall(:memcpy, Ptr{UInt16}, (Ptr{UInt16}, Ptr{UInt16}, Csize_t), plock, 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) + pset = ccall((:SetClipboardData, "user32"), stdcall, Ptr{UInt16}, (Cuint, Ptr{UInt16}), 13, pdata) # CF_UNICODETEXT pdata != pset && return cleanup(:SetClipboardData) cleanup(:success) end clipboard(x) = clipboard(sprint(print, x)::String) function clipboard() function cleanup(cause) - errno = cause == :success ? UInt32(0) : Libc.GetLastError() + errno = cause === :success ? UInt32(0) : Libc.GetLastError() if cause !== :OpenClipboard ccall((:CloseClipboard, "user32"), stdcall, Cint, ()) == 0 && Base.windowserror(:CloseClipboard) # this should never fail end - if cause !== :success && (cause !== :GetClipboardData || errno != 0) + if cause !== :success && !(cause === :GetClipboardData && (errno == 0x8004006A || errno == 0x800401D3)) # ignore DV_E_CLIPFORMAT and CLIPBRD_E_BAD_DATA from GetClipboardData Base.windowserror(cause, errno) end "" end ccall((:OpenClipboard, "user32"), stdcall, Cint, (Ptr{Cvoid},), C_NULL) == 0 && return Base.windowserror(:OpenClipboard) ccall(:SetLastError, stdcall, Cvoid, (UInt32,), 0) # allow distinguishing if the clipboard simply didn't have text - pdata = ccall((:GetClipboardData, "user32"), stdcall, Ptr{UInt16}, (Cuint,), 13) + pdata = ccall((:GetClipboardData, "user32"), stdcall, Ptr{UInt16}, (Cuint,), 13) # CF_UNICODETEXT pdata == C_NULL && return cleanup(:GetClipboardData) plock = ccall((:GlobalLock, "kernel32"), stdcall, Ptr{UInt16}, (Ptr{UInt16},), pdata) plock == C_NULL && return cleanup(:GlobalLock) @@ -154,7 +154,7 @@ Send a printed form of `x` to the operating system clipboard ("copy"). clipboard(x) """ - clipboard() -> AbstractString + clipboard() -> String Return a string with the contents of the operating system clipboard ("paste"). """ diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 0df2f83c45ed8..29a64343b8370 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -27,19 +27,31 @@ end # displaying type warnings -function warntype_type_printer(io::IO, @nospecialize(ty), used::Bool) - used || return - str = "::$ty" +function warntype_type_printer(io::IO; @nospecialize(type), used::Bool, show_type::Bool=true, _...) + (show_type && used) || return nothing + str = "::$type" if !highlighting[:warntype] print(io, str) - elseif ty isa Union && Base.is_expected_union(ty) + elseif type isa Union && is_expected_union(type) Base.emphasize(io, str, Base.warn_color()) # more mild user notification - elseif ty isa Type && (!Base.isdispatchelem(ty) || ty == Core.Box) + elseif type isa Type && (!Base.isdispatchelem(type) || type == Core.Box) Base.emphasize(io, str) else Base.printstyled(io, str, color=:cyan) # show the "good" type end - nothing + return nothing +end + +# True if one can be pretty certain that the compiler handles this union well, +# i.e. must be small with concrete types. +function is_expected_union(u::Union) + Base.unionlen(u) < 4 || return false + for x in Base.uniontypes(u) + if !Base.isdispatchelem(x) || x == Core.Box + return false + end + end + return true end """ @@ -47,11 +59,14 @@ end Prints lowered and type-inferred ASTs for the methods matching the given generic function and type signature to `io` which defaults to `stdout`. The ASTs are annotated in such a way -as to cause "non-leaf" types to be emphasized (if color is available, displayed in red). -This serves as a warning of potential type instability. Not all non-leaf types are particularly -problematic for performance, so the results need to be used judiciously. -In particular, unions containing either [`missing`](@ref) or [`nothing`](@ref) are displayed in yellow, since -these are often intentional. +as to cause "non-leaf" types which may be problematic for performance to be emphasized +(if color is available, displayed in red). This serves as a warning of potential type instability. + +Not all non-leaf types are particularly problematic for performance, and the performance +characteristics of a particular type is an implementation detail of the compiler. +`code_warntype` will err on the side of coloring types red if they might be a performance +concern, so some types may be colored red even if they do not impact performance. +Small unions of concrete types are usually not a concern, so these are highlighted in yellow. Keyword argument `debuginfo` may be one of `:source` or `:none` (default), to specify the verbosity of code comments. @@ -125,13 +140,13 @@ function code_warntype(io::IO, @nospecialize(f), @nospecialize(t=Base.default_tt end print(io, " ", slotnames[i]) if isa(slottypes, Vector{Any}) - warntype_type_printer(io, slottypes[i], true) + warntype_type_printer(io; type=slottypes[i], used=true) end println(io) end end print(io, "Body") - warntype_type_printer(io, rettype, true) + 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) @@ -144,6 +159,13 @@ code_warntype(@nospecialize(f), @nospecialize(t=Base.default_tt(f)); kwargs...) import Base.CodegenParams +const GENERIC_SIG_WARNING = "; WARNING: This code may not match what actually runs.\n" +const OC_MISMATCH_WARNING = +""" +; WARNING: The pre-inferred opaque closure is not callable with the given arguments +; and will error on dispatch with this signature. +""" + # Printing code representations in IR and assembly function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrapper::Bool, strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol, @@ -153,10 +175,28 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) end + warning = "" # get the MethodInstance for the method match - world = Base.get_world_counter() - match = Base._which(signature_type(f, t), world) - linfo = Core.Compiler.specialize_method(match) + if !isa(f, Core.OpaqueClosure) + world = Base.get_world_counter() + match = Base._which(signature_type(f, t); world) + linfo = Core.Compiler.specialize_method(match) + # TODO: use jl_is_cacheable_sig instead of isdispatchtuple + isdispatchtuple(linfo.specTypes) || (warning = GENERIC_SIG_WARNING) + else + world = UInt64(f.world) + if Core.Compiler.is_source_inferred(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 + linfo = f.source.specializations::Core.MethodInstance + Core.Compiler.hasintersect(typeof(f).parameters[1], t) || (warning = OC_MISMATCH_WARNING) + else + linfo = Core.Compiler.specialize_method(f.source, Tuple{typeof(f.captures), t.parameters...}, Core.svec()) + actual = isdispatchtuple(linfo.specTypes) + isdispatchtuple(linfo.specTypes) || (warning = GENERIC_SIG_WARNING) + end + end # get the code for it if debuginfo === :default debuginfo = :source @@ -175,8 +215,7 @@ function _dump_function(@nospecialize(f), @nospecialize(t), native::Bool, wrappe else str = _dump_function_linfo_llvm(linfo, world, wrapper, strip_ir_metadata, dump_module, optimize, debuginfo, params) end - # TODO: use jl_is_cacheable_sig instead of isdispatchtuple - isdispatchtuple(linfo.specTypes) || (str = "; WARNING: This code may not match what actually runs.\n" * str) + str = warning * str return str end @@ -187,12 +226,18 @@ function _dump_function_linfo_native(linfo::Core.MethodInstance, world::UInt, wr return str end +struct LLVMFDump + tsm::Ptr{Cvoid} # opaque + f::Ptr{Cvoid} # opaque +end + function _dump_function_linfo_native(linfo::Core.MethodInstance, world::UInt, wrapper::Bool, syntax::Symbol, debuginfo::Symbol, binary::Bool, params::CodegenParams) - llvmf = ccall(:jl_get_llvmf_defn, Ptr{Cvoid}, (Any, UInt, Bool, Bool, CodegenParams), linfo, world, wrapper, true, params) - llvmf == C_NULL && error("could not compile the specified method") + llvmf_dump = Ref{LLVMFDump}() + ccall(:jl_get_llvmf_defn, Cvoid, (Ptr{LLVMFDump}, Any, UInt, Bool, Bool, CodegenParams), llvmf_dump, linfo, world, wrapper, true, params) + llvmf_dump[].f == C_NULL && error("could not compile the specified method") str = ccall(:jl_dump_function_asm, Ref{String}, - (Ptr{Cvoid}, Bool, Ptr{UInt8}, Ptr{UInt8}, Bool), - llvmf, false, syntax, debuginfo, binary) + (Ptr{LLVMFDump}, Bool, Ptr{UInt8}, Ptr{UInt8}, Bool), + llvmf_dump, false, syntax, debuginfo, binary) return str end @@ -201,11 +246,12 @@ function _dump_function_linfo_llvm( strip_ir_metadata::Bool, dump_module::Bool, optimize::Bool, debuginfo::Symbol, params::CodegenParams) - llvmf = ccall(:jl_get_llvmf_defn, Ptr{Cvoid}, (Any, UInt, Bool, Bool, CodegenParams), linfo, world, wrapper, optimize, params) - llvmf == C_NULL && error("could not compile the specified method") + llvmf_dump = Ref{LLVMFDump}() + ccall(:jl_get_llvmf_defn, Cvoid, (Ptr{LLVMFDump}, Any, UInt, Bool, Bool, CodegenParams), llvmf_dump, linfo, world, wrapper, optimize, params) + llvmf_dump[].f == C_NULL && error("could not compile the specified method") str = ccall(:jl_dump_function_ir, Ref{String}, - (Ptr{Cvoid}, Bool, Bool, Ptr{UInt8}), - llvmf, strip_ir_metadata, dump_module, debuginfo) + (Ptr{LLVMFDump}, Bool, Bool, Ptr{UInt8}), + llvmf_dump, strip_ir_metadata, dump_module, debuginfo) return str end @@ -222,8 +268,8 @@ Keyword argument `debuginfo` may be one of source (default) or none, to specify """ function code_llvm(io::IO, @nospecialize(f), @nospecialize(types), raw::Bool, dump_module::Bool=false, optimize::Bool=true, debuginfo::Symbol=:default) - d = _dump_function(f, types, false, false, !raw, dump_module, :att, optimize, debuginfo, false) - if highlighting[:llvm] && get(io, :color, false) + d = _dump_function(f, types, false, false, !raw, dump_module, :intel, optimize, debuginfo, false) + if highlighting[:llvm] && get(io, :color, false)::Bool print_llvm(io, d) else print(io, d) @@ -235,12 +281,12 @@ code_llvm(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); raw=false, code_llvm(stdout, f, types; raw, dump_module, optimize, debuginfo) """ - code_native([io=stdout,], f, types; syntax=:att, debuginfo=:default, binary=false, dump_module=true) + code_native([io=stdout,], f, types; syntax=:intel, debuginfo=:default, binary=false, dump_module=true) Prints the native assembly instructions generated for running the method matching the given generic function and type signature to `io`. -* Set assembly syntax by setting `syntax` to `:att` (default) for AT&T syntax or `:intel` for Intel syntax. +* Set assembly syntax by setting `syntax` to `:intel` (default) for intel syntax or `:att` for AT&T syntax. * Specify verbosity of code comments by setting `debuginfo` to `:source` (default) or `:none`. * If `binary` is `true`, also print the binary machine code for each instruction precedented by an abbreviated address. * If `dump_module` is `false`, do not print metadata such as rodata or directives. @@ -248,15 +294,15 @@ generic function and type signature to `io`. See also: [`@code_native`](@ref), [`code_llvm`](@ref), [`code_typed`](@ref) and [`code_lowered`](@ref) """ function code_native(io::IO, @nospecialize(f), @nospecialize(types=Base.default_tt(f)); - dump_module::Bool=true, syntax::Symbol=:att, debuginfo::Symbol=:default, binary::Bool=false) + dump_module::Bool=true, syntax::Symbol=:intel, debuginfo::Symbol=:default, binary::Bool=false) d = _dump_function(f, types, true, false, false, dump_module, syntax, true, debuginfo, binary) - if highlighting[:native] && get(io, :color, false) + if highlighting[:native] && get(io, :color, false)::Bool print_native(io, d) else print(io, d) end end -code_native(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); dump_module::Bool=true, syntax::Symbol=:att, debuginfo::Symbol=:default, binary::Bool=false) = +code_native(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); dump_module::Bool=true, syntax::Symbol=:intel, debuginfo::Symbol=:default, binary::Bool=false) = code_native(stdout, f, types; dump_module, syntax, debuginfo, binary) code_native(::IO, ::Any, ::Symbol) = error("invalid code_native call") # resolve ambiguous call diff --git a/stdlib/InteractiveUtils/src/editless.jl b/stdlib/InteractiveUtils/src/editless.jl index 7a96323c9cdeb..539e9b12f4071 100644 --- a/stdlib/InteractiveUtils/src/editless.jl +++ b/stdlib/InteractiveUtils/src/editless.jl @@ -7,8 +7,8 @@ using Base: shell_split, shell_escape, find_source_file """ EDITOR_CALLBACKS :: Vector{Function} -A vector of editor callback functions, which take as arguments `cmd`, `path` and -`line` and which is then expected to either open an editor and return `true` to +A vector of editor callback functions, which take as arguments `cmd`, `path`, `line` +and `column` and which is then expected to either open an editor and return `true` to indicate that it has handled the request, or return `false` to decline the editing request. """ @@ -21,19 +21,20 @@ Define a new editor matching `pattern` that can be used to open a file (possibly at a given line number) using `fn`. The `fn` argument is a function that determines how to open a file with the -given editor. It should take three arguments, as follows: +given editor. It should take four arguments, as follows: * `cmd` - a base command object for the editor * `path` - the path to the source file to open * `line` - the line number to open the editor at +* `column` - the column number to open the editor at -Editors which cannot open to a specific line with a command may ignore the -`line` argument. The `fn` callback must return either an appropriate `Cmd` -object to open a file or `nothing` to indicate that they cannot edit this file. -Use `nothing` to indicate that this editor is not appropriate for the current -environment and another editor should be attempted. It is possible to add more -general editing hooks that need not spawn external commands by pushing a -callback directly to the vector `EDITOR_CALLBACKS`. +Editors which cannot open to a specific line with a command or a specific column +may ignore the `line` and/or `column` argument. The `fn` callback must return +either an appropriate `Cmd` object to open a file or `nothing` to indicate that +they cannot edit this file. Use `nothing` to indicate that this editor is not +appropriate for the current environment and another editor should be attempted. +It is possible to add more general editing hooks that need not spawn +external commands by pushing a callback directly to the vector `EDITOR_CALLBACKS`. The `pattern` argument is a string, regular expression, or an array of strings and regular expressions. For the `fn` to be called, one of the patterns must @@ -52,7 +53,7 @@ set `wait=true` and julia will wait for the editor to close before resuming. If one of the editor environment variables is set, but no editor entry matches it, the default editor entry is invoked: - (cmd, path, line) -> `\$cmd \$path` + (cmd, path, line, column) -> `\$cmd \$path` Note that many editors are already defined. All of the following commands should already work: @@ -64,6 +65,7 @@ already work: - nano - micro - kak +- helix - textmate - mate - kate @@ -88,9 +90,14 @@ The following defines the usage of terminal-based `emacs`: `define_editor` was introduced in Julia 1.4. """ function define_editor(fn::Function, pattern; wait::Bool=false) - callback = function (cmd::Cmd, path::AbstractString, line::Integer) + callback = function (cmd::Cmd, path::AbstractString, line::Integer, column::Integer) editor_matches(pattern, cmd) || return false - editor = fn(cmd, path, line) + editor = if !applicable(fn, cmd, path, line, column) + # Be backwards compatible with editors that did not define the newly added column argument + fn(cmd, path, line) + else + fn(cmd, path, line, column) + end if editor isa Cmd if wait run(editor) # blocks while editor runs @@ -113,35 +120,59 @@ editor_matches(ps::AbstractArray, cmd::Cmd) = any(editor_matches(p, cmd) for p i function define_default_editors() # fallback: just call the editor with the path as argument - define_editor(r".*") do cmd, path, line + define_editor(r".*") do cmd, path, line, column `$cmd $path` end - define_editor(Any[r"\bemacs", "gedit", r"\bgvim"]) do cmd, path, line - `$cmd +$line $path` + # vim family + for (editors, wait) in [ + [["vim", "vi", "nvim", "mvim"], true], + [[r"\bgvim"], false], + ] + define_editor(editors; wait) do cmd, path, line, column + cmd = line == 0 ? `$cmd $path` : + column == 0 ? `$cmd +$line $path` : + `$cmd "+normal $(line)G$(column)|" $path` + end + end + define_editor("nano"; wait=true) do cmd, path, line, column + cmd = `$cmd +$line,$column $path` end - # Must check that emacs not running in -t/-nw before regex match for general emacs - define_editor(Any[ - "vim", "vi", "nvim", "mvim", "nano", "micro", "kak", - r"\bemacs\b.*\s(-nw|--no-window-system)\b", - r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b", - ], wait=true) do cmd, path, line + # emacs (must check that emacs not running in -t/-nw + # before regex match for general emacs) + for (editors, wait) in [ + [[r"\bemacs"], false], + [[r"\bemacs\b.*\s(-nw|--no-window-system)\b", + r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b"], true], + ] + define_editor(editors; wait) do cmd, path, line, column + `$cmd +$line:$column $path` + end + end + # other editors + define_editor("gedit") do cmd, path, line, column + `$cmd +$line:$column $path` + end + define_editor(["micro", "kak"]; wait=true) do cmd, path, line, column `$cmd +$line $path` end - define_editor(["textmate", "mate", "kate"]) do cmd, path, line + define_editor(["hx", "helix"]; wait=true) do cmd, path, line, column + `$cmd $path:$line:$column` + end + define_editor(["textmate", "mate", "kate"]) do cmd, path, line, column `$cmd $path -l $line` end - define_editor(Any[r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line + define_editor([r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line, column `$cmd $path:$line` end - define_editor(["code", "code-insiders"]) do cmd, path, line - `$cmd -g $path:$line` + define_editor(["code", "code-insiders"]) do cmd, path, line, column + `$cmd -g $path:$line:$column` end - define_editor(r"\bnotepad++") do cmd, path, line + define_editor(r"\bnotepad++") do cmd, path, line, column `$cmd $path -n$line` end if Sys.iswindows() - define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line - `$cmd -g $path:$line` + define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line, column + `$cmd -g $path:$line:$column` end callback = function (cmd::Cmd, path::AbstractString, line::Integer) cmd == `open` || return false @@ -157,7 +188,7 @@ function define_default_editors() end pushfirst!(EDITOR_CALLBACKS, callback) elseif Sys.isapple() - define_editor("open") do cmd, path, line + define_editor("open") do cmd, path, line, column `open -t $path` end end @@ -186,7 +217,7 @@ function editor() end """ - edit(path::AbstractString, line::Integer=0) + edit(path::AbstractString, line::Integer=0, column::Integer=0) 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 @@ -194,7 +225,7 @@ by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable. See also [`define_editor`](@ref). """ -function edit(path::AbstractString, line::Integer=0) +function edit(path::AbstractString, line::Integer=0, column::Integer=0) path isa String || (path = convert(String, path)) if endswith(path, ".jl") p = find_source_file(path) @@ -202,7 +233,11 @@ function edit(path::AbstractString, line::Integer=0) end cmd = editor() for callback in EDITOR_CALLBACKS - callback(cmd, path, line) && return + if !applicable(callback, cmd, path, line, column) + callback(cmd, path, line) && return + else + callback(cmd, path, line, column) && return + end end # shouldn't happen unless someone has removed fallback entry error("no editor found") diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index 623873a3484b5..53242a422140b 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -2,7 +2,7 @@ # macro wrappers for various reflection functions -import Base: typesof, insert! +import Base: typesof, insert!, replace_ref_begin_end!, infer_effects separate_kwargs(args...; kwargs...) = (args, values(kwargs)) @@ -24,7 +24,8 @@ function recursive_dotcalls!(ex, args, i=1) end end (start, branches) = ex.head === :. ? (1, ex.args[2].args) : (2, ex.args) - for j in start:length(branches) + length_branches = length(branches)::Int + for j in start:length_branches branch, i = recursive_dotcalls!(branches[j], args, i) branches[j] = branch end @@ -32,6 +33,9 @@ function recursive_dotcalls!(ex, args, i=1) end function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) + if Meta.isexpr(ex0, :ref) + ex0 = replace_ref_begin_end!(ex0) + end if isa(ex0, Expr) if ex0.head === :do && Meta.isexpr(get(ex0.args, 1, nothing), :call) if length(ex0.args) != 2 @@ -39,12 +43,12 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) end i = findlast(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args[1].args) args = copy(ex0.args[1].args) - insert!(args, (isnothing(i) ? 2 : i+1), ex0.args[2]) + insert!(args, (isnothing(i) ? 2 : 1+i::Int), ex0.args[2]) ex0 = Expr(:call, args...) end if ex0.head === :. || (ex0.head === :call && ex0.args[1] !== :.. && string(ex0.args[1])[1] == '.') codemacro = startswith(string(fcn), "code_") - if codemacro && ex0.args[2] isa Expr + if codemacro && (ex0.head === :call || ex0.args[2] isa Expr) # Manually wrap a dot call in a function args = Any[] ex, i = recursive_dotcalls!(copy(ex0), args) @@ -53,7 +57,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) dotfuncdef = Expr(:local, Expr(:(=), Expr(:call, dotfuncname, xargs...), ex)) return quote $(esc(dotfuncdef)) - local args = typesof($(map(esc, args)...)) + local args = $typesof($(map(esc, args)...)) $(fcn)($(esc(dotfuncname)), args; $(kws...)) end elseif !codemacro @@ -77,7 +81,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) :(error("expression is not a function call")) end) else - local args = typesof($(map(esc, ex0.args)...)) + local args = $typesof($(map(esc, ex0.args)...)) $(fcn)(Base.getproperty, args) end end @@ -93,7 +97,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[]) return quote local arg1 = $(esc(ex0.args[1])) local args, kwargs = $separate_kwargs($(map(esc, ex0.args[2:end])...)) - $(fcn)(Core.kwfunc(arg1), + $(fcn)(Core.kwcall, Tuple{typeof(kwargs), Core.Typeof(arg1), map(Core.Typeof, args)...}; $(kws...)) end @@ -208,7 +212,7 @@ macro which(ex0::Symbol) return :(which($__module__, $ex0)) end -for fname in [:code_warntype, :code_llvm, :code_native] +for fname in [:code_warntype, :code_llvm, :code_native, :infer_effects] @eval begin macro ($fname)(ex0...) gen_call_with_extracted_types_and_kwargs(__module__, $(Expr(:quote, fname)), ex0) @@ -248,7 +252,7 @@ end Applied to a function or macro call, it evaluates the arguments to the specified call, and returns a tuple `(filename,line)` giving the location for the method that would be called for those arguments. -It calls out to the `functionloc` function. +It calls out to the [`functionloc`](@ref) function. """ :@functionloc @@ -267,7 +271,7 @@ See also: [`@less`](@ref), [`@edit`](@ref). """ @less -Evaluates the arguments to the function or macro call, determines their types, and calls the `less` +Evaluates the arguments to the function or macro call, determines their types, and calls the [`less`](@ref) function on the resulting expression. See also: [`@edit`](@ref), [`@which`](@ref), [`@code_lowered`](@ref). @@ -277,7 +281,7 @@ See also: [`@edit`](@ref), [`@which`](@ref), [`@code_lowered`](@ref). """ @edit -Evaluates the arguments to the function or macro call, determines their types, and calls the `edit` +Evaluates the arguments to the function or macro call, determines their types, and calls the [`edit`](@ref) function on the resulting expression. See also: [`@less`](@ref), [`@which`](@ref). @@ -341,7 +345,7 @@ by putting it before the function call, like this: @code_native syntax=:intel debuginfo=:default binary=true dump_module=false f(x) -* Set assembly syntax by setting `syntax` to `:att` (default) for AT&T syntax or `:intel` for Intel syntax. +* Set assembly syntax by setting `syntax` to `:intel` (default) for Intel syntax or `:att` for AT&T syntax. * Specify verbosity of code comments by setting `debuginfo` to `:source` (default) or `:none`. * If `binary` is `true`, also print the binary machine code for each instruction precedented by an abbreviated address. * If `dump_module` is `false`, do not print metadata such as rodata or directives. @@ -354,31 +358,37 @@ See also: [`code_native`](@ref), [`@code_llvm`](@ref), [`@code_typed`](@ref) and @time_imports A macro to execute an expression and produce a report of any time spent importing packages and their -dependencies. +dependencies. Any compilation time will be reported as a percentage, and how much of which was recompilation, if any. + +One line is printed per package or package extension. The duration shown is the time to import that package itself, not including the time to load any of its dependencies. -If a package's dependencies have already been imported either globally or by another dependency they will -not appear under that package and the package will accurately report a faster load time than if it were to -be loaded in isolation. +On Julia 1.9+ [package extensions](@ref man-extensions) will show as Parent → Extension. + +!!! note + During the load process a package sequentially imports all of its dependencies, not just its direct dependencies. ```julia-repl julia> @time_imports using CSV - 3.5 ms ┌ IteratorInterfaceExtensions - 27.4 ms ┌ TableTraits - 614.0 ms ┌ SentinelArrays - 138.6 ms ┌ Parsers - 2.7 ms ┌ DataValueInterfaces - 3.4 ms ┌ DataAPI - 59.0 ms ┌ WeakRefStrings - 35.4 ms ┌ Tables - 49.5 ms ┌ PooledArrays - 972.1 ms CSV + 50.7 ms Parsers 17.52% compilation time + 0.2 ms DataValueInterfaces + 1.6 ms DataAPI + 0.1 ms IteratorInterfaceExtensions + 0.1 ms TableTraits + 17.5 ms Tables + 26.8 ms PooledArrays + 193.7 ms SentinelArrays 75.12% compilation time + 8.6 ms InlineStrings + 20.3 ms WeakRefStrings + 2.0 ms TranscodingStreams + 1.4 ms Zlib_jll + 1.8 ms CodecZlib + 0.8 ms Compat + 13.1 ms FilePathsBase 28.39% compilation time + 1681.2 ms CSV 92.40% compilation time ``` -!!! note - During the load process a package sequentially imports where necessary all of its dependencies, not just - its direct dependencies. That is also true for the dependencies themselves so nested importing will likely - occur, but not always. Therefore the nesting shown in this output report is not equivalent to the dependency - tree, but does indicate where import time has accumulated. +!!! compat "Julia 1.8" + This macro requires at least Julia 1.8 """ :@time_imports diff --git a/stdlib/InteractiveUtils/test/highlighting.jl b/stdlib/InteractiveUtils/test/highlighting.jl index 1ab7dc4292ced..bac52e2945b5e 100644 --- a/stdlib/InteractiveUtils/test/highlighting.jl +++ b/stdlib/InteractiveUtils/test/highlighting.jl @@ -9,7 +9,7 @@ myzeros(::Type{T}, ::Type{S}, ::Type{R}, dims::Tuple{Vararg{Integer, N}}, dims2: Tuple{Type{<:Integer}, Type{>:String}, Type{T} where Signed<:T<:Real, Tuple{Vararg{Int}}, NTuple{4,Int}}) seekstart(io) @test startswith(readline(io), "MethodInstance for ") - @test startswith(readline(io), " from myzeros(::Type{T}, ::") + @test occursin(r"^ from myzeros\(::.*Type.*{T}, ::", readline(io)) @test occursin(r"^Static Parameters$", readline(io)) @test occursin(r"^ T <: .*Integer", readline(io)) @test occursin(r"^ .*Signed.* <: R <: .*Real", readline(io)) @@ -159,10 +159,10 @@ const XU = B * "}" * XB @testset "attributes" begin @test hilight_llvm( - """attributes #1 = { uwtable "frame-pointer"="all" "thunk" }""") == + """attributes #1 = { uwtable "frame-pointer"="all" }""") == "$(K)attributes$(XK) $(D)#1$(XD) $EQU " * "$U $(K)uwtable$(XK) $(V)\"frame-pointer\"$(XV)$EQU" * - "$(V)\"all\"$(XV) $(V)\"thunk\"$(XV) $XU\n" + "$(V)\"all\"$(XV) $XU\n" end @testset "terminator" begin diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index 05e3a744644e1..5f90491fd8151 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -51,6 +51,23 @@ tag = "UNION" @test warntype_hastag(pos_unstable, Tuple{Float64}, tag) @test !warntype_hastag(pos_stable, Tuple{Float64}, tag) +for u in Any[ + Union{Int, UInt}, + Union{Nothing, Vector{Tuple{String, Tuple{Char, Char}}}}, + Union{Char, UInt8, UInt}, + Union{Tuple{Int, Int}, Tuple{Char, Int}, Nothing}, + Union{Missing, Nothing} +] + @test InteractiveUtils.is_expected_union(u) +end + +for u in Any[ + Union{Nothing, Tuple{Vararg{Char}}}, + Union{Missing, Array}, + Union{Int, Tuple{Any, Int}} +] + @test !InteractiveUtils.is_expected_union(u) +end mutable struct Stable{T,N} A::Array{T,N} end @@ -245,7 +262,7 @@ const curmod_str = curmod === Main ? "Main" : join(curmod_name, ".") @test_throws ErrorException("\"this_is_not_defined\" is not defined in module $curmod_str") @which this_is_not_defined # issue #13264 -@test (@which vcat(1...)).name == :vcat +@test (@which vcat(1...)).name === :vcat # PR #28122, issue #25474 @test (@which [1][1]).name === :getindex @@ -314,7 +331,7 @@ end # manually generate a broken function, which will break codegen # and make sure Julia doesn't crash -@eval @noinline @Base.constprop :none f_broken_code() = 0 +@eval @noinline Base.@constprop :none f_broken_code() = 0 let m = which(f_broken_code, ()) let src = Base.uncompressed_ast(m) src.code = Any[ @@ -373,7 +390,7 @@ struct A14637 x end a14637 = A14637(0) -@test (@which a14637.x).name == :getproperty +@test (@which a14637.x).name === :getproperty @test (@functionloc a14637.x)[2] isa Integer # Issue #28615 @@ -383,6 +400,13 @@ a14637 = A14637(0) @test (@code_typed max.(Ref(true).x))[2] == Bool @test !isempty(@code_typed optimize=false max.(Ref.([5, 6])...)) +# Issue # 45889 +@test !isempty(@code_typed 3 .+ 6) +@test !isempty(@code_typed 3 .+ 6 .+ 7) +@test !isempty(@code_typed optimize=false (.- [3,4])) +@test !isempty(@code_typed optimize=false (6 .- [3,4])) +@test !isempty(@code_typed optimize=false (.- 0.5)) + # Issue #36261 @test (@code_typed max.(1 .+ 3, 5 - 7))[2] == Int f36261(x,y) = 3x + 4y @@ -432,6 +456,8 @@ end # module ReflectionTest @test_throws ArgumentError("argument is not a generic function") code_llvm(===, Tuple{Int, Int}) @test_throws ArgumentError("argument is not a generic function") code_native(===, Tuple{Int, Int}) +@test_throws ErrorException("argument tuple type must contain only types") code_native(sum, (Int64,1)) +@test_throws ErrorException("expected tuple type") code_native(sum, Vector{Int64}) # Issue #18883, code_llvm/code_native for generated functions @generated f18883() = nothing @@ -579,7 +605,7 @@ file, ln = functionloc(versioninfo, Tuple{}) @test e isa MethodError m = @which versioninfo() s = sprint(showerror, e) - m = match(Regex("at (.*?):$(m.line)"), s) + m = match(Regex("@ .+ (.*?):$(m.line)"), s) @test isfile(expanduser(m.captures[1])) g() = x @@ -608,7 +634,7 @@ end export B41010 ms = methodswith(A41010, @__MODULE__) |> collect - @test ms[1].name == :B41010 + @test ms[1].name === :B41010 end # macro options should accept both literals and variables @@ -641,7 +667,7 @@ end buf = read(fname) rm(fname) - @test occursin("ms Foo3242\n", String(buf)) + @test occursin("ms Foo3242", String(buf)) finally filter!((≠)(dir), LOAD_PATH) @@ -670,3 +696,28 @@ let # `default_tt` should work with any function with one method sin(a) end); true) end + +@testset "code_llvm on opaque_closure" begin + let ci = code_typed(+, (Int, Int))[1][1] + ir = Core.Compiler.inflate_ir(ci) + oc = Core.OpaqueClosure(ir) + @test (code_llvm(devnull, oc, Tuple{Int, Int}); true) + let io = IOBuffer() + code_llvm(io, oc, Tuple{}) + @test occursin(InteractiveUtils.OC_MISMATCH_WARNING, String(take!(io))) + end + end +end + +@testset "begin/end in gen_call_with_extracted_types users" begin + mktemp() do f, io + redirect_stdout(io) do + a = [1,2] + @test (@code_typed a[1:end]).second == Vector{Int} + @test (@code_llvm a[begin:2]) === nothing + @test (@code_native a[begin:end]) === nothing + end + end +end + +@test Base.infer_effects(sin, (Int,)) == InteractiveUtils.@infer_effects sin(42) diff --git a/stdlib/LLD_jll/Project.toml b/stdlib/LLD_jll/Project.toml new file mode 100644 index 0000000000000..90d867ca0f7da --- /dev/null +++ b/stdlib/LLD_jll/Project.toml @@ -0,0 +1,19 @@ +name = "LLD_jll" +uuid = "d55e3150-da41-5e91-b323-ecfd1eec6109" +version = "15.0.7+5" + +[deps] +Zlib_jll = "83775a58-1f1d-513f-b197-d71354ab007a" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +libLLVM_jll = "8f36deef-c2a5-5394-99ed-8e07531fb29a" +Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[compat] +julia = "1.9" +libLLVM_jll = "15.0.7" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/stdlib/LLD_jll/src/LLD_jll.jl b/stdlib/LLD_jll/src/LLD_jll.jl new file mode 100644 index 0000000000000..55ccec9cc4005 --- /dev/null +++ b/stdlib/LLD_jll/src/LLD_jll.jl @@ -0,0 +1,107 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +## dummy stub for https://github.com/JuliaBinaryWrappers/LLD_jll.jl + +baremodule LLD_jll +using Base, Libdl +Base.Experimental.@compiler_options compile=min optimize=0 infer=false + +const PATH_list = String[] +const LIBPATH_list = String[] + +export lld + +# These get calculated in __init__() +const PATH = Ref("") +const LIBPATH = Ref("") +artifact_dir::String = "" +lld_path::String = "" +if Sys.iswindows() + const lld_exe = "lld.exe" +else + const lld_exe = "lld" +end + +if Sys.iswindows() + const LIBPATH_env = "PATH" + const LIBPATH_default = "" + const pathsep = ';' +elseif Sys.isapple() + const LIBPATH_env = "DYLD_FALLBACK_LIBRARY_PATH" + const LIBPATH_default = "~/lib:/usr/local/lib:/lib:/usr/lib" + const pathsep = ':' +else + const LIBPATH_env = "LD_LIBRARY_PATH" + const LIBPATH_default = "" + const pathsep = ':' +end + +function adjust_ENV!(env::Dict, PATH::String, LIBPATH::String, adjust_PATH::Bool, adjust_LIBPATH::Bool) + if adjust_LIBPATH + LIBPATH_base = get(env, LIBPATH_env, expanduser(LIBPATH_default)) + if !isempty(LIBPATH_base) + env[LIBPATH_env] = string(LIBPATH, pathsep, LIBPATH_base) + else + env[LIBPATH_env] = LIBPATH + end + end + if adjust_PATH && (LIBPATH_env != "PATH" || !adjust_LIBPATH) + if adjust_PATH + if !isempty(get(env, "PATH", "")) + env["PATH"] = string(PATH, pathsep, env["PATH"]) + else + env["PATH"] = PATH + end + end + end + return env +end + +function lld(f::Function; adjust_PATH::Bool = true, adjust_LIBPATH::Bool = true) + env = adjust_ENV!(copy(ENV), PATH[], LIBPATH[], adjust_PATH, adjust_LIBPATH) + withenv(env...) do + return f(lld_path) + end +end +function lld(; adjust_PATH::Bool = true, adjust_LIBPATH::Bool = true) + env = adjust_ENV!(copy(ENV), PATH[], LIBPATH[], adjust_PATH, adjust_LIBPATH) + return Cmd(Cmd([lld_path]); env) +end + +function init_lld_path() + # Prefer our own bundled lld, but if we don't have one, pick it up off of the PATH + # If this is an in-tree build, `lld` will live in `tools`. Otherwise, it'll be in `private_libexecdir` + for bundled_lld_path in (joinpath(Sys.BINDIR, Base.PRIVATE_LIBEXECDIR, lld_exe), + joinpath(Sys.BINDIR, "..", "tools", lld_exe), + joinpath(Sys.BINDIR, lld_exe)) + if isfile(bundled_lld_path) + global lld_path = abspath(bundled_lld_path) + return + end + end + global lld_path = something(Sys.which(lld_exe), lld_exe) +end + +function __init__() + global artifact_dir = dirname(Sys.BINDIR) + init_lld_path() + PATH[] = dirname(lld_path) + push!(PATH_list, PATH[]) + if Sys.iswindows() + # On windows, the dynamic libraries (.dll) are in Sys.BINDIR ("usr\\bin") + append!(LIBPATH_list, [joinpath(Sys.BINDIR, Base.LIBDIR, "julia"), Sys.BINDIR]) + else + append!(LIBPATH_list, [joinpath(Sys.BINDIR, Base.LIBDIR, "julia"), joinpath(Sys.BINDIR, Base.LIBDIR)]) + end + LIBPATH[] = join(LIBPATH_list, pathsep) +end + +# JLLWrappers API compatibility shims. Note that not all of these will really make sense. +# For instance, `find_artifact_dir()` won't actually be the artifact directory, because +# there isn't one. It instead returns the overall Julia prefix. +is_available() = true +find_artifact_dir() = artifact_dir +dev_jll() = error("stdlib JLLs cannot be dev'ed") +best_wrapper = nothing + +end # module libLLD_jll diff --git a/stdlib/LLD_jll/test/runtests.jl b/stdlib/LLD_jll/test/runtests.jl new file mode 100644 index 0000000000000..f8eccfe939dce --- /dev/null +++ b/stdlib/LLD_jll/test/runtests.jl @@ -0,0 +1,9 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test, Libdl, LLD_jll + +@testset "LLD_jll" begin + @test isfile(LLD_jll.lld_path) + flavor = Sys.isapple() ? "darwin" : (Sys.iswindows() ? "link" : "gnu") + @test success(`$(LLD_jll.lld()) -flavor $flavor --version`) +end diff --git a/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl b/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl index 2196323ad35aa..5c4026291a673 100644 --- a/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl +++ b/stdlib/LLVMLibUnwind_jll/src/LLVMLibUnwind_jll.jl @@ -14,9 +14,9 @@ export llvmlibunwind # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -llvmlibunwind_handle = C_NULL -llvmlibunwind_path = "" +artifact_dir::String = "" +llvmlibunwind_handle::Ptr{Cvoid} = C_NULL +llvmlibunwind_path::String = "" const llvmlibunwind = "libunwind" diff --git a/stdlib/LazyArtifacts/test/Artifacts.toml b/stdlib/LazyArtifacts/test/Artifacts.toml deleted file mode 120000 index 1b01a83fcf079..0000000000000 --- a/stdlib/LazyArtifacts/test/Artifacts.toml +++ /dev/null @@ -1 +0,0 @@ -../../Artifacts/test/Artifacts.toml \ No newline at end of file diff --git a/stdlib/LazyArtifacts/test/Artifacts.toml b/stdlib/LazyArtifacts/test/Artifacts.toml new file mode 100644 index 0000000000000..4b715b74c128b --- /dev/null +++ b/stdlib/LazyArtifacts/test/Artifacts.toml @@ -0,0 +1,155 @@ +[[HelloWorldC]] +arch = "aarch64" +git-tree-sha1 = "95fce80ec703eeb5f4270fef6821b38d51387499" +os = "macos" + + [[HelloWorldC.download]] + sha256 = "23f45918421881de8e9d2d471c70f6b99c26edd1dacd7803d2583ba93c8bbb28" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.aarch64-apple-darwin.tar.gz" +[[HelloWorldC]] +arch = "aarch64" +git-tree-sha1 = "1ccbaad776766366943fd5a66a8cbc9877ee8df9" +libc = "glibc" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "82bca07ff25a75875936116ca977285160a2afcc4f58dd160c7b1600f55da655" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.aarch64-linux-gnu.tar.gz" +[[HelloWorldC]] +arch = "aarch64" +git-tree-sha1 = "dc43ab874611cfc26641741c31b8230276d7d664" +libc = "musl" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "36b7c554f1cb04d5282b991c66a10b2100085ac8deb2156bf52b4f7c4e406c04" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.aarch64-linux-musl.tar.gz" +[[HelloWorldC]] +arch = "armv6l" +call_abi = "eabihf" +git-tree-sha1 = "b7128521583d02d2dbe9c8de6fe156b79df781d9" +libc = "glibc" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "5e094b9c6e4c6a77ecc8dfc2b841ac1f2157f6a81f4c47f1e0d3e9a04eec7945" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.armv6l-linux-gnueabihf.tar.gz" +[[HelloWorldC]] +arch = "armv6l" +call_abi = "eabihf" +git-tree-sha1 = "edb3893a154519d6786234f5c83994c34e11feed" +libc = "musl" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "0a2203f061ba2ef7ce4c452ec7874be3acc6db1efac8091f85d113c3404e6bb6" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.armv6l-linux-musleabihf.tar.gz" +[[HelloWorldC]] +arch = "armv7l" +call_abi = "eabihf" +git-tree-sha1 = "5a8288c8a30578c0d0f24a9cded29579517ce7a8" +libc = "glibc" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "a4392a4c8f834c97f9d8822ddfb1813d8674fa602eeaf04d6359c0a9e98478ec" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.armv7l-linux-gnueabihf.tar.gz" +[[HelloWorldC]] +arch = "armv7l" +call_abi = "eabihf" +git-tree-sha1 = "169c261b321c4dc95894cdd2db9d0d0caa84677f" +libc = "musl" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "ed1aacbf197a6c78988725a39defad130ed31a2258f8e7846f73b459821f21d3" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.armv7l-linux-musleabihf.tar.gz" +[[HelloWorldC]] +arch = "i686" +git-tree-sha1 = "fd35f9155dc424602d01fbf983eb76be3217a28f" +libc = "glibc" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "048fcff5ff47a3cc1e84a2688935fcd658ad1c7e7c52c0e81fe88ce6c3697aba" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.i686-linux-gnu.tar.gz" +[[HelloWorldC]] +arch = "i686" +git-tree-sha1 = "8db14df0f1d2a3ed9c6a7b053a590ca6527eb95e" +libc = "musl" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "d521b4420392b8365de5ed0ef38a3b6c822665d7c257d3eef6f725c205bb3d78" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.i686-linux-musl.tar.gz" +[[HelloWorldC]] +arch = "i686" +git-tree-sha1 = "56f82168947b8dc7bb98038f063209b9f864eaff" +os = "windows" + + [[HelloWorldC.download]] + sha256 = "de578cf5ee2f457e9ff32089cbe17d03704a929980beddf4c41f4c0eb32f19c6" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.i686-w64-mingw32.tar.gz" +[[HelloWorldC]] +arch = "powerpc64le" +git-tree-sha1 = "9c8902b62f5b1aaa7c2839c804bed7c3a0912c7b" +libc = "glibc" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "63ddbfbb6ea0cafef544cc25415e7ebee6ee0a69db0878d0d4e1ed27c0ae0ab5" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.powerpc64le-linux-gnu.tar.gz" +[[HelloWorldC]] +arch = "x86_64" +git-tree-sha1 = "f8ab5a03697f9afc82210d8a2be1d94509aea8bc" +os = "macos" + + [[HelloWorldC.download]] + sha256 = "f5043338613672b12546c59359c7997c5381a9a60b86aeb951dee74de428d5e3" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.x86_64-apple-darwin.tar.gz" +[[HelloWorldC]] +arch = "x86_64" +git-tree-sha1 = "1ed3d81088f16e3a1fa4e3d4c4c509b8c117fecf" +libc = "glibc" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "a18212e7984b08b23bec06e8bf9286a89b9fa2e8ee0dd46af3b852fe22013a4f" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.x86_64-linux-gnu.tar.gz" +[[HelloWorldC]] +arch = "x86_64" +git-tree-sha1 = "c04ef757b8bb773d17a0fd0ea396e52db1c7c385" +libc = "musl" +os = "linux" + + [[HelloWorldC.download]] + sha256 = "7a3d1b09410989508774f00e073ea6268edefcaba7617fc5085255ec8e82555b" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.x86_64-linux-musl.tar.gz" +[[HelloWorldC]] +arch = "x86_64" +git-tree-sha1 = "5f7e7abf7d545a1aaa368f22e3e01ea0268870b1" +os = "freebsd" + + [[HelloWorldC.download]] + sha256 = "56aedffe38fe20294e93cfc2eb0a193c8e2ddda5a697b302e77ff48ac1195198" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.x86_64-unknown-freebsd.tar.gz" +[[HelloWorldC]] +arch = "x86_64" +git-tree-sha1 = "2f1a6d4f82cd1eea785a5141b992423c09491f1b" +os = "windows" + + [[HelloWorldC.download]] + sha256 = "aad77a16cbc9752f6ec62549a28c7e9f3f7f57919f6fa9fb924e0c669b11f8c4" + url = "https://github.com/JuliaBinaryWrappers/HelloWorldC_jll.jl/releases/download/HelloWorldC-v1.1.2+0/HelloWorldC.v1.1.2.x86_64-w64-mingw32.tar.gz" + +[socrates] +git-tree-sha1 = "43563e7631a7eafae1f9f8d9d332e3de44ad7239" +lazy = true + + [[socrates.download]] + url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.gz" + sha256 = "e65d2f13f2085f2c279830e863292312a72930fee5ba3c792b14c33ce5c5cc58" + + [[socrates.download]] + url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.bz2" + sha256 = "13fc17b97be41763b02cbb80e9d048302cec3bd3d446c2ed6e8210bddcd3ac76" diff --git a/stdlib/LazyArtifacts/test/runtests.jl b/stdlib/LazyArtifacts/test/runtests.jl index 5e3850caecf4c..1c8bbee269144 100644 --- a/stdlib/LazyArtifacts/test/runtests.jl +++ b/stdlib/LazyArtifacts/test/runtests.jl @@ -5,10 +5,12 @@ using Test mktempdir() do tempdir LazyArtifacts.Artifacts.with_artifacts_directory(tempdir) do - socrates_dir = artifact"socrates" - @test isdir(socrates_dir) + redirect_stderr(devnull) do + socrates_dir = artifact"socrates" + @test isdir(socrates_dir) + end ex = @test_throws ErrorException artifact"HelloWorldC" - @test startswith(ex.value.msg, "Artifact \"HelloWorldC\" was not installed correctly. ") + @test startswith(ex.value.msg, "Artifact \"HelloWorldC\" was not found") end end @@ -18,10 +20,12 @@ end using Test mktempdir() do tempdir Artifacts.with_artifacts_directory(tempdir) do - socrates_dir = @test_logs( - (:warn, "using Pkg instead of using LazyArtifacts is deprecated"), - artifact"socrates") - @test isdir(socrates_dir) + redirect_stderr(devnull) do + socrates_dir = @test_logs( + (:warn, "using Pkg instead of using LazyArtifacts is deprecated"), + artifact"socrates") + @test isdir(socrates_dir) + end end end'`, dir=@__DIR__))) diff --git a/stdlib/LibCURL.version b/stdlib/LibCURL.version index 715ca76a40cdf..216ab4e7aca22 100644 --- a/stdlib/LibCURL.version +++ b/stdlib/LibCURL.version @@ -1,4 +1,4 @@ LIBCURL_BRANCH = master -LIBCURL_SHA1 = fd8af649b38ae20c3ff7f5dca53753512ca00376 +LIBCURL_SHA1 = a65b64f6eabc932f63c2c0a4a5fb5d75f3e688d0 LIBCURL_GIT_URL := https://github.com/JuliaWeb/LibCURL.jl.git LIBCURL_TAR_URL = https://api.github.com/repos/JuliaWeb/LibCURL.jl/tarball/$1 diff --git a/stdlib/LibCURL_jll/Project.toml b/stdlib/LibCURL_jll/Project.toml index e4da34909a7eb..0ef46598b3118 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 = "7.81.0+0" +version = "8.0.1+0" [deps] LibSSH2_jll = "29816b5a-b9ab-546f-933c-edad1886dfa8" diff --git a/stdlib/LibCURL_jll/src/LibCURL_jll.jl b/stdlib/LibCURL_jll/src/LibCURL_jll.jl index 0911e68678657..cd67bfac0006a 100644 --- a/stdlib/LibCURL_jll/src/LibCURL_jll.jl +++ b/stdlib/LibCURL_jll/src/LibCURL_jll.jl @@ -14,16 +14,16 @@ export libcurl # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libcurl_handle = C_NULL -libcurl_path = "" +artifact_dir::String = "" +libcurl_handle::Ptr{Cvoid} = C_NULL +libcurl_path::String = "" if Sys.iswindows() const libcurl = "libcurl-4.dll" elseif Sys.isapple() const libcurl = "@rpath/libcurl.4.dylib" else - const libcurl = "libcurl.so" + const libcurl = "libcurl.so.4" end function __init__() diff --git a/stdlib/LibGit2/docs/src/index.md b/stdlib/LibGit2/docs/src/index.md index e53a9330cb1d2..3205c4c5d6987 100644 --- a/stdlib/LibGit2/docs/src/index.md +++ b/stdlib/LibGit2/docs/src/index.md @@ -9,7 +9,7 @@ It is expected that this module will eventually be moved into a separate package Some of this documentation assumes some prior knowledge of the libgit2 API. For more information on some of the objects and methods referenced here, consult the upstream -[libgit2 API reference](https://libgit2.org/libgit2/#v0.25.1). +[libgit2 API reference](https://libgit2.org/libgit2/#v1.0.0). ```@docs LibGit2.Buffer @@ -26,6 +26,7 @@ LibGit2.GitAnnotated LibGit2.GitBlame LibGit2.GitBlob LibGit2.GitCommit +LibGit2.GitConfig LibGit2.GitHash LibGit2.GitObject LibGit2.GitRemote @@ -52,6 +53,7 @@ LibGit2.StatusEntry LibGit2.StatusOptions LibGit2.StrArrayStruct LibGit2.TimeStruct +LibGit2.addfile LibGit2.add! LibGit2.add_fetch! LibGit2.add_push! @@ -158,4 +160,5 @@ LibGit2.CachedCredentials LibGit2.CredentialPayload LibGit2.approve LibGit2.reject +LibGit2.Consts.GIT_CONFIG ``` diff --git a/stdlib/LibGit2/src/LibGit2.jl b/stdlib/LibGit2/src/LibGit2.jl index 5970ae19359bf..6a797937ccf0b 100644 --- a/stdlib/LibGit2/src/LibGit2.jl +++ b/stdlib/LibGit2/src/LibGit2.jl @@ -87,7 +87,7 @@ is in the repository. # Examples ```julia-repl -julia> repo = LibGit2.GitRepo(repo_path); +julia> repo = GitRepo(repo_path); julia> LibGit2.add!(repo, test_file); @@ -230,7 +230,7 @@ Return `true` if `a`, a [`GitHash`](@ref) in string form, is an ancestor of # Examples ```julia-repl -julia> repo = LibGit2.GitRepo(repo_path); +julia> repo = GitRepo(repo_path); julia> LibGit2.add!(repo, test_file1); @@ -477,7 +477,7 @@ current changes. Note that this detaches the current HEAD. # Examples ```julia -repo = LibGit2.init(repo_path) +repo = LibGit2.GitRepo(repo_path) open(joinpath(LibGit2.path(repo), "file1"), "w") do f write(f, "111\n") end @@ -848,7 +848,7 @@ function rebase!(repo::GitRepo, upstream::AbstractString="", newbase::AbstractSt end finally if !isempty(newbase) - close(onto_ann) + close(onto_ann::GitAnnotated) end close(upst_ann) close(head_ann) @@ -1023,4 +1023,13 @@ function set_ssl_cert_locations(cert_loc) throw(Error.GitError(err.class, err.code, chomp(msg))) end +""" + trace_set(level::Union{Integer,GIT_TRACE_LEVEL}) + +Sets the system tracing configuration to the specified level. +""" +function trace_set(level::Union{Integer,Consts.GIT_TRACE_LEVEL}, cb=trace_cb()) + @check @ccall "libgit2".git_trace_set(level::Cint, cb::Ptr{Cvoid})::Cint +end + end # module diff --git a/stdlib/LibGit2/src/callbacks.jl b/stdlib/LibGit2/src/callbacks.jl index 5da032d3143e2..3bc6463140d5f 100644 --- a/stdlib/LibGit2/src/callbacks.jl +++ b/stdlib/LibGit2/src/callbacks.jl @@ -91,12 +91,15 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Cvoid}}, p::CredentialPayload, cred.user = unsafe_string(username_ptr) end - cred.prvkey = Base.get(ENV, "SSH_KEY_PATH") do - default = joinpath(homedir(), ".ssh", "id_rsa") - if isempty(cred.prvkey) && isfile(default) - default - else - cred.prvkey + if haskey(ENV, "SSH_KEY_PATH") + cred.prvkey = ENV["SSH_KEY_PATH"] + elseif isempty(cred.prvkey) + for keytype in ("rsa", "ecdsa") + private_key_file = joinpath(homedir(), ".ssh", "id_$keytype") + if isfile(private_key_file) + cred.prvkey = private_key_file + break + end end end @@ -273,18 +276,20 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring, # information cached inside the payload. if isempty(p.url) p.url = unsafe_string(url_ptr) - m = match(URL_REGEX, p.url) + m = match(URL_REGEX, p.url)::RegexMatch p.scheme = something(m[:scheme], SubString("")) p.username = something(m[:user], SubString("")) - p.host = m[:host] + p.host = something(m[:host]) # When an explicit credential is supplied we will make sure to use the given # credential during the first callback by modifying the allowed types. The # modification only is in effect for the first callback since `allowed_types` cannot # be mutated. - if p.explicit !== nothing - cred = p.explicit + cache = p.cache + explicit = p.explicit + if explicit !== nothing + cred = explicit # Copy explicit credentials to avoid mutating approved credentials. # invalidation fix from cred being non-inferrable @@ -297,16 +302,15 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Cvoid}}, url_ptr::Cstring, else allowed_types &= Cuint(0) # Unhandled credential type end - elseif p.cache !== nothing + elseif cache !== nothing cred_id = credential_identifier(p.scheme, p.host) # Perform a deepcopy as we do not want to mutate approved cached credentials - if haskey(p.cache, cred_id) - # invalidation fix from p.cache[cred_id] being non-inferrable - p.credential = Base.invokelatest(deepcopy, p.cache[cred_id]) + if haskey(cache, cred_id) + # invalidation fix from cache[cred_id] being non-inferrable + p.credential = Base.invokelatest(deepcopy, cache[cred_id]) end end - p.first_pass = true else p.first_pass = false @@ -444,7 +448,7 @@ function ssh_knownhost_check( ) if (m = match(r"^(.+):(\d+)$", host)) !== nothing host = m.captures[1] - port = parse(Int, m.captures[2]) + port = parse(Int, something(m.captures[2])) else port = 22 # default SSH port end @@ -499,6 +503,11 @@ function ssh_knownhost_check( return Consts.LIBSSH2_KNOWNHOST_CHECK_NOTFOUND end +function trace_callback(level::Cint, msg::Cstring)::Cint + println(stderr, "[$level]: $(unsafe_string(msg))") + return 0 +end + "C function pointer for `mirror_callback`" mirror_cb() = @cfunction(mirror_callback, Cint, (Ptr{Ptr{Cvoid}}, Ptr{Cvoid}, Cstring, Cstring, Ptr{Cvoid})) "C function pointer for `credentials_callback`" @@ -507,3 +516,5 @@ credentials_cb() = @cfunction(credentials_callback, Cint, (Ptr{Ptr{Cvoid}}, Cstr fetchhead_foreach_cb() = @cfunction(fetchhead_foreach_callback, Cint, (Cstring, Cstring, Ptr{GitHash}, Cuint, Any)) "C function pointer for `certificate_callback`" certificate_cb() = @cfunction(certificate_callback, Cint, (Ptr{CertHostKey}, Cint, Ptr{Cchar}, Ptr{Cvoid})) +"C function pointer for `trace_callback`" +trace_cb() = @cfunction(trace_callback, Cint, (Cint, Cstring)) diff --git a/stdlib/LibGit2/src/consts.jl b/stdlib/LibGit2/src/consts.jl index 2bc9edaf8950b..f3a460108db6b 100644 --- a/stdlib/LibGit2/src/consts.jl +++ b/stdlib/LibGit2/src/consts.jl @@ -258,6 +258,11 @@ const REMOTE_DOWNLOAD_TAGS_AUTO = Cint(1) const REMOTE_DOWNLOAD_TAGS_NONE = Cint(2) const REMOTE_DOWNLOAD_TAGS_ALL = Cint(3) +# remote_redirect +const REMOTE_REDIRECT_NONE = Cuint(1 << 0) +const REMOTE_REDIRECT_INITIAL = Cuint(1 << 1) +const REMOTE_REDIRECT_ALL = Cuint(1 << 2) + # clone const CLONE_LOCAL_AUTO = Cint(0) const CLONE_LOCAL = Cint(1) @@ -441,7 +446,6 @@ These are used to select which global option to set or get and are used in `git_ SET_TEMPLATE_PATH = 11, SET_SSL_CERT_LOCATIONS = 12) - """ Option flags for `GitProxy`. @@ -453,4 +457,15 @@ Option flags for `GitProxy`. PROXY_AUTO, PROXY_SPECIFIED) +# Available tracing levels. +@enum GIT_TRACE_LEVEL begin + TRACE_NONE + TRACE_FATAL + TRACE_ERROR + TRACE_WARN + TRACE_INFO + TRACE_DEBUG + TRACE_TRACE +end + end diff --git a/stdlib/LibGit2/src/error.jl b/stdlib/LibGit2/src/error.jl index d742cde1605b8..219b8cdf88e69 100644 --- a/stdlib/LibGit2/src/error.jl +++ b/stdlib/LibGit2/src/error.jl @@ -31,7 +31,8 @@ export GitError RETRY = Cint(-32), # internal only EMISMATCH = Cint(-33), # hashsum mismatch in object EINDEXDIRTY = Cint(-34), # unsaved changes in the index would be overwritten - EAPPLYFAIL = Cint(-35)) # patch application failed + EAPPLYFAIL = Cint(-35), # patch application failed + EOWNER = Cint(-36)) # the object is not owned by the current user @enum(Class, None, NoMemory, diff --git a/stdlib/LibGit2/src/gitcredential.jl b/stdlib/LibGit2/src/gitcredential.jl index 1b97c29cd933e..7ff20ca1fdf2c 100644 --- a/stdlib/LibGit2/src/gitcredential.jl +++ b/stdlib/LibGit2/src/gitcredential.jl @@ -46,7 +46,8 @@ function Base.shred!(cred::GitCredential) cred.host = nothing cred.path = nothing cred.username = nothing - cred.password !== nothing && Base.shred!(cred.password) + pwd = cred.password + pwd !== nothing && Base.shred!(pwd) cred.password = nothing return cred end @@ -122,7 +123,7 @@ function Base.read!(io::IO, cred::GitCredential) if key == "url" # Any components which are missing from the URL will be set to empty # https://git-scm.com/docs/git-credential#git-credential-codeurlcode - Base.shred!(parse(GitCredential, value)) do urlcred + Base.shred!(parse(GitCredential, value::AbstractString)) do urlcred copy!(cred, urlcred) end elseif key in GIT_CRED_ATTRIBUTES @@ -219,7 +220,7 @@ function credential_helpers(cfg::GitConfig, cred::GitCredential) helpers = GitCredentialHelper[] # https://git-scm.com/docs/gitcredentials#gitcredentials-helper - for entry in GitConfigIter(cfg, r"credential.*\.helper") + for entry in GitConfigIter(cfg, r"credential.*\.helper$") section, url, name, value = split_cfg_entry(entry) @assert name == "helper" diff --git a/stdlib/LibGit2/src/oid.jl b/stdlib/LibGit2/src/oid.jl index 1074f003ebd2f..937684439419f 100644 --- a/stdlib/LibGit2/src/oid.jl +++ b/stdlib/LibGit2/src/oid.jl @@ -154,7 +154,7 @@ end Get a shortened identifier (`GitShortHash`) of `obj`. The minimum length (in characters) is determined by the `core.abbrev` config option, and will be of sufficient length to -unambiuously identify the object in the repository. +unambiguously identify the object in the repository. """ function GitShortHash(obj::GitObject) ensure_initialized() diff --git a/stdlib/LibGit2/src/reference.jl b/stdlib/LibGit2/src/reference.jl index 345c546946ee5..c05b09ddfc518 100644 --- a/stdlib/LibGit2/src/reference.jl +++ b/stdlib/LibGit2/src/reference.jl @@ -53,7 +53,7 @@ Return a shortened version of the name of `ref` that's "human-readable". ```julia-repl -julia> repo = LibGit2.GitRepo(path_to_repo); +julia> repo = GitRepo(path_to_repo); julia> branch_ref = LibGit2.head(repo); diff --git a/stdlib/LibGit2/src/types.jl b/stdlib/LibGit2/src/types.jl index 2d95596cb276d..1ea6c797d1636 100644 --- a/stdlib/LibGit2/src/types.jl +++ b/stdlib/LibGit2/src/types.jl @@ -346,6 +346,9 @@ The fields represent: @static if LibGit2.VERSION >= v"0.25.0" proxy_opts::ProxyOptions = ProxyOptions() end + @static if LibGit2.VERSION >= v"1.4.0" + follow_redirects::Cuint = Cuint(0) + end @static if LibGit2.VERSION >= v"0.24.0" custom_headers::StrArrayStruct = StrArrayStruct() end @@ -677,6 +680,9 @@ The fields represent: @static if LibGit2.VERSION >= v"0.25.0" proxy_opts::ProxyOptions = ProxyOptions() end + @static if LibGit2.VERSION >= v"1.4.0" + follow_redirects::Cuint = Cuint(0) + end @static if LibGit2.VERSION >= v"0.24.0" custom_headers::StrArrayStruct = StrArrayStruct() end @@ -1383,7 +1389,8 @@ CredentialPayload(p::CredentialPayload) = p function Base.shred!(p::CredentialPayload) # Note: Avoid shredding the `explicit` or `cache` fields as these are just references # and it is not our responsibility to shred them. - p.credential !== nothing && Base.shred!(p.credential) + credential = p.credential + credential !== nothing && Base.shred!(credential) p.credential = nothing end @@ -1424,8 +1431,9 @@ function approve(p::CredentialPayload; shred::Bool=true) # Each `approve` call needs to avoid shredding the passed in credential as we need # the credential information intact for subsequent approve calls. - if p.cache !== nothing - approve(p.cache, cred, p.url) + cache = p.cache + if cache !== nothing + approve(cache, cred, p.url) shred = false # Avoid wiping `cred` as this would also wipe the cached copy end if p.allow_git_helpers @@ -1454,8 +1462,9 @@ function reject(p::CredentialPayload; shred::Bool=true) # Note: each `reject` call needs to avoid shredding the passed in credential as we need # the credential information intact for subsequent reject calls. - if p.cache !== nothing - reject(p.cache, cred, p.url) + cache = p.cache + if cache !== nothing + reject(cache, cred, p.url) end if p.allow_git_helpers reject(p.config, cred, p.url) diff --git a/stdlib/LibGit2/src/utils.jl b/stdlib/LibGit2/src/utils.jl index b601ea4efe601..5234e9b6fc291 100644 --- a/stdlib/LibGit2/src/utils.jl +++ b/stdlib/LibGit2/src/utils.jl @@ -171,7 +171,7 @@ end function credential_identifier(url::AbstractString) m = match(URL_REGEX, url) - scheme = something(m[:scheme], "") - host = m[:host] + scheme = something(m[:scheme], SubString("")) + host = something(m[:host]) credential_identifier(scheme, host) end diff --git a/stdlib/LibGit2/test/bad_ca_roots.jl b/stdlib/LibGit2/test/bad_ca_roots.jl index e4ebdc709637a..4882065167bdb 100644 --- a/stdlib/LibGit2/test/bad_ca_roots.jl +++ b/stdlib/LibGit2/test/bad_ca_roots.jl @@ -9,7 +9,9 @@ using Test, LibGit2, NetworkOptions # if that changes, this may need to be adjusted const CAN_SET_CA_ROOTS_PATH = !Sys.isapple() && !Sys.iswindows() -@testset "empty CA roots file" begin +# Given this is a sub-processed test file, not using @testsets avoids +# leaking the report print into the Base test runner report +begin # empty CA roots file # these fail for different reasons on different platforms: # - on Apple & Windows you cannot set the CA roots path location # - on Linux & FreeBSD you you can but these are invalid files @@ -29,14 +31,14 @@ const CAN_SET_CA_ROOTS_PATH = !Sys.isapple() && !Sys.iswindows() end if CAN_SET_CA_ROOTS_PATH - @testset "non-empty but bad CA roots file" begin + begin # non-empty but bad CA roots file # should still be possible to initialize ENV["JULIA_SSL_CA_ROOTS_PATH"] = joinpath(@__DIR__, "bad_ca_roots.pem") @test LibGit2.ensure_initialized() === nothing end mktempdir() do dir repo_url = "https://github.com/JuliaLang/Example.jl" - @testset "HTTPS clone with bad CA roots fails" begin + begin # HTTPS clone with bad CA roots fails repo_path = joinpath(dir, "Example.HTTPS") c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false) redirect_stderr(devnull) diff --git a/stdlib/LibGit2/test/libgit2-tests.jl b/stdlib/LibGit2/test/libgit2-tests.jl new file mode 100644 index 0000000000000..7dbbd10af6f67 --- /dev/null +++ b/stdlib/LibGit2/test/libgit2-tests.jl @@ -0,0 +1,3236 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module LibGit2Tests + +import LibGit2 +using Test +using Random, Serialization, Sockets + +const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +isdefined(Main, :FakePTYs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FakePTYs.jl")) +import .Main.FakePTYs: with_fake_pty + +const timeout = 60 + +function challenge_prompt(code::Expr, challenges) + input_code = tempname() + open(input_code, "w") do fp + serialize(fp, code) + end + output_file = tempname() + torun = """ + import LibGit2 + using Serialization + result = open($(repr(input_code))) do fp + eval(deserialize(fp)) + end + open($(repr(output_file)), "w") do fp + serialize(fp, result) + end""" + cmd = `$(Base.julia_cmd()) --startup-file=no -e $torun` + try + challenge_prompt(cmd, challenges) + return open(output_file, "r") do fp + deserialize(fp) + end + finally + isfile(output_file) && rm(output_file) + isfile(input_code) && rm(input_code) + end + return nothing +end + +function challenge_prompt(cmd::Cmd, challenges) + function format_output(output) + str = read(seekstart(output), String) + isempty(str) && return "" + return "Process output found:\n\"\"\"\n$str\n\"\"\"" + end + out = IOBuffer() + with_fake_pty() do pts, ptm + p = run(detach(cmd), pts, pts, pts, wait=false) # getpass uses stderr by default + Base.close_stdio(pts) + + # Kill the process if it takes too long. Typically occurs when process is waiting + # for input. + timer = Channel{Symbol}(1) + watcher = @async begin + waited = 0 + while waited < timeout && process_running(p) + sleep(1) + waited += 1 + end + + if process_running(p) + kill(p) + put!(timer, :timeout) + elseif success(p) + put!(timer, :success) + else + put!(timer, :failure) + end + + # SIGKILL stubborn processes + if process_running(p) + sleep(3) + process_running(p) && kill(p, Base.SIGKILL) + end + wait(p) + end + + wroteall = false + try + for (challenge, response) in challenges + write(out, readuntil(ptm, challenge, keep=true)) + if !isopen(ptm) + error("Could not locate challenge: \"$challenge\". ", + format_output(out)) + end + write(ptm, response) + end + wroteall = true + + # Capture output from process until `pts` is closed + write(out, ptm) + catch ex + if !(wroteall && ex isa Base.IOError && ex.code == Base.UV_EIO) + # ignore EIO from `ptm` after `pts` dies + error("Process failed possibly waiting for a response. ", + format_output(out)) + end + end + + status = fetch(timer) + close(ptm) + if status !== :success + if status === :timeout + error("Process timed out possibly waiting for a response. ", + format_output(out)) + else + error("Failed process. ", format_output(out), "\n", p) + end + end + wait(watcher) + end + nothing +end + +const LIBGIT2_MIN_VER = v"1.0.0" +const LIBGIT2_HELPER_PATH = joinpath(@__DIR__, "libgit2-helpers.jl") + +const KEY_DIR = joinpath(@__DIR__, "keys") +const HOME = Sys.iswindows() ? "USERPROFILE" : "HOME" # Environment variable name for home +const GIT_INSTALLED = try + success(`git --version`) +catch + false +end + +function get_global_dir() + buf = Ref(LibGit2.Buffer()) + + LibGit2.@check @ccall "libgit2".git_libgit2_opts( + LibGit2.Consts.GET_SEARCH_PATH::Cint; + LibGit2.Consts.CONFIG_LEVEL_GLOBAL::Cint, + buf::Ptr{LibGit2.Buffer})::Cint + path = unsafe_string(buf[].ptr) + LibGit2.free(buf) + return path +end + +function set_global_dir(dir) + LibGit2.@check @ccall "libgit2".git_libgit2_opts( + LibGit2.Consts.SET_SEARCH_PATH::Cint; + LibGit2.Consts.CONFIG_LEVEL_GLOBAL::Cint, + dir::Cstring)::Cint + return +end + +function with_libgit2_temp_home(f) + mktempdir() do tmphome + oldpath = get_global_dir() + set_global_dir(tmphome) + try + @test get_global_dir() == tmphome + f(tmphome) + finally + set_global_dir(oldpath) + end + return + end +end + +######### +# TESTS # +######### + +@testset "Check library version" begin + v = LibGit2.version() + @test v.major == LIBGIT2_MIN_VER.major && v.minor >= LIBGIT2_MIN_VER.minor +end + +@testset "Check library features" begin + f = LibGit2.features() + @test findfirst(isequal(LibGit2.Consts.FEATURE_SSH), f) !== nothing + @test findfirst(isequal(LibGit2.Consts.FEATURE_HTTPS), f) !== nothing +end + +@testset "OID" begin + z = LibGit2.GitHash() + @test LibGit2.iszero(z) + @test z == zero(LibGit2.GitHash) + @test z == LibGit2.GitHash(z) + rs = string(z) + rr = LibGit2.raw(z) + @test z == LibGit2.GitHash(rr) + @test z == LibGit2.GitHash(rs) + @test z == LibGit2.GitHash(pointer(rr)) + + @test LibGit2.GitShortHash(z, 20) == LibGit2.GitShortHash(rs[1:20]) + @test_throws ArgumentError LibGit2.GitHash(Ptr{UInt8}(C_NULL)) + @test_throws ArgumentError LibGit2.GitHash(rand(UInt8, 2*LibGit2.OID_RAWSZ)) + @test_throws ArgumentError LibGit2.GitHash("a") +end + +@testset "StrArrayStruct" begin + p = ["XXX","YYY"] + a = Base.cconvert(Ptr{LibGit2.StrArrayStruct}, p) + b = Base.unsafe_convert(Ptr{LibGit2.StrArrayStruct}, a) + @test p == convert(Vector{String}, unsafe_load(b)) + @noinline gcuse(a) = a + gcuse(a) +end + +@testset "Signature" begin + sig = LibGit2.Signature("AAA", "AAA@BBB.COM", round(time(); digits=0), 0) + git_sig = convert(LibGit2.GitSignature, sig) + sig2 = LibGit2.Signature(git_sig) + close(git_sig) + @test sig.name == sig2.name + @test sig.email == sig2.email + @test sig.time == sig2.time + sig3 = LibGit2.Signature("AAA","AAA@BBB.COM") + @test sig3.name == sig.name + @test sig3.email == sig.email +end + +@testset "Default config" begin + with_libgit2_temp_home() do tmphome + cfg = LibGit2.GitConfig() + @test isa(cfg, LibGit2.GitConfig) + @test LibGit2.getconfig("fake.property", "") == "" + LibGit2.set!(cfg, "fake.property", "AAAA") + @test LibGit2.getconfig("fake.property", "") == "AAAA" + end +end + +@testset "Trace" begin + code = "import LibGit2; LibGit2.trace_set(LibGit2.Consts.TRACE_DEBUG); exit(LibGit2.trace_set(0))" + run(`$(Base.julia_cmd()) --startup-file=no -e $code`) +end + +# See #21872 and #21636 +LibGit2.version() >= v"0.26.0" && Sys.isunix() && @testset "Default config with symlink" begin + with_libgit2_temp_home() do tmphome + write(joinpath(tmphome, "real_gitconfig"), "[fake]\n\tproperty = BBB") + symlink(joinpath(tmphome, "real_gitconfig"), + joinpath(tmphome, ".gitconfig")) + cfg = LibGit2.GitConfig() + @test isa(cfg, LibGit2.GitConfig) + LibGit2.getconfig("fake.property", "") == "BBB" + LibGit2.set!(cfg, "fake.property", "AAAA") + LibGit2.getconfig("fake.property", "") == "AAAA" + end +end + +@testset "Git URL parsing" begin + @testset "HTTPS URL" begin + m = match(LibGit2.URL_REGEX, "https://user:pass@server.com:80/org/project.git") + @test m[:scheme] == "https" + @test m[:user] == "user" + @test m[:password] == "pass" + @test m[:host] == "server.com" + @test m[:port] == "80" + @test m[:path] == "org/project.git" + end + + @testset "SSH URL" begin + m = match(LibGit2.URL_REGEX, "ssh://user:pass@server:22/project.git") + @test m[:scheme] == "ssh" + @test m[:user] == "user" + @test m[:password] == "pass" + @test m[:host] == "server" + @test m[:port] == "22" + @test m[:path] == "project.git" + end + + @testset "SSH URL, scp-like syntax" begin + m = match(LibGit2.URL_REGEX, "user@server:project.git") + @test m[:scheme] === nothing + @test m[:user] == "user" + @test m[:password] === nothing + @test m[:host] == "server" + @test m[:port] === nothing + @test m[:path] == "project.git" + end + + # scp-like syntax corner case. The SCP syntax does not support port so everything after + # the colon is part of the path. + @testset "scp-like syntax, no port" begin + m = match(LibGit2.URL_REGEX, "server:1234/repo") + @test m[:scheme] === nothing + @test m[:user] === nothing + @test m[:password] === nothing + @test m[:host] == "server" + @test m[:port] === nothing + @test m[:path] == "1234/repo" + end + + @testset "HTTPS URL, realistic" begin + m = match(LibGit2.URL_REGEX, "https://github.com/JuliaLang/Example.jl.git") + @test m[:scheme] == "https" + @test m[:user] === nothing + @test m[:password] === nothing + @test m[:host] == "github.com" + @test m[:port] === nothing + @test m[:path] == "JuliaLang/Example.jl.git" + end + + @testset "SSH URL, realistic" begin + m = match(LibGit2.URL_REGEX, "git@github.com:JuliaLang/Example.jl.git") + @test m[:scheme] === nothing + @test m[:user] == "git" + @test m[:password] === nothing + @test m[:host] == "github.com" + @test m[:port] === nothing + @test m[:path] == "JuliaLang/Example.jl.git" + end + + @testset "usernames with special characters" begin + m = match(LibGit2.URL_REGEX, "user-name@hostname.com") + @test m[:user] == "user-name" + end + + @testset "HTTPS URL, no path" begin + m = match(LibGit2.URL_REGEX, "https://user:pass@server.com:80") + @test m[:path] === nothing + end + + @testset "scp-like syntax, no path" begin + m = match(LibGit2.URL_REGEX, "user@server:") + @test m[:path] == "" + + m = match(LibGit2.URL_REGEX, "user@server") + @test m[:path] === nothing + end + + @testset "HTTPS URL, invalid path" begin + m = match(LibGit2.URL_REGEX, "https://git@server:repo") + @test m === nothing + end + + # scp-like syntax should have a colon separating the hostname from the path + @testset "scp-like syntax, invalid path" begin + m = match(LibGit2.URL_REGEX, "git@server/repo") + @test m === nothing + end +end + +@testset "Git URL formatting" begin + @testset "HTTPS URL" begin + url = LibGit2.git_url( + scheme="https", + username="user", + host="server.com", + port=80, + path="org/project.git") + @test url == "https://user@server.com:80/org/project.git" + end + + @testset "SSH URL" begin + url = LibGit2.git_url( + scheme="ssh", + username="user", + host="server", + port="22", + path="project.git") + @test url == "ssh://user@server:22/project.git" + end + + @testset "SSH URL, scp-like syntax" begin + url = LibGit2.git_url( + username="user", + host="server", + path="project.git") + @test url == "user@server:project.git" + end + + @testset "HTTPS URL, realistic" begin + url = LibGit2.git_url( + scheme="https", + host="github.com", + path="JuliaLang/Example.jl.git") + @test url == "https://github.com/JuliaLang/Example.jl.git" + end + + @testset "SSH URL, realistic" begin + url = LibGit2.git_url( + username="git", + host="github.com", + path="JuliaLang/Example.jl.git") + @test url == "git@github.com:JuliaLang/Example.jl.git" + end + + @testset "HTTPS URL, no path" begin + url = LibGit2.git_url( + scheme="https", + username="user", + host="server.com", + port="80") + @test url == "https://user@server.com:80" + end + + @testset "scp-like syntax, no path" begin + url = LibGit2.git_url( + username="user", + host="server.com") + @test url == "user@server.com" + end + + @testset "HTTP URL, path includes slash prefix" begin + url = LibGit2.git_url( + scheme="http", + host="server.com", + path="/path") + @test url == "http://server.com/path" + end + + @testset "empty" begin + @test_throws ArgumentError LibGit2.git_url() + + @test LibGit2.git_url(host="server.com") == "server.com" + url = LibGit2.git_url( + scheme="", + username="", + host="server.com", + port="", + path="") + @test url == "server.com" + end +end + +@testset "Passphrase Required" begin + @testset "missing file" begin + @test !LibGit2.is_passphrase_required("") + + file = joinpath(KEY_DIR, "foobar") + @test !isfile(file) + @test !LibGit2.is_passphrase_required(file) + end + + @testset "not private key" begin + @test !LibGit2.is_passphrase_required(joinpath(KEY_DIR, "invalid.pub")) + end + + @testset "private key, with passphrase" begin + @test LibGit2.is_passphrase_required(joinpath(KEY_DIR, "valid-passphrase")) + end + + @testset "private key, no passphrase" begin + @test !LibGit2.is_passphrase_required(joinpath(KEY_DIR, "valid")) + end +end + +@testset "GitCredential" begin + @testset "missing" begin + str = "" + cred = read!(IOBuffer(str), LibGit2.GitCredential()) + @test cred == LibGit2.GitCredential() + @test sprint(write, cred) == str + Base.shred!(cred) + end + + @testset "empty" begin + str = """ + protocol= + host= + path= + username= + password= + """ + cred = read!(IOBuffer(str), LibGit2.GitCredential()) + @test cred == LibGit2.GitCredential("", "", "", "", "") + @test sprint(write, cred) == str + Base.shred!(cred) + end + + @testset "input/output" begin + str = """ + protocol=https + host=example.com + username=alice + password=***** + """ + expected_cred = LibGit2.GitCredential("https", "example.com", nothing, "alice", "*****") + + cred = read!(IOBuffer(str), LibGit2.GitCredential()) + @test cred == expected_cred + @test sprint(write, cred) == str + Base.shred!(cred) + Base.shred!(expected_cred) + end + + @testset "extra newline" begin + # The "Git for Windows" installer will also install the "Git Credential Manager for + # Windows" (https://github.com/Microsoft/Git-Credential-Manager-for-Windows) (also + # known as "manager" in the .gitconfig files). This credential manager returns an + # additional newline when returning the results. + str = """ + protocol=https + host=example.com + path= + username=bob + password=***** + + """ + expected_cred = LibGit2.GitCredential("https", "example.com", "", "bob", "*****") + + cred = read!(IOBuffer(str), LibGit2.GitCredential()) + @test cred == expected_cred + @test sprint(write, cred) * "\n" == str + Base.shred!(cred) + Base.shred!(expected_cred) + end + + @testset "unknown attribute" begin + str = """ + protocol=https + host=example.com + attribute=value + username=bob + password=***** + """ + expected_cred = LibGit2.GitCredential("https", "example.com", nothing, "bob", "*****") + expected_log = (:warn, "Unknown git credential attribute found: \"attribute\"") + + cred = @test_logs expected_log read!(IOBuffer(str), LibGit2.GitCredential()) + @test cred == expected_cred + Base.shred!(cred) + Base.shred!(expected_cred) + end + + @testset "use http path" begin + cred = LibGit2.GitCredential("https", "example.com", "dir/file", "alice", "*****") + expected = """ + protocol=https + host=example.com + username=alice + password=***** + """ + + @test cred.use_http_path + cred.use_http_path = false + + @test cred.path == "dir/file" + @test sprint(write, cred) == expected + Base.shred!(cred) + end + + @testset "URL input/output" begin + str = """ + host=example.com + password=bar + url=https://a@b/c + username=foo + """ + expected_str = """ + protocol=https + host=b + path=c + username=foo + """ + expected_cred = LibGit2.GitCredential("https", "b", "c", "foo", nothing) + + cred = read!(IOBuffer(str), LibGit2.GitCredential()) + @test cred == expected_cred + @test sprint(write, cred) == expected_str + Base.shred!(cred) + Base.shred!(expected_cred) + end + + @testset "ismatch" begin + # Equal + cred = LibGit2.GitCredential("https", "github.com") + @test LibGit2.ismatch("https://github.com", cred) + Base.shred!(cred) + + # Credential hostname is different + cred = LibGit2.GitCredential("https", "github.com") + @test !LibGit2.ismatch("https://myhost", cred) + Base.shred!(cred) + + # Credential is less specific than URL + cred = LibGit2.GitCredential("https") + @test !LibGit2.ismatch("https://github.com", cred) + Base.shred!(cred) + + # Credential is more specific than URL + cred = LibGit2.GitCredential("https", "github.com", "path", "user", "pass") + @test LibGit2.ismatch("https://github.com", cred) + Base.shred!(cred) + + # Credential needs to have an "" username to match + cred = LibGit2.GitCredential("https", "github.com", nothing, "") + @test LibGit2.ismatch("https://@github.com", cred) + Base.shred!(cred) + + cred = LibGit2.GitCredential("https", "github.com", nothing, nothing) + @test !LibGit2.ismatch("https://@github.com", cred) + Base.shred!(cred) + end + + @testset "GITHUB_REGEX" begin + github_regex_test = function(url, user, repo) + m = match(LibGit2.GITHUB_REGEX, url) + @test m !== nothing + @test m[1] == "$user/$repo" + @test m[2] == user + @test m[3] == repo + end + user = "User" + repo = "Repo" + github_regex_test("git@github.com/$user/$repo.git", user, repo) + github_regex_test("https://github.com/$user/$repo.git", user, repo) + github_regex_test("https://username@github.com/$user/$repo.git", user, repo) + github_regex_test("ssh://git@github.com/$user/$repo.git", user, repo) + github_regex_test("git@github.com/$user/$repo", user, repo) + github_regex_test("https://github.com/$user/$repo", user, repo) + github_regex_test("https://username@github.com/$user/$repo", user, repo) + github_regex_test("ssh://git@github.com/$user/$repo", user, repo) + @test !occursin(LibGit2.GITHUB_REGEX, "git@notgithub.com/$user/$repo.git") + end + + @testset "UserPasswordCredential/url constructor" begin + user_pass_cred = LibGit2.UserPasswordCredential("user", "*******") + url = "https://github.com" + expected_cred = LibGit2.GitCredential("https", "github.com", nothing, "user", "*******") + + cred = LibGit2.GitCredential(user_pass_cred, url) + @test cred == expected_cred + + # Shredding the UserPasswordCredential shouldn't result in information being lost + # inside of a GitCredential. + Base.shred!(user_pass_cred) + @test cred == expected_cred + + Base.shred!(cred) + Base.shred!(expected_cred) + end +end + +mktempdir() do dir + dir = realpath(dir) + # test parameters + repo_url = "https://github.com/JuliaLang/Example.jl" + cache_repo = joinpath(dir, "Example") + test_repo = joinpath(dir, "Example.Test") + test_sig = LibGit2.Signature("TEST", "TEST@TEST.COM", round(time(); digits=0), 0) + test_dir = "testdir" + test_file = "$(test_dir)/testfile" + config_file = "testconfig" + commit_msg1 = randstring(10) + commit_msg2 = randstring(10) + commit_oid1 = LibGit2.GitHash() + commit_oid2 = LibGit2.GitHash() + commit_oid3 = LibGit2.GitHash() + default_branch = LibGit2.getconfig("init.defaultBranch", "master") + test_branch = "test_branch" + test_branch2 = "test_branch_two" + tag1 = "tag1" + tag2 = "tag2" + + @testset "Configuration" begin + LibGit2.with(LibGit2.GitConfig(joinpath(dir, config_file), LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + @test_throws LibGit2.Error.GitError LibGit2.get(AbstractString, cfg, "tmp.str") + @test isempty(LibGit2.get(cfg, "tmp.str", "")) == true + + LibGit2.set!(cfg, "tmp.str", "AAAA") + LibGit2.set!(cfg, "tmp.int32", Int32(1)) + LibGit2.set!(cfg, "tmp.int64", Int64(1)) + LibGit2.set!(cfg, "tmp.bool", true) + + @test LibGit2.get(cfg, "tmp.str", "") == "AAAA" + @test LibGit2.get(cfg, "tmp.int32", Int32(0)) == Int32(1) + @test LibGit2.get(cfg, "tmp.int64", Int64(0)) == Int64(1) + @test LibGit2.get(cfg, "tmp.bool", false) == true + + # Ordering of entries appears random when using `LibGit2.set!` + count = 0 + for entry in LibGit2.GitConfigIter(cfg, r"tmp.*") + count += 1 + name, value = unsafe_string(entry.name), unsafe_string(entry.value) + if name == "tmp.str" + @test value == "AAAA" + elseif name == "tmp.int32" + @test value == "1" + elseif name == "tmp.int64" + @test value == "1" + elseif name == "tmp.bool" + @test value == "true" + else + error("Found unexpected entry: $name") + end + show_str = sprint(show, entry) + @test show_str == string("ConfigEntry(\"", name, "\", \"", value, "\")") + end + @test count == 4 + end + end + + @testset "Configuration Iteration" begin + config_path = joinpath(dir, config_file) + + # Write config entries with duplicate names + open(config_path, "a") do fp + write(fp, """ + [credential] + helper = store + username = julia + [credential] + helper = cache + """) + end + + LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + # Will only see the last entry + @test LibGit2.get(cfg, "credential.helper", "") == "cache" + + count = 0 + for entry in LibGit2.GitConfigIter(cfg, "credential.helper") + count += 1 + name, value = unsafe_string(entry.name), unsafe_string(entry.value) + @test name == "credential.helper" + @test value == (count == 1 ? "store" : "cache") + end + @test count == 2 + end + end + + @testset "Initializing repository" begin + @testset "with remote branch" begin + LibGit2.with(LibGit2.init(cache_repo)) do repo + @test isdir(cache_repo) + @test LibGit2.path(repo) == LibGit2.posixpath(realpath(cache_repo)) + @test isdir(joinpath(cache_repo, ".git")) + # set a remote branch + branch = "upstream" + LibGit2.GitRemote(repo, branch, repo_url) |> close + + # test remote's representation in the repo's config + config = joinpath(cache_repo, ".git", "config") + lines = split(open(x->read(x, String), config, "r"), "\n") + @test any(map(x->x == "[remote \"upstream\"]", lines)) + + LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote + # test various remote properties + @test LibGit2.url(remote) == repo_url + @test LibGit2.push_url(remote) == "" + @test LibGit2.name(remote) == "upstream" + @test isa(remote, LibGit2.GitRemote) + + # test showing a GitRemote object + @test sprint(show, remote) == "GitRemote:\nRemote name: upstream url: $repo_url" + end + # test setting and getting the remote's URL + @test LibGit2.isattached(repo) + LibGit2.set_remote_url(repo, "upstream", "unknown") + LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote + @test LibGit2.url(remote) == "unknown" + @test LibGit2.push_url(remote) == "unknown" + @test sprint(show, remote) == "GitRemote:\nRemote name: upstream url: unknown" + end + LibGit2.set_remote_url(cache_repo, "upstream", repo_url) + LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote + @test LibGit2.url(remote) == repo_url + @test LibGit2.push_url(remote) == repo_url + @test sprint(show, remote) == "GitRemote:\nRemote name: upstream url: $repo_url" + LibGit2.add_fetch!(repo, remote, "upstream") + + # test setting fetch and push refspecs + @test LibGit2.fetch_refspecs(remote) == String["+refs/heads/*:refs/remotes/upstream/*"] + LibGit2.add_push!(repo, remote, "refs/heads/master") + end + LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote + @test LibGit2.push_refspecs(remote) == String["refs/heads/master"] + end + # constructor with a refspec + LibGit2.with(LibGit2.GitRemote(repo, "upstream2", repo_url, "upstream")) do remote + @test sprint(show, remote) == "GitRemote:\nRemote name: upstream2 url: $repo_url" + @test LibGit2.fetch_refspecs(remote) == String["upstream"] + end + + LibGit2.with(LibGit2.GitRemoteAnon(repo, repo_url)) do remote + @test LibGit2.url(remote) == repo_url + @test LibGit2.push_url(remote) == "" + @test LibGit2.name(remote) == "" + @test isa(remote, LibGit2.GitRemote) + end + end + end + + @testset "bare" begin + path = joinpath(dir, "Example.Bare") + LibGit2.with(LibGit2.init(path, true)) do repo + @test isdir(path) + @test LibGit2.path(repo) == LibGit2.posixpath(realpath(path)) + @test isfile(joinpath(path, LibGit2.Consts.HEAD_FILE)) + @test LibGit2.isattached(repo) + end + + path = joinpath("garbagefakery", "Example.Bare") + try + LibGit2.GitRepo(path) + error("unexpected") + catch e + @test typeof(e) == LibGit2.GitError + @test startswith( + lowercase(sprint(show, e)), + lowercase("GitError(Code:ENOTFOUND, Class:OS, failed to resolve path")) + end + path = joinpath(dir, "Example.BareTwo") + LibGit2.with(LibGit2.init(path, true)) do repo + #just to see if this works + LibGit2.cleanup(repo) + end + end + end + + @testset "Cloning repository" begin + function bare_repo_tests(repo, repo_path) + @test isdir(repo_path) + @test LibGit2.path(repo) == LibGit2.posixpath(realpath(repo_path)) + @test isfile(joinpath(repo_path, LibGit2.Consts.HEAD_FILE)) + @test LibGit2.isattached(repo) + @test LibGit2.remotes(repo) == ["origin"] + end + @testset "bare" begin + repo_path = joinpath(dir, "Example.Bare1") + LibGit2.with(LibGit2.clone(cache_repo, repo_path, isbare = true)) do repo + bare_repo_tests(repo, repo_path) + end + end + @testset "bare with remote callback" begin + repo_path = joinpath(dir, "Example.Bare2") + LibGit2.with(LibGit2.clone(cache_repo, repo_path, isbare = true, remote_cb = LibGit2.mirror_cb())) do repo + bare_repo_tests(repo, repo_path) + LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, "origin")) do rmt + @test LibGit2.fetch_refspecs(rmt)[1] == "+refs/*:refs/*" + end + end + end + @testset "normal" begin + LibGit2.with(LibGit2.clone(cache_repo, test_repo)) do repo + @test isdir(test_repo) + @test LibGit2.path(repo) == LibGit2.posixpath(realpath(test_repo)) + @test isdir(joinpath(test_repo, ".git")) + @test LibGit2.workdir(repo) == LibGit2.path(repo)*"/" + @test LibGit2.isattached(repo) + @test LibGit2.isorphan(repo) + repo_str = sprint(show, repo) + @test repo_str == "LibGit2.GitRepo($(sprint(show,LibGit2.path(repo))))" + end + end + @testset "credentials callback conflict" begin + callbacks = LibGit2.Callbacks(:credentials => (C_NULL, 0)) + cred_payload = LibGit2.CredentialPayload() + @test_throws ArgumentError LibGit2.clone(cache_repo, test_repo, callbacks=callbacks, credentials=cred_payload) + end + end + + @testset "Update cache repository" begin + + @testset "with commits" begin + repo = LibGit2.GitRepo(cache_repo) + repo_dir = joinpath(cache_repo,test_dir) + mkdir(repo_dir) + repo_file = open(joinpath(cache_repo,test_file), "a") + try + # create commits + println(repo_file, commit_msg1) + flush(repo_file) + LibGit2.add!(repo, test_file) + @test LibGit2.iszero(commit_oid1) + commit_oid1 = LibGit2.commit(repo, commit_msg1; author=test_sig, committer=test_sig) + @test !LibGit2.iszero(commit_oid1) + @test LibGit2.GitHash(LibGit2.head(cache_repo)) == commit_oid1 + + println(repo_file, randstring(10)) + flush(repo_file) + LibGit2.add!(repo, test_file) + commit_oid3 = LibGit2.commit(repo, randstring(10); author=test_sig, committer=test_sig) + + println(repo_file, commit_msg2) + flush(repo_file) + LibGit2.add!(repo, test_file) + @test LibGit2.iszero(commit_oid2) + commit_oid2 = LibGit2.commit(repo, commit_msg2; author=test_sig, committer=test_sig) + @test !LibGit2.iszero(commit_oid2) + + # test getting list of commit authors + auths = LibGit2.authors(repo) + @test length(auths) == 3 + for auth in auths + @test auth.name == test_sig.name + @test auth.time == test_sig.time + @test auth.email == test_sig.email + end + + # check various commit properties - commit_oid1 happened before + # commit_oid2, so it *is* an ancestor of commit_oid2 + @test LibGit2.is_ancestor_of(string(commit_oid1), string(commit_oid2), repo) + @test LibGit2.iscommit(string(commit_oid1), repo) + @test !LibGit2.iscommit(string(commit_oid1)*"fake", repo) + @test LibGit2.iscommit(string(commit_oid2), repo) + + # lookup commits + LibGit2.with(LibGit2.GitCommit(repo, commit_oid1)) do cmt + @test LibGit2.Consts.OBJECT(typeof(cmt)) == LibGit2.Consts.OBJ_COMMIT + @test commit_oid1 == LibGit2.GitHash(cmt) + short_oid1 = LibGit2.GitShortHash(string(commit_oid1)) + @test string(commit_oid1) == string(short_oid1) + @test cmp(commit_oid1, short_oid1) == 0 + @test cmp(short_oid1, commit_oid1) == 0 + @test !(short_oid1 < commit_oid1) + + # test showing ShortHash + short_str = sprint(show, short_oid1) + @test short_str == "GitShortHash(\"$(string(short_oid1))\")" + short_oid2 = LibGit2.GitShortHash(cmt) + @test startswith(string(commit_oid1), string(short_oid2)) + + LibGit2.with(LibGit2.GitCommit(repo, short_oid2)) do cmt2 + @test commit_oid1 == LibGit2.GitHash(cmt2) + end + # check that the author and committer signatures are correct + auth = LibGit2.author(cmt) + @test isa(auth, LibGit2.Signature) + @test auth.name == test_sig.name + @test auth.time == test_sig.time + @test auth.email == test_sig.email + short_auth = LibGit2.author(LibGit2.GitCommit(repo, short_oid1)) + @test short_auth.name == test_sig.name + @test short_auth.time == test_sig.time + @test short_auth.email == test_sig.email + cmtr = LibGit2.committer(cmt) + @test isa(cmtr, LibGit2.Signature) + @test cmtr.name == test_sig.name + @test cmtr.time == test_sig.time + @test cmtr.email == test_sig.email + @test LibGit2.message(cmt) == commit_msg1 + + # test showing the commit + showstr = split(sprint(show, cmt), "\n") + # the time of the commit will vary so just test the first two parts + @test occursin("Git Commit:", showstr[1]) + @test occursin("Commit Author: Name: TEST, Email: TEST@TEST.COM, Time:", showstr[2]) + @test occursin("Committer: Name: TEST, Email: TEST@TEST.COM, Time:", showstr[3]) + @test occursin("SHA:", showstr[4]) + @test showstr[5] == "Message:" + @test showstr[6] == commit_msg1 + @test LibGit2.revcount(repo, string(commit_oid1), string(commit_oid3)) == (-1,0) + + blame = LibGit2.GitBlame(repo, test_file) + @test LibGit2.counthunks(blame) == 3 + @test_throws BoundsError getindex(blame, LibGit2.counthunks(blame)+1) + @test_throws BoundsError getindex(blame, 0) + sig = LibGit2.Signature(blame[1].orig_signature) + @test sig.name == cmtr.name + @test sig.email == cmtr.email + show_strs = split(sprint(show, blame[1]), "\n") + @test show_strs[1] == "GitBlameHunk:" + @test show_strs[2] == "Original path: $test_file" + @test show_strs[3] == "Lines in hunk: 1" + @test show_strs[4] == "Final commit oid: $commit_oid1" + @test show_strs[6] == "Original commit oid: $commit_oid1" + @test length(show_strs) == 7 + end + finally + close(repo) + close(repo_file) + end + end + + @testset "with branch" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + brnch = LibGit2.branch(repo) + LibGit2.with(LibGit2.head(repo)) do brref + # various branch properties + @test LibGit2.isbranch(brref) + @test !LibGit2.isremote(brref) + @test LibGit2.name(brref) == "refs/heads/$(default_branch)" + @test LibGit2.shortname(brref) == default_branch + @test LibGit2.ishead(brref) + @test LibGit2.upstream(brref) === nothing + + # showing the GitReference to this branch + show_strs = split(sprint(show, brref), "\n") + @test show_strs[1] == "GitReference:" + @test show_strs[2] == "Branch with name refs/heads/$(default_branch)" + @test show_strs[3] == "Branch is HEAD." + @test repo.ptr == LibGit2.repository(brref).ptr + @test brnch == default_branch + @test LibGit2.headname(repo) == default_branch + + # create a branch *without* setting its tip as HEAD + LibGit2.branch!(repo, test_branch, string(commit_oid1), set_head=false) + # null because we are looking for a REMOTE branch + @test LibGit2.lookup_branch(repo, test_branch, true) === nothing + # not nothing because we are now looking for a LOCAL branch + LibGit2.with(LibGit2.lookup_branch(repo, test_branch, false)) do tbref + @test LibGit2.shortname(tbref) == test_branch + @test LibGit2.upstream(tbref) === nothing + end + @test LibGit2.lookup_branch(repo, test_branch2, true) === nothing + # test deleting the branch + LibGit2.branch!(repo, test_branch2; set_head=false) + LibGit2.with(LibGit2.lookup_branch(repo, test_branch2, false)) do tbref + @test LibGit2.shortname(tbref) == test_branch2 + LibGit2.delete_branch(tbref) + @test LibGit2.lookup_branch(repo, test_branch2, true) === nothing + end + end + branches = map(b->LibGit2.shortname(b[1]), LibGit2.GitBranchIter(repo)) + @test default_branch in branches + @test test_branch in branches + end + end + + @testset "with default configuration" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + try + LibGit2.Signature(repo) + catch ex + # these test configure repo with new signature + # in case when global one does not exist + @test isa(ex, LibGit2.Error.GitError) == true + + cfg = LibGit2.GitConfig(repo) + LibGit2.set!(cfg, "user.name", "AAAA") + LibGit2.set!(cfg, "user.email", "BBBB@BBBB.COM") + sig = LibGit2.Signature(repo) + @test sig.name == "AAAA" + @test sig.email == "BBBB@BBBB.COM" + @test LibGit2.getconfig(repo, "user.name", "") == "AAAA" + @test LibGit2.getconfig(cache_repo, "user.name", "") == "AAAA" + end + end + end + + @testset "with tags" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + tags = LibGit2.tag_list(repo) + @test length(tags) == 0 + + # create tag and extract it from a GitReference + tag_oid1 = LibGit2.tag_create(repo, tag1, commit_oid1, sig=test_sig) + @test !LibGit2.iszero(tag_oid1) + tags = LibGit2.tag_list(repo) + @test length(tags) == 1 + @test tag1 in tags + tag1ref = LibGit2.GitReference(repo, "refs/tags/$tag1") + # because this is a reference to an OID + @test isempty(LibGit2.fullname(tag1ref)) + + # test showing a GitReference to a GitTag, and the GitTag itself + show_strs = split(sprint(show, tag1ref), "\n") + @test show_strs[1] == "GitReference:" + @test show_strs[2] == "Tag with name refs/tags/$tag1" + tag1tag = LibGit2.peel(LibGit2.GitTag, tag1ref) + @test LibGit2.name(tag1tag) == tag1 + @test LibGit2.target(tag1tag) == commit_oid1 + @test sprint(show, tag1tag) == "GitTag:\nTag name: $tag1 target: $commit_oid1" + # peels to the commit the tag points to + tag1cmt = LibGit2.peel(tag1ref) + @test LibGit2.GitHash(tag1cmt) == commit_oid1 + tag_oid2 = LibGit2.tag_create(repo, tag2, commit_oid2) + @test !LibGit2.iszero(tag_oid2) + tags = LibGit2.tag_list(repo) + @test length(tags) == 2 + @test tag2 in tags + + refs = LibGit2.ref_list(repo) + @test refs == ["refs/heads/$(default_branch)", "refs/heads/test_branch", "refs/tags/tag1", "refs/tags/tag2"] + # test deleting a tag + LibGit2.tag_delete(repo, tag1) + tags = LibGit2.tag_list(repo) + @test length(tags) == 1 + @test tag2 ∈ tags + @test tag1 ∉ tags + + # test git describe functions applied to these GitTags + description = LibGit2.GitDescribeResult(repo) + fmtted_description = LibGit2.format(description) + @test sprint(show, description) == "GitDescribeResult:\n$fmtted_description\n" + @test fmtted_description == "tag2" + description = LibGit2.GitDescribeResult(LibGit2.GitObject(repo, "HEAD")) + fmtted_description = LibGit2.format(description) + @test sprint(show, description) == "GitDescribeResult:\n$fmtted_description\n" + @test fmtted_description == "tag2" + end + end + + @testset "status" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + status = LibGit2.GitStatus(repo) + @test length(status) == 0 + @test_throws BoundsError status[1] + repo_file = open(joinpath(cache_repo,"statusfile"), "a") + + # create commits + println(repo_file, commit_msg1) + flush(repo_file) + LibGit2.add!(repo, test_file) + status = LibGit2.GitStatus(repo) + @test length(status) != 0 + @test_throws BoundsError status[0] + @test_throws BoundsError status[length(status)+1] + # we've added a file - show that it is new + @test status[1].status == LibGit2.Consts.STATUS_WT_NEW + close(repo_file) + end + end + + @testset "blobs" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + # this is slightly dubious, as it assumes the object has not been packed + # could be replaced by another binary format + hash_string = string(commit_oid1) + blob_file = joinpath(cache_repo,".git/objects", hash_string[1:2], hash_string[3:end]) + + id = LibGit2.addblob!(repo, blob_file) + blob = LibGit2.GitBlob(repo, id) + @test LibGit2.isbinary(blob) + len1 = length(blob) + + # test showing a GitBlob + blob_show_strs = split(sprint(show, blob), "\n") + @test blob_show_strs[1] == "GitBlob:" + @test occursin("Blob id:", blob_show_strs[2]) + @test blob_show_strs[3] == "Contents are binary." + + blob2 = LibGit2.GitBlob(repo, LibGit2.GitHash(blob)) + @test LibGit2.isbinary(blob2) + @test length(blob2) == len1 + @test blob == blob2 + @test blob !== blob2 + end + end + @testset "trees" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + @test_throws LibGit2.Error.GitError LibGit2.GitTree(repo, "HEAD") + tree = LibGit2.GitTree(repo, "HEAD^{tree}") + @test isa(tree, LibGit2.GitTree) + @test isa(LibGit2.GitObject(repo, "HEAD^{tree}"), LibGit2.GitTree) + @test LibGit2.Consts.OBJECT(typeof(tree)) == LibGit2.Consts.OBJ_TREE + @test LibGit2.count(tree) == 1 + + # test showing the GitTree and its entries + tree_str = sprint(show, tree) + @test tree_str == "GitTree:\nOwner: $(LibGit2.repository(tree))\nNumber of entries: 1\n" + @test_throws BoundsError tree[0] + @test_throws BoundsError tree[2] + tree_entry = tree[1] + subtree = LibGit2.GitTree(tree_entry) + @test_throws BoundsError subtree[0] + @test_throws BoundsError subtree[2] + tree_entry = subtree[1] + @test LibGit2.filemode(tree_entry) == 33188 + te_str = sprint(show, tree_entry) + ref_te_str = "GitTreeEntry:\nEntry name: testfile\nEntry type: LibGit2.GitBlob\nEntry OID: " + ref_te_str *= "$(LibGit2.entryid(tree_entry))\n" + @test te_str == ref_te_str + blob = LibGit2.GitBlob(tree_entry) + blob_str = sprint(show, blob) + @test blob_str == "GitBlob:\nBlob id: $(LibGit2.GitHash(blob))\nContents:\n$(LibGit2.content(blob))\n" + + # tests for walking the tree and accessing objects + @test tree[""] == tree + @test tree["/"] == tree + @test isa(tree[test_dir], LibGit2.GitTree) + @test tree["$test_dir/"] == tree[test_dir] + @test isa(tree[test_file], LibGit2.GitBlob) + @test_throws KeyError tree["nonexistent"] + + # test workaround for git_tree_walk issue + # https://github.com/libgit2/libgit2/issues/4693 + ccall((:giterr_set_str, :libgit2), Cvoid, (Cint, Cstring), + Cint(LibGit2.Error.Invalid), "previous error") + try + # file needs to exist in tree in order to trigger the stop walk condition + tree[test_file] + catch err + if isa(err, LibGit2.Error.GitError) && err.class == LibGit2.Error.Invalid + @test false + else + rethrow() + end + end + end + end + + @testset "diff" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + @test !LibGit2.isdirty(repo) + @test !LibGit2.isdirty(repo, test_file) + @test !LibGit2.isdirty(repo, "nonexistent") + @test !LibGit2.isdiff(repo, "HEAD") + @test !LibGit2.isdirty(repo, cached=true) + @test !LibGit2.isdirty(repo, test_file, cached=true) + @test !LibGit2.isdirty(repo, "nonexistent", cached=true) + @test !LibGit2.isdiff(repo, "HEAD", cached=true) + open(joinpath(cache_repo,test_file), "a") do f + println(f, "zzzz") + end + @test LibGit2.isdirty(repo) + @test LibGit2.isdirty(repo, test_file) + @test !LibGit2.isdirty(repo, "nonexistent") + @test LibGit2.isdiff(repo, "HEAD") + @test !LibGit2.isdirty(repo, cached=true) + @test !LibGit2.isdiff(repo, "HEAD", cached=true) + LibGit2.add!(repo, test_file) + @test LibGit2.isdirty(repo) + @test LibGit2.isdiff(repo, "HEAD") + @test LibGit2.isdirty(repo, cached=true) + @test LibGit2.isdiff(repo, "HEAD", cached=true) + tree = LibGit2.GitTree(repo, "HEAD^{tree}") + + # test properties of the diff_tree + diff = LibGit2.diff_tree(repo, tree, "", cached=true) + @test LibGit2.count(diff) == 1 + @test_throws BoundsError diff[0] + @test_throws BoundsError diff[2] + @test LibGit2.Consts.DELTA_STATUS(diff[1].status) == LibGit2.Consts.DELTA_MODIFIED + @test diff[1].nfiles == 2 + + # test showing a DiffDelta + diff_strs = split(sprint(show, diff[1]), '\n') + @test diff_strs[1] == "DiffDelta:" + @test diff_strs[2] == "Status: DELTA_MODIFIED" + @test diff_strs[3] == "Number of files: 2" + @test diff_strs[4] == "Old file:" + @test diff_strs[5] == "DiffFile:" + @test occursin("Oid:", diff_strs[6]) + @test occursin("Path:", diff_strs[7]) + @test occursin("Size:", diff_strs[8]) + @test isempty(diff_strs[9]) + @test diff_strs[10] == "New file:" + + # test showing a GitDiff + diff_strs = split(sprint(show, diff), '\n') + @test diff_strs[1] == "GitDiff:" + @test diff_strs[2] == "Number of deltas: 1" + @test diff_strs[3] == "GitDiffStats:" + @test diff_strs[4] == "Files changed: 1" + @test diff_strs[5] == "Insertions: 1" + @test diff_strs[6] == "Deletions: 0" + + LibGit2.commit(repo, "zzz") + @test !LibGit2.isdirty(repo) + @test !LibGit2.isdiff(repo, "HEAD") + @test !LibGit2.isdirty(repo, cached=true) + @test !LibGit2.isdiff(repo, "HEAD", cached=true) + end + end + end + + function setup_clone_repo(cache_repo::AbstractString, path::AbstractString; name="AAAA", email="BBBB@BBBB.COM") + repo = LibGit2.clone(cache_repo, path) + # need to set this for merges to succeed + cfg = LibGit2.GitConfig(repo) + LibGit2.set!(cfg, "user.name", name) + LibGit2.set!(cfg, "user.email", email) + return repo + end + # TO DO: add more tests for various merge + # preference options + function add_and_commit_file(repo, filenm, filecontent) + open(joinpath(LibGit2.path(repo), filenm),"w") do f + write(f, filecontent) + end + LibGit2.add!(repo, filenm) + return LibGit2.commit(repo, "add $filenm") + end + @testset "Fastforward merges" begin + LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.FF"))) do repo + # Sets up a branch "branch/ff_a" which will be two commits ahead + # of "master". It's possible to fast-forward merge "branch/ff_a" + # into "master", which is the default behavior. + oldhead = LibGit2.head_oid(repo) + LibGit2.branch!(repo, "branch/ff_a") + add_and_commit_file(repo, "ff_file1", "111\n") + add_and_commit_file(repo, "ff_file2", "222\n") + LibGit2.branch!(repo, "master") + # switch back, now try to ff-merge the changes + # from branch/a + # set up the merge using GitAnnotated objects + upst_ann = LibGit2.GitAnnotated(repo, "branch/ff_a") + head_ann = LibGit2.GitAnnotated(repo, "master") + + # ff merge them + @test LibGit2.merge!(repo, [upst_ann], true) + @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) + + # Repeat the process, but specifying a commit to merge in as opposed + # to a branch name or GitAnnotated. + oldhead = LibGit2.head_oid(repo) + LibGit2.branch!(repo, "branch/ff_b") + add_and_commit_file(repo, "ff_file3", "333\n") + branchhead = add_and_commit_file(repo, "ff_file4", "444\n") + LibGit2.branch!(repo, "master") + # switch back, now try to ff-merge the changes + # from branch/a using committish + @test LibGit2.merge!(repo, committish=string(branchhead)) + @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) + + # Repeat the process, but specifying a branch name to merge in as opposed + # to a commit or GitAnnotated. + oldhead = LibGit2.head_oid(repo) + LibGit2.branch!(repo, "branch/ff_c") + add_and_commit_file(repo, "ff_file5", "555\n") + branchhead = add_and_commit_file(repo, "ff_file6", "666\n") + LibGit2.branch!(repo, "master") + # switch back, now try to ff-merge the changes + # from branch/ff_c using branch name + @test LibGit2.merge!(repo, branch="refs/heads/branch/ff_c") + @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) + + LibGit2.branch!(repo, "branch/ff_d") + branchhead = add_and_commit_file(repo, "ff_file7", "777\n") + LibGit2.branch!(repo, "master") + # switch back, now try to ff-merge the changes + # from branch/a + # set up the merge using GitAnnotated objects + # from a fetchhead + fh = LibGit2.fetchheads(repo) + upst_ann = LibGit2.GitAnnotated(repo, fh[1]) + @test LibGit2.merge!(repo, [upst_ann], true) + @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) + end + end + + @testset "Cherrypick" begin + LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.Cherrypick"))) do repo + # Create a commit on the new branch and cherry-pick it over to + # master. Since the cherry-pick does *not* make a new commit on + # master, we have to create our own commit of the dirty state. + oldhead = LibGit2.head_oid(repo) + LibGit2.branch!(repo, "branch/cherry_a") + cmt_oid = add_and_commit_file(repo, "file1", "111\n") + cmt = LibGit2.GitCommit(repo, cmt_oid) + # switch back, try to cherrypick + # from branch/cherry_a + LibGit2.branch!(repo, "master") + LibGit2.cherrypick(repo, cmt, options=LibGit2.CherrypickOptions()) + cmt_oid2 = LibGit2.commit(repo, "add file1") + @test isempty(LibGit2.diff_files(repo, "master", "branch/cherry_a")) + end + end + + @testset "Merges" begin + LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.Merge"))) do repo + oldhead = LibGit2.head_oid(repo) + LibGit2.branch!(repo, "branch/merge_a") + add_and_commit_file(repo, "file1", "111\n") + # switch back, add a commit, try to merge + # from branch/merge_a + LibGit2.branch!(repo, default_branch) + + # test for showing a Reference to a non-HEAD branch + brref = LibGit2.GitReference(repo, "refs/heads/branch/merge_a") + @test LibGit2.name(brref) == "refs/heads/branch/merge_a" + @test !LibGit2.ishead(brref) + show_strs = split(sprint(show, brref), "\n") + @test show_strs[1] == "GitReference:" + @test show_strs[2] == "Branch with name refs/heads/branch/merge_a" + @test show_strs[3] == "Branch is not HEAD." + + add_and_commit_file(repo, "file2", "222\n") + upst_ann = LibGit2.GitAnnotated(repo, "branch/merge_a") + head_ann = LibGit2.GitAnnotated(repo, default_branch) + + # (fail to) merge them because we can't fastforward + @test_logs (:warn,"Cannot perform fast-forward merge") !LibGit2.merge!(repo, [upst_ann], true) + # merge them now that we allow non-ff + @test_logs (:info,"Review and commit merged changes") LibGit2.merge!(repo, [upst_ann], false) + @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) + + # go back to merge_a and rename a file + LibGit2.branch!(repo, "branch/merge_b") + mv(joinpath(LibGit2.path(repo),"file1"),joinpath(LibGit2.path(repo),"mvfile1")) + LibGit2.add!(repo, "mvfile1") + LibGit2.commit(repo, "move file1") + LibGit2.branch!(repo, default_branch) + upst_ann = LibGit2.GitAnnotated(repo, "branch/merge_b") + rename_flag = Cint(0) + rename_flag = LibGit2.toggle(rename_flag, Cint(0)) # turns on the find renames opt + mos = LibGit2.MergeOptions(flags=rename_flag) + @test_logs (:info,"Review and commit merged changes") LibGit2.merge!(repo, [upst_ann], merge_opts=mos) + end + end + + @testset "push" begin + up_path = joinpath(dir, "Example.PushUp") + up_repo = setup_clone_repo(cache_repo, up_path) + our_repo = setup_clone_repo(cache_repo, joinpath(dir, "Example.Push")) + try + add_and_commit_file(our_repo, "file1", "111\n") + if LibGit2.version() >= v"0.26.0" # See #21872, #21639 and #21597 + # we cannot yet locally push to non-bare repos + @test_throws LibGit2.GitError LibGit2.push(our_repo, remoteurl=up_path) + end + finally + close(our_repo) + close(up_repo) + end + + @testset "credentials callback conflict" begin + callbacks = LibGit2.Callbacks(:credentials => (C_NULL, 0)) + cred_payload = LibGit2.CredentialPayload() + + LibGit2.with(LibGit2.GitRepo(joinpath(dir, "Example.Push"))) do repo + @test_throws ArgumentError LibGit2.push(repo, callbacks=callbacks, credentials=cred_payload) + end + end + end + + @testset "Show closed repo" begin + # Make sure this doesn't crash + buf = IOBuffer() + Base.show(buf, LibGit2.with(identity, LibGit2.GitRepo(test_repo))) + @test String(take!(buf)) == "LibGit2.GitRepo()" + end + + @testset "Fetch from cache repository" begin + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + # fetch changes + @test LibGit2.fetch(repo) == 0 + @test !isfile(joinpath(test_repo, test_file)) + + # ff merge them + @test LibGit2.merge!(repo, fastforward=true) + + # because there was not any file we need to reset branch + head_oid = LibGit2.head_oid(repo) + new_head = LibGit2.reset!(repo, head_oid, LibGit2.Consts.RESET_HARD) + @test isfile(joinpath(test_repo, test_file)) + @test new_head == head_oid + + # GitAnnotated for a fetchhead + fh_ann = LibGit2.GitAnnotated(repo, LibGit2.Consts.FETCH_HEAD) + @test LibGit2.GitHash(fh_ann) == head_oid + + # Detach HEAD - no merge + LibGit2.checkout!(repo, string(commit_oid3)) + @test_throws LibGit2.Error.GitError LibGit2.merge!(repo, fastforward=true) + + # Switch to a branch without remote - no merge + LibGit2.branch!(repo, test_branch) + @test_throws LibGit2.Error.GitError LibGit2.merge!(repo, fastforward=true) + + # Set the username and email for the test_repo (needed for rebase) + cfg = LibGit2.GitConfig(repo) + LibGit2.set!(cfg, "user.name", "AAAA") + LibGit2.set!(cfg, "user.email", "BBBB@BBBB.COM") + + # If upstream argument is empty, libgit2 will look for tracking + # information. If the current branch isn't tracking any upstream + # the rebase should fail. + @test_throws LibGit2.GitError LibGit2.rebase!(repo) + # Try rebasing on master instead + newhead = LibGit2.rebase!(repo, default_branch) + @test newhead == head_oid + + # Switch to the master branch + LibGit2.branch!(repo, default_branch) + + fetch_heads = LibGit2.fetchheads(repo) + @test fetch_heads[1].name == "refs/heads/$(default_branch)" + @test fetch_heads[1].ismerge == true # we just merged master + @test fetch_heads[2].name == "refs/heads/test_branch" + @test fetch_heads[2].ismerge == false + @test fetch_heads[3].name == "refs/tags/tag2" + @test fetch_heads[3].ismerge == false + for fh in fetch_heads + @test fh.url == cache_repo + fh_strs = split(sprint(show, fh), '\n') + @test fh_strs[1] == "FetchHead:" + @test fh_strs[2] == "Name: $(fh.name)" + @test fh_strs[3] == "URL: $(fh.url)" + @test fh_strs[5] == "Merged: $(fh.ismerge)" + end + end + + @testset "credentials callback conflict" begin + callbacks = LibGit2.Callbacks(:credentials => (C_NULL, 0)) + cred_payload = LibGit2.CredentialPayload() + + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + @test_throws ArgumentError LibGit2.fetch(repo, callbacks=callbacks, credentials=cred_payload) + end + end + end + + @testset "Examine test repository" begin + @testset "files" begin + @test readlines(joinpath(test_repo, test_file)) == readlines(joinpath(cache_repo, test_file)) + end + + @testset "tags & branches" begin + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + # all tag in place + tags = LibGit2.tag_list(repo) + @test length(tags) == 1 + @test tag2 in tags + + # all tag in place + branches = map(b->LibGit2.shortname(b[1]), LibGit2.GitBranchIter(repo)) + @test default_branch in branches + @test test_branch in branches + + # issue #16337 + LibGit2.with(LibGit2.GitReference(repo, "refs/tags/$tag2")) do tag2ref + @test_throws LibGit2.Error.GitError LibGit2.upstream(tag2ref) + end + end + end + + @testset "commits with revwalk" begin + repo = LibGit2.GitRepo(test_repo) + cache = LibGit2.GitRepo(cache_repo) + try + # test map with oid + oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker + LibGit2.map((oid,repo)->(oid,repo), walker, oid=commit_oid1, by=LibGit2.Consts.SORT_TIME) + end + @test length(oids) == 1 + # test map with range + str_1 = string(commit_oid1) + str_3 = string(commit_oid3) + oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker + LibGit2.map((oid,repo)->(oid,repo), walker, range="$str_1..$str_3", by=LibGit2.Consts.SORT_TIME) + end + @test length(oids) == 1 + + test_oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker + LibGit2.map((oid,repo)->string(oid), walker, by = LibGit2.Consts.SORT_TIME) + end + cache_oids = LibGit2.with(LibGit2.GitRevWalker(cache)) do walker + LibGit2.map((oid,repo)->string(oid), walker, by = LibGit2.Consts.SORT_TIME) + end + for i in eachindex(oids) + @test cache_oids[i] == test_oids[i] + end + # test with specified oid + LibGit2.with(LibGit2.GitRevWalker(repo)) do walker + @test LibGit2.count((oid,repo)->(oid == commit_oid1), walker, oid=commit_oid1, by=LibGit2.Consts.SORT_TIME) == 1 + end + # test without specified oid + LibGit2.with(LibGit2.GitRevWalker(repo)) do walker + @test LibGit2.count((oid,repo)->(oid == commit_oid1), walker, by=LibGit2.Consts.SORT_TIME) == 1 + end + finally + close(repo) + close(cache) + end + end + end + + @testset "Modify and reset repository" begin + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + # check index for file + LibGit2.with(LibGit2.GitIndex(repo)) do idx + i = findall(test_file, idx) + @test i !== nothing + idx_entry = idx[i] + @test idx_entry !== nothing + idx_entry_str = sprint(show, idx_entry) + @test idx_entry_str == "IndexEntry($(string(idx_entry.id)))" + @test LibGit2.stage(idx_entry) == 0 + + i = findall("zzz", idx) + @test i === nothing + idx_str = sprint(show, idx) + @test idx_str == "GitIndex:\nRepository: $(LibGit2.repository(idx))\nNumber of elements: 1\n" + + LibGit2.remove!(repo, test_file) + LibGit2.read!(repo) + @test LibGit2.count(idx) == 0 + LibGit2.add!(repo, test_file) + LibGit2.update!(repo, test_file) + @test LibGit2.count(idx) == 1 + end + + # check non-existent file status + st = LibGit2.status(repo, "XYZ") + @test st === nothing + + # check file status + st = LibGit2.status(repo, test_file) + @test st !== nothing + @test LibGit2.isset(st, LibGit2.Consts.STATUS_CURRENT) + + # modify file + open(joinpath(test_repo, test_file), "a") do io + write(io, 0x41) + end + + # file modified but not staged + st_mod = LibGit2.status(repo, test_file) + @test !LibGit2.isset(st_mod, LibGit2.Consts.STATUS_INDEX_MODIFIED) + @test LibGit2.isset(st_mod, LibGit2.Consts.STATUS_WT_MODIFIED) + + # stage file + LibGit2.add!(repo, test_file) + + # modified file staged + st_stg = LibGit2.status(repo, test_file) + @test LibGit2.isset(st_stg, LibGit2.Consts.STATUS_INDEX_MODIFIED) + @test !LibGit2.isset(st_stg, LibGit2.Consts.STATUS_WT_MODIFIED) + + # try to unstage to unknown commit + @test_throws LibGit2.Error.GitError LibGit2.reset!(repo, "XYZ", test_file) + + # status should not change + st_new = LibGit2.status(repo, test_file) + @test st_new == st_stg + + # try to unstage to HEAD + new_head = LibGit2.reset!(repo, LibGit2.Consts.HEAD_FILE, test_file) + st_uns = LibGit2.status(repo, test_file) + @test st_uns == st_mod + + # reset repo + @test_throws LibGit2.Error.GitError LibGit2.reset!(repo, LibGit2.GitHash(), LibGit2.Consts.RESET_HARD) + + new_head = LibGit2.reset!(repo, LibGit2.head_oid(repo), LibGit2.Consts.RESET_HARD) + open(joinpath(test_repo, test_file), "r") do io + @test read(io)[end] != 0x41 + end + end + end + + @testset "Modify remote" begin + path = test_repo + LibGit2.with(LibGit2.GitRepo(path)) do repo + remote_name = "test" + url = "https://test.com/repo" + + @test LibGit2.lookup_remote(repo, remote_name) === nothing + + for r in (repo, path) + # Set just the fetch URL + LibGit2.set_remote_fetch_url(r, remote_name, url) + remote = LibGit2.lookup_remote(repo, remote_name) + @test LibGit2.name(remote) == remote_name + @test LibGit2.url(remote) == url + @test LibGit2.push_url(remote) == "" + + LibGit2.remote_delete(repo, remote_name) + @test LibGit2.lookup_remote(repo, remote_name) === nothing + + # Set just the push URL + LibGit2.set_remote_push_url(r, remote_name, url) + remote = LibGit2.lookup_remote(repo, remote_name) + @test LibGit2.name(remote) == remote_name + @test LibGit2.url(remote) == "" + @test LibGit2.push_url(remote) == url + + LibGit2.remote_delete(repo, remote_name) + @test LibGit2.lookup_remote(repo, remote_name) === nothing + + # Set the fetch and push URL + LibGit2.set_remote_url(r, remote_name, url) + remote = LibGit2.lookup_remote(repo, remote_name) + @test LibGit2.name(remote) == remote_name + @test LibGit2.url(remote) == url + @test LibGit2.push_url(remote) == url + + LibGit2.remote_delete(repo, remote_name) + @test LibGit2.lookup_remote(repo, remote_name) === nothing + end + # Invalid remote name + @test_throws LibGit2.GitError LibGit2.set_remote_url(repo, "", url) + @test_throws LibGit2.GitError LibGit2.set_remote_url(repo, remote_name, "") + end + end + + @testset "rebase" begin + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + LibGit2.branch!(repo, "branch/a") + + oldhead = LibGit2.head_oid(repo) + add_and_commit_file(repo, "file1", "111\n") + add_and_commit_file(repo, "file2", "222\n") + LibGit2.branch!(repo, "branch/b") + + # squash last 2 commits + new_head = LibGit2.reset!(repo, oldhead, LibGit2.Consts.RESET_SOFT) + @test new_head == oldhead + LibGit2.commit(repo, "squash file1 and file2") + + # add another file + newhead = add_and_commit_file(repo, "file3", "333\n") + @test LibGit2.diff_files(repo, "branch/a", "branch/b", filter=Set([LibGit2.Consts.DELTA_ADDED])) == ["file3"] + @test LibGit2.diff_files(repo, "branch/a", "branch/b", filter=Set([LibGit2.Consts.DELTA_MODIFIED])) == [] + # switch back and rebase + LibGit2.branch!(repo, "branch/a") + newnewhead = LibGit2.rebase!(repo, "branch/b") + + # issue #19624 + @test newnewhead == newhead + + # add yet another file + add_and_commit_file(repo, "file4", "444\n") + # rebase with onto + newhead = LibGit2.rebase!(repo, "branch/a", default_branch) + + newerhead = LibGit2.head_oid(repo) + @test newerhead == newhead + + # add yet more files + add_and_commit_file(repo, "file5", "555\n") + pre_abort_head = add_and_commit_file(repo, "file6", "666\n") + # Rebase type + head_ann = LibGit2.GitAnnotated(repo, "branch/a") + upst_ann = LibGit2.GitAnnotated(repo, default_branch) + rb = LibGit2.GitRebase(repo, head_ann, upst_ann) + @test_throws BoundsError rb[3] + @test_throws BoundsError rb[0] + rbo, _ = iterate(rb) + rbo_str = sprint(show, rbo) + @test rbo_str == "RebaseOperation($(string(rbo.id)))\nOperation type: REBASE_OPERATION_PICK\n" + rb_str = sprint(show, rb) + @test rb_str == "GitRebase:\nNumber: 2\nCurrently performing operation: 1\n" + rbo = rb[2] + rbo_str = sprint(show, rbo) + @test rbo_str == "RebaseOperation($(string(rbo.id)))\nOperation type: REBASE_OPERATION_PICK\n" + + # test rebase abort + LibGit2.abort(rb) + @test LibGit2.head_oid(repo) == pre_abort_head + end + end + + @testset "merge" begin + LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.simple_merge"))) do repo + LibGit2.branch!(repo, "branch/merge_a") + + a_head = LibGit2.head_oid(repo) + add_and_commit_file(repo, "merge_file1", "111\n") + LibGit2.branch!(repo, default_branch) + a_head_ann = LibGit2.GitAnnotated(repo, "branch/merge_a") + # merge returns true if successful + @test_logs (:info,"Review and commit merged changes") LibGit2.merge!(repo, [a_head_ann]) + end + end + + @testset "Transact test repository" begin + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + cp(joinpath(test_repo, test_file), joinpath(test_repo, "CCC")) + cp(joinpath(test_repo, test_file), joinpath(test_repo, "AAA")) + LibGit2.add!(repo, "AAA") + @test_throws ErrorException LibGit2.transact(repo) do trepo + mv(joinpath(test_repo, test_file), joinpath(test_repo, "BBB")) + LibGit2.add!(trepo, "BBB") + oid = LibGit2.commit(trepo, "test commit"; author=test_sig, committer=test_sig) + error("Force recovery") + end + @test isfile(joinpath(test_repo, "AAA")) + @test isfile(joinpath(test_repo, "CCC")) + @test !isfile(joinpath(test_repo, "BBB")) + @test isfile(joinpath(test_repo, test_file)) + end + end + + @testset "checkout_head" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + # modify file + repo_file = open(joinpath(cache_repo,test_file), "a") + println(repo_file, commit_msg1 * randstring(10)) + close(repo_file) + # and checkout HEAD once more + LibGit2.checkout_head(repo, options=LibGit2.CheckoutOptions(checkout_strategy=LibGit2.Consts.CHECKOUT_FORCE)) + @test LibGit2.headname(repo) == default_branch + @test !LibGit2.isdirty(repo) + end + end + + @testset "checkout/headname" begin + LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo + LibGit2.checkout!(repo, string(commit_oid1)) + @test !LibGit2.isattached(repo) + @test LibGit2.headname(repo) == "(detached from $(string(commit_oid1)[1:7]))" + end + end + + if Sys.isunix() + @testset "checkout/proptest" begin + LibGit2.with(LibGit2.GitRepo(test_repo)) do repo + cp(joinpath(test_repo, test_file), joinpath(test_repo, "proptest")) + LibGit2.add!(repo, "proptest") + id1 = LibGit2.commit(repo, "test property change 1") + # change in file permissions (#17610) + chmod(joinpath(test_repo, "proptest"),0o744) + LibGit2.add!(repo, "proptest") + id2 = LibGit2.commit(repo, "test property change 2") + LibGit2.checkout!(repo, string(id1)) + @test !LibGit2.isdirty(repo) + # change file to symlink (#18420) + mv(joinpath(test_repo, "proptest"), joinpath(test_repo, "proptest2")) + symlink(joinpath(test_repo, "proptest2"), joinpath(test_repo, "proptest")) + LibGit2.add!(repo, "proptest", "proptest2") + id3 = LibGit2.commit(repo, "test symlink change") + LibGit2.checkout!(repo, string(id1)) + @test !LibGit2.isdirty(repo) + end + end + end + + + @testset "Credentials" begin + creds_user = "USER" + creds_pass = Base.SecretBuffer("PASS") + creds = LibGit2.UserPasswordCredential(creds_user, creds_pass) + @test creds.user == creds_user + @test creds.pass == creds_pass + creds2 = LibGit2.UserPasswordCredential(creds_user, creds_pass) + @test creds == creds2 + + sshcreds = LibGit2.SSHCredential(creds_user, creds_pass) + @test sshcreds.user == creds_user + @test sshcreds.pass == creds_pass + @test sshcreds.prvkey == "" + @test sshcreds.pubkey == "" + sshcreds2 = LibGit2.SSHCredential(creds_user, creds_pass) + @test sshcreds == sshcreds2 + + Base.shred!(creds) + Base.shred!(creds2) + Base.shred!(sshcreds) + Base.shred!(sshcreds2) + Base.shred!(creds_pass) + end + + @testset "CachedCredentials" begin + cache = LibGit2.CachedCredentials() + + url = "https://github.com/JuliaLang/Example.jl" + cred_id = LibGit2.credential_identifier(url) + cred = LibGit2.UserPasswordCredential("julia", "password") + + @test !haskey(cache, cred_id) + password = Base.SecretBuffer("password") + + # Attempt to reject a credential which wasn't stored + LibGit2.reject(cache, cred, url) + @test !haskey(cache, cred_id) + @test cred.user == "julia" + @test cred.pass == password + + # Approve a credential which causes it to be stored + LibGit2.approve(cache, cred, url) + @test haskey(cache, cred_id) + @test cache[cred_id] === cred + + # Approve the same credential again which does not overwrite + LibGit2.approve(cache, cred, url) + @test haskey(cache, cred_id) + @test cache[cred_id] === cred + + # Overwrite an already cached credential + dup_cred = deepcopy(cred) + LibGit2.approve(cache, dup_cred, url) # Shreds overwritten `cred` + @test haskey(cache, cred_id) + @test cache[cred_id] === dup_cred + @test cred.user != "julia" + @test cred.pass != password + @test dup_cred.user == "julia" + @test dup_cred.pass == password + + cred = dup_cred + + # Reject an approved credential + @test cache[cred_id] === cred + LibGit2.reject(cache, cred, url) # Avoids shredding the credential passed in + @test !haskey(cache, cred_id) + @test cred.user == "julia" + @test cred.pass == password + + # Reject and shred an approved credential + dup_cred = deepcopy(cred) + LibGit2.approve(cache, cred, url) + + LibGit2.reject(cache, dup_cred, url) # Shred `cred` but not passed in `dup_cred` + @test !haskey(cache, cred_id) + @test cred.user != "julia" + @test cred.pass != password + @test dup_cred.user == "julia" + @test dup_cred.pass == password + + Base.shred!(dup_cred) + Base.shred!(cache) + Base.shred!(password) + end + + @testset "Git credential username" begin + @testset "fill username" begin + config_path = joinpath(dir, config_file) + isfile(config_path) && rm(config_path) + + LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + # No credential settings should be set for these tests + @test isempty(collect(LibGit2.GitConfigIter(cfg, r"credential.*"))) + + github_cred = LibGit2.GitCredential("https", "github.com") + mygit_cred = LibGit2.GitCredential("https", "mygithost") + + # No credential settings in configuration. + username = LibGit2.default_username(cfg, github_cred) + @test username === nothing + + # Add a credential setting for a specific for a URL + LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.username", "foo") + + username = LibGit2.default_username(cfg, github_cred) + @test username == "foo" + + username = LibGit2.default_username(cfg, mygit_cred) + @test username === nothing + + # Add a global credential setting after the URL specific setting. The first + # setting to match will be the one that is used. + LibGit2.set!(cfg, "credential.username", "bar") + + username = LibGit2.default_username(cfg, github_cred) + @test username == "foo" + + username = LibGit2.default_username(cfg, mygit_cred) + @test username == "bar" + + Base.shred!(github_cred) + Base.shred!(mygit_cred) + end + end + + @testset "empty username" begin + config_path = joinpath(dir, config_file) + isfile(config_path) && rm(config_path) + + LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + # No credential settings should be set for these tests + @test isempty(collect(LibGit2.GitConfigIter(cfg, r"credential.*"))) + + # An empty username should count as being set + LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.username", "") + LibGit2.set!(cfg, "credential.username", "name") + + github_cred = LibGit2.GitCredential("https", "github.com") + mygit_cred = LibGit2.GitCredential("https", "mygithost", "path") + + username = LibGit2.default_username(cfg, github_cred) + @test username == "" + + username = LibGit2.default_username(cfg, mygit_cred) + @test username == "name" + + Base.shred!(github_cred) + Base.shred!(mygit_cred) + end + end + end + + @testset "Git helpers useHttpPath" begin + @testset "use_http_path" begin + config_path = joinpath(dir, config_file) + isfile(config_path) && rm(config_path) + + LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + # No credential settings should be set for these tests + @test isempty(collect(LibGit2.GitConfigIter(cfg, r"credential.*"))) + + github_cred = LibGit2.GitCredential("https", "github.com") + mygit_cred = LibGit2.GitCredential("https", "mygithost") + + # No credential settings in configuration. + @test !LibGit2.use_http_path(cfg, github_cred) + @test !LibGit2.use_http_path(cfg, mygit_cred) + + # Add a credential setting for a specific for a URL + LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.useHttpPath", "true") + + @test LibGit2.use_http_path(cfg, github_cred) + @test !LibGit2.use_http_path(cfg, mygit_cred) + + # Invert the current settings. + LibGit2.set!(cfg, "credential.useHttpPath", "true") + LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.useHttpPath", "false") + + @test !LibGit2.use_http_path(cfg, github_cred) + @test LibGit2.use_http_path(cfg, mygit_cred) + + Base.shred!(github_cred) + Base.shred!(mygit_cred) + end + end + end + + @testset "GitCredentialHelper" begin + GitCredentialHelper = LibGit2.GitCredentialHelper + GitCredential = LibGit2.GitCredential + + @testset "parse" begin + @test parse(GitCredentialHelper, "!echo hello") == GitCredentialHelper(`echo hello`) + @test parse(GitCredentialHelper, "/bin/bash") == GitCredentialHelper(`/bin/bash`) + @test parse(GitCredentialHelper, "store") == GitCredentialHelper(`git credential-store`) + end + + @testset "credential_helpers" begin + config_path = joinpath(dir, config_file) + + # Note: LibGit2.set! doesn't allow us to set duplicates or ordering + open(config_path, "w+") do fp + write(fp, """ + [credential] + helper = !echo first + [credential "https://mygithost"] + helper = "" + [credential] + helper = !echo second + """) + # Git for Windows uses this config (see issue #45693) + write(fp,""" + [credential "helperselector"] + selected = manager-core + """) + end + + LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + expected = [ + GitCredentialHelper(`echo first`), + GitCredentialHelper(`echo second`), + ] + + github_cred = GitCredential("https", "github.com") + mygit_cred = GitCredential("https", "mygithost") + + @test LibGit2.credential_helpers(cfg, github_cred) == expected + @test LibGit2.credential_helpers(cfg, mygit_cred) == expected[2:2] + + Base.shred!(github_cred) + Base.shred!(mygit_cred) + end + end + + @testset "approve/reject" begin + # In order to use the "store" credential helper `git` needs to be installed and + # on the path. + if GIT_INSTALLED + credential_path = joinpath(dir, ".git-credentials") + isfile(credential_path) && rm(credential_path) + + # Requires `git` to be installed and available on the path. + helper = parse(LibGit2.GitCredentialHelper, "store") + + # Set HOME to control where the .git-credentials file is written. + # Note: In Cygwin environments `git` will use HOME instead of USERPROFILE. + # Setting both environment variables ensures home was overridden. + withenv("HOME" => dir, "USERPROFILE" => dir) do + query = LibGit2.GitCredential("https", "mygithost") + filled = LibGit2.GitCredential("https", "mygithost", nothing, "bob", "s3cre7") + + @test !isfile(credential_path) + + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == query + end + + LibGit2.approve(helper, filled) + @test isfile(credential_path) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == filled + end + + LibGit2.reject(helper, filled) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == query + end + + Base.shred!(query) + Base.shred!(filled) + end + end + end + + @testset "approve/reject with path" begin + # In order to use the "store" credential helper `git` needs to be installed and + # on the path. + if GIT_INSTALLED + credential_path = joinpath(dir, ".git-credentials") + isfile(credential_path) && rm(credential_path) + + # Requires `git` to be installed and available on the path. + helper = parse(LibGit2.GitCredentialHelper, "store") + + # Set HOME to control where the .git-credentials file is written. + # Note: In Cygwin environments `git` will use HOME instead of USERPROFILE. + # Setting both environment variables ensures home was overridden. + withenv("HOME" => dir, "USERPROFILE" => dir) do + query = LibGit2.GitCredential("https", "mygithost") + query_a = LibGit2.GitCredential("https", "mygithost", "a") + query_b = LibGit2.GitCredential("https", "mygithost", "b") + + filled_a = LibGit2.GitCredential("https", "mygithost", "a", "alice", "1234") + filled_b = LibGit2.GitCredential("https", "mygithost", "b", "bob", "s3cre7") + + function without_path(cred) + c = deepcopy(cred) + c.path = nothing + c + end + + filled_without_path_a = without_path(filled_a) + filled_without_path_b = without_path(filled_b) + + @test !isfile(credential_path) + + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == query + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result + @test result == query_a + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result + @test result == query_b + end + + LibGit2.approve(helper, filled_a) + @test isfile(credential_path) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == filled_without_path_a + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result + @test result == filled_a + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result + @test result == query_b + end + + LibGit2.approve(helper, filled_b) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == filled_without_path_b + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result + @test result == filled_a + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result + @test result == filled_b + end + + LibGit2.reject(helper, filled_b) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == filled_without_path_a + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result + @test result == filled_a + end + Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result + @test result == query_b + end + + Base.shred!(query) + Base.shred!(query_a) + Base.shred!(query_b) + Base.shred!(filled_a) + Base.shred!(filled_b) + Base.shred!(filled_without_path_a) + Base.shred!(filled_without_path_b) + end + end + end + + @testset "approve/reject with UserPasswordCredential" begin + # In order to use the "store" credential helper `git` needs to be installed and + # on the path. + if GIT_INSTALLED + config_path = joinpath(dir, config_file) + isfile(config_path) && rm(config_path) + + credential_path = joinpath(dir, ".git-credentials") + isfile(credential_path) && rm(credential_path) + + LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + query = LibGit2.GitCredential("https", "mygithost") + filled = LibGit2.GitCredential("https", "mygithost", nothing, "alice", "1234") + user_pass_cred = LibGit2.UserPasswordCredential("alice", "1234") + url = "https://mygithost" + + # Requires `git` to be installed and available on the path. + LibGit2.set!(cfg, "credential.helper", "store --file \"$credential_path\"") + helper = only(LibGit2.credential_helpers(cfg, query)) + + @test !isfile(credential_path) + + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == query + end + + LibGit2.approve(cfg, user_pass_cred, url) + @test isfile(credential_path) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == filled + end + + LibGit2.reject(cfg, user_pass_cred, url) + Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result + @test result == query + end + + Base.shred!(query) + Base.shred!(filled) + Base.shred!(user_pass_cred) + end + end + end + end + + # The following tests require that we can fake a TTY so that we can provide passwords + # which use the `getpass` function. At the moment we can only fake this on UNIX based + # systems. + if Sys.isunix() + git_ok = LibGit2.GitError( + LibGit2.Error.None, LibGit2.Error.GIT_OK, + "No errors") + + abort_prompt = LibGit2.GitError( + LibGit2.Error.Callback, LibGit2.Error.EUSER, + "Aborting, user cancelled credential request.") + + prompt_limit = LibGit2.GitError( + LibGit2.Error.Callback, LibGit2.Error.EAUTH, + "Aborting, maximum number of prompts reached.") + + incompatible_error = LibGit2.GitError( + LibGit2.Error.Callback, LibGit2.Error.EAUTH, + "The explicitly provided credential is incompatible with the requested " * + "authentication methods.") + + exhausted_error = LibGit2.GitError( + LibGit2.Error.Callback, LibGit2.Error.EAUTH, + "All authentication methods have failed.") + + @testset "SSH credential prompt" begin + url = "git@github.com:test/package.jl" + username = "git" + + valid_key = joinpath(KEY_DIR, "valid") + valid_cred = LibGit2.SSHCredential(username, "", valid_key, valid_key * ".pub") + + valid_p_key = joinpath(KEY_DIR, "valid-passphrase") + passphrase = "secret" + valid_p_cred = LibGit2.SSHCredential(username, passphrase, valid_p_key, valid_p_key * ".pub") + + invalid_key = joinpath(KEY_DIR, "invalid") + + function gen_ex(cred; username="git") + url = username !== nothing && !isempty(username) ? "$username@" : "" + url *= "github.com:test/package.jl" + quote + include($LIBGIT2_HELPER_PATH) + credential_loop($cred, $url, $username) + end + end + + ssh_ex = gen_ex(valid_cred) + ssh_p_ex = gen_ex(valid_p_cred) + ssh_u_ex = gen_ex(valid_cred, username=nothing) + + # Note: We cannot use the default ~/.ssh/id_rsa for tests since we cannot be + # sure a users will actually have these files. Instead we will use the ENV + # variables to set the default values. + + # ENV credentials are valid + withenv("SSH_KEY_PATH" => valid_key) do + err, auth_attempts, p = challenge_prompt(ssh_ex, []) + @test err == git_ok + @test auth_attempts == 1 + end + + # ENV credentials are valid but requires a passphrase + withenv("SSH_KEY_PATH" => valid_p_key) do + challenges = [ + "Passphrase for $valid_p_key: " => "$passphrase\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # User mistypes passphrase. + # Note: In reality LibGit2 will raise an error upon using the invalid SSH + # credentials. Since we don't control the internals of LibGit2 though they + # could also just re-call the credential callback like they do for HTTP. + challenges = [ + "Passphrase for $valid_p_key: " => "foo\n", + "Private key location for 'git@github.com' [$valid_p_key]: " => "\n", + "Passphrase for $valid_p_key: " => "$passphrase\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) + @test err == git_ok + @test auth_attempts == 2 + + # User sends EOF in passphrase prompt which aborts the credential request + challenges = [ + "Passphrase for $valid_p_key: " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + + # User provides an empty passphrase + challenges = [ + "Passphrase for $valid_p_key: " => "\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + end + + # ENV credential requiring passphrase + withenv("SSH_KEY_PATH" => valid_p_key, "SSH_KEY_PASS" => passphrase) do + err, auth_attempts, p = challenge_prompt(ssh_p_ex, []) + @test err == git_ok + @test auth_attempts == 1 + end + + # Missing username + withenv("SSH_KEY_PATH" => valid_key) do + # User provides a valid username + challenges = [ + "Username for 'github.com': " => "$username\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # User sends EOF in username prompt which aborts the credential request + challenges = [ + "Username for 'github.com': " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + + # User provides an empty username + challenges = [ + "Username for 'github.com': " => "\n", + "Username for 'github.com': " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 2 + + # User repeatedly chooses an invalid username + challenges = [ + "Username for 'github.com': " => "foo\n", + "Username for 'github.com' [foo]: " => "\n", + "Private key location for 'foo@github.com' [$valid_key]: " => "\n", + "Username for 'github.com' [foo]: " => "\x04", # Need to manually abort + ] + err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 3 + + # Credential callback is given an empty string in the `username_ptr` + # instead of the C_NULL in the other missing username tests. + ssh_user_empty_ex = gen_ex(valid_cred, username="") + challenges = [ + "Username for 'github.com': " => "$username\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_user_empty_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + end + + # Explicitly setting these env variables to be empty means the user will be + # given a prompt with no defaults set. + withenv("SSH_KEY_PATH" => nothing, + "SSH_PUB_KEY_PATH" => nothing, + "SSH_KEY_PASS" => nothing, + HOME => dir) do + + # Set the USERPROFILE / HOME above to be a directory that does not contain + # the "~/.ssh/id_rsa" file. If this file exists the credential callback + # will default to use this private key instead of triggering a prompt. + @test !isfile(joinpath(homedir(), ".ssh", "id_rsa")) + + # User provides valid credentials + challenges = [ + "Private key location for 'git@github.com': " => "$valid_key\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # User provides valid credentials that requires a passphrase + challenges = [ + "Private key location for 'git@github.com': " => "$valid_p_key\n", + "Passphrase for $valid_p_key: " => "$passphrase\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # User sends EOF in private key prompt which aborts the credential request + challenges = [ + "Private key location for 'git@github.com': " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + + # User provides an empty private key which triggers a re-prompt + challenges = [ + "Private key location for 'git@github.com': " => "\n", + "Private key location for 'git@github.com': " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 2 + + # User provides an invalid private key until prompt limit reached. + # Note: the prompt should not supply an invalid default. + challenges = [ + "Private key location for 'git@github.com': " => "foo\n", + "Private key location for 'git@github.com' [foo]: " => "foo\n", + "Private key location for 'git@github.com' [foo]: " => "foo\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == prompt_limit + @test auth_attempts == 3 + end + + # Explicitly setting these env variables to an existing but invalid key pair + # means the user will be given a prompt with that defaults to the given values. + withenv("SSH_KEY_PATH" => invalid_key, + "SSH_PUB_KEY_PATH" => invalid_key * ".pub") do + challenges = [ + "Private key location for 'git@github.com' [$invalid_key]: " => "$valid_key\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 2 + + # User repeatedly chooses the default invalid private key until prompt limit reached + challenges = [ + "Private key location for 'git@github.com' [$invalid_key]: " => "\n", + "Private key location for 'git@github.com' [$invalid_key]: " => "\n", + "Private key location for 'git@github.com' [$invalid_key]: " => "\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == prompt_limit + @test auth_attempts == 4 + end + + # Explicitly set the public key ENV variable to a non-existent file. + withenv("SSH_KEY_PATH" => valid_key, + "SSH_PUB_KEY_PATH" => valid_key * ".public") do + @test !isfile(ENV["SSH_PUB_KEY_PATH"]) + + challenges = [ + # "Private key location for 'git@github.com' [$valid_key]: " => "\n" + "Public key location for 'git@github.com' [$valid_key.public]: " => "$valid_key.pub\n" + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + end + + # Explicitly set the public key ENV variable to a public key that doesn't match + # the private key. + withenv("SSH_KEY_PATH" => valid_key, + "SSH_PUB_KEY_PATH" => invalid_key * ".pub") do + @test isfile(ENV["SSH_PUB_KEY_PATH"]) + + challenges = [ + "Private key location for 'git@github.com' [$valid_key]: " => "\n" + "Public key location for 'git@github.com' [$invalid_key.pub]: " => "$valid_key.pub\n" + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 2 + end + + Base.shred!(valid_cred) + Base.shred!(valid_p_cred) + end + + @testset "SSH known host checking" begin + CHECK_MATCH = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_MATCH + CHECK_MISMATCH = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_MISMATCH + CHECK_NOTFOUND = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_NOTFOUND + CHECK_FAILURE = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_FAILURE + + # randomly generated hashes matching no hosts + random_key = collect(reinterpret(Cchar, codeunits("\0\0\0\assh-rsa\0\0\0\x01#\0\0\0\x81\0¿\x95\xbe9\xfc9g\n:\xcf&\x06YA\xb5`\x97\xc13A\xbf;T+C\xc9Ut J>\xc5ҍ\xc4_S\x8a \xc1S\xeb\x15FH\xd2a\x04.D\xeeb\xac\x8f\xdb\xcc\xef\xc4l G\x9bR\xafp\x17s<=\x12\xab\x04ڳif\\A\x9ba0\xde%\xdei\x04\xc3\r\xb3\x81w\x88\xec\xc0f\x15A;AÝ\xc0r\xa1\u5fe\xd3\xf6)8\x8e\xa3\xcbc\xee\xdd\$\x04\x0f\xc1\xb4\x1f\xcc\xecK\xe0\x99"))) + # hashes of the unique github.com fingerprint + github_key = collect(reinterpret(Cchar, codeunits("\0\0\0\assh-rsa\0\0\0\x01#\0\0\x01\x01\0\xab`;\x85\x11\xa6vy\xbd\xb5@\xdb;\xd2\x03K\0J\xe96\xd0k\xe3\xd7`\xf0\x8f˪\xdbN\xb4\xedóǑ\xc7\n\xae\x9at\xc9Xi\xe4wD!«\xea\x92\xe5T0_8\xb5\xfdAK2\b\xe5t\xc37\xe3 \x93e\x18F,vRɋ1\xe1n}\xa6R;\xd2\0t*dD\xd8?\xcd^\x172\xd06sǷ\x81\x15UH{U\xf0\xc4IO8)\xec\xe6\x0f\x94%Z\x95˚\xf57\xd7\xfc\x8c\x7f\xe4\x9e\xf3\x18GN\xf2\x92\t\x92\x05\"e\xb0\xa0n\xa6mJ\x16\x7f\xd9\xf3\xa4\x8a\x1aJ0~\xc1\xea\xaaQI\xa9i\xa6\xac]V\xa5\xefb~Q}\x81\xfbdO[t\\OG\x8e\xcd\b*\x94\x92\xf7D\xaa\xd3&\xf7l\x8cM\xc9\x10\vƫyF\x1d&W\xcbo\x06\xde\xc9.kd\xa6V/\xf0\xe3 \x84\xea\x06\xce\x0e\xa9\xd3ZX;\xfb\0\xbaӌ\x9d\x19p github_key, + "gitlab.com" => gitlab_key, + ] + for files in [[no_file], [empty_file]] + check = LibGit2.ssh_knownhost_check(files, host, key) + @test check == CHECK_NOTFOUND + end + for files in [ + [known_hosts], + [empty_file, known_hosts], + [known_hosts, empty_file], + [known_hosts, wrong_hosts], + ] + check = LibGit2.ssh_knownhost_check(files, host, key) + @test check == CHECK_MATCH + end + for files in [ + [wrong_hosts], + [empty_file, wrong_hosts], + [wrong_hosts, empty_file], + [wrong_hosts, known_hosts], + ] + check = LibGit2.ssh_knownhost_check(files, host, key) + @test check == CHECK_MISMATCH + end + end + end + + rm(empty_file) + end + + @testset "HTTPS credential prompt" begin + url = "https://github.com/test/package.jl" + + valid_username = "julia" + valid_password = randstring(16) + valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) + + https_ex = quote + include($LIBGIT2_HELPER_PATH) + credential_loop($valid_cred, $url) + end + + # User provides a valid username and password + challenges = [ + "Username for 'https://github.com': " => "$valid_username\n", + "Password for 'https://$valid_username@github.com': " => "$valid_password\n", + ] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # User sends EOF in username prompt which aborts the credential request + challenges = [ + "Username for 'https://github.com': " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + + # User sends EOF in password prompt which aborts the credential request + challenges = [ + "Username for 'https://github.com': " => "foo\n", + "Password for 'https://foo@github.com': " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + + # User provides an empty password which aborts the credential request since we + # cannot tell it apart from an EOF. + challenges = [ + "Username for 'https://github.com': " => "foo\n", + "Password for 'https://foo@github.com': " => "\n", + ] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == abort_prompt + @test auth_attempts == 1 + + # User repeatedly chooses invalid username/password until the prompt limit is + # reached + challenges = [ + "Username for 'https://github.com': " => "foo\n", + "Password for 'https://foo@github.com': " => "bar\n", + "Username for 'https://github.com' [foo]: " => "foo\n", + "Password for 'https://foo@github.com': " => "bar\n", + "Username for 'https://github.com' [foo]: " => "foo\n", + "Password for 'https://foo@github.com': " => "bar\n", + ] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == prompt_limit + @test auth_attempts == 3 + + Base.shred!(valid_cred) + end + + @testset "SSH agent username" begin + url = "github.com:test/package.jl" + + valid_key = joinpath(KEY_DIR, "valid") + valid_cred = LibGit2.SSHCredential("git", "", valid_key, valid_key * ".pub") + + function gen_ex(; username="git") + quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload(allow_prompt=false, allow_ssh_agent=true, + allow_git_helpers=false) + credential_loop($valid_cred, $url, $username, payload) + end + end + + # An empty string username_ptr + ex = gen_ex(username="") + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == exhausted_error + @test auth_attempts == 3 + + # A null username_ptr passed into `git_cred_ssh_key_from_agent` can cause a + # segfault. + ex = gen_ex(username=nothing) + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == exhausted_error + @test auth_attempts == 2 + + Base.shred!(valid_cred) + end + + @testset "SSH default" begin + mktempdir() do home_dir + url = "github.com:test/package.jl" + + default_key = joinpath(home_dir, ".ssh", "id_rsa") + mkdir(dirname(default_key)) + + valid_key = joinpath(KEY_DIR, "valid") + valid_cred = LibGit2.SSHCredential("git", "", valid_key, valid_key * ".pub") + + valid_p_key = joinpath(KEY_DIR, "valid-passphrase") + passphrase = "secret" + valid_p_cred = LibGit2.SSHCredential("git", passphrase, valid_p_key, valid_p_key * ".pub") + + function gen_ex(cred) + quote + valid_cred = $cred + + default_cred = deepcopy(valid_cred) + default_cred.prvkey = $default_key + default_cred.pubkey = $default_key * ".pub" + + cp(valid_cred.prvkey, default_cred.prvkey) + cp(valid_cred.pubkey, default_cred.pubkey) + + try + include($LIBGIT2_HELPER_PATH) + credential_loop(default_cred, $url, "git", shred=false) + finally + rm(default_cred.prvkey) + rm(default_cred.pubkey) + end + end + end + + withenv("SSH_KEY_PATH" => nothing, + "SSH_PUB_KEY_PATH" => nothing, + "SSH_KEY_PASS" => nothing, + HOME => home_dir) do + + # Automatically use the default key + ex = gen_ex(valid_cred) + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == git_ok + @test auth_attempts == 1 + @test p.credential.prvkey == default_key + @test p.credential.pubkey == default_key * ".pub" + + # Confirm the private key if any other prompting is required + ex = gen_ex(valid_p_cred) + challenges = [ + "Private key location for 'git@github.com' [$default_key]: " => "\n", + "Passphrase for $default_key: " => "$passphrase\n", + ] + err, auth_attempts, p = challenge_prompt(ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + end + + Base.shred!(valid_cred) + Base.shred!(valid_p_cred) + end + end + + @testset "SSH expand tilde" begin + url = "git@github.com:test/package.jl" + + valid_key = joinpath(KEY_DIR, "valid") + valid_cred = LibGit2.SSHCredential("git", "", valid_key, valid_key * ".pub") + + invalid_key = joinpath(KEY_DIR, "invalid") + + ssh_ex = quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload(allow_prompt=true, allow_ssh_agent=false, + allow_git_helpers=false) + credential_loop($valid_cred, $url, "git", payload, shred=false) + end + + withenv("SSH_KEY_PATH" => nothing, + "SSH_PUB_KEY_PATH" => nothing, + "SSH_KEY_PASS" => nothing, + HOME => KEY_DIR) do + + # Expand tilde during the private key prompt + challenges = [ + "Private key location for 'git@github.com': " => "~/valid\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + @test p.credential.prvkey == abspath(valid_key) + end + + withenv("SSH_KEY_PATH" => valid_key, + "SSH_PUB_KEY_PATH" => invalid_key * ".pub", + "SSH_KEY_PASS" => nothing, + HOME => KEY_DIR) do + + # Expand tilde during the public key prompt + challenges = [ + "Private key location for 'git@github.com' [$valid_key]: " => "\n", + "Public key location for 'git@github.com' [$invalid_key.pub]: " => "~/valid.pub\n", + ] + err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 2 + @test p.credential.pubkey == abspath(valid_key * ".pub") + end + + Base.shred!(valid_cred) + end + + @testset "SSH explicit credentials" begin + url = "git@github.com:test/package.jl" + username = "git" + + valid_p_key = joinpath(KEY_DIR, "valid-passphrase") + passphrase = "secret" + valid_cred = LibGit2.SSHCredential(username, passphrase, valid_p_key, valid_p_key * ".pub") + + invalid_key = joinpath(KEY_DIR, "invalid") + invalid_cred = LibGit2.SSHCredential(username, "", invalid_key, invalid_key * ".pub") + + function gen_ex(cred; allow_prompt=true, allow_ssh_agent=false) + quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload($cred, allow_prompt=$allow_prompt, + allow_ssh_agent=$allow_ssh_agent, + allow_git_helpers=false) + credential_loop($valid_cred, $url, $username, payload) + end + end + + # Explicitly provided credential is correct. Note: allowing prompting and + # SSH agent to ensure they are skipped. + ex = gen_ex(valid_cred, allow_prompt=true, allow_ssh_agent=true) + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == git_ok + @test auth_attempts == 1 + @test p.explicit == valid_cred + @test p.credential != valid_cred + + # Explicitly provided credential is incorrect + ex = gen_ex(invalid_cred, allow_prompt=false, allow_ssh_agent=false) + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == exhausted_error + @test auth_attempts == 3 + @test p.explicit == invalid_cred + @test p.credential != invalid_cred + + Base.shred!(valid_cred) + Base.shred!(invalid_cred) + end + + @testset "HTTPS explicit credentials" begin + url = "https://github.com/test/package.jl" + + valid_cred = LibGit2.UserPasswordCredential("julia", randstring(16)) + invalid_cred = LibGit2.UserPasswordCredential("alice", randstring(15)) + + function gen_ex(cred; allow_prompt=true) + quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload($cred, allow_prompt=$allow_prompt, + allow_git_helpers=false) + credential_loop($valid_cred, $url, "", payload) + end + end + + # Explicitly provided credential is correct + ex = gen_ex(valid_cred, allow_prompt=true) + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == git_ok + @test auth_attempts == 1 + @test p.explicit == valid_cred + @test p.credential != valid_cred + + # Explicitly provided credential is incorrect + ex = gen_ex(invalid_cred, allow_prompt=false) + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == exhausted_error + @test auth_attempts == 2 + @test p.explicit == invalid_cred + @test p.credential != invalid_cred + + Base.shred!(valid_cred) + Base.shred!(invalid_cred) + end + + @testset "Cached credentials" begin + url = "https://github.com/test/package.jl" + cred_id = "https://github.com" + + valid_username = "julia" + valid_password = randstring(16) + valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) + + invalid_username = "alice" + invalid_password = randstring(15) + invalid_cred = LibGit2.UserPasswordCredential(invalid_username, invalid_password) + + function gen_ex(; cached_cred=nothing, allow_prompt=true) + quote + include($LIBGIT2_HELPER_PATH) + cache = CachedCredentials() + $(cached_cred !== nothing && :(LibGit2.approve(cache, $cached_cred, $url))) + payload = CredentialPayload(cache, allow_prompt=$allow_prompt, + allow_git_helpers=false) + credential_loop($valid_cred, $url, "", payload) + end + end + + # Cache contains a correct credential + err, auth_attempts, p = challenge_prompt(gen_ex(cached_cred=valid_cred), []) + @test err == git_ok + @test auth_attempts == 1 + + # Note: Approved cached credentials are not shredded + + # Add a credential into the cache + ex = gen_ex() + challenges = [ + "Username for 'https://github.com': " => "$valid_username\n", + "Password for 'https://$valid_username@github.com': " => "$valid_password\n", + ] + err, auth_attempts, p = challenge_prompt(ex, challenges) + cache = p.cache + @test err == git_ok + @test auth_attempts == 1 + @test typeof(cache) == LibGit2.CachedCredentials + @test cache.cred == Dict(cred_id => valid_cred) + @test p.credential == valid_cred + + # Replace a credential in the cache + ex = gen_ex(cached_cred=invalid_cred) + challenges = [ + "Username for 'https://github.com' [alice]: " => "$valid_username\n", + "Password for 'https://$valid_username@github.com': " => "$valid_password\n", + ] + err, auth_attempts, p = challenge_prompt(ex, challenges) + cache = p.cache + @test err == git_ok + @test auth_attempts == 2 + @test typeof(cache) == LibGit2.CachedCredentials + @test cache.cred == Dict(cred_id => valid_cred) + @test p.credential == valid_cred + + # Canceling a credential request should leave the cache unmodified + ex = gen_ex(cached_cred=invalid_cred) + challenges = [ + "Username for 'https://github.com' [alice]: " => "foo\n", + "Password for 'https://foo@github.com': " => "bar\n", + "Username for 'https://github.com' [foo]: " => "\x04", + ] + err, auth_attempts, p = challenge_prompt(ex, challenges) + cache = p.cache + @test err == abort_prompt + @test auth_attempts == 3 + @test typeof(cache) == LibGit2.CachedCredentials + @test cache.cred == Dict(cred_id => invalid_cred) + @test p.credential != invalid_cred + + # An EAUTH error should remove credentials from the cache + ex = gen_ex(cached_cred=invalid_cred, allow_prompt=false) + err, auth_attempts, p = challenge_prompt(ex, []) + cache = p.cache + @test err == exhausted_error + @test auth_attempts == 2 + @test typeof(cache) == LibGit2.CachedCredentials + @test cache.cred == Dict() + @test p.credential != invalid_cred + + Base.shred!(valid_cred) + Base.shred!(invalid_cred) + end + + @testset "HTTPS git helper username" begin + url = "https://github.com/test/package.jl" + + valid_username = "julia" + valid_password = randstring(16) + valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) + + config_path = joinpath(dir, config_file) + write(config_path, """ + [credential] + username = $valid_username + """) + + https_ex = quote + include($LIBGIT2_HELPER_PATH) + LibGit2.with(LibGit2.GitConfig($config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + payload = CredentialPayload(nothing, + nothing, cfg, + allow_git_helpers=true) + credential_loop($valid_cred, $url, nothing, payload, shred=false) + end + end + + # Username is supplied from the git configuration file + challenges = [ + "Username for 'https://github.com' [$valid_username]: " => "\n", + "Password for 'https://$valid_username@github.com': " => "$valid_password\n", + ] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # Verify credential wasn't accidentally zeroed (#24731) + @test p.credential == valid_cred + + Base.shred!(valid_cred) + end + + @testset "HTTPS git helper password" begin + if GIT_INSTALLED + url = "https://github.com/test/package.jl" + + valid_username = "julia" + valid_password = randstring(16) + valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) + + cred_file = joinpath(dir, "test-credentials") + config_path = joinpath(dir, config_file) + write(config_path, """ + [credential] + helper = store --file $cred_file + """) + + # Directly write to the cleartext credential store. Note: we are not using + # the LibGit2.approve message to avoid any possibility of the tests + # accidentally writing to a user's global store. + write(cred_file, "https://$valid_username:$valid_password@github.com") + + https_ex = quote + include($LIBGIT2_HELPER_PATH) + LibGit2.with(LibGit2.GitConfig($config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg + payload = CredentialPayload(nothing, + nothing, cfg, + allow_git_helpers=true) + credential_loop($valid_cred, $url, nothing, payload, shred=false) + end + end + + # Username will be provided by the credential helper + challenges = [] + err, auth_attempts, p = challenge_prompt(https_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + + # Verify credential wasn't accidentally zeroed (#24731) + @test p.credential == valid_cred + + Base.shred!(valid_cred) + end + end + + @testset "Incompatible explicit credentials" begin + # User provides a user/password credential where a SSH credential is required. + valid_cred = LibGit2.UserPasswordCredential("foo", "bar") + expect_ssh_ex = quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload($valid_cred, allow_ssh_agent=false, + allow_git_helpers=false) + credential_loop($valid_cred, "ssh://github.com/repo", "", + Cuint(LibGit2.Consts.CREDTYPE_SSH_KEY), payload) + end + + err, auth_attempts, p = challenge_prompt(expect_ssh_ex, []) + @test err == incompatible_error + @test auth_attempts == 1 + @test p.explicit == valid_cred + @test p.credential != valid_cred + + Base.shred!(valid_cred) + + # User provides a SSH credential where a user/password credential is required. + valid_cred = LibGit2.SSHCredential("foo", "", "", "") + expect_https_ex = quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload($valid_cred, allow_ssh_agent=false, + allow_git_helpers=false) + credential_loop($valid_cred, "https://github.com/repo", "", + Cuint(LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT), payload) + end + + err, auth_attempts, p = challenge_prompt(expect_https_ex, []) + @test err == incompatible_error + @test auth_attempts == 1 + @test p.explicit == valid_cred + @test p.credential != valid_cred + + Base.shred!(valid_cred) + end + + # A hypothetical scenario where the allowed authentication can either be + # SSH or username/password. + @testset "SSH & HTTPS authentication" begin + allowed_types = Cuint(LibGit2.Consts.CREDTYPE_SSH_KEY) | + Cuint(LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT) + + # User provides a user/password credential where a SSH credential is required. + valid_cred = LibGit2.UserPasswordCredential("foo", "bar") + ex = quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload($valid_cred, allow_ssh_agent=false, + allow_git_helpers=false) + credential_loop($valid_cred, "foo://github.com/repo", "", + $allowed_types, payload) + end + + err, auth_attempts, p = challenge_prompt(ex, []) + @test err == git_ok + @test auth_attempts == 1 + + Base.shred!(valid_cred) + end + + @testset "CredentialPayload reset" begin + urls = [ + "https://github.com/test/package.jl" + "https://myhost.com/demo.jl" + ] + + valid_username = "julia" + valid_password = randstring(16) + valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) + + # Users should be able to re-use the same payload if the state is reset + ex = quote + include($LIBGIT2_HELPER_PATH) + user = nothing + payload = CredentialPayload(allow_git_helpers=false) + first_result = credential_loop($valid_cred, $(urls[1]), user, payload) + LibGit2.reset!(payload) + second_result = credential_loop($valid_cred, $(urls[2]), user, payload) + (first_result, second_result) + end + + challenges = [ + "Username for 'https://github.com': " => "$valid_username\n", + "Password for 'https://$valid_username@github.com': " => "$valid_password\n", + "Username for 'https://myhost.com': " => "$valid_username\n", + "Password for 'https://$valid_username@myhost.com': " => "$valid_password\n", + ] + first_result, second_result = challenge_prompt(ex, challenges) + + err, auth_attempts, p = first_result + @test err == git_ok + @test auth_attempts == 1 + + err, auth_attempts, p = second_result + @test err == git_ok + @test auth_attempts == 1 + + Base.shred!(valid_cred) + end + end + + # Note: Tests only work on linux as SSL_CERT_FILE is only respected on linux systems. + @testset "Hostname verification" begin + openssl_installed = false + common_name = "" + if Sys.islinux() + try + # OpenSSL needs to be on the path + openssl_installed = !isempty(read(`openssl version`, String)) + catch ex + @warn "Skipping hostname verification tests. Is `openssl` on the path?" exception=ex + end + + # Find a hostname that maps to the loopback address + hostnames = ["localhost"] + + # In minimal environments a hostname might not be available (issue #20758) + try + # In some environments, namely Macs, the hostname "macbook.local" is bound + # to the external address while "macbook" is bound to the loopback address. + pushfirst!(hostnames, replace(gethostname(), r"\..*$" => "")) + catch + end + + loopbacks = (ip"127.0.0.1", ip"::1") + for hostname in hostnames + local addr + try + addr = getaddrinfo(hostname) + catch + continue + end + + if addr ∈ loopbacks + common_name = hostname + break + end + end + + if isempty(common_name) + @warn "Skipping hostname verification tests. Unable to determine a hostname which maps to the loopback address" + end + end + if openssl_installed && !isempty(common_name) + mktempdir() do root + key = joinpath(root, common_name * ".key") + cert = joinpath(root, common_name * ".crt") + pem = joinpath(root, common_name * ".pem") + + # Generated a certificate which has the CN set correctly but no subjectAltName + run(pipeline(`openssl req -new -x509 -newkey rsa:2048 -sha256 -nodes -keyout $key -out $cert -days 1 -subj "/CN=$common_name"`, stderr=devnull)) + run(`openssl x509 -in $cert -out $pem -outform PEM`) + + local pobj, port + for attempt in 1:10 + # Find an available port by listening, but there's a race condition where + # another process could grab this port, so retry on failure + port, server = listenany(49152) + close(server) + + # Make a fake Julia package and minimal HTTPS server with our generated + # certificate. The minimal server can't actually serve a Git repository. + mkdir(joinpath(root, "Example.jl")) + pobj = cd(root) do + run(pipeline(`openssl s_server -key $key -cert $cert -WWW -accept $port`, stderr=RawFD(2)), wait=false) + end + @test readuntil(pobj, "ACCEPT") == "" + + # Two options: Either we reached "ACCEPT" and the process is running and ready + # or it failed to listen and exited, in which case we try again. + process_running(pobj) && break + end + + @test process_running(pobj) + + if process_running(pobj) + errfile = joinpath(root, "error") + repo_url = "https://$common_name:$port/Example.jl" + repo_dir = joinpath(root, "dest") + code = """ + using Serialization + import LibGit2 + dest_dir = "$repo_dir" + open("$errfile", "w+") do f + try + repo = LibGit2.clone("$repo_url", dest_dir) + catch err + serialize(f, err) + finally + isdir(dest_dir) && rm(dest_dir, recursive=true) + end + end + """ + cmd = `$(Base.julia_cmd()) --startup-file=no -e $code` + + try + # The generated certificate is normally invalid + run(cmd) + err = open(errfile, "r") do f + deserialize(f) + end + @test err.code == LibGit2.Error.ERROR + @test startswith(lowercase(err.msg), + lowercase("user rejected certificate for localhost")) + + rm(errfile) + + # Specify that Julia use only the custom certificate. Note: we need to + # spawn a new Julia process in order for this ENV variable to take effect. + withenv("SSL_CERT_FILE" => pem) do + run(cmd) + err = open(errfile, "r") do f + deserialize(f) + end + @test err.code == LibGit2.Error.ERROR + @test occursin(r"invalid content-type: '?text/plain'?"i, err.msg) + end + + # OpenSSL s_server should still be running + @test process_running(pobj) + finally + kill(pobj) + end + end + end + end + end +end + +let cache = LibGit2.CachedCredentials() + get!(cache, "foo", LibGit2.SSHCredential("", "bar")) + Base.shred!(cache) + @test all(cache["foo"].pass.data .== UInt(0)) +end + +end # module diff --git a/stdlib/LibGit2/test/libgit2.jl b/stdlib/LibGit2/test/libgit2.jl index 2357536e19b5c..c78ecc8fa8bfc 100644 --- a/stdlib/LibGit2/test/libgit2.jl +++ b/stdlib/LibGit2/test/libgit2.jl @@ -1,3216 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -module LibGit2Tests - -import LibGit2 -using Test -using Random, Serialization, Sockets - -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :FakePTYs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FakePTYs.jl")) -import .Main.FakePTYs: with_fake_pty - -function challenge_prompt(code::Expr, challenges; timeout::Integer=60, debug::Bool=true) - input_code = tempname() - open(input_code, "w") do fp - serialize(fp, code) - end - output_file = tempname() - wrapped_code = quote - using Serialization - result = open($input_code) do fp - eval(deserialize(fp)) - end - open($output_file, "w") do fp - serialize(fp, result) - end - end - torun = "import LibGit2; $wrapped_code" - cmd = `$(Base.julia_cmd()) --startup-file=no -e $torun` - try - challenge_prompt(cmd, challenges, timeout=timeout, debug=debug) - return open(output_file, "r") do fp - deserialize(fp) - end - finally - isfile(output_file) && rm(output_file) - isfile(input_code) && rm(input_code) - end - return nothing -end - -function challenge_prompt(cmd::Cmd, challenges; timeout::Integer=60, debug::Bool=true) - function format_output(output) - !debug && return "" - str = read(seekstart(output), String) - isempty(str) && return "" - return "Process output found:\n\"\"\"\n$str\n\"\"\"" - end - out = IOBuffer() - with_fake_pty() do pts, ptm - p = run(detach(cmd), pts, pts, pts, wait=false) - Base.close_stdio(pts) - - # Kill the process if it takes too long. Typically occurs when process is waiting - # for input. - timer = Channel{Symbol}(1) - watcher = @async begin - waited = 0 - while waited < timeout && process_running(p) - sleep(1) - waited += 1 - end - - if process_running(p) - kill(p) - put!(timer, :timeout) - elseif success(p) - put!(timer, :success) - else - put!(timer, :failure) - end - - # SIGKILL stubborn processes - if process_running(p) - sleep(3) - process_running(p) && kill(p, Base.SIGKILL) - end - wait(p) - end - - for (challenge, response) in challenges - write(out, readuntil(ptm, challenge, keep=true)) - if !isopen(ptm) - error("Could not locate challenge: \"$challenge\". ", - format_output(out)) - end - write(ptm, response) - end - - # Capture output from process until `pts` is closed - try - write(out, ptm) - catch ex - if !(ex isa Base.IOError && ex.code == Base.UV_EIO) - rethrow() # ignore EIO from `ptm` after `pts` dies - end - end - - status = fetch(timer) - close(ptm) - if status != :success - if status == :timeout - error("Process timed out possibly waiting for a response. ", - format_output(out)) - else - error("Failed process. ", format_output(out), "\n", p) - end - end - wait(watcher) - end - nothing -end - -const LIBGIT2_MIN_VER = v"1.0.0" -const LIBGIT2_HELPER_PATH = joinpath(@__DIR__, "libgit2-helpers.jl") - -const KEY_DIR = joinpath(@__DIR__, "keys") -const HOME = Sys.iswindows() ? "USERPROFILE" : "HOME" # Environment variable name for home -const GIT_INSTALLED = try - success(`git --version`) -catch - false -end - -function get_global_dir() - buf = Ref(LibGit2.Buffer()) - - LibGit2.@check @ccall "libgit2".git_libgit2_opts( - LibGit2.Consts.GET_SEARCH_PATH::Cint; - LibGit2.Consts.CONFIG_LEVEL_GLOBAL::Cint, - buf::Ptr{LibGit2.Buffer})::Cint - path = unsafe_string(buf[].ptr) - LibGit2.free(buf) - return path -end - -function set_global_dir(dir) - LibGit2.@check @ccall "libgit2".git_libgit2_opts( - LibGit2.Consts.SET_SEARCH_PATH::Cint; - LibGit2.Consts.CONFIG_LEVEL_GLOBAL::Cint, - dir::Cstring)::Cint - return -end - -function with_libgit2_temp_home(f) - mktempdir() do tmphome - oldpath = get_global_dir() - set_global_dir(tmphome) - try - @test get_global_dir() == tmphome - f(tmphome) - finally - set_global_dir(oldpath) - end - return - end -end - -######### -# TESTS # -######### - -@testset "Check library version" begin - v = LibGit2.version() - @test v.major == LIBGIT2_MIN_VER.major && v.minor >= LIBGIT2_MIN_VER.minor -end - -@testset "Check library features" begin - f = LibGit2.features() - @test findfirst(isequal(LibGit2.Consts.FEATURE_SSH), f) !== nothing - @test findfirst(isequal(LibGit2.Consts.FEATURE_HTTPS), f) !== nothing -end - -@testset "OID" begin - z = LibGit2.GitHash() - @test LibGit2.iszero(z) - @test z == zero(LibGit2.GitHash) - @test z == LibGit2.GitHash(z) - rs = string(z) - rr = LibGit2.raw(z) - @test z == LibGit2.GitHash(rr) - @test z == LibGit2.GitHash(rs) - @test z == LibGit2.GitHash(pointer(rr)) - - @test LibGit2.GitShortHash(z, 20) == LibGit2.GitShortHash(rs[1:20]) - @test_throws ArgumentError LibGit2.GitHash(Ptr{UInt8}(C_NULL)) - @test_throws ArgumentError LibGit2.GitHash(rand(UInt8, 2*LibGit2.OID_RAWSZ)) - @test_throws ArgumentError LibGit2.GitHash("a") -end - -@testset "StrArrayStruct" begin - p = ["XXX","YYY"] - a = Base.cconvert(Ptr{LibGit2.StrArrayStruct}, p) - b = Base.unsafe_convert(Ptr{LibGit2.StrArrayStruct}, a) - @test p == convert(Vector{String}, unsafe_load(b)) - @noinline gcuse(a) = a - gcuse(a) -end - -@testset "Signature" begin - sig = LibGit2.Signature("AAA", "AAA@BBB.COM", round(time(); digits=0), 0) - git_sig = convert(LibGit2.GitSignature, sig) - sig2 = LibGit2.Signature(git_sig) - close(git_sig) - @test sig.name == sig2.name - @test sig.email == sig2.email - @test sig.time == sig2.time - sig3 = LibGit2.Signature("AAA","AAA@BBB.COM") - @test sig3.name == sig.name - @test sig3.email == sig.email -end - -@testset "Default config" begin - with_libgit2_temp_home() do tmphome - cfg = LibGit2.GitConfig() - @test isa(cfg, LibGit2.GitConfig) - @test LibGit2.getconfig("fake.property", "") == "" - LibGit2.set!(cfg, "fake.property", "AAAA") - @test LibGit2.getconfig("fake.property", "") == "AAAA" - end -end - -# See #21872 and #21636 -LibGit2.version() >= v"0.26.0" && Sys.isunix() && @testset "Default config with symlink" begin - with_libgit2_temp_home() do tmphome - write(joinpath(tmphome, "real_gitconfig"), "[fake]\n\tproperty = BBB") - symlink(joinpath(tmphome, "real_gitconfig"), - joinpath(tmphome, ".gitconfig")) - cfg = LibGit2.GitConfig() - @test isa(cfg, LibGit2.GitConfig) - LibGit2.getconfig("fake.property", "") == "BBB" - LibGit2.set!(cfg, "fake.property", "AAAA") - LibGit2.getconfig("fake.property", "") == "AAAA" - end -end - -@testset "Git URL parsing" begin - @testset "HTTPS URL" begin - m = match(LibGit2.URL_REGEX, "https://user:pass@server.com:80/org/project.git") - @test m[:scheme] == "https" - @test m[:user] == "user" - @test m[:password] == "pass" - @test m[:host] == "server.com" - @test m[:port] == "80" - @test m[:path] == "org/project.git" - end - - @testset "SSH URL" begin - m = match(LibGit2.URL_REGEX, "ssh://user:pass@server:22/project.git") - @test m[:scheme] == "ssh" - @test m[:user] == "user" - @test m[:password] == "pass" - @test m[:host] == "server" - @test m[:port] == "22" - @test m[:path] == "project.git" - end - - @testset "SSH URL, scp-like syntax" begin - m = match(LibGit2.URL_REGEX, "user@server:project.git") - @test m[:scheme] === nothing - @test m[:user] == "user" - @test m[:password] === nothing - @test m[:host] == "server" - @test m[:port] === nothing - @test m[:path] == "project.git" - end - - # scp-like syntax corner case. The SCP syntax does not support port so everything after - # the colon is part of the path. - @testset "scp-like syntax, no port" begin - m = match(LibGit2.URL_REGEX, "server:1234/repo") - @test m[:scheme] === nothing - @test m[:user] === nothing - @test m[:password] === nothing - @test m[:host] == "server" - @test m[:port] === nothing - @test m[:path] == "1234/repo" - end - - @testset "HTTPS URL, realistic" begin - m = match(LibGit2.URL_REGEX, "https://github.com/JuliaLang/Example.jl.git") - @test m[:scheme] == "https" - @test m[:user] === nothing - @test m[:password] === nothing - @test m[:host] == "github.com" - @test m[:port] === nothing - @test m[:path] == "JuliaLang/Example.jl.git" - end - - @testset "SSH URL, realistic" begin - m = match(LibGit2.URL_REGEX, "git@github.com:JuliaLang/Example.jl.git") - @test m[:scheme] === nothing - @test m[:user] == "git" - @test m[:password] === nothing - @test m[:host] == "github.com" - @test m[:port] === nothing - @test m[:path] == "JuliaLang/Example.jl.git" - end - - @testset "usernames with special characters" begin - m = match(LibGit2.URL_REGEX, "user-name@hostname.com") - @test m[:user] == "user-name" - end - - @testset "HTTPS URL, no path" begin - m = match(LibGit2.URL_REGEX, "https://user:pass@server.com:80") - @test m[:path] === nothing - end - - @testset "scp-like syntax, no path" begin - m = match(LibGit2.URL_REGEX, "user@server:") - @test m[:path] == "" - - m = match(LibGit2.URL_REGEX, "user@server") - @test m[:path] === nothing - end - - @testset "HTTPS URL, invalid path" begin - m = match(LibGit2.URL_REGEX, "https://git@server:repo") - @test m === nothing - end - - # scp-like syntax should have a colon separating the hostname from the path - @testset "scp-like syntax, invalid path" begin - m = match(LibGit2.URL_REGEX, "git@server/repo") - @test m === nothing - end -end - -@testset "Git URL formatting" begin - @testset "HTTPS URL" begin - url = LibGit2.git_url( - scheme="https", - username="user", - host="server.com", - port=80, - path="org/project.git") - @test url == "https://user@server.com:80/org/project.git" - end - - @testset "SSH URL" begin - url = LibGit2.git_url( - scheme="ssh", - username="user", - host="server", - port="22", - path="project.git") - @test url == "ssh://user@server:22/project.git" - end - - @testset "SSH URL, scp-like syntax" begin - url = LibGit2.git_url( - username="user", - host="server", - path="project.git") - @test url == "user@server:project.git" - end - - @testset "HTTPS URL, realistic" begin - url = LibGit2.git_url( - scheme="https", - host="github.com", - path="JuliaLang/Example.jl.git") - @test url == "https://github.com/JuliaLang/Example.jl.git" - end - - @testset "SSH URL, realistic" begin - url = LibGit2.git_url( - username="git", - host="github.com", - path="JuliaLang/Example.jl.git") - @test url == "git@github.com:JuliaLang/Example.jl.git" - end - - @testset "HTTPS URL, no path" begin - url = LibGit2.git_url( - scheme="https", - username="user", - host="server.com", - port="80") - @test url == "https://user@server.com:80" - end - - @testset "scp-like syntax, no path" begin - url = LibGit2.git_url( - username="user", - host="server.com") - @test url == "user@server.com" - end - - @testset "HTTP URL, path includes slash prefix" begin - url = LibGit2.git_url( - scheme="http", - host="server.com", - path="/path") - @test url == "http://server.com/path" - end - - @testset "empty" begin - @test_throws ArgumentError LibGit2.git_url() - - @test LibGit2.git_url(host="server.com") == "server.com" - url = LibGit2.git_url( - scheme="", - username="", - host="server.com", - port="", - path="") - @test url == "server.com" - end -end - -@testset "Passphrase Required" begin - @testset "missing file" begin - @test !LibGit2.is_passphrase_required("") - - file = joinpath(KEY_DIR, "foobar") - @test !isfile(file) - @test !LibGit2.is_passphrase_required(file) - end - - @testset "not private key" begin - @test !LibGit2.is_passphrase_required(joinpath(KEY_DIR, "invalid.pub")) - end - - @testset "private key, with passphrase" begin - @test LibGit2.is_passphrase_required(joinpath(KEY_DIR, "valid-passphrase")) - end - - @testset "private key, no passphrase" begin - @test !LibGit2.is_passphrase_required(joinpath(KEY_DIR, "valid")) - end -end - -@testset "GitCredential" begin - @testset "missing" begin - str = "" - cred = read!(IOBuffer(str), LibGit2.GitCredential()) - @test cred == LibGit2.GitCredential() - @test sprint(write, cred) == str - Base.shred!(cred) - end - - @testset "empty" begin - str = """ - protocol= - host= - path= - username= - password= - """ - cred = read!(IOBuffer(str), LibGit2.GitCredential()) - @test cred == LibGit2.GitCredential("", "", "", "", "") - @test sprint(write, cred) == str - Base.shred!(cred) - end - - @testset "input/output" begin - str = """ - protocol=https - host=example.com - username=alice - password=***** - """ - expected_cred = LibGit2.GitCredential("https", "example.com", nothing, "alice", "*****") - - cred = read!(IOBuffer(str), LibGit2.GitCredential()) - @test cred == expected_cred - @test sprint(write, cred) == str - Base.shred!(cred) - Base.shred!(expected_cred) - end - - @testset "extra newline" begin - # The "Git for Windows" installer will also install the "Git Credential Manager for - # Windows" (https://github.com/Microsoft/Git-Credential-Manager-for-Windows) (also - # known as "manager" in the .gitconfig files). This credential manager returns an - # additional newline when returning the results. - str = """ - protocol=https - host=example.com - path= - username=bob - password=***** - - """ - expected_cred = LibGit2.GitCredential("https", "example.com", "", "bob", "*****") - - cred = read!(IOBuffer(str), LibGit2.GitCredential()) - @test cred == expected_cred - @test sprint(write, cred) * "\n" == str - Base.shred!(cred) - Base.shred!(expected_cred) - end - - @testset "unknown attribute" begin - str = """ - protocol=https - host=example.com - attribute=value - username=bob - password=***** - """ - expected_cred = LibGit2.GitCredential("https", "example.com", nothing, "bob", "*****") - expected_log = (:warn, "Unknown git credential attribute found: \"attribute\"") - - cred = @test_logs expected_log read!(IOBuffer(str), LibGit2.GitCredential()) - @test cred == expected_cred - Base.shred!(cred) - Base.shred!(expected_cred) - end - - @testset "use http path" begin - cred = LibGit2.GitCredential("https", "example.com", "dir/file", "alice", "*****") - expected = """ - protocol=https - host=example.com - username=alice - password=***** - """ - - @test cred.use_http_path - cred.use_http_path = false - - @test cred.path == "dir/file" - @test sprint(write, cred) == expected - Base.shred!(cred) - end - - @testset "URL input/output" begin - str = """ - host=example.com - password=bar - url=https://a@b/c - username=foo - """ - expected_str = """ - protocol=https - host=b - path=c - username=foo - """ - expected_cred = LibGit2.GitCredential("https", "b", "c", "foo", nothing) - - cred = read!(IOBuffer(str), LibGit2.GitCredential()) - @test cred == expected_cred - @test sprint(write, cred) == expected_str - Base.shred!(cred) - Base.shred!(expected_cred) - end - - @testset "ismatch" begin - # Equal - cred = LibGit2.GitCredential("https", "github.com") - @test LibGit2.ismatch("https://github.com", cred) - Base.shred!(cred) - - # Credential hostname is different - cred = LibGit2.GitCredential("https", "github.com") - @test !LibGit2.ismatch("https://myhost", cred) - Base.shred!(cred) - - # Credential is less specific than URL - cred = LibGit2.GitCredential("https") - @test !LibGit2.ismatch("https://github.com", cred) - Base.shred!(cred) - - # Credential is more specific than URL - cred = LibGit2.GitCredential("https", "github.com", "path", "user", "pass") - @test LibGit2.ismatch("https://github.com", cred) - Base.shred!(cred) - - # Credential needs to have an "" username to match - cred = LibGit2.GitCredential("https", "github.com", nothing, "") - @test LibGit2.ismatch("https://@github.com", cred) - Base.shred!(cred) - - cred = LibGit2.GitCredential("https", "github.com", nothing, nothing) - @test !LibGit2.ismatch("https://@github.com", cred) - Base.shred!(cred) - end - - @testset "GITHUB_REGEX" begin - github_regex_test = function(url, user, repo) - m = match(LibGit2.GITHUB_REGEX, url) - @test m !== nothing - @test m[1] == "$user/$repo" - @test m[2] == user - @test m[3] == repo - end - user = "User" - repo = "Repo" - github_regex_test("git@github.com/$user/$repo.git", user, repo) - github_regex_test("https://github.com/$user/$repo.git", user, repo) - github_regex_test("https://username@github.com/$user/$repo.git", user, repo) - github_regex_test("ssh://git@github.com/$user/$repo.git", user, repo) - github_regex_test("git@github.com/$user/$repo", user, repo) - github_regex_test("https://github.com/$user/$repo", user, repo) - github_regex_test("https://username@github.com/$user/$repo", user, repo) - github_regex_test("ssh://git@github.com/$user/$repo", user, repo) - @test !occursin(LibGit2.GITHUB_REGEX, "git@notgithub.com/$user/$repo.git") - end - - @testset "UserPasswordCredential/url constructor" begin - user_pass_cred = LibGit2.UserPasswordCredential("user", "*******") - url = "https://github.com" - expected_cred = LibGit2.GitCredential("https", "github.com", nothing, "user", "*******") - - cred = LibGit2.GitCredential(user_pass_cred, url) - @test cred == expected_cred - - # Shredding the UserPasswordCredential shouldn't result in information being lost - # inside of a GitCredential. - Base.shred!(user_pass_cred) - @test cred == expected_cred - - Base.shred!(cred) - Base.shred!(expected_cred) - end -end - +# Set HOME to control where the .gitconfig file may be found. +# Note: In Cygwin environments `git` will use HOME instead of USERPROFILE. +# Setting both environment variables ensures home was overridden. mktempdir() do dir dir = realpath(dir) - # test parameters - repo_url = "https://github.com/JuliaLang/Example.jl" - cache_repo = joinpath(dir, "Example") - test_repo = joinpath(dir, "Example.Test") - test_sig = LibGit2.Signature("TEST", "TEST@TEST.COM", round(time(); digits=0), 0) - test_dir = "testdir" - test_file = "$(test_dir)/testfile" - config_file = "testconfig" - commit_msg1 = randstring(10) - commit_msg2 = randstring(10) - commit_oid1 = LibGit2.GitHash() - commit_oid2 = LibGit2.GitHash() - commit_oid3 = LibGit2.GitHash() - master_branch = "master" - test_branch = "test_branch" - test_branch2 = "test_branch_two" - tag1 = "tag1" - tag2 = "tag2" - - @testset "Configuration" begin - LibGit2.with(LibGit2.GitConfig(joinpath(dir, config_file), LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - @test_throws LibGit2.Error.GitError LibGit2.get(AbstractString, cfg, "tmp.str") - @test isempty(LibGit2.get(cfg, "tmp.str", "")) == true - - LibGit2.set!(cfg, "tmp.str", "AAAA") - LibGit2.set!(cfg, "tmp.int32", Int32(1)) - LibGit2.set!(cfg, "tmp.int64", Int64(1)) - LibGit2.set!(cfg, "tmp.bool", true) - - @test LibGit2.get(cfg, "tmp.str", "") == "AAAA" - @test LibGit2.get(cfg, "tmp.int32", Int32(0)) == Int32(1) - @test LibGit2.get(cfg, "tmp.int64", Int64(0)) == Int64(1) - @test LibGit2.get(cfg, "tmp.bool", false) == true - - # Ordering of entries appears random when using `LibGit2.set!` - count = 0 - for entry in LibGit2.GitConfigIter(cfg, r"tmp.*") - count += 1 - name, value = unsafe_string(entry.name), unsafe_string(entry.value) - if name == "tmp.str" - @test value == "AAAA" - elseif name == "tmp.int32" - @test value == "1" - elseif name == "tmp.int64" - @test value == "1" - elseif name == "tmp.bool" - @test value == "true" - else - error("Found unexpected entry: $name") - end - show_str = sprint(show, entry) - @test show_str == string("ConfigEntry(\"", name, "\", \"", value, "\")") - end - @test count == 4 - end - end - - @testset "Configuration Iteration" begin - config_path = joinpath(dir, config_file) - - # Write config entries with duplicate names - open(config_path, "a") do fp - write(fp, """ - [credential] - helper = store - username = julia - [credential] - helper = cache - """) - end - - LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - # Will only see the last entry - @test LibGit2.get(cfg, "credential.helper", "") == "cache" - - count = 0 - for entry in LibGit2.GitConfigIter(cfg, "credential.helper") - count += 1 - name, value = unsafe_string(entry.name), unsafe_string(entry.value) - @test name == "credential.helper" - @test value == (count == 1 ? "store" : "cache") - end - @test count == 2 - end - end - - @testset "Initializing repository" begin - @testset "with remote branch" begin - LibGit2.with(LibGit2.init(cache_repo)) do repo - @test isdir(cache_repo) - @test LibGit2.path(repo) == LibGit2.posixpath(realpath(cache_repo)) - @test isdir(joinpath(cache_repo, ".git")) - # set a remote branch - branch = "upstream" - LibGit2.GitRemote(repo, branch, repo_url) |> close - - # test remote's representation in the repo's config - config = joinpath(cache_repo, ".git", "config") - lines = split(open(x->read(x, String), config, "r"), "\n") - @test any(map(x->x == "[remote \"upstream\"]", lines)) - - LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote - # test various remote properties - @test LibGit2.url(remote) == repo_url - @test LibGit2.push_url(remote) == "" - @test LibGit2.name(remote) == "upstream" - @test isa(remote, LibGit2.GitRemote) - - # test showing a GitRemote object - @test sprint(show, remote) == "GitRemote:\nRemote name: upstream url: $repo_url" - end - # test setting and getting the remote's URL - @test LibGit2.isattached(repo) - LibGit2.set_remote_url(repo, "upstream", "unknown") - LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote - @test LibGit2.url(remote) == "unknown" - @test LibGit2.push_url(remote) == "unknown" - @test sprint(show, remote) == "GitRemote:\nRemote name: upstream url: unknown" - end - LibGit2.set_remote_url(cache_repo, "upstream", repo_url) - LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote - @test LibGit2.url(remote) == repo_url - @test LibGit2.push_url(remote) == repo_url - @test sprint(show, remote) == "GitRemote:\nRemote name: upstream url: $repo_url" - LibGit2.add_fetch!(repo, remote, "upstream") - - # test setting fetch and push refspecs - @test LibGit2.fetch_refspecs(remote) == String["+refs/heads/*:refs/remotes/upstream/*"] - LibGit2.add_push!(repo, remote, "refs/heads/master") - end - LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, branch)) do remote - @test LibGit2.push_refspecs(remote) == String["refs/heads/master"] - end - # constructor with a refspec - LibGit2.with(LibGit2.GitRemote(repo, "upstream2", repo_url, "upstream")) do remote - @test sprint(show, remote) == "GitRemote:\nRemote name: upstream2 url: $repo_url" - @test LibGit2.fetch_refspecs(remote) == String["upstream"] - end - - LibGit2.with(LibGit2.GitRemoteAnon(repo, repo_url)) do remote - @test LibGit2.url(remote) == repo_url - @test LibGit2.push_url(remote) == "" - @test LibGit2.name(remote) == "" - @test isa(remote, LibGit2.GitRemote) - end - end - end - - @testset "bare" begin - path = joinpath(dir, "Example.Bare") - LibGit2.with(LibGit2.init(path, true)) do repo - @test isdir(path) - @test LibGit2.path(repo) == LibGit2.posixpath(realpath(path)) - @test isfile(joinpath(path, LibGit2.Consts.HEAD_FILE)) - @test LibGit2.isattached(repo) - end - - path = joinpath("garbagefakery", "Example.Bare") - try - LibGit2.GitRepo(path) - error("unexpected") - catch e - @test typeof(e) == LibGit2.GitError - @test startswith( - lowercase(sprint(show, e)), - lowercase("GitError(Code:ENOTFOUND, Class:OS, failed to resolve path")) - end - path = joinpath(dir, "Example.BareTwo") - LibGit2.with(LibGit2.init(path, true)) do repo - #just to see if this works - LibGit2.cleanup(repo) - end - end - end - - @testset "Cloning repository" begin - function bare_repo_tests(repo, repo_path) - @test isdir(repo_path) - @test LibGit2.path(repo) == LibGit2.posixpath(realpath(repo_path)) - @test isfile(joinpath(repo_path, LibGit2.Consts.HEAD_FILE)) - @test LibGit2.isattached(repo) - @test LibGit2.remotes(repo) == ["origin"] - end - @testset "bare" begin - repo_path = joinpath(dir, "Example.Bare1") - LibGit2.with(LibGit2.clone(cache_repo, repo_path, isbare = true)) do repo - bare_repo_tests(repo, repo_path) - end - end - @testset "bare with remote callback" begin - repo_path = joinpath(dir, "Example.Bare2") - LibGit2.with(LibGit2.clone(cache_repo, repo_path, isbare = true, remote_cb = LibGit2.mirror_cb())) do repo - bare_repo_tests(repo, repo_path) - LibGit2.with(LibGit2.get(LibGit2.GitRemote, repo, "origin")) do rmt - @test LibGit2.fetch_refspecs(rmt)[1] == "+refs/*:refs/*" - end - end - end - @testset "normal" begin - LibGit2.with(LibGit2.clone(cache_repo, test_repo)) do repo - @test isdir(test_repo) - @test LibGit2.path(repo) == LibGit2.posixpath(realpath(test_repo)) - @test isdir(joinpath(test_repo, ".git")) - @test LibGit2.workdir(repo) == LibGit2.path(repo)*"/" - @test LibGit2.isattached(repo) - @test LibGit2.isorphan(repo) - repo_str = sprint(show, repo) - @test repo_str == "LibGit2.GitRepo($(sprint(show,LibGit2.path(repo))))" - end - end - @testset "credentials callback conflict" begin - callbacks = LibGit2.Callbacks(:credentials => (C_NULL, 0)) - cred_payload = LibGit2.CredentialPayload() - @test_throws ArgumentError LibGit2.clone(cache_repo, test_repo, callbacks=callbacks, credentials=cred_payload) - end - end - - @testset "Update cache repository" begin - - @testset "with commits" begin - repo = LibGit2.GitRepo(cache_repo) - repo_dir = joinpath(cache_repo,test_dir) - mkdir(repo_dir) - repo_file = open(joinpath(cache_repo,test_file), "a") - try - # create commits - println(repo_file, commit_msg1) - flush(repo_file) - LibGit2.add!(repo, test_file) - @test LibGit2.iszero(commit_oid1) - commit_oid1 = LibGit2.commit(repo, commit_msg1; author=test_sig, committer=test_sig) - @test !LibGit2.iszero(commit_oid1) - @test LibGit2.GitHash(LibGit2.head(cache_repo)) == commit_oid1 - - println(repo_file, randstring(10)) - flush(repo_file) - LibGit2.add!(repo, test_file) - commit_oid3 = LibGit2.commit(repo, randstring(10); author=test_sig, committer=test_sig) - - println(repo_file, commit_msg2) - flush(repo_file) - LibGit2.add!(repo, test_file) - @test LibGit2.iszero(commit_oid2) - commit_oid2 = LibGit2.commit(repo, commit_msg2; author=test_sig, committer=test_sig) - @test !LibGit2.iszero(commit_oid2) - - # test getting list of commit authors - auths = LibGit2.authors(repo) - @test length(auths) == 3 - for auth in auths - @test auth.name == test_sig.name - @test auth.time == test_sig.time - @test auth.email == test_sig.email - end - - # check various commit properties - commit_oid1 happened before - # commit_oid2, so it *is* an ancestor of commit_oid2 - @test LibGit2.is_ancestor_of(string(commit_oid1), string(commit_oid2), repo) - @test LibGit2.iscommit(string(commit_oid1), repo) - @test !LibGit2.iscommit(string(commit_oid1)*"fake", repo) - @test LibGit2.iscommit(string(commit_oid2), repo) - - # lookup commits - LibGit2.with(LibGit2.GitCommit(repo, commit_oid1)) do cmt - @test LibGit2.Consts.OBJECT(typeof(cmt)) == LibGit2.Consts.OBJ_COMMIT - @test commit_oid1 == LibGit2.GitHash(cmt) - short_oid1 = LibGit2.GitShortHash(string(commit_oid1)) - @test string(commit_oid1) == string(short_oid1) - @test cmp(commit_oid1, short_oid1) == 0 - @test cmp(short_oid1, commit_oid1) == 0 - @test !(short_oid1 < commit_oid1) - - # test showing ShortHash - short_str = sprint(show, short_oid1) - @test short_str == "GitShortHash(\"$(string(short_oid1))\")" - short_oid2 = LibGit2.GitShortHash(cmt) - @test startswith(string(commit_oid1), string(short_oid2)) - - LibGit2.with(LibGit2.GitCommit(repo, short_oid2)) do cmt2 - @test commit_oid1 == LibGit2.GitHash(cmt2) - end - # check that the author and committer signatures are correct - auth = LibGit2.author(cmt) - @test isa(auth, LibGit2.Signature) - @test auth.name == test_sig.name - @test auth.time == test_sig.time - @test auth.email == test_sig.email - short_auth = LibGit2.author(LibGit2.GitCommit(repo, short_oid1)) - @test short_auth.name == test_sig.name - @test short_auth.time == test_sig.time - @test short_auth.email == test_sig.email - cmtr = LibGit2.committer(cmt) - @test isa(cmtr, LibGit2.Signature) - @test cmtr.name == test_sig.name - @test cmtr.time == test_sig.time - @test cmtr.email == test_sig.email - @test LibGit2.message(cmt) == commit_msg1 - - # test showing the commit - showstr = split(sprint(show, cmt), "\n") - # the time of the commit will vary so just test the first two parts - @test occursin("Git Commit:", showstr[1]) - @test occursin("Commit Author: Name: TEST, Email: TEST@TEST.COM, Time:", showstr[2]) - @test occursin("Committer: Name: TEST, Email: TEST@TEST.COM, Time:", showstr[3]) - @test occursin("SHA:", showstr[4]) - @test showstr[5] == "Message:" - @test showstr[6] == commit_msg1 - @test LibGit2.revcount(repo, string(commit_oid1), string(commit_oid3)) == (-1,0) - - blame = LibGit2.GitBlame(repo, test_file) - @test LibGit2.counthunks(blame) == 3 - @test_throws BoundsError getindex(blame, LibGit2.counthunks(blame)+1) - @test_throws BoundsError getindex(blame, 0) - sig = LibGit2.Signature(blame[1].orig_signature) - @test sig.name == cmtr.name - @test sig.email == cmtr.email - show_strs = split(sprint(show, blame[1]), "\n") - @test show_strs[1] == "GitBlameHunk:" - @test show_strs[2] == "Original path: $test_file" - @test show_strs[3] == "Lines in hunk: 1" - @test show_strs[4] == "Final commit oid: $commit_oid1" - @test show_strs[6] == "Original commit oid: $commit_oid1" - @test length(show_strs) == 7 - end - finally - close(repo) - close(repo_file) - end - end - - @testset "with branch" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - brnch = LibGit2.branch(repo) - LibGit2.with(LibGit2.head(repo)) do brref - # various branch properties - @test LibGit2.isbranch(brref) - @test !LibGit2.isremote(brref) - @test LibGit2.name(brref) == "refs/heads/master" - @test LibGit2.shortname(brref) == master_branch - @test LibGit2.ishead(brref) - @test LibGit2.upstream(brref) === nothing - - # showing the GitReference to this branch - show_strs = split(sprint(show, brref), "\n") - @test show_strs[1] == "GitReference:" - @test show_strs[2] == "Branch with name refs/heads/master" - @test show_strs[3] == "Branch is HEAD." - @test repo.ptr == LibGit2.repository(brref).ptr - @test brnch == master_branch - @test LibGit2.headname(repo) == master_branch - - # create a branch *without* setting its tip as HEAD - LibGit2.branch!(repo, test_branch, string(commit_oid1), set_head=false) - # null because we are looking for a REMOTE branch - @test LibGit2.lookup_branch(repo, test_branch, true) === nothing - # not nothing because we are now looking for a LOCAL branch - LibGit2.with(LibGit2.lookup_branch(repo, test_branch, false)) do tbref - @test LibGit2.shortname(tbref) == test_branch - @test LibGit2.upstream(tbref) === nothing - end - @test LibGit2.lookup_branch(repo, test_branch2, true) === nothing - # test deleting the branch - LibGit2.branch!(repo, test_branch2; set_head=false) - LibGit2.with(LibGit2.lookup_branch(repo, test_branch2, false)) do tbref - @test LibGit2.shortname(tbref) == test_branch2 - LibGit2.delete_branch(tbref) - @test LibGit2.lookup_branch(repo, test_branch2, true) === nothing - end - end - branches = map(b->LibGit2.shortname(b[1]), LibGit2.GitBranchIter(repo)) - @test master_branch in branches - @test test_branch in branches - end - end - - @testset "with default configuration" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - try - LibGit2.Signature(repo) - catch ex - # these test configure repo with new signature - # in case when global one does not exsist - @test isa(ex, LibGit2.Error.GitError) == true - - cfg = LibGit2.GitConfig(repo) - LibGit2.set!(cfg, "user.name", "AAAA") - LibGit2.set!(cfg, "user.email", "BBBB@BBBB.COM") - sig = LibGit2.Signature(repo) - @test sig.name == "AAAA" - @test sig.email == "BBBB@BBBB.COM" - @test LibGit2.getconfig(repo, "user.name", "") == "AAAA" - @test LibGit2.getconfig(cache_repo, "user.name", "") == "AAAA" - end - end - end - - @testset "with tags" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - tags = LibGit2.tag_list(repo) - @test length(tags) == 0 - - # create tag and extract it from a GitReference - tag_oid1 = LibGit2.tag_create(repo, tag1, commit_oid1, sig=test_sig) - @test !LibGit2.iszero(tag_oid1) - tags = LibGit2.tag_list(repo) - @test length(tags) == 1 - @test tag1 in tags - tag1ref = LibGit2.GitReference(repo, "refs/tags/$tag1") - # because this is a reference to an OID - @test isempty(LibGit2.fullname(tag1ref)) - - # test showing a GitReference to a GitTag, and the GitTag itself - show_strs = split(sprint(show, tag1ref), "\n") - @test show_strs[1] == "GitReference:" - @test show_strs[2] == "Tag with name refs/tags/$tag1" - tag1tag = LibGit2.peel(LibGit2.GitTag, tag1ref) - @test LibGit2.name(tag1tag) == tag1 - @test LibGit2.target(tag1tag) == commit_oid1 - @test sprint(show, tag1tag) == "GitTag:\nTag name: $tag1 target: $commit_oid1" - # peels to the commit the tag points to - tag1cmt = LibGit2.peel(tag1ref) - @test LibGit2.GitHash(tag1cmt) == commit_oid1 - tag_oid2 = LibGit2.tag_create(repo, tag2, commit_oid2) - @test !LibGit2.iszero(tag_oid2) - tags = LibGit2.tag_list(repo) - @test length(tags) == 2 - @test tag2 in tags - - refs = LibGit2.ref_list(repo) - @test refs == ["refs/heads/master", "refs/heads/test_branch", "refs/tags/tag1", "refs/tags/tag2"] - # test deleting a tag - LibGit2.tag_delete(repo, tag1) - tags = LibGit2.tag_list(repo) - @test length(tags) == 1 - @test tag2 ∈ tags - @test tag1 ∉ tags - - # test git describe functions applied to these GitTags - description = LibGit2.GitDescribeResult(repo) - fmtted_description = LibGit2.format(description) - @test sprint(show, description) == "GitDescribeResult:\n$fmtted_description\n" - @test fmtted_description == "tag2" - description = LibGit2.GitDescribeResult(LibGit2.GitObject(repo, "HEAD")) - fmtted_description = LibGit2.format(description) - @test sprint(show, description) == "GitDescribeResult:\n$fmtted_description\n" - @test fmtted_description == "tag2" - end - end - - @testset "status" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - status = LibGit2.GitStatus(repo) - @test length(status) == 0 - @test_throws BoundsError status[1] - repo_file = open(joinpath(cache_repo,"statusfile"), "a") - - # create commits - println(repo_file, commit_msg1) - flush(repo_file) - LibGit2.add!(repo, test_file) - status = LibGit2.GitStatus(repo) - @test length(status) != 0 - @test_throws BoundsError status[0] - @test_throws BoundsError status[length(status)+1] - # we've added a file - show that it is new - @test status[1].status == LibGit2.Consts.STATUS_WT_NEW - close(repo_file) - end - end - - @testset "blobs" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - # this is slightly dubious, as it assumes the object has not been packed - # could be replaced by another binary format - hash_string = string(commit_oid1) - blob_file = joinpath(cache_repo,".git/objects", hash_string[1:2], hash_string[3:end]) - - id = LibGit2.addblob!(repo, blob_file) - blob = LibGit2.GitBlob(repo, id) - @test LibGit2.isbinary(blob) - len1 = length(blob) - - # test showing a GitBlob - blob_show_strs = split(sprint(show, blob), "\n") - @test blob_show_strs[1] == "GitBlob:" - @test occursin("Blob id:", blob_show_strs[2]) - @test blob_show_strs[3] == "Contents are binary." - - blob2 = LibGit2.GitBlob(repo, LibGit2.GitHash(blob)) - @test LibGit2.isbinary(blob2) - @test length(blob2) == len1 - @test blob == blob2 - @test blob !== blob2 - end - end - @testset "trees" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - @test_throws LibGit2.Error.GitError LibGit2.GitTree(repo, "HEAD") - tree = LibGit2.GitTree(repo, "HEAD^{tree}") - @test isa(tree, LibGit2.GitTree) - @test isa(LibGit2.GitObject(repo, "HEAD^{tree}"), LibGit2.GitTree) - @test LibGit2.Consts.OBJECT(typeof(tree)) == LibGit2.Consts.OBJ_TREE - @test LibGit2.count(tree) == 1 - - # test showing the GitTree and its entries - tree_str = sprint(show, tree) - @test tree_str == "GitTree:\nOwner: $(LibGit2.repository(tree))\nNumber of entries: 1\n" - @test_throws BoundsError tree[0] - @test_throws BoundsError tree[2] - tree_entry = tree[1] - subtree = LibGit2.GitTree(tree_entry) - @test_throws BoundsError subtree[0] - @test_throws BoundsError subtree[2] - tree_entry = subtree[1] - @test LibGit2.filemode(tree_entry) == 33188 - te_str = sprint(show, tree_entry) - ref_te_str = "GitTreeEntry:\nEntry name: testfile\nEntry type: LibGit2.GitBlob\nEntry OID: " - ref_te_str *= "$(LibGit2.entryid(tree_entry))\n" - @test te_str == ref_te_str - blob = LibGit2.GitBlob(tree_entry) - blob_str = sprint(show, blob) - @test blob_str == "GitBlob:\nBlob id: $(LibGit2.GitHash(blob))\nContents:\n$(LibGit2.content(blob))\n" - - # tests for walking the tree and accessing objects - @test tree[""] == tree - @test tree["/"] == tree - @test isa(tree[test_dir], LibGit2.GitTree) - @test tree["$test_dir/"] == tree[test_dir] - @test isa(tree[test_file], LibGit2.GitBlob) - @test_throws KeyError tree["nonexistent"] - - # test workaround for git_tree_walk issue - # https://github.com/libgit2/libgit2/issues/4693 - ccall((:giterr_set_str, :libgit2), Cvoid, (Cint, Cstring), - Cint(LibGit2.Error.Invalid), "previous error") - try - # file needs to exist in tree in order to trigger the stop walk condition - tree[test_file] - catch err - if isa(err, LibGit2.Error.GitError) && err.class == LibGit2.Error.Invalid - @test false - else - rethrow() - end - end - end - end - - @testset "diff" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - @test !LibGit2.isdirty(repo) - @test !LibGit2.isdirty(repo, test_file) - @test !LibGit2.isdirty(repo, "nonexistent") - @test !LibGit2.isdiff(repo, "HEAD") - @test !LibGit2.isdirty(repo, cached=true) - @test !LibGit2.isdirty(repo, test_file, cached=true) - @test !LibGit2.isdirty(repo, "nonexistent", cached=true) - @test !LibGit2.isdiff(repo, "HEAD", cached=true) - open(joinpath(cache_repo,test_file), "a") do f - println(f, "zzzz") - end - @test LibGit2.isdirty(repo) - @test LibGit2.isdirty(repo, test_file) - @test !LibGit2.isdirty(repo, "nonexistent") - @test LibGit2.isdiff(repo, "HEAD") - @test !LibGit2.isdirty(repo, cached=true) - @test !LibGit2.isdiff(repo, "HEAD", cached=true) - LibGit2.add!(repo, test_file) - @test LibGit2.isdirty(repo) - @test LibGit2.isdiff(repo, "HEAD") - @test LibGit2.isdirty(repo, cached=true) - @test LibGit2.isdiff(repo, "HEAD", cached=true) - tree = LibGit2.GitTree(repo, "HEAD^{tree}") - - # test properties of the diff_tree - diff = LibGit2.diff_tree(repo, tree, "", cached=true) - @test LibGit2.count(diff) == 1 - @test_throws BoundsError diff[0] - @test_throws BoundsError diff[2] - @test LibGit2.Consts.DELTA_STATUS(diff[1].status) == LibGit2.Consts.DELTA_MODIFIED - @test diff[1].nfiles == 2 - - # test showing a DiffDelta - diff_strs = split(sprint(show, diff[1]), '\n') - @test diff_strs[1] == "DiffDelta:" - @test diff_strs[2] == "Status: DELTA_MODIFIED" - @test diff_strs[3] == "Number of files: 2" - @test diff_strs[4] == "Old file:" - @test diff_strs[5] == "DiffFile:" - @test occursin("Oid:", diff_strs[6]) - @test occursin("Path:", diff_strs[7]) - @test occursin("Size:", diff_strs[8]) - @test isempty(diff_strs[9]) - @test diff_strs[10] == "New file:" - - # test showing a GitDiff - diff_strs = split(sprint(show, diff), '\n') - @test diff_strs[1] == "GitDiff:" - @test diff_strs[2] == "Number of deltas: 1" - @test diff_strs[3] == "GitDiffStats:" - @test diff_strs[4] == "Files changed: 1" - @test diff_strs[5] == "Insertions: 1" - @test diff_strs[6] == "Deletions: 0" - - LibGit2.commit(repo, "zzz") - @test !LibGit2.isdirty(repo) - @test !LibGit2.isdiff(repo, "HEAD") - @test !LibGit2.isdirty(repo, cached=true) - @test !LibGit2.isdiff(repo, "HEAD", cached=true) - end - end - end - - function setup_clone_repo(cache_repo::AbstractString, path::AbstractString; name="AAAA", email="BBBB@BBBB.COM") - repo = LibGit2.clone(cache_repo, path) - # need to set this for merges to succeed - cfg = LibGit2.GitConfig(repo) - LibGit2.set!(cfg, "user.name", name) - LibGit2.set!(cfg, "user.email", email) - return repo - end - # TO DO: add more tests for various merge - # preference options - function add_and_commit_file(repo, filenm, filecontent) - open(joinpath(LibGit2.path(repo), filenm),"w") do f - write(f, filecontent) - end - LibGit2.add!(repo, filenm) - return LibGit2.commit(repo, "add $filenm") - end - @testset "Fastforward merges" begin - LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.FF"))) do repo - # Sets up a branch "branch/ff_a" which will be two commits ahead - # of "master". It's possible to fast-forward merge "branch/ff_a" - # into "master", which is the default behavior. - oldhead = LibGit2.head_oid(repo) - LibGit2.branch!(repo, "branch/ff_a") - add_and_commit_file(repo, "ff_file1", "111\n") - add_and_commit_file(repo, "ff_file2", "222\n") - LibGit2.branch!(repo, "master") - # switch back, now try to ff-merge the changes - # from branch/a - # set up the merge using GitAnnotated objects - upst_ann = LibGit2.GitAnnotated(repo, "branch/ff_a") - head_ann = LibGit2.GitAnnotated(repo, "master") - - # ff merge them - @test LibGit2.merge!(repo, [upst_ann], true) - @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) - - # Repeat the process, but specifying a commit to merge in as opposed - # to a branch name or GitAnnotated. - oldhead = LibGit2.head_oid(repo) - LibGit2.branch!(repo, "branch/ff_b") - add_and_commit_file(repo, "ff_file3", "333\n") - branchhead = add_and_commit_file(repo, "ff_file4", "444\n") - LibGit2.branch!(repo, "master") - # switch back, now try to ff-merge the changes - # from branch/a using committish - @test LibGit2.merge!(repo, committish=string(branchhead)) - @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) - - # Repeat the process, but specifying a branch name to merge in as opposed - # to a commit or GitAnnotated. - oldhead = LibGit2.head_oid(repo) - LibGit2.branch!(repo, "branch/ff_c") - add_and_commit_file(repo, "ff_file5", "555\n") - branchhead = add_and_commit_file(repo, "ff_file6", "666\n") - LibGit2.branch!(repo, "master") - # switch back, now try to ff-merge the changes - # from branch/ff_c using branch name - @test LibGit2.merge!(repo, branch="refs/heads/branch/ff_c") - @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) - - LibGit2.branch!(repo, "branch/ff_d") - branchhead = add_and_commit_file(repo, "ff_file7", "777\n") - LibGit2.branch!(repo, "master") - # switch back, now try to ff-merge the changes - # from branch/a - # set up the merge using GitAnnotated objects - # from a fetchhead - fh = LibGit2.fetchheads(repo) - upst_ann = LibGit2.GitAnnotated(repo, fh[1]) - @test LibGit2.merge!(repo, [upst_ann], true) - @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) - end - end - - @testset "Cherrypick" begin - LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.Cherrypick"))) do repo - # Create a commit on the new branch and cherry-pick it over to - # master. Since the cherry-pick does *not* make a new commit on - # master, we have to create our own commit of the dirty state. - oldhead = LibGit2.head_oid(repo) - LibGit2.branch!(repo, "branch/cherry_a") - cmt_oid = add_and_commit_file(repo, "file1", "111\n") - cmt = LibGit2.GitCommit(repo, cmt_oid) - # switch back, try to cherrypick - # from branch/cherry_a - LibGit2.branch!(repo, "master") - LibGit2.cherrypick(repo, cmt, options=LibGit2.CherrypickOptions()) - cmt_oid2 = LibGit2.commit(repo, "add file1") - @test isempty(LibGit2.diff_files(repo, "master", "branch/cherry_a")) - end - end - - @testset "Merges" begin - LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.Merge"))) do repo - oldhead = LibGit2.head_oid(repo) - LibGit2.branch!(repo, "branch/merge_a") - add_and_commit_file(repo, "file1", "111\n") - # switch back, add a commit, try to merge - # from branch/merge_a - LibGit2.branch!(repo, "master") - - # test for showing a Reference to a non-HEAD branch - brref = LibGit2.GitReference(repo, "refs/heads/branch/merge_a") - @test LibGit2.name(brref) == "refs/heads/branch/merge_a" - @test !LibGit2.ishead(brref) - show_strs = split(sprint(show, brref), "\n") - @test show_strs[1] == "GitReference:" - @test show_strs[2] == "Branch with name refs/heads/branch/merge_a" - @test show_strs[3] == "Branch is not HEAD." - - add_and_commit_file(repo, "file2", "222\n") - upst_ann = LibGit2.GitAnnotated(repo, "branch/merge_a") - head_ann = LibGit2.GitAnnotated(repo, "master") - - # (fail to) merge them because we can't fastforward - @test_logs (:warn,"Cannot perform fast-forward merge") !LibGit2.merge!(repo, [upst_ann], true) - # merge them now that we allow non-ff - @test_logs (:info,"Review and commit merged changes") LibGit2.merge!(repo, [upst_ann], false) - @test LibGit2.is_ancestor_of(string(oldhead), string(LibGit2.head_oid(repo)), repo) - - # go back to merge_a and rename a file - LibGit2.branch!(repo, "branch/merge_b") - mv(joinpath(LibGit2.path(repo),"file1"),joinpath(LibGit2.path(repo),"mvfile1")) - LibGit2.add!(repo, "mvfile1") - LibGit2.commit(repo, "move file1") - LibGit2.branch!(repo, "master") - upst_ann = LibGit2.GitAnnotated(repo, "branch/merge_b") - rename_flag = Cint(0) - rename_flag = LibGit2.toggle(rename_flag, Cint(0)) # turns on the find renames opt - mos = LibGit2.MergeOptions(flags=rename_flag) - @test_logs (:info,"Review and commit merged changes") LibGit2.merge!(repo, [upst_ann], merge_opts=mos) - end - end - - @testset "push" begin - up_path = joinpath(dir, "Example.PushUp") - up_repo = setup_clone_repo(cache_repo, up_path) - our_repo = setup_clone_repo(cache_repo, joinpath(dir, "Example.Push")) - try - add_and_commit_file(our_repo, "file1", "111\n") - if LibGit2.version() >= v"0.26.0" # See #21872, #21639 and #21597 - # we cannot yet locally push to non-bare repos - @test_throws LibGit2.GitError LibGit2.push(our_repo, remoteurl=up_path) - end - finally - close(our_repo) - close(up_repo) - end - - @testset "credentials callback conflict" begin - callbacks = LibGit2.Callbacks(:credentials => (C_NULL, 0)) - cred_payload = LibGit2.CredentialPayload() - - LibGit2.with(LibGit2.GitRepo(joinpath(dir, "Example.Push"))) do repo - @test_throws ArgumentError LibGit2.push(repo, callbacks=callbacks, credentials=cred_payload) - end - end - end - - @testset "Show closed repo" begin - # Make sure this doesn't crash - buf = IOBuffer() - Base.show(buf, LibGit2.with(identity, LibGit2.GitRepo(test_repo))) - @test String(take!(buf)) == "LibGit2.GitRepo()" - end - - @testset "Fetch from cache repository" begin - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - # fetch changes - @test LibGit2.fetch(repo) == 0 - @test !isfile(joinpath(test_repo, test_file)) - - # ff merge them - @test LibGit2.merge!(repo, fastforward=true) - - # because there was not any file we need to reset branch - head_oid = LibGit2.head_oid(repo) - new_head = LibGit2.reset!(repo, head_oid, LibGit2.Consts.RESET_HARD) - @test isfile(joinpath(test_repo, test_file)) - @test new_head == head_oid - - # GitAnnotated for a fetchhead - fh_ann = LibGit2.GitAnnotated(repo, LibGit2.Consts.FETCH_HEAD) - @test LibGit2.GitHash(fh_ann) == head_oid - - # Detach HEAD - no merge - LibGit2.checkout!(repo, string(commit_oid3)) - @test_throws LibGit2.Error.GitError LibGit2.merge!(repo, fastforward=true) - - # Switch to a branch without remote - no merge - LibGit2.branch!(repo, test_branch) - @test_throws LibGit2.Error.GitError LibGit2.merge!(repo, fastforward=true) - - # Set the username and email for the test_repo (needed for rebase) - cfg = LibGit2.GitConfig(repo) - LibGit2.set!(cfg, "user.name", "AAAA") - LibGit2.set!(cfg, "user.email", "BBBB@BBBB.COM") - - # If upstream argument is empty, libgit2 will look for tracking - # information. If the current branch isn't tracking any upstream - # the rebase should fail. - @test_throws LibGit2.GitError LibGit2.rebase!(repo) - # Try rebasing on master instead - newhead = LibGit2.rebase!(repo, master_branch) - @test newhead == head_oid - - # Switch to the master branch - LibGit2.branch!(repo, master_branch) - - fetch_heads = LibGit2.fetchheads(repo) - @test fetch_heads[1].name == "refs/heads/master" - @test fetch_heads[1].ismerge == true # we just merged master - @test fetch_heads[2].name == "refs/heads/test_branch" - @test fetch_heads[2].ismerge == false - @test fetch_heads[3].name == "refs/tags/tag2" - @test fetch_heads[3].ismerge == false - for fh in fetch_heads - @test fh.url == cache_repo - fh_strs = split(sprint(show, fh), '\n') - @test fh_strs[1] == "FetchHead:" - @test fh_strs[2] == "Name: $(fh.name)" - @test fh_strs[3] == "URL: $(fh.url)" - @test fh_strs[5] == "Merged: $(fh.ismerge)" - end - end - - @testset "credentials callback conflict" begin - callbacks = LibGit2.Callbacks(:credentials => (C_NULL, 0)) - cred_payload = LibGit2.CredentialPayload() - - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - @test_throws ArgumentError LibGit2.fetch(repo, callbacks=callbacks, credentials=cred_payload) - end - end - end - - @testset "Examine test repository" begin - @testset "files" begin - @test read(joinpath(test_repo, test_file), String) == read(joinpath(cache_repo, test_file), String) - end - - @testset "tags & branches" begin - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - # all tag in place - tags = LibGit2.tag_list(repo) - @test length(tags) == 1 - @test tag2 in tags - - # all tag in place - branches = map(b->LibGit2.shortname(b[1]), LibGit2.GitBranchIter(repo)) - @test master_branch in branches - @test test_branch in branches - - # issue #16337 - LibGit2.with(LibGit2.GitReference(repo, "refs/tags/$tag2")) do tag2ref - @test_throws LibGit2.Error.GitError LibGit2.upstream(tag2ref) - end - end - end - - @testset "commits with revwalk" begin - repo = LibGit2.GitRepo(test_repo) - cache = LibGit2.GitRepo(cache_repo) - try - # test map with oid - oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker - LibGit2.map((oid,repo)->(oid,repo), walker, oid=commit_oid1, by=LibGit2.Consts.SORT_TIME) - end - @test length(oids) == 1 - # test map with range - str_1 = string(commit_oid1) - str_3 = string(commit_oid3) - oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker - LibGit2.map((oid,repo)->(oid,repo), walker, range="$str_1..$str_3", by=LibGit2.Consts.SORT_TIME) - end - @test length(oids) == 1 - - test_oids = LibGit2.with(LibGit2.GitRevWalker(repo)) do walker - LibGit2.map((oid,repo)->string(oid), walker, by = LibGit2.Consts.SORT_TIME) - end - cache_oids = LibGit2.with(LibGit2.GitRevWalker(cache)) do walker - LibGit2.map((oid,repo)->string(oid), walker, by = LibGit2.Consts.SORT_TIME) - end - for i in eachindex(oids) - @test cache_oids[i] == test_oids[i] - end - # test with specified oid - LibGit2.with(LibGit2.GitRevWalker(repo)) do walker - @test LibGit2.count((oid,repo)->(oid == commit_oid1), walker, oid=commit_oid1, by=LibGit2.Consts.SORT_TIME) == 1 - end - # test without specified oid - LibGit2.with(LibGit2.GitRevWalker(repo)) do walker - @test LibGit2.count((oid,repo)->(oid == commit_oid1), walker, by=LibGit2.Consts.SORT_TIME) == 1 - end - finally - close(repo) - close(cache) - end - end - end - - @testset "Modify and reset repository" begin - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - # check index for file - LibGit2.with(LibGit2.GitIndex(repo)) do idx - i = findall(test_file, idx) - @test i !== nothing - idx_entry = idx[i] - @test idx_entry !== nothing - idx_entry_str = sprint(show, idx_entry) - @test idx_entry_str == "IndexEntry($(string(idx_entry.id)))" - @test LibGit2.stage(idx_entry) == 0 - - i = findall("zzz", idx) - @test i === nothing - idx_str = sprint(show, idx) - @test idx_str == "GitIndex:\nRepository: $(LibGit2.repository(idx))\nNumber of elements: 1\n" - - LibGit2.remove!(repo, test_file) - LibGit2.read!(repo) - @test LibGit2.count(idx) == 0 - LibGit2.add!(repo, test_file) - LibGit2.update!(repo, test_file) - @test LibGit2.count(idx) == 1 - end - - # check non-existent file status - st = LibGit2.status(repo, "XYZ") - @test st === nothing - - # check file status - st = LibGit2.status(repo, test_file) - @test st !== nothing - @test LibGit2.isset(st, LibGit2.Consts.STATUS_CURRENT) - - # modify file - open(joinpath(test_repo, test_file), "a") do io - write(io, 0x41) - end - - # file modified but not staged - st_mod = LibGit2.status(repo, test_file) - @test !LibGit2.isset(st_mod, LibGit2.Consts.STATUS_INDEX_MODIFIED) - @test LibGit2.isset(st_mod, LibGit2.Consts.STATUS_WT_MODIFIED) - - # stage file - LibGit2.add!(repo, test_file) - - # modified file staged - st_stg = LibGit2.status(repo, test_file) - @test LibGit2.isset(st_stg, LibGit2.Consts.STATUS_INDEX_MODIFIED) - @test !LibGit2.isset(st_stg, LibGit2.Consts.STATUS_WT_MODIFIED) - - # try to unstage to unknown commit - @test_throws LibGit2.Error.GitError LibGit2.reset!(repo, "XYZ", test_file) - - # status should not change - st_new = LibGit2.status(repo, test_file) - @test st_new == st_stg - - # try to unstage to HEAD - new_head = LibGit2.reset!(repo, LibGit2.Consts.HEAD_FILE, test_file) - st_uns = LibGit2.status(repo, test_file) - @test st_uns == st_mod - - # reset repo - @test_throws LibGit2.Error.GitError LibGit2.reset!(repo, LibGit2.GitHash(), LibGit2.Consts.RESET_HARD) - - new_head = LibGit2.reset!(repo, LibGit2.head_oid(repo), LibGit2.Consts.RESET_HARD) - open(joinpath(test_repo, test_file), "r") do io - @test read(io)[end] != 0x41 - end - end + withenv("HOME" => dir, "USERPROFILE" => dir) do + include("libgit2-tests.jl") end - - @testset "Modify remote" begin - path = test_repo - LibGit2.with(LibGit2.GitRepo(path)) do repo - remote_name = "test" - url = "https://test.com/repo" - - @test LibGit2.lookup_remote(repo, remote_name) === nothing - - for r in (repo, path) - # Set just the fetch URL - LibGit2.set_remote_fetch_url(r, remote_name, url) - remote = LibGit2.lookup_remote(repo, remote_name) - @test LibGit2.name(remote) == remote_name - @test LibGit2.url(remote) == url - @test LibGit2.push_url(remote) == "" - - LibGit2.remote_delete(repo, remote_name) - @test LibGit2.lookup_remote(repo, remote_name) === nothing - - # Set just the push URL - LibGit2.set_remote_push_url(r, remote_name, url) - remote = LibGit2.lookup_remote(repo, remote_name) - @test LibGit2.name(remote) == remote_name - @test LibGit2.url(remote) == "" - @test LibGit2.push_url(remote) == url - - LibGit2.remote_delete(repo, remote_name) - @test LibGit2.lookup_remote(repo, remote_name) === nothing - - # Set the fetch and push URL - LibGit2.set_remote_url(r, remote_name, url) - remote = LibGit2.lookup_remote(repo, remote_name) - @test LibGit2.name(remote) == remote_name - @test LibGit2.url(remote) == url - @test LibGit2.push_url(remote) == url - - LibGit2.remote_delete(repo, remote_name) - @test LibGit2.lookup_remote(repo, remote_name) === nothing - end - # Invalid remote name - @test_throws LibGit2.GitError LibGit2.set_remote_url(repo, "", url) - @test_throws LibGit2.GitError LibGit2.set_remote_url(repo, remote_name, "") - end - end - - @testset "rebase" begin - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - LibGit2.branch!(repo, "branch/a") - - oldhead = LibGit2.head_oid(repo) - add_and_commit_file(repo, "file1", "111\n") - add_and_commit_file(repo, "file2", "222\n") - LibGit2.branch!(repo, "branch/b") - - # squash last 2 commits - new_head = LibGit2.reset!(repo, oldhead, LibGit2.Consts.RESET_SOFT) - @test new_head == oldhead - LibGit2.commit(repo, "squash file1 and file2") - - # add another file - newhead = add_and_commit_file(repo, "file3", "333\n") - @test LibGit2.diff_files(repo, "branch/a", "branch/b", filter=Set([LibGit2.Consts.DELTA_ADDED])) == ["file3"] - @test LibGit2.diff_files(repo, "branch/a", "branch/b", filter=Set([LibGit2.Consts.DELTA_MODIFIED])) == [] - # switch back and rebase - LibGit2.branch!(repo, "branch/a") - newnewhead = LibGit2.rebase!(repo, "branch/b") - - # issue #19624 - @test newnewhead == newhead - - # add yet another file - add_and_commit_file(repo, "file4", "444\n") - # rebase with onto - newhead = LibGit2.rebase!(repo, "branch/a", "master") - - newerhead = LibGit2.head_oid(repo) - @test newerhead == newhead - - # add yet more files - add_and_commit_file(repo, "file5", "555\n") - pre_abort_head = add_and_commit_file(repo, "file6", "666\n") - # Rebase type - head_ann = LibGit2.GitAnnotated(repo, "branch/a") - upst_ann = LibGit2.GitAnnotated(repo, "master") - rb = LibGit2.GitRebase(repo, head_ann, upst_ann) - @test_throws BoundsError rb[3] - @test_throws BoundsError rb[0] - rbo, _ = iterate(rb) - rbo_str = sprint(show, rbo) - @test rbo_str == "RebaseOperation($(string(rbo.id)))\nOperation type: REBASE_OPERATION_PICK\n" - rb_str = sprint(show, rb) - @test rb_str == "GitRebase:\nNumber: 2\nCurrently performing operation: 1\n" - rbo = rb[2] - rbo_str = sprint(show, rbo) - @test rbo_str == "RebaseOperation($(string(rbo.id)))\nOperation type: REBASE_OPERATION_PICK\n" - - # test rebase abort - LibGit2.abort(rb) - @test LibGit2.head_oid(repo) == pre_abort_head - end - end - - @testset "merge" begin - LibGit2.with(setup_clone_repo(cache_repo, joinpath(dir, "Example.simple_merge"))) do repo - LibGit2.branch!(repo, "branch/merge_a") - - a_head = LibGit2.head_oid(repo) - add_and_commit_file(repo, "merge_file1", "111\n") - LibGit2.branch!(repo, "master") - a_head_ann = LibGit2.GitAnnotated(repo, "branch/merge_a") - # merge returns true if successful - @test_logs (:info,"Review and commit merged changes") LibGit2.merge!(repo, [a_head_ann]) - end - end - - @testset "Transact test repository" begin - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - cp(joinpath(test_repo, test_file), joinpath(test_repo, "CCC")) - cp(joinpath(test_repo, test_file), joinpath(test_repo, "AAA")) - LibGit2.add!(repo, "AAA") - @test_throws ErrorException LibGit2.transact(repo) do trepo - mv(joinpath(test_repo, test_file), joinpath(test_repo, "BBB")) - LibGit2.add!(trepo, "BBB") - oid = LibGit2.commit(trepo, "test commit"; author=test_sig, committer=test_sig) - error("Force recovery") - end - @test isfile(joinpath(test_repo, "AAA")) - @test isfile(joinpath(test_repo, "CCC")) - @test !isfile(joinpath(test_repo, "BBB")) - @test isfile(joinpath(test_repo, test_file)) - end - end - - @testset "checkout_head" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - # modify file - repo_file = open(joinpath(cache_repo,test_file), "a") - println(repo_file, commit_msg1 * randstring(10)) - close(repo_file) - # and checkout HEAD once more - LibGit2.checkout_head(repo, options=LibGit2.CheckoutOptions(checkout_strategy=LibGit2.Consts.CHECKOUT_FORCE)) - @test LibGit2.headname(repo) == master_branch - @test !LibGit2.isdirty(repo) - end - end - - @testset "checkout/headname" begin - LibGit2.with(LibGit2.GitRepo(cache_repo)) do repo - LibGit2.checkout!(repo, string(commit_oid1)) - @test !LibGit2.isattached(repo) - @test LibGit2.headname(repo) == "(detached from $(string(commit_oid1)[1:7]))" - end - end - - if Sys.isunix() - @testset "checkout/proptest" begin - LibGit2.with(LibGit2.GitRepo(test_repo)) do repo - cp(joinpath(test_repo, test_file), joinpath(test_repo, "proptest")) - LibGit2.add!(repo, "proptest") - id1 = LibGit2.commit(repo, "test property change 1") - # change in file permissions (#17610) - chmod(joinpath(test_repo, "proptest"),0o744) - LibGit2.add!(repo, "proptest") - id2 = LibGit2.commit(repo, "test property change 2") - LibGit2.checkout!(repo, string(id1)) - @test !LibGit2.isdirty(repo) - # change file to symlink (#18420) - mv(joinpath(test_repo, "proptest"), joinpath(test_repo, "proptest2")) - symlink(joinpath(test_repo, "proptest2"), joinpath(test_repo, "proptest")) - LibGit2.add!(repo, "proptest", "proptest2") - id3 = LibGit2.commit(repo, "test symlink change") - LibGit2.checkout!(repo, string(id1)) - @test !LibGit2.isdirty(repo) - end - end - end - - - @testset "Credentials" begin - creds_user = "USER" - creds_pass = Base.SecretBuffer("PASS") - creds = LibGit2.UserPasswordCredential(creds_user, creds_pass) - @test creds.user == creds_user - @test creds.pass == creds_pass - creds2 = LibGit2.UserPasswordCredential(creds_user, creds_pass) - @test creds == creds2 - - sshcreds = LibGit2.SSHCredential(creds_user, creds_pass) - @test sshcreds.user == creds_user - @test sshcreds.pass == creds_pass - @test sshcreds.prvkey == "" - @test sshcreds.pubkey == "" - sshcreds2 = LibGit2.SSHCredential(creds_user, creds_pass) - @test sshcreds == sshcreds2 - - Base.shred!(creds) - Base.shred!(creds2) - Base.shred!(sshcreds) - Base.shred!(sshcreds2) - Base.shred!(creds_pass) - end - - @testset "CachedCredentials" begin - cache = LibGit2.CachedCredentials() - - url = "https://github.com/JuliaLang/Example.jl" - cred_id = LibGit2.credential_identifier(url) - cred = LibGit2.UserPasswordCredential("julia", "password") - - @test !haskey(cache, cred_id) - password = Base.SecretBuffer("password") - - # Attempt to reject a credential which wasn't stored - LibGit2.reject(cache, cred, url) - @test !haskey(cache, cred_id) - @test cred.user == "julia" - @test cred.pass == password - - # Approve a credential which causes it to be stored - LibGit2.approve(cache, cred, url) - @test haskey(cache, cred_id) - @test cache[cred_id] === cred - - # Approve the same credential again which does not overwrite - LibGit2.approve(cache, cred, url) - @test haskey(cache, cred_id) - @test cache[cred_id] === cred - - # Overwrite an already cached credential - dup_cred = deepcopy(cred) - LibGit2.approve(cache, dup_cred, url) # Shreds overwritten `cred` - @test haskey(cache, cred_id) - @test cache[cred_id] === dup_cred - @test cred.user != "julia" - @test cred.pass != password - @test dup_cred.user == "julia" - @test dup_cred.pass == password - - cred = dup_cred - - # Reject an approved credential - @test cache[cred_id] === cred - LibGit2.reject(cache, cred, url) # Avoids shredding the credential passed in - @test !haskey(cache, cred_id) - @test cred.user == "julia" - @test cred.pass == password - - # Reject and shred an approved credential - dup_cred = deepcopy(cred) - LibGit2.approve(cache, cred, url) - - LibGit2.reject(cache, dup_cred, url) # Shred `cred` but not passed in `dup_cred` - @test !haskey(cache, cred_id) - @test cred.user != "julia" - @test cred.pass != password - @test dup_cred.user == "julia" - @test dup_cred.pass == password - - Base.shred!(dup_cred) - Base.shred!(cache) - Base.shred!(password) - end - - @testset "Git credential username" begin - @testset "fill username" begin - config_path = joinpath(dir, config_file) - isfile(config_path) && rm(config_path) - - LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - # No credential settings should be set for these tests - @test isempty(collect(LibGit2.GitConfigIter(cfg, r"credential.*"))) - - github_cred = LibGit2.GitCredential("https", "github.com") - mygit_cred = LibGit2.GitCredential("https", "mygithost") - - # No credential settings in configuration. - username = LibGit2.default_username(cfg, github_cred) - @test username === nothing - - # Add a credential setting for a specific for a URL - LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.username", "foo") - - username = LibGit2.default_username(cfg, github_cred) - @test username == "foo" - - username = LibGit2.default_username(cfg, mygit_cred) - @test username === nothing - - # Add a global credential setting after the URL specific setting. The first - # setting to match will be the one that is used. - LibGit2.set!(cfg, "credential.username", "bar") - - username = LibGit2.default_username(cfg, github_cred) - @test username == "foo" - - username = LibGit2.default_username(cfg, mygit_cred) - @test username == "bar" - - Base.shred!(github_cred) - Base.shred!(mygit_cred) - end - end - - @testset "empty username" begin - config_path = joinpath(dir, config_file) - isfile(config_path) && rm(config_path) - - LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - # No credential settings should be set for these tests - @test isempty(collect(LibGit2.GitConfigIter(cfg, r"credential.*"))) - - # An empty username should count as being set - LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.username", "") - LibGit2.set!(cfg, "credential.username", "name") - - github_cred = LibGit2.GitCredential("https", "github.com") - mygit_cred = LibGit2.GitCredential("https", "mygithost", "path") - - username = LibGit2.default_username(cfg, github_cred) - @test username == "" - - username = LibGit2.default_username(cfg, mygit_cred) - @test username == "name" - - Base.shred!(github_cred) - Base.shred!(mygit_cred) - end - end - end - - @testset "Git helpers useHttpPath" begin - @testset "use_http_path" begin - config_path = joinpath(dir, config_file) - isfile(config_path) && rm(config_path) - - LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - # No credential settings should be set for these tests - @test isempty(collect(LibGit2.GitConfigIter(cfg, r"credential.*"))) - - github_cred = LibGit2.GitCredential("https", "github.com") - mygit_cred = LibGit2.GitCredential("https", "mygithost") - - # No credential settings in configuration. - @test !LibGit2.use_http_path(cfg, github_cred) - @test !LibGit2.use_http_path(cfg, mygit_cred) - - # Add a credential setting for a specific for a URL - LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.useHttpPath", "true") - - @test LibGit2.use_http_path(cfg, github_cred) - @test !LibGit2.use_http_path(cfg, mygit_cred) - - # Invert the current settings. - LibGit2.set!(cfg, "credential.useHttpPath", "true") - LibGit2.set!(cfg, "credential.https://github.zerozr99.workers.dev.useHttpPath", "false") - - @test !LibGit2.use_http_path(cfg, github_cred) - @test LibGit2.use_http_path(cfg, mygit_cred) - - Base.shred!(github_cred) - Base.shred!(mygit_cred) - end - end - end - - @testset "GitCredentialHelper" begin - GitCredentialHelper = LibGit2.GitCredentialHelper - GitCredential = LibGit2.GitCredential - - @testset "parse" begin - @test parse(GitCredentialHelper, "!echo hello") == GitCredentialHelper(`echo hello`) - @test parse(GitCredentialHelper, "/bin/bash") == GitCredentialHelper(`/bin/bash`) - @test parse(GitCredentialHelper, "store") == GitCredentialHelper(`git credential-store`) - end - - @testset "empty helper" begin - config_path = joinpath(dir, config_file) - - # Note: LibGit2.set! doesn't allow us to set duplicates or ordering - open(config_path, "w+") do fp - write(fp, """ - [credential] - helper = !echo first - [credential "https://mygithost"] - helper = "" - [credential] - helper = !echo second - """) - end - - LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - iter = LibGit2.GitConfigIter(cfg, r"credential.*\.helper") - @test LibGit2.split_cfg_entry.(iter) == [ - ("credential", "", "helper", "!echo first"), - ("credential", "https://mygithost", "helper", ""), - ("credential", "", "helper", "!echo second"), - ] - - expected = [ - GitCredentialHelper(`echo first`), - GitCredentialHelper(`echo second`), - ] - - github_cred = GitCredential("https", "github.com") - mygit_cred = GitCredential("https", "mygithost") - - @test LibGit2.credential_helpers(cfg, github_cred) == expected - @test LibGit2.credential_helpers(cfg, mygit_cred) == expected[2:2] - - Base.shred!(github_cred) - Base.shred!(mygit_cred) - end - end - - @testset "approve/reject" begin - # In order to use the "store" credential helper `git` needs to be installed and - # on the path. - if GIT_INSTALLED - credential_path = joinpath(dir, ".git-credentials") - isfile(credential_path) && rm(credential_path) - - # Requires `git` to be installed and available on the path. - helper = parse(LibGit2.GitCredentialHelper, "store") - - # Set HOME to control where the .git-credentials file is written. - # Note: In Cygwin environments `git` will use HOME instead of USERPROFILE. - # Setting both environment variables ensures home was overridden. - withenv("HOME" => dir, "USERPROFILE" => dir) do - query = LibGit2.GitCredential("https", "mygithost") - filled = LibGit2.GitCredential("https", "mygithost", nothing, "bob", "s3cre7") - - @test !isfile(credential_path) - - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == query - end - - LibGit2.approve(helper, filled) - @test isfile(credential_path) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == filled - end - - LibGit2.reject(helper, filled) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == query - end - - Base.shred!(query) - Base.shred!(filled) - end - end - end - - @testset "approve/reject with path" begin - # In order to use the "store" credential helper `git` needs to be installed and - # on the path. - if GIT_INSTALLED - credential_path = joinpath(dir, ".git-credentials") - isfile(credential_path) && rm(credential_path) - - # Requires `git` to be installed and available on the path. - helper = parse(LibGit2.GitCredentialHelper, "store") - - # Set HOME to control where the .git-credentials file is written. - # Note: In Cygwin environments `git` will use HOME instead of USERPROFILE. - # Setting both environment variables ensures home was overridden. - withenv("HOME" => dir, "USERPROFILE" => dir) do - query = LibGit2.GitCredential("https", "mygithost") - query_a = LibGit2.GitCredential("https", "mygithost", "a") - query_b = LibGit2.GitCredential("https", "mygithost", "b") - - filled_a = LibGit2.GitCredential("https", "mygithost", "a", "alice", "1234") - filled_b = LibGit2.GitCredential("https", "mygithost", "b", "bob", "s3cre7") - - function without_path(cred) - c = deepcopy(cred) - c.path = nothing - c - end - - filled_without_path_a = without_path(filled_a) - filled_without_path_b = without_path(filled_b) - - @test !isfile(credential_path) - - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == query - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result - @test result == query_a - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result - @test result == query_b - end - - LibGit2.approve(helper, filled_a) - @test isfile(credential_path) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == filled_without_path_a - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result - @test result == filled_a - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result - @test result == query_b - end - - LibGit2.approve(helper, filled_b) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == filled_without_path_b - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result - @test result == filled_a - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result - @test result == filled_b - end - - LibGit2.reject(helper, filled_b) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == filled_without_path_a - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_a))) do result - @test result == filled_a - end - Base.shred!(LibGit2.fill!(helper, deepcopy(query_b))) do result - @test result == query_b - end - - Base.shred!(query) - Base.shred!(query_a) - Base.shred!(query_b) - Base.shred!(filled_a) - Base.shred!(filled_b) - Base.shred!(filled_without_path_a) - Base.shred!(filled_without_path_b) - end - end - end - - @testset "approve/reject with UserPasswordCredential" begin - # In order to use the "store" credential helper `git` needs to be installed and - # on the path. - if GIT_INSTALLED - config_path = joinpath(dir, config_file) - isfile(config_path) && rm(config_path) - - credential_path = joinpath(dir, ".git-credentials") - isfile(credential_path) && rm(credential_path) - - LibGit2.with(LibGit2.GitConfig(config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - query = LibGit2.GitCredential("https", "mygithost") - filled = LibGit2.GitCredential("https", "mygithost", nothing, "alice", "1234") - user_pass_cred = LibGit2.UserPasswordCredential("alice", "1234") - url = "https://mygithost" - - # Requires `git` to be installed and available on the path. - LibGit2.set!(cfg, "credential.helper", "store --file \"$credential_path\"") - helper = only(LibGit2.credential_helpers(cfg, query)) - - @test !isfile(credential_path) - - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == query - end - - LibGit2.approve(cfg, user_pass_cred, url) - @test isfile(credential_path) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == filled - end - - LibGit2.reject(cfg, user_pass_cred, url) - Base.shred!(LibGit2.fill!(helper, deepcopy(query))) do result - @test result == query - end - - Base.shred!(query) - Base.shred!(filled) - Base.shred!(user_pass_cred) - end - end - end - end - - # The following tests require that we can fake a TTY so that we can provide passwords - # which use the `getpass` function. At the moment we can only fake this on UNIX based - # systems. - if Sys.isunix() - git_ok = LibGit2.GitError( - LibGit2.Error.None, LibGit2.Error.GIT_OK, - "No errors") - - abort_prompt = LibGit2.GitError( - LibGit2.Error.Callback, LibGit2.Error.EUSER, - "Aborting, user cancelled credential request.") - - prompt_limit = LibGit2.GitError( - LibGit2.Error.Callback, LibGit2.Error.EAUTH, - "Aborting, maximum number of prompts reached.") - - incompatible_error = LibGit2.GitError( - LibGit2.Error.Callback, LibGit2.Error.EAUTH, - "The explicitly provided credential is incompatible with the requested " * - "authentication methods.") - - exhausted_error = LibGit2.GitError( - LibGit2.Error.Callback, LibGit2.Error.EAUTH, - "All authentication methods have failed.") - - @testset "SSH credential prompt" begin - url = "git@github.com:test/package.jl" - username = "git" - - valid_key = joinpath(KEY_DIR, "valid") - valid_cred = LibGit2.SSHCredential(username, "", valid_key, valid_key * ".pub") - - valid_p_key = joinpath(KEY_DIR, "valid-passphrase") - passphrase = "secret" - valid_p_cred = LibGit2.SSHCredential(username, passphrase, valid_p_key, valid_p_key * ".pub") - - invalid_key = joinpath(KEY_DIR, "invalid") - - function gen_ex(cred; username="git") - url = username !== nothing && !isempty(username) ? "$username@" : "" - url *= "github.com:test/package.jl" - quote - include($LIBGIT2_HELPER_PATH) - credential_loop($cred, $url, $username) - end - end - - ssh_ex = gen_ex(valid_cred) - ssh_p_ex = gen_ex(valid_p_cred) - ssh_u_ex = gen_ex(valid_cred, username=nothing) - - # Note: We cannot use the default ~/.ssh/id_rsa for tests since we cannot be - # sure a users will actually have these files. Instead we will use the ENV - # variables to set the default values. - - # ENV credentials are valid - withenv("SSH_KEY_PATH" => valid_key) do - err, auth_attempts, p = challenge_prompt(ssh_ex, []) - @test err == git_ok - @test auth_attempts == 1 - end - - # ENV credentials are valid but requires a passphrase - withenv("SSH_KEY_PATH" => valid_p_key) do - challenges = [ - "Passphrase for $valid_p_key: " => "$passphrase\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # User mistypes passphrase. - # Note: In reality LibGit2 will raise an error upon using the invalid SSH - # credentials. Since we don't control the internals of LibGit2 though they - # could also just re-call the credential callback like they do for HTTP. - challenges = [ - "Passphrase for $valid_p_key: " => "foo\n", - "Private key location for 'git@github.com' [$valid_p_key]: " => "\n", - "Passphrase for $valid_p_key: " => "$passphrase\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) - @test err == git_ok - @test auth_attempts == 2 - - # User sends EOF in passphrase prompt which aborts the credential request - challenges = [ - "Passphrase for $valid_p_key: " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - - # User provides an empty passphrase - challenges = [ - "Passphrase for $valid_p_key: " => "\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - end - - # ENV credential requiring passphrase - withenv("SSH_KEY_PATH" => valid_p_key, "SSH_KEY_PASS" => passphrase) do - err, auth_attempts, p = challenge_prompt(ssh_p_ex, []) - @test err == git_ok - @test auth_attempts == 1 - end - - # Missing username - withenv("SSH_KEY_PATH" => valid_key) do - # User provides a valid username - challenges = [ - "Username for 'github.com': " => "$username\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # User sends EOF in username prompt which aborts the credential request - challenges = [ - "Username for 'github.com': " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - - # User provides an empty username - challenges = [ - "Username for 'github.com': " => "\n", - "Username for 'github.com': " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 2 - - # User repeatedly chooses an invalid username - challenges = [ - "Username for 'github.com': " => "foo\n", - "Username for 'github.com' [foo]: " => "\n", - "Private key location for 'foo@github.com' [$valid_key]: " => "\n", - "Username for 'github.com' [foo]: " => "\x04", # Need to manually abort - ] - err, auth_attempts, p = challenge_prompt(ssh_u_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 3 - - # Credential callback is given an empty string in the `username_ptr` - # instead of the C_NULL in the other missing username tests. - ssh_user_empty_ex = gen_ex(valid_cred, username="") - challenges = [ - "Username for 'github.com': " => "$username\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_user_empty_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - end - - # Explicitly setting these env variables to be empty means the user will be - # given a prompt with no defaults set. - withenv("SSH_KEY_PATH" => nothing, - "SSH_PUB_KEY_PATH" => nothing, - "SSH_KEY_PASS" => nothing, - HOME => dir) do - - # Set the USERPROFILE / HOME above to be a directory that does not contain - # the "~/.ssh/id_rsa" file. If this file exists the credential callback - # will default to use this private key instead of triggering a prompt. - @test !isfile(joinpath(homedir(), ".ssh", "id_rsa")) - - # User provides valid credentials - challenges = [ - "Private key location for 'git@github.com': " => "$valid_key\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # User provides valid credentials that requires a passphrase - challenges = [ - "Private key location for 'git@github.com': " => "$valid_p_key\n", - "Passphrase for $valid_p_key: " => "$passphrase\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_p_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # User sends EOF in private key prompt which aborts the credential request - challenges = [ - "Private key location for 'git@github.com': " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - - # User provides an empty private key which triggers a re-prompt - challenges = [ - "Private key location for 'git@github.com': " => "\n", - "Private key location for 'git@github.com': " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 2 - - # User provides an invalid private key until prompt limit reached. - # Note: the prompt should not supply an invalid default. - challenges = [ - "Private key location for 'git@github.com': " => "foo\n", - "Private key location for 'git@github.com' [foo]: " => "foo\n", - "Private key location for 'git@github.com' [foo]: " => "foo\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == prompt_limit - @test auth_attempts == 3 - end - - # Explicitly setting these env variables to an existing but invalid key pair - # means the user will be given a prompt with that defaults to the given values. - withenv("SSH_KEY_PATH" => invalid_key, - "SSH_PUB_KEY_PATH" => invalid_key * ".pub") do - challenges = [ - "Private key location for 'git@github.com' [$invalid_key]: " => "$valid_key\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == git_ok - @test auth_attempts == 2 - - # User repeatedly chooses the default invalid private key until prompt limit reached - challenges = [ - "Private key location for 'git@github.com' [$invalid_key]: " => "\n", - "Private key location for 'git@github.com' [$invalid_key]: " => "\n", - "Private key location for 'git@github.com' [$invalid_key]: " => "\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == prompt_limit - @test auth_attempts == 4 - end - - # Explicitly set the public key ENV variable to a non-existent file. - withenv("SSH_KEY_PATH" => valid_key, - "SSH_PUB_KEY_PATH" => valid_key * ".public") do - @test !isfile(ENV["SSH_PUB_KEY_PATH"]) - - challenges = [ - # "Private key location for 'git@github.com' [$valid_key]: " => "\n" - "Public key location for 'git@github.com' [$valid_key.public]: " => "$valid_key.pub\n" - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - end - - # Explicitly set the public key ENV variable to a public key that doesn't match - # the private key. - withenv("SSH_KEY_PATH" => valid_key, - "SSH_PUB_KEY_PATH" => invalid_key * ".pub") do - @test isfile(ENV["SSH_PUB_KEY_PATH"]) - - challenges = [ - "Private key location for 'git@github.com' [$valid_key]: " => "\n" - "Public key location for 'git@github.com' [$invalid_key.pub]: " => "$valid_key.pub\n" - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == git_ok - @test auth_attempts == 2 - end - - Base.shred!(valid_cred) - Base.shred!(valid_p_cred) - end - - @testset "SSH known host checking" begin - CHECK_MATCH = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_MATCH - CHECK_MISMATCH = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_MISMATCH - CHECK_NOTFOUND = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_NOTFOUND - CHECK_FAILURE = LibGit2.Consts.LIBSSH2_KNOWNHOST_CHECK_FAILURE - - # randomly generated hashes matching no hosts - random_key = collect(reinterpret(Cchar, codeunits("\0\0\0\assh-rsa\0\0\0\x01#\0\0\0\x81\0¿\x95\xbe9\xfc9g\n:\xcf&\x06YA\xb5`\x97\xc13A\xbf;T+C\xc9Ut J>\xc5ҍ\xc4_S\x8a \xc1S\xeb\x15FH\xd2a\x04.D\xeeb\xac\x8f\xdb\xcc\xef\xc4l G\x9bR\xafp\x17s<=\x12\xab\x04ڳif\\A\x9ba0\xde%\xdei\x04\xc3\r\xb3\x81w\x88\xec\xc0f\x15A;AÝ\xc0r\xa1\u5fe\xd3\xf6)8\x8e\xa3\xcbc\xee\xdd\$\x04\x0f\xc1\xb4\x1f\xcc\xecK\xe0\x99"))) - # hashes of the unique github.com fingerprint - github_key = collect(reinterpret(Cchar, codeunits("\0\0\0\assh-rsa\0\0\0\x01#\0\0\x01\x01\0\xab`;\x85\x11\xa6vy\xbd\xb5@\xdb;\xd2\x03K\0J\xe96\xd0k\xe3\xd7`\xf0\x8f˪\xdbN\xb4\xedóǑ\xc7\n\xae\x9at\xc9Xi\xe4wD!«\xea\x92\xe5T0_8\xb5\xfdAK2\b\xe5t\xc37\xe3 \x93e\x18F,vRɋ1\xe1n}\xa6R;\xd2\0t*dD\xd8?\xcd^\x172\xd06sǷ\x81\x15UH{U\xf0\xc4IO8)\xec\xe6\x0f\x94%Z\x95˚\xf57\xd7\xfc\x8c\x7f\xe4\x9e\xf3\x18GN\xf2\x92\t\x92\x05\"e\xb0\xa0n\xa6mJ\x16\x7f\xd9\xf3\xa4\x8a\x1aJ0~\xc1\xea\xaaQI\xa9i\xa6\xac]V\xa5\xefb~Q}\x81\xfbdO[t\\OG\x8e\xcd\b*\x94\x92\xf7D\xaa\xd3&\xf7l\x8cM\xc9\x10\vƫyF\x1d&W\xcbo\x06\xde\xc9.kd\xa6V/\xf0\xe3 \x84\xea\x06\xce\x0e\xa9\xd3ZX;\xfb\0\xbaӌ\x9d\x19p github_key, - "gitlab.com" => gitlab_key, - ] - for files in [[no_file], [empty_file]] - check = LibGit2.ssh_knownhost_check(files, host, key) - @test check == CHECK_NOTFOUND - end - for files in [ - [known_hosts], - [empty_file, known_hosts], - [known_hosts, empty_file], - [known_hosts, wrong_hosts], - ] - check = LibGit2.ssh_knownhost_check(files, host, key) - @test check == CHECK_MATCH - end - for files in [ - [wrong_hosts], - [empty_file, wrong_hosts], - [wrong_hosts, empty_file], - [wrong_hosts, known_hosts], - ] - check = LibGit2.ssh_knownhost_check(files, host, key) - @test check == CHECK_MISMATCH - end - end - end - - rm(empty_file) - end - - @testset "HTTPS credential prompt" begin - url = "https://github.com/test/package.jl" - - valid_username = "julia" - valid_password = randstring(16) - valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) - - https_ex = quote - include($LIBGIT2_HELPER_PATH) - credential_loop($valid_cred, $url) - end - - # User provides a valid username and password - challenges = [ - "Username for 'https://github.com': " => "$valid_username\n", - "Password for 'https://$valid_username@github.com': " => "$valid_password\n", - ] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # User sends EOF in username prompt which aborts the credential request - challenges = [ - "Username for 'https://github.com': " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - - # User sends EOF in password prompt which aborts the credential request - challenges = [ - "Username for 'https://github.com': " => "foo\n", - "Password for 'https://foo@github.com': " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - - # User provides an empty password which aborts the credential request since we - # cannot tell it apart from an EOF. - challenges = [ - "Username for 'https://github.com': " => "foo\n", - "Password for 'https://foo@github.com': " => "\n", - ] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == abort_prompt - @test auth_attempts == 1 - - # User repeatedly chooses invalid username/password until the prompt limit is - # reached - challenges = [ - "Username for 'https://github.com': " => "foo\n", - "Password for 'https://foo@github.com': " => "bar\n", - "Username for 'https://github.com' [foo]: " => "foo\n", - "Password for 'https://foo@github.com': " => "bar\n", - "Username for 'https://github.com' [foo]: " => "foo\n", - "Password for 'https://foo@github.com': " => "bar\n", - ] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == prompt_limit - @test auth_attempts == 3 - - Base.shred!(valid_cred) - end - - @testset "SSH agent username" begin - url = "github.com:test/package.jl" - - valid_key = joinpath(KEY_DIR, "valid") - valid_cred = LibGit2.SSHCredential("git", "", valid_key, valid_key * ".pub") - - function gen_ex(; username="git") - quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload(allow_prompt=false, allow_ssh_agent=true, - allow_git_helpers=false) - credential_loop($valid_cred, $url, $username, payload) - end - end - - # An empty string username_ptr - ex = gen_ex(username="") - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == exhausted_error - @test auth_attempts == 3 - - # A null username_ptr passed into `git_cred_ssh_key_from_agent` can cause a - # segfault. - ex = gen_ex(username=nothing) - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == exhausted_error - @test auth_attempts == 2 - - Base.shred!(valid_cred) - end - - @testset "SSH default" begin - mktempdir() do home_dir - url = "github.com:test/package.jl" - - default_key = joinpath(home_dir, ".ssh", "id_rsa") - mkdir(dirname(default_key)) - - valid_key = joinpath(KEY_DIR, "valid") - valid_cred = LibGit2.SSHCredential("git", "", valid_key, valid_key * ".pub") - - valid_p_key = joinpath(KEY_DIR, "valid-passphrase") - passphrase = "secret" - valid_p_cred = LibGit2.SSHCredential("git", passphrase, valid_p_key, valid_p_key * ".pub") - - function gen_ex(cred) - quote - valid_cred = $cred - - default_cred = deepcopy(valid_cred) - default_cred.prvkey = $default_key - default_cred.pubkey = $default_key * ".pub" - - cp(valid_cred.prvkey, default_cred.prvkey) - cp(valid_cred.pubkey, default_cred.pubkey) - - try - include($LIBGIT2_HELPER_PATH) - credential_loop(default_cred, $url, "git", shred=false) - finally - rm(default_cred.prvkey) - rm(default_cred.pubkey) - end - end - end - - withenv("SSH_KEY_PATH" => nothing, - "SSH_PUB_KEY_PATH" => nothing, - "SSH_KEY_PASS" => nothing, - HOME => home_dir) do - - # Automatically use the default key - ex = gen_ex(valid_cred) - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == git_ok - @test auth_attempts == 1 - @test p.credential.prvkey == default_key - @test p.credential.pubkey == default_key * ".pub" - - # Confirm the private key if any other prompting is required - ex = gen_ex(valid_p_cred) - challenges = [ - "Private key location for 'git@github.com' [$default_key]: " => "\n", - "Passphrase for $default_key: " => "$passphrase\n", - ] - err, auth_attempts, p = challenge_prompt(ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - end - - Base.shred!(valid_cred) - Base.shred!(valid_p_cred) - end - end - - @testset "SSH expand tilde" begin - url = "git@github.com:test/package.jl" - - valid_key = joinpath(KEY_DIR, "valid") - valid_cred = LibGit2.SSHCredential("git", "", valid_key, valid_key * ".pub") - - invalid_key = joinpath(KEY_DIR, "invalid") - - ssh_ex = quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload(allow_prompt=true, allow_ssh_agent=false, - allow_git_helpers=false) - credential_loop($valid_cred, $url, "git", payload, shred=false) - end - - withenv("SSH_KEY_PATH" => nothing, - "SSH_PUB_KEY_PATH" => nothing, - "SSH_KEY_PASS" => nothing, - HOME => KEY_DIR) do - - # Expand tilde during the private key prompt - challenges = [ - "Private key location for 'git@github.com': " => "~/valid\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - @test p.credential.prvkey == abspath(valid_key) - end - - withenv("SSH_KEY_PATH" => valid_key, - "SSH_PUB_KEY_PATH" => invalid_key * ".pub", - "SSH_KEY_PASS" => nothing, - HOME => KEY_DIR) do - - # Expand tilde during the public key prompt - challenges = [ - "Private key location for 'git@github.com' [$valid_key]: " => "\n", - "Public key location for 'git@github.com' [$invalid_key.pub]: " => "~/valid.pub\n", - ] - err, auth_attempts, p = challenge_prompt(ssh_ex, challenges) - @test err == git_ok - @test auth_attempts == 2 - @test p.credential.pubkey == abspath(valid_key * ".pub") - end - - Base.shred!(valid_cred) - end - - @testset "SSH explicit credentials" begin - url = "git@github.com:test/package.jl" - username = "git" - - valid_p_key = joinpath(KEY_DIR, "valid-passphrase") - passphrase = "secret" - valid_cred = LibGit2.SSHCredential(username, passphrase, valid_p_key, valid_p_key * ".pub") - - invalid_key = joinpath(KEY_DIR, "invalid") - invalid_cred = LibGit2.SSHCredential(username, "", invalid_key, invalid_key * ".pub") - - function gen_ex(cred; allow_prompt=true, allow_ssh_agent=false) - quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload($cred, allow_prompt=$allow_prompt, - allow_ssh_agent=$allow_ssh_agent, - allow_git_helpers=false) - credential_loop($valid_cred, $url, $username, payload) - end - end - - # Explicitly provided credential is correct. Note: allowing prompting and - # SSH agent to ensure they are skipped. - ex = gen_ex(valid_cred, allow_prompt=true, allow_ssh_agent=true) - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == git_ok - @test auth_attempts == 1 - @test p.explicit == valid_cred - @test p.credential != valid_cred - - # Explicitly provided credential is incorrect - ex = gen_ex(invalid_cred, allow_prompt=false, allow_ssh_agent=false) - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == exhausted_error - @test auth_attempts == 3 - @test p.explicit == invalid_cred - @test p.credential != invalid_cred - - Base.shred!(valid_cred) - Base.shred!(invalid_cred) - end - - @testset "HTTPS explicit credentials" begin - url = "https://github.com/test/package.jl" - - valid_cred = LibGit2.UserPasswordCredential("julia", randstring(16)) - invalid_cred = LibGit2.UserPasswordCredential("alice", randstring(15)) - - function gen_ex(cred; allow_prompt=true) - quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload($cred, allow_prompt=$allow_prompt, - allow_git_helpers=false) - credential_loop($valid_cred, $url, "", payload) - end - end - - # Explicitly provided credential is correct - ex = gen_ex(valid_cred, allow_prompt=true) - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == git_ok - @test auth_attempts == 1 - @test p.explicit == valid_cred - @test p.credential != valid_cred - - # Explicitly provided credential is incorrect - ex = gen_ex(invalid_cred, allow_prompt=false) - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == exhausted_error - @test auth_attempts == 2 - @test p.explicit == invalid_cred - @test p.credential != invalid_cred - - Base.shred!(valid_cred) - Base.shred!(invalid_cred) - end - - @testset "Cached credentials" begin - url = "https://github.com/test/package.jl" - cred_id = "https://github.com" - - valid_username = "julia" - valid_password = randstring(16) - valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) - - invalid_username = "alice" - invalid_password = randstring(15) - invalid_cred = LibGit2.UserPasswordCredential(invalid_username, invalid_password) - - function gen_ex(; cached_cred=nothing, allow_prompt=true) - quote - include($LIBGIT2_HELPER_PATH) - cache = CachedCredentials() - $(cached_cred !== nothing && :(LibGit2.approve(cache, $cached_cred, $url))) - payload = CredentialPayload(cache, allow_prompt=$allow_prompt, - allow_git_helpers=false) - credential_loop($valid_cred, $url, "", payload) - end - end - - # Cache contains a correct credential - err, auth_attempts, p = challenge_prompt(gen_ex(cached_cred=valid_cred), []) - @test err == git_ok - @test auth_attempts == 1 - - # Note: Approved cached credentials are not shredded - - # Add a credential into the cache - ex = gen_ex() - challenges = [ - "Username for 'https://github.com': " => "$valid_username\n", - "Password for 'https://$valid_username@github.com': " => "$valid_password\n", - ] - err, auth_attempts, p = challenge_prompt(ex, challenges) - cache = p.cache - @test err == git_ok - @test auth_attempts == 1 - @test typeof(cache) == LibGit2.CachedCredentials - @test cache.cred == Dict(cred_id => valid_cred) - @test p.credential == valid_cred - - # Replace a credential in the cache - ex = gen_ex(cached_cred=invalid_cred) - challenges = [ - "Username for 'https://github.com' [alice]: " => "$valid_username\n", - "Password for 'https://$valid_username@github.com': " => "$valid_password\n", - ] - err, auth_attempts, p = challenge_prompt(ex, challenges) - cache = p.cache - @test err == git_ok - @test auth_attempts == 2 - @test typeof(cache) == LibGit2.CachedCredentials - @test cache.cred == Dict(cred_id => valid_cred) - @test p.credential == valid_cred - - # Canceling a credential request should leave the cache unmodified - ex = gen_ex(cached_cred=invalid_cred) - challenges = [ - "Username for 'https://github.com' [alice]: " => "foo\n", - "Password for 'https://foo@github.com': " => "bar\n", - "Username for 'https://github.com' [foo]: " => "\x04", - ] - err, auth_attempts, p = challenge_prompt(ex, challenges) - cache = p.cache - @test err == abort_prompt - @test auth_attempts == 3 - @test typeof(cache) == LibGit2.CachedCredentials - @test cache.cred == Dict(cred_id => invalid_cred) - @test p.credential != invalid_cred - - # An EAUTH error should remove credentials from the cache - ex = gen_ex(cached_cred=invalid_cred, allow_prompt=false) - err, auth_attempts, p = challenge_prompt(ex, []) - cache = p.cache - @test err == exhausted_error - @test auth_attempts == 2 - @test typeof(cache) == LibGit2.CachedCredentials - @test cache.cred == Dict() - @test p.credential != invalid_cred - - Base.shred!(valid_cred) - Base.shred!(invalid_cred) - end - - @testset "HTTPS git helper username" begin - url = "https://github.com/test/package.jl" - - valid_username = "julia" - valid_password = randstring(16) - valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) - - config_path = joinpath(dir, config_file) - write(config_path, """ - [credential] - username = $valid_username - """) - - https_ex = quote - include($LIBGIT2_HELPER_PATH) - LibGit2.with(LibGit2.GitConfig($config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - payload = CredentialPayload(nothing, - nothing, cfg, - allow_git_helpers=true) - credential_loop($valid_cred, $url, nothing, payload, shred=false) - end - end - - # Username is supplied from the git configuration file - challenges = [ - "Username for 'https://github.com' [$valid_username]: " => "\n", - "Password for 'https://$valid_username@github.com': " => "$valid_password\n", - ] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # Verify credential wasn't accidentally zeroed (#24731) - @test p.credential == valid_cred - - Base.shred!(valid_cred) - end - - @testset "HTTPS git helper password" begin - if GIT_INSTALLED - url = "https://github.com/test/package.jl" - - valid_username = "julia" - valid_password = randstring(16) - valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) - - cred_file = joinpath(dir, "test-credentials") - config_path = joinpath(dir, config_file) - write(config_path, """ - [credential] - helper = store --file $cred_file - """) - - # Directly write to the cleartext credential store. Note: we are not using - # the LibGit2.approve message to avoid any possibility of the tests - # accidentally writing to a user's global store. - write(cred_file, "https://$valid_username:$valid_password@github.com") - - https_ex = quote - include($LIBGIT2_HELPER_PATH) - LibGit2.with(LibGit2.GitConfig($config_path, LibGit2.Consts.CONFIG_LEVEL_APP)) do cfg - payload = CredentialPayload(nothing, - nothing, cfg, - allow_git_helpers=true) - credential_loop($valid_cred, $url, nothing, payload, shred=false) - end - end - - # Username will be provided by the credential helper - challenges = [] - err, auth_attempts, p = challenge_prompt(https_ex, challenges) - @test err == git_ok - @test auth_attempts == 1 - - # Verify credential wasn't accidentally zeroed (#24731) - @test p.credential == valid_cred - - Base.shred!(valid_cred) - end - end - - @testset "Incompatible explicit credentials" begin - # User provides a user/password credential where a SSH credential is required. - valid_cred = LibGit2.UserPasswordCredential("foo", "bar") - expect_ssh_ex = quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload($valid_cred, allow_ssh_agent=false, - allow_git_helpers=false) - credential_loop($valid_cred, "ssh://github.com/repo", "", - Cuint(LibGit2.Consts.CREDTYPE_SSH_KEY), payload) - end - - err, auth_attempts, p = challenge_prompt(expect_ssh_ex, []) - @test err == incompatible_error - @test auth_attempts == 1 - @test p.explicit == valid_cred - @test p.credential != valid_cred - - Base.shred!(valid_cred) - - # User provides a SSH credential where a user/password credential is required. - valid_cred = LibGit2.SSHCredential("foo", "", "", "") - expect_https_ex = quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload($valid_cred, allow_ssh_agent=false, - allow_git_helpers=false) - credential_loop($valid_cred, "https://github.com/repo", "", - Cuint(LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT), payload) - end - - err, auth_attempts, p = challenge_prompt(expect_https_ex, []) - @test err == incompatible_error - @test auth_attempts == 1 - @test p.explicit == valid_cred - @test p.credential != valid_cred - - Base.shred!(valid_cred) - end - - # A hypothetical scenario where the allowed authentication can either be - # SSH or username/password. - @testset "SSH & HTTPS authentication" begin - allowed_types = Cuint(LibGit2.Consts.CREDTYPE_SSH_KEY) | - Cuint(LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT) - - # User provides a user/password credential where a SSH credential is required. - valid_cred = LibGit2.UserPasswordCredential("foo", "bar") - ex = quote - include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload($valid_cred, allow_ssh_agent=false, - allow_git_helpers=false) - credential_loop($valid_cred, "foo://github.com/repo", "", - $allowed_types, payload) - end - - err, auth_attempts, p = challenge_prompt(ex, []) - @test err == git_ok - @test auth_attempts == 1 - - Base.shred!(valid_cred) - end - - @testset "CredentialPayload reset" begin - urls = [ - "https://github.com/test/package.jl" - "https://myhost.com/demo.jl" - ] - - valid_username = "julia" - valid_password = randstring(16) - valid_cred = LibGit2.UserPasswordCredential(valid_username, valid_password) - - # Users should be able to re-use the same payload if the state is reset - ex = quote - include($LIBGIT2_HELPER_PATH) - user = nothing - payload = CredentialPayload(allow_git_helpers=false) - first_result = credential_loop($valid_cred, $(urls[1]), user, payload) - LibGit2.reset!(payload) - second_result = credential_loop($valid_cred, $(urls[2]), user, payload) - (first_result, second_result) - end - - challenges = [ - "Username for 'https://github.com': " => "$valid_username\n", - "Password for 'https://$valid_username@github.com': " => "$valid_password\n", - "Username for 'https://myhost.com': " => "$valid_username\n", - "Password for 'https://$valid_username@myhost.com': " => "$valid_password\n", - ] - first_result, second_result = challenge_prompt(ex, challenges) - - err, auth_attempts, p = first_result - @test err == git_ok - @test auth_attempts == 1 - - err, auth_attempts, p = second_result - @test err == git_ok - @test auth_attempts == 1 - - Base.shred!(valid_cred) - end - end - - # Note: Tests only work on linux as SSL_CERT_FILE is only respected on linux systems. - @testset "Hostname verification" begin - openssl_installed = false - common_name = "" - if Sys.islinux() - try - # OpenSSL needs to be on the path - openssl_installed = !isempty(read(`openssl version`, String)) - catch ex - @warn "Skipping hostname verification tests. Is `openssl` on the path?" exception=ex - end - - # Find a hostname that maps to the loopback address - hostnames = ["localhost"] - - # In minimal environments a hostname might not be available (issue #20758) - try - # In some environments, namely Macs, the hostname "macbook.local" is bound - # to the external address while "macbook" is bound to the loopback address. - pushfirst!(hostnames, replace(gethostname(), r"\..*$" => "")) - catch - end - - loopback = ip"127.0.0.1" - for hostname in hostnames - local addr - try - addr = getaddrinfo(hostname) - catch - continue - end - - if addr == loopback - common_name = hostname - break - end - end - - if isempty(common_name) - @warn "Skipping hostname verification tests. Unable to determine a hostname which maps to the loopback address" - end - end - if openssl_installed && !isempty(common_name) - mktempdir() do root - key = joinpath(root, common_name * ".key") - cert = joinpath(root, common_name * ".crt") - pem = joinpath(root, common_name * ".pem") - - # Generated a certificate which has the CN set correctly but no subjectAltName - run(pipeline(`openssl req -new -x509 -newkey rsa:2048 -sha256 -nodes -keyout $key -out $cert -days 1 -subj "/CN=$common_name"`, stderr=devnull)) - run(`openssl x509 -in $cert -out $pem -outform PEM`) - - # Find an available port by listening - port, server = listenany(49152) - close(server) - - # Make a fake Julia package and minimal HTTPS server with our generated - # certificate. The minimal server can't actually serve a Git repository. - mkdir(joinpath(root, "Example.jl")) - pobj = cd(root) do - run(`openssl s_server -key $key -cert $cert -WWW -accept $port`, wait=false) - end - - errfile = joinpath(root, "error") - repo_url = "https://$common_name:$port/Example.jl" - repo_dir = joinpath(root, "dest") - code = """ - using Serialization - import LibGit2 - dest_dir = "$repo_dir" - open("$errfile", "w+") do f - try - repo = LibGit2.clone("$repo_url", dest_dir) - catch err - serialize(f, err) - finally - isdir(dest_dir) && rm(dest_dir, recursive=true) - end - end - """ - cmd = `$(Base.julia_cmd()) --startup-file=no -e $code` - - try - # The generated certificate is normally invalid - run(cmd) - err = open(errfile, "r") do f - deserialize(f) - end - @test err.code == LibGit2.Error.ECERTIFICATE - @test startswith(lowercase(err.msg), - lowercase("The SSL certificate is invalid")) - - rm(errfile) - - # Specify that Julia use only the custom certificate. Note: we need to - # spawn a new Julia process in order for this ENV variable to take effect. - withenv("SSL_CERT_FILE" => pem) do - run(cmd) - err = open(errfile, "r") do f - deserialize(f) - end - @test err.code == LibGit2.Error.ERROR - @test occursin(r"invalid content-type: '?text/plain'?"i, err.msg) - end - - # OpenSSL s_server should still be running - @test process_running(pobj) - finally - kill(pobj) - end - end - end - end -end - -let cache = LibGit2.CachedCredentials() - get!(cache, "foo", LibGit2.SSHCredential("", "bar")) - Base.shred!(cache) - @test all(cache["foo"].pass.data .== UInt(0)) end - -end # module diff --git a/stdlib/LibGit2/test/online-tests.jl b/stdlib/LibGit2/test/online-tests.jl new file mode 100644 index 0000000000000..96b6bf5b22371 --- /dev/null +++ b/stdlib/LibGit2/test/online-tests.jl @@ -0,0 +1,101 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module LibGit2OnlineTests + +using Test +import LibGit2 +using Random + +function transfer_progress(progress::Ptr{LibGit2.TransferProgress}, payload::Dict) + status = payload[:transfer_progress] + progress = unsafe_load(progress) + + status[] = (current=progress.received_objects, total=progress.total_objects) + + return Cint(0) +end + +######### +# TESTS # +######### +# init & clone +mktempdir() do dir + repo_url = "https://github.com/JuliaLang/Example.jl" + + @testset "Cloning repository" begin + @testset "HTTPS protocol" begin + repo_path = joinpath(dir, "Example.HTTPS") + c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false) + repo = LibGit2.clone(repo_url, repo_path, credentials=c) + try + @test isdir(repo_path) + @test isdir(joinpath(repo_path, ".git")) + finally + close(repo) + end + end + + @testset "Transfer progress callbacks" begin + status = Ref((current=0, total=-1)) + callbacks = LibGit2.Callbacks( + :transfer_progress => ( + @cfunction(transfer_progress, Cint, (Ptr{LibGit2.TransferProgress}, Any)), + status, + ) + ) + + repo_path = joinpath(dir, "Example.TransferProgress") + c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false) + repo = LibGit2.clone(repo_url, repo_path, credentials=c, callbacks=callbacks) + try + @test isdir(repo_path) + @test isdir(joinpath(repo_path, ".git")) + + @test status[].total >= 0 + @test status[].current == status[].total + finally + close(repo) + end + end + + @testset "Incorrect URL" begin + repo_path = joinpath(dir, "Example.IncorrectURL") + # credentials are required because github tries to authenticate on unknown repo + cred = LibGit2.UserPasswordCredential("JeffBezanson", "hunter2") # make sure Jeff is using a good password :) + c = LibGit2.CredentialPayload(cred, allow_prompt=false, allow_git_helpers=false) + try + LibGit2.clone(repo_url*randstring(10), repo_path, credentials=c) + error("unexpected") + catch ex + @test isa(ex, LibGit2.Error.GitError) + # Return code seems to vary, see #32186, #32219 + @test ex.code ∈ (LibGit2.Error.EAUTH, LibGit2.Error.ERROR) + end + Base.shred!(cred) + end + + @testset "Empty Credentials" begin + repo_path = joinpath(dir, "Example.EmptyCredentials") + # credentials are required because github tries to authenticate on unknown repo + cred = LibGit2.UserPasswordCredential("","") # empty credentials cause authentication error + c = LibGit2.CredentialPayload(cred, allow_prompt=false, allow_git_helpers=false) + try + LibGit2.clone(repo_url*randstring(10), repo_path, credentials=c) + error("unexpected") + catch ex + @test isa(ex, LibGit2.Error.GitError) + @test ex.code == LibGit2.Error.EAUTH + end + end + end +end + +# needs to be run in separate process so it can re-initialize libgit2 +# with a useless self-signed certificate authority root certificate +file = joinpath(@__DIR__, "bad_ca_roots.jl") +cmd = `$(Base.julia_cmd()) --depwarn=no --startup-file=no $file` +if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) + error("bad CA roots tests failed, cmd : $cmd") +end + +end # module diff --git a/stdlib/LibGit2/test/online.jl b/stdlib/LibGit2/test/online.jl index 96b6bf5b22371..b2bcab83d9f4e 100644 --- a/stdlib/LibGit2/test/online.jl +++ b/stdlib/LibGit2/test/online.jl @@ -1,101 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -module LibGit2OnlineTests - -using Test -import LibGit2 -using Random - -function transfer_progress(progress::Ptr{LibGit2.TransferProgress}, payload::Dict) - status = payload[:transfer_progress] - progress = unsafe_load(progress) - - status[] = (current=progress.received_objects, total=progress.total_objects) - - return Cint(0) -end - -######### -# TESTS # -######### -# init & clone +# Set HOME to control where the .gitconfig file may be found. +# Note: In Cygwin environments `git` will use HOME instead of USERPROFILE. +# Setting both environment variables ensures home was overridden. mktempdir() do dir - repo_url = "https://github.com/JuliaLang/Example.jl" - - @testset "Cloning repository" begin - @testset "HTTPS protocol" begin - repo_path = joinpath(dir, "Example.HTTPS") - c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false) - repo = LibGit2.clone(repo_url, repo_path, credentials=c) - try - @test isdir(repo_path) - @test isdir(joinpath(repo_path, ".git")) - finally - close(repo) - end - end - - @testset "Transfer progress callbacks" begin - status = Ref((current=0, total=-1)) - callbacks = LibGit2.Callbacks( - :transfer_progress => ( - @cfunction(transfer_progress, Cint, (Ptr{LibGit2.TransferProgress}, Any)), - status, - ) - ) - - repo_path = joinpath(dir, "Example.TransferProgress") - c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false) - repo = LibGit2.clone(repo_url, repo_path, credentials=c, callbacks=callbacks) - try - @test isdir(repo_path) - @test isdir(joinpath(repo_path, ".git")) - - @test status[].total >= 0 - @test status[].current == status[].total - finally - close(repo) - end - end - - @testset "Incorrect URL" begin - repo_path = joinpath(dir, "Example.IncorrectURL") - # credentials are required because github tries to authenticate on unknown repo - cred = LibGit2.UserPasswordCredential("JeffBezanson", "hunter2") # make sure Jeff is using a good password :) - c = LibGit2.CredentialPayload(cred, allow_prompt=false, allow_git_helpers=false) - try - LibGit2.clone(repo_url*randstring(10), repo_path, credentials=c) - error("unexpected") - catch ex - @test isa(ex, LibGit2.Error.GitError) - # Return code seems to vary, see #32186, #32219 - @test ex.code ∈ (LibGit2.Error.EAUTH, LibGit2.Error.ERROR) - end - Base.shred!(cred) - end - - @testset "Empty Credentials" begin - repo_path = joinpath(dir, "Example.EmptyCredentials") - # credentials are required because github tries to authenticate on unknown repo - cred = LibGit2.UserPasswordCredential("","") # empty credentials cause authentication error - c = LibGit2.CredentialPayload(cred, allow_prompt=false, allow_git_helpers=false) - try - LibGit2.clone(repo_url*randstring(10), repo_path, credentials=c) - error("unexpected") - catch ex - @test isa(ex, LibGit2.Error.GitError) - @test ex.code == LibGit2.Error.EAUTH - end - end + dir = realpath(dir) + withenv("HOME" => dir, "USERPROFILE" => dir) do + include("online-tests.jl") end end - -# needs to be run in separate process so it can re-initialize libgit2 -# with a useless self-signed certificate authority root certificate -file = joinpath(@__DIR__, "bad_ca_roots.jl") -cmd = `$(Base.julia_cmd()) --depwarn=no --startup-file=no $file` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("bad CA roots tests failed, cmd : $cmd") -end - -end # module diff --git a/stdlib/LibGit2/test/runtests.jl b/stdlib/LibGit2/test/runtests.jl index 69b20014d11e1..88aea77f25671 100644 --- a/stdlib/LibGit2/test/runtests.jl +++ b/stdlib/LibGit2/test/runtests.jl @@ -1,4 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -include("libgit2.jl") -include("online.jl") +using Test +@testset verbose=true "LibGit2 $test" for test in eachline(joinpath(@__DIR__, "testgroups")) + include("$test.jl") +end diff --git a/stdlib/LibGit2_jll/Project.toml b/stdlib/LibGit2_jll/Project.toml index 5c4c42945a2a9..4c16c1fb72e42 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.3.0+0" +version = "1.6.1+0" [deps] MbedTLS_jll = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" @@ -9,7 +9,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.8" +julia = "1.9" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/LibGit2_jll/src/LibGit2_jll.jl b/stdlib/LibGit2_jll/src/LibGit2_jll.jl index 1cd7aaa79f814..f8e814f1f7c30 100644 --- a/stdlib/LibGit2_jll/src/LibGit2_jll.jl +++ b/stdlib/LibGit2_jll/src/LibGit2_jll.jl @@ -14,16 +14,16 @@ export libgit2 # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libgit2_handle = C_NULL -libgit2_path = "" +artifact_dir::String = "" +libgit2_handle::Ptr{Cvoid} = C_NULL +libgit2_path::String = "" if Sys.iswindows() const libgit2 = "libgit2.dll" elseif Sys.isapple() - const libgit2 = "@rpath/libgit2.1.3.dylib" + const libgit2 = "@rpath/libgit2.1.6.dylib" else - const libgit2 = "libgit2.so.1.3" + const libgit2 = "libgit2.so.1.6" end function __init__() diff --git a/stdlib/LibGit2_jll/test/runtests.jl b/stdlib/LibGit2_jll/test/runtests.jl index 3a26e26d87ebd..32ada173f01a0 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.3.0" + @test VersionNumber(major[], minor[], patch[]) == v"1.6.1" end diff --git a/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl b/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl index 66987b30d090c..a809f7a912d6b 100644 --- a/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl +++ b/stdlib/LibSSH2_jll/src/LibSSH2_jll.jl @@ -14,9 +14,9 @@ export libssh2 # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libssh2_handle = C_NULL -libssh2_path = "" +artifact_dir::String = "" +libssh2_handle::Ptr{Cvoid} = C_NULL +libssh2_path::String = "" if Sys.iswindows() const libssh2 = "libssh2.dll" diff --git a/stdlib/LibUV_jll/Project.toml b/stdlib/LibUV_jll/Project.toml index ec084417b7744..2954809921440 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+5" +version = "2.0.1+13" [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 e4897138cc6cc..f6714fae536e9 100644 --- a/stdlib/LibUV_jll/src/LibUV_jll.jl +++ b/stdlib/LibUV_jll/src/LibUV_jll.jl @@ -14,9 +14,9 @@ export libuv # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libuv_handle = C_NULL -libuv_path = "" +artifact_dir::String = "" +libuv_handle::Ptr{Cvoid} = C_NULL +libuv_path::String = "" if Sys.iswindows() const libuv = "libuv-2.dll" diff --git a/stdlib/LibUnwind_jll/Project.toml b/stdlib/LibUnwind_jll/Project.toml index e22105ddacd47..1f5f695a26ba4 100644 --- a/stdlib/LibUnwind_jll/Project.toml +++ b/stdlib/LibUnwind_jll/Project.toml @@ -1,6 +1,6 @@ name = "LibUnwind_jll" uuid = "745a5e78-f969-53e9-954f-d19f2f74f4e3" -version = "1.5.0+0" +version = "1.5.0+4" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl b/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl index ae79e790a999b..12abeaf598151 100644 --- a/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl +++ b/stdlib/LibUnwind_jll/src/LibUnwind_jll.jl @@ -14,9 +14,9 @@ export libunwind # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libunwind_handle = C_NULL -libunwind_path = "" +artifact_dir::String = "" +libunwind_handle::Ptr{Cvoid} = C_NULL +libunwind_path::String = "" const libunwind = "libunwind.so.8" diff --git a/stdlib/Libdl/test/runtests.jl b/stdlib/Libdl/test/runtests.jl index 5c06dd929f1a1..6863e28959b5e 100644 --- a/stdlib/Libdl/test/runtests.jl +++ b/stdlib/Libdl/test/runtests.jl @@ -32,12 +32,12 @@ cd(@__DIR__) do # Find the library directory by finding the path of libjulia-internal (or libjulia-internal-debug, # as the case may be) to get the private library directory private_libdir = if Base.DARWIN_FRAMEWORK - if ccall(:jl_is_debugbuild, Cint, ()) != 0 + if Base.isdebugbuild() dirname(abspath(Libdl.dlpath(Base.DARWIN_FRAMEWORK_NAME * "_debug"))) else joinpath(dirname(abspath(Libdl.dlpath(Base.DARWIN_FRAMEWORK_NAME))),"Frameworks") end -elseif ccall(:jl_is_debugbuild, Cint, ()) != 0 +elseif Base.isdebugbuild() dirname(abspath(Libdl.dlpath("libjulia-internal-debug"))) else dirname(abspath(Libdl.dlpath("libjulia-internal"))) diff --git a/stdlib/LinearAlgebra/Project.toml b/stdlib/LinearAlgebra/Project.toml index d7121d2e3868e..46653aa795209 100644 --- a/stdlib/LinearAlgebra/Project.toml +++ b/stdlib/LinearAlgebra/Project.toml @@ -4,6 +4,7 @@ uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" libblastrampoline_jll = "8e850b90-86db-534c-a0d3-1478176c7d93" +OpenBLAS_jll = "4536629a-c528-5b80-bd46-f80d51c5b363" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/LinearAlgebra/docs/src/index.md b/stdlib/LinearAlgebra/docs/src/index.md index 47dc7e5d49eaf..00ce21ed6fcae 100644 --- a/stdlib/LinearAlgebra/docs/src/index.md +++ b/stdlib/LinearAlgebra/docs/src/index.md @@ -150,7 +150,10 @@ julia> sB\x -1.1086956521739126 -1.4565217391304346 ``` -The `\` operation here performs the linear solution. The left-division operator is pretty powerful and it's easy to write compact, readable code that is flexible enough to solve all sorts of systems of linear equations. + +The `\` operation here performs the linear solution. The left-division operator is pretty +powerful and it's easy to write compact, readable code that is flexible enough to solve all +sorts of systems of linear equations. ## Special matrices @@ -183,10 +186,10 @@ as well as whether hooks to various optimized methods for them in LAPACK are ava |:----------------------------- |:--- |:--- |:--- |:--- |:----------------------------------------------------------- | | [`Symmetric`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`exp`](@ref) | | [`Hermitian`](@ref) | | | | MV | [`inv`](@ref), [`sqrt`](@ref), [`exp`](@ref) | -| [`UpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | -| [`UnitUpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | -| [`LowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | -| [`UnitLowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref) | +| [`UpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | +| [`UnitUpperTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | +| [`LowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | +| [`UnitLowerTriangular`](@ref) | | | MV | MV | [`inv`](@ref), [`det`](@ref), [`logdet`](@ref) | | [`UpperHessenberg`](@ref) | | | | MM | [`inv`](@ref), [`det`](@ref) | | [`SymTridiagonal`](@ref) | M | M | MS | MV | [`eigmax`](@ref), [`eigmin`](@ref) | | [`Tridiagonal`](@ref) | M | M | MS | MV | | @@ -276,12 +279,11 @@ to first compute the Hessenberg factorization `F` of `A` via the [`hessenberg`]( Given `F`, Julia employs an efficient algorithm for `(F+μ*I) \ b` (equivalent to `(A+μ*I)x \ b`) and related operations like determinants. - ## [Matrix factorizations](@id man-linalg-factorizations) [Matrix factorizations (a.k.a. matrix decompositions)](https://en.wikipedia.org/wiki/Matrix_decomposition) compute the factorization of a matrix into a product of matrices, and are one of the central concepts -in linear algebra. +in (numerical) linear algebra. The following table summarizes the types of matrix factorizations that have been implemented in Julia. Details of their associated methods can be found in the [Standard functions](@ref) section @@ -306,6 +308,98 @@ of the Linear Algebra documentation. | `Schur` | [Schur decomposition](https://en.wikipedia.org/wiki/Schur_decomposition) | | `GeneralizedSchur` | [Generalized Schur decomposition](https://en.wikipedia.org/wiki/Schur_decomposition#Generalized_Schur_decomposition) | +Adjoints and transposes of [`Factorization`](@ref) objects are lazily wrapped in +`AdjointFactorization` and `TransposeFactorization` objects, respectively. Generically, +transpose of real `Factorization`s are wrapped as `AdjointFactorization`. + +## [Orthogonal matrices (`AbstractQ`)](@id man-linalg-abstractq) + +Some matrix factorizations generate orthogonal/unitary "matrix" factors. These +factorizations include QR-related factorizations obtained from calls to [`qr`](@ref), i.e., +`QR`, `QRCompactWY` and `QRPivoted`, the Hessenberg factorization obtained from calls to +[`hessenberg`](@ref), and the LQ factorization obtained from [`lq`](@ref). While these +orthogonal/unitary factors admit a matrix representation, their internal representation +is, for performance and memory reasons, different. Hence, they should be rather viewed as +matrix-backed, function-based linear operators. In particular, reading, for instance, a +column of its matrix representation requires running "matrix"-vector multiplication code, +rather than simply reading out data from memory (possibly filling parts of the vector with +structural zeros). Another clear distinction from other, non-triangular matrix types is +that the underlying multiplication code allows for in-place modification during multiplication. +Furthermore, objects of specific `AbstractQ` subtypes as those created via [`qr`](@ref), +[`hessenberg`](@ref) and [`lq`](@ref) can behave like a square or a rectangular matrix +depending on context: + +```julia +julia> using LinearAlgebra + +julia> Q = qr(rand(3,2)).Q +3×3 LinearAlgebra.QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}} + +julia> Matrix(Q) +3×2 Matrix{Float64}: + -0.320597 0.865734 + -0.765834 -0.475694 + -0.557419 0.155628 + +julia> Q*I +3×3 Matrix{Float64}: + -0.320597 0.865734 -0.384346 + -0.765834 -0.475694 -0.432683 + -0.557419 0.155628 0.815514 + +julia> Q*ones(2) +3-element Vector{Float64}: + 0.5451367118802273 + -1.241527373086654 + -0.40179067589600226 + +julia> Q*ones(3) +3-element Vector{Float64}: + 0.16079054743832022 + -1.674209978965636 + 0.41372375588835797 + +julia> ones(1,2) * Q' +1×3 Matrix{Float64}: + 0.545137 -1.24153 -0.401791 + +julia> ones(1,3) * Q' +1×3 Matrix{Float64}: + 0.160791 -1.67421 0.413724 +``` + +Due to this distinction from dense or structured matrices, the abstract `AbstractQ` type +does not subtype `AbstractMatrix`, but instead has its own type hierarchy. Custom types +that subtype `AbstractQ` can rely on generic fallbacks if the following interface is satisfied. +For example, for + +```julia +struct MyQ{T} <: LinearAlgebra.AbstractQ{T} + # required fields +end +``` + +provide overloads for + +```julia +Base.size(Q::MyQ) # size of corresponding square matrix representation +Base.convert(::Type{AbstractQ{T}}, Q::MyQ) # eltype promotion [optional] +LinearAlgebra.lmul!(Q::MyQ, x::AbstractVecOrMat) # left-multiplication +LinearAlgebra.rmul!(A::AbstractMatrix, Q::MyQ) # right-multiplication +``` + +If `eltype` promotion is not of interest, the `convert` method is unnecessary, since by +default `convert(::Type{AbstractQ{T}}, Q::AbstractQ{T})` returns `Q` itself. +Adjoints of `AbstractQ`-typed objects are lazily wrapped in an `AdjointQ` wrapper type, +which requires its own `LinearAlgebra.lmul!` and `LinearAlgebra.rmul!` methods. Given this +set of methods, any `Q::MyQ` can be used like a matrix, preferably in a multiplicative +context: multiplication via `*` with scalars, vectors and matrices from left and right, +obtaining a matrix representation of `Q` via `Matrix(Q)` (or `Q*I`) and indexing into the +matrix representation all work. In contrast, addition and subtraction as well as more +generally broadcasting over elements in the matrix representation fail because that would +be highly inefficient. For such use cases, consider computing the matrix representation +up front and cache it for future reuse. + ## Standard functions Linear algebra functions in Julia are largely implemented by calling functions from [LAPACK](http://www.netlib.org/lapack/). @@ -322,6 +416,10 @@ LinearAlgebra.ZeroPivotException LinearAlgebra.dot LinearAlgebra.dot(::Any, ::Any, ::Any) LinearAlgebra.cross +LinearAlgebra.axpy! +LinearAlgebra.axpby! +LinearAlgebra.rotate! +LinearAlgebra.reflect! LinearAlgebra.factorize LinearAlgebra.Diagonal LinearAlgebra.Bidiagonal @@ -456,13 +554,17 @@ LinearAlgebra.ishermitian Base.transpose LinearAlgebra.transpose! LinearAlgebra.Transpose +LinearAlgebra.TransposeFactorization Base.adjoint LinearAlgebra.adjoint! LinearAlgebra.Adjoint +LinearAlgebra.AdjointFactorization Base.copy(::Union{Transpose,Adjoint}) LinearAlgebra.stride1 LinearAlgebra.checksquare LinearAlgebra.peakflops +LinearAlgebra.hermitianpart +LinearAlgebra.hermitianpart! ``` ## Low-level matrix operations @@ -490,9 +592,11 @@ linear algebra routines it is useful to call the BLAS functions directly. `LinearAlgebra.BLAS` provides wrappers for some of the BLAS functions. Those BLAS functions that overwrite one of the input arrays have names ending in `'!'`. Usually, a BLAS function has -four methods defined, for [`Float64`](@ref), [`Float32`](@ref), `ComplexF64`, and `ComplexF32` arrays. +four methods defined, for [`Float32`](@ref), [`Float64`](@ref), [`ComplexF32`](@ref Complex), +and [`ComplexF64`](@ref Complex) arrays. ### [BLAS character arguments](@id stdlib-blas-chars) + Many BLAS functions accept arguments that determine whether to transpose an argument (`trans`), which triangle of a matrix to reference (`uplo` or `ul`), whether the diagonal of a triangular matrix can be assumed to @@ -500,18 +604,21 @@ be all ones (`dA`) or which side of a matrix multiplication the input argument belongs on (`side`). The possibilities are: #### [Multiplication order](@id stdlib-blas-side) + | `side` | Meaning | |:-------|:--------------------------------------------------------------------| | `'L'` | The argument goes on the *left* side of a matrix-matrix operation. | | `'R'` | The argument goes on the *right* side of a matrix-matrix operation. | #### [Triangle referencing](@id stdlib-blas-uplo) + | `uplo`/`ul` | Meaning | |:------------|:------------------------------------------------------| | `'U'` | Only the *upper* triangle of the matrix will be used. | | `'L'` | Only the *lower* triangle of the matrix will be used. | #### [Transposition operation](@id stdlib-blas-trans) + | `trans`/`tX` | Meaning | |:-------------|:--------------------------------------------------------| | `'N'` | The input matrix `X` is not transposed or conjugated. | @@ -519,6 +626,7 @@ the input argument belongs on (`side`). The possibilities are: | `'C'` | The input matrix `X` will be conjugated and transposed. | #### [Unit diagonal](@id stdlib-blas-diag) + | `diag`/`dX` | Meaning | |:------------|:----------------------------------------------------------| | `'N'` | The diagonal values of the matrix `X` will be read. | @@ -526,61 +634,123 @@ the input argument belongs on (`side`). The possibilities are: ```@docs LinearAlgebra.BLAS +LinearAlgebra.BLAS.set_num_threads +LinearAlgebra.BLAS.get_num_threads +``` + +BLAS functions can be divided into three groups, also called three levels, +depending on when they were first proposed, the type of input parameters, +and the complexity of the operation. + +### Level 1 BLAS functions + +The level 1 BLAS functions were first proposed in [(Lawson, 1979)][Lawson-1979] and +define operations between scalars and vectors. + +[Lawson-1979]: https://dl.acm.org/doi/10.1145/355841.355847 + +```@docs +# xROTG +# xROTMG +LinearAlgebra.BLAS.rot! +# xROTM +# xSWAP +LinearAlgebra.BLAS.scal! +LinearAlgebra.BLAS.scal +LinearAlgebra.BLAS.blascopy! +# xAXPY! +# xAXPBY! LinearAlgebra.BLAS.dot LinearAlgebra.BLAS.dotu LinearAlgebra.BLAS.dotc -LinearAlgebra.BLAS.blascopy! +# xxDOT LinearAlgebra.BLAS.nrm2 LinearAlgebra.BLAS.asum -LinearAlgebra.axpy! -LinearAlgebra.axpby! -LinearAlgebra.BLAS.scal! -LinearAlgebra.BLAS.scal LinearAlgebra.BLAS.iamax -LinearAlgebra.BLAS.ger! -LinearAlgebra.BLAS.syr! -LinearAlgebra.BLAS.syrk! -LinearAlgebra.BLAS.syrk -LinearAlgebra.BLAS.syr2k! -LinearAlgebra.BLAS.syr2k -LinearAlgebra.BLAS.her! -LinearAlgebra.BLAS.herk! -LinearAlgebra.BLAS.herk -LinearAlgebra.BLAS.her2k! -LinearAlgebra.BLAS.her2k +``` + +### Level 2 BLAS functions + +The level 2 BLAS functions were published in [(Dongarra, 1988)][Dongarra-1988], +and define matrix-vector operations. + +[Dongarra-1988]: https://dl.acm.org/doi/10.1145/42288.42291 + +**return a vector** + +```@docs +LinearAlgebra.BLAS.gemv! +LinearAlgebra.BLAS.gemv(::Any, ::Any, ::Any, ::Any) +LinearAlgebra.BLAS.gemv(::Any, ::Any, ::Any) LinearAlgebra.BLAS.gbmv! LinearAlgebra.BLAS.gbmv +LinearAlgebra.BLAS.hemv! +LinearAlgebra.BLAS.hemv(::Any, ::Any, ::Any, ::Any) +LinearAlgebra.BLAS.hemv(::Any, ::Any, ::Any) +# hbmv!, hbmv +LinearAlgebra.BLAS.hpmv! +LinearAlgebra.BLAS.symv! +LinearAlgebra.BLAS.symv(::Any, ::Any, ::Any, ::Any) +LinearAlgebra.BLAS.symv(::Any, ::Any, ::Any) LinearAlgebra.BLAS.sbmv! LinearAlgebra.BLAS.sbmv(::Any, ::Any, ::Any, ::Any, ::Any) LinearAlgebra.BLAS.sbmv(::Any, ::Any, ::Any, ::Any) +LinearAlgebra.BLAS.spmv! +LinearAlgebra.BLAS.trmv! +LinearAlgebra.BLAS.trmv +# xTBMV +# xTPMV +LinearAlgebra.BLAS.trsv! +LinearAlgebra.BLAS.trsv +# xTBSV +# xTPSV +``` + +**return a matrix** + +```@docs +LinearAlgebra.BLAS.ger! +# xGERU +# xGERC +LinearAlgebra.BLAS.her! +# xHPR +# xHER2 +# xHPR2 +LinearAlgebra.BLAS.syr! +LinearAlgebra.BLAS.spr! +# xSYR2 +# xSPR2 +``` + +### Level 3 BLAS functions + +The level 3 BLAS functions were published in [(Dongarra, 1990)][Dongarra-1990], +and define matrix-matrix operations. + +[Dongarra-1990]: https://dl.acm.org/doi/10.1145/77626.79170 + +```@docs LinearAlgebra.BLAS.gemm! LinearAlgebra.BLAS.gemm(::Any, ::Any, ::Any, ::Any, ::Any) LinearAlgebra.BLAS.gemm(::Any, ::Any, ::Any, ::Any) -LinearAlgebra.BLAS.gemv! -LinearAlgebra.BLAS.gemv(::Any, ::Any, ::Any, ::Any) -LinearAlgebra.BLAS.gemv(::Any, ::Any, ::Any) LinearAlgebra.BLAS.symm! LinearAlgebra.BLAS.symm(::Any, ::Any, ::Any, ::Any, ::Any) LinearAlgebra.BLAS.symm(::Any, ::Any, ::Any, ::Any) -LinearAlgebra.BLAS.symv! -LinearAlgebra.BLAS.symv(::Any, ::Any, ::Any, ::Any) -LinearAlgebra.BLAS.symv(::Any, ::Any, ::Any) LinearAlgebra.BLAS.hemm! LinearAlgebra.BLAS.hemm(::Any, ::Any, ::Any, ::Any, ::Any) LinearAlgebra.BLAS.hemm(::Any, ::Any, ::Any, ::Any) -LinearAlgebra.BLAS.hemv! -LinearAlgebra.BLAS.hemv(::Any, ::Any, ::Any, ::Any) -LinearAlgebra.BLAS.hemv(::Any, ::Any, ::Any) +LinearAlgebra.BLAS.syrk! +LinearAlgebra.BLAS.syrk +LinearAlgebra.BLAS.herk! +LinearAlgebra.BLAS.herk +LinearAlgebra.BLAS.syr2k! +LinearAlgebra.BLAS.syr2k +LinearAlgebra.BLAS.her2k! +LinearAlgebra.BLAS.her2k LinearAlgebra.BLAS.trmm! LinearAlgebra.BLAS.trmm LinearAlgebra.BLAS.trsm! LinearAlgebra.BLAS.trsm -LinearAlgebra.BLAS.trmv! -LinearAlgebra.BLAS.trmv -LinearAlgebra.BLAS.trsv! -LinearAlgebra.BLAS.trsv -LinearAlgebra.BLAS.set_num_threads -LinearAlgebra.BLAS.get_num_threads ``` ## LAPACK functions @@ -627,6 +797,7 @@ LinearAlgebra.LAPACK.ggsvd! LinearAlgebra.LAPACK.ggsvd3! LinearAlgebra.LAPACK.geevx! LinearAlgebra.LAPACK.ggev! +LinearAlgebra.LAPACK.ggev3! LinearAlgebra.LAPACK.gtsv! LinearAlgebra.LAPACK.gttrf! LinearAlgebra.LAPACK.gttrs! @@ -667,6 +838,7 @@ LinearAlgebra.LAPACK.hetri! LinearAlgebra.LAPACK.hetrs! LinearAlgebra.LAPACK.syev! LinearAlgebra.LAPACK.syevr! +LinearAlgebra.LAPACK.syevd! LinearAlgebra.LAPACK.sygvd! LinearAlgebra.LAPACK.bdsqr! LinearAlgebra.LAPACK.bdsdc! @@ -675,10 +847,12 @@ LinearAlgebra.LAPACK.gehrd! LinearAlgebra.LAPACK.orghr! LinearAlgebra.LAPACK.gees! LinearAlgebra.LAPACK.gges! +LinearAlgebra.LAPACK.gges3! LinearAlgebra.LAPACK.trexc! LinearAlgebra.LAPACK.trsen! LinearAlgebra.LAPACK.tgsen! LinearAlgebra.LAPACK.trsyl! +LinearAlgebra.LAPACK.hseqr! ``` ```@meta diff --git a/stdlib/LinearAlgebra/src/LinearAlgebra.jl b/stdlib/LinearAlgebra/src/LinearAlgebra.jl index 7417e6256cef5..a29c259dae607 100644 --- a/stdlib/LinearAlgebra/src/LinearAlgebra.jl +++ b/stdlib/LinearAlgebra/src/LinearAlgebra.jl @@ -9,16 +9,20 @@ module LinearAlgebra 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, conj, convert, copy, copyto!, 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, print_matrix, promote_rule, real, round, sec, sech, - setindex!, show, similar, sin, sincos, sinh, size, sqrt, - strides, stride, tan, tanh, transpose, trunc, typed_hcat, vec, zero + asin, asinh, atan, atanh, axes, big, broadcast, ceil, cis, 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, + print_matrix, promote_rule, real, round, 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, - @propagate_inbounds, @pure, reduce, typed_hvcat, typed_vcat, require_one_based_indexing, + @propagate_inbounds, reduce, typed_hvcat, typed_vcat, require_one_based_indexing, splat using Base.Broadcast: Broadcasted, broadcasted +using Base.PermutedDimsArrays: CommutativeOps +using OpenBLAS_jll +using libblastrampoline_jll import Libdl export @@ -45,6 +49,7 @@ export LU, LDLt, NoPivot, + RowNonZero, QR, QRPivoted, LQ, @@ -89,6 +94,8 @@ export eigvecs, factorize, givens, + hermitianpart, + hermitianpart!, hessenberg, hessenberg!, isdiag, @@ -100,6 +107,7 @@ export istril, istriu, kron, + kron!, ldiv!, ldlt!, ldlt, @@ -170,6 +178,7 @@ struct QRIteration <: Algorithm end abstract type PivotingStrategy end struct NoPivot <: PivotingStrategy end +struct RowNonZero <: PivotingStrategy end struct RowMaximum <: PivotingStrategy end struct ColumnNorm <: PivotingStrategy end @@ -276,6 +285,10 @@ The reason for this is that factorization itself is both expensive and typically and performance-critical situations requiring `ldiv!` usually also require fine-grained control over the factorization of `A`. +!!! note + Certain structured matrix types, such as `Diagonal` and `UpperTriangular`, are permitted, as + these are already in a factorized form + # Examples ```jldoctest julia> A = [1 2.2 4; 3.1 0.2 3; 4 1 2]; @@ -313,6 +326,10 @@ The reason for this is that factorization itself is both expensive and typically and performance-critical situations requiring `ldiv!` usually also require fine-grained control over the factorization of `A`. +!!! note + Certain structured matrix types, such as `Diagonal` and `UpperTriangular`, are permitted, as + these are already in a factorized form + # Examples ```jldoctest julia> A = [1 2.2 4; 3.1 0.2 3; 4 1 2]; @@ -350,13 +367,25 @@ The reason for this is that factorization itself is both expensive and typically (although it can also be done in-place via, e.g., [`lu!`](@ref)), and performance-critical situations requiring `rdiv!` usually also require fine-grained control over the factorization of `B`. + +!!! note + Certain structured matrix types, such as `Diagonal` and `UpperTriangular`, are permitted, as + these are already in a factorized form """ rdiv!(A, B) +""" + copy_oftype(A, T) +Creates a copy of `A` with eltype `T`. No assertions about mutability of the result are +made. When `eltype(A) == T`, then this calls `copy(A)` which may be overloaded for custom +array types. Otherwise, this calls `convert(AbstractArray{T}, A)`. +""" +copy_oftype(A::AbstractArray{T}, ::Type{T}) where {T} = copy(A) +copy_oftype(A::AbstractArray{T,N}, ::Type{S}) where {T,N,S} = convert(AbstractArray{S,N}, A) """ - copy_oftype(A, T) + copymutable_oftype(A, T) Copy `A` to a mutable array with eltype `T` based on `similar(A, T)`. @@ -364,33 +393,28 @@ The resulting matrix typically has similar algebraic structure as `A`. For example, supplying a tridiagonal matrix results in another tridiagonal matrix. In general, the type of the output corresponds to that of `similar(A, T)`. -There are three often used methods in LinearAlgebra to create a mutable copy -of an array with a given eltype. These copies can be passed to in-place -algorithms (such as `ldiv!`, `rdiv!`, `lu!` and so on). Which one to use in practice -depends on what is known (or assumed) about the structure of the array in that -algorithm. +In LinearAlgebra, mutable copies (of some desired eltype) are created to be passed +to in-place algorithms (such as `ldiv!`, `rdiv!`, `lu!` and so on). If the specific +algorithm is known to preserve the algebraic structure, use `copymutable_oftype`. +If the algorithm is known to return a dense matrix (or some wrapper backed by a dense +matrix), then use `copy_similar`. -See also: `copy_similar`. +See also: `Base.copymutable`, `copy_similar`. """ -copy_oftype(A::AbstractArray, ::Type{T}) where {T} = copyto!(similar(A, T), A) +copymutable_oftype(A::AbstractArray, ::Type{S}) where {S} = copyto!(similar(A, S), A) """ copy_similar(A, T) Copy `A` to a mutable array with eltype `T` based on `similar(A, T, size(A))`. -Compared to `copy_oftype`, the result can be more flexible. In general, the type +Compared to `copymutable_oftype`, the result can be more flexible. In general, the type of the output corresponds to that of the three-argument method `similar(A, T, size(A))`. -See also: `copy_oftype`. +See also: `copymutable_oftype`. """ copy_similar(A::AbstractArray, ::Type{T}) where {T} = copyto!(similar(A, T, size(A)), A) -# The three copy functions above return mutable arrays with eltype T. -# To only ensure a certain eltype, and if a mutable copy is not needed, it is -# more efficient to use: -# convert(AbstractArray{T}, A) - include("adjtrans.jl") include("transpose.jl") @@ -407,8 +431,6 @@ include("tridiag.jl") include("triangular.jl") include("factorization.jl") -include("qr.jl") -include("lq.jl") include("eigen.jl") include("svd.jl") include("symmetric.jl") @@ -419,7 +441,10 @@ include("diagonal.jl") include("symmetriceigen.jl") include("bidiag.jl") include("uniformscaling.jl") +include("qr.jl") +include("lq.jl") include("hessenberg.jl") +include("abstractq.jl") include("givens.jl") include("special.jl") include("bitarray.jl") @@ -439,20 +464,47 @@ _cut_B(x::AbstractVector, r::UnitRange) = length(x) > length(r) ? x[r] : x _cut_B(X::AbstractMatrix, r::UnitRange) = size(X, 1) > length(r) ? X[r,:] : X # SymTridiagonal ev can be the same length as dv, but the last element is -# ignored. However, some methods can fail if they read the entired ev +# ignored. However, some methods can fail if they read the entire ev # rather than just the meaningful elements. This is a helper function # for getting only the meaningful elements of ev. See #41089 -_evview(S::SymTridiagonal) = @view S.ev[begin:length(S.dv) - 1] +_evview(S::SymTridiagonal) = @view S.ev[begin:begin + length(S.dv) - 2] ## append right hand side with zeros if necessary _zeros(::Type{T}, b::AbstractVector, n::Integer) where {T} = zeros(T, max(length(b), n)) _zeros(::Type{T}, B::AbstractMatrix, n::Integer) where {T} = zeros(T, max(size(B, 1), n), size(B, 2)) +# convert to Vector, if necessary +_makevector(x::Vector) = x +_makevector(x::AbstractVector) = Vector(x) + +# append a zero element / drop the last element +_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)) + +# 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 +# `T(one(T))`, so the question is equivalent for whether one(T) is defined +onedefined(::Type) = false +onedefined(::Type{<:Number}) = true + +# initialize return array for op(A, B) +_init_eltype(::typeof(*), ::Type{TA}, ::Type{TB}) where {TA,TB} = + (onedefined(TA) && onedefined(TB)) ? + typeof(matprod(oneunit(TA), oneunit(TB))) : + promote_op(matprod, TA, TB) +_init_eltype(op, ::Type{TA}, ::Type{TB}) where {TA,TB} = + (onedefined(TA) && onedefined(TB)) ? + typeof(op(oneunit(TA), oneunit(TB))) : + promote_op(op, TA, TB) +_initarray(op, ::Type{TA}, ::Type{TB}, C) where {TA,TB} = + similar(C, _init_eltype(op, TA, TB), size(C)) + # General fallback definition for handling under- and overdetermined system as well as square problems # While this definition is pretty general, it does e.g. promote to common element type of lhs and rhs -# which is required by LAPACK but not SuiteSpase which allows real-complex solves in some cases. Hence, +# which is required by LAPACK but not SuiteSparse which allows real-complex solves in some cases. Hence, # we restrict this method to only the LAPACK factorizations in LinearAlgebra. -# The definition is put here since it explicitly references all the Factorizion structs so it has +# The definition is put here since it explicitly references all the Factorization structs so it has # to be located after all the files that define the structs. const LAPACKFactorizations{T,S} = Union{ BunchKaufman{T,S}, @@ -463,7 +515,12 @@ const LAPACKFactorizations{T,S} = Union{ QRCompactWY{T,S}, QRPivoted{T,S}, SVD{T,<:Real,S}} -function (\)(F::Union{<:LAPACKFactorizations,Adjoint{<:Any,<:LAPACKFactorizations}}, B::AbstractVecOrMat) + +(\)(F::LAPACKFactorizations, B::AbstractVecOrMat) = ldiv(F, B) +(\)(F::AdjointFactorization{<:Any,<:LAPACKFactorizations}, B::AbstractVecOrMat) = ldiv(F, B) +(\)(F::TransposeFactorization{<:Any,<:LU}, B::AbstractVecOrMat) = ldiv(F, B) + +function ldiv(F::Factorization, B::AbstractVecOrMat) require_one_based_indexing(B) m, n = size(F) if m != size(B, 1) @@ -493,7 +550,11 @@ function (\)(F::Union{<:LAPACKFactorizations,Adjoint{<:Any,<:LAPACKFactorization end # disambiguate (\)(F::LAPACKFactorizations{T}, B::VecOrMat{Complex{T}}) where {T<:BlasReal} = - invoke(\, Tuple{Factorization{T}, VecOrMat{Complex{T}}}, F, B) + @invoke \(F::Factorization{T}, B::VecOrMat{Complex{T}}) +(\)(F::AdjointFactorization{T,<:LAPACKFactorizations}, B::VecOrMat{Complex{T}}) where {T<:BlasReal} = + ldiv(F, B) +(\)(F::TransposeFactorization{T,<:LU}, B::VecOrMat{Complex{T}}) where {T<:BlasReal} = + ldiv(F, B) """ LinearAlgebra.peakflops(n::Integer=2000; parallel::Bool=false) @@ -531,56 +592,72 @@ end function versioninfo(io::IO=stdout) + indent = " " config = BLAS.get_config() - println(io, "BLAS: $(BLAS.libblastrampoline) ($(join(string.(config.build_flags), ", ")))") + build_flags = join(string.(config.build_flags), ", ") + println(io, "BLAS: ", BLAS.libblastrampoline, " (", build_flags, ")") for lib in config.loaded_libs - println(io, " --> $(lib.libname) ($(uppercase(string(lib.interface))))") + interface = uppercase(string(lib.interface)) + println(io, indent, "--> ", lib.libname, " (", interface, ")") end - return nothing -end - -function find_library_path(name) - shlib_ext = string(".", Libdl.dlext) - if !endswith(name, shlib_ext) - name_ext = string(name, shlib_ext) + println(io, "Threading:") + println(io, indent, "Threads.threadpoolsize() = ", Threads.threadpoolsize()) + println(io, indent, "Threads.maxthreadid() = ", Base.Threads.maxthreadid()) + println(io, indent, "LinearAlgebra.BLAS.get_num_threads() = ", BLAS.get_num_threads()) + println(io, "Relevant environment variables:") + env_var_names = [ + "JULIA_NUM_THREADS", + "MKL_DYNAMIC", + "MKL_NUM_THREADS", + # OpenBLAS has a hierarchy of environment variables for setting the + # number of threads, see + # https://github.com/xianyi/OpenBLAS/blob/c43ec53bdd00d9423fc609d7b7ecb35e7bf41b85/README.md#setting-the-number-of-threads-using-environment-variables + ("OPENBLAS_NUM_THREADS", "GOTO_NUM_THREADS", "OMP_NUM_THREADS"), + ] + printed_at_least_one_env_var = false + print_var(io, indent, name) = println(io, indent, name, " = ", ENV[name]) + for name in env_var_names + if name isa Tuple + # If `name` is a Tuple, then find the first environment which is + # defined, and disregard the following ones. + for nm in name + if haskey(ENV, nm) + print_var(io, indent, nm) + printed_at_least_one_env_var = true + break + end + end + else + if haskey(ENV, name) + print_var(io, indent, name) + printed_at_least_one_env_var = true + end + end end - - # On windows, we look in `bin` and never in `lib` - @static if Sys.iswindows() - path = joinpath(Sys.BINDIR, name_ext) - isfile(path) && return path - else - # On other platforms, we check `lib/julia` first, and if that doesn't exist, `lib`. - path = joinpath(Sys.BINDIR, Base.LIBDIR, "julia", name_ext) - isfile(path) && return path - - path = joinpath(Sys.BINDIR, Base.LIBDIR, name_ext) - isfile(path) && return path + if !printed_at_least_one_env_var + println(io, indent, "[none]") end - - # If we can't find it by absolute path, we'll try just passing this straight through to `dlopen()` - return name + return nothing end function __init__() try - libblas_path = find_library_path(Base.libblas_name) - liblapack_path = find_library_path(Base.liblapack_name) - # We manually `dlopen()` these libraries here, so that we search with `libjulia-internal`'s - # `RPATH` and not `libblastrampoline's`. Once it's been opened, when LBT tries to open it, - # it will find the library already loaded. - libblas_path = Libdl.dlpath(Libdl.dlopen(libblas_path)) - BLAS.lbt_forward(libblas_path; clear=true) - if liblapack_path != libblas_path - liblapack_path = Libdl.dlpath(Libdl.dlopen(liblapack_path)) - BLAS.lbt_forward(liblapack_path) - end + BLAS.lbt_forward(OpenBLAS_jll.libopenblas_path; clear=true) BLAS.check() catch ex Base.showerror_nostdio(ex, "WARNING: Error during initialization of module LinearAlgebra") end # register a hook to disable BLAS threading Base.at_disable_library_threading(() -> BLAS.set_num_threads(1)) + + # https://github.com/xianyi/OpenBLAS/blob/c43ec53bdd00d9423fc609d7b7ecb35e7bf41b85/README.md#setting-the-number-of-threads-using-environment-variables + if !haskey(ENV, "OPENBLAS_NUM_THREADS") && !haskey(ENV, "GOTO_NUM_THREADS") && !haskey(ENV, "OMP_NUM_THREADS") + @static if Sys.isapple() && Base.BinaryPlatforms.arch(Base.BinaryPlatforms.HostPlatform()) == "aarch64" + BLAS.set_num_threads(max(1, Sys.CPU_THREADS)) + else + BLAS.set_num_threads(max(1, Sys.CPU_THREADS ÷ 2)) + end + end end end # module LinearAlgebra diff --git a/stdlib/LinearAlgebra/src/abstractq.jl b/stdlib/LinearAlgebra/src/abstractq.jl new file mode 100644 index 0000000000000..88610dac2e6f6 --- /dev/null +++ b/stdlib/LinearAlgebra/src/abstractq.jl @@ -0,0 +1,622 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +abstract type AbstractQ{T} end + +struct AdjointQ{T,S<:AbstractQ{T}} <: AbstractQ{T} + Q::S +end + +parent(adjQ::AdjointQ) = adjQ.Q +eltype(::Type{<:AbstractQ{T}}) where {T} = T +ndims(::AbstractQ) = 2 + +# inversion/adjoint/transpose +inv(Q::AbstractQ) = Q' +adjoint(Q::AbstractQ) = AdjointQ(Q) +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 + +# promotion with AbstractMatrix, at least for equal eltypes +promote_rule(::Type{<:AbstractMatrix{T}}, ::Type{<:AbstractQ{T}}) where {T} = + (@inline; Union{AbstractMatrix{T},AbstractQ{T}}) + +# conversion +# the following eltype promotion should be defined for each subtype `QType` +# convert(::Type{AbstractQ{T}}, Q::QType) where {T} = QType{T}(Q) +# and then care has to be taken that +# QType{T}(Q::QType{T}) where T = ... +# is implemented as a no-op + +# the following conversion method ensures functionality when the above method is not defined +# (as for HessenbergQ), but no eltype conversion is required either (say, in multiplication) +convert(::Type{AbstractQ{T}}, Q::AbstractQ{T}) where {T} = Q +convert(::Type{AbstractQ{T}}, adjQ::AdjointQ{T}) where {T} = adjQ +convert(::Type{AbstractQ{T}}, adjQ::AdjointQ) where {T} = convert(AbstractQ{T}, adjQ.Q)' + +# ... to matrix +Matrix{T}(Q::AbstractQ) where {T} = convert(Matrix{T}, Q*I) # generic fallback, yields square matrix +Matrix{T}(adjQ::AdjointQ{S}) where {T,S} = convert(Matrix{T}, lmul!(adjQ, Matrix{S}(I, size(adjQ)))) +Matrix(Q::AbstractQ{T}) where {T} = Matrix{T}(Q) +Array{T}(Q::AbstractQ) where {T} = Matrix{T}(Q) +Array(Q::AbstractQ) = Matrix(Q) +convert(::Type{T}, Q::AbstractQ) where {T<:AbstractArray} = T(Q) +# legacy +@deprecate(convert(::Type{AbstractMatrix{T}}, Q::AbstractQ) where {T}, + convert(LinearAlgebra.AbstractQ{T}, Q)) + +function size(Q::AbstractQ, dim::Integer) + if dim < 1 + throw(BoundsError()) + elseif dim <= 2 # && 1 <= dim + return size(Q)[dim] + else # 2 < dim + return 1 + end +end +size(adjQ::AdjointQ) = reverse(size(adjQ.Q)) + +# pseudo-array behaviour, required for indexing with `begin` or `end` +axes(Q::AbstractQ) = map(Base.oneto, size(Q)) +axes(Q::AbstractQ, d::Integer) = d in (1, 2) ? axes(Q)[d] : Base.OneTo(1) + +copymutable(Q::AbstractQ{T}) where {T} = lmul!(Q, Matrix{T}(I, size(Q))) +copy(Q::AbstractQ) = copymutable(Q) + +# getindex +@inline function getindex(Q::AbstractQ, inds...) + @boundscheck Base.checkbounds_indices(Bool, axes(Q), inds) || Base.throw_boundserror(Q, inds) + return _getindex(Q, inds...) +end +@inline getindex(Q::AbstractQ, ::Colon) = copymutable(Q)[:] +@inline getindex(Q::AbstractQ, ::Colon, ::Colon) = copy(Q) + +@inline _getindex(Q::AbstractQ, inds...) = @inbounds copymutable(Q)[inds...] +@inline function _getindex(Q::AbstractQ, ::Colon, J::AbstractVector{<:Integer}) + Y = zeros(eltype(Q), size(Q, 2), length(J)) + @inbounds for (i,j) in enumerate(J) + Y[j,i] = oneunit(eltype(Q)) + end + lmul!(Q, Y) +end +@inline _getindex(Q::AbstractQ, I::AbstractVector{Int}, J::AbstractVector{Int}) = @inbounds Q[:,J][I,:] +@inline function _getindex(Q::AbstractQ, ::Colon, j::Int) + y = zeros(eltype(Q), size(Q, 2)) + y[j] = oneunit(eltype(Q)) + lmul!(Q, y) +end +@inline _getindex(Q::AbstractQ, i::Int, j::Int) = @inbounds Q[:,j][i] + +# needed because AbstractQ does not subtype AbstractMatrix +qr(Q::AbstractQ{T}, arg...; kwargs...) where {T} = qr!(Matrix{_qreltype(T)}(Q), arg...; kwargs...) +lq(Q::AbstractQ{T}, arg...; kwargs...) where {T} = lq!(Matrix{lq_eltype(T)}(Q), arg...; kwargs...) +hessenberg(Q::AbstractQ{T}) where {T} = hessenberg!(Matrix{eigtype(T)}(Q)) + +# needed when used interchangeably with AbstractMatrix (analogous to views of ranges) +view(A::AbstractQ, I...) = getindex(A, I...) + +# specialization avoiding the fallback using slow `getindex` +function copyto!(dest::AbstractMatrix, src::AbstractQ) + copyto!(dest, I) + lmul!(src, dest) +end +# needed to resolve method ambiguities +function copyto!(dest::PermutedDimsArray{T,2,perm}, src::AbstractQ) where {T,perm} + if perm == (1, 2) + copyto!(parent(dest), src) + else + @assert perm == (2, 1) # there are no other permutations of two indices + if T <: Real + copyto!(parent(dest), I) + lmul!(src', parent(dest)) + else + # LAPACK does not offer inplace lmul!(transpose(Q), B) for complex Q + tmp = similar(parent(dest)) + copyto!(tmp, I) + rmul!(tmp, src) + permutedims!(parent(dest), tmp, (2, 1)) + end + end + return dest +end + +function show(io::IO, ::MIME{Symbol("text/plain")}, Q::AbstractQ) + print(io, Base.dims2string(size(Q)), ' ', summary(Q)) +end + +# multiplication +(*)(Q::AbstractQ, J::UniformScaling) = Q*J.λ +function (*)(Q::AbstractQ, b::Number) + T = promote_type(eltype(Q), typeof(b)) + lmul!(convert(AbstractQ{T}, Q), Matrix{T}(b*I, size(Q))) +end +function (*)(A::AbstractQ, B::AbstractVecOrMat) + T = promote_type(eltype(A), eltype(B)) + lmul!(convert(AbstractQ{T}, A), copy_similar(B, T)) +end + +(*)(J::UniformScaling, Q::AbstractQ) = J.λ*Q +function (*)(a::Number, Q::AbstractQ) + T = promote_type(typeof(a), eltype(Q)) + rmul!(Matrix{T}(a*I, size(Q)), convert(AbstractQ{T}, Q)) +end +*(a::AbstractVector, Q::AbstractQ) = reshape(a, length(a), 1) * Q +function (*)(A::AbstractMatrix, Q::AbstractQ) + T = promote_type(eltype(A), eltype(Q)) + return rmul!(copy_similar(A, T), convert(AbstractQ{T}, Q)) +end +(*)(u::AdjointAbsVec, Q::AbstractQ) = (Q'u')' + +### Q*Q (including adjoints) +*(Q::AbstractQ, P::AbstractQ) = Q * (P*I) + +### mul! +function mul!(C::AbstractVecOrMat{T}, Q::AbstractQ{T}, B::Union{AbstractVecOrMat{T},AbstractQ{T}}) where {T} + require_one_based_indexing(C, B) + mB = size(B, 1) + mC = size(C, 1) + if mB < mC + inds = CartesianIndices(axes(B)) + copyto!(view(C, inds), B) + C[CartesianIndices((mB+1:mC, axes(C, 2)))] .= zero(T) + return lmul!(Q, C) + else + return lmul!(Q, copyto!(C, B)) + end +end +mul!(C::AbstractVecOrMat{T}, A::AbstractVecOrMat{T}, Q::AbstractQ{T}) where {T} = rmul!(copyto!(C, A), Q) +mul!(C::AbstractVecOrMat{T}, adjQ::AdjointQ{T}, B::AbstractVecOrMat{T}) where {T} = lmul!(adjQ, copyto!(C, B)) +mul!(C::AbstractVecOrMat{T}, A::AbstractVecOrMat{T}, adjQ::AdjointQ{T}) where {T} = rmul!(copyto!(C, A), adjQ) + +### division +\(Q::AbstractQ, A::AbstractVecOrMat) = Q'*A +/(A::AbstractVecOrMat, Q::AbstractQ) = A*Q' +ldiv!(Q::AbstractQ, A::AbstractVecOrMat) = lmul!(Q', A) +ldiv!(C::AbstractVecOrMat, Q::AbstractQ, A::AbstractVecOrMat) = mul!(C, Q', A) +rdiv!(A::AbstractVecOrMat, Q::AbstractQ) = rmul!(A, Q') + +logabsdet(Q::AbstractQ) = (d = det(Q); return log(abs(d)), sign(d)) +function logdet(A::AbstractQ) + d, s = logabsdet(A) + return d + log(s) +end + +########################################################### +################ Q from QR decompositions ################# +########################################################### + +""" + QRPackedQ <: LinearAlgebra.AbstractQ + +The orthogonal/unitary ``Q`` matrix of a QR factorization stored in [`QR`](@ref) or +[`QRPivoted`](@ref) format. +""" +struct QRPackedQ{T,S<:AbstractMatrix{T},C<:AbstractVector{T}} <: AbstractQ{T} + factors::S + τ::C + + function QRPackedQ{T,S,C}(factors, τ) where {T,S<:AbstractMatrix{T},C<:AbstractVector{T}} + require_one_based_indexing(factors, τ) + new{T,S,C}(factors, τ) + end +end +QRPackedQ(factors::AbstractMatrix{T}, τ::AbstractVector{T}) where {T} = + QRPackedQ{T,typeof(factors),typeof(τ)}(factors, τ) +QRPackedQ{T}(factors::AbstractMatrix, τ::AbstractVector) where {T} = + QRPackedQ(convert(AbstractMatrix{T}, factors), convert(AbstractVector{T}, τ)) +# backwards-compatible constructors (remove with Julia 2.0) +@deprecate(QRPackedQ{T,S}(factors::AbstractMatrix{T}, τ::AbstractVector{T}) where {T,S}, + QRPackedQ{T,S,typeof(τ)}(factors, τ), false) + +""" + QRCompactWYQ <: LinearAlgebra.AbstractQ + +The orthogonal/unitary ``Q`` matrix of a QR factorization stored in [`QRCompactWY`](@ref) +format. +""" +struct QRCompactWYQ{S, M<:AbstractMatrix{S}, C<:AbstractMatrix{S}} <: AbstractQ{S} + factors::M + T::C + + function QRCompactWYQ{S,M,C}(factors, T) where {S,M<:AbstractMatrix{S},C<:AbstractMatrix{S}} + require_one_based_indexing(factors, T) + new{S,M,C}(factors, T) + end +end +QRCompactWYQ(factors::AbstractMatrix{S}, T::AbstractMatrix{S}) where {S} = + QRCompactWYQ{S,typeof(factors),typeof(T)}(factors, T) +QRCompactWYQ{S}(factors::AbstractMatrix, T::AbstractMatrix) where {S} = + QRCompactWYQ(convert(AbstractMatrix{S}, factors), convert(AbstractMatrix{S}, T)) +# backwards-compatible constructors (remove with Julia 2.0) +@deprecate(QRCompactWYQ{S,M}(factors::AbstractMatrix{S}, T::AbstractMatrix{S}) where {S,M}, + QRCompactWYQ{S,M,typeof(T)}(factors, T), false) + +QRPackedQ{T}(Q::QRPackedQ) where {T} = QRPackedQ(convert(AbstractMatrix{T}, Q.factors), convert(AbstractVector{T}, Q.τ)) +QRCompactWYQ{S}(Q::QRCompactWYQ) where {S} = QRCompactWYQ(convert(AbstractMatrix{S}, Q.factors), convert(AbstractMatrix{S}, Q.T)) + +# override generic square fallback +Matrix{T}(Q::Union{QRCompactWYQ{S},QRPackedQ{S}}) where {T,S} = + convert(Matrix{T}, lmul!(Q, Matrix{S}(I, size(Q, 1), min(size(Q.factors)...)))) +Matrix(Q::Union{QRCompactWYQ{S},QRPackedQ{S}}) where {S} = Matrix{S}(Q) + +convert(::Type{AbstractQ{T}}, Q::QRPackedQ) where {T} = QRPackedQ{T}(Q) +convert(::Type{AbstractQ{T}}, Q::QRCompactWYQ) where {T} = QRCompactWYQ{T}(Q) + +size(Q::Union{QRCompactWYQ,QRPackedQ}, dim::Integer) = + size(Q.factors, dim == 2 ? 1 : dim) +size(Q::Union{QRCompactWYQ,QRPackedQ}) = (n = size(Q.factors, 1); (n, n)) + +## Multiplication +### QB +lmul!(A::QRCompactWYQ{T,<:StridedMatrix}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = + LAPACK.gemqrt!('L', 'N', A.factors, A.T, B) +lmul!(A::QRPackedQ{T,<:StridedMatrix}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = + LAPACK.ormqr!('L', 'N', A.factors, A.τ, B) +function lmul!(A::QRPackedQ, B::AbstractVecOrMat) + require_one_based_indexing(B) + 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)")) + end + Afactors = A.factors + @inbounds begin + for k = min(mA,nA):-1:1 + for j = 1:nB + vBj = B[k,j] + for i = k+1:mB + vBj += conj(Afactors[i,k])*B[i,j] + end + vBj = A.τ[k]*vBj + B[k,j] -= vBj + for i = k+1:mB + B[i,j] -= Afactors[i,k]*vBj + end + end + end + end + B +end + +### QcB +lmul!(adjQ::AdjointQ{<:Any,<:QRCompactWYQ{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasReal} = + (Q = adjQ.Q; LAPACK.gemqrt!('L', 'T', Q.factors, Q.T, B)) +lmul!(adjQ::AdjointQ{<:Any,<:QRCompactWYQ{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasComplex} = + (Q = adjQ.Q; LAPACK.gemqrt!('L', 'C', Q.factors, Q.T, B)) +lmul!(adjQ::AdjointQ{<:Any,<:QRPackedQ{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasReal} = + (Q = adjQ.Q; LAPACK.ormqr!('L', 'T', Q.factors, Q.τ, B)) +lmul!(adjQ::AdjointQ{<:Any,<:QRPackedQ{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasComplex} = + (Q = adjQ.Q; LAPACK.ormqr!('L', 'C', Q.factors, Q.τ, B)) +function lmul!(adjA::AdjointQ{<:Any,<:QRPackedQ}, B::AbstractVecOrMat) + require_one_based_indexing(B) + A = adjA.Q + 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)")) + end + Afactors = A.factors + @inbounds begin + for k = 1:min(mA,nA) + for j = 1:nB + vBj = B[k,j] + for i = k+1:mB + vBj += conj(Afactors[i,k])*B[i,j] + end + vBj = conj(A.τ[k])*vBj + B[k,j] -= vBj + for i = k+1:mB + B[i,j] -= Afactors[i,k]*vBj + end + end + end + end + B +end + +### AQ +rmul!(A::StridedVecOrMat{T}, B::QRCompactWYQ{T,<:StridedMatrix}) where {T<:BlasFloat} = + LAPACK.gemqrt!('R', 'N', B.factors, B.T, A) +rmul!(A::StridedVecOrMat{T}, B::QRPackedQ{T,<:StridedMatrix}) where {T<:BlasFloat} = + LAPACK.ormqr!('R', 'N', B.factors, B.τ, A) +function rmul!(A::AbstractMatrix, Q::QRPackedQ) + require_one_based_indexing(A) + 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)")) + end + Qfactors = Q.factors + @inbounds begin + for k = 1:min(mQ,nQ) + for i = 1:mA + vAi = A[i,k] + for j = k+1:mQ + vAi += A[i,j]*Qfactors[j,k] + end + vAi = vAi*Q.τ[k] + A[i,k] -= vAi + for j = k+1:nA + A[i,j] -= vAi*conj(Qfactors[j,k]) + end + end + end + end + A +end + +### AQc +rmul!(A::StridedVecOrMat{T}, adjQ::AdjointQ{<:Any,<:QRCompactWYQ{T}}) where {T<:BlasReal} = + (Q = adjQ.Q; LAPACK.gemqrt!('R', 'T', Q.factors, Q.T, A)) +rmul!(A::StridedVecOrMat{T}, adjQ::AdjointQ{<:Any,<:QRCompactWYQ{T}}) where {T<:BlasComplex} = + (Q = adjQ.Q; LAPACK.gemqrt!('R', 'C', Q.factors, Q.T, A)) +rmul!(A::StridedVecOrMat{T}, adjQ::AdjointQ{<:Any,<:QRPackedQ{T}}) where {T<:BlasReal} = + (Q = adjQ.Q; LAPACK.ormqr!('R', 'T', Q.factors, Q.τ, A)) +rmul!(A::StridedVecOrMat{T}, adjQ::AdjointQ{<:Any,<:QRPackedQ{T}}) where {T<:BlasComplex} = + (Q = adjQ.Q; LAPACK.ormqr!('R', 'C', Q.factors, Q.τ, A)) +function rmul!(A::AbstractMatrix, adjQ::AdjointQ{<:Any,<:QRPackedQ}) + require_one_based_indexing(A) + Q = adjQ.Q + 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)")) + end + Qfactors = Q.factors + @inbounds begin + for k = min(mQ,nQ):-1:1 + for i = 1:mA + vAi = A[i,k] + for j = k+1:mQ + vAi += A[i,j]*Qfactors[j,k] + end + vAi = vAi*conj(Q.τ[k]) + A[i,k] -= vAi + for j = k+1:nA + A[i,j] -= vAi*conj(Qfactors[j,k]) + end + end + end + end + A +end + +det(Q::QRPackedQ) = _det_tau(Q.τ) +det(Q::QRCompactWYQ) = + prod(i -> _det_tau(_diagview(Q.T[:, i:min(i + size(Q.T, 1), size(Q.T, 2))])), + 1:size(Q.T, 1):size(Q.T, 2)) + +_diagview(A) = @view A[diagind(A)] + +# Compute `det` from the number of Householder reflections. Handle +# the case `Q.τ` contains zeros. +_det_tau(τs::AbstractVector{<:Real}) = + isodd(count(!iszero, τs)) ? -one(eltype(τs)) : one(eltype(τs)) + +# In complex case, we need to compute the non-unit eigenvalue `λ = 1 - c*τ` +# (where `c = v'v`) of each Householder reflector. As we know that the +# reflector must have the determinant of 1, it must satisfy `abs2(λ) == 1`. +# Combining this with the constraint `c > 0`, it turns out that the eigenvalue +# (hence the determinant) can be computed as `λ = -sign(τ)^2`. +# See: https://github.com/JuliaLang/julia/pull/32887#issuecomment-521935716 +_det_tau(τs) = prod(τ -> iszero(τ) ? one(τ) : -sign(τ)^2, τs) + +########################################################### +######## Q from Hessenberg decomposition ################## +########################################################### + +""" + HessenbergQ <: AbstractQ + +Given a [`Hessenberg`](@ref) factorization object `F`, `F.Q` returns +a `HessenbergQ` object, which is an implicit representation of the unitary +matrix `Q` in the Hessenberg factorization `QHQ'` represented by `F`. +This `F.Q` object can be efficiently multiplied by matrices or vectors, +and can be converted to an ordinary matrix type with `Matrix(F.Q)`. +""" +struct HessenbergQ{T,S<:AbstractMatrix,W<:AbstractVector,sym} <: AbstractQ{T} + uplo::Char + factors::S + τ::W + function HessenbergQ{T,S,W,sym}(uplo::AbstractChar, factors, τ) where {T,S<:AbstractMatrix,W<:AbstractVector,sym} + new(uplo, factors, τ) + end +end +HessenbergQ(F::Hessenberg{<:Any,<:UpperHessenberg,S,W}) where {S,W} = HessenbergQ{eltype(F.factors),S,W,false}(F.uplo, F.factors, F.τ) +HessenbergQ(F::Hessenberg{<:Any,<:SymTridiagonal,S,W}) where {S,W} = HessenbergQ{eltype(F.factors),S,W,true}(F.uplo, F.factors, F.τ) + +size(Q::HessenbergQ, dim::Integer) = size(getfield(Q, :factors), dim == 2 ? 1 : dim) +size(Q::HessenbergQ) = size(Q, 1), size(Q, 2) + +# HessenbergQ from LAPACK/BLAS (as opposed to Julia libraries like GenericLinearAlgebra) +const BlasHessenbergQ{T,sym} = HessenbergQ{T,<:StridedMatrix{T},<:StridedVector{T},sym} where {T<:BlasFloat,sym} + +## reconstruct the original matrix +Matrix{T}(Q::BlasHessenbergQ{<:Any,false}) where {T} = convert(Matrix{T}, LAPACK.orghr!(1, size(Q.factors, 1), copy(Q.factors), Q.τ)) +Matrix{T}(Q::BlasHessenbergQ{<:Any,true}) where {T} = convert(Matrix{T}, LAPACK.orgtr!(Q.uplo, copy(Q.factors), Q.τ)) + +lmul!(Q::BlasHessenbergQ{T,false}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = + LAPACK.ormhr!('L', 'N', 1, size(Q.factors, 1), Q.factors, Q.τ, X) +rmul!(X::StridedVecOrMat{T}, Q::BlasHessenbergQ{T,false}) where {T<:BlasFloat} = + LAPACK.ormhr!('R', 'N', 1, size(Q.factors, 1), Q.factors, Q.τ, X) +lmul!(adjQ::AdjointQ{<:Any,<:BlasHessenbergQ{T,false}}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = + (Q = adjQ.Q; LAPACK.ormhr!('L', ifelse(T<:Real, 'T', 'C'), 1, size(Q.factors, 1), Q.factors, Q.τ, X)) +rmul!(X::StridedVecOrMat{T}, adjQ::AdjointQ{<:Any,<:BlasHessenbergQ{T,false}}) where {T<:BlasFloat} = + (Q = adjQ.Q; LAPACK.ormhr!('R', ifelse(T<:Real, 'T', 'C'), 1, size(Q.factors, 1), Q.factors, Q.τ, X)) + +lmul!(Q::BlasHessenbergQ{T,true}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = + LAPACK.ormtr!('L', Q.uplo, 'N', Q.factors, Q.τ, X) +rmul!(X::StridedVecOrMat{T}, Q::BlasHessenbergQ{T,true}) where {T<:BlasFloat} = + LAPACK.ormtr!('R', Q.uplo, 'N', Q.factors, Q.τ, X) +lmul!(adjQ::AdjointQ{<:Any,<:BlasHessenbergQ{T,true}}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = + (Q = adjQ.Q; LAPACK.ormtr!('L', Q.uplo, ifelse(T<:Real, 'T', 'C'), Q.factors, Q.τ, X)) +rmul!(X::StridedVecOrMat{T}, adjQ::AdjointQ{<:Any,<:BlasHessenbergQ{T,true}}) where {T<:BlasFloat} = + (Q = adjQ.Q; LAPACK.ormtr!('R', Q.uplo, ifelse(T<:Real, 'T', 'C'), Q.factors, Q.τ, X)) + +lmul!(Q::HessenbergQ{T}, X::Adjoint{T,<:StridedVecOrMat{T}}) where {T} = rmul!(X', Q')' +rmul!(X::Adjoint{T,<:StridedVecOrMat{T}}, Q::HessenbergQ{T}) where {T} = lmul!(Q', X')' +lmul!(adjQ::AdjointQ{<:Any,<:HessenbergQ{T}}, X::Adjoint{T,<:StridedVecOrMat{T}}) where {T} = rmul!(X', adjQ')' +rmul!(X::Adjoint{T,<:StridedVecOrMat{T}}, adjQ::AdjointQ{<:Any,<:HessenbergQ{T}}) where {T} = lmul!(adjQ', X')' + +# flexible left-multiplication (and adjoint right-multiplication) +function (*)(Q::Union{QRPackedQ,QRCompactWYQ,HessenbergQ}, b::AbstractVector) + T = promote_type(eltype(Q), eltype(b)) + if size(Q.factors, 1) == length(b) + bnew = copy_similar(b, T) + elseif size(Q.factors, 2) == length(b) + bnew = [b; zeros(T, size(Q.factors, 1) - length(b))] + else + throw(DimensionMismatch("vector must have length either $(size(Q.factors, 1)) or $(size(Q.factors, 2))")) + end + lmul!(convert(AbstractQ{T}, Q), bnew) +end +function (*)(Q::Union{QRPackedQ,QRCompactWYQ,HessenbergQ}, B::AbstractMatrix) + T = promote_type(eltype(Q), eltype(B)) + if size(Q.factors, 1) == size(B, 1) + Bnew = copy_similar(B, T) + elseif size(Q.factors, 2) == size(B, 1) + Bnew = [B; zeros(T, size(Q.factors, 1) - size(B,1), size(B, 2))] + else + throw(DimensionMismatch("first dimension of matrix must have size either $(size(Q.factors, 1)) or $(size(Q.factors, 2))")) + end + lmul!(convert(AbstractQ{T}, Q), Bnew) +end +function (*)(A::AbstractMatrix, adjQ::AdjointQ{<:Any,<:Union{QRPackedQ,QRCompactWYQ,HessenbergQ}}) + Q = adjQ.Q + T = promote_type(eltype(A), eltype(adjQ)) + adjQQ = convert(AbstractQ{T}, adjQ) + if size(A, 2) == size(Q.factors, 1) + AA = copy_similar(A, T) + return rmul!(AA, adjQQ) + elseif size(A, 2) == size(Q.factors, 2) + return rmul!([A zeros(T, size(A, 1), size(Q.factors, 1) - size(Q.factors, 2))], adjQQ) + else + throw(DimensionMismatch("matrix A has dimensions $(size(A)) but Q-matrix B has dimensions $(size(adjQ))")) + end +end +(*)(u::AdjointAbsVec, Q::AdjointQ{<:Any,<:Union{QRPackedQ,QRCompactWYQ,HessenbergQ}}) = (Q'u')' + +det(Q::HessenbergQ) = _det_tau(Q.τ) + +########################################################### +################ Q from LQ decomposition ################## +########################################################### + +struct LQPackedQ{T,S<:AbstractMatrix{T},C<:AbstractVector{T}} <: AbstractQ{T} + factors::S + τ::C +end + +LQPackedQ{T}(Q::LQPackedQ) where {T} = LQPackedQ(convert(AbstractMatrix{T}, Q.factors), convert(AbstractVector{T}, Q.τ)) +@deprecate(AbstractMatrix{T}(Q::LQPackedQ) where {T}, + convert(AbstractQ{T}, Q), + false) +Matrix{T}(A::LQPackedQ) where {T} = convert(Matrix{T}, LAPACK.orglq!(copy(A.factors), A.τ)) +convert(::Type{AbstractQ{T}}, Q::LQPackedQ) where {T} = LQPackedQ{T}(Q) + +# size(Q::LQPackedQ) yields the shape of Q's square form +size(Q::LQPackedQ) = (n = size(Q.factors, 2); return n, n) + +## Multiplication +### QB / QcB +lmul!(A::LQPackedQ{T}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = LAPACK.ormlq!('L','N',A.factors,A.τ,B) +lmul!(adjA::AdjointQ{<:Any,<:LQPackedQ{T}}, B::StridedVecOrMat{T}) where {T<:BlasReal} = + (A = adjA.Q; LAPACK.ormlq!('L', 'T', A.factors, A.τ, B)) +lmul!(adjA::AdjointQ{<:Any,<:LQPackedQ{T}}, B::StridedVecOrMat{T}) where {T<:BlasComplex} = + (A = adjA.Q; LAPACK.ormlq!('L', 'C', A.factors, A.τ, B)) + +function (*)(adjA::AdjointQ{<:Any,<:LQPackedQ}, B::AbstractVector) + A = adjA.Q + T = promote_type(eltype(A), eltype(B)) + if length(B) == size(A.factors, 2) + C = copy_similar(B, T) + elseif length(B) == size(A.factors, 1) + C = [B; zeros(T, size(A.factors, 2) - size(A.factors, 1), size(B, 2))] + else + throw(DimensionMismatch("length of B, $(length(B)), must equal one of the dimensions of A, $(size(A))")) + end + lmul!(convert(AbstractQ{T}, adjA), C) +end +function (*)(adjA::AdjointQ{<:Any,<:LQPackedQ}, B::AbstractMatrix) + A = adjA.Q + T = promote_type(eltype(A), eltype(B)) + if size(B,1) == size(A.factors,2) + C = copy_similar(B, T) + elseif size(B,1) == size(A.factors,1) + C = [B; zeros(T, size(A.factors, 2) - size(A.factors, 1), size(B, 2))] + else + throw(DimensionMismatch("first dimension of B, $(size(B,1)), must equal one of the dimensions of A, $(size(A))")) + end + lmul!(convert(AbstractQ{T}, adjA), C) +end + +# in-place right-application of LQPackedQs +# these methods require that the applied-to matrix's (A's) number of columns +# match the number of columns (nQ) of the LQPackedQ (Q) (necessary for in-place +# operation, and the underlying LAPACK routine (ormlq) treats the implicit Q +# as its (nQ-by-nQ) square form) +rmul!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasFloat} = + LAPACK.ormlq!('R', 'N', B.factors, B.τ, A) +rmul!(A::StridedMatrix{T}, adjB::AdjointQ{<:Any,<:LQPackedQ{T}}) where {T<:BlasReal} = + (B = adjB.Q; LAPACK.ormlq!('R', 'T', B.factors, B.τ, A)) +rmul!(A::StridedMatrix{T}, adjB::AdjointQ{<:Any,<:LQPackedQ{T}}) where {T<:BlasComplex} = + (B = adjB.Q; LAPACK.ormlq!('R', 'C', B.factors, B.τ, A)) + +# out-of-place right application of LQPackedQs +# +# these methods: (1) check whether the applied-to matrix's (A's) appropriate dimension +# (columns for A_*, rows for Ac_*) matches the number of columns (nQ) of the LQPackedQ (Q), +# and if so effectively apply Q's square form to A without additional shenanigans; and +# (2) if the preceding dimensions do not match, check whether the appropriate dimension of +# A instead matches the number of rows of the matrix of which Q is a factor (i.e. +# size(Q.factors, 1)), and if so implicitly apply Q's truncated form to A by zero extending +# A as necessary for check (1) to pass (if possible) and then applying Q's square form +# +function (*)(A::AbstractVector, Q::LQPackedQ) + T = promote_type(eltype(A), eltype(Q)) + if 1 == size(Q.factors, 2) + C = copy_similar(A, T) + elseif 1 == size(Q.factors, 1) + C = zeros(T, length(A), size(Q.factors, 2)) + copyto!(C, 1, A, 1, length(A)) + else + _rightappdimmismatch("columns") + end + return rmul!(C, convert(AbstractQ{T}, Q)) +end +function (*)(A::AbstractMatrix, Q::LQPackedQ) + T = promote_type(eltype(A), eltype(Q)) + if size(A, 2) == size(Q.factors, 2) + C = copy_similar(A, T) + elseif size(A, 2) == size(Q.factors, 1) + C = zeros(T, size(A, 1), size(Q.factors, 2)) + copyto!(C, 1, A, 1, length(A)) + else + _rightappdimmismatch("columns") + end + return rmul!(C, convert(AbstractQ{T}, Q)) +end +function (*)(adjA::AdjointAbsMat, Q::LQPackedQ) + A = adjA.parent + T = promote_type(eltype(A), eltype(Q)) + if size(A, 1) == size(Q.factors, 2) + C = copy_similar(adjA, T) + elseif size(A, 1) == size(Q.factors, 1) + C = zeros(T, size(A, 2), size(Q.factors, 2)) + adjoint!(view(C, :, 1:size(A, 1)), A) + else + _rightappdimmismatch("rows") + end + return rmul!(C, convert(AbstractQ{T}, Q)) +end +(*)(u::AdjointAbsVec, Q::LQPackedQ) = (Q'u')' + +_rightappdimmismatch(rowsorcols) = + throw(DimensionMismatch(string("the number of $(rowsorcols) of the matrix on the left ", + "must match either (1) the number of columns of the (LQPackedQ) matrix on the right ", + "or (2) the number of rows of that (LQPackedQ) matrix's internal representation ", + "(the factorization's originating matrix's number of rows)"))) + +# In LQ factorization, `Q` is expressed as the product of the adjoint of the +# reflectors. Thus, `det` has to be conjugated. +det(Q::LQPackedQ) = conj(_det_tau(Q.τ)) diff --git a/stdlib/LinearAlgebra/src/adjtrans.jl b/stdlib/LinearAlgebra/src/adjtrans.jl index b6a4548833eac..2f5c5508e0ee3 100644 --- a/stdlib/LinearAlgebra/src/adjtrans.jl +++ b/stdlib/LinearAlgebra/src/adjtrans.jl @@ -1,8 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using Base: @propagate_inbounds -import Base: length, size, axes, IndexStyle, getindex, setindex!, parent, vec, convert, similar - ### basic definitions (types, aliases, constructors, abstractarray interface, sundry similar) # note that Adjoint and Transpose must be able to wrap not only vectors and matrices @@ -12,7 +9,7 @@ import Base: length, size, axes, IndexStyle, getindex, setindex!, parent, vec, c Adjoint Lazy wrapper type for an adjoint view of the underlying linear algebra object, -usually an `AbstractVector`/`AbstractMatrix`, but also some `Factorization`, for instance. +usually an `AbstractVector`/`AbstractMatrix`. Usually, the `Adjoint` constructor should not be called directly, use [`adjoint`](@ref) instead. To materialize the view use [`copy`](@ref). @@ -21,15 +18,15 @@ This type is intended for linear algebra usage - for general data manipulation s # Examples ```jldoctest -julia> A = [3+2im 9+2im; 8+7im 4+6im] +julia> A = [3+2im 9+2im; 0 0] 2×2 Matrix{Complex{Int64}}: 3+2im 9+2im - 8+7im 4+6im + 0+0im 0+0im -julia> adjoint(A) +julia> Adjoint(A) 2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}: - 3-2im 8-7im - 9-2im 4-6im + 3-2im 0+0im + 9-2im 0+0im ``` """ struct Adjoint{T,S} <: AbstractMatrix{T} @@ -39,7 +36,7 @@ end Transpose Lazy wrapper type for a transpose view of the underlying linear algebra object, -usually an `AbstractVector`/`AbstractMatrix`, but also some `Factorization`, for instance. +usually an `AbstractVector`/`AbstractMatrix`. Usually, the `Transpose` constructor should not be called directly, use [`transpose`](@ref) instead. To materialize the view use [`copy`](@ref). @@ -48,15 +45,15 @@ This type is intended for linear algebra usage - for general data manipulation s # Examples ```jldoctest -julia> A = [3+2im 9+2im; 8+7im 4+6im] -2×2 Matrix{Complex{Int64}}: - 3+2im 9+2im - 8+7im 4+6im - -julia> transpose(A) -2×2 transpose(::Matrix{Complex{Int64}}) with eltype Complex{Int64}: - 3+2im 8+7im - 9+2im 4+6im +julia> A = [2 3; 0 0] +2×2 Matrix{Int64}: + 2 3 + 0 0 + +julia> Transpose(A) +2×2 transpose(::Matrix{Int64}) with eltype Int64: + 2 0 + 3 0 ``` """ struct Transpose{T,S} <: AbstractMatrix{T} @@ -67,6 +64,42 @@ end Adjoint(A) = Adjoint{Base.promote_op(adjoint,eltype(A)),typeof(A)}(A) Transpose(A) = Transpose{Base.promote_op(transpose,eltype(A)),typeof(A)}(A) +""" + adj_or_trans(::AbstractArray) -> adjoint|transpose|identity + adj_or_trans(::Type{<:AbstractArray}) -> adjoint|transpose|identity + +Return [`adjoint`](@ref) from an `Adjoint` type or object and +[`transpose`](@ref) from a `Transpose` type or object. Otherwise, +return [`identity`](@ref). Note that `Adjoint` and `Transpose` have +to be the outer-most wrapper object for a non-`identity` function to be +returned. +""" +adj_or_trans(::T) where {T<:AbstractArray} = adj_or_trans(T) +adj_or_trans(::Type{<:AbstractArray}) = identity +adj_or_trans(::Type{<:Adjoint}) = adjoint +adj_or_trans(::Type{<:Transpose}) = transpose + +""" + inplace_adj_or_trans(::AbstractArray) -> adjoint!|transpose!|copyto! + inplace_adj_or_trans(::Type{<:AbstractArray}) -> adjoint!|transpose!|copyto! + +Return [`adjoint!`](@ref) from an `Adjoint` type or object and +[`transpose!`](@ref) from a `Transpose` type or object. Otherwise, +return [`copyto!`](@ref). Note that `Adjoint` and `Transpose` have +to be the outer-most wrapper object for a non-`identity` function to be +returned. +""" +inplace_adj_or_trans(::T) where {T <: AbstractArray} = inplace_adj_or_trans(T) +inplace_adj_or_trans(::Type{<:AbstractArray}) = copyto! +inplace_adj_or_trans(::Type{<:Adjoint}) = adjoint! +inplace_adj_or_trans(::Type{<:Transpose}) = transpose! + +adj_or_trans_char(::T) where {T<:AbstractArray} = adj_or_trans_char(T) +adj_or_trans_char(::Type{<:AbstractArray}) = 'N' +adj_or_trans_char(::Type{<:Adjoint}) = 'C' +adj_or_trans_char(::Type{<:Adjoint{<:Real}}) = 'T' +adj_or_trans_char(::Type{<:Transpose}) = 'T' + Base.dataids(A::Union{Adjoint, Transpose}) = Base.dataids(A.parent) Base.unaliascopy(A::Union{Adjoint,Transpose}) = typeof(A)(Base.unaliascopy(A.parent)) @@ -86,24 +119,84 @@ This operation is intended for linear algebra usage - for general data manipulat # Examples ```jldoctest -julia> A = [3+2im 9+2im; 8+7im 4+6im] +julia> A = [3+2im 9+2im; 0 0] 2×2 Matrix{Complex{Int64}}: 3+2im 9+2im - 8+7im 4+6im + 0+0im 0+0im -julia> adjoint(A) +julia> B = A' # equivalently adjoint(A) 2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}: - 3-2im 8-7im - 9-2im 4-6im + 3-2im 0+0im + 9-2im 0+0im + +julia> B isa Adjoint +true + +julia> adjoint(B) === A # the adjoint of an adjoint unwraps the parent +true +julia> Adjoint(B) # however, the constructor always wraps its argument +2×2 adjoint(adjoint(::Matrix{Complex{Int64}})) with eltype Complex{Int64}: + 3+2im 9+2im + 0+0im 0+0im + +julia> B[1,2] = 4 + 5im; # modifying B will modify A automatically + +julia> A +2×2 Matrix{Complex{Int64}}: + 3+2im 9+2im + 4-5im 0+0im +``` + +For real matrices, the `adjoint` operation is equivalent to a `transpose`. + +```jldoctest +julia> A = reshape([x for x in 1:4], 2, 2) +2×2 Matrix{Int64}: + 1 3 + 2 4 + +julia> A' +2×2 adjoint(::Matrix{Int64}) with eltype Int64: + 1 2 + 3 4 + +julia> adjoint(A) == transpose(A) +true +``` + +The adjoint of an `AbstractVector` is a row-vector: +```jldoctest julia> x = [3, 4im] 2-element Vector{Complex{Int64}}: 3 + 0im 0 + 4im -julia> x'x +julia> x' +1×2 adjoint(::Vector{Complex{Int64}}) with eltype Complex{Int64}: + 3+0im 0-4im + +julia> x'x # compute the dot product, equivalently x' * x 25 + 0im ``` + +For a matrix of matrices, the individual blocks are recursively operated on: +```jldoctest +julia> A = reshape([x + im*x for x in 1:4], 2, 2) +2×2 Matrix{Complex{Int64}}: + 1+1im 3+3im + 2+2im 4+4im + +julia> C = reshape([A, 2A, 3A, 4A], 2, 2) +2×2 Matrix{Matrix{Complex{Int64}}}: + [1+1im 3+3im; 2+2im 4+4im] [3+3im 9+9im; 6+6im 12+12im] + [2+2im 6+6im; 4+4im 8+8im] [4+4im 12+12im; 8+8im 16+16im] + +julia> C' +2×2 adjoint(::Matrix{Matrix{Complex{Int64}}}) with eltype Adjoint{Complex{Int64}, Matrix{Complex{Int64}}}: + [1-1im 2-2im; 3-3im 4-4im] [2-2im 4-4im; 6-6im 8-8im] + [3-3im 6-6im; 9-9im 12-12im] [4-4im 8-8im; 12-12im 16-16im] +``` """ adjoint(A::AbstractVecOrMat) = Adjoint(A) @@ -119,15 +212,78 @@ This operation is intended for linear algebra usage - for general data manipulat # Examples ```jldoctest -julia> A = [3+2im 9+2im; 8+7im 4+6im] +julia> A = [3 2; 0 0] +2×2 Matrix{Int64}: + 3 2 + 0 0 + +julia> B = transpose(A) +2×2 transpose(::Matrix{Int64}) with eltype Int64: + 3 0 + 2 0 + +julia> B isa Transpose +true + +julia> transpose(B) === A # the transpose of a transpose unwraps the parent +true + +julia> Transpose(B) # however, the constructor always wraps its argument +2×2 transpose(transpose(::Matrix{Int64})) with eltype Int64: + 3 2 + 0 0 + +julia> B[1,2] = 4; # modifying B will modify A automatically + +julia> A +2×2 Matrix{Int64}: + 3 2 + 4 0 +``` + +For complex matrices, the `adjoint` operation is equivalent to a conjugate-transpose. +```jldoctest +julia> A = reshape([Complex(x, x) for x in 1:4], 2, 2) 2×2 Matrix{Complex{Int64}}: - 3+2im 9+2im - 8+7im 4+6im + 1+1im 3+3im + 2+2im 4+4im + +julia> adjoint(A) == conj(transpose(A)) +true +``` + +The `transpose` of an `AbstractVector` is a row-vector: +```jldoctest +julia> v = [1,2,3] +3-element Vector{Int64}: + 1 + 2 + 3 + +julia> transpose(v) # returns a row-vector +1×3 transpose(::Vector{Int64}) with eltype Int64: + 1 2 3 + +julia> transpose(v) * v # compute the dot product +14 +``` -julia> transpose(A) -2×2 transpose(::Matrix{Complex{Int64}}) with eltype Complex{Int64}: - 3+2im 8+7im - 9+2im 4+6im +For a matrix of matrices, the individual blocks are recursively operated on: +```jldoctest +julia> C = [1 3; 2 4] +2×2 Matrix{Int64}: + 1 3 + 2 4 + +julia> D = reshape([C, 2C, 3C, 4C], 2, 2) # construct a block matrix +2×2 Matrix{Matrix{Int64}}: + [1 3; 2 4] [3 9; 6 12] + [2 6; 4 8] [4 12; 8 16] + +julia> transpose(D) # blocks are recursively transposed +2×2 transpose(::Matrix{Matrix{Int64}}) with eltype Transpose{Int64, Matrix{Int64}}: + [1 2; 3 4] [2 4; 6 8] + [3 6; 9 12] [4 8; 12 16] ``` """ transpose(A::AbstractVecOrMat) = Transpose(A) @@ -168,6 +324,9 @@ wrapperop(_) = identity wrapperop(::Adjoint) = adjoint wrapperop(::Transpose) = transpose +# the following fallbacks can be removed if Adjoint/Transpose are restricted to AbstractVecOrMat +size(A::AdjOrTrans) = reverse(size(A.parent)) +axes(A::AdjOrTrans) = reverse(axes(A.parent)) # AbstractArray interface, basic definitions length(A::AdjOrTrans) = length(A.parent) size(v::AdjOrTransAbsVec) = (1, length(v.parent)) @@ -185,8 +344,8 @@ IndexStyle(::Type{<:AdjOrTransAbsMat}) = IndexCartesian() @propagate_inbounds getindex(v::AdjOrTransAbsVec, ::Colon, ::Colon) = wrapperop(v)(v.parent[:]) # conversion of underlying storage -convert(::Type{Adjoint{T,S}}, A::Adjoint) where {T,S} = Adjoint{T,S}(convert(S, A.parent)) -convert(::Type{Transpose{T,S}}, A::Transpose) where {T,S} = Transpose{T,S}(convert(S, A.parent)) +convert(::Type{Adjoint{T,S}}, A::Adjoint) where {T,S} = Adjoint{T,S}(convert(S, A.parent))::Adjoint{T,S} +convert(::Type{Transpose{T,S}}, A::Transpose) where {T,S} = Transpose{T,S}(convert(S, A.parent))::Transpose{T,S} # Strides and pointer for transposed strided arrays — but only if the elements are actually stored in memory Base.strides(A::Adjoint{<:Real, <:AbstractVector}) = (stride(A.parent, 2), stride(A.parent, 1)) @@ -255,28 +414,46 @@ Broadcast.broadcast_preserving_zero_d(f, tvs::Union{Number,TransposeAbsVec}...) ### reductions -# faster to sum the Array than to work through the wrapper -Base._mapreduce_dim(f, op, init::Base._InitialValue, A::Transpose, dims::Colon) = - transpose(Base._mapreduce_dim(_sandwich(transpose, f), _sandwich(transpose, op), init, parent(A), dims)) -Base._mapreduce_dim(f, op, init::Base._InitialValue, A::Adjoint, dims::Colon) = - adjoint(Base._mapreduce_dim(_sandwich(adjoint, f), _sandwich(adjoint, op), init, parent(A), dims)) +# faster to sum the Array than to work through the wrapper (but only in commutative reduction ops as in Base/permuteddimsarray.jl) +Base._mapreduce_dim(f, op::CommutativeOps, init::Base._InitialValue, A::Transpose, dims::Colon) = + Base._mapreduce_dim(f∘transpose, op, init, parent(A), dims) +Base._mapreduce_dim(f, op::CommutativeOps, init::Base._InitialValue, A::Adjoint, dims::Colon) = + Base._mapreduce_dim(f∘adjoint, op, init, parent(A), dims) +# in prod, use fast path only in the commutative case to avoid surprises +Base._mapreduce_dim(f::typeof(identity), op::Union{typeof(*),typeof(Base.mul_prod)}, init::Base._InitialValue, A::Transpose{<:Union{Real,Complex}}, dims::Colon) = + Base._mapreduce_dim(f∘transpose, op, init, parent(A), dims) +Base._mapreduce_dim(f::typeof(identity), op::Union{typeof(*),typeof(Base.mul_prod)}, init::Base._InitialValue, A::Adjoint{<:Union{Real,Complex}}, dims::Colon) = + Base._mapreduce_dim(f∘adjoint, op, init, parent(A), dims) +# count allows for optimization only if the parent array has Bool eltype +Base._count(::typeof(identity), A::Transpose{Bool}, ::Colon, init) = Base._count(identity, parent(A), :, init) +Base._count(::typeof(identity), A::Adjoint{Bool}, ::Colon, init) = Base._count(identity, parent(A), :, init) +Base._any(f, A::Transpose, ::Colon) = Base._any(f∘transpose, parent(A), :) +Base._any(f, A::Adjoint, ::Colon) = Base._any(f∘adjoint, parent(A), :) +Base._all(f, A::Transpose, ::Colon) = Base._all(f∘transpose, parent(A), :) +Base._all(f, A::Adjoint, ::Colon) = Base._all(f∘adjoint, parent(A), :) # sum(A'; dims) -Base.mapreducedim!(f, op, B::AbstractArray, A::TransposeAbsMat) = - transpose(Base.mapreducedim!(_sandwich(transpose, f), _sandwich(transpose, op), transpose(B), parent(A))) -Base.mapreducedim!(f, op, B::AbstractArray, A::AdjointAbsMat) = - adjoint(Base.mapreducedim!(_sandwich(adjoint, f), _sandwich(adjoint, op), adjoint(B), parent(A))) - -_sandwich(adj::Function, fun) = (xs...,) -> adj(fun(map(adj, xs)...)) -for fun in [:identity, :add_sum, :mul_prod] #, :max, :min] - @eval _sandwich(::Function, ::typeof(Base.$fun)) = Base.$fun -end - +Base.mapreducedim!(f, op::CommutativeOps, B::AbstractArray, A::TransposeAbsMat) = + (Base.mapreducedim!(f∘transpose, op, switch_dim12(B), parent(A)); B) +Base.mapreducedim!(f, op::CommutativeOps, B::AbstractArray, A::AdjointAbsMat) = + (Base.mapreducedim!(f∘adjoint, op, switch_dim12(B), parent(A)); B) +Base.mapreducedim!(f::typeof(identity), op::Union{typeof(*),typeof(Base.mul_prod)}, B::AbstractArray, A::TransposeAbsMat{<:Union{Real,Complex}}) = + (Base.mapreducedim!(f∘transpose, op, switch_dim12(B), parent(A)); B) +Base.mapreducedim!(f::typeof(identity), op::Union{typeof(*),typeof(Base.mul_prod)}, B::AbstractArray, A::AdjointAbsMat{<:Union{Real,Complex}}) = + (Base.mapreducedim!(f∘adjoint, op, switch_dim12(B), parent(A)); B) + +switch_dim12(B::AbstractVector) = permutedims(B) +switch_dim12(B::AbstractVector{<:Number}) = transpose(B) # avoid allocs due to permutedims +switch_dim12(B::AbstractArray{<:Any,0}) = B +switch_dim12(B::AbstractArray) = PermutedDimsArray(B, (2, 1, ntuple(Base.Fix1(+,2), ndims(B) - 2)...)) ### linear algebra (-)(A::Adjoint) = Adjoint( -A.parent) (-)(A::Transpose) = Transpose(-A.parent) +tr(A::Adjoint) = adjoint(tr(parent(A))) +tr(A::Transpose) = transpose(tr(parent(A))) + ## multiplication * function _dot_nonrecursive(u, v) @@ -323,8 +500,8 @@ pinv(v::TransposeAbsVec, tol::Real = 0) = pinv(conj(v.parent)).parent ## right-division / /(u::AdjointAbsVec, A::AbstractMatrix) = adjoint(adjoint(A) \ u.parent) /(u::TransposeAbsVec, A::AbstractMatrix) = transpose(transpose(A) \ u.parent) -/(u::AdjointAbsVec, A::Transpose{<:Any,<:AbstractMatrix}) = adjoint(conj(A.parent) \ u.parent) # technically should be adjoint(copy(adjoint(copy(A))) \ u.parent) -/(u::TransposeAbsVec, A::Adjoint{<:Any,<:AbstractMatrix}) = transpose(conj(A.parent) \ u.parent) # technically should be transpose(copy(transpose(copy(A))) \ u.parent) +/(u::AdjointAbsVec, A::TransposeAbsMat) = adjoint(conj(A.parent) \ u.parent) # technically should be adjoint(copy(adjoint(copy(A))) \ u.parent) +/(u::TransposeAbsVec, A::AdjointAbsMat) = transpose(conj(A.parent) \ u.parent) # technically should be transpose(copy(transpose(copy(A))) \ u.parent) ## complex conjugate conj(A::Transpose) = adjoint(A.parent) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index 243553ebc64c6..dd3783d67b0cf 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -22,6 +22,9 @@ function Bidiagonal{T}(dv::AbstractVector, ev::AbstractVector, uplo::Union{Symbo convert(AbstractVector{T}, ev)::AbstractVector{T}, uplo) end +function Bidiagonal{T,V}(A::Bidiagonal) where {T,V<:AbstractVector{T}} + Bidiagonal{T,V}(A.dv, A.ev, A.uplo) +end """ Bidiagonal(dv::V, ev::V, uplo::Symbol) where V <: AbstractVector @@ -168,21 +171,19 @@ end function Matrix{T}(A::Bidiagonal) where T n = size(A, 1) B = zeros(T, n, n) - if n == 0 - return B - end - for i = 1:n - 1 + n == 0 && return B + @inbounds for i = 1:n - 1 B[i,i] = A.dv[i] if A.uplo == 'U' - B[i, i + 1] = A.ev[i] + B[i,i+1] = A.ev[i] else - B[i + 1, i] = A.ev[i] + B[i+1,i] = A.ev[i] end end B[n,n] = A.dv[n] return B end -Matrix(A::Bidiagonal{T}) where {T} = Matrix{T}(A) +Matrix(A::Bidiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(A) Array(A::Bidiagonal) = Matrix(A) promote_rule(::Type{Matrix{T}}, ::Type{<:Bidiagonal{S}}) where {T,S} = @isdefined(T) && @isdefined(S) ? Matrix{promote_type(T,S)} : Matrix @@ -202,11 +203,19 @@ promote_rule(::Type{<:Tridiagonal}, ::Type{<:Bidiagonal}) = Tridiagonal # When asked to convert Bidiagonal to AbstractMatrix{T}, preserve structure by converting to Bidiagonal{T} <: AbstractMatrix{T} AbstractMatrix{T}(A::Bidiagonal) where {T} = convert(Bidiagonal{T}, A) -convert(T::Type{<:Bidiagonal}, m::AbstractMatrix) = m isa T ? m : T(m) +convert(::Type{T}, m::AbstractMatrix) where {T<:Bidiagonal} = m isa T ? m : T(m)::T similar(B::Bidiagonal, ::Type{T}) where {T} = Bidiagonal(similar(B.dv, T), similar(B.ev, T), B.uplo) -similar(B::Bidiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(B::Bidiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(B.dv, T, dims) +tr(B::Bidiagonal) = sum(B.dv) + +function kron(A::Diagonal, B::Bidiagonal) + # `_droplast!` is only guaranteed to work with `Vector` + kdv = _makevector(kron(diag(A), B.dv)) + kev = _droplast!(_makevector(kron(diag(A), _pushzero(B.ev)))) + Bidiagonal(kdv, kev, B.uplo) +end ################### # LAPACK routines # @@ -396,41 +405,30 @@ function ==(A::Bidiagonal, B::Bidiagonal) end end +const BandedMatrix = Union{Bidiagonal,Diagonal,Tridiagonal,SymTridiagonal} # or BiDiTriSym const BiTriSym = Union{Bidiagonal,Tridiagonal,SymTridiagonal} const BiTri = Union{Bidiagonal,Tridiagonal} -@inline mul!(C::AbstractMatrix, A::SymTridiagonal, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::AbstractTriangular, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::Adjoint{<:Any,<:AbstractVecOrMat}, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::Transpose{<:Any,<:AbstractVecOrMat}, B::BiTriSym, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractVector, A::BiTriSym, B::AbstractVector, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTriSym, B::AbstractVecOrMat, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTriSym, B::Transpose{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::BiTriSym, B::Adjoint{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = A_mul_B_td!(C, A, B, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractVector, A::BiTriSym, B::Transpose{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = throw(MethodError(mul!, (C, A, B)), MulAddMul(alpha, beta)) -@inline mul!(C::AbstractVector, A::BiTriSym, B::Adjoint{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = throw(MethodError(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)) function check_A_mul_B!_sizes(C, A, B) - require_one_based_indexing(C) - require_one_based_indexing(A) - require_one_based_indexing(B) - nA, mA = size(A) - nB, mB = size(B) - nC, mC = size(C) - if nA != nC - throw(DimensionMismatch("sizes size(A)=$(size(A)) and size(C) = $(size(C)) must match at first entry.")) - elseif mA != nB - throw(DimensionMismatch("second entry of size(A)=$(size(A)) and first entry of size(B) = $(size(B)) must match.")) - elseif mB != mC - throw(DimensionMismatch("sizes size(B)=$(size(B)) and size(C) = $(size(C)) must match at first second entry.")) + 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")) + elseif nA != mB + throw(DimensionMismatch("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")) end end # function to get the internally stored vectors for Bidiagonal and [Sym]Tridiagonal -# to avoid allocations in A_mul_B_td! below (#24324, #24578) +# to avoid allocations in _mul! below (#24324, #24578) _diag(A::Tridiagonal, k) = k == -1 ? A.dl : k == 0 ? A.d : A.du _diag(A::SymTridiagonal, k) = k == 0 ? A.dv : A.ev function _diag(A::Bidiagonal, k) @@ -443,8 +441,7 @@ function _diag(A::Bidiagonal, k) end end -function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, _add::MulAddMul = MulAddMul()) 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) @@ -501,10 +498,11 @@ function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, C end -function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C) check_A_mul_B!_sizes(C, A, B) n = size(A,1) + iszero(n) && return C n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) _rmul_or_fill!(C, _add.beta) # see the same use above iszero(_add.alpha) && return C @@ -536,10 +534,8 @@ function A_mul_B_td!(C::AbstractMatrix, A::BiTriSym, B::Diagonal, C end -function A_mul_B_td!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, - _add::MulAddMul = MulAddMul()) - require_one_based_indexing(C) - require_one_based_indexing(B) +function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C, B) nA = size(A,1) nB = size(B,2) if !(size(C,1) == size(B,1) == nA) @@ -548,6 +544,7 @@ function A_mul_B_td!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, 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")) end + iszero(nA) && return C iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) nA <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) l = _diag(A, -1) @@ -567,8 +564,8 @@ function A_mul_B_td!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, C end -function A_mul_B_td!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, _add::MulAddMul = 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) n = size(A,1) @@ -602,8 +599,8 @@ function A_mul_B_td!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, C end -function A_mul_B_td!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, - _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C) 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) @@ -638,80 +635,55 @@ function A_mul_B_td!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, C end -function *(A::AbstractTriangular, B::Union{SymTridiagonal, Tridiagonal}) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(zeros(TS, size(A)), A, B) -end - -const UpperOrUnitUpperTriangular{T} = Union{UpperTriangular{T}, UnitUpperTriangular{T}} -const LowerOrUnitLowerTriangular{T} = Union{LowerTriangular{T}, UnitLowerTriangular{T}} - function *(A::UpperOrUnitUpperTriangular, B::Bidiagonal) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(A, TS, size(A)), A, B) return B.uplo == 'U' ? UpperTriangular(C) : C end function *(A::LowerOrUnitLowerTriangular, B::Bidiagonal) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(A, TS, size(A)), A, B) return B.uplo == 'L' ? LowerTriangular(C) : C end -function *(A::Union{SymTridiagonal, Tridiagonal}, B::AbstractTriangular) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(zeros(TS, size(A)), A, B) -end - function *(A::Bidiagonal, B::UpperOrUnitUpperTriangular) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(B, TS, size(B)), A, B) return A.uplo == 'U' ? UpperTriangular(C) : C end function *(A::Bidiagonal, B::LowerOrUnitLowerTriangular) TS = promote_op(matprod, eltype(A), eltype(B)) - C = A_mul_B_td!(zeros(TS, size(A)), A, B) + C = mul!(similar(B, TS, size(B)), A, B) return A.uplo == 'L' ? LowerTriangular(C) : C end -function *(A::BiTri, B::Diagonal) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(similar(A, TS), A, B) -end - -function *(A::Diagonal, B::BiTri) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(similar(B, TS), A, B) -end - function *(A::Diagonal, B::SymTridiagonal) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(Tridiagonal(zeros(TS, size(A, 1)-1), zeros(TS, size(A, 1)), zeros(TS, size(A, 1)-1)), A, B) + TS = promote_op(*, eltype(A), eltype(B)) + out = Tridiagonal(similar(A, TS, size(A, 1)-1), similar(A, TS, size(A, 1)), similar(A, TS, size(A, 1)-1)) + mul!(out, A, B) end function *(A::SymTridiagonal, B::Diagonal) - TS = promote_op(matprod, eltype(A), eltype(B)) - A_mul_B_td!(Tridiagonal(zeros(TS, size(A, 1)-1), zeros(TS, size(A, 1)), zeros(TS, size(A, 1)-1)), A, B) -end - -function *(A::BiTriSym, B::BiTriSym) - TS = promote_op(matprod, eltype(A), eltype(B)) - mul!(similar(A, TS, size(A)), A, B) + TS = promote_op(*, eltype(A), eltype(B)) + out = Tridiagonal(similar(A, TS, size(A, 1)-1), similar(A, TS, size(A, 1)), similar(A, TS, size(A, 1)-1)) + mul!(out, A, B) end function dot(x::AbstractVector, B::Bidiagonal, y::AbstractVector) require_one_based_indexing(x, y) nx, ny = length(x), length(y) (nx == size(B, 1) == ny) || throw(DimensionMismatch()) - if iszero(nx) - return dot(zero(eltype(x)), zero(eltype(B)), zero(eltype(y))) + if nx ≤ 1 + nx == 0 && return dot(zero(eltype(x)), zero(eltype(B)), zero(eltype(y))) + return dot(x[1], B.dv[1], y[1]) end ev, dv = B.ev, B.dv - if B.uplo == 'U' + @inbounds if B.uplo == 'U' x₀ = x[1] r = dot(x[1], dv[1], y[1]) - @inbounds for j in 2:nx-1 + for j in 2:nx-1 x₋, x₀ = x₀, x[j] r += dot(adjoint(ev[j-1])*x₋ + adjoint(dv[j])*x₀, y[j]) end @@ -721,7 +693,7 @@ function dot(x::AbstractVector, B::Bidiagonal, y::AbstractVector) x₀ = x[1] x₊ = x[2] r = dot(adjoint(dv[1])*x₀ + adjoint(ev[1])*x₊, y[1]) - @inbounds for j in 2:nx-1 + for j in 2:nx-1 x₀, x₊ = x₊, x[j+1] r += dot(adjoint(dv[j])*x₀ + adjoint(ev[j])*x₊, y[j]) end @@ -767,57 +739,39 @@ function ldiv!(c::AbstractVecOrMat, A::Bidiagonal, b::AbstractVecOrMat) end return c end -ldiv!(A::Transpose{<:Any,<:Bidiagonal}, b::AbstractVecOrMat) = @inline ldiv!(b, A, b) -ldiv!(A::Adjoint{<:Any,<:Bidiagonal}, b::AbstractVecOrMat) = @inline ldiv!(b, A, b) -ldiv!(c::AbstractVecOrMat, A::Transpose{<:Any,<:Bidiagonal}, b::AbstractVecOrMat) = - (_rdiv!(transpose(c), transpose(b), transpose(A)); return c) -ldiv!(c::AbstractVecOrMat, A::Adjoint{<:Any,<:Bidiagonal}, b::AbstractVecOrMat) = - (_rdiv!(adjoint(c), adjoint(b), adjoint(A)); return c) +ldiv!(A::AdjOrTrans{<:Any,<:Bidiagonal}, b::AbstractVecOrMat) = @inline ldiv!(b, A, b) +ldiv!(c::AbstractVecOrMat, A::AdjOrTrans{<:Any,<:Bidiagonal}, b::AbstractVecOrMat) = + (t = adj_or_trans(A); _rdiv!(t(c), t(b), t(A)); return c) ### Generic promotion methods and fallbacks -function \(A::Bidiagonal{<:Number}, B::AbstractVecOrMat{<:Number}) - TA, TB = eltype(A), eltype(B) - TAB = typeof((oneunit(TA))\oneunit(TB)) - ldiv!(zeros(TAB, size(B)), A, B) -end -\(A::Bidiagonal, B::AbstractVecOrMat) = ldiv!(copy(B), A, B) -\(tA::Transpose{<:Any,<:Bidiagonal}, B::AbstractVecOrMat) = copy(tA) \ B -\(adjA::Adjoint{<:Any,<:Bidiagonal}, B::AbstractVecOrMat) = copy(adjA) \ B +\(A::Bidiagonal, B::AbstractVecOrMat) = ldiv!(_initarray(\, eltype(A), eltype(B), B), A, B) +\(xA::AdjOrTrans{<:Any,<:Bidiagonal}, B::AbstractVecOrMat) = copy(xA) \ B ### Triangular specializations -function \(B::Bidiagonal{<:Number}, U::UpperOrUnitUpperTriangular{<:Number}) - T = typeof((oneunit(eltype(B)))\oneunit(eltype(U))) - A = ldiv!(zeros(T, size(U)), B, U) - return B.uplo == 'U' ? UpperTriangular(A) : A -end -function \(B::Bidiagonal, U::UpperOrUnitUpperTriangular) - A = ldiv!(copy(parent(U)), B, U) - return B.uplo == 'U' ? UpperTriangular(A) : A -end -function \(B::Bidiagonal{<:Number}, L::LowerOrUnitLowerTriangular{<:Number}) - T = typeof((oneunit(eltype(B)))\oneunit(eltype(L))) - A = ldiv!(zeros(T, size(L)), B, L) - return B.uplo == 'L' ? LowerTriangular(A) : A +for tri in (:UpperTriangular, :UnitUpperTriangular) + @eval function \(B::Bidiagonal, U::$tri) + A = ldiv!(_initarray(\, eltype(B), eltype(U), 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) + return B.uplo == 'U' ? UpperTriangular(A) : A + end end -function \(B::Bidiagonal, L::LowerOrUnitLowerTriangular) - A = ldiv!(copy(parent(L)), B, L) - return B.uplo == 'L' ? LowerTriangular(A) : A +for tri in (:LowerTriangular, :UnitLowerTriangular) + @eval function \(B::Bidiagonal, L::$tri) + A = ldiv!(_initarray(\, eltype(B), eltype(L), 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) + return B.uplo == 'L' ? LowerTriangular(A) : A + end end -function \(U::UpperOrUnitUpperTriangular{<:Number}, B::Bidiagonal{<:Number}) - T = typeof((oneunit(eltype(U)))/oneunit(eltype(B))) - A = ldiv!(U, copy_similar(B, T)) - return B.uplo == 'U' ? UpperTriangular(A) : A -end -function \(L::LowerOrUnitLowerTriangular{<:Number}, B::Bidiagonal{<:Number}) - T = typeof((oneunit(eltype(L)))/oneunit(eltype(B))) - A = ldiv!(L, copy_similar(B, T)) - return B.uplo == 'L' ? LowerTriangular(A) : A -end ### Diagonal specialization -function \(B::Bidiagonal{<:Number}, D::Diagonal{<:Number}) - T = typeof((oneunit(eltype(B)))\oneunit(eltype(D))) - A = ldiv!(zeros(T, size(D)), B, D) +function \(B::Bidiagonal, D::Diagonal) + A = ldiv!(_initarray(\, eltype(B), eltype(D), D), B, D) return B.uplo == 'U' ? UpperTriangular(A) : LowerTriangular(A) end @@ -863,61 +817,43 @@ function _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::Bidiagonal) C end rdiv!(A::AbstractMatrix, B::Bidiagonal) = @inline _rdiv!(A, A, B) -rdiv!(A::AbstractMatrix, B::Adjoint{<:Any,<:Bidiagonal}) = @inline _rdiv!(A, A, B) -rdiv!(A::AbstractMatrix, B::Transpose{<:Any,<:Bidiagonal}) = @inline _rdiv!(A, A, B) -_rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::Adjoint{<:Any,<:Bidiagonal}) = - (ldiv!(adjoint(C), adjoint(B), adjoint(A)); return C) -_rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::Transpose{<:Any,<:Bidiagonal}) = - (ldiv!(transpose(C), transpose(B), transpose(A)); return C) +rdiv!(A::AbstractMatrix, B::AdjOrTrans{<:Any,<:Bidiagonal}) = @inline _rdiv!(A, A, B) +_rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::AdjOrTrans{<:Any,<:Bidiagonal}) = + (t = adj_or_trans(B); ldiv!(t(C), t(B), t(A)); return C) -function /(A::AbstractMatrix{<:Number}, B::Bidiagonal{<:Number}) - TA, TB = eltype(A), eltype(B) - TAB = typeof((oneunit(TA))/oneunit(TB)) - _rdiv!(zeros(TAB, size(A)), A, B) -end -/(A::AbstractMatrix, B::Bidiagonal) = _rdiv!(copy(A), A, B) +/(A::AbstractMatrix, B::Bidiagonal) = _rdiv!(_initarray(/, eltype(A), eltype(B), A), A, B) ### Triangular specializations -function /(U::UpperOrUnitUpperTriangular{<:Number}, B::Bidiagonal{<:Number}) - T = typeof((oneunit(eltype(U)))/oneunit(eltype(B))) - A = _rdiv!(zeros(T, size(U)), U, B) - return B.uplo == 'U' ? UpperTriangular(A) : A -end -function /(U::UpperOrUnitUpperTriangular, B::Bidiagonal) - A = _rdiv!(copy(parent(U)), U, B) - return B.uplo == 'U' ? UpperTriangular(A) : A -end -function /(L::LowerOrUnitLowerTriangular{<:Number}, B::Bidiagonal{<:Number}) - T = typeof((oneunit(eltype(L)))/oneunit(eltype(B))) - A = _rdiv!(zeros(T, size(L)), L, B) - return B.uplo == 'L' ? LowerTriangular(A) : A -end -function /(L::LowerOrUnitLowerTriangular, B::Bidiagonal) - A = _rdiv!(copy(parent(L)), L, B) - return B.uplo == 'L' ? LowerTriangular(A) : A -end -function /(B::Bidiagonal{<:Number}, U::UpperOrUnitUpperTriangular{<:Number}) - T = typeof((oneunit(eltype(B)))/oneunit(eltype(U))) - A = rdiv!(copy_similar(B, T), U) - return B.uplo == 'U' ? UpperTriangular(A) : A -end -function /(B::Bidiagonal{<:Number}, L::LowerOrUnitLowerTriangular{<:Number}) - T = typeof((oneunit(eltype(B)))\oneunit(eltype(L))) - A = rdiv!(copy_similar(B, T), L) - return B.uplo == 'L' ? LowerTriangular(A) : A +for tri in (:UpperTriangular, :UnitUpperTriangular) + @eval function /(U::$tri, B::Bidiagonal) + A = _rdiv!(_initarray(/, eltype(U), eltype(B), U), 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) + 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) + return B.uplo == 'L' ? LowerTriangular(A) : A + end + @eval function /(B::Bidiagonal, L::$tri) + A = _rdiv!(_initarray(/, eltype(B), eltype(L), L), B, L) + return B.uplo == 'L' ? LowerTriangular(A) : A + end +end + ### Diagonal specialization -function /(D::Diagonal{<:Number}, B::Bidiagonal{<:Number}) - T = typeof((oneunit(eltype(D)))/oneunit(eltype(B))) - A = _rdiv!(zeros(T, size(D)), D, B) +function /(D::Diagonal, B::Bidiagonal) + A = _rdiv!(_initarray(/, eltype(D), eltype(B), D), D, B) return B.uplo == 'U' ? UpperTriangular(A) : LowerTriangular(A) end /(A::AbstractMatrix, B::Transpose{<:Any,<:Bidiagonal}) = A / copy(B) /(A::AbstractMatrix, B::Adjoint{<:Any,<:Bidiagonal}) = A / copy(B) # disambiguation -/(A::AdjointAbsVec{<:Number}, B::Bidiagonal{<:Number}) = adjoint(adjoint(B) \ parent(A)) -/(A::TransposeAbsVec{<:Number}, B::Bidiagonal{<:Number}) = transpose(transpose(B) \ parent(A)) /(A::AdjointAbsVec, B::Bidiagonal) = adjoint(adjoint(B) \ parent(A)) /(A::TransposeAbsVec, B::Bidiagonal) = transpose(transpose(B) \ parent(A)) /(A::AdjointAbsVec, B::Transpose{<:Any,<:Bidiagonal}) = adjoint(adjoint(B) \ parent(A)) @@ -928,13 +864,13 @@ end factorize(A::Bidiagonal) = A function inv(B::Bidiagonal{T}) where T n = size(B, 1) - dest = zeros(typeof(oneunit(T)\one(T)), (n, n)) - ldiv!(dest, B, Diagonal{typeof(one(T)\one(T))}(I, n)) + dest = zeros(typeof(inv(oneunit(T))), (n, n)) + ldiv!(dest, B, Diagonal{typeof(one(T)/one(T))}(I, n)) return B.uplo == 'U' ? UpperTriangular(dest) : LowerTriangular(dest) end # Eigensystems -eigvals(M::Bidiagonal) = M.dv +eigvals(M::Bidiagonal) = copy(M.dv) function eigvecs(M::Bidiagonal{T}) where T n = length(M.dv) Q = Matrix{T}(undef, n,n) diff --git a/stdlib/LinearAlgebra/src/blas.jl b/stdlib/LinearAlgebra/src/blas.jl index e8d44c1ae1533..8da19baee5045 100644 --- a/stdlib/LinearAlgebra/src/blas.jl +++ b/stdlib/LinearAlgebra/src/blas.jl @@ -5,49 +5,64 @@ Interface to BLAS subroutines. """ module BLAS -import ..axpy!, ..axpby! 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 # Level 1 - asum, - axpy!, - axpby!, - blascopy!, - dotc, - dotu, + # xROTG + # xROTMG rot!, + # xROTM + # xSWAP scal!, scal, + blascopy!, + # xAXPY!, + # xAXPBY!, + # xDOT + dotc, + dotu, + # xxDOT nrm2, + asum, iamax, # Level 2 - gbmv!, - gbmv, gemv!, gemv, + gbmv!, + gbmv, hemv!, hemv, + # xHBMV hpmv!, + symv!, + symv, sbmv!, sbmv, spmv!, - spr!, - symv!, - symv, - trsv!, - trsv, trmv!, trmv, + # xTBMV + # xTPMV + trsv!, + trsv, + # xTBSV + # xTPSV ger!, - syr!, + # xGERU + # xGERC her!, + # xHPR + # xHER2 + # xHPR2 + syr!, + spr!, + # xSYR2 + # xSPR2 # Level 3 - herk!, - herk, - her2k!, - her2k, gemm!, gemm, symm!, @@ -56,16 +71,20 @@ export hemm, syrk!, syrk, + herk!, + herk, syr2k!, syr2k, + her2k!, + her2k, trmm!, trmm, trsm!, trsm -# Eventually this will be replaced with `libblastrampoline_jll.libblastrampoline` -const libblastrampoline = "libblastrampoline" -libblastrampoline_handle = C_NULL +using ..LinearAlgebra: libblastrampoline, BlasReal, BlasComplex, BlasFloat, BlasInt, DimensionMismatch, checksquare, stride1, chkstride1 + +include("lbt.jl") # Legacy bindings that some packages (such as NNlib.jl) use. # We maintain these for backwards-compatibility but new packages @@ -74,10 +93,7 @@ libblastrampoline_handle = C_NULL const libblas = libblastrampoline const liblapack = libblastrampoline -import LinearAlgebra -using LinearAlgebra: BlasReal, BlasComplex, BlasFloat, BlasInt, DimensionMismatch, checksquare, stride1, chkstride1 - -include("lbt.jl") +vendor() = :lbt """ get_config() @@ -89,17 +105,6 @@ Return an object representing the current `libblastrampoline` configuration. """ get_config() = lbt_get_config() -# We hard-lock `vendor()` to `openblas(64)` here to satisfy older code, but all new code should use -# `get_config()` since it is now possible to have multiple vendors loaded at once. -function vendor() - Base.depwarn("`vendor()` is deprecated, use `BLAS.get_config()` and inspect the output instead", :vendor; force=true) - if USE_BLAS64 - return :openblas64 - else - return :openblas - end -end - if USE_BLAS64 macro blasfunc(x) return Expr(:quote, Symbol(x, "64_")) @@ -161,12 +166,27 @@ end "Check that upper/lower (for special matrices) is correctly specified" function chkuplo(uplo::AbstractChar) if !(uplo == 'U' || uplo == 'L') - throw(ArgumentError("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 # Level 1 +# A help function to pick the pointer and inc for 1d like inputs. +@inline function vec_pointer_stride(x::AbstractArray, stride0check = nothing) + Base._checkcontiguous(Bool, x) && return pointer(x), 1 # simplify runtime check when possible + st, ptr = checkedstride(x), pointer(x) + isnothing(stride0check) || (st == 0 && throw(stride0check)) + ptr += min(st, 0) * sizeof(eltype(x)) * (length(x) - 1) + ptr, st +end +function checkedstride(x::AbstractArray) + szs::Dims = size(x) + sts::Dims = strides(x) + _, st, n = Base.merge_adjacent_dim(szs, sts) + n === ndims(x) && return st + throw(ArgumentError("only support vector like inputs")) +end ## copy """ @@ -257,7 +277,11 @@ for (fname, elty) in ((:dscal_,:Float64), DX end - scal!(DA::$elty, DX::AbstractArray{$elty}) = scal!(length(DX),DA,DX,stride(DX,1)) + function scal!(DA::$elty, DX::AbstractArray{$elty}) + p, st = vec_pointer_stride(DX, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve DX scal!(length(DX), DA, p, abs(st)) + DX + end end end scal(n, DA, DX, incx) = scal!(n, DA, copy(DX), incx) @@ -308,8 +332,8 @@ julia> BLAS.dotu(10, fill(1.0im, 10), 1, fill(1.0+im, 20), 2) """ function dotu end -for (fname, elty) in ((:ddot_,:Float64), - (:sdot_,:Float32)) +for (fname, elty) in ((:cblas_ddot,:Float64), + (:cblas_sdot,:Float32)) @eval begin # DOUBLE PRECISION FUNCTION DDOT(N,DX,INCX,DY,INCY) # * .. Scalar Arguments .. @@ -319,7 +343,7 @@ for (fname, elty) in ((:ddot_,:Float64), # DOUBLE PRECISION DX(*),DY(*) function dot(n::Integer, DX::Union{Ptr{$elty},AbstractArray{$elty}}, incx::Integer, DY::Union{Ptr{$elty},AbstractArray{$elty}}, incy::Integer) ccall((@blasfunc($fname), libblastrampoline), $elty, - (Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}), + (BlasInt, Ptr{$elty}, BlasInt, Ptr{$elty}, BlasInt), n, DX, incx, DY, incy) end end @@ -361,73 +385,16 @@ for (fname, elty) in ((:cblas_zdotu_sub,:ComplexF64), end end -@inline function _dot_length_check(x,y) - n = length(x) - if n != length(y) - throw(DimensionMismatch("dot product arguments have lengths $(length(x)) and $(length(y))")) - end - n -end - for (elty, f) in ((Float32, :dot), (Float64, :dot), (ComplexF32, :dotc), (ComplexF64, :dotc), (ComplexF32, :dotu), (ComplexF64, :dotu)) @eval begin - function $f(x::DenseArray{$elty}, y::DenseArray{$elty}) - n = _dot_length_check(x,y) - $f(n, x, 1, y, 1) - end - - function $f(x::StridedVector{$elty}, y::DenseArray{$elty}) - n = _dot_length_check(x,y) - xstride = stride(x,1) - ystride = stride(y,1) - x_delta = xstride < 0 ? n : 1 - GC.@preserve x $f(n, pointer(x, x_delta), xstride, y, ystride) - end - - function $f(x::DenseArray{$elty}, y::StridedVector{$elty}) - n = _dot_length_check(x,y) - xstride = stride(x,1) - ystride = stride(y,1) - y_delta = ystride < 0 ? n : 1 - GC.@preserve y $f(n, x, xstride, pointer(y, y_delta), ystride) + function $f(x::AbstractArray{$elty}, y::AbstractArray{$elty}) + n, m = length(x), length(y) + n == m || throw(DimensionMismatch(lazy"dot product arguments have lengths $n and $m")) + GC.@preserve x y $f(n, vec_pointer_stride(x)..., vec_pointer_stride(y)...) end - - function $f(x::StridedVector{$elty}, y::StridedVector{$elty}) - n = _dot_length_check(x,y) - xstride = stride(x,1) - ystride = stride(y,1) - x_delta = xstride < 0 ? n : 1 - y_delta = ystride < 0 ? n : 1 - GC.@preserve x y $f(n, pointer(x, x_delta), xstride, pointer(y, y_delta), ystride) - end - end -end - -function dot(DX::Union{DenseArray{T},AbstractVector{T}}, DY::Union{DenseArray{T},AbstractVector{T}}) where T<:BlasReal - require_one_based_indexing(DX, DY) - n = length(DX) - if n != length(DY) - throw(DimensionMismatch(lazy"dot product arguments have lengths $(length(DX)) and $(length(DY))")) end - return dot(n, DX, stride(DX, 1), DY, stride(DY, 1)) -end -function dotc(DX::Union{DenseArray{T},AbstractVector{T}}, DY::Union{DenseArray{T},AbstractVector{T}}) where T<:BlasComplex - require_one_based_indexing(DX, DY) - n = length(DX) - if n != length(DY) - throw(DimensionMismatch(lazy"dot product arguments have lengths $(length(DX)) and $(length(DY))")) - end - return dotc(n, DX, stride(DX, 1), DY, stride(DY, 1)) -end -function dotu(DX::Union{DenseArray{T},AbstractVector{T}}, DY::Union{DenseArray{T},AbstractVector{T}}) where T<:BlasComplex - require_one_based_indexing(DX, DY) - n = length(DX) - if n != length(DY) - throw(DimensionMismatch(lazy"dot product arguments have lengths $(length(DX)) and $(length(DY))")) - end - return dotu(n, DX, stride(DX, 1), DY, stride(DY, 1)) end ## nrm2 @@ -461,7 +428,11 @@ for (fname, elty, ret_type) in ((:dnrm2_,:Float64,:Float64), end end end -nrm2(x::Union{AbstractVector,DenseArray}) = nrm2(length(x), x, stride1(x)) +# openblas returns 0 for negative stride +function nrm2(x::AbstractArray) + p, st = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x nrm2(length(x), p, abs(st)) +end ## asum @@ -498,7 +469,10 @@ for (fname, elty, ret_type) in ((:dasum_,:Float64,:Float64), end end end -asum(x::Union{AbstractVector,DenseArray}) = asum(length(x), x, stride1(x)) +function asum(x::AbstractArray) + p, st = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x asum(length(x), p, abs(st)) +end ## axpy @@ -509,15 +483,13 @@ Overwrite `Y` with `X*a + Y`, where `a` is a scalar. Return `Y`. # Examples ```jldoctest -julia> x = [1; 2; 3]; +julia> x = [1.; 2; 3]; -julia> y = [4; 5; 6]; +julia> y = [4. ;; 5 ;; 6]; julia> BLAS.axpy!(2, x, y) -3-element Vector{Int64}: - 6 - 9 - 12 +1×3 Matrix{Float64}: + 6.0 9.0 12.0 ``` """ function axpy! end @@ -542,15 +514,18 @@ for (fname, elty) in ((:daxpy_,:Float64), end end end -function axpy!(alpha::Number, x::Union{DenseArray{T},StridedVector{T}}, y::Union{DenseArray{T},StridedVector{T}}) where T<:BlasFloat + +function axpy!(alpha::Number, x::AbstractArray{T}, y::AbstractArray{T}) where T<:BlasFloat if length(x) != length(y) throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) end - return axpy!(length(x), convert(T,alpha), x, stride(x, 1), y, stride(y, 1)) + GC.@preserve x y axpy!(length(x), T(alpha), vec_pointer_stride(x)..., + vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed"))...) + y end -function axpy!(alpha::Number, x::Array{T}, rx::Union{UnitRange{Ti},AbstractRange{Ti}}, - y::Array{T}, ry::Union{UnitRange{Ti},AbstractRange{Ti}}) where {T<:BlasFloat,Ti<:Integer} +function axpy!(alpha::Number, x::Array{T}, rx::AbstractRange{Ti}, + y::Array{T}, ry::AbstractRange{Ti}) where {T<:BlasFloat,Ti<:Integer} if length(rx) != length(ry) throw(DimensionMismatch("ranges of differing lengths")) end @@ -562,10 +537,10 @@ function axpy!(alpha::Number, x::Array{T}, rx::Union{UnitRange{Ti},AbstractRange end GC.@preserve x y axpy!( length(rx), - convert(T, alpha), - pointer(x) + (first(rx) - 1)*sizeof(T), + T(alpha), + pointer(x, minimum(rx)), step(rx), - pointer(y) + (first(ry) - 1)*sizeof(T), + pointer(y, minimum(ry)), step(ry)) return y @@ -612,12 +587,14 @@ for (fname, elty) in ((:daxpby_,:Float64), (:saxpby_,:Float32), end end -function axpby!(alpha::Number, x::Union{DenseArray{T},AbstractVector{T}}, beta::Number, y::Union{DenseArray{T},AbstractVector{T}}) where T<:BlasFloat +function axpby!(alpha::Number, x::AbstractArray{T}, beta::Number, y::AbstractArray{T}) where T<:BlasFloat require_one_based_indexing(x, y) if length(x) != length(y) throw(DimensionMismatch(lazy"x has length $(length(x)), but y has length $(length(y))")) end - return axpby!(length(x), convert(T, alpha), x, stride(x, 1), convert(T, beta), y, stride(y, 1)) + GC.@preserve x y axpby!(length(x), T(alpha), vec_pointer_stride(x)..., T(beta), + vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed"))...) + y end ## iamax @@ -633,7 +610,11 @@ for (fname, elty) in ((:idamax_,:Float64), end end end -iamax(dx::Union{AbstractVector,DenseArray}) = iamax(length(dx), dx, stride1(dx)) +function iamax(dx::AbstractArray) + p, st = vec_pointer_stride(dx) + st <= 0 && return BlasInt(0) + iamax(length(dx), p, st) +end """ iamax(n, dx, incx) @@ -673,20 +654,16 @@ for (fname, elty) in ((:dgemv_,:Float64), end chkstride1(A) lda = stride(A,2) - sX = stride(X,1) - sY = stride(Y,1) + pX, sX = vec_pointer_stride(X, ArgumentError("input vector with 0 stride is not allowed")) + pY, sY = vec_pointer_stride(Y, ArgumentError("dest vector with 0 stride is not allowed")) + pA = pointer(A) if lda < 0 - colindex = lastindex(A, 2) + pA += (size(A, 2) - 1) * lda * sizeof($elty) lda = -lda trans == 'N' ? (sX = -sX) : (sY = -sY) - else - colindex = firstindex(A, 2) end lda >= size(A,1) || size(A,2) <= 1 || error("when `size(A,2) > 1`, `abs(stride(A,2))` must be at least `size(A,1)`") lda = max(1, size(A,1), lda) - pA = pointer(A, Base._sub2ind(A, 1, colindex)) - pX = pointer(X, stride(X,1) > 0 ? firstindex(X) : lastindex(X)) - pY = pointer(Y, stride(Y,1) > 0 ? firstindex(Y) : lastindex(Y)) GC.@preserve A X Y ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, @@ -767,14 +744,16 @@ for (fname, elty) in ((:dgbmv_,:Float64), y::AbstractVector{$elty}) require_one_based_indexing(A, x, y) chkstride1(A) - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ref{BlasInt}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Clong), trans, m, size(A,2), kl, ku, alpha, A, max(1,stride(A,2)), - x, stride(x,1), beta, y, stride(y,1), 1) + px, stx, beta, py, sty, 1) y end function gbmv(trans::AbstractChar, m::Integer, kl::Integer, ku::Integer, alpha::($elty), A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -828,13 +807,15 @@ for (fname, elty, lib) in ((:dsymv_,:Float64,libblastrampoline), throw(DimensionMismatch(lazy"A has size $(size(A)), and y has length $(length(y))")) end chkstride1(A) - ccall((@blasfunc($fname), $lib), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y ccall((@blasfunc($fname), $lib), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Clong), uplo, n, alpha, A, - max(1,stride(A,2)), x, stride(x,1), beta, - y, stride(y,1), 1) + max(1,stride(A,2)), px, stx, beta, + py, sty, 1) y end function symv(uplo::AbstractChar, alpha::($elty), A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -891,15 +872,15 @@ for (fname, elty) in ((:zhemv_,:ComplexF64), end chkstride1(A) lda = max(1, stride(A, 2)) - incx = stride(x, 1) - incy = stride(y, 1) - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Clong), uplo, n, α, A, - lda, x, incx, β, - y, incy, 1) + lda, px, stx, β, + py, sty, 1) y end function hemv(uplo::AbstractChar, α::($elty), A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -977,19 +958,21 @@ for (fname, elty) in ((:zhpmv_, :ComplexF64), end function hpmv!(uplo::AbstractChar, - α::Number, AP::Union{DenseArray{T}, AbstractVector{T}}, x::Union{DenseArray{T}, AbstractVector{T}}, - β::Number, y::Union{DenseArray{T}, AbstractVector{T}}) where {T <: BlasComplex} - chkuplo(uplo) + α::Number, AP::AbstractArray{T}, x::AbstractArray{T}, + β::Number, y::AbstractArray{T}) where {T <: BlasComplex} require_one_based_indexing(AP, x, y) 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 if 2*length(AP) < N*(N + 1) - throw(DimensionMismatch("Packed Hermitian matrix A has size smaller than length(x) = $(N).")) + throw(DimensionMismatch(lazy"Packed hermitian matrix A has size smaller than length(x) = $(N).")) end chkstride1(AP) - return hpmv!(uplo, N, convert(T, α), AP, x, stride(x, 1), convert(T, β), y, stride(y, 1)) + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y hpmv!(uplo, N, T(α), AP, px, stx, T(β), py, sty) + y end """ @@ -1013,6 +996,9 @@ The scalar inputs `α` and `β` must be complex or real numbers. The array inputs `x`, `y` and `AP` must all be of `ComplexF32` or `ComplexF64` type. Return the updated `y`. + +!!! compat "Julia 1.5" + `hpmv!` requires at least Julia 1.5. """ hpmv! @@ -1031,13 +1017,15 @@ for (fname, elty) in ((:dsbmv_,:Float64), chkuplo(uplo) require_one_based_indexing(A, x, y) chkstride1(A) - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Clong), uplo, size(A,2), k, alpha, - A, max(1,stride(A,2)), x, stride(x,1), - beta, y, stride(y,1), 1) + A, max(1,stride(A,2)), px, stx, + beta, py, sty, 1) y end function sbmv(uplo::AbstractChar, k::Integer, alpha::($elty), A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -1130,19 +1118,21 @@ for (fname, elty) in ((:dspmv_, :Float64), end function spmv!(uplo::AbstractChar, - α::Real, AP::Union{DenseArray{T}, AbstractVector{T}}, x::Union{DenseArray{T}, AbstractVector{T}}, - β::Real, y::Union{DenseArray{T}, AbstractVector{T}}) where {T <: BlasReal} - chkuplo(uplo) + α::Real, AP::AbstractArray{T}, x::AbstractArray{T}, + β::Real, y::AbstractArray{T}) where {T <: BlasReal} require_one_based_indexing(AP, x, y) 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 if 2*length(AP) < N*(N + 1) - throw(DimensionMismatch("Packed symmetric matrix A has size smaller than length(x) = $(N).")) + throw(DimensionMismatch(lazy"Packed symmetric matrix A has size smaller than length(x) = $(N).")) end chkstride1(AP) - return spmv!(uplo, N, convert(T, α), AP, x, stride(x, 1), convert(T, β), y, stride(y, 1)) + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y spmv!(uplo, N, T(α), AP, px, stx, T(β), py, sty) + y end """ @@ -1166,6 +1156,9 @@ The scalar inputs `α` and `β` must be real. The array inputs `x`, `y` and `AP` must all be of `Float32` or `Float64` type. Return the updated `y`. + +!!! compat "Julia 1.5" + `spmv!` requires at least Julia 1.5. """ spmv! @@ -1201,22 +1194,23 @@ for (fname, elty) in ((:dspr_, :Float64), end function spr!(uplo::AbstractChar, - α::Real, x::Union{DenseArray{T}, AbstractVector{T}}, - AP::Union{DenseArray{T}, AbstractVector{T}}) where {T <: BlasReal} + α::Real, x::AbstractArray{T}, + AP::AbstractArray{T}) where {T <: BlasReal} chkuplo(uplo) require_one_based_indexing(AP, x) N = length(x) if 2*length(AP) < N*(N + 1) - throw(DimensionMismatch("Packed symmetric matrix A has size smaller than length(x) = $(N).")) + throw(DimensionMismatch(lazy"Packed symmetric matrix A has size smaller than length(x) = $(N).")) end chkstride1(AP) - return spr!(uplo, N, convert(T, α), x, stride(x, 1), AP) + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + return GC.@preserve x spr!(uplo, N, T(α), px, stx , AP) end """ spr!(uplo, α, x, AP) -Update matrix `A` as `α*A*x*x'`, where `A` is a symmetric matrix provided +Update matrix `A` as `A+α*x*x'`, where `A` is a symmetric matrix provided in packed format `AP` and `x` is a vector. With `uplo = 'U'`, the array AP must contain the upper triangular part of the @@ -1233,6 +1227,9 @@ The scalar input `α` must be real. The array inputs `x` and `AP` must all be of `Float32` or `Float64` type. Return the updated `AP`. + +!!! compat "Julia 1.8" + `spr!` requires at least Julia 1.8. """ spr! @@ -1251,13 +1248,15 @@ for (fname, elty) in ((:zhbmv_,:ComplexF64), chkuplo(uplo) require_one_based_indexing(A, x, y) chkstride1(A) - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + py, sty = vec_pointer_stride(y, ArgumentError("dest vector with 0 stride is not allowed")) + GC.@preserve x y ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Clong), uplo, size(A,2), k, alpha, - A, max(1,stride(A,2)), x, stride(x,1), - beta, y, stride(y,1), 1) + A, max(1,stride(A,2)), px, stx, + beta, py, sty, 1) y end function hbmv(uplo::AbstractChar, k::Integer, alpha::($elty), A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -1312,12 +1311,13 @@ for (fname, elty) in ((:dtrmv_,:Float64), throw(DimensionMismatch(lazy"A has size ($n,$n), x has length $(length(x))")) end chkstride1(A) - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Clong, Clong, Clong), uplo, trans, diag, n, - A, max(1,stride(A,2)), x, max(1,stride(x, 1)), 1, 1, 1) + A, max(1,stride(A,2)), px, stx, 1, 1, 1) x end function trmv(uplo::AbstractChar, trans::AbstractChar, diag::AbstractChar, A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -1368,12 +1368,13 @@ for (fname, elty) in ((:dtrsv_,:Float64), throw(DimensionMismatch(lazy"size of A is $n != length(x) = $(length(x))")) end chkstride1(A) - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Clong, Clong, Clong), uplo, trans, diag, n, - A, max(1,stride(A,2)), x, stride(x, 1), 1, 1, 1) + A, max(1,stride(A,2)), px, stx, 1, 1, 1) x end function trsv(uplo::AbstractChar, trans::AbstractChar, diag::AbstractChar, A::AbstractMatrix{$elty}, x::AbstractVector{$elty}) @@ -1402,13 +1403,13 @@ for (fname, elty) in ((:dger_,:Float64), 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 - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + 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, α, x, - stride(x, 1), y, stride(y, 1), A, - max(1,stride(A,2))) + m, n, α, px, stx, py, sty, A, max(1,stride(A,2))) A end end @@ -1436,11 +1437,11 @@ for (fname, elty, lib) in ((:dsyr_,:Float64,libblastrampoline), if length(x) != n throw(DimensionMismatch(lazy"A has size ($n,$n), x has length $(length(x))")) end - ccall((@blasfunc($fname), $lib), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x ccall((@blasfunc($fname), $lib), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}), - uplo, n, α, x, - stride(x, 1), A, max(1,stride(A, 2))) + uplo, n, α, px, stx, A, max(1,stride(A, 2))) A end end @@ -1467,11 +1468,11 @@ for (fname, elty, relty) in ((:zher_,:ComplexF64, :Float64), if length(x) != n throw(DimensionMismatch(lazy"A has size ($n,$n), x has length $(length(x))")) end - ccall((@blasfunc($fname), libblastrampoline), Cvoid, + px, stx = vec_pointer_stride(x, ArgumentError("input vector with 0 stride is not allowed")) + GC.@preserve x ccall((@blasfunc($fname), libblastrampoline), Cvoid, (Ref{UInt8}, Ref{BlasInt}, Ref{$relty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Clong), - uplo, n, α, x, - stride(x, 1), A, max(1,stride(A,2)), 1) + uplo, n, α, px, stx, A, max(1,stride(A,2)), 1) A end end @@ -1575,11 +1576,27 @@ for (mfname, elty) in ((:dsymm_,:Float64), require_one_based_indexing(A, B, C) m, n = size(C) j = checksquare(A) - if j != (side == 'L' ? m : n) - throw(DimensionMismatch(lazy"A has size $(size(A)), C has size ($m,$n)")) - end - if size(B,2) != n - throw(DimensionMismatch(lazy"B has second dimension $(size(B,2)) but needs to match second dimension of C, $n")) + M, N = size(B) + if side == 'L' + if j != m + throw(DimensionMismatch(lazy"A has first dimension $j but needs to match first dimension of C, $m")) + end + if N != n + throw(DimensionMismatch(lazy"B has second dimension $N but needs to match second dimension of C, $n")) + end + if j != M + throw(DimensionMismatch(lazy"A has second dimension $j but needs to match first dimension of B, $M")) + end + else + if j != n + throw(DimensionMismatch(lazy"B has second dimension $j but needs to match second dimension of C, $n")) + end + if N != j + throw(DimensionMismatch(lazy"A has second dimension $N but needs to match first dimension of B, $j")) + end + if M != m + throw(DimensionMismatch(lazy"A has first dimension $M but needs to match first dimension of C, $m")) + end end chkstride1(A) chkstride1(B) @@ -1649,11 +1666,27 @@ for (mfname, elty) in ((:zhemm_,:ComplexF64), require_one_based_indexing(A, B, C) m, n = size(C) j = checksquare(A) - if j != (side == 'L' ? m : n) - throw(DimensionMismatch(lazy"A has size $(size(A)), C has size ($m,$n)")) - end - if size(B,2) != n - throw(DimensionMismatch(lazy"B has second dimension $(size(B,2)) but needs to match second dimension of C, $n")) + M, N = size(B) + if side == 'L' + if j != m + throw(DimensionMismatch(lazy"A has first dimension $j but needs to match first dimension of C, $m")) + end + if N != n + throw(DimensionMismatch(lazy"B has second dimension $N but needs to match second dimension of C, $n")) + end + if j != M + throw(DimensionMismatch(lazy"A has second dimension $j but needs to match first dimension of B, $M")) + end + else + if j != n + throw(DimensionMismatch(lazy"B has second dimension $j but needs to match second dimension of C, $n")) + end + if N != j + throw(DimensionMismatch(lazy"A has second dimension $N but needs to match first dimension of B, $j")) + end + if M != m + throw(DimensionMismatch(lazy"A has first dimension $M but needs to match first dimension of C, $m")) + end end chkstride1(A) chkstride1(B) @@ -1711,14 +1744,14 @@ hemm! Rank-k update of the symmetric matrix `C` as `alpha*A*transpose(A) + beta*C` or `alpha*transpose(A)*A + beta*C` according to [`trans`](@ref stdlib-blas-trans). -Only the [`uplo`](@ref stdlib-blas-uplo) triangle of `C` is used. Returns `C`. +Only the [`uplo`](@ref stdlib-blas-uplo) triangle of `C` is used. Return `C`. """ function syrk! end """ syrk(uplo, trans, alpha, A) -Returns either the upper triangle or the lower triangle of `A`, +Return either the upper triangle or the lower triangle of `A`, according to [`uplo`](@ref stdlib-blas-uplo), of `alpha*A*transpose(A)` or `alpha*transpose(A)*A`, according to [`trans`](@ref stdlib-blas-trans). @@ -1890,7 +1923,7 @@ end """ syr2k(uplo, trans, A, B) -Returns the [`uplo`](@ref stdlib-blas-uplo) triangle of `A*transpose(B) + B*transpose(A)` +Return the [`uplo`](@ref stdlib-blas-uplo) triangle of `A*transpose(B) + B*transpose(A)` or `transpose(A)*B + transpose(B)*A`, according to [`trans`](@ref stdlib-blas-trans). """ syr2k(uplo::AbstractChar, trans::AbstractChar, A::AbstractVecOrMat, B::AbstractVecOrMat) = syr2k(uplo, trans, one(eltype(A)), A, B) @@ -1943,14 +1976,14 @@ end Rank-2k update of the Hermitian matrix `C` as `alpha*A*B' + alpha*B*A' + beta*C` or `alpha*A'*B + alpha*B'*A + beta*C` according to [`trans`](@ref stdlib-blas-trans). The scalar `beta` has to be real. -Only the [`uplo`](@ref stdlib-blas-uplo) triangle of `C` is used. Returns `C`. +Only the [`uplo`](@ref stdlib-blas-uplo) triangle of `C` is used. Return `C`. """ function her2k! end """ her2k(uplo, trans, alpha, A, B) -Returns the [`uplo`](@ref stdlib-blas-uplo) triangle of `alpha*A*B' + alpha*B*A'` +Return the [`uplo`](@ref stdlib-blas-uplo) triangle of `alpha*A*B' + alpha*B*A'` or `alpha*A'*B + alpha*B'*A`, according to [`trans`](@ref stdlib-blas-trans). """ her2k(uplo, trans, alpha, A, B) @@ -1958,7 +1991,7 @@ her2k(uplo, trans, alpha, A, B) """ her2k(uplo, trans, A, B) -Returns the [`uplo`](@ref stdlib-blas-uplo) triangle of `A*B' + B*A'` +Return the [`uplo`](@ref stdlib-blas-uplo) triangle of `A*B' + B*A'` or `A'*B + B'*A`, according to [`trans`](@ref stdlib-blas-trans). """ her2k(uplo, trans, A, B) @@ -1973,14 +2006,14 @@ Update `B` as `alpha*A*B` or one of the other three variants determined by Only the [`ul`](@ref stdlib-blas-uplo) triangle of `A` is used. [`dA`](@ref stdlib-blas-diag) determines if the diagonal values are read or are assumed to be all ones. -Returns the updated `B`. +Return the updated `B`. """ function trmm! end """ trmm(side, ul, tA, dA, alpha, A, B) -Returns `alpha*A*B` or one of the other three variants determined by +Return `alpha*A*B` or one of the other three variants determined by [`side`](@ref stdlib-blas-side) and [`tA`](@ref stdlib-blas-trans). Only the [`ul`](@ref stdlib-blas-uplo) triangle of `A` is used. [`dA`](@ref stdlib-blas-diag) determines if the diagonal values are read or @@ -2085,8 +2118,8 @@ end end # module -function copyto!(dest::Array{T}, rdest::Union{UnitRange{Ti},AbstractRange{Ti}}, - src::Array{T}, rsrc::Union{UnitRange{Ti},AbstractRange{Ti}}) where {T<:BlasFloat,Ti<:Integer} +function copyto!(dest::Array{T}, rdest::AbstractRange{Ti}, + src::Array{T}, rsrc::AbstractRange{Ti}) where {T<:BlasFloat,Ti<:Integer} if minimum(rdest) < 1 || maximum(rdest) > length(dest) throw(ArgumentError(lazy"range out of bounds for dest, of length $(length(dest))")) end @@ -2098,9 +2131,9 @@ function copyto!(dest::Array{T}, rdest::Union{UnitRange{Ti},AbstractRange{Ti}}, end GC.@preserve src dest BLAS.blascopy!( length(rsrc), - pointer(src) + (first(rsrc) - 1) * sizeof(T), + pointer(src, minimum(rsrc)), step(rsrc), - pointer(dest) + (first(rdest) - 1) * sizeof(T), + pointer(dest, minimum(rdest)), step(rdest)) return dest diff --git a/stdlib/LinearAlgebra/src/bunchkaufman.jl b/stdlib/LinearAlgebra/src/bunchkaufman.jl index 33da0af79793c..d1019a1a4ea5a 100644 --- a/stdlib/LinearAlgebra/src/bunchkaufman.jl +++ b/stdlib/LinearAlgebra/src/bunchkaufman.jl @@ -80,8 +80,8 @@ BunchKaufman(A::AbstractMatrix{T}, ipiv::AbstractVector{<:Integer}, uplo::Abstra symmetric::Bool, rook::Bool, info::BlasInt) where {T} = BunchKaufman{T,typeof(A),typeof(ipiv)}(A, ipiv, uplo, symmetric, rook, info) # backwards-compatible constructors (remove with Julia 2.0) -@deprecate(BunchKaufman(LD, ipiv, uplo, symmetric, rook, info) where {T,S}, - BunchKaufman{T,S,typeof(ipiv)}(LD, ipiv, uplo, symmetric, rook, info)) +@deprecate(BunchKaufman{T,S}(LD, ipiv, uplo, symmetric, rook, info) where {T,S}, + BunchKaufman{T,S,typeof(ipiv)}(LD, ipiv, uplo, symmetric, rook, info), false) # iteration for destructuring into components Base.iterate(S::BunchKaufman) = (S.D, Val(:UL)) @@ -96,13 +96,13 @@ Base.iterate(S::BunchKaufman, ::Val{:done}) = nothing `bunchkaufman!` is the same as [`bunchkaufman`](@ref), but saves space by overwriting the input `A`, instead of creating a copy. """ -function bunchkaufman!(A::RealHermSymComplexSym{T,S} where {T<:BlasReal,S<:StridedMatrix}, +function bunchkaufman!(A::RealHermSymComplexSym{<:BlasReal,<:StridedMatrix}, rook::Bool = false; check::Bool = true) LD, ipiv, info = rook ? LAPACK.sytrf_rook!(A.uplo, A.data) : LAPACK.sytrf!(A.uplo, A.data) check && checknonsingular(info) BunchKaufman(LD, ipiv, A.uplo, true, rook, info) end -function bunchkaufman!(A::Hermitian{T,S} where {T<:BlasComplex,S<:StridedMatrix{T}}, +function bunchkaufman!(A::Hermitian{<:BlasComplex,<:StridedMatrix}, rook::Bool = false; check::Bool = true) LD, ipiv, info = rook ? LAPACK.hetrf_rook!(A.uplo, A.data) : LAPACK.hetrf!(A.uplo, A.data) check && checknonsingular(info) @@ -197,7 +197,7 @@ julia> S.L*S.D*S.L' - A[S.p, S.p] ``` """ bunchkaufman(A::AbstractMatrix{T}, rook::Bool=false; check::Bool = true) where {T} = - bunchkaufman!(copy_oftype(A, typeof(sqrt(oneunit(T)))), rook; check = check) + bunchkaufman!(eigencopy_oftype(A, typeof(sqrt(oneunit(T)))), rook; check = check) BunchKaufman{T}(B::BunchKaufman) where {T} = BunchKaufman(convert(Matrix{T}, B.LD), B.ipiv, B.uplo, B.symmetric, B.rook, B.info) @@ -237,7 +237,7 @@ function _ipiv2perm_bk(v::AbstractVector{T}, maxi::Integer, uplo::AbstractChar, return p end -function getproperty(B::BunchKaufman{T}, d::Symbol) where {T<:BlasFloat} +function getproperty(B::BunchKaufman{T,<:StridedMatrix}, d::Symbol) where {T<:BlasFloat} n = size(B, 1) if d === :p return _ipiv2perm_bk(getfield(B, :ipiv), n, getfield(B, :uplo), B.rook) @@ -302,7 +302,7 @@ function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, B::BunchKaufman) end end -function inv(B::BunchKaufman{<:BlasReal}) +function inv(B::BunchKaufman{<:BlasReal,<:StridedMatrix}) if B.rook copytri!(LAPACK.sytri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo, true) else @@ -310,7 +310,7 @@ function inv(B::BunchKaufman{<:BlasReal}) end end -function inv(B::BunchKaufman{<:BlasComplex}) +function inv(B::BunchKaufman{<:BlasComplex,<:StridedMatrix}) if issymmetric(B) if B.rook copytri!(LAPACK.sytri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo) @@ -326,14 +326,14 @@ function inv(B::BunchKaufman{<:BlasComplex}) end end -function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasReal +function ldiv!(B::BunchKaufman{T,<:StridedMatrix}, R::StridedVecOrMat{T}) where {T<:BlasReal} if B.rook LAPACK.sytrs_rook!(B.uplo, B.LD, B.ipiv, R) else LAPACK.sytrs!(B.uplo, B.LD, B.ipiv, R) end end -function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasComplex +function ldiv!(B::BunchKaufman{T,<:StridedMatrix}, R::StridedVecOrMat{T}) where {T<:BlasComplex} if B.rook if issymmetric(B) LAPACK.sytrs_rook!(B.uplo, B.LD, B.ipiv, R) @@ -348,11 +348,6 @@ function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasComplex end end end -# There is no fallback solver for Bunch-Kaufman so we'll have to promote to same element type -function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{S}) where {T,S} - TS = promote_type(T,S) - return ldiv!(convert(BunchKaufman{TS}, B), convert(AbstractArray{TS}, R)) -end function logabsdet(F::BunchKaufman) M = F.LD diff --git a/stdlib/LinearAlgebra/src/cholesky.jl b/stdlib/LinearAlgebra/src/cholesky.jl index bb831f8dca164..82f138db7d7b9 100644 --- a/stdlib/LinearAlgebra/src/cholesky.jl +++ b/stdlib/LinearAlgebra/src/cholesky.jl @@ -168,7 +168,7 @@ CholeskyPivoted(A::AbstractMatrix{T}, uplo::AbstractChar, piv::AbstractVector{<: CholeskyPivoted{T,typeof(A),typeof(piv)}(A, uplo, piv, rank, tol, info) # backwards-compatible constructors (remove with Julia 2.0) @deprecate(CholeskyPivoted{T,S}(factors, uplo, piv, rank, tol, info) where {T,S<:AbstractMatrix}, - CholeskyPivoted{T,S,typeof(piv)}(factors, uplo, piv, rank, tol, info)) + CholeskyPivoted{T,S,typeof(piv)}(factors, uplo, piv, rank, tol, info), false) # iteration for destructuring into components @@ -178,10 +178,8 @@ Base.iterate(C::CholeskyPivoted, ::Val{:done}) = nothing # make a copy that allow inplace Cholesky factorization -@inline choltype(A) = promote_type(typeof(sqrt(oneunit(eltype(A)))), Float32) -@inline cholcopy(A::StridedMatrix) = copy_oftype(A, choltype(A)) -@inline cholcopy(A::RealHermSymComplexHerm) = copy_oftype(A, choltype(A)) -@inline cholcopy(A::AbstractMatrix) = copy_similar(A, choltype(A)) +choltype(A) = promote_type(typeof(sqrt(oneunit(eltype(A)))), Float32) +cholcopy(A::AbstractMatrix) = eigencopy_oftype(A, choltype(A)) # _chol!. Internal methods for calling unpivoted Cholesky ## BLAS/LAPACK element types @@ -208,7 +206,7 @@ function _chol!(A::AbstractMatrix, ::Type{UpperTriangular}) A[k,k] = Akk Akk, info = _chol!(Akk, UpperTriangular) if info != 0 - return UpperTriangular(A), info + return UpperTriangular(A), convert(BlasInt, k) end A[k,k] = Akk AkkInv = inv(copy(Akk')) @@ -235,7 +233,7 @@ function _chol!(A::AbstractMatrix, ::Type{LowerTriangular}) A[k,k] = Akk Akk, info = _chol!(Akk, LowerTriangular) if info != 0 - return LowerTriangular(A), info + return LowerTriangular(A), convert(BlasInt, k) end A[k,k] = Akk AkkInv = inv(Akk) @@ -253,11 +251,12 @@ function _chol!(A::AbstractMatrix, ::Type{LowerTriangular}) end ## Numbers -function _chol!(x::Number, uplo) +function _chol!(x::Number, _) rx = real(x) + iszero(rx) && return (rx, convert(BlasInt, 1)) rxr = sqrt(abs(rx)) rval = convert(promote_type(typeof(x), typeof(rxr)), rxr) - rx == abs(x) ? (rval, convert(BlasInt, 0)) : (rval, convert(BlasInt, 1)) + return (rval, convert(BlasInt, rx != abs(x))) end ## for StridedMatrices, check that matrix is symmetric/Hermitian @@ -557,7 +556,7 @@ issuccess(C::Union{Cholesky,CholeskyPivoted}) = C.info == 0 adjoint(C::Union{Cholesky,CholeskyPivoted}) = C -function show(io::IO, mime::MIME{Symbol("text/plain")}, C::Cholesky{<:Any,<:AbstractMatrix}) +function show(io::IO, mime::MIME{Symbol("text/plain")}, C::Cholesky) if issuccess(C) summary(io, C); println(io) println(io, "$(C.uplo) factor:") @@ -567,7 +566,7 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, C::Cholesky{<:Any,<:Abst end end -function show(io::IO, mime::MIME{Symbol("text/plain")}, C::CholeskyPivoted{<:Any,<:AbstractMatrix}) +function show(io::IO, mime::MIME{Symbol("text/plain")}, C::CholeskyPivoted) summary(io, C); println(io) println(io, "$(C.uplo) factor with rank $(rank(C)):") show(io, mime, C.uplo == 'U' ? C.U : C.L) @@ -578,7 +577,7 @@ end ldiv!(C::Cholesky{T,<:StridedMatrix}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = LAPACK.potrs!(C.uplo, C.factors, B) -function ldiv!(C::Cholesky{<:Any,<:AbstractMatrix}, B::StridedVecOrMat) +function ldiv!(C::Cholesky, B::AbstractVecOrMat) if C.uplo == 'L' return ldiv!(adjoint(LowerTriangular(C.factors)), ldiv!(LowerTriangular(C.factors), B)) else @@ -586,10 +585,10 @@ function ldiv!(C::Cholesky{<:Any,<:AbstractMatrix}, B::StridedVecOrMat) end end -function ldiv!(C::CholeskyPivoted{T}, B::StridedVector{T}) where T<:BlasFloat +function ldiv!(C::CholeskyPivoted{T,<:StridedMatrix}, B::StridedVector{T}) where T<:BlasFloat invpermute!(LAPACK.potrs!(C.uplo, C.factors, permute!(B, C.piv)), C.piv) end -function ldiv!(C::CholeskyPivoted{T}, B::StridedMatrix{T}) where T<:BlasFloat +function ldiv!(C::CholeskyPivoted{T,<:StridedMatrix}, B::StridedMatrix{T}) where T<:BlasFloat n = size(C, 1) for i=1:size(B, 2) permute!(view(B, 1:n, i), C.piv) @@ -630,7 +629,7 @@ function ldiv!(C::CholeskyPivoted, B::AbstractMatrix) B end -function rdiv!(B::AbstractMatrix, C::Cholesky{<:Any,<:AbstractMatrix}) +function rdiv!(B::AbstractMatrix, C::Cholesky) if C.uplo == 'L' return rdiv!(rdiv!(B, adjoint(LowerTriangular(C.factors))), LowerTriangular(C.factors)) else @@ -705,7 +704,7 @@ inv!(C::Cholesky{<:BlasFloat,<:StridedMatrix}) = inv(C::Cholesky{<:BlasFloat,<:StridedMatrix}) = inv!(copy(C)) -function inv(C::CholeskyPivoted) +function inv(C::CholeskyPivoted{<:BlasFloat,<:StridedMatrix}) ipiv = invperm(C.piv) copytri!(LAPACK.potri!(C.uplo, copy(C.factors)), C.uplo, true)[ipiv, ipiv] end diff --git a/stdlib/LinearAlgebra/src/dense.jl b/stdlib/LinearAlgebra/src/dense.jl index ffcd9e64e0752..56c5954cc28fe 100644 --- a/stdlib/LinearAlgebra/src/dense.jl +++ b/stdlib/LinearAlgebra/src/dense.jl @@ -257,6 +257,8 @@ Vector `kv.second` will be placed on the `kv.first` diagonal. By default the matrix is square and its size is inferred from `kv`, but a non-square size `m`×`n` (padded with zeros as needed) can be specified by passing `m,n` as the first arguments. +For repeated diagonal indices `kv.first` the values in the corresponding +vectors `kv.second` will be added. `diagm` constructs a full matrix; if you want storage-efficient versions with fast arithmetic, see [`Diagonal`](@ref), [`Bidiagonal`](@ref) @@ -277,6 +279,13 @@ julia> diagm(1 => [1,2,3], -1 => [4,5]) 4 0 2 0 0 5 0 3 0 0 0 0 + +julia> diagm(1 => [1,2,3], 1 => [1,2,3]) +4×4 Matrix{Int64}: + 0 2 0 0 + 0 0 4 0 + 0 0 0 6 + 0 0 0 0 ``` """ diagm(kv::Pair{<:Integer,<:AbstractVector}...) = _diagm(nothing, kv...) @@ -335,33 +344,73 @@ diagm(m::Integer, n::Integer, v::AbstractVector) = diagm(m, n, 0 => v) function tr(A::Matrix{T}) where T n = checksquare(A) t = zero(T) - for i=1:n + @inbounds @simd for i in 1:n t += A[i,i] end t end +_kronsize(A::AbstractMatrix, B::AbstractMatrix) = map(*, size(A), size(B)) +_kronsize(A::AbstractMatrix, B::AbstractVector) = (size(A, 1)*length(B), size(A, 2)) +_kronsize(A::AbstractVector, B::AbstractMatrix) = (length(A)*size(B, 1), size(B, 2)) + """ kron!(C, A, B) -`kron!` is the in-place version of [`kron`](@ref). Computes `kron(A, B)` and stores the result in `C` -overwriting the existing value of `C`. - -!!! tip - Bounds checking can be disabled by [`@inbounds`](@ref), but you need to take care of the shape - of `C`, `A`, `B` yourself. +Computes the Kronecker product of `A` and `B` and stores the result in `C`, +overwriting the existing content of `C`. This is the in-place version of [`kron`](@ref). !!! compat "Julia 1.6" This function requires Julia 1.6 or later. """ -@inline function kron!(C::AbstractMatrix, A::AbstractMatrix, B::AbstractMatrix) - require_one_based_indexing(A, B) - @boundscheck (size(C) == (size(A,1)*size(B,1), size(A,2)*size(B,2))) || throw(DimensionMismatch()) - m = 0 - @inbounds for j = 1:size(A,2), l = 1:size(B,2), i = 1:size(A,1) +function kron!(C::AbstractVecOrMat, A::AbstractVecOrMat, B::AbstractVecOrMat) + size(C) == _kronsize(A, B) || throw(DimensionMismatch("kron!")) + _kron!(C, A, B) +end +function kron!(c::AbstractVector, a::AbstractVector, b::AbstractVector) + length(c) == length(a) * length(b) || throw(DimensionMismatch("kron!")) + m = firstindex(c) + @inbounds for i in eachindex(a) + ai = a[i] + for k in eachindex(b) + c[m] = ai*b[k] + m += 1 + end + end + return c +end +kron!(c::AbstractVecOrMat, a::AbstractVecOrMat, b::Number) = mul!(c, a, b) +kron!(c::AbstractVecOrMat, a::Number, b::AbstractVecOrMat) = mul!(c, a, b) + +function _kron!(C, A::AbstractMatrix, B::AbstractMatrix) + m = firstindex(C) + @inbounds for j in axes(A,2), l in axes(B,2), i in axes(A,1) + Aij = A[i,j] + for k in axes(B,1) + C[m] = Aij*B[k,l] + m += 1 + end + end + return C +end +function _kron!(C, A::AbstractMatrix, b::AbstractVector) + m = firstindex(C) + @inbounds for j in axes(A,2), i in axes(A,1) Aij = A[i,j] - for k = 1:size(B,1) - C[m += 1] = Aij*B[k,l] + for k in eachindex(b) + C[m] = Aij*b[k] + m += 1 + end + end + return C +end +function _kron!(C, a::AbstractVector, B::AbstractMatrix) + m = firstindex(C) + @inbounds for l in axes(B,2), i in eachindex(a) + ai = a[i] + for k in axes(B,1) + C[m] = ai*B[k,l] + m += 1 end end return C @@ -370,7 +419,7 @@ end """ kron(A, B) -Kronecker tensor product of two vectors or two matrices. +Computes the Kronecker product of two vectors, matrices or numbers. For real vectors `v` and `w`, the Kronecker product is related to the outer product by `kron(v,w) == vec(w * transpose(v))` or @@ -413,31 +462,16 @@ julia> reshape(kron(v,w), (length(w), length(v))) 5 10 ``` """ -function kron(a::AbstractMatrix{T}, b::AbstractMatrix{S}) where {T,S} - R = Matrix{promote_op(*,T,S)}(undef, size(a,1)*size(b,1), size(a,2)*size(b,2)) - return @inbounds kron!(R, a, b) +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) end - -kron!(c::AbstractVecOrMat, a::AbstractVecOrMat, b::Number) = mul!(c, a, b) -kron!(c::AbstractVecOrMat, a::Number, b::AbstractVecOrMat) = mul!(c, a, b) - -Base.@propagate_inbounds function kron!(c::AbstractVector, a::AbstractVector, b::AbstractVector) - C = reshape(c, length(a)*length(b), 1) - A = reshape(a ,length(a), 1) - B = reshape(b, length(b), 1) - kron!(C, A, B) - return c +function kron(a::AbstractVector{T}, b::AbstractVector{S}) where {T,S} + c = Vector{promote_op(*,T,S)}(undef, length(a)*length(b)) + return kron!(c, a, b) end - -Base.@propagate_inbounds kron!(C::AbstractMatrix, a::AbstractMatrix, b::AbstractVector) = kron!(C, a, reshape(b, length(b), 1)) -Base.@propagate_inbounds kron!(C::AbstractMatrix, a::AbstractVector, b::AbstractMatrix) = kron!(C, reshape(a, length(a), 1), b) - kron(a::Number, b::Union{Number, AbstractVecOrMat}) = a * b kron(a::AbstractVecOrMat, b::Number) = a * b -kron(a::AbstractVector, b::AbstractVector) = vec(kron(reshape(a ,length(a), 1), reshape(b, length(b), 1))) -kron(a::AbstractMatrix, b::AbstractVector) = kron(a, reshape(b, length(b), 1)) -kron(a::AbstractVector, b::AbstractMatrix) = kron(reshape(a, length(a), 1), b) - kron(a::AdjointAbsVec, b::AdjointAbsVec) = adjoint(kron(adjoint(a), adjoint(b))) kron(a::AdjOrTransAbsVec, b::AdjOrTransAbsVec) = transpose(kron(transpose(a), transpose(b))) @@ -491,7 +525,7 @@ function (^)(A::AbstractMatrix{T}, p::Real) where T # Quicker return if A is diagonal if isdiag(A) TT = promote_op(^, T, typeof(p)) - retmat = copy_oftype(A, TT) + retmat = copymutable_oftype(A, TT) for i in 1:n retmat[i, i] = retmat[i, i] ^ p end @@ -557,10 +591,9 @@ julia> exp(A) 0.0 2.71828 ``` """ -exp(A::StridedMatrix{<:BlasFloat}) = exp!(copy(A)) -exp(A::StridedMatrix{<:Union{Integer,Complex{<:Integer}}}) = exp!(float.(A)) -exp(A::Adjoint{<:Any,<:AbstractMatrix}) = adjoint(exp(parent(A))) -exp(A::Transpose{<:Any,<:AbstractMatrix}) = transpose(exp(parent(A))) +exp(A::AbstractMatrix) = exp!(copy_similar(A, eigtype(eltype(A)))) +exp(A::AdjointAbsMat) = adjoint(exp(parent(A))) +exp(A::TransposeAbsMat) = transpose(exp(parent(A))) """ cis(A::AbstractMatrix) @@ -688,7 +721,7 @@ function exp!(A::StridedMatrix{T}) where T<:BlasFloat V = mul!(tmp2, A6, tmp1, true, true) tmp1 .= V .+ U - tmp2 .= V .- U # tmp2 aleady contained V but this seems more readable + tmp2 .= V .- U # tmp2 already contained V but this seems more readable X = LAPACK.gesv!(tmp2, tmp1)[1] # X now contains r_13 in Higham 2008 if s > 0 @@ -721,7 +754,7 @@ function exp!(A::StridedMatrix{T}) where T<:BlasFloat end ## Swap rows i and j and columns i and j in X -function rcswap!(i::Integer, j::Integer, X::StridedMatrix{<:Number}) +function rcswap!(i::Integer, j::Integer, X::AbstractMatrix{<:Number}) for k = 1:size(X,1) X[k,i], X[k,j] = X[k,j], X[k,i] end @@ -731,7 +764,7 @@ function rcswap!(i::Integer, j::Integer, X::StridedMatrix{<:Number}) end """ - log(A::StridedMatrix) + log(A::AbstractMatrix) If `A` has no negative real eigenvalue, compute the principal matrix logarithm of `A`, i.e. the unique matrix ``X`` such that ``e^X = A`` and ``-\\pi < Im(\\lambda) < \\pi`` for all @@ -762,7 +795,7 @@ julia> log(A) 0.0 1.0 ``` """ -function log(A::StridedMatrix) +function log(A::AbstractMatrix) # If possible, use diagonalization if ishermitian(A) logHermA = log(Hermitian(A)) @@ -790,8 +823,8 @@ function log(A::StridedMatrix) end end -log(A::Adjoint{<:Any,<:AbstractMatrix}) = adjoint(log(parent(A))) -log(A::Transpose{<:Any,<:AbstractMatrix}) = transpose(log(parent(A))) +log(A::AdjointAbsMat) = adjoint(log(parent(A))) +log(A::TransposeAbsMat) = transpose(log(parent(A))) """ sqrt(A::AbstractMatrix) @@ -839,9 +872,9 @@ julia> sqrt(A) 0.0 2.0 ``` """ -sqrt(::StridedMatrix) +sqrt(::AbstractMatrix) -function sqrt(A::StridedMatrix{T}) where {T<:Union{Real,Complex}} +function sqrt(A::AbstractMatrix{T}) where {T<:Union{Real,Complex}} if ishermitian(A) sqrtHermA = sqrt(Hermitian(A)) return ishermitian(sqrtHermA) ? copytri!(parent(sqrtHermA), 'U', true) : parent(sqrtHermA) @@ -869,19 +902,17 @@ function sqrt(A::StridedMatrix{T}) where {T<:Union{Real,Complex}} end end -sqrt(A::Adjoint{<:Any,<:AbstractMatrix}) = adjoint(sqrt(parent(A))) -sqrt(A::Transpose{<:Any,<:AbstractMatrix}) = transpose(sqrt(parent(A))) +sqrt(A::AdjointAbsMat) = adjoint(sqrt(parent(A))) +sqrt(A::TransposeAbsMat) = transpose(sqrt(parent(A))) function inv(A::StridedMatrix{T}) where T checksquare(A) - S = typeof((one(T)*zero(T) + one(T)*zero(T))/one(T)) - AA = convert(AbstractArray{S}, A) - if istriu(AA) - Ai = triu!(parent(inv(UpperTriangular(AA)))) - elseif istril(AA) - Ai = tril!(parent(inv(LowerTriangular(AA)))) + if istriu(A) + Ai = triu!(parent(inv(UpperTriangular(A)))) + elseif istril(A) + Ai = tril!(parent(inv(LowerTriangular(A)))) else - Ai = inv!(lu(AA)) + Ai = inv!(lu(A)) Ai = convert(typeof(parent(Ai)), Ai) end return Ai @@ -1307,7 +1338,7 @@ julia> factorize(A) # factorize will check to see that A is already factorized This returns a `5×5 Bidiagonal{Float64}`, which can now be passed to other linear algebra functions (e.g. eigensolvers) which will use specialized methods for `Bidiagonal` types. """ -function factorize(A::StridedMatrix{T}) where T +function factorize(A::AbstractMatrix{T}) where T m, n = size(A) if m == n if m == 1 return A[1] end @@ -1403,7 +1434,7 @@ The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of `M`, and `ϵ` is the [`eps`](@ref) of the element type of `M`. For inverting dense ill-conditioned matrices in a least-squares sense, -`rtol = sqrt(eps(real(float(one(eltype(M))))))` is recommended. +`rtol = sqrt(eps(real(float(oneunit(eltype(M))))))` is recommended. For more information, see [^issue8859], [^B96], [^S84], [^KY88]. @@ -1433,19 +1464,20 @@ julia> M * N [^KY88]: Konstantinos Konstantinides and Kung Yao, "Statistical analysis of effective singular values in matrix rank determination", IEEE Transactions on Acoustics, Speech and Signal Processing, 36(5), 1988, 757-763. [doi:10.1109/29.1585](https://doi.org/10.1109/29.1585) """ -function pinv(A::AbstractMatrix{T}; atol::Real = 0.0, rtol::Real = (eps(real(float(one(T))))*min(size(A)...))*iszero(atol)) where T +function pinv(A::AbstractMatrix{T}; atol::Real = 0.0, rtol::Real = (eps(real(float(oneunit(T))))*min(size(A)...))*iszero(atol)) where T m, n = size(A) - Tout = typeof(zero(T)/sqrt(one(T) + one(T))) + Tout = typeof(zero(T)/sqrt(oneunit(T) + oneunit(T))) if m == 0 || n == 0 return similar(A, Tout, (n, m)) end if isdiag(A) - ind = diagind(A) - dA = view(A, ind) + indA = diagind(A) + dA = view(A, indA) maxabsA = maximum(abs, dA) tol = max(rtol * maxabsA, atol) B = fill!(similar(A, Tout, (n, m)), 0) - B[ind] .= (x -> abs(x) > tol ? pinv(x) : zero(x)).(dA) + indB = diagind(B) + B[indB] .= (x -> abs(x) > tol ? pinv(x) : zero(x)).(dA) return B end SVD = svd(A) @@ -1502,13 +1534,13 @@ julia> nullspace(M, atol=0.95) 1.0 ``` """ -function nullspace(A::AbstractVecOrMat; atol::Real = 0.0, rtol::Real = (min(size(A, 1), size(A, 2))*eps(real(float(one(eltype(A))))))*iszero(atol)) +function nullspace(A::AbstractVecOrMat; atol::Real = 0.0, rtol::Real = (min(size(A, 1), size(A, 2))*eps(real(float(oneunit(eltype(A))))))*iszero(atol)) m, n = size(A, 1), size(A, 2) (m == 0 || n == 0) && return Matrix{eigtype(eltype(A))}(I, n, n) SVD = svd(A; full=true) tol = max(atol, SVD.S[1]*rtol) indstart = sum(s -> s .> tol, SVD.S) + 1 - return copy(SVD.Vt[indstart:end,:]') + return copy((@view SVD.Vt[indstart:end,:])') end """ @@ -1570,21 +1602,22 @@ julia> X = sylvester(A, B, C) -4.46667 1.93333 3.73333 -1.8 -julia> A*X + X*B + C -2×2 Matrix{Float64}: - 2.66454e-15 1.77636e-15 - -3.77476e-15 4.44089e-16 +julia> A*X + X*B ≈ -C +true ``` """ -function sylvester(A::StridedMatrix{T},B::StridedMatrix{T},C::StridedMatrix{T}) where T<:BlasFloat +function sylvester(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix) + T = promote_type(float(eltype(A)), float(eltype(B)), float(eltype(C))) + return sylvester(copy_similar(A, T), copy_similar(B, T), copy_similar(C, T)) +end +function sylvester(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}) where {T<:BlasFloat} RA, QA = schur(A) RB, QB = schur(B) - - D = -(adjoint(QA) * (C*QB)) - Y, scale = LAPACK.trsyl!('N','N', RA, RB, D) - rmul!(QA*(Y * adjoint(QB)), inv(scale)) + D = QA' * C * QB + D .= .-D + Y, scale = LAPACK.trsyl!('N', 'N', RA, RB, D) + rmul!(QA * Y * QB', inv(scale)) end -sylvester(A::StridedMatrix{T}, B::StridedMatrix{T}, C::StridedMatrix{T}) where {T<:Integer} = sylvester(float(A), float(B), float(C)) Base.@propagate_inbounds function _sylvester_2x1!(A, B, C) b = B[1] @@ -1642,18 +1675,19 @@ julia> X = lyap(A, B) 0.5 -0.5 -0.5 0.25 -julia> A*X + X*A' + B -2×2 Matrix{Float64}: - 0.0 6.66134e-16 - 6.66134e-16 8.88178e-16 +julia> A*X + X*A' ≈ -B +true ``` """ -function lyap(A::StridedMatrix{T}, C::StridedMatrix{T}) where {T<:BlasFloat} +function lyap(A::AbstractMatrix, C::AbstractMatrix) + T = promote_type(float(eltype(A)), float(eltype(C))) + return lyap(copy_similar(A, T), copy_similar(C, T)) +end +function lyap(A::AbstractMatrix{T}, C::AbstractMatrix{T}) where {T<:BlasFloat} R, Q = schur(A) - - D = -(adjoint(Q) * (C*Q)) + D = Q' * C * Q + D .= .-D Y, scale = LAPACK.trsyl!('N', T <: Complex ? 'C' : 'T', R, R, D) - rmul!(Q*(Y * adjoint(Q)), inv(scale)) + rmul!(Q * Y * Q', inv(scale)) end -lyap(A::StridedMatrix{T}, C::StridedMatrix{T}) where {T<:Integer} = lyap(float(A), float(C)) lyap(a::Union{Real,Complex}, c::Union{Real,Complex}) = -c/(2real(a)) diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index 4b7d9bd9d4af1..b9fa98a9b12b3 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -24,13 +24,14 @@ end """ Diagonal(V::AbstractVector) -Construct a matrix with `V` as its diagonal. +Construct a lazy matrix with `V` as its diagonal. -See also [`diag`](@ref), [`diagm`](@ref). +See also [`UniformScaling`](@ref) for the lazy identity matrix `I`, +[`diagm`](@ref) to make a dense matrix, and [`diag`](@ref) to extract diagonal elements. # Examples ```jldoctest -julia> Diagonal([1, 10, 100]) +julia> d = Diagonal([1, 10, 100]) 3×3 Diagonal{$Int, Vector{$Int}}: 1 ⋅ ⋅ ⋅ 10 ⋅ @@ -40,6 +41,30 @@ julia> diagm([7, 13]) 2×2 Matrix{$Int}: 7 0 0 13 + +julia> ans + I +2×2 Matrix{Int64}: + 8 0 + 0 14 + +julia> I(2) +2×2 Diagonal{Bool, Vector{Bool}}: + 1 ⋅ + ⋅ 1 +``` + +Note that a one-column matrix is not treated like a vector, but instead calls the +method `Diagonal(A::AbstractMatrix)` which extracts 1-element `diag(A)`: + +```jldoctest +julia> A = transpose([7.0 13.0]) +2×1 transpose(::Matrix{Float64}) with eltype Float64: + 7.0 + 13.0 + +julia> Diagonal(A) +1×1 Diagonal{Float64, Vector{Float64}}: + 7.0 ``` """ Diagonal(V::AbstractVector) @@ -71,14 +96,27 @@ julia> diag(A, 2) ``` """ Diagonal(A::AbstractMatrix) = Diagonal(diag(A)) +Diagonal{T}(A::AbstractMatrix) where T = Diagonal{T}(diag(A)) +function convert(::Type{T}, A::AbstractMatrix) where T<:Diagonal + checksquare(A) + isdiag(A) ? T(A) : throw(InexactError(:convert, T, A)) +end Diagonal(D::Diagonal) = D Diagonal{T}(D::Diagonal{T}) where {T} = D Diagonal{T}(D::Diagonal) where {T} = Diagonal{T}(D.diag) AbstractMatrix{T}(D::Diagonal) where {T} = Diagonal{T}(D) -Matrix(D::Diagonal) = diagm(0 => D.diag) -Array(D::Diagonal) = Matrix(D) +Matrix(D::Diagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(D) +Array(D::Diagonal{T}) where {T} = Matrix(D) +function Matrix{T}(D::Diagonal) where {T} + n = size(D, 1) + B = zeros(T, n, n) + @inbounds for i in 1:n + B[i,i] = D.diag[i] + end + return B +end """ Diagonal{T}(undef, n) @@ -88,7 +126,7 @@ Construct an uninitialized `Diagonal{T}` of length `n`. See `undef`. Diagonal{T}(::UndefInitializer, n::Integer) where T = Diagonal(Vector{T}(undef, n)) similar(D::Diagonal, ::Type{T}) where {T} = Diagonal(similar(D.diag, T)) -similar(::Diagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(D::Diagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(D.diag, T, dims) copyto!(D1::Diagonal, D2::Diagonal) = (copyto!(D1.diag, D2.diag); D1) @@ -237,125 +275,138 @@ function (*)(D::Diagonal, V::AbstractVector) 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(A), eltype(D.diag))), 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) = mul!(A, A, D) -lmul!(D::Diagonal, B::AbstractVecOrMat) = mul!(B, D, B) +rmul!(A::AbstractMatrix, D::Diagonal) = @inline mul!(A, A, D) +lmul!(D::Diagonal, B::AbstractVecOrMat) = @inline mul!(B, D, B) -#TODO: It seems better to call (D' * adjA')' directly? -function *(adjA::Adjoint{<:Any,<:AbstractMatrix}, D::Diagonal) - A = adjA.parent - Ac = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) - adjoint!(Ac, A) +function (*)(A::AdjOrTransAbsMat, D::Diagonal) + Ac = copy_similar(A, promote_op(*, eltype(A), eltype(D.diag))) rmul!(Ac, D) end - -function *(transA::Transpose{<:Any,<:AbstractMatrix}, D::Diagonal) - A = transA.parent - At = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) - transpose!(At, A) - rmul!(At, D) -end - -*(D::Diagonal, adjQ::Adjoint{<:Any,<:Union{QRCompactWYQ,QRPackedQ}}) = - rmul!(Array{promote_type(eltype(D), eltype(adjQ))}(D), adjQ) - -function *(D::Diagonal, adjA::Adjoint{<:Any,<:AbstractMatrix}) - A = adjA.parent - Ac = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) - adjoint!(Ac, A) +function (*)(D::Diagonal, A::AdjOrTransAbsMat) + Ac = copy_similar(A, promote_op(*, eltype(A), eltype(D.diag))) lmul!(D, Ac) end -function *(D::Diagonal, transA::Transpose{<:Any,<:AbstractMatrix}) - A = transA.parent - At = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) - transpose!(At, A) - lmul!(D, At) -end - -@inline function __muldiag!(out, D::Diagonal, B, alpha, beta) - if iszero(beta) - out .= (D.diag .* B) .*ₛ alpha +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 + if iszero(alpha) + _rmul_or_fill!(out, beta) else - out .= (D.diag .* B) .*ₛ alpha .+ out .* beta + if bis0 + @inbounds for j in axes(B, 2) + @simd for i in axes(B, 1) + out[i,j] = D.diag[i] * B[i,j] * alpha + end + end + else + @inbounds for j in axes(B, 2) + @simd for i in axes(B, 1) + out[i,j] = D.diag[i] * B[i,j] * alpha + out[i,j] * beta + end + end + end end return out end - -@inline function __muldiag!(out, A, D::Diagonal, alpha, beta) - if iszero(beta) - out .= (A .* permutedims(D.diag)) .*ₛ alpha +function __muldiag!(out, A, D::Diagonal, _add::MulAddMul{ais1,bis0}) where {ais1,bis0} + require_one_based_indexing(out, A) + alpha, beta = _add.alpha, _add.beta + if iszero(alpha) + _rmul_or_fill!(out, beta) else - out .= (A .* permutedims(D.diag)) .*ₛ alpha .+ out .* beta + if bis0 + @inbounds for j in axes(A, 2) + dja = D.diag[j] * alpha + @simd for i in axes(A, 1) + out[i,j] = A[i,j] * dja + end + end + else + @inbounds for j in axes(A, 2) + dja = D.diag[j] * alpha + @simd for i in axes(A, 1) + out[i,j] = A[i,j] * dja + out[i,j] * beta + end + end + end end return out end - -@inline function __muldiag!(out::Diagonal, D1::Diagonal, D2::Diagonal, alpha, beta) - if iszero(beta) - out.diag .= (D1.diag .* D2.diag) .*ₛ alpha +function __muldiag!(out::Diagonal, D1::Diagonal, D2::Diagonal, _add::MulAddMul{ais1,bis0}) where {ais1,bis0} + d1 = D1.diag + d2 = D2.diag + alpha, beta = _add.alpha, _add.beta + if iszero(alpha) + _rmul_or_fill!(out.diag, beta) else - out.diag .= (D1.diag .* D2.diag) .*ₛ alpha .+ out.diag .* beta + if bis0 + @inbounds @simd for i in eachindex(out.diag) + out.diag[i] = d1[i] * d2[i] * alpha + end + else + @inbounds @simd for i in eachindex(out.diag) + out.diag[i] = d1[i] * d2[i] * alpha + out.diag[i] * beta + end + end + end + return out +end +function __muldiag!(out, D1::Diagonal, D2::Diagonal, _add::MulAddMul{ais1,bis0}) where {ais1,bis0} + require_one_based_indexing(out) + alpha, beta = _add.alpha, _add.beta + mA = size(D1, 1) + d1 = D1.diag + d2 = D2.diag + _rmul_or_fill!(out, beta) + if !iszero(alpha) + @inbounds @simd for i in 1:mA + out[i,i] += d1[i] * d2[i] * alpha + end end return out end -# only needed for ambiguity resolution, as mul! is explicitly defined for these arguments -@inline __muldiag!(out, D1::Diagonal, D2::Diagonal, alpha, beta) = - mul!(out, D1, D2, alpha, beta) - -@inline function _muldiag!(out, A, B, alpha, beta) +function _mul_diag!(out, A, B, _add) _muldiag_size_check(out, A, B) - __muldiag!(out, A, B, alpha, beta) + __muldiag!(out, A, B, _add) return out end -# Get ambiguous method if try to unify AbstractVector/AbstractMatrix here using AbstractVecOrMat -@inline mul!(out::AbstractVector, D::Diagonal, V::AbstractVector, alpha::Number, beta::Number) = - _muldiag!(out, D, V, alpha, beta) -@inline mul!(out::AbstractMatrix, D::Diagonal, B::AbstractMatrix, alpha::Number, beta::Number) = - _muldiag!(out, D, B, alpha, beta) -@inline mul!(out::AbstractMatrix, D::Diagonal, B::Adjoint{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = _muldiag!(out, D, B, alpha, beta) -@inline mul!(out::AbstractMatrix, D::Diagonal, B::Transpose{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = _muldiag!(out, D, B, alpha, beta) - -@inline mul!(out::AbstractMatrix, A::AbstractMatrix, D::Diagonal, alpha::Number, beta::Number) = - _muldiag!(out, A, D, alpha, beta) -@inline mul!(out::AbstractMatrix, A::Adjoint{<:Any,<:AbstractVecOrMat}, D::Diagonal, - alpha::Number, beta::Number) = _muldiag!(out, A, D, alpha, beta) -@inline mul!(out::AbstractMatrix, A::Transpose{<:Any,<:AbstractVecOrMat}, D::Diagonal, - alpha::Number, beta::Number) = _muldiag!(out, A, D, alpha, beta) -@inline mul!(C::Diagonal, Da::Diagonal, Db::Diagonal, alpha::Number, beta::Number) = - _muldiag!(C, Da, Db, alpha, beta) - -function mul!(C::AbstractMatrix, Da::Diagonal, Db::Diagonal, alpha::Number, beta::Number) - _muldiag_size_check(C, Da, Db) - require_one_based_indexing(C) - mA = size(Da, 1) - da = Da.diag - db = Db.diag - _rmul_or_fill!(C, beta) - if iszero(beta) - @inbounds @simd for i in 1:mA - C[i,i] = Ref(da[i] * db[i]) .*ₛ alpha - end - else - @inbounds @simd for i in 1:mA - C[i,i] += Ref(da[i] * db[i]) .*ₛ alpha - end - end - return C +_mul!(out::AbstractVecOrMat, D::Diagonal, V::AbstractVector, _add) = + _mul_diag!(out, D, V, _add) +_mul!(out::AbstractMatrix, D::Diagonal, B::AbstractMatrix, _add) = + _mul_diag!(out, D, B, _add) +_mul!(out::AbstractMatrix, A::AbstractMatrix, D::Diagonal, _add) = + _mul_diag!(out, A, D, _add) +_mul!(C::Diagonal, Da::Diagonal, Db::Diagonal, _add) = + _mul_diag!(C, Da, Db, _add) +_mul!(C::AbstractMatrix, Da::Diagonal, Db::Diagonal, _add) = + _mul_diag!(C, Da, Db, _add) + +function (*)(Da::Diagonal, A::AbstractMatrix, Db::Diagonal) + _muldiag_size_check(Da, A) + _muldiag_size_check(A, Db) + return broadcast(*, Da.diag, A, permutedims(Db.diag)) end -_init(op, A::AbstractArray{<:Number}, B::AbstractArray{<:Number}) = - (_ -> zero(typeof(op(oneunit(eltype(A)), oneunit(eltype(B)))))) -_init(op, A::AbstractArray, B::AbstractArray) = promote_op(op, eltype(A), eltype(B)) +function (*)(Da::Diagonal, Db::Diagonal, Dc::Diagonal) + _muldiag_size_check(Da, Db) + _muldiag_size_check(Db, Dc) + 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!(_init(/, A, D).(A), A, D) rdiv!(A::AbstractVecOrMat, D::Diagonal) = @inline _rdiv!(A, A, D) # avoid copy when possible via internal 3-arg backend function _rdiv!(B::AbstractVecOrMat, A::AbstractVecOrMat, D::Diagonal) @@ -380,8 +431,8 @@ function \(D::Diagonal, B::AbstractVector) isnothing(j) || throw(SingularException(j)) return D.diag .\ B end -\(D::Diagonal, B::AbstractMatrix) = - ldiv!(_init(\, D, B).(B), D, B) +\(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) ldiv!(D::Diagonal, B::AbstractVecOrMat) = @inline ldiv!(B, D, B) function ldiv!(B::AbstractVecOrMat, D::Diagonal, A::AbstractVecOrMat) @@ -518,22 +569,23 @@ for Tri in (:UpperTriangular, :LowerTriangular) # 3-arg ldiv! @eval ldiv!(C::$Tri, D::Diagonal, A::$Tri) = $Tri(ldiv!(C.data, D, A.data)) @eval ldiv!(C::$Tri, D::Diagonal, A::$UTri) = $Tri(_setdiag!(ldiv!(C.data, D, A.data), inv, D.diag)) - # 3-arg mul!: invoke 5-arg mul! rather than lmul! - @eval mul!(C::$Tri, A::Union{$Tri,$UTri}, D::Diagonal) = mul!(C, A, D, true, false) + # 3-arg mul! is disambiguated in special.jl # 5-arg mul! - @eval @inline mul!(C::$Tri, D::Diagonal, A::$Tri, α::Number, β::Number) = $Tri(mul!(C.data, D, A.data, α, β)) - @eval @inline function mul!(C::$Tri, D::Diagonal, A::$UTri, α::Number, β::Number) + @eval _mul!(C::$Tri, D::Diagonal, A::$Tri, _add) = $Tri(mul!(C.data, D, A.data, _add.alpha, _add.beta)) + @eval function _mul!(C::$Tri, D::Diagonal, A::$UTri, _add::MulAddMul{ais1,bis0}) where {ais1,bis0} + α, β = _add.alpha, _add.beta iszero(α) && return _rmul_or_fill!(C, β) - diag′ = iszero(β) ? nothing : diag(C) + diag′ = bis0 ? nothing : diag(C) data = mul!(C.data, D, A.data, α, β) - $Tri(_setdiag!(data, MulAddMul(α, β), D.diag, diag′)) + $Tri(_setdiag!(data, _add, D.diag, diag′)) end - @eval @inline mul!(C::$Tri, A::$Tri, D::Diagonal, α::Number, β::Number) = $Tri(mul!(C.data, A.data, D, α, β)) - @eval @inline function mul!(C::$Tri, A::$UTri, D::Diagonal, α::Number, β::Number) + @eval _mul!(C::$Tri, A::$Tri, D::Diagonal, _add) = $Tri(mul!(C.data, A.data, D, _add.alpha, _add.beta)) + @eval function _mul!(C::$Tri, A::$UTri, D::Diagonal, _add::MulAddMul{ais1,bis0}) where {ais1,bis0} + α, β = _add.alpha, _add.beta iszero(α) && return _rmul_or_fill!(C, β) - diag′ = iszero(β) ? nothing : diag(C) + diag′ = bis0 ? nothing : diag(C) data = mul!(C.data, A.data, D, α, β) - $Tri(_setdiag!(data, MulAddMul(α, β), D.diag, diag′)) + $Tri(_setdiag!(data, _add, D.diag, diag′)) end end @@ -551,7 +603,21 @@ end return C end -kron(A::Diagonal{<:Number}, B::Diagonal{<:Number}) = Diagonal(kron(A.diag, B.diag)) +kron(A::Diagonal, B::Diagonal) = Diagonal(kron(A.diag, B.diag)) + +function kron(A::Diagonal, B::SymTridiagonal) + kdv = kron(diag(A), B.dv) + # We don't need to drop the last element + kev = kron(diag(A), _pushzero(_evview(B))) + SymTridiagonal(kdv, kev) +end +function kron(A::Diagonal, B::Tridiagonal) + # `_droplast!` is only guaranteed to work with `Vector` + kd = _makevector(kron(diag(A), B.d)) + kdl = _droplast!(_makevector(kron(diag(A), _pushzero(B.dl)))) + kdu = _droplast!(_makevector(kron(diag(A), _pushzero(B.du)))) + Tridiagonal(kdl, kd, kdu) +end @inline function kron!(C::AbstractMatrix, A::Diagonal, B::AbstractMatrix) require_one_based_indexing(B) @@ -602,7 +668,8 @@ end conj(D::Diagonal) = Diagonal(conj(D.diag)) transpose(D::Diagonal{<:Number}) = D transpose(D::Diagonal) = Diagonal(transpose.(D.diag)) -adjoint(D::Diagonal{<:Number}) = conj(D) +adjoint(D::Diagonal{<:Number}) = Diagonal(vec(adjoint(D.diag))) +adjoint(D::Diagonal{<:Number,<:Base.ReshapedArray{<:Number,1,<:Adjoint}}) = Diagonal(adjoint(parent(D.diag))) adjoint(D::Diagonal) = Diagonal(adjoint.(D.diag)) permutedims(D::Diagonal) = D permutedims(D::Diagonal, perm) = (Base.checkdims_perm(D, D, perm); D) @@ -636,9 +703,9 @@ for f in (:exp, :cis, :log, :sqrt, end function inv(D::Diagonal{T}) where T - Di = similar(D.diag, typeof(inv(zero(T)))) + Di = similar(D.diag, typeof(inv(oneunit(T)))) for i = 1:length(D.diag) - if D.diag[i] == zero(T) + if iszero(D.diag[i]) throw(SingularException(i)) end Di[i] = inv(D.diag[i]) @@ -647,27 +714,41 @@ function inv(D::Diagonal{T}) where T end function pinv(D::Diagonal{T}) where T - Di = similar(D.diag, typeof(inv(zero(T)))) + Di = similar(D.diag, typeof(inv(oneunit(T)))) for i = 1:length(D.diag) - isfinite(inv(D.diag[i])) ? Di[i]=inv(D.diag[i]) : Di[i]=zero(T) + if !iszero(D.diag[i]) + invD = inv(D.diag[i]) + if isfinite(invD) + Di[i] = invD + continue + end + end + # fallback + Di[i] = zero(T) end Diagonal(Di) end function pinv(D::Diagonal{T}, tol::Real) where T - Di = similar(D.diag, typeof(inv(zero(T)))) - if( !isempty(D.diag) ) maxabsD = maximum(abs.(D.diag)) end - for i = 1:length(D.diag) - if( abs(D.diag[i]) > tol*maxabsD && isfinite(inv(D.diag[i])) ) - Di[i]=inv(D.diag[i]) - else - Di[i]=zero(T) + Di = similar(D.diag, typeof(inv(oneunit(T)))) + if !isempty(D.diag) + maxabsD = maximum(abs, D.diag) + for i = 1:length(D.diag) + if abs(D.diag[i]) > tol*maxabsD + invD = inv(D.diag[i]) + if isfinite(invD) + Di[i] = invD + continue + end + end + # fallback + Di[i] = zero(T) end end Diagonal(Di) end #Eigensystem -eigvals(D::Diagonal{<:Number}; permute::Bool=true, scale::Bool=true) = D.diag +eigvals(D::Diagonal{<:Number}; permute::Bool=true, scale::Bool=true) = copy(D.diag) eigvals(D::Diagonal; permute::Bool=true, scale::Bool=true) = [eigvals(x) for x in D.diag] #For block matrices, etc. eigvecs(D::Diagonal) = Matrix{eltype(D)}(I, size(D)) @@ -679,7 +760,7 @@ function eigen(D::Diagonal; permute::Bool=true, scale::Bool=true, sortby::Union{ λ = eigvals(D) if !isnothing(sortby) p = sortperm(λ; alg=QuickSort, by=sortby) - λ = λ[p] # make a copy, otherwise this permutes D.diag + λ = λ[p] evecs = zeros(Td, size(D)) @inbounds for i in eachindex(p) evecs[p[i],i] = one(Td) @@ -689,6 +770,29 @@ function eigen(D::Diagonal; permute::Bool=true, scale::Bool=true, sortby::Union{ end Eigen(λ, evecs) end +function eigen(Da::Diagonal, Db::Diagonal; sortby::Union{Function,Nothing}=nothing) + if any(!isfinite, Da.diag) || any(!isfinite, Db.diag) + throw(ArgumentError("matrices contain Infs or NaNs")) + end + if any(iszero, Db.diag) + throw(ArgumentError("right-hand side diagonal matrix is singular")) + end + return GeneralizedEigen(eigen(Db \ Da; sortby)...) +end +function eigen(A::AbstractMatrix, D::Diagonal; sortby::Union{Function,Nothing}=nothing) + if any(iszero, D.diag) + throw(ArgumentError("right-hand side diagonal matrix is singular")) + end + if size(A, 1) == size(A, 2) && isdiag(A) + return eigen(Diagonal(A), D; sortby) + elseif ishermitian(A) + S = promote_type(eigtype(eltype(A)), eltype(D)) + return eigen!(eigencopy_oftype(Hermitian(A), S), Diagonal{S}(D); sortby) + else + S = promote_type(eigtype(eltype(A)), eltype(D)) + return eigen!(eigencopy_oftype(A, S), Diagonal{S}(D); sortby) + end +end #Singular system svdvals(D::Diagonal{<:Number}) = sort!(abs.(D.diag), rev = true) @@ -710,15 +814,15 @@ function svd(D::Diagonal{T}) where {T<:Number} end # disambiguation methods: * and / of Diagonal and Adj/Trans AbsVec -*(x::AdjointAbsVec, D::Diagonal) = Adjoint(map((t,s) -> t'*s, D.diag, parent(x))) -*(x::TransposeAbsVec, D::Diagonal) = Transpose(map((t,s) -> transpose(t)*s, D.diag, parent(x))) +*(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) = adjoint(adjoint(D) \ u.parent) -/(u::TransposeAbsVec, D::Diagonal) = transpose(transpose(D) \ u.parent) +/(u::AdjointAbsVec, D::Diagonal) = (D' \ u')' +/(u::TransposeAbsVec, D::Diagonal) = transpose(transpose(D) \ transpose(u)) # disambiguation methods: Call unoptimized version for user defined AbstractTriangular. -*(A::AbstractTriangular, D::Diagonal) = Base.@invoke *(A::AbstractMatrix, D::Diagonal) -*(D::Diagonal, A::AbstractTriangular) = Base.@invoke *(D::Diagonal, A::AbstractMatrix) +*(A::AbstractTriangular, D::Diagonal) = @invoke *(A::AbstractMatrix, D::Diagonal) +*(D::Diagonal, A::AbstractTriangular) = @invoke *(D::Diagonal, A::AbstractMatrix) dot(x::AbstractVector, D::Diagonal, y::AbstractVector) = _mapreduce_prod(dot, x, D, y) @@ -731,6 +835,9 @@ 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))")) + end if isempty(x) && isempty(D) && isempty(y) return zero(promote_op(f, eltype(x), eltype(D), eltype(y))) else @@ -755,8 +862,10 @@ end @deprecate cholesky!(A::Diagonal, ::Val{false}; check::Bool = true) cholesky!(A::Diagonal, NoPivot(); check) false @deprecate cholesky(A::Diagonal, ::Val{false}; check::Bool = true) cholesky(A::Diagonal, NoPivot(); check) false -@inline cholcopy(A::Diagonal) = copy_oftype(A, choltype(A)) -@inline cholcopy(A::RealHermSymComplexHerm{<:Real,<:Diagonal}) = copy_oftype(A, choltype(A)) +inv(C::Cholesky{<:Any,<:Diagonal}) = Diagonal(map(inv∘abs2, C.factors.diag)) + +cholcopy(A::Diagonal) = copymutable_oftype(A, choltype(A)) +cholcopy(A::RealHermSymComplexHerm{<:Any,<:Diagonal}) = Diagonal(copy_similar(diag(A), choltype(A))) function getproperty(C::Cholesky{<:Any,<:Diagonal}, d::Symbol) Cfactors = getfield(C, :factors) diff --git a/stdlib/LinearAlgebra/src/eigen.jl b/stdlib/LinearAlgebra/src/eigen.jl index 3060f2a086942..185061b0a3a7d 100644 --- a/stdlib/LinearAlgebra/src/eigen.jl +++ b/stdlib/LinearAlgebra/src/eigen.jl @@ -182,7 +182,9 @@ end Compute the eigenvalue decomposition of `A`, returning an [`Eigen`](@ref) factorization object `F` which contains the eigenvalues in `F.values` and the eigenvectors in the columns of the -matrix `F.vectors`. (The `k`th eigenvector can be obtained from the slice `F.vectors[:, k]`.) +matrix `F.vectors`. This corresponds to solving an eigenvalue problem of the form +`Ax = λx`, where `A` is a matrix, `x` is an eigenvector, and `λ` is an eigenvalue. +(The `k`th eigenvector can be obtained from the slice `F.vectors[:, k]`.) Iterating the decomposition produces the components `F.values` and `F.vectors`. @@ -233,16 +235,20 @@ true ``` """ function eigen(A::AbstractMatrix{T}; permute::Bool=true, scale::Bool=true, sortby::Union{Function,Nothing}=eigsortby) where T - AA = copy_oftype(A, eigtype(T)) - isdiag(AA) && return eigen(Diagonal(AA); permute=permute, scale=scale, sortby=sortby) - return eigen!(AA; permute=permute, scale=scale, sortby=sortby) + 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) end function eigen(A::AbstractMatrix{T}; permute::Bool=true, scale::Bool=true, sortby::Union{Function,Nothing}=eigsortby) where {T <: Union{Float16,Complex{Float16}}} - AA = copy_oftype(A, eigtype(T)) - isdiag(AA) && return eigen(Diagonal(AA); permute=permute, scale=scale, sortby=sortby) - A = eigen!(AA; permute, scale, sortby) - values = convert(AbstractVector{isreal(A.values) ? Float16 : Complex{Float16}}, A.values) - vectors = convert(AbstractMatrix{isreal(A.vectors) ? Float16 : Complex{Float16}}, A.vectors) + isdiag(A) && return eigen(Diagonal{eigtype(T)}(diag(A)); sortby) + E = 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)) @@ -333,7 +339,7 @@ julia> eigvals(diag_matrix) ``` """ eigvals(A::AbstractMatrix{T}; kws...) where T = - eigvals!(copy_oftype(A, eigtype(T)); kws...) + eigvals!(eigencopy_oftype(A, eigtype(T)); kws...) """ For a scalar input, `eigvals` will return a scalar. @@ -436,7 +442,11 @@ det(A::Eigen) = prod(A.values) function eigen!(A::StridedMatrix{T}, B::StridedMatrix{T}; sortby::Union{Function,Nothing}=eigsortby) where T<:BlasReal issymmetric(A) && isposdef(B) && return eigen!(Symmetric(A), Symmetric(B), sortby=sortby) n = size(A, 1) - alphar, alphai, beta, _, vr = LAPACK.ggev!('N', 'V', A, B) + if LAPACK.version() < v"3.6.0" + alphar, alphai, beta, _, vr = LAPACK.ggev!('N', 'V', A, B) + else + alphar, alphai, beta, _, vr = LAPACK.ggev3!('N', 'V', A, B) + end iszero(alphai) && return GeneralizedEigen(sorteig!(alphar ./ beta, vr, sortby)...) vecs = zeros(Complex{T}, n, n) @@ -458,7 +468,11 @@ end function eigen!(A::StridedMatrix{T}, B::StridedMatrix{T}; sortby::Union{Function,Nothing}=eigsortby) where T<:BlasComplex ishermitian(A) && isposdef(B) && return eigen!(Hermitian(A), Hermitian(B), sortby=sortby) - alpha, beta, _, vr = LAPACK.ggev!('N', 'V', A, B) + if LAPACK.version() < v"3.6.0" + alpha, beta, _, vr = LAPACK.ggev!('N', 'V', A, B) + else + alpha, beta, _, vr = LAPACK.ggev3!('N', 'V', A, B) + end return GeneralizedEigen(sorteig!(alpha./beta, vr, sortby)...) end @@ -468,6 +482,8 @@ end Compute the generalized eigenvalue decomposition of `A` and `B`, returning a [`GeneralizedEigen`](@ref) factorization object `F` which contains the generalized eigenvalues in `F.values` and the generalized eigenvectors in the columns of the matrix `F.vectors`. +This corresponds to solving a generalized eigenvalue problem of the form +`Ax = λBx`, where `A, B` are matrices, `x` is an eigenvector, and `λ` is an eigenvalue. (The `k`th generalized eigenvector can be obtained from the slice `F.vectors[:, k]`.) Iterating the decomposition produces the components `F.values` and `F.vectors`. @@ -507,12 +523,20 @@ true ``` """ function eigen(A::AbstractMatrix{TA}, B::AbstractMatrix{TB}; kws...) where {TA,TB} - S = promote_type(eigtype(TA),TB) - eigen!(copy_oftype(A, S), copy_oftype(B, S); kws...) + S = promote_type(eigtype(TA), TB) + eigen!(eigencopy_oftype(A, S), eigencopy_oftype(B, S); kws...) end - eigen(A::Number, B::Number) = eigen(fill(A,1,1), fill(B,1,1)) +""" + LinearAlgebra.eigencopy_oftype(A::AbstractMatrix, ::Type{S}) + +Creates a dense copy of `A` with eltype `S` by calling `copy_similar(A, S)`. +In the case of `Hermitian` or `Symmetric` matrices additionally retains the wrapper, +together with the `uplo` field. +""" +eigencopy_oftype(A, S) = copy_similar(A, S) + """ eigvals!(A, B; sortby) -> values @@ -553,12 +577,20 @@ julia> B """ function eigvals!(A::StridedMatrix{T}, B::StridedMatrix{T}; sortby::Union{Function,Nothing}=eigsortby) where T<:BlasReal issymmetric(A) && isposdef(B) && return sorteig!(eigvals!(Symmetric(A), Symmetric(B)), sortby) - alphar, alphai, beta, vl, vr = LAPACK.ggev!('N', 'N', A, B) + if LAPACK.version() < v"3.6.0" + alphar, alphai, beta, vl, vr = LAPACK.ggev!('N', 'N', A, B) + else + alphar, alphai, beta, vl, vr = LAPACK.ggev3!('N', 'N', A, B) + end return sorteig!((iszero(alphai) ? alphar : complex.(alphar, alphai))./beta, sortby) end function eigvals!(A::StridedMatrix{T}, B::StridedMatrix{T}; sortby::Union{Function,Nothing}=eigsortby) where T<:BlasComplex ishermitian(A) && isposdef(B) && return sorteig!(eigvals!(Hermitian(A), Hermitian(B)), sortby) - alpha, beta, vl, vr = LAPACK.ggev!('N', 'N', A, B) + if LAPACK.version() < v"3.6.0" + alpha, beta, vl, vr = LAPACK.ggev!('N', 'N', A, B) + else + alpha, beta, vl, vr = LAPACK.ggev3!('N', 'N', A, B) + end return sorteig!(alpha./beta, sortby) end @@ -586,8 +618,8 @@ julia> eigvals(A,B) ``` """ function eigvals(A::AbstractMatrix{TA}, B::AbstractMatrix{TB}; kws...) where {TA,TB} - S = promote_type(eigtype(TA),TB) - return eigvals!(copy_oftype(A, S), copy_oftype(B, S); kws...) + S = promote_type(eigtype(TA), TB) + return eigvals!(eigencopy_oftype(A, S), eigencopy_oftype(B, S); kws...) end """ diff --git a/stdlib/LinearAlgebra/src/exceptions.jl b/stdlib/LinearAlgebra/src/exceptions.jl index 6704a9ac6ae4d..a8d81aad3e067 100644 --- a/stdlib/LinearAlgebra/src/exceptions.jl +++ b/stdlib/LinearAlgebra/src/exceptions.jl @@ -50,7 +50,7 @@ end Exception thrown when a matrix factorization/solve encounters a zero in a pivot (diagonal) position and cannot proceed. This may *not* mean that the matrix is singular: -it may be fruitful to switch to a diffent factorization such as pivoted LU +it may be fruitful to switch to a different factorization such as pivoted LU that can re-order variables to eliminate spurious zero pivots. The `info` field indicates the location of (one of) the zero pivot(s). """ @@ -59,4 +59,4 @@ struct ZeroPivotException <: Exception end function Base.showerror(io::IO, ex::ZeroPivotException) print(io, "ZeroPivotException: factorization encountered one or more zero pivots. Consider switching to a pivoted LU factorization.") -end \ No newline at end of file +end diff --git a/stdlib/LinearAlgebra/src/factorization.jl b/stdlib/LinearAlgebra/src/factorization.jl index 626a1ae7b1a74..8c35a23e6b6d5 100644 --- a/stdlib/LinearAlgebra/src/factorization.jl +++ b/stdlib/LinearAlgebra/src/factorization.jl @@ -11,12 +11,62 @@ matrix factorizations. """ abstract type Factorization{T} end +""" + AdjointFactorization + +Lazy wrapper type for the adjoint of the underlying `Factorization` object. Usually, the +`AdjointFactorization` constructor should not be called directly, use +[`adjoint(:: Factorization)`](@ref) instead. +""" +struct AdjointFactorization{T,S<:Factorization} <: Factorization{T} + parent::S +end +AdjointFactorization(F::Factorization) = + AdjointFactorization{Base.promote_op(adjoint,eltype(F)),typeof(F)}(F) + +""" + TransposeFactorization + +Lazy wrapper type for the transpose of the underlying `Factorization` object. Usually, the +`TransposeFactorization` constructor should not be called directly, use +[`transpose(:: Factorization)`](@ref) instead. +""" +struct TransposeFactorization{T,S<:Factorization} <: Factorization{T} + parent::S +end +TransposeFactorization(F::Factorization) = + TransposeFactorization{Base.promote_op(adjoint,eltype(F)),typeof(F)}(F) + eltype(::Type{<:Factorization{T}}) where {T} = T -size(F::Adjoint{<:Any,<:Factorization}) = reverse(size(parent(F))) -size(F::Transpose{<:Any,<:Factorization}) = reverse(size(parent(F))) +size(F::AdjointFactorization) = reverse(size(parent(F))) +size(F::TransposeFactorization) = reverse(size(parent(F))) +size(F::Union{AdjointFactorization,TransposeFactorization}, d::Integer) = d in (1, 2) ? size(F)[d] : 1 +parent(F::Union{AdjointFactorization,TransposeFactorization}) = F.parent + +""" + adjoint(F::Factorization) + +Lazy adjoint of the factorization `F`. By default, returns an +[`AdjointFactorization`](@ref) wrapper. +""" +adjoint(F::Factorization) = AdjointFactorization(F) +""" + transpose(F::Factorization) + +Lazy transpose of the factorization `F`. By default, returns a [`TransposeFactorization`](@ref), +except for `Factorization`s with real `eltype`, in which case returns an [`AdjointFactorization`](@ref). +""" +transpose(F::Factorization) = TransposeFactorization(F) +transpose(F::Factorization{<:Real}) = AdjointFactorization(F) +adjoint(F::AdjointFactorization) = F.parent +transpose(F::TransposeFactorization) = F.parent +transpose(F::AdjointFactorization{<:Real}) = F.parent +conj(A::TransposeFactorization) = adjoint(A.parent) +conj(A::AdjointFactorization) = transpose(A.parent) 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()) @@ -31,12 +81,12 @@ Test that a factorization of a matrix succeeded. ```jldoctest julia> F = cholesky([1 0; 0 1]); -julia> LinearAlgebra.issuccess(F) +julia> issuccess(F) true julia> F = lu([1 0; 0 0]; check = false); -julia> LinearAlgebra.issuccess(F) +julia> issuccess(F) false ``` """ @@ -53,87 +103,100 @@ function det(F::Factorization) end convert(::Type{T}, f::T) where {T<:Factorization} = f -convert(::Type{T}, f::Factorization) where {T<:Factorization} = T(f) +convert(::Type{T}, f::Factorization) where {T<:Factorization} = T(f)::T -convert(::Type{T}, f::Factorization) where {T<:AbstractArray} = T(f) +convert(::Type{T}, f::Factorization) where {T<:AbstractArray} = T(f)::T ### General promotion rules Factorization{T}(F::Factorization{T}) where {T} = F -# This is a bit odd since the return is not a Factorization but it works well in generic code -Factorization{T}(A::Adjoint{<:Any,<:Factorization}) where {T} = +# This no longer looks odd since the return _is_ a Factorization! +Factorization{T}(A::AdjointFactorization) where {T} = adjoint(Factorization{T}(parent(A))) +Factorization{T}(A::TransposeFactorization) where {T} = + transpose(Factorization{T}(parent(A))) inv(F::Factorization{T}) where {T} = (n = size(F, 1); ldiv!(F, Matrix{T}(I, n, n))) Base.hash(F::Factorization, h::UInt) = mapreduce(f -> hash(getfield(F, f)), hash, 1:nfields(F); init=h) Base.:(==)( F::T, G::T) where {T<:Factorization} = all(f -> getfield(F, f) == getfield(G, f), 1:nfields(F)) Base.isequal(F::T, G::T) where {T<:Factorization} = all(f -> isequal(getfield(F, f), getfield(G, f)), 1:nfields(F))::Bool -function Base.show(io::IO, x::Adjoint{<:Any,<:Factorization}) - print(io, "Adjoint of ") +function Base.show(io::IO, x::AdjointFactorization) + print(io, "adjoint of ") show(io, parent(x)) end -function Base.show(io::IO, x::Transpose{<:Any,<:Factorization}) - print(io, "Transpose of ") +function Base.show(io::IO, x::TransposeFactorization) + print(io, "transpose of ") show(io, parent(x)) end -function Base.show(io::IO, ::MIME"text/plain", x::Adjoint{<:Any,<:Factorization}) - print(io, "Adjoint of ") +function Base.show(io::IO, ::MIME"text/plain", x::AdjointFactorization) + print(io, "adjoint of ") show(io, MIME"text/plain"(), parent(x)) end -function Base.show(io::IO, ::MIME"text/plain", x::Transpose{<:Any,<:Factorization}) - print(io, "Transpose of ") +function Base.show(io::IO, ::MIME"text/plain", x::TransposeFactorization) + print(io, "transpose of ") show(io, MIME"text/plain"(), parent(x)) end # With a real lhs and complex rhs with the same precision, we can reinterpret # the complex rhs as a real rhs with twice the number of columns or rows -function (\)(F::Factorization{T}, B::VecOrMat{Complex{T}}) where T<:BlasReal +function (\)(F::Factorization{T}, B::VecOrMat{Complex{T}}) where {T<:BlasReal} require_one_based_indexing(B) c2r = reshape(copy(transpose(reinterpret(T, reshape(B, (1, length(B)))))), size(B, 1), 2*size(B, 2)) x = ldiv!(F, c2r) return reshape(copy(reinterpret(Complex{T}, copy(transpose(reshape(x, div(length(x), 2), 2))))), _ret_size(F, B)) end -function (/)(B::VecOrMat{Complex{T}}, F::Factorization{T}) where T<:BlasReal +# don't do the reinterpretation for [Adjoint/Transpose]Factorization +(\)(F::TransposeFactorization{T}, B::VecOrMat{Complex{T}}) where {T<:BlasReal} = + conj!(adjoint(parent(F)) \ conj.(B)) +(\)(F::AdjointFactorization{T}, B::VecOrMat{Complex{T}}) where {T<:BlasReal} = + @invoke \(F::typeof(F), B::VecOrMat) + +function (/)(B::VecOrMat{Complex{T}}, F::Factorization{T}) where {T<:BlasReal} require_one_based_indexing(B) x = rdiv!(copy(reinterpret(T, B)), F) return copy(reinterpret(Complex{T}, x)) end +# don't do the reinterpretation for [Adjoint/Transpose]Factorization +(/)(B::VecOrMat{Complex{T}}, F::TransposeFactorization{T}) where {T<:BlasReal} = + conj!(adjoint(parent(F)) \ conj.(B)) +(/)(B::VecOrMat{Complex{T}}, F::AdjointFactorization{T}) where {T<:BlasReal} = + @invoke /(B::VecOrMat{Complex{T}}, F::Factorization{T}) -function \(F::Union{Factorization, Adjoint{<:Any,<:Factorization}}, B::AbstractVecOrMat) +function (\)(F::Factorization, B::AbstractVecOrMat) require_one_based_indexing(B) - TFB = typeof(oneunit(eltype(B)) / oneunit(eltype(F))) + TFB = typeof(oneunit(eltype(F)) \ oneunit(eltype(B))) ldiv!(F, copy_similar(B, TFB)) end +(\)(F::TransposeFactorization, B::AbstractVecOrMat) = conj!(adjoint(F.parent) \ conj.(B)) -function /(B::AbstractMatrix, F::Union{Factorization, Adjoint{<:Any,<:Factorization}}) +function (/)(B::AbstractMatrix, F::Factorization) require_one_based_indexing(B) TFB = typeof(oneunit(eltype(B)) / oneunit(eltype(F))) rdiv!(copy_similar(B, TFB), F) end -/(adjB::AdjointAbsVec, adjF::Adjoint{<:Any,<:Factorization}) = adjoint(adjF.parent \ adjB.parent) -/(B::TransposeAbsVec, adjF::Adjoint{<:Any,<:Factorization}) = adjoint(adjF.parent \ adjoint(B)) +(/)(A::AbstractMatrix, F::AdjointFactorization) = adjoint(adjoint(F) \ adjoint(A)) +(/)(A::AbstractMatrix, F::TransposeFactorization) = transpose(transpose(F) \ transpose(A)) - -# support the same 3-arg idiom as in our other in-place A_*_B functions: -function ldiv!(Y::AbstractVecOrMat, A::Factorization, B::AbstractVecOrMat) +function ldiv!(Y::AbstractVector, A::Factorization, B::AbstractVector) + require_one_based_indexing(Y, B) + m, n = size(A) + if m > n + Bc = copy(B) + ldiv!(A, Bc) + return copyto!(Y, 1, Bc, 1, n) + else + return ldiv!(A, copyto!(Y, B)) + end +end +function ldiv!(Y::AbstractMatrix, A::Factorization, B::AbstractMatrix) require_one_based_indexing(Y, B) - m, n = size(A, 1), size(A, 2) + m, n = size(A) if m > n Bc = copy(B) ldiv!(A, Bc) return copyto!(Y, view(Bc, 1:n, :)) else - return ldiv!(A, copyto!(Y, view(B, 1:m, :))) + copyto!(view(Y, 1:m, :), view(B, 1:m, :)) + return ldiv!(A, Y) end end - -# fallback methods for transposed solves -\(F::Transpose{<:Any,<:Factorization{<:Real}}, B::AbstractVecOrMat) = adjoint(F.parent) \ B -\(F::Transpose{<:Any,<:Factorization}, B::AbstractVecOrMat) = conj.(adjoint(F.parent) \ conj.(B)) - -/(B::AbstractMatrix, F::Transpose{<:Any,<:Factorization{<:Real}}) = B / adjoint(F.parent) -/(B::AbstractMatrix, F::Transpose{<:Any,<:Factorization}) = conj.(conj.(B) / adjoint(F.parent)) -/(B::AdjointAbsVec, F::Transpose{<:Any,<:Factorization{<:Real}}) = B / adjoint(F.parent) -/(B::TransposeAbsVec, F::Transpose{<:Any,<:Factorization{<:Real}}) = B / adjoint(F.parent) -/(B::AdjointAbsVec, F::Transpose{<:Any,<:Factorization}) = conj.(conj.(B) / adjoint(F.parent)) -/(B::TransposeAbsVec, F::Transpose{<:Any,<:Factorization}) = conj.(conj.(B) / adjoint(F.parent)) diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index 5565b28ebabe7..c66f59838e8ba 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -8,8 +8,7 @@ # inside this function. function *ₛ end Broadcast.broadcasted(::typeof(*ₛ), out, beta) = - iszero(beta::Number) ? false : - isone(beta::Number) ? broadcasted(identity, out) : broadcasted(*, out, beta) + iszero(beta::Number) ? false : broadcasted(*, out, beta) """ MulAddMul(alpha, beta) @@ -368,7 +367,7 @@ tril(M::AbstractMatrix) = tril!(copy(M)) """ triu(M, k::Integer) -Returns the upper triangle of `M` starting from the `k`th superdiagonal. +Return the upper triangle of `M` starting from the `k`th superdiagonal. # Examples ```jldoctest @@ -399,7 +398,7 @@ triu(M::AbstractMatrix,k::Integer) = triu!(copy(M),k) """ tril(M, k::Integer) -Returns the lower triangle of `M` starting from the `k`th superdiagonal. +Return the lower triangle of `M` starting from the `k`th superdiagonal. # Examples ```jldoctest @@ -462,7 +461,7 @@ norm_sqr(x::Union{T,Complex{T},Rational{T}}) where {T<:Integer} = abs2(float(x)) function generic_norm2(x) maxabs = normInf(x) - (iszero(maxabs) || isinf(maxabs)) && return maxabs + (ismissing(maxabs) || iszero(maxabs) || isinf(maxabs)) && return maxabs (v, s) = iterate(x)::Tuple T = typeof(maxabs) if isfinite(length(x)*maxabs*maxabs) && !iszero(maxabs*maxabs) # Scaling not necessary @@ -473,6 +472,7 @@ function generic_norm2(x) (v, s) = y sum += norm_sqr(v) end + ismissing(sum) && return missing return convert(T, sqrt(sum)) else sum = abs2(norm(v)/maxabs) @@ -482,6 +482,7 @@ function generic_norm2(x) (v, s) = y sum += (norm(v)/maxabs)^2 end + ismissing(sum) && return missing return convert(T, maxabs*sqrt(sum)) end end @@ -492,7 +493,7 @@ function generic_normp(x, p) (v, s) = iterate(x)::Tuple if p > 1 || p < -1 # might need to rescale to avoid overflow maxabs = p > 1 ? normInf(x) : normMinusInf(x) - (iszero(maxabs) || isinf(maxabs)) && return maxabs + (ismissing(maxabs) || iszero(maxabs) || isinf(maxabs)) && return maxabs T = typeof(maxabs) else T = typeof(float(norm(v))) @@ -504,15 +505,18 @@ function generic_normp(x, p) y = iterate(x, s) y === nothing && break (v, s) = y + ismissing(v) && return missing sum += norm(v)^spp end return convert(T, sum^inv(spp)) else # rescaling sum = (norm(v)/maxabs)^spp + ismissing(sum) && return missing while true y = iterate(x, s) y === nothing && break (v, s) = y + ismissing(v) && return missing sum += (norm(v)/maxabs)^spp end return convert(T, maxabs*sum^inv(spp)) @@ -801,7 +805,7 @@ opnorm(v::AdjointAbsVec, q::Real) = q == Inf ? norm(conj(v.parent), 1) : norm(co opnorm(v::AdjointAbsVec) = norm(conj(v.parent)) opnorm(v::TransposeAbsVec) = norm(v.parent) -norm(v::Union{TransposeAbsVec,AdjointAbsVec}, p::Real) = norm(v.parent, p) +norm(v::AdjOrTrans, p::Real) = norm(v.parent, p) """ dot(x, y) @@ -886,7 +890,9 @@ function dot(x::AbstractArray, y::AbstractArray) s end -dot(x::Adjoint, y::Adjoint) = conj(dot(parent(x), parent(y))) +function dot(x::Adjoint{<:Union{Real,Complex}}, y::Adjoint{<:Union{Real,Complex}}) + return conj(dot(parent(x), parent(y))) +end dot(x::Transpose, y::Transpose) = dot(parent(x), parent(y)) """ @@ -941,13 +947,22 @@ dot(x::AbstractVector, transA::Transpose{<:Real}, y::AbstractVector) = adjoint(d rank(A::AbstractMatrix; atol::Real=0, rtol::Real=atol>0 ? 0 : n*ϵ) rank(A::AbstractMatrix, rtol::Real) -Compute the rank of a matrix by counting how many singular -values of `A` have magnitude greater than `max(atol, rtol*σ₁)` where `σ₁` is -`A`'s largest singular value. `atol` and `rtol` are the absolute and relative +Compute the numerical rank of a matrix by counting how many outputs of +`svdvals(A)` are greater than `max(atol, rtol*σ₁)` where `σ₁` is `A`'s largest +calculated singular value. `atol` and `rtol` are the absolute and relative tolerances, respectively. The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of `A`, and `ϵ` is the [`eps`](@ref) of the element type of `A`. +!!! note + Numerical rank can be a sensitive and imprecise characterization of + ill-conditioned matrices with singular values that are close to the threshold + tolerance `max(atol, rtol*σ₁)`. In such cases, slight perturbations to the + singular-value computation or to the matrix can change the result of `rank` + by pushing one or more singular values across the threshold. These variations + can even occur due to changes in floating-point errors between different Julia + versions, architectures, compilers, or operating systems. + !!! compat "Julia 1.1" The `atol` and `rtol` keyword arguments requires at least Julia 1.1. In Julia 1.0 `rtol` is available as a positional argument, but this @@ -975,9 +990,9 @@ function rank(A::AbstractMatrix; atol::Real = 0.0, rtol::Real = (min(size(A)...) isempty(A) && return 0 # 0-dimensional case s = svdvals(A) tol = max(atol, rtol*s[1]) - count(x -> x > tol, s) + count(>(tol), s) end -rank(x::Number) = iszero(x) ? 0 : 1 +rank(x::Union{Number,AbstractVector}) = iszero(x) ? 0 : 1 """ tr(M) @@ -1140,9 +1155,6 @@ function (/)(A::AbstractVecOrMat, B::AbstractVecOrMat) size(A,2) != size(B,2) && throw(DimensionMismatch("Both inputs should have the same number of columns")) return copy(adjoint(adjoint(B) \ adjoint(A))) end -# \(A::StridedMatrix,x::Number) = inv(A)*x Should be added at some point when the old elementwise version has been deprecated long enough -# /(x::Number,A::StridedMatrix) = x*inv(A) -/(x::Number, v::AbstractVector) = x*pinv(v) cond(x::Number) = iszero(x) ? Inf : 1.0 cond(x::Number, p) = cond(x) @@ -1375,7 +1387,9 @@ isbanded(A::AbstractMatrix, kl::Integer, ku::Integer) = istriu(A, kl) && istril( """ isdiag(A) -> Bool -Test whether a matrix is diagonal. +Test whether a matrix is diagonal in the sense that `iszero(A[i,j])` is true unless `i == j`. +Note that it is not necessary for `A` to be square; +if you would also like to check that, you need to check that `size(A, 1) == size(A, 2)`. # Examples ```jldoctest @@ -1394,23 +1408,56 @@ julia> b = [im 0; 0 -im] julia> isdiag(b) true + +julia> c = [1 0 0; 0 2 0] +2×3 Matrix{Int64}: + 1 0 0 + 0 2 0 + +julia> isdiag(c) +true + +julia> d = [1 0 0; 0 2 3] +2×3 Matrix{Int64}: + 1 0 0 + 0 2 3 + +julia> isdiag(d) +false ``` """ isdiag(A::AbstractMatrix) = isbanded(A, 0, 0) isdiag(x::Number) = true +""" + axpy!(α, x::AbstractArray, y::AbstractArray) + +Overwrite `y` with `x * α + y` and return `y`. +If `x` and `y` have the same axes, it's equivalent with `y .+= x .* a`. + +# Examples +```jldoctest +julia> x = [1; 2; 3]; + +julia> y = [4; 5; 6]; -# BLAS-like in-place y = x*α+y function (see also the version in blas.jl -# for BlasFloat Arrays) +julia> axpy!(2, x, y) +3-element Vector{Int64}: + 6 + 9 + 12 +``` +""" 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))")) end + iszero(α) && return y for (IY, IX) in zip(eachindex(y), eachindex(x)) @inbounds y[IY] += x[IX]*α end - y + return y end function axpy!(α, x::AbstractArray, rx::AbstractArray{<:Integer}, y::AbstractArray, ry::AbstractArray{<:Integer}) @@ -1421,22 +1468,61 @@ function axpy!(α, x::AbstractArray, rx::AbstractArray{<:Integer}, y::AbstractAr elseif !checkindex(Bool, eachindex(IndexLinear(), y), ry) throw(BoundsError(y, ry)) end + iszero(α) && return y for (IY, IX) in zip(eachindex(ry), eachindex(rx)) @inbounds y[ry[IY]] += x[rx[IX]]*α end - y + return y end +""" + axpby!(α, x::AbstractArray, β, y::AbstractArray) + +Overwrite `y` with `x * α + y * β` and return `y`. +If `x` and `y` have the same axes, it's equivalent with `y .= x .* a .+ y .* β`. + +# Examples +```jldoctest +julia> x = [1; 2; 3]; + +julia> y = [4; 5; 6]; + +julia> axpby!(2, x, 2, y) +3-element Vector{Int64}: + 10 + 14 + 18 +``` +""" function axpby!(α, x::AbstractArray, β, y::AbstractArray) if length(x) != length(y) throw(DimensionMismatch("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)) @inbounds y[IY] = x[IX]*α + y[IY]*β end y end +DenseLike{T} = Union{DenseArray{T}, Base.StridedReshapedArray{T}, Base.StridedReinterpretArray{T}} +StridedVecLike{T} = Union{DenseLike{T}, Base.FastSubArray{T,<:Any,<:DenseLike{T}}} +axpy!(α::Number, x::StridedVecLike{T}, y::StridedVecLike{T}) where {T<:BlasFloat} = BLAS.axpy!(α, x, y) +axpby!(α::Number, x::StridedVecLike{T}, β::Number, y::StridedVecLike{T}) where {T<:BlasFloat} = BLAS.axpby!(α, x, β, y) +function axpy!(α::Number, + x::StridedVecLike{T}, rx::AbstractRange{<:Integer}, + y::StridedVecLike{T}, ry::AbstractRange{<:Integer}, +) where {T<:BlasFloat} + if Base.has_offset_axes(rx, ry) + return @invoke axpy!(α, + x::AbstractArray, rx::AbstractArray{<:Integer}, + y::AbstractArray, ry::AbstractArray{<:Integer}, + ) + end + @views BLAS.axpy!(α, x[rx], y[ry]) + return y +end + """ rotate!(x, y, c, s) @@ -1540,9 +1626,9 @@ julia> det(M) 2.0 ``` """ -function det(A::AbstractMatrix{T}) where T +function det(A::AbstractMatrix{T}) where {T} if istriu(A) || istril(A) - S = typeof((one(T)*zero(T) + zero(T))/one(T)) + S = promote_type(T, typeof((one(T)*zero(T) + zero(T))/one(T))) return convert(S, det(UpperTriangular(A))) end return det(lu(A; check = false)) @@ -1698,10 +1784,11 @@ function isapprox(x::AbstractArray, y::AbstractArray; nans::Bool=false, norm::Function=norm) d = norm(x - y) if isfinite(d) - return d <= max(atol, rtol*max(norm(x), norm(y))) + return iszero(rtol) ? d <= atol : d <= max(atol, rtol*max(norm(x), norm(y))) else # Fall back to a component-wise approximate comparison - return all(ab -> isapprox(ab[1], ab[2]; rtol=rtol, atol=atol, nans=nans), zip(x, y)) + # (mapreduce instead of all for greater generality [#44893]) + return mapreduce((a, b) -> isapprox(a, b; rtol=rtol, atol=atol, nans=nans), &, x, y) end end @@ -1717,29 +1804,27 @@ function normalize!(a::AbstractArray, p::Real=2) __normalize!(a, nrm) end -@inline function __normalize!(a::AbstractArray, nrm::Real) +@inline function __normalize!(a::AbstractArray, nrm) # The largest positive floating point number whose inverse is less than infinity δ = inv(prevfloat(typemax(nrm))) - if nrm ≥ δ # Safe to multiply with inverse invnrm = inv(nrm) rmul!(a, invnrm) - else # scale elements to avoid overflow εδ = eps(one(nrm))/δ rmul!(a, εδ) rmul!(a, inv(nrm*εδ)) end - - a + return a end """ - normalize(a::AbstractArray, p::Real=2) + normalize(a, p::Real=2) -Normalize the array `a` so that its `p`-norm equals unity, -i.e. `norm(a, p) == 1`. -See also [`normalize!`](@ref) and [`norm`](@ref). +Normalize `a` so that its `p`-norm equals unity, +i.e. `norm(a, p) == 1`. For scalars, this is similar to sign(a), +except normalize(0) = NaN. +See also [`normalize!`](@ref), [`norm`](@ref), and [`sign`](@ref). # Examples ```jldoctest @@ -1776,15 +1861,26 @@ julia> normalize(a) 0.154303 0.308607 0.617213 0.154303 0.308607 0.617213 +julia> normalize(3, 1) +1.0 + +julia> normalize(-8, 1) +-1.0 + +julia> normalize(0, 1) +NaN ``` """ function normalize(a::AbstractArray, p::Real = 2) nrm = norm(a, p) if !isempty(a) - aa = copy_oftype(a, typeof(first(a)/nrm)) + aa = copymutable_oftype(a, typeof(first(a)/nrm)) return __normalize!(aa, nrm) else T = typeof(zero(eltype(a))/nrm) return T[] end end + +normalize(x) = x / norm(x) +normalize(x, p::Real) = x / norm(x, p) diff --git a/stdlib/LinearAlgebra/src/givens.jl b/stdlib/LinearAlgebra/src/givens.jl index 1a71b0604b5a2..c37df41f9567c 100644 --- a/stdlib/LinearAlgebra/src/givens.jl +++ b/stdlib/LinearAlgebra/src/givens.jl @@ -3,23 +3,24 @@ # givensAlgorithm functions are derived from LAPACK, see below abstract type AbstractRotation{T} end +struct AdjointRotation{T,S<:AbstractRotation{T}} <: AbstractRotation{T} + R::S +end transpose(R::AbstractRotation) = error("transpose not implemented for $(typeof(R)). Consider using adjoint instead of transpose.") -function (*)(R::AbstractRotation{T}, A::AbstractVecOrMat{S}) where {T,S} - TS = typeof(zero(T)*zero(S) + zero(T)*zero(S)) - lmul!(convert(AbstractRotation{TS}, R), copy_oftype(A, TS)) -end -(*)(A::AbstractVector, adjR::Adjoint{<:Any,<:AbstractRotation}) = _absvecormat_mul_adjrot(A, adjR) -(*)(A::AbstractMatrix, adjR::Adjoint{<:Any,<:AbstractRotation}) = _absvecormat_mul_adjrot(A, adjR) -function _absvecormat_mul_adjrot(A::AbstractVecOrMat{T}, adjR::Adjoint{<:Any,<:AbstractRotation{S}}) where {T,S} - R = adjR.parent +(*)(R::AbstractRotation, A::AbstractVector) = _rot_mul_vecormat(R, A) +(*)(R::AbstractRotation, A::AbstractMatrix) = _rot_mul_vecormat(R, A) +function _rot_mul_vecormat(R::AbstractRotation{T}, A::AbstractVecOrMat{S}) where {T,S} TS = typeof(zero(T)*zero(S) + zero(T)*zero(S)) - rmul!(TS.(A), convert(AbstractRotation{TS}, R)') + lmul!(convert(AbstractRotation{TS}, R), copy_similar(A, TS)) end -function(*)(A::AbstractMatrix{T}, R::AbstractRotation{S}) where {T,S} + +(*)(A::AbstractVector, R::AbstractRotation) = _vecormat_mul_rot(A, R) +(*)(A::AbstractMatrix, R::AbstractRotation) = _vecormat_mul_rot(A, R) +function _vecormat_mul_rot(A::AbstractVecOrMat{T}, R::AbstractRotation{S}) where {T,S} TS = typeof(zero(T)*zero(S) + zero(T)*zero(S)) - rmul!(TS.(A), convert(AbstractRotation{TS}, R)) + rmul!(copy_similar(A, TS), convert(AbstractRotation{TS}, R)) end """ @@ -44,7 +45,9 @@ struct Rotation{T} <: AbstractRotation{T} end convert(::Type{T}, r::T) where {T<:AbstractRotation} = r -convert(::Type{T}, r::AbstractRotation) where {T<:AbstractRotation} = T(r) +convert(::Type{T}, r::AbstractRotation) where {T<:AbstractRotation} = T(r)::T +convert(::Type{AbstractRotation{T}}, r::AdjointRotation) where {T} = convert(AbstractRotation{T}, r.R)' +convert(::Type{AbstractRotation{T}}, r::AdjointRotation{T}) where {T} = r Givens(i1, i2, c, s) = Givens(i1, i2, promote(c, s)...) Givens{T}(G::Givens{T}) where {T} = G @@ -55,12 +58,11 @@ AbstractRotation{T}(G::Givens) where {T} = Givens{T}(G) AbstractRotation{T}(R::Rotation) where {T} = Rotation{T}(R) adjoint(G::Givens) = Givens(G.i1, G.i2, G.c', -G.s) -adjoint(R::Rotation) = Adjoint(R) -function Base.copy(aG::Adjoint{<:Any,<:Givens}) - G = aG.parent - return Givens(G.i1, G.i2, conj(G.c), -G.s) -end -Base.copy(aR::Adjoint{<:Any,Rotation{T}}) where {T} = Rotation{T}(reverse!([r' for r in aR.parent.rotations])) +adjoint(R::AbstractRotation) = AdjointRotation(R) +adjoint(adjR::AdjointRotation) = adjR.R + +Base.copy(aR::AdjointRotation{T,Rotation{T}}) where {T} = + Rotation{T}([r' for r in Iterators.reverse(aR.R.rotations)]) floatmin2(::Type{Float32}) = reinterpret(Float32, 0x26000000) floatmin2(::Type{Float64}) = reinterpret(Float64, 0x21a0000000000000) @@ -291,7 +293,7 @@ function givens(f::T, g::T, i1::Integer, i2::Integer) where T c, s, r = givensAlgorithm(f, g) if i1 > i2 s = -conj(s) - i1,i2 = i2,i1 + i1, i2 = i2, i1 end Givens(i1, i2, c, s), r end @@ -311,7 +313,7 @@ B[i2,j] = 0 See also [`LinearAlgebra.Givens`](@ref). """ givens(A::AbstractMatrix, i1::Integer, i2::Integer, j::Integer) = - givens(A[i1,j], A[i2,j],i1,i2) + givens(A[i1,j], A[i2,j], i1, i2) """ @@ -329,9 +331,7 @@ B[i2] = 0 See also [`LinearAlgebra.Givens`](@ref). """ -givens(x::AbstractVector, i1::Integer, i2::Integer) = - givens(x[i1], x[i2], i1, i2) - +givens(x::AbstractVector, i1::Integer, i2::Integer) = givens(x[i1], x[i2], i1, i2) function getindex(G::Givens, i::Integer, j::Integer) if i == j @@ -380,29 +380,49 @@ function lmul!(G::Givens, R::Rotation) push!(R.rotations, G) return R end -function lmul!(R::Rotation, A::AbstractMatrix) - @inbounds for i = 1:length(R.rotations) +function rmul!(R::Rotation, G::Givens) + pushfirst!(R.rotations, G) + return R +end + +function lmul!(R::Rotation, A::AbstractVecOrMat) + @inbounds for i in eachindex(R.rotations) lmul!(R.rotations[i], A) end return A end -function rmul!(A::AbstractMatrix, adjR::Adjoint{<:Any,<:Rotation}) - R = adjR.parent - @inbounds for i = 1:length(R.rotations) +function rmul!(A::AbstractMatrix, R::Rotation) + @inbounds for i in eachindex(R.rotations) + rmul!(A, R.rotations[i]) + end + return A +end + +function lmul!(adjR::AdjointRotation{<:Any,<:Rotation}, A::AbstractVecOrMat) + R = adjR.R + @inbounds for i in eachindex(R.rotations) + lmul!(adjoint(R.rotations[i]), A) + end + return A +end +function rmul!(A::AbstractMatrix, adjR::AdjointRotation{<:Any,<:Rotation}) + R = adjR.R + @inbounds for i in eachindex(R.rotations) rmul!(A, adjoint(R.rotations[i])) end return A end -*(G1::Givens{T}, G2::Givens{T}) where {T} = Rotation(push!(push!(Givens{T}[], G2), G1)) - -# TODO: None of the following disambiguation methods are great. They should perhaps -# instead be MethodErrors, or revised. -# -# disambiguation methods: *(Adj/Trans of AbsVec or AbsMat, Adj of AbstractRotation) -*(A::Adjoint{<:Any,<:AbstractVector}, B::Adjoint{<:Any,<:AbstractRotation}) = copy(A) * B -*(A::Adjoint{<:Any,<:AbstractMatrix}, B::Adjoint{<:Any,<:AbstractRotation}) = copy(A) * B -*(A::Transpose{<:Any,<:AbstractVector}, B::Adjoint{<:Any,<:AbstractRotation}) = copy(A) * B -*(A::Transpose{<:Any,<:AbstractMatrix}, B::Adjoint{<:Any,<:AbstractRotation}) = copy(A) * B -# disambiguation methods: *(Diag/AbsTri, Adj of AbstractRotation) -*(A::Diagonal, B::Adjoint{<:Any,<:AbstractRotation}) = A * copy(B) -*(A::AbstractTriangular, B::Adjoint{<:Any,<:AbstractRotation}) = A * copy(B) + +function *(G1::Givens{S}, G2::Givens{T}) where {S,T} + TS = promote_type(T, S) + Rotation{TS}([convert(AbstractRotation{TS}, G2), convert(AbstractRotation{TS}, G1)]) +end +*(G::Givens{T}...) where {T} = Rotation([reverse(G)...]) +function *(G::Givens{S}, R::Rotation{T}) where {S,T} + TS = promote_type(T, S) + Rotation(vcat(convert(AbstractRotation{TS}, R).rotations, convert(AbstractRotation{TS}, G))) +end +function *(R::Rotation{S}, G::Givens{T}) where {S,T} + TS = promote_type(T, S) + Rotation(vcat(convert(AbstractRotation{TS}, G), convert(AbstractRotation{TS}, R).rotations)) +end diff --git a/stdlib/LinearAlgebra/src/hessenberg.jl b/stdlib/LinearAlgebra/src/hessenberg.jl index 794e953cfb704..75b3e121f9086 100644 --- a/stdlib/LinearAlgebra/src/hessenberg.jl +++ b/stdlib/LinearAlgebra/src/hessenberg.jl @@ -9,6 +9,9 @@ Construct an `UpperHessenberg` view of the matrix `A`. Entries of `A` below the first subdiagonal are ignored. +!!! compat "Julia 1.3" + This type was added in Julia 1.3. + Efficient algorithms are implemented for `H \\ b`, `det(H)`, and similar. See also the [`hessenberg`](@ref) function to factor any matrix into a similar @@ -67,6 +70,11 @@ real(H::UpperHessenberg{<:Real}) = H real(H::UpperHessenberg{<:Complex}) = UpperHessenberg(triu!(real(H.data),-1)) imag(H::UpperHessenberg) = UpperHessenberg(triu!(imag(H.data),-1)) +function istriu(A::UpperHessenberg, k::Integer=0) + k <= -1 && return true + return _istriu(A, k) +end + function Matrix{T}(H::UpperHessenberg) where T m,n = size(H) return triu!(copyto!(Matrix{T}(undef, m, n), H.data), -1) @@ -121,71 +129,32 @@ for T = (:Number, :UniformScaling, :Diagonal) end function *(H::UpperHessenberg, U::UpperOrUnitUpperTriangular) - T = typeof(oneunit(eltype(H))*oneunit(eltype(U))) - HH = copy_similar(H, T) - rmul!(HH, U) + HH = _mulmattri!(_initarray(*, eltype(H), eltype(U), H), H, U) UpperHessenberg(HH) end function *(U::UpperOrUnitUpperTriangular, H::UpperHessenberg) - T = typeof(oneunit(eltype(H))*oneunit(eltype(U))) - HH = copy_similar(H, T) - lmul!(U, HH) + HH = _multrimat!(_initarray(*, eltype(U), eltype(H), H), U, H) UpperHessenberg(HH) end function /(H::UpperHessenberg, U::UpperTriangular) - T = typeof(oneunit(eltype(H))/oneunit(eltype(U))) - HH = copy_similar(H, T) - rdiv!(HH, U) + HH = _rdiv!(_initarray(/, eltype(H), eltype(U), H), H, U) UpperHessenberg(HH) end function /(H::UpperHessenberg, U::UnitUpperTriangular) - T = typeof(oneunit(eltype(H))/oneunit(eltype(U))) - HH = copy_similar(H, T) - rdiv!(HH, U) + HH = _rdiv!(_initarray(/, eltype(H), eltype(U), H), H, U) UpperHessenberg(HH) end function \(U::UpperTriangular, H::UpperHessenberg) - T = typeof(oneunit(eltype(U))\oneunit(eltype(H))) - HH = copy_similar(H, T) - ldiv!(U, HH) + HH = ldiv!(_initarray(\, eltype(U), eltype(H), H), U, H) UpperHessenberg(HH) end function \(U::UnitUpperTriangular, H::UpperHessenberg) - T = typeof(oneunit(eltype(U))\oneunit(eltype(H))) - HH = copy_similar(H, T) - ldiv!(U, HH) + HH = ldiv!(_initarray(\, eltype(U), eltype(H), H), U, H) UpperHessenberg(HH) end -function *(H::UpperHessenberg, B::Bidiagonal) - TS = promote_op(matprod, eltype(H), eltype(B)) - A = A_mul_B_td!(zeros(TS, size(H)), H, B) - return B.uplo == 'U' ? UpperHessenberg(A) : A -end -function *(B::Bidiagonal, H::UpperHessenberg) - TS = promote_op(matprod, eltype(B), eltype(H)) - A = A_mul_B_td!(zeros(TS, size(H)), B, H) - return B.uplo == 'U' ? UpperHessenberg(A) : A -end - -/(H::UpperHessenberg, B::Bidiagonal) = _rdiv(H, B) -/(H::UpperHessenberg{<:Number}, B::Bidiagonal{<:Number}) = _rdiv(H, B) -function _rdiv(H::UpperHessenberg, B::Bidiagonal) - T = typeof(oneunit(eltype(H))/oneunit(eltype(B))) - A = _rdiv!(zeros(T, size(H)), H, B) - return B.uplo == 'U' ? UpperHessenberg(A) : A -end - -\(B::Bidiagonal{<:Number}, H::UpperHessenberg{<:Number}) = _ldiv(B, H) -\(B::Bidiagonal, H::UpperHessenberg) = _ldiv(B, H) -function _ldiv(B::Bidiagonal, H::UpperHessenberg) - T = typeof(oneunit(eltype(B))\oneunit(eltype(H))) - A = ldiv!(zeros(T, size(H)), B, H) - return B.uplo == 'U' ? UpperHessenberg(A) : A -end - # Solving (H+µI)x = b: we can do this in O(m²) time and O(m) memory # (in-place in x) by the RQ algorithm from: # @@ -418,10 +387,12 @@ Hessenberg(F::Hessenberg, μ::Number) = Hessenberg(F.factors, F.τ, F.H, F.uplo; copy(F::Hessenberg{<:Any,<:UpperHessenberg}) = Hessenberg(copy(F.factors), copy(F.τ); μ=F.μ) copy(F::Hessenberg{<:Any,<:SymTridiagonal}) = Hessenberg(copy(F.factors), copy(F.τ), copy(F.H), F.uplo; μ=F.μ) -size(F::Hessenberg, d) = size(F.H, d) +size(F::Hessenberg, d::Integer) = size(F.H, d) size(F::Hessenberg) = size(F.H) -adjoint(F::Hessenberg) = Adjoint(F) +transpose(F::Hessenberg{<:Real}) = F' +transpose(::Hessenberg) = + throw(ArgumentError("transpose of Hessenberg decomposition is not supported, consider using adjoint")) # iteration for destructuring into components Base.iterate(S::Hessenberg) = (S.Q, Val(:H)) @@ -431,7 +402,7 @@ Base.iterate(S::Hessenberg, ::Val{:done}) = nothing hessenberg!(A::StridedMatrix{<:BlasFloat}) = Hessenberg(LAPACK.gehrd!(A)...) -function hessenberg!(A::Union{Symmetric{<:BlasReal},Hermitian{<:BlasFloat}}) +function hessenberg!(A::Union{Symmetric{<:BlasReal,<:StridedMatrix},Hermitian{<:BlasFloat,<:StridedMatrix}}) factors, τ, d, e = LAPACK.hetrd!(A.uplo, A.data) return Hessenberg(factors, τ, SymTridiagonal(d, e), A.uplo) end @@ -476,10 +447,7 @@ julia> A = [4. 9. 7.; 4. 4. 1.; 4. 3. 2.] julia> F = hessenberg(A) Hessenberg{Float64, UpperHessenberg{Float64, Matrix{Float64}}, Matrix{Float64}, Vector{Float64}, Bool} Q factor: -3×3 LinearAlgebra.HessenbergQ{Float64, Matrix{Float64}, Vector{Float64}, false}: - 1.0 0.0 0.0 - 0.0 -0.707107 -0.707107 - 0.0 -0.707107 0.707107 +3×3 LinearAlgebra.HessenbergQ{Float64, Matrix{Float64}, Vector{Float64}, false} H factor: 3×3 UpperHessenberg{Float64, Matrix{Float64}}: 4.0 -11.3137 -1.41421 @@ -499,7 +467,7 @@ true ``` """ hessenberg(A::AbstractMatrix{T}) where T = - hessenberg!(copy_oftype(A, eigtype(T))) + hessenberg!(eigencopy_oftype(A, eigtype(T))) function show(io::IO, mime::MIME"text/plain", F::Hessenberg) summary(io, F) @@ -512,26 +480,6 @@ function show(io::IO, mime::MIME"text/plain", F::Hessenberg) show(io, mime, F.H) end -""" - HessenbergQ <: AbstractQ - -Given a [`Hessenberg`](@ref) factorization object `F`, `F.Q` returns -a `HessenbergQ` object, which is an implicit representation of the unitary -matrix `Q` in the Hessenberg factorization `QHQ'` represented by `F`. -This `F.Q` object can be efficiently multiplied by matrices or vectors, -and can be converted to an ordinary matrix type with `Matrix(F.Q)`. -""" -struct HessenbergQ{T,S<:AbstractMatrix,W<:AbstractVector,sym} <: AbstractQ{T} - uplo::Char - factors::S - τ::W - function HessenbergQ{T,S,W,sym}(uplo::AbstractChar, factors, τ) where {T,S<:AbstractMatrix,W<:AbstractVector,sym} - new(uplo, factors, τ) - end -end -HessenbergQ(F::Hessenberg{<:Any,<:UpperHessenberg,S,W}) where {S,W} = HessenbergQ{eltype(F.factors),S,W,false}(F.uplo, F.factors, F.τ) -HessenbergQ(F::Hessenberg{<:Any,<:SymTridiagonal,S,W}) where {S,W} = HessenbergQ{eltype(F.factors),S,W,true}(F.uplo, F.factors, F.τ) - function getproperty(F::Hessenberg, d::Symbol) d === :Q && return HessenbergQ(F) return getfield(F, d) @@ -540,12 +488,6 @@ end Base.propertynames(F::Hessenberg, private::Bool=false) = (:Q, :H, :μ, (private ? (:τ, :factors, :uplo) : ())...) -# HessenbergQ from LAPACK/BLAS (as opposed to Julia libraries like GenericLinearAlgebra) -const BlasHessenbergQ{T,sym} = HessenbergQ{T,<:StridedMatrix{T},<:StridedVector{T},sym} where {T<:BlasFloat,sym} - -## reconstruct the original matrix -Matrix{T}(Q::BlasHessenbergQ{<:Any,false}) where {T} = convert(Matrix{T}, LAPACK.orghr!(1, size(Q.factors, 1), copy(Q.factors), Q.τ)) -Matrix{T}(Q::BlasHessenbergQ{<:Any,true}) where {T} = convert(Matrix{T}, LAPACK.orgtr!(Q.uplo, copy(Q.factors), Q.τ)) AbstractArray(F::Hessenberg) = AbstractMatrix(F) Matrix(F::Hessenberg) = Array(AbstractArray(F)) Array(F::Hessenberg) = Matrix(F) @@ -565,31 +507,6 @@ function AbstractMatrix(F::Hessenberg) end end -# adjoint(Q::HessenbergQ{<:Real}) - -lmul!(Q::BlasHessenbergQ{T,false}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = - LAPACK.ormhr!('L', 'N', 1, size(Q.factors, 1), Q.factors, Q.τ, X) -rmul!(X::StridedVecOrMat{T}, Q::BlasHessenbergQ{T,false}) where {T<:BlasFloat} = - LAPACK.ormhr!('R', 'N', 1, size(Q.factors, 1), Q.factors, Q.τ, X) -lmul!(adjQ::Adjoint{<:Any,<:BlasHessenbergQ{T,false}}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = - (Q = adjQ.parent; LAPACK.ormhr!('L', ifelse(T<:Real, 'T', 'C'), 1, size(Q.factors, 1), Q.factors, Q.τ, X)) -rmul!(X::StridedVecOrMat{T}, adjQ::Adjoint{<:Any,<:BlasHessenbergQ{T,false}}) where {T<:BlasFloat} = - (Q = adjQ.parent; LAPACK.ormhr!('R', ifelse(T<:Real, 'T', 'C'), 1, size(Q.factors, 1), Q.factors, Q.τ, X)) - -lmul!(Q::BlasHessenbergQ{T,true}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = - LAPACK.ormtr!('L', Q.uplo, 'N', Q.factors, Q.τ, X) -rmul!(X::StridedVecOrMat{T}, Q::BlasHessenbergQ{T,true}) where {T<:BlasFloat} = - LAPACK.ormtr!('R', Q.uplo, 'N', Q.factors, Q.τ, X) -lmul!(adjQ::Adjoint{<:Any,<:BlasHessenbergQ{T,true}}, X::StridedVecOrMat{T}) where {T<:BlasFloat} = - (Q = adjQ.parent; LAPACK.ormtr!('L', Q.uplo, ifelse(T<:Real, 'T', 'C'), Q.factors, Q.τ, X)) -rmul!(X::StridedVecOrMat{T}, adjQ::Adjoint{<:Any,<:BlasHessenbergQ{T,true}}) where {T<:BlasFloat} = - (Q = adjQ.parent; LAPACK.ormtr!('R', Q.uplo, ifelse(T<:Real, 'T', 'C'), Q.factors, Q.τ, X)) - -lmul!(Q::HessenbergQ{T}, X::Adjoint{T,<:StridedVecOrMat{T}}) where {T} = rmul!(X', Q')' -rmul!(X::Adjoint{T,<:StridedVecOrMat{T}}, Q::HessenbergQ{T}) where {T} = lmul!(Q', X')' -lmul!(adjQ::Adjoint{<:Any,<:HessenbergQ{T}}, X::Adjoint{T,<:StridedVecOrMat{T}}) where {T} = rmul!(X', adjQ')' -rmul!(X::Adjoint{T,<:StridedVecOrMat{T}}, adjQ::Adjoint{<:Any,<:HessenbergQ{T}}) where {T} = lmul!(adjQ', X')' - # multiply x by the entries of M in the upper-k triangle, which contains # the entries of the upper-Hessenberg matrix H for k=-1 function rmul_triu!(M::AbstractMatrix, x, k::Integer=0) @@ -680,8 +597,8 @@ function rdiv!(B::AbstractVecOrMat{<:Complex}, F::Hessenberg{<:Complex,<:Any,<:A return B .= Complex.(Br,Bi) end -ldiv!(F::Adjoint{<:Any,<:Hessenberg}, B::AbstractVecOrMat) = rdiv!(B', F')' -rdiv!(B::AbstractMatrix, F::Adjoint{<:Any,<:Hessenberg}) = ldiv!(F', B')' +ldiv!(F::AdjointFactorization{<:Any,<:Hessenberg}, B::AbstractVecOrMat) = rdiv!(B', F')' +rdiv!(B::AbstractMatrix, F::AdjointFactorization{<:Any,<:Hessenberg}) = ldiv!(F', B')' det(F::Hessenberg) = det(F.H; shift=F.μ) logabsdet(F::Hessenberg) = logabsdet(F.H; shift=F.μ) diff --git a/stdlib/LinearAlgebra/src/lapack.jl b/stdlib/LinearAlgebra/src/lapack.jl index 0aa8f1689f23c..066a858cacb30 100644 --- a/stdlib/LinearAlgebra/src/lapack.jl +++ b/stdlib/LinearAlgebra/src/lapack.jl @@ -5,20 +5,19 @@ module LAPACK Interfaces to LAPACK subroutines. """ LAPACK -const libblastrampoline = "libblastrampoline" - -# Legacy binding maintained for backwards-compatibility but new packages -# should not look at this, instead preferring to parse the output -# of BLAS.get_config() -const liblapack = libblastrampoline - using ..LinearAlgebra.BLAS: @blasfunc, chkuplo -using ..LinearAlgebra: BlasFloat, BlasInt, LAPACKException, DimensionMismatch, +using ..LinearAlgebra: libblastrampoline, BlasFloat, BlasInt, LAPACKException, DimensionMismatch, SingularException, PosDefException, chkstride1, checksquare,triu, tril, dot using Base: iszero, require_one_based_indexing + +# Legacy binding maintained for backwards-compatibility but new packages +# should not look at this, instead preferring to parse the output +# of BLAS.get_config() +const liblapack = libblastrampoline + #Generic LAPACK error handlers """ Handle only negative LAPACK error codes @@ -557,6 +556,7 @@ for (gebrd, gelqf, geqlf, geqrf, geqp3, geqrt, geqrt3, gerqf, getrf, elty, relty # DOUBLE PRECISION A( LDA, * ) function getrf!(A::AbstractMatrix{$elty}) require_one_based_indexing(A) + chkfinite(A) chkstride1(A) m, n = size(A) lda = max(1,stride(A, 2)) @@ -1010,6 +1010,9 @@ for (gels, gesv, getrs, getri, elty) in if n != size(B, 1) throw(DimensionMismatch("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")) + end nrhs = size(B, 2) info = Ref{BlasInt}() ccall((@blasfunc($getrs), libblastrampoline), Cvoid, @@ -2023,9 +2026,9 @@ the orthogonal/unitary matrix `Q` is computed. If `jobu`, `jobv`, or `jobq` is ggsvd3! ## Expert driver and generalized eigenvalue problem -for (geevx, ggev, elty) in - ((:dgeevx_,:dggev_,:Float64), - (:sgeevx_,:sggev_,:Float32)) +for (geevx, ggev, ggev3, elty) in + ((:dgeevx_,:dggev_,:dggev3_,:Float64), + (:sgeevx_,:sggev_,:sggev3_,:Float32)) @eval begin # SUBROUTINE DGEEVX( BALANC, JOBVL, JOBVR, SENSE, N, A, LDA, WR, WI, # VL, LDVL, VR, LDVR, ILO, IHI, SCALE, ABNRM, @@ -2093,7 +2096,7 @@ for (geevx, ggev, elty) in Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{BlasInt}, Ptr{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, - Ref{BlasInt}, Ptr{BlasInt}, Ptr{BlasInt}, + Ref{BlasInt}, Ptr{BlasInt}, Ref{BlasInt}, Clong, Clong, Clong, Clong), balanc, jobvl, jobvr, sense, n, A, lda, wr, @@ -2160,7 +2163,71 @@ for (geevx, ggev, elty) in Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, - Ptr{BlasInt}, Clong, Clong), + Ref{BlasInt}, Clong, Clong), + jobvl, jobvr, n, A, + lda, B, ldb, alphar, + alphai, beta, vl, ldvl, + vr, ldvr, work, lwork, + info, 1, 1) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(work[1]) + resize!(work, lwork) + end + end + alphar, alphai, beta, vl, vr + end + + # SUBROUTINE DGGEV3( JOBVL, JOBVR, N, A, LDA, B, LDB, ALPHAR, ALPHAI, + # $ BETA, VL, LDVL, VR, LDVR, WORK, LWORK, INFO ) + # * .. Scalar Arguments .. + # CHARACTER JOBVL, JOBVR + # INTEGER INFO, LDA, LDB, LDVL, LDVR, LWORK, N + # * .. + # * .. Array Arguments .. + # DOUBLE PRECISION A( LDA, * ), ALPHAI( * ), ALPHAR( * ), + # $ B( LDB, * ), BETA( * ), VL( LDVL, * ), + # $ VR( LDVR, * ), WORK( * ) + function ggev3!(jobvl::AbstractChar, jobvr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + 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")) + 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")) + 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")) + end + vr = similar(A, $elty, ldvr, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($ggev3), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ref{BlasInt}, Clong, Clong), jobvl, jobvr, n, A, lda, B, ldb, alphar, alphai, beta, vl, ldvl, @@ -2177,9 +2244,9 @@ for (geevx, ggev, elty) in end end -for (geevx, ggev, elty, relty) in - ((:zgeevx_,:zggev_,:ComplexF64,:Float64), - (:cgeevx_,:cggev_,:ComplexF32,:Float32)) +for (geevx, ggev, ggev3, elty, relty) in + ((:zgeevx_,:zggev_,:zggev3_,:ComplexF64,:Float64), + (:cgeevx_,:cggev_,:cggev3_,:ComplexF32,:Float32)) @eval begin # SUBROUTINE ZGEEVX( BALANC, JOBVL, JOBVR, SENSE, N, A, LDA, W, VL, # LDVL, VR, LDVR, ILO, IHI, SCALE, ABNRM, RCONDE, @@ -2241,7 +2308,7 @@ for (geevx, ggev, elty, relty) in Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{BlasInt}, Ptr{BlasInt}, Ptr{$relty}, Ptr{$relty}, Ptr{$relty}, Ptr{$relty}, Ptr{$elty}, Ref{BlasInt}, - Ptr{$relty}, Ptr{BlasInt}, Clong, Clong, Clong, Clong), + Ptr{$relty}, Ref{BlasInt}, Clong, Clong, Clong, Clong), balanc, jobvl, jobvr, sense, n, A, lda, w, VL, max(1,ldvl), VR, max(1,ldvr), @@ -2307,7 +2374,72 @@ for (geevx, ggev, elty, relty) in Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$relty}, - Ptr{BlasInt}, Clong, Clong), + Ref{BlasInt}, Clong, Clong), + jobvl, jobvr, n, A, + lda, B, ldb, alpha, + beta, vl, ldvl, vr, + ldvr, work, lwork, rwork, + info, 1, 1) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(work[1]) + resize!(work, lwork) + end + end + alpha, beta, vl, vr + end + + # SUBROUTINE ZGGEV3( JOBVL, JOBVR, N, A, LDA, B, LDB, ALPHA, BETA, + # $ VL, LDVL, VR, LDVR, WORK, LWORK, RWORK, INFO ) + # * .. Scalar Arguments .. + # CHARACTER JOBVL, JOBVR + # INTEGER INFO, LDA, LDB, LDVL, LDVR, LWORK, N + # * .. + # * .. Array Arguments .. + # DOUBLE PRECISION RWORK( * ) + # COMPLEX*16 A( LDA, * ), ALPHA( * ), B( LDB, * ), + # $ BETA( * ), VL( LDVL, * ), VR( LDVR, * ), + # $ WORK( * ) + function ggev3!(jobvl::AbstractChar, jobvr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + require_one_based_indexing(A, B) + 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")) + 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")) + 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")) + end + vr = similar(A, $elty, ldvr, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + rwork = Vector{$relty}(undef, 8n) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($ggev3), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$relty}, + Ref{BlasInt}, Clong, Clong), jobvl, jobvr, n, A, lda, B, ldb, alpha, beta, vl, ldvl, vr, @@ -2353,6 +2485,17 @@ corresponding eigenvectors are computed. """ ggev!(jobvl::AbstractChar, jobvr::AbstractChar, A::AbstractMatrix, B::AbstractMatrix) +""" + ggev3!(jobvl, jobvr, A, B) -> (alpha, beta, vl, vr) + +Finds the generalized eigendecomposition of `A` and `B` using a blocked +algorithm. If `jobvl = N`, the left eigenvectors aren't computed. If +`jobvr = N`, the right eigenvectors aren't computed. If `jobvl = V` or +`jobvr = V`, the corresponding eigenvectors are computed. This function +requires LAPACK 3.6.0. +""" +ggev3!(jobvl::AbstractChar, jobvr::AbstractChar, A::AbstractMatrix, B::AbstractMatrix) + # One step incremental condition estimation of max/min singular values for (laic1, elty) in ((:dlaic1_,:Float64), @@ -5030,9 +5173,9 @@ solution `X`. hetrs!(uplo::AbstractChar, A::AbstractMatrix, ipiv::AbstractVector{BlasInt}, B::AbstractVecOrMat) # Symmetric (real) eigensolvers -for (syev, syevr, sygvd, elty) in - ((:dsyev_,:dsyevr_,:dsygvd_,:Float64), - (:ssyev_,:ssyevr_,:ssygvd_,:Float32)) +for (syev, syevr, syevd, sygvd, elty) in + ((:dsyev_,:dsyevr_,:dsyevd_,:dsygvd_,:Float64), + (:ssyev_,:ssyevr_,:ssyevd_,:ssygvd_,:Float32)) @eval begin # SUBROUTINE DSYEV( JOBZ, UPLO, N, A, LDA, W, WORK, LWORK, INFO ) # * .. Scalar Arguments .. @@ -5085,7 +5228,7 @@ for (syev, syevr, sygvd, elty) in end lda = stride(A,2) m = Ref{BlasInt}() - w = similar(A, $elty, n) + W = similar(A, $elty, n) ldz = n if jobz == 'N' Z = similar(A, $elty, ldz, 0) @@ -5109,7 +5252,7 @@ for (syev, syevr, sygvd, elty) in jobz, range, uplo, n, A, max(1,lda), vl, vu, il, iu, abstol, m, - w, Z, max(1,ldz), isuppz, + W, Z, max(1,ldz), isuppz, work, lwork, iwork, liwork, info, 1, 1, 1) chklapackerror(info[]) @@ -5120,11 +5263,51 @@ for (syev, syevr, sygvd, elty) in resize!(iwork, liwork) end end - w[1:m[]], Z[:,1:(jobz == 'V' ? m[] : 0)] + W[1:m[]], Z[:,1:(jobz == 'V' ? m[] : 0)] end syevr!(jobz::AbstractChar, A::AbstractMatrix{$elty}) = syevr!(jobz, 'A', 'U', A, 0.0, 0.0, 0, 0, -1.0) + # SUBROUTINE DSYEVD( JOBZ, UPLO, N, A, LDA, W, WORK, LWORK, + # $ IWORK, LIWORK, INFO ) + # * .. Scalar Arguments .. + # CHARACTER JOBZ, UPLO + # INTEGER INFO, LDA, LIWORK, LWORK, N + # * .. + # * .. Array Arguments .. + # INTEGER IWORK( * ) + # DOUBLE PRECISION A( LDA, * ), W( * ), WORK( * ) + function syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) + chkstride1(A) + n = checksquare(A) + chkuplofinite(A, uplo) + lda = stride(A,2) + m = Ref{BlasInt}() + W = similar(A, $elty, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + iwork = Vector{BlasInt}(undef, 1) + liwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] and liwork as iwork[1] + ccall((@blasfunc($syevd), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{BlasInt}, Ref{BlasInt}, + Ptr{BlasInt}, Clong, Clong), + jobz, uplo, n, A, max(1,lda), + W, work, lwork, iwork, liwork, + info, 1, 1) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + liwork = iwork[1] + resize!(iwork, liwork) + end + end + jobz == 'V' ? (W, A) : W + end + # Generalized eigenproblem # SUBROUTINE DSYGVD( ITYPE, JOBZ, UPLO, N, A, LDA, B, LDB, W, WORK, # $ LWORK, IWORK, LIWORK, INFO ) @@ -5173,9 +5356,9 @@ for (syev, syevr, sygvd, elty) in end end # Hermitian eigensolvers -for (syev, syevr, sygvd, elty, relty) in - ((:zheev_,:zheevr_,:zhegvd_,:ComplexF64,:Float64), - (:cheev_,:cheevr_,:chegvd_,:ComplexF32,:Float32)) +for (syev, syevr, syevd, sygvd, elty, relty) in + ((:zheev_,:zheevr_,:zheevd_,:zhegvd_,:ComplexF64,:Float64), + (:cheev_,:cheevr_,:cheevd_,:chegvd_,:ComplexF32,:Float32)) @eval begin # SUBROUTINE ZHEEV( JOBZ, UPLO, N, A, LDA, W, WORK, LWORK, RWORK, INFO ) # * .. Scalar Arguments .. @@ -5187,6 +5370,7 @@ for (syev, syevr, sygvd, elty, relty) in # COMPLEX*16 A( LDA, * ), WORK( * ) function syev!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) chkstride1(A) + chkuplofinite(A, uplo) n = checksquare(A) W = similar(A, $relty, n) work = Vector{$elty}(undef, 1) @@ -5225,6 +5409,7 @@ for (syev, syevr, sygvd, elty, relty) in function syevr!(jobz::AbstractChar, range::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}, vl::AbstractFloat, vu::AbstractFloat, il::Integer, iu::Integer, abstol::AbstractFloat) 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")) @@ -5234,7 +5419,7 @@ for (syev, syevr, sygvd, elty, relty) in end lda = max(1,stride(A,2)) m = Ref{BlasInt}() - w = similar(A, $relty, n) + W = similar(A, $relty, n) if jobz == 'N' ldz = 1 Z = similar(A, $elty, ldz, 0) @@ -5262,7 +5447,7 @@ for (syev, syevr, sygvd, elty, relty) in jobz, range, uplo, n, A, lda, vl, vu, il, iu, abstol, m, - w, Z, ldz, isuppz, + W, Z, ldz, isuppz, work, lwork, rwork, lrwork, iwork, liwork, info, 1, 1, 1) @@ -5276,11 +5461,56 @@ for (syev, syevr, sygvd, elty, relty) in resize!(iwork, liwork) end end - w[1:m[]], Z[:,1:(jobz == 'V' ? m[] : 0)] + W[1:m[]], Z[:,1:(jobz == 'V' ? m[] : 0)] end syevr!(jobz::AbstractChar, A::AbstractMatrix{$elty}) = syevr!(jobz, 'A', 'U', A, 0.0, 0.0, 0, 0, -1.0) + # SUBROUTINE ZHEEVD( JOBZ, UPLO, N, A, LDA, W, WORK, LWORK, RWORK, + # $ LRWORK, IWORK, LIWORK, INFO ) + # * .. Scalar Arguments .. + # CHARACTER JOBZ, UPLO + # INTEGER INFO, LDA, LIWORK, LRWORK, LWORK, N + # * .. + # * .. Array Arguments .. + # INTEGER IWORK( * ) + # DOUBLE PRECISION RWORK( * ) + # COMPLEX*16 A( LDA, * ), WORK( * ) + function syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}) + chkstride1(A) + chkuplofinite(A, uplo) + n = checksquare(A) + lda = max(1, stride(A,2)) + m = Ref{BlasInt}() + W = similar(A, $relty, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + rwork = Vector{$relty}(undef, 1) + lrwork = BlasInt(-1) + iwork = Vector{BlasInt}(undef, 1) + liwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1], lrwork as rwork[1] and liwork as iwork[1] + ccall((@blasfunc($syevd), liblapack), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{$relty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$relty}, Ref{BlasInt}, + Ptr{BlasInt}, Ref{BlasInt}, Ptr{BlasInt}, Clong, Clong), + jobz, uplo, n, A, stride(A,2), + W, work, lwork, rwork, lrwork, + iwork, liwork, info, 1, 1) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + lrwork = BlasInt(rwork[1]) + resize!(rwork, lrwork) + liwork = iwork[1] + resize!(iwork, liwork) + end + end + jobz == 'V' ? (W, A) : W + end + # SUBROUTINE ZHEGVD( ITYPE, JOBZ, UPLO, N, A, LDA, B, LDB, W, WORK, # $ LWORK, RWORK, LRWORK, IWORK, LIWORK, INFO ) # * .. Scalar Arguments .. @@ -5293,6 +5523,8 @@ for (syev, syevr, sygvd, elty, relty) in # COMPLEX*16 A( LDA, * ), B( LDB, * ), WORK( * ) function sygvd!(itype::Integer, jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) 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")) @@ -5360,6 +5592,20 @@ The eigenvalues are returned in `W` and the eigenvectors in `Z`. syevr!(jobz::AbstractChar, range::AbstractChar, uplo::AbstractChar, A::AbstractMatrix, vl::AbstractFloat, vu::AbstractFloat, il::Integer, iu::Integer, abstol::AbstractFloat) +""" + syevd!(jobz, uplo, A) + +Finds the eigenvalues (`jobz = N`) or eigenvalues and eigenvectors +(`jobz = V`) of a symmetric matrix `A`. If `uplo = U`, the upper triangle +of `A` is used. If `uplo = L`, the lower triangle of `A` is used. + +Use the divide-and-conquer method, instead of the QR iteration used by +`syev!` or multiple relatively robust representations used by `syevr!`. +See James W. Demmel et al, SIAM J. Sci. Comput. 30, 3, 1508 (2008) for +a comparison of the accuracy and performatce of different methods. +""" +syevd!(jobz::AbstractChar, uplo::AbstractChar, A::AbstractMatrix) + """ sygvd!(itype, jobz, uplo, A, B) -> (w, A, B) @@ -5462,7 +5708,7 @@ for (bdsdc, elty) in elseif compq == 'P' @warn "COMPQ='P' is not tested" #TODO turn this into an actual LAPACK call - #smlsiz=ilaenv(9, $elty==:Float64 ? 'dbdsqr' : 'sbdsqr', string(uplo, compq), n,n,n,n) + #smlsiz=ilaenv(9, $elty === :Float64 ? 'dbdsqr' : 'sbdsqr', string(uplo, compq), n,n,n,n) smlsiz=100 #For now, completely overkill ldq = n*(11+2*smlsiz+8*round(Int,log((n/(smlsiz+1)))/log(2))) ldiq = n*(3+3*round(Int,log(n/(smlsiz+1))/log(2))) @@ -5737,6 +5983,104 @@ for (ormhr, elty) in end end +for (hseqr, elty) in + ((:zhseqr_,:ComplexF64), + (:chseqr_,:ComplexF32)) + @eval begin + # * .. Scalar Arguments .. + # CHARACTER JOB, COMPZ + # INTEGER N, ILO, IHI, LWORK, LDH, LDZ, INFO + # * .. + # * .. Array Arguments .. + # COMPLEX*16 H( LDH, * ), Z( LDZ, * ), WORK( * ) + function hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, + H::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) + require_one_based_indexing(H, Z) + chkstride1(H) + n = checksquare(H) + checksquare(Z) == n || throw(DimensionMismatch()) + ldh = max(1, stride(H, 2)) + ldz = max(1, stride(Z, 2)) + w = similar(H, $elty, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($hseqr), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{BlasInt}), + job, compz, n, ilo, ihi, + H, ldh, w, Z, ldz, work, + lwork, info) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + end + end + H, Z, w + end + end +end + +for (hseqr, elty) in + ((:dhseqr_,:Float64), + (:shseqr_,:Float32)) + @eval begin + # * .. Scalar Arguments .. + # CHARACTER JOB, COMPZ + # INTEGER N, ILO, IHI, LWORK, LDH, LDZ, INFO + # * .. + # * .. Array Arguments .. + # COMPLEX*16 H( LDH, * ), Z( LDZ, * ), WORK( * ) + function hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, + H::AbstractMatrix{$elty}, Z::AbstractMatrix{$elty}) + require_one_based_indexing(H, Z) + chkstride1(H) + n = checksquare(H) + checksquare(Z) == n || throw(DimensionMismatch()) + ldh = max(1, stride(H, 2)) + ldz = max(1, stride(Z, 2)) + wr = similar(H, $elty, n) + wi = similar(H, $elty, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($hseqr), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{BlasInt}, Ref{BlasInt}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{BlasInt}), + job, compz, n, ilo, ihi, + H, ldh, wr, wi, Z, ldz, work, + lwork, info) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + end + end + H, Z, complex.(wr, wi) + end + end +end +hseqr!(H::StridedMatrix{T}, Z::StridedMatrix{T}) where {T<:BlasFloat} = hseqr!('S', 'V', 1, size(H, 1), H, Z) +hseqr!(H::StridedMatrix{T}) where {T<:BlasFloat} = hseqr!('S', 'I', 1, size(H, 1), H, similar(H)) + +""" + hseqr!(job, compz, ilo, ihi, H, Z) -> (H, Z, w) + +Computes all eigenvalues and (optionally) the Schur factorization of a matrix +reduced to Hessenberg form. If `H` is balanced with `gebal!` +then `ilo` and `ihi` are the outputs of `gebal!`. Otherwise they should be +`ilo = 1` and `ihi = size(H,2)`. `tau` contains the elementary reflectors of +the factorization. +""" +hseqr!(job::AbstractChar, compz::AbstractChar, ilo::Integer, ihi::Integer, H::AbstractMatrix, Z::AbstractMatrix) + for (hetrd, elty) in ((:dsytrd_,Float64), (:ssytrd_,Float32), @@ -5891,9 +6235,9 @@ for (ormtr, elty) in end end -for (gees, gges, elty) in - ((:dgees_,:dgges_,:Float64), - (:sgees_,:sgges_,:Float32)) +for (gees, gges, gges3, elty) in + ((:dgees_,:dgges_,:dgges3_,:Float64), + (:sgees_,:sgges_,:sgges3_,:Float32)) @eval begin # .. Scalar Arguments .. # CHARACTER JOBVS, SORT @@ -5920,7 +6264,7 @@ for (gees, gges, elty) in (Ref{UInt8}, Ref{UInt8}, Ptr{Cvoid}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, - Ref{BlasInt}, Ptr{Cvoid}, Ptr{BlasInt}, Clong, Clong), + Ref{BlasInt}, Ptr{Cvoid}, Ref{BlasInt}, Clong, Clong), jobvs, 'N', C_NULL, n, A, max(1, stride(A, 2)), sdim, wr, wi, vs, ldvs, work, @@ -5967,7 +6311,56 @@ for (gees, gges, elty) in Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{Cvoid}, - Ptr{BlasInt}, Clong, Clong, Clong), + Ref{BlasInt}, Clong, Clong, Clong), + jobvsl, jobvsr, 'N', C_NULL, + n, A, max(1,stride(A, 2)), B, + max(1,stride(B, 2)), sdim, alphar, alphai, + beta, vsl, ldvsl, vsr, + ldvsr, work, lwork, C_NULL, + info, 1, 1, 1) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + end + end + A, B, complex.(alphar, alphai), beta, vsl[1:(jobvsl == 'V' ? n : 0),:], vsr[1:(jobvsr == 'V' ? n : 0),:] + end + + # * .. Scalar Arguments .. + # CHARACTER JOBVSL, JOBVSR, SORT + # INTEGER INFO, LDA, LDB, LDVSL, LDVSR, LWORK, N, SDIM + # * .. + # * .. Array Arguments .. + # LOGICAL BWORK( * ) + # DOUBLE PRECISION A( LDA, * ), ALPHAI( * ), ALPHAR( * ), + # $ B( LDB, * ), BETA( * ), VSL( LDVSL, * ), + # $ VSR( LDVSR, * ), WORK( * ) + function gges3!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + chkstride1(A, B) + n, m = checksquare(A, B) + if n != m + throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + end + sdim = BlasInt(0) + alphar = similar(A, $elty, n) + alphai = similar(A, $elty, n) + beta = similar(A, $elty, n) + ldvsl = jobvsl == 'V' ? max(1, n) : 1 + vsl = similar(A, $elty, ldvsl, n) + ldvsr = jobvsr == 'V' ? max(1, n) : 1 + vsr = similar(A, $elty, ldvsr, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($gges3), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ptr{Cvoid}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, + Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{Cvoid}, + Ref{BlasInt}, Clong, Clong, Clong), jobvsl, jobvsr, 'N', C_NULL, n, A, max(1,stride(A, 2)), B, max(1,stride(B, 2)), sdim, alphar, alphai, @@ -5985,9 +6378,9 @@ for (gees, gges, elty) in end end -for (gees, gges, elty, relty) in - ((:zgees_,:zgges_,:ComplexF64,:Float64), - (:cgees_,:cgges_,:ComplexF32,:Float32)) +for (gees, gges, gges3, elty, relty) in + ((:zgees_,:zgges_,:zgges3_,:ComplexF64,:Float64), + (:cgees_,:cgges_,:cgges3_,:ComplexF32,:Float32)) @eval begin # * .. Scalar Arguments .. # CHARACTER JOBVS, SORT @@ -6015,7 +6408,7 @@ for (gees, gges, elty, relty) in (Ref{UInt8}, Ref{UInt8}, Ptr{Cvoid}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, - Ptr{$relty}, Ptr{Cvoid}, Ptr{BlasInt}, Clong, Clong), + Ptr{$relty}, Ptr{Cvoid}, Ref{BlasInt}, Clong, Clong), jobvs, sort, C_NULL, n, A, max(1, stride(A, 2)), sdim, w, vs, ldvs, work, lwork, @@ -6063,7 +6456,57 @@ for (gees, gges, elty, relty) in Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$relty}, Ptr{Cvoid}, - Ptr{BlasInt}, Clong, Clong, Clong), + Ref{BlasInt}, Clong, Clong, Clong), + jobvsl, jobvsr, 'N', C_NULL, + n, A, max(1, stride(A, 2)), B, + max(1, stride(B, 2)), sdim, alpha, beta, + vsl, ldvsl, vsr, ldvsr, + work, lwork, rwork, C_NULL, + info, 1, 1, 1) + chklapackerror(info[]) + if i == 1 + lwork = BlasInt(real(work[1])) + resize!(work, lwork) + end + end + A, B, alpha, beta, vsl[1:(jobvsl == 'V' ? n : 0),:], vsr[1:(jobvsr == 'V' ? n : 0),:] + end + + # * .. Scalar Arguments .. + # CHARACTER JOBVSL, JOBVSR, SORT + # INTEGER INFO, LDA, LDB, LDVSL, LDVSR, LWORK, N, SDIM + # * .. + # * .. Array Arguments .. + # LOGICAL BWORK( * ) + # DOUBLE PRECISION RWORK( * ) + # COMPLEX*16 A( LDA, * ), ALPHA( * ), B( LDB, * ), + # $ BETA( * ), VSL( LDVSL, * ), VSR( LDVSR, * ), + # $ WORK( * ) + function gges3!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix{$elty}, B::AbstractMatrix{$elty}) + chkstride1(A, B) + n, m = checksquare(A, B) + if n != m + throw(DimensionMismatch("dimensions of A, ($n,$n), and B, ($m,$m), must match")) + end + sdim = BlasInt(0) + alpha = similar(A, $elty, n) + beta = similar(A, $elty, n) + ldvsl = jobvsl == 'V' ? max(1, n) : 1 + vsl = similar(A, $elty, ldvsl, n) + ldvsr = jobvsr == 'V' ? max(1, n) : 1 + vsr = similar(A, $elty, ldvsr, n) + work = Vector{$elty}(undef, 1) + lwork = BlasInt(-1) + rwork = Vector{$relty}(undef, 8n) + info = Ref{BlasInt}() + for i = 1:2 # first call returns lwork as work[1] + ccall((@blasfunc($gges3), libblastrampoline), Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ptr{Cvoid}, + Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, + Ref{BlasInt}, Ref{BlasInt}, Ptr{$elty}, Ptr{$elty}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$elty}, Ref{BlasInt}, + Ptr{$elty}, Ref{BlasInt}, Ptr{$relty}, Ptr{Cvoid}, + Ref{BlasInt}, Clong, Clong, Clong), jobvsl, jobvsr, 'N', C_NULL, n, A, max(1, stride(A, 2)), B, max(1, stride(B, 2)), sdim, alpha, beta, @@ -6105,6 +6548,18 @@ vectors are returned in `vsl` and the right Schur vectors are returned in `vsr`. """ gges!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix, B::AbstractMatrix) +""" + gges3!(jobvsl, jobvsr, A, B) -> (A, B, alpha, beta, vsl, vsr) + +Computes the generalized eigenvalues, generalized Schur form, left Schur +vectors (`jobsvl = V`), or right Schur vectors (`jobvsr = V`) of `A` and +`B` using a blocked algorithm. This function requires LAPACK 3.6.0. + +The generalized eigenvalues are returned in `alpha` and `beta`. The left Schur +vectors are returned in `vsl` and the right Schur vectors are returned in `vsr`. +""" +gges3!(jobvsl::AbstractChar, jobvsr::AbstractChar, A::AbstractMatrix, B::AbstractMatrix) + for (trexc, trsen, tgsen, elty) in ((:dtrexc_, :dtrsen_, :dtgsen_, :Float64), (:strexc_, :strsen_, :stgsen_, :Float32)) diff --git a/stdlib/LinearAlgebra/src/lbt.jl b/stdlib/LinearAlgebra/src/lbt.jl index e2efcc4b6993c..b133741611adc 100644 --- a/stdlib/LinearAlgebra/src/lbt.jl +++ b/stdlib/LinearAlgebra/src/lbt.jl @@ -83,11 +83,17 @@ struct lbt_config_t exported_symbols::Ptr{Cstring} num_exported_symbols::UInt32 end -const LBT_BUILDFLAGS_DEEPBINDLESS = 0x01 -const LBT_BUILDFLAGS_F2C_CAPABLE = 0x02 +const LBT_BUILDFLAGS_DEEPBINDLESS = 0x01 +const LBT_BUILDFLAGS_F2C_CAPABLE = 0x02 +const LBT_BUILDFLAGS_CBLAS_DIVERGENCE = 0x04 +const LBT_BUILDFLAGS_COMPLEX_RETSTYLE = 0x08 +const LBT_BUILDFLAGS_SYMBOL_TRIMMING = 0x10 const LBT_BUILDFLAGS_MAP = Dict( LBT_BUILDFLAGS_DEEPBINDLESS => :deepbindless, LBT_BUILDFLAGS_F2C_CAPABLE => :f2c_capable, + LBT_BUILDFLAGS_CBLAS_DIVERGENCE => :cblas_divergence, + LBT_BUILDFLAGS_COMPLEX_RETSTYLE => :complex_retstyle, + LBT_BUILDFLAGS_SYMBOL_TRIMMING => :symbol_trimming, ) struct LBTConfig @@ -138,8 +144,8 @@ function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, lbt::LBTLibraryInfo summary(io, lbt); println(io) println(io, "├ Library: ", basename(lbt.libname)) println(io, "├ Interface: ", lbt.interface) - print(io, "├ Complex return style: ", lbt.complex_retstyle) - print(io, "├ F2C: ", lbt.f2c) + println(io, "├ Complex return style: ", lbt.complex_retstyle) + println(io, "├ F2C: ", lbt.f2c) print(io, "└ CBLAS: ", lbt.cblas) end @@ -159,9 +165,9 @@ function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, lbt::LBTConfig) println(io, "Libraries: ") for (i,l) in enumerate(lbt.loaded_libs) char = i == length(lbt.loaded_libs) ? "└" : "├" - interface_str = if l.interface == :ilp64 + interface_str = if l.interface === :ilp64 "ILP64" - elseif l.interface == :lp64 + elseif l.interface === :lp64 " LP64" else "UNKWN" @@ -171,9 +177,32 @@ function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, lbt::LBTConfig) end end +mutable struct ConfigCache + @atomic config::Union{Nothing,LBTConfig} + lock::ReentrantLock +end + +# In the event that users want to call `lbt_get_config()` multiple times (e.g. for +# runtime checks of which BLAS vendor is providing a symbol), let's cache the value +# and clear it only when someone calls something that would cause it to change. +const _CACHED_CONFIG = ConfigCache(nothing, ReentrantLock()) + function lbt_get_config() - config_ptr = ccall((:lbt_get_config, libblastrampoline), Ptr{lbt_config_t}, ()) - return LBTConfig(unsafe_load(config_ptr)) + config = @atomic :acquire _CACHED_CONFIG.config + config === nothing || return config + return lock(_CACHED_CONFIG.lock) do + local config = @atomic :monotonic _CACHED_CONFIG.config + config === nothing || return config + config_ptr = ccall((:lbt_get_config, libblastrampoline), Ptr{lbt_config_t}, ()) + @atomic :release _CACHED_CONFIG.config = LBTConfig(unsafe_load(config_ptr)) + end +end + +function _clear_config_with(f) + lock(_CACHED_CONFIG.lock) do + @atomic :release _CACHED_CONFIG.config = nothing + f() + end end function lbt_get_num_threads() @@ -184,12 +213,17 @@ function lbt_set_num_threads(nthreads) return ccall((:lbt_set_num_threads, libblastrampoline), Cvoid, (Int32,), nthreads) end -function lbt_forward(path; clear::Bool = false, verbose::Bool = false, suffix_hint::Union{String,Nothing} = nothing) - ccall((:lbt_forward, libblastrampoline), Int32, (Cstring, Int32, Int32, Cstring), path, clear ? 1 : 0, verbose ? 1 : 0, something(suffix_hint, C_NULL)) +function lbt_forward(path::AbstractString; clear::Bool = false, verbose::Bool = false, suffix_hint::Union{String,Nothing} = nothing) + _clear_config_with() do + return ccall((:lbt_forward, libblastrampoline), Int32, (Cstring, Int32, Int32, Cstring), + path, clear ? 1 : 0, verbose ? 1 : 0, something(suffix_hint, C_NULL)) + end end function lbt_set_default_func(addr) - return ccall((:lbt_set_default_func, libblastrampoline), Cvoid, (Ptr{Cvoid},), addr) + _clear_config_with() do + return ccall((:lbt_set_default_func, libblastrampoline), Cvoid, (Ptr{Cvoid},), addr) + end end function lbt_get_default_func() @@ -213,7 +247,7 @@ 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(Argument("Invalid interface specification: '$(interface)'")) + throw(ArgumentError("Invalid interface specification: '$(interface)'")) end symbol_idx = findfirst(s -> s == symbol_name, config.exported_symbols) if symbol_idx === nothing @@ -241,17 +275,19 @@ end function lbt_set_forward(symbol_name, addr, interface, complex_retstyle = LBT_COMPLEX_RETSTYLE_NORMAL, f2c = LBT_F2C_PLAIN; verbose::Bool = false) - return ccall( - (:lbt_set_forward, libblastrampoline), - Int32, - (Cstring, Ptr{Cvoid}, Int32, Int32, Int32, Int32), - string(symbol_name), - addr, - Int32(interface), - Int32(complex_retstyle), - Int32(f2c), - verbose ? Int32(1) : Int32(0), - ) + _clear_config_with() do + return ccall( + (:lbt_set_forward, libblastrampoline), + Int32, + (Cstring, Ptr{Cvoid}, Int32, Int32, Int32, Int32), + string(symbol_name), + addr, + Int32(interface), + Int32(complex_retstyle), + Int32(f2c), + verbose ? Int32(1) : Int32(0), + ) + end end function lbt_set_forward(symbol_name, addr, interface::Symbol, complex_retstyle::Symbol = :normal, diff --git a/stdlib/LinearAlgebra/src/ldlt.jl b/stdlib/LinearAlgebra/src/ldlt.jl index e41a32d2a60f1..d3d6234961c44 100644 --- a/stdlib/LinearAlgebra/src/ldlt.jl +++ b/stdlib/LinearAlgebra/src/ldlt.jl @@ -62,7 +62,7 @@ LDLt{T}(F::LDLt) where {T} = LDLt(convert(AbstractMatrix{T}, F.data)::AbstractMa Factorization{T}(F::LDLt{T}) where {T} = F Factorization{T}(F::LDLt) where {T} = LDLt{T}(F) -function getproperty(F::LDLt, d::Symbol) +function getproperty(F::LDLt{<:Any, <:SymTridiagonal}, d::Symbol) Fdata = getfield(F, :data) if d === :d return Fdata.dv @@ -162,7 +162,7 @@ julia> S \\ b """ function ldlt(M::SymTridiagonal{T}; shift::Number=false) where T S = typeof((zero(T)+shift)/one(T)) - Mₛ = SymTridiagonal{S}(copy_oftype(M.dv, S), copy_oftype(M.ev, S)) + Mₛ = SymTridiagonal{S}(copymutable_oftype(M.dv, S), copymutable_oftype(M.ev, S)) if !iszero(shift) Mₛ.dv .+= shift end @@ -211,7 +211,7 @@ function logabsdet(F::LDLt{<:Any,<:SymTridiagonal}) end # Conversion methods -function SymTridiagonal(F::LDLt) +function SymTridiagonal(F::LDLt{<:Any, <:SymTridiagonal}) e = copy(F.data.ev) d = copy(F.data.dv) e .*= d[1:end-1] diff --git a/stdlib/LinearAlgebra/src/lq.jl b/stdlib/LinearAlgebra/src/lq.jl index f19df799bb4a7..33d794906c7e6 100644 --- a/stdlib/LinearAlgebra/src/lq.jl +++ b/stdlib/LinearAlgebra/src/lq.jl @@ -28,9 +28,7 @@ L factor: -8.60233 0.0 4.41741 -0.697486 Q factor: -2×2 LinearAlgebra.LQPackedQ{Float64, Matrix{Float64}, Vector{Float64}}: - -0.581238 -0.813733 - -0.813733 0.581238 +2×2 LinearAlgebra.LQPackedQ{Float64, Matrix{Float64}, Vector{Float64}} julia> S.L * S.Q 2×2 Matrix{Float64}: @@ -58,19 +56,13 @@ LQ{T}(factors::AbstractMatrix, τ::AbstractVector) where {T} = LQ(convert(AbstractMatrix{T}, factors), convert(AbstractVector{T}, τ)) # backwards-compatible constructors (remove with Julia 2.0) @deprecate(LQ{T,S}(factors::AbstractMatrix{T}, τ::AbstractVector{T}) where {T,S}, - LQ{T,S,typeof(τ)}(factors, τ)) + LQ{T,S,typeof(τ)}(factors, τ), false) # iteration for destructuring into components Base.iterate(S::LQ) = (S.L, Val(:Q)) Base.iterate(S::LQ, ::Val{:Q}) = (S.Q, Val(:done)) Base.iterate(S::LQ, ::Val{:done}) = nothing -struct LQPackedQ{T,S<:AbstractMatrix{T},C<:AbstractVector{T}} <: AbstractMatrix{T} - factors::S - τ::C -end - - """ lq!(A) -> LQ @@ -78,6 +70,7 @@ Compute the [`LQ`](@ref) factorization of `A`, using the input matrix as a workspace. See also [`lq`](@ref). """ lq!(A::StridedMatrix{<:BlasFloat}) = LQ(LAPACK.gelqf!(A)...) + """ lq(A) -> S::LQ @@ -105,9 +98,7 @@ L factor: -8.60233 0.0 4.41741 -0.697486 Q factor: -2×2 LinearAlgebra.LQPackedQ{Float64, Matrix{Float64}, Vector{Float64}}: - -0.581238 -0.813733 - -0.813733 0.581238 +2×2 LinearAlgebra.LQPackedQ{Float64, Matrix{Float64}, Vector{Float64}} julia> S.L * S.Q 2×2 Matrix{Float64}: @@ -120,7 +111,7 @@ julia> l == S.L && q == S.Q true ``` """ -lq(A::AbstractMatrix{T}) where {T} = lq!(copy_oftype(A, lq_eltype(T))) +lq(A::AbstractMatrix{T}) where {T} = lq!(copy_similar(A, lq_eltype(T))) lq(x::Number) = lq!(fill(convert(lq_eltype(typeof(x)), x), 1, 1)) lq_eltype(::Type{T}) where {T} = typeof(zero(T) / sqrt(abs2(one(T)))) @@ -135,8 +126,11 @@ AbstractArray(A::LQ) = AbstractMatrix(A) Matrix(A::LQ) = Array(AbstractArray(A)) Array(A::LQ) = Matrix(A) -adjoint(A::LQ) = Adjoint(A) -Base.copy(F::Adjoint{T,<:LQ{T}}) where {T} = +transpose(F::LQ{<:Real}) = F' +transpose(::LQ) = + throw(ArgumentError("transpose of LQ decomposition is not supported, consider using adjoint")) + +Base.copy(F::AdjointFactorization{T,<:LQ{T}}) where {T} = QR{T,typeof(F.parent.factors),typeof(F.parent.τ)}(copy(adjoint(F.parent.factors)), copy(F.parent.τ)) function getproperty(F::LQ, d::Symbol) @@ -153,8 +147,8 @@ end Base.propertynames(F::LQ, private::Bool=false) = (:L, :Q, (private ? fieldnames(typeof(F)) : ())...) -getindex(A::LQPackedQ, i::Integer, j::Integer) = - lmul!(A, setindex!(zeros(eltype(A), size(A, 2)), 1, j))[i] +# getindex(A::LQPackedQ, i::Integer, j::Integer) = +# lmul!(A, setindex!(zeros(eltype(A), size(A, 2)), 1, j))[i] function show(io::IO, mime::MIME{Symbol("text/plain")}, F::LQ) summary(io, F); println(io) @@ -164,162 +158,18 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, F::LQ) show(io, mime, F.Q) end -LQPackedQ{T}(Q::LQPackedQ) where {T} = LQPackedQ(convert(AbstractMatrix{T}, Q.factors), convert(Vector{T}, Q.τ)) -AbstractMatrix{T}(Q::LQPackedQ) where {T} = LQPackedQ{T}(Q) -Matrix{T}(A::LQPackedQ) where {T} = convert(Matrix{T}, LAPACK.orglq!(copy(A.factors),A.τ)) -Matrix(A::LQPackedQ{T}) where {T} = Matrix{T}(A) -Array{T}(A::LQPackedQ{T}) where {T} = Matrix{T}(A) -Array(A::LQPackedQ) = Matrix(A) - size(F::LQ, dim::Integer) = size(getfield(F, :factors), dim) size(F::LQ) = size(getfield(F, :factors)) -# size(Q::LQPackedQ) yields the shape of Q's square form -function size(Q::LQPackedQ) - n = size(Q.factors, 2) - return n, n -end -function size(Q::LQPackedQ, dim::Integer) - if dim < 1 - throw(BoundsError()) - elseif dim <= 2 # && 1 <= dim - return size(Q.factors, 2) - else # 2 < dim - return 1 - end -end - - ## Multiplication by LQ -function lmul!(A::LQ, B::StridedVecOrMat) +function lmul!(A::LQ, B::AbstractVecOrMat) lmul!(LowerTriangular(A.L), view(lmul!(A.Q, B), 1:size(A,1), axes(B,2))) return B end -function *(A::LQ{TA}, B::StridedVecOrMat{TB}) where {TA,TB} +function *(A::LQ{TA}, B::AbstractVecOrMat{TB}) where {TA,TB} TAB = promote_type(TA, TB) - _cut_B(lmul!(convert(Factorization{TAB}, A), copy_oftype(B, TAB)), 1:size(A,1)) -end - -## Multiplication by Q -### QB -lmul!(A::LQPackedQ{T}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = LAPACK.ormlq!('L','N',A.factors,A.τ,B) -function (*)(A::LQPackedQ, B::StridedVecOrMat) - TAB = promote_type(eltype(A), eltype(B)) - lmul!(AbstractMatrix{TAB}(A), copy_oftype(B, TAB)) -end - -### QcB -lmul!(adjA::Adjoint{<:Any,<:LQPackedQ{T}}, B::StridedVecOrMat{T}) where {T<:BlasReal} = - (A = adjA.parent; LAPACK.ormlq!('L', 'T', A.factors, A.τ, B)) -lmul!(adjA::Adjoint{<:Any,<:LQPackedQ{T}}, B::StridedVecOrMat{T}) where {T<:BlasComplex} = - (A = adjA.parent; LAPACK.ormlq!('L', 'C', A.factors, A.τ, B)) - -function *(adjA::Adjoint{<:Any,<:LQPackedQ}, B::StridedVecOrMat) - A = adjA.parent - TAB = promote_type(eltype(A), eltype(B)) - if size(B,1) == size(A.factors,2) - lmul!(adjoint(AbstractMatrix{TAB}(A)), copy_oftype(B, TAB)) - elseif size(B,1) == size(A.factors,1) - lmul!(adjoint(AbstractMatrix{TAB}(A)), [B; zeros(TAB, size(A.factors, 2) - size(A.factors, 1), size(B, 2))]) - else - throw(DimensionMismatch("first dimension of B, $(size(B,1)), must equal one of the dimensions of A, $(size(A))")) - end -end - -### QBc/QcBc -function *(A::LQPackedQ, adjB::Adjoint{<:Any,<:StridedVecOrMat}) - B = adjB.parent - TAB = promote_type(eltype(A), eltype(B)) - BB = similar(B, TAB, (size(B, 2), size(B, 1))) - adjoint!(BB, B) - return lmul!(A, BB) -end -function *(adjA::Adjoint{<:Any,<:LQPackedQ}, adjB::Adjoint{<:Any,<:StridedVecOrMat}) - B = adjB.parent - TAB = promote_type(eltype(adjA.parent), eltype(B)) - BB = similar(B, TAB, (size(B, 2), size(B, 1))) - adjoint!(BB, B) - return lmul!(adjA, BB) -end - -# in-place right-application of LQPackedQs -# these methods require that the applied-to matrix's (A's) number of columns -# match the number of columns (nQ) of the LQPackedQ (Q) (necessary for in-place -# operation, and the underlying LAPACK routine (ormlq) treats the implicit Q -# as its (nQ-by-nQ) square form) -rmul!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasFloat} = - LAPACK.ormlq!('R', 'N', B.factors, B.τ, A) -rmul!(A::StridedMatrix{T}, adjB::Adjoint{<:Any,<:LQPackedQ{T}}) where {T<:BlasReal} = - (B = adjB.parent; LAPACK.ormlq!('R', 'T', B.factors, B.τ, A)) -rmul!(A::StridedMatrix{T}, adjB::Adjoint{<:Any,<:LQPackedQ{T}}) where {T<:BlasComplex} = - (B = adjB.parent; LAPACK.ormlq!('R', 'C', B.factors, B.τ, A)) - -# out-of-place right application of LQPackedQs -# -# LQPackedQ's out-of-place multiplication behavior is context dependent. specifically, -# if the inner dimension in the multiplication is the LQPackedQ's second dimension, -# the LQPackedQ behaves like its square form. if the inner dimension in the -# multiplication is the LQPackedQ's first dimension, the LQPackedQ behaves like either -# its square form or its truncated form depending on the shape of the other object -# involved in the multiplication. we treat these cases separately. -# -# (1) the inner dimension in the multiplication is the LQPackedQ's second dimension. -# in this case, the LQPackedQ behaves like its square form. -# -function *(A::StridedVecOrMat, adjQ::Adjoint{<:Any,<:LQPackedQ}) - Q = adjQ.parent - TR = promote_type(eltype(A), eltype(Q)) - return rmul!(copy_oftype(A, TR), adjoint(AbstractMatrix{TR}(Q))) + _cut_B(lmul!(convert(Factorization{TAB}, A), copy_similar(B, TAB)), 1:size(A,1)) end -function *(adjA::Adjoint{<:Any,<:StridedMatrix}, adjQ::Adjoint{<:Any,<:LQPackedQ}) - A, Q = adjA.parent, adjQ.parent - TR = promote_type(eltype(A), eltype(Q)) - C = adjoint!(similar(A, TR, reverse(size(A))), A) - return rmul!(C, adjoint(AbstractMatrix{TR}(Q))) -end -# -# (2) the inner dimension in the multiplication is the LQPackedQ's first dimension. -# in this case, the LQPackedQ behaves like either its square form or its -# truncated form depending on the shape of the other object in the multiplication. -# -# these methods: (1) check whether the applied-to matrix's (A's) appropriate dimension -# (columns for A_*, rows for Ac_*) matches the number of columns (nQ) of the LQPackedQ (Q), -# and if so effectively apply Q's square form to A without additional shenanigans; and -# (2) if the preceding dimensions do not match, check whether the appropriate dimension of -# A instead matches the number of rows of the matrix of which Q is a factor (i.e. -# size(Q.factors, 1)), and if so implicitly apply Q's truncated form to A by zero extending -# A as necessary for check (1) to pass (if possible) and then applying Q's square form -# -function *(A::StridedVecOrMat, Q::LQPackedQ) - TR = promote_type(eltype(A), eltype(Q)) - if size(A, 2) == size(Q.factors, 2) - C = copy_oftype(A, TR) - elseif size(A, 2) == size(Q.factors, 1) - C = zeros(TR, size(A, 1), size(Q.factors, 2)) - copyto!(C, 1, A, 1, length(A)) - else - _rightappdimmismatch("columns") - end - return rmul!(C, AbstractMatrix{TR}(Q)) -end -function *(adjA::Adjoint{<:Any,<:StridedMatrix}, Q::LQPackedQ) - A = adjA.parent - TR = promote_type(eltype(A), eltype(Q)) - if size(A, 1) == size(Q.factors, 2) - C = adjoint!(similar(A, TR, reverse(size(A))), A) - elseif size(A, 1) == size(Q.factors, 1) - C = zeros(TR, size(A, 2), size(Q.factors, 2)) - adjoint!(view(C, :, 1:size(A, 1)), A) - else - _rightappdimmismatch("rows") - end - return rmul!(C, AbstractMatrix{TR}(Q)) -end -_rightappdimmismatch(rowsorcols) = - throw(DimensionMismatch(string("the number of $(rowsorcols) of the matrix on the left ", - "must match either (1) the number of columns of the (LQPackedQ) matrix on the right ", - "or (2) the number of rows of that (LQPackedQ) matrix's internal representation ", - "(the factorization's originating matrix's number of rows)"))) # With a real lhs and complex rhs with the same precision, we can reinterpret # the complex rhs as a real rhs with twice the number of columns @@ -334,7 +184,7 @@ function (\)(F::LQ{T}, B::VecOrMat{Complex{T}}) where T<:BlasReal end -function ldiv!(A::LQ, B::StridedVecOrMat) +function ldiv!(A::LQ, B::AbstractVecOrMat) require_one_based_indexing(B) m, n = size(A) m ≤ n || throw(DimensionMismatch("LQ solver does not support overdetermined systems (more rows than columns)")) @@ -343,7 +193,7 @@ function ldiv!(A::LQ, B::StridedVecOrMat) return lmul!(adjoint(A.Q), B) end -function ldiv!(Fadj::Adjoint{<:Any,<:LQ}, B::StridedVecOrMat) +function ldiv!(Fadj::AdjointFactorization{<:Any,<:LQ}, B::AbstractVecOrMat) require_one_based_indexing(B) m, n = size(Fadj) m >= n || throw(DimensionMismatch("solver does not support underdetermined systems (more columns than rows)")) @@ -353,7 +203,3 @@ function ldiv!(Fadj::Adjoint{<:Any,<:LQ}, B::StridedVecOrMat) ldiv!(UpperTriangular(adjoint(F.L)), view(B, 1:size(F,1), axes(B,2))) return B end - -# In LQ factorization, `Q` is expressed as the product of the adjoint of the -# reflectors. Thus, `det` has to be conjugated. -det(Q::LQPackedQ) = conj(_det_tau(Q.τ)) diff --git a/stdlib/LinearAlgebra/src/lu.jl b/stdlib/LinearAlgebra/src/lu.jl index eed82093af876..a93803ca2ea45 100644 --- a/stdlib/LinearAlgebra/src/lu.jl +++ b/stdlib/LinearAlgebra/src/lu.jl @@ -64,7 +64,7 @@ LU{T}(factors::AbstractMatrix, ipiv::AbstractVector{<:Integer}, info::Integer) w # backwards-compatible constructors (remove with Julia 2.0) @deprecate(LU{T,S}(factors::AbstractMatrix{T}, ipiv::AbstractVector{<:Integer}, info::BlasInt) where {T,S}, - LU{T,S,typeof(ipiv)}(factors, ipiv, info)) + LU{T,S,typeof(ipiv)}(factors, ipiv, info), false) # iteration for destructuring into components Base.iterate(S::LU) = (S.L, Val(:U)) @@ -72,21 +72,18 @@ Base.iterate(S::LU, ::Val{:U}) = (S.U, Val(:p)) Base.iterate(S::LU, ::Val{:p}) = (S.p, Val(:done)) Base.iterate(S::LU, ::Val{:done}) = nothing -adjoint(F::LU) = Adjoint(F) -transpose(F::LU) = Transpose(F) +# LU prefers transpose over adjoint in the real case, override the generic fallback +adjoint(F::LU{<:Real}) = TransposeFactorization(F) +transpose(F::LU{<:Real}) = TransposeFactorization(F) -# StridedMatrix +# 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} lpt = LAPACK.getrf!(A) check && checknonsingular(lpt[3]) return LU{T,typeof(lpt[1]),typeof(lpt[2])}(lpt[1], lpt[2], lpt[3]) end -function lu!(A::StridedMatrix{<:BlasFloat}, pivot::NoPivot; check::Bool = true) - return generic_lufact!(A, pivot; check = check) -end - -function lu!(A::HermOrSym, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); check::Bool = true) +function lu!(A::HermOrSym{T}, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(T); check::Bool = true) where {T} copytri!(A.data, A.uplo, isa(A, Hermitian)) lu!(A.data, pivot; check = check) end @@ -132,10 +129,11 @@ Stacktrace: [...] ``` """ -lu!(A::StridedMatrix, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); check::Bool = true) = +lu!(A::AbstractMatrix, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(eltype(A)); check::Bool = true) = generic_lufact!(A, pivot; check = check) -function generic_lufact!(A::StridedMatrix{T}, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); +function generic_lufact!(A::AbstractMatrix{T}, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(T); check::Bool = true) where {T} + LAPACK.chkfinite(A) # Extract values m, n = size(A) minmn = min(m,n) @@ -156,6 +154,13 @@ function generic_lufact!(A::StridedMatrix{T}, pivot::Union{RowMaximum,NoPivot} = amax = absi end end + elseif pivot === RowNonZero() + for i = k:m + if !iszero(A[i,k]) + kp = i + break + end + end end ipiv[k] = kp if !iszero(A[kp,k]) @@ -206,6 +211,8 @@ function lutype(T::Type) S = promote_type(T, LT, UT) end +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 @@ -217,9 +224,23 @@ When `check = false`, responsibility for checking the decomposition's validity (via [`issuccess`](@ref)) lies with the user. 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}}`. If -pivoting is chosen (default) the element type should also support [`abs`](@ref) and -[`<`](@ref). Pivoting can be turned off by passing `pivot = NoPivot()`. +type `T` supporting `+`, `-`, `*` and `/`, the return type is `LU{T,S{T}}`. + +In general, LU factorization involves a permutation of the rows of the matrix +(corresponding to the `F.p` output described below), known as "pivoting" (because it +corresponds to choosing which row contains the "pivot", the diagonal entry of `F.U`). +One of the following pivoting strategies can be selected via the optional `pivot` argument: + +* `RowMaximum()` (default): the standard pivoting strategy; the pivot corresponds + to the element of maximum absolute value among the remaining, to be factorized rows. + This pivoting strategy requires the element type to also support [`abs`](@ref) and + [`<`](@ref). (This is generally the only numerically stable option for floating-point + matrices.) +* `RowNonZero()`: the pivot corresponds to the first non-zero element among the remaining, + 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). The individual components of the factorization `F` can be accessed via [`getproperty`](@ref): @@ -275,7 +296,7 @@ julia> l == F.L && u == F.U && p == F.p true ``` """ -function lu(A::AbstractMatrix{T}, pivot::Union{RowMaximum,NoPivot} = RowMaximum(); check::Bool = true) where {T} +function lu(A::AbstractMatrix{T}, pivot::Union{RowMaximum,NoPivot,RowNonZero} = lupivottype(T); check::Bool = true) where {T} lu!(_lucopy(A, lutype(T)), pivot; check = check) end # TODO: remove for Julia v2.0 @@ -283,8 +304,8 @@ end @deprecate lu(A::AbstractMatrix, ::Val{false}; check::Bool = true) lu(A, NoPivot(); check=check) _lucopy(A::AbstractMatrix, T) = copy_similar(A, T) -_lucopy(A::HermOrSym, T) = copy_oftype(A, T) -_lucopy(A::Tridiagonal, T) = copy_oftype(A, T) +_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) @@ -304,7 +325,7 @@ Factorization{T}(F::LU) where {T} = LU{T}(F) copy(A::LU{T,S,P}) where {T,S,P} = LU{T,S,P}(copy(A.factors), copy(A.ipiv), A.info) size(A::LU) = size(getfield(A, :factors)) -size(A::LU, i) = size(getfield(A, :factors), i) +size(A::LU, i::Integer) = size(getfield(A, :factors), i) function ipiv2perm(v::AbstractVector{T}, maxi::Integer) where T require_one_based_indexing(v) @@ -315,7 +336,7 @@ function ipiv2perm(v::AbstractVector{T}, maxi::Integer) where T return p end -function getproperty(F::LU{T,<:StridedMatrix}, d::Symbol) where T +function getproperty(F::LU{T}, d::Symbol) where T m, n = size(F) if d === :L L = tril!(getfield(F, :factors)[1:m, 1:min(m,n)]) @@ -349,10 +370,10 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, F::LU) end end -_apply_ipiv_rows!(A::LU, B::StridedVecOrMat) = _ipiv_rows!(A, 1 : length(A.ipiv), B) -_apply_inverse_ipiv_rows!(A::LU, B::StridedVecOrMat) = _ipiv_rows!(A, length(A.ipiv) : -1 : 1, B) +_apply_ipiv_rows!(A::LU, B::AbstractVecOrMat) = _ipiv_rows!(A, 1 : length(A.ipiv), B) +_apply_inverse_ipiv_rows!(A::LU, B::AbstractVecOrMat) = _ipiv_rows!(A, length(A.ipiv) : -1 : 1, B) -function _ipiv_rows!(A::LU, order::OrdinalRange, B::StridedVecOrMat) +function _ipiv_rows!(A::LU, order::OrdinalRange, B::AbstractVecOrMat) for i = order if i != A.ipiv[i] _swap_rows!(B, i, A.ipiv[i]) @@ -361,22 +382,22 @@ function _ipiv_rows!(A::LU, order::OrdinalRange, B::StridedVecOrMat) B end -function _swap_rows!(B::StridedVector, i::Integer, j::Integer) +function _swap_rows!(B::AbstractVector, i::Integer, j::Integer) B[i], B[j] = B[j], B[i] B end -function _swap_rows!(B::StridedMatrix, i::Integer, j::Integer) +function _swap_rows!(B::AbstractMatrix, i::Integer, j::Integer) for col = 1 : size(B, 2) B[i,col], B[j,col] = B[j,col], B[i,col] end B end -_apply_ipiv_cols!(A::LU, B::StridedVecOrMat) = _ipiv_cols!(A, 1 : length(A.ipiv), B) -_apply_inverse_ipiv_cols!(A::LU, B::StridedVecOrMat) = _ipiv_cols!(A, length(A.ipiv) : -1 : 1, B) +_apply_ipiv_cols!(A::LU, B::AbstractVecOrMat) = _ipiv_cols!(A, 1 : length(A.ipiv), B) +_apply_inverse_ipiv_cols!(A::LU, B::AbstractVecOrMat) = _ipiv_cols!(A, length(A.ipiv) : -1 : 1, B) -function _ipiv_cols!(A::LU, order::OrdinalRange, B::StridedVecOrMat) +function _ipiv_cols!(A::LU, order::OrdinalRange, B::AbstractVecOrMat) for i = order if i != A.ipiv[i] _swap_cols!(B, i, A.ipiv[i]) @@ -385,18 +406,18 @@ function _ipiv_cols!(A::LU, order::OrdinalRange, B::StridedVecOrMat) B end -function _swap_cols!(B::StridedVector, i::Integer, j::Integer) +function _swap_cols!(B::AbstractVector, i::Integer, j::Integer) _swap_rows!(B, i, j) end -function _swap_cols!(B::StridedMatrix, i::Integer, j::Integer) +function _swap_cols!(B::AbstractMatrix, i::Integer, j::Integer) for row = 1 : size(B, 1) B[row,i], B[row,j] = B[row,j], B[row,i] end B end -function rdiv!(A::StridedVecOrMat, B::LU{<:Any,<:StridedMatrix}) +function rdiv!(A::AbstractVecOrMat, B::LU) rdiv!(rdiv!(A, UpperTriangular(B.factors)), UnitLowerTriangular(B.factors)) _apply_inverse_ipiv_cols!(B, A) end @@ -404,54 +425,34 @@ end ldiv!(A::LU{T,<:StridedMatrix}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = LAPACK.getrs!('N', A.factors, A.ipiv, B) -function ldiv!(A::LU{<:Any,<:StridedMatrix}, B::StridedVecOrMat) +function ldiv!(A::LU, B::AbstractVecOrMat) _apply_ipiv_rows!(A, B) ldiv!(UpperTriangular(A.factors), ldiv!(UnitLowerTriangular(A.factors), B)) end -ldiv!(transA::Transpose{T,<:LU{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = +ldiv!(transA::TransposeFactorization{T,<:LU{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = (A = transA.parent; LAPACK.getrs!('T', A.factors, A.ipiv, B)) -function ldiv!(transA::Transpose{<:Any,<:LU{<:Any,<:StridedMatrix}}, B::StridedVecOrMat) +function ldiv!(transA::TransposeFactorization{<:Any,<:LU}, B::AbstractVecOrMat) A = transA.parent ldiv!(transpose(UnitLowerTriangular(A.factors)), ldiv!(transpose(UpperTriangular(A.factors)), B)) _apply_inverse_ipiv_rows!(A, B) end -ldiv!(adjF::Adjoint{T,<:LU{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:Real} = - (F = adjF.parent; ldiv!(transpose(F), B)) -ldiv!(adjA::Adjoint{T,<:LU{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasComplex} = +ldiv!(adjA::AdjointFactorization{T,<:LU{T,<:StridedMatrix}}, B::StridedVecOrMat{T}) where {T<:BlasComplex} = (A = adjA.parent; LAPACK.getrs!('C', A.factors, A.ipiv, B)) -function ldiv!(adjA::Adjoint{<:Any,<:LU{<:Any,<:StridedMatrix}}, B::StridedVecOrMat) +function ldiv!(adjA::AdjointFactorization{<:Any,<:LU}, B::AbstractVecOrMat) A = adjA.parent ldiv!(adjoint(UnitLowerTriangular(A.factors)), ldiv!(adjoint(UpperTriangular(A.factors)), B)) _apply_inverse_ipiv_rows!(A, B) end -(\)(A::Adjoint{<:Any,<:LU}, B::Adjoint{<:Any,<:StridedVecOrMat}) = A \ copy(B) -(\)(A::Transpose{<:Any,<:LU}, B::Transpose{<:Any,<:StridedVecOrMat}) = A \ copy(B) -(\)(A::Adjoint{T,<:LU{T,<:StridedMatrix}}, B::Adjoint{T,<:StridedVecOrMat{T}}) where {T<:BlasComplex} = +(\)(A::AdjointFactorization{T,<:LU{T,<:StridedMatrix}}, B::Adjoint{T,<:StridedVecOrMat{T}}) where {T<:BlasComplex} = LAPACK.getrs!('C', A.parent.factors, A.parent.ipiv, copy(B)) -(\)(A::Transpose{T,<:LU{T,<:StridedMatrix}}, B::Transpose{T,<:StridedVecOrMat{T}}) where {T<:BlasFloat} = +(\)(A::TransposeFactorization{T,<:LU{T,<:StridedMatrix}}, B::Transpose{T,<:StridedVecOrMat{T}}) where {T<:BlasFloat} = LAPACK.getrs!('T', A.parent.factors, A.parent.ipiv, copy(B)) -function (/)(A::AbstractMatrix, F::Adjoint{<:Any,<:LU}) - T = promote_type(eltype(A), eltype(F)) - return adjoint(ldiv!(F.parent, copy_oftype(adjoint(A), T))) -end -# To avoid ambiguities with definitions in adjtrans.jl and factorizations.jl -(/)(adjA::Adjoint{<:Any,<:AbstractVector}, F::Adjoint{<:Any,<:LU}) = adjoint(F.parent \ adjA.parent) -(/)(adjA::Adjoint{<:Any,<:AbstractMatrix}, F::Adjoint{<:Any,<:LU}) = adjoint(F.parent \ adjA.parent) -function (/)(trA::Transpose{<:Any,<:AbstractVector}, F::Adjoint{<:Any,<:LU}) - T = promote_type(eltype(trA), eltype(F)) - return adjoint(ldiv!(F.parent, conj!(copy_oftype(trA.parent, T)))) -end -function (/)(trA::Transpose{<:Any,<:AbstractMatrix}, F::Adjoint{<:Any,<:LU}) - T = promote_type(eltype(trA), eltype(F)) - return adjoint(ldiv!(F.parent, conj!(copy_oftype(trA.parent, T)))) -end - function det(F::LU{T}) where T n = checksquare(F) issuccess(F) || return zero(T) @@ -507,7 +508,14 @@ function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum( if dl === du throw(ArgumentError("off-diagonals of `A` must not alias")) end - du2 = fill!(similar(d, n-2), 0)::V + # Check if Tridiagonal matrix already has du2 for pivoting + has_du2_defined = isdefined(A, :du2) && length(A.du2) == max(0, n-2) + if has_du2_defined + du2 = A.du2::V + else + du2 = similar(d, max(0, n-2))::V + end + fill!(du2, 0) @inbounds begin for i = 1:n @@ -562,7 +570,7 @@ function lu!(A::Tridiagonal{T,V}, pivot::Union{RowMaximum,NoPivot} = RowMaximum( end end end - B = Tridiagonal{T,V}(dl, d, du, du2) + B = has_du2_defined ? A : Tridiagonal{T,V}(dl, d, du, du2) check && checknonsingular(info, pivot) return LU{T,Tridiagonal{T,V},typeof(ipiv)}(B, ipiv, convert(BlasInt, info)) end @@ -627,7 +635,7 @@ function ldiv!(A::LU{T,Tridiagonal{T,V}}, B::AbstractVecOrMat) where {T,V} return B end -function ldiv!(transA::Transpose{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::AbstractVecOrMat) where {T,V} +function ldiv!(transA::TransposeFactorization{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::AbstractVecOrMat) where {T,V} require_one_based_indexing(B) A = transA.parent n = size(A,1) @@ -664,7 +672,7 @@ function ldiv!(transA::Transpose{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::AbstractVec end # Ac_ldiv_B!(A::LU{T,Tridiagonal{T}}, B::AbstractVecOrMat) where {T<:Real} = At_ldiv_B!(A,B) -function ldiv!(adjA::Adjoint{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::AbstractVecOrMat) where {T,V} +function ldiv!(adjA::AdjointFactorization{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::AbstractVecOrMat) where {T,V} require_one_based_indexing(B) A = adjA.parent n = size(A,1) @@ -701,8 +709,8 @@ function ldiv!(adjA::Adjoint{<:Any,<:LU{T,Tridiagonal{T,V}}}, B::AbstractVecOrMa end rdiv!(B::AbstractMatrix, A::LU) = transpose(ldiv!(transpose(A), transpose(B))) -rdiv!(B::AbstractMatrix, A::Transpose{<:Any,<:LU}) = transpose(ldiv!(A.parent, transpose(B))) -rdiv!(B::AbstractMatrix, A::Adjoint{<:Any,<:LU}) = adjoint(ldiv!(A.parent, adjoint(B))) +rdiv!(B::AbstractMatrix, A::TransposeFactorization{<:Any,<:LU}) = transpose(ldiv!(A.parent, transpose(B))) +rdiv!(B::AbstractMatrix, A::AdjointFactorization{<:Any,<:LU}) = adjoint(ldiv!(A.parent, adjoint(B))) # Conversions AbstractMatrix(F::LU) = (F.L * F.U)[invperm(F.p),:] diff --git a/stdlib/LinearAlgebra/src/matmul.jl b/stdlib/LinearAlgebra/src/matmul.jl index f27a3a768b866..170aacee6682f 100644 --- a/stdlib/LinearAlgebra/src/matmul.jl +++ b/stdlib/LinearAlgebra/src/matmul.jl @@ -1,18 +1,23 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# matmul.jl: Everything to do with dense matrix multiplication + # Matrix-matrix multiplication -AdjOrTransStridedMat{T} = Union{Adjoint{T, <:StridedMatrix}, Transpose{T, <:StridedMatrix}} -StridedMaybeAdjOrTransMat{T} = Union{StridedMatrix{T}, Adjoint{T, <:StridedMatrix}, Transpose{T, <:StridedMatrix}} +AdjOrTransStridedMat{T} = Union{Adjoint{<:Any, <:StridedMatrix{T}}, Transpose{<:Any, <:StridedMatrix{T}}} +StridedMaybeAdjOrTransMat{T} = Union{StridedMatrix{T}, Adjoint{<:Any, <:StridedMatrix{T}}, Transpose{<:Any, <:StridedMatrix{T}}} +StridedMaybeAdjOrTransVecOrMat{T} = Union{StridedVecOrMat{T}, AdjOrTrans{<:Any, <:StridedVecOrMat{T}}} -# matmul.jl: Everything to do with dense matrix multiplication +_parent(A) = A +_parent(A::Adjoint) = parent(A) +_parent(A::Transpose) = parent(A) matprod(x, y) = x*y + x*y # dot products -dot(x::Union{DenseArray{T},StridedVector{T}}, y::Union{DenseArray{T},StridedVector{T}}) where {T<:BlasReal} = BLAS.dot(x, y) -dot(x::Union{DenseArray{T},StridedVector{T}}, y::Union{DenseArray{T},StridedVector{T}}) where {T<:BlasComplex} = BLAS.dotc(x, y) +dot(x::StridedVecLike{T}, y::StridedVecLike{T}) where {T<:BlasReal} = BLAS.dot(x, y) +dot(x::StridedVecLike{T}, y::StridedVecLike{T}) where {T<:BlasComplex} = BLAS.dotc(x, y) function dot(x::Vector{T}, rx::AbstractRange{TI}, y::Vector{T}, ry::AbstractRange{TI}) where {T<:BlasReal,TI<:Integer} if length(rx) != length(ry) @@ -46,83 +51,47 @@ function *(transx::Transpose{<:Any,<:StridedVector{T}}, y::StridedVector{T}) whe end # Matrix-vector multiplication -function (*)(A::StridedMatrix{T}, x::StridedVector{S}) where {T<:BlasFloat,S<:Real} +function (*)(A::StridedMaybeAdjOrTransMat{T}, x::StridedVector{S}) where {T<:BlasFloat,S<:Real} TS = promote_op(matprod, T, S) y = isconcretetype(TS) ? convert(AbstractVector{TS}, x) : x mul!(similar(x, TS, size(A,1)), A, y) end function (*)(A::AbstractMatrix{T}, x::AbstractVector{S}) where {T,S} TS = promote_op(matprod, T, S) - mul!(similar(x,TS,axes(A,1)),A,x) + mul!(similar(x, TS, axes(A,1)), A, x) end # these will throw a DimensionMismatch unless B has 1 row (or 1 col for transposed case): -(*)(a::AbstractVector, tB::Transpose{<:Any,<:AbstractMatrix}) = reshape(a, length(a), 1) * tB -(*)(a::AbstractVector, adjB::Adjoint{<:Any,<:AbstractMatrix}) = reshape(a, length(a), 1) * adjB +(*)(a::AbstractVector, tB::TransposeAbsMat) = reshape(a, length(a), 1) * tB +(*)(a::AbstractVector, adjB::AdjointAbsMat) = reshape(a, length(a), 1) * adjB (*)(a::AbstractVector, B::AbstractMatrix) = reshape(a, length(a), 1) * B -@inline mul!(y::StridedVector{T}, A::StridedVecOrMat{T}, x::StridedVector{T}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - gemv!(y, 'N', A, x, alpha, beta) - -# Complex matrix times real vector. -# Reinterpret the matrix as a real matrix and do real matvec compuation. -@inline mul!(y::StridedVector{Complex{T}}, A::StridedVecOrMat{Complex{T}}, x::StridedVector{T}, - alpha::Number, beta::Number) where {T<:BlasReal} = - gemv!(y, 'N', A, x, alpha, beta) - -# Real matrix times complex vector. -# Multiply the matrix with the real and imaginary parts separately -@inline mul!(y::StridedVector{Complex{T}}, A::StridedMaybeAdjOrTransMat{T}, x::StridedVector{Complex{T}}, - alpha::Number, beta::Number) where {T<:BlasReal} = - gemv!(y, A isa StridedArray ? 'N' : 'T', A isa StridedArray ? A : parent(A), x, alpha, beta) - @inline mul!(y::AbstractVector, A::AbstractVecOrMat, x::AbstractVector, - alpha::Number, beta::Number) = - generic_matvecmul!(y, 'N', A, x, MulAddMul(alpha, beta)) - -function *(tA::Transpose{<:Any,<:StridedMatrix{T}}, x::StridedVector{S}) where {T<:BlasFloat,S} - TS = promote_op(matprod, T, S) - mul!(similar(x, TS, size(tA, 1)), tA, convert(AbstractVector{TS}, x)) -end -function *(tA::Transpose{<:Any,<:AbstractMatrix{T}}, x::AbstractVector{S}) where {T,S} - TS = promote_op(matprod, T, S) - mul!(similar(x, TS, size(tA, 1)), tA, x) -end -@inline mul!(y::StridedVector{T}, tA::Transpose{<:Any,<:StridedVecOrMat{T}}, x::StridedVector{T}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - gemv!(y, 'T', tA.parent, x, alpha, beta) -@inline mul!(y::AbstractVector, tA::Transpose{<:Any,<:AbstractVecOrMat}, x::AbstractVector, - alpha::Number, beta::Number) = - generic_matvecmul!(y, 'T', tA.parent, x, MulAddMul(alpha, beta)) - -function *(adjA::Adjoint{<:Any,<:StridedMatrix{T}}, x::StridedVector{S}) where {T<:BlasFloat,S} - TS = promote_op(matprod, T, S) - mul!(similar(x, TS, size(adjA, 1)), adjA, convert(AbstractVector{TS}, x)) -end -function *(adjA::Adjoint{<:Any,<:AbstractMatrix{T}}, x::AbstractVector{S}) where {T,S} - TS = promote_op(matprod, T, S) - mul!(similar(x, TS, size(adjA, 1)), adjA, x) -end - -@inline mul!(y::StridedVector{T}, adjA::Adjoint{<:Any,<:StridedVecOrMat{T}}, x::StridedVector{T}, - alpha::Number, beta::Number) where {T<:BlasReal} = - mul!(y, transpose(adjA.parent), x, alpha, beta) -@inline mul!(y::StridedVector{T}, adjA::Adjoint{<:Any,<:StridedVecOrMat{T}}, x::StridedVector{T}, - alpha::Number, beta::Number) where {T<:BlasComplex} = - gemv!(y, 'C', adjA.parent, x, alpha, beta) -@inline mul!(y::AbstractVector, adjA::Adjoint{<:Any,<:AbstractVecOrMat}, x::AbstractVector, - alpha::Number, beta::Number) = - generic_matvecmul!(y, 'C', adjA.parent, x, MulAddMul(alpha, beta)) + alpha::Number, beta::Number) = + generic_matvecmul!(y, adj_or_trans_char(A), _parent(A), x, MulAddMul(alpha, beta)) +# BLAS cases +# equal eltypes +@inline generic_matvecmul!(y::StridedVector{T}, tA, A::StridedVecOrMat{T}, x::StridedVector{T}, + _add::MulAddMul=MulAddMul()) where {T<:BlasFloat} = + gemv!(y, tA, _parent(A), x, _add.alpha, _add.beta) +# Real (possibly transposed) matrix times complex vector. +# Multiply the matrix with the real and imaginary parts separately +@inline generic_matvecmul!(y::StridedVector{Complex{T}}, tA, A::StridedVecOrMat{T}, x::StridedVector{Complex{T}}, + _add::MulAddMul=MulAddMul()) where {T<:BlasReal} = + gemv!(y, tA, _parent(A), x, _add.alpha, _add.beta) +# Complex matrix times real vector. +# Reinterpret the matrix as a real matrix and do real matvec computation. +# works only in cooperation with BLAS when A is untransposed (tA == 'N') +# but that check is included in gemv! anyway +@inline generic_matvecmul!(y::StridedVector{Complex{T}}, tA, A::StridedVecOrMat{Complex{T}}, x::StridedVector{T}, + _add::MulAddMul=MulAddMul()) where {T<:BlasReal} = + gemv!(y, tA, _parent(A), x, _add.alpha, _add.beta) # Vector-Matrix multiplication (*)(x::AdjointAbsVec, A::AbstractMatrix) = (A'*x')' (*)(x::TransposeAbsVec, A::AbstractMatrix) = transpose(transpose(A)*transpose(x)) -_parent(A) = A -_parent(A::Adjoint) = parent(A) -_parent(A::Transpose) = parent(A) - +# Matrix-matrix multiplication """ *(A::AbstractMatrix, B::AbstractMatrix) @@ -156,10 +125,6 @@ function (*)(A::StridedMaybeAdjOrTransMat{<:BlasComplex}, B::StridedMaybeAdjOrTr wrapperop(B)(convert(AbstractArray{TS}, _parent(B)))) end -@inline function mul!(C::StridedMatrix{T}, A::StridedVecOrMat{T}, B::StridedVecOrMat{T}, - alpha::Number, beta::Number) where {T<:BlasFloat} - return gemm_wrapper!(C, 'N', 'N', A, B, MulAddMul(alpha, beta)) -end # Complex Matrix times real matrix: We use that it is generally faster to reinterpret the # first matrix as a real matrix and carry out real matrix matrix multiply function (*)(A::StridedMatrix{<:BlasComplex}, B::StridedMaybeAdjOrTransMat{<:BlasReal}) @@ -171,7 +136,7 @@ end function (*)(A::AdjOrTransStridedMat{<:BlasComplex}, B::StridedMaybeAdjOrTransMat{<:BlasReal}) TS = promote_type(eltype(A), eltype(B)) mul!(similar(B, TS, (size(A, 1), size(B, 2))), - copy_oftype(A, TS), # remove AdjOrTrans to use reinterpret trick below + copymutable_oftype(A, TS), # remove AdjOrTrans to use reinterpret trick below wrapperop(B)(convert(AbstractArray{real(TS)}, _parent(B)))) end # the following case doesn't seem to benefit from the translation A*B = (B' * A')' @@ -299,9 +264,15 @@ julia> C 730.0 740.0 ``` """ -@inline mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'N', 'N', A, B, MulAddMul(alpha, beta)) +@inline mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat, α::Number, β::Number) = + generic_matmatmul!( + C, + adj_or_trans_char(A), + adj_or_trans_char(B), + _parent(A), + _parent(B), + MulAddMul(α, β) + ) """ rmul!(A, B) @@ -315,9 +286,9 @@ see [`QR`](@ref). ```jldoctest julia> A = [0 1; 1 0]; -julia> B = LinearAlgebra.UpperTriangular([1 2; 0 3]); +julia> B = UpperTriangular([1 2; 0 3]); -julia> LinearAlgebra.rmul!(A, B); +julia> rmul!(A, B); julia> A 2×2 Matrix{Int64}: @@ -348,9 +319,9 @@ see [`QR`](@ref). ```jldoctest julia> B = [0 1; 1 0]; -julia> A = LinearAlgebra.UpperTriangular([1 2; 0 3]); +julia> A = UpperTriangular([1 2; 0 3]); -julia> LinearAlgebra.lmul!(A, B); +julia> lmul!(A, B); julia> B 2×2 Matrix{Int64}: @@ -369,107 +340,31 @@ julia> lmul!(F.Q, B) """ lmul!(A, B) -@inline function mul!(C::StridedMatrix{T}, tA::Transpose{<:Any,<:StridedVecOrMat{T}}, B::StridedVecOrMat{T}, - alpha::Number, beta::Number) where {T<:BlasFloat} - A = tA.parent - if A === B - return syrk_wrapper!(C, 'T', A, MulAddMul(alpha, beta)) - else - return gemm_wrapper!(C, 'T', 'N', A, B, MulAddMul(alpha, beta)) - end -end -@inline mul!(C::AbstractMatrix, tA::Transpose{<:Any,<:AbstractVecOrMat}, B::AbstractVecOrMat, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'T', 'N', tA.parent, B, MulAddMul(alpha, beta)) - -@inline function mul!(C::StridedMatrix{T}, A::StridedVecOrMat{T}, tB::Transpose{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasFloat} - B = tB.parent - if A === B - return syrk_wrapper!(C, 'N', A, MulAddMul(alpha, beta)) +@inline function generic_matmatmul!(C::StridedMatrix{T}, tA, tB, A::StridedVecOrMat{T}, B::StridedVecOrMat{T}, + _add::MulAddMul=MulAddMul()) where {T<:BlasFloat} + if tA == 'T' && tB == 'N' && A === B + return syrk_wrapper!(C, 'T', A, _add) + elseif tA == 'N' && tB == 'T' && A === B + return syrk_wrapper!(C, 'N', A, _add) + elseif tA == 'C' && tB == 'N' && A === B + return herk_wrapper!(C, 'C', A, _add) + elseif tA == 'N' && tB == 'C' && A === B + return herk_wrapper!(C, 'N', A, _add) else - return gemm_wrapper!(C, 'N', 'T', A, B, MulAddMul(alpha, beta)) + return gemm_wrapper!(C, tA, tB, A, B, _add) end end + # Complex matrix times (transposed) real matrix. Reinterpret the first matrix to real for efficiency. -@inline mul!(C::StridedMatrix{Complex{T}}, A::StridedVecOrMat{Complex{T}}, B::StridedVecOrMat{T}, - alpha::Number, beta::Number) where {T<:BlasReal} = - gemm_wrapper!(C, 'N', 'N', A, B, MulAddMul(alpha, beta)) -@inline mul!(C::StridedMatrix{Complex{T}}, A::StridedVecOrMat{Complex{T}}, tB::Transpose{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasReal} = - gemm_wrapper!(C, 'N', 'T', A, parent(tB), MulAddMul(alpha, beta)) - -# collapsing the following two defs with C::AbstractVecOrMat yields ambiguities -@inline mul!(C::AbstractVector, A::AbstractVecOrMat, tB::Transpose{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'N', 'T', A, tB.parent, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, A::AbstractVecOrMat, tB::Transpose{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'N', 'T', A, tB.parent, MulAddMul(alpha, beta)) - -@inline mul!(C::StridedMatrix{T}, tA::Transpose{<:Any,<:StridedVecOrMat{T}}, tB::Transpose{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - gemm_wrapper!(C, 'T', 'T', tA.parent, tB.parent, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, tA::Transpose{<:Any,<:AbstractVecOrMat}, tB::Transpose{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'T', 'T', tA.parent, tB.parent, MulAddMul(alpha, beta)) - -@inline mul!(C::StridedMatrix{T}, tA::Transpose{<:Any,<:StridedVecOrMat{T}}, adjB::Adjoint{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - gemm_wrapper!(C, 'T', 'C', tA.parent, adjB.parent, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, tA::Transpose{<:Any,<:AbstractVecOrMat}, tB::Adjoint{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'T', 'C', tA.parent, tB.parent, MulAddMul(alpha, beta)) - -@inline mul!(C::StridedMatrix{T}, adjA::Adjoint{<:Any,<:StridedVecOrMat{T}}, B::StridedVecOrMat{T}, - alpha::Real, beta::Real) where {T<:BlasReal} = - mul!(C, transpose(adjA.parent), B, alpha, beta) -@inline function mul!(C::StridedMatrix{T}, adjA::Adjoint{<:Any,<:StridedVecOrMat{T}}, B::StridedVecOrMat{T}, - alpha::Number, beta::Number) where {T<:BlasComplex} - A = adjA.parent - if A === B - return herk_wrapper!(C, 'C', A, MulAddMul(alpha, beta)) - else - return gemm_wrapper!(C, 'C', 'N', A, B, MulAddMul(alpha, beta)) - end -end -@inline mul!(C::AbstractMatrix, adjA::Adjoint{<:Any,<:AbstractVecOrMat}, B::AbstractVecOrMat, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'C', 'N', adjA.parent, B, MulAddMul(alpha, beta)) - -@inline mul!(C::StridedMatrix{T}, A::StridedVecOrMat{T}, adjB::Adjoint{<:Any,<:StridedVecOrMat{<:BlasReal}}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - mul!(C, A, transpose(adjB.parent), alpha, beta) -@inline function mul!(C::StridedMatrix{T}, A::StridedVecOrMat{T}, adjB::Adjoint{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasComplex} - B = adjB.parent - if A === B - return herk_wrapper!(C, 'N', A, MulAddMul(alpha, beta)) - else - return gemm_wrapper!(C, 'N', 'C', A, B, MulAddMul(alpha, beta)) - end +@inline function generic_matmatmul!(C::StridedVecOrMat{Complex{T}}, tA, tB, A::StridedVecOrMat{Complex{T}}, B::StridedVecOrMat{T}, + _add::MulAddMul=MulAddMul()) where {T<:BlasReal} + gemm_wrapper!(C, tA, tB, A, B, _add) end -@inline mul!(C::AbstractMatrix, A::AbstractVecOrMat, adjB::Adjoint{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'N', 'C', A, adjB.parent, MulAddMul(alpha, beta)) - -@inline mul!(C::StridedMatrix{T}, adjA::Adjoint{<:Any,<:StridedVecOrMat{T}}, adjB::Adjoint{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - gemm_wrapper!(C, 'C', 'C', adjA.parent, adjB.parent, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, adjA::Adjoint{<:Any,<:AbstractVecOrMat}, adjB::Adjoint{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'C', 'C', adjA.parent, adjB.parent, MulAddMul(alpha, beta)) - -@inline mul!(C::StridedMatrix{T}, adjA::Adjoint{<:Any,<:StridedVecOrMat{T}}, tB::Transpose{<:Any,<:StridedVecOrMat{T}}, - alpha::Number, beta::Number) where {T<:BlasFloat} = - gemm_wrapper!(C, 'C', 'T', adjA.parent, tB.parent, MulAddMul(alpha, beta)) -@inline mul!(C::AbstractMatrix, adjA::Adjoint{<:Any,<:AbstractVecOrMat}, tB::Transpose{<:Any,<:AbstractVecOrMat}, - alpha::Number, beta::Number) = - generic_matmatmul!(C, 'C', 'T', adjA.parent, tB.parent, MulAddMul(alpha, beta)) + # Supporting functions for matrix multiplication -# copy transposed(adjoint) of upper(lower) side-digonals. Optionally include diagonal. +# copy transposed(adjoint) of upper(lower) side-diagonals. Optionally include diagonal. @inline function copytri!(A::AbstractMatrix, uplo::AbstractChar, conjugate::Bool=false, diag::Bool=false) n = checksquare(A) off = diag ? 0 : 1 @@ -498,10 +393,11 @@ function gemv!(y::StridedVector{T}, tA::AbstractChar, A::StridedVecOrMat{T}, x:: nA == 0 && return _rmul_or_fill!(y, β) alpha, beta = promote(α, β, zero(T)) if alpha isa Union{Bool,T} && beta isa Union{Bool,T} && - stride(A, 1) == 1 && stride(A, 2) >= size(A, 1) + stride(A, 1) == 1 && abs(stride(A, 2)) >= size(A, 1) && + !iszero(stride(x, 1)) # We only check input's stride here. return BLAS.gemv!(tA, alpha, A, x, beta, y) else - return generic_matvecmul!(y, tA, A, x, MulAddMul(α, β)) + return _generic_matvecmul!(y, tA, A, x, MulAddMul(α, β)) end end @@ -516,12 +412,13 @@ function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, A::StridedVecOrMa nA == 0 && return _rmul_or_fill!(y, β) alpha, beta = promote(α, β, zero(T)) if alpha isa Union{Bool,T} && beta isa Union{Bool,T} && - stride(A, 1) == 1 && stride(A, 2) >= size(A, 1) && - stride(y, 1) == 1 && tA == 'N' # reinterpret-based optimization is valid only for contiguous `y` + stride(A, 1) == 1 && abs(stride(A, 2)) >= size(A, 1) && + stride(y, 1) == 1 && tA == 'N' && # reinterpret-based optimization is valid only for contiguous `y` + !iszero(stride(x, 1)) BLAS.gemv!(tA, alpha, reinterpret(T, A), x, beta, reinterpret(T, y)) return y else - return generic_matvecmul!(y, tA, A, x, MulAddMul(α, β)) + return _generic_matvecmul!(y, tA, A, x, MulAddMul(α, β)) end end @@ -535,14 +432,16 @@ function gemv!(y::StridedVector{Complex{T}}, tA::AbstractChar, A::StridedVecOrMa mA == 0 && return y nA == 0 && return _rmul_or_fill!(y, β) alpha, beta = promote(α, β, zero(T)) - @views if alpha isa Union{Bool,T} && beta isa Union{Bool,T} && stride(A, 1) == 1 && stride(A, 2) >= size(A, 1) + @views if alpha isa Union{Bool,T} && beta isa Union{Bool,T} && + stride(A, 1) == 1 && abs(stride(A, 2)) >= size(A, 1) && + !iszero(stride(x, 1)) xfl = reinterpret(reshape, T, x) # Use reshape here. yfl = reinterpret(reshape, T, y) BLAS.gemv!(tA, alpha, A, xfl[1, :], beta, yfl[1, :]) BLAS.gemv!(tA, alpha, A, xfl[2, :], beta, yfl[2, :]) return y else - return generic_matvecmul!(y, tA, A, x, MulAddMul(α, β)) + return _generic_matvecmul!(y, tA, A, x, MulAddMul(α, β)) end end @@ -669,7 +568,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, tA, tB, A, B, _add) end function gemm_wrapper!(C::StridedVecOrMat{Complex{T}}, tA::AbstractChar, tB::AbstractChar, @@ -712,7 +611,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, tA, tB, A, B, _add) end # blas.jl defines matmul for floats; other integer and mixed precision @@ -746,8 +645,12 @@ end # NOTE: the generic version is also called as fallback for # strides != 1 cases -function generic_matvecmul!(C::AbstractVector{R}, tA, A::AbstractVecOrMat, B::AbstractVector, - _add::MulAddMul = MulAddMul()) where R +generic_matvecmul!(C::AbstractVector, tA, A::AbstractVecOrMat, B::AbstractVector, + _add::MulAddMul = MulAddMul()) = + _generic_matvecmul!(C, tA, A, B, _add) + +function _generic_matvecmul!(C::AbstractVector, tA, A::AbstractVecOrMat, B::AbstractVector, + _add::MulAddMul = MulAddMul()) require_one_based_indexing(C, A, B) mB = length(B) mA, nA = lapack_size(tA, A) @@ -803,7 +706,7 @@ function generic_matvecmul!(C::AbstractVector{R}, tA, A::AbstractVecOrMat, B::Ab end for k = 1:mB aoffs = (k-1)*Astride - b = _add(B[k], false) + b = _add(B[k]) for i = 1:mA C[i] += A[aoffs + i] * b end diff --git a/stdlib/LinearAlgebra/src/qr.jl b/stdlib/LinearAlgebra/src/qr.jl index 16e066ed1e030..43d04ac5fa415 100644 --- a/stdlib/LinearAlgebra/src/qr.jl +++ b/stdlib/LinearAlgebra/src/qr.jl @@ -32,7 +32,6 @@ The object has two fields: ``v_i`` is the ``i``th column of the matrix `V = I + tril(F.factors, -1)`. * `τ` is a vector of length `min(m,n)` containing the coefficients ``\tau_i``. - """ struct QR{T,S<:AbstractMatrix{T},C<:AbstractVector{T}} <: Factorization{T} factors::S @@ -49,7 +48,7 @@ QR{T}(factors::AbstractMatrix, τ::AbstractVector) where {T} = QR(convert(AbstractMatrix{T}, factors), convert(AbstractVector{T}, τ)) # backwards-compatible constructors (remove with Julia 2.0) @deprecate(QR{T,S}(factors::AbstractMatrix{T}, τ::AbstractVector{T}) where {T,S}, - QR{T,S,typeof(τ)}(factors, τ)) + QR{T,S,typeof(τ)}(factors, τ), false) # iteration for destructuring into components Base.iterate(S::QR) = (S.Q, Val(:R)) @@ -126,7 +125,7 @@ QRCompactWY{S}(factors::AbstractMatrix, T::AbstractMatrix) where {S} = QRCompactWY(convert(AbstractMatrix{S}, factors), convert(AbstractMatrix{S}, T)) # backwards-compatible constructors (remove with Julia 2.0) @deprecate(QRCompactWY{S,M}(factors::AbstractMatrix{S}, T::AbstractMatrix{S}) where {S,M}, - QRCompactWY{S,M,typeof(T)}(factors, T)) + QRCompactWY{S,M,typeof(T)}(factors, T), false) # iteration for destructuring into components Base.iterate(S::QRCompactWY) = (S.Q, Val(:R)) @@ -219,7 +218,7 @@ QRPivoted{T}(factors::AbstractMatrix, τ::AbstractVector, # backwards-compatible constructors (remove with Julia 2.0) @deprecate(QRPivoted{T,S}(factors::AbstractMatrix{T}, τ::AbstractVector{T}, jpvt::AbstractVector{<:Integer}) where {T,S}, - QRPivoted{T,S,typeof(τ),typeof(jpvt)}(factors, τ, jpvt)) + QRPivoted{T,S,typeof(τ),typeof(jpvt)}(factors, τ, jpvt), false) # iteration for destructuring into components Base.iterate(S::QRPivoted) = (S.Q, Val(:R)) @@ -298,7 +297,7 @@ qr!(A::StridedMatrix{<:BlasFloat}, ::ColumnNorm) = QRPivoted(LAPACK.geqp3!(A)... """ qr!(A, pivot = NoPivot(); blocksize) -`qr!` is the same as [`qr`](@ref) when `A` is a subtype of [`StridedMatrix`](@ref), +`qr!` is the same as [`qr`](@ref) when `A` is a subtype of [`AbstractMatrix`](@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. @@ -314,11 +313,9 @@ julia> a = [1. 2.; 3. 4.] 3.0 4.0 julia> qr!(a) -QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} +LinearAlgebra.QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} Q factor: -2×2 QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}: - -0.316228 -0.948683 - -0.948683 0.316228 +2×2 LinearAlgebra.QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}} R factor: 2×2 Matrix{Float64}: -3.16228 -4.42719 @@ -380,14 +377,14 @@ norm solution. Multiplication with respect to either full/square or non-full/square `Q` is allowed, i.e. both `F.Q*F.R` and `F.Q*A` are supported. A `Q` matrix can be converted into a regular matrix with -[`Matrix`](@ref). This operation returns the "thin" Q factor, i.e., if `A` is `m`×`n` with `m>=n`, then +[`Matrix`](@ref). This operation returns the "thin" Q factor, i.e., if `A` is `m`×`n` with `m>=n`, then `Matrix(F.Q)` yields an `m`×`n` matrix with orthonormal columns. To retrieve the "full" Q factor, an -`m`×`m` orthogonal matrix, use `F.Q*Matrix(I,m,m)`. If `m<=n`, then `Matrix(F.Q)` yields an `m`×`m` +`m`×`m` orthogonal matrix, use `F.Q*I`. If `m<=n`, then `Matrix(F.Q)` yields an `m`×`m` orthogonal matrix. The block size for QR decomposition can be specified by keyword argument `blocksize :: Integer` when `pivot == NoPivot()` and `A isa StridedMatrix{<:BlasFloat}`. -It is ignored when `blocksize > minimum(size(A))`. See [`QRCompactWY`](@ref). +It is ignored when `blocksize > minimum(size(A))`. See [`QRCompactWY`](@ref). !!! compat "Julia 1.4" The `blocksize` keyword argument requires Julia 1.4 or later. @@ -401,12 +398,9 @@ julia> A = [3.0 -6.0; 4.0 -8.0; 0.0 1.0] 0.0 1.0 julia> F = qr(A) -QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} +LinearAlgebra.QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}} Q factor: -3×3 QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}: - -0.6 0.0 0.8 - -0.8 0.0 -0.6 - 0.0 -1.0 0.0 +3×3 LinearAlgebra.QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}} R factor: 2×2 Matrix{Float64}: -5.0 10.0 @@ -514,411 +508,113 @@ end Base.propertynames(F::QRPivoted, private::Bool=false) = (:R, :Q, :p, :P, (private ? fieldnames(typeof(F)) : ())...) -adjoint(F::Union{QR,QRPivoted,QRCompactWY}) = Adjoint(F) - -abstract type AbstractQ{T} <: AbstractMatrix{T} end +transpose(F::Union{QR{<:Real},QRPivoted{<:Real},QRCompactWY{<:Real}}) = F' +transpose(::Union{QR,QRPivoted,QRCompactWY}) = + throw(ArgumentError("transpose of QR decomposition is not supported, consider using adjoint")) -inv(Q::AbstractQ) = Q' - -""" - QRPackedQ <: AbstractMatrix +size(F::Union{QR,QRCompactWY,QRPivoted}) = size(getfield(F, :factors)) +size(F::Union{QR,QRCompactWY,QRPivoted}, dim::Integer) = size(getfield(F, :factors), dim) -The orthogonal/unitary ``Q`` matrix of a QR factorization stored in [`QR`](@ref) or -[`QRPivoted`](@ref) format. -""" -struct QRPackedQ{T,S<:AbstractMatrix{T},C<:AbstractVector{T}} <: AbstractQ{T} - factors::S - τ::C - function QRPackedQ{T,S,C}(factors, τ) where {T,S<:AbstractMatrix{T},C<:AbstractVector{T}} - require_one_based_indexing(factors) - new{T,S,C}(factors, τ) - end +function ldiv!(A::QRCompactWY{T}, b::AbstractVector{T}) where {T} + require_one_based_indexing(b) + m, n = size(A) + ldiv!(UpperTriangular(view(A.factors, 1:min(m,n), 1:n)), view(lmul!(adjoint(A.Q), b), 1:size(A, 2))) + return b +end +function ldiv!(A::QRCompactWY{T}, B::AbstractMatrix{T}) where {T} + require_one_based_indexing(B) + m, n = size(A) + ldiv!(UpperTriangular(view(A.factors, 1:min(m,n), 1:n)), view(lmul!(adjoint(A.Q), B), 1:size(A, 2), 1:size(B, 2))) + return B end -QRPackedQ(factors::AbstractMatrix{T}, τ::AbstractVector{T}) where {T} = - QRPackedQ{T,typeof(factors),typeof(τ)}(factors, τ) -QRPackedQ{T}(factors::AbstractMatrix, τ::AbstractVector) where {T} = - QRPackedQ(convert(AbstractMatrix{T}, factors), convert(AbstractVector{T}, τ)) -# backwards-compatible constructors (remove with Julia 2.0) -@deprecate(QRPackedQ{T,S}(factors::AbstractMatrix{T}, τ::AbstractVector{T}) where {T,S}, - QRPackedQ{T,S,typeof(τ)}(factors, τ)) - -""" - QRCompactWYQ <: AbstractMatrix -The orthogonal/unitary ``Q`` matrix of a QR factorization stored in [`QRCompactWY`](@ref) -format. -""" -struct QRCompactWYQ{S, M<:AbstractMatrix{S}, C<:AbstractMatrix{S}} <: AbstractQ{S} - factors::M - T::C +# Julia implementation similar to xgelsy +function ldiv!(A::QRPivoted{T,<:StridedMatrix}, B::AbstractMatrix{T}, rcond::Real) where {T<:BlasFloat} + require_one_based_indexing(B) + m, n = size(A) - function QRCompactWYQ{S,M,C}(factors, T) where {S,M<:AbstractMatrix{S},C<:AbstractMatrix{S}} - require_one_based_indexing(factors) - new{S,M,C}(factors, T) + 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))")) end -end -QRCompactWYQ(factors::AbstractMatrix{S}, T::AbstractMatrix{S}) where {S} = - QRCompactWYQ{S,typeof(factors),typeof(T)}(factors, T) -QRCompactWYQ{S}(factors::AbstractMatrix, T::AbstractMatrix) where {S} = - QRCompactWYQ(convert(AbstractMatrix{S}, factors), convert(AbstractMatrix{S}, T)) -# backwards-compatible constructors (remove with Julia 2.0) -@deprecate(QRCompactWYQ{S,M}(factors::AbstractMatrix{S}, T::AbstractMatrix{S}) where {S,M}, - QRCompactWYQ{S,M,typeof(T)}(factors, T)) - -QRPackedQ{T}(Q::QRPackedQ) where {T} = QRPackedQ(convert(AbstractMatrix{T}, Q.factors), convert(Vector{T}, Q.τ)) -AbstractMatrix{T}(Q::QRPackedQ{T}) where {T} = Q -AbstractMatrix{T}(Q::QRPackedQ) where {T} = QRPackedQ{T}(Q) -QRCompactWYQ{S}(Q::QRCompactWYQ) where {S} = QRCompactWYQ(convert(AbstractMatrix{S}, Q.factors), convert(AbstractMatrix{S}, Q.T)) -AbstractMatrix{S}(Q::QRCompactWYQ{S}) where {S} = Q -AbstractMatrix{S}(Q::QRCompactWYQ) where {S} = QRCompactWYQ{S}(Q) -Matrix{T}(Q::AbstractQ{S}) where {T,S} = Matrix{T}(lmul!(Q, Matrix{S}(I, size(Q, 1), min(size(Q.factors)...)))) -Matrix(Q::AbstractQ{T}) where {T} = Matrix{T}(Q) -Array{T}(Q::AbstractQ) where {T} = Matrix{T}(Q) -Array(Q::AbstractQ) = Matrix(Q) -size(F::Union{QR,QRCompactWY,QRPivoted}, dim::Integer) = size(getfield(F, :factors), dim) -size(F::Union{QR,QRCompactWY,QRPivoted}) = size(getfield(F, :factors)) -size(Q::AbstractQ, dim::Integer) = size(getfield(Q, :factors), dim == 2 ? 1 : dim) -size(Q::AbstractQ) = size(Q, 1), size(Q, 2) + if length(A.factors) == 0 || length(B) == 0 + return B, 0 + end -copy(Q::AbstractQ{T}) where {T} = lmul!(Q, Matrix{T}(I, size(Q))) -getindex(Q::AbstractQ, inds...) = copy(Q)[inds...] -getindex(Q::AbstractQ, ::Colon, ::Colon) = copy(Q) + @inbounds begin + smin = smax = abs(A.factors[1]) -function getindex(Q::AbstractQ, ::Colon, j::Int) - y = zeros(eltype(Q), size(Q, 2)) - y[j] = 1 - lmul!(Q, y) -end + if smax == 0 + return fill!(B, 0), 0 + end -getindex(Q::AbstractQ, i::Int, j::Int) = Q[:, j][i] + mn = min(m, n) -# specialization avoiding the fallback using slow `getindex` -function copyto!(dest::AbstractMatrix, src::AbstractQ) - copyto!(dest, I) - lmul!(src, dest) -end -# needed to resolve method ambiguities -function copyto!(dest::PermutedDimsArray{T,2,perm}, src::AbstractQ) where {T,perm} - if perm == (1, 2) - copyto!(parent(dest), src) - else - @assert perm == (2, 1) # there are no other permutations of two indices - if T <: Real - copyto!(parent(dest), I) - lmul!(src', parent(dest)) - else - # LAPACK does not offer inplace lmul!(transpose(Q), B) for complex Q - tmp = similar(parent(dest)) - copyto!(tmp, I) - rmul!(tmp, src) - permutedims!(parent(dest), tmp, (2, 1)) - end - end - return dest -end + # allocate temporary work space + tmp = Vector{T}(undef, 2mn) + wmin = view(tmp, 1:mn) + wmax = view(tmp, mn+1:2mn) -## Multiplication by Q -### QB -lmul!(A::QRCompactWYQ{T,S}, B::StridedVecOrMat{T}) where {T<:BlasFloat, S<:StridedMatrix} = - LAPACK.gemqrt!('L', 'N', A.factors, A.T, B) -lmul!(A::QRPackedQ{T,S}, B::StridedVecOrMat{T}) where {T<:BlasFloat, S<:StridedMatrix} = - LAPACK.ormqr!('L', 'N', A.factors, A.τ, B) -function lmul!(A::QRPackedQ, B::AbstractVecOrMat) - require_one_based_indexing(B) - 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)")) - end - Afactors = A.factors - @inbounds begin - for k = min(mA,nA):-1:1 - for j = 1:nB - vBj = B[k,j] - for i = k+1:mB - vBj += conj(Afactors[i,k])*B[i,j] - end - vBj = A.τ[k]*vBj - B[k,j] -= vBj - for i = k+1:mB - B[i,j] -= Afactors[i,k]*vBj - end - end - end - end - B -end + rnk = 1 + wmin[1] = 1 + wmax[1] = 1 -function (*)(A::AbstractQ, b::StridedVector) - TAb = promote_type(eltype(A), eltype(b)) - Anew = convert(AbstractMatrix{TAb}, A) - if size(A.factors, 1) == length(b) - bnew = copy_oftype(b, TAb) - elseif size(A.factors, 2) == length(b) - bnew = [b; zeros(TAb, size(A.factors, 1) - length(b))] - else - throw(DimensionMismatch("vector must have length either $(size(A.factors, 1)) or $(size(A.factors, 2))")) - end - lmul!(Anew, bnew) -end -function (*)(A::AbstractQ, B::StridedMatrix) - TAB = promote_type(eltype(A), eltype(B)) - Anew = convert(AbstractMatrix{TAB}, A) - if size(A.factors, 1) == size(B, 1) - Bnew = copy_oftype(B, TAB) - elseif size(A.factors, 2) == size(B, 1) - Bnew = [B; zeros(TAB, size(A.factors, 1) - size(B,1), size(B, 2))] - else - throw(DimensionMismatch("first dimension of matrix must have size either $(size(A.factors, 1)) or $(size(A.factors, 2))")) - end - lmul!(Anew, Bnew) -end + while rnk < mn + i = rnk + 1 -function (*)(A::AbstractQ, b::Number) - TAb = promote_type(eltype(A), typeof(b)) - dest = similar(A, TAb) - copyto!(dest, b*I) - lmul!(A, dest) -end + smin, s1, c1 = LAPACK.laic1!(2, view(wmin, 1:rnk), smin, view(A.factors, 1:rnk, i), A.factors[i,i]) + smax, s2, c2 = LAPACK.laic1!(1, view(wmax, 1:rnk), smax, view(A.factors, 1:rnk, i), A.factors[i,i]) -### QcB -lmul!(adjA::Adjoint{<:Any,<:QRCompactWYQ{T,S}}, B::StridedVecOrMat{T}) where {T<:BlasReal,S<:StridedMatrix} = - (A = adjA.parent; LAPACK.gemqrt!('L', 'T', A.factors, A.T, B)) -lmul!(adjA::Adjoint{<:Any,<:QRCompactWYQ{T,S}}, B::StridedVecOrMat{T}) where {T<:BlasComplex,S<:StridedMatrix} = - (A = adjA.parent; LAPACK.gemqrt!('L', 'C', A.factors, A.T, B)) -lmul!(adjA::Adjoint{<:Any,<:QRPackedQ{T,S}}, B::StridedVecOrMat{T}) where {T<:BlasReal,S<:StridedMatrix} = - (A = adjA.parent; LAPACK.ormqr!('L', 'T', A.factors, A.τ, B)) -lmul!(adjA::Adjoint{<:Any,<:QRPackedQ{T,S}}, B::StridedVecOrMat{T}) where {T<:BlasComplex,S<:StridedMatrix} = - (A = adjA.parent; LAPACK.ormqr!('L', 'C', A.factors, A.τ, B)) -function lmul!(adjA::Adjoint{<:Any,<:QRPackedQ}, B::AbstractVecOrMat) - require_one_based_indexing(B) - A = adjA.parent - 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)")) - end - Afactors = A.factors - @inbounds begin - for k = 1:min(mA,nA) - for j = 1:nB - vBj = B[k,j] - for i = k+1:mB - vBj += conj(Afactors[i,k])*B[i,j] - end - vBj = conj(A.τ[k])*vBj - B[k,j] -= vBj - for i = k+1:mB - B[i,j] -= Afactors[i,k]*vBj - end + if smax*rcond > smin + break end - end - end - B -end -function *(adjQ::Adjoint{<:Any,<:AbstractQ}, B::StridedVecOrMat) - Q = adjQ.parent - TQB = promote_type(eltype(Q), eltype(B)) - return lmul!(adjoint(convert(AbstractMatrix{TQB}, Q)), copy_oftype(B, TQB)) -end - -### QBc/QcBc -function *(Q::AbstractQ, adjB::Adjoint{<:Any,<:StridedVecOrMat}) - B = adjB.parent - TQB = promote_type(eltype(Q), eltype(B)) - Bc = similar(B, TQB, (size(B, 2), size(B, 1))) - adjoint!(Bc, B) - return lmul!(convert(AbstractMatrix{TQB}, Q), Bc) -end -function *(adjQ::Adjoint{<:Any,<:AbstractQ}, adjB::Adjoint{<:Any,<:StridedVecOrMat}) - Q, B = adjQ.parent, adjB.parent - TQB = promote_type(eltype(Q), eltype(B)) - Bc = similar(B, TQB, (size(B, 2), size(B, 1))) - adjoint!(Bc, B) - return lmul!(adjoint(convert(AbstractMatrix{TQB}, Q)), Bc) -end -### AQ -rmul!(A::StridedVecOrMat{T}, B::QRCompactWYQ{T,S}) where {T<:BlasFloat,S<:StridedMatrix} = - LAPACK.gemqrt!('R', 'N', B.factors, B.T, A) -rmul!(A::StridedVecOrMat{T}, B::QRPackedQ{T,S}) where {T<:BlasFloat,S<:StridedMatrix} = - LAPACK.ormqr!('R', 'N', B.factors, B.τ, A) -function rmul!(A::StridedMatrix,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)")) - end - Qfactors = Q.factors - @inbounds begin - for k = 1:min(mQ,nQ) - for i = 1:mA - vAi = A[i,k] - for j = k+1:mQ - vAi += A[i,j]*Qfactors[j,k] - end - vAi = vAi*Q.τ[k] - A[i,k] -= vAi - for j = k+1:nA - A[i,j] -= vAi*conj(Qfactors[j,k]) - end + for j in 1:rnk + wmin[j] *= s1 + wmax[j] *= s2 end + wmin[i] = c1 + wmax[i] = c2 + + rnk += 1 end - end - A -end -function (*)(A::StridedMatrix, Q::AbstractQ) - TAQ = promote_type(eltype(A), eltype(Q)) + if rnk < n + C, τ = LAPACK.tzrzf!(A.factors[1:rnk, :]) + work = vec(C) + else + C, τ = A.factors, A.τ + work = resize!(tmp, n) + end - return rmul!(copy_oftype(A, TAQ), convert(AbstractMatrix{TAQ}, Q)) -end + lmul!(adjoint(A.Q), view(B, 1:m, :)) + ldiv!(UpperTriangular(view(C, 1:rnk, 1:rnk)), view(B, 1:rnk, :)) -function (*)(a::Number, B::AbstractQ) - TaB = promote_type(typeof(a), eltype(B)) - dest = similar(B, TaB) - copyto!(dest, a*I) - rmul!(dest, B) -end + if rnk < n + B[rnk+1:n,:] .= zero(T) + LAPACK.ormrz!('L', T <: Complex ? 'C' : 'T', C, τ, view(B, 1:n, :)) + end -### AQc -rmul!(A::StridedVecOrMat{T}, adjB::Adjoint{<:Any,<:QRCompactWYQ{T}}) where {T<:BlasReal} = - (B = adjB.parent; LAPACK.gemqrt!('R', 'T', B.factors, B.T, A)) -rmul!(A::StridedVecOrMat{T}, adjB::Adjoint{<:Any,<:QRCompactWYQ{T}}) where {T<:BlasComplex} = - (B = adjB.parent; LAPACK.gemqrt!('R', 'C', B.factors, B.T, A)) -rmul!(A::StridedVecOrMat{T}, adjB::Adjoint{<:Any,<:QRPackedQ{T}}) where {T<:BlasReal} = - (B = adjB.parent; LAPACK.ormqr!('R', 'T', B.factors, B.τ, A)) -rmul!(A::StridedVecOrMat{T}, adjB::Adjoint{<:Any,<:QRPackedQ{T}}) where {T<:BlasComplex} = - (B = adjB.parent; LAPACK.ormqr!('R', 'C', B.factors, B.τ, A)) -function rmul!(A::StridedMatrix, adjQ::Adjoint{<:Any,<:QRPackedQ}) - Q = adjQ.parent - 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)")) - end - Qfactors = Q.factors - @inbounds begin - for k = min(mQ,nQ):-1:1 - for i = 1:mA - vAi = A[i,k] - for j = k+1:mQ - vAi += A[i,j]*Qfactors[j,k] - end - vAi = vAi*conj(Q.τ[k]) - A[i,k] -= vAi - for j = k+1:nA - A[i,j] -= vAi*conj(Qfactors[j,k]) - end + for j in axes(B, 2) + for i in 1:n + work[A.p[i]] = B[i,j] + end + for i in 1:n + B[i,j] = work[i] end end end - A -end -function *(A::StridedMatrix, adjB::Adjoint{<:Any,<:AbstractQ}) - B = adjB.parent - TAB = promote_type(eltype(A),eltype(B)) - BB = convert(AbstractMatrix{TAB}, B) - if size(A,2) == size(B.factors, 1) - AA = copy_similar(A, TAB) - return rmul!(AA, adjoint(BB)) - elseif size(A,2) == size(B.factors,2) - return rmul!([A zeros(TAB, size(A, 1), size(B.factors, 1) - size(B.factors, 2))], adjoint(BB)) - else - throw(DimensionMismatch("matrix A has dimensions $(size(A)) but matrix B has dimensions $(size(B))")) - end -end -*(u::AdjointAbsVec, A::Adjoint{<:Any,<:AbstractQ}) = adjoint(A.parent * u.parent) - -### AcQ/AcQc -function *(adjA::Adjoint{<:Any,<:StridedVecOrMat}, Q::AbstractQ) - A = adjA.parent - TAQ = promote_type(eltype(A), eltype(Q)) - Ac = similar(A, TAQ, (size(A, 2), size(A, 1))) - adjoint!(Ac, A) - return rmul!(Ac, convert(AbstractMatrix{TAQ}, Q)) -end -function *(adjA::Adjoint{<:Any,<:StridedVecOrMat}, adjQ::Adjoint{<:Any,<:AbstractQ}) - A, Q = adjA.parent, adjQ.parent - TAQ = promote_type(eltype(A), eltype(Q)) - Ac = similar(A, TAQ, (size(A, 2), size(A, 1))) - adjoint!(Ac, A) - return rmul!(Ac, adjoint(convert(AbstractMatrix{TAQ}, Q))) -end - -### mul! -function mul!(C::StridedVecOrMat{T}, Q::AbstractQ{T}, B::StridedVecOrMat{T}) where {T} - require_one_based_indexing(C, B) - mB = size(B, 1) - mC = size(C, 1) - if mB < mC - inds = CartesianIndices(B) - copyto!(C, inds, B, inds) - C[CartesianIndices((mB+1:mC, axes(C, 2)))] .= zero(T) - return lmul!(Q, C) - else - return lmul!(Q, copyto!(C, B)) - end + return B, rnk end -mul!(C::StridedVecOrMat{T}, A::StridedVecOrMat{T}, Q::AbstractQ{T}) where {T} = rmul!(copyto!(C, A), Q) -mul!(C::StridedVecOrMat{T}, adjQ::Adjoint{<:Any,<:AbstractQ{T}}, B::StridedVecOrMat{T}) where {T} = lmul!(adjQ, copyto!(C, B)) -mul!(C::StridedVecOrMat{T}, A::StridedVecOrMat{T}, adjQ::Adjoint{<:Any,<:AbstractQ{T}}) where {T} = rmul!(copyto!(C, A), adjQ) -function ldiv!(A::QRCompactWY{T}, b::StridedVector{T}) where {T<:BlasFloat} - m,n = size(A) - ldiv!(UpperTriangular(view(A.factors, 1:min(m,n), 1:n)), view(lmul!(adjoint(A.Q), b), 1:size(A, 2))) - return b -end -function ldiv!(A::QRCompactWY{T}, B::StridedMatrix{T}) where {T<:BlasFloat} - m,n = size(A) - ldiv!(UpperTriangular(view(A.factors, 1:min(m,n), 1:n)), view(lmul!(adjoint(A.Q), B), 1:size(A, 2), 1:size(B, 2))) - return B -end +ldiv!(A::QRPivoted{T,<:StridedMatrix}, B::AbstractVector{T}) where {T<:BlasFloat} = + vec(ldiv!(A, reshape(B, length(B), 1))) +ldiv!(A::QRPivoted{T,<:StridedMatrix}, B::AbstractMatrix{T}) where {T<:BlasFloat} = + ldiv!(A, B, min(size(A)...)*eps(real(T)))[1] -# Julia implementation similar to xgelsy -function ldiv!(A::QRPivoted{T}, B::StridedMatrix{T}, rcond::Real) where T<:BlasFloat - mA, nA = size(A.factors) - nr = min(mA,nA) - nrhs = size(B, 2) - if nr == 0 - return B, 0 - end - ar = abs(A.factors[1]) - if ar == 0 - B[1:nA, :] .= 0 - return B, 0 - end - rnk = 1 - xmin = T[1] - xmax = T[1] - tmin = tmax = ar - while rnk < nr - tmin, smin, cmin = LAPACK.laic1!(2, xmin, tmin, view(A.factors, 1:rnk, rnk + 1), A.factors[rnk + 1, rnk + 1]) - tmax, smax, cmax = LAPACK.laic1!(1, xmax, tmax, view(A.factors, 1:rnk, rnk + 1), A.factors[rnk + 1, rnk + 1]) - tmax*rcond > tmin && break - push!(xmin, cmin) - push!(xmax, cmax) - for i = 1:rnk - xmin[i] *= smin - xmax[i] *= smax - end - rnk += 1 - end - C, τ = LAPACK.tzrzf!(A.factors[1:rnk,:]) - ldiv!(UpperTriangular(C[1:rnk,1:rnk]),view(lmul!(adjoint(A.Q), view(B, 1:mA, 1:nrhs)), 1:rnk, 1:nrhs)) - B[rnk+1:end,:] .= zero(T) - LAPACK.ormrz!('L', eltype(B)<:Complex ? 'C' : 'T', C, τ, view(B,1:nA,1:nrhs)) - B[1:nA,:] = view(B, 1:nA, :)[invperm(A.p),:] - return B, rnk -end -ldiv!(A::QRPivoted{T}, B::StridedVector{T}) where {T<:BlasFloat} = - vec(ldiv!(A,reshape(B,length(B),1))) -ldiv!(A::QRPivoted{T}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = - ldiv!(A, B, min(size(A)...)*eps(real(float(one(eltype(B))))))[1] -function _wide_qr_ldiv!(A::QR{T}, B::StridedMatrix{T}) where T +function _wide_qr_ldiv!(A::QR{T}, B::AbstractMatrix{T}) where T m, n = size(A) minmn = min(m,n) mB, nB = size(B) @@ -949,14 +645,14 @@ function _wide_qr_ldiv!(A::QR{T}, B::StridedMatrix{T}) where T B[m + 1:mB,1:nB] .= zero(T) for j = 1:nB for k = 1:m - vBj = B[k,j] + vBj = B[k,j]' for i = m + 1:n - vBj += B[i,j]*R[k,i]' + vBj += B[i,j]'*R[k,i]' end vBj *= τ[k] - B[k,j] -= vBj + B[k,j] -= vBj' for i = m + 1:n - B[i,j] -= R[k,i]*vBj + B[i,j] -= R[k,i]'*vBj' end end end @@ -966,7 +662,7 @@ function _wide_qr_ldiv!(A::QR{T}, B::StridedMatrix{T}) where T end -function ldiv!(A::QR{T}, B::StridedMatrix{T}) where T +function ldiv!(A::QR{T}, B::AbstractMatrix{T}) where T m, n = size(A) m < n && return _wide_qr_ldiv!(A, B) @@ -975,17 +671,17 @@ function ldiv!(A::QR{T}, B::StridedMatrix{T}) where T ldiv!(UpperTriangular(view(R,1:n,:)), view(B, 1:n, :)) return B end -function ldiv!(A::QR, B::StridedVector) +function ldiv!(A::QR, B::AbstractVector) ldiv!(A, reshape(B, length(B), 1)) return B end -function ldiv!(A::QRPivoted, b::StridedVector) +function ldiv!(A::QRPivoted, b::AbstractVector) ldiv!(QR(A.factors,A.τ), b) b[1:size(A.factors, 2)] = view(b, 1:size(A.factors, 2))[invperm(A.jpvt)] b end -function ldiv!(A::QRPivoted, B::StridedMatrix) +function ldiv!(A::QRPivoted, B::AbstractMatrix) ldiv!(QR(A.factors, A.τ), B) B[1:size(A.factors, 2),:] = view(B, 1:size(A.factors, 2), :)[invperm(A.jpvt),:] B @@ -997,9 +693,9 @@ function _apply_permutation!(F::QRPivoted, B::AbstractVecOrMat) B[1:length(F.p), :] = B[F.p, :] return B end -_apply_permutation!(F::Factorization, B::AbstractVecOrMat) = B +_apply_permutation!(::Factorization, B::AbstractVecOrMat) = B -function ldiv!(Fadj::Adjoint{<:Any,<:Union{QR,QRCompactWY,QRPivoted}}, B::AbstractVecOrMat) +function ldiv!(Fadj::AdjointFactorization{<:Any,<:Union{QR,QRCompactWY,QRPivoted}}, B::AbstractVecOrMat) require_one_based_indexing(B) m, n = size(Fadj) @@ -1058,25 +754,3 @@ end ## Lower priority: Add LQ, QL and RQ factorizations # FIXME! Should add balancing option through xgebal - - -det(Q::QRPackedQ) = _det_tau(Q.τ) - -det(Q::QRCompactWYQ) = - prod(i -> _det_tau(_diagview(Q.T[:, i:min(i + size(Q.T, 1), size(Q.T, 2))])), - 1:size(Q.T, 1):size(Q.T, 2)) - -_diagview(A) = @view A[diagind(A)] - -# Compute `det` from the number of Householder reflections. Handle -# the case `Q.τ` contains zeros. -_det_tau(τs::AbstractVector{<:Real}) = - isodd(count(!iszero, τs)) ? -one(eltype(τs)) : one(eltype(τs)) - -# In complex case, we need to compute the non-unit eigenvalue `λ = 1 - c*τ` -# (where `c = v'v`) of each Householder reflector. As we know that the -# reflector must have the determinant of 1, it must satisfy `abs2(λ) == 1`. -# Combining this with the constraint `c > 0`, it turns out that the eigenvalue -# (hence the determinant) can be computed as `λ = -sign(τ)^2`. -# See: https://github.com/JuliaLang/julia/pull/32887#issuecomment-521935716 -_det_tau(τs) = prod(τ -> iszero(τ) ? one(τ) : -sign(τ)^2, τs) diff --git a/stdlib/LinearAlgebra/src/schur.jl b/stdlib/LinearAlgebra/src/schur.jl index 75cef93ee2f4b..7257544ff872e 100644 --- a/stdlib/LinearAlgebra/src/schur.jl +++ b/stdlib/LinearAlgebra/src/schur.jl @@ -68,7 +68,7 @@ Base.iterate(S::Schur, ::Val{:values}) = (S.values, Val(:done)) Base.iterate(S::Schur, ::Val{:done}) = nothing """ - schur!(A::StridedMatrix) -> F::Schur + schur!(A) -> F::Schur Same as [`schur`](@ref) but uses the input argument `A` as workspace. @@ -102,6 +102,8 @@ julia> A """ schur!(A::StridedMatrix{<:BlasFloat}) = Schur(LinearAlgebra.LAPACK.gees!('V', A)...) +schur!(A::UpperHessenberg{T}) where {T<:BlasFloat} = Schur(LinearAlgebra.LAPACK.hseqr!(parent(A))...) + """ schur(A) -> F::Schur @@ -153,6 +155,7 @@ true ``` """ schur(A::AbstractMatrix{T}) where {T} = schur!(copy_similar(A, eigtype(T))) +schur(A::UpperHessenberg{T}) where {T} = schur!(copy_similar(A, eigtype(T))) function schur(A::RealHermSymComplexHerm) F = eigen(A; sortby=nothing) return Schur(typeof(F.vectors)(Diagonal(F.values)), F.vectors, F.values) @@ -342,8 +345,13 @@ Base.iterate(S::GeneralizedSchur, ::Val{:done}) = nothing Same as [`schur`](@ref) but uses the input matrices `A` and `B` as workspace. """ -schur!(A::StridedMatrix{T}, B::StridedMatrix{T}) where {T<:BlasFloat} = - GeneralizedSchur(LinearAlgebra.LAPACK.gges!('V', 'V', A, B)...) +function schur!(A::StridedMatrix{T}, B::StridedMatrix{T}) where {T<:BlasFloat} + if LAPACK.version() < v"3.6.0" + GeneralizedSchur(LinearAlgebra.LAPACK.gges!('V', 'V', A, B)...) + else + GeneralizedSchur(LinearAlgebra.LAPACK.gges3!('V', 'V', A, B)...) + end +end """ schur(A, B) -> F::GeneralizedSchur diff --git a/stdlib/LinearAlgebra/src/special.jl b/stdlib/LinearAlgebra/src/special.jl index 39b62d5e3ca03..1744a2301f48a 100644 --- a/stdlib/LinearAlgebra/src/special.jl +++ b/stdlib/LinearAlgebra/src/special.jl @@ -2,13 +2,6 @@ # Methods operating on different special matrix types - -# Usually, reducedim_initarray calls similar, which yields a sparse matrix for a -# Diagonal/Bidiagonal/Tridiagonal/SymTridiagonal matrix. However, reducedim should -# yield a dense vector to increase performance. -Base.reducedim_initarray(A::Union{Diagonal,Bidiagonal,Tridiagonal,SymTridiagonal}, region, init, ::Type{R}) where {R} = fill(convert(R, init), Base.reduced_indices(A,region)) - - # Interconversion between special matrix types # conversions from Diagonal to other special matrix types @@ -50,8 +43,8 @@ Bidiagonal(A::AbstractTriangular) = isbanded(A, -1, 0) ? Bidiagonal(diag(A, 0), diag(A, -1), :L) : # is lower bidiagonal throw(ArgumentError("matrix cannot be represented as Bidiagonal")) -_lucopy(A::Bidiagonal, T) = copy_oftype(Tridiagonal(A), T) -_lucopy(A::Diagonal, T) = copy_oftype(Tridiagonal(A), T) +_lucopy(A::Bidiagonal, T) = copymutable_oftype(Tridiagonal(A), T) +_lucopy(A::Diagonal, T) = copymutable_oftype(Tridiagonal(A), T) function _lucopy(A::SymTridiagonal, T) du = copy_similar(_evview(A), T) dl = copy.(transpose.(du)) @@ -62,27 +55,27 @@ end const ConvertibleSpecialMatrix = Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal,AbstractTriangular} const PossibleTriangularMatrix = Union{Diagonal, Bidiagonal, AbstractTriangular} -convert(T::Type{<:Diagonal}, m::ConvertibleSpecialMatrix) = m isa T ? m : - isdiag(m) ? T(m) : throw(ArgumentError("matrix cannot be represented as Diagonal")) -convert(T::Type{<:SymTridiagonal}, m::ConvertibleSpecialMatrix) = m isa T ? m : - issymmetric(m) && isbanded(m, -1, 1) ? T(m) : throw(ArgumentError("matrix cannot be represented as SymTridiagonal")) -convert(T::Type{<:Tridiagonal}, m::ConvertibleSpecialMatrix) = m isa T ? m : - isbanded(m, -1, 1) ? T(m) : throw(ArgumentError("matrix cannot be represented as Tridiagonal")) +convert(::Type{T}, m::ConvertibleSpecialMatrix) where {T<:Diagonal} = m isa T ? m : + isdiag(m) ? T(m)::T : throw(ArgumentError("matrix cannot be represented as Diagonal")) +convert(::Type{T}, m::ConvertibleSpecialMatrix) where {T<:SymTridiagonal} = m isa T ? m : + issymmetric(m) && isbanded(m, -1, 1) ? T(m)::T : throw(ArgumentError("matrix cannot be represented as SymTridiagonal")) +convert(::Type{T}, m::ConvertibleSpecialMatrix) where {T<:Tridiagonal} = m isa T ? m : + isbanded(m, -1, 1) ? T(m)::T : throw(ArgumentError("matrix cannot be represented as Tridiagonal")) -convert(T::Type{<:LowerTriangular}, m::Union{LowerTriangular,UnitLowerTriangular}) = m isa T ? m : T(m) -convert(T::Type{<:UpperTriangular}, m::Union{UpperTriangular,UnitUpperTriangular}) = m isa T ? m : T(m) +convert(::Type{T}, m::Union{LowerTriangular,UnitLowerTriangular}) where {T<:LowerTriangular} = m isa T ? m : T(m)::T +convert(::Type{T}, m::Union{UpperTriangular,UnitUpperTriangular}) where {T<:UpperTriangular} = m isa T ? m : T(m)::T -convert(T::Type{<:LowerTriangular}, m::PossibleTriangularMatrix) = m isa T ? m : - istril(m) ? T(m) : throw(ArgumentError("matrix cannot be represented as LowerTriangular")) -convert(T::Type{<:UpperTriangular}, m::PossibleTriangularMatrix) = m isa T ? m : - istriu(m) ? T(m) : throw(ArgumentError("matrix cannot be represented as UpperTriangular")) +convert(::Type{T}, m::PossibleTriangularMatrix) where {T<:LowerTriangular} = m isa T ? m : + istril(m) ? T(m)::T : throw(ArgumentError("matrix cannot be represented as LowerTriangular")) +convert(::Type{T}, m::PossibleTriangularMatrix) where {T<:UpperTriangular} = m isa T ? m : + istriu(m) ? T(m)::T : throw(ArgumentError("matrix cannot be represented as UpperTriangular")) # Constructs two method definitions taking into account (assumed) commutativity # e.g. @commutative f(x::S, y::T) where {S,T} = x+y is the same is defining # f(x::S, y::T) where {S,T} = x+y # f(y::T, x::S) where {S,T} = f(x, y) macro commutative(myexpr) - @assert myexpr.head===:(=) || myexpr.head===:function # Make sure it is a function definition + @assert Base.is_function_def(myexpr) # Make sure it is a function definition y = copy(myexpr.args[1].args[2:end]) reverse!(y) reversed_call = Expr(:(=), Expr(:call,myexpr.args[1].args[1],y...), myexpr.args[1]) @@ -114,6 +107,37 @@ for op in (:+, :-) end 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, A, B, MulAddMul(alpha, beta)) +mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractTriangular, alpha::Number, beta::Number) = + _mul!(C, A, B, MulAddMul(alpha, beta)) + +function *(H::UpperHessenberg, B::Bidiagonal) + T = promote_op(matprod, eltype(H), eltype(B)) + A = mul!(similar(H, T, size(H)), H, B) + return B.uplo == 'U' ? UpperHessenberg(A) : A +end +function *(B::Bidiagonal, H::UpperHessenberg) + T = promote_op(matprod, eltype(B), eltype(H)) + A = mul!(similar(H, T, size(H)), B, H) + return B.uplo == 'U' ? UpperHessenberg(A) : A +end + +function /(H::UpperHessenberg, B::Bidiagonal) + T = typeof(oneunit(eltype(H))/oneunit(eltype(B))) + A = _rdiv!(similar(H, T, size(H)), H, B) + return B.uplo == 'U' ? UpperHessenberg(A) : A +end + +function \(B::Bidiagonal, H::UpperHessenberg) + T = typeof(oneunit(eltype(B))\oneunit(eltype(H))) + A = ldiv!(similar(H, T, size(H)), B, H) + return B.uplo == 'U' ? UpperHessenberg(A) : A +end + # specialized +/- for structured matrices. If these are removed, it falls # back to broadcasting which has ~2-10x speed regressions. # For the other structure matrix pairs, broadcasting works well. @@ -124,7 +148,7 @@ end # the off diagonal could be a different type after the operation resulting in # an error. See issue #28994 -function (+)(A::Bidiagonal, B::Diagonal) +@commutative function (+)(A::Bidiagonal, B::Diagonal) newdv = A.dv + B.diag Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) end @@ -134,174 +158,123 @@ function (-)(A::Bidiagonal, B::Diagonal) Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) end -function (+)(A::Diagonal, B::Bidiagonal) - newdv = A.diag + B.dv - Bidiagonal(newdv, typeof(newdv)(B.ev), B.uplo) -end - function (-)(A::Diagonal, B::Bidiagonal) - newdv = A.diag-B.dv + newdv = A.diag - B.dv Bidiagonal(newdv, typeof(newdv)(-B.ev), B.uplo) end -function (+)(A::Diagonal, B::SymTridiagonal) - newdv = A.diag+B.dv - SymTridiagonal(A.diag+B.dv, typeof(newdv)(B.ev)) +@commutative function (+)(A::Diagonal, B::SymTridiagonal) + newdv = A.diag + B.dv + SymTridiagonal(A.diag + B.dv, typeof(newdv)(B.ev)) end function (-)(A::Diagonal, B::SymTridiagonal) - newdv = A.diag-B.dv + newdv = A.diag - B.dv SymTridiagonal(newdv, typeof(newdv)(-B.ev)) end -function (+)(A::SymTridiagonal, B::Diagonal) - newdv = A.dv+B.diag - SymTridiagonal(newdv, typeof(newdv)(A.ev)) -end - function (-)(A::SymTridiagonal, B::Diagonal) - newdv = A.dv-B.diag + newdv = A.dv - B.diag SymTridiagonal(newdv, typeof(newdv)(A.ev)) end # this set doesn't have the aforementioned problem -+(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl+_evview(B), A.d+B.dv, A.du+_evview(B)) +@commutative (+)(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl+_evview(B), A.d+B.dv, A.du+_evview(B)) -(A::Tridiagonal, B::SymTridiagonal) = Tridiagonal(A.dl-_evview(B), A.d-B.dv, A.du-_evview(B)) -+(A::SymTridiagonal, B::Tridiagonal) = Tridiagonal(_evview(A)+B.dl, A.dv+B.d, _evview(A)+B.du) -(A::SymTridiagonal, B::Tridiagonal) = Tridiagonal(_evview(A)-B.dl, A.dv-B.d, _evview(A)-B.du) - -function (+)(A::Diagonal, B::Tridiagonal) - newdv = A.diag+B.d +@commutative function (+)(A::Diagonal, B::Tridiagonal) + newdv = A.diag + B.d Tridiagonal(typeof(newdv)(B.dl), newdv, typeof(newdv)(B.du)) end function (-)(A::Diagonal, B::Tridiagonal) - newdv = A.diag-B.d + newdv = A.diag - B.d Tridiagonal(typeof(newdv)(-B.dl), newdv, typeof(newdv)(-B.du)) end -function (+)(A::Tridiagonal, B::Diagonal) - newdv = A.d+B.diag - Tridiagonal(typeof(newdv)(A.dl), newdv, typeof(newdv)(A.du)) -end - function (-)(A::Tridiagonal, B::Diagonal) - newdv = A.d-B.diag + newdv = A.d - B.diag Tridiagonal(typeof(newdv)(A.dl), newdv, typeof(newdv)(A.du)) end -function (+)(A::Bidiagonal, B::Tridiagonal) - newdv = A.dv+B.d +@commutative function (+)(A::Bidiagonal, B::Tridiagonal) + newdv = A.dv + B.d Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(B.dl), newdv, A.ev+B.du) : (A.ev+B.dl, newdv, typeof(newdv)(B.du)))...) end function (-)(A::Bidiagonal, B::Tridiagonal) - newdv = A.dv-B.d + newdv = A.dv - B.d Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(-B.dl), newdv, A.ev-B.du) : (A.ev-B.dl, newdv, typeof(newdv)(-B.du)))...) end -function (+)(A::Tridiagonal, B::Bidiagonal) - newdv = A.d+B.dv - Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(A.dl), newdv, A.du+B.ev) : (A.dl+B.ev, newdv, typeof(newdv)(A.du)))...) -end - function (-)(A::Tridiagonal, B::Bidiagonal) - newdv = A.d-B.dv + newdv = A.d - B.dv Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(A.dl), newdv, A.du-B.ev) : (A.dl-B.ev, newdv, typeof(newdv)(A.du)))...) end -function (+)(A::Bidiagonal, B::SymTridiagonal) - newdv = A.dv+B.dv +@commutative function (+)(A::Bidiagonal, B::SymTridiagonal) + newdv = A.dv + B.dv Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(_evview(B)), A.dv+B.dv, A.ev+_evview(B)) : (A.ev+_evview(B), A.dv+B.dv, typeof(newdv)(_evview(B))))...) end function (-)(A::Bidiagonal, B::SymTridiagonal) - newdv = A.dv-B.dv + newdv = A.dv - B.dv Tridiagonal((A.uplo == 'U' ? (typeof(newdv)(-_evview(B)), newdv, A.ev-_evview(B)) : (A.ev-_evview(B), newdv, typeof(newdv)(-_evview(B))))...) end -function (+)(A::SymTridiagonal, B::Bidiagonal) - newdv = A.dv+B.dv - Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview(A)), newdv, _evview(A)+B.ev) : (_evview(A)+B.ev, newdv, typeof(newdv)(_evview(A))))...) -end - function (-)(A::SymTridiagonal, B::Bidiagonal) - newdv = A.dv-B.dv + newdv = A.dv - B.dv Tridiagonal((B.uplo == 'U' ? (typeof(newdv)(_evview(A)), newdv, _evview(A)-B.ev) : (_evview(A)-B.ev, newdv, typeof(newdv)(_evview(A))))...) end -# fixing uniform scaling problems from #28994 -# {<:Number} is required due to the test case from PR #27289 where eltype is a matrix. - -function (+)(A::Tridiagonal{<:Number}, B::UniformScaling) - newd = A.d .+ B.λ +@commutative function (+)(A::Tridiagonal, B::UniformScaling) + newd = A.d .+ Ref(B) Tridiagonal(typeof(newd)(A.dl), newd, typeof(newd)(A.du)) end -function (+)(A::SymTridiagonal{<:Number}, B::UniformScaling) - newdv = A.dv .+ B.λ +@commutative function (+)(A::SymTridiagonal, B::UniformScaling) + newdv = A.dv .+ Ref(B) SymTridiagonal(newdv, typeof(newdv)(A.ev)) end -function (+)(A::Bidiagonal{<:Number}, B::UniformScaling) - newdv = A.dv .+ B.λ +@commutative function (+)(A::Bidiagonal, B::UniformScaling) + newdv = A.dv .+ Ref(B) Bidiagonal(newdv, typeof(newdv)(A.ev), A.uplo) end -function (+)(A::Diagonal{<:Number}, B::UniformScaling) - Diagonal(A.diag .+ B.λ) -end - -function (+)(A::UniformScaling, B::Tridiagonal{<:Number}) - newd = A.λ .+ B.d - Tridiagonal(typeof(newd)(B.dl), newd, typeof(newd)(B.du)) +@commutative function (+)(A::Diagonal, B::UniformScaling) + Diagonal(A.diag .+ Ref(B)) end -function (+)(A::UniformScaling, B::SymTridiagonal{<:Number}) - newdv = A.λ .+ B.dv - SymTridiagonal(newdv, typeof(newdv)(B.ev)) +# StructuredMatrix - UniformScaling = StructuredMatrix + (-UniformScaling) => +# no need to define reversed order +function (-)(A::UniformScaling, B::Tridiagonal) + d = Ref(A) .- B.d + Tridiagonal(convert(typeof(d), -B.dl), d, convert(typeof(d), -B.du)) end - -function (+)(A::UniformScaling, B::Bidiagonal{<:Number}) - newdv = A.λ .+ B.dv - Bidiagonal(newdv, typeof(newdv)(B.ev), B.uplo) +function (-)(A::UniformScaling, B::SymTridiagonal) + dv = Ref(A) .- B.dv + SymTridiagonal(dv, convert(typeof(dv), -B.ev)) end - -function (+)(A::UniformScaling, B::Diagonal{<:Number}) - Diagonal(A.λ .+ B.diag) +function (-)(A::UniformScaling, B::Bidiagonal) + dv = Ref(A) .- B.dv + Bidiagonal(dv, convert(typeof(dv), -B.ev), B.uplo) end - -function (-)(A::UniformScaling, B::Tridiagonal{<:Number}) - newd = A.λ .- B.d - Tridiagonal(typeof(newd)(-B.dl), newd, typeof(newd)(-B.du)) -end - -function (-)(A::UniformScaling, B::SymTridiagonal{<:Number}) - newdv = A.λ .- B.dv - SymTridiagonal(newdv, typeof(newdv)(-B.ev)) +function (-)(A::UniformScaling, B::Diagonal) + Diagonal(Ref(A) .- B.diag) end -function (-)(A::UniformScaling, B::Bidiagonal{<:Number}) - newdv = A.λ .- B.dv - Bidiagonal(newdv, typeof(newdv)(-B.ev), B.uplo) -end +## Diagonal construction from UniformScaling +Diagonal{T}(s::UniformScaling, m::Integer) where {T} = Diagonal{T}(fill(T(s.λ), m)) +Diagonal(s::UniformScaling, m::Integer) = Diagonal{eltype(s)}(s, m) -function (-)(A::UniformScaling, B::Diagonal{<:Number}) - Diagonal(A.λ .- B.diag) -end +Base.muladd(A::Union{Diagonal, UniformScaling}, B::Union{Diagonal, UniformScaling}, z::Union{Diagonal, UniformScaling}) = + Diagonal(_diag_or_value(A) .* _diag_or_value(B) .+ _diag_or_value(z)) -rmul!(A::AbstractTriangular, adjB::Adjoint{<:Any,<:Union{QRCompactWYQ,QRPackedQ}}) = - rmul!(full!(A), adjB) -*(A::AbstractTriangular, adjB::Adjoint{<:Any,<:Union{QRCompactWYQ,QRPackedQ}}) = - *(copyto!(similar(parent(A)), A), adjB) -*(A::BiTriSym, adjB::Adjoint{<:Any,<:Union{QRCompactWYQ, QRPackedQ}}) = - rmul!(copyto!(Array{promote_type(eltype(A), eltype(adjB))}(undef, size(A)...), A), adjB) -*(adjA::Adjoint{<:Any,<:Union{QRCompactWYQ, QRPackedQ}}, B::Diagonal) = - lmul!(adjA, copyto!(Array{promote_type(eltype(adjA), eltype(B))}(undef, size(B)...), B)) -*(adjA::Adjoint{<:Any,<:Union{QRCompactWYQ, QRPackedQ}}, B::BiTriSym) = - lmul!(adjA, copyto!(Array{promote_type(eltype(adjA), eltype(B))}(undef, size(B)...), B)) +_diag_or_value(A::Diagonal) = A.diag +_diag_or_value(A::UniformScaling) = A.λ # fill[stored]! methods fillstored!(A::Diagonal, x) = (fill!(A.diag, x); A) @@ -332,6 +305,10 @@ end zero(D::Diagonal) = Diagonal(zero.(D.diag)) oneunit(D::Diagonal) = Diagonal(oneunit.(D.diag)) +isdiag(A::HermOrSym{<:Any,<:Diagonal}) = isdiag(parent(A)) +dot(x::AbstractVector, A::RealHermSymComplexSym{<:Real,<:Diagonal}, y::AbstractVector) = + dot(x, A.data, y) + # equals and approx equals methods for structured matrices # SymTridiagonal == Tridiagonal is already defined in tridiag.jl @@ -365,14 +342,12 @@ const _TypedDenseConcatGroup{T} = Union{Vector{T}, Adjoint{T,Vector{T}}, Transpo promote_to_array_type(::Tuple{Vararg{Union{_DenseConcatGroup,UniformScaling}}}) = Matrix -Base._cat(dims, xs::_DenseConcatGroup...) = Base.cat_t(promote_eltype(xs...), xs...; dims=dims) -vcat(A::Vector...) = Base.typed_vcat(promote_eltype(A...), A...) +Base._cat(dims, xs::_DenseConcatGroup...) = Base._cat_t(dims, promote_eltype(xs...), xs...) vcat(A::_DenseConcatGroup...) = Base.typed_vcat(promote_eltype(A...), A...) -hcat(A::Vector...) = Base.typed_hcat(promote_eltype(A...), A...) hcat(A::_DenseConcatGroup...) = Base.typed_hcat(promote_eltype(A...), A...) hvcat(rows::Tuple{Vararg{Int}}, xs::_DenseConcatGroup...) = Base.typed_hvcat(promote_eltype(xs...), rows, xs...) # For performance, specially handle the case where the matrices/vectors have homogeneous eltype -Base._cat(dims, xs::_TypedDenseConcatGroup{T}...) where {T} = Base.cat_t(T, xs...; dims=dims) +Base._cat(dims, xs::_TypedDenseConcatGroup{T}...) where {T} = Base._cat_t(dims, T, xs...) vcat(A::_TypedDenseConcatGroup{T}...) where {T} = Base.typed_vcat(T, A...) hcat(A::_TypedDenseConcatGroup{T}...) where {T} = Base.typed_hcat(T, A...) hvcat(rows::Tuple{Vararg{Int}}, xs::_TypedDenseConcatGroup{T}...) where {T} = Base.typed_hvcat(T, rows, xs...) diff --git a/stdlib/LinearAlgebra/src/structuredbroadcast.jl b/stdlib/LinearAlgebra/src/structuredbroadcast.jl index 95a1842702291..02e39b199679b 100644 --- a/stdlib/LinearAlgebra/src/structuredbroadcast.jl +++ b/stdlib/LinearAlgebra/src/structuredbroadcast.jl @@ -9,35 +9,41 @@ 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} -Broadcast.BroadcastStyle(::Type{T}) where {T<:StructuredMatrix} = StructuredMatrixStyle{T}() +for ST in Base.uniontypes(StructuredMatrix) + @eval Broadcast.BroadcastStyle(::Type{<:$ST}) = $(StructuredMatrixStyle{ST}()) +end # Promotion of broadcasts between structured matrices. This is slightly unusual # as we define them symmetrically. This allows us to have a fallback to DefaultArrayStyle{2}(). # Diagonal can cavort with all the other structured matrix types. # Bidiagonal doesn't know if it's upper or lower, so it becomes Tridiagonal -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Diagonal}, ::StructuredMatrixStyle{<:Diagonal}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Diagonal}, ::StructuredMatrixStyle{Diagonal}) = StructuredMatrixStyle{Diagonal}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Diagonal}, ::StructuredMatrixStyle{<:Union{Bidiagonal,SymTridiagonal,Tridiagonal}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Diagonal}, ::StructuredMatrixStyle{Bidiagonal}) = + StructuredMatrixStyle{Bidiagonal}() +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Diagonal}, ::StructuredMatrixStyle{<:Union{SymTridiagonal,Tridiagonal}}) = StructuredMatrixStyle{Tridiagonal}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Diagonal}, ::StructuredMatrixStyle{<:Union{LowerTriangular,UnitLowerTriangular}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Diagonal}, ::StructuredMatrixStyle{<:Union{LowerTriangular,UnitLowerTriangular}}) = StructuredMatrixStyle{LowerTriangular}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Diagonal}, ::StructuredMatrixStyle{<:Union{UpperTriangular,UnitUpperTriangular}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Diagonal}, ::StructuredMatrixStyle{<:Union{UpperTriangular,UnitUpperTriangular}}) = StructuredMatrixStyle{UpperTriangular}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Bidiagonal}, ::StructuredMatrixStyle{<:Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Bidiagonal}, ::StructuredMatrixStyle{Diagonal}) = + StructuredMatrixStyle{Bidiagonal}() +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Bidiagonal}, ::StructuredMatrixStyle{<:Union{Bidiagonal,SymTridiagonal,Tridiagonal}}) = StructuredMatrixStyle{Tridiagonal}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:SymTridiagonal}, ::StructuredMatrixStyle{<:Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{SymTridiagonal}, ::StructuredMatrixStyle{<:Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal}}) = StructuredMatrixStyle{Tridiagonal}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Tridiagonal}, ::StructuredMatrixStyle{<:Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{Tridiagonal}, ::StructuredMatrixStyle{<:Union{Diagonal,Bidiagonal,SymTridiagonal,Tridiagonal}}) = StructuredMatrixStyle{Tridiagonal}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:LowerTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,LowerTriangular,UnitLowerTriangular}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{LowerTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,LowerTriangular,UnitLowerTriangular}}) = StructuredMatrixStyle{LowerTriangular}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:UpperTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,UpperTriangular,UnitUpperTriangular}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{UpperTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,UpperTriangular,UnitUpperTriangular}}) = StructuredMatrixStyle{UpperTriangular}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:UnitLowerTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,LowerTriangular,UnitLowerTriangular}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{UnitLowerTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,LowerTriangular,UnitLowerTriangular}}) = StructuredMatrixStyle{LowerTriangular}() -Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:UnitUpperTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,UpperTriangular,UnitUpperTriangular}}) = +Broadcast.BroadcastStyle(::StructuredMatrixStyle{UnitUpperTriangular}, ::StructuredMatrixStyle{<:Union{Diagonal,UpperTriangular,UnitUpperTriangular}}) = StructuredMatrixStyle{UpperTriangular}() Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Union{LowerTriangular,UnitLowerTriangular}}, ::StructuredMatrixStyle{<:Union{UpperTriangular,UnitUpperTriangular}}) = @@ -45,17 +51,17 @@ Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Union{LowerTriangular,UnitLow Broadcast.BroadcastStyle(::StructuredMatrixStyle{<:Union{UpperTriangular,UnitUpperTriangular}}, ::StructuredMatrixStyle{<:Union{LowerTriangular,UnitLowerTriangular}}) = StructuredMatrixStyle{Matrix}() -# Make sure that `StructuredMatrixStyle{<:Matrix}` doesn't ever end up falling +# Make sure that `StructuredMatrixStyle{Matrix}` doesn't ever end up falling # through and give back `DefaultArrayStyle{2}` -Broadcast.BroadcastStyle(T::StructuredMatrixStyle{<:Matrix}, ::StructuredMatrixStyle) = T -Broadcast.BroadcastStyle(::StructuredMatrixStyle, T::StructuredMatrixStyle{<:Matrix}) = T -Broadcast.BroadcastStyle(T::StructuredMatrixStyle{<:Matrix}, ::StructuredMatrixStyle{<:Matrix}) = T +Broadcast.BroadcastStyle(T::StructuredMatrixStyle{Matrix}, ::StructuredMatrixStyle) = T +Broadcast.BroadcastStyle(::StructuredMatrixStyle, T::StructuredMatrixStyle{Matrix}) = T +Broadcast.BroadcastStyle(T::StructuredMatrixStyle{Matrix}, ::StructuredMatrixStyle{Matrix}) = T # All other combinations fall back to the default style Broadcast.BroadcastStyle(::StructuredMatrixStyle, ::StructuredMatrixStyle) = DefaultArrayStyle{2}() # And a definition akin to similar using the structured type: -structured_broadcast_alloc(bc, ::Type{<:Diagonal}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{Diagonal}, ::Type{ElType}, n) where {ElType} = Diagonal(Array{ElType}(undef, n)) # Bidiagonal is tricky as we need to know if it's upper or lower. The promotion # system will return Tridiagonal when there's more than one Bidiagonal, but when @@ -67,9 +73,9 @@ merge_uplos(a, b) = a == b ? a : 'T' find_uplo(a::Bidiagonal) = a.uplo find_uplo(a) = nothing -find_uplo(bc::Broadcasted) = mapreduce(find_uplo, merge_uplos, bc.args, init=nothing) +find_uplo(bc::Broadcasted) = mapfoldl(find_uplo, merge_uplos, Broadcast.cat_nested(bc), init=nothing) -function structured_broadcast_alloc(bc, ::Type{<:Bidiagonal}, ::Type{ElType}, n) where {ElType} +function structured_broadcast_alloc(bc, ::Type{Bidiagonal}, ::Type{ElType}, n) where {ElType} uplo = n > 0 ? find_uplo(bc) : 'U' n1 = max(n - 1, 0) if uplo == 'T' @@ -77,19 +83,19 @@ function structured_broadcast_alloc(bc, ::Type{<:Bidiagonal}, ::Type{ElType}, n) end return Bidiagonal(Array{ElType}(undef, n),Array{ElType}(undef, n1), uplo) end -structured_broadcast_alloc(bc, ::Type{<:SymTridiagonal}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{SymTridiagonal}, ::Type{ElType}, n) where {ElType} = SymTridiagonal(Array{ElType}(undef, n),Array{ElType}(undef, n-1)) -structured_broadcast_alloc(bc, ::Type{<:Tridiagonal}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{Tridiagonal}, ::Type{ElType}, n) where {ElType} = Tridiagonal(Array{ElType}(undef, n-1),Array{ElType}(undef, n),Array{ElType}(undef, n-1)) -structured_broadcast_alloc(bc, ::Type{<:LowerTriangular}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{LowerTriangular}, ::Type{ElType}, n) where {ElType} = LowerTriangular(Array{ElType}(undef, n, n)) -structured_broadcast_alloc(bc, ::Type{<:UpperTriangular}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{UpperTriangular}, ::Type{ElType}, n) where {ElType} = UpperTriangular(Array{ElType}(undef, n, n)) -structured_broadcast_alloc(bc, ::Type{<:UnitLowerTriangular}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{UnitLowerTriangular}, ::Type{ElType}, n) where {ElType} = UnitLowerTriangular(Array{ElType}(undef, n, n)) -structured_broadcast_alloc(bc, ::Type{<:UnitUpperTriangular}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{UnitUpperTriangular}, ::Type{ElType}, n) where {ElType} = UnitUpperTriangular(Array{ElType}(undef, n, n)) -structured_broadcast_alloc(bc, ::Type{<:Matrix}, ::Type{ElType}, n) where {ElType} = +structured_broadcast_alloc(bc, ::Type{Matrix}, ::Type{ElType}, n) where {ElType} = Matrix(Array{ElType}(undef, n, n)) # A _very_ limited list of structure-preserving functions known at compile-time. This list is @@ -126,6 +132,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) 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 @@ -151,83 +158,91 @@ function Base.similar(bc::Broadcasted{StructuredMatrixStyle{T}}, ::Type{ElType}) return similar(convert(Broadcasted{DefaultArrayStyle{ndims(bc)}}, bc), ElType) end +isvalidstructbc(dest, bc::Broadcasted{T}) where {T<:StructuredMatrixStyle} = + Broadcast.combine_styles(dest, bc) === Broadcast.combine_styles(dest) && + (isstructurepreserving(bc) || fzeropreserving(bc)) + +isvalidstructbc(dest::Bidiagonal, bc::Broadcasted{StructuredMatrixStyle{Bidiagonal}}) = + (size(dest, 1) < 2 || find_uplo(bc) == dest.uplo) && + (isstructurepreserving(bc) || fzeropreserving(bc)) + function copyto!(dest::Diagonal, bc::Broadcasted{<:StructuredMatrixStyle}) - !isstructurepreserving(bc) && !fzeropreserving(bc) && return copyto!(dest, convert(Broadcasted{Nothing}, bc)) + isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.diag[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.diag[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) end return dest end function copyto!(dest::Bidiagonal, bc::Broadcasted{<:StructuredMatrixStyle}) - !isstructurepreserving(bc) && !fzeropreserving(bc) && return copyto!(dest, convert(Broadcasted{Nothing}, bc)) + isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.dv[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.dv[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) end if dest.uplo == 'U' for i = 1:size(dest, 1)-1 - dest.ev[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) + dest.ev[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) end else for i = 1:size(dest, 1)-1 - dest.ev[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i)) + dest.ev[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i)) end end return dest end function copyto!(dest::SymTridiagonal, bc::Broadcasted{<:StructuredMatrixStyle}) - !isstructurepreserving(bc) && !fzeropreserving(bc) && return copyto!(dest, convert(Broadcasted{Nothing}, bc)) + isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.dv[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.dv[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) end for i = 1:size(dest, 1)-1 - v = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) - v == 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 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)")) dest.ev[i] = v end return dest end function copyto!(dest::Tridiagonal, bc::Broadcasted{<:StructuredMatrixStyle}) - !isstructurepreserving(bc) && !fzeropreserving(bc) && return copyto!(dest, convert(Broadcasted{Nothing}, bc)) + isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for i in axs[1] - dest.d[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) + dest.d[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i)) end for i = 1:size(dest, 1)-1 - dest.du[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) - dest.dl[i] = Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i)) + dest.du[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i, i+1)) + dest.dl[i] = @inbounds Broadcast._broadcast_getindex(bc, CartesianIndex(i+1, i)) end return dest end function copyto!(dest::LowerTriangular, bc::Broadcasted{<:StructuredMatrixStyle}) - !isstructurepreserving(bc) && !fzeropreserving(bc) && return copyto!(dest, convert(Broadcasted{Nothing}, bc)) + isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for j in axs[2] for i in j:axs[1][end] - dest.data[i,j] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, j)) + @inbounds dest.data[i,j] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, j)) end end return dest end function copyto!(dest::UpperTriangular, bc::Broadcasted{<:StructuredMatrixStyle}) - !isstructurepreserving(bc) && !fzeropreserving(bc) && return copyto!(dest, convert(Broadcasted{Nothing}, bc)) + isvalidstructbc(dest, bc) || return copyto!(dest, convert(Broadcasted{Nothing}, bc)) axs = axes(dest) axes(bc) == axs || Broadcast.throwdm(axes(bc), axs) for j in axs[2] for i in 1:j - dest.data[i,j] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, j)) + @inbounds dest.data[i,j] = Broadcast._broadcast_getindex(bc, CartesianIndex(i, j)) end end return dest diff --git a/stdlib/LinearAlgebra/src/svd.jl b/stdlib/LinearAlgebra/src/svd.jl index 15fcdd4dee9c8..c1b886f616f02 100644 --- a/stdlib/LinearAlgebra/src/svd.jl +++ b/stdlib/LinearAlgebra/src/svd.jl @@ -26,10 +26,10 @@ julia> F = svd(A) SVD{Float64, Float64, Matrix{Float64}, Vector{Float64}} U factor: 4×4 Matrix{Float64}: - 0.0 1.0 0.0 0.0 - 1.0 0.0 0.0 0.0 - 0.0 0.0 0.0 -1.0 - 0.0 0.0 1.0 0.0 + 0.0 1.0 0.0 0.0 + 1.0 0.0 0.0 0.0 + 0.0 0.0 0.0 1.0 + 0.0 0.0 -1.0 0.0 singular values: 4-element Vector{Float64}: 3.0 @@ -38,10 +38,10 @@ singular values: 0.0 Vt factor: 4×5 Matrix{Float64}: - -0.0 0.0 1.0 -0.0 0.0 - 0.447214 0.0 0.0 0.0 0.894427 - -0.0 1.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.447214 0.0 0.0 0.0 0.894427 + 0.0 -1.0 0.0 0.0 0.0 + 0.0 0.0 0.0 1.0 0.0 julia> F.U * Diagonal(F.S) * F.Vt 4×5 Matrix{Float64}: @@ -175,11 +175,11 @@ julia> Uonly == U true ``` """ -function svd(A::StridedVecOrMat{T}; full::Bool = false, alg::Algorithm = default_svd_alg(A)) where {T} - svd!(copy_oftype(A, eigtype(T)), full = full, alg = alg) +function svd(A::AbstractVecOrMat{T}; full::Bool = false, alg::Algorithm = default_svd_alg(A)) where {T} + svd!(eigencopy_oftype(A, eigtype(T)), full = full, alg = alg) end -function svd(A::StridedVecOrMat{T}; full::Bool = false, alg::Algorithm = default_svd_alg(A)) where {T <: Union{Float16,Complex{Float16}}} - A = svd!(copy_oftype(A, eigtype(T)), full = full, alg = alg) +function svd(A::AbstractVecOrMat{T}; full::Bool = false, alg::Algorithm = default_svd_alg(A)) where {T <: Union{Float16,Complex{Float16}}} + A = svd!(eigencopy_oftype(A, eigtype(T)), full = full, alg = alg) return SVD{T}(A) end function svd(x::Number; full::Bool = false, alg::Algorithm = default_svd_alg(x)) @@ -213,7 +213,6 @@ Base.propertynames(F::SVD, private::Bool=false) = Return the singular values of `A`, saving space by overwriting the input. See also [`svdvals`](@ref) and [`svd`](@ref). -``` """ svdvals!(A::StridedMatrix{T}) where {T<:BlasFloat} = isempty(A) ? zeros(real(T), 0) : LAPACK.gesdd!('N', A)[2] svdvals!(A::StridedVector{T}) where {T<:BlasFloat} = svdvals!(reshape(A, (length(A), 1))) @@ -240,15 +239,13 @@ julia> svdvals(A) 0.0 ``` """ -svdvals(A::AbstractMatrix{T}) where {T} = svdvals!(copy_oftype(A, eigtype(T))) +svdvals(A::AbstractMatrix{T}) where {T} = svdvals!(eigencopy_oftype(A, eigtype(T))) svdvals(A::AbstractVector{T}) where {T} = [convert(eigtype(T), norm(A))] -svdvals(A::AbstractMatrix{<:BlasFloat}) = svdvals!(copy(A)) -svdvals(A::AbstractVector{<:BlasFloat}) = [norm(A)] svdvals(x::Number) = abs(x) svdvals(S::SVD{<:Any,T}) where {T} = (S.S)::Vector{T} ### SVD least squares ### -function ldiv!(A::SVD{T}, B::StridedVecOrMat) where T +function ldiv!(A::SVD{T}, B::AbstractVecOrMat) where T m, n = size(A) k = searchsortedlast(A.S, eps(real(T))*A.S[1], rev=true) mul!(view(B, 1:n, :), view(A.Vt, 1:k, :)', view(A.S, 1:k) .\ (view(A.U, :, 1:k)' * _cut_B(B, 1:m))) @@ -404,7 +401,8 @@ function svd!(A::StridedMatrix{T}, B::StridedMatrix{T}) where T<:BlasFloat end GeneralizedSVD(U, V, Q, a, b, Int(k), Int(l), R) end -svd(A::StridedMatrix{T}, B::StridedMatrix{T}) where {T<:BlasFloat} = svd!(copy(A),copy(B)) +svd(A::AbstractMatrix{T}, B::AbstractMatrix{T}) where {T<:BlasFloat} = + svd!(copy_similar(A, T), copy_similar(B, T)) """ @@ -457,9 +455,9 @@ julia> U == Uonly true ``` """ -function svd(A::StridedMatrix{TA}, B::StridedMatrix{TB}) where {TA,TB} +function svd(A::AbstractMatrix{TA}, B::AbstractMatrix{TB}) where {TA,TB} S = promote_type(eigtype(TA),TB) - return svd!(copy_oftype(A, S), copy_oftype(B, S)) + return svd!(copy_similar(A, S), copy_similar(B, S)) end # This method can be heavily optimized but it is probably not critical # and might introduce bugs or inconsistencies relative to the 1x1 matrix @@ -541,7 +539,6 @@ function svdvals!(A::StridedMatrix{T}, B::StridedMatrix{T}) where T<:BlasFloat end a[1:k + l] ./ b[1:k + l] end -svdvals(A::StridedMatrix{T},B::StridedMatrix{T}) where {T<:BlasFloat} = svdvals!(copy(A),copy(B)) """ svdvals(A, B) @@ -567,9 +564,9 @@ julia> svdvals(A, B) 1.0 ``` """ -function svdvals(A::StridedMatrix{TA}, B::StridedMatrix{TB}) where {TA,TB} +function svdvals(A::AbstractMatrix{TA}, B::AbstractMatrix{TB}) where {TA,TB} S = promote_type(eigtype(TA), TB) - return svdvals!(copy_oftype(A, S), copy_oftype(B, S)) + return svdvals!(copy_similar(A, S), copy_similar(B, S)) end svdvals(x::Number, y::Number) = abs(x/y) diff --git a/stdlib/LinearAlgebra/src/symmetric.jl b/stdlib/LinearAlgebra/src/symmetric.jl index 7347dd6f78639..f96ca812ea0ec 100644 --- a/stdlib/LinearAlgebra/src/symmetric.jl +++ b/stdlib/LinearAlgebra/src/symmetric.jl @@ -17,34 +17,45 @@ end Construct a `Symmetric` view of the upper (if `uplo = :U`) or lower (if `uplo = :L`) triangle of the matrix `A`. +`Symmetric` views are mainly useful for real-symmetric matrices, for which +specialized algorithms (e.g. for eigenproblems) are enabled for `Symmetric` types. +More generally, see also [`Hermitian(A)`](@ref) for Hermitian matrices `A == A'`, which +is effectively equivalent to `Symmetric` for real matrices but is also useful for +complex matrices. (Whereas complex `Symmetric` matrices are supported but have few +if any specialized algorithms.) + +To compute the symmetric part of a real matrix, or more generally the Hermitian part `(A + A') / 2` of +a real or complex matrix `A`, use [`hermitianpart`](@ref). + # Examples ```jldoctest -julia> A = [1 0 2 0 3; 0 4 0 5 0; 6 0 7 0 8; 0 9 0 1 0; 2 0 3 0 4] -5×5 Matrix{Int64}: - 1 0 2 0 3 - 0 4 0 5 0 - 6 0 7 0 8 - 0 9 0 1 0 - 2 0 3 0 4 +julia> A = [1 2 3; 4 5 6; 7 8 9] +3×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 julia> Supper = Symmetric(A) -5×5 Symmetric{Int64, Matrix{Int64}}: - 1 0 2 0 3 - 0 4 0 5 0 - 2 0 7 0 8 - 0 5 0 1 0 - 3 0 8 0 4 +3×3 Symmetric{Int64, Matrix{Int64}}: + 1 2 3 + 2 5 6 + 3 6 9 julia> Slower = Symmetric(A, :L) -5×5 Symmetric{Int64, Matrix{Int64}}: - 1 0 6 0 2 - 0 4 0 9 0 - 6 0 7 0 3 - 0 9 0 1 0 - 2 0 3 0 4 +3×3 Symmetric{Int64, Matrix{Int64}}: + 1 4 7 + 4 5 8 + 7 8 9 + +julia> hermitianpart(A) +3×3 Hermitian{Float64, Matrix{Float64}}: + 1.0 3.0 5.0 + 3.0 5.0 7.0 + 5.0 7.0 9.0 ``` -Note that `Supper` will not be equal to `Slower` unless `A` is itself symmetric (e.g. if `A == transpose(A)`). +Note that `Supper` will not be equal to `Slower` unless `A` is itself symmetric (e.g. if +`A == transpose(A)`). """ function Symmetric(A::AbstractMatrix, uplo::Symbol=:U) checksquare(A) @@ -99,25 +110,33 @@ end Construct a `Hermitian` view of the upper (if `uplo = :U`) or lower (if `uplo = :L`) triangle of the matrix `A`. +To compute the Hermitian part of `A`, use [`hermitianpart`](@ref). + # Examples ```jldoctest -julia> A = [1 0 2+2im 0 3-3im; 0 4 0 5 0; 6-6im 0 7 0 8+8im; 0 9 0 1 0; 2+2im 0 3-3im 0 4]; +julia> A = [1 2+2im 3-3im; 4 5 6-6im; 7 8+8im 9] +3×3 Matrix{Complex{Int64}}: + 1+0im 2+2im 3-3im + 4+0im 5+0im 6-6im + 7+0im 8+8im 9+0im julia> Hupper = Hermitian(A) -5×5 Hermitian{Complex{Int64}, Matrix{Complex{Int64}}}: - 1+0im 0+0im 2+2im 0+0im 3-3im - 0+0im 4+0im 0+0im 5+0im 0+0im - 2-2im 0+0im 7+0im 0+0im 8+8im - 0+0im 5+0im 0+0im 1+0im 0+0im - 3+3im 0+0im 8-8im 0+0im 4+0im +3×3 Hermitian{Complex{Int64}, Matrix{Complex{Int64}}}: + 1+0im 2+2im 3-3im + 2-2im 5+0im 6-6im + 3+3im 6+6im 9+0im julia> Hlower = Hermitian(A, :L) -5×5 Hermitian{Complex{Int64}, Matrix{Complex{Int64}}}: - 1+0im 0+0im 6+6im 0+0im 2-2im - 0+0im 4+0im 0+0im 9+0im 0+0im - 6-6im 0+0im 7+0im 0+0im 3+3im - 0+0im 9+0im 0+0im 1+0im 0+0im - 2+2im 0+0im 3-3im 0+0im 4+0im +3×3 Hermitian{Complex{Int64}, Matrix{Complex{Int64}}}: + 1+0im 4+0im 7+0im + 4+0im 5+0im 8-8im + 7+0im 8+8im 9+0im + +julia> hermitianpart(A) +3×3 Hermitian{ComplexF64, Matrix{ComplexF64}}: + 1.0+0.0im 3.0+1.0im 5.0-1.5im + 3.0-1.0im 5.0+0.0im 7.0-7.0im + 5.0+1.5im 7.0+7.0im 9.0+0.0im ``` Note that `Hupper` will not be equal to `Hlower` unless `A` is itself Hermitian (e.g. if `A == adjoint(A)`). @@ -192,8 +211,8 @@ for (S, H) in ((:Symmetric, :Hermitian), (:Hermitian, :Symmetric)) end end -convert(T::Type{<:Symmetric}, m::Union{Symmetric,Hermitian}) = m isa T ? m : T(m) -convert(T::Type{<:Hermitian}, m::Union{Symmetric,Hermitian}) = m isa T ? m : T(m) +convert(::Type{T}, m::Union{Symmetric,Hermitian}) where {T<:Symmetric} = m isa T ? m : T(m)::T +convert(::Type{T}, m::Union{Symmetric,Hermitian}) where {T<:Hermitian} = m isa T ? m : T(m)::T const HermOrSym{T, S} = Union{Hermitian{T,S}, Symmetric{T,S}} const RealHermSym{T<:Real,S} = Union{Hermitian{T,S}, Symmetric{T,S}} @@ -241,6 +260,8 @@ end 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)) + # 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. similar(A::Symmetric, ::Type{T}) where {T} = Symmetric(similar(parent(A), T), ifelse(A.uplo == 'U', :U, :L)) @@ -316,6 +337,7 @@ function fillstored!(A::HermOrSym{T}, x) where T return A end +Base.isreal(A::HermOrSym{<:Real}) = true function Base.isreal(A::HermOrSym) n = size(A, 1) @inbounds if A.uplo == 'U' @@ -363,6 +385,7 @@ Base.copy(A::Adjoint{<:Any,<:Symmetric}) = Base.copy(A::Transpose{<:Any,<:Hermitian}) = Hermitian(copy(transpose(A.parent.data)), ifelse(A.parent.uplo == 'U', :L, :U)) +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) @@ -577,9 +600,11 @@ end function dot(x::AbstractVector, A::RealHermSymComplexHerm, y::AbstractVector) require_one_based_indexing(x, y) - (length(x) == length(y) == size(A, 1)) || throw(DimensionMismatch()) + n = length(x) + (n == length(y) == size(A, 1)) || throw(DimensionMismatch()) data = A.data - r = zero(eltype(x)) * zero(eltype(A)) * zero(eltype(y)) + r = dot(zero(eltype(x)), zero(eltype(A)), zero(eltype(y))) + iszero(n) && return r if A.uplo == 'U' @inbounds for j = 1:length(y) r += dot(x[j], real(data[j,j]), y[j]) @@ -611,7 +636,9 @@ end factorize(A::HermOrSym) = _factorize(A) function _factorize(A::HermOrSym{T}; check::Bool=true) where T TT = typeof(sqrt(oneunit(T))) - if TT <: BlasFloat + if isdiag(A) + return Diagonal(A) + elseif TT <: BlasFloat return bunchkaufman(A; check=check) else # fallback return lu(A; check=check) @@ -622,10 +649,10 @@ 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)) -\(A::HermOrSym{<:Any,<:StridedMatrix}, B::AbstractVector) = \(factorize(A), B) +\(A::HermOrSym, B::AbstractVector) = \(factorize(A), B) # Bunch-Kaufman solves can not utilize BLAS-3 for multiple right hand sides # so using LU is faster for AbstractMatrix right hand side -\(A::HermOrSym{<:Any,<:StridedMatrix}, B::AbstractMatrix) = \(lu(A), B) +\(A::HermOrSym, B::AbstractMatrix) = \(isdiag(A) ? Diagonal(A) : lu(A), B) function _inv(A::HermOrSym) n = checksquare(A) @@ -643,6 +670,7 @@ function _inv(A::HermOrSym) end B end +# StridedMatrix restriction seems necessary due to inv! call in _inv above inv(A::Hermitian{<:Any,<:StridedMatrix}) = Hermitian(_inv(A), sym_uplo(A.uplo)) inv(A::Symmetric{<:Any,<:StridedMatrix}) = Symmetric(_inv(A), sym_uplo(A.uplo)) @@ -854,3 +882,49 @@ for func in (:log, :sqrt) end end end + +""" + hermitianpart(A, uplo=:U) -> Hermitian + +Return the Hermitian part of the square matrix `A`, defined as `(A + A') / 2`, as a +[`Hermitian`](@ref) matrix. For real matrices `A`, this is also known as the symmetric part +of `A`; it is also sometimes called the "operator real part". The optional argument `uplo` controls the corresponding argument of the +[`Hermitian`](@ref) view. For real matrices, the latter is equivalent to a +[`Symmetric`](@ref) view. + +See also [`hermitianpart!`](@ref) for the corresponding in-place operation. + +!!! compat "Julia 1.10" + This function requires Julia 1.10 or later. +""" +hermitianpart(A::AbstractMatrix, uplo::Symbol=:U) = Hermitian(_hermitianpart(A), uplo) + +""" + hermitianpart!(A, uplo=:U) -> Hermitian + +Overwrite the square matrix `A` in-place with its Hermitian part `(A + A') / 2`, and return +[`Hermitian(A, uplo)`](@ref). For real matrices `A`, this is also known as the symmetric +part of `A`. + +See also [`hermitianpart`](@ref) for the corresponding out-of-place operation. + +!!! compat "Julia 1.10" + This function requires Julia 1.10 or later. +""" +hermitianpart!(A::AbstractMatrix, uplo::Symbol=:U) = Hermitian(_hermitianpart!(A), uplo) + +_hermitianpart(A::AbstractMatrix) = _hermitianpart!(copy_similar(A, Base.promote_op(/, eltype(A), Int))) +_hermitianpart(a::Number) = real(a) + +function _hermitianpart!(A::AbstractMatrix) + require_one_based_indexing(A) + n = checksquare(A) + @inbounds for j in 1:n + A[j, j] = _hermitianpart(A[j, j]) + for i in 1:j-1 + A[i, j] = val = (A[i, j] + adjoint(A[j, i])) / 2 + A[j, i] = adjoint(val) + end + end + return A +end diff --git a/stdlib/LinearAlgebra/src/symmetriceigen.jl b/stdlib/LinearAlgebra/src/symmetriceigen.jl index 8d90f370e06b6..17371b74bb343 100644 --- a/stdlib/LinearAlgebra/src/symmetriceigen.jl +++ b/stdlib/LinearAlgebra/src/symmetriceigen.jl @@ -1,13 +1,16 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# preserve HermOrSym wrapper +eigencopy_oftype(A::Hermitian, S) = Hermitian(copy_similar(A, S), sym_uplo(A.uplo)) +eigencopy_oftype(A::Symmetric, S) = Symmetric(copy_similar(A, S), sym_uplo(A.uplo)) + # Eigensolvers for symmetric and Hermitian matrices eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) = Eigen(sorteig!(LAPACK.syevr!('V', 'A', A.uplo, A.data, 0.0, 0.0, 0, 0, -1.0)..., sortby)...) function eigen(A::RealHermSymComplexHerm; sortby::Union{Function,Nothing}=nothing) - T = eltype(A) - S = eigtype(T) - eigen!(S != T ? convert(AbstractMatrix{S}, A) : copy(A), sortby=sortby) + S = eigtype(eltype(A)) + eigen!(eigencopy_oftype(A, S), sortby=sortby) end eigen!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}, irange::UnitRange) = @@ -31,9 +34,8 @@ The [`UnitRange`](@ref) `irange` specifies indices of the sorted eigenvalues to will be a *truncated* factorization. """ function eigen(A::RealHermSymComplexHerm, irange::UnitRange) - T = eltype(A) - S = eigtype(T) - eigen!(S != T ? convert(AbstractMatrix{S}, A) : copy(A), irange) + S = eigtype(eltype(A)) + eigen!(eigencopy_oftype(A, S), irange) end eigen!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, vl::Real, vh::Real) where {T<:BlasReal} = @@ -57,9 +59,8 @@ The following functions are available for `Eigen` objects: [`inv`](@ref), [`det` will be a *truncated* factorization. """ function eigen(A::RealHermSymComplexHerm, vl::Real, vh::Real) - T = eltype(A) - S = eigtype(T) - eigen!(S != T ? convert(AbstractMatrix{S}, A) : copy(A), vl, vh) + S = eigtype(eltype(A)) + eigen!(eigencopy_oftype(A, S), vl, vh) end function eigvals!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby::Union{Function,Nothing}=nothing) @@ -69,9 +70,8 @@ function eigvals!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix}; sortby: end function eigvals(A::RealHermSymComplexHerm; sortby::Union{Function,Nothing}=nothing) - T = eltype(A) - S = eigtype(T) - eigvals!(S != T ? convert(AbstractMatrix{S}, A) : copy(A), sortby=sortby) + S = eigtype(eltype(A)) + eigvals!(eigencopy_oftype(A, S), sortby=sortby) end """ @@ -110,9 +110,8 @@ julia> eigvals(A) ``` """ function eigvals(A::RealHermSymComplexHerm, irange::UnitRange) - T = eltype(A) - S = eigtype(T) - eigvals!(S != T ? convert(AbstractMatrix{S}, A) : copy(A), irange) + S = eigtype(eltype(A)) + eigvals!(eigencopy_oftype(A, S), irange) end """ @@ -150,13 +149,12 @@ julia> eigvals(A) ``` """ function eigvals(A::RealHermSymComplexHerm, vl::Real, vh::Real) - T = eltype(A) - S = eigtype(T) - eigvals!(S != T ? convert(AbstractMatrix{S}, A) : copy(A), vl, vh) + S = eigtype(eltype(A)) + eigvals!(eigencopy_oftype(A, S), vl, vh) end -eigmax(A::RealHermSymComplexHerm{<:Real,<:StridedMatrix}) = eigvals(A, size(A, 1):size(A, 1))[1] -eigmin(A::RealHermSymComplexHerm{<:Real,<:StridedMatrix}) = eigvals(A, 1:1)[1] +eigmax(A::RealHermSymComplexHerm{<:Real}) = eigvals(A, size(A, 1):size(A, 1))[1] +eigmin(A::RealHermSymComplexHerm{<:Real}) = eigvals(A, 1:1)[1] function eigen!(A::HermOrSym{T,S}, B::HermOrSym{T,S}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasReal,S<:StridedMatrix} vals, vecs, _ = LAPACK.sygvd!(1, 'V', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data')) @@ -166,107 +164,25 @@ function eigen!(A::Hermitian{T,S}, B::Hermitian{T,S}; sortby::Union{Function,Not vals, vecs, _ = LAPACK.sygvd!(1, 'V', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data')) GeneralizedEigen(sorteig!(vals, vecs, sortby)...) end - -function eigen!(A::RealHermSymComplexHerm{T,S}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number,S<:StridedMatrix} +function eigen!(A::RealHermSymComplexHerm{T,<:StridedMatrix}, B::AbstractMatrix{T}; sortby::Union{Function,Nothing}=nothing) where {T<:Number} + return _choleigen!(A, B, sortby) +end +function eigen!(A::StridedMatrix{T}, B::Union{RealHermSymComplexHerm{T},Diagonal{T}}; sortby::Union{Function,Nothing}=nothing) where {T<:Number} + return _choleigen!(A, B, sortby) +end +function _choleigen!(A, B, sortby) U = cholesky(B).U vals, w = eigen!(UtiAUi!(A, U)) vecs = U \ w GeneralizedEigen(sorteig!(vals, vecs, sortby)...) end -# Perform U' \ A / U in-place. -UtiAUi!(As::Symmetric, Utr::UpperTriangular) = Symmetric(_UtiAsymUi!(As.uplo, parent(As), parent(Utr)), sym_uplo(As.uplo)) -UtiAUi!(As::Hermitian, Utr::UpperTriangular) = Hermitian(_UtiAsymUi!(As.uplo, parent(As), parent(Utr)), sym_uplo(As.uplo)) -UtiAUi!(As::Symmetric, Udi::Diagonal) = Symmetric(_UtiAsymUi_diag!(As.uplo, parent(As), Udi), sym_uplo(As.uplo)) -UtiAUi!(As::Hermitian, Udi::Diagonal) = Hermitian(_UtiAsymUi_diag!(As.uplo, parent(As), Udi), sym_uplo(As.uplo)) - -# U is upper triangular -function _UtiAsymUi!(uplo, A, U) - n = size(A, 1) - μ⁻¹ = 1 / U[1, 1] - αμ⁻² = A[1, 1] * μ⁻¹' * μ⁻¹ - - # Update (1, 1) element - A[1, 1] = αμ⁻² - if n > 1 - Unext = view(U, 2:n, 2:n) - - if uplo === 'U' - # Update submatrix - for j in 2:n, i in 2:j - A[i, j] = ( - A[i, j] - - μ⁻¹' * U[1, j] * A[1, i]' - - μ⁻¹ * A[1, j] * U[1, i]' - + αμ⁻² * U[1, j] * U[1, i]' - ) - end - - # Update vector - for j in 2:n - A[1, j] = A[1, j] * μ⁻¹' - U[1, j] * αμ⁻² - end - ldiv!(view(A', 2:n, 1), UpperTriangular(Unext)', view(A', 2:n, 1)) - else - # Update submatrix - for j in 2:n, i in 2:j - A[j, i] = ( - A[j, i] - - μ⁻¹ * A[i, 1]' * U[1, j]' - - μ⁻¹' * U[1, i] * A[j, 1] - + αμ⁻² * U[1, i] * U[1, j]' - ) - end - - # Update vector - for j in 2:n - A[j, 1] = A[j, 1] * μ⁻¹ - U[1, j]' * αμ⁻² - end - ldiv!(view(A, 2:n, 1), UpperTriangular(Unext)', view(A, 2:n, 1)) - end - - # Recurse - _UtiAsymUi!(uplo, view(A, 2:n, 2:n), Unext) - end - - return A -end +# Perform U' \ A / U in-place, where U::Union{UpperTriangular,Diagonal} +UtiAUi!(A::StridedMatrix, U) = _UtiAUi!(A, U) +UtiAUi!(A::Symmetric, U) = Symmetric(_UtiAUi!(copytri!(parent(A), A.uplo), U), sym_uplo(A.uplo)) +UtiAUi!(A::Hermitian, U) = Hermitian(_UtiAUi!(copytri!(parent(A), A.uplo, true), U), sym_uplo(A.uplo)) -# U is diagonal -function _UtiAsymUi_diag!(uplo, A, U) - n = size(A, 1) - μ⁻¹ = 1 / U[1, 1] - αμ⁻² = A[1, 1] * μ⁻¹' * μ⁻¹ - - # Update (1, 1) element - A[1, 1] = αμ⁻² - if n > 1 - Unext = view(U, 2:n, 2:n) - - if uplo === 'U' - # No need to update any submatrix when U is diagonal - - # Update vector - for j in 2:n - A[1, j] = A[1, j] * μ⁻¹' - end - ldiv!(view(A', 2:n, 1), Diagonal(Unext)', view(A', 2:n, 1)) - else - # No need to update any submatrix when U is diagonal - - # Update vector - for j in 2:n - A[j, 1] = A[j, 1] * μ⁻¹ - end - ldiv!(view(A, 2:n, 1), Diagonal(Unext)', view(A, 2:n, 1)) - end - - # Recurse - _UtiAsymUi!(uplo, view(A, 2:n, 2:n), Unext) - end - - return A -end +_UtiAUi!(A, U) = rdiv!(ldiv!(U', A), U) function eigvals!(A::HermOrSym{T,S}, B::HermOrSym{T,S}; sortby::Union{Function,Nothing}=nothing) where {T<:BlasReal,S<:StridedMatrix} vals = LAPACK.sygvd!(1, 'N', A.uplo, A.data, B.uplo == A.uplo ? B.data : copy(B.data'))[1] diff --git a/stdlib/LinearAlgebra/src/transpose.jl b/stdlib/LinearAlgebra/src/transpose.jl index c7ca6339aac6a..9d70ac3add34b 100644 --- a/stdlib/LinearAlgebra/src/transpose.jl +++ b/stdlib/LinearAlgebra/src/transpose.jl @@ -175,8 +175,8 @@ julia> copy(T) """ copy(::Union{Transpose,Adjoint}) -Base.copy(A::Transpose{<:Any,<:AbstractMatrix}) = transpose!(similar(A.parent, reverse(axes(A.parent))), A.parent) -Base.copy(A::Adjoint{<:Any,<:AbstractMatrix}) = adjoint!(similar(A.parent, reverse(axes(A.parent))), A.parent) +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}) @@ -201,3 +201,12 @@ function copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_de end 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)) +end diff --git a/stdlib/LinearAlgebra/src/triangular.jl b/stdlib/LinearAlgebra/src/triangular.jl index d939a5df0da01..1e4ba4119393d 100644 --- a/stdlib/LinearAlgebra/src/triangular.jl +++ b/stdlib/LinearAlgebra/src/triangular.jl @@ -151,13 +151,17 @@ julia> UnitUpperTriangular(A) """ UnitUpperTriangular +const UpperOrUnitUpperTriangular{T,S} = Union{UpperTriangular{T,S}, UnitUpperTriangular{T,S}} +const LowerOrUnitLowerTriangular{T,S} = Union{LowerTriangular{T,S}, UnitLowerTriangular{T,S}} +const UpperOrLowerTriangular{T,S} = Union{UpperOrUnitUpperTriangular{T,S}, LowerOrUnitLowerTriangular{T,S}} + 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)) Array(A::AbstractTriangular) = Matrix(A) -parent(A::AbstractTriangular) = A.data +parent(A::UpperOrLowerTriangular) = A.data # then handle all methods that requires specific handling of upper/lower and unit diagonal @@ -172,7 +176,7 @@ function Matrix{T}(A::UnitLowerTriangular) where T copyto!(B, A.data) tril!(B) for i = 1:size(B,1) - B[i,i] = 1 + B[i,i] = oneunit(T) end B end @@ -187,7 +191,7 @@ function Matrix{T}(A::UnitUpperTriangular) where T copyto!(B, A.data) triu!(B) for i = 1:size(B,1) - B[i,i] = 1 + B[i,i] = oneunit(T) end B end @@ -201,7 +205,7 @@ function full!(A::UnitLowerTriangular) B = A.data tril!(B) for i = 1:size(A,1) - B[i,i] = 1 + B[i,i] = oneunit(eltype(B)) end B end @@ -214,7 +218,7 @@ function full!(A::UnitUpperTriangular) B = A.data triu!(B) for i = 1:size(A,1) - B[i,i] = 1 + B[i,i] = oneunit(eltype(B)) end B end @@ -230,7 +234,7 @@ getindex(A::UpperTriangular, i::Integer, j::Integer) = function setindex!(A::UpperTriangular, x, i::Integer, j::Integer) if i > j - x == 0 || throw(ArgumentError("cannot set index in the lower triangular part " * + iszero(x) || throw(ArgumentError("cannot set index in the lower triangular part " * "($i, $j) of an UpperTriangular matrix to a nonzero value ($x)")) else A.data[i,j] = x @@ -240,10 +244,10 @@ end function setindex!(A::UnitUpperTriangular, x, i::Integer, j::Integer) if i > j - x == 0 || throw(ArgumentError("cannot set index in the lower triangular part " * + iszero(x) || throw(ArgumentError("cannot set index in the lower triangular part " * "($i, $j) of a UnitUpperTriangular matrix to a nonzero value ($x)")) elseif i == j - x == 1 || throw(ArgumentError("cannot set index on the diagonal ($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)")) else A.data[i,j] = x @@ -253,7 +257,7 @@ end function setindex!(A::LowerTriangular, x, i::Integer, j::Integer) if i < j - x == 0 || throw(ArgumentError("cannot set index in the upper triangular part " * + iszero(x) || throw(ArgumentError("cannot set index in the upper triangular part " * "($i, $j) of a LowerTriangular matrix to a nonzero value ($x)")) else A.data[i,j] = x @@ -263,10 +267,10 @@ end function setindex!(A::UnitLowerTriangular, x, i::Integer, j::Integer) if i < j - x == 0 || throw(ArgumentError("cannot set index in the upper triangular part " * + iszero(x) || throw(ArgumentError("cannot set index in the upper triangular part " * "($i, $j) of a UnitLowerTriangular matrix to a nonzero value ($x)")) elseif i == j - x == 1 || throw(ArgumentError("cannot set index on the diagonal ($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)")) else A.data[i,j] = x @@ -293,28 +297,28 @@ function istriu(A::Union{UpperTriangular,UnitUpperTriangular}, k::Integer=0) k <= 0 && return true return _istriu(A, k) end -istril(A::Adjoint) = istriu(A.parent) -istril(A::Transpose) = istriu(A.parent) -istriu(A::Adjoint) = istril(A.parent) -istriu(A::Transpose) = istril(A.parent) +istril(A::Adjoint, k::Integer=0) = istriu(A.parent, -k) +istril(A::Transpose, k::Integer=0) = istriu(A.parent, -k) +istriu(A::Adjoint, k::Integer=0) = istril(A.parent, -k) +istriu(A::Transpose, k::Integer=0) = istril(A.parent, -k) -function tril!(A::UpperTriangular, k::Integer=0) +function tril!(A::UpperTriangular{T}, k::Integer=0) where {T} n = size(A,1) if k < 0 - fill!(A.data,0) + fill!(A.data, zero(T)) return A elseif k == 0 for j in 1:n, i in 1:j-1 - A.data[i,j] = 0 + A.data[i,j] = zero(T) end return A else return UpperTriangular(tril!(A.data,k)) end end -triu!(A::UpperTriangular, k::Integer=0) = UpperTriangular(triu!(A.data,k)) +triu!(A::UpperTriangular, k::Integer=0) = UpperTriangular(triu!(A.data, k)) -function tril!(A::UnitUpperTriangular{T}, k::Integer=0) where T +function tril!(A::UnitUpperTriangular{T}, k::Integer=0) where {T} n = size(A,1) if k < 0 fill!(A.data, zero(T)) @@ -337,25 +341,25 @@ function triu!(A::UnitUpperTriangular, k::Integer=0) for i in diagind(A) A.data[i] = oneunit(eltype(A)) end - return triu!(UpperTriangular(A.data),k) + return triu!(UpperTriangular(A.data), k) end -function triu!(A::LowerTriangular, k::Integer=0) +function triu!(A::LowerTriangular{T}, k::Integer=0) where {T} n = size(A,1) if k > 0 - fill!(A.data,0) + fill!(A.data, zero(T)) return A elseif k == 0 for j in 1:n, i in j+1:n - A.data[i,j] = 0 + A.data[i,j] = zero(T) end return A else - return LowerTriangular(triu!(A.data,k)) + return LowerTriangular(triu!(A.data, k)) end end -tril!(A::LowerTriangular, k::Integer=0) = LowerTriangular(tril!(A.data,k)) +tril!(A::LowerTriangular, k::Integer=0) = LowerTriangular(tril!(A.data, k)) function triu!(A::UnitLowerTriangular{T}, k::Integer=0) where T n = size(A,1) @@ -372,7 +376,7 @@ function triu!(A::UnitLowerTriangular{T}, k::Integer=0) where T for i in diagind(A) A.data[i] = oneunit(T) end - return LowerTriangular(triu!(A.data,k)) + return LowerTriangular(triu!(A.data, k)) end end @@ -380,7 +384,7 @@ function tril!(A::UnitLowerTriangular, k::Integer=0) for i in diagind(A) A.data[i] = oneunit(eltype(A)) end - return tril!(LowerTriangular(A.data),k) + return tril!(LowerTriangular(A.data), k) end adjoint(A::LowerTriangular) = UpperTriangular(adjoint(A.data)) @@ -402,9 +406,9 @@ adjoint!(A::UpperTriangular) = LowerTriangular(copytri!(A.data, 'U' , true, true adjoint!(A::UnitUpperTriangular) = UnitLowerTriangular(copytri!(A.data, 'U' , true, true)) diag(A::LowerTriangular) = diag(A.data) -diag(A::UnitLowerTriangular) = fill(one(eltype(A)), size(A,1)) +diag(A::UnitLowerTriangular) = fill(oneunit(eltype(A)), size(A,1)) diag(A::UpperTriangular) = diag(A.data) -diag(A::UnitUpperTriangular) = fill(one(eltype(A)), size(A,1)) +diag(A::UnitUpperTriangular) = fill(oneunit(eltype(A)), size(A,1)) # Unary operations -(A::LowerTriangular) = LowerTriangular(-A.data) @@ -412,20 +416,25 @@ diag(A::UnitUpperTriangular) = fill(one(eltype(A)), size(A,1)) function -(A::UnitLowerTriangular) Anew = -A.data for i = 1:size(A, 1) - Anew[i, i] = -1 + Anew[i, i] = -A[i, i] end LowerTriangular(Anew) end function -(A::UnitUpperTriangular) Anew = -A.data for i = 1:size(A, 1) - Anew[i, i] = -1 + Anew[i, i] = -A[i, i] end UpperTriangular(Anew) end +tr(A::LowerTriangular) = tr(A.data) +tr(A::UnitLowerTriangular) = size(A, 1) * oneunit(eltype(A)) +tr(A::UpperTriangular) = tr(A.data) +tr(A::UnitUpperTriangular) = size(A, 1) * oneunit(eltype(A)) + # copy and scale -function copyto!(A::T, B::T) where T<:Union{UpperTriangular,UnitUpperTriangular} +function copyto!(A::T, B::T) where {T<:Union{UpperTriangular,UnitUpperTriangular}} n = size(B,1) for j = 1:n for i = 1:(isa(B, UnitUpperTriangular) ? j-1 : j) @@ -434,7 +443,7 @@ function copyto!(A::T, B::T) where T<:Union{UpperTriangular,UnitUpperTriangular} end return A end -function copyto!(A::T, B::T) where T<:Union{LowerTriangular,UnitLowerTriangular} +function copyto!(A::T, B::T) where {T<:Union{LowerTriangular,UnitLowerTriangular}} n = size(B,1) for j = 1:n for i = (isa(B, UnitLowerTriangular) ? j+1 : j):n @@ -444,106 +453,100 @@ function copyto!(A::T, B::T) where T<:Union{LowerTriangular,UnitLowerTriangular} return A end -# Define `mul!` for (Unit){Upper,Lower}Triangular matrices times a -# number. -for (Trig, UnitTrig) in Any[(UpperTriangular, UnitUpperTriangular), - (LowerTriangular, UnitLowerTriangular)] - for (TB, TC) in Any[(Trig, Number), - (Number, Trig), - (UnitTrig, Number), - (Number, UnitTrig)] - @eval @inline mul!(A::$Trig, B::$TB, C::$TC, alpha::Number, beta::Number) = - _mul!(A, B, C, MulAddMul(alpha, beta)) - end -end +# Define `mul!` for (Unit){Upper,Lower}Triangular matrices times a number. +# be permissive here and require compatibility later in _triscale! +@inline mul!(A::UpperOrLowerTriangular, B::UpperOrLowerTriangular, C::Number, alpha::Number, beta::Number) = + _triscale!(A, B, C, MulAddMul(alpha, beta)) +@inline mul!(A::UpperOrLowerTriangular, B::Number, C::UpperOrLowerTriangular, alpha::Number, beta::Number) = + _triscale!(A, B, C, MulAddMul(alpha, beta)) -@inline function _mul!(A::UpperTriangular, B::UpperTriangular, c::Number, _add::MulAddMul) +function _triscale!(A::UpperTriangular, B::UpperTriangular, c::Number, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n for i = 1:j - @inbounds _modify!(_add, B[i,j] * c, A, (i,j)) + @inbounds _modify!(_add, B.data[i,j] * c, A.data, (i,j)) end end return A end -@inline function _mul!(A::UpperTriangular, c::Number, B::UpperTriangular, _add::MulAddMul) +function _triscale!(A::UpperTriangular, c::Number, B::UpperTriangular, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n for i = 1:j - @inbounds _modify!(_add, c * B[i,j], A, (i,j)) + @inbounds _modify!(_add, c * B.data[i,j], A.data, (i,j)) end end return A end -@inline function _mul!(A::UpperTriangular, B::UnitUpperTriangular, c::Number, _add::MulAddMul) +function _triscale!(A::UpperOrUnitUpperTriangular, B::UnitUpperTriangular, c::Number, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = 1:(j - 1) - @inbounds _modify!(_add, B[i,j] * c, A, (i,j)) + @inbounds _modify!(_add, B.data[i,j] * c, A.data, (i,j)) end end return A end -@inline function _mul!(A::UpperTriangular, c::Number, B::UnitUpperTriangular, _add::MulAddMul) +function _triscale!(A::UpperOrUnitUpperTriangular, c::Number, B::UnitUpperTriangular, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = 1:(j - 1) - @inbounds _modify!(_add, c * B[i,j], A, (i,j)) + @inbounds _modify!(_add, c * B.data[i,j], A.data, (i,j)) end end return A end -@inline function _mul!(A::LowerTriangular, B::LowerTriangular, c::Number, _add::MulAddMul) +function _triscale!(A::LowerTriangular, B::LowerTriangular, c::Number, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n for i = j:n - @inbounds _modify!(_add, B[i,j] * c, A, (i,j)) + @inbounds _modify!(_add, B.data[i,j] * c, A.data, (i,j)) end end return A end -@inline function _mul!(A::LowerTriangular, c::Number, B::LowerTriangular, _add::MulAddMul) +function _triscale!(A::LowerTriangular, c::Number, B::LowerTriangular, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n for i = j:n - @inbounds _modify!(_add, c * B[i,j], A, (i,j)) + @inbounds _modify!(_add, c * B.data[i,j], A.data, (i,j)) end end return A end -@inline function _mul!(A::LowerTriangular, B::UnitLowerTriangular, c::Number, _add::MulAddMul) +function _triscale!(A::LowerOrUnitLowerTriangular, B::UnitLowerTriangular, c::Number, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = (j + 1):n - @inbounds _modify!(_add, B[i,j] * c, A, (i,j)) + @inbounds _modify!(_add, B.data[i,j] * c, A.data, (i,j)) end end return A end -@inline function _mul!(A::LowerTriangular, c::Number, B::UnitLowerTriangular, _add::MulAddMul) +function _triscale!(A::LowerOrUnitLowerTriangular, c::Number, B::UnitLowerTriangular, _add) n = checksquare(B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) for j = 1:n @inbounds _modify!(_add, c, A, (j,j)) for i = (j + 1):n - @inbounds _modify!(_add, c * B[i,j], A, (i,j)) + @inbounds _modify!(_add, c * B.data[i,j], A.data, (i,j)) end end return A end -rmul!(A::Union{UpperTriangular,LowerTriangular}, c::Number) = mul!(A, A, c) -lmul!(c::Number, A::Union{UpperTriangular,LowerTriangular}) = mul!(A, c, A) +rmul!(A::UpperOrLowerTriangular, c::Number) = @inline _triscale!(A, A, c, MulAddMul()) +lmul!(c::Number, A::UpperOrLowerTriangular) = @inline _triscale!(A, c, A, MulAddMul()) function dot(x::AbstractVector, A::UpperTriangular, y::AbstractVector) require_one_based_indexing(x, y) @@ -659,32 +662,39 @@ fillstored!(A::UnitUpperTriangular, x) = (fillband!(A.data, x, 1, size(A,2)-1); # BlasFloat routines # ###################### -lmul!(A::Tridiagonal, B::AbstractTriangular) = A*full!(B) # is this necessary? - -@inline mul!(C::AbstractMatrix, A::AbstractTriangular, B::Tridiagonal, alpha::Number, beta::Number) = - mul!(C, copyto!(similar(parent(A)), A), B, alpha, beta) -@inline mul!(C::AbstractMatrix, A::Tridiagonal, B::AbstractTriangular, alpha::Number, beta::Number) = - mul!(C, A, copyto!(similar(parent(B)), B), alpha, beta) -mul!(C::AbstractVector, A::AbstractTriangular, transB::Transpose{<:Any,<:AbstractVecOrMat}) = - (B = transB.parent; lmul!(A, transpose!(C, B))) -mul!(C::AbstractMatrix, A::AbstractTriangular, transB::Transpose{<:Any,<:AbstractVecOrMat}) = - (B = transB.parent; lmul!(A, transpose!(C, B))) -mul!(C::AbstractMatrix, A::AbstractTriangular, adjB::Adjoint{<:Any,<:AbstractVecOrMat}) = - (B = adjB.parent; lmul!(A, adjoint!(C, B))) -mul!(C::AbstractVecOrMat, A::AbstractTriangular, adjB::Adjoint{<:Any,<:AbstractVecOrMat}) = - (B = adjB.parent; lmul!(A, adjoint!(C, B))) - -# The three methods are neceesary to avoid ambiguities with definitions in matmul.jl -mul!(C::AbstractVector , A::AbstractTriangular, B::AbstractVector) = lmul!(A, copyto!(C, B)) -mul!(C::AbstractMatrix , A::AbstractTriangular, B::AbstractVecOrMat) = lmul!(A, copyto!(C, B)) -mul!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVecOrMat) = lmul!(A, copyto!(C, B)) - -@inline mul!(C::AbstractMatrix, A::AbstractTriangular, B::Adjoint{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = - mul!(C, A, copy(B), alpha, beta) -@inline mul!(C::AbstractMatrix, A::AbstractTriangular, B::Transpose{<:Any,<:AbstractVecOrMat}, alpha::Number, beta::Number) = - mul!(C, A, copy(B), alpha, beta) -mul!(C::AbstractVector, A::AbstractTriangular{<:Any,<:Adjoint}, B::Transpose{<:Any,<:AbstractVecOrMat}) = throw(MethodError(mul!, (C, A, B))) -mul!(C::AbstractVector, A::AbstractTriangular{<:Any,<:Transpose}, B::Transpose{<:Any,<:AbstractVecOrMat}) = throw(MethodError(mul!, (C, A, B))) +lmul!(A::Tridiagonal, B::AbstractTriangular) = A*full!(B) +mul!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVector) = _multrimat!(C, A, B) +mul!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractMatrix) = _multrimat!(C, A, B) +mul!(C::AbstractMatrix, A::AbstractMatrix, B::AbstractTriangular) = _mulmattri!(C, A, B) +mul!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractTriangular) = _multrimat!(C, A, B) + +for TC in (:AbstractVector, :AbstractMatrix) + @eval @inline function mul!(C::$TC, A::AbstractTriangular, B::AbstractVector, alpha::Number, beta::Number) + if isone(alpha) && iszero(beta) + return mul!(C, A, B) + else + return generic_matvecmul!(C, 'N', A, B, MulAddMul(alpha, beta)) + end + end +end +for (TA, TB) in ((:AbstractTriangular, :AbstractMatrix), + (:AbstractMatrix, :AbstractTriangular), + (:AbstractTriangular, :AbstractTriangular) + ) + @eval @inline function mul!(C::AbstractMatrix, A::$TA, B::$TB, alpha::Number, beta::Number) + if isone(alpha) && iszero(beta) + return mul!(C, A, B) + else + return generic_matmatmul!(C, 'N', 'N', A, B, MulAddMul(alpha, beta)) + end + end +end + + +# generic fallback for AbstractTriangular matrices outside of the four subtypes provided here +_multrimat!(C::AbstractVecOrMat, A::AbstractTriangular, B::AbstractVecOrMat) = + lmul!(A, inplace_adj_or_trans(B)(C, _parent(B))) +_mulmattri!(C::AbstractMatrix, A::AbstractMatrix, B::AbstractTriangular) = rmul!(copyto!(C, A), B) # preserve triangular structure in in-place multiplication for (cty, aty, bty) in ((:UpperTriangular, :UpperTriangular, :UpperTriangular), @@ -695,18 +705,10 @@ for (cty, aty, bty) in ((:UpperTriangular, :UpperTriangular, :UpperTriangular), (:LowerTriangular, :LowerTriangular, :UnitLowerTriangular), (:LowerTriangular, :UnitLowerTriangular, :LowerTriangular), (:UnitLowerTriangular, :UnitLowerTriangular, :UnitLowerTriangular)) - @eval function mul!(C::$cty, A::$aty, B::$bty) - lmul!(A, copyto!(parent(C), B)) + @eval function _multrimat!(C::$cty, A::$aty, B::$bty) + _multrimat!(parent(C), A, B) return C end - - @eval @inline function mul!(C::$cty, A::$aty, B::$bty, alpha::Number, beta::Number) - if isone(alpha) && iszero(beta) - return mul!(C, A, B) - else - return generic_matmatmul!(C, 'N', 'N', A, B, MulAddMul(alpha, beta)) - end - end end # direct multiplication/division @@ -742,7 +744,7 @@ for (t, uploc, isunitc) in ((:LowerTriangular, 'L', 'N'), LAPACK.trrfs!($uploc, 'N', $isunitc, A.data, B, X) # Condition numbers - function cond(A::$t{<:BlasFloat}, p::Real=2) + function cond(A::$t{<:BlasFloat,<:StridedMatrix}, p::Real=2) checksquare(A) if p == 1 return inv(LAPACK.trcon!('O', $uploc, $isunitc, A.data)) @@ -802,20 +804,50 @@ for (t, uploc, isunitc) in ((:LowerTriangular, 'U', 'N'), end end -function inv(A::LowerTriangular{T}) where T - S = typeof((zero(T)*one(T) + zero(T))/one(T)) - LowerTriangular(ldiv!(convert(AbstractArray{S}, A), Matrix{S}(I, size(A, 1), size(A, 1)))) -end -function inv(A::UpperTriangular{T}) where T - S = typeof((zero(T)*one(T) + zero(T))/one(T)) - UpperTriangular(ldiv!(convert(AbstractArray{S}, A), Matrix{S}(I, size(A, 1), size(A, 1)))) +# redirect back to BLAS +for t in (:UpperTriangular, :UnitUpperTriangular, :LowerTriangular, :UnitLowerTriangular) + @eval _multrimat!(C::StridedVecOrMat{T}, A::$t{T,<:StridedMatrix}, B::AbstractVecOrMat{T}) where {T<:BlasFloat} = + lmul!(A, copyto!(C, B)) + @eval _multrimat!(C::StridedVecOrMat{T}, A::$t{<:Any,<:Adjoint{T,<:StridedMatrix}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat} = + lmul!(A, copyto!(C, B)) + @eval _multrimat!(C::StridedVecOrMat{T}, A::$t{<:Any,<:Transpose{T,<:StridedMatrix}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat} = + lmul!(A, copyto!(C, B)) + @eval _mulmattri!(C::StridedMatrix{T}, A::AbstractMatrix{T}, B::$t{T,<:StridedMatrix}) where {T<:BlasFloat} = + rmul!(copyto!(C, A), B) + @eval _mulmattri!(C::StridedMatrix{T}, A::AbstractMatrix{T}, B::$t{<:Any,<:Adjoint{T,<:StridedMatrix}}) where {T<:BlasFloat} = + rmul!(copyto!(C, A), B) + @eval _mulmattri!(C::StridedMatrix{T}, A::AbstractMatrix{T}, B::$t{<:Any,<:Transpose{T,<:StridedMatrix}}) where {T<:BlasFloat} = + rmul!(copyto!(C, A), B) + + @eval ldiv!(C::StridedVecOrMat{T}, A::$t{T,<:StridedMatrix}, B::AbstractVecOrMat{T}) where {T<:BlasFloat} = + ldiv!(A, copyto!(C, B)) + @eval ldiv!(C::StridedVecOrMat{T}, A::$t{<:Any,<:Adjoint{T,<:StridedMatrix}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat} = + ldiv!(A, copyto!(C, B)) + @eval ldiv!(C::StridedVecOrMat{T}, A::$t{<:Any,<:Transpose{T,<:StridedMatrix}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat} = + ldiv!(A, copyto!(C, B)) + @eval _rdiv!(C::StridedMatrix{T}, A::AbstractMatrix{T}, B::$t{T,<:StridedMatrix}) where {T<:BlasFloat} = + rdiv!(copyto!(C, A), B) + @eval _rdiv!(C::StridedMatrix{T}, A::AbstractMatrix{T}, B::$t{<:Any,<:Adjoint{T,<:StridedMatrix}}) where {T<:BlasFloat} = + rdiv!(copyto!(C, A), B) + @eval _rdiv!(C::StridedMatrix{T}, A::AbstractMatrix{T}, B::$t{<:Any,<:Transpose{T,<:StridedMatrix}}) where {T<:BlasFloat} = + rdiv!(copyto!(C, A), B) +end + +for t in (:LowerTriangular, :UnitLowerTriangular, :UpperTriangular, :UnitUpperTriangular) + @eval function inv(A::$t{T}) where {T} + S = typeof(inv(oneunit(T))) + if S <: BlasFloat || S === T # i.e. A is unitless + $t(ldiv!(convert(AbstractArray{S}, A), Matrix{S}(I, size(A)))) + else + J = (one(T)*I)(size(A, 1)) + $t(ldiv!(similar(A, S, size(A)), A, J)) + end + end end -inv(A::UnitUpperTriangular{T}) where {T} = UnitUpperTriangular(ldiv!(A, Matrix{T}(I, size(A, 1), size(A, 1)))) -inv(A::UnitLowerTriangular{T}) where {T} = UnitLowerTriangular(ldiv!(A, Matrix{T}(I, size(A, 1), size(A, 1)))) -errorbounds(A::AbstractTriangular{T,<:StridedMatrix}, X::StridedVecOrMat{T}, B::StridedVecOrMat{T}) where {T<:Union{BigFloat,Complex{BigFloat}}} = +errorbounds(A::AbstractTriangular{T,<:AbstractMatrix}, X::AbstractVecOrMat{T}, B::AbstractVecOrMat{T}) where {T<:Union{BigFloat,Complex{BigFloat}}} = error("not implemented yet! Please submit a pull request.") -function errorbounds(A::AbstractTriangular{TA,<:StridedMatrix}, X::StridedVecOrMat{TX}, B::StridedVecOrMat{TB}) where {TA<:Number,TX<:Number,TB<:Number} +function errorbounds(A::AbstractTriangular{TA,<:AbstractMatrix}, X::AbstractVecOrMat{TX}, B::AbstractVecOrMat{TB}) where {TA<:Number,TX<:Number,TB<:Number} TAXB = promote_type(TA, TB, TX, Float32) errorbounds(convert(AbstractMatrix{TAXB}, A), convert(AbstractArray{TAXB}, X), convert(AbstractArray{TAXB}, B)) end @@ -889,296 +921,193 @@ for (t, unitt) in ((UpperTriangular, UnitUpperTriangular), end $t(B) end + + lmul!(A::$t, B::AbstractVecOrMat) = @inline _multrimat!(B, A, B) + lmul!(A::$unitt, B::AbstractVecOrMat) = @inline _multrimat!(B, A, B) + + rmul!(A::AbstractMatrix, B::$t) = @inline _mulmattri!(A, A, B) + rmul!(A::AbstractMatrix, B::$unitt) = @inline _mulmattri!(A, A, B) end end ## Generic triangular multiplication -function lmul!(A::UpperTriangular, B::StridedVecOrMat) +function _multrimat!(C::AbstractVecOrMat, A::UpperTriangular, B::AbstractVecOrMat) + require_one_based_indexing(C, A, B) m, n = size(B, 1), size(B, 2) - if m != size(A, 1) + N = size(A, 1) + if m != N throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) end - for j = 1:n - for i = 1:m - Bij = A.data[i,i]*B[i,j] - for k = i + 1:m - Bij += A.data[i,k]*B[k,j] + mc, nc = size(C, 1), size(C, 2) + if mc != N || nc != n + throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($N,$n)")) + end + @inbounds for j in 1:n + for i in 1:m + Cij = A.data[i,i] * B[i,j] + for k in i + 1:m + Cij += A.data[i,k] * B[k,j] end - B[i,j] = Bij + C[i,j] = Cij end end - B + return C end - -function lmul!(A::UnitUpperTriangular, B::StridedVecOrMat) +function _multrimat!(C::AbstractVecOrMat, A::UnitUpperTriangular, B::AbstractVecOrMat) + require_one_based_indexing(C, A, B) m, n = size(B, 1), size(B, 2) - if m != size(A, 1) + N = size(A, 1) + if m != N throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) end - for j = 1:n - for i = 1:m - Bij = B[i,j] - for k = i + 1:m - Bij += A.data[i,k]*B[k,j] + + mc, nc = size(C, 1), size(C, 2) + if mc != N || nc != n + throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($N,$n)")) + end + @inbounds for j in 1:n + for i in 1:m + Cij = oneunit(eltype(A)) * B[i,j] + for k in i + 1:m + Cij += A.data[i,k] * B[k,j] end - B[i,j] = Bij + C[i,j] = Cij end end - B + return C end - -function lmul!(A::LowerTriangular, B::StridedVecOrMat) +function _multrimat!(C::AbstractVecOrMat, A::LowerTriangular, B::AbstractVecOrMat) + require_one_based_indexing(C, A, B) m, n = size(B, 1), size(B, 2) - if m != size(A, 1) + N = size(A, 1) + if m != N throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) end - for j = 1:n - for i = m:-1:1 - Bij = A.data[i,i]*B[i,j] - for k = 1:i - 1 - Bij += A.data[i,k]*B[k,j] + mc, nc = size(C, 1), size(C, 2) + if mc != N || nc != n + throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($N,$n)")) + end + @inbounds for j in 1:n + for i in m:-1:1 + Cij = A.data[i,i] * B[i,j] + for k in 1:i - 1 + Cij += A.data[i,k] * B[k,j] end - B[i,j] = Bij + C[i,j] = Cij end end - B + return C end -function lmul!(A::UnitLowerTriangular, B::StridedVecOrMat) +function _multrimat!(C::AbstractVecOrMat, A::UnitLowerTriangular, B::AbstractVecOrMat) + require_one_based_indexing(C, A, B) m, n = size(B, 1), size(B, 2) - if m != size(A, 1) + N = size(A, 1) + if m != N throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) end - for j = 1:n - for i = m:-1:1 - Bij = B[i,j] - for k = 1:i - 1 - Bij += A.data[i,k]*B[k,j] - end - B[i,j] = Bij - 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)")) end - B -end - -for (t, tfun) in ((:Adjoint, :adjoint), (:Transpose, :transpose)) - @eval begin - function lmul!(xA::UpperTriangular{<:Any,<:$t}, B::StridedVecOrMat) - A = xA.data - m, n = size(B, 1), size(B, 2) - if m != size(A, 1) - throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) - end - pA = parent(A) - for j = 1:n - for i = 1:m - Bij = $tfun(pA[i,i])*B[i,j] - for k = i + 1:m - Bij += $tfun(pA[k,i])*B[k,j] - end - B[i,j] = Bij - end - end - B - end - - function lmul!(xA::UnitUpperTriangular{<:Any,<:$t}, B::StridedVecOrMat) - A = xA.data - m, n = size(B, 1), size(B, 2) - if m != size(A, 1) - throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) - end - pA = parent(A) - for j = 1:n - for i = 1:m - Bij = B[i,j] - for k = i + 1:m - Bij += $tfun(pA[k,i])*B[k,j] - end - B[i,j] = Bij - end - end - B - end - - function lmul!(xA::LowerTriangular{<:Any,<:$t}, B::StridedVecOrMat) - A = xA.data - m, n = size(B, 1), size(B, 2) - if m != size(A, 1) - throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) - end - pA = parent(A) - for j = 1:n - for i = m:-1:1 - Bij = $tfun(pA[i,i])*B[i,j] - for k = 1:i - 1 - Bij += $tfun(pA[k,i])*B[k,j] - end - B[i,j] = Bij - end - end - B - end - function lmul!(xA::UnitLowerTriangular{<:Any,<:$t}, B::StridedVecOrMat) - A = xA.data - m, n = size(B, 1), size(B, 2) - if m != size(A, 1) - throw(DimensionMismatch("right hand side B needs first dimension of size $(size(A,1)), has size $m")) - end - pA = parent(A) - for j = 1:n - for i = m:-1:1 - Bij = B[i,j] - for k = 1:i - 1 - Bij += $tfun(pA[k,i])*B[k,j] - end - B[i,j] = Bij - end + @inbounds for j in 1:n + for i in m:-1:1 + Cij = oneunit(eltype(A)) * B[i,j] + for k in 1:i - 1 + Cij += A.data[i,k] * B[k,j] end - B + C[i,j] = Cij end end + return C end -function rmul!(A::StridedMatrix, B::UpperTriangular) - 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))")) +function _mulmattri!(C::AbstractMatrix, A::AbstractMatrix, B::UpperTriangular) + require_one_based_indexing(C, A, B) + 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")) end - for i = 1:m - for j = n:-1:1 - Aij = A[i,j]*B[j,j] - for k = 1:j - 1 - Aij += A[i,k]*B.data[k,j] + mc, nc = size(C, 1), size(C, 2) + if mc != m || nc != N + throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($m,$N)")) + end + @inbounds for i in 1:m + for j in n:-1:1 + Cij = A[i,j] * B.data[j,j] + for k in 1:j - 1 + Cij += A[i,k] * B.data[k,j] end - A[i,j] = Aij + C[i,j] = Cij end end - A + return C end -function rmul!(A::StridedMatrix, B::UnitUpperTriangular) - 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))")) +function _mulmattri!(C::AbstractMatrix, A::AbstractMatrix, B::UnitUpperTriangular) + require_one_based_indexing(C, A, B) + 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")) end - for i = 1:m - for j = n:-1:1 - Aij = A[i,j] - for k = 1:j - 1 - Aij += A[i,k]*B.data[k,j] + mc, nc = size(C, 1), size(C, 2) + if mc != m || nc != N + throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($m,$N)")) + end + @inbounds for i in 1:m + for j in n:-1:1 + Cij = A[i,j] * oneunit(eltype(B)) + for k in 1:j - 1 + Cij += A[i,k] * B.data[k,j] end - A[i,j] = Aij + C[i,j] = Cij end end - A + return C end - -function rmul!(A::StridedMatrix, B::LowerTriangular) - 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))")) +function _mulmattri!(C::AbstractMatrix, A::AbstractMatrix, B::LowerTriangular) + require_one_based_indexing(C, A, B) + 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")) end - for i = 1:m - for j = 1:n - Aij = A[i,j]*B[j,j] - for k = j + 1:n - Aij += A[i,k]*B.data[k,j] + mc, nc = size(C, 1), size(C, 2) + if mc != m || nc != N + throw(DimensionMismatch("output has dimensions ($mc,$nc), should have ($m,$N)")) + end + @inbounds for i in 1:m + for j in 1:n + Cij = A[i,j] * B.data[j,j] + for k in j + 1:n + Cij += A[i,k] * B.data[k,j] end - A[i,j] = Aij + C[i,j] = Cij end end - A + return C end -function rmul!(A::StridedMatrix, B::UnitLowerTriangular) - 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))")) +function _mulmattri!(C::AbstractMatrix, A::AbstractMatrix, B::UnitLowerTriangular) + require_one_based_indexing(C, A, B) + 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")) end - for i = 1:m - for j = 1:n - Aij = A[i,j] - for k = j + 1:n - Aij += A[i,k]*B.data[k,j] - end - A[i,j] = Aij - 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)")) end - A -end - -for (t, tfun) in ((:Adjoint, :adjoint), (:Transpose, :transpose)) - @eval begin - function rmul!(A::StridedMatrix, B::UpperTriangular{<:Any,<:$t}) - 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))")) - end - pB = parent(parent(B)) - for i = 1:m - for j = n:-1:1 - Aij = A[i,j]*$tfun(pB[j,j]) - for k = 1:j - 1 - Aij += A[i,k]*$tfun(pB[j,k]) - end - A[i,j] = Aij - end - end - A - end - - function rmul!(A::StridedMatrix, B::UnitUpperTriangular{<:Any,<:$t}) - 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))")) - end - pB = parent(parent(B)) - for i = 1:m - for j = n:-1:1 - Aij = A[i,j] - for k = 1:j - 1 - Aij += A[i,k]*$tfun(pB[j,k]) - end - A[i,j] = Aij - end - end - A - end - - function rmul!(A::StridedMatrix, B::LowerTriangular{<:Any,<:$t}) - 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))")) - end - pB = parent(parent(B)) - for i = 1:m - for j = 1:n - Aij = A[i,j]*$tfun(pB[j,j]) - for k = j + 1:n - Aij += A[i,k]*$tfun(pB[j,k]) - end - A[i,j] = Aij - end + @inbounds for i in 1:m + for j in 1:n + Cij = A[i,j] * oneunit(eltype(B)) + for k in j + 1:n + Cij += A[i,k] * B.data[k,j] end - A - end - - function rmul!(A::StridedMatrix, B::UnitLowerTriangular{<:Any,<:$t}) - 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))")) - end - pB = parent(parent(B)) - for i = 1:m - for j = 1:n - Aij = A[i,j] - for k = j + 1:n - Aij += A[i,k]*$tfun(pB[j,k]) - end - A[i,j] = Aij - end - end - A + C[i,j] = Cij end end + return C end #Generic solver using naive substitution @@ -1189,318 +1118,258 @@ end # does not significantly impact performance as of Dec 2015 # replacing repeated references to A.data[j,j] with [Ajj = A.data[j,j] and references to Ajj] # does not significantly impact performance as of Dec 2015 -function ldiv!(A::UpperTriangular, b::AbstractVector) - require_one_based_indexing(A, b) - n = size(A, 2) - if !(n == length(b)) - throw(DimensionMismatch("second dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) +ldiv!(A::AbstractTriangular, b::AbstractVecOrMat) = @inline ldiv!(b, A, b) +function ldiv!(C::AbstractMatrix, A::AbstractTriangular, B::AbstractMatrix) + require_one_based_indexing(C, A, B) + nA, mA = size(A) + n = size(B, 1) + if nA != n + throw(DimensionMismatch("second dimension of left hand side A, $mA, and first dimension of right hand side B, $n, must be equal")) end - @inbounds for j in n:-1:1 - iszero(A.data[j,j]) && throw(SingularException(j)) - bj = b[j] = A.data[j,j] \ b[j] - for i in j-1:-1:1 # counterintuitively 1:j-1 performs slightly better - b[i] -= A.data[i,j] * bj + if size(C) != size(B) + throw(DimensionMismatch("size of output, $(size(C)), does not match size of right hand side, $(size(B))")) + end + @inbounds for (c, b) in zip(eachcol(C), eachcol(B)) + ldiv!(c, A, b) + end + C +end +@inline function ldiv!(c::AbstractVector, A::AbstractTriangular, b::AbstractVector) + @boundscheck begin + require_one_based_indexing(c, A, b) + n = size(A, 2) + if !(n == length(b)) + throw(DimensionMismatch("second dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) + end + if !(n == length(c)) + throw(DimensionMismatch("length of output c, $(length(c)), does not match length of right hand side b, $(length(b))")) end end - return b + return _ldiv!(c, A, b) end -function ldiv!(A::UnitUpperTriangular, b::AbstractVector) - require_one_based_indexing(A, b) + +_uconvert_copyto!(c, b, oA) = (c .= Ref(oA) .\ b) +_uconvert_copyto!(c::AbstractArray{T}, b::AbstractArray{T}, _) where {T} = copyto!(c, b) + +@inline _ustrip(a) = oneunit(a) \ a +@inline _ustrip(a::Union{AbstractFloat,Integer,Complex,Rational}) = a + +# all of the following _ldiv! methods are "unsafe" in that they assume one-based indexing +# and compatible sizes +function _ldiv!(c::AbstractVector, A::UpperTriangular, b::AbstractVector) n = size(A, 2) - if !(n == length(b)) - throw(DimensionMismatch("second dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) - end + c !== b && _uconvert_copyto!(c, b, oneunit(eltype(A))) @inbounds for j in n:-1:1 - bj = b[j] - for i in j-1:-1:1 # counterintuitively 1:j-1 performs slightly better - b[i] -= A.data[i,j] * bj + ajj = A.data[j,j] + iszero(ajj) && throw(SingularException(j)) + cj = c[j] = _ustrip(ajj) \ c[j] + for i in j-1:-1:1 + c[i] -= _ustrip(A.data[i,j]) * cj end end - return b + return c end -function ldiv!(A::LowerTriangular, b::AbstractVector) - require_one_based_indexing(A, b) +function _ldiv!(c::AbstractVector, A::UnitUpperTriangular, b::AbstractVector) n = size(A, 2) - if !(n == length(b)) - throw(DimensionMismatch("second dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) + c !== b && _uconvert_copyto!(c, b, oneunit(eltype(A))) + @inbounds for j in n:-1:1 + cj = c[j] + for i in 1:j-1 + c[i] -= _ustrip(A.data[i,j]) * cj + end end + return c +end +function _ldiv!(c::AbstractVector, A::LowerTriangular, b::AbstractVector) + n = size(A, 2) + c !== b && _uconvert_copyto!(c, b, oneunit(eltype(A))) @inbounds for j in 1:n - iszero(A.data[j,j]) && throw(SingularException(j)) - bj = b[j] = A.data[j,j] \ b[j] + ajj = A.data[j,j] + iszero(ajj) && throw(SingularException(j)) + cj = c[j] = _ustrip(ajj) \ c[j] for i in j+1:n - b[i] -= A.data[i,j] * bj + c[i] -= _ustrip(A.data[i,j]) * cj end end - return b + return c end -function ldiv!(A::UnitLowerTriangular, b::AbstractVector) - require_one_based_indexing(A, b) +function _ldiv!(c::AbstractVector, A::UnitLowerTriangular, b::AbstractVector) n = size(A, 2) - if !(n == length(b)) - throw(DimensionMismatch("second dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) - end + c !== b && _uconvert_copyto!(c, b, oneunit(eltype(A))) @inbounds for j in 1:n - bj = b[j] + cj = c[j] for i in j+1:n - b[i] -= A.data[i,j] * bj + c[i] -= _ustrip(A.data[i,j]) * cj end end - return b -end -function ldiv!(A::AbstractTriangular, B::AbstractMatrix) - require_one_based_indexing(A, B) - nA, mA = size(A) - n = size(B, 1) - if nA != n - throw(DimensionMismatch("second dimension of left hand side A, $mA, and first dimension of right hand side B, $n, must be equal")) - end - for b in eachcol(B) - ldiv!(A, b) - end - B + return c end + # in the following transpose and conjugate transpose naive substitution variants, # accumulating in z rather than b[j,k] significantly improves performance as of Dec 2015 -for (t, tfun) in ((:Adjoint, :adjoint), (:Transpose, :transpose)) - @eval begin - function ldiv!(xA::UpperTriangular{<:Any,<:$t}, b::AbstractVector) - require_one_based_indexing(xA, b) - A = parent(parent(xA)) - n = size(A, 1) - if !(n == length(b)) - throw(DimensionMismatch("first dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) - end - @inbounds for j in n:-1:1 - z = b[j] - for i in n:-1:j+1 - z -= $tfun(A[i,j]) * b[i] - end - iszero(A[j,j]) && throw(SingularException(j)) - b[j] = $tfun(A[j,j]) \ z - end - return b +function _ldiv!(c::AbstractVector, xA::UpperTriangular{<:Any,<:AdjOrTrans}, b::AbstractVector) + tfun = adj_or_trans(parent(xA)) + A = parent(parent(xA)) + n = size(A, 2) + @inbounds for j in n:-1:1 + ajj = A[j,j] + iszero(ajj) && throw(SingularException(j)) + bj = b[j] + for i in j+1:n + bj -= tfun(A[i,j]) * c[i] end - - function ldiv!(xA::UnitUpperTriangular{<:Any,<:$t}, b::AbstractVector) - require_one_based_indexing(xA, b) - A = parent(parent(xA)) - n = size(A, 1) - if !(n == length(b)) - throw(DimensionMismatch("first dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) - end - @inbounds for j in n:-1:1 - z = b[j] - for i in n:-1:j+1 - z -= $tfun(A[i,j]) * b[i] - end - b[j] = z - end - return b + c[j] = tfun(ajj) \ bj + end + return c +end +function _ldiv!(c::AbstractVector, xA::UnitUpperTriangular{<:Any,<:AdjOrTrans}, b::AbstractVector) + tfun = adj_or_trans(parent(xA)) + A = parent(parent(xA)) + oA = oneunit(eltype(A)) + n = size(A, 2) + @inbounds for j in n:-1:1 + bj = b[j] + for i in j+1:n + bj -= tfun(A[i,j]) * c[i] end - - function ldiv!(xA::LowerTriangular{<:Any,<:$t}, b::AbstractVector) - require_one_based_indexing(xA, b) - A = parent(parent(xA)) - n = size(A, 1) - if !(n == length(b)) - throw(DimensionMismatch("first dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) - end - @inbounds for j in 1:n - z = b[j] - for i in 1:j-1 - z -= $tfun(A[i,j]) * b[i] - end - iszero(A[j,j]) && throw(SingularException(j)) - b[j] = $tfun(A[j,j]) \ z - end - return b + c[j] = oA \ bj + end + return c +end +function _ldiv!(c::AbstractVector, xA::LowerTriangular{<:Any,<:AdjOrTrans}, b::AbstractVector) + tfun = adj_or_trans(parent(xA)) + A = parent(parent(xA)) + n = size(A, 2) + @inbounds for j in 1:n + ajj = A[j,j] + iszero(ajj) && throw(SingularException(j)) + bj = b[j] + for i in 1:j-1 + bj -= tfun(A[i,j]) * c[i] end - - function ldiv!(xA::UnitLowerTriangular{<:Any,<:$t}, b::AbstractVector) - require_one_based_indexing(xA, b) - A = parent(parent(xA)) - n = size(A, 1) - if !(n == length(b)) - throw(DimensionMismatch("first dimension of left hand side A, $n, and length of right hand side b, $(length(b)), must be equal")) - end - @inbounds for j in 1:n - z = b[j] - for i in 1:j-1 - z -= $tfun(A[i,j]) * b[i] - end - b[j] = z - end - return b + c[j] = tfun(ajj) \ bj + end + return c +end +function _ldiv!(c::AbstractVector, xA::UnitLowerTriangular{<:Any,<:AdjOrTrans}, b::AbstractVector) + tfun = adj_or_trans(parent(xA)) + A = parent(parent(xA)) + oA = oneunit(eltype(A)) + n = size(A, 2) + @inbounds for j in 1:n + bj = b[j] + for i in 1:j-1 + bj -= tfun(A[i,j]) * c[i] end + c[j] = oA \ bj end + return c end -function rdiv!(A::StridedMatrix, B::UpperTriangular) +rdiv!(A::AbstractMatrix, B::AbstractTriangular) = @inline _rdiv!(A, A, B) +function _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::UpperTriangular) + 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))")) end - for i = 1:m - for j = 1:n + if size(C) != size(A) + throw(DimensionMismatch("size of output, $(size(C)), does not match size of left hand side, $(size(A))")) + end + @inbounds for i in 1:m + for j in 1:n Aij = A[i,j] - for k = 1:j - 1 - Aij -= A[i,k]*B.data[k,j] + for k in 1:j - 1 + Aij -= C[i,k]*B.data[k,j] end - A[i,j] = Aij/B[j,j] + iszero(B.data[j,j]) && throw(SingularException(j)) + C[i,j] = Aij / B.data[j,j] end end - A + C end -function rdiv!(A::StridedMatrix, B::UnitUpperTriangular) +function _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::UnitUpperTriangular) + 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))")) end - for i = 1:m - for j = 1:n + if size(C) != size(A) + throw(DimensionMismatch("size of output, $(size(C)), does not match size of left hand side, $(size(A))")) + end + @inbounds for i in 1:m + for j in 1:n Aij = A[i,j] - for k = 1:j - 1 - Aij -= A[i,k]*B.data[k,j] + for k in 1:j - 1 + Aij -= C[i,k]*B.data[k,j] end - A[i,j] = Aij + C[i,j] = Aij / oneunit(eltype(B)) end end - A + C end - -function rdiv!(A::StridedMatrix, B::LowerTriangular) +function _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::LowerTriangular) + 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))")) end - for i = 1:m - for j = n:-1:1 + if size(C) != size(A) + throw(DimensionMismatch("size of output, $(size(C)), does not match size of left hand side, $(size(A))")) + end + @inbounds for i in 1:m + for j in n:-1:1 Aij = A[i,j] - for k = j + 1:n - Aij -= A[i,k]*B.data[k,j] + for k in j + 1:n + Aij -= C[i,k]*B.data[k,j] end - A[i,j] = Aij/B[j,j] + iszero(B.data[j,j]) && throw(SingularException(j)) + C[i,j] = Aij / B.data[j,j] end end - A + C end -function rdiv!(A::StridedMatrix, B::UnitLowerTriangular) +function _rdiv!(C::AbstractMatrix, A::AbstractMatrix, B::UnitLowerTriangular) + 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))")) end - for i = 1:m - for j = n:-1:1 + if size(C) != size(A) + throw(DimensionMismatch("size of output, $(size(C)), does not match size of left hand side, $(size(A))")) + end + @inbounds for i in 1:m + for j in n:-1:1 Aij = A[i,j] - for k = j + 1:n - Aij -= A[i,k]*B.data[k,j] + for k in j + 1:n + Aij -= C[i,k]*B.data[k,j] end - A[i,j] = Aij + C[i,j] = Aij / oneunit(eltype(B)) end end - A + C end -for (t, tfun) in ((:Adjoint, :adjoint), (:Transpose, :transpose)) - @eval begin - function rdiv!(A::StridedMatrix, xB::LowerTriangular{<:Any,<:$t}) - B = parent(parent(xB)) - 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))")) - end - for i = 1:m - for j = n:-1:1 - Aij = A[i,j] - for k = j + 1:n - Aij -= A[i,k]*$tfun(B[j,k]) - end - A[i,j] = Aij/$tfun(B[j,j]) - end - end - A - end - function rdiv!(A::StridedMatrix, xB::UnitLowerTriangular{<:Any,<:$t}) - B = parent(parent(xB)) - 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))")) - end - for i = 1:m - for j = n:-1:1 - Aij = A[i,j] - for k = j + 1:n - Aij -= A[i,k]*$tfun(B[j,k]) - end - A[i,j] = Aij - end - end - A - end +lmul!(A::UpperTriangular, B::UpperTriangular) = UpperTriangular(lmul!(A, triu!(B.data))) +lmul!(A::UnitUpperTriangular, B::UpperTriangular) = UpperTriangular(lmul!(A, triu!(B.data))) +lmul!(A::LowerTriangular, B::LowerTriangular) = LowerTriangular(lmul!(A, tril!(B.data))) +lmul!(A::UnitLowerTriangular, B::LowerTriangular) = LowerTriangular(lmul!(A, tril!(B.data))) - function rdiv!(A::StridedMatrix, xB::UpperTriangular{<:Any,<:$t}) - B = parent(parent(xB)) - 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))")) - end - for i = 1:m - for j = 1:n - Aij = A[i,j] - for k = 1:j - 1 - Aij -= A[i,k]*$tfun(B[j,k]) - end - A[i,j] = Aij/$tfun(B[j,j]) - end - end - A - end - function rdiv!(A::StridedMatrix, xB::UnitUpperTriangular{<:Any,<:$t}) - B = parent(parent(xB)) - 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))")) - end - for i = 1:m - for j = 1:n - Aij = A[i,j] - for k = 1:j - 1 - Aij -= A[i,k]*$tfun(B[j,k]) - end - A[i,j] = Aij - end - end - A - end - end -end +ldiv!(A::UpperTriangular, B::UpperTriangular) = UpperTriangular(ldiv!(A, triu!(B.data))) +ldiv!(A::UnitUpperTriangular, B::UpperTriangular) = UpperTriangular(ldiv!(A, triu!(B.data))) +ldiv!(A::LowerTriangular, B::LowerTriangular) = LowerTriangular(ldiv!(A, tril!(B.data))) +ldiv!(A::UnitLowerTriangular, B::LowerTriangular) = LowerTriangular(ldiv!(A, tril!(B.data))) -function lmul!(A::Union{UpperTriangular,UnitUpperTriangular}, B::UpperTriangular) - UpperTriangular(lmul!(A, triu!(B.data))) -end -function lmul!(A::Union{LowerTriangular,UnitLowerTriangular}, B::LowerTriangular) - return LowerTriangular(lmul!(A, tril!(B.data))) -end -function ldiv!(xA::Union{UpperTriangular,UnitUpperTriangular}, B::UpperTriangular) - return UpperTriangular(ldiv!(xA, triu!(B.data))) -end -function ldiv!(xA::Union{LowerTriangular,UnitLowerTriangular}, B::LowerTriangular) - return LowerTriangular(ldiv!(xA, tril!(B.data))) -end +rdiv!(A::UpperTriangular, B::UpperTriangular) = UpperTriangular(rdiv!(triu!(A.data), B)) +rdiv!(A::UpperTriangular, B::UnitUpperTriangular) = UpperTriangular(rdiv!(triu!(A.data), B)) +rdiv!(A::LowerTriangular, B::LowerTriangular) = LowerTriangular(rdiv!(tril!(A.data), B)) +rdiv!(A::LowerTriangular, B::UnitLowerTriangular) = LowerTriangular(rdiv!(tril!(A.data), B)) -function rdiv!(A::UpperTriangular, B::Union{UpperTriangular,UnitUpperTriangular}) - return UpperTriangular(rdiv!(triu!(A.data), B)) -end -function rdiv!(A::LowerTriangular, B::Union{LowerTriangular,UnitLowerTriangular}) - return LowerTriangular(rdiv!(tril!(A.data), B)) -end -function rmul!(A::UpperTriangular, B::Union{UpperTriangular,UnitUpperTriangular}) - return UpperTriangular(rmul!(triu!(A.data), B)) -end -function rmul!(A::LowerTriangular, B::Union{LowerTriangular,UnitLowerTriangular}) - return LowerTriangular(rmul!(tril!(A.data), B)) -end +rmul!(A::UpperTriangular, B::UpperTriangular) = UpperTriangular(rmul!(triu!(A.data), B)) +rmul!(A::UpperTriangular, B::UnitUpperTriangular) = UpperTriangular(rmul!(triu!(A.data), B)) +rmul!(A::LowerTriangular, B::LowerTriangular) = LowerTriangular(rmul!(tril!(A.data), B)) +rmul!(A::LowerTriangular, B::UnitLowerTriangular) = LowerTriangular(rmul!(tril!(A.data), B)) # Promotion ## Promotion methods in matmul don't apply to triangular multiplication since @@ -1509,184 +1378,125 @@ end ## the element type doesn't have to be stable under division whereas that is ## necessary in the general triangular solve problem. -## Some Triangular-Triangular cases. We might want to write tailored methods -## for these cases, but I'm not sure it is worth it. - -for (f, f2!) in ((:*, :lmul!), (:\, :ldiv!)) - @eval begin - function ($f)(A::LowerTriangular, B::LowerTriangular) - TAB = typeof(($f)(zero(eltype(A)), zero(eltype(B))) + - ($f)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return LowerTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function $(f)(A::UnitLowerTriangular, B::LowerTriangular) - TAB = typeof((*)(zero(eltype(A)), zero(eltype(B))) + - (*)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return LowerTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function $(f)(A::LowerTriangular, B::UnitLowerTriangular) - TAB = typeof(($f)(zero(eltype(A)), zero(eltype(B))) + - ($f)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return LowerTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function $(f)(A::UnitLowerTriangular, B::UnitLowerTriangular) - TAB = typeof((*)(zero(eltype(A)), zero(eltype(B))) + - (*)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return UnitLowerTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function ($f)(A::UpperTriangular, B::UpperTriangular) - TAB = typeof(($f)(zero(eltype(A)), zero(eltype(B))) + - ($f)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return UpperTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function ($f)(A::UnitUpperTriangular, B::UpperTriangular) - TAB = typeof((*)(zero(eltype(A)), zero(eltype(B))) + - (*)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return UpperTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function ($f)(A::UpperTriangular, B::UnitUpperTriangular) - TAB = typeof(($f)(zero(eltype(A)), zero(eltype(B))) + - ($f)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return UpperTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - - function ($f)(A::UnitUpperTriangular, B::UnitUpperTriangular) - TAB = typeof((*)(zero(eltype(A)), zero(eltype(B))) + - (*)(zero(eltype(A)), zero(eltype(B)))) - BB = copy_similar(B, TAB) - return UnitUpperTriangular($f2!(convert(AbstractMatrix{TAB}, A), BB)) - end - end -end - -function (/)(A::LowerTriangular, B::LowerTriangular) - TAB = typeof((/)(zero(eltype(A)), one(eltype(B))) + - (/)(zero(eltype(A)), one(eltype(B)))) - AA = copy_similar(A, TAB) - return LowerTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::UnitLowerTriangular, B::LowerTriangular) - TAB = typeof((/)(zero(eltype(A)), one(eltype(B))) + - (/)(zero(eltype(A)), one(eltype(B)))) - AA = copy_similar(A, TAB) - return LowerTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::LowerTriangular, B::UnitLowerTriangular) - TAB = typeof((/)(zero(eltype(A)), one(eltype(B))) + - (/)(zero(eltype(A)), one(eltype(B)))) - AA = copy_similar(A, TAB) - return LowerTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::UnitLowerTriangular, B::UnitLowerTriangular) - TAB = typeof((*)(zero(eltype(A)), zero(eltype(B))) + - (*)(zero(eltype(A)), zero(eltype(B)))) - AA = copy_similar(A, TAB) - return UnitLowerTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::UpperTriangular, B::UpperTriangular) - TAB = typeof((/)(zero(eltype(A)), one(eltype(B))) + - (/)(zero(eltype(A)), one(eltype(B)))) - AA = copy_similar(A, TAB) - return UpperTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::UnitUpperTriangular, B::UpperTriangular) - TAB = typeof((/)(zero(eltype(A)), one(eltype(B))) + - (/)(zero(eltype(A)), one(eltype(B)))) - AA = copy_similar(A, TAB) - return UpperTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::UpperTriangular, B::UnitUpperTriangular) - TAB = typeof((/)(zero(eltype(A)), one(eltype(B))) + - (/)(zero(eltype(A)), one(eltype(B)))) - AA = copy_similar(A, TAB) - return UpperTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end -function (/)(A::UnitUpperTriangular, B::UnitUpperTriangular) - TAB = typeof((*)(zero(eltype(A)), zero(eltype(B))) + - (*)(zero(eltype(A)), zero(eltype(B)))) - AA = copy_similar(A, TAB) - return UnitUpperTriangular(rdiv!(AA, convert(AbstractMatrix{TAB}, B))) -end - -_inner_type_promotion(A,B) = promote_type(eltype(A), eltype(B), typeof(zero(eltype(A))*zero(eltype(B)) + zero(eltype(A))*zero(eltype(B)))) +_inner_type_promotion(op, ::Type{TA}, ::Type{TB}) where {TA<:Integer,TB<:Integer} = + _init_eltype(*, TA, TB) +_inner_type_promotion(op, ::Type{TA}, ::Type{TB}) where {TA,TB} = + _init_eltype(op, TA, TB) ## The general promotion methods function *(A::AbstractTriangular, B::AbstractTriangular) - TAB = _inner_type_promotion(A,B) - BB = copy_similar(B, TAB) - lmul!(convert(AbstractArray{TAB}, A), BB) + TAB = _init_eltype(*, eltype(A), eltype(B)) + if TAB <: BlasFloat + lmul!(convert(AbstractArray{TAB}, A), copy_similar(B, TAB)) + else + mul!(similar(B, TAB, size(B)), A, B) + end 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 = _inner_type_promotion(A,B) - BB = copy_similar(B, TAB) - lmul!(convert(AbstractArray{TAB}, A), BB) + TAB = _init_eltype(*, eltype(A), eltype(B)) + if TAB <: BlasFloat + lmul!(convert(AbstractArray{TAB}, A), copy_similar(B, TAB)) + else + mul!(similar(B, TAB, size(B)), A, B) + end 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) - TAB = _inner_type_promotion(A,B) - BB = copy_similar(B, TAB) - ldiv!(convert(AbstractArray{TAB}, A), BB) + TAB = _inner_type_promotion(\, eltype(A), eltype(B)) + if TAB <: BlasFloat + ldiv!(convert(AbstractArray{TAB}, A), copy_similar(B, TAB)) + else + ldiv!(similar(B, TAB, size(B)), A, B) + end end ### 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 = typeof((zero(eltype(A))*zero(eltype(B)) + zero(eltype(A))*zero(eltype(B)))/one(eltype(A))) - BB = copy_similar(B, TAB) - ldiv!(convert(AbstractArray{TAB}, A), BB) + TAB = _init_eltype(\, eltype(A), eltype(B)) + if TAB <: BlasFloat + ldiv!(convert(AbstractArray{TAB}, A), copy_similar(B, TAB)) + else + ldiv!(similar(B, TAB, size(B)), A, B) + end end ### Right division with triangle to the right hence lhs cannot be transposed. No quotients. @eval function /(A::$mat, B::Union{UnitUpperTriangular, UnitLowerTriangular}) require_one_based_indexing(A) - TAB = _inner_type_promotion(A,B) - AA = copy_similar(A, TAB) - rdiv!(AA, convert(AbstractArray{TAB}, B)) + TAB = _inner_type_promotion(/, eltype(A), eltype(B)) + if TAB <: BlasFloat + rdiv!(copy_similar(A, TAB), convert(AbstractArray{TAB}, B)) + else + _rdiv!(similar(A, TAB, size(A)), A, B) + end end ### 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 = typeof((zero(eltype(A))*zero(eltype(B)) + zero(eltype(A))*zero(eltype(B)))/one(eltype(A))) - AA = copy_similar(A, TAB) - rdiv!(AA, convert(AbstractArray{TAB}, B)) + TAB = _init_eltype(/, eltype(A), eltype(B)) + if TAB <: BlasFloat + rdiv!(copy_similar(A, TAB), convert(AbstractArray{TAB}, B)) + else + _rdiv!(similar(A, TAB, size(A)), A, B) + end 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 = _inner_type_promotion(A,B) - AA = copy_similar(A, TAB) - rmul!(AA, convert(AbstractArray{TAB}, B)) + TAB = _init_eltype(*, eltype(A), eltype(B)) + if TAB <: BlasFloat + rmul!(copy_similar(A, TAB), convert(AbstractArray{TAB}, B)) + else + mul!(similar(A, TAB, size(A)), A, B) + end end -# ambiguity resolution with definitions in linalg/rowvector.jl +# 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) -# If these are not defined, they will fallback to the versions in matmul.jl -# and dispatch to generic_matmatmul! which is very costly to compile. The methods -# below might compute an unnecessary copy. Eliminating the copy requires adding -# all the promotion logic here once again. Since these methods are probably relatively -# rare, we chose not to bother for now. -*(A::Adjoint{<:Any,<:AbstractMatrix}, B::AbstractTriangular) = copy(A) * B -*(A::Transpose{<:Any,<:AbstractMatrix}, B::AbstractTriangular) = copy(A) * B -*(A::AbstractTriangular, B::Adjoint{<:Any,<:AbstractMatrix}) = A * copy(B) -*(A::AbstractTriangular, B::Transpose{<:Any,<:AbstractMatrix}) = A * copy(B) +## Some Triangular-Triangular cases. We might want to write tailored methods +## for these cases, but I'm not sure it is worth it. +for f in (:*, :\) + @eval begin + ($f)(A::LowerTriangular, B::LowerTriangular) = + LowerTriangular(@invoke $f(A::LowerTriangular, B::AbstractMatrix)) + ($f)(A::LowerTriangular, B::UnitLowerTriangular) = + LowerTriangular(@invoke $f(A::LowerTriangular, B::AbstractMatrix)) + ($f)(A::UnitLowerTriangular, B::LowerTriangular) = + LowerTriangular(@invoke $f(A::UnitLowerTriangular, B::AbstractMatrix)) + ($f)(A::UnitLowerTriangular, B::UnitLowerTriangular) = + UnitLowerTriangular(@invoke $f(A::UnitLowerTriangular, B::AbstractMatrix)) + ($f)(A::UpperTriangular, B::UpperTriangular) = + UpperTriangular(@invoke $f(A::UpperTriangular, B::AbstractMatrix)) + ($f)(A::UpperTriangular, B::UnitUpperTriangular) = + UpperTriangular(@invoke $f(A::UpperTriangular, B::AbstractMatrix)) + ($f)(A::UnitUpperTriangular, B::UpperTriangular) = + UpperTriangular(@invoke $f(A::UnitUpperTriangular, B::AbstractMatrix)) + ($f)(A::UnitUpperTriangular, B::UnitUpperTriangular) = + UnitUpperTriangular(@invoke $f(A::UnitUpperTriangular, B::AbstractMatrix)) + end +end +(/)(A::LowerTriangular, B::LowerTriangular) = + LowerTriangular(@invoke /(A::AbstractMatrix, B::LowerTriangular)) +(/)(A::LowerTriangular, B::UnitLowerTriangular) = + LowerTriangular(@invoke /(A::AbstractMatrix, B::UnitLowerTriangular)) +(/)(A::UnitLowerTriangular, B::LowerTriangular) = + LowerTriangular(@invoke /(A::AbstractMatrix, B::LowerTriangular)) +(/)(A::UnitLowerTriangular, B::UnitLowerTriangular) = + UnitLowerTriangular(@invoke /(A::AbstractMatrix, B::UnitLowerTriangular)) +(/)(A::UpperTriangular, B::UpperTriangular) = + UpperTriangular(@invoke /(A::AbstractMatrix, B::UpperTriangular)) +(/)(A::UpperTriangular, B::UnitUpperTriangular) = + UpperTriangular(@invoke /(A::AbstractMatrix, B::UnitUpperTriangular)) +(/)(A::UnitUpperTriangular, B::UpperTriangular) = + UpperTriangular(@invoke /(A::AbstractMatrix, B::UpperTriangular)) +(/)(A::UnitUpperTriangular, B::UnitUpperTriangular) = + UnitUpperTriangular(@invoke /(A::AbstractMatrix, B::UnitUpperTriangular)) # Complex matrix power for upper triangular factor, see: # Higham and Lin, "A Schur-Padé algorithm for fractional powers of a Matrix", @@ -2304,7 +2114,7 @@ function sqrt(A::UnitUpperTriangular{T}) where T n = checksquare(B) t = typeof(sqrt(zero(T))) R = Matrix{t}(I, n, n) - tt = typeof(zero(t)*zero(t)) + tt = typeof(oneunit(t)*oneunit(t)) half = inv(R[1,1]+R[1,1]) # for general, algebraic cases. PR#20214 @inbounds for j = 1:n for i = j-1:-1:1 @@ -2312,7 +2122,7 @@ function sqrt(A::UnitUpperTriangular{T}) where T @simd for k = i+1:j-1 r -= R[i,k]*R[k,j] end - r==0 || (R[i,j] = half*r) + iszero(r) || (R[i,j] = half*r) end end return UnitUpperTriangular(R) @@ -2658,10 +2468,6 @@ end factorize(A::AbstractTriangular) = A -# disambiguation methods: *(AbstractTriangular, Adj/Trans of AbstractVector) -*(A::AbstractTriangular, B::AdjointAbsVec) = adjoint(adjoint(B) * adjoint(A)) -*(A::AbstractTriangular, B::TransposeAbsVec) = transpose(transpose(B) * transpose(A)) - # disambiguation methods: /(Adjoint of AbsVec, <:AbstractTriangular) /(u::AdjointAbsVec, A::Union{LowerTriangular,UpperTriangular}) = adjoint(adjoint(A) \ u.parent) /(u::AdjointAbsVec, A::Union{UnitLowerTriangular,UnitUpperTriangular}) = adjoint(adjoint(A) \ u.parent) diff --git a/stdlib/LinearAlgebra/src/tridiag.jl b/stdlib/LinearAlgebra/src/tridiag.jl index 5a3c7612f6784..2739400bb393c 100644 --- a/stdlib/LinearAlgebra/src/tridiag.jl +++ b/stdlib/LinearAlgebra/src/tridiag.jl @@ -134,7 +134,7 @@ function Matrix{T}(M::SymTridiagonal) where T Mf[n,n] = symmetric(M.dv[n], :U) return Mf end -Matrix(M::SymTridiagonal{T}) where {T} = Matrix{T}(M) +Matrix(M::SymTridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M) Array(M::SymTridiagonal) = Matrix(M) size(A::SymTridiagonal) = (length(A.dv), length(A.dv)) @@ -149,7 +149,7 @@ function size(A::SymTridiagonal, d::Integer) end 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} = zeros(T, dims...) +similar(S::SymTridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(S.dv, T, dims) copyto!(dest::SymTridiagonal, src::SymTridiagonal) = (copyto!(dest.dv, src.dv); copyto!(dest.ev, _evview(src)); dest) @@ -172,6 +172,8 @@ Base.copy(S::Adjoint{<:Any,<:SymTridiagonal}) = SymTridiagonal(map(x -> copy.(ad ishermitian(S::SymTridiagonal) = isreal(S.dv) && isreal(_evview(S)) issymmetric(S::SymTridiagonal) = true +tr(S::SymTridiagonal) = sum(S.dv) + function diag(M::SymTridiagonal{T}, n::Integer=0) where T<:Number # every branch call similar(..., ::Int) to make sure the # same vector type is returned independent of n @@ -211,100 +213,66 @@ end *(B::Number, A::SymTridiagonal) = SymTridiagonal(B*A.dv, B*A.ev) /(A::SymTridiagonal, B::Number) = SymTridiagonal(A.dv/B, A.ev/B) \(B::Number, A::SymTridiagonal) = SymTridiagonal(B\A.dv, B\A.ev) -==(A::SymTridiagonal, B::SymTridiagonal) = (A.dv==B.dv) && (_evview(A)==_evview(B)) - -@inline mul!(A::StridedVecOrMat, B::SymTridiagonal, C::StridedVecOrMat, - alpha::Number, beta::Number) = - _mul!(A, B, C, MulAddMul(alpha, beta)) - -@inline function _mul!(C::StridedVecOrMat, S::SymTridiagonal, B::StridedVecOrMat, - _add::MulAddMul) - m, n = size(B, 1), size(B, 2) - if !(m == size(S, 1) == size(C, 1)) - throw(DimensionMismatch("A has first dimension $(size(S,1)), B has $(size(B,1)), C has $(size(C,1)) but all must match")) - end - if n != size(C, 2) - throw(DimensionMismatch("second dimension of B, $n, doesn't match second dimension of C, $(size(C,2))")) - end - - if m == 0 - return C - elseif iszero(_add.alpha) - return _rmul_or_fill!(C, _add.beta) - end - - α = S.dv - β = S.ev - @inbounds begin - for j = 1:n - x₊ = B[1, j] - x₀ = zero(x₊) - # If m == 1 then β[1] is out of bounds - β₀ = m > 1 ? zero(β[1]) : zero(eltype(β)) - for i = 1:m - 1 - x₋, x₀, x₊ = x₀, x₊, B[i + 1, j] - β₋, β₀ = β₀, β[i] - _modify!(_add, β₋*x₋ + α[i]*x₀ + β₀*x₊, C, (i, j)) - end - _modify!(_add, β₀*x₀ + α[m]*x₊, C, (m, j)) - end - end - - return C -end +==(A::SymTridiagonal{<:Number}, B::SymTridiagonal{<:Number}) = + (A.dv == B.dv) && (_evview(A) == _evview(B)) +==(A::SymTridiagonal, B::SymTridiagonal) = + size(A) == size(B) && all(i -> A[i,i] == B[i,i], axes(A, 1)) && (_evview(A) == _evview(B)) function dot(x::AbstractVector, S::SymTridiagonal, y::AbstractVector) require_one_based_indexing(x, y) nx, ny = length(x), length(y) - (nx == size(S, 1) == ny) || throw(DimensionMismatch()) - if iszero(nx) - return dot(zero(eltype(x)), zero(eltype(S)), zero(eltype(y))) + (nx == size(S, 1) == ny) || throw(DimensionMismatch("dot")) + if nx ≤ 1 + nx == 0 && return dot(zero(eltype(x)), zero(eltype(S)), zero(eltype(y))) + return dot(x[1], S.dv[1], y[1]) end dv, ev = S.dv, S.ev - x₀ = x[1] - x₊ = x[2] - sub = transpose(ev[1]) - r = dot(adjoint(dv[1])*x₀ + adjoint(sub)*x₊, y[1]) - @inbounds for j in 2:nx-1 - x₋, x₀, x₊ = x₀, x₊, x[j+1] - sup, sub = transpose(sub), transpose(ev[j]) - r += dot(adjoint(sup)*x₋ + adjoint(dv[j])*x₀ + adjoint(sub)*x₊, y[j]) - end - r += dot(adjoint(transpose(sub))*x₀ + adjoint(dv[nx])*x₊, y[nx]) + @inbounds begin + x₀ = x[1] + x₊ = x[2] + sub = transpose(ev[1]) + r = dot(adjoint(dv[1])*x₀ + adjoint(sub)*x₊, y[1]) + for j in 2:nx-1 + x₋, x₀, x₊ = x₀, x₊, x[j+1] + sup, sub = transpose(sub), transpose(ev[j]) + r += dot(adjoint(sup)*x₋ + adjoint(dv[j])*x₀ + adjoint(sub)*x₊, y[j]) + end + r += dot(adjoint(transpose(sub))*x₀ + adjoint(dv[nx])*x₊, y[nx]) + end return r end -(\)(T::SymTridiagonal, B::StridedVecOrMat) = ldlt(T)\B +(\)(T::SymTridiagonal, B::AbstractVecOrMat) = ldlt(T)\B # division with optional shift for use in shifted-Hessenberg solvers (hessenberg.jl): ldiv!(A::SymTridiagonal, B::AbstractVecOrMat; shift::Number=false) = ldiv!(ldlt(A, shift=shift), B) rdiv!(B::AbstractVecOrMat, A::SymTridiagonal; shift::Number=false) = rdiv!(B, ldlt(A, shift=shift)) -eigen!(A::SymTridiagonal{<:BlasReal}) = Eigen(LAPACK.stegr!('V', A.dv, A.ev)...) -eigen(A::SymTridiagonal{T}) where T = eigen!(copy_oftype(A, eigtype(T))) +eigen!(A::SymTridiagonal{<:BlasReal,<:StridedVector}) = Eigen(LAPACK.stegr!('V', A.dv, A.ev)...) +eigen(A::SymTridiagonal{T}) where T = eigen!(copymutable_oftype(A, eigtype(T))) -eigen!(A::SymTridiagonal{<:BlasReal}, irange::UnitRange) = +eigen!(A::SymTridiagonal{<:BlasReal,<:StridedVector}, irange::UnitRange) = Eigen(LAPACK.stegr!('V', 'I', A.dv, A.ev, 0.0, 0.0, irange.start, irange.stop)...) eigen(A::SymTridiagonal{T}, irange::UnitRange) where T = - eigen!(copy_oftype(A, eigtype(T)), irange) + eigen!(copymutable_oftype(A, eigtype(T)), irange) -eigen!(A::SymTridiagonal{<:BlasReal}, vl::Real, vu::Real) = +eigen!(A::SymTridiagonal{<:BlasReal,<:StridedVector}, vl::Real, vu::Real) = Eigen(LAPACK.stegr!('V', 'V', A.dv, A.ev, vl, vu, 0, 0)...) eigen(A::SymTridiagonal{T}, vl::Real, vu::Real) where T = - eigen!(copy_oftype(A, eigtype(T)), vl, vu) + eigen!(copymutable_oftype(A, eigtype(T)), vl, vu) -eigvals!(A::SymTridiagonal{<:BlasReal}) = LAPACK.stev!('N', A.dv, A.ev)[1] -eigvals(A::SymTridiagonal{T}) where T = eigvals!(copy_oftype(A, eigtype(T))) +eigvals!(A::SymTridiagonal{<:BlasReal,<:StridedVector}) = LAPACK.stev!('N', A.dv, A.ev)[1] +eigvals(A::SymTridiagonal{T}) where T = eigvals!(copymutable_oftype(A, eigtype(T))) -eigvals!(A::SymTridiagonal{<:BlasReal}, irange::UnitRange) = +eigvals!(A::SymTridiagonal{<:BlasReal,<:StridedVector}, irange::UnitRange) = LAPACK.stegr!('N', 'I', A.dv, A.ev, 0.0, 0.0, irange.start, irange.stop)[1] eigvals(A::SymTridiagonal{T}, irange::UnitRange) where T = - eigvals!(copy_oftype(A, eigtype(T)), irange) + eigvals!(copymutable_oftype(A, eigtype(T)), irange) -eigvals!(A::SymTridiagonal{<:BlasReal}, vl::Real, vu::Real) = +eigvals!(A::SymTridiagonal{<:BlasReal,<:StridedVector}, vl::Real, vu::Real) = LAPACK.stegr!('N', 'V', A.dv, A.ev, vl, vu, 0, 0)[1] eigvals(A::SymTridiagonal{T}, vl::Real, vu::Real) where T = - eigvals!(copy_oftype(A, eigtype(T)), vl, vu) + eigvals!(copymutable_oftype(A, eigtype(T)), vl, vu) #Computes largest and smallest eigenvalue eigmax(A::SymTridiagonal) = eigvals(A, size(A, 1):size(A, 1))[1] @@ -349,7 +317,7 @@ julia> eigvecs(A, [1.]) -0.5547001962252291 ``` """ -eigvecs(A::SymTridiagonal{<:BlasFloat}, eigvals::Vector{<:Real}) = LAPACK.stein!(A.dv, A.ev, eigvals) +eigvecs(A::SymTridiagonal{<:BlasFloat,<:StridedVector}, eigvals::Vector{<:Real}) = LAPACK.stein!(A.dv, A.ev, eigvals) function svdvals!(A::SymTridiagonal) vals = eigvals!(A) @@ -522,6 +490,9 @@ Tridiagonal(dl::V, d::V, du::V, du2::V) where {T,V<:AbstractVector{T}} = Tridiag function Tridiagonal{T}(dl::AbstractVector, d::AbstractVector, du::AbstractVector) where {T} Tridiagonal(map(x->convert(AbstractVector{T}, x), (dl, d, du))...) end +function Tridiagonal{T,V}(A::Tridiagonal) where {T,V<:AbstractVector{T}} + Tridiagonal{T,V}(A.dl, A.d, A.du) +end """ Tridiagonal(A) @@ -571,22 +542,23 @@ function size(M::Tridiagonal, d::Integer) end end -function Matrix{T}(M::Tridiagonal{T}) where T +function Matrix{T}(M::Tridiagonal) where {T} A = zeros(T, size(M)) - for i = 1:length(M.d) + n = length(M.d) + n == 0 && return A + for i in 1:n-1 A[i,i] = M.d[i] - end - for i = 1:length(M.d)-1 A[i+1,i] = M.dl[i] A[i,i+1] = M.du[i] end + A[n,n] = M.d[n] A end -Matrix(M::Tridiagonal{T}) where {T} = Matrix{T}(M) +Matrix(M::Tridiagonal{T}) where {T} = Matrix{promote_type(T, typeof(zero(T)))}(M) Array(M::Tridiagonal) = Matrix(M) similar(M::Tridiagonal, ::Type{T}) where {T} = Tridiagonal(similar(M.dl, T), similar(M.d, T), similar(M.du, T)) -similar(M::Tridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = zeros(T, dims...) +similar(M::Tridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = similar(M.d, T, dims) # Operations on Tridiagonal matrices copyto!(dest::Tridiagonal, src::Tridiagonal) = (copyto!(dest.dl, src.dl); copyto!(dest.d, src.d); copyto!(dest.du, src.du); dest) @@ -613,7 +585,7 @@ Base.copy(tS::Transpose{<:Any,<:Tridiagonal}) = (S = tS.parent; Tridiagonal(map( ishermitian(S::Tridiagonal) = all(ishermitian, S.d) && all(Iterators.map((x, y) -> x == y', S.du, S.dl)) issymmetric(S::Tridiagonal) = all(issymmetric, S.d) && all(Iterators.map((x, y) -> x == transpose(y), S.du, S.dl)) -\(A::Adjoint{<:Any,<:Tridiagonal}, B::Adjoint{<:Any,<:StridedVecOrMat}) = copy(A) \ B +\(A::Adjoint{<:Any,<:Tridiagonal}, B::Adjoint{<:Any,<:AbstractVecOrMat}) = copy(A) \ B function diag(M::Tridiagonal{T}, n::Integer=0) where T # every branch call similar(..., ::Int) to make sure the @@ -730,6 +702,8 @@ function triu!(M::Tridiagonal{T}, k::Integer=0) where T return M end +tr(M::Tridiagonal) = sum(M.d) + ################### # Generic methods # ################### @@ -840,18 +814,21 @@ function dot(x::AbstractVector, A::Tridiagonal, y::AbstractVector) require_one_based_indexing(x, y) nx, ny = length(x), length(y) (nx == size(A, 1) == ny) || throw(DimensionMismatch()) - if iszero(nx) - return dot(zero(eltype(x)), zero(eltype(A)), zero(eltype(y))) - end - x₀ = x[1] - x₊ = x[2] - dl, d, du = A.dl, A.d, A.du - r = dot(adjoint(d[1])*x₀ + adjoint(dl[1])*x₊, y[1]) - @inbounds for j in 2:nx-1 - x₋, x₀, x₊ = x₀, x₊, x[j+1] - r += dot(adjoint(du[j-1])*x₋ + adjoint(d[j])*x₀ + adjoint(dl[j])*x₊, y[j]) - end - r += dot(adjoint(du[nx-1])*x₀ + adjoint(d[nx])*x₊, y[nx]) + if nx ≤ 1 + nx == 0 && return dot(zero(eltype(x)), zero(eltype(A)), zero(eltype(y))) + return dot(x[1], A.d[1], y[1]) + end + @inbounds begin + x₀ = x[1] + x₊ = x[2] + dl, d, du = A.dl, A.d, A.du + r = dot(adjoint(d[1])*x₀ + adjoint(dl[1])*x₊, y[1]) + for j in 2:nx-1 + x₋, x₀, x₊ = x₀, x₊, x[j+1] + r += dot(adjoint(du[j-1])*x₋ + adjoint(d[j])*x₀ + adjoint(dl[j])*x₊, y[j]) + end + r += dot(adjoint(du[nx-1])*x₀ + adjoint(d[nx])*x₊, y[nx]) + end return r end diff --git a/stdlib/LinearAlgebra/src/uniformscaling.jl b/stdlib/LinearAlgebra/src/uniformscaling.jl index 98e3ed4dfcc59..21ae8a1bb913a 100644 --- a/stdlib/LinearAlgebra/src/uniformscaling.jl +++ b/stdlib/LinearAlgebra/src/uniformscaling.jl @@ -118,7 +118,7 @@ function show(io::IO, ::MIME"text/plain", J::UniformScaling) end copy(J::UniformScaling) = UniformScaling(J.λ) -Base.convert(::Type{UniformScaling{T}}, J::UniformScaling) where {T} = UniformScaling(convert(T, J.λ)) +Base.convert(::Type{UniformScaling{T}}, J::UniformScaling) where {T} = UniformScaling(convert(T, J.λ))::UniformScaling{T} conj(J::UniformScaling) = UniformScaling(conj(J.λ)) real(J::UniformScaling) = UniformScaling(real(J.λ)) @@ -179,7 +179,7 @@ for (t1, t2) in ((:UnitUpperTriangular, :UpperTriangular), (:UnitLowerTriangular, :LowerTriangular)) @eval begin function (+)(UL::$t1, J::UniformScaling) - ULnew = copy_oftype(UL.data, Base._return_type(+, Tuple{eltype(UL), typeof(J)})) + ULnew = copymutable_oftype(UL.data, Base.promote_op(+, eltype(UL), typeof(J))) for i in axes(ULnew, 1) ULnew[i,i] = one(ULnew[i,i]) + J end @@ -193,8 +193,8 @@ end # However, to preserve type stability, we do not special-case a # UniformScaling{<:Complex} that happens to be real. function (+)(A::Hermitian, J::UniformScaling{<:Complex}) - TS = Base._return_type(+, Tuple{eltype(A), typeof(J)}) - B = copytri!(copy_oftype(parent(A), TS), A.uplo, true) + TS = Base.promote_op(+, eltype(A), typeof(J)) + B = copytri!(copymutable_oftype(parent(A), TS), A.uplo, true) for i in diagind(B) B[i] = A[i] + J end @@ -202,8 +202,8 @@ function (+)(A::Hermitian, J::UniformScaling{<:Complex}) end function (-)(J::UniformScaling{<:Complex}, A::Hermitian) - TS = Base._return_type(+, Tuple{eltype(A), typeof(J)}) - B = copytri!(copy_oftype(parent(A), TS), A.uplo, true) + TS = Base.promote_op(+, eltype(A), typeof(J)) + B = copytri!(copymutable_oftype(parent(A), TS), A.uplo, true) B .= .-B for i in diagind(B) B[i] = J - A[i] @@ -213,7 +213,7 @@ end function (+)(A::AbstractMatrix, J::UniformScaling) checksquare(A) - B = copy_oftype(A, Base._return_type(+, Tuple{eltype(A), typeof(J)})) + B = copymutable_oftype(A, Base.promote_op(+, eltype(A), typeof(J))) for i in intersect(axes(A,1), axes(A,2)) @inbounds B[i,i] += J end @@ -222,7 +222,7 @@ end function (-)(J::UniformScaling, A::AbstractMatrix) checksquare(A) - B = convert(AbstractMatrix{Base._return_type(+, Tuple{eltype(A), typeof(J)})}, -A) + B = convert(AbstractMatrix{Base.promote_op(+, eltype(A), typeof(J))}, -A) for i in intersect(axes(A,1), axes(A,2)) @inbounds B[i,i] += J end @@ -381,6 +381,22 @@ function copyto!(A::AbstractMatrix, J::UniformScaling) return A end +function copyto!(A::Diagonal, J::UniformScaling) + A.diag .= J.λ + return A +end +function copyto!(A::Union{Bidiagonal, SymTridiagonal}, J::UniformScaling) + A.ev .= 0 + A.dv .= J.λ + return A +end +function copyto!(A::Tridiagonal, J::UniformScaling) + A.dl .= 0 + A.du .= 0 + A.d .= J.λ + return A +end + function cond(J::UniformScaling{T}) where T onereal = inv(one(real(J.λ))) return J.λ ≠ zero(T) ? onereal : oftype(onereal, Inf) @@ -403,10 +419,14 @@ promote_to_arrays(n,k, ::Type{T}, A, B, Cs...) where {T} = (promote_to_arrays_(n[k], T, A), promote_to_arrays_(n[k+1], T, B), promote_to_arrays(n,k+2, T, Cs...)...) promote_to_array_type(A::Tuple{Vararg{Union{AbstractVecOrMat,UniformScaling,Number}}}) = Matrix +_us2number(A) = A +_us2number(J::UniformScaling) = J.λ + for (f, _f, dim, name) in ((:hcat, :_hcat, 1, "rows"), (:vcat, :_vcat, 2, "cols")) @eval begin @inline $f(A::Union{AbstractVecOrMat,UniformScaling}...) = $_f(A...) - @inline $f(A::Union{AbstractVecOrMat,UniformScaling,Number}...) = $_f(A...) + # if there's a Number present, J::UniformScaling must be 1x1-dimensional + @inline $f(A::Union{AbstractVecOrMat,UniformScaling,Number}...) = $f(map(_us2number, A)...) function $_f(A::Union{AbstractVecOrMat,UniformScaling,Number}...; array_type = promote_to_array_type(A)) n = -1 for a in A @@ -509,10 +529,6 @@ Array{T}(s::UniformScaling, m::Integer, n::Integer) where {T} = Matrix{T}(s, m, Array(s::UniformScaling, m::Integer, n::Integer) = Matrix(s, m, n) Array(s::UniformScaling, dims::Dims{2}) = Matrix(s, dims) -## Diagonal construction from UniformScaling -Diagonal{T}(s::UniformScaling, m::Integer) where {T} = Diagonal{T}(fill(T(s.λ), m)) -Diagonal(s::UniformScaling, m::Integer) = Diagonal{eltype(s)}(s, m) - dot(A::AbstractMatrix, J::UniformScaling) = dot(tr(A), J.λ) dot(J::UniformScaling, A::AbstractMatrix) = dot(J.λ, tr(A)) @@ -523,8 +539,3 @@ dot(x::AbstractVector, a::Union{Real,Complex}, y::AbstractVector) = a*dot(x, y) # muladd Base.muladd(A::UniformScaling, B::UniformScaling, z::UniformScaling) = UniformScaling(A.λ * B.λ + z.λ) -Base.muladd(A::Union{Diagonal, UniformScaling}, B::Union{Diagonal, UniformScaling}, z::Union{Diagonal, UniformScaling}) = - Diagonal(_diag_or_value(A) .* _diag_or_value(B) .+ _diag_or_value(z)) - -_diag_or_value(A::Diagonal) = A.diag -_diag_or_value(A::UniformScaling) = A.λ diff --git a/stdlib/LinearAlgebra/test/abstractq.jl b/stdlib/LinearAlgebra/test/abstractq.jl new file mode 100644 index 0000000000000..e3f48c7b2e3fd --- /dev/null +++ b/stdlib/LinearAlgebra/test/abstractq.jl @@ -0,0 +1,89 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module TestAbstractQ + +using Test +using LinearAlgebra +using LinearAlgebra: AbstractQ, AdjointQ +import LinearAlgebra: lmul!, rmul! +import Base: size, convert + +n = 5 + +@testset "custom AbstractQ type" begin + struct MyQ{T,S<:AbstractQ{T}} <: AbstractQ{T} + Q::S + end + MyQ{T}(Q::AbstractQ) where {T} = (P = convert(AbstractQ{T}, Q); MyQ{T,typeof(P)}(P)) + MyQ(Q::MyQ) = Q + + Base.size(Q::MyQ) = size(Q.Q) + LinearAlgebra.lmul!(Q::MyQ, B::AbstractVecOrMat) = lmul!(Q.Q, B) + LinearAlgebra.lmul!(adjQ::AdjointQ{<:Any,<:MyQ}, B::AbstractVecOrMat) = lmul!(parent(adjQ).Q', B) + LinearAlgebra.rmul!(A::AbstractMatrix, Q::MyQ) = rmul!(A, Q.Q) + LinearAlgebra.rmul!(A::AbstractMatrix, adjQ::AdjointQ{<:Any,<:MyQ}) = rmul!(A, parent(adjQ).Q') + Base.convert(::Type{AbstractQ{T}}, Q::MyQ) where {T} = MyQ{T}(Q.Q) + LinearAlgebra.det(Q::MyQ) = det(Q.Q) + + for T in (Float64, ComplexF64) + A = rand(T, n, n) + F = qr(A) + Q = MyQ(F.Q) + @test ndims(Q) == 2 + T <: Real && @test transpose(Q) == adjoint(Q) + 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*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 abs(det(Q)) ≈ 1 + @test logabsdet(Q)[1] ≈ 0 atol=2n*eps(real(T)) + y = rand(T, n) + @test Q * y ≈ Q.Q * y ≈ Q' \ y ≈ ldiv!(Q', copy(y)) ≈ ldiv!(zero(y), Q', y) + @test Q'y ≈ Q.Q' * y ≈ Q \ y ≈ ldiv!(Q, copy(y)) ≈ ldiv!(zero(y), Q, y) + @test y'Q ≈ y'Q.Q ≈ y' / Q' + @test y'Q' ≈ y'Q.Q' ≈ y' / Q + y = Matrix(y') + @test y*Q ≈ y*Q.Q ≈ y / Q' ≈ rdiv!(copy(y), Q') + @test y*Q' ≈ y*Q.Q' ≈ y / Q ≈ rdiv!(copy(y), Q) + Y = rand(T, n, n); X = similar(Y) + for transQ in (identity, adjoint), transY in (identity, adjoint), Y in (Y, Y') + @test mul!(X, transQ(Q), transY(Y)) ≈ transQ(Q) * transY(Y) ≈ transQ(Q.Q) * transY(Y) + @test mul!(X, transY(Y), transQ(Q)) ≈ transY(Y) * transQ(Q) ≈ transY(Y) * transQ(Q.Q) + end + @test convert(Matrix, Q) ≈ Matrix(Q) ≈ Q[:,:] ≈ copyto!(zeros(T, size(Q)), Q) ≈ Q.Q*I + @test convert(Matrix, Q') ≈ Matrix(Q') ≈ (Q')[:,:] ≈ copyto!(zeros(T, size(Q)), Q') ≈ Q.Q'*I + @test Q[1,:] == Q.Q[1,:] == view(Q, 1, :) + @test Q[:,1] == Q.Q[:,1] == view(Q, :, 1) + @test Q[1,1] == Q.Q[1,1] + @test Q[:] == Q.Q[:] + @test Q[:,1:3] == Q.Q[:,1:3] == view(Q, :, 1:3) + @test Q[:,1:3] ≈ Matrix(Q)[:,1:3] + @test Q[2:3,2:3] == view(Q, 2:3, 2:3) ≈ Matrix(Q)[2:3,2:3] + @test_throws BoundsError Q[0,1] + @test_throws BoundsError Q[n+1,1] + @test_throws BoundsError Q[1,0] + @test_throws BoundsError Q[1,n+1] + @test_throws BoundsError Q[:,1:n+1] + @test_throws BoundsError Q[:,0:n] + for perm in ((1, 2), (2, 1)) + P = PermutedDimsArray(zeros(T, size(Q)), perm) + @test copyto!(P, Q) ≈ Matrix(Q) + end + x = randn(T) + @test x * Q ≈ (x*I)*Q ≈ x * Q.Q + @test Q * x ≈ Q*(x*I) ≈ Q.Q * x + @test x * Q' ≈ (x*I)* Q' ≈ x * Q.Q' + @test Q' * x ≈ Q'*(x*I) ≈ Q.Q' * x + x = rand(T, 1) + Q = MyQ(qr(rand(T, 1, 1)).Q) + @test x * Q ≈ x * Q.Q + @test x * Q' ≈ x * Q.Q' + @test Q * x ≈ Q.Q * x + @test Q' * x ≈ Q.Q' * x + end +end + +end # module diff --git a/stdlib/LinearAlgebra/test/adjtrans.jl b/stdlib/LinearAlgebra/test/adjtrans.jl index 7b782d463768d..e40beb29787cf 100644 --- a/stdlib/LinearAlgebra/test/adjtrans.jl +++ b/stdlib/LinearAlgebra/test/adjtrans.jl @@ -489,13 +489,13 @@ end @test B == A .* A' end -@testset "test show methods for $t of Factorizations" for t in (Adjoint, Transpose) - A = randn(4, 4) +@testset "test show methods for $t of Factorizations" for t in (adjoint, transpose) + A = randn(ComplexF64, 4, 4) F = lu(A) Fop = t(F) - @test "LinearAlgebra."*sprint(show, Fop) == + @test sprint(show, Fop) == "$t of "*sprint(show, parent(Fop)) - @test "LinearAlgebra."*sprint((io, t) -> show(io, MIME"text/plain"(), t), Fop) == + @test sprint((io, t) -> show(io, MIME"text/plain"(), t), Fop) == "$t of "*sprint((io, t) -> show(io, MIME"text/plain"(), t), parent(Fop)) end @@ -588,24 +588,59 @@ end @test transpose(Int[]) * Int[] == 0 end -@testset "reductions: $adjtrans" for adjtrans in [transpose, adjoint] - mat = rand(ComplexF64, 3,5) - @test sum(adjtrans(mat)) ≈ sum(collect(adjtrans(mat))) - @test sum(adjtrans(mat), dims=1) ≈ sum(collect(adjtrans(mat)), dims=1) - @test sum(adjtrans(mat), dims=(1,2)) ≈ sum(collect(adjtrans(mat)), dims=(1,2)) - - @test sum(imag, adjtrans(mat)) ≈ sum(imag, collect(adjtrans(mat))) - @test sum(imag, adjtrans(mat), dims=1) ≈ sum(imag, collect(adjtrans(mat)), dims=1) - - mat = [rand(ComplexF64,2,2) for _ in 1:3, _ in 1:5] - @test sum(adjtrans(mat)) ≈ sum(collect(adjtrans(mat))) - @test sum(adjtrans(mat), dims=1) ≈ sum(collect(adjtrans(mat)), dims=1) - @test sum(adjtrans(mat), dims=(1,2)) ≈ sum(collect(adjtrans(mat)), dims=(1,2)) +@testset "reductions: $adjtrans" for adjtrans in (transpose, adjoint) + for (reduction, reduction!, op) in ((sum, sum!, +), (prod, prod!, *), (minimum, minimum!, min), (maximum, maximum!, max)) + T = op in (max, min) ? Float64 : ComplexF64 + mat = rand(T, 3,5) + rd1 = zeros(T, 1, 3) + rd2 = zeros(T, 5, 1) + rd3 = zeros(T, 1, 1) + @test reduction(adjtrans(mat)) ≈ reduction(copy(adjtrans(mat))) + @test reduction(adjtrans(mat), dims=1) ≈ reduction(copy(adjtrans(mat)), dims=1) + @test reduction(adjtrans(mat), dims=2) ≈ reduction(copy(adjtrans(mat)), dims=2) + @test reduction(adjtrans(mat), dims=(1,2)) ≈ reduction(copy(adjtrans(mat)), dims=(1,2)) + + @test reduction!(rd1, adjtrans(mat)) ≈ reduction!(rd1, copy(adjtrans(mat))) + @test reduction!(rd2, adjtrans(mat)) ≈ reduction!(rd2, copy(adjtrans(mat))) + @test reduction!(rd3, adjtrans(mat)) ≈ reduction!(rd3, copy(adjtrans(mat))) + + @test reduction(imag, adjtrans(mat)) ≈ reduction(imag, copy(adjtrans(mat))) + @test reduction(imag, adjtrans(mat), dims=1) ≈ reduction(imag, copy(adjtrans(mat)), dims=1) + @test reduction(imag, adjtrans(mat), dims=2) ≈ reduction(imag, copy(adjtrans(mat)), dims=2) + @test reduction(imag, adjtrans(mat), dims=(1,2)) ≈ reduction(imag, copy(adjtrans(mat)), dims=(1,2)) + + @test Base.mapreducedim!(imag, op, rd1, adjtrans(mat)) ≈ Base.mapreducedim!(imag, op, rd1, copy(adjtrans(mat))) + @test Base.mapreducedim!(imag, op, rd2, adjtrans(mat)) ≈ Base.mapreducedim!(imag, op, rd2, copy(adjtrans(mat))) + @test Base.mapreducedim!(imag, op, rd3, adjtrans(mat)) ≈ Base.mapreducedim!(imag, op, rd3, copy(adjtrans(mat))) + + op in (max, min) && continue + mat = [rand(T,2,2) for _ in 1:3, _ in 1:5] + rd1 = fill(zeros(T, 2, 2), 1, 3) + rd2 = fill(zeros(T, 2, 2), 5, 1) + rd3 = fill(zeros(T, 2, 2), 1, 1) + @test reduction(adjtrans(mat)) ≈ reduction(copy(adjtrans(mat))) + @test reduction(adjtrans(mat), dims=1) ≈ reduction(copy(adjtrans(mat)), dims=1) + @test reduction(adjtrans(mat), dims=2) ≈ reduction(copy(adjtrans(mat)), dims=2) + @test reduction(adjtrans(mat), dims=(1,2)) ≈ reduction(copy(adjtrans(mat)), dims=(1,2)) + + @test reduction(imag, adjtrans(mat)) ≈ reduction(imag, copy(adjtrans(mat))) + @test reduction(x -> x[1,2], adjtrans(mat)) ≈ reduction(x -> x[1,2], copy(adjtrans(mat))) + @test reduction(imag, adjtrans(mat), dims=1) ≈ reduction(imag, copy(adjtrans(mat)), dims=1) + @test reduction(x -> x[1,2], adjtrans(mat), dims=1) ≈ reduction(x -> x[1,2], copy(adjtrans(mat)), dims=1) + end + # see #46605 + Ac = [1 2; 3 4]' + @test mapreduce(identity, (x, y) -> 10x+y, copy(Ac)) == mapreduce(identity, (x, y) -> 10x+y, Ac) == 1234 + @test extrema([3,7,4]') == (3, 7) + @test mapreduce(x -> [x;;;], +, [1, 2, 3]') == sum(x -> [x;;;], [1, 2, 3]') == [6;;;] + @test mapreduce(string, *, [1 2; 3 4]') == mapreduce(string, *, copy([1 2; 3 4]')) == "1234" +end - @test sum(imag, adjtrans(mat)) ≈ sum(imag, collect(adjtrans(mat))) - @test sum(x -> x[1,2], adjtrans(mat)) ≈ sum(x -> x[1,2], collect(adjtrans(mat))) - @test sum(imag, adjtrans(mat), dims=1) ≈ sum(imag, collect(adjtrans(mat)), dims=1) - @test sum(x -> x[1,2], adjtrans(mat), dims=1) ≈ sum(x -> x[1,2], collect(adjtrans(mat)), dims=1) +@testset "trace" begin + for T in (Float64, ComplexF64), t in (adjoint, transpose) + A = randn(T, 10, 10) + @test tr(t(A)) == tr(copy(t(A))) == t(tr(A)) + end end end # module TestAdjointTranspose diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index 422984d84eb6b..89f2b21a6a973 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -13,6 +13,12 @@ using .Main.Furlongs isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) using .Main.Quaternions +isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "InfiniteArrays.jl")) +using .Main.InfiniteArrays + +isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +using .Main.FillArrays + include("testutils.jl") # test_approx_eq_modphase n = 10 #Size of test matrix @@ -52,6 +58,9 @@ Random.seed!(1) # from matrix @test Bidiagonal(ubd, :U) == Bidiagonal(Matrix(ubd), :U) == ubd @test Bidiagonal(lbd, :L) == Bidiagonal(Matrix(lbd), :L) == lbd + # from its own type + @test typeof(ubd)(ubd) === ubd + @test typeof(lbd)(lbd) === lbd end @test eltype(Bidiagonal{elty}([1,2,3,4], [1.0f0,2.0f0,3.0f0], :U)) == elty @test eltype(Bidiagonal([1,2,3,4], [1.0f0,2.0f0,3.0f0], :U)) == Float32 # promotion test @@ -126,12 +135,12 @@ Random.seed!(1) @testset "Constructor and basic properties" begin @test size(T, 1) == size(T, 2) == n @test size(T) == (n, n) - @test Array(T) == diagm(0 => dv, (uplo == :U ? 1 : -1) => ev) + @test Array(T) == diagm(0 => dv, (uplo === :U ? 1 : -1) => ev) @test Bidiagonal(Array(T), uplo) == T @test big.(T) == T - @test Array(abs.(T)) == abs.(diagm(0 => dv, (uplo == :U ? 1 : -1) => ev)) - @test Array(real(T)) == real(diagm(0 => dv, (uplo == :U ? 1 : -1) => ev)) - @test Array(imag(T)) == imag(diagm(0 => dv, (uplo == :U ? 1 : -1) => ev)) + @test Array(abs.(T)) == abs.(diagm(0 => dv, (uplo === :U ? 1 : -1) => ev)) + @test Array(real(T)) == real(diagm(0 => dv, (uplo === :U ? 1 : -1) => ev)) + @test Array(imag(T)) == imag(diagm(0 => dv, (uplo === :U ? 1 : -1) => ev)) end @testset for func in (conj, transpose, adjoint) @@ -215,6 +224,17 @@ Random.seed!(1) end end + @testset "trace" begin + for uplo in (:U, :L) + B = Bidiagonal(dv, ev, uplo) + if relty <: Integer + @test tr(B) == tr(Matrix(B)) + else + @test tr(B) ≈ tr(Matrix(B)) rtol=2eps(relty) + end + end + end + Tfull = Array(T) @testset "Linear solves" begin if relty <: AbstractFloat @@ -309,36 +329,35 @@ Random.seed!(1) @test norm(x-tx,Inf) <= 4*condT*max(eps()*norm(tx,Inf), eps(promty)*norm(x,Inf)) end @testset "Specialized multiplication/division" begin + getval(x) = x + getval(x::Furlong) = x.val function _bidiagdivmultest(T, x, typemul=T.uplo == 'U' ? UpperTriangular : Matrix, typediv=T.uplo == 'U' ? UpperTriangular : Matrix, typediv2=T.uplo == 'U' ? UpperTriangular : Matrix) TM = Matrix(T) - @test (T*x)::typemul ≈ TM*x #broken=eltype(x) <: Furlong - @test (x*T)::typemul ≈ x*TM #broken=eltype(x) <: Furlong - @test (x\T)::typediv ≈ x\TM #broken=eltype(T) <: Furlong - @test (T/x)::typediv ≈ TM/x #broken=eltype(T) <: Furlong + @test map(getval, (T*x)::typemul) ≈ map(getval, TM*x) + @test map(getval, (x*T)::typemul) ≈ map(getval, x*TM) + @test map(getval, (x\T)::typediv) ≈ map(getval, x\TM) + @test map(getval, (T/x)::typediv) ≈ map(getval, TM/x) if !isa(x, Number) - @test (T\x)::typediv2 ≈ TM\x #broken=eltype(x) <: Furlong - @test (x/T)::typediv2 ≈ x/TM #broken=eltype(x) <: Furlong + @test map(getval, Array((T\x)::typediv2)) ≈ map(getval, Array(TM\x)) + @test map(getval, Array((x/T)::typediv2)) ≈ map(getval, Array(x/TM)) end return nothing end - A = randn(n,n) - d = randn(n) - dl = randn(n-1) - t = T - for t in (T, #=Furlong.(T)=#), (A, d, dl) in ((A, d, dl), #=(Furlong.(A), Furlong.(d), Furlong.(dl))=#) + A = Matrix(T) + for t in (T, Furlong.(T)), (A, dv, ev) in ((A, dv, ev), (Furlong.(A), Furlong.(dv), Furlong.(ev))) _bidiagdivmultest(t, 5, Bidiagonal, Bidiagonal) _bidiagdivmultest(t, 5I, Bidiagonal, Bidiagonal, t.uplo == 'U' ? UpperTriangular : LowerTriangular) - _bidiagdivmultest(t, Diagonal(d), Bidiagonal, Bidiagonal, t.uplo == 'U' ? UpperTriangular : LowerTriangular) + _bidiagdivmultest(t, Diagonal(dv), Bidiagonal, Bidiagonal, t.uplo == 'U' ? UpperTriangular : LowerTriangular) _bidiagdivmultest(t, UpperTriangular(A)) _bidiagdivmultest(t, UnitUpperTriangular(A)) _bidiagdivmultest(t, LowerTriangular(A), t.uplo == 'L' ? LowerTriangular : Matrix, t.uplo == 'L' ? LowerTriangular : Matrix, t.uplo == 'L' ? LowerTriangular : Matrix) _bidiagdivmultest(t, UnitLowerTriangular(A), t.uplo == 'L' ? LowerTriangular : Matrix, t.uplo == 'L' ? LowerTriangular : Matrix, t.uplo == 'L' ? LowerTriangular : Matrix) - _bidiagdivmultest(t, Bidiagonal(d, dl, :U), Matrix, Matrix, Matrix) - _bidiagdivmultest(t, Bidiagonal(d, dl, :L), Matrix, Matrix, Matrix) + _bidiagdivmultest(t, Bidiagonal(dv, ev, :U), Matrix, Matrix, Matrix) + _bidiagdivmultest(t, Bidiagonal(dv, ev, :L), Matrix, Matrix, Matrix) end end end @@ -352,7 +371,7 @@ Random.seed!(1) @testset "diag" begin @test (@inferred diag(T))::typeof(dv) == dv - @test (@inferred diag(T, uplo == :U ? 1 : -1))::typeof(dv) == ev + @test (@inferred diag(T, uplo === :U ? 1 : -1))::typeof(dv) == ev @test (@inferred diag(T,2))::typeof(dv) == zeros(elty, n-2) @test_throws ArgumentError diag(T, -n - 1) @test_throws ArgumentError diag(T, n + 1) @@ -360,7 +379,7 @@ Random.seed!(1) gdv, gev = GenericArray(dv), GenericArray(ev) G = Bidiagonal(gdv, gev, uplo) @test (@inferred diag(G))::typeof(gdv) == gdv - @test (@inferred diag(G, uplo == :U ? 1 : -1))::typeof(gdv) == gev + @test (@inferred diag(G, uplo === :U ? 1 : -1))::typeof(gdv) == gev @test (@inferred diag(G,2))::typeof(gdv) == GenericArray(zeros(elty, n-2)) end @@ -368,9 +387,9 @@ Random.seed!(1) if relty <: AbstractFloat d1, v1 = eigen(T) d2, v2 = eigen(map(elty<:Complex ? ComplexF64 : Float64,Tfull), sortby=nothing) - @test (uplo == :U ? d1 : reverse(d1)) ≈ d2 + @test (uplo === :U ? d1 : reverse(d1)) ≈ d2 if elty <: Real - test_approx_eq_modphase(v1, uplo == :U ? v2 : v2[:,n:-1:1]) + test_approx_eq_modphase(v1, uplo === :U ? v2 : v2[:,n:-1:1]) end end end @@ -437,7 +456,7 @@ Random.seed!(1) Tridiag = Tridiagonal(rand(elty, 9), rand(elty, 10), rand(elty, 9)) SymTri = SymTridiagonal(rand(elty, 10), rand(elty, 9)) - mats = [Diag, BidiagU, BidiagL, Tridiag, SymTri] + mats = Any[Diag, BidiagU, BidiagL, Tridiag, SymTri] for a in mats for b in mats @test a*b ≈ Matrix(a)*Matrix(b) @@ -623,14 +642,14 @@ end end @testset "generalized dot" begin - for elty in (Float64, ComplexF64) - dv = randn(elty, 5) - ev = randn(elty, 4) - x = randn(elty, 5) - y = randn(elty, 5) + for elty in (Float64, ComplexF64), n in (5, 1) + dv = randn(elty, n) + ev = randn(elty, n-1) + x = randn(elty, n) + y = randn(elty, n) for uplo in (:U, :L) B = Bidiagonal(dv, ev, uplo) - @test dot(x, B, y) ≈ dot(B'x, y) ≈ dot(x, Matrix(B), y) + @test dot(x, B, y) ≈ dot(B'x, y) ≈ dot(x, B*y) ≈ dot(x, Matrix(B), y) end dv = Vector{elty}(undef, 0) ev = Vector{elty}(undef, 0) @@ -638,7 +657,7 @@ end y = Vector{elty}(undef, 0) for uplo in (:U, :L) B = Bidiagonal(dv, ev, uplo) - @test dot(x, B, y) ≈ dot(zero(elty), zero(elty), zero(elty)) + @test dot(x, B, y) === zero(elty) end end end @@ -780,4 +799,19 @@ end end end +@testset "copyto! with UniformScaling" begin + @testset "Fill" begin + for len in (4, InfiniteArrays.Infinity()) + d = FillArrays.Fill(1, len) + ud = FillArrays.Fill(0, len-1) + B = Bidiagonal(d, ud, :U) + @test copyto!(B, I) === B + end + end + B = Bidiagonal(fill(2, 4), fill(3, 3), :U) + copyto!(B, I) + @test all(isone, diag(B)) + @test all(iszero, diag(B, 1)) +end + end # module TestBidiagonal diff --git a/stdlib/LinearAlgebra/test/blas.jl b/stdlib/LinearAlgebra/test/blas.jl index 54b227bca7685..4252d9ee7938b 100644 --- a/stdlib/LinearAlgebra/test/blas.jl +++ b/stdlib/LinearAlgebra/test/blas.jl @@ -4,20 +4,37 @@ module TestBLAS using Test, LinearAlgebra, Random using LinearAlgebra: BlasReal, BlasComplex +using Libdl: dlsym, dlopen +fabs(x::Real) = abs(x) +fabs(x::Complex) = abs(real(x)) + abs(imag(x)) + +# help function to build packed storage +function pack(A, uplo) + AP = eltype(A)[] + n = size(A, 1) + for j in 1:n, i in (uplo === :L ? (j:n) : (1:j)) + push!(AP, A[i,j]) + end + return AP +end +@testset "vec_pointer_stride" begin + a = float(rand(1:20,4,4,4)) + @test BLAS.asum(a) == sum(a) # dense case + @test BLAS.asum(view(a,1:2:4,:,:)) == sum(view(a,1:2:4,:,:)) # vector like + @test BLAS.asum(view(a,1:3,2:2,3:3)) == sum(view(a,1:3,2:2,3:3)) + @test BLAS.asum(view(a,1:1,1:3,1:1)) == sum(view(a,1:1,1:3,1:1)) + @test BLAS.asum(view(a,1:1,1:1,1:3)) == sum(view(a,1:1,1:1,1:3)) + @test_throws ArgumentError BLAS.asum(view(a,1:3:4,:,:)) # non-vector like + @test_throws ArgumentError BLAS.asum(view(a,1:2,1:1,1:3)) +end Random.seed!(100) ## BLAS tests - testing the interface code to BLAS routines @testset for elty in [Float32, Float64, ComplexF32, ComplexF64] @testset "syr2k!" begin - U = randn(5,2) - V = randn(5,2) - if elty == ComplexF32 || elty == ComplexF64 - U = complex.(U, U) - V = complex.(V, V) - end - U = convert(Array{elty, 2}, U) - V = convert(Array{elty, 2}, V) + U = randn(elty, 5, 2) + V = randn(elty, 5, 2) @test tril(LinearAlgebra.BLAS.syr2k('L','N',U,V)) ≈ tril(U*transpose(V) + V*transpose(U)) @test triu(LinearAlgebra.BLAS.syr2k('U','N',U,V)) ≈ triu(U*transpose(V) + V*transpose(U)) @test tril(LinearAlgebra.BLAS.syr2k('L','T',U,V)) ≈ tril(transpose(U)*V + transpose(V)*U) @@ -26,12 +43,8 @@ Random.seed!(100) if elty in (ComplexF32, ComplexF64) @testset "her2k!" begin - U = randn(5,2) - V = randn(5,2) - U = complex.(U, U) - V = complex.(V, V) - U = convert(Array{elty, 2}, U) - V = convert(Array{elty, 2}, V) + U = randn(elty, 5, 2) + V = randn(elty, 5, 2) @test tril(LinearAlgebra.BLAS.her2k('L','N',U,V)) ≈ tril(U*V' + V*U') @test triu(LinearAlgebra.BLAS.her2k('U','N',U,V)) ≈ triu(U*V' + V*U') @test tril(LinearAlgebra.BLAS.her2k('L','C',U,V)) ≈ tril(U'*V + V'*U) @@ -48,21 +61,21 @@ Random.seed!(100) U4 = triu(fill(elty(1), 4,4)) Z4 = zeros(elty, (4,4)) - elm1 = convert(elty, -1) - el2 = convert(elty, 2) - v14 = convert(Vector{elty}, [1:4;]) - v41 = convert(Vector{elty}, [4:-1:1;]) + elm1 = elty(-1) + el2 = elty(2) + v14 = elty[1:4;] + v41 = elty[4:-1:1;] let n = 10 @testset "dot products" begin if elty <: Real - x1 = convert(Vector{elty}, randn(n)) - x2 = convert(Vector{elty}, randn(n)) + x1 = randn(elty, n) + x2 = randn(elty, n) @test BLAS.dot(x1,x2) ≈ sum(x1.*x2) @test_throws DimensionMismatch BLAS.dot(x1,rand(elty, n + 1)) else - z1 = convert(Vector{elty}, complex.(randn(n),randn(n))) - z2 = convert(Vector{elty}, complex.(randn(n),randn(n))) + z1 = randn(elty, n) + z2 = randn(elty, n) @test BLAS.dotc(z1,z2) ≈ sum(conj(z1).*z2) @test BLAS.dotu(z1,z2) ≈ sum(z1.*z2) @test_throws DimensionMismatch BLAS.dotc(z1,rand(elty, n + 1)) @@ -70,92 +83,60 @@ Random.seed!(100) end end @testset "iamax" begin - if elty <: Real - x = convert(Vector{elty}, randn(n)) - @test BLAS.iamax(x) == argmax(abs.(x)) - else - z = convert(Vector{elty}, complex.(randn(n),randn(n))) - @test BLAS.iamax(z) == argmax(map(x -> abs(real(x)) + abs(imag(x)), z)) - end + x = randn(elty, n) + @test BLAS.iamax(x) == findmax(fabs, x)[2] end @testset "rot!" begin - if elty <: Real - x = convert(Vector{elty}, randn(n)) - y = convert(Vector{elty}, randn(n)) - c = rand(elty) - s = rand(elty) + x = randn(elty, n) + y = randn(elty, n) + c = rand(real(elty)) + for sty in unique!([real(elty), elty]) + s = rand(sty) x2 = copy(x) y2 = copy(y) BLAS.rot!(n, x, 1, y, 1, c, s) @test x ≈ c*x2 + s*y2 - @test y ≈ -s*x2 + c*y2 - else - x = convert(Vector{elty}, complex.(randn(n),rand(n))) - y = convert(Vector{elty}, complex.(randn(n),rand(n))) - cty = (elty == ComplexF32) ? Float32 : Float64 - c = rand(cty) - for sty in [cty, elty] - s = rand(sty) - x2 = copy(x) - y2 = copy(y) - BLAS.rot!(n, x, 1, y, 1, c, s) - @test x ≈ c*x2 + s*y2 - @test y ≈ -conj(s)*x2 + c*y2 - end + @test y ≈ -conj(s)*x2 + c*y2 end end @testset "axp(b)y" begin - if elty <: Real - x1 = convert(Vector{elty}, randn(n)) - x2 = convert(Vector{elty}, randn(n)) - α = rand(elty) - β = rand(elty) - @test BLAS.axpy!(α,copy(x1),copy(x2)) ≈ α*x1 + x2 - @test BLAS.axpby!(α,copy(x1),β,copy(x2)) ≈ α*x1 + β*x2 - @test_throws DimensionMismatch BLAS.axpy!(α, copy(x1), rand(elty, n + 1)) - @test_throws DimensionMismatch BLAS.axpby!(α, copy(x1), β, rand(elty, n + 1)) - @test_throws DimensionMismatch BLAS.axpy!(α, copy(x1), 1:div(n,2), copy(x2), 1:n) - @test_throws ArgumentError BLAS.axpy!(α, copy(x1), 0:div(n,2), copy(x2), 1:(div(n, 2) + 1)) - @test_throws ArgumentError BLAS.axpy!(α, copy(x1), 1:div(n,2), copy(x2), 0:(div(n, 2) - 1)) - @test BLAS.axpy!(α,copy(x1),1:n,copy(x2),1:n) ≈ x2 + α*x1 - else - z1 = convert(Vector{elty}, complex.(randn(n), randn(n))) - z2 = convert(Vector{elty}, complex.(randn(n), randn(n))) - α = rand(elty) - @test BLAS.axpy!(α, copy(z1), copy(z2)) ≈ z2 + α * z1 - @test_throws DimensionMismatch BLAS.axpy!(α, copy(z1), rand(elty, n + 1)) - @test_throws DimensionMismatch BLAS.axpy!(α, copy(z1), 1:div(n, 2), copy(z2), 1:(div(n, 2) + 1)) - @test_throws ArgumentError BLAS.axpy!(α, copy(z1), 0:div(n,2), copy(z2), 1:(div(n, 2) + 1)) - @test_throws ArgumentError BLAS.axpy!(α, copy(z1), 1:div(n,2), copy(z2), 0:(div(n, 2) - 1)) - @test BLAS.axpy!(α,copy(z1),1:n,copy(z2),1:n) ≈ z2 + α*z1 + x1 = randn(elty, n) + x2 = randn(elty, n) + α = rand(elty) + β = rand(elty) + for X1 in (x1, view(x1,n:-1:1)), X2 in (x2, view(x2, n:-1:1)) + @test BLAS.axpy!(α,deepcopy(X1),deepcopy(X2)) ≈ α*X1 + X2 + @test BLAS.axpby!(α,deepcopy(X1),β,deepcopy(X2)) ≈ α*X1 + β*X2 end + for ind1 in (1:n, n:-1:1), ind2 in (1:n, n:-1:1) + @test BLAS.axpy!(α,copy(x1),ind1,copy(x2),ind2) ≈ x2 + α*(ind1 == ind2 ? x1 : reverse(x1)) + end + @test_throws DimensionMismatch BLAS.axpy!(α, copy(x1), rand(elty, n + 1)) + @test_throws DimensionMismatch BLAS.axpby!(α, copy(x1), β, rand(elty, n + 1)) + @test_throws DimensionMismatch BLAS.axpy!(α, copy(x1), 1:div(n,2), copy(x2), 1:n) + @test_throws ArgumentError BLAS.axpy!(α, copy(x1), 0:div(n,2), copy(x2), 1:(div(n, 2) + 1)) + @test_throws ArgumentError BLAS.axpy!(α, copy(x1), 1:div(n,2), copy(x2), 0:(div(n, 2) - 1)) end @testset "nrm2, iamax, and asum for StridedVectors" begin a = rand(elty,n) - b = view(a,2:2:n,1) - @test BLAS.nrm2(b) ≈ norm(b) - if elty <: Real - @test BLAS.asum(b) ≈ sum(abs.(b)) - @test BLAS.iamax(b) ≈ argmax(abs.(b)) - else - @test BLAS.asum(b) ≈ sum(abs.(real(b))) + sum(abs.(imag(b))) - @test BLAS.iamax(b) == argmax(map(x -> abs(real(x)) + abs(imag(x)), b)) + for ind in (2:2:n, n:-2:2) + b = view(a, ind, 1) + @test BLAS.nrm2(b) ≈ sqrt(sum(abs2, b)) + @test BLAS.asum(b) ≈ sum(fabs, b) + @test BLAS.iamax(b) == findmax(fabs, b)[2] * (step(ind) >= 0) end end - # scal - α = rand(elty) - a = rand(elty,n) - @test BLAS.scal(n,α,a,1) ≈ α * a - - @testset "trsv" begin - A = triu(rand(elty,n,n)) - @testset "Vector and SubVector" for x in (rand(elty, n), view(rand(elty,2n),1:2:2n)) - @test A\x ≈ BLAS.trsv('U','N','N',A,x) - @test_throws DimensionMismatch BLAS.trsv('U','N','N',A,Vector{elty}(undef,n+1)) + @testset "scal" begin + α = rand(elty) + a = rand(elty,n) + @test BLAS.scal(n,α,a,1) ≈ α * a + for v in (a, view(a, n:-1:1)) + @test BLAS.scal!(α, deepcopy(v)) ≈ α * v end end - @testset "ger, her, syr" for x in (rand(elty, n), view(rand(elty,2n), 1:2:2n)), - y in (rand(elty,n), view(rand(elty,3n), 1:3:3n)) + + @testset "ger, 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) α = rand(elty) @@ -178,32 +159,66 @@ Random.seed!(100) end end @testset "copy" begin - x1 = convert(Vector{elty}, randn(n)) - x2 = convert(Vector{elty}, randn(n)) - BLAS.copyto!(x2, 1:n, x1, 1:n) - @test x2 == x1 + x1 = randn(elty, n) + x2 = randn(elty, n) + for ind1 in (1:n, n:-1:1), ind2 in (1:n, n:-1:1) + @test x2 === BLAS.copyto!(x2, ind1, x1, ind2) == (ind1 == ind2 ? x1 : reverse(x1)) + end @test_throws DimensionMismatch BLAS.copyto!(x2, 1:n, x1, 1:(n - 1)) @test_throws ArgumentError BLAS.copyto!(x1, 0:div(n, 2), x2, 1:(div(n, 2) + 1)) @test_throws ArgumentError BLAS.copyto!(x1, 1:(div(n, 2) + 1), x2, 0:div(n, 2)) end - # trmv - A = triu(rand(elty,n,n)) - x = rand(elty,n) - @test BLAS.trmv('U','N','N',A,x) ≈ A*x + @testset "trmv and trsv" begin + A = rand(elty,n,n) + x = rand(elty,n) + xerr = Vector{elty}(undef,n+1) + for uplo in ('U', 'L'), diag in ('U','N'), trans in ('N', 'T', 'C') + Wrapper = if uplo == 'U' + diag == 'U' ? UnitUpperTriangular : UpperTriangular + else + diag == 'U' ? UnitLowerTriangular : LowerTriangular + end + fun = trans == 'N' ? identity : trans == 'T' ? transpose : adjoint + fullA = collect(fun(Wrapper(A))) + @testset "trmv" begin + @test BLAS.trmv(uplo,trans,diag,A,x) ≈ fullA * x + @test_throws DimensionMismatch BLAS.trmv(uplo,trans,diag,A,xerr) + for xx in (x, view(x, n:-1:1)) + @test BLAS.trmv!(uplo,trans,diag,A,deepcopy(xx)) ≈ fullA * xx + end + end + @testset "trsv" begin + @test BLAS.trsv(uplo,trans,diag,A,x) ≈ fullA \ x + @test_throws DimensionMismatch BLAS.trsv(uplo,trans,diag,A,xerr) + for xx in (x, view(x, n:-1:1)) + @test BLAS.trsv!(uplo,trans,diag,A,deepcopy(xx)) ≈ fullA \ xx + end + end + end + end @testset "symmetric/Hermitian multiplication" begin x = rand(elty,n) A = rand(elty,n,n) + y = rand(elty, n) + α = randn(elty) + β = randn(elty) Aherm = A + A' Asymm = A + transpose(A) - @testset "symv and hemv" begin - @test BLAS.symv('U',Asymm,x) ≈ Asymm*x - offsizevec, offsizemat = Array{elty}.(undef,(n+1, (n,n+1))) - @test_throws DimensionMismatch BLAS.symv!('U',one(elty),Asymm,x,one(elty),offsizevec) - @test_throws DimensionMismatch BLAS.symv('U',offsizemat,x) + offsizevec, offsizemat = Array{elty}.(undef,(n+1, (n,n+1))) + @testset "symv and hemv" for uplo in ('U', 'L') + @test BLAS.symv(uplo,Asymm,x) ≈ Asymm*x + for xx in (x, view(x, n:-1:1)), yy in (y, view(y, n:-1:1)) + @test BLAS.symv!(uplo,α,Asymm,xx,β,deepcopy(yy)) ≈ α * Asymm * xx + β * yy + end + @test_throws DimensionMismatch BLAS.symv!(uplo,α,Asymm,x,β,offsizevec) + @test_throws DimensionMismatch BLAS.symv(uplo,offsizemat,x) if elty <: BlasComplex - @test BLAS.hemv('U',Aherm,x) ≈ Aherm*x - @test_throws DimensionMismatch BLAS.hemv('U',offsizemat,x) - @test_throws DimensionMismatch BLAS.hemv!('U',one(elty),Aherm,x,one(elty),offsizevec) + @test BLAS.hemv(uplo,Aherm,x) ≈ Aherm*x + for xx in (x, view(x, n:-1:1)), yy in (y, view(y, n:-1:1)) + @test BLAS.hemv!(uplo,α,Aherm,xx,β,deepcopy(yy)) ≈ α * Aherm * xx + β * yy + end + @test_throws DimensionMismatch BLAS.hemv(uplo,offsizemat,x) + @test_throws DimensionMismatch BLAS.hemv!(uplo,one(elty),Aherm,x,one(elty),offsizevec) end end @@ -213,11 +228,19 @@ Random.seed!(100) @test_throws DimensionMismatch BLAS.symm('R','U',Cmn,Cnn) @test_throws DimensionMismatch BLAS.symm!('L','U',one(elty),Asymm,Cnn,one(elty),Cmn) @test_throws DimensionMismatch BLAS.symm!('L','U',one(elty),Asymm,Cnn,one(elty),Cnm) + @test_throws DimensionMismatch BLAS.symm!('L','U',one(elty),Asymm,Cmn,one(elty),Cnn) + @test_throws DimensionMismatch BLAS.symm!('R','U',one(elty),Asymm,Cnm,one(elty),Cmn) + @test_throws DimensionMismatch BLAS.symm!('R','U',one(elty),Asymm,Cnn,one(elty),Cnm) + @test_throws DimensionMismatch BLAS.symm!('R','U',one(elty),Asymm,Cmn,one(elty),Cnn) if elty <: BlasComplex @test_throws DimensionMismatch BLAS.hemm('L','U',Cnm,Cnn) @test_throws DimensionMismatch BLAS.hemm('R','U',Cmn,Cnn) @test_throws DimensionMismatch BLAS.hemm!('L','U',one(elty),Aherm,Cnn,one(elty),Cmn) @test_throws DimensionMismatch BLAS.hemm!('L','U',one(elty),Aherm,Cnn,one(elty),Cnm) + @test_throws DimensionMismatch BLAS.hemm!('L','U',one(elty),Aherm,Cmn,one(elty),Cnn) + @test_throws DimensionMismatch BLAS.hemm!('R','U',one(elty),Aherm,Cnm,one(elty),Cmn) + @test_throws DimensionMismatch BLAS.hemm!('R','U',one(elty),Aherm,Cnn,one(elty),Cnm) + @test_throws DimensionMismatch BLAS.hemm!('R','U',one(elty),Aherm,Cmn,one(elty),Cnn) end end end @@ -233,40 +256,24 @@ Random.seed!(100) # Both matrix dimensions n coincide, as we have Hermitian matrices. # Define the inputs and outputs of hpmv!, y = α*A*x+β*y α = rand(elty) - M = rand(elty, n, n) - AL = Hermitian(M, :L) - AU = Hermitian(M, :U) + A = rand(elty, n, n) x = rand(elty, n) β = rand(elty) y = rand(elty, n) - - y_result_julia_lower = α*AL*x + β*y - - # Create lower triangular packing of AL - AP = typeof(AL[1,1])[] - for j in 1:n - for i in j:n - push!(AP, AL[i,j]) - end - end - - y_result_blas_lower = copy(y) - BLAS.hpmv!('L', α, AP, x, β, y_result_blas_lower) - @test y_result_julia_lower ≈ y_result_blas_lower - - y_result_julia_upper = α*AU*x + β*y - - # Create upper triangular packing of AU - AP = typeof(AU[1,1])[] - for j in 1:n - for i in 1:j - push!(AP, AU[i,j]) + for uplo in (:L, :U) + Cuplo = String(uplo)[1] + AH = Hermitian(A, uplo) + # Create lower/upper triangular packing of AL + AP = pack(AH, uplo) + for xx in (x, view(x,n:-1:1)), yy in (y, view(y,n:-1:1)) + @test BLAS.hpmv!(Cuplo, α, AP, xx, β, deepcopy(yy)) ≈ α*AH*xx + β*yy end + AP′ = view(zeros(elty, n*(n+1)),1:2:n*(n+1)) + @test_throws ErrorException BLAS.hpmv!(Cuplo, α, AP′, x, β, y) + AP′ = view(AP, 1:length(AP′) - 1) + @test_throws DimensionMismatch BLAS.hpmv!(Cuplo, α, AP′, x, β, y) + @test_throws DimensionMismatch BLAS.hpmv!(Cuplo, α, AP′, x, β, view(y,1:n-1)) end - - y_result_blas_upper = copy(y) - BLAS.hpmv!('U', α, AP, x, β, y_result_blas_upper) - @test y_result_julia_upper ≈ y_result_blas_upper end end @@ -276,41 +283,24 @@ Random.seed!(100) # Both matrix dimensions n coincide, as we have symmetric matrices. # Define the inputs and outputs of spmv!, y = α*A*x+β*y α = rand(elty) - M = rand(elty, n, n) - AL = Symmetric(M, :L) - AU = Symmetric(M, :U) + A = rand(elty, n, n) x = rand(elty, n) β = rand(elty) y = rand(elty, n) - - y_result_julia_lower = α*AL*x + β*y - - # Create lower triangular packing of AL - AP = typeof(M[1,1])[] - for j in 1:n - for i in j:n - push!(AP, AL[i,j]) + for uplo in (:L, :U) + Cuplo = String(uplo)[1] + AS = Symmetric(A, uplo) + # Create lower/upper triangular packing of AL + AP = pack(AS, uplo) + for xx in (x, view(x,n:-1:1)), yy in (y, view(y,n:-1:1)) + @test BLAS.spmv!(Cuplo, α, AP, xx, β, deepcopy(yy)) ≈ α*AS*xx + β*yy end + AP′ = view(zeros(elty, n*(n+1)),1:2:n*(n+1)) + @test_throws ErrorException BLAS.spmv!(Cuplo, α, AP′, x, β, y) + AP′ = view(AP, 1:length(AP′) - 1) + @test_throws DimensionMismatch BLAS.spmv!(Cuplo, α, AP′, x, β, y) + @test_throws DimensionMismatch BLAS.spmv!(Cuplo, α, AP′, x, β, view(y,1:n-1)) end - - y_result_blas_lower = copy(y) - BLAS.spmv!('L', α, AP, x, β, y_result_blas_lower) - @test y_result_julia_lower ≈ y_result_blas_lower - - - y_result_julia_upper = α*AU*x + β*y - - # Create upper triangular packing of AU - AP = typeof(M[1,1])[] - for j in 1:n - for i in 1:j - push!(AP, AU[i,j]) - end - end - - y_result_blas_upper = copy(y) - BLAS.spmv!('U', α, AP, x, β, y_result_blas_upper) - @test y_result_julia_upper ≈ y_result_blas_upper end end @@ -321,39 +311,29 @@ Random.seed!(100) M = rand(elty, n, n) AL = Symmetric(M, :L) AU = Symmetric(M, :U) - x = rand(elty, n) - - function pack(A, uplo) - AP = elty[] - for j in 1:n - for i in (uplo==:L ? (j:n) : (1:j)) - push!(AP, A[i,j]) - end - end - return AP + for x in (rand(elty, n), view(rand(elty, n), n:-1:1)) + ALP_result_julia_lower = pack(α*x*x' + AL, :L) + ALP_result_blas_lower = pack(AL, :L) + BLAS.spr!('L', α, x, ALP_result_blas_lower) + @test ALP_result_julia_lower ≈ ALP_result_blas_lower + ALP_result_blas_lower = append!(pack(AL, :L), ones(elty, 10)) + BLAS.spr!('L', α, x, ALP_result_blas_lower) + @test ALP_result_julia_lower ≈ ALP_result_blas_lower[1:end-10] + ALP_result_blas_lower = reshape(pack(AL, :L), 1, length(ALP_result_julia_lower), 1) + BLAS.spr!('L', α, x, ALP_result_blas_lower) + @test ALP_result_julia_lower ≈ vec(ALP_result_blas_lower) + + AUP_result_julia_upper = pack(α*x*x' + AU, :U) + AUP_result_blas_upper = pack(AU, :U) + BLAS.spr!('U', α, x, AUP_result_blas_upper) + @test AUP_result_julia_upper ≈ AUP_result_blas_upper + AUP_result_blas_upper = append!(pack(AU, :U), ones(elty, 10)) + BLAS.spr!('U', α, x, AUP_result_blas_upper) + @test AUP_result_julia_upper ≈ AUP_result_blas_upper[1:end-10] + AUP_result_blas_upper = reshape(pack(AU, :U), 1, length(AUP_result_julia_upper), 1) + BLAS.spr!('U', α, x, AUP_result_blas_upper) + @test AUP_result_julia_upper ≈ vec(AUP_result_blas_upper) end - - ALP_result_julia_lower = pack(α*x*x' + AL, :L) - ALP_result_blas_lower = pack(AL, :L) - BLAS.spr!('L', α, x, ALP_result_blas_lower) - @test ALP_result_julia_lower ≈ ALP_result_blas_lower - ALP_result_blas_lower = append!(pack(AL, :L), ones(elty, 10)) - BLAS.spr!('L', α, x, ALP_result_blas_lower) - @test ALP_result_julia_lower ≈ ALP_result_blas_lower[1:end-10] - ALP_result_blas_lower = reshape(pack(AL, :L), 1, length(ALP_result_julia_lower), 1) - BLAS.spr!('L', α, x, ALP_result_blas_lower) - @test ALP_result_julia_lower ≈ vec(ALP_result_blas_lower) - - AUP_result_julia_upper = pack(α*x*x' + AU, :U) - AUP_result_blas_upper = pack(AU, :U) - BLAS.spr!('U', α, x, AUP_result_blas_upper) - @test AUP_result_julia_upper ≈ AUP_result_blas_upper - AUP_result_blas_upper = append!(pack(AU, :U), ones(elty, 10)) - BLAS.spr!('U', α, x, AUP_result_blas_upper) - @test AUP_result_julia_upper ≈ AUP_result_blas_upper[1:end-10] - AUP_result_blas_upper = reshape(pack(AU, :U), 1, length(AUP_result_julia_upper), 1) - BLAS.spr!('U', α, x, AUP_result_blas_upper) - @test AUP_result_julia_upper ≈ vec(AUP_result_blas_upper) end end @@ -365,33 +345,51 @@ Random.seed!(100) #will work for SymTridiagonal,Tridiagonal,Bidiagonal! @testset "banded matrix mv" begin @testset "gbmv" begin - TD = Tridiagonal(rand(elty,n-1),rand(elty,n),rand(elty,n-1)) - x = rand(elty,n) + TD = Tridiagonal(rand(elty,n-1),rand(elty,n),rand(elty,n-1)) + x = rand(elty, n) #put TD into the BLAS format! fTD = zeros(elty,3,n) fTD[1,2:n] = TD.du fTD[2,:] = TD.d fTD[3,1:n-1] = TD.dl @test BLAS.gbmv('N',n,1,1,fTD,x) ≈ TD*x + y = rand(elty, n) + α = randn(elty) + β = randn(elty) + for xx in (x, view(x, n:-1:1)), yy in (y, view(y, n:-1:1)) + @test BLAS.gbmv!('N',n,1,1,α,fTD,xx,β,deepcopy(yy)) ≈ α * TD * xx + β * yy + end end #will work for SymTridiagonal only! - @testset "sbmv" begin + @testset "sbmv and hbmv" begin + x = rand(elty,n) if elty <: BlasReal ST = SymTridiagonal(rand(elty,n),rand(elty,n-1)) - x = rand(elty,n) #put TD into the BLAS format! fST = zeros(elty,2,n) fST[1,2:n] = ST.ev fST[2,:] = ST.dv @test BLAS.sbmv('U',1,fST,x) ≈ ST*x + y = rand(elty, n) + α = randn(elty) + β = randn(elty) + for xx in (x, view(x, n:-1:1)), yy in (y, view(y, n:-1:1)) + @test BLAS.sbmv!('U',1,α,fST,xx,β,deepcopy(yy)) ≈ α * ST * xx + β * yy + end else - dv = real(rand(elty,n)) + dv = rand(real(elty),n) ev = rand(elty,n-1) bH = zeros(elty,2,n) bH[1,2:n] = ev bH[2,:] = dv fullH = diagm(0 => dv, -1 => conj(ev), 1 => ev) @test BLAS.hbmv('U',1,bH,x) ≈ fullH*x + y = rand(elty, n) + α = randn(elty) + β = randn(elty) + for xx in (x, view(x, n:-1:1)), yy in (y, view(y, n:-1:1)) + @test BLAS.hbmv!('U',1,α,bH,xx,β,deepcopy(yy)) ≈ α * fullH * xx + β * yy + end end end end @@ -578,7 +576,7 @@ end @testset "strided interface blas" begin for elty in (Float32, Float64, ComplexF32, ComplexF64) - # Level 1 + # Level 1 x = WrappedArray(elty[1, 2, 3, 4]) y = WrappedArray(elty[5, 6, 7, 8]) BLAS.blascopy!(2, x, 1, y, 2) @@ -595,8 +593,8 @@ end @test BLAS.iamax(x) == 2 M = fill(elty(1.0), 3, 3) - BLAS.scal!(elty(2), view(M,:,2)) - BLAS.scal!(elty(3), view(M,3,:)) + @test BLAS.scal!(elty(2), view(M,:,2)) === view(M,:,2) + @test BLAS.scal!(elty(3), view(M,3,:)) === view(M,3,:) @test M == elty[1. 2. 1.; 1. 2. 1.; 3. 6. 3.] # Level 2 A = WrappedArray(elty[1 2; 3 4]) @@ -638,7 +636,7 @@ end x = WrappedArray(elty[1, 2, 3, 4]) y = WrappedArray(elty[5, 6, 7, 8]) @test BLAS.dot(2, x, 1, y, 2) == elty(19) - # Level 2 + # Level 2 A = WrappedArray(elty[1 2; 3 4]) x = WrappedArray(elty[1, 2]) y = WrappedArray(elty[3, 4]) @@ -684,8 +682,43 @@ end @test BLAS.get_num_threads() === default end -# https://github.com/JuliaLang/julia/pull/39845 -@test LinearAlgebra.BLAS.libblas == "libblastrampoline" -@test LinearAlgebra.BLAS.liblapack == "libblastrampoline" +@testset "test for 0-strides" for elty in (Float32, Float64, ComplexF32, ComplexF64) + A = randn(elty, 10, 10); + a = view([randn(elty)], 1 .+ 0(1:10)) + b = view([randn(elty)], 1 .+ 0(1:10)) + α, β = randn(elty), randn(elty) + @testset "dot/dotc/dotu" begin + if elty <: Real + @test BLAS.dot(a,b) ≈ sum(a.*b) + else + @test BLAS.dotc(a,b) ≈ sum(conj(a).*b) + @test BLAS.dotu(a,b) ≈ sum(a.*b) + end + end + @testset "axp(b)y!" begin + @test BLAS.axpy!(α,a,copy(b)) ≈ α*a + b + @test BLAS.axpby!(α,a,β,copy(b)) ≈ α*a + β*b + @test_throws "dest" BLAS.axpy!(α,a,b) + @test_throws "dest" BLAS.axpby!(α,a,β,b) + end + @test BLAS.iamax(a) == 0 + @test_throws "dest" BLAS.scal!(b[1], a) + @testset "nrm2/asum" begin # OpenBLAS always return 0.0 + @test_throws "input" BLAS.nrm2(a) + @test_throws "input" BLAS.asum(a) + end + # All level2 reject 0-stride array. + @testset "gemv!" begin + @test_throws "input" BLAS.gemv!('N', true, A, a, false, copy(b)) + @test_throws "dest" BLAS.gemv!('N', true, A, copy(a), false, b) + end +end + +# Make sure we can use `Base.libblas_name`. Avoid causing +# https://github.com/JuliaLang/julia/issues/48427 again. +@testset "libblas_name" begin + dot_sym = dlsym(dlopen(Base.libblas_name), "cblas_ddot" * (Sys.WORD_SIZE == 64 ? "64_" : "")) + @test 23.0 === @ccall $(dot_sym)(2::Int, [2.0, 3.0]::Ref{Cdouble}, 1::Int, [4.0, 5.0]::Ref{Cdouble}, 1::Int)::Cdouble +end end # module TestBLAS diff --git a/stdlib/LinearAlgebra/test/bunchkaufman.jl b/stdlib/LinearAlgebra/test/bunchkaufman.jl index f1da22d8733e2..613e4d09a3cc6 100644 --- a/stdlib/LinearAlgebra/test/bunchkaufman.jl +++ b/stdlib/LinearAlgebra/test/bunchkaufman.jl @@ -70,10 +70,10 @@ bimg = randn(n,2)/2 @test getproperty(bc1, uplo)*bc1.D*transpose(getproperty(bc1, uplo)) ≈ asym[bc1.p, bc1.p] @test getproperty(bc1, uplo)*bc1.D*transpose(getproperty(bc1, uplo)) ≈ bc1.P*asym*transpose(bc1.P) @test_throws ErrorException bc1.Z - @test_throws ArgumentError uplo == :L ? bc1.U : bc1.L + @test_throws ArgumentError uplo === :L ? bc1.U : bc1.L end # test Base.iterate - ref_objs = (bc1.D, uplo == :L ? bc1.L : bc1.U, bc1.p) + ref_objs = (bc1.D, uplo === :L ? bc1.L : bc1.U, bc1.p) for (bki, bkobj) in enumerate(bc1) @test bkobj == ref_objs[bki] end @@ -162,7 +162,7 @@ end @test B.D == Tridiagonal([], [], []) @test B.P == ones(0, 0) @test B.p == [] - if ul == :U + if ul === :U @test B.U == UnitUpperTriangular(ones(0, 0)) @test_throws ArgumentError B.L else @@ -190,4 +190,10 @@ end @test_throws ArgumentError("adjoint not implemented for complex symmetric matrices") F' end +@testset "BunchKaufman for AbstractMatrix" begin + S = SymTridiagonal(fill(2.0, 4), ones(3)) + B = bunchkaufman(S) + @test B.U * B.D * B.U' ≈ S +end + end # module TestBunchKaufman diff --git a/stdlib/LinearAlgebra/test/cholesky.jl b/stdlib/LinearAlgebra/test/cholesky.jl index 8e6cac65f7dfb..a795eb8d44a03 100644 --- a/stdlib/LinearAlgebra/test/cholesky.jl +++ b/stdlib/LinearAlgebra/test/cholesky.jl @@ -260,11 +260,12 @@ end end end -@testset "behavior for non-positive definite matrices" for T in (Float64, ComplexF64) +@testset "behavior for non-positive definite matrices" for T in (Float64, ComplexF64, BigFloat) A = T[1 2; 2 1] B = T[1 2; 0 1] + C = T[2 0; 0 0] # check = (true|false) - for M in (A, Hermitian(A), B) + for M in (A, Hermitian(A), B, C) @test_throws PosDefException cholesky(M) @test_throws PosDefException cholesky!(copy(M)) @test_throws PosDefException cholesky(M; check = true) @@ -272,17 +273,19 @@ end @test !LinearAlgebra.issuccess(cholesky(M; check = false)) @test !LinearAlgebra.issuccess(cholesky!(copy(M); check = false)) end - for M in (A, Hermitian(A), B) - @test_throws RankDeficientException cholesky(M, RowMaximum()) - @test_throws RankDeficientException cholesky!(copy(M), RowMaximum()) - @test_throws RankDeficientException cholesky(M, RowMaximum(); check = true) - @test_throws RankDeficientException cholesky!(copy(M), RowMaximum(); check = true) - @test !LinearAlgebra.issuccess(cholesky(M, RowMaximum(); check = false)) - @test !LinearAlgebra.issuccess(cholesky!(copy(M), RowMaximum(); check = false)) - C = cholesky(M, RowMaximum(); check = false) - @test_throws RankDeficientException chkfullrank(C) - C = cholesky!(copy(M), RowMaximum(); check = false) - @test_throws RankDeficientException chkfullrank(C) + if T !== BigFloat # generic pivoted cholesky is not implemented + for M in (A, Hermitian(A), B) + @test_throws RankDeficientException cholesky(M, RowMaximum()) + @test_throws RankDeficientException cholesky!(copy(M), RowMaximum()) + @test_throws RankDeficientException cholesky(M, RowMaximum(); check = true) + @test_throws RankDeficientException cholesky!(copy(M), RowMaximum(); check = true) + @test !LinearAlgebra.issuccess(cholesky(M, RowMaximum(); check = false)) + @test !LinearAlgebra.issuccess(cholesky!(copy(M), RowMaximum(); check = false)) + C = cholesky(M, RowMaximum(); check = false) + @test_throws RankDeficientException chkfullrank(C) + C = cholesky!(copy(M), RowMaximum(); check = false) + @test_throws RankDeficientException chkfullrank(C) + end end @test !isposdef(A) str = sprint((io, x) -> show(io, "text/plain", x), cholesky(A; check = false)) @@ -303,6 +306,7 @@ end v = rand(5) @test cholesky(Diagonal(v)) \ B ≈ Diagonal(v) \ B @test B / cholesky(Diagonal(v)) ≈ B / Diagonal(v) + @test inv(cholesky(Diagonal(v)))::Diagonal ≈ Diagonal(1 ./ v) end struct WrappedVector{T} <: AbstractVector{T} @@ -389,9 +393,9 @@ end # complex D = complex(D) - CD = cholesky(D) - CM = cholesky(Matrix(D)) - @test CD isa Cholesky{ComplexF64} + CD = cholesky(Hermitian(D)) + CM = cholesky(Matrix(Hermitian(D))) + @test CD isa Cholesky{ComplexF64,<:Diagonal} @test CD.U ≈ Diagonal(.√d) ≈ CM.U @test D ≈ CD.L * CD.U @test CD.info == 0 @@ -406,6 +410,12 @@ end @test_throws InexactError cholesky!(Diagonal([2, 1])) end +@testset "Cholesky for AbstractMatrix" begin + S = SymTridiagonal(fill(2.0, 4), ones(3)) + C = cholesky(S) + @test C.L * C.U ≈ S +end + @testset "constructor with non-BlasInt arguments" begin x = rand(5,5) diff --git a/stdlib/LinearAlgebra/test/dense.jl b/stdlib/LinearAlgebra/test/dense.jl index f03bf4a953ac6..1546f3247acf4 100644 --- a/stdlib/LinearAlgebra/test/dense.jl +++ b/stdlib/LinearAlgebra/test/dense.jl @@ -132,8 +132,20 @@ bimg = randn(n,2)/2 @testset "Lyapunov/Sylvester" begin x = lyap(a, a2) @test -a2 ≈ a*x + x*a' + y = lyap(a', a2') + @test y ≈ lyap(Array(a'), Array(a2')) + @test -a2' ≈ a'y + y*a + z = lyap(Tridiagonal(a)', Diagonal(a2)) + @test z ≈ lyap(Array(Tridiagonal(a)'), Array(Diagonal(a2))) + @test -Diagonal(a2) ≈ Tridiagonal(a)'*z + z*Tridiagonal(a) x2 = sylvester(a[1:3, 1:3], a[4:n, 4:n], a2[1:3,4:n]) @test -a2[1:3, 4:n] ≈ a[1:3, 1:3]*x2 + x2*a[4:n, 4:n] + y2 = sylvester(a[1:3, 1:3]', a[4:n, 4:n]', a2[4:n,1:3]') + @test y2 ≈ sylvester(Array(a[1:3, 1:3]'), Array(a[4:n, 4:n]'), Array(a2[4:n,1:3]')) + @test -a2[4:n, 1:3]' ≈ a[1:3, 1:3]'*y2 + y2*a[4:n, 4:n]' + z2 = sylvester(Tridiagonal(a[1:3, 1:3]), Diagonal(a[4:n, 4:n]), a2[1:3,4:n]) + @test z2 ≈ sylvester(Array(Tridiagonal(a[1:3, 1:3])), Array(Diagonal(a[4:n, 4:n])), Array(a2[1:3,4:n])) + @test -a2[1:3, 4:n] ≈ Tridiagonal(a[1:3, 1:3])*z2 + z2*Diagonal(a[4:n, 4:n]) end @testset "Matrix square root" begin @@ -226,6 +238,15 @@ end @test pinv(M,rtol=0.5)== M end +@testset "Test inv of matrix of NaNs" begin + for eltya in (NaN16, NaN32, NaN32) + r = fill(eltya, 2, 2) + @test_throws ArgumentError inv(r) + c = fill(complex(eltya, eltya), 2, 2) + @test_throws ArgumentError inv(c) + end +end + @testset "test out of bounds triu/tril" begin local m, n = 5, 7 ainit = rand(m, n) @@ -1108,12 +1129,12 @@ end end function test_rdiv_pinv_consistency(a, b) - @test (a*b)/b ≈ a*(b/b) ≈ (a*b)*pinv(b) ≈ a*(b*pinv(b)) - @test typeof((a*b)/b) == typeof(a*(b/b)) == typeof((a*b)*pinv(b)) == typeof(a*(b*pinv(b))) + @test a*(b/b) ≈ (a*b)*pinv(b) ≈ a*(b*pinv(b)) + @test typeof(a*(b/b)) == typeof((a*b)*pinv(b)) == typeof(a*(b*pinv(b))) end function test_ldiv_pinv_consistency(a, b) - @test a\(a*b) ≈ (a\a)*b ≈ (pinv(a)*a)*b ≈ pinv(a)*(a*b) - @test typeof(a\(a*b)) == typeof((a\a)*b) == typeof((pinv(a)*a)*b) == typeof(pinv(a)*(a*b)) + @test (a\a)*b ≈ (pinv(a)*a)*b ≈ pinv(a)*(a*b) + @test typeof((a\a)*b) == typeof((pinv(a)*a)*b) == typeof(pinv(a)*(a*b)) end function test_div_pinv_consistency(a, b) test_rdiv_pinv_consistency(a, b) diff --git a/stdlib/LinearAlgebra/test/diagonal.jl b/stdlib/LinearAlgebra/test/diagonal.jl index 6efed3b7d9cff..5f169d21ff6fb 100644 --- a/stdlib/LinearAlgebra/test/diagonal.jl +++ b/stdlib/LinearAlgebra/test/diagonal.jl @@ -9,7 +9,16 @@ 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 -n=12 #Size of matrix problem to test +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +using .Main.OffsetArrays + +isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "InfiniteArrays.jl")) +using .Main.InfiniteArrays + +isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +using .Main.FillArrays + +const n=12 # Size of matrix problem to test Random.seed!(1) @testset for relty in (Float32, Float64, BigFloat), elty in (relty, Complex{relty}) @@ -34,11 +43,14 @@ Random.seed!(1) end @test eltype(Diagonal{elty}([1,2,3,4])) == elty @test isa(Diagonal{elty,Vector{elty}}(GenericArray([1,2,3,4])), Diagonal{elty,Vector{elty}}) + @test isa(Diagonal{elty}(rand(Int,n,n)), Diagonal{elty,Vector{elty}}) DI = Diagonal([1,2,3,4]) @test Diagonal(DI) === DI @test isa(Diagonal{elty}(DI), Diagonal{elty}) # issue #26178 - @test_throws MethodError convert(Diagonal, [1, 2, 3, 4]) + @test_throws MethodError convert(Diagonal, [1,2,3,4]) + @test_throws DimensionMismatch convert(Diagonal, [1 2 3 4]) + @test_throws InexactError convert(Diagonal, ones(2,2)) end @testset "Basic properties" begin @@ -369,9 +381,17 @@ Random.seed!(1) @testset "conj and transpose" begin @test transpose(D) == D - if elty <: BlasComplex + if elty <: Real + @test transpose(D) === D + @test adjoint(D) === D + elseif elty <: BlasComplex @test Array(conj(D)) ≈ conj(DM) @test adjoint(D) == conj(D) + local D2 = copy(D) + local D2adj = adjoint(D2) + D2adj[1,1] = rand(eltype(D2adj)) + @test D2[1,1] == adjoint(D2adj[1,1]) + @test D2adj' === D2 end # Translates to Ac/t_mul_B, which is specialized after issue 21286 @test(D' * vv == conj(D) * vv) @@ -459,12 +479,56 @@ end @test kron(Ad, Ad).diag == kron([1, 2, 3], [1, 2, 3]) end +# Define a vector type that does not support `deleteat!`, to ensure that `kron` handles this +struct SimpleVector{T} <: AbstractVector{T} + vec::Vector{T} +end +SimpleVector(x::SimpleVector) = SimpleVector(Vector(x.vec)) +SimpleVector{T}(::UndefInitializer, n::Integer) where {T} = SimpleVector(Vector{T}(undef, n)) +Base.:(==)(x::SimpleVector, y::SimpleVector) = x == y +Base.axes(x::SimpleVector) = axes(x.vec) +Base.convert(::Type{Vector{T}}, x::SimpleVector) where {T} = convert(Vector{T}, x.vec) +Base.convert(::Type{Vector}, x::SimpleVector{T}) where {T} = convert(Vector{T}, x) +Base.convert(::Type{Array{T}}, x::SimpleVector) where {T} = convert(Vector{T}, x) +Base.convert(::Type{Array}, x::SimpleVector) = convert(Vector, x) +Base.copyto!(x::SimpleVector, y::SimpleVector) = (copyto!(x.vec, y.vec); x) +Base.eltype(::Type{SimpleVector{T}}) where {T} = T +Base.getindex(x::SimpleVector, ind...) = getindex(x.vec, ind...) +Base.kron(x::SimpleVector, y::SimpleVector) = SimpleVector(kron(x.vec, y.vec)) +Base.promote_rule(::Type{<:AbstractVector{T}}, ::Type{SimpleVector{U}}) where {T,U} = Vector{promote_type(T, U)} +Base.promote_rule(::Type{SimpleVector{T}}, ::Type{SimpleVector{U}}) where {T,U} = SimpleVector{promote_type(T, U)} +Base.setindex!(x::SimpleVector, val, ind...) = (setindex!(x.vec, val, ind...), x) +Base.similar(x::SimpleVector, ::Type{T}) where {T} = SimpleVector(similar(x.vec, T)) +Base.similar(x::SimpleVector, ::Type{T}, dims::Dims{1}) where {T} = SimpleVector(similar(x.vec, T, dims)) +Base.size(x::SimpleVector) = size(x.vec) + +@testset "kron (issue #46456)" for repr in Any[identity, SimpleVector] + A = Diagonal(repr(randn(10))) + BL = Bidiagonal(repr(randn(10)), repr(randn(9)), :L) + BU = Bidiagonal(repr(randn(10)), repr(randn(9)), :U) + C = SymTridiagonal(repr(randn(10)), repr(randn(9))) + Cl = SymTridiagonal(repr(randn(10)), repr(randn(10))) + D = Tridiagonal(repr(randn(9)), repr(randn(10)), repr(randn(9))) + @test kron(A, BL)::Bidiagonal == kron(Array(A), Array(BL)) + @test kron(A, BU)::Bidiagonal == kron(Array(A), Array(BU)) + @test kron(A, C)::SymTridiagonal == kron(Array(A), Array(C)) + @test kron(A, Cl)::SymTridiagonal == kron(Array(A), Array(Cl)) + @test kron(A, D)::Tridiagonal == kron(Array(A), Array(D)) +end + @testset "svdvals and eigvals (#11120/#11247)" begin D = Diagonal(Matrix{Float64}[randn(3,3), randn(2,2)]) @test sort([svdvals(D)...;], rev = true) ≈ svdvals([D.diag[1] zeros(3,2); zeros(2,3) D.diag[2]]) @test sort([eigvals(D)...;], by=LinearAlgebra.eigsortby) ≈ eigvals([D.diag[1] zeros(3,2); zeros(2,3) D.diag[2]]) end +@testset "eigvals should return a copy of the diagonal" begin + D = Diagonal([1, 2, 3]) + lam = eigvals(D) + D[3,3] = 4 # should not affect lam + @test lam == [1, 2, 3] +end + @testset "eigmin (#27847)" begin for _ in 1:100 d = randn(rand(1:10)) @@ -519,7 +583,7 @@ end end @testset "inverse" begin - for d in (randn(n), [1, 2, 3], [1im, 2im, 3im]) + for d in Any[randn(n), Int[], [1, 2, 3], [1im, 2im, 3im], [1//1, 2//1, 3//1], [1+1im//1, 2//1, 3im//1]] D = Diagonal(d) @test inv(D) ≈ inv(Array(D)) end @@ -528,6 +592,14 @@ end @test_throws SingularException inv(Diagonal([0im, 1im, 2im])) end +@testset "pseudoinverse" begin + for d in Any[randn(n), zeros(n), Int[], [0, 2, 0.003], [0im, 1+2im, 0.003im], [0//1, 2//1, 3//100], [0//1, 1//1+2im, 3im//100]] + D = Diagonal(d) + @test pinv(D) ≈ pinv(Array(D)) + @test pinv(D, 1.0e-2) ≈ pinv(Array(D), 1.0e-2) + end +end + # allow construct from range @test all(Diagonal(range(1, stop=3, length=3)) .== Diagonal([1.0,2.0,3.0])) @@ -635,6 +707,16 @@ end @test D2 == D * D end +@testset "multiplication of 2 Diagonal and a Matrix (#46400)" begin + A = randn(10, 10) + D = Diagonal(randn(10)) + D2 = Diagonal(randn(10)) + @test D * A * D2 ≈ D * (A * D2) + @test D * A * D2 ≈ (D * A) * D2 + @test_throws DimensionMismatch Diagonal(ones(9)) * A * D2 + @test_throws DimensionMismatch D * A * Diagonal(ones(9)) +end + @testset "multiplication of QR Q-factor and Diagonal (#16615 spot test)" begin D = Diagonal(randn(5)) Q = qr(randn(5, 5)).Q @@ -778,6 +860,16 @@ end @test_throws DimensionMismatch lmul!(Diagonal([1]), [1,2,3]) # nearby end +@testset "Multiplication of a Diagonal with an OffsetArray" begin + # Offset indices should throw + D = Diagonal(1:4) + A = OffsetArray(rand(4,4), 2, 2) + @test_throws ArgumentError D * A + @test_throws ArgumentError A * D + @test_throws ArgumentError mul!(similar(A, size(A)), A, D) + @test_throws ArgumentError mul!(similar(A, size(A)), D, A) +end + @testset "Triangular division by Diagonal #27989" begin K = 5 for elty in (Float32, Float64, ComplexF32, ComplexF64) @@ -902,10 +994,14 @@ end @test s1 == prod(sign, d) end -@testset "Empty (#35424)" begin +@testset "Empty (#35424) & size checks (#47060)" begin @test zeros(0)'*Diagonal(zeros(0))*zeros(0) === 0.0 @test transpose(zeros(0))*Diagonal(zeros(Complex{Int}, 0))*zeros(0) === 0.0 + 0.0im @test dot(zeros(Int32, 0), Diagonal(zeros(Int, 0)), zeros(Int16, 0)) === 0 + @test_throws DimensionMismatch zeros(2)' * Diagonal(zeros(2)) * zeros(3) + @test_throws DimensionMismatch zeros(3)' * Diagonal(zeros(2)) * zeros(2) + @test_throws DimensionMismatch dot(zeros(2), Diagonal(zeros(2)), zeros(3)) + @test_throws DimensionMismatch dot(zeros(3), Diagonal(zeros(2)), zeros(2)) end @testset "Diagonal(undef)" begin @@ -1031,4 +1127,51 @@ end @test outTri === mul!(outTri, UTriA, D, 2, 1)::Tri == mul!(out, Matrix(UTriA), D, 2, 1) end +struct SMatrix1{T} <: AbstractArray{T,2} + elt::T +end +Base.:(==)(A::SMatrix1, B::SMatrix1) = A.elt == B.elt +Base.zero(::Type{SMatrix1{T}}) where {T} = SMatrix1(zero(T)) +Base.iszero(A::SMatrix1) = iszero(A.elt) +Base.getindex(A::SMatrix1, inds...) = A.elt +Base.size(::SMatrix1) = (1, 1) +@testset "map for Diagonal matrices (#46292)" begin + A = Diagonal([1]) + @test A isa Diagonal{Int,Vector{Int}} + @test 2*A isa Diagonal{Int,Vector{Int}} + @test A.+1 isa Matrix{Int} + # Numeric element types remain diagonal + B = map(SMatrix1, A) + @test B == fill(SMatrix1(1), 1, 1) + @test B isa Diagonal{SMatrix1{Int},Vector{SMatrix1{Int}}} + # Non-numeric element types become dense + C = map(a -> SMatrix1(string(a)), A) + @test C == fill(SMatrix1(string(1)), 1, 1) + @test C isa Matrix{SMatrix1{String}} +end + +@testset "copyto! with UniformScaling" begin + @testset "Fill" begin + for len in (4, InfiniteArrays.Infinity()) + d = FillArrays.Fill(1, len) + D = Diagonal(d) + @test copyto!(D, I) === D + end + end + D = Diagonal(fill(2, 2)) + copyto!(D, I) + @test all(isone, diag(D)) +end + +@testset "diagonal triple multiplication (#49005)" begin + n = 10 + @test *(Diagonal(ones(n)), Diagonal(1:n), Diagonal(ones(n))) isa Diagonal + @test_throws DimensionMismatch (*(Diagonal(ones(n)), Diagonal(1:n), Diagonal(ones(n+1)))) + @test_throws DimensionMismatch (*(Diagonal(ones(n)), Diagonal(1:n+1), Diagonal(ones(n+1)))) + @test_throws DimensionMismatch (*(Diagonal(ones(n+1)), Diagonal(1:n), Diagonal(ones(n)))) + + # currently falls back to two-term * + @test *(Diagonal(ones(n)), Diagonal(1:n), Diagonal(ones(n)), Diagonal(1:n)) isa Diagonal +end + end # module TestDiagonal diff --git a/stdlib/LinearAlgebra/test/eigen.jl b/stdlib/LinearAlgebra/test/eigen.jl index 4ee1845ecc385..413a8df0474fa 100644 --- a/stdlib/LinearAlgebra/test/eigen.jl +++ b/stdlib/LinearAlgebra/test/eigen.jl @@ -45,6 +45,16 @@ aimg = randn(n,n)/2 @test eigvecs(f) === f.vectors @test Array(f) ≈ a + for T in (Tridiagonal(a), Hermitian(Tridiagonal(a))) + f = eigen(T) + d, v = f + for i in 1:size(a,2) + @test T*v[:,i] ≈ d[i]*v[:,i] + end + @test det(T) ≈ det(f) + @test inv(T) ≈ inv(f) + end + num_fact = eigen(one(eltya)) @test num_fact.values[1] == one(eltya) h = asym @@ -61,43 +71,60 @@ aimg = randn(n,n)/2 asym_sg = view(asym, 1:n1, 1:n1) a_sg = view(a, 1:n, n1+1:n2) end - f = eigen(asym_sg, a_sg'a_sg) - @test asym_sg*f.vectors ≈ (a_sg'a_sg*f.vectors) * Diagonal(f.values) - @test f.values ≈ eigvals(asym_sg, a_sg'a_sg) - @test prod(f.values) ≈ prod(eigvals(asym_sg/(a_sg'a_sg))) atol=200ε - @test eigvecs(asym_sg, a_sg'a_sg) == f.vectors + ASG2 = a_sg'a_sg + f = eigen(asym_sg, ASG2) + @test asym_sg*f.vectors ≈ (ASG2*f.vectors) * Diagonal(f.values) + @test f.values ≈ eigvals(asym_sg, ASG2) + @test prod(f.values) ≈ prod(eigvals(asym_sg/(ASG2))) atol=200ε + @test eigvecs(asym_sg, ASG2) == f.vectors @test eigvals(f) === f.values @test eigvecs(f) === f.vectors @test_throws ErrorException f.Z - d,v = eigen(asym_sg, a_sg'a_sg) + d,v = eigen(asym_sg, ASG2) @test d == f.values @test v == f.vectors # solver for in-place U' \ A / U (#14896) if !(eltya <: Integer) for atyp in (eltya <: Real ? (Symmetric, Hermitian) : (Hermitian,)) - for utyp in (UpperTriangular, Diagonal) - A = atyp(asym_sg) - U = utyp(a_sg'a_sg) + for utyp in (UpperTriangular, Diagonal), uplo in (:L, :U) + A = atyp(asym_sg, uplo) + U = utyp(ASG2) @test UtiAUi!(copy(A), U) ≈ U' \ A / U end end end # matrices of different types (#14896) - if eltya <: Real - fs = eigen(Symmetric(asym_sg), a_sg'a_sg) - @test fs.values ≈ f.values - @test abs.(fs.vectors) ≈ abs.(f.vectors) # may change sign - gs = eigen(Symmetric(asym_sg), Diagonal(a_sg'a_sg)) - @test Symmetric(asym_sg)*gs.vectors ≈ (Diagonal(a_sg'a_sg)*gs.vectors) * Diagonal(gs.values) + D = Diagonal(ASG2) + for uplo in (:L, :U) + if eltya <: Real + fs = eigen(Symmetric(asym_sg, uplo), ASG2) + @test fs.values ≈ f.values + @test abs.(fs.vectors) ≈ abs.(f.vectors) # may change sign + gs = eigen(Symmetric(asym_sg, uplo), D) + @test Symmetric(asym_sg, uplo)*gs.vectors ≈ (D*gs.vectors) * Diagonal(gs.values) + end + fh = eigen(Hermitian(asym_sg, uplo), ASG2) + @test fh.values ≈ f.values + @test abs.(fh.vectors) ≈ abs.(f.vectors) # may change sign + gh = eigen(Hermitian(asym_sg, uplo), D) + @test Hermitian(asym_sg, uplo)*gh.vectors ≈ (D*gh.vectors) * Diagonal(gh.values) + gd = eigen(Matrix(Hermitian(ASG2, uplo)), D) + @test Hermitian(ASG2, uplo) * gd.vectors ≈ D * gd.vectors * Diagonal(gd.values) + gd = eigen(Hermitian(Tridiagonal(ASG2), uplo), D) + @test Hermitian(Tridiagonal(ASG2), uplo) * gd.vectors ≈ D * gd.vectors * Diagonal(gd.values) end - fh = eigen(Hermitian(asym_sg), a_sg'a_sg) - @test fh.values ≈ f.values - @test abs.(fh.vectors) ≈ abs.(f.vectors) # may change sign - gh = eigen(Hermitian(asym_sg), Diagonal(a_sg'a_sg)) - @test Hermitian(asym_sg)*gh.vectors ≈ (Diagonal(a_sg'a_sg)*gh.vectors) * Diagonal(gh.values) + gd = eigen(D, D) + @test all(≈(1), gd.values) + @test D * gd.vectors ≈ D * gd.vectors * Diagonal(gd.values) + gd = eigen(Matrix(D), D) + @test D * gd.vectors ≈ D * gd.vectors * Diagonal(gd.values) + gd = eigen(D, Matrix(D)) + @test D * gd.vectors ≈ D * gd.vectors * Diagonal(gd.values) + gd = eigen(Tridiagonal(ASG2), Matrix(D)) + @test Tridiagonal(ASG2) * gd.vectors ≈ D * gd.vectors * Diagonal(gd.values) end @testset "Non-symmetric generalized eigenproblem" begin if isa(a, Array) @@ -115,6 +142,9 @@ aimg = randn(n,n)/2 @test eigvecs(a1_nsg, a2_nsg; sortby = sortfunc) == f.vectors @test_throws ErrorException f.Z + g = eigen(a1_nsg, Diagonal(1:n1)) + @test a1_nsg*g.vectors ≈ (Diagonal(1:n1)*g.vectors) * Diagonal(g.values) + d,v = eigen(a1_nsg, a2_nsg; sortby = sortfunc) @test d == f.values @test v == f.vectors @@ -129,8 +159,17 @@ end test_matrix = rand(typeof(eltya),3,3) test_matrix[1,3] = eltya @test_throws(ArgumentError, eigen(test_matrix)) + @test_throws(ArgumentError, eigvals(test_matrix)) + @test_throws(ArgumentError, eigvecs(test_matrix)) @test_throws(ArgumentError, eigen(Symmetric(test_matrix))) + @test_throws(ArgumentError, eigvals(Symmetric(test_matrix))) + @test_throws(ArgumentError, eigvecs(Symmetric(test_matrix))) @test_throws(ArgumentError, eigen(Hermitian(test_matrix))) + @test_throws(ArgumentError, eigvals(Hermitian(test_matrix))) + @test_throws(ArgumentError, eigvecs(Hermitian(test_matrix))) + @test_throws(ArgumentError, eigen(Hermitian(complex.(test_matrix)))) + @test_throws(ArgumentError, eigvals(Hermitian(complex.(test_matrix)))) + @test_throws(ArgumentError, eigvecs(Hermitian(complex.(test_matrix)))) @test eigen(Symmetric(test_matrix, :L)) isa Eigen @test eigen(Hermitian(test_matrix, :L)) isa Eigen end diff --git a/stdlib/LinearAlgebra/test/factorization.jl b/stdlib/LinearAlgebra/test/factorization.jl index d200eff2f17bf..72233293ff515 100644 --- a/stdlib/LinearAlgebra/test/factorization.jl +++ b/stdlib/LinearAlgebra/test/factorization.jl @@ -56,11 +56,24 @@ end A = randn(3, 3) A = A * A' # ensure A is pos. def. and symmetric F = f(A) - tF = Transpose(F) - aF = Adjoint(F) @test size(F) == size(A) - @test size(tF) == size(Transpose(A)) - @test size(aF) == size(Adjoint(A)) + @test size(F') == size(A') +end + +@testset "size for transpose factorizations - $f" for f in Any[ + bunchkaufman, + cholesky, + x -> cholesky(x, RowMaximum()), + hessenberg, + lq, + lu, + svd, +] + A = randn(3, 3) + A = A * A' # ensure A is pos. def. and symmetric + F = f(A) + @test size(F) == size(A) + @test size(transpose(F)) == size(transpose(A)) end @testset "equality of QRCompactWY" begin diff --git a/stdlib/LinearAlgebra/test/generic.jl b/stdlib/LinearAlgebra/test/generic.jl index 26534a2cdf0cd..3ebaf38e84945 100644 --- a/stdlib/LinearAlgebra/test/generic.jl +++ b/stdlib/LinearAlgebra/test/generic.jl @@ -12,6 +12,8 @@ using .Main.Quaternions isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) using .Main.OffsetArrays +isdefined(Main, :DualNumbers) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "DualNumbers.jl")) +using .Main.DualNumbers Random.seed!(123) @@ -76,6 +78,10 @@ n = 5 # should be odd @test logabsdet(x)[1] ≈ logabsdet(X)[1] @test logabsdet(x)[2] ≈ logabsdet(X)[2] end + + @testset "det with nonstandard Number type" begin + elty <: Real && @test det(Dual.(triu(A), zero(A))) isa Dual + end end @testset "diag" begin @@ -90,12 +96,12 @@ end x = ['a','b','c','d','e'] y = ['a','b','c','d','e'] α, β = 'f', 'g' - @test_throws DimensionMismatch LinearAlgebra.axpy!(α,x,['g']) - @test_throws DimensionMismatch LinearAlgebra.axpby!(α,x,β,['g']) - @test_throws BoundsError LinearAlgebra.axpy!(α,x,Vector(-1:5),y,Vector(1:7)) - @test_throws BoundsError LinearAlgebra.axpy!(α,x,Vector(1:7),y,Vector(-1:5)) - @test_throws BoundsError LinearAlgebra.axpy!(α,x,Vector(1:7),y,Vector(1:7)) - @test_throws DimensionMismatch LinearAlgebra.axpy!(α,x,Vector(1:3),y,Vector(1:5)) + @test_throws DimensionMismatch axpy!(α, x, ['g']) + @test_throws DimensionMismatch axpby!(α, x, β, ['g']) + @test_throws BoundsError axpy!(α, x, Vector(-1:5), y, Vector(1:7)) + @test_throws BoundsError axpy!(α, x, Vector(1:7), y, Vector(-1:5)) + @test_throws BoundsError axpy!(α, x, Vector(1:7), y, Vector(1:7)) + @test_throws DimensionMismatch axpy!(α, x, Vector(1:3), y, Vector(1:5)) end @test !issymmetric(fill(1,5,3)) @@ -208,6 +214,8 @@ end @test norm(NaN, 0) === NaN end +@test rank(zeros(4)) == 0 +@test rank(1:10) == 1 @test rank(fill(0, 0, 0)) == 0 @test rank([1.0 0.0; 0.0 0.9],0.95) == 1 @test rank([1.0 0.0; 0.0 0.9],rtol=0.95) == 1 @@ -240,6 +248,24 @@ end @test norm(x, 3) ≈ cbrt(5^3 +sqrt(5)^3) end +@testset "norm of transpose/adjoint equals norm of parent #32739" begin + for t in (transpose, adjoint), elt in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}) + # Vector/matrix of scalars + for sz in ((2,), (2, 3)) + A = rand(elt, sz...) + Aᵀ = t(A) + @test norm(Aᵀ) ≈ norm(Matrix(Aᵀ)) + end + + # Vector/matrix of vectors/matrices + for sz_outer in ((2,), (2, 3)), sz_inner in ((3,), (1, 2)) + A = [rand(elt, sz_inner...) for _ in CartesianIndices(sz_outer)] + Aᵀ = t(A) + @test norm(Aᵀ) ≈ norm(Matrix(Matrix.(Aᵀ))) + end + end +end + @testset "rotate! and reflect!" begin x = rand(ComplexF64, 10) y = rand(ComplexF64, 10) @@ -273,26 +299,51 @@ end end end -@testset "LinearAlgebra.axp(b)y! for element type without commutative multiplication" begin +@testset "axp(b)y! for element type without commutative multiplication" begin α = [1 2; 3 4] β = [5 6; 7 8] x = fill([ 9 10; 11 12], 3) y = fill([13 14; 15 16], 3) - axpy = LinearAlgebra.axpy!(α, x, deepcopy(y)) - axpby = LinearAlgebra.axpby!(α, x, β, deepcopy(y)) + axpy = axpy!(α, x, deepcopy(y)) + axpby = axpby!(α, x, β, deepcopy(y)) @test axpy == x .* [α] .+ y @test axpy != [α] .* x .+ y @test axpby == x .* [α] .+ y .* [β] @test axpby != [α] .* x .+ [β] .* y + axpy = axpy!(zero(α), x, deepcopy(y)) + axpby = axpby!(zero(α), x, one(β), deepcopy(y)) + @test axpy == y + @test axpy == y + @test axpby == y + @test axpby == y end -@testset "LinearAlgebra.axpy! for x and y of different dimensions" begin +@testset "axpy! for x and y of different dimensions" begin α = 5 x = 2:5 y = fill(1, 2, 4) rx = [1 4] ry = [2 8] - @test LinearAlgebra.axpy!(α, x, rx, y, ry) == [1 1 1 1; 11 1 1 26] + @test axpy!(α, x, rx, y, ry) == [1 1 1 1; 11 1 1 26] +end + +@testset "axp(b)y! for non strides input" begin + a = rand(5, 5) + @test axpby!(1, Hermitian(a), 1, zeros(size(a))) == Hermitian(a) + @test axpby!(1, 1.:5, 1, zeros(5)) == 1.:5 + @test axpy!(1, Hermitian(a), zeros(size(a))) == Hermitian(a) + @test axpy!(1, 1.:5, zeros(5)) == 1.:5 +end + +@testset "LinearAlgebra.axp(b)y! for stride-vector like input" begin + for T in (Float32, Float64, ComplexF32, ComplexF64) + a = rand(T, 5, 5) + @test axpby!(1, view(a, :, 1:5), 1, zeros(T, size(a))) == a + @test axpy!(1, view(a, :, 1:5), zeros(T, size(a))) == a + b = view(a, 25:-2:1) + @test axpby!(1, b, 1, zeros(T, size(b))) == b + @test axpy!(1, b, zeros(T, size(b))) == b + end end @testset "norm and normalize!" begin @@ -318,6 +369,7 @@ end [1.0 2.0 3.0; 4.0 5.0 6.0], # 2-dim rand(1,2,3), # higher dims rand(1,2,3,4), + Dual.(randn(2,3), randn(2,3)), OffsetArray([-1,0], (-2,)) # no index 1 ) @test normalize(arr) == normalize!(copy(arr)) @@ -329,6 +381,13 @@ end @test typeof(normalize([1 2 3; 4 5 6])) == Array{Float64,2} end +@testset "normalize for scalars" begin + @test normalize(8.0) == 1.0 + @test normalize(-3.0) == -1.0 + @test normalize(-3.0, 1) == -1.0 + @test isnan(normalize(0.0)) +end + @testset "Issue #30466" begin @test norm([typemin(Int), typemin(Int)], Inf) == -float(typemin(Int)) @test norm([typemin(Int), typemin(Int)], 1) == -2float(typemin(Int)) @@ -390,11 +449,13 @@ Base.:-(a::ModInt{n}) where {n} = ModInt{n}(-a.k) Base.inv(a::ModInt{n}) where {n} = ModInt{n}(invmod(a.k, n)) Base.:/(a::ModInt{n}, b::ModInt{n}) where {n} = a*inv(b) +Base.isfinite(a::ModInt{n}) where {n} = isfinite(a.k) Base.zero(::Type{ModInt{n}}) where {n} = ModInt{n}(0) Base.zero(::ModInt{n}) where {n} = ModInt{n}(0) Base.one(::Type{ModInt{n}}) where {n} = ModInt{n}(1) Base.one(::ModInt{n}) where {n} = ModInt{n}(1) Base.conj(a::ModInt{n}) where {n} = a +LinearAlgebra.lupivottype(::Type{ModInt{n}}) where {n} = RowNonZero() Base.adjoint(a::ModInt{n}) where {n} = ModInt{n}(conj(a)) Base.transpose(a::ModInt{n}) where {n} = a # see Issue 20978 LinearAlgebra.Adjoint(a::ModInt{n}) where {n} = adjoint(a) @@ -404,13 +465,22 @@ LinearAlgebra.Transpose(a::ModInt{n}) where {n} = transpose(a) A = [ModInt{2}(1) ModInt{2}(0); ModInt{2}(1) ModInt{2}(1)] b = [ModInt{2}(1), ModInt{2}(0)] + @test A*(A\b) == b + @test A*(lu(A)\b) == b @test A*(lu(A, NoPivot())\b) == b + @test A*(lu(A, RowNonZero())\b) == b + @test_throws MethodError lu(A, RowMaximum()) # Needed for pivoting: Base.abs(a::ModInt{n}) where {n} = a Base.:<(a::ModInt{n}, b::ModInt{n}) where {n} = a.k < b.k + @test A*(lu(A, RowMaximum())\b) == b + A = [ModInt{2}(0) ModInt{2}(1); ModInt{2}(1) ModInt{2}(1)] + @test A*(A\b) == b + @test A*(lu(A)\b) == b @test A*(lu(A, RowMaximum())\b) == b + @test A*(lu(A, RowNonZero())\b) == b end @testset "Issue 18742" begin @@ -478,6 +548,13 @@ end @testset "missing values" begin @test ismissing(norm(missing)) + x = [5, 6, missing] + y = [missing, 5, 6] + for p in (-Inf, -1, 1, 2, 3, Inf) + @test ismissing(norm(x, p)) + @test ismissing(norm(y, p)) + end + @test_broken ismissing(norm(x, 0)) end @testset "peakflops" begin @@ -497,20 +574,21 @@ end end @testset "adjtrans dot" begin - for t in (transpose, adjoint) - x, y = t(rand(ComplexF64, 10)), t(rand(ComplexF64, 10)) + for t in (transpose, adjoint), T in (ComplexF64, Quaternion{Float64}) + x, y = t(rand(T, 10)), t(rand(T, 10)) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) - x, y = t([rand(ComplexF64, 2, 2) for _ in 1:5]), t([rand(ComplexF64, 2, 2) for _ in 1:5]) + x, y = t([rand(T, 2, 2) for _ in 1:5]), t([rand(T, 2, 2) for _ in 1:5]) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) - x, y = t(rand(ComplexF64, 10, 5)), t(rand(ComplexF64, 10, 5)) + x, y = t(rand(T, 10, 5)), t(rand(T, 10, 5)) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) - x = t([rand(ComplexF64, 2, 2) for _ in 1:5, _ in 1:5]) - y = t([rand(ComplexF64, 2, 2) for _ in 1:5, _ in 1:5]) + x = t([rand(T, 2, 2) for _ in 1:5, _ in 1:5]) + y = t([rand(T, 2, 2) for _ in 1:5, _ in 1:5]) X, Y = copy(x), copy(y) @test dot(x, y) ≈ dot(X, Y) + x, y = t([rand(T, 2, 2) for _ in 1:5]), t([rand(T, 2, 2) for _ in 1:5]) end end diff --git a/stdlib/LinearAlgebra/test/givens.jl b/stdlib/LinearAlgebra/test/givens.jl index c1d0caf7b8883..a2556b45d1280 100644 --- a/stdlib/LinearAlgebra/test/givens.jl +++ b/stdlib/LinearAlgebra/test/givens.jl @@ -3,10 +3,10 @@ module TestGivens using Test, LinearAlgebra, Random -using LinearAlgebra: rmul!, lmul!, Givens +using LinearAlgebra: Givens, Rotation # Test givens rotations -@testset for elty in (Float32, Float64, ComplexF32, ComplexF64) +@testset "Test Givens for $elty" for elty in (Float32, Float64, ComplexF32, ComplexF64) if elty <: Real raw_A = convert(Matrix{elty}, randn(10,10)) else @@ -14,25 +14,35 @@ using LinearAlgebra: rmul!, lmul!, Givens end @testset for A in (raw_A, view(raw_A, 1:10, 1:10)) Ac = copy(A) - R = LinearAlgebra.Rotation(LinearAlgebra.Givens{elty}[]) + R = Rotation(Givens{elty}[]) + T = Rotation(Givens{elty}[]) for j = 1:8 for i = j+2:10 G, _ = givens(A, j+1, i, j) lmul!(G, A) rmul!(A, adjoint(G)) lmul!(G, R) + rmul!(T, G) - @test lmul!(G,Matrix{elty}(I, 10, 10)) == [G[i,j] for i=1:10,j=1:10] + @test lmul!(G, Matrix{elty}(I, 10, 10)) == [G[i,j] for i=1:10,j=1:10] @testset "transposes" begin - @test G'*G*Matrix(elty(1)I, 10, 10) ≈ Matrix(I, 10, 10) + @test (@inferred G'*G)*Matrix(elty(1)I, 10, 10) ≈ Matrix(I, 10, 10) @test (G*Matrix(elty(1)I, 10, 10))*G' ≈ Matrix(I, 10, 10) - @test copy(R')*(R*Matrix(elty(1)I, 10, 10)) ≈ Matrix(I, 10, 10) + @test (@inferred copy(R'))*(R*Matrix(elty(1)I, 10, 10)) ≈ Matrix(I, 10, 10) @test_throws ErrorException transpose(G) @test_throws ErrorException transpose(R) end end end + @test (R')' === R + # test products of Givens and Rotations + for r in (R, T, *(R.rotations...), *(R.rotations[1], *(R.rotations[2:end]...))) + @test r * A ≈ (A' * r')' ≈ lmul!(r, copy(A)) + @test A * r ≈ (r' * A')' ≈ rmul!(copy(A), r) + @test r' * A ≈ lmul!(r', copy(A)) + @test A * r' ≈ rmul!(copy(A), r') + end @test_throws ArgumentError givens(A, 3, 3, 2) @test_throws ArgumentError givens(one(elty),zero(elty),2,2) G, _ = givens(one(elty),zero(elty),11,12) @@ -46,27 +56,29 @@ using LinearAlgebra: rmul!, lmul!, Givens @test (G*I10)' * (G*I10) ≈ I10 K, _ = givens(zero(elty),one(elty),9,10) @test (K*I10)' * (K*I10) ≈ I10 + end - @testset "Givens * vectors" begin - if isa(A, Array) - x = A[:, 1] - else - x = view(A, 1:10, 1) - end - G, r = givens(x[2], x[4], 2, 4) + @testset "Givens * vectors" begin + for x in (raw_A[:,1], view(raw_A, :, 1)) + G, r = @inferred givens(x[2], x[4], 2, 4) @test (G*x)[2] ≈ r @test abs((G*x)[4]) < eps(real(elty)) - @inferred givens(x[2], x[4], 2, 4) - G, r = givens(x, 2, 4) + G, r = @inferred givens(x, 2, 4) @test (G*x)[2] ≈ r @test abs((G*x)[4]) < eps(real(elty)) - @inferred givens(x, 2, 4) G, r = givens(x, 4, 2) @test (G*x)[4] ≈ r @test abs((G*x)[2]) < eps(real(elty)) end + d = rand(4) + l = d[1] + g2, l = givens(l, d[2], 1, 2) + g3, l = givens(l, d[3], 1, 3) + g4, l = givens(l, d[4], 1, 4) + @test g2*(g3*d) ≈ g2*g3*d ≈ (g2*g3)*d + @test g2*g3*g4 isa Rotation end end diff --git a/stdlib/LinearAlgebra/test/hessenberg.jl b/stdlib/LinearAlgebra/test/hessenberg.jl index b2b23caac6865..91e4e1b1b3df0 100644 --- a/stdlib/LinearAlgebra/test/hessenberg.jl +++ b/stdlib/LinearAlgebra/test/hessenberg.jl @@ -24,6 +24,11 @@ let n = 10 A = Areal H = UpperHessenberg(A) AH = triu(A,-1) + for k in -2:2 + @test istril(H, k) == istril(AH, k) + @test istriu(H, k) == istriu(AH, k) + @test (k <= -1 ? istriu(H, k) : !istriu(H, k)) + end @test UpperHessenberg(H) === H @test parent(H) === A @test Matrix(H) == Array(H) == H == AH @@ -92,10 +97,10 @@ let n = 10 @testset "Multiplication/division" begin for x = (5, 5I, Diagonal(d), Bidiagonal(d,dl,:U), UpperTriangular(A), UnitUpperTriangular(A)) - @test (H*x)::UpperHessenberg == Array(H)*x - @test (x*H)::UpperHessenberg == x*Array(H) - @test H/x == Array(H)/x broken = eltype(H) <: Furlong && x isa UpperTriangular - @test x\H == x\Array(H) broken = eltype(H) <: Furlong && x isa UpperTriangular + @test (H*x)::UpperHessenberg ≈ Array(H)*x + @test (x*H)::UpperHessenberg ≈ x*Array(H) + @test H/x ≈ Array(H)/x# broken = eltype(H) <: Furlong && x isa UpperTriangular + @test x\H ≈ x\Array(H)# broken = eltype(H) <: Furlong && x isa UpperTriangular @test H/x isa UpperHessenberg @test x\H isa UpperHessenberg end @@ -108,13 +113,12 @@ let n = 10 H = UpperHessenberg(Furlong.(Areal)) for A in (A, Furlong.(A)) @testset "Multiplication/division Furlong" begin - for x = (5, 5I, Diagonal(d), Bidiagonal(d,dl,:U)) - @test (H*x)::UpperHessenberg == Array(H)*x - @test (x*H)::UpperHessenberg == x*Array(H) - @test H/x == Array(H)/x broken = eltype(H) <: Furlong && x isa UpperTriangular - @test x\H == x\Array(H) broken = eltype(H) <: Furlong && x isa UpperTriangular - @test H/x isa UpperHessenberg - @test x\H isa UpperHessenberg + for x = (5, 5I, Diagonal(d), Bidiagonal(d,dl,:U), + UpperTriangular(A), UnitUpperTriangular(A)) + @test map(x -> x.val, (H*x)::UpperHessenberg) ≈ map(x -> x.val, Array(H)*x) + @test map(x -> x.val, (x*H)::UpperHessenberg) ≈ map(x -> x.val, x*Array(H)) + @test map(x -> x.val, (H/x)::UpperHessenberg) ≈ map(x -> x.val, Array(H)/x) + @test map(x -> x.val, (x\H)::UpperHessenberg) ≈ map(x -> x.val, x\Array(H)) end x = Bidiagonal(d, dl, :L) @test H*x == Array(H)*x @@ -144,9 +148,11 @@ let n = 10 @test_throws ErrorException H.Z @test convert(Array, H) ≈ A @test (H.Q * H.H) * H.Q' ≈ A ≈ (Matrix(H.Q) * Matrix(H.H)) * Matrix(H.Q)' - @test (H.Q' *A) * H.Q ≈ H.H + @test (H.Q' * A) * H.Q ≈ H.H #getindex for HessenbergQ @test H.Q[1,1] ≈ Array(H.Q)[1,1] + @test det(H.Q) ≈ det(Matrix(H.Q)) + @test logabsdet(H.Q)[1] ≈ logabsdet(Matrix(H.Q))[1] atol=2n*eps(float(real(eltya))) # REPL show hessstring = sprint((t, s) -> show(t, "text/plain", s), H) @@ -191,6 +197,13 @@ let n = 10 end end +@testset "hessenberg(::AbstractMatrix)" begin + n = 10 + A = Tridiagonal(rand(n-1), rand(n), rand(n-1)) + H = hessenberg(A) + @test convert(Array, H) ≈ A +end + # check logdet on a matrix that has a positive determinant let A = [0.5 0.1 0.9 0.4; 0.9 0.7 0.5 0.4; 0.3 0.4 0.9 0.0; 0.4 0.0 0.0 0.5] @test logdet(hessenberg(A)) ≈ logdet(A) ≈ -3.5065578973199822 diff --git a/stdlib/LinearAlgebra/test/lapack.jl b/stdlib/LinearAlgebra/test/lapack.jl index 284b512d93a18..2c5d92541af93 100644 --- a/stdlib/LinearAlgebra/test/lapack.jl +++ b/stdlib/LinearAlgebra/test/lapack.jl @@ -24,10 +24,17 @@ using LinearAlgebra: BlasInt vals, Z = LAPACK.syevr!('V', copy(Asym)) @test Z*(Diagonal(vals)*Z') ≈ Asym @test all(vals .> 0.0) - @test LAPACK.syevr!('N','V','U',copy(Asym),0.0,1.0,4,5,-1.0)[1] ≈ vals[vals .< 1.0] - @test LAPACK.syevr!('N','I','U',copy(Asym),0.0,1.0,4,5,-1.0)[1] ≈ vals[4:5] - @test vals ≈ LAPACK.syev!('N','U',copy(Asym)) - @test_throws DimensionMismatch LAPACK.sygvd!(1,'V','U',copy(Asym),Matrix{elty}(undef,6,6)) + @test LAPACK.syevr!('N', 'V', 'U', copy(Asym), 0.0, 1.0, 4, 5, -1.0)[1] ≈ vals[vals .< 1.0] + @test LAPACK.syevr!('N', 'I', 'U', copy(Asym), 0.0, 1.0, 4, 5, -1.0)[1] ≈ vals[4:5] + @test vals ≈ LAPACK.syev!('N', 'U', copy(Asym)) + @test vals ≈ LAPACK.syevd!('N', 'U', copy(Asym)) + vals_test, Z_test = LAPACK.syev!('V', 'U', copy(Asym)) + @test vals_test ≈ vals + @test Z_test*(Diagonal(vals)*Z_test') ≈ Asym + vals_test, Z_test = LAPACK.syevd!('V', 'U', copy(Asym)) + @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)) end end @@ -180,7 +187,7 @@ end end end -@testset "geevx, ggev errors" begin +@testset "geevx, ggev, ggev3 errors" begin @testset for elty in (Float32, Float64, ComplexF32, ComplexF64) A = rand(elty,10,10) B = rand(elty,10,10) @@ -191,12 +198,16 @@ end @test_throws ArgumentError LAPACK.ggev!('N','B',A,B) @test_throws ArgumentError LAPACK.ggev!('B','N',A,B) @test_throws DimensionMismatch LAPACK.ggev!('N','N',A,zeros(elty,12,12)) + @test_throws ArgumentError LAPACK.ggev3!('N','B',A,B) + @test_throws ArgumentError LAPACK.ggev3!('B','N',A,B) + @test_throws DimensionMismatch LAPACK.ggev3!('N','N',A,zeros(elty,12,12)) end end @testset "gebal/gebak" begin @testset for elty in (Float32, Float64, ComplexF32, ComplexF64) - A = rand(elty,10,10) * Diagonal(exp10.(range(-10, stop=10, length=10))) + typescale = log10(eps(real(elty))) / 3 * 2 + A = rand(elty,10,10) * Diagonal(exp10.(range(typescale, stop=-typescale, length=10))) B = copy(A) ilo, ihi, scale = LAPACK.gebal!('S',B) Bvs = eigvecs(B) @@ -589,11 +600,12 @@ end end end -@testset "gees, gges error throwing" begin +@testset "gees, gges, gges3 error throwing" begin @testset for elty in (Float32, Float64, ComplexF32, ComplexF64) A = rand(elty,10,10) B = rand(elty,11,11) @test_throws DimensionMismatch LAPACK.gges!('V','V',A,B) + @test_throws DimensionMismatch LAPACK.gges3!('V','V',A,B) end end @@ -702,13 +714,19 @@ let A = [NaN NaN; NaN NaN] @test_throws ArgumentError eigen(A) end -# # https://github.com/JuliaLang/julia/pull/39845 -@test LinearAlgebra.LAPACK.liblapack == "libblastrampoline" - # Issue #42762 https://github.com/JuliaLang/julia/issues/42762 # Tests geqrf! and gerqf! with null column dimensions a = zeros(2,0), zeros(0) @test LinearAlgebra.LAPACK.geqrf!(a...) === a @test LinearAlgebra.LAPACK.gerqf!(a...) === a +# Issue #49489: https://github.com/JuliaLang/julia/issues/49489 +# Dimension mismatch between A and ipiv causes segfaults +@testset "issue #49489" begin + A = randn(23,23) + b = randn(23) + ipiv = collect(1:20) + @test_throws DimensionMismatch LinearAlgebra.LAPACK.getrs!('N', A, ipiv, b) +end + end # module TestLAPACK diff --git a/stdlib/LinearAlgebra/test/lq.jl b/stdlib/LinearAlgebra/test/lq.jl index 96f31ded78d6d..8b4af6a0a5f8d 100644 --- a/stdlib/LinearAlgebra/test/lq.jl +++ b/stdlib/LinearAlgebra/test/lq.jl @@ -37,10 +37,10 @@ rectangularQ(Q::LinearAlgebra.LQPackedQ) = convert(Array, Q) @testset for isview in (false,true) let a = isview ? view(a, 1:m - 1, 1:n - 1) : a, b = isview ? view(b, 1:m - 1) : b, m = m - isview, n = n - isview - lqa = lq(a) + lqa = lq(a) x = lqa\b - l,q = lqa.L, lqa.Q - qra = qr(a, ColumnNorm()) + l, q = lqa.L, lqa.Q + qra = qr(a, ColumnNorm()) @testset "Basic ops" begin @test size(lqa,1) == size(a,1) @test size(lqa,3) == 1 @@ -62,18 +62,20 @@ rectangularQ(Q::LinearAlgebra.LQPackedQ) = convert(Array, Q) @test Array{eltya}(q) ≈ Matrix(q) end @testset "Binary ops" begin + k = size(a, 2) + T = Tridiagonal(rand(eltya, k-1), rand(eltya, k), rand(eltya, k-1)) + @test lq(T) * T ≈ T * T rtol=3000ε + @test lqa * T ≈ a * T rtol=3000ε @test a*x ≈ b rtol=3000ε @test x ≈ qra \ b rtol=3000ε @test lqa*x ≈ a*x rtol=3000ε @test (sq = size(q.factors, 2); *(Matrix{eltyb}(I, sq, sq), adjoint(q))*squareQ(q)) ≈ Matrix(I, n, n) rtol=5000ε if eltya != Int - @test Matrix{eltyb}(I, n, n)*q ≈ convert(AbstractMatrix{tab},q) + @test Matrix{eltyb}(I, n, n)*q ≈ Matrix(I, n, n) * convert(LinearAlgebra.AbstractQ{tab}, q) end @test q*x ≈ squareQ(q)*x rtol=100ε - @test transpose(q)*x ≈ transpose(squareQ(q))*x rtol=100ε @test q'*x ≈ squareQ(q)'*x rtol=100ε @test a*q ≈ a*squareQ(q) rtol=100ε - @test a*transpose(q) ≈ a*transpose(squareQ(q)) rtol=100ε @test a*q' ≈ a*squareQ(q)' rtol=100ε @test q*a'≈ squareQ(q)*a' rtol=100ε @test q'*a' ≈ squareQ(q)'*a' rtol=100ε @@ -85,7 +87,6 @@ rectangularQ(Q::LinearAlgebra.LQPackedQ) = convert(Array, Q) pad_a = vcat(I, a) pad_x = hcat(I, x) @test pad_a*q ≈ pad_a*squareQ(q) rtol=100ε - @test transpose(q)*pad_x ≈ transpose(squareQ(q))*pad_x rtol=100ε @test q'*pad_x ≈ squareQ(q)'*pad_x rtol=100ε end end @@ -189,12 +190,12 @@ end @testset for n in 1:3, m in 1:3 @testset "real" begin _, Q = lq(randn(n, m)) - @test det(Q) ≈ det(collect(Q)) + @test det(Q) ≈ det(Q*I) @test abs(det(Q)) ≈ 1 end @testset "complex" begin _, Q = lq(randn(ComplexF64, n, m)) - @test det(Q) ≈ det(collect(Q)) + @test det(Q) ≈ det(Q*I) @test abs(det(Q)) ≈ 1 end end @@ -213,11 +214,7 @@ L factor: 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 Q factor: -4×4 LinearAlgebra.LQPackedQ{Float64, Matrix{Float64}, Vector{Float64}}: - 1.0 0.0 0.0 0.0 - 0.0 1.0 0.0 0.0 - 0.0 0.0 1.0 0.0 - 0.0 0.0 0.0 1.0""" +4×4 LinearAlgebra.LQPackedQ{Float64, Matrix{Float64}, Vector{Float64}}""" end @testset "adjoint of LQ" begin diff --git a/stdlib/LinearAlgebra/test/lu.jl b/stdlib/LinearAlgebra/test/lu.jl index f07ceceec8444..aa73bee6ddc38 100644 --- a/stdlib/LinearAlgebra/test/lu.jl +++ b/stdlib/LinearAlgebra/test/lu.jl @@ -224,6 +224,11 @@ dimg = randn(n)/2 end end +@testset "Small tridiagonal matrices" for T in (Float64, ComplexF64) + A = Tridiagonal(T[], T[1], T[]) + @test inv(A) == A +end + @testset "Singular matrices" for T in (Float64, ComplexF64) A = T[1 2; 0 0] @test_throws SingularException lu(A) @@ -426,4 +431,37 @@ end end end +@testset "can push to vector after 3-arg ldiv! (#43507)" begin + u = rand(3) + A = rand(3,3) + b = rand(3) + ldiv!(u,lu(A),b) + push!(b,4.0) + @test length(b) == 4 +end + +@testset "NaN matrix should throw error" begin + for eltya in (NaN16, NaN32, NaN64, BigFloat(NaN)) + r = fill(eltya, 2, 3) + c = fill(complex(eltya, eltya), 2, 3) + @test_throws ArgumentError lu(r) + @test_throws ArgumentError lu(c) + end +end + +@testset "more generic ldiv! #35419" begin + A = rand(3, 3) + b = rand(3) + @test A * ldiv!(lu(A), Base.ReshapedArray(copy(b)', (3,), ())) ≈ b +end + +@testset "generic lu!" begin + A = rand(3,3); B = deepcopy(A); C = A[2:3,2:3] + Asub1 = @view(A[2:3,2:3]) + F1 = lu!(Asub1) + Asub2 = @view(B[[2,3],[2,3]]) + F2 = lu!(Asub2) + @test Matrix(F1) ≈ Matrix(F2) ≈ C +end + end # module TestLU diff --git a/stdlib/LinearAlgebra/test/matmul.jl b/stdlib/LinearAlgebra/test/matmul.jl index 1c482f8cae97a..2d99856a2667b 100644 --- a/stdlib/LinearAlgebra/test/matmul.jl +++ b/stdlib/LinearAlgebra/test/matmul.jl @@ -156,6 +156,58 @@ end end end +@testset "generic_matvecmul for vectors of vectors" begin + @testset "matrix of scalars" begin + u = [[1, 2], [3, 4]] + A = [1 2; 3 4] + v = [[0, 0], [0, 0]] + Au = [[7, 10], [15, 22]] + @test A * u == Au + mul!(v, A, u) + @test v == Au + mul!(v, A, u, 2, -1) + @test v == Au + end + + @testset "matrix of matrices" begin + u = [[1, 2], [3, 4]] + A = Matrix{Matrix{Int}}(undef, 2, 2) + A[1, 1] = [1 2; 3 4] + A[1, 2] = [5 6; 7 8] + A[2, 1] = [9 10; 11 12] + A[2, 2] = [13 14; 15 16] + v = [[0, 0], [0, 0]] + Au = [[44, 64], [124, 144]] + @test A * u == Au + mul!(v, A, u) + @test v == Au + mul!(v, A, u, 2, -1) + @test v == Au + end +end + +@testset "generic_matmatmul for matrices of vectors" begin + B = Matrix{Vector{Int}}(undef, 2, 2) + B[1, 1] = [1, 2] + B[2, 1] = [3, 4] + B[1, 2] = [5, 6] + B[2, 2] = [7, 8] + A = [1 2; 3 4] + C = Matrix{Vector{Int}}(undef, 2, 2) + AB = Matrix{Vector{Int}}(undef, 2, 2) + AB[1, 1] = [7, 10] + AB[2, 1] = [15, 22] + AB[1, 2] = [19, 22] + AB[2, 2] = [43, 50] + @test A * B == AB + mul!(C, A, B) + @test C == AB + mul!(C, A, B, 2, -1) + @test C == AB + LinearAlgebra._generic_matmatmul!(C, 'N', 'N', A, B, LinearAlgebra.MulAddMul(2, -1)) + @test C == AB +end + @testset "fallbacks & such for BlasFloats" begin AA = rand(Float64, 6, 6) BB = rand(Float64, 6, 6) @@ -226,6 +278,19 @@ end end end +@testset "dot product of stride-vector like input" begin + for T in (Float32, Float64, ComplexF32, ComplexF64) + a = randn(T, 10) + b = view(a, 1:10) + c = reshape(b, 5, 2) + d = view(c, :, 1:2) + r = sum(abs2, a) + for x in (a,b,c,d), y in (a,b,c,d) + @test dot(x, y) ≈ r + end + end +end + @testset "Complex matrix x real MatOrVec etc (issue #29224)" for T in (Float32, Float64) A0 = randn(complex(T), 10, 10) B0 = randn(T, 10, 10) @@ -297,6 +362,15 @@ end end end +@testset "matrix x vector with negative lda or 0 stride" for T in (Float32, Float64) + for TA in (T, complex(T)), TB in (T, complex(T)) + A = view(randn(TA, 10, 10), 1:10, 10:-1:1) # negative lda + v = view([randn(TB)], 1 .+ 0(1:10)) # 0 stride + Ad, vd = copy(A), copy(v) + @test Ad * vd ≈ A * vd ≈ Ad * v ≈ A * v + end +end + @testset "issue #15286" begin A = reshape(map(Float64, 1:20), 5, 4) C = zeros(8, 8) @@ -468,7 +542,7 @@ end X = convert(Matrix{elty}, [1.0 2.0; 3.0 4.0]) Y = convert(Matrix{elty}, [1.5 2.5; 3.5 4.5]) @test dot(X, Y) == convert(elty, 35.0) - Z = convert(Vector{Matrix{elty}}, [reshape(1:4, 2, 2), fill(1, 2, 2)]) + Z = Matrix{elty}[reshape(1:4, 2, 2), fill(1, 2, 2)] @test dot(Z, Z) == convert(elty, 34.0) end @@ -581,10 +655,10 @@ Transpose(x::RootInt) = x @testset "#14293" begin a = [RootInt(3)] - C = [0] + C = [0;;] mul!(C, a, transpose(a)) @test C[1] == 9 - C = [1] + C = [1;;] mul!(C, a, transpose(a), 2, 3) @test C[1] == 21 a = [RootInt(2), RootInt(10)] diff --git a/stdlib/LinearAlgebra/test/pinv.jl b/stdlib/LinearAlgebra/test/pinv.jl index d3eafb26797a9..c7268865a0505 100644 --- a/stdlib/LinearAlgebra/test/pinv.jl +++ b/stdlib/LinearAlgebra/test/pinv.jl @@ -63,39 +63,23 @@ function tridiag(T::Type, m::Integer, n::Integer) end tridiag(m::Integer, n::Integer) = tridiag(Float64, m::Integer, n::Integer) -function randn_float64(m::Integer, n::Integer) - a=randn(m,n) - b = Matrix{Float64}(undef, m, n) - for i=1:n - for j=1:m - b[j,i]=convert(Float64,a[j,i]) - end - end - return b -end - -function randn_float32(m::Integer, n::Integer) - a=randn(m,n) - b = Matrix{Float32}(undef, m, n) - for i=1:n - for j=1:m - b[j,i]=convert(Float32,a[j,i]) - end - end - return b -end - +function test_pinv(a,tol1,tol2) + m,n = size(a) -function test_pinv(a,m,n,tol1,tol2,tol3) apinv = @inferred pinv(a) - + @test size(apinv) == (n,m) @test norm(a*apinv*a-a)/norm(a) ≈ 0 atol=tol1 - x0 = randn(n); b = a*x0; x = apinv*b + @test norm(apinv*a*apinv-apinv)/norm(apinv) ≈ 0 atol=tol1 + b = a*randn(n) + x = apinv*b @test norm(a*x-b)/norm(b) ≈ 0 atol=tol1 - apinv = pinv(a,sqrt(eps(real(one(eltype(a)))))) + apinv = @inferred pinv(a,sqrt(eps(real(one(eltype(a)))))) + @test size(apinv) == (n,m) @test norm(a*apinv*a-a)/norm(a) ≈ 0 atol=tol2 - x0 = randn(n); b = a*x0; x = apinv*b + @test norm(apinv*a*apinv-apinv)/norm(apinv) ≈ 0 atol=tol2 + b = a*randn(n) + x = apinv*b @test norm(a*x-b)/norm(b) ≈ 0 atol=tol2 end @@ -104,28 +88,25 @@ end default_tol = (real(one(eltya))) * max(m,n) * 10 tol1 = 1e-2 tol2 = 1e-5 - tol3 = 1e-5 if real(eltya) == Float32 tol1 = 1e0 tol2 = 1e-2 - tol3 = 1e-2 end @testset "dense/ill-conditioned matrix" begin - ### a = randn_float64(m,n) * hilb(eltya,n) a = hilb(eltya, m, n) - test_pinv(a, m, n, tol1, tol2, tol3) + test_pinv(a, tol1, tol2) end @testset "dense/diagonal matrix" begin a = onediag(eltya, m, n) - test_pinv(a, m, n, default_tol, default_tol, default_tol) + test_pinv(a, default_tol, default_tol) end @testset "dense/tri-diagonal matrix" begin a = tridiag(eltya, m, n) - test_pinv(a, m, n, default_tol, tol2, default_tol) + test_pinv(a, default_tol, tol2) end @testset "Diagonal matrix" begin a = onediag_sparse(eltya, m) - test_pinv(a, m, m, default_tol, default_tol, default_tol) + test_pinv(a, default_tol, default_tol) end @testset "Vector" begin a = rand(eltya, m) @@ -164,6 +145,18 @@ end @test C ≈ ones(2,2) end + @testset "non-square diagonal matrices" begin + A = eltya[1 0 ; 0 1 ; 0 0] + B = pinv(A) + @test A*B*A ≈ A + @test B*A*B ≈ B + + A = eltya[1 0 0 ; 0 1 0] + B = pinv(A) + @test A*B*A ≈ A + @test B*A*B ≈ B + end + if eltya <: LinearAlgebra.BlasReal @testset "sub-normal numbers/vectors/matrices" begin a = pinv(floatmin(eltya)/100) diff --git a/stdlib/LinearAlgebra/test/qr.jl b/stdlib/LinearAlgebra/test/qr.jl index f9acbdb376465..6e2e9a7b20603 100644 --- a/stdlib/LinearAlgebra/test/qr.jl +++ b/stdlib/LinearAlgebra/test/qr.jl @@ -21,8 +21,8 @@ breal = randn(n,2)/2 bimg = randn(n,2)/2 # helper functions to unambiguously recover explicit forms of an implicit QR Q -squareQ(Q::LinearAlgebra.AbstractQ) = (sq = size(Q.factors, 1); lmul!(Q, Matrix{eltype(Q)}(I, sq, sq))) -rectangularQ(Q::LinearAlgebra.AbstractQ) = convert(Array, Q) +squareQ(Q::LinearAlgebra.AbstractQ) = Q*I +rectangularQ(Q::LinearAlgebra.AbstractQ) = Matrix(Q) @testset for eltya in (Float32, Float64, ComplexF32, ComplexF64, BigFloat, Int) raw_a = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(areal, aimg) : areal) @@ -62,7 +62,7 @@ rectangularQ(Q::LinearAlgebra.AbstractQ) = convert(Array, Q) sq = size(q.factors, 2) @test *(Matrix{eltyb}(I, sq, sq), adjoint(q)) * squareQ(q) ≈ Matrix(I, sq, sq) atol=5000ε if eltya != Int - @test Matrix{eltyb}(I, a_1, a_1)*q ≈ convert(AbstractMatrix{tab}, q) + @test Matrix{eltyb}(I, a_1, a_1)*q ≈ squareQ(convert(LinearAlgebra.AbstractQ{tab}, q)) ac = copy(a) @test qr!(a[:, 1:5])\b == qr!(view(ac, :, 1:5))\b end @@ -86,14 +86,14 @@ rectangularQ(Q::LinearAlgebra.AbstractQ) = convert(Array, Q) @test q*b[1:n1] ≈ rectangularQ(q)*b[1:n1] atol=100ε @test q*b ≈ squareQ(q)*b atol=100ε if eltya != Int - @test Array{eltya}(q) ≈ Matrix(q) + @test Array{eltya}(q) ≈ rectangularQ(q) end @test_throws DimensionMismatch q*b[1:n1 + 1] @test_throws DimensionMismatch b[1:n1 + 1]*q' sq = size(q.factors, 2) @test *(UpperTriangular(Matrix{eltyb}(I, sq, sq)), adjoint(q))*squareQ(q) ≈ Matrix(I, n1, a_1) atol=5000ε if eltya != Int - @test Matrix{eltyb}(I, a_1, a_1)*q ≈ convert(AbstractMatrix{tab},q) + @test Matrix{eltyb}(I, a_1, a_1)*q ≈ squareQ(convert(LinearAlgebra.AbstractQ{tab},q)) end # iterate q, r = qra @@ -123,7 +123,7 @@ rectangularQ(Q::LinearAlgebra.AbstractQ) = convert(Array, Q) @test_throws DimensionMismatch q*b[1:n1+1] @test_throws DimensionMismatch b[1:n1+1]*q' if eltya != Int - @test Matrix{eltyb}(I, n1, n1)*q ≈ convert(AbstractMatrix{tab},q) + @test Matrix{eltyb}(I, n1, n1)*q ≈ squareQ(convert(LinearAlgebra.AbstractQ{tab},q)) end # iterate q, r, p = qrpa @@ -149,7 +149,7 @@ rectangularQ(Q::LinearAlgebra.AbstractQ) = convert(Array, Q) sq = size(q.factors, 2) @test *(UpperTriangular(Matrix{eltyb}(I, sq, sq)), adjoint(q))*squareQ(q) ≈ Matrix(I, n1, a_1) atol=5000ε if eltya != Int - @test Matrix{eltyb}(I, a_1, a_1)*q ≈ convert(AbstractMatrix{tab},q) + @test Matrix{eltyb}(I, a_1, a_1)*q ≈ squareQ(convert(LinearAlgebra.AbstractQ{tab},q)) end qrstring = sprint((t, s) -> show(t, "text/plain", s), qrpa) rstring = sprint((t, s) -> show(t, "text/plain", s), r) @@ -205,15 +205,22 @@ rectangularQ(Q::LinearAlgebra.AbstractQ) = convert(Array, Q) @test mul!(c, b, q) ≈ b*q @test mul!(c, b, q') ≈ b*q' @test_throws DimensionMismatch mul!(Matrix{eltya}(I, n+1, n), q, b) + + b = similar(a[:,1]); rand!(b) + c = similar(a[:,1]) + d = similar(a[:,1]) + @test mul!(c, q, b) ≈ q*b + @test mul!(c, q', b) ≈ q'*b + @test_throws DimensionMismatch mul!(Vector{eltya}(undef, n+1), q, b) end end end end @testset "transpose errors" begin - @test_throws MethodError transpose(qr(randn(3,3))) - @test_throws MethodError transpose(qr(randn(3,3), NoPivot())) - @test_throws MethodError transpose(qr(big.(randn(3,3)))) + @test_throws ArgumentError transpose(qr(randn(ComplexF64,3,3))) + @test_throws ArgumentError transpose(qr(randn(ComplexF64,3,3), NoPivot())) + @test_throws ArgumentError transpose(qr(big.(randn(ComplexF64,3,3)))) end @testset "Issue 7304" begin @@ -228,7 +235,7 @@ end for T in (Tr, Complex{Tr}) v = convert(Vector{T}, vr) nv, nm = qr(v) - @test norm(nv - [-0.6 -0.8; -0.8 0.6], Inf) < eps(Tr) + @test norm(nv*Matrix(I, (2,2)) - [-0.6 -0.8; -0.8 0.6], Inf) < eps(Tr) @test nm == fill(-5.0, 1, 1) end end @@ -244,7 +251,7 @@ end end @testset "Issue 16520" begin - @test_throws DimensionMismatch Matrix{Float64}(undef,3,2)\(1:5) + @test_throws DimensionMismatch rand(3,2)\(1:5) end @testset "Issue 22810" begin @@ -261,7 +268,7 @@ end @testset "Issue 24589. Promotion of rational matrices" begin A = rand(1//1:5//5, 4,3) - @test first(qr(A)) == first(qr(float(A))) + @test Matrix(first(qr(A))) == Matrix(first(qr(float(A)))) end @testset "Issue Test Factorization fallbacks for rectangular problems" begin @@ -270,11 +277,19 @@ end b = randn(3) b0 = copy(b) c = randn(2) + B = randn(3,3) + B0 = copy(B) + C = randn(2,3) @test A \b ≈ ldiv!(c, qr(A ), b) @test b == b0 + @test A \B ≈ ldiv!(C, qr(A ), B) + @test B == B0 c0 = copy(c) + C0 = copy(C) @test Ac\c ≈ ldiv!(b, qr(Ac, ColumnNorm()), c) @test c0 == c + @test Ac\C ≈ ldiv!(B, qr(Ac, ColumnNorm()), C) + @test C0 == C end @testset "Issue reflector of zero-length vector" begin @@ -295,7 +310,7 @@ end @testset for k in 0:min(n, m, 5) A = cat(Array(I(k)), randn(n - k, m - k); dims=(1, 2)) Q, = qr(A, pivot) - @test det(Q) ≈ det(collect(Q)) + @test det(Q) ≈ det(Q*Matrix(I, size(Q, 1), size(Q, 1))) @test abs(det(Q)) ≈ 1 end end @@ -303,7 +318,7 @@ end @testset for k in 0:min(n, m, 5) A = cat(Array(I(k)), randn(ComplexF64, n - k, m - k); dims=(1, 2)) Q, = qr(A, pivot) - @test det(Q) ≈ det(collect(Q)) + @test det(Q) ≈ det(Q*Matrix(I, size(Q, 1), size(Q, 1))) @test abs(det(Q)) ≈ 1 end end @@ -314,6 +329,7 @@ end for T in (Float64, ComplexF64) Q = qr(randn(T,5,5)).Q @test inv(Q) === Q' + @test inv(Q)' === inv(Q') === Q end end @@ -321,7 +337,7 @@ end for T in (Float32, Float64, ComplexF32, ComplexF64) Q1, R1 = qr(randn(T,5,5)) Q2, R2 = qr(Q1) - @test Q1 ≈ Q2 + @test Matrix(Q1) ≈ Matrix(Q2) @test R2 ≈ I end end @@ -354,13 +370,13 @@ end n = 5 Q, R = qr(randn(T,n,n)) Qmat = Matrix(Q) - dest1 = similar(Q) + dest1 = Matrix{T}(undef, size(Q)) copyto!(dest1, Q) @test dest1 ≈ Qmat - dest2 = PermutedDimsArray(similar(Q), (1, 2)) + dest2 = PermutedDimsArray(Matrix{T}(undef, size(Q)), (1, 2)) copyto!(dest2, Q) @test dest2 ≈ Qmat - dest3 = PermutedDimsArray(similar(Q), (2, 1)) + dest3 = PermutedDimsArray(Matrix{T}(undef, size(Q)), (2, 1)) copyto!(dest3, Q) @test dest3 ≈ Qmat end @@ -411,8 +427,8 @@ end A = qr(ones(3, 1)) B = I(3) C = B*A.Q' - @test C ≈ A.Q - @test A.Q' * B ≈ A.Q + @test C ≈ A.Q * Matrix(I, 3, 3) + @test A.Q' * B ≈ A.Q * Matrix(I, 3, 3) end @testset "convert between eltypes" begin @@ -449,6 +465,43 @@ end @test Q2[:, :] ≈ M[:, :] @test Q2[:, :, :] ≈ M[:, :, :] end + # Check that getindex works if copy returns itself (#44729) + struct MyIdentity{T} <: LinearAlgebra.AbstractQ{T} end + Base.size(::MyIdentity, dim::Integer) = dim in (1,2) ? 2 : 1 + Base.size(::MyIdentity) = (2, 2) + Base.copy(J::MyIdentity) = J + LinearAlgebra.lmul!(::MyIdentity{T}, M::Array{T}) where {T} = M + @test MyIdentity{Float64}()[1,:] == [1.0, 0.0] +end + +@testset "issue #48911" begin + # testcase in the original issue + # test ldiv!(::QRPivoted, ::AbstractVector) + A = Complex{BigFloat}[1+im 1-im] + b = Complex{BigFloat}[3+im] + x = A\b + AF = Complex{Float64}[1+im 1-im] + bf = Complex{Float64}[3+im] + xf = AF\bf + @test x ≈ xf + + # test ldiv!(::QRPivoted, ::AbstractVector) + A = Complex{BigFloat}[1+im 2-2im 3+3im; 4-4im 5+5im 6-6im] + b = Complex{BigFloat}[1+im; 0] + x = A\b + AF = Complex{Float64}[1+im 2-2im 3+3im; 4-4im 5+5im 6-6im] + bf = Complex{Float64}[1+im; 0] + xf = AF\bf + @test x ≈ xf + + # test ldiv!(::QRPivoted, ::AbstractMatrix) + C = Complex{BigFloat}[1+im 2-2im 3+3im; 4-4im 5+5im 6-6im] + D = Complex{BigFloat}[1+im 1-im; 0 0] + x = C\D + CF = Complex{Float64}[1+im 2-2im 3+3im; 4-4im 5+5im 6-6im] + DF = Complex{Float64}[1+im 1-im; 0 0] + xf = CF\DF + @test x ≈ xf end end # module TestQR diff --git a/stdlib/LinearAlgebra/test/schur.jl b/stdlib/LinearAlgebra/test/schur.jl index d047ca12abc1f..c9a5d92dbdae8 100644 --- a/stdlib/LinearAlgebra/test/schur.jl +++ b/stdlib/LinearAlgebra/test/schur.jl @@ -202,4 +202,20 @@ end @test A' ≈ C ≈ E end +@testset "UpperHessenberg schur" begin + A = UpperHessenberg(rand(ComplexF64, 100, 100)) + B = Array(A) + fact1 = schur(A) + fact2 = schur(B) + @test fact1.values ≈ fact2.values + @test fact1.Z * fact1.T * fact1.Z' ≈ B + + A = UpperHessenberg(rand(Int32, 50, 50)) + B = Array(A) + fact1 = schur(A) + fact2 = schur(B) + @test fact1.values ≈ fact2.values + @test fact1.Z * fact1.T * fact1.Z' ≈ B +end + end # module TestSchur diff --git a/stdlib/LinearAlgebra/test/special.jl b/stdlib/LinearAlgebra/test/special.jl index ced2681ff0969..eaa297e05d957 100644 --- a/stdlib/LinearAlgebra/test/special.jl +++ b/stdlib/LinearAlgebra/test/special.jl @@ -104,6 +104,28 @@ Random.seed!(1) @test LowerTriangular(C) == LowerTriangular(Cdense) end end + + @testset "Matrix constructor for !isa(zero(T), T)" begin + # the following models JuMP.jl's VariableRef and AffExpr, resp. + struct TypeWithoutZero end + struct TypeWithZero end + Base.promote_rule(::Type{TypeWithoutZero}, ::Type{TypeWithZero}) = TypeWithZero + Base.convert(::Type{TypeWithZero}, ::TypeWithoutZero) = TypeWithZero() + Base.zero(::Type{<:Union{TypeWithoutZero, TypeWithZero}}) = TypeWithZero() + LinearAlgebra.symmetric(::TypeWithoutZero, ::Symbol) = TypeWithoutZero() + Base.transpose(::TypeWithoutZero) = TypeWithoutZero() + d = fill(TypeWithoutZero(), 3) + du = fill(TypeWithoutZero(), 2) + dl = fill(TypeWithoutZero(), 2) + D = Diagonal(d) + Bu = Bidiagonal(d, du, :U) + Bl = Bidiagonal(d, dl, :L) + Tri = Tridiagonal(dl, d, du) + Sym = SymTridiagonal(d, dl) + for M in (D, Bu, Bl, Tri, Sym) + @test Matrix(M) == zeros(TypeWithZero, 3, 3) + end + end end @testset "Binary ops among special types" begin @@ -145,7 +167,7 @@ end LoBi = Bidiagonal(rand(20,20), :L) Sym = SymTridiagonal(rand(20), rand(19)) Dense = rand(20, 20) - mats = [UpTri, LoTri, Diag, Tridiag, UpBi, LoBi, Sym, Dense] + mats = Any[UpTri, LoTri, Diag, Tridiag, UpBi, LoBi, Sym, Dense] for op in (+,-,*) for A in mats @@ -160,7 +182,7 @@ end diag = 1:5 offdiag = 1:4 uniformscalingmats = [UniformScaling(3), UniformScaling(1.0), UniformScaling(3//5), UniformScaling(ComplexF64(1.3, 3.5))] - mats = [Diagonal(diag), Bidiagonal(diag, offdiag, 'U'), Bidiagonal(diag, offdiag, 'L'), Tridiagonal(offdiag, diag, offdiag), SymTridiagonal(diag, offdiag)] + mats = Any[Diagonal(diag), Bidiagonal(diag, offdiag, 'U'), Bidiagonal(diag, offdiag, 'L'), Tridiagonal(offdiag, diag, offdiag), SymTridiagonal(diag, offdiag)] for T in [ComplexF64, Int64, Rational{Int64}, Float64] push!(mats, Diagonal(Vector{T}(diag))) push!(mats, Bidiagonal(Vector{T}(diag), Vector{T}(offdiag), 'U')) @@ -169,7 +191,7 @@ end push!(mats, SymTridiagonal(Vector{T}(diag), Vector{T}(offdiag))) end - for op in (+,*) # to do: fix when operation is - and the matrix has a range as the underlying representation and we get a step size of 0. + for op in (+,-,*) for A in mats for B in mats @test (op)(A, B) ≈ (op)(Matrix(A), Matrix(B)) ≈ Matrix((op)(A, B)) @@ -184,20 +206,49 @@ end end end end + diag = [randn(ComplexF64, 2, 2) for _ in 1:3] + odiag = [randn(ComplexF64, 2, 2) for _ in 1:2] + for A in (Diagonal(diag), + Bidiagonal(diag, odiag, :U), + Bidiagonal(diag, odiag, :L), + Tridiagonal(odiag, diag, odiag), + SymTridiagonal(diag, odiag)), B in uniformscalingmats + @test (A + B)::typeof(A) == (B + A)::typeof(A) + @test (A - B)::typeof(A) == ((A + (-B))::typeof(A)) + @test (B - A)::typeof(A) == ((B + (-A))::typeof(A)) + end end @testset "Triangular Types and QR" begin - for typ in [UpperTriangular,LowerTriangular,LinearAlgebra.UnitUpperTriangular,LinearAlgebra.UnitLowerTriangular] + for typ in (UpperTriangular, LowerTriangular, UnitUpperTriangular, UnitLowerTriangular) a = rand(n,n) atri = typ(a) + matri = Matrix(atri) b = rand(n,n) - qrb = qr(b, ColumnNorm()) - @test *(atri, adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' - @test rmul!(copy(atri), adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' - qrb = qr(b, NoPivot()) - @test *(atri, adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' - @test rmul!(copy(atri), adjoint(qrb.Q)) ≈ Matrix(atri) * qrb.Q' + for pivot in (ColumnNorm(), NoPivot()) + qrb = qr(b, pivot) + @test atri * qrb.Q ≈ matri * qrb.Q + @test atri * qrb.Q' ≈ matri * qrb.Q' + @test qrb.Q * atri ≈ qrb.Q * matri + @test qrb.Q' * atri ≈ qrb.Q' * matri + end + end +end + +@testset "Multiplication of Qs" begin + for pivot in (ColumnNorm(), NoPivot()), A in (rand(5, 3), rand(5, 5), rand(3, 5)) + Q = qr(A, pivot).Q + m = size(A, 1) + C = Matrix{Float64}(undef, (m, m)) + @test Q*Q ≈ (Q*I) * (Q*I) ≈ mul!(C, Q, Q) + @test size(Q*Q) == (m, m) + @test Q'Q ≈ (Q'*I) * (Q*I) ≈ mul!(C, Q', Q) + @test size(Q'Q) == (m, m) + @test Q*Q' ≈ (Q*I) * (Q'*I) ≈ mul!(C, Q, Q') + @test size(Q*Q') == (m, m) + @test Q'Q' ≈ (Q'*I) * (Q'*I) ≈ mul!(C, Q', Q') + @test size(Q'Q') == (m, m) end end @@ -216,8 +267,7 @@ end @test hvcat((1,1), specialmata, specialmatb) == hvcat((1,1), MA, MB) @test cat(specialmata, specialmatb; dims=(1,2)) == cat(MA, MB; dims=(1,2)) end - # Test concatenating pairwise combinations of special matrices with sparse matrices, - # dense matrices, or dense vectors + # Test concatenating pairwise combinations of special matrices with dense matrices or dense vectors densevec = fill(1., N) densemat = diagm(0 => densevec) for specialmat in specialmats @@ -241,7 +291,7 @@ end @testset "concatenations of annotated types" begin N = 4 # The tested annotation types - testfull = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) + testfull = Base.get_bool_env("JULIA_TESTFULL", false) utriannotations = (UpperTriangular, UnitUpperTriangular) ltriannotations = (LowerTriangular, UnitLowerTriangular) triannotations = (utriannotations..., ltriannotations...) @@ -321,7 +371,7 @@ using .Main.Furlongs Bl = Bidiagonal(rand(elty, 10), rand(elty, 9), 'L') T = Tridiagonal(rand(elty, 9),rand(elty, 10), rand(elty, 9)) S = SymTridiagonal(rand(elty, 10), rand(elty, 9)) - mats = [D, Bu, Bl, T, S] + mats = Any[D, Bu, Bl, T, S] for A in mats @test iszero(zero(A)) @test isone(one(A)) @@ -421,19 +471,18 @@ end end @testset "BiTriSym*Q' and Q'*BiTriSym" begin - dl = [1, 1, 1]; - d = [1, 1, 1, 1]; - Tri = Tridiagonal(dl, d, dl) + dl = [1, 1, 1] + d = [1, 1, 1, 1] + D = Diagonal(d) Bi = Bidiagonal(d, dl, :L) + Tri = Tridiagonal(dl, d, dl) Sym = SymTridiagonal(d, dl) F = qr(ones(4, 1)) A = F.Q' - @test Tri*A ≈ Matrix(Tri)*A - @test A*Tri ≈ A*Matrix(Tri) - @test Bi*A ≈ Matrix(Bi)*A - @test A*Bi ≈ A*Matrix(Bi) - @test Sym*A ≈ Matrix(Sym)*A - @test A*Sym ≈ A*Matrix(Sym) + for A in (F.Q, F.Q'), B in (D, Bi, Tri, Sym) + @test B*A ≈ Matrix(B)*A + @test A*B ≈ A*Matrix(B) + end end @testset "Ops on SymTridiagonal ev has the same length as dv" begin diff --git a/stdlib/LinearAlgebra/test/structuredbroadcast.jl b/stdlib/LinearAlgebra/test/structuredbroadcast.jl index 4aeca31a79a03..2ca1904b2ff2d 100644 --- a/stdlib/LinearAlgebra/test/structuredbroadcast.jl +++ b/stdlib/LinearAlgebra/test/structuredbroadcast.jl @@ -100,6 +100,8 @@ end @test_throws ArgumentError broadcast!(+, copy(T), T, A) == Tridiagonal(broadcast(*, T, A)) @test_throws ArgumentError broadcast!(+, copy(◣), ◣, A) == LowerTriangular(broadcast(*, ◣, A)) @test_throws ArgumentError broadcast!(+, copy(◥), ◥, A) == UpperTriangular(broadcast(*, ◥, A)) + @test_throws ArgumentError broadcast!(*, copy(◥), ◣, 2) + @test_throws ArgumentError broadcast!(*, copy(Bu), Bl, 2) end @testset "map[!] over combinations of structured matrices" begin @@ -200,7 +202,7 @@ end Bu2 = 2 .* Bl @test typeof(Bl2) <: Bidiagonal && Bl2.uplo == 'L' - # Example of Nested Brodacasts + # Example of Nested Broadcasts tmp = (1 .* 2) .* (Bidiagonal(1:3, 1:2, 'U') .* (3 .* 4)) .* (5 .* Bidiagonal(1:3, 1:2, 'L')) @test typeof(tmp) <: Tridiagonal diff --git a/stdlib/LinearAlgebra/test/svd.jl b/stdlib/LinearAlgebra/test/svd.jl index 8bd3edadc911d..7f2aad904a88f 100644 --- a/stdlib/LinearAlgebra/test/svd.jl +++ b/stdlib/LinearAlgebra/test/svd.jl @@ -127,8 +127,20 @@ aimg = randn(n,n)/2 gsvd = svd(b,c) @test gsvd.U*gsvd.D1*gsvd.R*gsvd.Q' ≈ b @test gsvd.V*gsvd.D2*gsvd.R*gsvd.Q' ≈ c + # AbstractMatrix svd + T = Tridiagonal(a) + asvd = svd(T, a) + @test asvd.U*asvd.D1*asvd.R*asvd.Q' ≈ T + @test asvd.V*asvd.D2*asvd.R*asvd.Q' ≈ a + @test all(≈(1), svdvals(T, T)) end end + @testset "singular value decomposition of AbstractMatrix" begin + A = Tridiagonal(aa) + F = svd(A) + @test Matrix(F) ≈ A + @test svdvals(A) ≈ F.S + end @testset "singular value decomposition of Hermitian/real-Symmetric" begin for T in (eltya <: Real ? (Symmetric, Hermitian) : (Hermitian,)) usv = svd(T(asym)) diff --git a/stdlib/LinearAlgebra/test/symmetric.jl b/stdlib/LinearAlgebra/test/symmetric.jl index 47a36df5e7883..04621c4b49e86 100644 --- a/stdlib/LinearAlgebra/test/symmetric.jl +++ b/stdlib/LinearAlgebra/test/symmetric.jl @@ -76,8 +76,16 @@ end end @testset "diag" begin D = Diagonal(x) - @test diag(Symmetric(D, :U))::Vector == x - @test diag(Hermitian(D, :U))::Vector == real(x) + DM = Matrix(D) + B = diagm(-1 => x, 1 => x) + for uplo in (:U, :L) + @test diag(Symmetric(D, uplo))::Vector == x + @test diag(Hermitian(D, uplo))::Vector == real(x) + @test isdiag(Symmetric(DM, uplo)) + @test isdiag(Hermitian(DM, uplo)) + @test !isdiag(Symmetric(B, uplo)) + @test !isdiag(Hermitian(B, uplo)) + end end @testset "similar" begin @test isa(similar(Symmetric(asym)), Symmetric{eltya}) @@ -252,6 +260,14 @@ end end end end + if eltya <: AbstractFloat + @testset "inv should error with NaNs/Infs" begin + h = Hermitian(fill(eltya(NaN), 2, 2)) + @test_throws ArgumentError inv(h) + s = Symmetric(fill(eltya(NaN), 2, 2)) + @test_throws ArgumentError inv(s) + end + end end # Revisit when implemented in julia @@ -352,6 +368,9 @@ end C = zeros(eltya,n,n) @test Hermitian(aherm) * a ≈ aherm * a @test a * Hermitian(aherm) ≈ a * aherm + # rectangular multiplication + @test [a; a] * Hermitian(aherm) ≈ [a; a] * aherm + @test Hermitian(aherm) * [a a] ≈ aherm * [a a] @test Hermitian(aherm) * Hermitian(aherm) ≈ aherm*aherm @test_throws DimensionMismatch Hermitian(aherm) * Vector{eltya}(undef, n+1) LinearAlgebra.mul!(C,a,Hermitian(aherm)) @@ -360,6 +379,9 @@ end @test Symmetric(asym) * Symmetric(asym) ≈ asym*asym @test Symmetric(asym) * a ≈ asym * a @test a * Symmetric(asym) ≈ a * asym + # rectangular multiplication + @test Symmetric(asym) * [a a] ≈ asym * [a a] + @test [a; a] * Symmetric(asym) ≈ [a; a] * asym @test_throws DimensionMismatch Symmetric(asym) * Vector{eltya}(undef, n+1) LinearAlgebra.mul!(C,a,Symmetric(asym)) @test C ≈ a*asym @@ -380,6 +402,10 @@ end @test Hermitian(aherm)\b ≈ aherm\b @test Symmetric(asym)\x ≈ asym\x @test Symmetric(asym)\b ≈ asym\b + @test Hermitian(Diagonal(aherm))\x ≈ Diagonal(aherm)\x + @test Hermitian(Matrix(Diagonal(aherm)))\b ≈ Diagonal(aherm)\b + @test Symmetric(Diagonal(asym))\x ≈ Diagonal(asym)\x + @test Symmetric(Matrix(Diagonal(asym)))\b ≈ Diagonal(asym)\b end end @testset "generalized dot product" begin @@ -387,6 +413,8 @@ end @test dot(x, Hermitian(aherm, uplo), y) ≈ dot(x, Hermitian(aherm, uplo)*y) ≈ dot(x, Matrix(Hermitian(aherm, uplo)), y) @test dot(x, Hermitian(aherm, uplo), x) ≈ dot(x, Hermitian(aherm, uplo)*x) ≈ dot(x, Matrix(Hermitian(aherm, uplo)), x) end + @test dot(x, Hermitian(Diagonal(a)), y) ≈ dot(x, Hermitian(Diagonal(a))*y) ≈ dot(x, Matrix(Hermitian(Diagonal(a))), y) + @test dot(x, Hermitian(Diagonal(a)), x) ≈ dot(x, Hermitian(Diagonal(a))*x) ≈ dot(x, Matrix(Hermitian(Diagonal(a))), x) if eltya <: Real for uplo in (:U, :L) @test dot(x, Symmetric(aherm, uplo), y) ≈ dot(x, Symmetric(aherm, uplo)*y) ≈ dot(x, Matrix(Symmetric(aherm, uplo)), y) @@ -574,13 +602,13 @@ end # Hermitian A = Hermitian(fill(1.0+0im, 2, 2), uplo) @test fill!(A, 2) == fill(2, 2, 2) - @test A.data == (uplo == :U ? [2 2; 1.0+0im 2] : [2 1.0+0im; 2 2]) + @test A.data == (uplo === :U ? [2 2; 1.0+0im 2] : [2 1.0+0im; 2 2]) @test_throws ArgumentError fill!(A, 2+im) # Symmetric A = Symmetric(fill(1.0+im, 2, 2), uplo) @test fill!(A, 2) == fill(2, 2, 2) - @test A.data == (uplo == :U ? [2 2; 1.0+im 2] : [2 1.0+im; 2 2]) + @test A.data == (uplo === :U ? [2 2; 1.0+im 2] : [2 1.0+im; 2 2]) end end @@ -762,4 +790,38 @@ end end end +@testset "hermitian part" begin + for T in [Float32, Complex{Float32}, Int32, Rational{Int32}, + Complex{Int32}, Complex{Rational{Int32}}] + f, f!, t = hermitianpart, hermitianpart!, T <: Real ? transpose : adjoint + X = T[1 2 3; 4 5 6; 7 8 9] + T <: Complex && (X .+= im .* X) + Xc = copy(X) + Y = (X + t(X)) / 2 + U = f(X) + L = f(X, :L) + @test U isa Hermitian + @test L isa Hermitian + @test U.uplo == 'U' + @test L.uplo == 'L' + @test U == L == Y + if T <: AbstractFloat || real(T) <: AbstractFloat + HU = f!(X) + @test HU == Y + @test triu(X) == triu(Y) + HL = f!(Xc, :L) + @test HL == Y + @test tril(Xc) == tril(Y) + end + end + @test_throws DimensionMismatch hermitianpart(ones(1,2)) + for T in (Float64, ComplexF64), uplo in (:U, :L) + A = [randn(T, 2, 2) for _ in 1:2, _ in 1:2] + Aherm = hermitianpart(A, uplo) + @test Aherm == Aherm.data == (A + A')/2 + @test Aherm isa Hermitian + @test Aherm.uplo == LinearAlgebra.char_uplo(uplo) + end +end + end # module TestSymmetric diff --git a/stdlib/LinearAlgebra/test/testgroups b/stdlib/LinearAlgebra/test/testgroups index de082d8e7dce0..e281203bf3fa3 100644 --- a/stdlib/LinearAlgebra/test/testgroups +++ b/stdlib/LinearAlgebra/test/testgroups @@ -1,28 +1,29 @@ triangular -qr -dense -matmul -schur -special -eigen -bunchkaufman -svd -lapack -tridiag +addmul bidiag +matmul +dense +symmetric diagonal +special +qr cholesky +blas lu -symmetric -generic uniformscaling -lq +structuredbroadcast hessenberg -blas +svd +eigen +tridiag +lapack +lq adjtrans -pinv +generic +schur +bunchkaufman givens -structuredbroadcast -addmul -ldlt +pinv factorization +abstractq +ldlt diff --git a/stdlib/LinearAlgebra/test/triangular.jl b/stdlib/LinearAlgebra/test/triangular.jl index dfb4d7c8a0b95..78fc2d5e0e74c 100644 --- a/stdlib/LinearAlgebra/test/triangular.jl +++ b/stdlib/LinearAlgebra/test/triangular.jl @@ -26,7 +26,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo (UnitLowerTriangular, :L)) # 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'))) + 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'))) @test t1(A1) === A1 @test t1{elty1}(A1) === A1 # test the ctor works for AbstractMatrix @@ -77,7 +77,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo A1c = copy(A1) for i = 1:size(A1, 1) for j = 1:size(A1, 2) - if uplo1 == :U + if uplo1 === :U if i > j A1c[i,j] = 0 @test_throws ArgumentError A1c[i,j] = 1 @@ -104,7 +104,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo end # istril/istriu - if uplo1 == :L + if uplo1 === :L @test istril(A1) @test !istriu(A1) @test istriu(A1') @@ -119,9 +119,19 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo @test !istriu(A1') @test !istriu(transpose(A1)) end + M = copy(parent(A1)) + for trans in (adjoint, transpose), k in -1:1 + triu!(M, k) + @test istril(trans(M), -k) == istril(copy(trans(M)), -k) == true + end + M = copy(parent(A1)) + for trans in (adjoint, transpose), k in 1:-1:-1 + tril!(M, k) + @test istriu(trans(M), -k) == istriu(copy(trans(M)), -k) == true + end #tril/triu - if uplo1 == :L + 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))) @@ -169,6 +179,9 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo # diag @test diag(A1) == diag(Matrix(A1)) + # tr + @test tr(A1)::elty1 == tr(Matrix(A1)) + # real @test real(A1) == real(Matrix(A1)) @test imag(A1) == imag(Matrix(A1)) @@ -248,11 +261,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo for eltyb in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}) b1 = convert(Vector{eltyb}, (elty1 <: Complex ? real(A1) : A1)*fill(1., n)) b2 = convert(Vector{eltyb}, (elty1 <: Complex ? real(A1) : A1)*randn(n)) - if elty1 in (BigFloat, Complex{BigFloat}) || eltyb in (BigFloat, Complex{BigFloat}) - @test dot(b1, A1, b2) ≈ dot(A1'b1, b2) atol=sqrt(max(eps(real(float(one(elty1)))),eps(real(float(one(eltyb))))))*n*n - else - @test dot(b1, A1, b2) ≈ dot(A1'b1, b2) atol=sqrt(max(eps(real(float(one(elty1)))),eps(real(float(one(eltyb))))))*n*n - end + @test dot(b1, A1, b2) ≈ dot(A1'b1, b2) atol=sqrt(max(eps(real(float(one(elty1)))),eps(real(float(one(eltyb))))))*n*n end # Binary operations @@ -319,7 +328,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo debug && println("elty1: $elty1, A1: $t1, elty2: $elty2") - 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'))) + 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'))) # Convert if elty1 <: Real && !(elty2 <: Integer) @@ -332,7 +341,7 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo @test A1 + A2 == Matrix(A1) + Matrix(A2) @test A1 - A2 == Matrix(A1) - Matrix(A2) - # Triangular-Triangualar multiplication and division + # 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)) @@ -348,21 +357,29 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo if t1 === UnitUpperTriangular && t2 === UnitUpperTriangular @test A1*A2 isa UnitUpperTriangular @test A1/A2 isa UnitUpperTriangular + elty1 == Int && elty2 == Int && @test eltype(A1/A2) == Int @test A1\A2 isa UnitUpperTriangular + elty1 == Int && elty2 == Int && @test eltype(A1\A2) == Int else @test A1*A2 isa UpperTriangular @test A1/A2 isa UpperTriangular + elty1 == Int && elty2 == Int && t2 === UnitUpperTriangular && @test eltype(A1/A2) == Int @test A1\A2 isa UpperTriangular + elty1 == Int && elty2 == Int && t1 === UnitUpperTriangular && @test eltype(A1\A2) == Int end elseif uplo1 === :L && uplo2 === :L if t1 === UnitLowerTriangular && t2 === UnitLowerTriangular @test A1*A2 isa UnitLowerTriangular @test A1/A2 isa UnitLowerTriangular + elty1 == Int && elty2 == Int && @test eltype(A1/A2) == Int @test A1\A2 isa UnitLowerTriangular + elty1 == Int && elty2 == Int && @test eltype(A1\A2) == Int else @test A1*A2 isa LowerTriangular @test A1/A2 isa LowerTriangular + elty1 == Int && elty2 == Int && t2 === UnitLowerTriangular && @test eltype(A1/A2) == Int @test A1\A2 isa LowerTriangular + elty1 == Int && elty2 == Int && t1 === UnitLowerTriangular && @test eltype(A1\A2) == Int end end offsizeA = Matrix{Float64}(I, n+1, n+1) @@ -399,17 +416,15 @@ for elty1 in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFlo debug && println("elty1: $elty1, A1: $t1, B: $eltyB") - if !(eltyB in (BigFloat, Complex{BigFloat})) # rand does not support BigFloat and Complex{BigFloat} as of Dec 2015 - Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) - @test lmul!(Tri,copy(A1)) ≈ Tri*Matrix(A1) - 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, copy(A1)) - @test C ≈ Tri*Matrix(A1) - Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) - mul!(C, copy(A1), Tri) - @test C ≈ Matrix(A1)*Tri - end + Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) + @test lmul!(Tri,copy(A1)) ≈ Tri*Matrix(A1) + 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, copy(A1)) + @test C ≈ Tri*Matrix(A1) + Tri = Tridiagonal(rand(eltyB,n-1),rand(eltyB,n),rand(eltyB,n-1)) + mul!(C, copy(A1), Tri) + @test C ≈ Matrix(A1)*Tri # Triangular-dense Matrix/vector multiplication @test A1*B[:,1] ≈ Matrix(A1)*B[:,1] @@ -686,8 +701,23 @@ isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "te using .Main.Furlongs LinearAlgebra.sylvester(a::Furlong,b::Furlong,c::Furlong) = -c / (a + b) -let A = UpperTriangular([Furlong(1) Furlong(4); Furlong(0) Furlong(1)]) - @test sqrt(A) == Furlong{1//2}.(UpperTriangular([1 2; 0 1])) +@testset "dimensional correctness" begin + A = UpperTriangular([Furlong(1) Furlong(4); Furlong(0) Furlong(1)]) + @test sqrt(A)::UpperTriangular == Furlong{1//2}.(UpperTriangular([1 2; 0 1])) + @test inv(A)::UpperTriangular == Furlong{-1}.(UpperTriangular([1 -4; 0 1])) + B = UnitUpperTriangular([Furlong(1) Furlong(4); Furlong(0) Furlong(1)]) + @test sqrt(B)::UnitUpperTriangular == Furlong{1//2}.(UpperTriangular([1 2; 0 1])) + @test inv(B)::UnitUpperTriangular == Furlong{-1}.(UpperTriangular([1 -4; 0 1])) + b = [Furlong(5), Furlong(8)] + @test (A \ b)::Vector{<:Furlong{0}} == (B \ b)::Vector{<:Furlong{0}} == Furlong{0}.([-27, 8]) + C = LowerTriangular([Furlong(1) Furlong(0); Furlong(4) Furlong(1)]) + @test sqrt(C)::LowerTriangular == Furlong{1//2}.(LowerTriangular([1 0; 2 1])) + @test inv(C)::LowerTriangular == Furlong{-1}.(LowerTriangular([1 0; -4 1])) + D = UnitLowerTriangular([Furlong(1) Furlong(0); Furlong(4) Furlong(1)]) + @test sqrt(D)::UnitLowerTriangular == Furlong{1//2}.(UnitLowerTriangular([1 0; 2 1])) + @test inv(D)::UnitLowerTriangular == Furlong{-1}.(UnitLowerTriangular([1 0; -4 1])) + b = [Furlong(5), Furlong(8)] + @test (C \ b)::Vector{<:Furlong{0}} == (D \ b)::Vector{<:Furlong{0}} == Furlong{0}.([5, -12]) end isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) @@ -825,4 +855,15 @@ end test_one_oneunit_triangular(c) end +@testset "LowerTriangular(Diagonal(...)) and friends (issue #28869)" begin + for elty in (Float32, Float64, BigFloat, ComplexF32, ComplexF64, Complex{BigFloat}, Int) + V = elty ≡ Int ? rand(1:10, 5) : elty.(randn(5)) + D = Diagonal(V) + for dty in (UpperTriangular, LowerTriangular) + A = dty(D) + @test A * A' == D * D' + end + end +end + end # module TestTriangular diff --git a/stdlib/LinearAlgebra/test/trickyarithmetic.jl b/stdlib/LinearAlgebra/test/trickyarithmetic.jl index c5faf57acd857..ad04ac89c2761 100644 --- a/stdlib/LinearAlgebra/test/trickyarithmetic.jl +++ b/stdlib/LinearAlgebra/test/trickyarithmetic.jl @@ -8,12 +8,15 @@ module TrickyArithmetic Base.convert(::Type{A}, i::Int) = A(i) Base.zero(::Union{A, Type{A}}) = A(0) Base.one(::Union{A, Type{A}}) = A(1) + Base.isfinite(a::A) = isfinite(a.x) struct B x::Int end struct C x::Int end + Base.isfinite(b::B) = isfinite(b.x) + Base.isfinite(c::C) = isfinite(c.x) C(a::A) = C(a.x) Base.zero(::Union{C, Type{C}}) = C(0) Base.one(::Union{C, Type{C}}) = C(1) @@ -40,6 +43,7 @@ module TrickyArithmetic Base.:(*)(a::Union{A,B,C}, b::D) = b * a Base.inv(a::Union{A,B,C}) = A(1) / a Base.inv(a::D) = a.d / a.n + Base.isfinite(a::D) = isfinite(a.n) && isfinite(a.d) Base.:(/)(a::Union{A,B,C}, b::Union{A,B,C}) = D(a, b) Base.:(/)(a::D, b::Union{A,B,C}) = a.n / (a.d*b) Base.:(/)(a::Union{A,B,C,D}, b::D) = a * inv(b) diff --git a/stdlib/LinearAlgebra/test/tridiag.jl b/stdlib/LinearAlgebra/test/tridiag.jl index ecdf6b416baa5..e45fc9a65dba0 100644 --- a/stdlib/LinearAlgebra/test/tridiag.jl +++ b/stdlib/LinearAlgebra/test/tridiag.jl @@ -9,6 +9,12 @@ 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, :InfiniteArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "InfiniteArrays.jl")) +using .Main.InfiniteArrays + +isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +using .Main.FillArrays + include("testutils.jl") # test_approx_eq_modphase #Test equivalence of eigenvectors/singular vectors taking into account possible phase (sign) differences @@ -71,11 +77,13 @@ end @test ST == Matrix(ST) @test ST.dv === x @test ST.ev === y + @test typeof(ST)(ST) === ST TT = (Tridiagonal(y, x, y))::Tridiagonal{elty, typeof(x)} @test TT == Matrix(TT) @test TT.dl === y @test TT.d === x @test TT.du === y + @test typeof(TT)(TT) === TT end ST = SymTridiagonal{elty}([1,2,3,4], [1,2,3]) @test eltype(ST) == elty @@ -261,6 +269,13 @@ end @test (@inferred diag(GA))::typeof(GenericArray(d)) == GenericArray(d) @test (@inferred diag(GA, -1))::typeof(GenericArray(d)) == GenericArray(dl) end + @testset "trace" begin + if real(elty) <: Integer + @test tr(A) == tr(fA) + else + @test tr(A) ≈ tr(fA) rtol=2eps(real(elty)) + end + end @testset "Idempotent tests" begin for func in (conj, transpose, adjoint) @test func(func(A)) == A @@ -434,7 +449,11 @@ end @testset "generalized dot" begin x = fill(convert(elty, 1), n) y = fill(convert(elty, 1), n) - @test dot(x, A, y) ≈ dot(A'x, y) + @test dot(x, A, y) ≈ dot(A'x, y) ≈ dot(x, A*y) + @test dot([1], SymTridiagonal([1], Int[]), [1]) == 1 + @test dot([1], Tridiagonal(Int[], [1], Int[]), [1]) == 1 + @test dot(Int[], SymTridiagonal(Int[], Int[]), Int[]) === 0 + @test dot(Int[], Tridiagonal(Int[], Int[], Int[]), Int[]) === 0 end end end @@ -725,4 +744,38 @@ using .Main.SizedArrays @test S !== Tridiagonal(diag(Sdense, 1), diag(Sdense), diag(Sdense, 1)) !== S end end + +@testset "copyto! with UniformScaling" begin + @testset "Tridiagonal" begin + @testset "Fill" begin + for len in (4, InfiniteArrays.Infinity()) + d = FillArrays.Fill(1, len) + ud = FillArrays.Fill(0, len-1) + T = Tridiagonal(ud, d, ud) + @test copyto!(T, I) === T + end + end + T = Tridiagonal(fill(3, 3), fill(2, 4), fill(3, 3)) + copyto!(T, I) + @test all(isone, diag(T)) + @test all(iszero, diag(T, 1)) + @test all(iszero, diag(T, -1)) + end + @testset "SymTridiagonal" begin + @testset "Fill" begin + for len in (4, InfiniteArrays.Infinity()) + d = FillArrays.Fill(1, len) + ud = FillArrays.Fill(0, len-1) + ST = SymTridiagonal(d, ud) + @test copyto!(ST, I) === ST + end + end + ST = SymTridiagonal(fill(2, 4), fill(3, 3)) + copyto!(ST, I) + @test all(isone, diag(ST)) + @test all(iszero, diag(ST, 1)) + @test all(iszero, diag(ST, -1)) + end +end + end # module TestTridiagonal diff --git a/stdlib/Logging/docs/src/index.md b/stdlib/Logging/docs/src/index.md index 7a6fbbbdd2081..9a269ee54571b 100644 --- a/stdlib/Logging/docs/src/index.md +++ b/stdlib/Logging/docs/src/index.md @@ -182,8 +182,8 @@ pattern match against the log event stream. Message filtering can be influenced through the `JULIA_DEBUG` environment variable, and serves as an easy way to enable debug logging for a file or -module. For example, loading julia with `JULIA_DEBUG=loading` will activate -`@debug` log messages in `loading.jl`: +module. Loading julia with `JULIA_DEBUG=loading` will activate +`@debug` log messages in `loading.jl`. For example, in Linux shells: ``` $ JULIA_DEBUG=loading julia -e 'using OhMyREPL' @@ -195,6 +195,9 @@ $ JULIA_DEBUG=loading julia -e 'using OhMyREPL' ... ``` +On windows, the same can be achieved in `CMD` via first running `set JULIA_DEBUG="loading"` and in `Powershell` via +`$env:JULIA_DEBUG="loading"`. + Similarly, the environment variable can be used to enable debug logging of modules, such as `Pkg`, or module roots (see [`Base.moduleroot`](@ref)). To enable all debug logging, use the special value `all`. diff --git a/stdlib/Logging/src/ConsoleLogger.jl b/stdlib/Logging/src/ConsoleLogger.jl index 4e32b6b71f656..747f8a2b22966 100644 --- a/stdlib/Logging/src/ConsoleLogger.jl +++ b/stdlib/Logging/src/ConsoleLogger.jl @@ -73,7 +73,7 @@ function default_metafmt(level::LogLevel, _module, group, id, file, line) _module !== nothing && (suffix *= string(_module)::String) if file !== nothing _module !== nothing && (suffix *= " ") - suffix *= Base.contractuser(file)::String + suffix *= contractuser(file)::String if line !== nothing suffix *= ":$(isa(line, UnitRange) ? "$(first(line))-$(last(line))" : line)" end @@ -116,9 +116,9 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module # 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(string(message)::String), '\n')] - stream = logger.stream - if !isopen(stream) + msglines = [(indent=0, msg=l) for l in split(chomp(convert(String, string(message))::String), '\n')] + stream::IO = logger.stream + if !(isopen(stream)::Bool) stream = stderr end dsize = displaysize(stream)::Tuple{Int,Int} diff --git a/stdlib/MPFR_jll/Project.toml b/stdlib/MPFR_jll/Project.toml index 22aa30d20511b..39f99815832eb 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.1.1+1" +version = "4.2.0+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 5b2dbd1e84b24..c184a9801102f 100644 --- a/stdlib/MPFR_jll/src/MPFR_jll.jl +++ b/stdlib/MPFR_jll/src/MPFR_jll.jl @@ -13,9 +13,9 @@ export libmpfr # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libmpfr_handle = C_NULL -libmpfr_path = "" +artifact_dir::String = "" +libmpfr_handle::Ptr{Cvoid} = C_NULL +libmpfr_path::String = "" if Sys.iswindows() const libmpfr = "libmpfr-6.dll" diff --git a/stdlib/MPFR_jll/test/runtests.jl b/stdlib/MPFR_jll/test/runtests.jl index 68bb6d3ec40e4..81b6e06ed7b49 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.1.0" + @test vn == v"4.2.0" end diff --git a/stdlib/Makefile b/stdlib/Makefile index 9c18fa261b985..e42061d593905 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -6,20 +6,22 @@ SRCCACHE := $(abspath $(SRCDIR)/srccache) BUILDDIR := . include $(JULIAHOME)/Make.inc -include $(JULIAHOME)/deps/Versions.make include $(JULIAHOME)/deps/tools/common.mk include $(JULIAHOME)/deps/tools/stdlib-external.mk +# include all `*.version` files, import `LIBNAME_JLL_NAME` and `LIBNAME_JLL_VER` +# Note: Some deps do not have a `_jll` pkg: [libwhich, patchelf, utf8proc] +include $(JULIAHOME)/deps/*.version -VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) -$(build_datarootdir)/julia/stdlib/$(VERSDIR): - mkdir -p $@ +VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) +DIRS := $(build_datarootdir)/julia/stdlib/$(VERSDIR) $(build_prefix)/manifest/$(VERSDIR) +$(foreach dir,$(DIRS),$(eval $(call dir_target,$(dir)))) JLLS = DSFMT GMP CURL LIBGIT2 LLVM LIBSSH2 LIBUV MBEDTLS MPFR NGHTTP2 \ BLASTRAMPOLINE OPENBLAS OPENLIBM P7ZIP PCRE LIBSUITESPARSE ZLIB \ - LLVMUNWIND CSL UNWIND + LLVMUNWIND CSL UNWIND LLD -# Initialize this with JLLs that aren't in deps/Versions.make +# Initialize this with JLLs that aren't in "deps/$(LibName).version" JLL_NAMES := MozillaCACerts_jll get-MozillaCACerts_jll: install-MozillaCACerts_jll: @@ -38,16 +40,21 @@ endef $(foreach jll,$(JLLS),$(eval $(call download-artifacts-toml,$(jll)))) -STDLIBS = Artifacts Base64 CRC32c Dates DelimitedFiles Distributed FileWatching \ +STDLIBS = Artifacts Base64 CRC32c Dates Distributed FileWatching \ Future InteractiveUtils LazyArtifacts Libdl LibGit2 LinearAlgebra Logging \ - Markdown Mmap Printf Profile Random REPL Serialization SHA \ - SharedArrays Sockets SparseArrays SuiteSparse Test TOML Unicode UUIDs \ + Markdown Mmap Printf Profile Random REPL Serialization \ + SharedArrays Sockets Test TOML Unicode UUIDs \ $(JLL_NAMES) -STDLIBS_EXT = Pkg Statistics LibCURL Downloads ArgTools Tar NetworkOptions SuiteSparse SparseArrays SHA +STDLIBS_EXT = Pkg Statistics LibCURL DelimitedFiles Downloads ArgTools Tar NetworkOptions SuiteSparse SparseArrays SHA $(foreach module, $(STDLIBS_EXT), $(eval $(call stdlib-external,$(module),$(shell echo $(module) | tr a-z A-Z)))) +ifneq ($(filter $(STDLIBS),$(STDLIBS_EXT)),) +$(error ERROR duplicated STDLIBS in list) +endif + + # Generate symlinks to all stdlibs at usr/share/julia/stdlib/vX.Y/ $(foreach module, $(STDLIBS), $(eval $(call symlink_target,$$(JULIAHOME)/stdlib/$(module),$$(build_datarootdir)/julia/stdlib/$$(VERSDIR),$(module)))) @@ -65,5 +72,5 @@ clean: $(addprefix clean-, $(STDLIBS_EXT)) $(CLEAN_TARGETS) extstdlibclean distclean: $(addprefix distclean-, $(STDLIBS_EXT)) clean checksumall: $(addprefix checksum-, $(STDLIBS_EXT)) -DEP_LIBS_STAGED_ALL := $(STDLIBS_EXT) +DEP_LIBS_STAGED_ALL := $(addprefix $(VERSDIR)/,$(STDLIBS_EXT)) include $(JULIAHOME)/deps/tools/uninstallers.mk diff --git a/stdlib/Markdown/src/Common/Common.jl b/stdlib/Markdown/src/Common/Common.jl index 0891765b277ba..3036f2b4b730b 100644 --- a/stdlib/Markdown/src/Common/Common.jl +++ b/stdlib/Markdown/src/Common/Common.jl @@ -8,4 +8,3 @@ include("inline.jl") linebreak, escapes, inline_code, asterisk_bold, underscore_bold, asterisk_italic, underscore_italic, image, footnote_link, link, autolink] - diff --git a/stdlib/Markdown/src/Common/block.jl b/stdlib/Markdown/src/Common/block.jl index 366a7283f0be5..bd184b60c40fa 100644 --- a/stdlib/Markdown/src/Common/block.jl +++ b/stdlib/Markdown/src/Common/block.jl @@ -61,7 +61,7 @@ function hashheader(stream::IO, md::MD) if c != '\n' # Empty header h = strip(readline(stream)) - h = match(r"(.*?)( +#+)?$", h).captures[1] + h = (match(r"(.*?)( +#+)?$", h)::AbstractMatch).captures[1] buffer = IOBuffer() print(buffer, h) push!(md.content, Header(parseinline(seek(buffer, 0), md), level)) @@ -136,7 +136,7 @@ function footnote(stream::IO, block::MD) if isempty(str) return false else - ref = match(regex, str).captures[1] + ref = (match(regex, str)::AbstractMatch).captures[1] buffer = IOBuffer() write(buffer, readline(stream, keep=true)) while !eof(stream) @@ -211,11 +211,11 @@ function admonition(stream::IO, block::MD) titled = r"^([a-z]+) \"(.*)\"$", # !!! "" line = strip(readline(stream)) if occursin(untitled, line) - m = match(untitled, line) + m = match(untitled, line)::AbstractMatch # When no title is provided we use CATEGORY_NAME, capitalising it. m.captures[1], uppercasefirst(m.captures[1]) elseif occursin(titled, line) - m = match(titled, line) + m = match(titled, line)::AbstractMatch # To have a blank TITLE provide an explicit empty string as TITLE. m.captures[1], m.captures[2] else @@ -274,7 +274,7 @@ function list(stream::IO, block::MD) elseif occursin(r"^ {0,3}\d+(\.|\))( |$)", bullet) # An ordered list. Either with `1. ` or `1) ` style numbering. r = occursin(".", bullet) ? r"^ {0,3}(\d+)\.( |$)" : r"^ {0,3}(\d+)\)( |$)" - Base.parse(Int, match(r, bullet).captures[1]), r + Base.parse(Int, (match(r, bullet)::AbstractMatch).captures[1]), r else # Failed to match any bullets. This branch shouldn't actually be needed # since the `NUM_OR_BULLETS` regex should cover this, but we include it diff --git a/stdlib/Markdown/src/Common/inline.jl b/stdlib/Markdown/src/Common/inline.jl index fd5134481e113..fda716a10fae7 100644 --- a/stdlib/Markdown/src/Common/inline.jl +++ b/stdlib/Markdown/src/Common/inline.jl @@ -112,7 +112,7 @@ function footnote_link(stream::IO, md::MD) if isempty(str) return else - ref = match(regex, str).captures[1] + ref = (match(regex, str)::AbstractMatch).captures[1] return Footnote(ref, nothing) end end diff --git a/stdlib/Markdown/src/GitHub/GitHub.jl b/stdlib/Markdown/src/GitHub/GitHub.jl index 493e01b085258..61807d267511d 100644 --- a/stdlib/Markdown/src/GitHub/GitHub.jl +++ b/stdlib/Markdown/src/GitHub/GitHub.jl @@ -62,4 +62,3 @@ end linebreak, escapes, en_dash, inline_code, asterisk_bold, underscore_bold, asterisk_italic, underscore_italic, image, footnote_link, link, autolink] - diff --git a/stdlib/Markdown/src/Julia/Julia.jl b/stdlib/Markdown/src/Julia/Julia.jl index 7ee049970277a..3797c5a8a0f79 100644 --- a/stdlib/Markdown/src/Julia/Julia.jl +++ b/stdlib/Markdown/src/Julia/Julia.jl @@ -12,4 +12,3 @@ include("interp.jl") linebreak, escapes, tex, interp, en_dash, inline_code, asterisk_bold, underscore_bold, asterisk_italic, underscore_italic, image, footnote_link, link, autolink] - diff --git a/stdlib/Markdown/src/parse/util.jl b/stdlib/Markdown/src/parse/util.jl index 7be845c96a9fc..aabfcbb3ddc62 100644 --- a/stdlib/Markdown/src/parse/util.jl +++ b/stdlib/Markdown/src/parse/util.jl @@ -36,7 +36,7 @@ function skipblank(io::IO) end """ -Returns true if the line contains only (and, unless allowempty, +Return true if the line contains only (and, unless allowempty, at least one of) the characters given. """ function linecontains(io::IO, chars; allow_whitespace = true, diff --git a/stdlib/Markdown/src/render/terminal/formatting.jl b/stdlib/Markdown/src/render/terminal/formatting.jl index 87022124b9c8a..a031de4d9ad82 100644 --- a/stdlib/Markdown/src/render/terminal/formatting.jl +++ b/stdlib/Markdown/src/render/terminal/formatting.jl @@ -3,7 +3,7 @@ # Wrapping function ansi_length(s) - replace(s, r"\e\[[0-9]+m" => "") |> length + replace(s, r"\e\[[0-9]+m" => "") |> textwidth end words(s) = split(s, " ") @@ -17,6 +17,23 @@ function wrapped_line(io::IO, s::AbstractString, width, i) word_length == 0 && continue if isempty(lines) || i + word_length + 1 > width i = word_length + if length(lines) > 0 + last_line = lines[end] + maybe_underline = findlast(Base.text_colors[:underline], last_line) + if !isnothing(maybe_underline) + # disable underline style at end of line if not already disabled. + maybe_disable_underline = max( + last(something(findlast(Base.disable_text_style[:underline], last_line), -1)), + last(something(findlast(Base.text_colors[:normal], last_line), -1)), + ) + + if maybe_disable_underline < 0 || maybe_disable_underline < last(maybe_underline) + + lines[end] = last_line * Base.disable_text_style[:underline] + word = Base.text_colors[:underline] * word + end + end + end push!(lines, word) else i += word_length + 1 diff --git a/stdlib/Markdown/src/render/terminal/render.jl b/stdlib/Markdown/src/render/terminal/render.jl index 3fd274aee2a2e..20b1ef6d041fc 100644 --- a/stdlib/Markdown/src/render/terminal/render.jl +++ b/stdlib/Markdown/src/render/terminal/render.jl @@ -81,14 +81,16 @@ end function _term_header(io::IO, md, char, columns) text = terminline_string(io, md.text) with_output_color(:bold, io) do io - print(io, ' '^margin) + pre = ' '^margin + print(io, pre) line_no, lastline_width = print_wrapped(io, text, - width=columns - 4margin; pre=" ") - line_width = min(1 + lastline_width, columns) + width=columns - 4margin; pre) + line_width = min(lastline_width, columns) if line_no > 1 - line_width = max(line_width, div(columns, 3)) + line_width = max(line_width, div(columns, 3)+length(pre)) end - char != ' ' && print(io, '\n', ' '^(margin), char^line_width) + header_width = max(0, line_width-length(pre)) + char != ' ' && header_width > 0 && print(io, '\n', ' '^(margin), char^header_width) end end diff --git a/stdlib/Markdown/test/runtests.jl b/stdlib/Markdown/test/runtests.jl index dfe80430a00d6..19d821a0254d7 100644 --- a/stdlib/Markdown/test/runtests.jl +++ b/stdlib/Markdown/test/runtests.jl @@ -376,7 +376,8 @@ table = md""" # mime output let out = @test sprint(show, "text/plain", book) == - " Title\n ≡≡≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n ===================\n\n Some bolded\n\n • list1\n\n • list2" + " Title\n ≡≡≡≡≡\n\n Some discussion\n\n │ A quote\n\n Section important\n =================\n\n Some bolded\n\n • list1\n\n • list2" + @test sprint(show, "text/plain", md"#") == " " # edge case of empty header @test sprint(show, "text/markdown", book) == """ # Title @@ -1148,7 +1149,7 @@ end # issue 20225, check this can print @test typeof(sprint(Markdown.term, Markdown.parse(" "))) == String -# different output depending on whether color is requested: +# issue 20225, check this can print +# different output depending on whether color is requested: +# issue 20225, check this can print let buf = IOBuffer() @test typeof(sprint(Markdown.term, Markdown.parse(" "))) == String show(buf, "text/plain", md"*emph*") @@ -1159,6 +1160,38 @@ let buf = IOBuffer() @test String(take!(buf)) == " \e[4memph\e[24m" end +let word = "Markdown" # disable underline when wrapping lines + buf = IOBuffer() + ctx = IOContext(buf, :color => true, :displaysize => (displaysize(buf)[1], length(word))) + long_italic_text = Markdown.parse('_' * join(fill(word, 10), ' ') * '_') + show(ctx, MIME("text/plain"), long_italic_text) + lines = split(String(take!(buf)), '\n') + @test endswith(lines[begin], Base.disable_text_style[:underline]) + @test startswith(lines[begin+1], ' '^Markdown.margin * Base.text_colors[:underline]) +end + +let word = "Markdown" # pre is of size Markdown.margin when wrapping title + buf = IOBuffer() + ctx = IOContext(buf, :color => true, :displaysize => (displaysize(buf)[1], length(word))) + long_title = Markdown.parse("# " * join(fill(word, 3))) + show(ctx, MIME("text/plain"), long_title) + lines = split(String(take!(buf)), '\n') + @test all(startswith(Base.text_colors[:bold] * ' '^Markdown.margin), lines) +end + +struct Struct49454 end +Base.show(io::IO, ::Struct49454) = + print(io, Base.text_colors[:underline], "Struct 49454()", Base.text_colors[:normal]) + +let buf = IOBuffer() + ctx = IOContext(buf, :color => true, :displaysize => (displaysize(buf)[1], 10)) + show(stdout, MIME("text/plain"), md""" + text without $(Struct49454()) underline. + """) + lines = split(String(take!(buf)), '\n') + @test !occursin(Base.text_colors[:underline], lines[end]) +end + # table rendering with term #25213 t = """ a | b diff --git a/stdlib/MbedTLS_jll/Project.toml b/stdlib/MbedTLS_jll/Project.toml index 00a6b29426d91..2e8d0d384f88a 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.0+0" +version = "2.28.2+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 338bec9503c07..e46da42a9a638 100644 --- a/stdlib/MbedTLS_jll/src/MbedTLS_jll.jl +++ b/stdlib/MbedTLS_jll/src/MbedTLS_jll.jl @@ -14,13 +14,13 @@ export libmbedcrypto, libmbedtls, libmbedx509 # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libmbedcrypto_handle = C_NULL -libmbedcrypto_path = "" -libmbedtls_handle = C_NULL -libmbedtls_path = "" -libmbedx509_handle = C_NULL -libmbedx509_path = "" +artifact_dir::String = "" +libmbedcrypto_handle::Ptr{Cvoid} = C_NULL +libmbedcrypto_path::String = "" +libmbedtls_handle::Ptr{Cvoid} = C_NULL +libmbedtls_path::String = "" +libmbedx509_handle::Ptr{Cvoid} = C_NULL +libmbedx509_path::String = "" if Sys.iswindows() const libmbedcrypto = "libmbedcrypto.dll" diff --git a/stdlib/MbedTLS_jll/test/runtests.jl b/stdlib/MbedTLS_jll/test/runtests.jl index b731d7f833043..2d82fa564cd18 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.0" + @test vn == v"2.28.2" end diff --git a/stdlib/Mmap/docs/src/index.md b/stdlib/Mmap/docs/src/index.md index ada88b153de64..5c40f11db4a4c 100644 --- a/stdlib/Mmap/docs/src/index.md +++ b/stdlib/Mmap/docs/src/index.md @@ -1,5 +1,7 @@ # Memory-mapped I/O +Low level module for mmap (memory mapping of files). + ```@docs Mmap.Anonymous Mmap.mmap diff --git a/stdlib/MozillaCACerts_jll/Project.toml b/stdlib/MozillaCACerts_jll/Project.toml index 0db86a1dd5319..cef860fda4acd 100644 --- a/stdlib/MozillaCACerts_jll/Project.toml +++ b/stdlib/MozillaCACerts_jll/Project.toml @@ -1,6 +1,6 @@ name = "MozillaCACerts_jll" uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.2.1" +version = "2023.01.10" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/NetworkOptions.version b/stdlib/NetworkOptions.version index a07211b9dcfdf..64d3fab9d7bf4 100644 --- a/stdlib/NetworkOptions.version +++ b/stdlib/NetworkOptions.version @@ -1,4 +1,4 @@ NETWORKOPTIONS_BRANCH = master -NETWORKOPTIONS_SHA1 = 01e6ec17aa4ef74b4a0ea19c193dacf8d2cfc353 +NETWORKOPTIONS_SHA1 = f7bbeb66f05fc651adb12758b650e8630a998fbd NETWORKOPTIONS_GIT_URL := https://github.com/JuliaLang/NetworkOptions.jl.git NETWORKOPTIONS_TAR_URL = https://api.github.com/repos/JuliaLang/NetworkOptions.jl/tarball/$1 diff --git a/stdlib/OpenBLAS_jll/Project.toml b/stdlib/OpenBLAS_jll/Project.toml index 3ab110db99410..6d953327003be 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.17+2" +version = "0.3.23+0" [deps] CompilerSupportLibraries_jll = "e66e0078-7015-5450-92f7-15fbd957f2ae" @@ -8,7 +8,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [compat] -julia = "1.7" +julia = "1.9" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl index f656621d957d6..4f1c57a7d06be 100644 --- a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl +++ b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl @@ -13,9 +13,9 @@ export libopenblas # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libopenblas_handle = C_NULL -libopenblas_path = "" +artifact_dir::String = "" +libopenblas_handle::Ptr{Cvoid} = C_NULL +libopenblas_path::String = "" if Base.USE_BLAS64 const libsuffix = "64_" @@ -37,6 +37,19 @@ function __init__() ENV["OPENBLAS_MAIN_FREE"] = "1" end + # Ensure that OpenBLAS does not grab a huge amount of memory at first, + # since it instantly allocates scratch buffer space for the number of + # threads it thinks it needs to use. + # X-ref: https://github.com/xianyi/OpenBLAS/blob/c43ec53bdd00d9423fc609d7b7ecb35e7bf41b85/README.md#setting-the-number-of-threads-using-environment-variables + # X-ref: https://github.com/JuliaLang/julia/issues/45434 + if !haskey(ENV, "OPENBLAS_NUM_THREADS") && + !haskey(ENV, "GOTO_NUM_THREADS") && + !haskey(ENV, "OMP_NUM_THREADS") + # We set this to `1` here, and then LinearAlgebra will update + # to the true value in its `__init__()` function. + ENV["OPENBLAS_DEFAULT_NUM_THREADS"] = "1" + end + global libopenblas_handle = dlopen(libopenblas) global libopenblas_path = dlpath(libopenblas_handle) global artifact_dir = dirname(Sys.BINDIR) diff --git a/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl b/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl index e3536021ad4c9..f2dee45a279cd 100644 --- a/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl +++ b/stdlib/OpenLibm_jll/src/OpenLibm_jll.jl @@ -13,9 +13,9 @@ export libopenlibm # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libopenlibm_handle = C_NULL -libopenlibm_path = "" +artifact_dir::String = "" +libopenlibm_handle::Ptr{Cvoid} = C_NULL +libopenlibm_path::String = "" if Sys.iswindows() const libopenlibm = "libopenlibm.dll" diff --git a/stdlib/PCRE2_jll/Project.toml b/stdlib/PCRE2_jll/Project.toml index b7718fcf79f48..d630c04383bfb 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.36.0+2" +version = "10.42.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 81048a45998b5..e7f685820830b 100644 --- a/stdlib/PCRE2_jll/src/PCRE2_jll.jl +++ b/stdlib/PCRE2_jll/src/PCRE2_jll.jl @@ -13,9 +13,9 @@ export libpcre2_8 # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libpcre2_8_handle = C_NULL -libpcre2_8_path = "" +artifact_dir::String = "" +libpcre2_8_handle::Ptr{Cvoid} = C_NULL +libpcre2_8_path::String = "" if Sys.iswindows() const libpcre2_8 = "libpcre2-8-0.dll" diff --git a/stdlib/PCRE2_jll/test/runtests.jl b/stdlib/PCRE2_jll/test/runtests.jl index b2446e7e5caab..d593b07af31ce 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.36.0" + @test vn == v"10.42.0" end diff --git a/stdlib/Pkg.version b/stdlib/Pkg.version index 5706cef05e9a5..7b5006f2141ff 100644 --- a/stdlib/Pkg.version +++ b/stdlib/Pkg.version @@ -1,4 +1,4 @@ PKG_BRANCH = master -PKG_SHA1 = e31a3dc77201e1c7c469f6d4572c521f93fefb20 +PKG_SHA1 = daf02a458ae6daa402a5dd6683c40d6910325c4e 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/src/Printf.jl b/stdlib/Printf/src/Printf.jl index 05e1621dcb795..62a84d7d36984 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -15,6 +15,10 @@ const Pointer = Val{'p'} const HexBases = Union{Val{'x'}, Val{'X'}, Val{'a'}, Val{'A'}} const PositionCounter = Val{'n'} +const MAX_FRACTIONAL_PART_WIDTH = 17 # max significant decimals + 1: `ceil(Int, log10(1 / eps(Float64))) + 1` +const MAX_INTEGER_PART_WIDTH = 309 # max exponent: `ceil(Int, log10(prevfloat(typemax(Float64))))` +const MAX_FMT_CHARS_WIDTH = 5 # hash | sign +/- | decimal dot | exponent e/E | exponent sign + """ Typed representation of a format specifier. @@ -30,19 +34,29 @@ struct Spec{T} # T => %type => Val{'type'} hash::Bool width::Int precision::Int + dynamic_width::Bool + dynamic_precision::Bool end # recreate the format specifier string from a typed Spec Base.string(f::Spec{T}; modifier::String="") where {T} = - string("%", f.leftalign ? "-" : "", f.plus ? "+" : "", f.space ? " " : "", - f.zero ? "0" : "", f.hash ? "#" : "", f.width > 0 ? f.width : "", - f.precision == 0 ? ".0" : f.precision > 0 ? ".$(f.precision)" : "", modifier, char(T)) + string("%", + f.leftalign ? "-" : "", + f.plus ? "+" : "", + f.space ? " " : "", + f.zero ? "0" : "", + f.hash ? "#" : "", + f.dynamic_width ? "*" : (f.width > 0 ? f.width : ""), + f.dynamic_precision ? ".*" : (f.precision == 0 ? ".0" : (f.precision > 0 ? ".$(f.precision)" : "")), + modifier, + char(T)) + Base.show(io::IO, f::Spec) = print(io, string(f)) floatfmt(s::Spec{T}) where {T} = - Spec{Val{'f'}}(s.leftalign, s.plus, s.space, s.zero, s.hash, s.width, 0) + Spec{Val{'f'}}(s.leftalign, s.plus, s.space, s.zero, s.hash, s.width, 0, s.dynamic_width, s.dynamic_precision) ptrfmt(s::Spec{T}, x) where {T} = - Spec{Val{'x'}}(s.leftalign, s.plus, s.space, s.zero, true, s.width, sizeof(x) == 8 ? 16 : 8) + Spec{Val{'x'}}(s.leftalign, s.plus, s.space, s.zero, true, s.width, sizeof(x) == 8 ? 16 : 8, s.dynamic_width, s.dynamic_precision) """ Printf.Format(format_str) @@ -71,24 +85,59 @@ struct Format{S, T} # and so on, then at the end, str[substringranges[end]] substringranges::Vector{UnitRange{Int}} formats::T # Tuple of Specs + numarguments::Int # required for dynamic format specifiers end # what number base should be used for a given format specifier? base(T) = T <: HexBases ? 16 : T <: Val{'o'} ? 8 : 10 char(::Type{Val{c}}) where {c} = c +struct InvalidFormatStringError <: Exception + message::String + format::String + start_color::Int + end_color::Int +end + +function Base.showerror(io::IO, err::InvalidFormatStringError) + io_has_color = get(io, :color, false)::Bool + + println(io, "InvalidFormatStringError: ", err.message) + print(io, " \"", @view(err.format[begin:prevind(err.format, err.start_color)])) + invalid_text = @view err.format[err.start_color:err.end_color] + + printstyled(io, invalid_text, color=:red) + + # +1 is okay, since all format characters are single bytes + println(io, @view(err.format[err.end_color+1:end]), "\"") + + arrow_error = '-'^(length(invalid_text)-1) + arrow = " " * ' '^err.start_color * arrow_error * "^\n" + if io_has_color + printstyled(io, arrow, color=:red) + else + print(io, arrow) + end +end + # parse format string function Format(f::AbstractString) - isempty(f) && throw(ArgumentError("empty format string")) + isempty(f) && throw(InvalidFormatStringError("Format string must not be empty", f, 1, 1)) bytes = codeunits(f) len = length(bytes) pos = 1 + numarguments = 0 + b = 0x00 + local last_percent_pos + + # skip ahead to first format specifier while pos <= len b = bytes[pos] pos += 1 if b == UInt8('%') - pos > len && throw(ArgumentError("invalid format string: '$f'")) + last_percent_pos = pos-1 + pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos)) if bytes[pos] == UInt8('%') # escaped '%' b = bytes[pos] @@ -120,7 +169,7 @@ function Format(f::AbstractString) else break end - pos > len && throw(ArgumentError("incomplete format string: '$f'")) + pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 end @@ -129,47 +178,68 @@ function Format(f::AbstractString) end # parse width width = 0 - while b - UInt8('0') < 0x0a - width = 10 * width + (b - UInt8('0')) + dynamic_width = false + if b == UInt8('*') + dynamic_width = true + numarguments += 1 b = bytes[pos] pos += 1 - pos > len && break + else + while b - UInt8('0') < 0x0a + width = 10 * width + (b - UInt8('0')) + b = bytes[pos] + pos += 1 + pos > len && break + end end # parse precision precision = 0 parsedprecdigits = false + dynamic_precision = false if b == UInt8('.') - pos > len && throw(ArgumentError("incomplete format string: '$f'")) + pos > len && throw(InvalidFormatStringError("Precision specifier is missing precision", f, last_percent_pos, pos-1)) parsedprecdigits = true b = bytes[pos] pos += 1 if pos <= len - while b - UInt8('0') < 0x0a - precision = 10precision + (b - UInt8('0')) + if b == UInt8('*') + dynamic_precision = true + numarguments += 1 b = bytes[pos] pos += 1 - pos > len && break + else + precision = 0 + while b - UInt8('0') < 0x0a + precision = 10precision + (b - UInt8('0')) + b = bytes[pos] + pos += 1 + pos > len && break + end end end end # parse length modifier (ignored) if b == UInt8('h') || b == UInt8('l') prev = b + pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 if b == prev - pos > len && throw(ArgumentError("invalid format string: '$f'")) + pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 end - elseif b in b"Ljqtz" + elseif b in b"Ljqtz" # q was a synonym for ll above, see `man 3 printf`. Not to be used. + pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 end # parse type - !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(ArgumentError("invalid format string: '$f', invalid type specifier: '$(Char(b))'")) + !(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 + # this is taken care of in fmt() for Ints zero = false elseif (type <: Strings || type <: Chars) && !parsedprecdigits precision = -1 @@ -178,13 +248,15 @@ function Format(f::AbstractString) elseif type <: Floats && !parsedprecdigits precision = 6 end - push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision)) + numarguments += 1 + push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision, dynamic_width, dynamic_precision)) start = pos while pos <= len b = bytes[pos] pos += 1 if b == UInt8('%') - pos > len && throw(ArgumentError("invalid format string: '$f'")) + last_percent_pos = pos-1 + pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos)) if bytes[pos] == UInt8('%') # escaped '%' b = bytes[pos] @@ -196,7 +268,7 @@ function Format(f::AbstractString) end push!(strs, start:pos - 1 - (b == UInt8('%'))) end - return Format(bytes, strs, Tuple(fmts)) + return Format(bytes, strs, Tuple(fmts), numarguments) end macro format_str(str) @@ -218,6 +290,28 @@ const HEX = b"0123456789ABCDEF" return pos end + +@inline function rmdynamic(spec::Spec{T}, args, argp) where {T} + zero, width, precision = spec.zero, spec.width, spec.precision + if spec.dynamic_width + width = args[argp] + argp += 1 + end + if spec.dynamic_precision + precision = args[argp] + if zero && T <: Ints && precision > 0 + zero = false + end + argp += 1 + end + (Spec{T}(spec.leftalign, spec.plus, spec.space, zero, spec.hash, width, precision, false, false), argp) +end + +@inline function fmt(buf, pos, args, argp, spec::Spec{T}) where {T} + spec, argp = rmdynamic(spec, args, argp) + (fmt(buf, pos, args[argp], spec), argp+1) +end + @inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Chars} leftalign, width = spec.leftalign, spec.width c = Char(first(arg)) @@ -242,7 +336,7 @@ end @inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Strings} leftalign, hash, width, prec = spec.leftalign, spec.hash, spec.width, spec.precision str = string(arg) - slen = textwidth(str) + (hash ? arg isa AbstractString ? 2 : 1 : 0) + slen = textwidth(str)::Int + (hash ? arg isa AbstractString ? 2 : 1 : 0) op = p = prec == -1 ? slen : min(slen, prec) if !leftalign && width > p for _ = 1:(width - p) @@ -394,6 +488,10 @@ _snprintf(ptr, siz, str, arg) = @ccall "libmpfr".mpfr_snprintf(ptr::Ptr{UInt8}, siz::Csize_t, str::Ptr{UInt8}; arg::Ref{BigFloat})::Cint +# Arbitrary constant for a maximum number of bytes we want to output for a BigFloat. +# 8KiB seems like a reasonable default. Larger BigFloat representations should probably +# use a custom printing routine. Printing values with results larger than this ourselves +# seems like a dangerous thing to do. const __BIG_FLOAT_MAX__ = 8192 @inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Floats} @@ -405,17 +503,15 @@ const __BIG_FLOAT_MAX__ = 8192 GC.@preserve buf begin siz = length(buf) - pos + 1 str = string(spec; modifier="R") - len = _snprintf(pointer(buf, pos), siz, str, x) - if len > siz - maxout = max(__BIG_FLOAT_MAX__, - ceil(Int, precision(x) * log(2) / log(10)) + 25) - len > maxout && - error("Over $maxout bytes $len needed to output BigFloat $x") - resize!(buf, len + 1) - len = _snprintf(pointer(buf, pos), len + 1, str, x) + required_length = _snprintf(pointer(buf, pos), siz, str, x) + if required_length > siz + required_length > __BIG_FLOAT_MAX__ && + throw(ArgumentError("The given BigFloat requires $required_length bytes to be printed, which is more than the maximum of $__BIG_FLOAT_MAX__ bytes supported.")) + resize!(buf, required_length + 1) + required_length = _snprintf(pointer(buf, pos), required_length + 1, str, x) end - len > 0 || throw(ArgumentError("invalid printf formatting $str for BigFloat")) - return pos + len + required_length > 0 || throw(ArgumentError("The given BigFloat would produce less than the maximum allowed number of bytes $__BIG_FLOAT_MAX__, but still couldn't be printed fully for an unknown reason.")) + return pos + required_length end end x = Float64(x) @@ -593,7 +689,9 @@ function ini_dec end function fmtfallback(buf, pos, arg, spec::Spec{T}) where {T} leftalign, plus, space, zero, hash, width, prec = spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision - buf2 = Base.StringVector(309 + 17 + 5) + buf2 = Base.StringVector( + MAX_INTEGER_PART_WIDTH + MAX_FRACTIONAL_PART_WIDTH + MAX_FMT_CHARS_WIDTH + ) ise = T <: Union{Val{'e'}, Val{'E'}} isg = T <: Union{Val{'g'}, Val{'G'}} isf = T <: Val{'f'} @@ -729,9 +827,10 @@ const UNROLL_UPTO = 16 # for each format, write out arg and next substring # unroll up to 16 formats N = length(f.formats) + argp = 1 Base.@nexprs 16 i -> begin if N >= i - pos = fmt(buf, pos, args[i], f.formats[i]) + pos, argp = fmt(buf, pos, args, argp, f.formats[i]) for j in f.substringranges[i + 1] b = f.str[j] if !escapechar @@ -746,7 +845,7 @@ const UNROLL_UPTO = 16 end if N > 16 for i = 17:length(f.formats) - pos = fmt(buf, pos, args[i], f.formats[i]) + pos, argp = fmt(buf, pos, args, argp, f.formats[i]) for j in f.substringranges[i + 1] b = f.str[j] if !escapechar @@ -762,11 +861,17 @@ const UNROLL_UPTO = 16 return pos end +@inline function plength(f::Spec{T}, args, argp) where {T} + f, argp = rmdynamic(f, args, argp) + (plength(f, args[argp]), argp+1) +end + function plength(f::Spec{T}, x) where {T <: Chars} c = Char(first(x)) w = textwidth(c) return max(f.width, w) + (ncodeunits(c) - w) end + plength(f::Spec{Pointer}, x) = max(f.width, 2 * sizeof(x) + 2) function plength(f::Spec{T}, x) where {T <: Strings} @@ -778,34 +883,40 @@ end function plength(f::Spec{T}, x) where {T <: Ints} x2 = toint(x) - return max(f.width, f.precision + ndigits(x2, base=base(T), pad=1) + 5) + return max( + f.width, + f.precision + ndigits(x2, base=base(T), pad=1) + MAX_FMT_CHARS_WIDTH + ) end plength(f::Spec{T}, x::AbstractFloat) where {T <: Ints} = - max(f.width, 0 + 309 + 17 + f.hash + 5) + max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + 0 + MAX_FMT_CHARS_WIDTH) plength(f::Spec{T}, x) where {T <: Floats} = - max(f.width, f.precision + 309 + 17 + f.hash + 5) + max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + f.precision + MAX_FMT_CHARS_WIDTH) plength(::Spec{PositionCounter}, x) = 0 @inline function computelen(substringranges, formats, args) len = sum(length, substringranges) N = length(formats) # unroll up to 16 formats + argp = 1 Base.@nexprs 16 i -> begin if N >= i - len += plength(formats[i], args[i]) + l, argp = plength(formats[i], args, argp) + len += l end end if N > 16 for i = 17:length(formats) - len += plength(formats[i], args[i]) + l, argp = plength(formats[i], args, argp) + len += l end end return len end @noinline argmismatch(a, b) = - throw(ArgumentError("mismatch between # of format specifiers and provided args: $a != $b")) + throw(ArgumentError("Number of format specifiers and number of provided args differ: $a != $b")) """ Printf.format(f::Printf.Format, args...) => String @@ -818,7 +929,7 @@ for more details on C `printf` support. function format end function format(io::IO, f::Format, args...) # => Nothing - length(f.formats) == length(args) || argmismatch(length(f.formats), length(args)) + f.numarguments == length(args) || argmismatch(f.numarguments, length(args)) buf = Base.StringVector(computelen(f.substringranges, f.formats, args)) pos = format(buf, 1, f, args...) write(io, resize!(buf, pos - 1)) @@ -826,7 +937,7 @@ function format(io::IO, f::Format, args...) # => Nothing end function format(f::Format, args...) # => String - length(f.formats) == length(args) || argmismatch(length(f.formats), length(args)) + f.numarguments == length(args) || argmismatch(f.numarguments, length(args)) buf = Base.StringVector(computelen(f.substringranges, f.formats, args)) pos = format(buf, 1, f, args...) return String(resize!(buf, pos - 1)) @@ -860,10 +971,12 @@ Padded with zeros to length 6 000123 julia> @printf "Use shorter of decimal or scientific %g %g" 1.23 12300000.0 Use shorter of decimal or scientific 1.23 1.23e+07 -``` +julia> @printf "Use dynamic width and precision %*.*f" 10 2 0.12345 +Use dynamic width and precision 0.12 +``` For a systematic specification of the format, see [here](https://www.cplusplus.com/reference/cstdio/printf/). -See also [`@sprintf`](@ref). +See also [`@sprintf`](@ref) to get the result as a `String` instead of it being printed. # Caveats `Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`, @@ -885,6 +998,9 @@ julia> @printf "%.0f %.1f %f" 0.5 0.025 -0.0078125 using [`textwidth`](@ref), which e.g. ignores zero-width characters (such as combining characters for diacritical marks) and treats certain "wide" characters (e.g. emoji) as width `2`. + +!!! compat "Julia 1.10" + Dynamic width specifiers like `%*s` and `%0*.*f` require Julia 1.10. """ macro printf(io_or_fmt, args...) if io_or_fmt isa String @@ -892,8 +1008,10 @@ macro printf(io_or_fmt, args...) return esc(:($Printf.format(stdout, $fmt, $(args...)))) else io = io_or_fmt - isempty(args) && throw(ArgumentError("must provide required format string")) - fmt = Format(args[1]) + isempty(args) && throw(ArgumentError("No format string provided to `@printf` - use like `@printf [io] <format string> [<args...>].")) + fmt_str = first(args) + fmt_str isa String || throw(ArgumentError("First argument to `@printf` after `io` must be a format string")) + fmt = Format(fmt_str) return esc(:($Printf.format($io, $fmt, $(Base.tail(args)...)))) end end @@ -910,6 +1028,7 @@ julia> @sprintf "this is a %s %15.1f" "test" 34.567 ``` """ macro sprintf(fmt, args...) + fmt isa String || throw(ArgumentError("First argument to `@sprintf` must be a format string.")) f = Format(fmt) return esc(:($Printf.format($f, $(args...)))) end diff --git a/stdlib/Printf/test/runtests.jl b/stdlib/Printf/test/runtests.jl index e80cbe9626823..96d61b61d02e3 100644 --- a/stdlib/Printf/test/runtests.jl +++ b/stdlib/Printf/test/runtests.jl @@ -339,10 +339,10 @@ end @test Printf.@sprintf("1%%2%%3") == "1%2%3" @test Printf.@sprintf("GAP[%%]") == "GAP[%]" @test Printf.@sprintf("hey there") == "hey there" - @test_throws ArgumentError Printf.Format("") - @test_throws ArgumentError Printf.Format("%+") - @test_throws ArgumentError Printf.Format("%.") - @test_throws ArgumentError Printf.Format("%.0") + @test_throws Printf.InvalidFormatStringError Printf.Format("") + @test_throws Printf.InvalidFormatStringError Printf.Format("%+") + @test_throws Printf.InvalidFormatStringError Printf.Format("%.") + @test_throws Printf.InvalidFormatStringError Printf.Format("%.0") @test isempty(Printf.Format("%%").formats) @test Printf.@sprintf("%d%d", 1, 2) == "12" @test (Printf.@sprintf "%d%d" [1 2]...) == "12" @@ -355,10 +355,10 @@ end @test (Printf.@sprintf("%d\u0f00%d", 1, 2)) == "1\u0f002" @test (Printf.@sprintf("%d\U0001ffff%d", 1, 2)) == "1\U0001ffff2" @test (Printf.@sprintf("%d\u2203%d\u0203", 1, 2)) == "1\u22032\u0203" - @test_throws ArgumentError Printf.Format("%y%d") - @test_throws ArgumentError Printf.Format("%\u00d0%d") - @test_throws ArgumentError Printf.Format("%\u0f00%d") - @test_throws ArgumentError Printf.Format("%\U0001ffff%d") + @test_throws Printf.InvalidFormatStringError Printf.Format("%y%d") + @test_throws Printf.InvalidFormatStringError Printf.Format("%\u00d0%d") + @test_throws Printf.InvalidFormatStringError Printf.Format("%\u0f00%d") + @test_throws Printf.InvalidFormatStringError Printf.Format("%\U0001ffff%d") @test Printf.@sprintf("%10.5d", 4) == " 00004" @test (Printf.@sprintf "%d" typemax(Int64)) == "9223372036854775807" @@ -444,8 +444,8 @@ end @test (Printf.@sprintf("%f", parse(BigFloat, "1e400"))) == "10000000000000000000000000000000000000000000000000000000000000000000000000000025262527574416492004687051900140830217136998040684679611623086405387447100385714565637522507383770691831689647535911648520404034824470543643098638520633064715221151920028135130764414460468236314621044034960475540018328999334468948008954289495190631358190153259681118693204411689043999084305348398480210026863210192871358464.000000" - # Check that does not attempt to output incredibly large amounts of digits - @test_throws ErrorException Printf.@sprintf("%f", parse(BigFloat, "1e99999")) + # Check that Printf does not attempt to output more than 8KiB worth of digits + @test_throws ArgumentError Printf.@sprintf("%f", parse(BigFloat, "1e99999")) # Check bug with precision > length of string @test Printf.@sprintf("%4.2s", "a") == " a" @@ -528,13 +528,13 @@ end @test Printf.@sprintf( "%0-5d", -42) == "-42 " @test Printf.@sprintf( "%0-15d", 42) == "42 " @test Printf.@sprintf( "%0-15d", -42) == "-42 " - @test_throws ArgumentError Printf.Format("%d %") + @test_throws Printf.InvalidFormatStringError Printf.Format("%d %") @test Printf.@sprintf("%lld", 18446744065119617025) == "18446744065119617025" @test Printf.@sprintf("%+8lld", 100) == " +100" @test Printf.@sprintf("%+.8lld", 100) == "+00000100" @test Printf.@sprintf("%+10.8lld", 100) == " +00000100" - @test_throws ArgumentError Printf.Format("%_1lld") + @test_throws Printf.InvalidFormatStringError Printf.Format("%_1lld") @test Printf.@sprintf("%-1.5lld", -100) == "-00100" @test Printf.@sprintf("%5lld", 100) == " 100" @test Printf.@sprintf("%5lld", -100) == " -100" @@ -775,6 +775,7 @@ end @test Printf.@sprintf("%40d", typemax(Int128)) == " 170141183460469231731687303715884105727" end + @testset "%n" begin x = Ref{Int}() @test (Printf.@sprintf("%d4%n", 123, x); x[] == 4) @@ -782,4 +783,363 @@ end @test (Printf.@sprintf("%s%n", "1234", x); x[] == 4) end +@testset "dynamic" begin + + # dynamic width and precision + @test Printf.@sprintf("%*d", 10, 12) == " 12" + @test Printf.@sprintf("%.*d", 4, 12) == "0012" + @test Printf.@sprintf("%*.*d", 10, 4, 12) == " 0012" + @test Printf.@sprintf("%+*.*d", 10, 4, 12) == " +0012" + @test Printf.@sprintf("%0*.*d", 10, 4, 12) == " 0012" + + @test Printf.@sprintf("%*d%*d%*d", 4, 12, 4, 13, 4, 14) == " 12 13 14" + @test Printf.@sprintf("%*d%*d%*d", 4, 12, 5, 13, 6, 14) == " 12 13 14" + + # dynamic should return whatever the static width and precision returns + + + # pointers + @test Printf.@sprintf("%*p", 20, 0) == Printf.@sprintf("%20p", 0) + @test Printf.@sprintf("%-*p", 20, 0) == Printf.@sprintf("%-20p", 0) + @test Printf.@sprintf("%*p", 20, C_NULL) == Printf.@sprintf("%20p", C_NULL) + @test Printf.@sprintf("%-*p", 20, C_NULL) == Printf.@sprintf("%-20p", C_NULL) + + # hex float + @test Printf.@sprintf("%.*a", 0, 3.14) == Printf.@sprintf("%.0a", 3.14) + @test Printf.@sprintf("%.*a", 1, 3.14) == Printf.@sprintf("%.1a", 3.14) + @test Printf.@sprintf("%.*a", 2, 3.14) == Printf.@sprintf("%.2a", 3.14) + @test Printf.@sprintf("%#.*a", 0, 3.14) == Printf.@sprintf("%#.0a", 3.14) + @test Printf.@sprintf("%#.*a", 1, 3.14) == Printf.@sprintf("%#.1a", 3.14) + @test Printf.@sprintf("%#.*a", 2, 3.14) == Printf.@sprintf("%#.2a", 3.14) + @test Printf.@sprintf("%.*a", 6, 1.5) == Printf.@sprintf("%.6a", 1.5) + + # "%g" + @test Printf.@sprintf("%*.*g", 10, 5, -123.4 ) == Printf.@sprintf( "%10.5g", -123.4 ) + @test Printf.@sprintf("%0*.*g", 10, 5, -123.4 ) == Printf.@sprintf( "%010.5g", -123.4 ) + @test Printf.@sprintf("%.*g", 6, 12340000.0 ) == Printf.@sprintf( "%.6g", 12340000.0 ) + @test Printf.@sprintf("%#.*g", 6, 12340000.0 ) == Printf.@sprintf( "%#.6g", 12340000.0 ) + @test Printf.@sprintf("%*.*g", 10, 5, big"-123.4" ) == Printf.@sprintf( "%10.5g", big"-123.4" ) + @test Printf.@sprintf("%0*.*g", 10, 5, big"-123.4" ) == Printf.@sprintf( "%010.5g", big"-123.4" ) + @test Printf.@sprintf("%.*g", 6, big"12340000.0" ) == Printf.@sprintf( "%.6g", big"12340000.0" ) + @test Printf.@sprintf("%#.*g", 6, big"12340000.0") == Printf.@sprintf( "%#.6g", big"12340000.0") + + @test Printf.@sprintf("%.*g", 5, 42) == Printf.@sprintf( "%.5g", 42) + @test Printf.@sprintf("%#.*g", 2, 42) == Printf.@sprintf( "%#.2g", 42) + @test Printf.@sprintf("%#.*g", 5, 42) == Printf.@sprintf( "%#.5g", 42) + + @test Printf.@sprintf("%.*g", 15, 0) == Printf.@sprintf("%.15g", 0) + @test Printf.@sprintf("%#.*g", 15, 0) == Printf.@sprintf("%#.15g", 0) + + # "%f" + @test Printf.@sprintf("%.*f", 0, 3e142) == Printf.@sprintf( "%.0f", 3e142) + @test Printf.@sprintf("%.*f", 2, 1.234) == Printf.@sprintf("%.2f", 1.234) + @test Printf.@sprintf("%.*f", 2, 1.235) == Printf.@sprintf("%.2f", 1.235) + @test Printf.@sprintf("%.*f", 2, 0.235) == Printf.@sprintf("%.2f", 0.235) + @test Printf.@sprintf("%*.*f", 4, 1, 1.234) == Printf.@sprintf("%4.1f", 1.234) + @test Printf.@sprintf("%*.*f", 8, 1, 1.234) == Printf.@sprintf("%8.1f", 1.234) + @test Printf.@sprintf("%+*.*f", 8, 1, 1.234) == Printf.@sprintf("%+8.1f", 1.234) + @test Printf.@sprintf("% *.*f", 8, 1, 1.234) == Printf.@sprintf("% 8.1f", 1.234) + @test Printf.@sprintf("% *.*f", 7, 1, 1.234) == Printf.@sprintf("% 7.1f", 1.234) + @test Printf.@sprintf("% 0*.*f", 8, 1, 1.234) == Printf.@sprintf("% 08.1f", 1.234) + @test Printf.@sprintf("%0*.*f", 8, 1, 1.234) == Printf.@sprintf("%08.1f", 1.234) + @test Printf.@sprintf("%-0*.*f", 8, 1, 1.234) == Printf.@sprintf("%-08.1f", 1.234) + @test Printf.@sprintf("%-*.*f", 8, 1, 1.234) == Printf.@sprintf("%-8.1f", 1.234) + @test Printf.@sprintf("%0*.*f", 8, 1, -1.234) == Printf.@sprintf("%08.1f", -1.234) + @test Printf.@sprintf("%0*.*f", 9, 1, -1.234) == Printf.@sprintf("%09.1f", -1.234) + @test Printf.@sprintf("%0*.*f", 9, 1, 1.234) == Printf.@sprintf("%09.1f", 1.234) + @test Printf.@sprintf("%+0*.*f", 9, 1, 1.234) == Printf.@sprintf("%+09.1f", 1.234) + @test Printf.@sprintf("% 0*.*f", 9, 1, 1.234) == Printf.@sprintf("% 09.1f", 1.234) + @test Printf.@sprintf("%+ 0*.*f", 9, 1, 1.234) == Printf.@sprintf("%+ 09.1f", 1.234) + @test Printf.@sprintf("%+ 0*.*f", 9, 1, 1.234) == Printf.@sprintf("%+ 09.1f", 1.234) + @test Printf.@sprintf("%+ 0*.*f", 9, 0, 1.234) == Printf.@sprintf("%+ 09.0f", 1.234) + @test Printf.@sprintf("%+ #0*.*f", 9, 0, 1.234) == Printf.@sprintf("%+ #09.0f", 1.234) + + # "%e" + @test Printf.@sprintf("%*.*e", 10, 4, Inf) == Printf.@sprintf("%10.4e", Inf) + @test Printf.@sprintf("%*.*e", 10, 4, NaN) == Printf.@sprintf("%10.4e", NaN) + @test Printf.@sprintf("%*.*e", 10, 4, big"Inf") == Printf.@sprintf("%10.4e", big"Inf") + @test Printf.@sprintf("%*.*e", 10, 4, big"NaN") == Printf.@sprintf("%10.4e", big"NaN") + + @test Printf.@sprintf("%.*e", 0, 3e142) == Printf.@sprintf("%.0e",3e142) + @test Printf.@sprintf("%#.*e", 0, 3e142) == Printf.@sprintf("%#.0e", 3e142) + @test Printf.@sprintf("%.*e", 0, big"3e142") == Printf.@sprintf("%.0e", big"3e142") + + @test Printf.@sprintf("%#.*e", 0, big"3e142") == Printf.@sprintf("%#.0e", big"3e142") + @test Printf.@sprintf("%.*e", 0, big"3e1042") == Printf.@sprintf("%.0e", big"3e1042") + + @test Printf.@sprintf("%.*e", 2, 1.234) == Printf.@sprintf("%.2e", 1.234) + @test Printf.@sprintf("%.*e", 2, 1.235) == Printf.@sprintf("%.2e", 1.235) + @test Printf.@sprintf("%.*e", 2, 0.235) == Printf.@sprintf("%.2e", 0.235) + @test Printf.@sprintf("%*.*e", 4, 1, 1.234) == Printf.@sprintf("%4.1e", 1.234) + @test Printf.@sprintf("%*.*e", 8, 1, 1.234) == Printf.@sprintf("%8.1e", 1.234) + @test Printf.@sprintf("%+*.*e", 8, 1, 1.234) == Printf.@sprintf("%+8.1e", 1.234) + @test Printf.@sprintf("% *.*e", 8, 1, 1.234) == Printf.@sprintf("% 8.1e", 1.234) + @test Printf.@sprintf("% *.*e", 7, 1, 1.234) == Printf.@sprintf("% 7.1e", 1.234) + @test Printf.@sprintf("% 0*.*e", 8, 1, 1.234) == Printf.@sprintf("% 08.1e", 1.234) + @test Printf.@sprintf("%0*.*e", 8, 1, 1.234) == Printf.@sprintf("%08.1e", 1.234) + @test Printf.@sprintf("%-0*.*e", 8, 1, 1.234) == Printf.@sprintf("%-08.1e", 1.234) + @test Printf.@sprintf("%-*.*e", 8, 1, 1.234) == Printf.@sprintf("%-8.1e", 1.234) + @test Printf.@sprintf("%-*.*e", 8, 1, 1.234) == Printf.@sprintf("%-8.1e", 1.234) + @test Printf.@sprintf("%0*.*e", 8, 1, -1.234) == Printf.@sprintf("%08.1e", -1.234) + @test Printf.@sprintf("%0*.*e", 9, 1, -1.234) == Printf.@sprintf("%09.1e", -1.234) + @test Printf.@sprintf("%0*.*e", 9, 1, 1.234) == Printf.@sprintf("%09.1e", 1.234) + @test Printf.@sprintf("%+0*.*e", 9, 1, 1.234) == Printf.@sprintf("%+09.1e", 1.234) + @test Printf.@sprintf("% 0*.*e", 9, 1, 1.234) == Printf.@sprintf("% 09.1e", 1.234) + @test Printf.@sprintf("%+ 0*.*e", 9, 1, 1.234) == Printf.@sprintf("%+ 09.1e", 1.234) + @test Printf.@sprintf("%+ 0*.*e", 9, 1, 1.234) == Printf.@sprintf("%+ 09.1e", 1.234) + @test Printf.@sprintf("%+ 0*.*e", 9, 0, 1.234) == Printf.@sprintf("%+ 09.0e", 1.234) + @test Printf.@sprintf("%+ #0*.*e", 9, 0, 1.234) == Printf.@sprintf("%+ #09.0e", 1.234) + + # strings + @test Printf.@sprintf("%.*s", 1, "foo") == Printf.@sprintf("%.1s", "foo") + @test Printf.@sprintf("%*s", 1, "Hallo heimur") == Printf.@sprintf("%1s", "Hallo heimur") + @test Printf.@sprintf("%*s", 20, "Hallo") == Printf.@sprintf("%20s", "Hallo") + @test Printf.@sprintf("%-*s", 20, "Hallo") == Printf.@sprintf("%-20s", "Hallo") + @test Printf.@sprintf("%0-*s", 20, "Hallo") == Printf.@sprintf("%0-20s", "Hallo") + @test Printf.@sprintf("%.*s", 20, "Hallo heimur") == Printf.@sprintf("%.20s", "Hallo heimur") + @test Printf.@sprintf("%*.*s", 20, 5, "Hallo heimur") == Printf.@sprintf("%20.5s", "Hallo heimur") + @test Printf.@sprintf("%.*s", 0, "Hallo heimur") == Printf.@sprintf("%.0s", "Hallo heimur") + @test Printf.@sprintf("%*.*s", 20, 0, "Hallo heimur") == Printf.@sprintf("%20.0s", "Hallo heimur") + @test Printf.@sprintf("%.s", "Hallo heimur") == Printf.@sprintf("%.s", "Hallo heimur") + @test Printf.@sprintf("%*.s", 20, "Hallo heimur") == Printf.@sprintf("%20.s", "Hallo heimur") + @test Printf.@sprintf("%*sø", 4, "ø") == Printf.@sprintf("%4sø", "ø") + @test Printf.@sprintf("%-*sø", 4, "ø") == Printf.@sprintf("%-4sø", "ø") + + @test Printf.@sprintf("%*s", 8, "test") == Printf.@sprintf("%8s", "test") + @test Printf.@sprintf("%-*s", 8, "test") == Printf.@sprintf("%-8s", "test") + + @test Printf.@sprintf("%#*s", 8, :test) == Printf.@sprintf("%#8s", :test) + @test Printf.@sprintf("%#-*s", 8, :test) == Printf.@sprintf("%#-8s", :test) + + @test Printf.@sprintf("%*.*s", 8, 3, "test") == Printf.@sprintf("%8.3s", "test") + @test Printf.@sprintf("%#*.*s", 8, 3, "test") == Printf.@sprintf("%#8.3s", "test") + @test Printf.@sprintf("%-*.*s", 8, 3, "test") == Printf.@sprintf("%-8.3s", "test") + @test Printf.@sprintf("%#-*.*s", 8, 3, "test") == Printf.@sprintf("%#-8.3s", "test") + @test Printf.@sprintf("%.*s", 3, "test") == Printf.@sprintf("%.3s", "test") + @test Printf.@sprintf("%#.*s", 3, "test") == Printf.@sprintf("%#.3s", "test") + @test Printf.@sprintf("%-.*s", 3, "test") == Printf.@sprintf("%-.3s", "test") + @test Printf.@sprintf("%#-.*s", 3, "test") == Printf.@sprintf("%#-.3s", "test") + + # chars + @test Printf.@sprintf("%*c", 3, 'a') == Printf.@sprintf("%3c", 'a') + @test Printf.@sprintf("%*c", 1, 'x') == Printf.@sprintf("%1c", 'x') + @test Printf.@sprintf("%*c" , 20, 'x') == Printf.@sprintf("%20c" , 'x') + @test Printf.@sprintf("%-*c" , 20, 'x') == Printf.@sprintf("%-20c" , 'x') + @test Printf.@sprintf("%-0*c", 20, 'x') == Printf.@sprintf("%-020c", 'x') + @test Printf.@sprintf("%*c", 3, 'A') == Printf.@sprintf("%3c", 'A') + @test Printf.@sprintf("%-*c", 3, 'A') == Printf.@sprintf("%-3c", 'A') + + # more than 16 formats/args + @test Printf.@sprintf("%*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f %*.*f", 4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345,4,2,1.2345) == Printf.@sprintf("%4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f %4.2f", 1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345,1.2345) + + # Check bug with trailing nul printing BigFloat + @test (Printf.@sprintf("%.*f", 330, BigFloat(1)))[end] != '\0' + + # Check bug with precision > length of string + @test Printf.@sprintf("%*.*s", 4, 2, "a") == Printf.@sprintf("%4.2s", "a") + + # issue #29662 + @test Printf.@sprintf("%*.*e", 12, 3, pi*1e100) == Printf.@sprintf("%12.3e", pi*1e100) + @test Printf.@sprintf("%*d", 2, 3.14) == Printf.@sprintf("%*d", 2, 3.14) + @test Printf.@sprintf("%*d", 2, big(3.14)) == Printf.@sprintf("%*d", 2, big(3.14)) + + # 37539 + @test Printf.@sprintf(" %.*e\n", 1, 0.999) == Printf.@sprintf(" %.1e\n", 0.999) + @test Printf.@sprintf(" %.*f", 1, 9.999) == Printf.@sprintf(" %.1f", 9.999) + + # integers + @test Printf.@sprintf("%*d", 10, 12) == (Printf.@sprintf("%10d", 12)) + @test Printf.@sprintf("%.*d", 4, 12) == (Printf.@sprintf("%.4d", 12)) + @test Printf.@sprintf("%*.*d", 10, 4, 12) == (Printf.@sprintf("%10.4d", 12)) + @test Printf.@sprintf("%+*.*d", 10, 4, 12) == (Printf.@sprintf("%+10.4d", 12)) + @test Printf.@sprintf("%0*.*d", 10, 4, 12) == (Printf.@sprintf("%010.4d", 12)) + + @test Printf.@sprintf( "% *d", 5, 42) == Printf.@sprintf( "% 5d", 42) + @test Printf.@sprintf( "% *d", 5, -42) == Printf.@sprintf( "% 5d", -42) + @test Printf.@sprintf( "% *d", 15, 42) == Printf.@sprintf( "% 15d", 42) + @test Printf.@sprintf( "% *d", 15, -42) == Printf.@sprintf( "% 15d", -42) + + @test Printf.@sprintf("%+*d", 5, 42) == Printf.@sprintf("%+5d", 42) + @test Printf.@sprintf("%+*d", 5, -42) == Printf.@sprintf("%+5d", -42) + @test Printf.@sprintf("%+*d", 15, 42) == Printf.@sprintf("%+15d", 42) + @test Printf.@sprintf("%+*d", 15, -42) == Printf.@sprintf("%+15d", -42) + @test Printf.@sprintf( "%*d", 0, 42) == Printf.@sprintf( "%0d", 42) + @test Printf.@sprintf( "%*d", 0, -42) == Printf.@sprintf( "%0d", -42) + + @test Printf.@sprintf("%-*d", 5, 42) == Printf.@sprintf("%-5d", 42) + @test Printf.@sprintf("%-*d", 5, -42) == Printf.@sprintf("%-5d", -42) + @test Printf.@sprintf("%-*d", 15, 42) == Printf.@sprintf("%-15d", 42) + @test Printf.@sprintf("%-*d", 15, -42) == Printf.@sprintf("%-15d", -42) + + @test Printf.@sprintf("%+*lld", 8, 100) == Printf.@sprintf("%+8lld", 100) + @test Printf.@sprintf("%+.*lld", 8, 100) == Printf.@sprintf("%+.8lld", 100) + @test Printf.@sprintf("%+*.*lld", 10, 8, 100) == Printf.@sprintf("%+10.8lld", 100) + + @test Printf.@sprintf("%-*.*lld", 1, 5, -100) == Printf.@sprintf("%-1.5lld", -100) + @test Printf.@sprintf("%*lld", 5, 100) == Printf.@sprintf("%5lld", 100) + @test Printf.@sprintf("%*lld", 5, -100) == Printf.@sprintf("%5lld", -100) + @test Printf.@sprintf("%-*lld", 5, 100) == Printf.@sprintf("%-5lld", 100) + @test Printf.@sprintf("%-*lld", 5, -100) == Printf.@sprintf("%-5lld", -100) + @test Printf.@sprintf("%-.*lld", 5, 100) == Printf.@sprintf("%-.5lld", 100) + @test Printf.@sprintf("%-.*lld", 5, -100) == Printf.@sprintf("%-.5lld", -100) + @test Printf.@sprintf("%-*.*lld", 8, 5, 100) == Printf.@sprintf("%-8.5lld", 100) + @test Printf.@sprintf("%-*.*lld", 8, 5, -100) == Printf.@sprintf("%-8.5lld", -100) + @test Printf.@sprintf("%0*lld", 5, 100) == Printf.@sprintf("%05lld", 100) + @test Printf.@sprintf("%0*lld", 5, -100) == Printf.@sprintf("%05lld", -100) + @test Printf.@sprintf("% *lld", 5, 100) == Printf.@sprintf("% 5lld", 100) + @test Printf.@sprintf("% *lld", 5, -100) == Printf.@sprintf("% 5lld", -100) + @test Printf.@sprintf("% .*lld", 5, 100) == Printf.@sprintf("% .5lld", 100) + @test Printf.@sprintf("% .*lld", 5, -100) == Printf.@sprintf("% .5lld", -100) + @test Printf.@sprintf("% *.*lld", 8, 5, 100) == Printf.@sprintf("% 8.5lld", 100) + @test Printf.@sprintf("% *.*lld", 8, 5, -100) == Printf.@sprintf("% 8.5lld", -100) + @test Printf.@sprintf("%.*lld", 0, 0) == Printf.@sprintf("%.0lld", 0) + @test Printf.@sprintf("%#+*.*llx", 21, 18, -100) == Printf.@sprintf("%#+21.18llx", -100) + @test Printf.@sprintf("%#.*llo", 25, -100) == Printf.@sprintf("%#.25llo", -100) + @test Printf.@sprintf("%#+*.*llo", 24, 20, -100) == Printf.@sprintf("%#+24.20llo", -100) + @test Printf.@sprintf("%#+*.*llX", 18, 21, -100) == Printf.@sprintf("%#+18.21llX", -100) + @test Printf.@sprintf("%#+*.*llo", 20, 24, -100) == Printf.@sprintf("%#+20.24llo", -100) + @test Printf.@sprintf("%#+*.*llu", 25, 22, -1) == Printf.@sprintf("%#+25.22llu", -1) + @test Printf.@sprintf("%#+*.*llu", 30, 25, -1) == Printf.@sprintf("%#+30.25llu", -1) + @test Printf.@sprintf("%+#*.*lld", 25, 22, -1) == Printf.@sprintf("%+#25.22lld", -1) + @test Printf.@sprintf("%#-*.*llo", 8, 5, 100) == Printf.@sprintf("%#-8.5llo", 100) + @test Printf.@sprintf("%#-+ 0*.*lld", 8, 5, 100) == Printf.@sprintf("%#-+ 08.5lld", 100) + @test Printf.@sprintf("%#-+ 0*.*lld", 8, 5, 100) == Printf.@sprintf("%#-+ 08.5lld", 100) + @test Printf.@sprintf("%.*lld", 40, 1) == Printf.@sprintf("%.40lld", 1) + @test Printf.@sprintf("% .*lld", 40, 1) == Printf.@sprintf("% .40lld", 1) + @test Printf.@sprintf("% .*d", 40, 1) == Printf.@sprintf("% .40d", 1) + + @test Printf.@sprintf("%#0*x", 12, 1) == Printf.@sprintf("%#012x", 1) + @test Printf.@sprintf("%#0*.*x", 4, 8, 1) == Printf.@sprintf("%#04.8x", 1) + + @test Printf.@sprintf("%#-0*.*x", 8, 2, 1) == Printf.@sprintf("%#-08.2x", 1) + @test Printf.@sprintf("%#0*o", 8, 1) == Printf.@sprintf("%#08o", 1) + + @test Printf.@sprintf("%*d", 20, 1024) == Printf.@sprintf("%20d", 1024) + @test Printf.@sprintf("%*d", 20,-1024) == Printf.@sprintf("%20d", -1024) + @test Printf.@sprintf("%*i", 20, 1024) == Printf.@sprintf("%20i", 1024) + @test Printf.@sprintf("%*i", 20,-1024) == Printf.@sprintf("%20i", -1024) + @test Printf.@sprintf("%*u", 20, 1024) == Printf.@sprintf("%20u", 1024) + @test Printf.@sprintf("%*u", 20, UInt(4294966272)) == Printf.@sprintf("%20u", UInt(4294966272)) + @test Printf.@sprintf("%*o", 20, 511) == Printf.@sprintf("%20o", 511) + @test Printf.@sprintf("%*o", 20, UInt(4294966785)) == Printf.@sprintf("%20o", UInt(4294966785)) + @test Printf.@sprintf("%*x", 20, 305441741) == Printf.@sprintf("%20x", 305441741) + @test Printf.@sprintf("%*x", 20, UInt(3989525555)) == Printf.@sprintf("%20x", UInt(3989525555)) + @test Printf.@sprintf("%*X", 20, 305441741) == Printf.@sprintf("%20X", 305441741) + @test Printf.@sprintf("%*X", 20, UInt(3989525555)) == Printf.@sprintf("%20X", UInt(3989525555)) + @test Printf.@sprintf("%-*d", 20, 1024) == Printf.@sprintf("%-20d", 1024) + @test Printf.@sprintf("%-*d", 20,-1024) == Printf.@sprintf("%-20d", -1024) + @test Printf.@sprintf("%-*i", 20, 1024) == Printf.@sprintf("%-20i", 1024) + @test Printf.@sprintf("%-*i", 20,-1024) == Printf.@sprintf("%-20i", -1024) + @test Printf.@sprintf("%-*u", 20, 1024) == Printf.@sprintf("%-20u", 1024) + @test Printf.@sprintf("%-*u", 20, UInt(4294966272)) == Printf.@sprintf("%-20u", UInt(4294966272)) + @test Printf.@sprintf("%-*o", 20, 511) == Printf.@sprintf("%-20o", 511) + @test Printf.@sprintf("%-*o", 20, UInt(4294966785)) == Printf.@sprintf("%-20o", UInt(4294966785)) + @test Printf.@sprintf("%-*x", 20, 305441741) == Printf.@sprintf("%-20x", 305441741) + @test Printf.@sprintf("%-*x", 20, UInt(3989525555)) == Printf.@sprintf("%-20x", UInt(3989525555)) + @test Printf.@sprintf("%-*X", 20, 305441741) == Printf.@sprintf("%-20X", 305441741) + @test Printf.@sprintf("%-*X", 20, UInt(3989525555)) == Printf.@sprintf("%-20X", UInt(3989525555)) + @test Printf.@sprintf("%0*d", 20, 1024) == Printf.@sprintf("%020d", 1024) + @test Printf.@sprintf("%0*d", 20,-1024) == Printf.@sprintf("%020d", -1024) + @test Printf.@sprintf("%0*i", 20, 1024) == Printf.@sprintf("%020i", 1024) + @test Printf.@sprintf("%0*i", 20,-1024) == Printf.@sprintf("%020i", -1024) + @test Printf.@sprintf("%0*u", 20, 1024) == Printf.@sprintf("%020u", 1024) + @test Printf.@sprintf("%0*u", 20, UInt(4294966272)) == Printf.@sprintf("%020u", UInt(4294966272)) + @test Printf.@sprintf("%0*o", 20, 511) == Printf.@sprintf("%020o", 511) + @test Printf.@sprintf("%0*o", 20, UInt(4294966785)) == Printf.@sprintf("%020o", UInt(4294966785)) + @test Printf.@sprintf("%0*x", 20, 305441741) == Printf.@sprintf("%020x", 305441741) + @test Printf.@sprintf("%0*x", 20, UInt(3989525555)) == Printf.@sprintf("%020x", UInt(3989525555)) + @test Printf.@sprintf("%0*X", 20, 305441741) == Printf.@sprintf("%020X", 305441741) + @test Printf.@sprintf("%0*X", 20, UInt(3989525555)) == Printf.@sprintf("%020X", UInt(3989525555)) + @test Printf.@sprintf("%#*o", 20, 511) == Printf.@sprintf("%#20o", 511) + @test Printf.@sprintf("%#*o", 20, UInt(4294966785)) == Printf.@sprintf("%#20o", UInt(4294966785)) + @test Printf.@sprintf("%#*x", 20, 305441741) == Printf.@sprintf("%#20x", 305441741) + @test Printf.@sprintf("%#*x", 20, UInt(3989525555)) == Printf.@sprintf("%#20x", UInt(3989525555)) + @test Printf.@sprintf("%#*X", 20, 305441741) == Printf.@sprintf("%#20X", 305441741) + @test Printf.@sprintf("%#*X", 20, UInt(3989525555)) == Printf.@sprintf("%#20X", UInt(3989525555)) + @test Printf.@sprintf("%#0*o", 20, 511) == Printf.@sprintf("%#020o", 511) + @test Printf.@sprintf("%#0*o", 20, UInt(4294966785)) == Printf.@sprintf("%#020o", UInt(4294966785)) + @test Printf.@sprintf("%#0*x", 20, 305441741) == Printf.@sprintf("%#020x", 305441741) + @test Printf.@sprintf("%#0*x", 20, UInt(3989525555)) == Printf.@sprintf("%#020x", UInt(3989525555)) + @test Printf.@sprintf("%#0*X", 20, 305441741) == Printf.@sprintf("%#020X", 305441741) + @test Printf.@sprintf("%#0*X", 20, UInt(3989525555)) == Printf.@sprintf("%#020X", UInt(3989525555)) + @test Printf.@sprintf("%0-*d", 20, 1024) == Printf.@sprintf("%0-20d", 1024) + @test Printf.@sprintf("%0-*d", 20,-1024) == Printf.@sprintf("%0-20d", -1024) + @test Printf.@sprintf("%0-*i", 20, 1024) == Printf.@sprintf("%0-20i", 1024) + @test Printf.@sprintf("%0-*i", 20,-1024) == Printf.@sprintf("%0-20i", -1024) + @test Printf.@sprintf("%0-*u", 20, 1024) == Printf.@sprintf("%0-20u", 1024) + @test Printf.@sprintf("%0-*u", 20, UInt(4294966272)) == Printf.@sprintf("%0-20u", UInt(4294966272)) + @test Printf.@sprintf("%-0*o", 20, 511) == Printf.@sprintf("%-020o", 511) + @test Printf.@sprintf("%-0*o", 20, UInt(4294966785)) == Printf.@sprintf("%-020o", UInt(4294966785)) + @test Printf.@sprintf("%-0*x", 20, 305441741) == Printf.@sprintf("%-020x", 305441741) + @test Printf.@sprintf("%-0*x", 20, UInt(3989525555)) == Printf.@sprintf("%-020x", UInt(3989525555)) + @test Printf.@sprintf("%-0*X", 20, 305441741) == Printf.@sprintf("%-020X", 305441741) + @test Printf.@sprintf("%-0*X", 20, UInt(3989525555)) == Printf.@sprintf("%-020X", UInt(3989525555)) + @test Printf.@sprintf("%.*d", 20, 1024) == Printf.@sprintf("%.20d", 1024) + @test Printf.@sprintf("%.*d", 20,-1024) == Printf.@sprintf("%.20d", -1024) + @test Printf.@sprintf("%.*i", 20, 1024) == Printf.@sprintf("%.20i", 1024) + @test Printf.@sprintf("%.*i", 20,-1024) == Printf.@sprintf("%.20i", -1024) + @test Printf.@sprintf("%.*u", 20, 1024) == Printf.@sprintf("%.20u", 1024) + @test Printf.@sprintf("%.*u", 20, UInt(4294966272)) == Printf.@sprintf("%.20u", UInt(4294966272)) + @test Printf.@sprintf("%.*o", 20, 511) == Printf.@sprintf("%.20o", 511) + @test Printf.@sprintf("%.*o", 20, UInt(4294966785)) == Printf.@sprintf("%.20o", UInt(4294966785)) + @test Printf.@sprintf("%.*x", 20, 305441741) == Printf.@sprintf("%.20x", 305441741) + @test Printf.@sprintf("%.*x", 20, UInt(3989525555)) == Printf.@sprintf("%.20x", UInt(3989525555)) + @test Printf.@sprintf("%.*X", 20, 305441741) == Printf.@sprintf("%.20X", 305441741) + @test Printf.@sprintf("%.*X", 20, UInt(3989525555)) == Printf.@sprintf("%.20X", UInt(3989525555)) + @test Printf.@sprintf("%*.*d", 20, 5, 1024) == Printf.@sprintf("%20.5d", 1024) + @test Printf.@sprintf("%*.*d", 20, 5, -1024) == Printf.@sprintf("%20.5d", -1024) + @test Printf.@sprintf("%*.*i", 20, 5, 1024) == Printf.@sprintf("%20.5i", 1024) + @test Printf.@sprintf("%*.*i", 20, 5,-1024) == Printf.@sprintf("%20.5i", -1024) + @test Printf.@sprintf("%*.*u", 20, 5, 1024) == Printf.@sprintf("%20.5u", 1024) + @test Printf.@sprintf("%*.*u", 20, 5, UInt(4294966272)) == Printf.@sprintf("%20.5u", UInt(4294966272)) + @test Printf.@sprintf("%*.*o", 20, 5, 511) == Printf.@sprintf("%20.5o", 511) + @test Printf.@sprintf("%*.*o", 20, 5, UInt(4294966785)) == Printf.@sprintf("%20.5o", UInt(4294966785)) + @test Printf.@sprintf("%*.*x", 20, 5, 305441741) == Printf.@sprintf("%20.5x", 305441741) + @test Printf.@sprintf("%*.*x", 20, 10, UInt(3989525555)) == Printf.@sprintf("%20.10x", UInt(3989525555)) + @test Printf.@sprintf("%*.*X", 20, 5, 305441741) == Printf.@sprintf("%20.5X", 305441741) + @test Printf.@sprintf("%*.*X", 20, 10, UInt(3989525555)) == Printf.@sprintf("%20.10X", UInt(3989525555)) + @test Printf.@sprintf("%0*.*d", 20, 5, 1024) == Printf.@sprintf("%020.5d", 1024) + @test Printf.@sprintf("%0*.*d", 20, 5,-1024) == Printf.@sprintf("%020.5d", -1024) + @test Printf.@sprintf("%0*.*i", 20, 5, 1024) == Printf.@sprintf("%020.5i", 1024) + @test Printf.@sprintf("%0*.*i", 20, 5,-1024) == Printf.@sprintf("%020.5i", -1024) + @test Printf.@sprintf("%0*.*u", 20, 5, 1024) == Printf.@sprintf("%020.5u", 1024) + @test Printf.@sprintf("%0*.*u", 20, 5, UInt(4294966272)) == Printf.@sprintf("%020.5u", UInt(4294966272)) + @test Printf.@sprintf("%0*.*o", 20, 5, 511) == Printf.@sprintf("%020.5o", 511) + @test Printf.@sprintf("%0*.*o", 20, 5, UInt(4294966785)) == Printf.@sprintf("%020.5o", UInt(4294966785)) + @test Printf.@sprintf("%0*.*x", 20, 5, 305441741) == Printf.@sprintf("%020.5x", 305441741) + @test Printf.@sprintf("%0*.*x", 20, 10, UInt(3989525555)) == Printf.@sprintf("%020.10x", UInt(3989525555)) + @test Printf.@sprintf("%0*.*X", 20, 5, 305441741) == Printf.@sprintf("%020.5X", 305441741) + @test Printf.@sprintf("%0*.*X", 20, 10, UInt(3989525555)) == Printf.@sprintf("%020.10X", UInt(3989525555)) + @test Printf.@sprintf("%*.0d", 20, 1024) == Printf.@sprintf("%20.0d", 1024) + @test Printf.@sprintf("%*.d", 20,-1024) == Printf.@sprintf("%20.d", -1024) + @test Printf.@sprintf("%*.d", 20, 0) == Printf.@sprintf("%20.d", 0) + @test Printf.@sprintf("%*.0i", 20, 1024) == Printf.@sprintf("%20.0i", 1024) + @test Printf.@sprintf("%*.i", 20,-1024) == Printf.@sprintf("%20.i", -1024) + @test Printf.@sprintf("%*.i", 20, 0) == Printf.@sprintf("%20.i", 0) + @test Printf.@sprintf("%*.u", 20, 1024) == Printf.@sprintf("%20.u", 1024) + @test Printf.@sprintf("%*.0u", 20, UInt(4294966272)) == Printf.@sprintf("%20.0u", UInt(4294966272)) + @test Printf.@sprintf("%*.u", 20, UInt(0)) == Printf.@sprintf("%20.u", UInt(0)) + @test Printf.@sprintf("%*.o", 20, 511) == Printf.@sprintf("%20.o", 511) + @test Printf.@sprintf("%*.0o", 20, UInt(4294966785)) == Printf.@sprintf("%20.0o", UInt(4294966785)) + @test Printf.@sprintf("%*.o", 20, UInt(0)) == Printf.@sprintf("%20.o", UInt(0)) + @test Printf.@sprintf("%*.x", 20, 305441741) == Printf.@sprintf("%20.x", 305441741) + @test Printf.@sprintf("%*.0x", 20, UInt(3989525555)) == Printf.@sprintf("%20.0x", UInt(3989525555)) + @test Printf.@sprintf("%*.x", 20, UInt(0)) == Printf.@sprintf("%20.x", UInt(0)) + @test Printf.@sprintf("%*.X", 20, 305441741) == Printf.@sprintf("%20.X", 305441741) + @test Printf.@sprintf("%*.0X", 20, UInt(3989525555)) == Printf.@sprintf("%20.0X", UInt(3989525555)) + @test Printf.@sprintf("%*.X", 20, UInt(0)) == Printf.@sprintf("%20.X", UInt(0)) + + x = Ref{Int}() + y = Ref{Int}() + @test (Printf.@sprintf("%10s%n", "😉", x); Printf.@sprintf("%*s%n", 10, "😉", y); x[] == y[]) + @test (Printf.@sprintf("%10s%n", "1234", x); Printf.@sprintf("%*s%n", 10, "1234", y); x[] == y[]) + +end + +@testset "length modifiers" begin + @test_throws Printf.InvalidFormatStringError Printf.Format("%h") + @test_throws Printf.InvalidFormatStringError Printf.Format("%hh") + @test_throws Printf.InvalidFormatStringError Printf.Format("%z") +end + end # @testset "Printf" diff --git a/stdlib/Profile/Project.toml b/stdlib/Profile/Project.toml index 1d13dad22233a..334d475832b6d 100644 --- a/stdlib/Profile/Project.toml +++ b/stdlib/Profile/Project.toml @@ -5,9 +5,10 @@ uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" [extras] +Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Logging", "Serialization", "Test"] +test = ["Base64", "Logging", "Serialization", "Test"] diff --git a/stdlib/Profile/docs/src/index.md b/stdlib/Profile/docs/src/index.md index 8701dded0d427..adb91cebb8c46 100644 --- a/stdlib/Profile/docs/src/index.md +++ b/stdlib/Profile/docs/src/index.md @@ -34,6 +34,9 @@ First, a single stack trace at the instant that the signal was thrown is shown, followed by the profile report at the next yield point, which may be at task completion for code without yield points e.g. tight loops. +Optionally set environment variable `JULIA_PROFILE_PEEK_HEAP_SNAPSHOT` to `1` to also automatically collect a +[heap snapshot](@ref Heap-Snapshots). + ```julia-repl julia> foo() ##== the user sends a trigger while foo is running ==## @@ -107,3 +110,24 @@ Profile.Allocs.fetch Profile.Allocs.start Profile.Allocs.stop ``` + +## Heap Snapshots + +```@docs +Profile.take_heap_snapshot +``` + +The methods in `Profile` are not exported and need to be called e.g. as `Profile.take_heap_snapshot()`. + +```julia-repl +julia> using Profile + +julia> Profile.take_heap_snapshot("snapshot.heapsnapshot") +``` + +Traces and records julia objects on the heap. This only records objects known to the Julia +garbage collector. Memory allocated by external libraries not managed by the garbage +collector will not show up in the snapshot. + +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 aa689936d4598..e45f4dca9607f 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -30,7 +30,7 @@ struct RawResults end """ - Profile.Allocs.@profile [sample_rate=0.0001] expr + Profile.Allocs.@profile [sample_rate=0.1] expr Profile allocations that happen during `expr`, returning both the result and and AllocResults struct. @@ -47,6 +47,10 @@ 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`. + !!! note The current implementation of the Allocations Profiler does not capture types for all allocations. Allocations for which the profiler @@ -54,7 +58,7 @@ Profile.Allocs.Alloc(Vector{Any}, Base.StackTraces.StackFrame[_new_array_ at arr `Profile.Allocs.UnknownType`. You can read more about the missing types and the plan to improve this, here: - https://github.com/JuliaLang/julia/issues/43688. + <https://github.com/JuliaLang/julia/issues/43688>. !!! compat "Julia 1.8" The allocation profiler was added in Julia 1.8. @@ -63,7 +67,7 @@ macro profile(opts, ex) _prof_expr(ex, opts) end macro profile(ex) - _prof_expr(ex, :(sample_rate=0.0001)) + _prof_expr(ex, :(sample_rate=0.1)) end function _prof_expr(expr, opts) @@ -123,7 +127,7 @@ struct Alloc type::Any stacktrace::StackTrace size::Int - task::Ptr{Cvoid} + task::Ptr{Cvoid} # N.B. unrooted, may not be valid timestamp::UInt64 end @@ -140,9 +144,13 @@ end const BacktraceCache = Dict{BTElement,Vector{StackFrame}} # copied from julia_internal.h -const JL_BUFF_TAG = UInt(0x4eadc000) +JL_BUFF_TAG::UInt = ccall(:jl_get_buff_tag, UInt, ()) const JL_GC_UNKNOWN_TYPE_TAG = UInt(0xdeadaa03) +function __init__() + global JL_BUFF_TAG = ccall(:jl_get_buff_tag, UInt, ()) +end + struct CorruptType end struct BufferType end struct UnknownType end @@ -208,9 +216,4 @@ function stacktrace_memoized( return stack end -# Precompile once for the package cache. -@assert precompile(start, ()) -@assert precompile(stop, ()) -@assert precompile(fetch, ()) - end diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 50e03d2c79a5a..4bce0c4fecd88 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -23,10 +23,7 @@ appended to an internal buffer of backtraces. macro profile(ex) return quote try - status = start_timer() - if status < 0 - error(error_codes[status]) - end + start_timer() $(esc(ex)) finally stop_timer() @@ -34,27 +31,12 @@ macro profile(ex) end end -# triggers printing the report after a SIGINFO/SIGUSR1 profile request -const PROFILE_PRINT_COND = Ref{Base.AsyncCondition}() -function profile_printing_listener() - try - while true - wait(PROFILE_PRINT_COND[]) - peek_report[]() - end - catch ex - if !isa(ex, InterruptException) - @error "Profile printing listener crashed" exception=ex,catch_backtrace() - end - end -end - # An internal function called to show the report after an information request (SIGINFO or SIGUSR1). function _peek_report() iob = IOBuffer() - ioc = IOContext(IOContext(iob, stdout), :displaysize=>displaysize(stdout)) + ioc = IOContext(IOContext(iob, stderr), :displaysize=>displaysize(stderr)) print(ioc, groupby = [:thread, :task]) - Base.print(stdout, String(resize!(iob.data, iob.size))) + Base.print(stderr, String(take!(iob))) end # This is a ref so that it can be overridden by other profile info consumers. const peek_report = Ref{Function}(_peek_report) @@ -72,11 +54,7 @@ Set the duration in seconds of the profile "peek" that is triggered via `SIGINFO """ set_peek_duration(t::Float64) = ccall(:jl_set_profile_peek_duration, Cvoid, (Float64,), t) -precompile_script = """ -Profile.@profile sleep(0.5) -Profile.peek_report[]() -Profile.clear() -""" + #### #### User-level functions @@ -90,17 +68,17 @@ stored per thread. Each instruction pointer corresponds to a single line of code list of instruction pointers. Note that 6 spaces for instruction pointers per backtrace are used to store metadata and two NULL end markers. Current settings can be obtained by calling this function with no arguments, and each can be set independently using keywords or in the order `(n, delay)`. - -!!! compat "Julia 1.8" - As of Julia 1.8, this function allocates space for `n` instruction pointers per thread being profiled. - Previously this was `n` total. """ function init(; n::Union{Nothing,Integer} = nothing, delay::Union{Nothing,Real} = nothing, limitwarn::Bool = true) n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ()) + if n_cur == 0 && isnothing(n) && isnothing(delay) + # indicates that the buffer hasn't been initialized at all, so set the default + default_init() + n_cur = ccall(:jl_profile_maxlen_data, Csize_t, ()) + end delay_cur = ccall(:jl_profile_delay_nsec, UInt64, ())/10^9 if n === nothing && delay === nothing - nthreads = Sys.iswindows() ? 1 : Threads.nthreads() # windows only profiles the main thread - return round(Int, n_cur / nthreads), delay_cur + return n_cur, delay_cur end nnew = (n === nothing) ? n_cur : n delaynew = (delay === nothing) ? delay_cur : delay @@ -108,24 +86,21 @@ function init(; n::Union{Nothing,Integer} = nothing, delay::Union{Nothing,Real} end function init(n::Integer, delay::Real; limitwarn::Bool = true) - nthreads = Sys.iswindows() ? 1 : Threads.nthreads() # windows only profiles the main thread sample_size_bytes = sizeof(Ptr) # == Sys.WORD_SIZE / 8 - buffer_samples = n * nthreads + buffer_samples = n buffer_size_bytes = buffer_samples * sample_size_bytes if buffer_size_bytes > 2^29 && Sys.WORD_SIZE == 32 - buffer_size_bytes_per_thread = floor(Int, 2^29 / nthreads) - buffer_samples_per_thread = floor(Int, buffer_size_bytes_per_thread / sample_size_bytes) - buffer_samples = buffer_samples_per_thread * nthreads + buffer_samples = floor(Int, 2^29 / sample_size_bytes) buffer_size_bytes = buffer_samples * sample_size_bytes - limitwarn && @warn "Requested profile buffer limited to 512MB (n = $buffer_samples_per_thread per thread) given that this system is 32-bit" + limitwarn && @warn "Requested profile buffer limited to 512MB (n = $buffer_samples) given that this system is 32-bit" end - status = ccall(:jl_profile_init, Cint, (Csize_t, UInt64), buffer_samples, round(UInt64,10^9*delay)) + status = ccall(:jl_profile_init, Cint, (Csize_t, UInt64), buffer_samples, round(UInt64, 10^9*delay)) if status == -1 - error("could not allocate space for ", n, " instruction pointers per thread being profiled ($nthreads threads, $(Base.format_bytes(buffer_size_bytes)) total)") + error("could not allocate space for ", n, " instruction pointers ($(Base.format_bytes(buffer_size_bytes)))") end end -function __init__() +function default_init() # init with default values # Use a max size of 10M profile samples, and fire timer every 1ms # (that should typically give around 100 seconds of record) @@ -135,13 +110,19 @@ function __init__() n = 1_000_000 delay = 0.01 else + # Keep these values synchronized with trigger_profile_peek n = 10_000_000 delay = 0.001 end init(n, delay, limitwarn = false) - PROFILE_PRINT_COND[] = Base.AsyncCondition() - ccall(:jl_set_peek_cond, Cvoid, (Ptr{Cvoid},), PROFILE_PRINT_COND[].handle) - errormonitor(Threads.@spawn(profile_printing_listener())) +end + +# Checks whether the profile buffer has been initialized. If not, initializes it with the default size. +function check_init() + buffer_size = @ccall jl_profile_maxlen_data()::Int + if buffer_size == 0 + default_init() + end end """ @@ -216,7 +197,7 @@ The keyword arguments can be any combination of: `:flatc` does the same but also includes collapsing of C frames (may do odd things around `jl_apply`). - `threads::Union{Int,AbstractVector{Int}}` -- Specify which threads to include snapshots from in the report. Note that - this does not control which threads samples are collected on. + this does not control which threads samples are collected on (which may also have been collected on another machine). - `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. @@ -234,11 +215,11 @@ function print(io::IO, sortedby::Symbol = :filefuncline, groupby::Union{Symbol,AbstractVector{Symbol}} = :none, recur::Symbol = :off, - threads::Union{Int,AbstractVector{Int}} = 1:Threads.nthreads(), + threads::Union{Int,AbstractVector{Int}} = 1:typemax(Int), tasks::Union{UInt,AbstractVector{UInt}} = typemin(UInt):typemax(UInt)) pf = ProfileFormat(;C, combine, maxdepth, mincount, noisefloor, sortedby, recur) - if groupby == :none + if groupby === :none print(io, data, lidict, pf, format, threads, tasks, false) else if !in(groupby, [:thread, :task, [:task, :thread], [:thread, :task]]) @@ -281,7 +262,7 @@ function print(io::IO, end end end - elseif groupby == :task + elseif groupby === :task threads = 1:typemax(Int) for taskid in intersect(get_task_ids(data), tasks) printstyled(io, "Task $(Base.repr(taskid)) "; bold=true, color=Base.debug_color()) @@ -289,7 +270,7 @@ function print(io::IO, nosamples && (any_nosamples = true) println(io) end - elseif groupby == :thread + elseif groupby === :thread tasks = 1:typemax(UInt) for threadid in intersect(get_thread_ids(data), threads) printstyled(io, "Thread $threadid "; bold=true, color=Base.info_color()) @@ -406,7 +387,7 @@ function getdict!(dict::LineInfoDict, data::Vector{UInt}) n_unique_ips = length(unique_ips) n_unique_ips == 0 && return dict iplookups = similar(unique_ips, Vector{StackFrame}) - @sync for indexes_part in Iterators.partition(eachindex(unique_ips), div(n_unique_ips, Threads.nthreads(), RoundUp)) + @sync for indexes_part in Iterators.partition(eachindex(unique_ips), div(n_unique_ips, Threads.threadpoolsize(), RoundUp)) Threads.@spawn begin for i in indexes_part iplookups[i] = _lookup_corrected(unique_ips[i]) @@ -563,7 +544,14 @@ Julia, and examine the resulting `*.mem` files. clear_malloc_data() = ccall(:jl_clear_malloc_data, Cvoid, ()) # C wrappers -start_timer() = ccall(:jl_profile_start_timer, Cint, ()) +function start_timer() + check_init() # if the profile buffer hasn't been initialized, initialize with default size + status = ccall(:jl_profile_start_timer, Cint, ()) + if status < 0 + error(error_codes[status]) + end +end + stop_timer() = ccall(:jl_profile_stop_timer, Cvoid, ()) @@ -587,7 +575,7 @@ error_codes = Dict( """ fetch(;include_meta = true) -> data -Returns a copy of the buffer of profile backtraces. Note that the +Return a copy of the buffer of profile backtraces. Note that the values in `data` have meaning only on this machine in the current session, because it depends on the exact memory addresses used in JIT-compiling. This function is primarily for internal use; [`retrieve`](@ref) may be a better choice for most users. @@ -595,6 +583,9 @@ By default metadata such as threadid and taskid is included. Set `include_meta` """ function fetch(;include_meta = true, limitwarn = true) maxlen = maxlen_data() + if maxlen == 0 + error("The profiling data buffer is not initialized. A profile has not been requested this session.") + end len = len_data() if limitwarn && is_buffer_full() @warn """The profile data buffer is full; profiling probably terminated @@ -961,8 +952,8 @@ function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineI root.count += 1 startframe = i elseif !skip - pushfirst!(build, parent) if recur === :flat || recur === :flatc + pushfirst!(build, parent) # Rewind the `parent` tree back, if this exact ip was already present *higher* in the current tree found = false for j in 1:(startframe - i) @@ -1208,6 +1199,35 @@ function warning_empty(;summary = false) end 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) + +Write a snapshot of the heap, in the JSON format expected by the Chrome +Devtools Heap Snapshot viewer (.heapsnapshot extension), to a file +(`\$pid_\$timestamp.heapsnapshot`) in the current directory, or the given +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. +""" +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) + end + return filepath +end +function take_heap_snapshot(all_one::Bool=false) + f = abspath("$(getpid())_$(time_ns()).heapsnapshot") + return take_heap_snapshot(f, all_one) +end + + include("Allocs.jl") +include("precompile.jl") end # module diff --git a/stdlib/Profile/src/precompile.jl b/stdlib/Profile/src/precompile.jl new file mode 100644 index 0000000000000..2d947429861a9 --- /dev/null +++ b/stdlib/Profile/src/precompile.jl @@ -0,0 +1,11 @@ +if ccall(:jl_generating_output, Cint, ()) == 1 + precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Int, UInt}) + precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Int, UnitRange{UInt}}) + precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, UnitRange{Int}, UInt}) + precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, UnitRange{Int}, UnitRange{UInt}}) + precompile(Tuple{typeof(Profile.tree!), Profile.StackFrameTree{UInt64}, Vector{UInt64}, Dict{UInt64, Vector{Base.StackTraces.StackFrame}}, Bool, Symbol, Vector{Int}, Vector{UInt}}) + precompile(Tuple{typeof(Profile._peek_report)}) + precompile(Tuple{typeof(Profile.Allocs.start)}) + precompile(Tuple{typeof(Profile.Allocs.stop)}) + precompile(Tuple{typeof(Profile.Allocs.fetch)}) +end diff --git a/stdlib/Profile/test/allocs.jl b/stdlib/Profile/test/allocs.jl index b8d6222d07567..c2ec7d2f6cb54 100644 --- a/stdlib/Profile/test/allocs.jl +++ b/stdlib/Profile/test/allocs.jl @@ -64,7 +64,8 @@ end @testset "alloc profiler start stop fetch clear" begin function do_work() # Compiling allocates a lot - for f in (gensym() for _ in 1:10) + nsyms = @static Sys.WORD_SIZE == 32 ? 1 : 10 + for f in (gensym() for _ in 1:nsyms) @eval begin $f() = 10 $f() diff --git a/stdlib/Profile/test/runtests.jl b/stdlib/Profile/test/runtests.jl index 50917b9797c7d..2d6df81b1015d 100644 --- a/stdlib/Profile/test/runtests.jl +++ b/stdlib/Profile/test/runtests.jl @@ -3,6 +3,8 @@ using Test, Profile, Serialization, Logging using Base.StackTraces: StackFrame +@test_throws "The profiling data buffer is not initialized. A profile has not been requested this session." Profile.print() + Profile.clear() Profile.init() @@ -64,8 +66,8 @@ end iobuf = IOBuffer() with_logger(NullLogger()) do @testset for format in [:flat, :tree] - @testset for threads in [1:Threads.nthreads(), 1, 1:1, 1:2, [1,2]] - @testset for groupby in [:none, :thread, :task, [:thread, :task], [:task, :thread]] + @testset for threads in Any[1:typemax(Int), 1, 1:1, 1:2, [1,2]] + @testset for groupby in Any[:none, :thread, :task, [:thread, :task], [:task, :thread]] Profile.print(iobuf; groupby, threads, format) @test !isempty(String(take!(iobuf))) end @@ -118,11 +120,10 @@ end @testset "setting sample count and delay in init" begin n_, delay_ = Profile.init() n_original = n_ - nthreads = Sys.iswindows() ? 1 : Threads.nthreads() sample_size_bytes = sizeof(Ptr) def_n = Sys.iswindows() && Sys.WORD_SIZE == 32 ? 1_000_000 : 10_000_000 - if Sys.WORD_SIZE == 32 && (def_n * nthreads * sample_size_bytes) > 2^29 - @test n_ * nthreads * sample_size_bytes <= 2^29 + if Sys.WORD_SIZE == 32 && (def_n * sample_size_bytes) > 2^29 + @test n_ * sample_size_bytes <= 2^29 else @test n_ == def_n end @@ -131,8 +132,8 @@ end @test delay_ == def_delay Profile.init(n=1_000_001, delay=0.0005) n_, delay_ = Profile.init() - if Sys.WORD_SIZE == 32 && (1_000_001 * nthreads * sample_size_bytes) > 2^29 - @test n_ * nthreads * sample_size_bytes <= 2^29 + if Sys.WORD_SIZE == 32 && (1_000_001 * sample_size_bytes) > 2^29 + @test n_ * sample_size_bytes <= 2^29 else @test n_ == 1_000_001 end @@ -151,14 +152,14 @@ end @profile busywait(1, 20) _, fdict0 = Profile.flatten(Profile.retrieve()...) Base.update_stackframes_callback[] = function(list) - modify((sf, n)) = sf.func == :busywait ? (StackTraces.StackFrame(sf.func, sf.file, sf.line+2, sf.linfo, sf.from_c, sf.inlined, sf.pointer), n) : (sf, n) + modify((sf, n)) = sf.func === :busywait ? (StackTraces.StackFrame(sf.func, sf.file, sf.line+2, sf.linfo, sf.from_c, sf.inlined, sf.pointer), n) : (sf, n) map!(modify, list, list) end _, fdictc = Profile.flatten(Profile.retrieve()...) Base.update_stackframes_callback[] = identity function getline(sfs) for sf in sfs - sf.func == :busywait && return sf.line + sf.func === :busywait && return sf.line end nothing end @@ -170,7 +171,11 @@ let cmd = Base.julia_cmd() script = """ using Profile f(::Val) = GC.safepoint() - @profile for i = 1:10^3; f(Val(i)); end + @profile for i = 1:10^3 + println(i) + f(Val(i)) + end + println("done") print(Profile.len_data()) """ p = open(`$cmd -e $script`) @@ -184,50 +189,60 @@ let cmd = Base.julia_cmd() s = read(p, String) close(t) @test success(p) - @test parse(Int, s) > 100 + @test !isempty(s) + @test occursin("done", s) + @test parse(Int, split(s, '\n')[end]) > 100 end if Sys.isbsd() || Sys.islinux() @testset "SIGINFO/SIGUSR1 profile triggering" begin let cmd = Base.julia_cmd() script = """ - x = rand(1000, 1000) - println("started") - while true - x * x - yield() - end + print(stderr, "started\n") + eof(stdin) + close(t) """ iob = Base.BufferStream() - p = run(pipeline(`$cmd -e $script`, stderr = devnull, stdout = iob), wait = false) - t = Timer(60) do t # should be done in under 10 seconds + notify_exit = Base.PipeEndpoint() + p = run(pipeline(`$cmd -e $script`, stdin=notify_exit, stderr=iob, stdout=devnull), wait=false) + t = Timer(120) do t + # should be under 10 seconds, so give it 2 minutes then report failure + println("KILLING BY PROFILE TEST WATCHDOG\n") + kill(p, Base.SIGTERM) + sleep(10) kill(p, Base.SIGKILL) - sleep(5) - close(iob) + close(p) end try - s = readuntil(iob, "started", keep = true) + s = readuntil(iob, "started", keep=true) @assert occursin("started", s) @assert process_running(p) - for _ in 1:2 - sleep(2) + for i in 1:2 + i > 1 && sleep(5) if Sys.isbsd() kill(p, 29) # SIGINFO elseif Sys.islinux() kill(p, 10) # SIGUSR1 end - s = readuntil(iob, "Overhead ╎", keep = true) + s = readuntil(iob, "Overhead ╎", keep=true) @test process_running(p) + readavailable(iob) @test occursin("Overhead ╎", s) end - finally - kill(p, Base.SIGKILL) + close(notify_exit) # notify test finished + s = read(iob, String) # consume test output + wait(p) # wait for test completion + close(t) + catch + close(notify_exit) + 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 end end -else - @warn "Skipping \"SIGINFO/SIGUSR1 profile triggering\" test as it is not supported on this platform" end @testset "FlameGraphs" begin @@ -256,11 +271,27 @@ end Profile.tree!(root, backtraces, lidict, #= C =# true, :off) @test length(root.down) == 2 for k in keys(root.down) - @test k.file == :file1 + @test k.file === :file1 @test k.line ∈ (1, 2) end node = root.down[stackframe(:f1, :file1, 2)] @test only(node.down).first == lidict[8] end +@testset "HeapSnapshot" begin + tmpdir = mktempdir() + fname = cd(tmpdir) do + read(`$(Base.julia_cmd()) --startup-file=no -e "using Profile; print(Profile.take_heap_snapshot())"`, String) + end + + @test isfile(fname) + + open(fname) do fs + @test readline(fs) != "" + end + + rm(fname) + rm(tmpdir, force = true, recursive = true) +end + include("allocs.jl") diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index 1d1feea6d5a09..ce594d55863bc 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -32,7 +32,7 @@ julia> string(1 + 2) "3" ``` -There are a number useful features unique to interactive work. In addition to showing the result, +There are a number of useful features unique to interactive work. In addition to showing the result, the REPL also binds the result to the variable `ans`. A trailing semicolon on the line can be used as a flag to suppress showing the result. @@ -43,14 +43,14 @@ julia> ans "12" ``` -In Julia mode, the REPL supports something called *prompt pasting*. This activates when pasting -text that starts with `julia> ` into the REPL. In that case, only expressions starting with -`julia> ` are parsed, others are removed. This makes it possible to paste a chunk of code -that has been copied from a REPL session without having to scrub away prompts and outputs. This -feature is enabled by default but can be disabled or enabled at will with `REPL.enable_promptpaste(::Bool)`. -If it is enabled, you can try it out by pasting the code block above this paragraph straight into -the REPL. This feature does not work on the standard Windows command prompt due to its limitation -at detecting when a paste occurs. +In Julia mode, the REPL supports something called *prompt pasting*. This activates when pasting text +that starts with `julia> ` into the REPL. In that case, only expressions starting with `julia> ` (as +well as the other REPL mode prompts: `shell> `, `help?> `, `pkg>` ) are parsed, but others are +removed. This makes it possible to paste a chunk of text that has been copied from a REPL session +without having to scrub away prompts and outputs. This feature is enabled by default but can be +disabled or enabled at will with `REPL.enable_promptpaste(::Bool)`. If it is enabled, you can try it +out by pasting the code block above this paragraph straight into the REPL. This feature does not +work on the standard Windows command prompt due to its limitation at detecting when a paste occurs. Objects are printed at the REPL using the [`show`](@ref) function with a specific [`IOContext`](@ref). In particular, the `:limit` attribute is set to `true`. @@ -225,7 +225,7 @@ to do so), or pressing Esc and then the key. | Keybinding | Description | |:------------------- |:---------------------------------------------------------------------------------------------------------- | -| **Program control** |   | +| **Program control** | | | `^D` | Exit (when buffer is empty) | | `^C` | Interrupt or cancel | | `^L` | Clear console screen | @@ -233,7 +233,7 @@ to do so), or pressing Esc and then the key. | meta-Return/Enter | Insert new line without executing it | | `?` or `;` | Enter help or shell mode (when at start of a line) | | `^R`, `^S` | Incremental history search, described above | -| **Cursor movement** |   | +| **Cursor movement** | | | Right arrow, `^F` | Move right one character | | Left arrow, `^B` | Move left one character | | ctrl-Right, `meta-F`| Move right one word | @@ -251,7 +251,7 @@ to do so), or pressing Esc and then the key. | `^-Space ^-Space` | Set the "mark" in the editing region and make the region "active", i.e. highlighted | | `^G` | De-activate the region (i.e. make it not highlighted) | | `^X^X` | Exchange the current position with the mark | -| **Editing** |   | +| **Editing** | | | Backspace, `^H` | Delete the previous character, or the whole region when it's active | | Delete, `^D` | Forward delete one character (when buffer has text) | | meta-Backspace | Delete the previous word | @@ -259,6 +259,7 @@ to do so), or pressing Esc and then the key. | `^W` | Delete previous text up to the nearest whitespace | | `meta-w` | Copy the current region in the kill ring | | `meta-W` | "Kill" the current region, placing the text in the kill ring | +| `^U` | "Kill" to beginning of line, placing the text in the kill ring | | `^K` | "Kill" to end of line, placing the text in the kill ring | | `^Y` | "Yank" insert the text from the kill ring | | `meta-y` | Replace a previously yanked text with an older entry from the kill ring | @@ -270,9 +271,10 @@ to do so), or pressing Esc and then the key. | `meta-l` | Change the next word to lowercase | | `^/`, `^_` | Undo previous editing action | | `^Q` | Write a number in REPL and press `^Q` to open editor at corresponding stackframe or method | -| `meta-Left Arrow` | indent the current line on the left | -| `meta-Right Arrow` | indent the current line on the right | -| `meta-.` | insert last word from previous history entry | +| `meta-Left Arrow` | Indent the current line on the left | +| `meta-Right Arrow` | Indent the current line on the right | +| `meta-.` | Insert last word from previous history entry | +| `meta-e` | Edit the current input in an editor | ### Customizing keybindings @@ -412,7 +414,7 @@ Tab completion can also help completing fields: ```julia-repl julia> x = 3 + 4im; -julia> julia> x.[TAB][TAB] +julia> x.[TAB][TAB] im re julia> import UUIDs @@ -556,6 +558,101 @@ ENV["JULIA_WARN_COLOR"] = :yellow ENV["JULIA_INFO_COLOR"] = :cyan ``` + +## Changing the contextual module which is active at the REPL + +When entering expressions at the REPL, they are by default evaluated in the `Main` module; + +```julia-repl +julia> @__MODULE__ +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: + +```julia-repl +julia> using REPL + +julia> REPL.activate(Base) + +(Base) julia> @__MODULE__ +Base + +(Base) julia> using REPL # Need to load REPL into Base module to use it + +(Base) julia> REPL.activate(Main) + +julia> + +julia> Core<Alt-m> # using the keybinding to change module + +(Core) julia> + +(Core) julia> Main<Alt-m> # going back to Main via keybinding + +julia> +``` + +Functions that take an optional module argument often defaults to the REPL +context module. As an example, calling `varinfo()` will show the variables of +the current active module: + +```julia-repl +julia> module CustomMod + export var, f + var = 1 + f(x) = x^2 + end; + +julia> REPL.activate(CustomMod) + +(Main.CustomMod) julia> varinfo() + name size summary + ––––––––– ––––––– –––––––––––––––––––––––––––––––––– + CustomMod Module + f 0 bytes f (generic function with 1 method) + var 8 bytes Int64 +``` + +## Numbered prompt + +It is possible to get an interface which is similar to the IPython REPL and the Mathematica notebook with numbered input prompts and output prefixes. This is done by calling `REPL.numbered_prompt!()`. If you want to have this enabled on startup, add + +```julia +atreplinit() do repl + @eval import REPL + if !isdefined(repl, :interface) + repl.interface = REPL.setup_interface(repl) + end + REPL.numbered_prompt!(repl) +end +``` + +to your `startup.jl` file. In numbered prompt the variable `Out[n]` (where `n` is an integer) can be used to refer to earlier results: + +```julia-repl +In [1]: 5 + 3 +Out[1]: 8 + +In [2]: Out[1] + 5 +Out[2]: 13 + +In [3]: Out +Out[3]: Dict{Int64, Any} with 2 entries: + 2 => 13 + 1 => 8 +``` + +!!! note + Since all outputs from previous REPL evaluations are saved in the `Out` variable, one should be careful if they are returning many + large in-memory objects like arrays, since they will be protected from garbage collection so long as a reference to them remains in + `Out`. If you need to remove references to objects in `Out`, you can clear the entire history it stores with `empty!(Out)`, or clear + an individual entry with `Out[n] = nothing`. + + ## TerminalMenus TerminalMenus is a submodule of the Julia REPL and enables small, low-profile interactive menus in the terminal. @@ -633,7 +730,7 @@ Output: ``` Select the fruits you like: -[press: d=done, a=all, n=none] +[press: Enter=toggle, a=all, n=none, d=done, q=abort] [ ] apple > [X] orange [X] grape @@ -659,7 +756,7 @@ For instance, the default multiple-selection menu julia> menu = MultiSelectMenu(options, pagesize=5); julia> request(menu) # ASCII is used by default -[press: d=done, a=all, n=none] +[press: Enter=toggle, a=all, n=none, d=done, q=abort] [ ] apple [X] orange [ ] grape @@ -673,7 +770,7 @@ can instead be rendered with Unicode selection and navigation characters with julia> menu = MultiSelectMenu(options, pagesize=5, charset=:unicode); julia> request(menu) -[press: d=done, a=all, n=none] +[press: Enter=toggle, a=all, n=none, d=done, q=abort] ⬚ apple ✓ orange ⬚ grape @@ -688,7 +785,7 @@ julia> menu = MultiSelectMenu(options, pagesize=5, charset=:unicode, checked="YE julia> request(menu) julia> request(menu) -[press: d=done, a=all, n=none] +[press: Enter=toggle, a=all, n=none, d=done, q=abort] NOPE apple YEP! orange NOPE grape @@ -728,6 +825,13 @@ Base.atreplinit ### TerminalMenus +### Menus + +```@docs +REPL.TerminalMenus.RadioMenu +REPL.TerminalMenus.MultiSelectMenu +``` + #### Configuration ```@docs diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index cf09cb1966bf1..ff67e849fcc5a 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -12,6 +12,8 @@ import ..Terminals: raw!, width, height, cmove, getX, import Base: ensureroom, show, AnyDict, position using Base: something +using InteractiveUtils: InteractiveUtils + abstract type TextInterface end # see interface immediately below abstract type ModeState end # see interface below abstract type HistoryProvider end @@ -47,6 +49,9 @@ mutable struct Prompt <: TextInterface prompt_prefix::Union{String,Function} # Same as prefix except after the prompt prompt_suffix::Union{String,Function} + output_prefix::Union{String,Function} + output_prefix_prefix::Union{String,Function} + output_prefix_suffix::Union{String,Function} keymap_dict::Dict{Char,Any} repl::Union{AbstractREPL,Nothing} complete::CompletionProvider @@ -61,6 +66,7 @@ show(io::IO, x::Prompt) = show(io, string("Prompt(\"", prompt_string(x.prompt), mutable struct MIState interface::ModalInterface + active_module::Module current_mode::TextInterface aborted::Bool mode_state::IdDict{TextInterface,ModeState} @@ -72,7 +78,7 @@ mutable struct MIState current_action::Symbol end -MIState(i, c, a, m) = MIState(i, c, a, m, String[], 0, Char[], 0, :none, :none) +MIState(i, mod, c, a, m) = MIState(i, mod, c, a, m, String[], 0, Char[], 0, :none, :none) const BufferLike = Union{MIState,ModeState,IOBuffer} const State = Union{MIState,ModeState} @@ -175,6 +181,10 @@ reset_state(::EmptyHistoryProvider) = nothing complete_line(c::EmptyCompletionProvider, s) = String[], "", true +# complete_line can be specialized for only two arguments, when the active module +# doesn't matter (e.g. Pkg does this) +complete_line(c::CompletionProvider, s, ::Module) = complete_line(c, s) + terminal(s::IO) = s terminal(s::PromptState) = s.terminal @@ -313,25 +323,38 @@ function common_prefix(completions::Vector{String}) end end +# This is the maximum number of completions that will be displayed in a single +# column, anything above that and multiple columns will be used. Note that this +# does not restrict column length when multiple columns are used. +const MULTICOLUMN_THRESHOLD = 5 + # Show available completions function show_completions(s::PromptState, completions::Vector{String}) - colmax = maximum(map(length, completions)) - num_cols = max(div(width(terminal(s)), colmax+2), 1) - entries_per_col, r = divrem(length(completions), num_cols) - entries_per_col += r != 0 # skip any lines of input after the cursor cmove_down(terminal(s), input_string_newlines_aftercursor(s)) println(terminal(s)) - for row = 1:entries_per_col - for col = 0:num_cols - idx = row + col*entries_per_col - if idx <= length(completions) - cmove_col(terminal(s), (colmax+2)*col+1) + if any(Base.Fix1(occursin, '\n'), completions) + foreach(Base.Fix1(println, terminal(s)), completions) + else + n = length(completions) + colmax = 2 + maximum(length, completions; init=1) # n.b. length >= textwidth + + num_cols = min(cld(n, MULTICOLUMN_THRESHOLD), + max(div(width(terminal(s)), colmax), 1)) + + entries_per_col = cld(n, num_cols) + idx = 0 + for _ in 1:entries_per_col + for col = 0:(num_cols-1) + idx += 1 + idx > n && break + cmove_col(terminal(s), colmax*col+1) print(terminal(s), completions[idx]) end + println(terminal(s)) end - println(terminal(s)) end + # make space for the prompt for i = 1:input_string_newlines(s) println(terminal(s)) @@ -341,7 +364,7 @@ end # Prompt Completions function complete_line(s::MIState) set_action!(s, :complete_line) - if complete_line(state(s), s.key_repeats) + if complete_line(state(s), s.key_repeats, s.active_module) return refresh_line(s) else beep(s) @@ -349,8 +372,8 @@ function complete_line(s::MIState) end end -function complete_line(s::PromptState, repeats::Int) - completions, partial, should_complete = complete_line(s.p.complete, s)::Tuple{Vector{String},String,Bool} +function complete_line(s::PromptState, repeats::Int, mod::Module) + completions, partial, should_complete = complete_line(s.p.complete, s, mod)::Tuple{Vector{String},String,Bool} isempty(completions) && return false if !should_complete # should_complete is false for cases where we only want to show @@ -437,7 +460,7 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf # Write out the prompt string lindent = write_prompt(termbuf, prompt, hascolor(terminal))::Int # Count the '\n' at the end of the line if the terminal emulator does (specific to DOS cmd prompt) - miscountnl = @static Sys.iswindows() ? (isa(Terminals.pipe_reader(terminal), Base.TTY) && !Base.ispty(Terminals.pipe_reader(terminal))) : false + miscountnl = @static Sys.iswindows() ? (isa(Terminals.pipe_reader(terminal), Base.TTY) && !(Base.ispty(Terminals.pipe_reader(terminal)))::Bool) : false # Now go through the buffer line by line seek(buf, 0) @@ -746,10 +769,11 @@ function edit_splice!(s::BufferLike, r::Region=region(s), ins::String = ""; rigi elseif buf.mark >= B buf.mark += sizeof(ins) - B + A 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(ret) + return String(copy(ret)) end edit_splice!(s::MIState, ins::AbstractString) = edit_splice!(s, region(s), ins) @@ -1085,8 +1109,9 @@ function edit_transpose_chars(s::MIState) end function edit_transpose_chars(buf::IOBuffer) + # Moving left but not transpoing anything is intentional, and matches Emacs's behavior + eof(buf) && position(buf) !== 0 && char_move_left(buf) position(buf) == 0 && return false - eof(buf) && char_move_left(buf) char_move_left(buf) pos = position(buf) a, b = read(buf, Char), read(buf, Char) @@ -1265,7 +1290,7 @@ end # compute the number of spaces from b till the next non-space on the right # (which can also be "end of line" or "end of buffer") function leadingspaces(buf::IOBuffer, b::Int) - ls = something(findnext(_notspace, buf.data, b+1), 0)-1 + @views ls = something(findnext(_notspace, buf.data[1:buf.size], b+1), 0)-1 ls == -1 && (ls = buf.size) ls -= b return ls @@ -1295,6 +1320,112 @@ _edit_indent(buf::IOBuffer, b::Int, num::Int) = num >= 0 ? edit_splice!(buf, b => b, ' '^num, rigid_mark=false) : edit_splice!(buf, b => (b - num)) +function mode_idx(hist::HistoryProvider, mode::TextInterface) + c = :julia + for (k,v) in hist.mode_mapping + isequal(v, mode) && (c = k) + end + return c +end + +function guess_current_mode_name(s) + try + mode_idx(s.current_mode.hist, s.current_mode) + catch + nothing + end +end + +# edit current input in editor +function edit_input(s, f = (filename, line, column) -> InteractiveUtils.edit(filename, line, column)) + mode_name = guess_current_mode_name(s) + filename = tempname() + if mode_name === :julia + filename *= ".jl" + elseif mode_name === :shell + filename *= ".sh" + end + buf = buffer(s) + pos = position(buf) + str = String(take!(buf)) + lines = readlines(IOBuffer(str); keep=true) + + # Compute line + line_start_offset = 0 + line = 1 + while line < length(lines) && line_start_offset + sizeof(lines[line]) <= pos + line_start_offset += sizeof(lines[line]) + line += 1 + end + + # Compute column + col = 0 + off = line_start_offset + while off <= pos + off = nextind(str, off) + col += 1 + end + + # Write current input to temp file, edit, read back + write(filename, str) + f(filename, line, col) + str_mod = readchomp(filename) + rm(filename) + + # Write updated content + write(buf, str_mod) + if str == str_mod + # If input was not modified: reset cursor + seek(buf, pos) + else + # If input was modified: move cursor to end + move_input_end(s) + end + refresh_line(s) +end + +# return the identifier under the cursor, possibly with other words concatenated +# to it with dots (e.g. "A.B.C" in "X; A.B.C*3", if the cursor is between "A" and "C") +function current_word_with_dots(buf::IOBuffer) + pos = position(buf) + while true + char_move_word_right(buf) + if eof(buf) || peek(buf, Char) != '.' + break + end + end + pend = position(buf) + while true + char_move_word_left(buf) + p = position(buf) + p == 0 && break + seek(buf, p-1) + if peek(buf, Char) != '.' + seek(buf, p) + break + end + end + pbegin = position(buf) + word = pend > pbegin ? + String(buf.data[pbegin+1:pend]) : + "" + seek(buf, pos) + word +end + +current_word_with_dots(s::MIState) = current_word_with_dots(buffer(s)) + +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 + beep(s) + end +end history_prev(::EmptyHistoryProvider) = ("", false) history_next(::EmptyHistoryProvider) = ("", false) @@ -1336,7 +1467,6 @@ default_completion_cb(::IOBuffer) = [] default_enter_cb(_) = true write_prompt(terminal::AbstractTerminal, s::PromptState, color::Bool) = write_prompt(terminal, s.p, color) - function write_prompt(terminal::AbstractTerminal, p::Prompt, color::Bool) prefix = prompt_string(p.prompt_prefix) suffix = prompt_string(p.prompt_suffix) @@ -1348,6 +1478,17 @@ function write_prompt(terminal::AbstractTerminal, p::Prompt, color::Bool) return width end +function write_output_prefix(io::IO, p::Prompt, color::Bool) + prefix = prompt_string(p.output_prefix_prefix) + suffix = prompt_string(p.output_prefix_suffix) + print(io, prefix) + color && write(io, Base.text_colors[:bold]) + width = write_prompt(io, p.output_prefix, color) + color && write(io, Base.text_colors[:normal]) + print(io, suffix) + return width +end + # On Windows, when launching external processes, we cannot control what assumption they make on the # console mode. We thus forcibly reset the console mode at the start of the prompt to ensure they do # not leave the console mode in a corrupt state. @@ -1379,7 +1520,7 @@ end end # returns the width of the written prompt -function write_prompt(terminal, s::Union{AbstractString,Function}, color::Bool) +function write_prompt(terminal::Union{IO, AbstractTerminal}, s::Union{AbstractString,Function}, color::Bool) @static Sys.iswindows() && _reset_console_mode() promptstr = prompt_string(s)::String write(terminal, promptstr) @@ -1437,7 +1578,7 @@ function normalize_keys(keymap::Union{Dict{Char,Any},AnyDict}) return ret end -function add_nested_key!(keymap::Dict, key::Union{String, Char}, value; override = false) +function add_nested_key!(keymap::Dict{Char, Any}, key::Union{String, Char}, value; override::Bool = false) y = iterate(key) while y !== nothing c, i = y @@ -1452,7 +1593,7 @@ function add_nested_key!(keymap::Dict, key::Union{String, Char}, value; override elseif !(c in keys(keymap) && isa(keymap[c], Dict)) keymap[c] = Dict{Char,Any}() end - keymap = keymap[c] + keymap = keymap[c]::Dict{Char, Any} end end @@ -1597,7 +1738,7 @@ end function getEntry(keymap::Dict{Char,Any},key::Union{String,Char}) v = keymap for c in key - if !haskey(v,c) + if !(haskey(v,c)::Bool) return nothing end v = v[c] @@ -1646,7 +1787,7 @@ end throw_eager_redirection_cycle(key::Union{Char, String}) = error("Eager redirection cycle detected for key ", repr(key)) throw_could_not_find_redirected_value(key::Union{Char, String}) = - error("Could not find redirected value ", repl(key)) + error("Could not find redirected value ", repr(key)) function keymap_unify(keymaps) ret = Dict{Char,Any}() @@ -1917,8 +2058,8 @@ setmodifiers!(p::Prompt, m::Modifiers) = setmodifiers!(p.complete, m) setmodifiers!(c) = nothing # Search Mode completions -function complete_line(s::SearchState, repeats) - completions, partial, should_complete = complete_line(s.histprompt.complete, s) +function complete_line(s::SearchState, repeats, mod::Module) + completions, partial, should_complete = complete_line(s.histprompt.complete, s, mod) # For now only allow exact completions in search mode if length(completions) == 1 prev_pos = position(s) @@ -2106,7 +2247,7 @@ end function move_line_end(buf::IOBuffer) eof(buf) && return - pos = findnext(isequal(UInt8('\n')), buf.data, position(buf)+1) + @views pos = findnext(isequal(UInt8('\n')), buf.data[1:buf.size], position(buf)+1) if pos === nothing move_input_end(buf) return @@ -2337,6 +2478,8 @@ AnyDict( "\eu" => (s::MIState,o...)->edit_upper_case(s), "\el" => (s::MIState,o...)->edit_lower_case(s), "\ec" => (s::MIState,o...)->edit_title_case(s), + "\ee" => (s::MIState,o...) -> edit_input(s), + "\em" => (s::MIState, o...) -> activate_module(s) ) const history_keymap = AnyDict( @@ -2373,6 +2516,7 @@ const prefix_history_keymap = merge!( end, # match escape sequences for pass through "^x*" => "*", + "\em*" => "*", "\e*" => "*", "\e[*" => "*", "\eO*" => "*", @@ -2472,6 +2616,9 @@ function Prompt(prompt ; prompt_prefix = "", prompt_suffix = "", + output_prefix = "", + output_prefix_prefix = "", + output_prefix_suffix = "", keymap_dict = default_keymap_dict, repl = nothing, complete = EmptyCompletionProvider(), @@ -2480,8 +2627,8 @@ function Prompt(prompt hist = EmptyHistoryProvider(), sticky = false) - return Prompt(prompt, prompt_prefix, prompt_suffix, keymap_dict, repl, - complete, on_enter, on_done, hist, sticky) + return Prompt(prompt, prompt_prefix, prompt_suffix, output_prefix, output_prefix_prefix, output_prefix_suffix, + keymap_dict, repl, complete, on_enter, on_done, hist, sticky) end run_interface(::Prompt) = nothing @@ -2491,7 +2638,7 @@ init_state(terminal, prompt::Prompt) = #=indent(spaces)=# -1, Threads.SpinLock(), 0.0, -Inf, nothing) function init_state(terminal, m::ModalInterface) - s = MIState(m, m.modes[1], false, IdDict{Any,Any}()) + s = MIState(m, Main, m.modes[1], false, IdDict{Any,Any}()) for mode in m.modes s.mode_state[mode] = init_state(terminal, mode) end diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 3308760046d4e..f8bb442ad6ec4 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -3,17 +3,19 @@ """ Run Evaluate Print Loop (REPL) - Example minimal code - ``` - import REPL - term = REPL.Terminals.TTYTerminal("dumb", stdin, stdout, stderr) - repl = REPL.LineEditREPL(term, true) - REPL.run_repl(repl) - ``` +Example minimal code + +```julia +import REPL +term = REPL.Terminals.TTYTerminal("dumb", stdin, stdout, stderr) +repl = REPL.LineEditREPL(term, true) +REPL.run_repl(repl) +``` """ module REPL Base.Experimental.@optlevel 1 +Base.Experimental.@max_methods 1 using Base.Meta, Sockets import InteractiveUtils @@ -59,7 +61,8 @@ import ..LineEdit: terminal, MIState, PromptState, - TextInterface + TextInterface, + mode_idx include("REPLCompletions.jl") using .REPLCompletions @@ -69,10 +72,6 @@ include("docview.jl") @nospecialize # use only declared type signatures -function __init__() - Base.REPL_MODULE_REF[] = REPL -end - answer_color(::AbstractREPL) = "" const JULIA_PROMPT = "julia> " @@ -132,7 +131,7 @@ const repl_ast_transforms = Any[softscope] # defaults for new REPL backends # to e.g. install packages on demand const install_packages_hooks = Any[] -function eval_user_input(@nospecialize(ast), backend::REPLBackend) +function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module) lasterr = nothing Base.sigatomic_begin() while true @@ -148,10 +147,9 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend) for xf in backend.ast_transforms ast = Base.invokelatest(xf, ast) end - value = Core.eval(Main, ast) + value = Core.eval(mod, ast) backend.in_eval = false - # note: use jl_set_global to make sure value isn't passed through `expand` - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, value) + setglobal!(Base.MainInclude, :ans, value) put!(backend.response_channel, Pair{Any, Bool}(value, false)) end break @@ -179,8 +177,8 @@ function check_for_missing_packages_and_run_hooks(ast) end function modules_to_be_loaded(ast::Expr, mods::Vector{Symbol} = Symbol[]) - ast.head == :quote && return mods # don't search if it's not going to be run during this eval - if ast.head in [:using, :import] + 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 arg = arg::Expr arg1 = first(arg.args) @@ -210,11 +208,12 @@ end Deprecated since sync / async behavior cannot be selected """ -function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any}) +function start_repl_backend(repl_channel::Channel{Any}, response_channel::Channel{Any} + ; get_module::Function = ()->Main) # Maintain legacy behavior of asynchronous backend backend = REPLBackend(repl_channel, response_channel, false) # Assignment will be made twice, but will be immediately available - backend.backend_task = @async start_repl_backend(backend) + backend.backend_task = @async start_repl_backend(backend; get_module) return backend end @@ -226,14 +225,14 @@ end Does not return backend until loop is finished. """ -function start_repl_backend(backend::REPLBackend, @nospecialize(consumer = x -> nothing)) +function start_repl_backend(backend::REPLBackend, @nospecialize(consumer = x -> nothing); get_module::Function = ()->Main) backend.backend_task = Base.current_task() consumer(backend) - repl_backend_loop(backend) + repl_backend_loop(backend, get_module) return backend end -function repl_backend_loop(backend::REPLBackend) +function repl_backend_loop(backend::REPLBackend, get_module::Function) # include looks at this to determine the relative include path # nothing means cwd while true @@ -244,7 +243,7 @@ function repl_backend_loop(backend::REPLBackend) # exit flag break end - eval_user_input(ast, backend) + eval_user_input(ast, backend, get_module()) end return nothing end @@ -258,8 +257,15 @@ end function display(d::REPLDisplay, mime::MIME"text/plain", x) x = Ref{Any}(x) with_repl_linfo(d.repl) do io - io = IOContext(io, :limit => true, :module => Main::Module) - get(io, :color, false) && write(io, answer_color(d.repl)) + io = IOContext(io, :limit => true, :module => active_module(d)::Module) + if d.repl isa LineEditREPL + mistate = d.repl.mistate + mode = LineEdit.mode(mistate) + if mode isa LineEdit.Prompt + LineEdit.write_output_prefix(io, mode, get(io, :color, false)::Bool) + end + end + get(io, :color, false)::Bool && write(io, answer_color(d.repl)) if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext) # this can override the :limit property set initially io = foldl(IOContext, d.repl.options.iocontext, init=io) @@ -274,7 +280,7 @@ display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x) function print_response(repl::AbstractREPL, response, show_value::Bool, have_color::Bool) repl.waserror = response[2] with_repl_linfo(repl) do io - io = IOContext(io, :module => Main::Module) + io = IOContext(io, :module => active_module(repl)::Module) print_response(io, response, show_value, have_color, specialdisplay(repl)) end return nothing @@ -287,7 +293,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, Base.sigatomic_end() if iserr val = Base.scrub_repl_backtrace(val) - Base.istrivialerror(val) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val) + Base.istrivialerror(val) || setglobal!(Base.MainInclude, :err, val) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value @@ -304,13 +310,13 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, end end break - catch + catch ex if iserr println(errio) # an error during printing is likely to leave us mid-line println(errio, "SYSTEM (REPL): showing an error caused an error") try excs = Base.scrub_repl_backtrace(current_exceptions()) - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, excs) + setglobal!(Base.MainInclude, :err, excs) Base.invokelatest(Base.display_error, errio, excs) catch e # at this point, only print the name of the type as a Symbol to @@ -335,6 +341,7 @@ struct REPLBackendRef response_channel::Channel{Any} end REPLBackendRef(backend::REPLBackend) = REPLBackendRef(backend.repl_channel, backend.response_channel) + function destroy(ref::REPLBackendRef, state::Task) if istaskfailed(state) close(ref.repl_channel, TaskFailedException(state)) @@ -352,8 +359,7 @@ end consumer is an optional function that takes a REPLBackend as an argument """ -function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true) - backend = REPLBackend() +function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); backend_on_current_task::Bool = true, backend = REPLBackend()) backend_ref = REPLBackendRef(backend) cleanup = @task try destroy(backend_ref, t) @@ -362,13 +368,14 @@ function run_repl(repl::AbstractREPL, @nospecialize(consumer = x -> nothing); ba Core.println(Core.stderr, e) Core.println(Core.stderr, catch_backtrace()) end + get_module = () -> active_module(repl) if backend_on_current_task t = @async run_frontend(repl, backend_ref) errormonitor(t) Base._wait2(t, cleanup) - start_repl_backend(backend, consumer) + start_repl_backend(backend, consumer; get_module) else - t = @async start_repl_backend(backend, consumer) + t = @async start_repl_backend(backend, consumer; get_module) errormonitor(t) Base._wait2(t, cleanup) run_frontend(repl, backend_ref) @@ -381,6 +388,7 @@ end mutable struct BasicREPL <: AbstractREPL terminal::TextTerminal waserror::Bool + frontend_task::Task BasicREPL(t) = new(t, false) end @@ -388,6 +396,7 @@ outstream(r::BasicREPL) = r.terminal hascolor(r::BasicREPL) = hascolor(r.terminal) function run_frontend(repl::BasicREPL, backend::REPLBackendRef) + repl.frontend_task = current_task() d = REPLDisplay(repl) dopushdisplay = !in(d,Base.Multimedia.displays) dopushdisplay && pushdisplay(d) @@ -454,6 +463,7 @@ mutable struct LineEditREPL <: AbstractREPL last_shown_line_infos::Vector{Tuple{String,Int}} interface::ModalInterface backendref::REPLBackendRef + frontend_task::Task function LineEditREPL(t,hascolor,prompt_color,input_color,answer_color,shell_color,help_color,history_file,in_shell,in_help,envcolors) opts = Options() opts.hascolor = hascolor @@ -464,7 +474,7 @@ mutable struct LineEditREPL <: AbstractREPL in_help,envcolors,false,nothing, opts, nothing, Tuple{String,Int}[]) end end -outstream(r::LineEditREPL) = r.t isa TTYTerminal ? r.t.out_stream : r.t +outstream(r::LineEditREPL) = (t = r.t; t isa TTYTerminal ? t.out_stream : t) specialdisplay(r::LineEditREPL) = r.specialdisplay specialdisplay(r::AbstractREPL) = nothing terminal(r::LineEditREPL) = r.t @@ -484,17 +494,40 @@ mutable struct REPLCompletionProvider <: CompletionProvider modifiers::LineEdit.Modifiers end REPLCompletionProvider() = REPLCompletionProvider(LineEdit.Modifiers()) + mutable struct ShellCompletionProvider <: CompletionProvider end struct LatexCompletions <: CompletionProvider end +function active_module() # this method is also called from Base + isdefined(Base, :active_repl) || return Main + return active_module(Base.active_repl::AbstractREPL) +end +active_module((; mistate)::LineEditREPL) = mistate === nothing ? Main : mistate.active_module +active_module(::AbstractREPL) = Main +active_module(d::REPLDisplay) = active_module(d.repl) + setmodifiers!(c::REPLCompletionProvider, m::LineEdit.Modifiers) = c.modifiers = m +""" + activate(mod::Module=Main) + +Set `mod` as the default contextual module in the REPL, +both for evaluating expressions and printing them. +""" +function activate(mod::Module=Main) + mistate = (Base.active_repl::LineEditREPL).mistate + mistate === nothing && return nothing + mistate.active_module = mod + Base.load_InteractiveUtils(mod) + return nothing +end + beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1]) -function complete_line(c::REPLCompletionProvider, s::PromptState) +function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module) partial = beforecursor(s.input_buffer) full = LineEdit.input_string(s) - ret, range, should_complete = completions(full, lastindex(partial), Main, c.modifiers.shift) + ret, range, should_complete = completions(full, lastindex(partial), mod, c.modifiers.shift) c.modifiers = LineEdit.Modifiers() return unique!(map(completion_text, ret)), partial[range], should_complete end @@ -527,6 +560,7 @@ end mutable struct REPLHistoryProvider <: HistoryProvider history::Vector{String} + file_path::String history_file::Union{Nothing,IO} start_idx::Int cur_idx::Int @@ -537,7 +571,7 @@ mutable struct REPLHistoryProvider <: HistoryProvider modes::Vector{Symbol} end REPLHistoryProvider(mode_mapping::Dict{Symbol}) = - REPLHistoryProvider(String[], nothing, 0, 0, -1, IOBuffer(), + REPLHistoryProvider(String[], "", nothing, 0, 0, -1, IOBuffer(), nothing, mode_mapping, UInt8[]) invalid_history_message(path::String) = """ @@ -550,6 +584,12 @@ munged_history_message(path::String) = """ Invalid history file ($path) format: An editor may have converted tabs to spaces at line """ +function hist_open_file(hp::REPLHistoryProvider) + f = open(hp.file_path, read=true, write=true, create=true) + hp.history_file = f + seekend(f) +end + function hist_from_file(hp::REPLHistoryProvider, path::String) getline(lines, i) = i > length(lines) ? "" : lines[i] file_lines = readlines(path) @@ -595,14 +635,6 @@ function hist_from_file(hp::REPLHistoryProvider, path::String) return hp end -function mode_idx(hist::REPLHistoryProvider, mode::TextInterface) - c = :julia - for (k,v) in hist.mode_mapping - isequal(v, mode) && (c = k) - end - return c -end - function add_history(hist::REPLHistoryProvider, s::PromptState) str = rstrip(String(take!(copy(s.input_buffer)))) isempty(strip(str)) && return @@ -618,7 +650,14 @@ function add_history(hist::REPLHistoryProvider, s::PromptState) $(replace(str, r"^"ms => "\t")) """ # TODO: write-lock history file - seekend(hist.history_file) + try + seekend(hist.history_file) + catch err + (err isa SystemError) || rethrow() + # File handle might get stale after a while, especially under network file systems + # If this doesn't fix it (e.g. when file is deleted), we'll end up rethrowing anyway + hist_open_file(hist) + end print(hist.history_file, entry) flush(hist.history_file) nothing @@ -733,7 +772,7 @@ function history_move_prefix(s::LineEdit.PrefixSearchState, max_idx = length(hist.history)+1 idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx) for idx in idxs - if (idx == max_idx) || (startswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || hist.modes[idx] != LineEdit.mode(s))) + if (idx == max_idx) || (startswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || get(hist.mode_mapping, hist.modes[idx], nothing) !== LineEdit.mode(s))) m = history_move(s, hist, idx) if m === :ok if idx == max_idx @@ -900,6 +939,15 @@ repl_filename(repl, hp) = "REPL" const JL_PROMPT_PASTE = Ref(true) enable_promptpaste(v::Bool) = JL_PROMPT_PASTE[] = v +function contextual_prompt(repl::LineEditREPL, prompt::Union{String,Function}) + function () + mod = active_module(repl) + prefix = mod == Main ? "" : string('(', mod, ") ") + pr = prompt isa String ? prompt : prompt() + prefix * pr + end +end + setup_interface( repl::LineEditREPL; # those keyword arguments may be deprecated eventually in favor of the Options mechanism @@ -944,7 +992,7 @@ function setup_interface( replc = REPLCompletionProvider() # Set up the main Julia prompt - julia_prompt = Prompt(JULIA_PROMPT; + julia_prompt = Prompt(contextual_prompt(repl, JULIA_PROMPT); # Copy colors from the prompt object prompt_prefix = hascolor ? repl.prompt_color : "", prompt_suffix = hascolor ? @@ -954,15 +1002,15 @@ function setup_interface( on_enter = return_callback) # Setup help mode - help_mode = Prompt(HELP_PROMPT, + help_mode = Prompt(contextual_prompt(repl, "help?> "), prompt_prefix = hascolor ? repl.help_color : "", prompt_suffix = hascolor ? (repl.envcolors ? Base.input_color : repl.input_color) : "", repl = repl, complete = replc, # When we're done transform the entered line into a call to helpmode function - on_done = respond(line::String->helpmode(outstream(repl), line), repl, julia_prompt, - pass_empty=true, suppress_on_semicolon=false)) + on_done = respond(line::String->helpmode(outstream(repl), line, repl.mistate.active_module), + repl, julia_prompt, pass_empty=true, suppress_on_semicolon=false)) # Set up shell mode @@ -994,11 +1042,10 @@ function setup_interface( try hist_path = find_hist_file() mkpath(dirname(hist_path)) - f = open(hist_path, read=true, write=true, create=true) - hp.history_file = f - seekend(f) + hp.file_path = hist_path + hist_open_file(hp) finalizer(replc) do replc - close(f) + close(hp.history_file) end hist_from_file(hp, hist_path) catch @@ -1020,10 +1067,9 @@ function setup_interface( search_prompt, skeymap = LineEdit.setup_search_keymap(hp) search_prompt.complete = LatexCompletions() - jl_prompt_len = length(JULIA_PROMPT) - pkg_prompt_len = length(PKG_PROMPT) shell_prompt_len = length(SHELL_PROMPT) help_prompt_len = length(HELP_PROMPT) + jl_prompt_regex = r"^In \[[0-9]+\]: |^(?:\(.+\) )?julia> " pkg_prompt_regex = r"^(?:\(.+\) )?pkg> " # Canonicalize user keymap input @@ -1090,30 +1136,32 @@ function setup_interface( oldpos = nextind(input, oldpos) oldpos >= sizeof(input) && return end + substr = SubString(input, oldpos) # Check if input line starts with "julia> ", remove it if we are in prompt paste mode - if (firstline || isprompt_paste) && startswith(SubString(input, oldpos), JULIA_PROMPT) + if (firstline || isprompt_paste) && startswith(substr, jl_prompt_regex) + detected_jl_prompt = match(jl_prompt_regex, substr).match isprompt_paste = true - oldpos += jl_prompt_len - curr_prompt_len = jl_prompt_len + curr_prompt_len = sizeof(detected_jl_prompt) + oldpos += curr_prompt_len transition(s, julia_prompt) pasting_help = false # Check if input line starts with "pkg> " or "(...) pkg> ", remove it if we are in prompt paste mode and switch mode - elseif (firstline || isprompt_paste) && startswith(SubString(input, oldpos), pkg_prompt_regex) - detected_pkg_prompt = match(pkg_prompt_regex, SubString(input, oldpos)).match + elseif (firstline || isprompt_paste) && startswith(substr, pkg_prompt_regex) + detected_pkg_prompt = match(pkg_prompt_regex, substr).match isprompt_paste = true curr_prompt_len = sizeof(detected_pkg_prompt) oldpos += curr_prompt_len Base.active_repl.interface.modes[1].keymap_dict[']'](s, o...) pasting_help = false # Check if input line starts with "shell> ", remove it if we are in prompt paste mode and switch mode - elseif (firstline || isprompt_paste) && startswith(SubString(input, oldpos), SHELL_PROMPT) + elseif (firstline || isprompt_paste) && startswith(substr, SHELL_PROMPT) isprompt_paste = true oldpos += shell_prompt_len curr_prompt_len = shell_prompt_len transition(s, shell_mode) pasting_help = false # Check if input line starts with "help?> ", remove it if we are in prompt paste mode and switch mode - elseif (firstline || isprompt_paste) && startswith(SubString(input, oldpos), HELP_PROMPT) + elseif (firstline || isprompt_paste) && startswith(substr, HELP_PROMPT) isprompt_paste = true oldpos += help_prompt_len curr_prompt_len = help_prompt_len @@ -1203,7 +1251,7 @@ function setup_interface( @goto writeback end try - InteractiveUtils.edit(linfos[n][1], linfos[n][2]) + InteractiveUtils.edit(Base.fixup_stdlib_path(linfos[n][1]), linfos[n][2]) catch ex ex isa ProcessFailedException || ex isa Base.IOError || ex isa SystemError || rethrow() @info "edit failed" _exception=ex @@ -1235,6 +1283,7 @@ function setup_interface( end function run_frontend(repl::LineEditREPL, backend::REPLBackendRef) + repl.frontend_task = current_task() d = REPLDisplay(repl) dopushdisplay = repl.specialdisplay === nothing && !in(d,Base.Multimedia.displays) dopushdisplay && pushdisplay(d) @@ -1260,6 +1309,7 @@ mutable struct StreamREPL <: AbstractREPL input_color::String answer_color::String waserror::Bool + frontend_task::Task StreamREPL(stream,pc,ic,ac) = new(stream,pc,ic,ac,false) end StreamREPL(stream::IO) = StreamREPL(stream, Base.text_colors[:green], Base.input_color(), Base.answer_color()) @@ -1318,6 +1368,7 @@ ends_with_semicolon(code::Union{String,SubString{String}}) = contains(_rm_strings_and_comments(code), r";\s*$") function run_frontend(repl::StreamREPL, backend::REPLBackendRef) + repl.frontend_task = current_task() have_color = hascolor(repl) Base.banner(repl.stream) d = REPLDisplay(repl) @@ -1347,4 +1398,98 @@ function run_frontend(repl::StreamREPL, backend::REPLBackendRef) nothing end +module Numbered + +using ..REPL + +__current_ast_transforms() = isdefined(Base, :active_repl_backend) ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms + +function repl_eval_counter(hp) + return length(hp.history) - hp.start_idx +end + +function out_transform(@nospecialize(x), n::Ref{Int}) + return Expr(:toplevel, get_usings!([], x)..., quote + let __temp_val_a72df459 = $x + $capture_result($n, __temp_val_a72df459) + __temp_val_a72df459 + end + end) +end + +function get_usings!(usings, ex) + # get all `using` and `import` statements which are at the top level + for (i, arg) in enumerate(ex.args) + if Base.isexpr(arg, :toplevel) + get_usings!(usings, arg) + elseif Base.isexpr(arg, [:using, :import]) + push!(usings, popat!(ex.args, i)) + end + end + return usings +end + +function capture_result(n::Ref{Int}, @nospecialize(x)) + n = n[] + mod = Base.MainInclude + if !isdefined(mod, :Out) + @eval mod global Out + @eval mod export Out + setglobal!(mod, :Out, Dict{Int, Any}()) + end + if x !== getglobal(mod, :Out) && x !== nothing # remove this? + getglobal(mod, :Out)[n] = x + end + nothing +end + +function set_prompt(repl::LineEditREPL, n::Ref{Int}) + julia_prompt = repl.interface.modes[1] + julia_prompt.prompt = function() + n[] = repl_eval_counter(julia_prompt.hist)+1 + string("In [", n[], "]: ") + end + nothing +end + +function set_output_prefix(repl::LineEditREPL, n::Ref{Int}) + julia_prompt = repl.interface.modes[1] + if REPL.hascolor(repl) + julia_prompt.output_prefix_prefix = Base.text_colors[:red] + end + julia_prompt.output_prefix = () -> string("Out[", n[], "]: ") + nothing +end + +function __current_ast_transforms(backend) + if backend === nothing + isdefined(Base, :active_repl_backend) ? Base.active_repl_backend.ast_transforms : REPL.repl_ast_transforms + else + backend.ast_transforms + end +end + + +function numbered_prompt!(repl::LineEditREPL=Base.active_repl, backend=nothing) + n = Ref{Int}(0) + set_prompt(repl, n) + set_output_prefix(repl, n) + push!(__current_ast_transforms(backend), @nospecialize(ast) -> out_transform(ast, n)) + return +end + +""" + Out[n] + +A variable referring to all previously computed values, automatically imported to the interactive prompt. +Only defined and exists while using [Numbered prompt](@ref Numbered-prompt). + +See also [`ans`](@ref). +""" +Base.MainInclude.Out + +end + +import .Numbered.numbered_prompt! + end # module diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 162d1184d18c3..e09e3b2aa9e6b 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -4,6 +4,8 @@ module REPLCompletions export completions, shell_completions, bslash_completions, completion_text +using Core: CodeInfo, MethodInstance, CodeInstance, Const +const CC = Core.Compiler using Base.Meta using Base: propertynames, something @@ -59,6 +61,10 @@ struct DictCompletion <: Completion key::String end +struct KeywordArgumentCompletion <: Completion + kwarg::String +end + # interface definition function Base.getproperty(c::Completion, name::Symbol) if name === :text @@ -85,6 +91,8 @@ function Base.getproperty(c::Completion, name::Symbol) return getfield(c, :text)::String elseif name === :key return getfield(c, :key)::String + elseif name === :kwarg + return getfield(c, :kwarg)::String end return getfield(c, name) end @@ -94,12 +102,13 @@ _completion_text(c::KeywordCompletion) = c.keyword _completion_text(c::PathCompletion) = c.path _completion_text(c::ModuleCompletion) = c.mod _completion_text(c::PackageCompletion) = c.package -_completion_text(c::PropertyCompletion) = string(c.property) -_completion_text(c::FieldCompletion) = string(c.field) +_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property) +_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field) _completion_text(c::MethodCompletion) = repr(c.method) _completion_text(c::BslashCompletion) = c.bslash _completion_text(c::ShellCompletion) = c.text _completion_text(c::DictCompletion) = c.key +_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'=' completion_text(c) = _completion_text(c)::String @@ -110,7 +119,8 @@ function completes_global(x, name) end function appendmacro!(syms, macros, needle, endchar) - for s in macros + for macsym in macros + s = String(macsym) if endswith(s, needle) from = nextind(s, firstindex(s)) to = prevind(s, sizeof(s)-sizeof(needle)+1) @@ -122,43 +132,36 @@ end function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, all::Bool = false, imported::Bool = false) ssyms = names(mod, all = all, imported = imported) filter!(ffunc, ssyms) - syms = String[string(s) for s in ssyms] - macros = filter(x -> startswith(x, "@" * name), syms) + macros = filter(x -> startswith(String(x), "@" * name), ssyms) + syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)] appendmacro!(syms, macros, "_str", "\"") appendmacro!(syms, macros, "_cmd", "`") - filter!(x->completes_global(x, name), syms) return [ModuleCompletion(mod, sym) for sym in syms] end # REPL Symbol Completions -function complete_symbol(sym::String, @nospecialize(ffunc), context_module::Module=Main) +function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), context_module::Module=Main) mod = context_module - name = sym lookup_module = true t = Union{} val = nothing - if something(findlast(in(non_identifier_chars), sym), 0) < something(findlast(isequal('.'), sym), 0) - # Find module - lookup_name, name = rsplit(sym, ".", limit=2) - - ex = Meta.parse(lookup_name, raise=false, depwarn=false) - - b, found = get_value(ex, context_module) - if found - val = b - if isa(b, Module) - mod = b + if ex !== nothing + res = repl_eval_ex(ex, context_module) + res === nothing && return Completion[] + if res isa Const + val = res.val + if isa(val, Module) + mod = val lookup_module = true else lookup_module = false - t = typeof(b) + t = typeof(val) end - else # If the value is not found using get_value, the expression contain an advanced expression + else lookup_module = false - t, found = get_type(ex, context_module) + t = CC.widenconst(res) end - found || return Completion[] end suggestions = Completion[] @@ -191,13 +194,14 @@ function complete_symbol(sym::String, @nospecialize(ffunc), context_module::Modu # Looking for a member of a type if t isa DataType && t != Any # Check for cases like Type{typeof(+)} - if t isa DataType && t.name === Base._TYPE_NAME + if Base.isType(t) t = typeof(t.parameters[1]) end # Only look for fields if this is a concrete type if isconcretetype(t) fields = fieldnames(t) for field in fields + isa(field, Symbol) || continue # Tuple type has ::Int field name s = string(field) if startswith(s, name) push!(suggestions, FieldCompletion(t, field)) @@ -312,24 +316,13 @@ function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_ end function complete_expanduser(path::AbstractString, r) - expanded = expanduser(path) - return Completion[PathCompletion(expanded)], r, path != expanded -end - -# Determines whether method_complete should be tried. It should only be done if -# the string endswiths ',' or '(' when disregarding whitespace_chars -function should_method_complete(s::AbstractString) - method_complete = false - for c in reverse(s) - if c in [',', '(', ';'] - method_complete = true - break - elseif !(c in whitespace_chars) - method_complete = false - break + expanded = + try expanduser(path) + catch e + e isa ArgumentError || rethrow() + path end - end - method_complete + return Completion[PathCompletion(expanded)], r, path != expanded end # Returns a range that includes the method name in front of the first non @@ -407,161 +400,233 @@ function find_start_brace(s::AbstractString; c_start='(', c_end=')') return (startind:lastindex(s), method_name_end) end -# Returns the value in a expression if sym is defined in current namespace fn. -# This method is used to iterate to the value of a expression like: -# :(REPL.REPLCompletions.whitespace_chars) a `dump` of this expression -# will show it consist of Expr, QuoteNode's and Symbol's which all needs to -# be handled differently to iterate down to get the value of whitespace_chars. -function get_value(sym::Expr, fn) - if sym.head === :quote || sym.head === :inert - return sym.args[1], true - end - sym.head !== :. && return (nothing, false) - for ex in sym.args - ex, found = get_value(ex, fn) - !found && return (nothing, false) - fn, found = get_value(ex, fn) - !found && return (nothing, false) - end - return (fn, true) -end -get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (getfield(fn, sym), true) : (nothing, false) -get_value(sym::QuoteNode, fn) = (sym.value, true) -get_value(sym::GlobalRef, fn) = get_value(sym.name, sym.mod) -get_value(sym, fn) = (sym, true) - -# Return the type of a getfield call expression -function get_type_getfield(ex::Expr, fn::Module) - length(ex.args) == 3 || return Any, false # should never happen, but just for safety - fld, found = get_value(ex.args[3], fn) - fld isa Symbol || return Any, false - obj = ex.args[2] - objt, found = get_type(obj, fn) - found || return Any, false - objt isa DataType || return Any, false - hasfield(objt, fld) || return Any, false - return fieldtype(objt, fld), true -end - -# Determines the return type with the Compiler of a function call using the type information of the arguments. -function get_type_call(expr::Expr, fn::Module) - f_name = expr.args[1] - f, found = get_type(f_name, fn) - found || return (Any, false) # If the function f is not found return Any. - args = Any[] - for i in 2:length(expr.args) # Find the type of the function arguments - typ, found = get_type(expr.args[i], fn) - found ? push!(args, typ) : push!(args, Any) - end - world = Base.get_world_counter() - return_type = Core.Compiler.return_type(Tuple{f, args...}, world) - return (return_type, true) -end - -# Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (SubString{String}, true) -function try_get_type(sym::Expr, fn::Module) - val, found = get_value(sym, fn) - found && return Core.Typeof(val), found - if sym.head === :call - # getfield call is special cased as the evaluation of getfield provides good type information, - # is inexpensive and it is also performed in the complete_symbol function. - a1 = sym.args[1] - if a1 === :getfield || a1 === GlobalRef(Core, :getfield) - return get_type_getfield(sym, fn) +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 (@ccall jl_generating_output()::Cint) == 1 + return REPLInterpreterCache() + else + return REPL_INTERPRETER_CACHE + end +end + +struct REPLInterpreter <: CC.AbstractInterpreter + repl_frame::CC.InferenceResult + world::UInt + inf_params::CC.InferenceParams + opt_params::CC.OptimizationParams + inf_cache::Vector{CC.InferenceResult} + code_cache::REPLInterpreterCache + function REPLInterpreter(repl_frame::CC.InferenceResult; + 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::REPLInterpreterCache = get_code_cache()) + return new(repl_frame, world, inf_params, opt_params, inf_cache, code_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_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) + +# REPLInterpreter is only used for type analysis, so it should disable optimization entirely +CC.may_optimize(::REPLInterpreter) = false + +# REPLInterpreter analyzes a top-level frame, so better to not bail out from it +CC.bail_out_toplevel_call(::REPLInterpreter, ::CC.InferenceLoopState, ::CC.InferenceState) = false + +# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions +# for lines like `Mod.a.|` (where `|` is the cursor position). +# Aggressive binding resolution poses challenges for the inference cache validation +# (until https://github.com/JuliaLang/julia/issues/40399 is implemented). +# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding +# resolution for top-level frame representing REPL input code (`repl_frame`) and for child +# `getproperty` frames that are constant propagated from the `repl_frame`. This works, since +# a.) these frames are never cached, and +# b.) their results are only observed by the non-cached `repl_frame`. +# +# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within +# `repl_frame` to provide reasonable completions for lines like `Ref(Some(42))[].|`. +# Aggressive concrete evaluation allows us to get accurate type information about complex +# expressions that otherwise can not be constant folded, in a safe way, i.e. it still +# doesn't evaluate effectful expressions like `pop!(xs)`. +# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't +# present any cache validation issues because `repl_frame` is never cached. + +is_repl_frame(interp::REPLInterpreter, sv::CC.InferenceState) = interp.repl_frame === sv.result + +# aggressive global binding resolution within `repl_frame` +function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, + sv::CC.InferenceState) + if is_repl_frame(interp, sv) + if CC.isdefined_globalref(g) + return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) + end + return Union{} + end + return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, + sv::CC.InferenceState) +end + +function is_repl_frame_getproperty(interp::REPLInterpreter, sv::CC.InferenceState) + def = sv.linfo.def + def isa Method || return false + def.name === :getproperty || return false + sv.cached && return false + return is_repl_frame(interp, sv.parent) +end + +# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame` +function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f), + argtypes::Vector{Any}, sv::CC.InferenceState) + if f === Core.getglobal && is_repl_frame_getproperty(interp, sv) + if length(argtypes) == 2 + a1, a2 = argtypes + if isa(a1, Const) && isa(a2, Const) + a1val, a2val = a1.val, a2.val + if isa(a1val, Module) && isa(a2val, Symbol) + g = GlobalRef(a1val, a2val) + if CC.isdefined_globalref(g) + return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) + end + return Union{} + end + end end - return get_type_call(sym, fn) - elseif sym.head === :thunk - thk = sym.args[1] - rt = ccall(:jl_infer_thunk, Any, (Any, Any), thk::Core.CodeInfo, fn) - rt !== Any && return (rt, true) - elseif sym.head === :ref - # some simple cases of `expand` - return try_get_type(Expr(:call, GlobalRef(Base, :getindex), sym.args...), fn) - elseif sym.head === :. && sym.args[2] isa QuoteNode # second check catches broadcasting - return try_get_type(Expr(:call, GlobalRef(Core, :getfield), sym.args...), fn) - end - return (Any, false) -end - -try_get_type(other, fn::Module) = get_type(other, fn) - -function get_type(sym::Expr, fn::Module) - # try to analyze nests of calls. if this fails, try using the expanded form. - val, found = try_get_type(sym, fn) - found && return val, found - # https://github.com/JuliaLang/julia/issues/27184 - if isexpr(sym, :macrocall) - _, found = get_type(first(sym.args), fn) - found || return Any, false - end - newsym = try - Meta.lower(fn, sym) - catch e - e isa LoadError && return Any, false - # If e is not a LoadError then Meta.lower crashed in an unexpected way. - # Since this is not a specific to the user code but an internal error, - # rethrow the error to allow reporting it. - rethrow() end - return try_get_type(newsym, fn) + return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any, + argtypes::Vector{Any}, sv::CC.InferenceState) end -function get_type(sym, fn::Module) - val, found = get_value(sym, fn) - return found ? Core.Typeof(val) : Any, found +# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame` +function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f), + result::CC.MethodCallResult, arginfo::CC.ArgInfo, + sv::CC.InferenceState) + if is_repl_frame(interp, sv) + neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE) + result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited, + result.edge, neweffects) + end +return @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any, + result::CC.MethodCallResult, arginfo::CC.ArgInfo, + sv::CC.InferenceState) end -function get_type(T, found::Bool, default_any::Bool) - return found ? T : - default_any ? Any : throw(ArgumentError("argument not found")) +function resolve_toplevel_symbols!(mod::Module, src::Core.CodeInfo) + newsrc = copy(src) + @ccall jl_resolve_globals_in_ir( + #=jl_array_t *stmts=# newsrc.code::Any, + #=jl_module_t *m=# mod::Any, + #=jl_svec_t *sparam_vals=# Core.svec()::Any, + #=int binding_effects=# 0::Int)::Cvoid + return newsrc +end + +# lower `ex` and run type inference on the resulting top-level expression +function repl_eval_ex(@nospecialize(ex), context_module::Module) + lwr = try + Meta.lower(context_module, ex) + catch # macro expansion failed, etc. + return nothing + end + if lwr isa Symbol + return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing + end + lwr isa Expr || return Const(lwr) # `ex` is literal + isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar + src = lwr.args[1]::Core.CodeInfo + + # construct top-level `MethodInstance` + mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ()); + mi.specTypes = Tuple{} + + mi.def = context_module + src = resolve_toplevel_symbols!(context_module, src) + @atomic mi.uninferred = src + + result = CC.InferenceResult(mi) + interp = REPLInterpreter(result) + frame = CC.InferenceState(result, src, #=cache=#:no, interp)::CC.InferenceState + + CC.typeinf(interp, frame) + + result = frame.result.result + result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead + return result end # Method completion on function call expression that look like :(max(1)) MAX_METHOD_COMPLETIONS::Int = 40 -function complete_methods(ex_org::Expr, context_module::Module=Main) - out = Completion[] - funct, found = get_type(ex_org.args[1], context_module)::Tuple{Any,Bool} - !found && return out - - args_ex, kwargs_ex = complete_methods_args(ex_org.args[2:end], ex_org, context_module, true, true) - push!(args_ex, Vararg{Any}) - complete_methods!(out, funct, args_ex, kwargs_ex, MAX_METHOD_COMPLETIONS) +function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool) + funct = repl_eval_ex(ex_org.args[1], context_module) + funct === nothing && return 2, nothing, [], Set{Symbol}() + funct = CC.widenconst(funct) + args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true) + return kwargs_flag, funct, args_ex, kwargs_ex +end +function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false) + kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}} + out = Completion[] + kwargs_flag == 2 && return out # one of the kwargs is invalid + kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon + complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1) return out end MAX_ANY_METHOD_COMPLETIONS::Int = 10 function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool) out = Completion[] - args_ex, kwargs_ex = try + args_ex, kwargs_ex, kwargs_flag = try # this may throw, since we set default_any to false - complete_methods_args(ex_org.args[2:end], ex_org, context_module, false, false) + complete_methods_args(ex_org, context_module, false, false) catch ex ex isa ArgumentError || rethrow() return out end + kwargs_flag == 2 && return out # one of the kwargs is invalid + + # moreargs determines whether to accept more args, independently of the presence of a + # 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) + 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) + 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) + 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) + complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false) end end end @@ -583,44 +648,77 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul return out end -function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool) +function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool) + n = isexpr(x, :kw) ? x.args[1] : x + if n isa Symbol + push!(kwargs_ex, n) + return kwargs_flag + end + possible_splat && isexpr(x, :...) && return kwargs_flag + return 2 # The kwarg is invalid +end + +function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool) args_ex = Any[] - kwargs_ex = false - if allow_broadcasting && ex_org.head === :. && ex_org.args[2] isa Expr - # handle broadcasting, but only handle number of arguments instead of - # argument types - for _ in (ex_org.args[2]::Expr).args - push!(args_ex, Any) - end - else - for ex in funargs - if isexpr(ex, :parameters) - if !isempty(ex.args) - kwargs_ex = true - end - elseif isexpr(ex, :kw) - kwargs_ex = true + kwargs_ex = Symbol[] + kwargs_flag = 0 + # kwargs_flag is: + # * 0 if there is no semicolon and no invalid kwarg + # * 1 if there is a semicolon and no invalid kwarg + # * 2 if there are two semicolons or more, or if some kwarg is invalid, which + # means that it is not of the form "bar=foo", "bar" or "bar..." + for i in (1+!broadcasting):length(funargs) + ex = funargs[i] + if isexpr(ex, :parameters) + kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters + for x in ex.args + kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true) + end + elseif isexpr(ex, :kw) + kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false) + else + if broadcasting + # handle broadcasting, but only handle number of arguments instead of + # argument types + push!(args_ex, Any) else - push!(args_ex, get_type(get_type(ex, context_module)..., default_any)) + argt = repl_eval_ex(ex, context_module) + if argt !== nothing + push!(args_ex, CC.widenconst(argt)) + elseif default_any + push!(args_ex, Any) + else + throw(ArgumentError("argument not found")) + end end end end - return args_ex, kwargs_ex + return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag end -function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Bool, max_method_completions::Int) +is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple) + +function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool) + if allow_broadcasting && is_broadcasting_expr(ex) + return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true) + end + return detect_args_kwargs(ex.args, context_module, default_any, false) +end + +function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool) # Input types and number of arguments t_in = Tuple{funct, args_ex...} m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(), #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL)) - if m === false - push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods to show )")) + if !isa(m, Vector) + push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )")) + return end - m isa Vector || return for match in m # TODO: if kwargs_ex, filter out methods without kwargs? push!(out, MethodCompletion(match.spec_types, match.method)) end + # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set end include("latex_symbols.jl") @@ -665,7 +763,6 @@ function close_path_completion(str, startpos, r, paths, pos) return lastindex(str) <= pos || str[nextind(str, pos)] != '"' end - function bslash_completions(string::String, pos::Int) slashpos = something(findprev(isequal('\\'), string, pos), 0) if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos && @@ -732,6 +829,76 @@ end return matches end +# Identify an argument being completed in a method call. If the argument is empty, method +# suggestions will be provided instead of argument completions. +function identify_possible_method_completion(partial, last_idx) + fail = 0:-1, Expr(:nothing), 0:-1, 0 + + # First, check that the last punctuation is either ',', ';' or '(' + idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int + idx_last_punct == 0 && return fail + last_punct = partial[idx_last_punct] + last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail + + # Then, check that `last_punct` is only followed by an identifier or nothing + before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0) + before_last_word_start == 0 && return fail + all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail + + # Check that `last_punct` is either the last '(' or placed after a previous '(' + frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct]) + method_name_end ∈ frange || return fail + + # Strip the preceding ! operators, if any, and close the expression with a ')' + s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')' + ex = Meta.parse(s, raise=false, depwarn=false) + isa(ex, Expr) || return fail + + # `wordrange` is the position of the last argument to complete + wordrange = nextind(partial, before_last_word_start):last_idx + return frange, ex, wordrange, method_name_end +end + +# Provide completion for keyword arguments in function calls +function complete_keyword_argument(partial, last_idx, context_module) + frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx) + fail = Completion[], 0:-1, frange + ex.head === :call || is_broadcasting_expr(ex) || return fail + + kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}} + kwargs_flag == 2 && return fail # one of the previous kwargs is invalid + + methods = Completion[] + complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1) + # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for + # method calls compatible with the current arguments. + + # For each method corresponding to the function call, provide completion suggestions + # for each keyword that starts like the last word and that is not already used + # previously in the expression. The corresponding suggestion is "kwname=". + # If the keyword corresponds to an existing name, also include "kwname" as a suggestion + # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)". + last_word = partial[wordrange] # the word to complete + kwargs = Set{String}() + for m in methods + m::MethodCompletion + possible_kwargs = Base.kwarg_decl(m.method) + current_kwarg_candidates = String[] + for _kw in possible_kwargs + kw = String(_kw) + if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex + push!(current_kwarg_candidates, kw) + end + end + union!(kwargs, current_kwarg_candidates) + end + + suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs] + append!(suggestions, complete_symbol(nothing, last_word, Returns(true), context_module)) + + return sort!(suggestions, by=completion_text), wordrange +end + function project_deps_get_completion_candidates(pkgstarts::String, project_file::String) loading_candidates = String[] d = Base.parsed_toml(project_file) @@ -748,6 +915,55 @@ 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) + ex = nothing + comp_keywords && append!(suggestions, complete_keyword(name)) + if dotpos > 1 && string[dotpos] == '.' + s = string[1:dotpos-1] + # First see if the whole string up to `pos` is a valid expression. If so, use it. + ex = Meta.parse(s, raise=false, depwarn=false) + if isexpr(ex, :incomplete) + s = string[startpos:pos] + # Heuristic to find the start of the expression. TODO: This would be better + # done with a proper error-recovering parser. + if 0 < startpos <= lastindex(string) && string[startpos] == '.' + i = prevind(string, startpos) + while 0 < i + c = string[i] + if c in (')', ']') + if c == ')' + c_start = '(' + c_end = ')' + elseif c == ']' + c_start = '[' + c_end = ']' + end + frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end) + isempty(frange) && break # unbalanced parens + startpos = first(frange) + i = prevind(string, startpos) + elseif c in ('\'', '\"', '\`') + s = "$c$c"*string[startpos:pos] + break + else + break + end + s = string[startpos:pos] + end + end + 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) + end + end + append!(suggestions, complete_symbol(ex, name, ffunc, context_module)) + return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true +end + function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true) # First parse everything up to the current position partial = string[1:pos] @@ -791,8 +1007,25 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true end + ffunc = Returns(true) + suggestions = Completion[] + + # Check if this is a var"" string macro that should be completed like + # an identifier rather than a string. + # TODO: It would be nice for the parser to give us more information here + # so that we can lookup the macro by identity rather than pattern matching + # its invocation. + varrange = findprev("var\"", string, pos) + + if varrange !== nothing + ok, ret = bslash_completions(string, pos) + 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) # otherwise... - if inc_tag in [:cmd, :string] + elseif inc_tag in [:cmd, :string] m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) startpos = nextind(partial, reverseind(partial, m.offset)) r = startpos:pos @@ -807,41 +1040,40 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif end #Latex symbols can be completed for strings - (success || inc_tag==:cmd) && return sort!(paths, by=p->p.path), r, success + (success || inc_tag === :cmd) && return sort!(paths, by=p->p.path), r, success end ok, ret = bslash_completions(string, pos) ok && return ret # Make sure that only bslash_completions is working on strings - inc_tag==:string && return Completion[], 0:-1, false - if inc_tag === :other && should_method_complete(partial) - frange, method_name_end = find_start_brace(partial) - # strip preceding ! operator - s = replace(partial[frange], r"\!+([^=\(]+)" => s"\1") - ex = Meta.parse(s * ")", raise=false, depwarn=false) - - if isa(ex, Expr) + inc_tag === :string && return Completion[], 0:-1, false + if inc_tag === :other + frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos) + if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete if ex.head === :call - return complete_methods(ex, context_module), first(frange):method_name_end, false - elseif ex.head === :. && ex.args[2] isa Expr && (ex.args[2]::Expr).head === :tuple - return complete_methods(ex, context_module), first(frange):(method_name_end - 1), false + return complete_methods(ex, context_module, shift), first(frange):method_name_end, false + elseif is_broadcasting_expr(ex) + return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false end end elseif inc_tag === :comment return Completion[], 0:-1, false end + # Check whether we can complete a keyword argument in a function call + kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module) + isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion) + dotpos = something(findprev(isequal('.'), string, pos), 0) startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0)) # strip preceding ! operator - if (m = match(r"^\!+", string[startpos:pos])) !== nothing + if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch startpos += length(m.match) end - ffunc = (mod,x)->true - suggestions = Completion[] - comp_keywords = true + name = string[max(startpos, dotpos+1):pos] + comp_keywords = !isempty(name) && startpos > dotpos if afterusing(string, startpos) # We're right after using or import. Let's look only for packages # and modules we can reach from here @@ -883,38 +1115,11 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module)) comp_keywords = false end + startpos == 0 && (pos = -1) dotpos < startpos && (dotpos = startpos - 1) - s = string[startpos:pos] - comp_keywords && append!(suggestions, complete_keyword(s)) - # if the start of the string is a `.`, try to consume more input to get back to the beginning of the last expression - if 0 < startpos <= lastindex(string) && string[startpos] == '.' - i = prevind(string, startpos) - while 0 < i - c = string[i] - if c in (')', ']') - if c == ')' - c_start = '(' - c_end = ')' - elseif c == ']' - c_start = '[' - c_end = ']' - end - frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end) - isempty(frange) && break # unbalanced parens - startpos = first(frange) - i = prevind(string, startpos) - elseif c in ('\'', '\"', '\`') - s = "$c$c"*string[startpos:pos] - break - else - break - end - s = string[startpos:pos] - end - end - append!(suggestions, complete_symbol(s, ffunc, context_module)) - return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true + return complete_identifiers!(suggestions, ffunc, context_module, string, + name, pos, dotpos, startpos, comp_keywords) end function shell_completions(string, pos) diff --git a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl index b87a9c8c26464..a1f94852b38ec 100644 --- a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl +++ b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl @@ -131,7 +131,7 @@ end """ header(m::AbstractMenu) -> String -Returns a header string to be printed above the menu. +Return a header string to be printed above the menu. Defaults to "". """ header(m::AbstractMenu) = "" @@ -192,7 +192,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio REPL.Terminals.raw!(term, true) true catch err - suppress_output || @warn("TerminalMenus: Unable to enter raw mode: $err") + suppress_output || @warn "TerminalMenus: Unable to enter raw mode: " exception=(err, catch_backtrace()) false end # hide the cursor @@ -216,7 +216,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio m.pageoffset = 0 elseif c == Int(END_KEY) cursor[] = lastoption - m.pageoffset = lastoption - m.pagesize + m.pageoffset = max(0, lastoption - m.pagesize) elseif c == 13 # <enter> # will break if pick returns true pick(m, cursor[]) && break @@ -269,7 +269,7 @@ function move_up!(m::AbstractMenu, cursor::Int, lastoption::Int=numoptions(m)) elseif scroll_wrap(m) # wrap to bottom cursor = lastoption - m.pageoffset = lastoption - m.pagesize + m.pageoffset = max(0, lastoption - m.pagesize) end return cursor end @@ -299,7 +299,7 @@ end function page_down!(m::AbstractMenu, cursor::Int, lastoption::Int=numoptions(m)) m.pageoffset += m.pagesize - (cursor == 1 ? 1 : 0) - m.pageoffset = min(m.pageoffset, lastoption - m.pagesize) + m.pageoffset = max(0, min(m.pageoffset, lastoption - m.pagesize)) return min(cursor + m.pagesize, lastoption) end diff --git a/stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl b/stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl index bcca3bd8d851e..5c3ecf3808c49 100644 --- a/stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl +++ b/stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl @@ -11,7 +11,7 @@ A menu that allows a user to select a multiple options from a list. ```julia-repl julia> request(MultiSelectMenu(options)) Select the fruits you like: -[press: d=done, a=all, n=none] +[press: Enter=toggle, a=all, n=none, d=done, q=abort] [ ] apple > [X] orange [X] grape @@ -86,7 +86,7 @@ end # See AbstractMenu.jl ####################################### -header(m::MultiSelectMenu) = "[press: d=done, a=all, n=none]" +header(m::MultiSelectMenu) = "[press: Enter=toggle, a=all, n=none, d=done, q=abort]" options(m::MultiSelectMenu) = m.options diff --git a/stdlib/REPL/src/TerminalMenus/util.jl b/stdlib/REPL/src/TerminalMenus/util.jl index 8ad9ec0e4100d..91e336070d2cf 100644 --- a/stdlib/REPL/src/TerminalMenus/util.jl +++ b/stdlib/REPL/src/TerminalMenus/util.jl @@ -17,24 +17,24 @@ readbyte(stream::IO=stdin) = read(stream, Char) # Read the next key from stdin. It is also able to read several bytes for # escaped keys such as the arrow keys, home/end keys, etc. # Escaped keys are returned using the `Key` enum. -readkey(stream::Base.LibuvStream=stdin) = UInt32(_readkey(stream)) -function _readkey(stream::Base.LibuvStream=stdin) +readkey(stream::IO=stdin) = UInt32(_readkey(stream)) +function _readkey(stream::IO=stdin) c = readbyte(stream) # Escape characters if c == '\x1b' - stream.buffer.size < 2 && return '\x1b' + bytesavailable(stream) < 1 && return '\x1b' esc_a = readbyte(stream) esc_a == 'v' && return PAGE_UP # M-v esc_a == '<' && return HOME_KEY # M-< esc_a == '>' && return END_KEY # M-> - stream.buffer.size < 3 && return '\x1b' + bytesavailable(stream) < 1 && return '\x1b' esc_b = readbyte(stream) if esc_a == '[' || esc_a == 'O' if esc_b >= '0' && esc_b <= '9' - stream.buffer.size < 4 && return '\x1b' + bytesavailable(stream) < 1 && return '\x1b' esc_c = readbyte(stream) if esc_c == '~' esc_b == '1' && return HOME_KEY diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index 66f2aba86384d..db28c84b07cb6 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -20,12 +20,12 @@ using Unicode: normalize ## Help mode ## # This is split into helpmode and _helpmode to easier unittest _helpmode -helpmode(io::IO, line::AbstractString) = :($REPL.insert_hlines($io, $(REPL._helpmode(io, line)))) -helpmode(line::AbstractString) = helpmode(stdout, line) +helpmode(io::IO, line::AbstractString, mod::Module=Main) = :($REPL.insert_hlines($io, $(REPL._helpmode(io, line, mod)))) +helpmode(line::AbstractString, mod::Module=Main) = helpmode(stdout, line, mod) const extended_help_on = Ref{Any}(nothing) -function _helpmode(io::IO, line::AbstractString) +function _helpmode(io::IO, line::AbstractString, mod::Module=Main) line = strip(line) ternary_operator_help = (line == "?" || line == "?:") if startswith(line, '?') && !ternary_operator_help @@ -64,9 +64,9 @@ function _helpmode(io::IO, line::AbstractString) end # the following must call repl(io, expr) via the @repl macro # so that the resulting expressions are evaluated in the Base.Docs namespace - :($REPL.@repl $io $expr $brief) + :($REPL.@repl $io $expr $brief $mod) end -_helpmode(line::AbstractString) = _helpmode(stdout, line) +_helpmode(line::AbstractString, mod::Module=Main) = _helpmode(stdout, line, mod) # Print vertical lines along each docstring if there are multiple docs function insert_hlines(io::IO, docs) @@ -164,7 +164,8 @@ function doc(binding::Binding, sig::Type = Union{}) results, groups = DocStr[], MultiDoc[] # Lookup `binding` and `sig` for matches in all modules of the docsystem. for mod in modules - dict = meta(mod) + dict = meta(mod; autoinit=false) + isnothing(dict) && continue if haskey(dict, binding) multidoc = dict[binding] push!(groups, multidoc) @@ -226,11 +227,15 @@ function lookup_doc(ex) return Markdown.parse("`x $op= y` is a synonym for `x $eq x $op y`") elseif isdotted && ex !== :(..) op = str[2:end] - return Markdown.parse("`x $ex y` is akin to `broadcast($op, x, y)`. See [`broadcast`](@ref).") + if op in ("&&", "||") + return Markdown.parse("`x $ex y` broadcasts the boolean operator `$op` to `x` and `y`. See [`broadcast`](@ref).") + else + return Markdown.parse("`x $ex y` is akin to `broadcast($op, x, y)`. See [`broadcast`](@ref).") + end end end binding = esc(bindingexpr(namify(ex))) - if isexpr(ex, :call) || isexpr(ex, :macrocall) + if isexpr(ex, :call) || isexpr(ex, :macrocall) || isexpr(ex, :where) sig = esc(signature(ex)) :($(doc)($binding, $sig)) else @@ -250,7 +255,11 @@ function summarize(binding::Binding, sig) else println(io, "No documentation found.\n") quot = any(isspace, sprint(print, binding)) ? "'" : "" - println(io, "Binding ", quot, "`", binding, "`", quot, " does not exist.") + if Base.isbindingresolved(binding.mod, binding.var) + println(io, "Binding ", quot, "`", binding, "`", quot, " exists, but has not been assigned a value.") + else + println(io, "Binding ", quot, "`", binding, "`", quot, " does not exist.") + end end md = Markdown.parse(seekstart(io)) # Save metadata in the generated markdown. @@ -369,21 +378,23 @@ end quote_spaces(x) = any(isspace, x) ? "'" * x * "'" : x -function repl_search(io::IO, s::Union{Symbol,String}) +function repl_search(io::IO, s::Union{Symbol,String}, mod::Module) pre = "search:" print(io, pre) - printmatches(io, s, map(quote_spaces, doc_completions(s)), cols = _displaysize(io)[2] - length(pre)) + printmatches(io, s, map(quote_spaces, doc_completions(s, mod)), cols = _displaysize(io)[2] - length(pre)) println(io, "\n") end -repl_search(s) = repl_search(stdout, s) -function repl_corrections(io::IO, s) +# TODO: document where this is used +repl_search(s, mod::Module) = repl_search(stdout, s, mod) + +function repl_corrections(io::IO, s, mod::Module) print(io, "Couldn't find ") quot = any(isspace, s) ? "'" : "" print(io, quot) printstyled(io, s, color=:cyan) print(io, quot, '\n') - print_correction(io, s) + print_correction(io, s, mod) end repl_corrections(s) = repl_corrections(stdout, s) @@ -460,27 +471,29 @@ function repl_latex(io::IO, s0::String) end repl_latex(s::String) = repl_latex(stdout, s) -macro repl(ex, brief::Bool=false) repl(ex; brief=brief) end -macro repl(io, ex, brief) repl(io, ex; brief=brief) end +macro repl(ex, brief::Bool=false, mod::Module=Main) repl(ex; brief, mod) end +macro repl(io, ex, brief, mod) repl(io, ex; brief, mod) end -function repl(io::IO, s::Symbol; brief::Bool=true) +function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main) str = string(s) quote repl_latex($io, $str) - repl_search($io, $str) - $(if !isdefined(Main, s) && !haskey(keywords, s) && !Base.isoperator(s) - :(repl_corrections($io, $str)) + repl_search($io, $str, $mod) + $(if !isdefined(mod, s) && !Base.isbindingresolved(mod, s) && !haskey(keywords, s) && !Base.isoperator(s) + # n.b. we call isdefined for the side-effect of resolving the binding, if possible + :(repl_corrections($io, $str, $mod)) end) $(_repl(s, brief)) end end isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3]) -repl(io::IO, ex::Expr; brief::Bool=true) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief) -repl(io::IO, str::AbstractString; brief::Bool=true) = :(apropos($io, $str)) -repl(io::IO, other; brief::Bool=true) = esc(:(@doc $other)) + +repl(io::IO, ex::Expr; brief::Bool=true, mod::Module=Main) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief) +repl(io::IO, str::AbstractString; brief::Bool=true, mod::Module=Main) = :(apropos($io, $str)) +repl(io::IO, other; brief::Bool=true, mod::Module=Main) = esc(:(@doc $other)) #repl(io::IO, other) = lookup_doc(other) # TODO -repl(x; brief::Bool=true) = repl(stdout, x; brief=brief) +repl(x; brief::Bool=true, mod::Module=Main) = repl(stdout, x; brief, mod) function _repl(x, brief::Bool=true) if isexpr(x, :call) @@ -558,7 +571,8 @@ Return documentation for a particular `field` of a type if it exists. """ function fielddoc(binding::Binding, field::Symbol) for mod in modules - dict = meta(mod) + dict = meta(mod; autoinit=false) + isnothing(dict) && continue if haskey(dict, binding) multidoc = dict[binding] if haskey(multidoc.docs, Union{}) @@ -697,8 +711,8 @@ end print_joined_cols(args...; cols::Int = _displaysize(stdout)[2]) = print_joined_cols(stdout, args...; cols=cols) -function print_correction(io::IO, word::String) - cors = map(quote_spaces, levsort(word, accessible(Main))) +function print_correction(io::IO, word::String, mod::Module) + cors = map(quote_spaces, levsort(word, accessible(mod))) pre = "Perhaps you meant " print(io, pre) print_joined_cols(io, cors, ", ", " or "; cols = _displaysize(io)[2] - length(pre)) @@ -706,7 +720,8 @@ function print_correction(io::IO, word::String) return end -print_correction(word) = print_correction(stdout, word) +# TODO: document where this is used +print_correction(word, mod::Module) = print_correction(stdout, word, mod) # Completion data @@ -720,8 +735,21 @@ accessible(mod::Module) = map(names, moduleusings(mod))...; collect(keys(Base.Docs.keywords))] |> unique |> filtervalid -doc_completions(name) = fuzzysort(name, accessible(Main)) -doc_completions(name::Symbol) = doc_completions(string(name)) +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) + 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)\"\"") + end + res +end +doc_completions(name::Symbol) = doc_completions(string(name), mod) # Searching and apropos @@ -813,7 +841,9 @@ function apropos(io::IO, needle::Regex) for mod in modules # Module doc might be in README.md instead of the META dict docsearch(doc(mod), needle) && println(io, mod) - for (k, v) in meta(mod) + dict = meta(mod; autoinit=false) + isnothing(dict) && continue + for (k, v) in dict docsearch(v, needle) && println(io, k) end end diff --git a/stdlib/REPL/src/latex_symbols.jl b/stdlib/REPL/src/latex_symbols.jl index 237aba92c45c7..3c2be918d6bd2 100644 --- a/stdlib/REPL/src/latex_symbols.jl +++ b/stdlib/REPL/src/latex_symbols.jl @@ -596,6 +596,7 @@ const latex_symbols = Dict( "\\triangleq" => "≜", "\\questeq" => "≟", "\\ne" => "≠", + "\\neq" => "≠", "\\equiv" => "≡", "\\nequiv" => "≢", "\\le" => "≤", @@ -725,7 +726,6 @@ const latex_symbols = Dict( "\\gtreqless" => "⋛", "\\curlyeqprec" => "⋞", "\\curlyeqsucc" => "⋟", - "\\sqspne" => "⋥", "\\lnsim" => "⋦", "\\gnsim" => "⋧", "\\precnsim" => "⋨", @@ -1126,6 +1126,7 @@ const latex_symbols = Dict( "\\nsqsubseteq" => "⋢", # not, square subset, equals "\\nsqsupseteq" => "⋣", # not, square superset, equals "\\sqsubsetneq" => "⋤", # square subset, not equals + "\\sqsupsetneq" => "⋥", # square superset, not equals "\\disin" => "⋲", # element of with long horizontal stroke "\\varisins" => "⋳", # element of with vertical bar at end of horizontal stroke "\\isins" => "⋴", # small element of with vertical bar at end of horizontal stroke @@ -1288,6 +1289,7 @@ const latex_symbols = Dict( "\\bsolhsub" => "\u27c8", # reverse solidus preceding subset "\\suphsol" => "\u27c9", # superset preceding solidus "\\wedgedot" => "⟑", # and with dot + "\\veedot" => "⟇", # or with dot "\\upin" => "⟒", # element of opening upwards "\\bigbot" => "⟘", # large up tack "\\bigtop" => "⟙", # large down tack @@ -1568,7 +1570,9 @@ const latex_symbols = Dict( "\\bsimilarleftarrow" => "\u2b41", # reverse tilde operator above leftwards arrow "\\leftarrowbackapprox" => "\u2b42", # leftwards arrow above reverse almost equal to "\\rightarrowgtr" => "\u2b43", # rightwards arrow through greater-than - "\\rightarrowsupset" => "\u2b44", # rightwards arrow through subset + "\\leftarrowless" => "\u2977", # leftwards arrow through less-than + "\\rightarrowsupset" => "\u2b44", # rightwards arrow through superset + "\\leftarrowsubset" => "\u297a", # leftwards arrow through subset "\\LLeftarrow" => "\u2b45", # leftwards quadruple arrow "\\RRightarrow" => "\u2b46", # rightwards quadruple arrow "\\bsimilarrightarrow" => "\u2b47", # reverse tilde operator above rightwards arrow @@ -2662,4 +2666,5 @@ const symbols_latex_canonical = Dict( "⊻" => "\\xor", "⊼" => "\\nand", "⊽" => "\\nor", + "≠" => "\\ne", ) diff --git a/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl b/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl index 23d026358385f..63b48b7173491 100644 --- a/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/dynamic_menu.jl @@ -116,3 +116,36 @@ str = String(take!(io)) nback, strs = linesplitter(str) @test nback == 3 @test strs == ["^ 3", " 4", " 5", " > 6*"] + +# Test with page size larger than number of options. +# END_KEY, PAGE_DOWN, and ARROW_UP (from first element with scroll +# wrap) used to be problematic. The last two are tested here, whereas +# the first one is unreachable within the `request` function. +menu = DynamicMenu(4, 0, -1, 2, TerminalMenus.Config(scroll_wrap = true)) + +cursor = 1 +state = TerminalMenus.printmenu(io, menu, cursor; init=true) +str = String(take!(io)) +@test count(isequal('\n'), str) == state +nback, strs = linesplitter(str) +@test nback == 0 +@test strs == [" > 1*", " 2"] + +cursor = TerminalMenus.page_down!(menu, cursor) +@test cursor == menu.numopts +@test menu.pageoffset == 0 +state = TerminalMenus.printmenu(io, menu, cursor; oldstate=state) +str = String(take!(io)) +nback, strs = linesplitter(str) +@test nback == 1 +@test strs == [" 1", " > 2*"] + +cursor = TerminalMenus.page_up!(menu, cursor) +cursor = TerminalMenus.move_up!(menu, cursor) +@test cursor == menu.numopts +@test menu.pageoffset == 0 +state = TerminalMenus.printmenu(io, menu, cursor; oldstate=state) +str = String(take!(io)) +nback, strs = linesplitter(str) +@test nback == 1 +@test strs == [" 1", " > 2*"] diff --git a/stdlib/REPL/test/TerminalMenus/legacytests/old_multiselect_menu.jl b/stdlib/REPL/test/TerminalMenus/legacytests/old_multiselect_menu.jl index 49dbcc42c3095..2a78d18bfa739 100644 --- a/stdlib/REPL/test/TerminalMenus/legacytests/old_multiselect_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/legacytests/old_multiselect_menu.jl @@ -14,7 +14,7 @@ multi_menu = MultiSelectMenu(string.(1:20), warn=false) @test TerminalMenus.options(multi_menu) == string.(1:20) -@test TerminalMenus.header(multi_menu) == "[press: d=done, a=all, n=none]" +@test TerminalMenus.header(multi_menu) == "[press: Enter=toggle, a=all, n=none, d=done, q=abort]" # Output TerminalMenus.config() # Use default chars @@ -33,6 +33,6 @@ TerminalMenus.writeLine(buf, multi_menu, 1, true) # Test SDTIN multi_menu = MultiSelectMenu(string.(1:10), warn=false) -@test simulate_input(Set([1,2]), multi_menu, :enter, :down, :enter, 'd') +@test simulate_input(multi_menu, :enter, :down, :enter, 'd') == Set([1,2]) multi_menu = MultiSelectMenu(["single option"], warn=false) -@test simulate_input(Set([1]), multi_menu, :up, :up, :down, :enter, 'd') +@test simulate_input(multi_menu, :up, :up, :down, :enter, 'd') == Set([1]) diff --git a/stdlib/REPL/test/TerminalMenus/legacytests/old_radio_menu.jl b/stdlib/REPL/test/TerminalMenus/legacytests/old_radio_menu.jl index 9438808a847d6..248d5cd6a3183 100644 --- a/stdlib/REPL/test/TerminalMenus/legacytests/old_radio_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/legacytests/old_radio_menu.jl @@ -36,8 +36,8 @@ TerminalMenus.writeLine(buf, radio_menu, 1, true) # Test using stdin radio_menu = RadioMenu(string.(1:10), warn=false) -@test simulate_input(3, radio_menu, :down, :down, :enter) +@test simulate_input(radio_menu, :down, :down, :enter) == 3 radio_menu = RadioMenu(["single option"], warn=false) -@test simulate_input(1, radio_menu, :up, :up, :down, :up, :enter) +@test simulate_input(radio_menu, :up, :up, :down, :up, :enter) == 1 radio_menu = RadioMenu(string.(1:3), pagesize=1, warn=false) -@test simulate_input(3, radio_menu, :down, :down, :down, :down, :enter) +@test simulate_input(radio_menu, :down, :down, :down, :down, :enter) == 3 diff --git a/stdlib/REPL/test/TerminalMenus/multiselect_menu.jl b/stdlib/REPL/test/TerminalMenus/multiselect_menu.jl index d625554c813b0..61d2dba95a0b0 100644 --- a/stdlib/REPL/test/TerminalMenus/multiselect_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/multiselect_menu.jl @@ -14,7 +14,7 @@ multi_menu = MultiSelectMenu(string.(1:20), charset=:ascii) @test TerminalMenus.options(multi_menu) == string.(1:20) -@test TerminalMenus.header(multi_menu) == "[press: d=done, a=all, n=none]" +@test TerminalMenus.header(multi_menu) == "[press: Enter=toggle, a=all, n=none, d=done, q=abort]" # Output for kws in ((charset=:ascii,), @@ -30,10 +30,10 @@ for kws in ((charset=:ascii,), TerminalMenus.writeline(buf, multi_menu, 1, true) @test String(take!(buf)) == "$uck 1" TerminalMenus.printmenu(buf, multi_menu, 1; init=true) - @test startswith(String(take!(buf)), string("\e[2K[press: d=done, a=all, n=none]\r\n\e[2K $cur $uck 1")) + @test startswith(String(take!(buf)), string("\e[2K[press: Enter=toggle, a=all, n=none, d=done, q=abort]\r\n\e[2K $cur $uck 1")) push!(multi_menu.selected, 1) TerminalMenus.printmenu(buf, multi_menu, 2; init=true) - @test startswith(String(take!(buf)), string("\e[2K[press: d=done, a=all, n=none]\r\n\e[2K $chk 1\r\n\e[2K $cur $uck 2")) + @test startswith(String(take!(buf)), string("\e[2K[press: Enter=toggle, a=all, n=none, d=done, q=abort]\r\n\e[2K $chk 1\r\n\e[2K $cur $uck 2")) end # Preselection @@ -52,6 +52,6 @@ end # Test SDTIN multi_menu = MultiSelectMenu(string.(1:10), charset=:ascii) -@test simulate_input(Set([1,2]), multi_menu, :enter, :down, :enter, 'd') +@test simulate_input(multi_menu, :enter, :down, :enter, 'd') == Set([1,2]) multi_menu = MultiSelectMenu(["single option"], charset=:ascii) -@test simulate_input(Set([1]), multi_menu, :up, :up, :down, :enter, 'd') +@test simulate_input(multi_menu, :up, :up, :down, :enter, 'd') == Set([1]) diff --git a/stdlib/REPL/test/TerminalMenus/multiselect_with_skip_menu.jl b/stdlib/REPL/test/TerminalMenus/multiselect_with_skip_menu.jl index 84f259ad7642c..609b168c2ddba 100644 --- a/stdlib/REPL/test/TerminalMenus/multiselect_with_skip_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/multiselect_with_skip_menu.jl @@ -121,10 +121,10 @@ menu = MultiSelectWithSkipMenu(string.(1:5), selected=[2, 3]) buf = IOBuffer() TerminalMenus.printmenu(buf, menu, 1; init=true) @test occursin("2 items selected", String(take!(buf))) -@test simulate_input(Set([2, 3, 4]), menu, 'n', :enter, 'd') +@test simulate_input(menu, 'n', :enter, 'd') == Set([2, 3, 4]) buf = IOBuffer() TerminalMenus.printmenu(buf, menu, 1; init=true) @test occursin("3 items selected", String(take!(buf))) menu = MultiSelectWithSkipMenu(string.(1:5), selected=[2, 3]) -@test simulate_input(Set([2]), menu, 'P', :enter, 'd', cursor=5) +@test simulate_input(menu, 'P', :enter, 'd', cursor=5) == Set([2]) diff --git a/stdlib/REPL/test/TerminalMenus/radio_menu.jl b/stdlib/REPL/test/TerminalMenus/radio_menu.jl index 696be1324a8e3..5ca6422717425 100644 --- a/stdlib/REPL/test/TerminalMenus/radio_menu.jl +++ b/stdlib/REPL/test/TerminalMenus/radio_menu.jl @@ -45,10 +45,10 @@ end # Test using stdin radio_menu = RadioMenu(string.(1:10); charset=:ascii) -@test simulate_input(3, radio_menu, :down, :down, :enter) +@test simulate_input(radio_menu, :down, :down, :enter) == 3 radio_menu = RadioMenu(["single option"], charset=:ascii) -@test simulate_input(1, radio_menu, :up, :up, :down, :up, :enter) +@test simulate_input(radio_menu, :up, :up, :down, :up, :enter) == 1 radio_menu = RadioMenu(string.(1:3), pagesize=1, charset=:ascii) -@test simulate_input(3, radio_menu, :down, :down, :down, :down, :enter) +@test simulate_input(radio_menu, :down, :down, :down, :down, :enter) == 3 radio_menu = RadioMenu(["apple", "banana", "cherry"]; keybindings=collect('a':'c'), charset=:ascii) -@test simulate_input(2, radio_menu, 'b') +@test simulate_input(radio_menu, 'b') == 2 diff --git a/stdlib/REPL/test/TerminalMenus/runtests.jl b/stdlib/REPL/test/TerminalMenus/runtests.jl index 62a91cc0a1256..c594958a36670 100644 --- a/stdlib/REPL/test/TerminalMenus/runtests.jl +++ b/stdlib/REPL/test/TerminalMenus/runtests.jl @@ -4,21 +4,22 @@ import REPL using REPL.TerminalMenus using Test -function simulate_input(expected, menu::TerminalMenus.AbstractMenu, keys...; - kwargs...) +function simulate_input(menu::TerminalMenus.AbstractMenu, keys...; kwargs...) keydict = Dict(:up => "\e[A", :down => "\e[B", :enter => "\r") + new_stdin = Base.BufferStream() for key in keys if isa(key, Symbol) - write(stdin.buffer, keydict[key]) + write(new_stdin, keydict[key]) else - write(stdin.buffer, "$key") + write(new_stdin, "$key") end end + TerminalMenus.terminal.in_stream = new_stdin - request(menu; suppress_output=true, kwargs...) == expected + return request(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 9757cdb5df097..22701ead7883d 100644 --- a/stdlib/REPL/test/docview.jl +++ b/stdlib/REPL/test/docview.jl @@ -42,6 +42,14 @@ end @test REPL.insert_hlines(IOBuffer(), nothing) === nothing end +@testset "Check @var_str also completes to var\"\" in REPL.doc_completions()" begin + checks = ["var", "raw", "r"] + symbols = "@" .* checks .* "_str" + results = checks .* "\"\"" + for (i,r) in zip(symbols,results) + @test r ∈ REPL.doc_completions(i) + end +end @testset "fuzzy score" begin # https://github.com/JunoLab/FuzzyCompletions.jl/issues/7 # shouldn't throw when there is a space in a middle of query @@ -57,5 +65,3 @@ end b = REPL.Binding(@__MODULE__, :R) @test REPL.summarize(b, Tuple{}) isa Markdown.MD end - - diff --git a/stdlib/REPL/test/lineedit.jl b/stdlib/REPL/test/lineedit.jl index decad3eb07938..cf87e811508a0 100644 --- a/stdlib/REPL/test/lineedit.jl +++ b/stdlib/REPL/test/lineedit.jl @@ -29,7 +29,7 @@ function transform!(f, s, i = -1) # i is char-based (not bytes) buffer position # simulate what happens in LineEdit.set_action! s isa LineEdit.MIState && (s.current_action = :unknown) status = f(s) - if s isa LineEdit.MIState && status != :ignore + if s isa LineEdit.MIState && status !== :ignore # simulate what happens in LineEdit.prompt! s.last_action = s.current_action end @@ -306,21 +306,21 @@ seek(buf,0) ## edit_delete_prev_word ## -buf = IOBuffer("type X\n ") +buf = IOBuffer(Vector{UInt8}("type X\n "), read=true, write=true) seekend(buf) @test !isempty(@inferred(LineEdit.edit_delete_prev_word(buf))) @test position(buf) == 5 @test buf.size == 5 @test content(buf) == "type " -buf = IOBuffer("4 +aaa+ x") +buf = IOBuffer(Vector{UInt8}("4 +aaa+ x"), read=true, write=true) seek(buf,8) @test !isempty(LineEdit.edit_delete_prev_word(buf)) @test position(buf) == 3 @test buf.size == 4 @test content(buf) == "4 +x" -buf = IOBuffer("x = func(arg1,arg2 , arg3)") +buf = IOBuffer(Vector{UInt8}("x = func(arg1,arg2 , arg3)"), read=true, write=true) seekend(buf) LineEdit.char_move_word_left(buf) @test position(buf) == 21 @@ -375,6 +375,25 @@ let buf = IOBuffer() @test content(buf) == "βγαεδ" LineEdit.edit_transpose_chars(buf) @test content(buf) == "βγαδε" + + + # Transposing a one-char buffer should behave like Emacs + seek(buf, 0) + @inferred(LineEdit.edit_clear(buf)) + edit_insert(buf, "a") + LineEdit.edit_transpose_chars(buf) + @test content(buf) == "a" + seekend(buf) + LineEdit.edit_transpose_chars(buf) + @test content(buf) == "a" + @test position(buf) == 0 + + # Transposing an empty buffer shouldn't implode + seek(buf, 0) + LineEdit.edit_clear(buf) + LineEdit.edit_transpose_chars(buf) + @test content(buf) == "" + @test position(buf) == 0 end @testset "edit_word_transpose" begin @@ -455,7 +474,8 @@ end # julia> is 6 characters + 1 character for space, # so the rest of the terminal is 73 characters ######################################################################### -let buf = IOBuffer( +withenv("COLUMNS"=>"80") do + buf = IOBuffer( "begin\nprint(\"A very very very very very very very very very very very very ve\")\nend") seek(buf, 4) outbuf = IOBuffer() @@ -896,3 +916,27 @@ end @test get_last_word("a[b[]]") == "b" @test get_last_word("a[]") == "a[]" end + +@testset "show_completions" begin + term = FakeTerminal(IOBuffer(), IOBuffer(), IOBuffer()) + + function getcompletion(completions) + promptstate = REPL.LineEdit.init_state(term, REPL.LineEdit.mode(new_state())) + REPL.LineEdit.show_completions(promptstate, completions) + return String(take!(term.out_stream)) + end + + # When the number of completions is less than + # LineEdit.MULTICOLUMN_THRESHOLD, they should be in a single column. + strings = ["abcdef", "123456", "ijklmn"] + @assert length(strings) < LineEdit.MULTICOLUMN_THRESHOLD + @test getcompletion(strings) == "\033[0B\n\rabcdef\n\r123456\n\rijklmn\n" + + # But with more than the threshold there should be multiple columns + strings2 = repeat(["foo"], LineEdit.MULTICOLUMN_THRESHOLD + 1) + @test getcompletion(strings2) == "\033[0B\n\rfoo\r\e[5Cfoo\n\rfoo\r\e[5Cfoo\n\rfoo\r\e[5Cfoo\n" + + # Check that newlines in completions are handled correctly (issue #45836) + strings3 = ["abcdef", "123456\nijklmn"] + @test getcompletion(strings3) == "\033[0B\nabcdef\n123456\nijklmn\n" +end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index d711d0be5e243..8a6c6a3445e0a 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -35,6 +35,33 @@ function kill_timer(delay) return Timer(kill_test, delay) end +## Debugging toys. Usage: +## stdout_read = tee_repr_stdout(stdout_read) +## ccall(:jl_breakpoint, Cvoid, (Any,), stdout_read) +#function tee(f, in::IO) +# copy = Base.BufferStream() +# t = @async try +# while !eof(in) +# l = readavailable(in) +# f(l) +# write(copy, l) +# end +# catch ex +# if !(ex isa Base.IOError && ex.code == Base.UV_EIO) +# rethrow() # ignore EIO on `in` stream +# end +# finally +# # TODO: could we call closewrite to propagate an error, instead of always doing a clean close here? +# closewrite(copy) +# end +# Base.errormonitor(t) +# return copy +#end +#tee(out::IO, in::IO) = tee(l -> write(out, l), in) +#tee_repr_stdout(io) = tee(io) do x +# print(repr(String(copy(x))) * "\n") +#end + # REPL tests function fake_repl(@nospecialize(f); options::REPL.Options=REPL.Options(confirm_exit=false)) # Use pipes so we can easily do blocking reads @@ -99,8 +126,8 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri end global inc = false - global b = Condition() - global c = Condition() + global b = Base.Event(true) + global c = Base.Event(true) let cmd = "\"Hello REPL\"" write(stdin_write, "$(curmod_prefix)inc || wait($(curmod_prefix)b); r = $cmd; notify($(curmod_prefix)c); r\r") end @@ -143,44 +170,46 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri homedir_pwd = cd(pwd, homedir()) # Test `cd`'ing to an absolute path - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - write(stdin_write, "cd $(escape_string(tmpdir))\n") + wait(t) + t = @async write(stdin_write, "cd $(escape_string(tmpdir))\n") readuntil(stdout_read, "cd $(escape_string(tmpdir))") - readuntil(stdout_read, tmpdir_pwd) - readuntil(stdout_read, "\n") - readuntil(stdout_read, "\n") + readuntil(stdout_read, tmpdir_pwd * "\n\n") + wait(t) @test samefile(".", tmpdir) write(stdin_write, "\b") # Test using `cd` to move to the home directory - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - write(stdin_write, "cd\n") - readuntil(stdout_read, homedir_pwd) - readuntil(stdout_read, "\n") - readuntil(stdout_read, "\n") + wait(t) + t = @async write(stdin_write, "cd\n") + readuntil(stdout_read, homedir_pwd * "\n\n") + wait(t) @test samefile(".", homedir_pwd) - write(stdin_write, "\b") + t1 = @async write(stdin_write, "\b") # Test using `-` to jump backward to tmpdir - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - write(stdin_write, "cd -\n") - readuntil(stdout_read, tmpdir_pwd) - readuntil(stdout_read, "\n") - readuntil(stdout_read, "\n") + wait(t1) + wait(t) + t = @async write(stdin_write, "cd -\n") + readuntil(stdout_read, tmpdir_pwd * "\n\n") + wait(t) @test samefile(".", tmpdir) - write(stdin_write, "\b") + t1 = @async write(stdin_write, "\b") # Test using `~` (Base.expanduser) in `cd` commands if !Sys.iswindows() - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - write(stdin_write, "cd ~\n") - readuntil(stdout_read, homedir_pwd) - readuntil(stdout_read, "\n") - readuntil(stdout_read, "\n") + wait(t1) + wait(t) + t = @async write(stdin_write, "cd ~\n") + readuntil(stdout_read, homedir_pwd * "\n\n") + wait(t) @test samefile(".", homedir_pwd) write(stdin_write, "\b") end @@ -203,9 +232,10 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri # issue #20771 let s - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - write(stdin_write, "'\n") # invalid input + wait(t) + t = @async write(stdin_write, "'\n") # invalid input s = readuntil(stdout_read, "\n") @test occursin("shell> ", s) # check for the echo of the prompt @test occursin("'", s) # check for the echo of the input @@ -213,26 +243,28 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri @test startswith(s, "\e[0mERROR: unterminated single quote\nStacktrace:\n [1] ") || startswith(s, "\e[0m\e[1m\e[91mERROR: \e[39m\e[22m\e[91munterminated single quote\e[39m\nStacktrace:\n [1] ") write(stdin_write, "\b") + wait(t) end # issue #27293 if Sys.isunix() let s, old_stdout = stdout - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - write(stdin_write, "echo ~") - s = readuntil(stdout_read, "~") + wait(t) proc_stdout_read, proc_stdout = redirect_stdout() get_stdout = @async read(proc_stdout_read, String) try - write(stdin_write, "\n") + t = @async write(stdin_write, "echo ~\n") + readuntil(stdout_read, "~") readuntil(stdout_read, "\n") - s = readuntil(stdout_read, "\n") + s = readuntil(stdout_read, "\n") # the child has exited + wait(t) finally redirect_stdout(old_stdout) end - @test s == "\e[0m" # the child has exited + @test s == "\e[0m" close(proc_stdout) # check for the correct, expanded response @test occursin(expanduser("~"), fetch(get_stdout)) @@ -261,28 +293,33 @@ fake_repl(options = REPL.Options(confirm_exit=false,hascolor=true)) do stdin_wri # issue #10120 # ensure that command quoting works correctly let s, old_stdout = stdout - write(stdin_write, ";") + t = @async write(stdin_write, ";") readuntil(stdout_read, "shell> ") - Base.print_shell_escaped(stdin_write, Base.julia_cmd().exec..., special=Base.shell_special) - write(stdin_write, """ -e "println(\\"HI\\")\" """) + wait(t) + t = @async begin + Base.print_shell_escaped(stdin_write, Base.julia_cmd().exec..., special=Base.shell_special) + write(stdin_write, """ -e "println(\\"HI\\")\"""") + end readuntil(stdout_read, ")\"") + wait(t) proc_stdout_read, proc_stdout = redirect_stdout() get_stdout = @async read(proc_stdout_read, String) try - write(stdin_write, '\n') - s = readuntil(stdout_read, "\n", keep=true) - if s == "\n" + t = @async write(stdin_write, '\n') + s = readuntil(stdout_read, "\n") + if s == "" # if shell width is precisely the text width, # we may print some extra characters to fix the cursor state - s = readuntil(stdout_read, "\n", keep=true) + s = readuntil(stdout_read, "\n") @test occursin("shell> ", s) - s = readuntil(stdout_read, "\n", keep=true) - @test s == "\r\r\n" + s = readuntil(stdout_read, "\n") + @test s == "\r\r" else @test occursin("shell> ", s) end - s = readuntil(stdout_read, "\n", keep=true) - @test s == "\e[0m\n" # the child has exited + s = readuntil(stdout_read, "\n") + @test s == "\e[0m" # the child printed nothing + wait(t) finally redirect_stdout(old_stdout) end @@ -478,6 +515,7 @@ for prompt = ["TestΠ", () -> randstring(rand(1:10))] # Some manual setup s = LineEdit.init_state(repl.t, repl.interface) + repl.mistate = s LineEdit.edit_insert(s, "wip") # LineEdit functions related to history @@ -698,14 +736,22 @@ fake_repl() do stdin_write, stdout_read, repl REPL.run_repl(repl) end - global c = Condition() - sendrepl2(cmd) = write(stdin_write, "$cmd\n notify($(curmod_prefix)c)\n") + global c = Base.Event(true) + function sendrepl2(cmd) + t = @async readuntil(stdout_read, "\"done\"\n\n") + write(stdin_write, "$cmd\n notify($(curmod_prefix)c); \"done\"\n") + wait(c) + fetch(t) + end # Test removal of prefix in single statement paste sendrepl2("\e[200~julia> A = 2\e[201~\n") - wait(c) @test Main.A == 2 + # Test removal of prefix in single statement paste + sendrepl2("\e[200~In [12]: A = 2.2\e[201~\n") + @test Main.A == 2.2 + # Test removal of prefix in multiple statement paste sendrepl2("""\e[200~ julia> mutable struct T17599; a::Int; end @@ -716,7 +762,6 @@ fake_repl() do stdin_write, stdout_read, repl julia> A = 3\e[201~ """) - wait(c) @test Main.A == 3 @test Base.invokelatest(Main.foo, 4) @test Base.invokelatest(Main.T17599, 3).a == 3 @@ -729,28 +774,25 @@ fake_repl() do stdin_write, stdout_read, repl julia> A = 4 4\e[201~ """) - wait(c) @test Main.A == 4 @test Base.invokelatest(Main.goo, 4) == 5 # Test prefix removal only active in bracket paste mode sendrepl2("julia = 4\n julia> 3 && (A = 1)\n") - wait(c) @test Main.A == 1 # Test that indentation corresponding to the prompt is removed - sendrepl2("""\e[200~julia> begin\n α=1\n β=2\n end\n\e[201~""") - wait(c) - readuntil(stdout_read, "begin") - @test readuntil(stdout_read, "end", keep=true) == "\n\r\e[7C α=1\n\r\e[7C β=2\n\r\e[7Cend" + s = sendrepl2("""\e[200~julia> begin\n α=1\n β=2\n end\n\e[201~""") + s2 = split(rsplit(s, "begin", limit=2)[end], "end", limit=2)[1] + @test s2 == "\n\r\e[7C α=1\n\r\e[7C β=2\n\r\e[7C" + # for incomplete input (`end` below is added after the end of bracket paste) - sendrepl2("""\e[200~julia> begin\n α=1\n β=2\n\e[201~end""") - wait(c) - readuntil(stdout_read, "begin") - readuntil(stdout_read, "begin") - @test readuntil(stdout_read, "end", keep=true) == "\n\r\e[7C α=1\n\r\e[7C β=2\n\r\e[7Cend" + s = sendrepl2("""\e[200~julia> begin\n α=1\n β=2\n\e[201~end""") + s2 = split(rsplit(s, "begin", limit=2)[end], "end", limit=2)[1] + @test s2 == "\n\r\e[7C α=1\n\r\e[7C β=2\n\r\e[7C" # Test switching repl modes + redirect_stdout(devnull) do # to suppress "foo" echoes sendrepl2("""\e[200~ julia> A = 1 1 @@ -772,9 +814,9 @@ fake_repl() do stdin_write, stdout_read, repl julia> B = 2 2\e[201~ """) - wait(c) @test Main.A == 1 @test Main.B == 2 + end # redirect_stdout # Close repl write(stdin_write, '\x04') @@ -809,13 +851,13 @@ fake_repl() do stdin_write, stdout_read, repl repltask = @async REPL.run_interface(repl.t, LineEdit.ModalInterface(Any[panel, search_prompt])) - write(stdin_write,"a\n") + write(stdin_write, "a\n") @test wait(c) == "a" # Up arrow enter should recall history even at the start - write(stdin_write,"\e[A\n") + write(stdin_write, "\e[A\n") @test wait(c) == "a" # And again - write(stdin_write,"\e[A\n") + write(stdin_write, "\e[A\n") @test wait(c) == "a" # Close REPL ^D write(stdin_write, '\x04') @@ -841,7 +883,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` output = readuntil(ptm, ' ', keep=true) if Sys.iswindows() - # Our fake pty is actually a pipe, and thus lacks the input echo feature of posix + # Our fake pty is actually a pipe, and thus lacks the input echo feature of posix @test output == "1\n\njulia> " else @test output == "1\r\nexit()\r\n1\r\n\r\njulia> " @@ -1077,16 +1119,18 @@ fake_repl() do stdin_write, stdout_read, repl end @eval Main module TestShowTypeREPL; export TypeA; struct TypeA end; end - write(stdin_write, "TestShowTypeREPL.TypeA\n") - @test endswith(readline(stdout_read), "\r\e[7CTestShowTypeREPL.TypeA\r\e[29C") - readline(stdout_read) - @test readline(stdout_read) == "" + t = @async write(stdin_write, "TestShowTypeREPL.TypeA\n") + s = readuntil(stdout_read, "\n\n") + s2 = rsplit(s, "\n", limit=2)[end] + @test s2 == "\e[0mMain.TestShowTypeREPL.TypeA" + wait(t) @eval Main using .TestShowTypeREPL readuntil(stdout_read, "julia> ", keep=true) - write(stdin_write, "TypeA\n") - @test endswith(readline(stdout_read), "\r\e[7CTypeA\r\e[12C") - readline(stdout_read) - @test readline(stdout_read) == "" + t = @async write(stdin_write, "TypeA\n") + s = readuntil(stdout_read, "\n\n") + s2 = rsplit(s, "\n", limit=2)[end] + @test s2 == "\e[0mTypeA" + wait(t) # Close REPL ^D readuntil(stdout_read, "julia> ", keep=true) @@ -1094,7 +1138,33 @@ fake_repl() do stdin_write, stdout_read, repl Base.wait(repltask) end -help_result(line) = Base.eval(REPL._helpmode(IOBuffer(), line)) +# test activate_module +fake_repl() do stdin_write, stdout_read, repl + repl.history_file = false + repl.interface = REPL.setup_interface(repl) + repl.mistate = LineEdit.init_state(repl.t, repl.interface) + + repltask = @async begin + REPL.run_repl(repl) + end + + write(stdin_write, " ( 123 , Base.Fix1 , ) \n") + s = readuntil(stdout_read, "\n\n") + @test endswith(s, "(123, Base.Fix1)") + + repl.mistate.active_module = Base # simulate activate_module(Base) + write(stdin_write, " ( 456 , Base.Fix2 , ) \n") + s = readuntil(stdout_read, "\n\n") + # ".Base" prefix not shown here + @test endswith(s, "(456, Fix2)") + + # Close REPL ^D + readuntil(stdout_read, "julia> ", keep=true) + write(stdin_write, '\x04') + Base.wait(repltask) +end + +help_result(line, mod::Module=Base) = Core.eval(mod, REPL._helpmode(IOBuffer(), line)) # Docs.helpmode tests: we test whether the correct expressions are being generated here, # rather than complete integration with Julia's REPL mode system. @@ -1133,10 +1203,19 @@ end @test occursin("broadcast", sprint(show, help_result(".<="))) # Issue 39427 -@test occursin("does not exist", sprint(show, help_result(":="))) +@test occursin("does not exist.", sprint(show, help_result(":="))) +global some_undef_global +@test occursin("exists,", sprint(show, help_result("some_undef_global", @__MODULE__))) # Issue #40563 @test occursin("does not exist", sprint(show, help_result(".."))) +# test that helpmode is sensitive to contextual module +@test occursin("No documentation found", sprint(show, help_result("Fix2", Main))) +@test occursin("A type representing a partially-applied version", # exact string may change + sprint(show, help_result("Base.Fix2", Main))) +@test occursin("A type representing a partially-applied version", # exact string may change + sprint(show, help_result("Fix2", Base))) + # Issue #25930 @@ -1211,15 +1290,18 @@ fake_repl() do stdin_write, stdout_read, repl repltask = @async begin REPL.run_repl(repl) end - write(stdin_write, "Expr(:call, GlobalRef(Base.Math, :float), Core.SlotNumber(1))\n") + t = @async write(stdin_write, "Expr(:call, GlobalRef(Base.Math, :float), Core.SlotNumber(1))\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0m:(Base.Math.float(_1))" - @test readline(stdout_read) == "" + s = readuntil(stdout_read, "\n\n") + @test endswith(s, "\e[0m:(Base.Math.float(_1))") + wait(t) + readuntil(stdout_read, "julia> ", keep=true) - write(stdin_write, "ans\n") + t = @async write(stdin_write, "ans\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0m:(Base.Math.float(_1))" - @test readline(stdout_read) == "" + s = readuntil(stdout_read, "\n\n") + @test endswith(s, "\e[0m:(Base.Math.float(_1))") + wait(t) readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, '\x04') Base.wait(repltask) @@ -1230,17 +1312,21 @@ fake_repl() do stdin_write, stdout_read, repl repltask = @async begin REPL.run_repl(repl) end - write(stdin_write, "struct Errs end\n") - readline(stdout_read) + t = @async write(stdin_write, "struct Errs end\n") + readuntil(stdout_read, "\e[0m") readline(stdout_read) + wait(t) readuntil(stdout_read, "julia> ", keep=true) - write(stdin_write, "Base.show(io::IO, ::Errs) = throw(Errs())\n") + t = @async write(stdin_write, "Base.show(io::IO, ::Errs) = throw(Errs())\n") readline(stdout_read) + readuntil(stdout_read, "\e[0m") readline(stdout_read) + wait(t) readuntil(stdout_read, "julia> ", keep=true) - write(stdin_write, "Errs()\n") - readline(stdout_read) + t = @async write(stdin_write, "Errs()\n") readline(stdout_read) + readuntil(stdout_read, "\n\n") + wait(t) readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, '\x04') wait(repltask) @@ -1254,7 +1340,8 @@ fake_repl() do stdin_write, stdout_read, repl end write(stdin_write, "?;\n") readline(stdout_read) - @test endswith(readline(stdout_read), "search: ;") + s = readline(stdout_read) + @test endswith(s, "search: ;") readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, '\x04') Base.wait(repltask) @@ -1286,7 +1373,7 @@ fake_repl() do stdin_write, stdout_read, repl # necessary to read at least some part of the buffer, # for the "region_active" to have time to be updated - @test LineEdit.state(repl.mistate).region_active == :off + @test LineEdit.state(repl.mistate).region_active === :off @test s4 == "anything" # no control characters between the last two occurrences of "anything" write(stdin_write, "\x15\x04") Base.wait(repltask) @@ -1395,14 +1482,14 @@ fake_repl() do stdin_write, stdout_read, repl REPL.run_repl(repl) end # initialize `err` to `nothing` - write(stdin_write, "global err = nothing\n") - readline(stdout_read) - readline(stdout_read) == "\e[0m" + t = @async (readline(stdout_read); readuntil(stdout_read, "\e[0m\n")) + write(stdin_write, "setglobal!(Base.MainInclude, :err, nothing)\n") + wait(t) readuntil(stdout_read, "julia> ", keep=true) # 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" @test readline(stdout_read) == "" readuntil(stdout_read, "julia> ", keep=true) # check that top-level error did not change `err` @@ -1412,18 +1499,159 @@ fake_repl() do stdin_write, stdout_read, repl readuntil(stdout_read, "julia> ", keep=true) # generate deeper error write(stdin_write, "foo() = foobar\n") + readuntil(stdout_read, "\n\e[0m", keep=true) readline(stdout_read) 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" 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" @test readline(stdout_read) == "Stacktrace:" + readuntil(stdout_read, "\n\n", keep=true) + readuntil(stdout_read, "julia> ", keep=true) + write(stdin_write, '\x04') + Base.wait(repltask) +end + +fakehistory_2 = """ +# time: 2014-06-29 20:44:29 EDT +# mode: shell +\txyz = 2 +# time: 2014-06-29 20:44:29 EDT +# mode: julia +\txyz = 2 +# time: 2014-06-29 21:44:29 EDT +# mode: julia +\txyz = 1 +# time: 2014-06-30 17:32:49 EDT +# mode: julia +\tabc = 3 +# time: 2014-06-30 17:32:59 EDT +# mode: julia +\txyz = 1 +# time: 2014-06-30 99:99:99 EDT +# mode: julia +\txyz = 2 +# time: 2014-06-30 99:99:99 EDT +# mode: extended +\tuser imported custom mode +""" + +# Test various history related issues +for prompt = ["TestΠ", () -> randstring(rand(1:10))] + fake_repl() do stdin_write, stdout_read, repl + # In the future if we want we can add a test that the right object + # gets displayed by intercepting the display + repl.specialdisplay = REPL.REPLDisplay(repl) + + errormonitor(@async write(devnull, stdout_read)) # redirect stdout to devnull so we drain the output pipe + + repl.interface = REPL.setup_interface(repl) + repl_mode = repl.interface.modes[1] + shell_mode = repl.interface.modes[2] + help_mode = repl.interface.modes[3] + histp = repl.interface.modes[4] + prefix_mode = repl.interface.modes[5] + + hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:julia => repl_mode, + :shell => shell_mode, + :help => help_mode)) + hist_path = tempname() + write(hist_path, fakehistory_2) + REPL.hist_from_file(hp, hist_path) + f = open(hist_path, read=true, write=true, create=true) + hp.history_file = f + seekend(f) + REPL.history_reset_state(hp) + + histp.hp = repl_mode.hist = shell_mode.hist = help_mode.hist = hp + + s = LineEdit.init_state(repl.t, prefix_mode) + prefix_prev() = REPL.history_prev_prefix(s, hp, "x") + prefix_prev() + @test LineEdit.mode(s) == repl_mode + @test buffercontents(LineEdit.buffer(s)) == "xyz = 2" + prefix_prev() + @test LineEdit.mode(s) == repl_mode + @test buffercontents(LineEdit.buffer(s)) == "xyz = 1" + prefix_prev() + @test LineEdit.mode(s) == repl_mode + @test buffercontents(LineEdit.buffer(s)) == "xyz = 2" + prefix_prev() + @test LineEdit.mode(s) == shell_mode + @test buffercontents(LineEdit.buffer(s)) == "xyz = 2" + end +end + +fake_repl() do stdin_write, stdout_read, repl + repltask = @async begin + REPL.run_repl(repl) + end + repl.interface = REPL.setup_interface(repl) + s = LineEdit.init_state(repl.t, repl.interface) + LineEdit.edit_insert(s, "1234αβ") + input_f = function(filename, line, column) + write(filename, "1234αβ56γ\n") + end + LineEdit.edit_input(s, input_f) + @test buffercontents(LineEdit.buffer(s)) == "1234αβ56γ" +end + +# Non standard output_prefix, tested via `numbered_prompt!` +fake_repl() do stdin_write, stdout_read, repl + repl.interface = REPL.setup_interface(repl) + + backend = REPL.REPLBackend() + repltask = @async begin + REPL.run_repl(repl; backend) + end + + REPL.numbered_prompt!(repl, backend) + + global c = Base.Event(true) + function sendrepl2(cmd, txt) + t = @async write(stdin_write, "$cmd\n notify($(curmod_prefix)c); \"done\"\n") + r = readuntil(stdout_read, txt, keep=true) + readuntil(stdout_read, "\"done\"\n\n", keep=true) + wait(c) + wait(t) + return r + end + + s = sendrepl2("\"z\" * \"z\"\n", "\"zz\"") + @test contains(s, "In [1]") + @test endswith(s, "Out[1]: \"zz\"") + + s = sendrepl2("\"y\" * \"y\"\n", "\"yy\"") + @test endswith(s, "Out[3]: \"yy\"") + + s = sendrepl2("Out[1] * Out[3]\n", "\"zzyy\"") + @test endswith(s, "Out[5]: \"zzyy\"") + + # test a top-level expression + s = sendrepl2("import REPL\n", "In [8]") + @test !contains(s, "ERROR") + @test !contains(s, "[6]") + @test !contains(s, "Out[7]:") + @test contains(s, "In [7]: ") + @test contains(s, "import REPL") + s = sendrepl2("REPL\n", "In [10]") + @test contains(s, "Out[9]: REPL") + + # Test for https://github.com/JuliaLang/julia/issues/46451 + s = sendrepl2("x_47878 = range(-1; stop = 1)\n", "-1:1") + @test contains(s, "Out[11]: -1:1") + + # Test for https://github.com/JuliaLang/julia/issues/49041 + s = sendrepl2("using Test; @test true", "In [14]") + @test !contains(s, "ERROR") + @test contains(s, "Test Passed") + write(stdin_write, '\x04') Base.wait(repltask) end diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 1d59b6e057882..b0d1ff4b5237a 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -7,7 +7,7 @@ using REPL @testset "Check symbols previously not shown by REPL.doc_completions()" begin symbols = ["?","=","[]","[","]","{}","{","}",";","","'","&&","||","julia","Julia","new","@var_str"] for i in symbols - @test REPL.doc_completions(i)[1]==i + @test i ∈ REPL.doc_completions(i, Main) end end let ex = quote @@ -101,15 +101,27 @@ let ex = quote test11(x::Int, y::Int, z) = pass test11(_, _, s::String) = pass + test!12() = pass + kwtest(; x=1, y=2, w...) = pass kwtest2(a; x=1, y=2, w...) = pass + kwtest3(a::Number; length, len2, foobar, kwargs...) = pass + kwtest3(a::Real; another!kwarg, len2) = pass + kwtest3(a::Integer; namedarg, foobar, slurp...) = pass + kwtest4(a::AbstractString; _a1b, x23) = pass + kwtest4(a::String; _a1b, xαβγ) = pass + kwtest4(a::SubString; x23, _something) = pass + kwtest5(a::Int, b, x...; somekwarg, somekotherkwarg) = pass + kwtest5(a::Char, b; xyz) = pass + + const named = (; len2=3) array = [1, 1] varfloat = 0.1 const tuple = (1, 2) - test_y_array=[CompletionFoo.Test_y(rand()) for i in 1:10] + test_y_array=[(@__MODULE__).Test_y(rand()) for i in 1:10] test_dict = Dict("abc"=>1, "abcd"=>10, :bar=>2, :bar2=>9, Base=>3, occursin=>4, `ls`=>5, 66=>7, 67=>8, ("q",3)=>11, "α"=>12, :α=>13) @@ -120,7 +132,12 @@ let ex = quote macro testcmd_cmd(s) end macro tϵsτcmδ_cmd(s) end - end + var"complicated symbol with spaces" = 5 + + struct WeirdNames end + Base.propertynames(::WeirdNames) = (Symbol("oh no!"), Symbol("oh yes!")) + + end # module CompletionFoo test_repl_comp_dict = CompletionFoo.test_dict test_repl_comp_customdict = CompletionFoo.test_customdict test_dict_ℂ = Dict(1=>2) @@ -141,9 +158,23 @@ test_complete_context(s, m) = map_completion_text(@inferred(completions(s,lasti test_complete_foo(s) = test_complete_context(s, Main.CompletionFoo) test_complete_noshift(s) = map_completion_text(@inferred(completions(s, lastindex(s), Main, false))) +test_methods_list(@nospecialize(f), tt) = map(x -> string(x.method), Base._methods_by_ftype(Base.signature_type(f, tt), 10, Base.get_world_counter())) + + module M32377 end test_complete_32377(s) = map_completion_text(completions(s,lastindex(s), M32377)) +macro test_nocompletion(s) + tests = [ + :(@test c == String[]), + :(@test res === false) + ] + for t in tests + t.args[2] = __source__ # fix the LineNumberNode + end + return Expr(:let, Expr(:(=), :((c, _, res)), :(test_complete($(esc(s))))), Expr(:block, tests...)) +end + let s = "" c, r = test_complete(s) @test "CompletionFoo" in c @@ -270,17 +301,17 @@ let @test isempty(c) end -# inexistent completion inside a string -let s = "Base.print(\"lol" - c, r, res = test_complete(s) - @test res == false +# issue 46800: (3,2).<TAB> errors in the REPL +let + c, r = test_complete("(3,2).") + @test isempty(c) end +# inexistent completion inside a string +@test_nocompletion("Base.print(\"lol") + # inexistent completion inside a cmd -let s = "run(`lol" - c, r, res = test_complete(s) - @test res == false -end +@test_nocompletion("run(`lol") # test latex symbol completions let s = "\\alpha" @@ -395,8 +426,9 @@ end let s = "CompletionFoo.test(1, 1, " c, r, res = test_complete(s) @test !res - @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Int, Int}))) - @test c[2] == string(first(methods(Main.CompletionFoo.test, Tuple{}))) # corresponding to the vararg + m = test_methods_list(Main.CompletionFoo.test, Tuple{Int, Int, Vararg}) + @test c[1] == m[1] + @test c[2] == m[2] @test length(c) == 2 # In particular, this checks that test(x::Real, y::Real) is not a valid completion # since it is strictly less specific than test(x::T, y::T) where T @@ -407,7 +439,7 @@ end let s = "CompletionFoo.test(CompletionFoo.array," c, r, res = test_complete(s) @test !res - @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Array{Int, 1}, Any}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test, Tuple{Array{Int, 1}, Any, Vararg})) @test length(c) == 2 @test r == 1:18 @test s[r] == "CompletionFoo.test" @@ -416,7 +448,7 @@ end let s = "CompletionFoo.test(1,1,1," c, r, res = test_complete(s) @test !res - @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Any, Any, Any}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test, Tuple{Any, Any, Any, Vararg})) @test length(c) == 1 @test r == 1:18 @test s[r] == "CompletionFoo.test" @@ -440,7 +472,7 @@ end let s = "prevind(\"θ\",1," c, r, res = test_complete(s) - @test c[1] == string(first(methods(prevind, Tuple{String, Int}))) + @test c[1] == first(test_methods_list(prevind, Tuple{String, Int, Vararg})) @test r == 1:7 @test s[r] == "prevind" end @@ -449,7 +481,7 @@ for (T, arg) in [(String,"\")\""),(Char, "')'")] s = "(1, CompletionFoo.test2($arg," c, r, res = test_complete(s) @test length(c) == 1 - @test c[1] == string(first(methods(Main.CompletionFoo.test2, Tuple{T,}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test2, Tuple{T, Vararg})) @test r == 5:23 @test s[r] == "CompletionFoo.test2" end @@ -457,19 +489,19 @@ end let s = "(1, CompletionFoo.test2(`')'`," c, r, res = test_complete(s) @test length(c) == 1 - @test c[1] == string(first(methods(Main.CompletionFoo.test2, Tuple{Cmd}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test2, Tuple{Cmd, Vararg})) end let s = "CompletionFoo.test3([1, 2] .+ CompletionFoo.varfloat," c, r, res = test_complete(s) @test !res - @test_broken only(c) == string(first(methods(Main.CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64}))) + @test_broken only(c) == first(test_methods_list(Main.CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64, Vararg})) end let s = "CompletionFoo.test3([1.,2.], 1.," c, r, res = test_complete(s) @test !res - @test c[1] == string(first(methods(Main.CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64, Vararg})) @test r == 1:19 @test length(c) == 1 @test s[r] == "CompletionFoo.test3" @@ -478,7 +510,7 @@ end let s = "CompletionFoo.test4(\"e\",r\" \"," c, r, res = test_complete(s) @test !res - @test c[1] == string(first(methods(Main.CompletionFoo.test4, Tuple{String, Regex}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test4, Tuple{String, Regex, Vararg})) @test r == 1:19 @test length(c) == 1 @test s[r] == "CompletionFoo.test4" @@ -489,7 +521,7 @@ end let s = "CompletionFoo.test5(broadcast((x,y)->x==y, push!(Base.split(\"\",' '),\"\",\"\"), \"\")," c, r, res = test_complete(s) @test !res - @test_broken only(c) == string(first(methods(Main.CompletionFoo.test5, Tuple{BitArray{1}}))) + @test_broken only(c) == first(test_methods_list(Main.CompletionFoo.test5, Tuple{BitArray{1}, Vararg})) end # test partial expression expansion @@ -497,39 +529,45 @@ let s = "CompletionFoo.test5(Bool[x==1 for x=1:4]," c, r, res = test_complete(s) @test !res @test length(c) == 1 - @test c[1] == string(first(methods(Main.CompletionFoo.test5, Tuple{Array{Bool,1}}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test5, Tuple{Array{Bool,1}, Vararg})) end let s = "CompletionFoo.test4(CompletionFoo.test_y_array[1]()[1], CompletionFoo.test_y_array[1]()[2], " c, r, res = test_complete(s) @test !res @test length(c) == 1 - @test c[1] == string(first(methods(Main.CompletionFoo.test4, Tuple{String, String}))) + @test c[1] == first(test_methods_list(Main.CompletionFoo.test4, Tuple{String, String, Vararg})) end -# Test that string escaption is handled correct +# Test that string escaping is handled correct let s = """CompletionFoo.test4("\\"",""" c, r, res = test_complete(s) @test !res @test length(c) == 2 end -########## Test where the current inference logic fails ######## -# Fails due to inference fails to determine a concrete type for arg 1 -# But it returns AbstractArray{T,N} and hence is able to remove test5(x::Float64) from the suggestions -let s = "CompletionFoo.test5(AbstractArray[[]][1]," +# Test max method suggestions +let s = "convert(" + c, _, res = test_complete_noshift(s) + @test !res + @test only(c) == "convert( too many methods, use SHIFT-TAB to show )" + c2, _, res2 = test_complete(s) + @test !res2 + @test any(==(string(first(methods(convert)))), c2) + @test length(c2) > REPL.REPLCompletions.MAX_METHOD_COMPLETIONS +end + +let s = "CompletionFoo.test5(AbstractArray[Bool[]][1]," c, r, res = test_complete(s) @test !res - @test length(c) == 2 + @test length(c) == 1 end -# equivalent to above but due to the time macro the completion fails to find the concrete type -let s = "CompletionFoo.test3(@time([1, 2] + CompletionFoo.varfloat)," +let s = "CompletionFoo.test3(@time([1, 2] .+ CompletionFoo.varfloat)," c, r, res = test_complete(s) @test !res @test length(c) == 2 end -################################################################# # method completions with kwargs let s = "CompletionFoo.kwtest( " @@ -537,27 +575,58 @@ let s = "CompletionFoo.kwtest( " @test !res @test length(c) == 1 @test occursin("x, y, w...", c[1]) + @test (c, r, res) == test_complete("CompletionFoo.kwtest(;") + @test (c, r, res) == test_complete("CompletionFoo.kwtest(; x=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest(; kw=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest(x=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest(x=1; ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest(x=kw=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest(; x=kw=1, ") end -for s in ("CompletionFoo.kwtest(;", - "CompletionFoo.kwtest(; x=1, ", - "CompletionFoo.kwtest(; kw=1, ", - ) +let s = "CompletionFoo.kwtest2(1, x=1," c, r, res = test_complete(s) @test !res @test length(c) == 1 - @test occursin("x, y, w...", c[1]) + @test occursin("a; x, y, w...", c[1]) + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1; x=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1, x=1; ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1, kw=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1; kw=1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1, kw=1; ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(y=3, 1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(y=3, 1; ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(kw=3, 1, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(kw=3, 1; ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1; ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest2(1, ") +end + +let s = "CompletionFoo.kwtest4(x23=18, x; " + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 # TODO: remove "kwtest4(a::String; _a1b, xαβγ)" + @test any(str->occursin("kwtest4(a::SubString", str), c) + @test any(str->occursin("kwtest4(a::AbstractString", str), c) + @test (c, r, res) == test_complete("CompletionFoo.kwtest4(x23=18, x, ") + @test (c, r, res) == test_complete("CompletionFoo.kwtest4(x23=18, ") end -for s in ("CompletionFoo.kwtest2(1; x=1,", - "CompletionFoo.kwtest2(1; kw=1, ", - ) +# TODO: @test_nocompletion("CompletionFoo.kwtest4(x23=17; ") +# TODO: @test_nocompletion("CompletionFoo.kwtest4.(x23=17; ") + +let s = "CompletionFoo.kwtest5(3, somekwarg=6," c, r, res = test_complete(s) @test !res @test length(c) == 1 - @test occursin("a; x, y, w...", c[1]) + @test occursin("kwtest5(a::$(Int), b, x...; somekwarg, somekotherkwarg)", c[1]) + @test (c, r, res) == test_complete("CompletionFoo.kwtest5(3, somekwarg=6, anything, ") end +# TODO: @test_nocompletion("CompletionFoo.kwtest5(3; somekwarg=6,") +# TODO: @test_nocompletion("CompletionFoo.kwtest5(3;") +# TODO: @test_nocompletion("CompletionFoo.kwtest5(3; somekwarg=6, anything, ") + ################################################################# # method completion with `?` (arbitrary method with given argument types) @@ -633,6 +702,36 @@ let s = "CompletionFoo.?()" @test occursin("test10(s::String...)", c[1]) end +#= TODO: restrict the number of completions when a semicolon is present in ".?(" syntax +let s = "CompletionFoo.?(; y=2, " + c, r, res = test_complete(s) + @test !res + @test length(c) == 4 + @test all(x -> occursin("kwtest", x), c) + # We choose to include kwtest2 and kwtest3 although the number of args if wrong. + # This is because the ".?(" syntax with no closing parenthesis does not constrain the + # number of arguments in the methods it suggests. +end + +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]) + # the other two kwtest3 methods should not appear because of specificity +end +=# + +# For the ".?(" syntax, do not constrain the number of arguments even with a semicolon. +@test test_complete("CompletionFoo.?(; ") == + test_complete("CompletionFoo.?(") + +#TODO: @test test_complete("CompletionFoo.?(Any[]...; ") == test_complete("CompletionFoo.?(Cmd[]..., ") == test_complete("CompletionFoo.?(") + +@test test_complete("CompletionFoo.?()") == test_complete("CompletionFoo.?(;)") + +#TODO: @test_nocompletion("CompletionFoo.?(3; len2=5; ") + ################################################################# # Test method completion with varargs @@ -685,7 +784,7 @@ end let s = "CompletionFoo.test10(\"a\", Union{Signed,Bool,String}[3][1], " c, r, res = test_complete(s) @test !res - @test length(c) == 4 + @test length(c) == 2 @test all(startswith("test10("), c) @test allunique(c) @test !any(str->occursin("test10(a::Integer, b::Integer, c)", str), c) @@ -695,7 +794,7 @@ end let s = "CompletionFoo.test11(Integer[false][1], Integer[14][1], " c, r, res = test_complete(s) @test !res - @test length(c) == 4 + @test length(c) == 3 @test all(startswith("test11("), c) @test allunique(c) end @@ -703,16 +802,16 @@ end let s = "CompletionFoo.test11(Integer[-7][1], Integer[0x6][1], 6," c, r, res = test_complete(s) @test !res - @test length(c) == 3 + @test length(c) == 2 @test any(str->occursin("test11(a::Integer, b, c)", str), c) @test any(str->occursin("test11(u, v::Integer, w)", str), c) - @test any(str->occursin("test11(x::$Int, y::$Int, z)", str), c) + @test !any(str->occursin("test11(x::$Int, y::$Int, z)", str), c) end let s = "CompletionFoo.test11(3, 4," c, r, res = test_complete(s) @test !res - @test length(c) == 4 + @test length(c) == 2 @test any(str->occursin("test11(x::$Int, y::$Int, z)", str), c) @test any(str->occursin("test11(::Any, ::Any, s::String)", str), c) end @@ -742,6 +841,56 @@ let s = "CompletionFoo.test11('d', 3," @test any(str->occursin("test11(::Any, ::Any, s::String)", str), c) end +let s = "CompletionFoo.test!12(" + c, r, res = test_complete(s) + @test !res + @test occursin("test!12()", only(c)) +end + +#= TODO: Test method completion depending on the number of arguments with splatting + +@test_nocompletion("CompletionFoo.test3(unknown; ") +@test_nocompletion("CompletionFoo.test3.(unknown; ") + +let s = "CompletionFoo.test2(unknown..., somethingelse..., xyz...; " # splat may be empty + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 + @test all(str->occursin("test2(", str), c) + @test (c, r, res) == test_complete("CompletionFoo.test2(unknown..., somethingelse..., xyz, ") + @test (c, r, res) == test_complete("CompletionFoo.test2(unknown..., somethingelse..., xyz; ") +end + +let s = "CompletionFoo.test('a', args..., 'b';" + c, r, res = test_complete(s) + @test !res + @test length(c) == 1 + @test occursin("test(args...)", c[1]) + @test (c, r, res) == test_complete("CompletionFoo.test(a, args..., b, c;") +end + +let s = "CompletionFoo.test(3, 5, args...,;" + c, r, res = test_complete(s) + @test !res + @test length(c) == 2 + @test any(str->occursin("test(x::T, y::T) where T<:Real", str), c) + @test any(str->occursin("test(args...)", str), c) +end +=# + +# Test that method calls with ill-formed kwarg syntax are not completed + +@test_nocompletion("CompletionFoo.kwtest(; x=2, y=4; kw=3, ") +@test_nocompletion("CompletionFoo.kwtest(x=2; y=4; ") +@test_nocompletion("CompletionFoo.kwtest((x=y)=4, ") +@test_nocompletion("CompletionFoo.kwtest(; (x=y)=4, ") +@test_nocompletion("CompletionFoo.kwtest(; w...=16, ") +@test_nocompletion("CompletionFoo.kwtest(; 2, ") +@test_nocompletion("CompletionFoo.kwtest(; 2=3, ") +@test_nocompletion("CompletionFoo.kwtest3(im; (true ? length : length), ") +@test_nocompletion("CompletionFoo.kwtest.(x=2; y=4; ") +@test_nocompletion("CompletionFoo.kwtest.(; w...=16, ") + # Test of inference based getfield completion let s = "(1+2im)." c,r = test_complete(s) @@ -778,6 +927,13 @@ let s = "CompletionFoo.test6()[1](CompletionFoo.Test_y(rand())).y" @test c[1] == "yy" end +let s = "CompletionFoo.named." + c, r = test_complete(s) + @test length(c) == 1 + @test r == (lastindex(s) + 1):lastindex(s) + @test c[1] == "len2" +end + # Test completion in multi-line comments let s = "#=\n\\alpha" c, r, res = test_complete(s) @@ -947,6 +1103,9 @@ let s, c, r s = "\"~" @test "tmpfoobar/" in c c,r = test_complete(s) + s = "\"~user" + c, r = test_complete(s) + @test isempty(c) rm(dir) end end @@ -1249,6 +1408,129 @@ test_dict_completion("test_repl_comp_customdict") @test "tϵsτcmδ`" in c end +@testset "Keyword-argument completion" begin + 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 "len2=" ∈ c + @test "len2" ∉ c + c, r = test_complete("CompletionFoo.kwtest3.(a;\nlength") + @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 "foobar=" ∈ c + c, r = test_complete("CompletionFoo.kwtest3(a; another!kwarg=0, le") + @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!") + @test c == ["another!kwarg="] + c, r = test_complete("CompletionFoo.kwtest3(a; another!kwarg=0, foob") + @test c == ["foobar="] # the first method could be called and `anotherkwarg` slurped + c, r = test_complete("CompletionFoo.kwtest3(a; namedarg=0, foob") + @test c == ["foobar="] + + # Check for confusion with CompletionFoo.named + 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 "namedarg=" ∈ c + @test "len2" ∉ c + c, r = test_complete_foo("kwtest3(blabla; named.") + @test c == ["len2"] + 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 "len2=" ∈ c + c, r = test_complete_foo("kwtest3(1+3im; named") + @test "named" ∈ c + # TODO: @test "namedarg=" ∉ c + @test "len2" ∉ c + c, r = test_complete_foo("kwtest3(1+3im; named.") + @test c == ["len2"] + + c, r = test_complete("CompletionFoo.kwtest4(a; x23=0, _") + @test "_a1b=" ∈ c + @test "_something=" ∈ c + c, r = test_complete("CompletionFoo.kwtest4(a; xαβγ=1, _") + @test "_a1b=" ∈ c + # TODO: @test "_something=" ∉ c # no such keyword for the method with keyword `xαβγ` + c, r = test_complete("CompletionFoo.kwtest4.(a; xαβγ=1, _") + @test "_a1b=" ∈ c + # TODO: @test "_something=" ∉ c # broadcasting does not affect the existence of kwargs + c, r = test_complete("CompletionFoo.kwtest4(a; x23=0, x") + @test "x23=" ∉ c + # TODO: @test "xαβγ=" ∉ c + c, r = test_complete("CompletionFoo.kwtest4.(a; x23=0, x") + @test "x23=" ∉ c + # TODO: @test "xαβγ=" ∉ c + c, r = test_complete("CompletionFoo.kwtest4(a; _a1b=1, x") + @test "x23=" ∈ c + @test "xαβγ=" ∈ c + + c, r = test_complete("CompletionFoo.kwtest5(3, 5; somek") + @test c == ["somekotherkwarg=", "somekwarg="] + c, r = test_complete("CompletionFoo.kwtest5(3, 5, somekwarg=4, somek") + @test c == ["somekotherkwarg="] + c, r = test_complete("CompletionFoo.kwtest5(3, 5, 7; somekw") + @test c == ["somekwarg="] + c, r = test_complete("CompletionFoo.kwtest5(3, 5, 7, 9; somekw") + @test c == ["somekwarg="] + c, r = test_complete("CompletionFoo.kwtest5(3, 5, 7, 9, Any[]...; somek") + @test c == ["somekotherkwarg=", "somekwarg="] + c, r = test_complete("CompletionFoo.kwtest5(unknownsplat...; somekw") + @test c == ["somekwarg="] + c, r = test_complete("CompletionFoo.kwtest5(3, 5, 7, 9, somekwarg=4, somek") + @test c == ["somekotherkwarg="] + c, r = test_complete("CompletionFoo.kwtest5(String[]..., unknownsplat...; xy") + @test c == ["xyz="] + c, r = test_complete("CompletionFoo.kwtest5('a', unknownsplat...; xy") + @test c == ["xyz="] + c, r = test_complete("CompletionFoo.kwtest5('a', 3, String[]...; xy") + @test c == ["xyz="] + + # return true if no completion suggests a keyword argument + function hasnokwsuggestions(str) + c, _ = test_complete(str) + return !any(x -> endswith(x, r"[a-z]="), c) + end + @test hasnokwsuggestions("Completio") + @test hasnokwsuggestions("CompletionFoo.kwt") + @test hasnokwsuggestions("CompletionFoo.kwtest3(") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a;") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; len2=") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; len2=le") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; len2=3 ") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; [le") + @test hasnokwsuggestions("CompletionFoo.kwtest3([length; le") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; (le") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; foo(le") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; (; le") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; length, ") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; kwargs..., ") + + #= TODO: Test the absence of kwarg completion the call is incompatible with the method bearing the kwarg. + @test hasnokwsuggestions("CompletionFoo.kwtest3(a") + @test hasnokwsuggestions("CompletionFoo.kwtest3(le") + @test hasnokwsuggestions("CompletionFoo.kwtest3(a; unknown=4, another!kw") # only methods 1 and 3 could slurp `unknown` + @test hasnokwsuggestions("CompletionFoo.kwtest3(1+3im; nameda") + @test hasnokwsuggestions("CompletionFoo.kwtest3(12//7; foob") # because of specificity + @test hasnokwsuggestions("CompletionFoo.kwtest3(a, len2=b, length, foob") # length is not length=length + @test hasnokwsuggestions("CompletionFoo.kwtest5('a', 3, 5, unknownsplat...; xy") + @test hasnokwsuggestions("CompletionFoo.kwtest5(3; somek") + =# +end + # Test completion in context # No CompletionFoo.CompletionFoo @@ -1328,11 +1610,17 @@ let s = ":(function foo(::Int) end).args[1].args[2]." @test c == Any[] end -let s = "log(log.(x)," +let s = "log(log.(varfloat)," c, r = test_complete_foo(s) @test !isempty(c) end +# TODO: this is a bad test +#let s = "log(log.(noexist)," +# c, r = test_complete_foo(s) +# @test isempty(c) +#end + let s = "Base.return_types(getin" c, r = test_complete_foo(s) @test "getindex" in c @@ -1348,9 +1636,10 @@ end let s = "test(1,1, " c, r, res = test_complete_foo(s) @test !res - @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Int, Int}))) - @test c[2] == string(first(methods(Main.CompletionFoo.test, Tuple{}))) # corresponding to the vararg - @test length(c) == 2 + m = test_methods_list(Main.CompletionFoo.test, Tuple{Int, Int, Vararg}) + @test length(m) == 2 == length(c) + @test c[1] == m[1] + @test c[2] == m[2] # In particular, this checks that test(x::Real, y::Real) is not a valid completion # since it is strictly less specific than test(x::T, y::T) where T @test r == 1:4 @@ -1363,11 +1652,13 @@ let s = "test.(1,1, " @test length(c) == 4 @test r == 1:4 @test s[r] == "test" + # TODO: @test (c, r, res) == test_complete_foo("test.(1, 1, String[]..., ") + # TODO: @test (c, r, res) == test_complete_foo("test.(1, Any[]..., 2, ") end let s = "prevind(\"θ\",1," c, r, res = test_complete_foo(s) - @test c[1] == string(first(methods(prevind, Tuple{String, Int}))) + @test c[1] == first(test_methods_list(prevind, Tuple{String, Int, Vararg})) @test r == 1:7 @test s[r] == "prevind" end @@ -1418,8 +1709,7 @@ end @testset "https://github.com/JuliaLang/julia/issues/40247" begin # getfield type completion can work for complicated expression - let - m = Module() + let m = Module() @eval m begin struct Rs rs::Vector{Regex} @@ -1436,8 +1726,7 @@ end @test length(c) == fieldcount(Regex) end - let - m = Module() + let m = Module() @eval m begin struct R r::Regex @@ -1458,3 +1747,92 @@ end @test length(c) == fieldcount(Regex) end end + +@testset "https://github.com/JuliaLang/julia/issues/47593" begin + let m = Module() + @eval m begin + struct TEST_47594 + var"("::Int + end + test_47594 = TEST_47594(1) + end + + c, r = test_complete_context("test_47594.", m) + @test c == Any["var\"(\""] + end +end + +# https://github.com/JuliaLang/julia/issues/36437 +struct Issue36437{T} + v::T +end +Base.propertynames(::Issue36437) = (:a, :b, :c) +function Base.getproperty(v::Issue36437, s::Symbol) + if s === :a + return 1 + elseif s === :b + return 2 + elseif s === :c + return getfield(v, :v) + else + throw(ArgumentError(lazy"`(v::Issue36437).$s` is not supported")) + end +end + +let s = "Issue36437(42)." + c, r, res = test_complete_context(s, @__MODULE__) + @test res + for n in ("a", "b", "c") + @test n in c + end +end + +let s = "Some(Issue36437(42)).value." + c, r, res = test_complete_context(s, @__MODULE__) + @test res + for n in ("a", "b", "c") + @test n in c + end +end + +# aggressive concrete evaluation on mutable allocation in `repl_frame` +let s = "Ref(Issue36437(42))[]." + c, r, res = test_complete_context(s, @__MODULE__) + @test res + for n in ("a", "b", "c") + @test n in c + end + @test "v" ∉ c +end + +const global_xs = [Some(42)] +let s = "pop!(global_xs)." + c, r, res = test_complete_context(s, @__MODULE__) + @test res + @test "value" in c +end +@test length(global_xs) == 1 # the completion above shouldn't evaluate `pop!` call + +# Test completion of var"" identifiers (#49280) +let s = "var\"complicated " + c, r = test_complete_foo(s) + @test c == Any["var\"complicated symbol with spaces\""] +end + +for s in ("WeirdNames().var\"oh ", "WeirdNames().var\"") + c, r = test_complete_foo(s) + @test c == Any["var\"oh no!\"", "var\"oh yes!\""] +end + +# Test completion of non-Expr literals +let s = "\"abc\"." + c, r = test_complete(s) + # (no completion, but shouldn't error) + @test isempty(c) +end + +let s = "`abc`.e" + c, r = test_complete(s) + # (completions for the fields of `Cmd`) + @test c == Any["env", "exec"] +end diff --git a/stdlib/REPL/test/runtests.jl b/stdlib/REPL/test/runtests.jl index 2d46491103d01..e152677ccf7bb 100644 --- a/stdlib/REPL/test/runtests.jl +++ b/stdlib/REPL/test/runtests.jl @@ -1,5 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Make a copy of the original environment +original_env = copy(ENV) + module REPLTests include("repl.jl") end @@ -15,3 +18,13 @@ end module TerminalMenusTest include("TerminalMenus/runtests.jl") end + +# Restore the original environment +for k in keys(ENV) + if !haskey(original_env, k) + delete!(ENV, k) + end +end +for (k, v) in pairs(original_env) + ENV[k] = v +end diff --git a/stdlib/Random/Project.toml b/stdlib/Random/Project.toml index 199dcab940c86..f32fc3e2a4f84 100644 --- a/stdlib/Random/Project.toml +++ b/stdlib/Random/Project.toml @@ -2,7 +2,6 @@ name = "Random" uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [deps] -Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" [extras] diff --git a/stdlib/Random/docs/src/index.md b/stdlib/Random/docs/src/index.md index 059cd8f600e7d..e344e47947440 100644 --- a/stdlib/Random/docs/src/index.md +++ b/stdlib/Random/docs/src/index.md @@ -33,6 +33,8 @@ unbounded integers, the interval must be specified (e.g. `rand(big.(1:6))`). Additionally, normal and exponential distributions are implemented for some `AbstractFloat` and `Complex` types, see [`randn`](@ref) and [`randexp`](@ref) for details. +To generate random numbers from other distributions, see the [Distributions.jl](https://juliastats.org/Distributions.jl/stable/) package. + !!! warning Because the precise way in which random numbers are generated is considered an implementation detail, bug fixes and speed improvements may change the stream of numbers that are generated after a version change. Relying on a specific seed or generated stream of numbers during unit testing is thus discouraged - consider testing properties of the methods in question instead. @@ -70,6 +72,7 @@ Random.shuffle! ## Generators (creation and seeding) ```@docs +Random.default_rng Random.seed! Random.AbstractRNG Random.TaskLocalRNG diff --git a/stdlib/Random/src/RNGs.jl b/stdlib/Random/src/RNGs.jl index a50f633e68a9c..292ae00d33628 100644 --- a/stdlib/Random/src/RNGs.jl +++ b/stdlib/Random/src/RNGs.jl @@ -2,50 +2,6 @@ ## RandomDevice -if Sys.iswindows() - struct RandomDevice <: AbstractRNG - buffer::Vector{UInt128} - - RandomDevice() = new(Vector{UInt128}(undef, 1)) - end - - function rand(rd::RandomDevice, sp::SamplerBoolBitInteger) - rand!(rd, rd.buffer) - @inbounds return rd.buffer[1] % sp[] - end - - show(io::IO, ::RandomDevice) = print(io, RandomDevice, "()") - -else # !windows - struct RandomDevice <: AbstractRNG - unlimited::Bool - - RandomDevice(; unlimited::Bool=true) = new(unlimited) - end - - getfile(rd::RandomDevice) = Base._get_dev_random_fd(rd.unlimited) - - rand(rd::RandomDevice, sp::SamplerBoolBitInteger) = read(getfile(rd), sp[]) - rand(rd::RandomDevice, ::SamplerType{Bool}) = read(getfile(rd), UInt8) % Bool - - show(io::IO, rd::RandomDevice) = - print(io, RandomDevice, rd.unlimited ? "()" : "(unlimited=false)") -end # os-test - -# NOTE: this can't be put within the if-else block above -for T in (Bool, BitInteger_types...) - if Sys.iswindows() - @eval function rand!(rd::RandomDevice, A::Array{$T}, ::SamplerType{$T}) - Base.RtlGenRandom!(A) - A - end - else - @eval rand!(rd::RandomDevice, A::Array{$T}, ::SamplerType{$T}) = read!(getfile(rd), A) - end -end - -# RandomDevice produces natively UInt64 -rng_native_52(::RandomDevice) = UInt64 """ RandomDevice() @@ -54,11 +10,31 @@ Create a `RandomDevice` RNG object. Two such objects will always generate different streams of random numbers. The entropy is obtained from the operating system. """ -RandomDevice - -RandomDevice(::Nothing) = RandomDevice() +struct RandomDevice <: AbstractRNG; end +RandomDevice(seed::Nothing) = RandomDevice() seed!(rng::RandomDevice) = rng +rand(rd::RandomDevice, sp::SamplerBoolBitInteger) = Libc.getrandom!(Ref{sp[]}())[] +rand(rd::RandomDevice, ::SamplerType{Bool}) = rand(rd, UInt8) % Bool +function rand!(rd::RandomDevice, A::Array{Bool}, ::SamplerType{Bool}) + Libc.getrandom!(A) + # we need to mask the result so that only the LSB in each byte can be non-zero + GC.@preserve A begin + p = Ptr{UInt8}(pointer(A)) + for i = 1:length(A) + unsafe_store!(p, unsafe_load(p) & 0x1) + p += 1 + end + end + return A +end +for T in BitInteger_types + @eval rand!(rd::RandomDevice, A::Array{$T}, ::SamplerType{$T}) = Libc.getrandom!(A) +end + +# RandomDevice produces natively UInt64 +rng_native_52(::RandomDevice) = UInt64 + ## MersenneTwister @@ -307,11 +283,10 @@ end function make_seed() try return rand(RandomDevice(), UInt32, 4) - catch - println(stderr, - "Entropy pool not available to seed RNG; using ad-hoc entropy sources.") - Base._ad_hoc_entropy_source() - return make_seed(seed) + catch ex + ex isa IOError || rethrow() + @warn "Entropy pool not available to seed RNG; using ad-hoc entropy sources." + return make_seed(Libc.rand()) end end @@ -355,6 +330,19 @@ end # GLOBAL_RNG currently uses TaskLocalRNG typeof_rng(::_GLOBAL_RNG) = TaskLocalRNG +""" + default_rng() -> rng + +Return the default global random number generator (RNG). + +!!! note + What the default RNG is is an implementation detail. Across different versions of + Julia, you should not expect the default RNG to be always the same, nor that it will + return the same stream of random numbers for a given seed. + +!!! compat "Julia 1.3" + This function was introduced in Julia 1.3. +""" @inline default_rng() = TaskLocalRNG() @inline default_rng(tid::Int) = TaskLocalRNG() @@ -400,6 +388,7 @@ end function __init__() seed!(GLOBAL_RNG) + ccall(:jl_gc_init_finalizer_rng_state, Cvoid, ()) end @@ -477,7 +466,7 @@ end ##### Array : internal functions -# internal array-like type to circumevent the lack of flexibility with reinterpret +# internal array-like type to circumvent the lack of flexibility with reinterpret struct UnsafeView{T} <: DenseArray{T,1} ptr::Ptr{T} len::Int @@ -737,8 +726,8 @@ jump!(r::MersenneTwister, steps::Integer) = copy!(r, jump(r, steps)) # parameters in the tuples are: # 1: .adv_jump (jump steps) # 2: .adv (number of generated floats at the DSFMT_state level since seeding, besides jumps) -# 3, 4: .adv_vals, .idxF (counters to reconstruct the float chache, optional if 5-6 not shown)) -# 5, 6: .adv_ints, .idxI (counters to reconstruct the integer chache, optional) +# 3, 4: .adv_vals, .idxF (counters to reconstruct the float cache, optional if 5-6 not shown)) +# 5, 6: .adv_ints, .idxI (counters to reconstruct the integer cache, optional) Random.MersenneTwister(seed::Union{Integer,Vector{UInt32}}, advance::NTuple{6,Integer}) = advance!(MersenneTwister(seed), advance...) diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index 432fab1638dda..8da2dd6f3e9c7 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -143,8 +143,10 @@ Sampler(rng::AbstractRNG, ::Type{X}, r::Repetition=Val(Inf)) where {X} = typeof_rng(rng::AbstractRNG) = typeof(rng) -Sampler(::Type{<:AbstractRNG}, sp::Sampler, ::Repetition) = - throw(ArgumentError("Sampler for this object is not defined")) +# this method is necessary to prevent rand(rng::AbstractRNG, X) from +# recursively constructing nested Sampler types. +Sampler(T::Type{<:AbstractRNG}, sp::Sampler, r::Repetition) = + throw(MethodError(Sampler, (T, sp, r))) # default shortcut for the general case Sampler(::Type{RNG}, X) where {RNG<:AbstractRNG} = Sampler(RNG, X, Val(Inf)) @@ -213,7 +215,7 @@ end # TODO: make constraining constructors to enforce that those # types are <: Sampler{T} -##### Adapter to generate a randome value in [0, n] +##### Adapter to generate a random value in [0, n] struct LessThan{T<:Integer,S} <: Sampler{T} sup::T @@ -254,7 +256,7 @@ rand(rng::AbstractRNG, ::UniformT{T}) where {T} = rand(rng, T) rand(rng::AbstractRNG, X) = rand(rng, Sampler(rng, X, Val(1))) # this is needed to disambiguate rand(rng::AbstractRNG, X::Dims) = rand(rng, Sampler(rng, X, Val(1))) -rand(rng::AbstractRNG=default_rng(), ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))::X +rand(rng::AbstractRNG=default_rng(), ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))::X rand(X) = rand(default_rng(), X) rand(::Type{X}) where {X} = rand(default_rng(), X) @@ -307,7 +309,7 @@ include("XoshiroSimd.jl") ## rand & rand! & seed! docstrings """ - rand([rng=GLOBAL_RNG], [S], [dims...]) + rand([rng=default_rng()], [S], [dims...]) Pick a random element or array of random elements from the set of values specified by `S`; `S` can be @@ -359,7 +361,7 @@ julia> rand(Float64, (2, 3)) rand """ - rand!([rng=GLOBAL_RNG], A, [S=eltype(A)]) + rand!([rng=default_rng()], A, [S=eltype(A)]) Populate the array `A` with random values. If `S` is specified (`S` can be a type or a collection, cf. [`rand`](@ref) for details), @@ -383,8 +385,8 @@ julia> rand!(rng, zeros(5)) rand! """ - seed!([rng=GLOBAL_RNG], seed) -> rng - seed!([rng=GLOBAL_RNG]) -> rng + seed!([rng=default_rng()], seed) -> rng + seed!([rng=default_rng()]) -> rng Reseed the random number generator: `rng` will give a reproducible sequence of numbers if and only if a `seed` is provided. Some RNGs @@ -400,33 +402,33 @@ shared task-local generator. julia> Random.seed!(1234); julia> x1 = rand(2) -2-element Array{Float64,1}: - 0.590845 - 0.766797 +2-element Vector{Float64}: + 0.32597672886359486 + 0.5490511363155669 julia> Random.seed!(1234); julia> x2 = rand(2) -2-element Array{Float64,1}: - 0.590845 - 0.766797 +2-element Vector{Float64}: + 0.32597672886359486 + 0.5490511363155669 julia> x1 == x2 true -julia> rng = MersenneTwister(1234); rand(rng, 2) == x1 +julia> rng = Xoshiro(1234); rand(rng, 2) == x1 true -julia> MersenneTwister(1) == Random.seed!(rng, 1) +julia> Xoshiro(1) == Random.seed!(rng, 1) true julia> rand(Random.seed!(rng), Bool) # not reproducible true -julia> rand(Random.seed!(rng), Bool) +julia> rand(Random.seed!(rng), Bool) # not reproducible either false -julia> rand(MersenneTwister(), Bool) # not reproducible either +julia> rand(Xoshiro(), Bool) # not reproducible either true ``` """ diff --git a/stdlib/Random/src/Xoshiro.jl b/stdlib/Random/src/Xoshiro.jl index 5b8aa4644d140..3be276ad23754 100644 --- a/stdlib/Random/src/Xoshiro.jl +++ b/stdlib/Random/src/Xoshiro.jl @@ -113,12 +113,17 @@ struct TaskLocalRNG <: AbstractRNG end TaskLocalRNG(::Nothing) = TaskLocalRNG() rng_native_52(::TaskLocalRNG) = UInt64 -function setstate!(x::TaskLocalRNG, s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64) +function setstate!( + x::TaskLocalRNG, + s0::UInt64, s1::UInt64, s2::UInt64, s3::UInt64, # xoshiro256 state + s4::UInt64 = 1s0 + 3s1 + 5s2 + 7s3, # internal splitmix state +) t = current_task() t.rngState0 = s0 t.rngState1 = s1 t.rngState2 = s2 t.rngState3 = s3 + t.rngState4 = s4 x end @@ -128,11 +133,11 @@ end tmp = s0 + s3 res = ((tmp << 23) | (tmp >> 41)) + s0 t = s1 << 17 - s2 = xor(s2, s0) - s3 = xor(s3, s1) - s1 = xor(s1, s2) - s0 = xor(s0, s3) - s2 = xor(s2, t) + s2 ⊻= s0 + s3 ⊻= s1 + s1 ⊻= s2 + s0 ⊻= s3 + s2 ⊻= t s3 = s3 << 45 | s3 >> 19 task.rngState0, task.rngState1, task.rngState2, task.rngState3 = s0, s1, s2, s3 res @@ -159,7 +164,7 @@ seed!(rng::Union{TaskLocalRNG, Xoshiro}, seed::Integer) = seed!(rng, make_seed(s @inline function rand(rng::Union{TaskLocalRNG, Xoshiro}, ::SamplerType{UInt128}) first = rand(rng, UInt64) second = rand(rng,UInt64) - second + UInt128(first)<<64 + second + UInt128(first) << 64 end @inline rand(rng::Union{TaskLocalRNG, Xoshiro}, ::SamplerType{Int128}) = rand(rng, UInt128) % Int128 @@ -178,14 +183,14 @@ end function copy!(dst::TaskLocalRNG, src::Xoshiro) t = current_task() - t.rngState0, t.rngState1, t.rngState2, t.rngState3 = src.s0, src.s1, src.s2, src.s3 - dst + setstate!(dst, src.s0, src.s1, src.s2, src.s3) + return dst end function copy!(dst::Xoshiro, src::TaskLocalRNG) t = current_task() - dst.s0, dst.s1, dst.s2, dst.s3 = t.rngState0, t.rngState1, t.rngState2, t.rngState3 - dst + setstate!(dst, t.rngState0, t.rngState1, t.rngState2, t.rngState3) + return dst end function ==(a::Xoshiro, b::TaskLocalRNG) diff --git a/stdlib/Random/src/generation.jl b/stdlib/Random/src/generation.jl index ddbf6dce98bec..cc9840f678413 100644 --- a/stdlib/Random/src/generation.jl +++ b/stdlib/Random/src/generation.jl @@ -132,7 +132,7 @@ rand(r::AbstractRNG, sp::SamplerTrivial{<:UniformBits{T}}) where {T} = # rand_generic methods are intended to help RNG implementors with common operations # we don't call them simply `rand` as this can easily contribute to create -# amibuities with user-side methods (forcing the user to resort to @eval) +# ambiguities with user-side methods (forcing the user to resort to @eval) rand_generic(r::AbstractRNG, T::Union{Bool,Int8,UInt8,Int16,UInt16,Int32,UInt32}) = rand(r, UInt52Raw()) % T[] @@ -210,9 +210,9 @@ SamplerRangeFast(r::AbstractUnitRange{T}) where T<:BitInteger = SamplerRangeFast(r, uint_sup(T)) function SamplerRangeFast(r::AbstractUnitRange{T}, ::Type{U}) where {T,U} - isempty(r) && throw(ArgumentError("range must be non-empty")) + isempty(r) && throw(ArgumentError("collection must be non-empty")) m = (last(r) - first(r)) % unsigned(T) % U # % unsigned(T) to not propagate sign bit - bw = (sizeof(U) << 3 - leading_zeros(m)) % UInt # bit-width + bw = (Base.top_set_bit(m)) % UInt # bit-width mask = ((1 % U) << bw) - (1 % U) SamplerRangeFast{U,T}(first(r), bw, m, mask) end @@ -284,11 +284,11 @@ SamplerRangeInt(r::AbstractUnitRange{T}) where T<:BitInteger = SamplerRangeInt(r, uint_sup(T)) function SamplerRangeInt(r::AbstractUnitRange{T}, ::Type{U}) where {T,U} - isempty(r) && throw(ArgumentError("range must be non-empty")) + isempty(r) && throw(ArgumentError("collection must be non-empty")) a = first(r) m = (last(r) - first(r)) % unsigned(T) % U k = m + one(U) - bw = (sizeof(U) << 3 - leading_zeros(m)) % Int + bw = (Base.top_set_bit(m)) % Int mult = if U === UInt32 maxmultiple(k) elseif U === UInt64 @@ -330,7 +330,7 @@ struct SamplerRangeNDL{U<:Unsigned,T} <: Sampler{T} end function SamplerRangeNDL(r::AbstractUnitRange{T}) where {T} - isempty(r) && throw(ArgumentError("range must be non-empty")) + isempty(r) && throw(ArgumentError("collection must be non-empty")) a = first(r) U = uint_sup(T) s = (last(r) - first(r)) % unsigned(T) % U + one(U) # overflow ok @@ -369,7 +369,7 @@ end function SamplerBigInt(::Type{RNG}, r::AbstractUnitRange{BigInt}, N::Repetition=Val(Inf) ) where {RNG<:AbstractRNG} m = last(r) - first(r) - m.size < 0 && throw(ArgumentError("range must be non-empty")) + m.size < 0 && throw(ArgumentError("collection must be non-empty")) nlimbs = Int(m.size) hm = nlimbs == 0 ? Limb(0) : GC.@preserve m unsafe_load(m.d, nlimbs) highsp = Sampler(RNG, Limb(0):hm, N) diff --git a/stdlib/Random/src/misc.jl b/stdlib/Random/src/misc.jl index 0d6e06c444a09..b1e3a4808e026 100644 --- a/stdlib/Random/src/misc.jl +++ b/stdlib/Random/src/misc.jl @@ -11,7 +11,7 @@ function rand!(rng::AbstractRNG, B::BitArray, ::SamplerType{Bool}) end """ - bitrand([rng=GLOBAL_RNG], [dims...]) + bitrand([rng=default_rng()], [dims...]) Generate a `BitArray` of random boolean values. @@ -43,7 +43,7 @@ bitrand(dims::Integer...) = rand!(BitArray(undef, convert(Dims, dims))) ## randstring (often useful for temporary filenames/dirnames) """ - randstring([rng=GLOBAL_RNG], [chars], [len=8]) + randstring([rng=default_rng()], [chars], [len=8]) Create a random string of length `len`, consisting of characters from `chars`, which defaults to the set of upper- and lower-case letters @@ -71,12 +71,20 @@ function randstring end let b = UInt8['0':'9';'A':'Z';'a':'z'] global randstring + function randstring(r::AbstractRNG, chars=b, n::Integer=8) T = eltype(chars) - v = T === UInt8 ? Base.StringVector(n) : Vector{T}(undef, n) - rand!(r, v, chars) - return String(v) + if T === UInt8 + str = Base._string_n(n) + GC.@preserve str rand!(r, UnsafeView(pointer(str), n), chars) + return str + else + v = Vector{T}(undef, n) + rand!(r, v, chars) + return String(v) + end end + randstring(r::AbstractRNG, n::Integer) = randstring(r, b, n) randstring(chars=b, n::Integer=8) = randstring(default_rng(), chars, n) randstring(n::Integer) = randstring(default_rng(), b, n) @@ -126,7 +134,7 @@ function randsubseq!(r::AbstractRNG, S::AbstractArray, A::AbstractArray, p::Real end """ - randsubseq!([rng=GLOBAL_RNG,] S, A, p) + randsubseq!([rng=default_rng(),] S, A, p) Like [`randsubseq`](@ref), but the results are stored in `S` (which is resized as needed). @@ -154,7 +162,7 @@ randsubseq(r::AbstractRNG, A::AbstractArray{T}, p::Real) where {T} = randsubseq!(r, T[], A, p) """ - randsubseq([rng=GLOBAL_RNG,] A, p) -> Vector + randsubseq([rng=default_rng(),] A, p) -> Vector Return a vector consisting of a random subsequence of the given array `A`, where each element of `A` is included (in order) with independent probability `p`. (Complexity is @@ -182,7 +190,7 @@ ltm52(n::Int, mask::Int=nextpow(2, n)-1) = LessThan(n-1, Masked(mask, UInt52Raw( ## shuffle & shuffle! """ - shuffle!([rng=GLOBAL_RNG,] v::AbstractArray) + shuffle!([rng=default_rng(),] v::AbstractArray) In-place version of [`shuffle`](@ref): randomly permute `v` in-place, optionally supplying the random-number generator `rng`. @@ -228,7 +236,7 @@ end shuffle!(a::AbstractArray) = shuffle!(default_rng(), a) """ - shuffle([rng=GLOBAL_RNG,] v::AbstractArray) + shuffle([rng=default_rng(),] v::AbstractArray) Return a randomly permuted copy of `v`. The optional `rng` argument specifies a random number generator (see [Random Numbers](@ref)). @@ -256,11 +264,12 @@ julia> shuffle(rng, Vector(1:10)) shuffle(r::AbstractRNG, a::AbstractArray) = shuffle!(r, copymutable(a)) shuffle(a::AbstractArray) = shuffle(default_rng(), a) +shuffle(r::AbstractRNG, a::Base.OneTo) = randperm(r, last(a)) ## randperm & randperm! """ - randperm([rng=GLOBAL_RNG,] n::Integer) + randperm([rng=default_rng(),] n::Integer) Construct a random permutation of length `n`. The optional `rng` argument specifies a random number generator (see [Random @@ -288,7 +297,7 @@ randperm(r::AbstractRNG, n::T) where {T <: Integer} = randperm!(r, Vector{T}(und randperm(n::Integer) = randperm(default_rng(), n) """ - randperm!([rng=GLOBAL_RNG,] A::Array{<:Integer}) + randperm!([rng=default_rng(),] A::Array{<:Integer}) Construct in `A` a random permutation of length `length(A)`. The optional `rng` argument specifies a random number generator (see @@ -328,7 +337,7 @@ randperm!(a::Array{<:Integer}) = randperm!(default_rng(), a) ## randcycle & randcycle! """ - randcycle([rng=GLOBAL_RNG,] n::Integer) + randcycle([rng=default_rng(),] n::Integer) Construct a random cyclic permutation of length `n`. The optional `rng` argument specifies a random number generator, see [Random Numbers](@ref). @@ -354,7 +363,7 @@ randcycle(r::AbstractRNG, n::T) where {T <: Integer} = randcycle!(r, Vector{T}(u randcycle(n::Integer) = randcycle(default_rng(), n) """ - randcycle!([rng=GLOBAL_RNG,] A::Array{<:Integer}) + randcycle!([rng=default_rng(),] A::Array{<:Integer}) Construct in `A` a random cyclic permutation of length `length(A)`. The optional `rng` argument specifies a random number generator, see diff --git a/stdlib/Random/src/normal.jl b/stdlib/Random/src/normal.jl index 6bb4cd2c36ce8..9d0f1595f052f 100644 --- a/stdlib/Random/src/normal.jl +++ b/stdlib/Random/src/normal.jl @@ -10,7 +10,7 @@ ## randn """ - randn([rng=GLOBAL_RNG], [T=Float64], [dims...]) + randn([rng=default_rng()], [T=Float64], [dims...]) Generate a normally-distributed random number of type `T` with mean 0 and standard deviation 1. @@ -90,10 +90,19 @@ randn(rng::AbstractRNG, ::Type{Complex{T}}) where {T<:AbstractFloat} = Complex{T}(SQRT_HALF * randn(rng, T), SQRT_HALF * randn(rng, T)) +### fallback randn for float types defining rand: +function randn(rng::AbstractRNG, ::Type{T}) where {T<:AbstractFloat} + # Marsaglia polar variant of Box–Muller transform: + while true + x, y = 2rand(rng, T)-1, 2rand(rng, T)-1 + 0 < (s = x^2 + y^2) < 1 && return x * sqrt(-2log(s)/s) + end +end + ## randexp """ - randexp([rng=GLOBAL_RNG], [T=Float64], [dims...]) + randexp([rng=default_rng()], [T=Float64], [dims...]) Generate a random number of type `T` according to the exponential distribution with scale 1. @@ -137,11 +146,14 @@ end end end +### fallback randexp for float types defining rand: +randexp(rng::AbstractRNG, ::Type{T}) where {T<:AbstractFloat} = + -log1p(-rand(rng, T)) ## arrays & other scalar methods """ - randn!([rng=GLOBAL_RNG], A::AbstractArray) -> A + randn!([rng=default_rng()], A::AbstractArray) -> A Fill the array `A` with normally-distributed (mean 0, standard deviation 1) random numbers. Also see the [`rand`](@ref) function. @@ -162,7 +174,7 @@ julia> randn!(rng, zeros(5)) function randn! end """ - randexp!([rng=GLOBAL_RNG], A::AbstractArray) -> A + randexp!([rng=default_rng()], A::AbstractArray) -> A Fill the array `A` with random numbers following the exponential distribution (with scale 1). diff --git a/stdlib/Random/test/runtests.jl b/stdlib/Random/test/runtests.jl index c8be4c95cdaf2..3f570d862b743 100644 --- a/stdlib/Random/test/runtests.jl +++ b/stdlib/Random/test/runtests.jl @@ -2,7 +2,6 @@ using Test, SparseArrays using Test: guardseed -using Statistics: mean const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) @@ -47,7 +46,7 @@ let A = zeros(2, 2) 0.9103565379264364 0.17732884646626457] end let A = zeros(2, 2) - @test_throws ArgumentError rand!(MersenneTwister(0), A, 5) + @test_throws MethodError rand!(MersenneTwister(0), A, 5) @test rand(MersenneTwister(0), Int64, 1) == [-3433174948434291912] end let A = zeros(Int64, 2, 2) @@ -307,9 +306,32 @@ let a = [rand(RandomDevice(), UInt128) for i=1:10] @test reduce(|, a)>>>64 != 0 end +# wrapper around Float64 to check fallback random generators +struct FakeFloat64 <: AbstractFloat + x::Float64 +end +Base.rand(rng::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{FakeFloat64}}) = FakeFloat64(rand(rng)) +for f in (:sqrt, :log, :log1p, :one, :zero, :abs, :+, :-) + @eval Base.$f(x::FakeFloat64) = FakeFloat64($f(x.x)) +end +for f in (:+, :-, :*, :/) + @eval begin + Base.$f(x::FakeFloat64, y::FakeFloat64) = FakeFloat64($f(x.x,y.x)) + Base.$f(x::FakeFloat64, y::Real) = FakeFloat64($f(x.x,y)) + Base.$f(x::Real, y::FakeFloat64) = FakeFloat64($f(x,y.x)) + end +end +for f in (:<, :<=, :>, :>=, :(==), :(!=)) + @eval begin + Base.$f(x::FakeFloat64, y::FakeFloat64) = $f(x.x,y.x) + Base.$f(x::FakeFloat64, y::Real) = $f(x.x,y) + Base.$f(x::Real, y::FakeFloat64) = $f(x,y.x) + end +end + # test all rand APIs for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) - ftypes = [Float16, Float32, Float64] + ftypes = [Float16, Float32, Float64, FakeFloat64, BigFloat] cftypes = [ComplexF16, ComplexF32, ComplexF64, ftypes...] types = [Bool, Char, BigFloat, Base.BitInteger_types..., ftypes...] randset = Set(rand(Int, 20)) @@ -406,15 +428,12 @@ for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()]) rand!(rng..., BitMatrix(undef, 2, 3)) ::BitArray{2} # Test that you cannot call randn or randexp with non-Float types. - for r in [randn, randexp, randn!, randexp!] - local r + for r in [randn, randexp] @test_throws MethodError r(Int) @test_throws MethodError r(Int32) @test_throws MethodError r(Bool) @test_throws MethodError r(String) @test_throws MethodError r(AbstractFloat) - # TODO(#17627): Consider adding support for randn(BigFloat) and removing this test. - @test_throws MethodError r(BigFloat) @test_throws MethodError r(Int64, (2,3)) @test_throws MethodError r(String, 1) @@ -449,6 +468,7 @@ end @testset "rand(Bool) uniform distribution" begin for n in [rand(1:8), rand(9:16), rand(17:64)] a = zeros(Bool, n) + a8 = unsafe_wrap(Array, Ptr{UInt8}(pointer(a)), length(a); own=false) # unsafely observe the actual bit patterns in `a` as = zeros(Int, n) # we will test statistical properties for each position of a, # but also for 3 linear combinations of positions (for the array version) @@ -466,6 +486,7 @@ end end else as .+= rand!(rng, a) + @test all(x -> x === 0x00 || x === 0x01, a8) aslcs .+= [xor(getindex.(Ref(a), lcs[i])...) for i in 1:3] end end @@ -662,7 +683,7 @@ let b = ['0':'9';'A':'Z';'a':'z'] end # this shouldn't crash (#22403) -@test_throws ArgumentError rand!(Union{UInt,Int}[1, 2, 3]) +@test_throws MethodError rand!(Union{UInt,Int}[1, 2, 3]) @testset "$RNG() & Random.seed!(rng::$RNG) initializes randomly" for RNG in (MersenneTwister, RandomDevice, Xoshiro) m = RNG() @@ -734,8 +755,8 @@ end struct RandomStruct23964 end @testset "error message when rand not defined for a type" begin - @test_throws ArgumentError rand(nothing) - @test_throws ArgumentError rand(RandomStruct23964()) + @test_throws MethodError rand(nothing) + @test_throws MethodError rand(RandomStruct23964()) end @testset "rand(::$(typeof(RNG)), ::UnitRange{$T}" for RNG ∈ (MersenneTwister(rand(UInt128)), RandomDevice(), Xoshiro()), @@ -867,7 +888,8 @@ end @test (x >> 64) % UInt64 == xs[end-6] @test x % UInt64 == xs[end-7] x = rand(m, UInt64) - @test x == xs[end-8] # should not be == xs[end-7] + @test x == xs[end-8] + @test x != xs[end-7] s = Set{UInt64}() n = 0 @@ -912,9 +934,6 @@ end @testset "RandomDevice" begin @test string(RandomDevice()) == "$RandomDevice()" - if !Sys.iswindows() - @test string(RandomDevice(unlimited=false)) == "$RandomDevice(unlimited=false)" - end end end @@ -969,10 +988,80 @@ end # Test that shuffle! is uniformly random on BitArrays rng = MersenneTwister(123) a = (reshape(1:(4*5), 4, 5) .<= 2) # 4x5 BitMatrix whose first two elements are true, rest are false - m = mean(1:50_000) do _ + m = sum(1:50_000) do _ shuffle!(rng, a) - end # mean result of shuffle!-ing a 50_000 times. If the shuffle! is uniform, then each index has a + end/50_000 # mean result of shuffle!-ing a 50_000 times. If the shuffle! is uniform, then each index has a # 10% chance of having a true in it, so each value should converge to 0.1. @test minimum(m) >= 0.094 @test maximum(m) <= 0.106 end + +# issue #42752 +# test that running finalizers that launch tasks doesn't change RNG stream +function f42752(do_gc::Bool, cell = (()->Any[[]])()) + a = rand() + if do_gc + finalizer(cell[1]) do _ + @async nothing + end + cell[1] = nothing + GC.gc() + end + b = rand() + (a, b) +end +guardseed() do + for _ in 1:4 + Random.seed!(1) + val = f42752(false) + Random.seed!(1) + @test f42752(true) === val + end +end + +@testset "TaskLocalRNG: stream collision smoke test" begin + # spawn a trinary tree of tasks: + # - spawn three recursive child tasks in each + # - generate a random UInt64 in each before, after and between + # - collect and count all the generated random values + # these should all be distinct across all tasks + function gen(d) + r = rand(UInt64) + vals = [r] + if d ≥ 0 + append!(vals, gent(d - 1)) + isodd(r) && append!(vals, gent(d - 1)) + push!(vals, rand(UInt64)) + iseven(r) && append!(vals, gent(d - 1)) + end + push!(vals, rand(UInt64)) + end + gent(d) = fetch(@async gen(d)) + seeds = rand(RandomDevice(), UInt64, 5) + for seed in seeds + Random.seed!(seed) + vals = gen(6) + @test allunique(vals) + end +end + +@testset "TaskLocalRNG: child doesn't affect parent" begin + seeds = rand(RandomDevice(), UInt64, 5) + for seed in seeds + Random.seed!(seed) + x = rand(UInt64) + y = rand(UInt64) + n = 3 + for i = 1:n + Random.seed!(seed) + @sync for j = 0:i + @async rand(UInt64) + end + @test x == rand(UInt64) + @sync for j = 0:(n-i) + @async rand(UInt64) + end + @test y == rand(UInt64) + end + end +end diff --git a/stdlib/SHA.version b/stdlib/SHA.version index 312fbc55ea97c..f2242a336c6fe 100644 --- a/stdlib/SHA.version +++ b/stdlib/SHA.version @@ -1,4 +1,4 @@ SHA_BRANCH = master -SHA_SHA1 = 57c3a8c8358021b7a58526364e6885768fd95de2 +SHA_SHA1 = 2d1f84e6f8417a1a368de48318640d948b023e7a SHA_GIT_URL := https://github.com/JuliaCrypto/SHA.jl.git SHA_TAR_URL = https://api.github.com/repos/JuliaCrypto/SHA.jl/tarball/$1 diff --git a/stdlib/Serialization/docs/src/index.md b/stdlib/Serialization/docs/src/index.md index c01ead7a7eaea..9f593a2e807d9 100644 --- a/stdlib/Serialization/docs/src/index.md +++ b/stdlib/Serialization/docs/src/index.md @@ -1,5 +1,7 @@ # Serialization +Provides serialization of Julia objects. + ```@docs Serialization.serialize Serialization.deserialize diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index f36999d63d311..dd901d6910abf 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -39,7 +39,7 @@ const TAGS = Any[ Float16, Float32, Float64, Char, DataType, Union, UnionAll, Core.TypeName, Tuple, Array, Expr, LineNumberNode, :__LabelNode__, GotoNode, QuoteNode, CodeInfo, TypeVar, Core.Box, Core.MethodInstance, Module, Task, String, SimpleVector, Method, - GlobalRef, SlotNumber, TypedSlot, NewvarNode, SSAValue, + GlobalRef, SlotNumber, Const, NewvarNode, SSAValue, # dummy entries for tags that don't correspond directly to types Symbol, # UNDEFREF_TAG @@ -77,15 +77,14 @@ const TAGS = Any[ (Int64(0):Int64(n_int_literals-1))... ] -@assert length(TAGS) == 255 +const NTAGS = length(TAGS) +@assert NTAGS == 255 -const ser_version = 17 # do not make changes without bumping the version #! +const ser_version = 23 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version -const NTAGS = length(TAGS) - function sertag(@nospecialize(v)) # NOTE: we use jl_value_ptr directly since we know at least one of the arguments # in the comparison below is a singleton. @@ -194,7 +193,7 @@ serialize(s::AbstractSerializer, ::Tuple{}) = writetag(s.io, EMPTYTUPLE_TAG) function serialize(s::AbstractSerializer, t::Tuple) l = length(t) - if l <= 255 + if l <= NTAGS writetag(s.io, TUPLE_TAG) write(s.io, UInt8(l)) else @@ -224,7 +223,7 @@ function serialize(s::AbstractSerializer, x::Symbol) if len > 7 serialize_cycle(s, x) && return end - if len <= 255 + if len <= NTAGS writetag(s.io, SYMBOL_TAG) write(s.io, UInt8(len)) else @@ -295,7 +294,7 @@ function serialize(s::AbstractSerializer, ss::String) serialize_cycle(s, ss) && return writetag(s.io, SHARED_REF_TAG) end - if len <= 255 + if len <= NTAGS writetag(s.io, STRING_TAG) write(s.io, UInt8(len)) else @@ -327,7 +326,7 @@ end function serialize(s::AbstractSerializer, ex::Expr) serialize_cycle(s, ex) && return l = length(ex.args) - if l <= 255 + if l <= NTAGS writetag(s.io, EXPR_TAG) write(s.io, UInt8(l)) else @@ -364,7 +363,8 @@ function serialize_mod_names(s::AbstractSerializer, m::Module) p = parentmodule(m) if p === m || m === Base key = Base.root_module_key(m) - serialize(s, key.uuid === nothing ? nothing : key.uuid.value) + uuid = key.uuid + serialize(s, uuid === nothing ? nothing : uuid.value) serialize(s, Symbol(key.name)) else serialize_mod_names(s, p) @@ -481,7 +481,7 @@ function serialize(s::AbstractSerializer, g::GlobalRef) if (g.mod === __deserialized_types__ ) || (g.mod === Main && isdefined(g.mod, g.name) && isconst(g.mod, g.name)) - v = getfield(g.mod, g.name) + v = getglobal(g.mod, g.name) unw = unwrap_unionall(v) if isa(unw,DataType) && v === unw.name.wrapper && should_send_whole_type(s, unw) # handle references to types in Main by sending the whole type. @@ -514,14 +514,16 @@ function serialize_typename(s::AbstractSerializer, t::Core.TypeName) serialize(s, t.flags & 0x1 == 0x1) # .abstract serialize(s, t.flags & 0x2 == 0x2) # .mutable serialize(s, Int32(length(primary.types) - t.n_uninitialized)) + serialize(s, t.max_methods) if isdefined(t, :mt) && t.mt !== Symbol.name.mt serialize(s, t.mt.name) serialize(s, collect(Base.MethodList(t.mt))) serialize(s, t.mt.max_args) - if isdefined(t.mt, :kwsorter) - serialize(s, t.mt.kwsorter) - else + kws = collect(methods(Core.kwcall, (Any, t.wrapper, Vararg))) + if isempty(kws) writetag(s.io, UNDEFREF_TAG) + else + serialize(s, kws) end else writetag(s.io, UNDEFREF_TAG) @@ -540,7 +542,7 @@ function should_send_whole_type(s, t::DataType) isanonfunction = mod === Main && # only Main t.super === Function && # only Functions unsafe_load(unsafe_convert(Ptr{UInt8}, tn.name)) == UInt8('#') && # hidden type - (!isdefined(mod, name) || t != typeof(getfield(mod, name))) # XXX: 95% accurate test for this being an inner function + (!isdefined(mod, name) || t != typeof(getglobal(mod, name))) # XXX: 95% accurate test for this being an inner function # TODO: more accurate test? (tn.name !== "#" name) #TODO: iskw = startswith(tn.name, "#kw#") && ??? #TODO: iskw && return send-as-kwftype @@ -563,10 +565,8 @@ function serialize_type_data(s, @nospecialize(t::DataType)) serialize(s, t.name) else writetag(s.io, DATATYPE_TAG) - tname = t.name.name - serialize(s, tname) - mod = t.name.module - serialize(s, mod) + serialize(s, nameof(t)) + serialize(s, parentmodule(t)) end if !isempty(t.parameters) if iswrapper @@ -659,8 +659,7 @@ function serialize_any(s::AbstractSerializer, @nospecialize(x)) return write_as_tag(s.io, tag) end t = typeof(x)::DataType - nf = nfields(x) - if nf == 0 && t.size > 0 + if isprimitivetype(t) serialize_type(s, t) write(s.io, x) else @@ -670,6 +669,7 @@ function serialize_any(s::AbstractSerializer, @nospecialize(x)) else serialize_type(s, t, false) end + nf = nfields(x) for i in 1:nf if isdefined(x, i) serialize(s, getfield(x, i)) @@ -985,7 +985,7 @@ function deserialize_module(s::AbstractSerializer) end m = Base.root_module(mkey[1]) for i = 2:length(mkey) - m = getfield(m, mkey[i])::Module + m = getglobal(m, mkey[i])::Module end else name = String(deserialize(s)::Symbol) @@ -993,7 +993,7 @@ function deserialize_module(s::AbstractSerializer) m = Base.root_module(pkg) mname = deserialize(s) while mname !== () - m = getfield(m, mname)::Module + m = getglobal(m, mname)::Module mname = deserialize(s) end end @@ -1026,8 +1026,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) nargs = deserialize(s)::Int32 isva = deserialize(s)::Bool is_for_opaque_closure = false - constprop = 0x00 - purity = 0x00 + constprop = purity = 0x00 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque @@ -1060,7 +1059,6 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) if template !== nothing # TODO: compress template meth.source = template::CodeInfo - meth.pure = template.pure if !@isdefined(slot_syms) slot_syms = ccall(:jl_compress_argnames, Ref{String}, (Any,), meth.source.slotnames) end @@ -1088,7 +1086,7 @@ function deserialize(s::AbstractSerializer, ::Type{Core.MethodInstance}) deserialize_cycle(s, linfo) tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG - linfo.uninferred = handle_deserialize(s, tag)::CodeInfo + setfield!(linfo, :uninferred, handle_deserialize(s, tag)::CodeInfo, :monotonic) end tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG @@ -1111,7 +1109,7 @@ function deserialize(s::AbstractSerializer, ::Type{Core.LineInfoNode}) method = mod mod = Main end - return Core.LineInfoNode(mod, method, deserialize(s)::Symbol, deserialize(s)::Int, deserialize(s)::Int) + 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}) @@ -1182,15 +1180,33 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) end end ci.inferred = deserialize(s) - ci.inlineable = deserialize(s) + if format_version(s) < 22 + inlining_cost = deserialize(s) + if isa(inlining_cost, Bool) + Core.Compiler.set_inlineable!(ci, inlining_cost) + else + ci.inlining_cost = inlining_cost + end + end ci.propagate_inbounds = deserialize(s) - ci.pure = deserialize(s) + if format_version(s) < 23 + deserialize(s) # `pure` field has been removed + end + if format_version(s) >= 20 + ci.has_fcall = deserialize(s) + end + if format_version(s) >= 21 + ci.inlining = deserialize(s)::UInt8 + end if format_version(s) >= 14 ci.constprop = deserialize(s)::UInt8 end if format_version(s) >= 17 ci.purity = deserialize(s)::UInt8 end + if format_version(s) >= 22 + ci.inlining_cost = deserialize(s)::UInt16 + end return ci end @@ -1299,6 +1315,7 @@ function deserialize_typename(s::AbstractSerializer, number) abstr = deserialize(s)::Bool mutabl = deserialize(s)::Bool ninitialized = deserialize(s)::Int32 + maxm = format_version(s) >= 18 ? deserialize(s)::UInt8 : UInt8(0) if makenew # TODO: there's an unhanded cycle in the dependency graph at this point: @@ -1310,9 +1327,10 @@ function deserialize_typename(s::AbstractSerializer, number) @assert tn == ndt.name ccall(:jl_set_const, Cvoid, (Any, Any, Any), tn.module, tn.name, tn.wrapper) ty = tn.wrapper + tn.max_methods = maxm if has_instance ty = ty::DataType - if !isdefined(ty, :instance) + if !Base.issingletontype(ty) 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) @@ -1331,7 +1349,7 @@ function deserialize_typename(s::AbstractSerializer, number) mt.offs = 0 end mt.name = mtname - mt.max_args = maxa + setfield!(mt, :max_args, maxa, :monotonic) ccall(:jl_set_nth_field, Cvoid, (Any, Csize_t, Any), tn, Base.fieldindex(Core.TypeName, :mt)-1, mt) for def in defs if isdefined(def, :sig) @@ -1343,7 +1361,15 @@ function deserialize_typename(s::AbstractSerializer, number) if tag != UNDEFREF_TAG kws = handle_deserialize(s, tag) if makenew - tn.mt.kwsorter = kws + 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 end end elseif makenew @@ -1361,7 +1387,7 @@ function deserialize_datatype(s::AbstractSerializer, full::Bool) else name = deserialize(s)::Symbol mod = deserialize(s)::Module - ty = getfield(mod,name) + ty = getglobal(mod, name) end if isa(ty,DataType) && isempty(ty.parameters) t = ty @@ -1455,8 +1481,7 @@ end # default DataType deserializer function deserialize(s::AbstractSerializer, t::DataType) nf = length(t.types) - if nf == 0 && t.size > 0 - # bits type + if isprimitivetype(t) return read(s.io, t) elseif ismutabletype(t) x = ccall(:jl_new_struct_uninit, Any, (Any,), t) @@ -1562,5 +1587,7 @@ function deserialize(s::AbstractSerializer, ::Type{T}) where T<:Base.GenericCond return cond end +serialize(s::AbstractSerializer, l::LazyString) = + invoke(serialize, Tuple{AbstractSerializer,Any}, s, Base._LazyString((), string(l))) end diff --git a/stdlib/Serialization/test/runtests.jl b/stdlib/Serialization/test/runtests.jl index ceacb7f33c27a..46749d4375538 100644 --- a/stdlib/Serialization/test/runtests.jl +++ b/stdlib/Serialization/test/runtests.jl @@ -317,18 +317,23 @@ main_ex = quote using Serialization $create_serialization_stream() do s local g() = :magic_token_anon_fun_test + local gkw(; kw=:thekw) = kw serialize(s, g) serialize(s, g) + serialize(s, gkw) seekstart(s) ds = Serializer(s) local g2 = deserialize(ds) - Base.invokelatest() do - $Test.@test g2 !== g - $Test.@test g2() == :magic_token_anon_fun_test - $Test.@test g2() == :magic_token_anon_fun_test - $Test.@test deserialize(ds) === g2 - end + @test g2 !== g + $Test.@test Base.invokelatest(g2) === :magic_token_anon_fun_test + $Test.@test Base.invokelatest(g2) === :magic_token_anon_fun_test + deserialize(ds) === g2 + + local gkw2 = deserialize(s) + $Test.@test gkw2 !== gkw + $Test.@test Base.invokelatest(gkw2) === :thekw + $Test.@test Base.invokelatest(gkw2, kw="kwtest") === "kwtest" # issue #21793 y = x -> (() -> x) @@ -336,10 +341,10 @@ main_ex = quote serialize(s, y) seekstart(s) y2 = deserialize(s) - Base.invokelatest() do + $Test.@test Base.invokelatest() do x2 = y2(2) - $Test.@test x2() == 2 - end + x2() + end === 2 end end # This needs to be run on `Main` since the serializer treats it differently. @@ -354,7 +359,7 @@ create_serialization_stream() do s # user-defined type array seek(s, 0) r = deserialize(s) @test r.storage[:v] == 2 - @test r.state == :done + @test r.state === :done @test r.exception === nothing end @@ -366,7 +371,7 @@ create_serialization_stream() do s # user-defined type array serialize(s, t) seek(s, 0) r = deserialize(s) - @test r.state == :failed + @test r.state === :failed end # corner case: undefined inside immutable struct @@ -642,3 +647,11 @@ let c1 = Threads.Condition() unlock(c2) wait(t) end + +@testset "LazyString" begin + l1 = lazy"a $1 b $2" + l2 = deserialize(IOBuffer(sprint(serialize, l1))) + @test l2.str === l1.str + @test l2 == l1 + @test l2.parts === () +end diff --git a/stdlib/SharedArrays/docs/src/index.md b/stdlib/SharedArrays/docs/src/index.md index 7b23ec15fdaa2..67ceabf42115a 100644 --- a/stdlib/SharedArrays/docs/src/index.md +++ b/stdlib/SharedArrays/docs/src/index.md @@ -1,5 +1,7 @@ # Shared Arrays +`SharedArray` represents an array, which is shared across multiple processes, on a single machine. + ```@docs SharedArrays.SharedArray SharedArrays.SharedVector diff --git a/stdlib/SharedArrays/src/SharedArrays.jl b/stdlib/SharedArrays/src/SharedArrays.jl index a961be4e534b3..f9f701c61fcea 100644 --- a/stdlib/SharedArrays/src/SharedArrays.jl +++ b/stdlib/SharedArrays/src/SharedArrays.jl @@ -328,7 +328,7 @@ procs(S::SharedArray) = S.pids """ indexpids(S::SharedArray) -Returns the current worker's index in the list of workers +Return the current worker's index in the list of workers mapping the `SharedArray` (i.e. in the same list returned by `procs(S)`), or 0 if the `SharedArray` is not mapped locally. """ @@ -337,7 +337,7 @@ indexpids(S::SharedArray) = S.pidx """ sdata(S::SharedArray) -Returns the actual `Array` object backing `S`. +Return the actual `Array` object backing `S`. """ sdata(S::SharedArray) = S.s sdata(A::AbstractArray) = A @@ -345,7 +345,7 @@ sdata(A::AbstractArray) = A """ localindices(S::SharedArray) -Returns a range describing the "default" indices to be handled by the +Return a range describing the "default" indices to be handled by the current process. This range should be interpreted in the sense of linear indexing, i.e., as a sub-range of `1:length(S)`. In multi-process contexts, returns an empty range in the parent process @@ -374,7 +374,7 @@ function SharedArray{TS,N}(A::Array{TA,N}) where {TS,TA,N} copyto!(S, A) end -convert(T::Type{<:SharedArray}, a::Array) = T(a) +convert(T::Type{<:SharedArray}, a::Array) = T(a)::T function deepcopy_internal(S::SharedArray, stackdict::IdDict) haskey(stackdict, S) && return stackdict[S] diff --git a/stdlib/Sockets/src/IPAddr.jl b/stdlib/Sockets/src/IPAddr.jl index 1792008620981..04710e400fe87 100644 --- a/stdlib/Sockets/src/IPAddr.jl +++ b/stdlib/Sockets/src/IPAddr.jl @@ -31,7 +31,7 @@ end """ IPv4(host::Integer) -> IPv4 -Returns 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 @@ -84,7 +84,7 @@ end """ IPv6(host::Integer) -> IPv6 -Returns 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 diff --git a/stdlib/Sockets/src/Sockets.jl b/stdlib/Sockets/src/Sockets.jl index 4b5518a1fde61..33767c2153211 100644 --- a/stdlib/Sockets/src/Sockets.jl +++ b/stdlib/Sockets/src/Sockets.jl @@ -200,7 +200,6 @@ end show(io::IO, stream::UDPSocket) = print(io, typeof(stream), "(", uv_status_string(stream), ")") function _uv_hook_close(sock::UDPSocket) - sock.handle = C_NULL lock(sock.cond) try sock.status = StatusClosed @@ -626,7 +625,7 @@ listen(port::Integer; backlog::Integer=BACKLOG_DEFAULT) = listen(localhost, port listen(host::IPAddr, port::Integer; backlog::Integer=BACKLOG_DEFAULT) = listen(InetAddr(host, port); backlog=backlog) function listen(sock::LibuvServer; backlog::Integer=BACKLOG_DEFAULT) - uv_error("listen", trylisten(sock)) + uv_error("listen", trylisten(sock; backlog=backlog)) return sock end @@ -710,16 +709,17 @@ end const localhost = ip"127.0.0.1" """ - listenany([host::IPAddr,] port_hint) -> (UInt16, TCPServer) + listenany([host::IPAddr,] port_hint; backlog::Integer=BACKLOG_DEFAULT) -> (UInt16, TCPServer) Create a `TCPServer` on any port, using hint as a starting point. Returns a tuple of the actual port that the server was created on and the server itself. +The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. """ -function listenany(host::IPAddr, default_port) +function listenany(host::IPAddr, default_port; backlog::Integer=BACKLOG_DEFAULT) addr = InetAddr(host, default_port) while true sock = TCPServer() - if bind(sock, addr) && trylisten(sock) == 0 + if bind(sock, addr) && trylisten(sock; backlog) == 0 if default_port == 0 _addr, port = getsockname(sock) return (port, sock) @@ -727,14 +727,14 @@ function listenany(host::IPAddr, default_port) return (addr.port, sock) end close(sock) - addr = InetAddr(addr.host, addr.port + 1) + addr = InetAddr(addr.host, addr.port + UInt16(1)) if addr.port == default_port error("no ports available") end end end -listenany(default_port) = listenany(localhost, default_port) +listenany(default_port; backlog::Integer=BACKLOG_DEFAULT) = listenany(localhost, default_port; backlog) function udp_set_membership(sock::UDPSocket, group_addr::String, interface_addr::Union{Nothing, String}, operation) diff --git a/stdlib/Sockets/src/addrinfo.jl b/stdlib/Sockets/src/addrinfo.jl index 586463ba0fa21..dda9dac308f38 100644 --- a/stdlib/Sockets/src/addrinfo.jl +++ b/stdlib/Sockets/src/addrinfo.jl @@ -170,7 +170,7 @@ using the operating system's underlying `getnameinfo` implementation. # Examples ```julia-repl -julia> getnameinfo(Sockets.IPv4("8.8.8.8")) +julia> getnameinfo(IPv4("8.8.8.8")) "google-public-dns-a.google.com" ``` """ diff --git a/stdlib/Sockets/test/runtests.jl b/stdlib/Sockets/test/runtests.jl index 6b8b1be6e055f..02a994460afbf 100644 --- a/stdlib/Sockets/test/runtests.jl +++ b/stdlib/Sockets/test/runtests.jl @@ -16,7 +16,7 @@ function killjob(d) end if @isdefined(SIGINFO) ccall(:uv_kill, Cint, (Cint, Cint), getpid(), SIGINFO) - sleep(1) + sleep(5) # Allow time for profile to collect and print before killing end ccall(:uv_kill, Cint, (Cint, Cint), getpid(), Base.SIGTERM) nothing @@ -136,7 +136,7 @@ defaultport = rand(2000:4000) write(sock, "Hello World\n") # test "locked" println to a socket - @Experimental.sync begin + Experimental.@sync begin for i in 1:100 @async println(sock, "a", 1) end @@ -307,7 +307,7 @@ end bind(a, ip"127.0.0.1", randport) bind(b, ip"127.0.0.1", randport + 1) - @Experimental.sync begin + Experimental.@sync begin let i = 0 for _ = 1:30 @async let msg = String(recv(a)) @@ -387,7 +387,7 @@ end # connect to it client_sock = connect(addr, port) test_done = false - @Experimental.sync begin + Experimental.@sync begin @async begin Base.wait_readnb(client_sock, 1) test_done || error("Client disconnected prematurely.") diff --git a/stdlib/SparseArrays.version b/stdlib/SparseArrays.version index 1d93c36f6d7f0..d4a548daef5d7 100644 --- a/stdlib/SparseArrays.version +++ b/stdlib/SparseArrays.version @@ -1,4 +1,4 @@ SPARSEARRAYS_BRANCH = main -SPARSEARRAYS_SHA1 = aa51c9b82d952502139213715c9b077ec36c4623 +SPARSEARRAYS_SHA1 = 8affe9e499379616e33fc60a24bb31500e8423d7 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 7ad39f00f4cbe..27197b12be54c 100644 --- a/stdlib/Statistics.version +++ b/stdlib/Statistics.version @@ -1,4 +1,4 @@ STATISTICS_BRANCH = master -STATISTICS_SHA1 = 61a021bcb330e6c52f2435f2abaffc77875ab6f2 -STATISTICS_GIT_URL := https://github.com/JuliaLang/Statistics.jl.git -STATISTICS_TAR_URL = https://api.github.com/repos/JuliaLang/Statistics.jl/tarball/$1 +STATISTICS_SHA1 = a3feba2bb63f06b7f40024185e9fa5f6385e2510 +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/SuiteSparse.version b/stdlib/SuiteSparse.version index 27e835befbc38..a5d7d781eff3d 100644 --- a/stdlib/SuiteSparse.version +++ b/stdlib/SuiteSparse.version @@ -1,4 +1,4 @@ SUITESPARSE_BRANCH = master -SUITESPARSE_SHA1 = f63732c1c6adecb277d8f2981cc8c1883c321bcc +SUITESPARSE_SHA1 = e8285dd13a6d5b5cf52d8124793fc4d622d07554 SUITESPARSE_GIT_URL := https://github.com/JuliaSparse/SuiteSparse.jl.git SUITESPARSE_TAR_URL = https://api.github.com/repos/JuliaSparse/SuiteSparse.jl/tarball/$1 diff --git a/stdlib/SuiteSparse_jll/Project.toml b/stdlib/SuiteSparse_jll/Project.toml index f36ce756c834c..d1fb2c25fa68b 100644 --- a/stdlib/SuiteSparse_jll/Project.toml +++ b/stdlib/SuiteSparse_jll/Project.toml @@ -1,6 +1,6 @@ name = "SuiteSparse_jll" uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "5.10.1+0" +version = "5.10.1+6" [deps] libblastrampoline_jll = "8e850b90-86db-534c-a0d3-1478176c7d93" diff --git a/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl b/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl index 2940970ceff9f..6b87d417fc2a8 100644 --- a/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl +++ b/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl @@ -14,31 +14,31 @@ export libamd, libbtf, libcamd, libccolamd, libcholmod, libcolamd, libklu, libld # Man I can't wait until these are automatically handled by an in-Base JLLWrappers clone. const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libamd_handle = C_NULL -libamd_path = "" -libbtf_handle = C_NULL -libbtf_path = "" -libcamd_handle = C_NULL -libcamd_path = "" -libccolamd_handle = C_NULL -libccolamd_path = "" -libcholmod_handle = C_NULL -libcholmod_path = "" -libcolamd_handle = C_NULL -libcolamd_path = "" -libklu_handle = C_NULL -libklu_path = "" -libldl_handle = C_NULL -libldl_path = "" -librbio_handle = C_NULL -librbio_path = "" -libspqr_handle = C_NULL -libspqr_path = "" -libsuitesparseconfig_handle = C_NULL -libsuitesparseconfig_path = "" -libumfpack_handle = C_NULL -libumfpack_path = "" +artifact_dir::String = "" +libamd_handle::Ptr{Cvoid} = C_NULL +libamd_path::String = "" +libbtf_handle::Ptr{Cvoid} = C_NULL +libbtf_path::String = "" +libcamd_handle::Ptr{Cvoid} = C_NULL +libcamd_path::String = "" +libccolamd_handle::Ptr{Cvoid} = C_NULL +libccolamd_path::String = "" +libcholmod_handle::Ptr{Cvoid} = C_NULL +libcholmod_path::String = "" +libcolamd_handle::Ptr{Cvoid} = C_NULL +libcolamd_path::String = "" +libklu_handle::Ptr{Cvoid} = C_NULL +libklu_path::String = "" +libldl_handle::Ptr{Cvoid} = C_NULL +libldl_path::String = "" +librbio_handle::Ptr{Cvoid} = C_NULL +librbio_path::String = "" +libspqr_handle::Ptr{Cvoid} = C_NULL +libspqr_path::String = "" +libsuitesparseconfig_handle::Ptr{Cvoid} = C_NULL +libsuitesparseconfig_path::String = "" +libumfpack_handle::Ptr{Cvoid} = C_NULL +libumfpack_path::String = "" if Sys.iswindows() const libamd = "libamd.dll" diff --git a/stdlib/TOML/Project.toml b/stdlib/TOML/Project.toml index 48bf828a370c9..17fc8be19ec8e 100644 --- a/stdlib/TOML/Project.toml +++ b/stdlib/TOML/Project.toml @@ -1,12 +1,18 @@ name = "TOML" uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.0" +version = "1.0.3" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +[compat] +julia = "1.6" + [extras] +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +p7zip_jll = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" [targets] -test = ["Test"] +test = ["Downloads", "p7zip_jll", "Tar", "Test"] diff --git a/stdlib/TOML/benchmark/tune.json b/stdlib/TOML/benchmark/tune.json index d8d7ca2ebf889..f1b12c393587f 100644 --- a/stdlib/TOML/benchmark/tune.json +++ b/stdlib/TOML/benchmark/tune.json @@ -1 +1 @@ -[{"Julia":"1.5.0","BenchmarkTools":"0.4.3"},[["BenchmarkGroup",{"data":{"strings":["BenchmarkGroup",{"data":{"long":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"short":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"numbers":["BenchmarkGroup",{"data":{"integers":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"floats":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"registry":["BenchmarkGroup",{"data":{"Registry.toml":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"Compat.toml":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"arrays":["BenchmarkGroup",{"data":{"heterogeneous":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"homogeneous":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"array of tables":["BenchmarkGroup",{"data":{"empty":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"parse empty":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":340,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}]]] \ No newline at end of file +[{"Julia":"1.5.0","BenchmarkTools":"0.4.3"},[["BenchmarkGroup",{"data":{"strings":["BenchmarkGroup",{"data":{"long":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"short":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"numbers":["BenchmarkGroup",{"data":{"integers":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"floats":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"registry":["BenchmarkGroup",{"data":{"Registry.toml":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"Compat.toml":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"arrays":["BenchmarkGroup",{"data":{"heterogeneous":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}],"homogeneous":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"array of tables":["BenchmarkGroup",{"data":{"empty":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":1,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}],"parse empty":["BenchmarkTools.Parameters",{"gctrial":true,"time_tolerance":0.05,"samples":10000,"evals":340,"gcsample":false,"seconds":5.0,"overhead":0.0,"memory_tolerance":0.01}]},"tags":[]}]]] diff --git a/stdlib/TOML/src/TOML.jl b/stdlib/TOML/src/TOML.jl index 4765a05c05f52..a2ea1869b4079 100644 --- a/stdlib/TOML/src/TOML.jl +++ b/stdlib/TOML/src/TOML.jl @@ -110,7 +110,7 @@ const ParserError = Internals.ParserError 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`. -The following data types are supported: `AbstractDict`, `Integer`, `AbstractFloat`, `Bool`, +The following data types are supported: `AbstractDict`, `AbstractVector`, `AbstractString`, `Integer`, `AbstractFloat`, `Bool`, `Dates.DateTime`, `Dates.Time`, `Dates.Date`. Note that the integers and floats need to be convertible to `Float64` and `Int64` respectively. For other data types, pass the function `to_toml` that takes the data types and returns a value of a diff --git a/stdlib/TOML/src/print.jl b/stdlib/TOML/src/print.jl index 059414152f727..1fa9f97405504 100644 --- a/stdlib/TOML/src/print.jl +++ b/stdlib/TOML/src/print.jl @@ -33,6 +33,14 @@ function print_toml_escaped(io::IO, s::AbstractString) end end +const MbyFunc = Union{Function, Nothing} +const TOMLValue = Union{AbstractVector, AbstractDict, Dates.DateTime, Dates.Time, Dates.Date, Bool, Integer, AbstractFloat, AbstractString} + + +######## +# Keys # +######## + function printkey(io::IO, keys::Vector{String}) for (i, k) in enumerate(keys) i != 1 && Base.print(io, ".") @@ -50,48 +58,85 @@ function printkey(io::IO, keys::Vector{String}) end end -const MbyFunc = Union{Function, Nothing} -const TOMLValue = Union{AbstractVector, AbstractDict, Dates.DateTime, Dates.Time, Dates.Date, Bool, Integer, AbstractFloat, AbstractString} -function printvalue(f::MbyFunc, io::IO, value::AbstractVector; sorted=false, by=identity) +function to_toml_value(f::MbyFunc, value) + if f === nothing + error("type `$(typeof(value))` is not a valid TOML type, pass a conversion function to `TOML.print`") + end + toml_value = f(value) + if !(toml_value isa TOMLValue) + error("TOML syntax function for type `$(typeof(value))` did not return a valid TOML type but a `$(typeof(toml_value))`") + end + return toml_value +end + +########## +# Values # +########## + +# Fallback +function printvalue(f::MbyFunc, io::IO, value) + toml_value = to_toml_value(f, value) + @invokelatest printvalue(f, io, toml_value) +end + +function printvalue(f::MbyFunc, io::IO, value::AbstractVector) Base.print(io, "[") for (i, x) in enumerate(value) i != 1 && Base.print(io, ", ") - if isa(x, AbstractDict) - _print(f, io, x; sorted, by) - else - printvalue(f, io, x; sorted, by) - end + printvalue(f, io, x) end Base.print(io, "]") end -printvalue(f::MbyFunc, io::IO, value::AbstractDict; sorted=false, by=identity) = - _print(f, io, value; sorted, by) -printvalue(f::MbyFunc, io::IO, value::Dates.DateTime; _...) = - Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd\THH:MM:SS.sss\Z")) -printvalue(f::MbyFunc, io::IO, value::Dates.Time; _...) = - Base.print(io, Dates.format(value, Dates.dateformat"HH:MM:SS.sss")) -printvalue(f::MbyFunc, io::IO, value::Dates.Date; _...) = - Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd")) -printvalue(f::MbyFunc, io::IO, value::Bool; _...) = - Base.print(io, value ? "true" : "false") -printvalue(f::MbyFunc, io::IO, value::Integer; _...) = - Base.print(io, Int64(value)) # TOML specifies 64-bit signed long range for integer -printvalue(f::MbyFunc, io::IO, value::AbstractFloat; _...) = - Base.print(io, isnan(value) ? "nan" : - isinf(value) ? string(value > 0 ? "+" : "-", "inf") : - Float64(value)) # TOML specifies IEEE 754 binary64 for float -function printvalue(f::MbyFunc, io::IO, value::AbstractString; _...) - Base.print(io, "\"") - print_toml_escaped(io, value) - Base.print(io, "\"") + +function printvalue(f::MbyFunc, io::IO, value::TOMLValue) + 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")) : + value isa Bool ? Base.print(io, value ? "true" : "false") : + value isa Integer ? print_integer(io, value) : # Julia's own printing should be compatible with TOML on integers + value isa AbstractFloat ? Base.print(io, isnan(value) ? "nan" : + isinf(value) ? string(value > 0 ? "+" : "-", "inf") : + Float64(value)) : # TOML specifies IEEE 754 binary64 for float + value isa AbstractString ? (Base.print(io, "\""); + print_toml_escaped(io, value); + Base.print(io, "\"")) : + value isa AbstractDict ? print_inline_table(f, io, value) : + error("internal error in TOML printing, unhandled value") +end + +function print_integer(io::IO, value::Integer) + value isa Signed && return Base.show(io, value) + # unsigned integers are printed as hex + n = 2 * ndigits(value, base=256) + Base.print(io, "0x", string(value, base=16, pad=n)) + return +end + +function print_inline_table(f::MbyFunc, io::IO, value::AbstractDict) + Base.print(io, "{") + for (i, (k,v)) in enumerate(value) + i != 1 && Base.print(io, ", ") + printkey(io, [String(k)]) + Base.print(io, " = ") + printvalue(f, io, v) + end + Base.print(io, "}") end + +########## +# Tables # +########## + is_table(value) = isa(value, AbstractDict) is_array_of_tables(value) = isa(value, AbstractArray) && - length(value) > 0 && isa(value[1], AbstractDict) -is_tabular(value) = is_table(value) || is_array_of_tables(value) + length(value) > 0 && ( + isa(value, AbstractArray{<:AbstractDict}) || + all(v -> isa(v, AbstractDict), value) + ) +is_tabular(value) = is_table(value) || @invokelatest(is_array_of_tables(value)) -function _print(f::MbyFunc, io::IO, a::AbstractDict, +function print_table(f::MbyFunc, io::IO, a::AbstractDict, ks::Vector{String} = String[]; indent::Int = 0, first_block::Bool = true, @@ -100,40 +145,34 @@ function _print(f::MbyFunc, io::IO, a::AbstractDict, ) akeys = keys(a) if sorted - akeys = sort!(collect(akeys); by) + akeys = sort!(collect(akeys); by=by) end # First print non-tabular entries for key in akeys value = a[key] - is_tabular(value) && continue if !isa(value, TOMLValue) - if f === nothing - error("type `$(typeof(value))` is not a valid TOML type, pass a conversion function to `TOML.print`") - end - toml_value = f(value) - if !(toml_value isa TOMLValue) - error("TOML syntax function for type `$(typeof(value))` did not return a valid TOML type but a `$(typeof(toml_value))`") - end - value = toml_value - end - if is_tabular(value) - _print(f, io, Dict(key => value); indent, first_block, sorted, by) - else - Base.print(io, ' '^4max(0,indent-1)) - printkey(io, [String(key)]) - Base.print(io, " = ") # print separator - printvalue(f, io, value; sorted, by) - Base.print(io, "\n") # new line? + value = to_toml_value(f, value) end + is_tabular(value) && continue + + Base.print(io, ' '^4max(0,indent-1)) + printkey(io, [String(key)]) + Base.print(io, " = ") # print separator + printvalue(f, io, value) + Base.print(io, "\n") # new line? first_block = false end for key in akeys value = a[key] + if !isa(value, TOMLValue) + value = to_toml_value(f, value) + end if is_table(value) push!(ks, String(key)) - header = isempty(value) || !all(is_tabular(v) for v in values(value))::Bool + _values = @invokelatest values(value) + header = isempty(value) || !all(is_tabular(v) for v in _values)::Bool if header # print table first_block || println(io) @@ -144,9 +183,9 @@ function _print(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(f, io, value, ks; indent = indent + header, first_block = header, sorted, by) + @invokelatest print_table(f, io, value, ks; indent = indent + header, first_block = header, sorted=sorted, by=by) pop!(ks) - elseif is_array_of_tables(value) + elseif @invokelatest(is_array_of_tables(value)) # print array of tables first_block || println(io) first_block = false @@ -158,14 +197,19 @@ function _print(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(f, io, v, ks; indent = indent + 1, sorted, by) + @invokelatest print_table(f, io, v, ks; indent = indent + 1, sorted=sorted, by=by) end pop!(ks) end end end -print(f::MbyFunc, io::IO, a::AbstractDict; sorted::Bool=false, by=identity) = _print(f, io, a; sorted, by) -print(f::MbyFunc, a::AbstractDict; sorted::Bool=false, by=identity) = print(f, stdout, a; sorted, by) -print(io::IO, a::AbstractDict; sorted::Bool=false, by=identity) = _print(nothing, io, a; sorted, by) -print(a::AbstractDict; sorted::Bool=false, by=identity) = print(nothing, stdout, a; sorted, by) + +####### +# 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) diff --git a/stdlib/TOML/test/print.jl b/stdlib/TOML/test/print.jl index 4ab5e2d8d066d..765b6feb491a5 100644 --- a/stdlib/TOML/test/print.jl +++ b/stdlib/TOML/test/print.jl @@ -71,3 +71,72 @@ end d = Dict("str" => string(Char(0xd800))) @test_throws ErrorException TOML.print(devnull, d) end + +str = """ +[[dataset.loader]] +driver = "nested" +loaders = ["gzip", { driver = "csv", args = {delim = "\t"}}] +""" +@test roundtrip(str) + + +@testset "vec with dicts and non-dicts" begin + # https://github.com/JuliaLang/julia/issues/45340 + d = Dict("b" => Any[111, Dict("a" => 222, "d" => 333)]) + @test toml_str(d) == "b = [111, {a = 222, d = 333}]\n" + + d = Dict("b" => Any[Dict("a" => 222, "d" => 333), 111]) + @test toml_str(d) == "b = [{a = 222, d = 333}, 111]\n" + + d = Dict("b" => Any[Dict("a" => 222, "d" => 333)]) + @test toml_str(d) == """ + [[b]] + a = 222 + d = 333 + """ +end + +@testset "unsigned integers" for (x, s) in [ + 0x1a0 => "0x01a0", + 0x1aea8 => "0x01aea8", + 0x1aeee8 => "0x1aeee8", + 0x1aea01231 => "0x01aea01231", + 0x1aea01231213ae13125 => "0x01aea01231213ae13125", + ] + d = Dict("x" => x) + @test toml_str(d) == """ + x = $s + """ +end + +struct Foo + a::Int64 + b::Float64 +end + +struct Bar + c::Float64 + d::String +end + + +f = Foo(2,9.9) +b = Bar(1.345, "hello") + +dd = Dict("hello"=>"world", "f"=>f, "b"=>b) + +to_dict(foo::Foo) = Dict("a"=>foo.a, "b"=>foo.b) +to_dict(bar::Bar) = Dict("c"=>bar.c, "d"=>bar.d) + +@test toml_str(to_dict, dd; sorted=true) == +""" +hello = "world" + +[b] +c = 1.345 +d = "hello" + +[f] +a = 2 +b = 9.9 +""" diff --git a/stdlib/TOML/test/readme.jl b/stdlib/TOML/test/readme.jl index 21961cc6f7ec8..ee267414485ba 100644 --- a/stdlib/TOML/test/readme.jl +++ b/stdlib/TOML/test/readme.jl @@ -410,31 +410,90 @@ d = parse(str) @test d["oct2"] == 0o755 @test d["bin1"] == 0b11010110 +str = """ +hex1 = 0x6E # UInt8 +hex2 = 0x8f1e # UInt16 +hex3 = 0x765f3173 # UInt32 +hex4 = 0xc13b830a807cc7f4 # UInt64 +hex5 = 0x937efe0a4241edb24a04b97bd90ef363 # UInt128 +hex6 = 0x937efe0a4241edb24a04b97bd90ef3632 # BigInt +""" +@test roundtrip(str) +d = parse(str) +@test d["hex1"] isa UInt64 +@test d["hex2"] isa UInt64 +@test d["hex3"] isa UInt64 +@test d["hex4"] isa UInt64 +@test d["hex5"] isa UInt128 +@test d["hex6"] isa BigInt + +str = """ +oct1 = 0o140 # UInt8 +oct2 = 0o46244 # UInt16 +oct3 = 0o32542120656 # UInt32 +oct4 = 0o1526535761042630654411 # UInt64 +oct5 = 0o3467204325743773607311464533371572447656531 # UInt128 +oct6 = 0o34672043257437736073114645333715724476565312 # BigInt +""" +@test roundtrip(str) +d = parse(str) +@test d["oct1"] isa UInt64 +@test d["oct2"] isa UInt64 +@test d["oct3"] isa UInt64 +@test d["oct4"] isa UInt64 +@test d["oct5"] isa UInt128 +@test d["oct6"] isa BigInt + +str = """ +bin1 = 0b10001010 # UInt8 +bin2 = 0b11111010001100 # UInt16 +bin3 = 0b11100011110000010101000010101 # UInt32 +bin4 = 0b10000110100111011010001000000111110110000011111101101110011011 # UInt64 +bin5 = 0b1101101101101100110001010110111011101000111010101110011000011100110100101111110001010001011001000001000001010010011101100100111 # UInt128 +bin6 = 0b110110110110110011000101011011101110100011101010111001100001110011010010111111000101000101100100000100000101001001110110010011111 # BigInt +""" + +@test roundtrip(str) +d = parse(str) +@test d["bin1"] isa UInt64 +@test d["bin2"] isa UInt64 +@test d["bin3"] isa UInt64 +@test d["bin4"] isa UInt64 +@test d["bin5"] isa UInt128 +@test d["bin6"] isa BigInt + #Arbitrary 64-bit signed integers (from −2^63 to 2^63−1) should be accepted and #handled losslessly. If an integer cannot be represented losslessly, an error #must be thrown. str = """ -low = -9_223_372_036_854_775_808 -high = 9_223_372_036_854_775_807 +low = -170_141_183_460_469_231_731_687_303_715_884_105_728 +high = 170_141_183_460_469_231_731_687_303_715_884_105_727 +""" +@test roundtrip(str) +d = parse(str) +@test d["low"] == typemin(Int128) +@test d["high"] == typemax(Int128) + +str = """ +low = -170_141_183_460_469_231_731_687_303_715_884_105_728_123 +high = 170_141_183_460_469_231_731_687_303_715_884_105_727_123 """ @test roundtrip(str) d = parse(str) -@test d["low"] == -9_223_372_036_854_775_808 -@test d["high"] == 9_223_372_036_854_775_807 +@test d["low"] == big"-170_141_183_460_469_231_731_687_303_715_884_105_728_123" +@test d["high"] == big"170_141_183_460_469_231_731_687_303_715_884_105_727_123" str = """ toolow = -9_223_372_036_854_775_809 """ -err = tryparse(str) -@test err isa ParserError -@test err.type == Internals.ErrOverflowError +d = parse(str) +@test d["toolow"] == -9223372036854775809 str = """ toohigh = 9_223_372_036_854_775_808 """ -err = tryparse(str) -@test err isa ParserError -@test err.type == Internals.ErrOverflowError +d = parse(str) +d["toohigh"] == 9_223_372_036_854_775_808 end @@ -613,7 +672,7 @@ contributors = [ { name = \"Baz Qux\", email = \"bazqux@example.com\", url = \"https://example.com/bazqux\" } ] """ -@test_broken roundtrip(str) # Printer doesn't handle inline tables in arrays? +@test roundtrip(str) d = parse(str) @test d["integers"] == [1,2,3] @test d["colors"] == ["red", "yellow", "green"] diff --git a/stdlib/TOML/test/runtests.jl b/stdlib/TOML/test/runtests.jl index 6228b3c2fc11c..7376fab914636 100644 --- a/stdlib/TOML/test/runtests.jl +++ b/stdlib/TOML/test/runtests.jl @@ -16,6 +16,7 @@ function roundtrip(data) end include("readme.jl") +include("utils/utils.jl") include("toml_test.jl") include("values.jl") include("invalids.jl") diff --git a/stdlib/TOML/test/testfiles/COPYING b/stdlib/TOML/test/testfiles/COPYING deleted file mode 100644 index 93b22020a83d8..0000000000000 --- a/stdlib/TOML/test/testfiles/COPYING +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 TOML authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-leads.toml b/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-leads.toml deleted file mode 100644 index 123f173beb3ac..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-leads.toml +++ /dev/null @@ -1 +0,0 @@ -no-leads = 1987-7-05T17:45:00Z diff --git a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-secs.toml b/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-secs.toml deleted file mode 100644 index ba9390076273d..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-secs.toml +++ /dev/null @@ -1 +0,0 @@ -no-secs = 1987-07-05T17:45Z diff --git a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-t.toml b/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-t.toml deleted file mode 100644 index 617e3c56d4008..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-no-t.toml +++ /dev/null @@ -1 +0,0 @@ -no-t = 1987-07-0517:45:00Z diff --git a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-with-milli.toml b/stdlib/TOML/test/testfiles/invalid/datetime-malformed-with-milli.toml deleted file mode 100644 index eef792f34d6ef..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/datetime-malformed-with-milli.toml +++ /dev/null @@ -1 +0,0 @@ -with-milli = 1987-07-5T17:45:00.12Z diff --git a/stdlib/TOML/test/testfiles/invalid/duplicate-key-table.toml b/stdlib/TOML/test/testfiles/invalid/duplicate-key-table.toml deleted file mode 100644 index cedf05fc53bff..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/duplicate-key-table.toml +++ /dev/null @@ -1,5 +0,0 @@ -[fruit] -type = "apple" - -[fruit.type] -apple = "yes" diff --git a/stdlib/TOML/test/testfiles/invalid/duplicate-keys.toml b/stdlib/TOML/test/testfiles/invalid/duplicate-keys.toml deleted file mode 100644 index 9b5aee0e59b35..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/duplicate-keys.toml +++ /dev/null @@ -1,2 +0,0 @@ -dupe = false -dupe = true diff --git a/stdlib/TOML/test/testfiles/invalid/duplicate-tables.toml b/stdlib/TOML/test/testfiles/invalid/duplicate-tables.toml deleted file mode 100644 index 8ddf49b4e8930..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/duplicate-tables.toml +++ /dev/null @@ -1,2 +0,0 @@ -[a] -[a] diff --git a/stdlib/TOML/test/testfiles/invalid/empty-implicit-table.toml b/stdlib/TOML/test/testfiles/invalid/empty-implicit-table.toml deleted file mode 100644 index 0cc36d0d28154..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/empty-implicit-table.toml +++ /dev/null @@ -1 +0,0 @@ -[naughty..naughty] diff --git a/stdlib/TOML/test/testfiles/invalid/empty-table.toml b/stdlib/TOML/test/testfiles/invalid/empty-table.toml deleted file mode 100644 index fe51488c7066f..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/empty-table.toml +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/stdlib/TOML/test/testfiles/invalid/float-leading-zero-neg.toml b/stdlib/TOML/test/testfiles/invalid/float-leading-zero-neg.toml deleted file mode 100644 index dbc16ff161787..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-leading-zero-neg.toml +++ /dev/null @@ -1 +0,0 @@ -leading-zero = -03.14 diff --git a/stdlib/TOML/test/testfiles/invalid/float-leading-zero-pos.toml b/stdlib/TOML/test/testfiles/invalid/float-leading-zero-pos.toml deleted file mode 100644 index 6de9634c6b110..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-leading-zero-pos.toml +++ /dev/null @@ -1 +0,0 @@ -leading-zero = +03.14 diff --git a/stdlib/TOML/test/testfiles/invalid/float-leading-zero.toml b/stdlib/TOML/test/testfiles/invalid/float-leading-zero.toml deleted file mode 100644 index 551fb2551053a..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-leading-zero.toml +++ /dev/null @@ -1 +0,0 @@ -leading-zero = 03.14 diff --git a/stdlib/TOML/test/testfiles/invalid/float-no-leading-zero.toml b/stdlib/TOML/test/testfiles/invalid/float-no-leading-zero.toml deleted file mode 100644 index cab76bfd15887..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-no-leading-zero.toml +++ /dev/null @@ -1,2 +0,0 @@ -answer = .12345 -neganswer = -.12345 diff --git a/stdlib/TOML/test/testfiles/invalid/float-no-trailing-digits.toml b/stdlib/TOML/test/testfiles/invalid/float-no-trailing-digits.toml deleted file mode 100644 index cbff2d06f05cc..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-no-trailing-digits.toml +++ /dev/null @@ -1,2 +0,0 @@ -answer = 1. -neganswer = -1. diff --git a/stdlib/TOML/test/testfiles/invalid/float-underscore-after-point.toml b/stdlib/TOML/test/testfiles/invalid/float-underscore-after-point.toml deleted file mode 100644 index fe2f2e2e7a981..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-underscore-after-point.toml +++ /dev/null @@ -1 +0,0 @@ -bad = 1._2 diff --git a/stdlib/TOML/test/testfiles/invalid/float-underscore-after.toml b/stdlib/TOML/test/testfiles/invalid/float-underscore-after.toml deleted file mode 100644 index 33f2bae570c57..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-underscore-after.toml +++ /dev/null @@ -1 +0,0 @@ -bad = 1.2_ diff --git a/stdlib/TOML/test/testfiles/invalid/float-underscore-before-point.toml b/stdlib/TOML/test/testfiles/invalid/float-underscore-before-point.toml deleted file mode 100644 index 0aa1722f790c2..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-underscore-before-point.toml +++ /dev/null @@ -1 +0,0 @@ -bad = 1_.2 diff --git a/stdlib/TOML/test/testfiles/invalid/float-underscore-before.toml b/stdlib/TOML/test/testfiles/invalid/float-underscore-before.toml deleted file mode 100644 index 155de0f65d1e7..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/float-underscore-before.toml +++ /dev/null @@ -1 +0,0 @@ -bad = _1.2 diff --git a/stdlib/TOML/test/testfiles/invalid/inline-table-linebreak.toml b/stdlib/TOML/test/testfiles/invalid/inline-table-linebreak.toml deleted file mode 100644 index 727fb2a4991b0..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/inline-table-linebreak.toml +++ /dev/null @@ -1,2 +0,0 @@ -simple = { a = 1 -} diff --git a/stdlib/TOML/test/testfiles/invalid/integer-leading-zero-neg.toml b/stdlib/TOML/test/testfiles/invalid/integer-leading-zero-neg.toml deleted file mode 100644 index ff6836b690b6e..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/integer-leading-zero-neg.toml +++ /dev/null @@ -1 +0,0 @@ -leading-zero = -012 diff --git a/stdlib/TOML/test/testfiles/invalid/integer-leading-zero-pos.toml b/stdlib/TOML/test/testfiles/invalid/integer-leading-zero-pos.toml deleted file mode 100644 index 4e635421de813..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/integer-leading-zero-pos.toml +++ /dev/null @@ -1 +0,0 @@ -leading-zero = +012 diff --git a/stdlib/TOML/test/testfiles/invalid/integer-leading-zero.toml b/stdlib/TOML/test/testfiles/invalid/integer-leading-zero.toml deleted file mode 100644 index 38b1ca40529ff..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/integer-leading-zero.toml +++ /dev/null @@ -1 +0,0 @@ -leading-zero = 012 diff --git a/stdlib/TOML/test/testfiles/invalid/integer-underscore-after.toml b/stdlib/TOML/test/testfiles/invalid/integer-underscore-after.toml deleted file mode 100644 index b9ec0ee8978e4..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/integer-underscore-after.toml +++ /dev/null @@ -1 +0,0 @@ -bad = 123_ diff --git a/stdlib/TOML/test/testfiles/invalid/integer-underscore-before.toml b/stdlib/TOML/test/testfiles/invalid/integer-underscore-before.toml deleted file mode 100644 index 1f96c4a5943b4..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/integer-underscore-before.toml +++ /dev/null @@ -1 +0,0 @@ -bad = _123 diff --git a/stdlib/TOML/test/testfiles/invalid/integer-underscore-double.toml b/stdlib/TOML/test/testfiles/invalid/integer-underscore-double.toml deleted file mode 100644 index 490adb3547a7b..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/integer-underscore-double.toml +++ /dev/null @@ -1 +0,0 @@ -bad = 1__23 diff --git a/stdlib/TOML/test/testfiles/invalid/key-after-array.toml b/stdlib/TOML/test/testfiles/invalid/key-after-array.toml deleted file mode 100644 index 5c1a1b0a9bc50..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-after-array.toml +++ /dev/null @@ -1 +0,0 @@ -[[agencies]] owner = "S Cjelli" diff --git a/stdlib/TOML/test/testfiles/invalid/key-after-table.toml b/stdlib/TOML/test/testfiles/invalid/key-after-table.toml deleted file mode 100644 index 68867842cb8e2..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-after-table.toml +++ /dev/null @@ -1 +0,0 @@ -[error] this = "should not be here" diff --git a/stdlib/TOML/test/testfiles/invalid/key-empty.toml b/stdlib/TOML/test/testfiles/invalid/key-empty.toml deleted file mode 100644 index 09f998f4163e1..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-empty.toml +++ /dev/null @@ -1 +0,0 @@ - = 1 diff --git a/stdlib/TOML/test/testfiles/invalid/key-hash.toml b/stdlib/TOML/test/testfiles/invalid/key-hash.toml deleted file mode 100644 index e321b1fbd0c96..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-hash.toml +++ /dev/null @@ -1 +0,0 @@ -a# = 1 diff --git a/stdlib/TOML/test/testfiles/invalid/key-newline.toml b/stdlib/TOML/test/testfiles/invalid/key-newline.toml deleted file mode 100644 index 707aad54ec34f..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-newline.toml +++ /dev/null @@ -1,2 +0,0 @@ -a -= 1 diff --git a/stdlib/TOML/test/testfiles/invalid/key-no-eol.toml b/stdlib/TOML/test/testfiles/invalid/key-no-eol.toml deleted file mode 100644 index 3c58eee182b21..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-no-eol.toml +++ /dev/null @@ -1 +0,0 @@ -a = 1 b = 2 diff --git a/stdlib/TOML/test/testfiles/invalid/key-open-bracket.toml b/stdlib/TOML/test/testfiles/invalid/key-open-bracket.toml deleted file mode 100644 index f0aeb16e50003..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-open-bracket.toml +++ /dev/null @@ -1 +0,0 @@ -[abc = 1 diff --git a/stdlib/TOML/test/testfiles/invalid/key-single-open-bracket.toml b/stdlib/TOML/test/testfiles/invalid/key-single-open-bracket.toml deleted file mode 100644 index 8e2f0bef135ba..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-single-open-bracket.toml +++ /dev/null @@ -1 +0,0 @@ -[ \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/invalid/key-space.toml b/stdlib/TOML/test/testfiles/invalid/key-space.toml deleted file mode 100644 index 201806d280132..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-space.toml +++ /dev/null @@ -1 +0,0 @@ -a b = 1 \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/invalid/key-start-bracket.toml b/stdlib/TOML/test/testfiles/invalid/key-start-bracket.toml deleted file mode 100644 index e0597ae1c6f1c..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-start-bracket.toml +++ /dev/null @@ -1,3 +0,0 @@ -[a] -[xyz = 5 -[b] diff --git a/stdlib/TOML/test/testfiles/invalid/key-two-equals.toml b/stdlib/TOML/test/testfiles/invalid/key-two-equals.toml deleted file mode 100644 index 25a037894eb0f..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/key-two-equals.toml +++ /dev/null @@ -1 +0,0 @@ -key= = 1 diff --git a/stdlib/TOML/test/testfiles/invalid/llbrace.toml b/stdlib/TOML/test/testfiles/invalid/llbrace.toml deleted file mode 100644 index 047978e5bc784..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/llbrace.toml +++ /dev/null @@ -1 +0,0 @@ -[ [table]] diff --git a/stdlib/TOML/test/testfiles/invalid/multi-line-inline-table.toml b/stdlib/TOML/test/testfiles/invalid/multi-line-inline-table.toml deleted file mode 100644 index a195e1b5dcd84..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/multi-line-inline-table.toml +++ /dev/null @@ -1,4 +0,0 @@ -json_like = { - first = "Tom", - last = "Preston-Werner" -} \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/invalid/multi-line-string-no-close.toml b/stdlib/TOML/test/testfiles/invalid/multi-line-string-no-close.toml deleted file mode 100644 index 4ca959715a953..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/multi-line-string-no-close.toml +++ /dev/null @@ -1,2 +0,0 @@ -invalid = """ - this will fail diff --git a/stdlib/TOML/test/testfiles/invalid/rrbrace.toml b/stdlib/TOML/test/testfiles/invalid/rrbrace.toml deleted file mode 100644 index 3a4dee4712685..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/rrbrace.toml +++ /dev/null @@ -1 +0,0 @@ -[[table] ] diff --git a/stdlib/TOML/test/testfiles/invalid/string-bad-byte-escape.toml b/stdlib/TOML/test/testfiles/invalid/string-bad-byte-escape.toml deleted file mode 100644 index 4c7be59f4b16c..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-bad-byte-escape.toml +++ /dev/null @@ -1 +0,0 @@ -naughty = "\xAg" diff --git a/stdlib/TOML/test/testfiles/invalid/string-bad-codepoint.toml b/stdlib/TOML/test/testfiles/invalid/string-bad-codepoint.toml deleted file mode 100644 index aa81356dc94dc..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-bad-codepoint.toml +++ /dev/null @@ -1 +0,0 @@ -invalid-codepoint = "This string contains a non scalar unicode codepoint \uD801" \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/invalid/string-bad-escape.toml b/stdlib/TOML/test/testfiles/invalid/string-bad-escape.toml deleted file mode 100644 index 60acb0ccc5077..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-bad-escape.toml +++ /dev/null @@ -1 +0,0 @@ -invalid-escape = "This string has a bad \a escape character." diff --git a/stdlib/TOML/test/testfiles/invalid/string-bad-slash-escape.toml b/stdlib/TOML/test/testfiles/invalid/string-bad-slash-escape.toml deleted file mode 100644 index 154abadd5c3eb..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-bad-slash-escape.toml +++ /dev/null @@ -1 +0,0 @@ -invalid-escape = "This string has a bad \/ escape character." diff --git a/stdlib/TOML/test/testfiles/invalid/string-bad-uni-esc.toml b/stdlib/TOML/test/testfiles/invalid/string-bad-uni-esc.toml deleted file mode 100644 index 9eae4ab96e5fd..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-bad-uni-esc.toml +++ /dev/null @@ -1 +0,0 @@ -str = "val\ue" diff --git a/stdlib/TOML/test/testfiles/invalid/string-byte-escapes.toml b/stdlib/TOML/test/testfiles/invalid/string-byte-escapes.toml deleted file mode 100644 index e94452a8dfc88..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-byte-escapes.toml +++ /dev/null @@ -1 +0,0 @@ -answer = "\x33" diff --git a/stdlib/TOML/test/testfiles/invalid/string-no-close.toml b/stdlib/TOML/test/testfiles/invalid/string-no-close.toml deleted file mode 100644 index 0c292fcab730d..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/string-no-close.toml +++ /dev/null @@ -1 +0,0 @@ -no-ending-quote = "One time, at band camp diff --git a/stdlib/TOML/test/testfiles/invalid/table-array-implicit.toml b/stdlib/TOML/test/testfiles/invalid/table-array-implicit.toml deleted file mode 100644 index 55094605bb8f6..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-array-implicit.toml +++ /dev/null @@ -1,14 +0,0 @@ -# This test is a bit tricky. It should fail because the first use of -# `[[albums.songs]]` without first declaring `albums` implies that `albums` -# must be a table. The alternative would be quite weird. Namely, it wouldn't -# comply with the TOML spec: "Each double-bracketed sub-table will belong to -# the most *recently* defined table element *above* it." -# -# This is in contrast to the *valid* test, table-array-implicit where -# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared -# later. (Although, `[albums]` could be.) -[[albums.songs]] -name = "Glory Days" - -[[albums]] -name = "Born in the USA" diff --git a/stdlib/TOML/test/testfiles/invalid/table-array-malformed-bracket.toml b/stdlib/TOML/test/testfiles/invalid/table-array-malformed-bracket.toml deleted file mode 100644 index 39c73b05c44e4..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-array-malformed-bracket.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[albums] -name = "Born to Run" diff --git a/stdlib/TOML/test/testfiles/invalid/table-array-malformed-empty.toml b/stdlib/TOML/test/testfiles/invalid/table-array-malformed-empty.toml deleted file mode 100644 index a470ca332f31f..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-array-malformed-empty.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[]] -name = "Born to Run" diff --git a/stdlib/TOML/test/testfiles/invalid/table-empty.toml b/stdlib/TOML/test/testfiles/invalid/table-empty.toml deleted file mode 100644 index fe51488c7066f..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-empty.toml +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/stdlib/TOML/test/testfiles/invalid/table-nested-brackets-close.toml b/stdlib/TOML/test/testfiles/invalid/table-nested-brackets-close.toml deleted file mode 100644 index c8b5a67858006..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-nested-brackets-close.toml +++ /dev/null @@ -1,2 +0,0 @@ -[a]b] -zyx = 42 diff --git a/stdlib/TOML/test/testfiles/invalid/table-nested-brackets-open.toml b/stdlib/TOML/test/testfiles/invalid/table-nested-brackets-open.toml deleted file mode 100644 index 246d7e91fe4fb..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-nested-brackets-open.toml +++ /dev/null @@ -1,2 +0,0 @@ -[a[b] -zyx = 42 diff --git a/stdlib/TOML/test/testfiles/invalid/table-whitespace.toml b/stdlib/TOML/test/testfiles/invalid/table-whitespace.toml deleted file mode 100644 index 79bbcb1e29832..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-whitespace.toml +++ /dev/null @@ -1 +0,0 @@ -[invalid key] \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/invalid/table-with-pound.toml b/stdlib/TOML/test/testfiles/invalid/table-with-pound.toml deleted file mode 100644 index 0d8edb524fe1a..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/table-with-pound.toml +++ /dev/null @@ -1,2 +0,0 @@ -[key#group] -answer = 42 \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/invalid/text-after-array-entries.toml b/stdlib/TOML/test/testfiles/invalid/text-after-array-entries.toml deleted file mode 100644 index 1a7289074ed13..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/text-after-array-entries.toml +++ /dev/null @@ -1,4 +0,0 @@ -array = [ - "Is there life after an array separator?", No - "Entry" -] diff --git a/stdlib/TOML/test/testfiles/invalid/text-after-integer.toml b/stdlib/TOML/test/testfiles/invalid/text-after-integer.toml deleted file mode 100644 index 42de7aff4d856..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/text-after-integer.toml +++ /dev/null @@ -1 +0,0 @@ -answer = 42 the ultimate answer? diff --git a/stdlib/TOML/test/testfiles/invalid/text-after-string.toml b/stdlib/TOML/test/testfiles/invalid/text-after-string.toml deleted file mode 100644 index c92a6f11d85a7..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/text-after-string.toml +++ /dev/null @@ -1 +0,0 @@ -string = "Is there life after strings?" No. diff --git a/stdlib/TOML/test/testfiles/invalid/text-after-table.toml b/stdlib/TOML/test/testfiles/invalid/text-after-table.toml deleted file mode 100644 index 87da9db26dffc..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/text-after-table.toml +++ /dev/null @@ -1 +0,0 @@ -[error] this shouldn't be here diff --git a/stdlib/TOML/test/testfiles/invalid/text-before-array-separator.toml b/stdlib/TOML/test/testfiles/invalid/text-before-array-separator.toml deleted file mode 100644 index 9b06a39241063..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/text-before-array-separator.toml +++ /dev/null @@ -1,4 +0,0 @@ -array = [ - "Is there life before an array separator?" No, - "Entry" -] diff --git a/stdlib/TOML/test/testfiles/invalid/text-in-array.toml b/stdlib/TOML/test/testfiles/invalid/text-in-array.toml deleted file mode 100644 index a6a6c42075e24..0000000000000 --- a/stdlib/TOML/test/testfiles/invalid/text-in-array.toml +++ /dev/null @@ -1,5 +0,0 @@ -array = [ - "Entry 1", - I don't belong, - "Entry 2", -] diff --git a/stdlib/TOML/test/testfiles/valid/array-empty.jl b/stdlib/TOML/test/testfiles/valid/array-empty.jl deleted file mode 100644 index 78a2489844b1a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-empty.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("thevoid" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => Any[Dict{String,Any}("value" => Any[Dict{String,Any}("value" => Any[Dict{String,Any}("value" => Any[],"type" => "array")],"type" => "array")],"type" => "array")],"type" => "array")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/array-empty.json b/stdlib/TOML/test/testfiles/valid/array-empty.json deleted file mode 100644 index 2fbf2567f87bc..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-empty.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "thevoid": { "type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": [ - {"type": "array", "value": []} - ]} - ]} - ]} - ]} -} diff --git a/stdlib/TOML/test/testfiles/valid/array-empty.toml b/stdlib/TOML/test/testfiles/valid/array-empty.toml deleted file mode 100644 index fa58dc63d4880..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-empty.toml +++ /dev/null @@ -1 +0,0 @@ -thevoid = [[[[[]]]]] diff --git a/stdlib/TOML/test/testfiles/valid/array-nospaces.jl b/stdlib/TOML/test/testfiles/valid/array-nospaces.jl deleted file mode 100644 index e5b8c98f00f3e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-nospaces.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("ints" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1","type" => "integer"), Dict{String,Any}("value" => "2","type" => "integer"), Dict{String,Any}("value" => "3","type" => "integer")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/array-nospaces.json b/stdlib/TOML/test/testfiles/valid/array-nospaces.json deleted file mode 100644 index 1833d61c55973..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-nospaces.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/array-nospaces.toml b/stdlib/TOML/test/testfiles/valid/array-nospaces.toml deleted file mode 100644 index 66189367fe9eb..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-nospaces.toml +++ /dev/null @@ -1 +0,0 @@ -ints = [1,2,3] diff --git a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.jl b/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.jl deleted file mode 100644 index 0c11baa1b27bf..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("title" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => " \", ","type" => "string")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.json b/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.json deleted file mode 100644 index a88eb26ba12ea..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.json +++ /dev/null @@ -1 +0,0 @@ -{"title": {"type": "array", "value": [{"type": "string", "value": " \", "}]}} diff --git a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.toml b/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.toml deleted file mode 100644 index 4758ddcade2f4..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma-2.toml +++ /dev/null @@ -1 +0,0 @@ -title = [ " \", ",] diff --git a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.jl b/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.jl deleted file mode 100644 index c291fb0b2b51f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("title" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "Client: \"XXXX\", Job: XXXX","type" => "string"), Dict{String,Any}("value" => "Code: XXXX","type" => "string")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.json b/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.json deleted file mode 100644 index c6f031f595c9f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": { - "type": "array", - "value": [ - {"type": "string", "value": "Client: \"XXXX\", Job: XXXX"}, - {"type": "string", "value": "Code: XXXX"} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.toml b/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.toml deleted file mode 100644 index 6b458e1e8b96b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-quote-comma.toml +++ /dev/null @@ -1,4 +0,0 @@ -title = [ -"Client: \"XXXX\", Job: XXXX", -"Code: XXXX" -] diff --git a/stdlib/TOML/test/testfiles/valid/array-string-with-comma.jl b/stdlib/TOML/test/testfiles/valid/array-string-with-comma.jl deleted file mode 100644 index fac0d3f5098bd..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-with-comma.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("title" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "Client: XXXX, Job: XXXX","type" => "string"), Dict{String,Any}("value" => "Code: XXXX","type" => "string")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/array-string-with-comma.json b/stdlib/TOML/test/testfiles/valid/array-string-with-comma.json deleted file mode 100644 index d879c4c22ce4f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-with-comma.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": { - "type": "array", - "value": [ - {"type": "string", "value": "Client: XXXX, Job: XXXX"}, - {"type": "string", "value": "Code: XXXX"} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/array-string-with-comma.toml b/stdlib/TOML/test/testfiles/valid/array-string-with-comma.toml deleted file mode 100644 index 655c40e27ed44..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-string-with-comma.toml +++ /dev/null @@ -1,4 +0,0 @@ -title = [ -"Client: XXXX, Job: XXXX", -"Code: XXXX" -] diff --git a/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.jl b/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.jl deleted file mode 100644 index 2350b3cd70cba..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("foo" => Any[Dict{String,Any}("bar" => Dict{String,Any}("value" => "\"{{baz}}\"","type" => "string"))]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.json b/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.json deleted file mode 100644 index 4797be94c24b6..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "foo": [ - { - "bar": {"type": "string", "value": "\"{{baz}}\"" } - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.toml b/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.toml deleted file mode 100644 index f0de81e0d646d..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/array-table-array-string-backslash.toml +++ /dev/null @@ -1 +0,0 @@ -foo = [ { bar="\"{{baz}}\""} ] diff --git a/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.jl b/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.jl deleted file mode 100644 index dc143c8f8e685..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("mixed" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1","type" => "integer"), Dict{String,Any}("value" => "2","type" => "integer")],"type" => "array"), Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "a","type" => "string"), Dict{String,Any}("value" => "b","type" => "string")],"type" => "array"), Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1.1","type" => "float"), Dict{String,Any}("value" => "2.1","type" => "float")],"type" => "array")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.json b/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.json deleted file mode 100644 index 478fa5c706b2f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "mixed": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"} - ]}, - {"type": "array", "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"} - ]} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.toml b/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.toml deleted file mode 100644 index a246fcf1deb37..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays-hetergeneous.toml +++ /dev/null @@ -1 +0,0 @@ -mixed = [[1, 2], ["a", "b"], [1.1, 2.1]] diff --git a/stdlib/TOML/test/testfiles/valid/arrays-nested.jl b/stdlib/TOML/test/testfiles/valid/arrays-nested.jl deleted file mode 100644 index 69e925e4e36f8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays-nested.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("nest" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "a","type" => "string")],"type" => "array"), Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "b","type" => "string")],"type" => "array")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/arrays-nested.json b/stdlib/TOML/test/testfiles/valid/arrays-nested.json deleted file mode 100644 index d21920cc3eb41..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays-nested.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "nest": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "string", "value": "a"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "b"} - ]} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/arrays-nested.toml b/stdlib/TOML/test/testfiles/valid/arrays-nested.toml deleted file mode 100644 index ce3302249b72d..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays-nested.toml +++ /dev/null @@ -1 +0,0 @@ -nest = [["a"], ["b"]] diff --git a/stdlib/TOML/test/testfiles/valid/arrays.jl b/stdlib/TOML/test/testfiles/valid/arrays.jl deleted file mode 100644 index e00d308bf577e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("strings" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "a","type" => "string"), Dict{String,Any}("value" => "b","type" => "string"), Dict{String,Any}("value" => "c","type" => "string")],"type" => "array"),"ints" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1","type" => "integer"), Dict{String,Any}("value" => "2","type" => "integer"), Dict{String,Any}("value" => "3","type" => "integer")],"type" => "array"),"dates" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1987-07-05T17:45:00Z","type" => "datetime"), Dict{String,Any}("value" => "1979-05-27T07:32:00Z","type" => "datetime"), Dict{String,Any}("value" => "2006-06-01T11:00:00Z","type" => "datetime")],"type" => "array"),"comments" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1","type" => "integer"), Dict{String,Any}("value" => "2","type" => "integer")],"type" => "array"),"floats" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "1.1","type" => "float"), Dict{String,Any}("value" => "2.1","type" => "float"), Dict{String,Any}("value" => "3.1","type" => "float")],"type" => "array")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/arrays.json b/stdlib/TOML/test/testfiles/valid/arrays.json deleted file mode 100644 index 244511695b67d..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - }, - "floats": { - "type": "array", - "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"}, - {"type": "float", "value": "3.1"} - ] - }, - "strings": { - "type": "array", - "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"}, - {"type": "string", "value": "c"} - ] - }, - "dates": { - "type": "array", - "value": [ - {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, - {"type": "datetime", "value": "2006-06-01T11:00:00Z"} - ] - }, - "comments": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/arrays.toml b/stdlib/TOML/test/testfiles/valid/arrays.toml deleted file mode 100644 index db1c40020ff5d..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/arrays.toml +++ /dev/null @@ -1,12 +0,0 @@ -ints = [1, 2, 3] -floats = [1.1, 2.1, 3.1] -strings = ["a", "b", "c"] -dates = [ - 1987-07-05T17:45:00Z, - 1979-05-27T07:32:00Z, - 2006-06-01T11:00:00Z, -] -comments = [ - 1, - 2, #this is ok -] diff --git a/stdlib/TOML/test/testfiles/valid/bool.jl b/stdlib/TOML/test/testfiles/valid/bool.jl deleted file mode 100644 index aaa55c790e409..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/bool.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("f" => Dict{String,Any}("value" => "false","type" => "bool"),"t" => Dict{String,Any}("value" => "true","type" => "bool")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/bool.json b/stdlib/TOML/test/testfiles/valid/bool.json deleted file mode 100644 index ae368e9492e35..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/bool.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "f": {"type": "bool", "value": "false"}, - "t": {"type": "bool", "value": "true"} -} diff --git a/stdlib/TOML/test/testfiles/valid/bool.toml b/stdlib/TOML/test/testfiles/valid/bool.toml deleted file mode 100644 index a8a829b34de9b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/bool.toml +++ /dev/null @@ -1,2 +0,0 @@ -t = true -f = false diff --git a/stdlib/TOML/test/testfiles/valid/comments-at-eof.jl b/stdlib/TOML/test/testfiles/valid/comments-at-eof.jl deleted file mode 100644 index 230bf448a5740..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-at-eof.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("key" => Dict{String,Any}("value" => "value","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/comments-at-eof.json b/stdlib/TOML/test/testfiles/valid/comments-at-eof.json deleted file mode 100644 index 458c38a3377e8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-at-eof.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": {"type": "string", "value": "value"} -} diff --git a/stdlib/TOML/test/testfiles/valid/comments-at-eof.toml b/stdlib/TOML/test/testfiles/valid/comments-at-eof.toml deleted file mode 100644 index 090b474834610..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-at-eof.toml +++ /dev/null @@ -1,2 +0,0 @@ -# This is a full-line comment -key = "value" # This is a comment at the end of a line diff --git a/stdlib/TOML/test/testfiles/valid/comments-at-eof2.jl b/stdlib/TOML/test/testfiles/valid/comments-at-eof2.jl deleted file mode 100644 index 230bf448a5740..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-at-eof2.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("key" => Dict{String,Any}("value" => "value","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/comments-at-eof2.json b/stdlib/TOML/test/testfiles/valid/comments-at-eof2.json deleted file mode 100644 index 458c38a3377e8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-at-eof2.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": {"type": "string", "value": "value"} -} diff --git a/stdlib/TOML/test/testfiles/valid/comments-at-eof2.toml b/stdlib/TOML/test/testfiles/valid/comments-at-eof2.toml deleted file mode 100644 index 026c93a8b8d78..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-at-eof2.toml +++ /dev/null @@ -1,2 +0,0 @@ -# This is a full-line comment -key = "value" # This is a comment at the end of a line \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/comments-everywhere.jl b/stdlib/TOML/test/testfiles/valid/comments-everywhere.jl deleted file mode 100644 index 3a0cc4b062fac..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-everywhere.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("group" => Dict{String,Any}("more" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "42","type" => "integer"), Dict{String,Any}("value" => "42","type" => "integer")],"type" => "array"),"answer" => Dict{String,Any}("value" => "42","type" => "integer"))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/comments-everywhere.json b/stdlib/TOML/test/testfiles/valid/comments-everywhere.json deleted file mode 100644 index e69a2e9582395..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-everywhere.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "group": { - "answer": {"type": "integer", "value": "42"}, - "more": { - "type": "array", - "value": [ - {"type": "integer", "value": "42"}, - {"type": "integer", "value": "42"} - ] - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/comments-everywhere.toml b/stdlib/TOML/test/testfiles/valid/comments-everywhere.toml deleted file mode 100644 index 3dca74cade516..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/comments-everywhere.toml +++ /dev/null @@ -1,24 +0,0 @@ -# Top comment. - # Top comment. -# Top comment. - -# [no-extraneous-groups-please] - -[group] # Comment -answer = 42 # Comment -# no-extraneous-keys-please = 999 -# Inbetween comment. -more = [ # Comment - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. - 42, 42, # Comments within arrays are fun. - # What about multiple # comments? - # Can you handle it? - # - # Evil. -# Evil. -# ] Did I fool you? -] # Hopefully not. diff --git a/stdlib/TOML/test/testfiles/valid/datetime-timezone.jl b/stdlib/TOML/test/testfiles/valid/datetime-timezone.jl deleted file mode 100644 index 7741e94a33b34..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/datetime-timezone.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("bestdayever" => Dict{String,Any}("value" => "2017-06-06T12:34:56-05:00","type" => "datetime")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/datetime-timezone.json b/stdlib/TOML/test/testfiles/valid/datetime-timezone.json deleted file mode 100644 index 0b70f141c06c9..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/datetime-timezone.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "bestdayever": {"type": "datetime", "value": "2017-06-06T12:34:56-05:00"} -} diff --git a/stdlib/TOML/test/testfiles/valid/datetime-timezone.toml b/stdlib/TOML/test/testfiles/valid/datetime-timezone.toml deleted file mode 100644 index e59cb842c40bf..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/datetime-timezone.toml +++ /dev/null @@ -1 +0,0 @@ -bestdayever = 2017-06-06T12:34:56-05:00 diff --git a/stdlib/TOML/test/testfiles/valid/datetime.jl b/stdlib/TOML/test/testfiles/valid/datetime.jl deleted file mode 100644 index a64b34c1e2247..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/datetime.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("milliseconds" => Dict{String,Any}("value" => "1977-12-21T03:32:00.555+00:00","type" => "datetime"),"bestdayever" => Dict{String,Any}("value" => "1987-07-05T17:45:00Z","type" => "datetime"),"numoffset" => Dict{String,Any}("value" => "1977-06-28T12:32:00Z","type" => "datetime")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/datetime.json b/stdlib/TOML/test/testfiles/valid/datetime.json deleted file mode 100644 index 4cdc0006580cc..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/datetime.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - "numoffset": {"type": "datetime", "value": "1977-06-28T12:32:00Z"}, - "milliseconds": {"type": "datetime", "value": "1977-12-21T03:32:00.555+00:00"} -} diff --git a/stdlib/TOML/test/testfiles/valid/datetime.toml b/stdlib/TOML/test/testfiles/valid/datetime.toml deleted file mode 100644 index ee787b7ed6762..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/datetime.toml +++ /dev/null @@ -1,3 +0,0 @@ -bestdayever = 1987-07-05T17:45:00Z -numoffset = 1977-06-28T07:32:00-05:00 -milliseconds = 1977-12-21T10:32:00.555+07:00 diff --git a/stdlib/TOML/test/testfiles/valid/double-quote-escape.jl b/stdlib/TOML/test/testfiles/valid/double-quote-escape.jl deleted file mode 100644 index fccbb9e75005c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/double-quote-escape.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("test" => Dict{String,Any}("value" => "\"one\"","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/double-quote-escape.json b/stdlib/TOML/test/testfiles/valid/double-quote-escape.json deleted file mode 100644 index 0c4ac37e0a95e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/double-quote-escape.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "test": { - "type": "string", - "value": "\"one\"" - } -} diff --git a/stdlib/TOML/test/testfiles/valid/double-quote-escape.toml b/stdlib/TOML/test/testfiles/valid/double-quote-escape.toml deleted file mode 100644 index 78e7e72927950..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/double-quote-escape.toml +++ /dev/null @@ -1 +0,0 @@ -test = "\"one\"" diff --git a/stdlib/TOML/test/testfiles/valid/empty.jl b/stdlib/TOML/test/testfiles/valid/empty.jl deleted file mode 100644 index edc491b03230c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/empty.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}() \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/empty.json b/stdlib/TOML/test/testfiles/valid/empty.json deleted file mode 100644 index 0967ef424bce6..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/empty.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/stdlib/TOML/test/testfiles/valid/empty.toml b/stdlib/TOML/test/testfiles/valid/empty.toml deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/stdlib/TOML/test/testfiles/valid/escaped-escape.jl b/stdlib/TOML/test/testfiles/valid/escaped-escape.jl deleted file mode 100644 index 97c80799c4290..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/escaped-escape.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer" => Dict{String,Any}("value" => "\\x64","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/escaped-escape.json b/stdlib/TOML/test/testfiles/valid/escaped-escape.json deleted file mode 100644 index 9db7f8ab5f251..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/escaped-escape.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "answer": {"type": "string", "value": "\\x64"} -} diff --git a/stdlib/TOML/test/testfiles/valid/escaped-escape.toml b/stdlib/TOML/test/testfiles/valid/escaped-escape.toml deleted file mode 100644 index d5758761457f1..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/escaped-escape.toml +++ /dev/null @@ -1 +0,0 @@ -answer = "\\x64" diff --git a/stdlib/TOML/test/testfiles/valid/example.jl b/stdlib/TOML/test/testfiles/valid/example.jl deleted file mode 100644 index 8307133217263..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/example.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("best-day-ever" => Dict{String,Any}("value" => "1987-07-05T17:45:00Z","type" => "datetime"),"numtheory" => Dict{String,Any}("perfection" => Dict{String,Any}("value" => Any[Dict{String,Any}("value" => "6","type" => "integer"), Dict{String,Any}("value" => "28","type" => "integer"), Dict{String,Any}("value" => "496","type" => "integer")],"type" => "array"),"boring" => Dict{String,Any}("value" => "false","type" => "bool"))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/example.json b/stdlib/TOML/test/testfiles/valid/example.json deleted file mode 100644 index 48aa90784a4eb..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/example.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - "numtheory": { - "boring": {"type": "bool", "value": "false"}, - "perfection": { - "type": "array", - "value": [ - {"type": "integer", "value": "6"}, - {"type": "integer", "value": "28"}, - {"type": "integer", "value": "496"} - ] - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/example.toml b/stdlib/TOML/test/testfiles/valid/example.toml deleted file mode 100644 index 8cb02e01b0348..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/example.toml +++ /dev/null @@ -1,5 +0,0 @@ -best-day-ever = 1987-07-05T17:45:00Z - -[numtheory] -boring = false -perfection = [6, 28, 496] diff --git a/stdlib/TOML/test/testfiles/valid/exponent-part-float.jl b/stdlib/TOML/test/testfiles/valid/exponent-part-float.jl deleted file mode 100644 index 5446e515ed2cb..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/exponent-part-float.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("million" => Dict{String,Any}("value" => "1000000","type" => "float"),"minustenth" => Dict{String,Any}("value" => "-0.1","type" => "float"),"beast" => Dict{String,Any}("value" => "666","type" => "float")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/exponent-part-float.json b/stdlib/TOML/test/testfiles/valid/exponent-part-float.json deleted file mode 100644 index 4dbfbeec030d0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/exponent-part-float.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "million": {"type": "float", "value": "1000000"}, - "minustenth": {"type": "float", "value": "-0.1"}, - "beast": {"type": "float", "value": "666"} -} diff --git a/stdlib/TOML/test/testfiles/valid/exponent-part-float.toml b/stdlib/TOML/test/testfiles/valid/exponent-part-float.toml deleted file mode 100644 index 41bd282d824d7..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/exponent-part-float.toml +++ /dev/null @@ -1,3 +0,0 @@ -million = 1e6 -minustenth = -1E-1 -beast = 6.66E2 diff --git a/stdlib/TOML/test/testfiles/valid/float-exponent.jl b/stdlib/TOML/test/testfiles/valid/float-exponent.jl deleted file mode 100644 index b35991f2467fa..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float-exponent.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("neg" => Dict{String,Any}("value" => "0.03","type" => "float"),"zero" => Dict{String,Any}("value" => "3.0","type" => "float"),"pointupper" => Dict{String,Any}("value" => "310.0","type" => "float"),"lower" => Dict{String,Any}("value" => "300.0","type" => "float"),"upper" => Dict{String,Any}("value" => "300.0","type" => "float"),"pos" => Dict{String,Any}("value" => "300.0","type" => "float"),"pointlower" => Dict{String,Any}("value" => "310.0","type" => "float")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/float-exponent.json b/stdlib/TOML/test/testfiles/valid/float-exponent.json deleted file mode 100644 index b0d40bd0be156..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float-exponent.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "lower": {"type": "float", "value": "300.0"}, - "upper": {"type": "float", "value": "300.0"}, - "neg": {"type": "float", "value": "0.03"}, - "pos": {"type": "float", "value": "300.0"}, - "zero": {"type": "float", "value": "3.0"}, - "pointlower": {"type": "float", "value": "310.0"}, - "pointupper": {"type": "float", "value": "310.0"} -} diff --git a/stdlib/TOML/test/testfiles/valid/float-exponent.toml b/stdlib/TOML/test/testfiles/valid/float-exponent.toml deleted file mode 100644 index d0db16fd557c7..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float-exponent.toml +++ /dev/null @@ -1,7 +0,0 @@ -lower = 3e2 -upper = 3E2 -neg = 3e-2 -pos = 3E+2 -zero = 3e0 -pointlower = 3.1e2 -pointupper = 3.1E2 diff --git a/stdlib/TOML/test/testfiles/valid/float-underscore.jl b/stdlib/TOML/test/testfiles/valid/float-underscore.jl deleted file mode 100644 index c48c5ed7aadf6..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float-underscore.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("after" => Dict{String,Any}("value" => "3141.5927","type" => "float"),"exponent" => Dict{String,Any}("value" => "3e14","type" => "float"),"before" => Dict{String,Any}("value" => "3141.5927","type" => "float")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/float-underscore.json b/stdlib/TOML/test/testfiles/valid/float-underscore.json deleted file mode 100644 index f86cdd790f07c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float-underscore.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "before": {"type": "float", "value": "3141.5927"}, - "after": {"type": "float", "value": "3141.5927"}, - "exponent": {"type": "float", "value": "3e14"} -} diff --git a/stdlib/TOML/test/testfiles/valid/float-underscore.toml b/stdlib/TOML/test/testfiles/valid/float-underscore.toml deleted file mode 100644 index 343353a89e063..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float-underscore.toml +++ /dev/null @@ -1,3 +0,0 @@ -before = 3_141.5927 -after = 3141.592_7 -exponent = 3e1_4 diff --git a/stdlib/TOML/test/testfiles/valid/float.jl b/stdlib/TOML/test/testfiles/valid/float.jl deleted file mode 100644 index 45a52e3af1675..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("negpi" => Dict{String,Any}("value" => "-3.14","type" => "float"),"pospi" => Dict{String,Any}("value" => "3.14","type" => "float"),"pi" => Dict{String,Any}("value" => "3.14","type" => "float"),"zero-intpart" => Dict{String,Any}("value" => "0.123","type" => "float")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/float.json b/stdlib/TOML/test/testfiles/valid/float.json deleted file mode 100644 index 3f69b172c98fc..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pi": {"type": "float", "value": "3.14"}, - "pospi": {"type": "float", "value": "3.14"}, - "negpi": {"type": "float", "value": "-3.14"}, - "zero-intpart": {"type": "float", "value": "0.123"} -} diff --git a/stdlib/TOML/test/testfiles/valid/float.toml b/stdlib/TOML/test/testfiles/valid/float.toml deleted file mode 100644 index 5f023229486b9..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/float.toml +++ /dev/null @@ -1,4 +0,0 @@ -pi = 3.14 -pospi = +3.14 -negpi = -3.14 -zero-intpart = 0.123 diff --git a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.jl b/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.jl deleted file mode 100644 index f1ebc4aa65af0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}("b" => Dict{String,Any}("c" => Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer"))),"better" => Dict{String,Any}("value" => "43","type" => "integer"))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.json b/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.json deleted file mode 100644 index 374bd09343ef1..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.toml b/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.toml deleted file mode 100644 index c0e8865b392c2..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-after.toml +++ /dev/null @@ -1,5 +0,0 @@ -[a.b.c] -answer = 42 - -[a] -better = 43 diff --git a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.jl b/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.jl deleted file mode 100644 index f1ebc4aa65af0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}("b" => Dict{String,Any}("c" => Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer"))),"better" => Dict{String,Any}("value" => "43","type" => "integer"))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.json b/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.json deleted file mode 100644 index 374bd09343ef1..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "a": { - "better": {"type": "integer", "value": "43"}, - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.toml b/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.toml deleted file mode 100644 index eee68ff5143aa..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-and-explicit-before.toml +++ /dev/null @@ -1,5 +0,0 @@ -[a] -better = 43 - -[a.b.c] -answer = 42 diff --git a/stdlib/TOML/test/testfiles/valid/implicit-groups.jl b/stdlib/TOML/test/testfiles/valid/implicit-groups.jl deleted file mode 100644 index 2fa2c2156bb67..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-groups.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}("b" => Dict{String,Any}("c" => Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer"))))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/implicit-groups.json b/stdlib/TOML/test/testfiles/valid/implicit-groups.json deleted file mode 100644 index fbae7fc71beff..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-groups.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "a": { - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/implicit-groups.toml b/stdlib/TOML/test/testfiles/valid/implicit-groups.toml deleted file mode 100644 index b6333e49d577e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/implicit-groups.toml +++ /dev/null @@ -1,2 +0,0 @@ -[a.b.c] -answer = 42 diff --git a/stdlib/TOML/test/testfiles/valid/inline-table-array.jl b/stdlib/TOML/test/testfiles/valid/inline-table-array.jl deleted file mode 100644 index 7e9f0ede91368..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/inline-table-array.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("people" => Any[Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Bruce","type" => "string"),"last_name" => Dict{String,Any}("value" => "Springsteen","type" => "string")), Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Eric","type" => "string"),"last_name" => Dict{String,Any}("value" => "Clapton","type" => "string")), Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Bob","type" => "string"),"last_name" => Dict{String,Any}("value" => "Seger","type" => "string"))]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/inline-table-array.json b/stdlib/TOML/test/testfiles/valid/inline-table-array.json deleted file mode 100644 index 84df2dabb0d6b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/inline-table-array.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - }, - { - "first_name": {"type": "string", "value": "Eric"}, - "last_name": {"type": "string", "value": "Clapton"} - }, - { - "first_name": {"type": "string", "value": "Bob"}, - "last_name": {"type": "string", "value": "Seger"} - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/inline-table-array.toml b/stdlib/TOML/test/testfiles/valid/inline-table-array.toml deleted file mode 100644 index 3fa60d6695574..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/inline-table-array.toml +++ /dev/null @@ -1,3 +0,0 @@ -people = [{first_name = "Bruce", last_name = "Springsteen"}, - {first_name = "Eric", last_name = "Clapton"}, - {first_name = "Bob", last_name = "Seger"}] diff --git a/stdlib/TOML/test/testfiles/valid/inline-table.jl b/stdlib/TOML/test/testfiles/valid/inline-table.jl deleted file mode 100644 index 39f9f52be24f3..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/inline-table.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("point" => Dict{String,Any}("x" => Dict{String,Any}("value" => "1","type" => "integer"),"y" => Dict{String,Any}("value" => "2","type" => "integer")),"name" => Dict{String,Any}("first" => Dict{String,Any}("value" => "Tom","type" => "string"),"last" => Dict{String,Any}("value" => "Preston-Werner","type" => "string")),"str-key" => Dict{String,Any}("a" => Dict{String,Any}("value" => "1","type" => "integer")),"simple" => Dict{String,Any}("a" => Dict{String,Any}("value" => "1","type" => "integer")),"table-array" => Any[Dict{String,Any}("a" => Dict{String,Any}("value" => "1","type" => "integer")), Dict{String,Any}("b" => Dict{String,Any}("value" => "2","type" => "integer"))]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/inline-table.json b/stdlib/TOML/test/testfiles/valid/inline-table.json deleted file mode 100644 index 71cc119c5bce3..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/inline-table.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": { - "first": {"type": "string", "value": "Tom"}, - "last": {"type": "string", "value": "Preston-Werner"} - }, - "point": { - "x": {"type": "integer", "value": "1"}, - "y": {"type": "integer", "value": "2"} - }, - "simple": { "a": {"type": "integer", "value": "1"} }, - "str-key": { "a": {"type": "integer", "value": "1"} }, - "table-array": [ - { "a": {"type": "integer", "value": "1"} }, - { "b": {"type": "integer", "value": "2"} } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/inline-table.toml b/stdlib/TOML/test/testfiles/valid/inline-table.toml deleted file mode 100644 index 257047eebc019..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/inline-table.toml +++ /dev/null @@ -1,5 +0,0 @@ -name = { first = "Tom", last = "Preston-Werner" } -point = { x = 1, y = 2 } -simple = { a = 1 } -str-key = { "a" = 1 } -table-array = [{ "a" = 1 }, { "b" = 2 }] diff --git a/stdlib/TOML/test/testfiles/valid/integer-underscore.jl b/stdlib/TOML/test/testfiles/valid/integer-underscore.jl deleted file mode 100644 index 47a91e29343b4..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/integer-underscore.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("kilo" => Dict{String,Any}("value" => "1000","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/integer-underscore.json b/stdlib/TOML/test/testfiles/valid/integer-underscore.json deleted file mode 100644 index bb6c3e7ba7d69..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/integer-underscore.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "kilo": {"type": "integer", "value": "1000"} -} diff --git a/stdlib/TOML/test/testfiles/valid/integer-underscore.toml b/stdlib/TOML/test/testfiles/valid/integer-underscore.toml deleted file mode 100644 index 45eb4f71ab583..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/integer-underscore.toml +++ /dev/null @@ -1 +0,0 @@ -kilo = 1_000 diff --git a/stdlib/TOML/test/testfiles/valid/integer.jl b/stdlib/TOML/test/testfiles/valid/integer.jl deleted file mode 100644 index ad8a94c4ccd9a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/integer.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("zero" => Dict{String,Any}("value" => "0","type" => "integer"),"posanswer" => Dict{String,Any}("value" => "42","type" => "integer"),"answer" => Dict{String,Any}("value" => "42","type" => "integer"),"neganswer" => Dict{String,Any}("value" => "-42","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/integer.json b/stdlib/TOML/test/testfiles/valid/integer.json deleted file mode 100644 index 543738ba87999..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/integer.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "answer": {"type": "integer", "value": "42"}, - "neganswer": {"type": "integer", "value": "-42"}, - "posanswer": {"type": "integer", "value": "42"}, - "zero": {"type": "integer", "value": "0"} -} diff --git a/stdlib/TOML/test/testfiles/valid/integer.toml b/stdlib/TOML/test/testfiles/valid/integer.toml deleted file mode 100644 index b62de30aee0e3..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/integer.toml +++ /dev/null @@ -1,4 +0,0 @@ -answer = 42 -posanswer = +42 -neganswer = -42 -zero = 0 diff --git a/stdlib/TOML/test/testfiles/valid/key-equals-nospace.jl b/stdlib/TOML/test/testfiles/valid/key-equals-nospace.jl deleted file mode 100644 index 8b553e2655481..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-equals-nospace.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/key-equals-nospace.json b/stdlib/TOML/test/testfiles/valid/key-equals-nospace.json deleted file mode 100644 index 1f8709ab9f46f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-equals-nospace.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "answer": {"type": "integer", "value": "42"} -} diff --git a/stdlib/TOML/test/testfiles/valid/key-equals-nospace.toml b/stdlib/TOML/test/testfiles/valid/key-equals-nospace.toml deleted file mode 100644 index 560901c5a43f2..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-equals-nospace.toml +++ /dev/null @@ -1 +0,0 @@ -answer=42 diff --git a/stdlib/TOML/test/testfiles/valid/key-numeric.jl b/stdlib/TOML/test/testfiles/valid/key-numeric.jl deleted file mode 100644 index 10cd8e0e80782..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-numeric.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("1" => Dict{String,Any}("value" => "1","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/key-numeric.json b/stdlib/TOML/test/testfiles/valid/key-numeric.json deleted file mode 100644 index 862f8cbba9a29..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-numeric.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "1": {"type": "integer", "value": "1"} -} diff --git a/stdlib/TOML/test/testfiles/valid/key-numeric.toml b/stdlib/TOML/test/testfiles/valid/key-numeric.toml deleted file mode 100644 index 532356f49b43e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-numeric.toml +++ /dev/null @@ -1 +0,0 @@ -1 = 1 diff --git a/stdlib/TOML/test/testfiles/valid/key-space.jl b/stdlib/TOML/test/testfiles/valid/key-space.jl deleted file mode 100644 index 97439fcfccdd0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-space.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a b" => Dict{String,Any}("value" => "1","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/key-space.json b/stdlib/TOML/test/testfiles/valid/key-space.json deleted file mode 100644 index 9d1f76911d523..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-space.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "a b": {"type": "integer", "value": "1"} -} diff --git a/stdlib/TOML/test/testfiles/valid/key-space.toml b/stdlib/TOML/test/testfiles/valid/key-space.toml deleted file mode 100644 index f4f36c4f6df2c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-space.toml +++ /dev/null @@ -1 +0,0 @@ -"a b" = 1 diff --git a/stdlib/TOML/test/testfiles/valid/key-special-chars.jl b/stdlib/TOML/test/testfiles/valid/key-special-chars.jl deleted file mode 100644 index 90d934f45741e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-special-chars.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("~!@\$^&*()_+-`1234567890[]|/?><.,;:'" => Dict{String,Any}("value" => "1","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/key-special-chars.json b/stdlib/TOML/test/testfiles/valid/key-special-chars.json deleted file mode 100644 index 3585b2cfb464e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-special-chars.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "~!@$^&*()_+-`1234567890[]|/?><.,;:'": { - "type": "integer", "value": "1" - } -} diff --git a/stdlib/TOML/test/testfiles/valid/key-special-chars.toml b/stdlib/TOML/test/testfiles/valid/key-special-chars.toml deleted file mode 100644 index cc572befd06e5..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/key-special-chars.toml +++ /dev/null @@ -1 +0,0 @@ -"~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1 diff --git a/stdlib/TOML/test/testfiles/valid/keys-with-dots.jl b/stdlib/TOML/test/testfiles/valid/keys-with-dots.jl deleted file mode 100644 index 52607b35b91ea..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/keys-with-dots.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("with.dot" => Dict{String,Any}("value" => "2","type" => "integer"),"plain_table" => Dict{String,Any}("with.dot" => Dict{String,Any}("value" => "4","type" => "integer"),"plain" => Dict{String,Any}("value" => "3","type" => "integer")),"table" => Dict{String,Any}("withdot" => Dict{String,Any}("key.with.dots" => Dict{String,Any}("value" => "6","type" => "integer"),"plain" => Dict{String,Any}("value" => "5","type" => "integer"))),"plain" => Dict{String,Any}("value" => "1","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/keys-with-dots.json b/stdlib/TOML/test/testfiles/valid/keys-with-dots.json deleted file mode 100644 index d2ee0021f6302..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/keys-with-dots.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "plain": {"type": "integer", "value": "1"}, - "with.dot": {"type": "integer", "value": "2"}, - "plain_table": { - "plain": {"type": "integer", "value": "3"}, - "with.dot": {"type": "integer", "value": "4"} - }, - "table": { - "withdot": { - "plain": {"type": "integer", "value": "5"}, - "key.with.dots": {"type": "integer", "value": "6"} - } - } -} \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/keys-with-dots.toml b/stdlib/TOML/test/testfiles/valid/keys-with-dots.toml deleted file mode 100644 index 24905929b22f3..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/keys-with-dots.toml +++ /dev/null @@ -1,10 +0,0 @@ -plain = 1 -"with.dot" = 2 - -[plain_table] -plain = 3 -"with.dot" = 4 - -[table.withdot] -plain = 5 -"key.with.dots" = 6 \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/long-float.jl b/stdlib/TOML/test/testfiles/valid/long-float.jl deleted file mode 100644 index b960a20d97605..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/long-float.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("longpi" => Dict{String,Any}("value" => "3.141592653589793","type" => "float"),"neglongpi" => Dict{String,Any}("value" => "-3.141592653589793","type" => "float")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/long-float.json b/stdlib/TOML/test/testfiles/valid/long-float.json deleted file mode 100644 index 8ceed47971ef0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/long-float.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "longpi": {"type": "float", "value": "3.141592653589793"}, - "neglongpi": {"type": "float", "value": "-3.141592653589793"} -} diff --git a/stdlib/TOML/test/testfiles/valid/long-float.toml b/stdlib/TOML/test/testfiles/valid/long-float.toml deleted file mode 100644 index 9558ae47c023f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/long-float.toml +++ /dev/null @@ -1,2 +0,0 @@ -longpi = 3.141592653589793 -neglongpi = -3.141592653589793 diff --git a/stdlib/TOML/test/testfiles/valid/long-integer.jl b/stdlib/TOML/test/testfiles/valid/long-integer.jl deleted file mode 100644 index 051da8e7c940b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/long-integer.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer" => Dict{String,Any}("value" => "9223372036854775807","type" => "integer"),"neganswer" => Dict{String,Any}("value" => "-9223372036854775808","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/long-integer.json b/stdlib/TOML/test/testfiles/valid/long-integer.json deleted file mode 100644 index 16c331ed3983a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/long-integer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "answer": {"type": "integer", "value": "9223372036854775807"}, - "neganswer": {"type": "integer", "value": "-9223372036854775808"} -} diff --git a/stdlib/TOML/test/testfiles/valid/long-integer.toml b/stdlib/TOML/test/testfiles/valid/long-integer.toml deleted file mode 100644 index 424a13ac2af1b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/long-integer.toml +++ /dev/null @@ -1,2 +0,0 @@ -answer = 9223372036854775807 -neganswer = -9223372036854775808 diff --git a/stdlib/TOML/test/testfiles/valid/multiline-string.jl b/stdlib/TOML/test/testfiles/valid/multiline-string.jl deleted file mode 100644 index ba1eb06c41868..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/multiline-string.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("equivalent_two" => Dict{String,Any}("value" => "The quick brown fox jumps over the lazy dog.","type" => "string"),"multiline_empty_four" => Dict{String,Any}("value" => "","type" => "string"),"multiline_empty_one" => Dict{String,Any}("value" => "","type" => "string"),"equivalent_three" => Dict{String,Any}("value" => "The quick brown fox jumps over the lazy dog.","type" => "string"),"equivalent_one" => Dict{String,Any}("value" => "The quick brown fox jumps over the lazy dog.","type" => "string"),"multiline_empty_two" => Dict{String,Any}("value" => "","type" => "string"),"multiline_empty_three" => Dict{String,Any}("value" => "","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/multiline-string.json b/stdlib/TOML/test/testfiles/valid/multiline-string.json deleted file mode 100644 index 075bf505464b5..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/multiline-string.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "multiline_empty_one": { - "type": "string", - "value": "" - }, - "multiline_empty_two": { - "type": "string", - "value": "" - }, - "multiline_empty_three": { - "type": "string", - "value": "" - }, - "multiline_empty_four": { - "type": "string", - "value": "" - }, - "equivalent_one": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_two": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - }, - "equivalent_three": { - "type": "string", - "value": "The quick brown fox jumps over the lazy dog." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/multiline-string.toml b/stdlib/TOML/test/testfiles/valid/multiline-string.toml deleted file mode 100644 index 15b11434ff009..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/multiline-string.toml +++ /dev/null @@ -1,23 +0,0 @@ -multiline_empty_one = """""" -multiline_empty_two = """ -""" -multiline_empty_three = """\ - """ -multiline_empty_four = """\ - \ - \ - """ - -equivalent_one = "The quick brown fox jumps over the lazy dog." -equivalent_two = """ -The quick brown \ - - - fox jumps over \ - the lazy dog.""" - -equivalent_three = """\ - The quick brown \ - fox jumps over \ - the lazy dog.\ - """ diff --git a/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.jl b/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.jl deleted file mode 100644 index 32520dc02ae1b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Any[Dict{String,Any}("b" => Dict{String,Any}())]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.json b/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.json deleted file mode 100644 index 89cd83e22eefc..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "a": [ - { - "b": {} - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.toml b/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.toml deleted file mode 100644 index e1e24f6c38f8a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/nested-inline-table-array.toml +++ /dev/null @@ -1 +0,0 @@ -a = [ { b = {} } ] diff --git a/stdlib/TOML/test/testfiles/valid/newline-crlf.jl b/stdlib/TOML/test/testfiles/valid/newline-crlf.jl deleted file mode 100644 index 489a35df0ccf0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/newline-crlf.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("newline" => Dict{String,Any}("value" => "crlf","type" => "string"),"os" => Dict{String,Any}("value" => "DOS","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/newline-crlf.json b/stdlib/TOML/test/testfiles/valid/newline-crlf.json deleted file mode 100644 index d32f230b2b826..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/newline-crlf.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "os": {"type": "string", "value": "DOS"}, - "newline": {"type": "string", "value": "crlf"} -} diff --git a/stdlib/TOML/test/testfiles/valid/newline-crlf.toml b/stdlib/TOML/test/testfiles/valid/newline-crlf.toml deleted file mode 100644 index 9b13df0412235..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/newline-crlf.toml +++ /dev/null @@ -1,2 +0,0 @@ -os = "DOS" -newline = "crlf" diff --git a/stdlib/TOML/test/testfiles/valid/newline-lf.jl b/stdlib/TOML/test/testfiles/valid/newline-lf.jl deleted file mode 100644 index f422b1a0014a5..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/newline-lf.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("newline" => Dict{String,Any}("value" => "lf","type" => "string"),"os" => Dict{String,Any}("value" => "unix","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/newline-lf.json b/stdlib/TOML/test/testfiles/valid/newline-lf.json deleted file mode 100644 index 8114848b53193..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/newline-lf.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "os": {"type": "string", "value": "unix"}, - "newline": {"type": "string", "value": "lf"} -} diff --git a/stdlib/TOML/test/testfiles/valid/newline-lf.toml b/stdlib/TOML/test/testfiles/valid/newline-lf.toml deleted file mode 100644 index 0f3377cd990e3..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/newline-lf.toml +++ /dev/null @@ -1,2 +0,0 @@ -os = "unix" -newline = "lf" diff --git a/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.jl b/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.jl deleted file mode 100644 index 7b5dcfb55251a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("multiline" => Dict{String,Any}("value" => "This string\r\nhas ' a quote character\r\nand more than\r\none newline\r\nin it.","type" => "string"),"firstnl" => Dict{String,Any}("value" => "This string has a ' quote character.","type" => "string"),"oneline" => Dict{String,Any}("value" => "This string has a ' quote character.","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.json b/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.json deleted file mode 100644 index 90e27df8ac804..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "oneline": { - "type": "string", - "value": "This string has a ' quote character." - }, - "firstnl": { - "type": "string", - "value": "This string has a ' quote character." - }, - "multiline": { - "type": "string", - "value": "This string\r\nhas ' a quote character\r\nand more than\r\none newline\r\nin it." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.toml b/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.toml deleted file mode 100644 index 8094c03e31a40..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-multiline-string-win.toml +++ /dev/null @@ -1,9 +0,0 @@ -oneline = '''This string has a ' quote character.''' -firstnl = ''' -This string has a ' quote character.''' -multiline = ''' -This string -has ' a quote character -and more than -one newline -in it.''' diff --git a/stdlib/TOML/test/testfiles/valid/raw-multiline-string.jl b/stdlib/TOML/test/testfiles/valid/raw-multiline-string.jl deleted file mode 100644 index 308070b558fa4..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-multiline-string.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("multiline" => Dict{String,Any}("value" => "This string\nhas ' a quote character\nand more than\none newline\nin it.","type" => "string"),"firstnl" => Dict{String,Any}("value" => "This string has a ' quote character.","type" => "string"),"oneline" => Dict{String,Any}("value" => "This string has a ' quote character.","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/raw-multiline-string.json b/stdlib/TOML/test/testfiles/valid/raw-multiline-string.json deleted file mode 100644 index b43cce5a2d173..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-multiline-string.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "oneline": { - "type": "string", - "value": "This string has a ' quote character." - }, - "firstnl": { - "type": "string", - "value": "This string has a ' quote character." - }, - "multiline": { - "type": "string", - "value": "This string\nhas ' a quote character\nand more than\none newline\nin it." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/raw-multiline-string.toml b/stdlib/TOML/test/testfiles/valid/raw-multiline-string.toml deleted file mode 100644 index 8094c03e31a40..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-multiline-string.toml +++ /dev/null @@ -1,9 +0,0 @@ -oneline = '''This string has a ' quote character.''' -firstnl = ''' -This string has a ' quote character.''' -multiline = ''' -This string -has ' a quote character -and more than -one newline -in it.''' diff --git a/stdlib/TOML/test/testfiles/valid/raw-string.jl b/stdlib/TOML/test/testfiles/valid/raw-string.jl deleted file mode 100644 index c7e01501bb8f1..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-string.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("slash" => Dict{String,Any}("value" => "This string has a \\/ slash character.","type" => "string"),"formfeed" => Dict{String,Any}("value" => "This string has a \\f form feed character.","type" => "string"),"backslash" => Dict{String,Any}("value" => "This string has a \\\\ backslash character.","type" => "string"),"newline" => Dict{String,Any}("value" => "This string has a \\n new line character.","type" => "string"),"carriage" => Dict{String,Any}("value" => "This string has a \\r carriage return character.","type" => "string"),"backspace" => Dict{String,Any}("value" => "This string has a \\b backspace character.","type" => "string"),"tab" => Dict{String,Any}("value" => "This string has a \\t tab character.","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/raw-string.json b/stdlib/TOML/test/testfiles/valid/raw-string.json deleted file mode 100644 index 693ab9b54a493..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-string.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "backspace": { - "type": "string", - "value": "This string has a \\b backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \\t tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \\n new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \\f form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \\r carriage return character." - }, - "slash": { - "type": "string", - "value": "This string has a \\/ slash character." - }, - "backslash": { - "type": "string", - "value": "This string has a \\\\ backslash character." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/raw-string.toml b/stdlib/TOML/test/testfiles/valid/raw-string.toml deleted file mode 100644 index 92acd2557c4c2..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/raw-string.toml +++ /dev/null @@ -1,7 +0,0 @@ -backspace = 'This string has a \b backspace character.' -tab = 'This string has a \t tab character.' -newline = 'This string has a \n new line character.' -formfeed = 'This string has a \f form feed character.' -carriage = 'This string has a \r carriage return character.' -slash = 'This string has a \/ slash character.' -backslash = 'This string has a \\ backslash character.' diff --git a/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.jl b/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.jl deleted file mode 100644 index 38c34b74ac937..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("black" => Dict{String,Any}("allow_prereleases" => Dict{String,Any}("value" => "true","type" => "bool"),"python" => Dict{String,Any}("value" => ">3.6","type" => "string"),"version" => Dict{String,Any}("value" => ">=18.9b0","type" => "string"))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.json b/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.json deleted file mode 100644 index a6c11ea86eea8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "black":{ - "allow_prereleases":{ - "type":"bool", - "value":"true" - }, - "python":{ - "type":"string", - "value":">3.6" - }, - "version":{ - "type":"string", - "value":">=18.9b0" - } - } - } \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.toml b/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.toml deleted file mode 100644 index 94e5651d582e2..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/right-curly-brace-after-boolean.toml +++ /dev/null @@ -1 +0,0 @@ -black = { python=">3.6", version=">=18.9b0", allow_prereleases=true } diff --git a/stdlib/TOML/test/testfiles/valid/string-empty.jl b/stdlib/TOML/test/testfiles/valid/string-empty.jl deleted file mode 100644 index 42004373795c9..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-empty.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer" => Dict{String,Any}("value" => "","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/string-empty.json b/stdlib/TOML/test/testfiles/valid/string-empty.json deleted file mode 100644 index 6c26d695b29a6..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-empty.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "answer": { - "type": "string", - "value": "" - } -} diff --git a/stdlib/TOML/test/testfiles/valid/string-empty.toml b/stdlib/TOML/test/testfiles/valid/string-empty.toml deleted file mode 100644 index e37e6815bc73d..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-empty.toml +++ /dev/null @@ -1 +0,0 @@ -answer = "" diff --git a/stdlib/TOML/test/testfiles/valid/string-escapes.jl b/stdlib/TOML/test/testfiles/valid/string-escapes.jl deleted file mode 100644 index 2f7c117d16131..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-escapes.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("formfeed" => Dict{String,Any}("value" => "This string has a \f form feed character.","type" => "string"),"notunicode2" => Dict{String,Any}("value" => "This string does not have a unicode \\u escape.","type" => "string"),"backslash" => Dict{String,Any}("value" => "This string has a \\ backslash character.","type" => "string"),"notunicode3" => Dict{String,Any}("value" => "This string does not have a unicode \\u0075 escape.","type" => "string"),"notunicode4" => Dict{String,Any}("value" => "This string does not have a unicode \\u escape.","type" => "string"),"tab" => Dict{String,Any}("value" => "This string has a \t tab character.","type" => "string"),"carriage" => Dict{String,Any}("value" => "This string has a \r carriage return character.","type" => "string"),"quote" => Dict{String,Any}("value" => "This string has a \" quote character.","type" => "string"),"newline" => Dict{String,Any}("value" => "This string has a \n new line character.","type" => "string"),"notunicode1" => Dict{String,Any}("value" => "This string does not have a unicode \\u escape.","type" => "string"),"backspace" => Dict{String,Any}("value" => "This string has a \b backspace character.","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/string-escapes.json b/stdlib/TOML/test/testfiles/valid/string-escapes.json deleted file mode 100644 index 98e2c82d1ce8a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-escapes.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "backspace": { - "type": "string", - "value": "This string has a \u0008 backspace character." - }, - "tab": { - "type": "string", - "value": "This string has a \u0009 tab character." - }, - "newline": { - "type": "string", - "value": "This string has a \u000A new line character." - }, - "formfeed": { - "type": "string", - "value": "This string has a \u000C form feed character." - }, - "carriage": { - "type": "string", - "value": "This string has a \u000D carriage return character." - }, - "quote": { - "type": "string", - "value": "This string has a \u0022 quote character." - }, - "backslash": { - "type": "string", - "value": "This string has a \u005C backslash character." - }, - "notunicode1": { - "type": "string", - "value": "This string does not have a unicode \\u escape." - }, - "notunicode2": { - "type": "string", - "value": "This string does not have a unicode \u005Cu escape." - }, - "notunicode3": { - "type": "string", - "value": "This string does not have a unicode \\u0075 escape." - }, - "notunicode4": { - "type": "string", - "value": "This string does not have a unicode \\\u0075 escape." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/string-escapes.toml b/stdlib/TOML/test/testfiles/valid/string-escapes.toml deleted file mode 100644 index 6d554e4553bdc..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-escapes.toml +++ /dev/null @@ -1,11 +0,0 @@ -backspace = "This string has a \b backspace character." -tab = "This string has a \t tab character." -newline = "This string has a \n new line character." -formfeed = "This string has a \f form feed character." -carriage = "This string has a \r carriage return character." -quote = "This string has a \" quote character." -backslash = "This string has a \\ backslash character." -notunicode1 = "This string does not have a unicode \\u escape." -notunicode2 = "This string does not have a unicode \u005Cu escape." -notunicode3 = "This string does not have a unicode \\u0075 escape." -notunicode4 = "This string does not have a unicode \\\u0075 escape." diff --git a/stdlib/TOML/test/testfiles/valid/string-nl.jl b/stdlib/TOML/test/testfiles/valid/string-nl.jl deleted file mode 100644 index 839d5d29a887c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-nl.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("nl_end" => Dict{String,Any}("value" => "value\n","type" => "string"),"lit_nl_mid" => Dict{String,Any}("value" => "val\\nue","type" => "string"),"nl_mid" => Dict{String,Any}("value" => "val\nue","type" => "string"),"lit_nl_uni" => Dict{String,Any}("value" => "val\\ue","type" => "string"),"lit_nl_end" => Dict{String,Any}("value" => "value\\n","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/string-nl.json b/stdlib/TOML/test/testfiles/valid/string-nl.json deleted file mode 100644 index 54a4a9831813e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-nl.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "nl_mid": { - "type": "string", - "value": "val\nue" - }, - "nl_end": { - "type": "string", - "value": "value\n" - }, - "lit_nl_end": { - "type": "string", - "value": "value\\n" - }, - "lit_nl_mid": { - "type": "string", - "value": "val\\nue" - }, - "lit_nl_uni": { - "type": "string", - "value": "val\\ue" - } -} diff --git a/stdlib/TOML/test/testfiles/valid/string-nl.toml b/stdlib/TOML/test/testfiles/valid/string-nl.toml deleted file mode 100644 index 1e09a8bf78d68..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-nl.toml +++ /dev/null @@ -1,6 +0,0 @@ -nl_mid = "val\nue" -nl_end = """value\n""" - -lit_nl_end = '''value\n''' -lit_nl_mid = 'val\nue' -lit_nl_uni = 'val\ue' diff --git a/stdlib/TOML/test/testfiles/valid/string-simple.jl b/stdlib/TOML/test/testfiles/valid/string-simple.jl deleted file mode 100644 index ea78bcb43c4b3..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-simple.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer" => Dict{String,Any}("value" => "You are not drinking enough whisky.","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/string-simple.json b/stdlib/TOML/test/testfiles/valid/string-simple.json deleted file mode 100644 index 2e05f99b4d181..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-simple.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "answer": { - "type": "string", - "value": "You are not drinking enough whisky." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/string-simple.toml b/stdlib/TOML/test/testfiles/valid/string-simple.toml deleted file mode 100644 index e17ade6237b7b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-simple.toml +++ /dev/null @@ -1 +0,0 @@ -answer = "You are not drinking enough whisky." diff --git a/stdlib/TOML/test/testfiles/valid/string-with-pound.jl b/stdlib/TOML/test/testfiles/valid/string-with-pound.jl deleted file mode 100644 index d8f25e780830f..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-with-pound.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("pound" => Dict{String,Any}("value" => "We see no # comments here.","type" => "string"),"poundcomment" => Dict{String,Any}("value" => "But there are # some comments here.","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/string-with-pound.json b/stdlib/TOML/test/testfiles/valid/string-with-pound.json deleted file mode 100644 index 33cdc9c4b58c8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-with-pound.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "pound": {"type": "string", "value": "We see no # comments here."}, - "poundcomment": { - "type": "string", - "value": "But there are # some comments here." - } -} diff --git a/stdlib/TOML/test/testfiles/valid/string-with-pound.toml b/stdlib/TOML/test/testfiles/valid/string-with-pound.toml deleted file mode 100644 index 5fd87466dff05..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/string-with-pound.toml +++ /dev/null @@ -1,2 +0,0 @@ -pound = "We see no # comments here." -poundcomment = "But there are # some comments here." # Did I # mess you up? diff --git a/stdlib/TOML/test/testfiles/valid/table-array-implicit.jl b/stdlib/TOML/test/testfiles/valid/table-array-implicit.jl deleted file mode 100644 index e255197c60b5c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-implicit.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("albums" => Dict{String,Any}("songs" => Any[Dict{String,Any}("name" => Dict{String,Any}("value" => "Glory Days","type" => "string"))])) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-array-implicit.json b/stdlib/TOML/test/testfiles/valid/table-array-implicit.json deleted file mode 100644 index 32e464012d63d..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-implicit.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "albums": { - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}} - ] - } -} diff --git a/stdlib/TOML/test/testfiles/valid/table-array-implicit.toml b/stdlib/TOML/test/testfiles/valid/table-array-implicit.toml deleted file mode 100644 index 3157ac981d379..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-implicit.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[albums.songs]] -name = "Glory Days" diff --git a/stdlib/TOML/test/testfiles/valid/table-array-many.jl b/stdlib/TOML/test/testfiles/valid/table-array-many.jl deleted file mode 100644 index 7e9f0ede91368..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-many.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("people" => Any[Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Bruce","type" => "string"),"last_name" => Dict{String,Any}("value" => "Springsteen","type" => "string")), Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Eric","type" => "string"),"last_name" => Dict{String,Any}("value" => "Clapton","type" => "string")), Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Bob","type" => "string"),"last_name" => Dict{String,Any}("value" => "Seger","type" => "string"))]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-array-many.json b/stdlib/TOML/test/testfiles/valid/table-array-many.json deleted file mode 100644 index 84df2dabb0d6b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-many.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - }, - { - "first_name": {"type": "string", "value": "Eric"}, - "last_name": {"type": "string", "value": "Clapton"} - }, - { - "first_name": {"type": "string", "value": "Bob"}, - "last_name": {"type": "string", "value": "Seger"} - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/table-array-many.toml b/stdlib/TOML/test/testfiles/valid/table-array-many.toml deleted file mode 100644 index 46062beb8e747..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-many.toml +++ /dev/null @@ -1,11 +0,0 @@ -[[people]] -first_name = "Bruce" -last_name = "Springsteen" - -[[people]] -first_name = "Eric" -last_name = "Clapton" - -[[people]] -first_name = "Bob" -last_name = "Seger" diff --git a/stdlib/TOML/test/testfiles/valid/table-array-nest.jl b/stdlib/TOML/test/testfiles/valid/table-array-nest.jl deleted file mode 100644 index f9fbb34b6a39c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-nest.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("albums" => Any[Dict{String,Any}("name" => Dict{String,Any}("value" => "Born to Run","type" => "string"),"songs" => Any[Dict{String,Any}("name" => Dict{String,Any}("value" => "Jungleland","type" => "string")), Dict{String,Any}("name" => Dict{String,Any}("value" => "Meeting Across the River","type" => "string"))]), Dict{String,Any}("name" => Dict{String,Any}("value" => "Born in the USA","type" => "string"),"songs" => Any[Dict{String,Any}("name" => Dict{String,Any}("value" => "Glory Days","type" => "string")), Dict{String,Any}("name" => Dict{String,Any}("value" => "Dancing in the Dark","type" => "string"))])]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-array-nest.json b/stdlib/TOML/test/testfiles/valid/table-array-nest.json deleted file mode 100644 index c117afa40d4d0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-nest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "albums": [ - { - "name": {"type": "string", "value": "Born to Run"}, - "songs": [ - {"name": {"type": "string", "value": "Jungleland"}}, - {"name": {"type": "string", "value": "Meeting Across the River"}} - ] - }, - { - "name": {"type": "string", "value": "Born in the USA"}, - "songs": [ - {"name": {"type": "string", "value": "Glory Days"}}, - {"name": {"type": "string", "value": "Dancing in the Dark"}} - ] - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/table-array-nest.toml b/stdlib/TOML/test/testfiles/valid/table-array-nest.toml deleted file mode 100644 index ce3cae15dbadc..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-nest.toml +++ /dev/null @@ -1,17 +0,0 @@ -[[albums]] -name = "Born to Run" - - [[albums.songs]] - name = "Jungleland" - - [[albums.songs]] - name = "Meeting Across the River" - -[[albums]] -name = "Born in the USA" - - [[albums.songs]] - name = "Glory Days" - - [[albums.songs]] - name = "Dancing in the Dark" diff --git a/stdlib/TOML/test/testfiles/valid/table-array-one.jl b/stdlib/TOML/test/testfiles/valid/table-array-one.jl deleted file mode 100644 index 26f1597e0696b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-one.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("people" => Any[Dict{String,Any}("first_name" => Dict{String,Any}("value" => "Bruce","type" => "string"),"last_name" => Dict{String,Any}("value" => "Springsteen","type" => "string"))]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-array-one.json b/stdlib/TOML/test/testfiles/valid/table-array-one.json deleted file mode 100644 index d75faaeb23904..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-one.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "people": [ - { - "first_name": {"type": "string", "value": "Bruce"}, - "last_name": {"type": "string", "value": "Springsteen"} - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/table-array-one.toml b/stdlib/TOML/test/testfiles/valid/table-array-one.toml deleted file mode 100644 index cd7e1b6907110..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-one.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[people]] -first_name = "Bruce" -last_name = "Springsteen" diff --git a/stdlib/TOML/test/testfiles/valid/table-array-table-array.jl b/stdlib/TOML/test/testfiles/valid/table-array-table-array.jl deleted file mode 100644 index 536330b3afe5e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-table-array.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Any[Dict{String,Any}("b" => Any[Dict{String,Any}("c" => Dict{String,Any}("d" => Dict{String,Any}("value" => "val0","type" => "string"))), Dict{String,Any}("c" => Dict{String,Any}("d" => Dict{String,Any}("value" => "val1","type" => "string")))])]) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-array-table-array.json b/stdlib/TOML/test/testfiles/valid/table-array-table-array.json deleted file mode 100644 index e5b7e0aab9e80..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-table-array.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "a": [ - { - "b": [ - { "c" : { "d": {"type": "string", "value": "val0" } } }, - { "c" : { "d": {"type": "string", "value": "val1" } } } - ] - } - ] -} diff --git a/stdlib/TOML/test/testfiles/valid/table-array-table-array.toml b/stdlib/TOML/test/testfiles/valid/table-array-table-array.toml deleted file mode 100644 index a07b0c7fe3fdd..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-array-table-array.toml +++ /dev/null @@ -1,7 +0,0 @@ -[[a]] - [[a.b]] - [a.b.c] - d = "val0" - [[a.b]] - [a.b.c] - d = "val1" diff --git a/stdlib/TOML/test/testfiles/valid/table-empty.jl b/stdlib/TOML/test/testfiles/valid/table-empty.jl deleted file mode 100644 index 8ed753e5f5e57..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-empty.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}()) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-empty.json b/stdlib/TOML/test/testfiles/valid/table-empty.json deleted file mode 100644 index 6f3873af6b2f8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-empty.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "a": {} -} diff --git a/stdlib/TOML/test/testfiles/valid/table-empty.toml b/stdlib/TOML/test/testfiles/valid/table-empty.toml deleted file mode 100644 index 8bb6a0aa07ea6..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-empty.toml +++ /dev/null @@ -1 +0,0 @@ -[a] diff --git a/stdlib/TOML/test/testfiles/valid/table-no-eol.jl b/stdlib/TOML/test/testfiles/valid/table-no-eol.jl deleted file mode 100644 index e9014a1e55ee8..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-no-eol.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("table" => Dict{String,Any}()) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-no-eol.json b/stdlib/TOML/test/testfiles/valid/table-no-eol.json deleted file mode 100644 index 11fa444073cfb..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-no-eol.json +++ /dev/null @@ -1 +0,0 @@ -{ "table": {} } diff --git a/stdlib/TOML/test/testfiles/valid/table-no-eol.toml b/stdlib/TOML/test/testfiles/valid/table-no-eol.toml deleted file mode 100644 index 741b2d1c2056a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-no-eol.toml +++ /dev/null @@ -1 +0,0 @@ -[table] \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-sub-empty.jl b/stdlib/TOML/test/testfiles/valid/table-sub-empty.jl deleted file mode 100644 index ced2225a6cd90..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-sub-empty.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}("b" => Dict{String,Any}())) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-sub-empty.json b/stdlib/TOML/test/testfiles/valid/table-sub-empty.json deleted file mode 100644 index 97877708e6d9b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-sub-empty.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "a": { "b": {} } -} diff --git a/stdlib/TOML/test/testfiles/valid/table-sub-empty.toml b/stdlib/TOML/test/testfiles/valid/table-sub-empty.toml deleted file mode 100644 index 70b7fe11c3d12..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-sub-empty.toml +++ /dev/null @@ -1,2 +0,0 @@ -[a] -[a.b] diff --git a/stdlib/TOML/test/testfiles/valid/table-whitespace.jl b/stdlib/TOML/test/testfiles/valid/table-whitespace.jl deleted file mode 100644 index 7a74b1b6b0fa6..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-whitespace.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("valid key" => Dict{String,Any}()) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-whitespace.json b/stdlib/TOML/test/testfiles/valid/table-whitespace.json deleted file mode 100644 index 3a73ec864537e..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-whitespace.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "valid key": {} -} diff --git a/stdlib/TOML/test/testfiles/valid/table-whitespace.toml b/stdlib/TOML/test/testfiles/valid/table-whitespace.toml deleted file mode 100644 index daf881d13a560..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-whitespace.toml +++ /dev/null @@ -1 +0,0 @@ -["valid key"] diff --git a/stdlib/TOML/test/testfiles/valid/table-with-literal-string.jl b/stdlib/TOML/test/testfiles/valid/table-with-literal-string.jl deleted file mode 100644 index b4ea19cf15d48..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-literal-string.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}("\"b\"" => Dict{String,Any}("c" => Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer"))))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-with-literal-string.json b/stdlib/TOML/test/testfiles/valid/table-with-literal-string.json deleted file mode 100644 index 8f006b0e24747..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-literal-string.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "a": { - "\"b\"": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/table-with-literal-string.toml b/stdlib/TOML/test/testfiles/valid/table-with-literal-string.toml deleted file mode 100644 index 63d20a2c672bb..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-literal-string.toml +++ /dev/null @@ -1,4 +0,0 @@ -['a'] -[a.'"b"'] -[a.'"b"'.c] -answer = 42 diff --git a/stdlib/TOML/test/testfiles/valid/table-with-pound.jl b/stdlib/TOML/test/testfiles/valid/table-with-pound.jl deleted file mode 100644 index d95d29b2e7eae..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-pound.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("key#group" => Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer"))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-with-pound.json b/stdlib/TOML/test/testfiles/valid/table-with-pound.json deleted file mode 100644 index 5e594e4191981..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-pound.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "key#group": { - "answer": {"type": "integer", "value": "42"} - } -} diff --git a/stdlib/TOML/test/testfiles/valid/table-with-pound.toml b/stdlib/TOML/test/testfiles/valid/table-with-pound.toml deleted file mode 100644 index 33f2c4fd6cf02..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-pound.toml +++ /dev/null @@ -1,2 +0,0 @@ -["key#group"] -answer = 42 diff --git a/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.jl b/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.jl deleted file mode 100644 index 2fa2c2156bb67..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("a" => Dict{String,Any}("b" => Dict{String,Any}("c" => Dict{String,Any}("answer" => Dict{String,Any}("value" => "42","type" => "integer"))))) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.json b/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.json deleted file mode 100644 index fbae7fc71beff..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "a": { - "b": { - "c": { - "answer": {"type": "integer", "value": "42"} - } - } - } -} diff --git a/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.toml b/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.toml deleted file mode 100644 index b04efcc02c3de..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/table-with-single-quotes.toml +++ /dev/null @@ -1,4 +0,0 @@ -['a'] -[a.'b'] -[a.'b'.c] -answer = 42 diff --git a/stdlib/TOML/test/testfiles/valid/underscored-float.jl b/stdlib/TOML/test/testfiles/valid/underscored-float.jl deleted file mode 100644 index 7ee220ca0cf8b..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/underscored-float.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("electron_mass" => Dict{String,Any}("value" => "9.109109383e-31","type" => "float")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/underscored-float.json b/stdlib/TOML/test/testfiles/valid/underscored-float.json deleted file mode 100644 index 480109c200be9..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/underscored-float.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "electron_mass": {"type": "float", "value": "9.109109383e-31"} -} diff --git a/stdlib/TOML/test/testfiles/valid/underscored-float.toml b/stdlib/TOML/test/testfiles/valid/underscored-float.toml deleted file mode 100644 index 025b02a177bce..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/underscored-float.toml +++ /dev/null @@ -1 +0,0 @@ -electron_mass = 9_109.109_383e-3_4 diff --git a/stdlib/TOML/test/testfiles/valid/underscored-integer.jl b/stdlib/TOML/test/testfiles/valid/underscored-integer.jl deleted file mode 100644 index 0aa27784aba48..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/underscored-integer.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("million" => Dict{String,Any}("value" => "1000000","type" => "integer")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/underscored-integer.json b/stdlib/TOML/test/testfiles/valid/underscored-integer.json deleted file mode 100644 index 0804919f10a54..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/underscored-integer.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "million": {"type": "integer", "value": "1000000"} -} diff --git a/stdlib/TOML/test/testfiles/valid/underscored-integer.toml b/stdlib/TOML/test/testfiles/valid/underscored-integer.toml deleted file mode 100644 index 6be8b5153794c..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/underscored-integer.toml +++ /dev/null @@ -1 +0,0 @@ -million = 1_000_000 diff --git a/stdlib/TOML/test/testfiles/valid/unicode-escape.jl b/stdlib/TOML/test/testfiles/valid/unicode-escape.jl deleted file mode 100644 index a2e66db0d51e9..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/unicode-escape.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer8" => Dict{String,Any}("value" => "δ","type" => "string"),"answer4" => Dict{String,Any}("value" => "δ","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/unicode-escape.json b/stdlib/TOML/test/testfiles/valid/unicode-escape.json deleted file mode 100644 index 216f8f7c9318a..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/unicode-escape.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "answer4": {"type": "string", "value": "\u03B4"}, - "answer8": {"type": "string", "value": "\u03B4"} -} diff --git a/stdlib/TOML/test/testfiles/valid/unicode-escape.toml b/stdlib/TOML/test/testfiles/valid/unicode-escape.toml deleted file mode 100644 index 82faecbfa5997..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/unicode-escape.toml +++ /dev/null @@ -1,2 +0,0 @@ -answer4 = "\u03B4" -answer8 = "\U000003B4" diff --git a/stdlib/TOML/test/testfiles/valid/unicode-literal.jl b/stdlib/TOML/test/testfiles/valid/unicode-literal.jl deleted file mode 100644 index bdcb5d4cf0ea0..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/unicode-literal.jl +++ /dev/null @@ -1 +0,0 @@ -Dict{String,Any}("answer" => Dict{String,Any}("value" => "δ","type" => "string")) \ No newline at end of file diff --git a/stdlib/TOML/test/testfiles/valid/unicode-literal.json b/stdlib/TOML/test/testfiles/valid/unicode-literal.json deleted file mode 100644 index 00aa2f8325ecb..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/unicode-literal.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "answer": {"type": "string", "value": "δ"} -} diff --git a/stdlib/TOML/test/testfiles/valid/unicode-literal.toml b/stdlib/TOML/test/testfiles/valid/unicode-literal.toml deleted file mode 100644 index c65723ca1d273..0000000000000 --- a/stdlib/TOML/test/testfiles/valid/unicode-literal.toml +++ /dev/null @@ -1 +0,0 @@ -answer = "δ" diff --git a/stdlib/TOML/test/toml_test.jl b/stdlib/TOML/test/toml_test.jl index 45fbd20dbcdab..f4670058223a1 100644 --- a/stdlib/TOML/test/toml_test.jl +++ b/stdlib/TOML/test/toml_test.jl @@ -5,11 +5,16 @@ using TOML using Test using Dates +testfiles = get_data() + const jsnval = Dict{String,Function}( "string" =>identity, "float" => (s -> Base.parse(Float64, s)), "integer" => (s -> Base.parse(Int64, s)), - "datetime" => (s -> Base.parse(DateTime, s, dateformat"yyyy-mm-ddTHH:MM:SSZ")), + "datetime" => (s -> Base.parse(DateTime, endswith(s, 'Z') ? chop(s) : s)), + "datetime-local" => (s -> Base.parse(DateTime, endswith(s, 'Z') ? chop(s) : s)), + "date-local" => (s -> Base.parse(DateTime, endswith(s, 'Z') ? chop(s) : s)), + "time-local" => (s -> Base.parse(Time, s)), "array" => (a -> map(jsn2data, a)), "bool" => (b -> b == "true") ) @@ -29,163 +34,125 @@ end # Valid # ######### -valid_test_folder = joinpath(@__DIR__, "testfiles", "valid") - function check_valid(f) - fp = joinpath(valid_test_folder, f) - jsn = jsn2data(@eval include($fp * ".jl")) - tml = TOML.parsefile(fp * ".toml") + jsn = try jsn2data(@eval include($f * ".jl")) + # Some files cannot be reprsented with julias DateTime (timezones) + catch + return false + end + tml = TOML.tryparsefile(f * ".toml") + tml isa TOML.Internals.ParserError && return false return isequal(tml, jsn) end @testset "valid" begin -@test check_valid("array-empty") -@test check_valid("array-nospaces") -@test check_valid("array-string-quote-comma-2") -@test check_valid("array-string-quote-comma") -@test check_valid("array-string-with-comma") -@test check_valid("array-table-array-string-backslash") -@test check_valid("arrays-hetergeneous") -@test check_valid("arrays-nested") -@test check_valid("arrays") -@test check_valid("bool") -@test check_valid("comments-at-eof") -@test check_valid("comments-at-eof2") -@test check_valid("comments-everywhere") -@test_broken check_valid("datetime-timezone") -@test_broken check_valid("datetime") -@test check_valid("double-quote-escape") -@test check_valid("empty") -@test check_valid("escaped-escape") -@test check_valid("example") -@test check_valid("exponent-part-float") -@test check_valid("float-exponent") -@test check_valid("float-underscore") -@test check_valid("float") -@test check_valid("implicit-and-explicit-after") -@test check_valid("implicit-and-explicit-before") -@test check_valid("implicit-groups") -@test check_valid("inline-table-array") -@test check_valid("inline-table") -@test check_valid("integer-underscore") -@test check_valid("integer") -@test check_valid("key-equals-nospace") -@test check_valid("key-numeric") -@test check_valid("key-space") -@test check_valid("key-special-chars") -@test check_valid("keys-with-dots") -@test check_valid("long-float") -@test check_valid("long-integer") -@test check_valid("multiline-string") -@test check_valid("nested-inline-table-array") -@test check_valid("newline-crlf") -@test check_valid("newline-lf") -if Sys.iswindows() && - # Sometimes git normalizes the line endings - contains(read(joinpath(valid_test_folder, "raw-multiline-string-win.toml"), String), '\r') - @test check_valid("raw-multiline-string-win") -else - @test check_valid("raw-multiline-string") +failures = [ + "valid/spec-example-1.toml", + "valid/spec-example-1-compact.toml", + "valid/datetime/datetime.toml", + "valid/comment/everywhere.toml", + "valid/datetime/milliseconds.toml", + "valid/datetime/timezone.toml", + "valid/string/multiline-quotes.toml", + "valid/string/multiline.toml", + "valid/float/zero.toml", # this one has a buggy .json file + "valid/string/escape-esc.toml", +] + +n_files_valid = 0 +valid_test_folder = joinpath(testfiles, "valid") +for (root, dirs, files) in walkdir(valid_test_folder) + for f in files + if endswith(f, ".toml") + n_files_valid += 1 + file = joinpath(root, f) + rel = relpath(file, testfiles) + if Sys.iswindows() + rel = replace(rel, '\\' => '/') + end + v = check_valid(splitext(file)[1]) + if rel in failures + @test_broken v + else + @test v + end + end + end end -@test check_valid("raw-string") -@test check_valid("right-curly-brace-after-boolean") -@test check_valid("string-empty") -@test check_valid("string-escapes") -@test check_valid("string-nl") -@test check_valid("string-simple") -@test check_valid("string-with-pound") -@test check_valid("table-array-implicit") -@test check_valid("table-array-many") -@test check_valid("table-array-nest") -@test check_valid("table-array-one") -@test check_valid("table-array-table-array") -@test check_valid("table-empty") -@test check_valid("table-no-eol") -@test check_valid("table-sub-empty") -@test check_valid("table-whitespace") -@test check_valid("table-with-literal-string") -@test check_valid("table-with-pound") -@test check_valid("table-with-single-quotes") -@test check_valid("underscored-float") -@test check_valid("underscored-integer") -@test check_valid("unicode-escape") -@test check_valid("unicode-literal") +@test n_files_valid >= 100 -end +end # testset ########### # Invalid # ########### -invalid_test_folder = joinpath(@__DIR__, "testfiles", "invalid") - # TODO: Check error type function check_invalid(f) - fp = joinpath(invalid_test_folder, f) - tml = TOML.tryparsefile(fp * ".toml") + tml = try TOML.tryparsefile(f) + catch + return false + end return tml isa TOML.Internals.ParserError end -@test check_invalid("datetime-malformed-no-leads") -@test check_invalid("datetime-malformed-no-secs") -@test check_invalid("datetime-malformed-no-t") -@test check_invalid("datetime-malformed-with-milli") -@test check_invalid("duplicate-key-table") -@test check_invalid("duplicate-keys") -@test check_invalid("duplicate-tables") -@test check_invalid("empty-implicit-table") -@test check_invalid("empty-table") -@test check_invalid("float-leading-zero-neg") -@test check_invalid("float-leading-zero-pos") -@test check_invalid("float-leading-zero") -@test check_invalid("float-no-leading-zero") -@test check_invalid("float-no-trailing-digits") -@test check_invalid("float-underscore-after-point") -@test check_invalid("float-underscore-after") -@test check_invalid("float-underscore-before-point") -@test check_invalid("float-underscore-before") -@test check_invalid("inline-table-linebreak") -@test check_invalid("integer-leading-zero-neg") -@test check_invalid("integer-leading-zero-pos") -@test check_invalid("integer-leading-zero") -@test check_invalid("integer-underscore-after") -@test check_invalid("integer-underscore-before") -@test check_invalid("integer-underscore-double") -@test check_invalid("key-after-array") -@test check_invalid("key-after-table") -@test check_invalid("key-empty") -@test check_invalid("key-hash") -@test check_invalid("key-newline") -@test check_invalid("key-no-eol") -@test check_invalid("key-open-bracket") -@test check_invalid("key-single-open-bracket") -@test check_invalid("key-space") -@test check_invalid("key-start-bracket") -@test check_invalid("key-two-equals") -@test check_invalid("llbrace") -@test check_invalid("multi-line-inline-table") -@test check_invalid("multi-line-string-no-close") -@test check_invalid("rrbrace") -@test check_invalid("string-bad-byte-escape") -@test check_invalid("string-bad-codepoint") -@test check_invalid("string-bad-escape") -@test check_invalid("string-bad-slash-escape") -@test check_invalid("string-bad-uni-esc") -@test check_invalid("string-byte-escapes") -@test check_invalid("string-no-close") -@test check_invalid("table-array-implicit") -@test check_invalid("table-array-malformed-bracket") -@test check_invalid("table-array-malformed-empty") -@test check_invalid("table-empty") -@test check_invalid("table-nested-brackets-close") -@test check_invalid("table-nested-brackets-open") -@test check_invalid("table-whitespace") -@test check_invalid("table-with-pound") -@test check_invalid("text-after-array-entries") -@test check_invalid("text-after-integer") -@test check_invalid("text-after-string") -@test check_invalid("text-after-table") -@test check_invalid("text-before-array-separator") -@test check_invalid("text-in-array") +@testset "invalid" begin + +failures = [ + "invalid/control/bare-cr.toml", + "invalid/control/comment-del.toml", + "invalid/control/comment-lf.toml", + "invalid/control/comment-null.toml", + "invalid/control/comment-us.toml", + "invalid/control/comment-cr.toml", + "invalid/datetime/time-no-leads.toml", + "invalid/control/multi-del.toml", + "invalid/control/multi-lf.toml", + "invalid/control/multi-null.toml", + "invalid/control/multi-us.toml", + "invalid/control/rawmulti-del.toml", + "invalid/control/rawmulti-lf.toml", + "invalid/control/rawmulti-null.toml", + "invalid/control/rawmulti-us.toml", + "invalid/control/rawstring-del.toml", + "invalid/control/rawstring-lf.toml", + "invalid/control/rawstring-null.toml", + "invalid/control/rawstring-us.toml", + "invalid/control/string-bs.toml", + "invalid/control/string-del.toml", + "invalid/control/string-lf.toml", + "invalid/control/string-null.toml", + "invalid/control/string-us.toml", + "invalid/encoding/bad-utf8-in-comment.toml", + "invalid/encoding/bad-utf8-in-string.toml", + "invalid/key/multiline.toml", + "invalid/table/append-with-dotted-keys-2.toml", + "invalid/table/duplicate-key-dotted-table.toml", + "invalid/table/duplicate-key-dotted-table2.toml", +] + +n_invalid = 0 +invalid_test_folder = joinpath(testfiles, "invalid") +for (root, dirs, files) in walkdir(invalid_test_folder) + for f in files + if endswith(f, ".toml") + n_invalid += 1 + file = joinpath(root, f) + rel = relpath(file, testfiles) + if Sys.iswindows() + rel = replace(rel, '\\' => '/') + end + v = check_invalid(file) + if rel in failures + @test_broken v + else + @test v + end + end + end +end +@test n_invalid > 50 + +end # testset diff --git a/stdlib/TOML/test/utils/convert_json_to_jl.jl b/stdlib/TOML/test/utils/convert_json_to_jl.jl deleted file mode 100644 index a2c049fac0c7d..0000000000000 --- a/stdlib/TOML/test/utils/convert_json_to_jl.jl +++ /dev/null @@ -1,19 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -# This converts the ground-truth JSON files to the Julia repr format so -# we can use that without requiring a JSON parser during testing. - -using JSON - -const testfiles = joinpath(@__DIR__, "..", "testfiles") - -function convert_json_files() - for folder in ("invalid", "valid") - for file in readdir(joinpath(testfiles, folder); join=true) - endswith(file, ".json") || continue - d_json = open(JSON.parse, file) - d_jl = repr(d_json) - write(splitext(file)[1] * ".jl", d_jl) - end - end -end \ No newline at end of file diff --git a/stdlib/TOML/test/utils/utils.jl b/stdlib/TOML/test/utils/utils.jl new file mode 100644 index 0000000000000..c484a61cee25a --- /dev/null +++ b/stdlib/TOML/test/utils/utils.jl @@ -0,0 +1,39 @@ +# This converts the ground-truth JSON files to the Julia repr format so +# we can use that without requiring a JSON parser during testing. + +using Downloads +using Tar +using p7zip_jll + +const url = "https://github.com/KristofferC/toml-test-julia/archive/refs/tags/v1.2.0.tar.gz" +const tarname = basename(url) +const version = lstrip(split(tarname, ".tar.gz")[1], 'v') + +# From Pkg +function exe7z() + # If the JLL is available, use the wrapper function defined in there + if p7zip_jll.is_available() + return p7zip_jll.p7zip() + end + return Cmd([find7z()]) +end + +function find7z() + name = "7z" + Sys.iswindows() && (name = "$name.exe") + for dir in (joinpath("..", "libexec"), ".") + path = normpath(Sys.BINDIR::String, dir, name) + isfile(path) && return path + end + path = Sys.which(name) + path !== nothing && return path + error("7z binary not found") +end + +function get_data() + tmp = mktempdir() + path = joinpath(tmp, basename(url)) + Downloads.download(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/TOML/test/values.jl b/stdlib/TOML/test/values.jl index 8337bb5a54714..be2ed3acce5b5 100644 --- a/stdlib/TOML/test/values.jl +++ b/stdlib/TOML/test/values.jl @@ -23,8 +23,6 @@ end @test failval("00.0" , Internals.ErrParsingDateTime) @test failval("-00.0" , Internals.ErrParsingDateTime) @test failval("+00.0" , Internals.ErrParsingDateTime) - @test failval("9223372036854775808" , Internals.ErrOverflowError) - @test failval("-9223372036854775809" , Internals.ErrOverflowError) @test failval("0." , Internals.ErrNoTrailingDigitAfterDot) @test failval("0.e" , Internals.ErrNoTrailingDigitAfterDot) @@ -54,6 +52,30 @@ end @test testval("+1_000" , 1000 |> Int64) @test testval("-1_000" , -1000 |> Int64) + @test testval("0x6E", 0x6E|> UInt64) + @test testval("0x8f1e", 0x8f1e|> UInt64) + @test testval("0x765f3173", 0x765f3173|> UInt64) + @test testval("0xc13b830a807cc7f4", 0xc13b830a807cc7f4|> UInt64) + @test testval("0x937efe_0a4241_edb24a04b97bd90ef363", 0x937efe0a4241edb24a04b97bd90ef363 |> UInt128) + + @test testval("0o140", 0o140 |> UInt64) # UInt8 + @test testval("0o46244", 0o46244 |> UInt64) # UInt16 + @test testval("0o32542120656", 0o32542120656 |> UInt64) # UInt32 + @test testval("0o1526535761042630654411", 0o1526535761042630654411 |> UInt64) # UInt64 + @test testval("0o3467204325743773607311464533371572447656531", 0o3467204325743773607311464533371572447656531 |> UInt128) # UInt128 + @test testval("0o34672043257437736073114645333715724476565312", 0o34672043257437736073114645333715724476565312 |> BigInt) # BigInt + + @test testval("0b10001010",0b10001010 |> UInt64) # UInt8 + @test testval("0b11111010001100",0b11111010001100 |> UInt64) # UInt16 + @test testval("0b11100011110000010101000010101",0b11100011110000010101000010101 |> UInt64) # UInt32 + @test testval("0b10000110100111011010001000000111110110000011111101101110011011",0b10000110100111011010001000000111110110000011111101101110011011 |> UInt64) # UInt64 + @test testval( + "0b1101101101101100110001010110111011101000111010101110011000011100110100101111110001010001011001000001000001010010011101100100111", + 0b1101101101101100110001010110111011101000111010101110011000011100110100101111110001010001011001000001000001010010011101100100111 |> UInt128) # UInt128 + @test testval( + "0b110110110110110011000101011011101110100011101010111001100001110011010010111111000101000101100100000100000101001001110110010011111", + 0b110110110110110011000101011011101110100011101010111001100001110011010010111111000101000101100100000100000101001001110110010011111 |> BigInt) # BigInt + @test failval("0_" , Internals.ErrUnderscoreNotSurroundedByDigits) @test failval("0__0" , Internals.ErrUnderscoreNotSurroundedByDigits) @test failval("__0" , Internals.ErrUnexpectedStartOfValue) diff --git a/stdlib/Tar.version b/stdlib/Tar.version index b7ee00e5a2666..44e829b5fea54 100644 --- a/stdlib/Tar.version +++ b/stdlib/Tar.version @@ -1,4 +1,4 @@ TAR_BRANCH = master -TAR_SHA1 = 0f8a73d5cd4b0c8f1f3c36799c96e9515e9dc595 +TAR_SHA1 = ff55460f4d329949661a33e6c8168ce6d890676c TAR_GIT_URL := https://github.com/JuliaIO/Tar.jl.git TAR_TAR_URL = https://api.github.com/repos/JuliaIO/Tar.jl/tarball/$1 diff --git a/stdlib/Test/docs/src/index.md b/stdlib/Test/docs/src/index.md index 077d350554775..1c9a55480d2c9 100644 --- a/stdlib/Test/docs/src/index.md +++ b/stdlib/Test/docs/src/index.md @@ -55,6 +55,7 @@ julia> @test foo("f") == 20 Test Failed at none:1 Expression: foo("f") == 20 Evaluated: 1 == 20 + ERROR: There was an error during testing ``` @@ -224,6 +225,7 @@ julia> @test 1 ≈ 0.999999 Test Failed at none:1 Expression: 1 ≈ 0.999999 Evaluated: 1 ≈ 0.999999 + ERROR: There was an error during testing ``` You can specify relative and absolute tolerances by setting the `rtol` and `atol` keyword arguments of `isapprox`, respectively, @@ -258,6 +260,16 @@ in the test set reporting. The test will not run but gives a `Broken` `Result`. Test.@test_skip ``` +## Test result types + +```@docs +Test.Result +Test.Pass +Test.Fail +Test.Error +Test.Broken +``` + ## Creating Custom `AbstractTestSet` Types Packages can create their own `AbstractTestSet` subtypes by implementing the `record` and `finish` @@ -332,6 +344,148 @@ Test.detect_ambiguities Test.detect_unbound_args ``` +## Workflow for Testing Packages + +Using the tools available to us in the previous sections, here is a potential workflow of creating a package and adding tests to it. + +### Generating an Example Package + +For this workflow, we will create a package called `Example`: + +```julia +pkg> generate Example +shell> cd Example +shell> mkdir test +pkg> activate . +``` + +### Creating Sample Functions + +The number one requirement for testing a package is to have functionality to test. +For that, we will add some simple functions to `Example` that we can test. +Add the following to `src/Example.jl`: + +```julia +module Example + +function greet() + "Hello world!" +end + +function simple_add(a, b) + a + b +end + +function type_multiply(a::Float64, b::Float64) + a * b +end + +end +``` + +### Creating a Test Environment + +From within the root of the `Example` package, navigate to the `test` directory, activate a new environment there, and add the `Test` package to the environment: + +```julia +shell> cd test +pkg> activate . +(test) pkg> add Test +``` + +### Testing Our Package + +Now, we are ready to add tests to `Example`. +It is standard practice to create a file within the `test` directory called `runtests.jl` which contains the test sets we want to run. +Go ahead and create that file within the `test` directory and add the following code to it: + +```julia +using Example +using Test + +@testset "Example tests" begin + + @testset "Math tests" begin + include("math_tests.jl") + end + + @testset "Greeting tests" begin + include("greeting_tests.jl") + end +end +``` + +We will need to create those two included files, `math_tests.jl` and `greeting_tests.jl`, and add some tests to them. + +> **Note:** Notice how we did not have to specify add `Example` into the `test` environment's `Project.toml`. +> This is a benefit of Julia's testing system that you could [read about more here](https://pkgdocs.julialang.org/dev/creating-packages/). + +#### Writing Tests for `math_tests.jl` + +Using our knowledge of `Test.jl`, here are some example tests we could add to `math_tests.jl`: + +```julia +@testset "Testset 1" begin + @test 2 == simple_add(1, 1) + @test 3.5 == simple_add(1, 2.5) + @test_throws MethodError simple_add(1, "A") + @test_throws MethodError simple_add(1, 2, 3) +end + +@testset "Testset 2" begin + @test 1.0 == type_multiply(1.0, 1.0) + @test isa(type_multiply(2.0, 2.0), Float64) + @test_throws MethodError type_multiply(1, 2.5) +end +``` + +#### Writing Tests for `greeting_tests.jl` + +Using our knowledge of `Test.jl`, here are some example tests we could add to `math_tests.jl`: + +```julia +@testset "Testset 3" begin + @test "Hello world!" == greet() + @test_throws MethodError greet("Antonia") +end +``` + +### Testing Our Package + +Now that we have added our tests and our `runtests.jl` script in `test`, we can test our `Example` package by going back to the root of the `Example` package environment and reactivating the `Example` environment: + +```julia +shell> cd .. +pkg> activate . +``` + +From there, we can finally run our test suite as follows: + +```julia +(Example) pkg> test + Testing Example + Status `/tmp/jl_Yngpvy/Project.toml` + [fa318bd2] Example v0.1.0 `/home/src/Projects/tmp/errata/Example` + [8dfed614] Test `@stdlib/Test` + Status `/tmp/jl_Yngpvy/Manifest.toml` + [fa318bd2] Example v0.1.0 `/home/src/Projects/tmp/errata/Example` + [2a0f44e3] Base64 `@stdlib/Base64` + [b77e0a4c] InteractiveUtils `@stdlib/InteractiveUtils` + [56ddb016] Logging `@stdlib/Logging` + [d6f4376e] Markdown `@stdlib/Markdown` + [9a3f8284] Random `@stdlib/Random` + [ea8e919c] SHA `@stdlib/SHA` + [9e88b42a] Serialization `@stdlib/Serialization` + [8dfed614] Test `@stdlib/Test` + Testing Running tests... +Test Summary: | Pass Total +Example tests | 9 9 + Testing Example tests passed +``` + +And if all went correctly, you should see a similar output as above. +Using `Test.jl`, more complicated tests can be added for packages but this should ideally point developers in the direction of how to get started with testing their own created packages. + ```@meta DocTestSetup = nothing ``` diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 5693d65c7f913..392b736c09837 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -41,34 +41,70 @@ const DISPLAY_FAILED = ( :contains ) +const FAIL_FAST = Ref{Bool}(false) + #----------------------------------------------------------------------- # Backtrace utility functions function ip_has_file_and_func(ip, file, funcs) - return any(fr -> (string(fr.file) == file && fr.func in funcs), StackTraces.lookup(ip)) + return any(fr -> (in_file(fr, file) && fr.func in funcs), StackTraces.lookup(ip)) end +in_file(frame, file) = string(frame.file) == file -function scrub_backtrace(bt) +function test_location(bt, file_ts, file_t) + if (isnothing(file_ts) || isnothing(file_t)) + return macrocall_location(bt, something(file_ts, @__FILE__)) + else + return test_callsite(bt, file_ts, file_t) + end +end + +function test_callsite(bt, file_ts, file_t) + # We avoid duplicate calls to `StackTraces.lookup`, as it is an expensive call. + # For that, we retrieve locations from lower to higher stack elements + # and only traverse parts of the backtrace which we haven't traversed before. + # The order will always be <internal functions> -> `@test` -> `@testset`. + internal = @something(macrocall_location(bt, @__FILE__), return nothing) + test = internal - 1 + @something(findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), @view bt[internal:end]), return nothing) + testset = test - 1 + @something(macrocall_location(@view(bt[test:end]), file_ts), return nothing) + + # If stacktrace locations differ, include frames until the `@testset` appears. + test != testset && return testset + # `@test` and `@testset` occurred at the same stacktrace location. + # This may happen if `@test` occurred directly in scope of the testset, + # or if `@test` occurred in a function that has been inlined in the testset. + frames = StackTraces.lookup(bt[testset]) + outer_frame = findfirst(frame -> in_file(frame, file_ts) && frame.func == Symbol("macro expansion"), frames) + isnothing(outer_frame) && return nothing + # The `@test` call occurred directly in scope of a `@testset`. + # The __source__ from `@test` will be printed in the test message upon failure. + # There is no need to include more frames, but always include at least the internal macrocall location in the stacktrace. + in_file(frames[outer_frame], file_t) && return internal + # The `@test` call was inlined, so we still need to include the callsite. + return testset +end + +macrocall_location(bt, file) = findfirst(ip -> ip_has_file_and_func(ip, file, (Symbol("macro expansion"),)), bt) + +function scrub_backtrace(bt, file_ts, file_t) do_test_ind = findfirst(ip -> ip_has_file_and_func(ip, @__FILE__, (:do_test, :do_test_throws)), bt) if do_test_ind !== nothing && length(bt) > do_test_ind bt = bt[do_test_ind + 1:end] end - name_ind = findfirst(ip -> ip_has_file_and_func(ip, @__FILE__, (Symbol("macro expansion"),)), bt) - if name_ind !== nothing && length(bt) != 0 - bt = bt[1:name_ind] - end + stop_at = test_location(bt, file_ts, file_t) + !isnothing(stop_at) && !isempty(bt) && return bt[1:stop_at] return bt end -function scrub_exc_stack(stack) - return Any[ (x[1], scrub_backtrace(x[2]::Vector{Union{Ptr{Nothing},Base.InterpreterIP}})) for x in stack ] +function scrub_exc_stack(stack, file_ts, file_t) + return Any[ (x[1], scrub_backtrace(x[2]::Vector{Union{Ptr{Nothing},Base.InterpreterIP}}, file_ts, file_t)) for x in stack ] end # define most of the test infrastructure without type specialization @nospecialize """ - Result + Test.Result All tests produce a result object. This object may or may not be stored, depending on whether the test is part of a test set. @@ -76,7 +112,7 @@ stored, depending on whether the test is part of a test set. abstract type Result end """ - Pass + Test.Pass <: Test.Result The test condition was true, i.e. the expression evaluated to true or the correct exception was thrown. @@ -106,7 +142,7 @@ function Base.show(io::IO, t::Pass) end """ - Fail + Test.Fail <: Test.Result The test condition was false, i.e. the expression evaluated to false or the correct exception was not thrown. @@ -116,18 +152,24 @@ struct Fail <: Result orig_expr::String data::Union{Nothing, String} value::String + context::Union{Nothing, String} source::LineNumberNode message_only::Bool - function Fail(test_type::Symbol, orig_expr, data, value, source::LineNumberNode, message_only::Bool=false) + function Fail(test_type::Symbol, orig_expr, data, value, context, source::LineNumberNode, message_only::Bool) return new(test_type, string(orig_expr), data === nothing ? nothing : string(data), string(isa(data, Type) ? typeof(value) : value), + context, source, message_only) end end +# Deprecated fallback constructor without `context` argument (added in Julia 1.9). Remove in Julia 2.0. +Fail(test_type::Symbol, orig_expr, data, value, source::LineNumberNode, message_only::Bool=false) = + Fail(test_type, orig_expr, data, value, nothing, source, message_only) + function Base.show(io::IO, t::Fail) printstyled(io, "Test Failed"; bold=true, color=Base.error_color()) print(io, " at ") @@ -147,15 +189,21 @@ function Base.show(io::IO, t::Fail) # An exception was expected, but no exception was thrown print(io, "\n Expected: ", data) print(io, "\n No exception thrown") - elseif t.test_type === :test && data !== nothing - # The test was an expression, so display the term-by-term - # evaluated version as well - print(io, "\n Evaluated: ", data) + elseif t.test_type === :test + if data !== nothing + # The test was an expression, so display the term-by-term + # evaluated version as well + print(io, "\n Evaluated: ", data) + end + if t.context !== nothing + print(io, "\n Context: ", t.context) + end end + println(io) # add some visual space to separate sequential failures end """ - Error + Test.Error <: Test.Result The test condition couldn't be evaluated due to an exception, or it evaluated to something other than a [`Bool`](@ref). @@ -171,7 +219,7 @@ struct Error <: Result function Error(test_type::Symbol, orig_expr, value, bt, source::LineNumberNode) if test_type === :test_error - bt = scrub_exc_stack(bt) + bt = scrub_exc_stack(bt, nothing, extract_file(source)) end if test_type === :test_error || test_type === :nontest_error bt_str = try # try the latest world for this, since we might have eval'd new code for show @@ -236,7 +284,7 @@ function Base.show(io::IO, t::Error) end """ - Broken + Test.Broken <: Test.Result The test condition is the expected (failed) result of a broken test, or was explicitly skipped with `@test_skip`. @@ -641,7 +689,7 @@ function do_test(result::ExecutionResult, orig_expr) testres = if isa(value, Bool) # a true value Passes value ? Pass(:test, orig_expr, result.data, value, result.source) : - Fail(:test, orig_expr, result.data, value, result.source) + Fail(:test, orig_expr, result.data, value, nothing, result.source, false) else # If the result is non-Boolean, this counts as an Error Error(:test_nonbool, orig_expr, value, nothing, result.source) @@ -652,7 +700,7 @@ function do_test(result::ExecutionResult, orig_expr) @assert isa(result, Threw) testres = Error(:test_error, orig_expr, result.exception, result.backtrace::Vector{Any}, result.source) end - isa(testres, Pass) || ccall(:jl_breakpoint, Cvoid, (Any,), result) + isa(testres, Pass) || trigger_test_failure_break(result) record(get_testset(), testres) end @@ -661,8 +709,13 @@ function do_broken_test(result::ExecutionResult, orig_expr) # Assume the test is broken and only change if the result is true if isa(result, Returned) value = result.value - if isa(value, Bool) && value - testres = Error(:test_unbroken, orig_expr, value, nothing, result.source) + if isa(value, Bool) + if value + testres = Error(:test_unbroken, orig_expr, value, nothing, result.source) + end + else + # If the result is non-Boolean, this counts as an Error + testres = Error(:test_nonbool, orig_expr, value, nothing, result.source) end end record(get_testset(), testres) @@ -680,6 +733,9 @@ a matching function, or a value (which will be tested for equality by comparing fields). Note that `@test_throws` does not support a trailing keyword form. +!!! compat "Julia 1.8" + The ability to specify anything other than a type or a value as `exception` requires Julia v1.8 or later. + # Examples ```jldoctest julia> @test_throws BoundsError [1, 2, 3][4] @@ -692,7 +748,7 @@ Test Passed julia> @test_throws "Try sqrt(Complex" sqrt(-1) Test Passed - Message: "DomainError with -1.0:\\nsqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x))." + Message: "DomainError with -1.0:\\nsqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x))." ``` In the final example, instead of matching a single string it could alternatively have been performed with: @@ -768,10 +824,10 @@ function do_test_throws(result::ExecutionResult, orig_expr, extype) if success testres = Pass(:test_throws, orig_expr, extype, exc, result.source, message_only) else - testres = Fail(:test_throws_wrong, orig_expr, extype, exc, result.source, message_only) + testres = Fail(:test_throws_wrong, orig_expr, extype, exc, nothing, result.source, message_only) end else - testres = Fail(:test_throws_nothing, orig_expr, extype, nothing, result.source) + testres = Fail(:test_throws_nothing, orig_expr, extype, nothing, nothing, result.source, false) end record(get_testset(), testres) end @@ -947,6 +1003,33 @@ finish(ts::FallbackTestSet) = ts #----------------------------------------------------------------------- +""" + ContextTestSet + +Passes test failures through to the parent test set, while adding information +about a context object that is being tested. +""" +struct ContextTestSet <: AbstractTestSet + parent_ts::AbstractTestSet + context_name::Union{Symbol, Expr} + context::Any +end + +function ContextTestSet(name::Union{Symbol, Expr}, @nospecialize(context)) + if (name isa Expr) && (name.head != :tuple) + error("Invalid syntax: $(name)") + end + return ContextTestSet(get_testset(), name, context) +end +record(c::ContextTestSet, t) = record(c.parent_ts, t) +function record(c::ContextTestSet, t::Fail) + context = string(c.context_name, " = ", c.context) + context = t.context === nothing ? context : string(t.context, "\n ", context) + record(c.parent_ts, Fail(t.test_type, t.orig_expr, t.data, t.value, context, t.source, t.message_only)) +end + +#----------------------------------------------------------------------- + """ DefaultTestSet @@ -963,8 +1046,26 @@ mutable struct DefaultTestSet <: AbstractTestSet showtiming::Bool time_start::Float64 time_end::Union{Float64,Nothing} + failfast::Bool + file::Union{String,Nothing} end -DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true) = DefaultTestSet(String(desc)::String, [], 0, false, verbose, showtiming, time(), nothing) +function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true, failfast::Union{Nothing,Bool} = nothing, source = nothing) + if isnothing(failfast) + # pass failfast state into child testsets + parent_ts = get_testset() + if parent_ts isa DefaultTestSet + failfast = parent_ts.failfast + else + failfast = false + end + end + return DefaultTestSet(String(desc)::String, [], 0, false, verbose, showtiming, time(), nothing, failfast, extract_file(source)) +end +extract_file(source::LineNumberNode) = extract_file(source.file) +extract_file(file::Symbol) = string(file) +extract_file(::Nothing) = nothing + +struct FailFastError <: Exception end # For a broken result, simply store the result record(ts::DefaultTestSet, t::Broken) = (push!(ts.results, t); t) @@ -973,19 +1074,20 @@ record(ts::DefaultTestSet, t::Pass) = (ts.n_passed += 1; t) # For the other result types, immediately print the error message # but do not terminate. Print a backtrace. -function record(ts::DefaultTestSet, t::Union{Fail, Error}) - if TESTSET_PRINT_ENABLE[] +function record(ts::DefaultTestSet, t::Union{Fail, Error}; print_result::Bool=TESTSET_PRINT_ENABLE[]) + if print_result print(ts.description, ": ") # don't print for interrupted tests if !(t isa Error) || t.test_type !== :test_interrupted print(t) if !isa(t, Error) # if not gets printed in the show method - Base.show_backtrace(stdout, scrub_backtrace(backtrace())) + Base.show_backtrace(stdout, scrub_backtrace(backtrace(), ts.file, extract_file(t.source))) end println() end end push!(ts.results, t) + (FAIL_FAST[] || ts.failfast) && throw(FailFastError()) return t end @@ -1064,7 +1166,7 @@ const TESTSET_PRINT_ENABLE = Ref(true) # Called at the end of a @testset, behaviour depends on whether # this is a child of another testset, or the "root" testset -function finish(ts::DefaultTestSet) +function finish(ts::DefaultTestSet; print_results::Bool=TESTSET_PRINT_ENABLE[]) ts.time_end = time() # If we are a nested test set, do not print a full summary # now - let the parent test set do the printing @@ -1081,7 +1183,7 @@ function finish(ts::DefaultTestSet) total_broken = broken + c_broken total = total_pass + total_fail + total_error + total_broken - if TESTSET_PRINT_ENABLE[] + if print_results print_test_results(ts) end @@ -1146,10 +1248,11 @@ function get_test_counts(ts::DefaultTestSet) end end ts.anynonpass = (fails + errors + c_fails + c_errors > 0) - duration = if isnothing(ts.time_end) + (; time_start, time_end) = ts + duration = if isnothing(time_end) "" else - dur_s = ts.time_end - ts.time_start + dur_s = time_end - time_start if dur_s < 60 string(round(dur_s, digits = 1), "s") else @@ -1250,9 +1353,13 @@ end @testset [CustomTestSet] [option=val ...] ["description"] begin ... end @testset [CustomTestSet] [option=val ...] ["description \$v"] for v in (...) ... end @testset [CustomTestSet] [option=val ...] ["description \$v, \$w"] for v in (...), w in (...) ... end - @testset [CustomTestSet] [option=val ...] ["description \$v, \$w"] foo() + @testset [CustomTestSet] [option=val ...] ["description"] foo() + @testset let v = (...) ... end + +# With begin/end or function call -Starts a new test set, or multiple test sets if a `for` loop is provided. +When @testset is used, with begin/end or a single function call, the macro +starts a new test set in which to evaluate the given expression. If no custom testset type is given it defaults to creating a `DefaultTestSet`. `DefaultTestSet` records all the results and, if there are any `Fail`s or @@ -1262,15 +1369,25 @@ along with a summary of the test results. Any custom testset type (subtype of `AbstractTestSet`) can be given and it will also be used for any nested `@testset` invocations. The given options are only applied to the test set where they are given. The default test set type -accepts two boolean options: +accepts three boolean options: - `verbose`: if `true`, the result summary of the nested testsets is shown even -when they all pass (the default is `false`). + when they all pass (the default is `false`). - `showtiming`: if `true`, the duration of each displayed testset is shown -(the default is `true`). + (the default is `true`). +- `failfast`: if `true`, any test failure or error will cause the testset and any + child testsets to return immediately (the default is `false`). + This can also be set globally via the env var `JULIA_TEST_FAILFAST`. + +!!! compat "Julia 1.8" + `@testset foo()` requires at least Julia 1.8. + +!!! compat "Julia 1.9" + `failfast` requires at least Julia 1.9. The description string accepts interpolation from the loop indices. If no description is provided, one is constructed based on the variables. -If a function call is provided, its name will be used. Explicit description strings override this behavior. +If a function call is provided, its name will be used. +Explicit description strings override this behavior. By default the `@testset` macro will return the testset object itself, though this behavior can be customized in other testset types. If a `for` loop is used @@ -1286,7 +1403,7 @@ reproducibility in case of failure, and to allow seamless re-arrangements of `@testset`s regardless of their side-effect on the global RNG state. -# Examples +## Examples ```jldoctest; filter = r"trigonometric identities | 4 4 [0-9\\.]+s" julia> @testset "trigonometric identities" begin θ = 2/3*π @@ -1298,6 +1415,38 @@ julia> @testset "trigonometric identities" begin Test Summary: | Pass Total Time trigonometric identities | 4 4 0.2s ``` + +# `@testset for` + +When `@testset for` is used, the macro starts a new test for each iteration of +the provided loop. The semantics of each test set are otherwise identical to that +of that `begin/end` case (as if used for each loop iteration). + +# `@testset let` + +When `@testset let` is used, the macro starts a *transparent* test set with +the given object added as a context object to any failing test contained +therein. This is useful when performing a set of related tests on one larger +object and it is desirable to print this larger object when any of the +individual tests fail. Transparent test sets do not introduce additional levels +of nesting in the test set hierarchy and are passed through directly to the +parent test set (with the context object appended to any failing tests.) + +!!! compat "Julia 1.9" + `@testset let` requires at least Julia 1.9. + +## Examples +```jldoctest +julia> @testset let logi = log(im) + @test imag(logi) == π/2 + @test !iszero(real(logi)) + end +Test Failed at none:3 + Expression: !(iszero(real(logi))) + Context: logi = 0.0 + 1.5707963267948966im + +ERROR: There was an error during testing +``` """ macro testset(args...) isempty(args) && error("No arguments to @testset") @@ -1305,18 +1454,54 @@ macro testset(args...) tests = args[end] # Determine if a single block or for-loop style - if !isa(tests,Expr) || (tests.head !== :for && tests.head !== :block && tests.head != :call) + if !isa(tests,Expr) || (tests.head !== :for && tests.head !== :block && tests.head !== :call && tests.head !== :let) error("Expected function call, begin/end block or for loop as argument to @testset") end + FAIL_FAST[] = Base.get_bool_env("JULIA_TEST_FAILFAST", false) + if tests.head === :for return testset_forloop(args, tests, __source__) + elseif tests.head === :let + return testset_context(args, tests, __source__) else return testset_beginend_call(args, tests, __source__) end end +trigger_test_failure_break(@nospecialize(err)) = + ccall(:jl_test_failure_breakpoint, Cvoid, (Any,), err) + +""" +Generate the code for an `@testset` with a `let` argument. +""" +function testset_context(args, tests, source) + desc, testsettype, options = parse_testset_args(args[1:end-1]) + if desc !== nothing || testsettype !== nothing + # Reserve this syntax if we ever want to allow this, but for now, + # just do the transparent context test set. + error("@testset with a `let` argument cannot be customized") + end + + assgn = tests.args[1] + if !isa(assgn, Expr) || assgn.head !== :(=) + error("`@testset let` must have exactly one assignment") + end + assignee = assgn.args[1] + + tests.args[2] = quote + $push_testset($(ContextTestSet)($(QuoteNode(assignee)), $assignee; $options...)) + try + $(tests.args[2]) + finally + $pop_testset() + end + end + + return esc(tests) +end + """ Generate the code for a `@testset` with a function call or `begin`/`end` argument """ @@ -1342,7 +1527,11 @@ function testset_beginend_call(args, tests, source) ex = quote _check_testset($testsettype, $(QuoteNode(testsettype.args[1]))) local ret - local ts = $(testsettype)($desc; $options...) + local ts = if ($testsettype === $DefaultTestSet) && $(isa(source, LineNumberNode)) + $(testsettype)($desc; source=$(QuoteNode(source.file)), $options...) + else + $(testsettype)($desc; $options...) + end push_testset(ts) # we reproduce the logic of guardseed, but this function # cannot be used as it changes slightly the semantic of @testset, @@ -1360,7 +1549,12 @@ function testset_beginend_call(args, tests, source) err isa InterruptException && rethrow() # something in the test block threw an error. Count that as an # error in this test set - record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source)))) + trigger_test_failure_break(err) + if err isa FailFastError + get_testset_depth() > 1 ? rethrow() : failfast_print() + else + record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source)))) + end finally copy!(RNG, oldrng) Random.set_global_seed!(oldseed) @@ -1376,6 +1570,10 @@ function testset_beginend_call(args, tests, source) return ex end +function failfast_print() + printstyled("\nFail-fast enabled:"; color = Base.error_color(), bold=true) + printstyled(" Fail or Error occurred\n\n"; color = Base.error_color()) +end """ Generate the code for a `@testset` with a `for` loop argument @@ -1421,12 +1619,19 @@ function testset_forloop(args, testloop, source) # they can be handled properly by `finally` lowering. if !first_iteration pop_testset() + finish_errored = true push!(arr, finish(ts)) + finish_errored = false + # it's 1000 times faster to copy from tmprng rather than calling Random.seed! copy!(RNG, tmprng) end - ts = $(testsettype)($desc; $options...) + ts = if ($testsettype === $DefaultTestSet) && $(isa(source, LineNumberNode)) + $(testsettype)($desc; source=$(QuoteNode(source.file)), $options...) + else + $(testsettype)($desc; $options...) + end push_testset(ts) first_iteration = false try @@ -1435,13 +1640,17 @@ function testset_forloop(args, testloop, source) err isa InterruptException && rethrow() # Something in the test block threw an error. Count that as an # error in this test set - record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source)))) + trigger_test_failure_break(err) + if !isa(err, FailFastError) + record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source)))) + end end end quote local arr = Vector{Any}() local first_iteration = true local ts + local finish_errored = false local RNG = default_rng() local oldrng = copy(RNG) local oldseed = Random.GLOBAL_SEED @@ -1453,7 +1662,7 @@ function testset_forloop(args, testloop, source) end finally # Handle `return` in test body - if !first_iteration + if !first_iteration && !finish_errored pop_testset() push!(arr, finish(ts)) end @@ -1534,7 +1743,7 @@ end """ get_testset_depth() -Returns the number of active test sets, not including the default test set +Return the number of active test sets, not including the default test set """ function get_testset_depth() testsets = get(task_local_storage(), :__BASETESTNEXT__, AbstractTestSet[]) @@ -1567,7 +1776,7 @@ Int64 julia> @code_warntype f(2) MethodInstance for f(::Int64) - from f(a) in Main at none:1 + from f(a) @ Main none:1 Arguments #self#::Core.Const(f) a::Int64 @@ -1664,7 +1873,7 @@ end ambiguous_bottom=false, allowed_undefineds=nothing) -Returns a vector of `(Method,Method)` pairs of ambiguous methods +Return a vector of `(Method,Method)` pairs of ambiguous methods defined in the specified modules. Use `recursive=true` to test in all submodules. @@ -1686,16 +1895,16 @@ function detect_ambiguities(mods::Module...; ambs = Set{Tuple{Method,Method}}() mods = collect(mods)::Vector{Module} function sortdefs(m1::Method, m2::Method) - ord12 = m1.file < m2.file - if !ord12 && (m1.file == m2.file) - ord12 = m1.line < m2.line + ord12 = cmp(m1.file, m2.file) + if ord12 == 0 + ord12 = cmp(m1.line, m2.line) end - return ord12 ? (m1, m2) : (m2, m1) + return ord12 <= 0 ? (m1, m2) : (m2, m1) end function examine(mt::Core.MethodTable) for m in Base.MethodList(mt) m.sig == Tuple && continue # ignore Builtins - is_in_mods(m.module, recursive, mods) || continue + is_in_mods(parentmodule(m), recursive, mods) || continue world = Base.get_world_counter() ambig = Ref{Int32}(0) ms = Base._methods_by_ftype(m.sig, nothing, -1, world, true, Ref(typemin(UInt)), Ref(typemax(UInt)), ambig)::Vector @@ -1728,7 +1937,7 @@ function detect_ambiguities(mods::Module...; f = Base.unwrap_unionall(getfield(mod, n)) if isa(f, Module) && f !== mod && parentmodule(f) === mod && nameof(f) === n push!(work, f) - elseif isa(f, DataType) && isdefined(f.name, :mt) && f.name.module === mod && f.name.name === n && f.name.mt !== Symbol.name.mt && f.name.mt !== DataType.name.mt + elseif isa(f, DataType) && isdefined(f.name, :mt) && parentmodule(f) === mod && nameof(f) === n && f.name.mt !== Symbol.name.mt && f.name.mt !== DataType.name.mt examine(f.name.mt) end end @@ -1741,7 +1950,7 @@ end """ detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing) -Returns a vector of `Method`s which may have unbound type parameters. +Return a vector of `Method`s which may have unbound type parameters. Use `recursive=true` to test in all submodules. By default, any undefined symbols trigger a warning. This warning can @@ -1749,8 +1958,8 @@ be suppressed by supplying a collection of `GlobalRef`s for which the warning can be skipped. For example, setting ``` -allow_undefineds = Set([GlobalRef(Base, :active_repl), - GlobalRef(Base, :active_repl_backend)]) +allowed_undefineds = Set([GlobalRef(Base, :active_repl), + GlobalRef(Base, :active_repl_backend)]) ``` would suppress warnings about `Base.active_repl` and @@ -1767,14 +1976,14 @@ function detect_unbound_args(mods...; mods = collect(mods)::Vector{Module} function examine(mt::Core.MethodTable) for m in Base.MethodList(mt) - is_in_mods(m.module, recursive, mods) || continue + is_in_mods(parentmodule(m), recursive, mods) || continue has_unbound_vars(m.sig) || continue tuple_sig = Base.unwrap_unionall(m.sig)::DataType if Base.isvatuple(tuple_sig) params = tuple_sig.parameters[1:(end - 1)] tuple_sig = Base.rewrap_unionall(Tuple{params...}, m.sig) world = Base.get_world_counter() - mf = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tuple_sig, world) + mf = ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tuple_sig, nothing, world) if mf !== nothing && mf !== m && mf.sig <: tuple_sig continue end @@ -1799,7 +2008,7 @@ function detect_unbound_args(mods...; f = Base.unwrap_unionall(getfield(mod, n)) if isa(f, Module) && f !== mod && parentmodule(f) === mod && nameof(f) === n push!(work, f) - elseif isa(f, DataType) && isdefined(f.name, :mt) && f.name.module === mod && f.name.name === n && f.name.mt !== Symbol.name.mt && f.name.mt !== DataType.name.mt + elseif isa(f, DataType) && isdefined(f.name, :mt) && parentmodule(f) === mod && nameof(f) === n && f.name.mt !== Symbol.name.mt && f.name.mt !== DataType.name.mt examine(f.name.mt) end end @@ -1809,54 +2018,11 @@ function detect_unbound_args(mods...; return collect(ambs) end -# find if var will be constrained to have a definite value -# in any concrete leaftype subtype of typ -function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool) - typ === var && return true - while typ isa UnionAll - covariant && constrains_param(var, typ.var.ub, covariant) && return true - # typ.var.lb doesn't constrain var - typ = typ.body - end - if typ isa Union - # for unions, verify that both options would constrain var - ba = constrains_param(var, typ.a, covariant) - bb = constrains_param(var, typ.b, covariant) - (ba && bb) && return true - elseif typ isa DataType - # return true if any param constrains var - fc = length(typ.parameters) - if fc > 0 - if typ.name === Tuple.name - # vararg tuple needs special handling - for i in 1:(fc - 1) - p = typ.parameters[i] - constrains_param(var, p, covariant) && return true - end - lastp = typ.parameters[fc] - vararg = Base.unwrap_unionall(lastp) - if vararg isa Core.TypeofVararg && isdefined(vararg, :N) - constrains_param(var, vararg.N, covariant) && return true - # T = vararg.parameters[1] doesn't constrain var - else - constrains_param(var, lastp, covariant) && return true - end - else - for i in 1:fc - p = typ.parameters[i] - constrains_param(var, p, false) && return true - end - end - end - end - return false -end - function has_unbound_vars(@nospecialize sig) while sig isa UnionAll var = sig.var sig = sig.body - if !constrains_param(var, sig, true) + if !Core.Compiler.constrains_param(var, sig, #=covariant=#true, #=type_constrains=#true) return true end end @@ -1975,49 +2141,7 @@ function _check_bitarray_consistency(B::BitArray{N}) where N return true end -# 0.7 deprecations - -begin - approx_full(x::AbstractArray) = x - approx_full(x::Number) = x - approx_full(x) = full(x) - - function test_approx_eq(va, vb, Eps, astr, bstr) - va = approx_full(va) - vb = approx_full(vb) - la, lb = length(LinearIndices(va)), length(LinearIndices(vb)) - if la != lb - error("lengths of ", astr, " and ", bstr, " do not match: ", - "\n ", astr, " (length $la) = ", va, - "\n ", bstr, " (length $lb) = ", vb) - end - diff = real(zero(eltype(va))) - for (xa, xb) = zip(va, vb) - if isfinite(xa) && isfinite(xb) - diff = max(diff, abs(xa-xb)) - elseif !isequal(xa,xb) - error("mismatch of non-finite elements: ", - "\n ", astr, " = ", va, - "\n ", bstr, " = ", vb) - end - end - - if !isnan(Eps) && !(diff <= Eps) - sdiff = string("|", astr, " - ", bstr, "| <= ", Eps) - error("assertion failed: ", sdiff, - "\n ", astr, " = ", va, - "\n ", bstr, " = ", vb, - "\n difference = ", diff, " > ", Eps) - end - end - - array_eps(a::AbstractArray{Complex{T}}) where {T} = eps(float(maximum(x->(isfinite(x) ? abs(x) : T(NaN)), a))) - array_eps(a) = eps(float(maximum(x->(isfinite(x) ? abs(x) : oftype(x,NaN)), a))) - - test_approx_eq(va, vb, astr, bstr) = - test_approx_eq(va, vb, 1E4*length(LinearIndices(va))*max(array_eps(va), array_eps(vb)), astr, bstr) -end - include("logging.jl") +include("precompile.jl") end # module diff --git a/stdlib/Test/src/logging.jl b/stdlib/Test/src/logging.jl index d7146b121d47d..4e444874d0fb8 100644 --- a/stdlib/Test/src/logging.jl +++ b/stdlib/Test/src/logging.jl @@ -120,9 +120,9 @@ end # Log testing tools # Failure result type for log testing -mutable struct LogTestFailure <: Result +struct LogTestFailure <: Result orig_expr - source::Union{Nothing,LineNumberNode} + source::LineNumberNode patterns logs end @@ -153,8 +153,8 @@ function record(ts::DefaultTestSet, t::LogTestFailure) println() end # Hack: convert to `Fail` so that test summarization works correctly - push!(ts.results, Fail(:test, t.orig_expr, t.logs, nothing, t.source)) - t + push!(ts.results, Fail(:test, t.orig_expr, t.logs, nothing, nothing, t.source, false)) + return t end """ diff --git a/stdlib/Test/src/precompile.jl b/stdlib/Test/src/precompile.jl new file mode 100644 index 0000000000000..2cb2fb7f3f0c6 --- /dev/null +++ b/stdlib/Test/src/precompile.jl @@ -0,0 +1,9 @@ +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 diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 579b81cd5ace9..0388e2107e098 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -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", "acess", "1-element", "at index [2]"] [1][2] + @test_throws ["BoundsError", "aquire", "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\", \"acess\", \"1-element\", \"at index [2]\"]", str) + @test occursin("Expected: [\"BoundsError\", \"aquire\", \"1-element\", \"at index [2]\"]", str) @test occursin(r"Message: \"BoundsError.* 1-element.*at index \[2\]", str) end @@ -346,7 +346,7 @@ let retval_tests = @testset NoThrowTestSet begin @test Test.record(ts, pass_mock) isa Test.Pass error_mock = Test.Error(:test, 1, 2, 3, LineNumberNode(0, "An Error Mock")) @test Test.record(ts, error_mock) isa Test.Error - fail_mock = Test.Fail(:test, 1, 2, 3, LineNumberNode(0, "A Fail Mock")) + fail_mock = Test.Fail(:test, 1, 2, 3, nothing, LineNumberNode(0, "A Fail Mock"), false) @test Test.record(ts, fail_mock) isa Test.Fail broken_mock = Test.Broken(:test, LineNumberNode(0, "A Broken Mock")) @test Test.record(ts, broken_mock) isa Test.Broken @@ -409,19 +409,19 @@ end @test true @test false @test 1 == 1 - @test 2 == :foo + @test 2 === :foo @test 3 == 3 @testset "d" begin @test 4 == 4 end @testset begin - @test :blank != :notblank + @test :blank !== :notblank end end @testset "inner1" begin @test 1 == 1 @test 2 == 2 - @test 3 == :bar + @test 3 === :bar @test 4 == 4 @test_throws ErrorException 1+1 @test_throws ErrorException error() @@ -722,6 +722,115 @@ end rm(f; force=true) end +@testset "provide informative location in backtrace for test failures" begin + win2unix(filename) = replace(filename, "\\" => '/') + utils = win2unix(tempname()) + write(utils, + """ + function test_properties2(value) + @test isodd(value) + end + """) + + included = win2unix(tempname()) + write(included, + """ + @testset "Other tests" begin + @test 1 + 1 == 3 + test_properties2(2) + end + test_properties2(8) + + # Test calls to `@test` and `@testset` with no file/lineno information (__source__ == nothing). + eval(Expr(:macrocall, Symbol("@test"), nothing, :false)) + eval(Expr(:macrocall, Symbol("@testset"), nothing, "Testset without source", quote + @test false + @test error("failed") + end)) + """) + + runtests = win2unix(tempname()) + write(runtests, + """ + using Test + + include("$utils") + + function test_properties(value) + @test isodd(value) + end + + @testset "Tests" begin + test_properties(8) + @noinline test_properties(8) + test_properties2(8) + + include("$included") + end + """) + msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $runtests`), stderr=devnull), String) + msg = win2unix(msg) + regex = r"((?:Tests|Other tests|Testset without source): Test Failed (?:.|\n)*?)\n\nStacktrace:(?:.|\n)*?(?=\n(?:Tests|Other tests))" + failures = map(eachmatch(regex, msg)) do m + m = match(r"(Tests|Other tests|Testset without source): .*? at (.*?)\n Expression: (.*)(?:.|\n)*\n+Stacktrace:\n((?:.|\n)*)", m.match) + (; testset = m[1], source = m[2], ex = m[3], stacktrace = m[4]) + end + @test length(failures) == 8 # 8 failed tests + @test count(contains("Error During Test"), split(msg, '\n')) == 1 # 1 error + test_properties_macro_source = runtests * ":6" + test_properties2_macro_source = utils * ":2" + + fail = failures[1]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 6 + @test fail.testset == "Tests" && fail.source == test_properties_macro_source && fail.ex == "isodd(value)" + @test count(contains(runtests * ":10"), lines) == 2 # @testset + test + + fail = failures[2]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 6 + @test fail.testset == "Tests" && fail.source == test_properties_macro_source && fail.ex == "isodd(value)" + @test count(contains(runtests * ":10"), lines) == 1 # @testset + @test count(contains(runtests * ":11"), lines) == 1 # test + + fail = failures[3]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 6 + @test fail.testset == "Tests" && fail.source == test_properties2_macro_source && fail.ex == "isodd(value)" + @test count(contains(runtests * ":10"), lines) == 1 # @testset + @test count(contains(runtests * ":12"), lines) == 1 # test + + fail = failures[4]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 5 + @test fail.testset == "Other tests" && fail.source == included * ":2" && fail.ex == "1 + 1 == 3" + @test count(contains(included * ":2"), lines) == 2 # @testset + test + @test count(contains(runtests * ":10"), lines) == 0 # @testset (stop at the innermost testset) + + fail = failures[5]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 6 + @test fail.testset == "Other tests" && fail.source == test_properties2_macro_source && fail.ex == "isodd(value)" + @test count(contains(included * ":2"), lines) == 1 # @testset + @test count(contains(included * ":3"), lines) == 1 # test + @test count(contains(runtests * ":10"), lines) == 0 # @testset (stop at the innermost testset) + + fail = failures[6]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 8 + @test fail.testset == "Tests" && fail.source == test_properties2_macro_source && fail.ex == "isodd(value)" + @test count(contains(runtests * ":10"), lines) == 1 # @testset + @test count(contains(runtests * ":14"), lines) == 1 # include + @test count(contains(included * ":5"), lines) == 1 # test + + fail = failures[7]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 9 + @test fail.testset == "Tests" && fail.source == "none:0" && fail.ex == "false" + @test count(contains(runtests * ":10"), lines) == 1 # @testset + @test count(contains(runtests * ":14"), lines) == 1 # include + @test count(contains(included * ":8"), lines) == 1 # test + + fail = failures[8]; lines = split(fail.stacktrace, '\n') + @test length(lines)/2 ≤ 5 + @test fail.testset == "Testset without source" && fail.source == included * ":10" && fail.ex == "false" + @test count(contains(included * ":10"), lines) == 2 # @testset + test + @test count(contains(runtests * ":10"), lines) == 0 # @testset (stop at the innermost testset) +end + let io = IOBuffer() exc = Test.TestSetException(1,2,3,4,Vector{Union{Test.Error, Test.Fail}}()) Base.showerror(io, exc, backtrace()) @@ -1160,6 +1269,111 @@ end end end +@testset "failfast option" begin + @testset "non failfast (default)" begin + expected = r""" + Test Summary: | Pass Fail Error Total Time + Foo | 1 2 1 4 \s*\d*.\ds + Bar | 1 1 2 \s*\d*.\ds + """ + + mktemp() do f, _ + write(f, + """ + using Test + + @testset "Foo" begin + @test false + @test error() + @testset "Bar" begin + @test false + @test true + end + end + """) + cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` + result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) + @test occursin(expected, result) + end + end + @testset "failfast" begin + expected = r""" + Test Summary: | Fail Total Time + Foo | 1 1 \s*\d*.\ds + """ + + mktemp() do f, _ + write(f, + """ + using Test + + @testset "Foo" failfast=true begin + @test false + @test error() + @testset "Bar" begin + @test false + @test true + end + end + """) + cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` + result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) + @test occursin(expected, result) + end + end + @testset "failfast passes to child testsets" begin + expected = r""" + Test Summary: | Fail Total Time + PackageName | 1 1 \s*\d*.\ds + 1 | 1 1 \s*\d*.\ds + """ + + mktemp() do f, _ + write(f, + """ + using Test + + @testset "Foo" failfast=true begin + @testset "1" begin + @test false + end + @testset "2" begin + @test true + end + end + """) + cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` + result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) + @test occursin(expected, result) + end + end + @testset "failfast via env var" begin + expected = r""" + Test Summary: | Fail Total Time + Foo | 1 1 \s*\d*.\ds + """ + + mktemp() do f, _ + write(f, + """ + using Test + ENV["JULIA_TEST_FAILFAST"] = true + @testset "Foo" begin + @test false + @test error() + @testset "Bar" begin + @test false + @test true + end + end + """) + cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` + result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) + @test occursin(expected, result) + end + end +end + # Non-booleans in @test (#35888) struct T35888 end Base.isequal(::T35888, ::T35888) = T35888() @@ -1282,12 +1496,12 @@ Test.finish(ts::PassInformationTestSet) = ts end test_line_number = (@__LINE__) - 3 test_throws_line_number = (@__LINE__) - 3 - @test ts.results[1].test_type == :test + @test ts.results[1].test_type === :test @test ts.results[1].orig_expr == :(1 == 1) @test ts.results[1].data == Expr(:comparison, 1, :(==), 1) @test ts.results[1].value == true @test ts.results[1].source == LineNumberNode(test_line_number, @__FILE__) - @test ts.results[2].test_type == :test_throws + @test ts.results[2].test_type === :test_throws @test ts.results[2].orig_expr == :(throw(ErrorException("Msg"))) @test ts.results[2].data == ErrorException @test ts.results[2].value == ErrorException("Msg") diff --git a/stdlib/Unicode/src/Unicode.jl b/stdlib/Unicode/src/Unicode.jl index e31f7ee1e27f2..58b9ab41b790a 100644 --- a/stdlib/Unicode/src/Unicode.jl +++ b/stdlib/Unicode/src/Unicode.jl @@ -120,7 +120,7 @@ normalize(s::AbstractString; kwargs...) = Base.Unicode.normalize(s; kwargs...) """ Unicode.isassigned(c) -> Bool -Returns `true` if the given char or integer is an assigned Unicode code point. +Return `true` if the given char or integer is an assigned Unicode code point. # Examples ```jldoctest @@ -136,13 +136,76 @@ isassigned(c) = Base.Unicode.isassigned(c) """ graphemes(s::AbstractString) -> GraphemeIterator -Returns an iterator over substrings of `s` that correspond to the extended graphemes in the +Return an iterator over substrings of `s` that correspond to the extended graphemes in the string, as defined by Unicode UAX #29. (Roughly, these are what users would perceive as single characters, even though they may contain more than one codepoint; for example a letter combined with an accent mark is a single grapheme.) """ graphemes(s::AbstractString) = Base.Unicode.GraphemeIterator{typeof(s)}(s) +""" + graphemes(s::AbstractString, m:n) -> SubString + +Returns a [`SubString`](@ref) of `s` consisting of the `m`-th +through `n`-th graphemes of the string `s`, where the second +argument `m:n` is an integer-valued [`AbstractUnitRange`](@ref). + +Loosely speaking, this corresponds to the `m:n`-th user-perceived +"characters" in the string. For example: + +```jldoctest +julia> s = graphemes("exposé", 3:6) +"posé" + +julia> collect(s) +5-element Vector{Char}: + 'p': ASCII/Unicode U+0070 (category Ll: Letter, lowercase) + 'o': ASCII/Unicode U+006F (category Ll: Letter, lowercase) + 's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase) + 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase) + '́': Unicode U+0301 (category Mn: Mark, nonspacing) +``` +This consists of the 3rd to *7th* codepoints ([`Char`](@ref)s) in `"exposé"`, +because the grapheme `"é"` is actually *two* Unicode codepoints +(an `'e'` followed by an acute-accent combining character U+0301). + +Because finding grapheme boundaries requires iteration over the +string contents, the `graphemes(s, m:n)` function requires time +proportional to the length of the string (number of codepoints) +before the end of the substring. + +!!! compat "Julia 1.9" + The `m:n` argument of `graphemes` requires Julia 1.9. +""" +function graphemes(s::AbstractString, r::AbstractUnitRange{<:Integer}) + m, n = Int(first(r)), Int(last(r)) + m > 0 || throw(ArgumentError("starting index $m is not ≥ 1")) + n < m && return @view s[1:0] + c0 = eltype(s)(0x00000000) + state = Ref{Int32}(0) + count = 0 + i, iprev, ilast = 1, 1, lastindex(s) + # find the start of the m-th grapheme + while i ≤ ilast && count < m + @inbounds c = s[i] + count += Base.Unicode.isgraphemebreak!(state, c0, c) + c0 = c + i, iprev = nextind(s, i), i + end + start = iprev + count < m && throw(BoundsError(s, i)) + # find the end of the n-th grapheme + while i ≤ ilast + @inbounds c = s[i] + count += Base.Unicode.isgraphemebreak!(state, c0, c) + count > n && break + c0 = c + i, iprev = nextind(s, i), i + end + count < n && throw(BoundsError(s, i)) + return @view s[start:iprev] +end + using Base.Unicode: utf8proc_error, UTF8PROC_DECOMPOSE, UTF8PROC_CASEFOLD, UTF8PROC_STRIPMARK function _decompose_char!(codepoint::Union{Integer,Char}, dest::Vector{UInt32}, options::Integer) diff --git a/stdlib/Unicode/test/runtests.jl b/stdlib/Unicode/test/runtests.jl index a4faac2bd3ba9..5c5a75b33e363 100644 --- a/stdlib/Unicode/test/runtests.jl +++ b/stdlib/Unicode/test/runtests.jl @@ -271,6 +271,16 @@ end @test Base.Unicode.isgraphemebreak('α', 'β') @test !Base.Unicode.isgraphemebreak('α', '\u0302') + + for pre in ("","ä"), post in ("","x̂") + prelen = length(graphemes(pre)) + @test graphemes(pre * "öü" * post, (1:2) .+ prelen) == "öü" + @test graphemes(pre * "ö" * post, (1:1) .+ prelen) == "ö" + end + @test graphemes("äöüx", 6:5)::SubString{String} == "" + @test_throws BoundsError graphemes("äöüx", 2:5) + @test_throws BoundsError graphemes("äöüx", 5:5) + @test_throws ArgumentError graphemes("äöüx", 0:1) end @testset "#3721, #6939 up-to-date character widths" begin @@ -423,6 +433,28 @@ end @test prod(["*" for i in 1:0]) == "" end +@testset "Grapheme breaks and iterator" begin + u1 = reinterpret(Char, UInt32(0xc0) << 24) + u2 = reinterpret(Char, UInt32(0xc1) << 24) + + overlong_uint = UInt32(0xc0) << 24 + overlong_char = reinterpret(Char, overlong_uint) + + state = Ref(Int32(1)) + @test Base.Unicode.isgraphemebreak(u1, u2) + @test Base.Unicode.isgraphemebreak!(state, u1, u2) + @test state[] == 0 + + @test_throws( + ErrorException("An unknown error occurred while processing UTF-8 data."), + Base.Unicode.utf8proc_error(2) + ) + gi = Base.Unicode.graphemes("This is a string") + @test gi isa Base.Unicode.GraphemeIterator{String} + @test Base.Unicode.isvalid(Char, 'c') + @test !Base.Unicode.isvalid(Char, overlong_char) +end + @testset "Unicode equivalence" begin @test isequal_normalized("no\u00EBl", "noe\u0308l") @test !isequal_normalized("no\u00EBl", "noe\u0308l ") diff --git a/stdlib/Zlib_jll/Project.toml b/stdlib/Zlib_jll/Project.toml index cafaf9c1b577c..575863062d8bb 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.12+1" +version = "1.2.13+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 c05e26c4c6993..ea381b8b0683c 100644 --- a/stdlib/Zlib_jll/src/Zlib_jll.jl +++ b/stdlib/Zlib_jll/src/Zlib_jll.jl @@ -13,9 +13,9 @@ export libz # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libz_handle = C_NULL -libz_path = "" +artifact_dir::String = "" +libz_handle::Ptr{Cvoid} = C_NULL +libz_path::String = "" if Sys.iswindows() const libz = "libz.dll" diff --git a/stdlib/Zlib_jll/test/runtests.jl b/stdlib/Zlib_jll/test/runtests.jl index e6adc6b7c951f..f04f9c70a7054 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.11" + @test VersionNumber(unsafe_string(ccall((:zlibVersion, libz), Cstring, ()))) == v"1.2.13" end diff --git a/stdlib/dSFMT_jll/src/dSFMT_jll.jl b/stdlib/dSFMT_jll/src/dSFMT_jll.jl index f1d6d019faf59..35ada23778a94 100644 --- a/stdlib/dSFMT_jll/src/dSFMT_jll.jl +++ b/stdlib/dSFMT_jll/src/dSFMT_jll.jl @@ -14,9 +14,9 @@ export libdSFMT # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libdSFMT_handle = C_NULL -libdSFMT_path = "" +artifact_dir::String = "" +libdSFMT_handle::Ptr{Cvoid} = C_NULL +libdSFMT_path::String = "" if Sys.iswindows() const libdSFMT = "libdSFMT.dll" diff --git a/stdlib/libLLVM_jll/Project.toml b/stdlib/libLLVM_jll/Project.toml index 64de5adc434ba..87519e5a824b0 100644 --- a/stdlib/libLLVM_jll/Project.toml +++ b/stdlib/libLLVM_jll/Project.toml @@ -1,6 +1,6 @@ name = "libLLVM_jll" uuid = "8f36deef-c2a5-5394-99ed-8e07531fb29a" -version = "13.0.1+0" +version = "15.0.7+5" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/libLLVM_jll/src/libLLVM_jll.jl b/stdlib/libLLVM_jll/src/libLLVM_jll.jl index 09e01207ec9d6..3140dc3989a72 100644 --- a/stdlib/libLLVM_jll/src/libLLVM_jll.jl +++ b/stdlib/libLLVM_jll/src/libLLVM_jll.jl @@ -14,16 +14,16 @@ export libLLVM # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libLLVM_handle = C_NULL -libLLVM_path = "" +artifact_dir::String = "" +libLLVM_handle::Ptr{Cvoid} = C_NULL +libLLVM_path::String = "" if Sys.iswindows() - const libLLVM = "libLLVM-13jl.dll" + const libLLVM = "$(Base.libllvm_name).dll" elseif Sys.isapple() const libLLVM = "@rpath/libLLVM.dylib" else - const libLLVM = "libLLVM-13jl.so" + const libLLVM = "$(Base.libllvm_name).so" end function __init__() diff --git a/stdlib/libblastrampoline_jll/Project.toml b/stdlib/libblastrampoline_jll/Project.toml index 26c67dae8dffd..4699baa7dad23 100644 --- a/stdlib/libblastrampoline_jll/Project.toml +++ b/stdlib/libblastrampoline_jll/Project.toml @@ -1,14 +1,13 @@ name = "libblastrampoline_jll" uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.0.1+0" +version = "5.8.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" -OpenBLAS_jll = "4536629a-c528-5b80-bd46-f80d51c5b363" [compat] -julia = "1.8" +julia = "1.10" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl index 77882067ed633..49e7932a6b701 100644 --- a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl +++ b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl @@ -14,16 +14,17 @@ export libblastrampoline # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libblastrampoline_handle = C_NULL -libblastrampoline_path = "" +artifact_dir::String = "" +libblastrampoline_handle::Ptr{Cvoid} = C_NULL +libblastrampoline_path::String = "" +# NOTE: keep in sync with `Base.libblas_name` and `Base.liblapack_name`. const libblastrampoline = if Sys.iswindows() - "libblastrampoline.dll" + "libblastrampoline-5.dll" elseif Sys.isapple() - "@rpath/libblastrampoline.dylib" + "@rpath/libblastrampoline.5.dylib" else - "libblastrampoline.so" + "libblastrampoline.so.5" end function __init__() diff --git a/stdlib/libblastrampoline_jll/test/runtests.jl b/stdlib/libblastrampoline_jll/test/runtests.jl index 80095e70f0c76..e64fc328771be 100644 --- a/stdlib/libblastrampoline_jll/test/runtests.jl +++ b/stdlib/libblastrampoline_jll/test/runtests.jl @@ -3,5 +3,5 @@ using Test, Libdl, libblastrampoline_jll @testset "libblastrampoline_jll" begin - @test isa(Libdl.dlsym(Libdl.dlopen(:libblastrampoline), :dgemm_64_), Ptr{Nothing}) + @test isa(Libdl.dlsym(libblastrampoline_jll.libblastrampoline_handle, :dgemm_64_), Ptr{Nothing}) end diff --git a/stdlib/nghttp2_jll/Project.toml b/stdlib/nghttp2_jll/Project.toml index 3051afe57d23a..b8a9394c50e37 100644 --- a/stdlib/nghttp2_jll/Project.toml +++ b/stdlib/nghttp2_jll/Project.toml @@ -1,6 +1,6 @@ name = "nghttp2_jll" uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.41.0+1" +version = "1.52.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/nghttp2_jll/src/nghttp2_jll.jl b/stdlib/nghttp2_jll/src/nghttp2_jll.jl index 09af350636943..76e8d3582c402 100644 --- a/stdlib/nghttp2_jll/src/nghttp2_jll.jl +++ b/stdlib/nghttp2_jll/src/nghttp2_jll.jl @@ -13,9 +13,9 @@ export libnghttp2 # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -libnghttp2_handle = C_NULL -libnghttp2_path = "" +artifact_dir::String = "" +libnghttp2_handle::Ptr{Cvoid} = C_NULL +libnghttp2_path::String = "" if Sys.iswindows() const libnghttp2 = "libnghttp2-14.dll" diff --git a/stdlib/nghttp2_jll/test/runtests.jl b/stdlib/nghttp2_jll/test/runtests.jl index 07e0a3b8c7730..2f9af6d6a3338 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.41.0" + @test VersionNumber(unsafe_string(info.version_str)) == v"1.52.0" end diff --git a/stdlib/p7zip_jll/Project.toml b/stdlib/p7zip_jll/Project.toml index 75e04b6362fdf..4c9bf62ad7ec1 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 = "16.2.1+1" +version = "17.4.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 99b346017ad97..01f26de936e78 100644 --- a/stdlib/p7zip_jll/src/p7zip_jll.jl +++ b/stdlib/p7zip_jll/src/p7zip_jll.jl @@ -13,8 +13,8 @@ export p7zip # These get calculated in __init__() const PATH = Ref("") const LIBPATH = Ref("") -artifact_dir = "" -p7zip_path = "" +artifact_dir::String = "" +p7zip_path::String = "" if Sys.iswindows() const p7zip_exe = "7z.exe" else @@ -35,7 +35,7 @@ else const pathsep = ':' end -function adjust_ENV!(env::Dict, PATH::String, LIBPATH::String, adjust_PATH::Bool, adjust_LIBPATH::Bool) +function adjust_ENV!(env::Dict{keytype(Base.EnvDict),valtype(Base.EnvDict)}, PATH::String, LIBPATH::String, adjust_PATH::Bool, adjust_LIBPATH::Bool) if adjust_LIBPATH LIBPATH_base = get(env, LIBPATH_env, expanduser(LIBPATH_default)) if !isempty(LIBPATH_base) @@ -69,8 +69,8 @@ end function init_p7zip_path() # Prefer our own bundled p7zip, but if we don't have one, pick it up off of the PATH - # If this is an in-tree build, `7z` will live in `bin`. Otherwise, it'll be in `libexec` - for bundled_p7zip_path in (joinpath(Sys.BINDIR, Base.LIBEXECDIR, p7zip_exe), + # If this is an in-tree build, `7z` will live in `bindir`. Otherwise, it'll be in `private_libexecdir` + for bundled_p7zip_path in (joinpath(Sys.BINDIR, Base.PRIVATE_LIBEXECDIR, p7zip_exe), joinpath(Sys.BINDIR, p7zip_exe)) if isfile(bundled_p7zip_path) global p7zip_path = abspath(bundled_p7zip_path) diff --git a/sysimage.mk b/sysimage.mk index de5c3e22f253a..7ed61d471a153 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -10,7 +10,7 @@ sysimg-bc: $(build_private_libdir)/sys-bc.a sysimg-release: $(build_private_libdir)/sys.$(SHLIB_EXT) sysimg-debug: $(build_private_libdir)/sys-debug.$(SHLIB_EXT) -VERSDIR := v`cut -d. -f1-2 < $(JULIAHOME)/VERSION` +VERSDIR := v$(shell cut -d. -f1-2 < $(JULIAHOME)/VERSION) $(build_private_libdir)/%.$(SHLIB_EXT): $(build_private_libdir)/%-o.a @$(call PRINT_LINK, $(CXX) $(LDFLAGS) -shared $(fPIC) -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -o $@ \ @@ -54,13 +54,13 @@ COMPILER_SRCS += $(shell find $(JULIAHOME)/base/compiler -name \*.jl) 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 $(shell find $(build_datarootdir)/julia/stdlib/$(VERSDIR)/*/src -name \*.jl) \ - $(build_prefix)/manifest/Pkg + $(wildcard $(build_prefix)/manifest/$(VERSDIR)/*) RELBUILDROOT := $(call rel_path,$(JULIAHOME)/base,$(BUILDROOT)/base)/ # <-- make sure this always has a trailing slash $(build_private_libdir)/corecompiler.ji: $(COMPILER_SRCS) @$(call PRINT_JULIA, cd $(JULIAHOME)/base && \ $(call spawn,$(JULIA_EXECUTABLE)) -C "$(JULIA_CPU_TARGET)" --output-ji $(call cygpath_w,$@).tmp \ - --startup-file=no --warn-overwrite=yes -g0 -O0 compiler/compiler.jl) + --startup-file=no --warn-overwrite=yes -g$(BOOTSTRAP_DEBUG_LEVEL) -O0 compiler/compiler.jl) @mv $@.tmp $@ $(build_private_libdir)/sys.ji: $(build_private_libdir)/corecompiler.ji $(JULIAHOME)/VERSION $(BASE_SRCS) $(STDLIB_SRCS) @@ -74,9 +74,10 @@ $(build_private_libdir)/sys.ji: $(build_private_libdir)/corecompiler.ji $(JULIAH @mv $@.tmp $@ define sysimg_builder -$$(build_private_libdir)/sys$1-o.a $$(build_private_libdir)/sys$1-bc.a : $$(build_private_libdir)/sys$1-%.a : $$(build_private_libdir)/sys.ji +$$(build_private_libdir)/sys$1-o.a $$(build_private_libdir)/sys$1-bc.a : $$(build_private_libdir)/sys$1-%.a : $$(build_private_libdir)/sys.ji $$(JULIAHOME)/contrib/generate_precompile.jl @$$(call PRINT_JULIA, cd $$(JULIAHOME)/base && \ if ! JULIA_BINDIR=$$(call cygpath_w,$(build_bindir)) WINEPATH="$$(call cygpath_w,$$(build_bindir));$$$$WINEPATH" \ + JULIA_NUM_THREADS=1 \ $$(call spawn, $3) $2 -C "$$(JULIA_CPU_TARGET)" --output-$$* $$(call cygpath_w,$$@).tmp $$(JULIA_SYSIMG_BUILD_FLAGS) \ --startup-file=no --warn-overwrite=yes --sysimage $$(call cygpath_w,$$<) $$(call cygpath_w,$$(JULIAHOME)/contrib/generate_precompile.jl) $(JULIA_PRECOMPILE); then \ echo '*** This error is usually fixed by running `make clean`. If the error persists$$(COMMA) try `make cleanall`. ***'; \ diff --git a/test/TestPkg/Project.toml b/test/TestPkg/Project.toml index 0786722612bf3..0dfe48c3e9acb 100644 --- a/test/TestPkg/Project.toml +++ b/test/TestPkg/Project.toml @@ -1,6 +1,6 @@ name = "TestPkg" uuid = "69145d58-7df6-11e8-0660-cf7622583916" - +version = "1.2.3" [deps] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 060f1ffa8b8cb..c5ff97deb6777 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -494,9 +494,9 @@ function test_primitives(::Type{T}, shape, ::Type{TestAbstractArray}) where T # isassigned(a::AbstractArray, i::Int...) j = rand(1:length(B)) - @test isassigned(B, j) == true + @test isassigned(B, j) if T == T24Linear - @test isassigned(B, length(B) + 1) == false + @test !isassigned(B, length(B) + 1) end # reshape(a::AbstractArray, dims::Dims) @@ -520,9 +520,6 @@ function test_primitives(::Type{T}, shape, ::Type{TestAbstractArray}) where T @test convert(Matrix, Y) == Y @test convert(Matrix, view(Y, 1:2, 1:2)) == Y @test_throws MethodError convert(Matrix, X) - - # convert(::Type{Union{}}, A::AbstractMatrix) - @test_throws MethodError convert(Union{}, X) end mutable struct TestThrowNoGetindex{T} <: AbstractVector{T} end @@ -732,6 +729,11 @@ function test_cat(::Type{TestAbstractArray}) @test @inferred(cat(As...; dims=Val(3))) == zeros(2, 2, 2) cat3v(As) = cat(As...; dims=Val(3)) @test @inferred(cat3v(As)) == zeros(2, 2, 2) + @test @inferred(cat(As...; dims=Val((1,2)))) == zeros(4, 4) + + r = rand(Float32, 56, 56, 64, 1); + f(r) = cat(r, r, dims=(3,)) + @inferred f(r); end function test_ind2sub(::Type{TestAbstractArray}) @@ -988,9 +990,9 @@ end end i = CartesianIndex(17,-2) - @test CR .+ i === i .+ CR === CartesianIndices((19:21, -1:3)) - @test CR .- i === CartesianIndices((-15:-13, 3:7)) - @test collect(i .- CR) == Ref(i) .- collect(CR) + @test CR .+ i === i .+ CR === CartesianIndices((19:21, -1:3)) == collect(CR) .+ i + @test CR .- i === CartesianIndices((-15:-13, 3:7)) == collect(CR) .- i + @test collect(i .- CR) == Ref(i) .- collect(CR) == i .- collect(CR) end @testset "issue #25770" begin @@ -1155,8 +1157,9 @@ Base.unsafe_convert(::Type{Ptr{T}}, S::Strider{T}) where {T} = pointer(S.data, S Ps = Strider{Int, 3}(vec(A), 1, strides(A)[collect(perm)], sz[collect(perm)]) @test pointer(Ap) == pointer(Sp) == pointer(Ps) for i in 1:length(Ap) - # This is intentionally disabled due to ambiguity - @test_broken pointer(Ap, i) == pointer(Sp, i) == pointer(Ps, i) + # This is intentionally disabled due to ambiguity. See `Base.pointer(A::PermutedDimsArray, i::Integer)`. + # But only evaluate one iteration as broken to reduce test report noise + i == 1 && @test_broken pointer(Ap, i) == pointer(Sp, i) == pointer(Ps, i) @test P[i] == Ap[i] == Sp[i] == Ps[i] end Pv = view(P, idxs[collect(perm)]...) @@ -1175,8 +1178,9 @@ Base.unsafe_convert(::Type{Ptr{T}}, S::Strider{T}) where {T} = pointer(S.data, S Svp = Base.PermutedDimsArray(Sv, perm) @test pointer(Avp) == pointer(Svp) for i in 1:length(Avp) - # This is intentionally disabled due to ambiguity - @test_broken pointer(Avp, i) == pointer(Svp, i) + # This is intentionally disabled due to ambiguity. See `Base.pointer(A::PermutedDimsArray, i::Integer)` + # But only evaluate one iteration as broken to reduce test report noise + i == 1 && @test_broken pointer(Avp, i) == pointer(Svp, i) @test Ip[i] == Vp[i] == Avp[i] == Svp[i] end end @@ -1215,8 +1219,9 @@ end Ps = Strider{Int, 2}(vec(A), 1, strides(A)[collect(perm)], sz[collect(perm)]) @test pointer(Ap) == pointer(Sp) == pointer(Ps) == pointer(At) == pointer(Aa) for i in 1:length(Ap) - # This is intentionally disabled due to ambiguity - @test_broken pointer(Ap, i) == pointer(Sp, i) == pointer(Ps, i) == pointer(At, i) == pointer(Aa, i) == pointer(St, i) == pointer(Sa, i) + # This is intentionally disabled due to ambiguity. See `Base.pointer(A::PermutedDimsArray, i::Integer)` + # But only evaluate one iteration as broken to reduce test report noise + i == 1 && @test_broken pointer(Ap, i) == pointer(Sp, i) == pointer(Ps, i) == pointer(At, i) == pointer(Aa, i) == pointer(St, i) == pointer(Sa, i) @test pointer(Ps, i) == pointer(At, i) == pointer(Aa, i) == pointer(St, i) == pointer(Sa, i) @test P[i] == Ap[i] == Sp[i] == Ps[i] == At[i] == Aa[i] == St[i] == Sa[i] end @@ -1242,8 +1247,9 @@ end Svp = Base.PermutedDimsArray(Sv, perm) @test pointer(Avp) == pointer(Svp) == pointer(Avt) == pointer(Ava) for i in 1:length(Avp) - # This is intentionally disabled due to ambiguity - @test_broken pointer(Avp, i) == pointer(Svp, i) == pointer(Avt, i) == pointer(Ava, i) == pointer(Svt, i) == pointer(Sva, i) + # This is intentionally disabled due to ambiguity. See `Base.pointer(A::PermutedDimsArray, i::Integer)` + # But only evaluate one iteration as broken to reduce test report noise + i == 1 && @test_broken pointer(Avp, i) == pointer(Svp, i) == pointer(Avt, i) == pointer(Ava, i) == pointer(Svt, i) == pointer(Sva, i) @test pointer(Avt, i) == pointer(Ava, i) == pointer(Svt, i) == pointer(Sva, i) @test Vp[i] == Avp[i] == Svp[i] == Avt[i] == Ava[i] == Svt[i] == Sva[i] end @@ -1263,6 +1269,13 @@ end @test last(itr, 25) !== itr @test last(itr, 1) == [itr[end]] @test_throws ArgumentError last(itr, -6) + + @testset "overflow (issue #45842)" begin + @test_throws OverflowError first(typemin(Int):typemax(Int), 10) + @test first(2:typemax(Int)-1, typemax(Int)÷2) === 2:((typemax(Int)÷2) + 1) + @test last(2:typemax(Int), typemax(Int)÷2) === + range(stop=typemax(Int), length=typemax(Int)÷2) + end end @testset "Base.rest" begin @@ -1464,9 +1477,7 @@ using Base: typed_hvncat v1 = zeros(Int, 0, 0, 0) for v2 ∈ (1, [1]) for v3 ∈ (2, [2]) - # current behavior, not potentially dangerous. - # should throw error like above loop - @test [v1 ;;; v2 v3] == [v2 v3;;;] + @test_throws ArgumentError [v1 ;;; v2 v3] @test_throws ArgumentError [v1 ;;; v2] @test_throws ArgumentError [v1 v1 ;;; v2 v3] end @@ -1538,6 +1549,142 @@ using Base: typed_hvncat # Issue 43933 - semicolon precedence mistake should produce an error @test_throws ArgumentError [[1 1]; 2 ;; 3 ; [3 4]] @test_throws ArgumentError [[1 ;;; 1]; 2 ;;; 3 ; [3 ;;; 4]] + + @test [[1 2; 3 4] [5; 6]; [7 8] 9;;;] == [1 2 5; 3 4 6; 7 8 9;;;] + + #45461, #46133 - ensure non-numeric types do not error + @test [1;;; 2;;; nothing;;; 4] == reshape([1; 2; nothing; 4], (1, 1, 4)) + @test [1 2;;; nothing 4] == reshape([1; 2; nothing; 4], (1, 2, 2)) + @test [[1 2];;; nothing 4] == reshape([1; 2; nothing; 4], (1, 2, 2)) + @test ["A";;"B";;"C";;"D"] == ["A" "B" "C" "D"] + @test ["A";"B";;"C";"D"] == ["A" "C"; "B" "D"] + @test [["A";"B"];;"C";"D"] == ["A" "C"; "B" "D"] +end + +@testset "stack" begin + # Basics + for args in ([[1, 2]], [1:2, 3:4], [[1 2; 3 4], [5 6; 7 8]], + AbstractVector[1:2, [3.5, 4.5]], Vector[[1,2], [3im, 4im]], + [[1:2, 3:4], [5:6, 7:8]], [fill(1), fill(2)]) + X = stack(args) + Y = cat(args...; dims=ndims(args[1])+1) + @test X == Y + @test typeof(X) === typeof(Y) + + X2 = stack(x for x in args) + @test X2 == Y + @test typeof(X2) === typeof(Y) + + X3 = stack(x for x in args if true) + @test X3 == Y + @test typeof(X3) === typeof(Y) + + if isconcretetype(eltype(args)) + @inferred stack(args) + @inferred stack(x for x in args) + end + end + + # Higher dims + @test size(stack([rand(2,3) for _ in 1:4, _ in 1:5])) == (2,3,4,5) + @test size(stack(rand(2,3) for _ in 1:4, _ in 1:5)) == (2,3,4,5) + @test size(stack(rand(2,3) for _ in 1:4, _ in 1:5 if true)) == (2, 3, 20) + @test size(stack([rand(2,3) for _ in 1:4, _ in 1:5]; dims=1)) == (20, 2, 3) + @test size(stack(rand(2,3) for _ in 1:4, _ in 1:5; dims=2)) == (2, 20, 3) + + # Tuples + @test stack([(1,2), (3,4)]) == [1 3; 2 4] + @test stack(((1,2), (3,4))) == [1 3; 2 4] + @test stack(Any[(1,2), (3,4)]) == [1 3; 2 4] + @test stack([(1,2), (3,4)]; dims=1) == [1 2; 3 4] + @test stack(((1,2), (3,4)); dims=1) == [1 2; 3 4] + @test stack(Any[(1,2), (3,4)]; dims=1) == [1 2; 3 4] + @test size(@inferred stack(Iterators.product(1:3, 1:4))) == (2,3,4) + @test @inferred(stack([('a', 'b'), ('c', 'd')])) == ['a' 'c'; 'b' 'd'] + @test @inferred(stack([(1,2+3im), (4, 5+6im)])) isa Matrix{Number} + + # stack(f, iter) + @test @inferred(stack(x -> [x, 2x], 3:5)) == [3 4 5; 6 8 10] + @test @inferred(stack(x -> x*x'/2, [1:2, 3:4])) == [0.5 1.0; 1.0 2.0;;; 4.5 6.0; 6.0 8.0] + @test @inferred(stack(*, [1:2, 3:4], 5:6)) == [5 18; 10 24] + + # Iterators + @test stack([(a=1,b=2), (a=3,b=4)]) == [1 3; 2 4] + @test stack([(a=1,b=2), (c=3,d=4)]) == [1 3; 2 4] + @test stack([(a=1,b=2), (c=3,d=4)]; dims=1) == [1 2; 3 4] + @test stack([(a=1,b=2), (c=3,d=4)]; dims=2) == [1 3; 2 4] + @test stack((x/y for x in 1:3) for y in 4:5) == (1:3) ./ (4:5)' + @test stack((x/y for x in 1:3) for y in 4:5; dims=1) == (1:3)' ./ (4:5) + + # Exotic + ips = ((Iterators.product([i,i^2], [2i,3i,4i], 1:4)) for i in 1:5) + @test size(stack(ips)) == (2, 3, 4, 5) + @test stack(ips) == cat(collect.(ips)...; dims=4) + ips_cat2 = cat(reshape.(collect.(ips), Ref((2,1,3,4)))...; dims=2) + @test stack(ips; dims=2) == ips_cat2 + @test stack(collect.(ips); dims=2) == ips_cat2 + ips_cat3 = cat(reshape.(collect.(ips), Ref((2,3,1,4)))...; dims=3) + @test stack(ips; dims=3) == ips_cat3 # path for non-array accumulation on non-final dims + @test stack(collect, ips; dims=3) == ips_cat3 # ... and for array accumulation + @test stack(collect.(ips); dims=3) == ips_cat3 + + # Trivial, because numbers are iterable: + @test stack(abs2, 1:3) == [1, 4, 9] == collect(Iterators.flatten(abs2(x) for x in 1:3)) + + # Allocation tests + xv = [rand(10) for _ in 1:100] + xt = Tuple.(xv) + for dims in (1, 2, :) + @test stack(xv; dims) == stack(xt; dims) + @test_skip 9000 > @allocated stack(xv; dims) + @test_skip 9000 > @allocated stack(xt; dims) + end + xr = (reshape(1:1000,10,10,10) for _ = 1:1000) + for dims in (1, 2, 3, :) + stack(xr; dims) + @test_skip 8.1e6 > @allocated stack(xr; dims) + end + + # Mismatched sizes + @test_throws DimensionMismatch stack([1:2, 1:3]) + @test_throws DimensionMismatch stack([1:2, 1:3]; dims=1) + @test_throws DimensionMismatch stack([1:2, 1:3]; dims=2) + @test_throws DimensionMismatch stack([(1,2), (3,4,5)]) + @test_throws DimensionMismatch stack([(1,2), (3,4,5)]; dims=1) + @test_throws DimensionMismatch stack(x for x in [1:2, 1:3]) + @test_throws DimensionMismatch stack([[5 6; 7 8], [1, 2, 3, 4]]) + @test_throws DimensionMismatch stack([[5 6; 7 8], [1, 2, 3, 4]]; dims=1) + @test_throws DimensionMismatch stack(x for x in [[5 6; 7 8], [1, 2, 3, 4]]) + # Inner iterator of unknown length + @test_throws MethodError stack((x for x in 1:3 if true) for _ in 1:4) + @test_throws MethodError stack((x for x in 1:3 if true) for _ in 1:4; dims=1) + + @test_throws ArgumentError stack([1:3, 4:6]; dims=0) + @test_throws ArgumentError stack([1:3, 4:6]; dims=3) + @test_throws ArgumentError stack(abs2, 1:3; dims=2) + + # Empty + @test_throws ArgumentError stack(()) + @test_throws ArgumentError stack([]) + @test_throws ArgumentError stack(x for x in 1:3 if false) +end + +@testset "tests from PR 31644" begin + v_v_same = [rand(128) for ii in 1:100] + v_v_diff = Any[rand(128), rand(Float32,128), rand(Int, 128)] + v_v_diff_typed = Union{Vector{Float64},Vector{Float32},Vector{Int}}[rand(128), rand(Float32,128), rand(Int, 128)] + for v_v in (v_v_same, v_v_diff, v_v_diff_typed) + # Cover all combinations of iterator traits. + g_v = (x for x in v_v) + f_g_v = Iterators.filter(x->true, g_v) + f_v_v = Iterators.filter(x->true, v_v); + hcat_expected = hcat(v_v...) + vcat_expected = vcat(v_v...) + @testset "$(typeof(data))" for data in (v_v, g_v, f_g_v, f_v_v) + @test stack(data) == hcat_expected + @test vec(stack(data)) == vcat_expected + end + end end @testset "keepat!" begin @@ -1562,26 +1709,72 @@ end end @testset "reshape methods for AbstractVectors" begin - r = Base.IdentityUnitRange(3:4) - @test reshape(r, :) === reshape(r, (:,)) === r + for r in Any[1:3, Base.IdentityUnitRange(3:4)] + @test reshape(r, :) === reshape(r, (:,)) === r + end + r = 3:5 + rr = reshape(r, 1, 3) + @test length(rr) == length(r) +end + +module IRUtils + include("compiler/irutils.jl") end @testset "strides for ReshapedArray" begin - # Type-based contiguous check is tested in test/compiler/inline.jl + 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 + end + # 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 strides(vec(a)) == (1,) + @test check_strides(vec(a)) b = view(parent(a), 1:9, 1:10) - @test_throws "Parent must be contiguous." strides(vec(b)) + @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 strides(reshape(a, 3, 4, 5)) == (n, 3n, 12n) - @test strides(reshape(a, 5, 6, 2)) == (n, 5n, 30n) + @test check_strides(reshape(a, 3, 4, 5)) + @test check_strides(reshape(a, 5, 6, 2)) b = view(parent(a), 60n:-n:1) - @test strides(reshape(b, 3, 4, 5)) == (-n, -3n, -12n) - @test strides(reshape(b, 5, 6, 2)) == (-n, -5n, -30n) + @test check_strides(reshape(b, 3, 4, 5)) + @test check_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)) + # 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_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)) + @test_throws "Input is not strided." strides(reshape(a,5,3,3,2)) + # Zero dimensional parent + struct FakeZeroDimArray <: AbstractArray{Int, 0} end + Base.strides(::FakeZeroDimArray) = () + Base.size(::FakeZeroDimArray) = () + a = reshape(FakeZeroDimArray(),1,1,1) + @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)) end @testset "stride for 0 dims array #44087" begin @@ -1598,10 +1791,35 @@ end end @testset "to_indices inference (issue #42001 #44059)" begin - @test (@inferred to_indices([], ntuple(Returns(CartesianIndex(1)), 32))) == ntuple(Returns(1), 32) - @test (@inferred to_indices([], ntuple(Returns(CartesianIndices(1:1)), 32))) == ntuple(Returns(Base.OneTo(1)), 32) - @test (@inferred to_indices([], (CartesianIndex(),1,CartesianIndex(1,1,1)))) == ntuple(Returns(1), 4) - A = randn(2,2,2,2,2,2); - i = CartesianIndex((1,1)) + CIdx = CartesianIndex + CIdc = CartesianIndices + @test (@inferred to_indices([], ntuple(Returns(CIdx(1)), 32))) == ntuple(Returns(1), 32) + @test (@inferred to_indices([], ntuple(Returns(CIdc(1:1)), 32))) == ntuple(Returns(Base.OneTo(1)), 32) + @test (@inferred to_indices([], (CIdx(), 1, CIdx(1,1,1)))) == ntuple(Returns(1), 4) + A = randn(2, 2, 2, 2, 2, 2); + i = CIdx((1, 1)) @test (@inferred A[i,i,i]) === A[1] -end + @test (@inferred to_indices([], (1, CIdx(1, 1), 1, CIdx(1, 1), 1, CIdx(1, 1), 1))) == ntuple(Returns(1), 10) +end + +@testset "type-based offset axes check" begin + a = randn(ComplexF64, 10) + ta = reinterpret(Float64, a) + tb = reinterpret(Float64, view(a, 1:2:10)) + tc = reinterpret(Float64, reshape(view(a, 1:3:10), 2, 2, 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)) + # 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. + @inline _has_offset_axes(A) = @inline any(x -> Int(first(x))::Int != 1, axes(A)) + @inline _has_offset_axes(As...) = @inline any(_has_offset_axes, As) + a, b = zeros(2, 2, 2), zeros(2, 2) + @test_broken IRUtils.fully_eliminated(_has_offset_axes, Base.typesof(a, a, b, b)) +end + +# type stable [x;;] (https://github.com/JuliaLang/julia/issues/45952) +f45952(x) = [x;;] +@inferred f45952(1.0) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index e7b3b13fba0ff..5056fc626e84a 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -39,19 +39,15 @@ let err = try end io = IOBuffer() Base.showerror(io, err) - lines = split(String(take!(io)), '\n') - ambig_checkline(str) = startswith(str, " ambig(x, y::Integer) in $curmod_str at") || - startswith(str, " ambig(x::Integer, y) in $curmod_str at") || - startswith(str, " ambig(x::Number, y) in $curmod_str at") - @test ambig_checkline(lines[2]) - @test ambig_checkline(lines[3]) - @test ambig_checkline(lines[4]) - @test lines[5] == "Possible fix, define" - @test lines[6] == " ambig(::Integer, ::Integer)" + errstr = String(take!(io)) + @test occursin(" ambig(x, y::Integer)\n @ $curmod_str", errstr) + @test occursin(" ambig(x::Integer, y)\n @ $curmod_str", errstr) + @test occursin(" ambig(x::Number, y)\n @ $curmod_str", errstr) + @test occursin("Possible fix, define\n ambig(::Integer, ::Integer)", errstr) end -ambig_with_bounds(x, ::Int, ::T) where {T<:Integer,S} = 0 -ambig_with_bounds(::Int, x, ::T) where {T<:Integer,S} = 1 +@test_warn "declares type variable S but does not use it" @eval ambig_with_bounds(x, ::Int, ::T) where {T<:Integer,S} = 0 +@test_warn "declares type variable S but does not use it" @eval ambig_with_bounds(::Int, x, ::T) where {T<:Integer,S} = 1 let err = try ambig_with_bounds(1, 2, 3) catch _e_ @@ -60,7 +56,7 @@ let err = try io = IOBuffer() Base.showerror(io, err) lines = split(String(take!(io)), '\n') - @test lines[end] == " ambig_with_bounds(::$Int, ::$Int, ::T) where T<:Integer" + @test lines[end-1] == " ambig_with_bounds(::$Int, ::$Int, ::T) where T<:Integer" end ## Other ways of accessing functions @@ -104,10 +100,6 @@ ambig(x::Union{Char, Int16}) = 's' const allowed_undefineds = Set([ GlobalRef(Base, :active_repl), GlobalRef(Base, :active_repl_backend), - GlobalRef(Base.Filesystem, :JL_O_TEMPORARY), - GlobalRef(Base.Filesystem, :JL_O_SHORT_LIVED), - GlobalRef(Base.Filesystem, :JL_O_SEQUENTIAL), - GlobalRef(Base.Filesystem, :JL_O_RANDOM), ]) let Distributed = get(Base.loaded_modules, @@ -161,37 +153,34 @@ ambig(x::Int8, y) = 1 ambig(x::Integer, y) = 2 ambig(x, y::Int) = 3 end - ambs = detect_ambiguities(Ambig5) @test length(ambs) == 2 - -using LinearAlgebra, SparseArrays, SuiteSparse +module Ambig48312 +ambig(::Integer, ::Int) = 1 +ambig(::Int, ::Integer) = 2 +ambig(::Signed, ::Int) = 3 +ambig(::Int, ::Signed) = 4 +end +ambs = detect_ambiguities(Ambig48312) +@test length(ambs) == 4 # Test that Core and Base are free of ambiguities # not using isempty so this prints more information when it fails @testset "detect_ambiguities" begin - let ambig = Set{Any}(((m1.sig, m2.sig) for (m1, m2) in detect_ambiguities(Core, Base; recursive=true, ambiguous_bottom=false, allowed_undefineds))) - @test isempty(ambig) - expect = [] + let ambig = Set(detect_ambiguities(Core, Base; recursive=true, ambiguous_bottom=false, allowed_undefineds)) good = true - while !isempty(ambig) - sigs = pop!(ambig) - i = findfirst(==(sigs), expect) - if i === nothing - println(stderr, "push!(expect, (", sigs[1], ", ", sigs[2], "))") - good = false - continue - end - deleteat!(expect, i) + for (sig1, sig2) in ambig + @test sig1 === sig2 # print this ambiguity + good = false end - @test isempty(expect) @test good end - # some ambiguities involving Union{} type parameters are expected, but not required + # some ambiguities involving Union{} type parameters may be expected, but not required let ambig = Set(detect_ambiguities(Core; recursive=true, ambiguous_bottom=true)) @test !isempty(ambig) + @test length(ambig) < 30 end STDLIB_DIR = Sys.STDLIB @@ -366,33 +355,46 @@ f35983(::Type, ::Type) = 2 @test length(Base.methods(f35983, (Any, Any))) == 2 @test first(Base.methods(f35983, (Any, Any))).sig == Tuple{typeof(f35983), Type, Type} let ambig = Ref{Int32}(0) - ms = Base._methods_by_ftype(Tuple{typeof(f35983), Type, Type}, nothing, -1, typemax(UInt), true, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + ms = Base._methods_by_ftype(Tuple{typeof(f35983), Type, Type}, nothing, -1, Base.get_world_counter(), true, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + @test ms isa Vector @test length(ms) == 1 @test ambig[] == 0 end f35983(::Type{Int16}, ::Any) = 3 @test length(Base.methods_including_ambiguous(f35983, (Type, Type))) == 2 -@test length(Base.methods(f35983, (Type, Type))) == 2 +@test length(Base.methods(f35983, (Type, Type))) == 1 let ambig = Ref{Int32}(0) - ms = Base._methods_by_ftype(Tuple{typeof(f35983), Type, Type}, nothing, -1, typemax(UInt), true, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + ms = Base._methods_by_ftype(Tuple{typeof(f35983), Type, Type}, nothing, -1, Base.get_world_counter(), true, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + @test ms isa Vector @test length(ms) == 2 @test ambig[] == 1 end struct B38280 <: Real; val; end let ambig = Ref{Int32}(0) - ms = Base._methods_by_ftype(Tuple{Type{B38280}, Any}, nothing, 1, typemax(UInt), false, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) + ms = Base._methods_by_ftype(Tuple{Type{B38280}, Any}, nothing, 1, Base.get_world_counter(), false, Ref{UInt}(typemin(UInt)), Ref{UInt}(typemax(UInt)), ambig) @test ms isa Vector @test length(ms) == 1 @test ambig[] == 1 end +fnoambig(::Int,::Int) = 1 +fnoambig(::Int,::Any) = 2 +fnoambig(::Any,::Int) = 3 +fnoambig(::Any,::Any) = 4 +let has_ambig = Ref(Int32(0)) + ms = Base._methods_by_ftype(Tuple{typeof(fnoambig), Any, Any}, nothing, 4, Base.get_world_counter(), false, Ref(typemin(UInt)), Ref(typemax(UInt)), has_ambig) + @test ms isa Vector + @test length(ms) == 4 + @test has_ambig[] == 0 +end + # issue #11407 f11407(::Dict{K,V}, ::Dict{Any,V}) where {K,V} = 1 f11407(::Dict{K,V}, ::Dict{K,Any}) where {K,V} = 2 @test_throws MethodError f11407(Dict{Any,Any}(), Dict{Any,Any}()) # ambiguous @test f11407(Dict{Any,Int}(), Dict{Any,Int}()) == 1 -f11407(::Dict{Any,Any}, ::Dict{Any,Any}) where {K,V} = 3 +@test_warn "declares type variable V but does not use it" @eval f11407(::Dict{Any,Any}, ::Dict{Any,Any}) where {K,V} = 3 @test f11407(Dict{Any,Any}(), Dict{Any,Any}()) == 3 # issue #12814 @@ -406,10 +408,23 @@ end # issue #43040 module M43040 + using Test struct C end - stripType(::Type{C}) where {T} = C # where {T} is intentionally incorrect + @test_warn "declares type variable T but does not use it" @eval M43040 stripType(::Type{C}) where {T} = C # where {T} is intentionally incorrect end @test isempty(detect_ambiguities(M43040; recursive=true)) +cc46601(T::Type{<:Core.IntrinsicFunction}, x) = 1 +cc46601(::Type{T}, x::Number) where {T<:AbstractChar} = 2 +cc46601(T::Type{<:Nothing}, x) = 3 +cc46601(::Type{T}, x::T) where {T<:Number} = 4 +cc46601(::Type{T}, arg) where {T<:VecElement} = 5 +cc46601(::Type{T}, x::Number) where {T<:Number} = 6 +@test length(methods(cc46601, Tuple{Type{<:Integer}, Integer})) == 2 +@test length(Base.methods_including_ambiguous(cc46601, Tuple{Type{<:Integer}, Integer})) == 6 +cc46601(::Type{T}, x::Int) where {T<:AbstractString} = 7 +@test length(methods(cc46601, Tuple{Type{<:Integer}, Integer})) == 2 +@test length(Base.methods_including_ambiguous(cc46601, Tuple{Type{<:Integer}, Integer})) == 7 + nothing diff --git a/test/arrayops.jl b/test/arrayops.jl index 84b0e7d259f45..770cec3705038 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -545,9 +545,17 @@ end @testset "findall, findfirst, findnext, findlast, findprev" begin a = [0,1,2,3,0,1,2,3] + m = [false false; true false] @test findall(!iszero, a) == [2,3,4,6,7,8] @test findall(a.==2) == [3,7] @test findall(isodd,a) == [2,4,6,8] + @test findall(Bool[]) == Int[] + @test findall([false, false]) == Int[] + @test findall(m) == [k for (k,v) in pairs(m) if v] + @test findall(!, [false, true, true]) == [1] + @test findall(i -> true, [false, true, false]) == [1, 2, 3] + @test findall(i -> false, rand(2, 2)) == Int[] + @test findall(!, m) == [k for (k,v) in pairs(m) if !v] @test findfirst(!iszero, a) == 2 @test findfirst(a.==0) == 1 @test findfirst(a.==5) == nothing @@ -700,7 +708,7 @@ end ap = PermutedDimsArray(Array(a), (2,1,3)) @test strides(ap) == (3,1,12) - for A in [rand(1,2,3,4),rand(2,2,2,2),rand(5,6,5,6),rand(1,1,1,1)] + for A in [rand(1,2,3,4),rand(2,2,2,2),rand(5,6,5,6),rand(1,1,1,1), [rand(ComplexF64, 2,2) for _ in 1:2, _ in 1:3, _ in 1:2, _ in 1:4]] perm = randperm(4) @test isequal(A,permutedims(permutedims(A,perm),invperm(perm))) @test isequal(A,permutedims(permutedims(A,invperm(perm)),perm)) @@ -708,6 +716,10 @@ end @test sum(permutedims(A,perm)) ≈ sum(PermutedDimsArray(A,perm)) @test sum(permutedims(A,perm), dims=2) ≈ sum(PermutedDimsArray(A,perm), dims=2) @test sum(permutedims(A,perm), dims=(2,4)) ≈ sum(PermutedDimsArray(A,perm), dims=(2,4)) + + @test prod(permutedims(A,perm)) ≈ prod(PermutedDimsArray(A,perm)) + @test prod(permutedims(A,perm), dims=2) ≈ prod(PermutedDimsArray(A,perm), dims=2) + @test prod(permutedims(A,perm), dims=(2,4)) ≈ prod(PermutedDimsArray(A,perm), dims=(2,4)) end m = [1 2; 3 4] @@ -757,6 +769,18 @@ end @test circshift(src, 1) == src src = zeros(Bool, (4,0)) @test circshift(src, 1) == src + + # 1d circshift! (https://github.com/JuliaLang/julia/issues/46533) + a = [1:5;] + @test circshift!(a, 1) === a + @test a == circshift([1:5;], 1) == [5, 1, 2, 3, 4] + a = [1:5;] + @test circshift!(a, -2) === a + @test a == circshift([1:5;], -2) == [3, 4, 5, 1, 2] + a = [1:5;] + oa = OffsetVector(copy(a), -1) + @test circshift!(oa, 1) === oa + @test oa == circshift(OffsetVector(a, -1), 1) end @testset "circcopy" begin @@ -1128,7 +1152,7 @@ end @test isequal(setdiff([1,2,3,4], [7,8,9]), [1,2,3,4]) @test isequal(setdiff([1,2,3,4], Int64[]), Int64[1,2,3,4]) @test isequal(setdiff([1,2,3,4], [1,2,3,4,5]), Int64[]) - @test isequal(symdiff([1,2,3], [4,3,4]), [1,2]) + @test isequal(symdiff([1,2,3], [4,3,4]), [1,2,4]) @test isequal(symdiff(['e','c','a'], ['b','a','d']), ['e','c','b','d']) @test isequal(symdiff([1,2,3], [4,3], [5]), [1,2,4,5]) @test isequal(symdiff([1,2,3,4,5], [1,2,3], [3,4]), [3,5]) @@ -1173,7 +1197,6 @@ end @test mapslices(prod,["1"],dims=1) == ["1"] # issue #5177 - c = fill(1,2,3,4) m1 = mapslices(_ -> fill(1,2,3), c, dims=[1,2]) m2 = mapslices(_ -> fill(1,2,4), c, dims=[1,3]) @@ -1196,9 +1219,26 @@ end @test o == fill(1, 3, 4) # issue #18524 - m = mapslices(x->tuple(x), [1 2; 3 4], dims=1) + m = mapslices(x->tuple(x), [1 2; 3 4], dims=1) # see variations of this below @test m[1,1] == ([1,3],) @test m[1,2] == ([2,4],) + + r = rand(Int8, 4,5,2) + @test vec(mapslices(repr, r, dims=(2,1))) == map(repr, eachslice(r, dims=3)) + @test mapslices(tuple, [1 2; 3 4], dims=1) == [([1, 3],) ([2, 4],)] + @test mapslices(transpose, r, dims=(1,3)) == permutedims(r, (3,2,1)) + + # failures + @test_broken @inferred(mapslices(tuple, [1 2; 3 4], dims=1)) == [([1, 3],) ([2, 4],)] + @test_broken @inferred(mapslices(transpose, r, dims=(1,3))) == permutedims(r, (3,2,1)) + + # re-write, #40996 + @test_throws ArgumentError mapslices(identity, rand(2,3), dims=0) # previously BoundsError + @test_throws ArgumentError mapslices(identity, rand(2,3), dims=(1,3)) # previously BoundsError + @test_throws DimensionMismatch mapslices(x -> x * x', rand(2,3), dims=1) # explicitly caught + @test @inferred(mapslices(hcat, [1 2; 3 4], dims=1)) == [1 2; 3 4] # previously an error, now allowed + @test mapslices(identity, [1 2; 3 4], dims=(2,2)) == [1 2; 3 4] # previously an error + @test_broken @inferred(mapslices(identity, [1 2; 3 4], dims=(2,2))) == [1 2; 3 4] end @testset "single multidimensional index" begin @@ -1461,6 +1501,9 @@ end @test isempty(eoa) end +@testset "filter curried #41173" begin + @test -5:5 |> filter(iseven) == -4:2:4 +end @testset "logical keepat!" begin # Vector a = Vector(1:10) @@ -1634,15 +1677,65 @@ end end @testset "isdiag, istril, istriu" begin + # Scalar @test isdiag(3) @test istril(4) @test istriu(5) + + # Square matrix @test !isdiag([1 2; 3 4]) @test !istril([1 2; 3 4]) @test !istriu([1 2; 3 4]) @test isdiag([1 0; 0 4]) @test istril([1 0; 3 4]) @test istriu([1 2; 0 4]) + + # Non-square matrix + @test !isdiag([1 2 0; 3 4 0]) + @test !istril([1 2 0; 3 4 0]) + @test !istriu([1 2 0; 3 4 0]) + @test isdiag([1 0 0; 0 4 0]) + @test istril([1 0 0; 3 4 0]) + @test istriu([1 2 0; 0 4 0]) + @test !isdiag([1 2 0; 3 4 1]) + @test !istril([1 2 0; 3 4 1]) + @test !istriu([1 2 0; 3 4 1]) + @test !isdiag([1 0 0; 0 4 1]) + @test !istril([1 0 0; 3 4 1]) + @test istriu([1 2 0; 0 4 1]) +end + +#issue 49021 +@testset "reverse cartesian indices" begin + @test reverse(CartesianIndices((2, 3))) === CartesianIndices((2:-1:1, 3:-1:1)) + @test reverse(CartesianIndices((2:5, 3:7))) === CartesianIndices((5:-1:2, 7:-1:3)) + @test reverse(CartesianIndices((5:-1:2, 7:-1:3))) === CartesianIndices((2:1:5, 3:1:7)) +end + +@testset "reverse cartesian indices dim" begin + A = CartesianIndices((2, 3, 5:-1:1)) + @test reverse(A, dims=1) === CartesianIndices((2:-1:1, 3, 5:-1:1)) + @test reverse(A, dims=3) === CartesianIndices((2, 3, 1:1:5)) + @test_throws ArgumentError reverse(A, dims=0) + @test_throws ArgumentError reverse(A, dims=4) +end + +@testset "reverse cartesian indices multiple dims" begin + A = CartesianIndices((2, 3, 5:-1:1)) + @test reverse(A, dims=(1, 3)) === CartesianIndices((2:-1:1, 3, 1:1:5)) + @test reverse(A, dims=(3, 1)) === CartesianIndices((2:-1:1, 3, 1:1:5)) + @test_throws ArgumentError reverse(A, dims=(1, 2, 4)) + @test_throws ArgumentError reverse(A, dims=(0, 1, 2)) + @test_throws ArgumentError reverse(A, dims=(1, 1)) +end + +@testset "stability of const propagation" begin + A = CartesianIndices((2, 3, 5:-1:1)) + f1(x) = reverse(x; dims=1) + f2(x) = reverse(x; dims=(1, 3)) + @test @inferred(f1(A)) === CartesianIndices((2:-1:1, 3, 5:-1:1)) + @test @inferred(f2(A)) === CartesianIndices((2:-1:1, 3, 1:1:5)) + @test @inferred(reverse(A; dims=())) === A end # issue 4228 @@ -2105,6 +2198,16 @@ end @test_throws ArgumentError LinearAlgebra.copy_transpose!(a,2:3,1:3,b,1:5,2:7) end +@testset "empty copyto!" begin + @test isempty(copyto!(Int[], ())) + @test isempty(copyto!(Int[], Int[])) + @test copyto!([1,2], ()) == [1,2] + + @test isempty(copyto!(Int[], 1, ())) + @test isempty(copyto!(Int[], 1, Int[])) + @test copyto!([1,2], 1, ()) == [1,2] +end + module RetTypeDecl using Test import Base: +, *, broadcast, convert @@ -2154,19 +2257,84 @@ end end # row/column/slice iterator tests -using Base: eachrow, eachcol @testset "row/column/slice iterators" begin + # check type aliases + @test RowSlices <: AbstractSlices{<:AbstractVector, 1} <: AbstractVector{<:AbstractVector} + @test eachrow(ones(3)) isa RowSlices + @test eachrow(ones(3,3)) isa RowSlices + @test ColumnSlices <: AbstractSlices{<:AbstractVector, 1} <: AbstractVector{<:AbstractVector} + @test eachcol(ones(3)) isa ColumnSlices + @test eachcol(ones(3,3)) isa ColumnSlices + # Simple ones M = [1 2 3; 4 5 6; 7 8 9] - @test collect(eachrow(M)) == collect(eachslice(M, dims = 1)) == [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - @test collect(eachcol(M)) == collect(eachslice(M, dims = 2)) == [[1, 4, 7], [2, 5, 8], [3, 6, 9]] + @test eachrow(M) == eachslice(M, dims = 1) == [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + @test eachcol(M) == eachslice(M, dims = 2) == [[1, 4, 7], [2, 5, 8], [3, 6, 9]] @test_throws DimensionMismatch eachslice(M, dims = 4) - # Higher-dimensional case - M = reshape([(1:16)...], 2, 2, 2, 2) + SR = @inferred eachrow(M) + @test SR[2] isa eltype(SR) + SR[2] = [14,15,16] + @test SR[2] == M[2,:] == [14,15,16] + @test parent(SR) === M + + SC = @inferred eachcol(M) + @test SC[3] isa eltype(SC) + SC[3] = [23,26,29] + @test SC[3] == M[:,3] == [23,26,29] + @test parent(SC) === M + + # Higher-dimensional cases + M = reshape(collect(1:16), (2,2,2,2)) @test_throws MethodError collect(eachrow(M)) @test_throws MethodError collect(eachcol(M)) - @test collect(eachslice(M, dims = 1))[1][:, :, 1] == [1 5; 3 7] + + S1 = eachslice(M, dims = 1) + @test S1 isa AbstractSlices{<:AbstractArray{Int, 3}, 1} + @test size(S1) == (2,) + @test S1[1] == M[1,:,:,:] + + S1K = eachslice(M, dims = 1, drop=false) + @test S1K isa AbstractSlices{<:AbstractArray{Int, 3}, 4} + @test size(S1K) == (2,1,1,1) + @test S1K[1,1,1,1] == M[1,:,:,:] + + S23 = eachslice(M, dims = (2,3)) + @test S23 isa AbstractSlices{<:AbstractArray{Int, 2}, 2} + @test size(S23) == (2,2) + @test S23[2,1] == M[:,2,1,:] + + S23K = eachslice(M, dims = (2,3), drop=false) + @test S23K isa AbstractSlices{<:AbstractArray{Int, 2}, 4} + @test size(S23K) == (1,2,2,1) + @test S23K[1,2,1,1] == M[:,2,1,:] + + S32 = eachslice(M, dims = (3,2)) + @test S32 isa AbstractSlices{<:AbstractArray{Int, 2}, 2} + @test size(S32) == (2,2) + @test S32[2,1] == M[:,1,2,:] + + S32K = eachslice(M, dims = (3,2), drop=false) + @test S32K isa AbstractSlices{<:AbstractArray{Int, 2}, 4} + @test size(S32K) == (1,2,2,1) + @test S32K[1,2,1,1] == M[:,2,1,:] + + @testset "eachslice inference (#45923)" begin + a = [1 2; 3 4] + f1(a) = eachslice(a, dims=1) + @test (@inferred f1(a)) == eachrow(a) + f2(a) = eachslice(a, dims=2) + @test (@inferred f2(a)) == eachcol(a) + end + + @testset "eachslice bounds checking" begin + # https://github.com/JuliaLang/julia/pull/32310#issuecomment-1146911510 + A = eachslice(rand(2,3), dims = 2, drop = false) + @test_throws BoundsError A[2, 1] + @test_throws BoundsError A[4] + @test_throws BoundsError A[2,3] = [4,5] + @test_throws BoundsError A[2,3] .= [4,5] + end end ### @@ -2318,10 +2486,12 @@ let A = zeros(Int, 2, 2), B = zeros(Float64, 2, 2) f40() = Float64[A A] f41() = [A B] f42() = Int[A B] + f43() = Int[A...] + f44() = Float64[A..., B...] for f in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, - f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, f42] + f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, f42, f43, f44] @test isconcretetype(Base.return_types(f, ())[1]) end end diff --git a/test/asyncmap.jl b/test/asyncmap.jl index ec49230dbce14..5dc79e612acda 100644 --- a/test/asyncmap.jl +++ b/test/asyncmap.jl @@ -64,7 +64,7 @@ let end @test e isa CapturedException @test e.ex == ErrorException("captured") - @test e.processed_bt[2][1].func == :f42105 + @test e.processed_bt[2][1].func === :f42105 end include("generic_map_tests.jl") diff --git a/test/atexit.jl b/test/atexit.jl index 103cb1e52bca6..bf46edae6eaad 100644 --- a/test/atexit.jl +++ b/test/atexit.jl @@ -28,6 +28,11 @@ using Test exit(22) """ => 0, # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + """ + atexit(exitcode -> exitcode > 10 && exit(0)) + exit(22) + """ => 0, + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ) for julia_expr in keys(julia_expr_list) cmd_eval = _atexit_tests_gen_cmd_eval(julia_expr) @@ -87,6 +92,11 @@ using Test """ => 13, # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ """ + atexit(exitcode -> exit(exitcode+3)) + exit(22) + """ => 25, + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + """ atexit(() -> ("No error")) atexit(() -> exit(5)) exit(22) @@ -135,6 +145,18 @@ using Test exit(22) """ => 4, # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + """ + atexit(() -> exit(21)) + atexit(exitcode -> exit(exitcode+3)) + exit(22) + """ => 21, + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + """ + atexit(exitcode -> exit(exitcode+3)) + atexit(() -> exit(21)) + exit(22) + """ => 24, + # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ) for julia_expr in keys(julia_expr_list) cmd_eval = _atexit_tests_gen_cmd_eval(julia_expr) diff --git a/test/atomics.jl b/test/atomics.jl index 15ffd84a2c0a2..dd50fb96be49f 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -22,19 +22,20 @@ mutable struct Refxy{T} Refxy{T}() where {T} = new() # unused, but sets ninitialized to 0 end -@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} +modname = String(nameof(@__MODULE__)) +@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} @atomic x::T @atomic y::T end -@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} +@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} x::T y::T end -@test_throws ErrorException("invalid redefinition of constant ARefxy") @eval mutable struct ARefxy{T} +@test_throws ErrorException("invalid redefinition of constant $modname.ARefxy") @eval mutable struct ARefxy{T} x::T @atomic y::T end -@test_throws ErrorException("invalid redefinition of constant Refxy") @eval mutable struct Refxy{T} +@test_throws ErrorException("invalid redefinition of constant $modname.Refxy") @eval mutable struct Refxy{T} x::T @atomic y::T end @@ -266,8 +267,10 @@ test_field_operators(ARefxy{Float64}(123_10, 123_20)) nothing end @noinline function test_field_orderings(r, x, y) - _test_field_orderings(Ref(copy(r)), x, y) - _test_field_orderings(Ref{Any}(copy(r)), x, y) + @testset "$r" begin + _test_field_orderings(Ref(copy(r)), x, y) + _test_field_orderings(Ref{Any}(copy(r)), x, y) + end nothing end @noinline test_field_orderings(x, y) = (@nospecialize; test_field_orderings(ARefxy(x, y), x, y)) diff --git a/test/backtrace.jl b/test/backtrace.jl index 3aebfec410f34..38019880da35d 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -35,7 +35,7 @@ catch err @test endswith(string(lkup[2].file), "backtrace.jl") @test lkup[2].line == 42 # TODO: we don't support surface AST locations with inlined function names - @test_broken lkup[1].func == :inlfunc + @test_broken lkup[1].func === :inlfunc @test endswith(string(lkup[1].file), "backtrace.jl") @test lkup[1].line == 37 end @@ -106,10 +106,10 @@ lkup = map(lookup, bt()) hasbt = hasbt2 = false for sfs in lkup for sf in sfs - if sf.func == :bt + if sf.func === :bt global hasbt = true end - if sf.func == :bt2 + if sf.func === :bt2 global hasbt2 = true end end @@ -125,10 +125,10 @@ lkup = map(lookup, btmacro()) hasme = hasbtmacro = false for sfs in lkup for sf in sfs - if sf.func == Symbol("macro expansion") + if sf.func === Symbol("macro expansion") global hasme = true end - if sf.func == :btmacro + if sf.func === :btmacro global hasbtmacro = true end end @@ -175,7 +175,7 @@ let bt, found = false bt = backtrace() end for frame in map(lookup, bt) - if frame[1].line == @__LINE__() - 3 && frame[1].file == Symbol(@__FILE__) + if frame[1].line == @__LINE__() - 3 && frame[1].file === Symbol(@__FILE__) found = true; break end end @@ -184,10 +184,10 @@ end # issue 28618 let bt, found = false - @info "" + @debug "" bt = backtrace() for frame in map(lookup, bt) - if frame[1].line == @__LINE__() - 2 && frame[1].file == Symbol(@__FILE__) + if frame[1].line == @__LINE__() - 2 && frame[1].file === Symbol(@__FILE__) found = true; break end end @@ -205,8 +205,8 @@ let trace = try catch stacktrace(catch_backtrace()) end - @test trace[1].func == Symbol("top-level scope") - @test trace[1].file == :a_filename + @test trace[1].func === Symbol("top-level scope") + @test trace[1].file === :a_filename @test trace[1].line == 2 end let trace = try @@ -219,11 +219,24 @@ let trace = try catch stacktrace(catch_backtrace()) end - @test trace[1].func == Symbol("top-level scope") - @test trace[1].file == :a_filename + @test trace[1].func === Symbol("top-level scope") + @test trace[1].file === :a_filename @test trace[1].line == 2 end +# issue #45171 +linenum = @__LINE__; function f45171(;kwarg = true) + 1 + error() +end +let trace = try + f45171() + catch + stacktrace(catch_backtrace()) + end + @test trace[3].line == linenum +end + # issue #29695 (see also test for #28442) let code = """ f29695(c) = g29695(c) diff --git a/test/binaryplatforms.jl b/test/binaryplatforms.jl index 793a9b1f06a41..8de522e9c6c8b 100644 --- a/test/binaryplatforms.jl +++ b/test/binaryplatforms.jl @@ -315,8 +315,9 @@ end P("x86_64", "linux"; libgfortran_version=v"5") => "linux8", # Ambiguity test - P("aarch64", "linux"; libgfortran_version=v"3") => "linux4", + P("aarch64", "linux"; libgfortran_version=v"3") => "linux3", P("aarch64", "linux"; libgfortran_version=v"3", libstdcxx_version=v"3.4.18") => "linux5", + P("aarch64", "linux"; libgfortran_version=v"3", libstdcxx_version=v"3.4.18", foo="bar") => "linux9", # OS test P("x86_64", "macos"; libgfortran_version=v"3") => "mac4", @@ -327,8 +328,9 @@ end @test select_platform(platforms, P("x86_64", "linux"; libgfortran_version=v"4")) == "linux7" # Ambiguity test - @test select_platform(platforms, P("aarch64", "linux")) == "linux5" - @test select_platform(platforms, P("aarch64", "linux"; libgfortran_version=v"3")) == "linux5" + @test select_platform(platforms, P("aarch64", "linux")) == "linux3" + @test select_platform(platforms, P("aarch64", "linux"; libgfortran_version=v"3")) == "linux3" + @test select_platform(platforms, P("aarch64", "linux"; libgfortran_version=v"3", libstdcxx_version=v"3.4.18")) === "linux5" @test select_platform(platforms, P("aarch64", "linux"; libgfortran_version=v"4")) === nothing @test select_platform(platforms, P("x86_64", "macos")) == "mac4" @@ -339,6 +341,22 @@ end # Sorry, Alex. ;) @test select_platform(platforms, P("x86_64", "freebsd")) === nothing + + # The new "most complete match" algorithm deals with ambiguities as follows: + platforms = Dict( + P("x86_64", "linux") => "normal", + P("x86_64", "linux"; sanitize="memory") => "sanitized", + ) + @test select_platform(platforms, P("x86_64", "linux")) == "normal" + @test select_platform(platforms, P("x86_64", "linux"; sanitize="memory")) == "sanitized" + + # Ties are broken by reverse-sorting by triplet: + platforms = Dict( + P("x86_64", "linux"; libgfortran_version=v"3") => "libgfortran3", + P("x86_64", "linux"; libgfortran_version=v"4") => "libgfortran4", + ) + @test select_platform(platforms, P("x86_64", "linux")) == "libgfortran4" + @test select_platform(platforms, P("x86_64", "linux"; libgfortran_version=v"3")) == "libgfortran3" end @testset "Custom comparators" begin diff --git a/test/bitarray.jl b/test/bitarray.jl index 75a6389815336..5d0bff62ab6e1 100644 --- a/test/bitarray.jl +++ b/test/bitarray.jl @@ -15,12 +15,11 @@ bitcheck(x) = true bcast_setindex!(b, x, I...) = (b[I...] .= x; b) function check_bitop_call(ret_type, func, args...; kwargs...) - r1 = func(args...; kwargs...) r2 = func(map(x->(isa(x, BitArray) ? Array(x) : x), args)...; kwargs...) - ret_type ≢ nothing && !isa(r1, ret_type) && @show ret_type, typeof(r1) - ret_type ≢ nothing && @test isa(r1, ret_type) + r1 = func(args...; kwargs...) + ret_type ≢ nothing && (@test isa(r1, ret_type) || @show ret_type, typeof(r1)) @test tc(r1, r2) - @test isequal(r1, ret_type ≡ nothing ? r2 : r2) + @test isequal(r1, r2) @test bitcheck(r1) end macro check_bit_operation(ex, ret_type) @@ -98,6 +97,20 @@ end timesofar("conversions") +@testset "Promotions for size $sz" for (sz, T) in allsizes + @test_broken isequal(promote(falses(sz...), zeros(sz...)), + (zeros(sz...), zeros(sz...))) + @test_broken isequal(promote(trues(sz...), ones(sz...)), + (ones(sz...), ones(sz...))) + ae = falses(1, sz...) + ex = (@test_throws ErrorException promote(ae, ones(sz...))).value + @test startswith(ex.msg, "promotion of types Bit") + ex = (@test_throws ErrorException promote(ae, falses(sz...))).value + @test startswith(ex.msg, "promotion of types Bit") +end + +timesofar("promotions") + @testset "utility functions" begin b1 = bitrand(v1) @test isequal(fill!(b1, true), trues(size(b1))) @@ -203,6 +216,11 @@ timesofar("utils") @test_throws DimensionMismatch BitMatrix((isodd(i) for i in 1:3)) end + @testset "constructor from infinite iterator" begin + inf_iter = Base.Iterators.cycle([true]) + @test_throws ArgumentError BitArray(inf_iter) + end + @testset "constructor from NTuple" begin for nt in ((true, false, false), NTuple{0,Bool}(), (false,), (true,)) @test BitVector(nt) == BitVector(collect(nt)) @@ -499,12 +517,14 @@ timesofar("constructors") end end + self_copyto!(a, n1, n2, l) = copyto!(a, n1, a, n2, l) for p1 = [rand(1:v1) 1 63 64 65 191 192 193] for p2 = [rand(1:v1) 1 63 64 65 191 192 193] for n = 0 : min(v1 - p1 + 1, v1 - p2 + 1) b1 = bitrand(v1) b2 = bitrand(v1) @check_bit_operation copyto!(b1, p1, b2, p2, n) BitVector + @check_bit_operation self_copyto!(b1, p1, p2, n) BitVector end end end @@ -1474,6 +1494,51 @@ timesofar("reductions") C17970 = map(x -> x ? false : true, A17970) @test C17970::BitArray{1} == map(~, A17970) end + + #= + |<----------------dest----------(original_tail)->| + |<------------------b2(l)------>| extra_l | + |<------------------b3(l)------>| + |<------------------b4(l+extra_l)--------------->| + |<--------------desk_inbetween-------->| extra÷2 | + =# + @testset "Issue #47011, map! over unequal length bitarray" begin + for l = [0, 1, 63, 64, 65, 127, 128, 129, 255, 256, 257, 6399, 6400, 6401] + for extra_l = [10, 63, 64, 65, 127, 128, 129, 255, 256, 257, 6399, 6400, 6401] + + dest = bitrand(l+extra_l) + b2 = bitrand(l) + original_tail = last(dest, extra_l) + for op in (!, ~) + map!(op, dest, b2) + @test first(dest, l) == map(op, b2) + # check we didn't change bits we're not suppose to + @test last(dest, extra_l) == original_tail + end + + b3 = bitrand(l) + b4 = bitrand(l+extra_l) + # when dest is longer than one source but shorter than the other + dest_inbetween = bitrand(l + extra_l÷2) + original_tail_inbetween = last(dest_inbetween, extra_l÷2) + for op in (|, ⊻) + map!(op, dest, b2, b3) + @test first(dest, l) == map(op, b2, b3) + # check we didn't change bits we're not suppose to + @test last(dest, extra_l) == original_tail + + map!(op, dest, b2, b4) + @test first(dest, l) == map(op, b2, b4) + # check we didn't change bits we're not suppose to + @test last(dest, extra_l) == original_tail + + map!(op, dest_inbetween, b2, b4) + @test first(dest_inbetween, l) == map(op, b2, b4) + @test last(dest_inbetween, extra_l÷2) == original_tail_inbetween + end + end + end + end end ## Filter ## @@ -1587,7 +1652,7 @@ timesofar("cat") @test ((svdb1, svdb1A) = (svd(b1), svd(Array(b1))); svdb1.U == svdb1A.U && svdb1.S == svdb1A.S && svdb1.V == svdb1A.V) @test ((qrb1, qrb1A) = (qr(b1), qr(Array(b1))); - qrb1.Q == qrb1A.Q && qrb1.R == qrb1A.R) + Matrix(qrb1.Q) == Matrix(qrb1A.Q) && qrb1.R == qrb1A.R) b1 = bitrand(v1) @check_bit_operation diagm(0 => b1) BitMatrix @@ -1767,4 +1832,39 @@ end @test all(bitarray[rangeout, rangein] .== true) @test all(bitarray[rangein, rangeout] .== true) end -end \ No newline at end of file +end + +# issue #45825 + +isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl") +using .Main.OffsetArrays + +let all_false = OffsetArray(falses(2001), -1000:1000) + @test !any(==(true), all_false) + # should be run with --check-bounds=yes + @test_throws DimensionMismatch BitArray(all_false) + all_false = OffsetArray(falses(2001), 1:2001) + @test !any(==(true), BitArray(all_false)) + all_false = OffsetArray(falses(100, 100), 0:99, -1:98) + @test !any(==(true), all_false) + @test_throws DimensionMismatch BitArray(all_false) + all_false = OffsetArray(falses(100, 100), 1:100, 1:100) + @test !any(==(true), all_false) +end +let a = falses(1000), + msk = BitArray(rand(Bool, 1000)), + n = count(msk), + b = OffsetArray(rand(Bool, n), (-n÷2):(n÷2)-iseven(n)) + a[msk] = b + @test a[msk] == collect(b) + a = falses(100, 100) + msk = BitArray(rand(Bool, 100, 100)) + n = count(msk) + b = OffsetArray(rand(Bool, 1, n), 1:1, (-n÷2):(n÷2)-iseven(n)) + a[msk] = b + @test a[msk] == vec(collect(b)) +end +let b = trues(10) + copyto!(b, view([0,0,0], :)) + @test b == [0,0,0,1,1,1,1,1,1,1] +end diff --git a/test/bitset.jl b/test/bitset.jl index 1919da4f3702a..f8c5d3fffd7d2 100644 --- a/test/bitset.jl +++ b/test/bitset.jl @@ -38,9 +38,12 @@ end @test !in(1,s) @test in(2,s) @test !in(10002,s) - @test in(10000,s) + @test in(UInt128(10000),s) + @test in(Int32(10000),s) @test in(10000.0,s) @test !in(10002.0,s) + @test !in(typemax(UInt), s) + @test !in(typemin(Int)-Int128(14), s) @test_throws ArgumentError first(BitSet()) @test_throws ArgumentError last(BitSet()) t = copy(s) @@ -65,11 +68,13 @@ end @test !(-1 in BitSet(1:10)) end -# # issue #8570 -# This requires 2^29 bytes of storage, which is too much for a simple test -# s = BitSet(typemax(Int32)) -# @test length(s) === 1 -# for b in s; b; end +@testset "issue #8570" begin + let s + @test 400 > @allocated s = BitSet(typemax(Int32)) + @test length(s) === 1 + @test only(s) == typemax(Int32) + end +end @testset "union!, symdiff!" begin i = BitSet([1, 2, 3]) @@ -155,13 +160,16 @@ end for n in -20:0 @test length(delete!(s, n)) == len end + @test length(delete!(s, typemax(UInt))) == len @test pop!(s, 1) === 1 @test !(1 in s) @test_throws KeyError pop!(s, 1) @test_throws KeyError pop!(s, -1) @test pop!(s, -1, 1) === 1 @test pop!(s, 1, 0) === 0 - @test s === delete!(s, 1) + @test 5 in s + @test s === delete!(s, 1) === delete!(s, Int8(5)) + @test !(5 in s) for i in s; pop!(s, i); end @test isempty(s) push!(s, 100) @@ -346,8 +354,17 @@ end x = BitSet(rand(-1000:1000, 500)) y = copy(x) @test union!(x, BitSet(a:b)) == union!(y, BitSet(a:1:b)) - @test_throws ArgumentError BitSet(Int128(typemin(Int))-1:typemin(Int)) - @test_throws ArgumentError BitSet(typemax(Int):Int128(typemax(Int))+1) + @test_throws InexactError BitSet(Int128(typemin(Int))-1:typemin(Int)) + @test_throws InexactError BitSet(typemax(Int):Int128(typemax(Int))+1) # union! with an empty range doesn't modify the BitSet @test union!(x, b:a) == y end + +@testset "union!(::BitSet, ::AbstractUnitRange) when two ranges do not overlap" begin + # see #45574 + a, b = rand(-10000:-5000), rand(5000:10000) + c, d = minmax(rand(20000:30000, 2)...) + @test length(union!(BitSet(a:b), c:d)) == length(a:b) + length(c:d) + c, d = minmax(rand(-30000:-20000, 2)...) + @test length(union!(BitSet(a:b), c:d)) == length(a:b) + length(c:d) +end diff --git a/test/boundscheck.jl b/test/boundscheck.jl index 09cc8d2cd13e8..ad7f50a84e086 100644 --- a/test/boundscheck.jl +++ b/test/boundscheck.jl @@ -2,17 +2,14 @@ # run boundscheck tests on separate workers launched with --check-bounds={default,yes,no} -cmd = `$(Base.julia_cmd()) --depwarn=error --startup-file=no boundscheck_exec.jl` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("boundscheck test failed, cmd : $cmd") +let cmd = `$(Base.julia_cmd()) --check-bounds=auto --depwarn=error --startup-file=no boundscheck_exec.jl` + success(pipeline(cmd; stdout=stdout, stderr=stderr)) || error("boundscheck test failed, cmd : $cmd") end -cmd = `$(Base.julia_cmd()) --check-bounds=yes --startup-file=no --depwarn=error boundscheck_exec.jl` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("boundscheck test failed, cmd : $cmd") +let cmd = `$(Base.julia_cmd()) --check-bounds=yes --startup-file=no --depwarn=error boundscheck_exec.jl` + success(pipeline(cmd; stdout=stdout, stderr=stderr)) || error("boundscheck test failed, cmd : $cmd") end -cmd = `$(Base.julia_cmd()) --check-bounds=no --startup-file=no --depwarn=error boundscheck_exec.jl` -if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("boundscheck test failed, cmd : $cmd") +let cmd = `$(Base.julia_cmd()) --check-bounds=no --startup-file=no --depwarn=error boundscheck_exec.jl` + success(pipeline(cmd; stdout=stdout, stderr=stderr)) || error("boundscheck test failed, cmd : $cmd") end diff --git a/test/boundscheck_exec.jl b/test/boundscheck_exec.jl index 71690c55faeca..403014c94ed0d 100644 --- a/test/boundscheck_exec.jl +++ b/test/boundscheck_exec.jl @@ -239,17 +239,16 @@ if bc_opt != bc_off @test_throws BoundsError BadVector20469([1,2,3])[:] end -# Ensure iteration over arrays is vectorizable with boundschecks off +# Ensure iteration over arrays is vectorizable function g27079(X) r = 0 - @inbounds for x in X + for x in X r += x end r end -if bc_opt == bc_default || bc_opt == bc_off - @test occursin("vector.body", sprint(code_llvm, g27079, Tuple{Vector{Int}})) -end + +@test occursin("vector.reduce.add", sprint(code_llvm, g27079, Tuple{Vector{Int}})) # Boundschecking removal of indices with different type, see #40281 getindex_40281(v, a, b, c) = @inbounds getindex(v, a, b, c) @@ -259,7 +258,9 @@ if bc_opt == bc_default || bc_opt == bc_off @test !occursin("arrayref(true", typed_40281) end -@testset "pass inbounds meta to getindex on CartesianIndices (#42115)" begin +# Given this is a sub-processed test file, not using @testsets avoids +# leaking the report print into the Base test runner report +begin # Pass inbounds meta to getindex on CartesianIndices (#42115) @inline getindex_42115(r, i) = @inbounds getindex(r, i) @inline getindex_42115(r, i, j) = @inbounds getindex(r, i, j) diff --git a/test/broadcast.jl b/test/broadcast.jl index 5cddd0cb174f8..87858dd0f08fc 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -691,14 +691,17 @@ end @test a == [1 1; 2 2; 3 3] end -@testset "scalar .=" begin - A = [[1,2,3],4:5,6] +@testset "scalar .= and promotion" begin + A = [[1, 2, 3], 4:5, 6] + @test A isa Vector{Any} A[1] .= 0 - @test A[1] == [0,0,0] + @test A[1] == [0, 0, 0] @test_throws Base.CanonicalIndexError A[2] .= 0 @test_throws MethodError A[3] .= 0 - A = [[1,2,3],4:5] + A = [[1, 2, 3], 4:5] A[1] .= 0 + @test A[1] isa Vector{Int} + @test A[2] isa UnitRange @test A[1] == [0,0,0] @test_throws Base.CanonicalIndexError A[2] .= 0 end @@ -852,6 +855,39 @@ let @test ndims(copy(bc)) == ndims([v for v in bc]) == ndims(collect(bc)) == ndims(bc) end +# issue 43847: collect preserves shape of broadcasted +let + bc = Broadcast.broadcasted(*, [1 2; 3 4], 2) + @test collect(Iterators.product(bc, bc)) == collect(Iterators.product(copy(bc), copy(bc))) + + a1 = AD1(rand(2,3)) + bc1 = Broadcast.broadcasted(*, a1, 2) + @test collect(Iterators.product(bc1, bc1)) == collect(Iterators.product(copy(bc1), copy(bc1))) + + # using ndims of second arg + bc2 = Broadcast.broadcasted(*, 2, a1) + @test collect(Iterators.product(bc2, bc2)) == collect(Iterators.product(copy(bc2), copy(bc2))) + + # >2 args + bc3 = Broadcast.broadcasted(*, a1, 3, a1) + @test collect(Iterators.product(bc3, bc3)) == collect(Iterators.product(copy(bc3), copy(bc3))) + + # including a tuple and custom array type + bc4 = Broadcast.broadcasted(*, (1,2,3), AD1(rand(3))) + @test collect(Iterators.product(bc4, bc4)) == collect(Iterators.product(copy(bc4), copy(bc4))) + + # testing ArrayConflict + @test Broadcast.broadcasted(+, AD1(rand(3)), AD2(rand(3))) isa Broadcast.Broadcasted{Broadcast.ArrayConflict} + @test Broadcast.broadcasted(+, AD1(rand(3)), AD2(rand(3))) isa Broadcast.Broadcasted{<:Broadcast.AbstractArrayStyle{Any}} + + @test @inferred(Base.IteratorSize(Broadcast.broadcasted(+, (1,2,3), a1, zeros(3,3,3)))) === Base.HasShape{3}() + + # inference on nested + bc = Base.broadcasted(+, AD1(randn(3)), AD1(randn(3))) + bc_nest = Base.broadcasted(+, bc , bc) + @test @inferred(Base.IteratorSize(bc_nest)) === Base.HasShape{1}() + end + # issue #31295 let a = rand(5), b = rand(5), c = copy(a) view(identity(a), 1:3) .+= view(b, 1:3) @@ -1068,7 +1104,7 @@ end end arr = rand(1000) @allocated test(arr) - @test (@allocated test(arr)) == 0 + @test (@allocated test(arr)) <= 16 end @testset "Fix type unstable .&& #43470" begin @@ -1079,3 +1115,21 @@ end y = randn(2) @inferred(test(x, y)) == [0, 0] end + +@testset "issue #45903, in place broadcast into a bit-masked bitmatrix" begin + A = BitArray(ones(3,3)) + pos = randn(3,3) + A[pos .< 0] .= false + @test all(>=(0), pos[A]) + @test count(A) == count(>=(0), pos) +end + +@testset "issue #38432: make CartesianIndex a broadcast scalar" begin + @test CartesianIndex(1,2) .+ (CartesianIndex(3,4), CartesianIndex(5,6)) == (CartesianIndex(4, 6), CartesianIndex(6, 8)) + @test CartesianIndex(1,2) .+ [CartesianIndex(3,4), CartesianIndex(5,6)] == [CartesianIndex(4, 6), CartesianIndex(6, 8)] +end + +# test that `Broadcast` definition is defined as total and eligible for concrete evaluation +import Base.Broadcast: BroadcastStyle, DefaultArrayStyle +@test Base.infer_effects(BroadcastStyle, (DefaultArrayStyle{1},DefaultArrayStyle{2},)) |> + Core.Compiler.is_foldable diff --git a/test/cartesian.jl b/test/cartesian.jl index b3cb8315decad..ed33f2c1035f7 100644 --- a/test/cartesian.jl +++ b/test/cartesian.jl @@ -515,9 +515,30 @@ end f39705() = Base.Cartesian.@nany 0 _ -> true @test f39705() === false +@testset "Cartesian @nall macro test" begin + i_1, i_2, i_3 = 1, 2, 3; + @test Base.Cartesian.@nall 2 d->(i_d <= 2) + @test !Base.Cartesian.@nall 3 d->(i_d <= 2) +end + @testset "CartesianIndices with Bool" begin @test @inferred(CartesianIndices((true,))) == CartesianIndices((1,)) @test @inferred(CartesianIndices((false,))) == CartesianIndices((0,)) @test @inferred(CartesianIndices((true, false))) == CartesianIndices((1, 0)) @test @inferred(CartesianIndices((false, true))) == CartesianIndices((0, 1)) end + +@testset "CartedianIndex isassigned" begin + A = rand(2, 3, 3) + @test isassigned(A, CartesianIndex(1, 2, 3)) + @test !isassigned(A, CartesianIndex(1, 2, 5)) + @test isassigned(A, 1, CartesianIndex(2, 3)) + @test isassigned(A, CartesianIndex(1, 2), 3) + @test !isassigned(A, CartesianIndex(5, 2), 3) +end + +@testset "`CartedianIndex(x::Union{Integer,CartedianIndex}...)`'s stability" begin + CI = CartesianIndex + 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 diff --git a/test/ccall.jl b/test/ccall.jl index 3a1b6ff3db733..0266dabd6332b 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -802,7 +802,7 @@ if cfunction_closure verbose && println("Testing cfunction closures: ") # helper Type for testing that constructors work -# with cfucntion and that object identity is preserved +# with cfunction and that object identity is preserved mutable struct IdentityTestKV{K, V} (T::Type{<:IdentityTestKV})(S) = (@test T === S; T) end @@ -1020,7 +1020,7 @@ end else -@test_broken "cfunction: no support for closures on this platform" +@test_broken "cfunction: no support for closures on this platform" === nothing end @@ -1125,12 +1125,12 @@ struct Struct_AA64_2 v2::Float64 end -# This is a homogenious short vector aggregate +# This is a homogeneous short vector aggregate struct Struct_AA64_3 v1::VecReg{8,Int8} v2::VecReg{2,Float32} end -# This is NOT a homogenious short vector aggregate +# This is NOT a homogeneous short vector aggregate struct Struct_AA64_4 v2::VecReg{2,Float32} v1::VecReg{8,Int16} @@ -1516,6 +1516,12 @@ end @test_throws(ErrorException("ccall return type struct fields cannot contain a reference"), @eval ccall(:fn, typeof(Ref("")), ())) +fn45187() = nothing + +@test_throws(TypeError, @eval ccall(nothing, Cvoid, ())) +@test_throws(TypeError, @eval ccall(49142, Cvoid, ())) +@test_throws(TypeError, @eval ccall((:fn, fn45187), Cvoid, ())) + # test for malformed syntax errors @test Expr(:error, "more arguments than types for ccall") == Meta.lower(@__MODULE__, :(ccall(:fn, A, (), x))) @test Expr(:error, "more arguments than types for ccall") == Meta.lower(@__MODULE__, :(ccall(:fn, A, (B,), x, y))) @@ -1590,6 +1596,32 @@ function caller22734(ptr) end @test caller22734(ptr22734) === 32.0 +# issue #46786 -- non-isbitstypes passed "by-value" +struct NonBits46786 + x::Union{Int16,NTuple{3,UInt8}} +end +let ptr = @cfunction(identity, NonBits46786, (NonBits46786,)) + obj1 = NonBits46786((0x01,0x02,0x03)) + obj2 = ccall(ptr, NonBits46786, (NonBits46786,), obj1) + @test obj1 === obj2 +end +let ptr = @cfunction(identity, Base.RefValue{NonBits46786}, (Base.RefValue{NonBits46786},)) + obj1 = Base.RefValue(NonBits46786((0x01,0x02,0x03))) + obj2 = ccall(ptr, Base.RefValue{NonBits46786}, (Base.RefValue{NonBits46786},), obj1) + @test obj1 !== obj2 + @test obj1.x === obj2.x +end + +mutable struct MutNonBits46786 + x::Union{Int16,NTuple{3,UInt8}} +end +let ptr = @cfunction(identity, MutNonBits46786, (MutNonBits46786,)) + obj1 = MutNonBits46786((0x01,0x02,0x03)) + obj2 = ccall(ptr, MutNonBits46786, (MutNonBits46786,), obj1) + @test obj1 !== obj2 + @test obj1.x === obj2.x +end + # 26297#issuecomment-371165725 # test that the first argument to cglobal is recognized as a tuple literal even through # macro expansion @@ -1767,7 +1799,7 @@ end @test_throws ArgumentError("args in @ccall need type annotations. 'x' doesn't have one.") ccall_macro_parse(:( foo(x)::Cint )) # missing type annotations on varargs arguments @test_throws ArgumentError("args in @ccall need type annotations. 'y' doesn't have one.") ccall_macro_parse(:( foo(x::Cint ; y)::Cint )) - # no reqired args on varargs call + # no required args on varargs call @test_throws ArgumentError("C ABI prohibits vararg without one required argument") ccall_macro_parse(:( foo(; x::Cint)::Cint )) # not a function pointer @test_throws ArgumentError("interpolated function `PROGRAM_FILE` was not a Ptr{Cvoid}, but String") @ccall $PROGRAM_FILE("foo"::Cstring)::Cvoid @@ -1791,7 +1823,7 @@ end str_identity = @cfunction(identity, Cstring, (Cstring,)) foo = @ccall $str_identity("foo"::Cstring)::Cstring @test unsafe_string(foo) == "foo" - # test interpolation of an expresison that returns a pointer. + # test interpolation of an expression that returns a pointer. foo = @ccall $(@cfunction(identity, Cstring, (Cstring,)))("foo"::Cstring)::Cstring @test unsafe_string(foo) == "foo" @@ -1884,6 +1916,12 @@ end function cglobal33413_literal_notype() return cglobal(:sin) end + function cglobal49142_nothing() + return cglobal(nothing) + end + function cglobal45187fn() + return cglobal((:fn, fn45187)) + end @test unsafe_load(cglobal33413_ptrvar()) == 1 @test unsafe_load(cglobal33413_ptrinline()) == 1 @test unsafe_load(cglobal33413_tupleliteral()) == 1 @@ -1892,10 +1930,14 @@ end @test unsafe_load(convert(Ptr{Cint}, cglobal33413_tupleliteral_notype())) == 1 @test cglobal33413_literal() != C_NULL @test cglobal33413_literal_notype() != C_NULL + @test_throws(TypeError, cglobal49142_nothing()) + @test_throws(TypeError, cglobal45187fn()) + @test_throws(TypeError, @eval cglobal(nothing)) + @test_throws(TypeError, @eval cglobal((:fn, fn45187))) end @testset "ccall_effects" begin - ctest_total(x) = @Base.assume_effects :total @ccall libccalltest.ctest(x::Complex{Int})::Complex{Int} + ctest_total(x) = Base.@assume_effects :total @ccall libccalltest.ctest(x::Complex{Int})::Complex{Int} ctest_total_const() = Val{ctest_total(1 + 2im)}() Core.Compiler.return_type(ctest_total_const, Tuple{}) == Val{2 + 0im} end diff --git a/test/channels.jl b/test/channels.jl index 1a989747c3863..dbda5cf069081 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -14,6 +14,28 @@ using Base: n_avail @test fetch(t) == "finished" end +@testset "wait first behavior of wait on Condition" begin + a = Condition() + waiter1 = @async begin + wait(a) + end + waiter2 = @async begin + wait(a) + end + waiter3 = @async begin + wait(a; first=true) + end + waiter4 = @async begin + wait(a) + end + t = @async begin + Base.notify(a, "success"; all=false) + "finished" + end + @test fetch(waiter3) == "success" + @test fetch(t) == "finished" +end + @testset "various constructors" begin c = Channel() @test eltype(c) == Any @@ -275,21 +297,8 @@ end end end - try - timedwait(failure_cb(1), 0) - @test false - catch e - @test e isa CapturedException - @test e.ex isa ErrorException - end - - try - timedwait(failure_cb(2), 0) - @test false - catch e - @test e isa CapturedException - @test e.ex isa ErrorException - end + @test_throws ErrorException("callback failed") timedwait(failure_cb(1), 0) + @test_throws ErrorException("callback failed") timedwait(failure_cb(2), 0) # Validate that `timedwait` actually waits. Ideally we should also test that `timedwait` # doesn't exceed a maximum duration but that would require guarantees from the OS. @@ -301,7 +310,8 @@ end end @testset "timedwait on multiple channels" begin - @Experimental.sync begin + Experimental.@sync begin + sync = Channel(1) rr1 = Channel(1) rr2 = Channel(1) rr3 = Channel(1) @@ -311,20 +321,17 @@ end @test !callback() @test timedwait(callback, 0) === :timed_out - @async begin sleep(0.5); put!(rr1, :ok) end + @async begin put!(sync, :ready); sleep(0.5); put!(rr1, :ok) end @async begin sleep(1.0); put!(rr2, :ok) end - @async begin sleep(2.0); put!(rr3, :ok) end + @async begin @test take!(rr3) == :done end + @test take!(sync) == :ready et = @elapsed timedwait(callback, 1) - # assuming that 0.5 seconds is a good enough buffer on a typical modern CPU - try - @assert (et >= 1.0) && (et <= 1.5) - @assert !isready(rr3) - catch - @warn "`timedwait` tests delayed. et=$et, isready(rr3)=$(isready(rr3))" - end + @test et >= 1.0 + @test isready(rr1) + put!(rr3, :done) end end @@ -372,7 +379,7 @@ end redirect_stderr(oldstderr) close(newstderr[2]) end - @test fetch(errstream) == "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state != :runnable\n" + @test fetch(errstream) == "\nWARNING: Workqueue inconsistency detected: popfirst!(Workqueue).state !== :runnable\n" end @testset "throwto" begin diff --git a/test/char.jl b/test/char.jl index 615c31cfe44e5..1639c62ec819d 100644 --- a/test/char.jl +++ b/test/char.jl @@ -4,6 +4,7 @@ @test typemax(Char) == reinterpret(Char, typemax(UInt32)) @test typemin(Char) == Char(0) + @test typemax(Char) == reinterpret(Char, 0xffffffff) @test ndims(Char) == 0 @test getindex('a', 1) == 'a' @test_throws BoundsError getindex('a', 2) @@ -307,3 +308,27 @@ end @test repr("text/plain", '\U001f428') == "'🐨': Unicode U+1F428 (category So: Symbol, other)" @test repr("text/plain", '\U010f321') == "'\\U10f321': Unicode U+10F321 (category Co: Other, private use)" end + +@testset "malformed chars" begin + u1 = UInt32(0xc0) << 24 + u2 = UInt32(0xc1) << 24 + u3 = UInt32(0x0704) << 21 + u4 = UInt32(0x0f08) << 20 + + overlong_uints = [u1, u2, u3, u4] + overlong_chars = reinterpret.(Char, overlong_uints) + @test all(Base.is_overlong_enc, overlong_uints) + @test all(Base.isoverlong, overlong_chars) + @test all(Base.ismalformed, overlong_chars) + @test repr("text/plain", overlong_chars[1]) == + "'\\xc0': Malformed UTF-8 (category Ma: Malformed, bad data)" +end + +@testset "More fallback tests" begin + @test length(ASCIIChar('x')) == 1 + @test firstindex(ASCIIChar('x')) == 1 + @test !isempty(ASCIIChar('x')) + @test hash(ASCIIChar('x'), UInt(10)) == hash('x', UInt(10)) + @test Base.IteratorSize(Char) == Base.HasShape{0}() + @test convert(ASCIIChar, 1) == Char(1) +end diff --git a/test/checked.jl b/test/checked.jl index 938002cbabcdc..bacda3db75dec 100644 --- a/test/checked.jl +++ b/test/checked.jl @@ -357,4 +357,4 @@ end @test checked_mul(1, 2, 3, 4, 5, 6) === 720 @test checked_mul(1, 2, 3, 4, 5, 6, 7) === 5040 @test checked_mul(1, 2, 3, 4, 5, 6, 7, 8) === 40320 -end \ No newline at end of file +end diff --git a/test/choosetests.jl b/test/choosetests.jl index f86f665bc2217..18af88ea191e9 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -21,7 +21,7 @@ const TESTNAMES = [ "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", "euler", "show", "client", "errorshow", "sets", "goto", "llvmcall", "llvmcall2", "ryu", - "some", "meta", "stacktraces", "docs", + "some", "meta", "stacktraces", "docs", "gc", "misc", "threads", "stress", "binaryplatforms", "atexit", "enums", "cmdlineargs", "int", "interpreter", "checked", "bitset", "floatfuncs", "precompile", @@ -31,6 +31,19 @@ const TESTNAMES = [ "smallarrayshrink", "opaque_closure", "filesystem", "download", ] +const INTERNET_REQUIRED_LIST = [ + "Artifacts", + "Downloads", + "LazyArtifacts", + "LibCURL", + "LibGit2", + "Pkg", + "TOML", + "download", +] + +const NETWORK_REQUIRED_LIST = vcat(INTERNET_REQUIRED_LIST, ["Sockets"]) + """ `(; tests, net_on, exit_on_error, seed) = choosetests(choices)` selects a set of tests to be run. `choices` should be a vector of test names; if empty or set to @@ -140,13 +153,16 @@ function choosetests(choices = []) "strings/io", "strings/types"]) # do subarray before sparse but after linalg filtertests!(tests, "subarray") - filtertests!(tests, "compiler", ["compiler/inference", "compiler/validation", - "compiler/ssair", "compiler/irpasses", "compiler/codegen", - "compiler/inline", "compiler/contextual", + 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/local", "compiler/EscapeAnalysis/interprocedural"]) filtertests!(tests, "compiler/EscapeAnalysis", [ "compiler/EscapeAnalysis/local", "compiler/EscapeAnalysis/interprocedural"]) filtertests!(tests, "stdlib", STDLIBS) + filtertests!(tests, "internet_required", INTERNET_REQUIRED_LIST) # do ambiguous first to avoid failing if ambiguities are introduced by other tests filtertests!(tests, "ambiguous") @@ -157,43 +173,35 @@ function choosetests(choices = []) filter!(x -> (x != "Profile"), tests) end - net_required_for = [ - "Artifacts", - "Downloads", - "LazyArtifacts", - "LibCURL", - "LibGit2", - "Sockets", - "download", - ] + if ccall(:jl_running_on_valgrind,Cint,()) != 0 && "rounding" in tests + @warn "Running under valgrind: Skipping rounding tests" + filter!(x -> x != "rounding", tests) + end + + net_required_for = filter!(in(tests), NETWORK_REQUIRED_LIST) net_on = true - JULIA_TEST_NETWORKING_AVAILABLE = get(ENV, "JULIA_TEST_NETWORKING_AVAILABLE", "") |> - strip |> - lowercase |> - s -> tryparse(Bool, s) |> - x -> x === true + JULIA_TEST_NETWORKING_AVAILABLE = Base.get_bool_env("JULIA_TEST_NETWORKING_AVAILABLE", false) === true # If the `JULIA_TEST_NETWORKING_AVAILABLE` environment variable is set to `true`, we # always set `net_on` to `true`. # Otherwise, we set `net_on` to true if and only if networking is actually available. if !JULIA_TEST_NETWORKING_AVAILABLE try - ipa = getipaddr() + getipaddr() catch if ci_option_passed @error("Networking unavailable, but `--ci` was passed") rethrow() end net_on = false - @warn "Networking unavailable: Skipping tests [" * join(net_required_for, ", ") * "]" - filter!(!in(net_required_for), tests) + if isempty(net_required_for) + @warn "Networking unavailable" + else + @warn "Networking unavailable: Skipping tests [" * join(net_required_for, ", ") * "]" + filter!(!in(net_required_for), tests) + end end end - if ccall(:jl_running_on_valgrind,Cint,()) != 0 && "rounding" in tests - @warn "Running under valgrind: Skipping rounding tests" - filter!(x -> x != "rounding", tests) - end - filter!(!in(tests), unhandled) filter!(!in(skip_tests), tests) diff --git a/test/clangsa/GCPushPop.cpp b/test/clangsa/GCPushPop.cpp index f8dcfdafa5aa9..a62c1501bf323 100644 --- a/test/clangsa/GCPushPop.cpp +++ b/test/clangsa/GCPushPop.cpp @@ -3,6 +3,7 @@ // RUN: clang -D__clang_gcanalyzer__ --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang libGCCheckerPlugin%shlibext -Xclang -verify -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} -Xclang -analyzer-checker=core,julia.GCChecker --analyzer-no-default-checks -x c++ %s #include "julia.h" +#include <string> void missingPop() { jl_value_t *x = NULL; @@ -34,3 +35,21 @@ void jl_gc_run_finalizers_in_list(jl_ptls_t ptls, arraylist_t *list) // run_finalizer(ptls, items[i], items[i + 1]); JL_GC_POP(); } + +bool testfunc1() JL_NOTSAFEPOINT +{ + struct implied_struct1 { // expected-note{{Tried to call method defined here}} + std::string s; + struct implied_constructor { } x; + } x; // expected-warning{{Calling potential safepoint as CXXConstructorCall from function annotated JL_NOTSAFEPOINT}} + // expected-note@-1{{Calling potential safepoint as CXXConstructorCall from function annotated JL_NOTSAFEPOINT}} + return 1; +} +bool testfunc2() JL_NOTSAFEPOINT +{ + struct implied_struct2 { // expected-note{{Tried to call method defined here}} + std::string s; + } x{""}; + return 1; // expected-warning{{Calling potential safepoint as CXXDestructorCall from function annotated JL_NOTSAFEPOINT}} + // expected-note@-1{{Calling potential safepoint as CXXDestructorCall from function annotated JL_NOTSAFEPOINT}} +} diff --git a/test/clangsa/ImplicitAtomicsTest.c b/test/clangsa/ImplicitAtomicsTest.c index 2ad1e0b5f1016..87154347d9757 100644 --- a/test/clangsa/ImplicitAtomicsTest.c +++ b/test/clangsa/ImplicitAtomicsTest.c @@ -1,9 +1,7 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -// RUN-TODO: clang-tidy %s --checks=-*,concurrency-implicit-atomics -load libImplicitAtomics2Plugin%shlibext -- -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} -x c -std=c11 | FileCheck --check-prefixes=CHECK,CHECK-C %s -// RUN-TODO: clang-tidy %s --checks=-*,concurrency-implicit-atomics -load libImplicitAtomics2Plugin%shlibext -- -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} ${CXXFLAGS} -x c++ -std=c++11 | FileCheck --check-prefixes=CHECK,CHECK-CXX %s -// RUN: clang --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang libImplicitAtomicsPlugin%shlibext -Xclang -verify -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} -Xclang -analyzer-checker=core,julia.ImplicitAtomics --analyzer-no-default-checks -x c -std=c11 %s -v -// RUN: clang --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang libImplicitAtomicsPlugin%shlibext -Xclang -verify -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} ${CXXFLAGS} -Xclang -analyzer-checker=core,julia.ImplicitAtomics --analyzer-no-default-checks -x c++ -std=c++11 %s -v +// RUN: clang-tidy %s --checks=-*,concurrency-implicit-atomics -load libImplicitAtomicsPlugin%shlibext -- -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} -x c -std=c11 | FileCheck --check-prefixes=CHECK,CHECK-C %s +// RUN: clang-tidy %s --checks=-*,concurrency-implicit-atomics -load libImplicitAtomicsPlugin%shlibext -- -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} ${CXXFLAGS} -x c++ -std=c++11 | FileCheck --check-prefixes=CHECK,CHECK-CXX %s #include "julia_atomics.h" @@ -19,85 +17,50 @@ _Atomic(int) z[2]; // jwn: add tests for casts, and *py = y; void hiddenAtomics(void) { - // CHECK-NOT: [[@LINE+1]] - px = &x; - // CHECK-NOT: [[@LINE+1]] - py = &y; - // CHECK-NOT: [[@LINE+1]] - y.px = &y.x; - // CHECK: [[@LINE+1]]:7: warning: Implicit Atomic seq_cst synchronization - ++x; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:7: warning: Implicit Atomic seq_cst synchronization - --x; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x++; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x--; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x += 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x -= 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + px = &x; // CHECK-NOT: [[@LINE]] + py = &y; // CHECK-NOT: [[@LINE]] + y.px = &y.x; // CHECK-NOT: [[@LINE]] + ++x; // CHECK: [[@LINE]]:7: warning: Implicit Atomic seq_cst synchronization + --x; // CHECK: [[@LINE]]:7: warning: Implicit Atomic seq_cst synchronization + x++; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + x--; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + x += 2; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + x -= 2; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization #ifndef __cplusplus // invalid C++ code - // CHECK-CXX-NOT: [[@LINE+2]]:5: - // CHECK-C: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x *= 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x = // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:9: warning: Implicit Atomic seq_cst synchronization - x; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + // CHECK-CXX-NOT: [[@LINE+1]] + x *= 2; // CHECK-C: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + x = // CHECK-C: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + x; // CHECK-C: [[@LINE]]:9: warning: Implicit Atomic seq_cst synchronization #endif - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x = 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x + 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + x = 2; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + x + 2; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization - // CHECK: [[@LINE+1]]:8: warning: Implicit Atomic seq_cst synchronization - ++*px; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:8: warning: Implicit Atomic seq_cst synchronization - --*px; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-NOT: [[@LINE+1]] - px++; - // CHECK-NOT: [[@LINE+1]] - px--; - // CHECK: [[@LINE+1]]:10: warning: Implicit Atomic seq_cst synchronization - 1 + *px++; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:10: warning: Implicit Atomic seq_cst synchronization - 1 + *px--; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:7: warning: Implicit Atomic seq_cst synchronization - (*px)++; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:7: warning: Implicit Atomic seq_cst synchronization - (*px)--; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *px += 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *px -= 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + ++*px; // CHECK: [[@LINE]]:8: warning: Implicit Atomic seq_cst synchronization + --*px; // CHECK: [[@LINE]]:8: warning: Implicit Atomic seq_cst synchronization + px++; // CHECK-NOT: [[@LINE]] + px--; // CHECK-NOT: [[@LINE]] + 1 + *px++; // CHECK: [[@LINE]]:10: warning: Implicit Atomic seq_cst synchronization + 1 + *px--; // CHECK: [[@LINE]]:10: warning: Implicit Atomic seq_cst synchronization + (*px)++; // CHECK: [[@LINE]]:7: warning: Implicit Atomic seq_cst synchronization + (*px)--; // CHECK: [[@LINE]]:7: warning: Implicit Atomic seq_cst synchronization + *px += 2; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization + *px -= 2; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization #ifndef __cplusplus // invalid C++ code - // CHECK-CXX-NOT: [[@LINE+2]] - // CHECK-C: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *px *= 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *px = // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:9: warning: Implicit Atomic seq_cst synchronization - x; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x = // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:10: warning: Implicit Atomic seq_cst synchronization - *px; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + // CHECK-CXX-NOT: [[@LINE+1]] + *px *= 2; // CHECK-C: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization + *px = // CHECK-C: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization + x; // CHECK-C: [[@LINE]]:9: warning: Implicit Atomic seq_cst synchronization + x = // CHECK-C: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + *px; // CHECK-C: [[@LINE]]:10: warning: Implicit Atomic seq_cst synchronization #endif - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *px = 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *px + 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + *px = 2; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization + *px + 2; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization - // CHECK-NOT: [[@LINE+1]] - *(int*)&x = 3; - // CHECK-NOT: [[@LINE+1]] - *(int*)px = 3; + *(int*)&x = 3; // CHECK-NOT: [[@LINE]] + *(int*)px = 3; // CHECK-NOT: [[@LINE]] - // CHECK-NOT: [[@LINE+1]] - y.y = 2; - // CHECK-NOT: [[@LINE+1]] - py->y = 2; + y.y = 2; // CHECK-NOT: [[@LINE]] + py->y = 2; // CHECK-NOT: [[@LINE]] #ifndef __cplusplus // invalid C++ code // CHECK-CXX-NOT: [[@LINE+1]] *py = // TODO @@ -105,64 +68,45 @@ void hiddenAtomics(void) { y = // TODO *py; // TODO #endif - // CHECK: [[@LINE+1]]:22: warning: Implicit Atomic seq_cst synchronization - *(_Atomic(int)*)&y.y = 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:22: warning: Implicit Atomic seq_cst synchronization - *(_Atomic(int)*)&py->y = 2; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + *(_Atomic(int)*)&y.y = 2; // CHECK: [[@LINE]]:22: warning: Implicit Atomic seq_cst synchronization + *(_Atomic(int)*)&py->y = 2; // CHECK: [[@LINE]]:22: warning: Implicit Atomic seq_cst synchronization - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - y.x = 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *y.px = 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + y.x = 1; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + *y.px = 1; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization #ifndef __cplusplus // invalid C++ code - // CHECK-CXX-NOT: [[@LINE+2]] - // CHECK-C: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x = // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:13: warning: Implicit Atomic seq_cst synchronization - py->x; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - x = // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-C: [[@LINE+1]]:10: warning: Implicit Atomic seq_cst synchronization - *py->px; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + // CHECK-CXX-NOT: [[@LINE+1]] + x = // CHECK-C: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + py->x; // CHECK-C: [[@LINE]]:9: warning: Implicit Atomic seq_cst synchronization + x = // CHECK-C: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + *py->px; // CHECK-C: [[@LINE]]:10: warning: Implicit Atomic seq_cst synchronization #endif - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - py->x = 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *py->px = 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + py->x = 1; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + *py->px = 1; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization - // CHECK: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - z[1] = 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *z = 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *z += 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + z[1] = 1; // CHECK: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + *z = 1; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization + *z += 1; // CHECK: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization #ifdef __cplusplus // check initialization / finalization // CHECK-NOT: [[@LINE+1]] _Atomic(int) lx{2}; - // CHECK-CXX: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - lx = 3; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-CXX: [[@LINE+1]]:5: warning: Implicit Atomic seq_cst synchronization - lx += 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + lx = 3; // CHECK-CXX: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization + lx += 1; // CHECK-CXX: [[@LINE]]:5: warning: Implicit Atomic seq_cst synchronization // CHECK-NOT: [[@LINE+1]] struct large_type { int x[16]; }; // CHECK-NOT: [[@LINE+1]] auto *ly = new std::atomic<struct large_type>(); - // CHECK-CXX: [[@LINE+1]]:6: warning: Implicit Atomic seq_cst synchronization - *ly = // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-NOT: [[@LINE+1]] - ly->load(); - // CHECK-CXX: [[@LINE+1]]:28: warning: Implicit Atomic seq_cst synchronization - struct large_type a = *ly; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - // CHECK-NOT: [[@LINE+1]] - delete ly; + *ly = // CHECK-CXX: [[@LINE]]:6: warning: Implicit Atomic seq_cst synchronization + ly->load(); // CHECK-NOT: [[@LINE]] + struct large_type a = *ly; // CHECK-CXX: [[@LINE]]:28: warning: Implicit Atomic seq_cst synchronization + delete ly; // CHECK-NOT: [[@LINE]] #if 0 // enable for C++2a std::atomic_ref<int> lz(*(int*)px); - lz = 3; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} - lz += 1; // expected-warning{{Implicit Atomic seq_cst synchronization}} expected-note{{Implicit Atomic seq_cst synchronization}} + lz = 3; + lz += 1; #endif #endif } diff --git a/test/clangsa/MissingRoots.c b/test/clangsa/MissingRoots.c index f0b32c54bc7b8..0ff5e633622ce 100644 --- a/test/clangsa/MissingRoots.c +++ b/test/clangsa/MissingRoots.c @@ -352,6 +352,9 @@ void assoc_exact_broken(jl_value_t **args, size_t n, int8_t offs, size_t world) } */ +// declare +jl_typemap_level_t *jl_new_typemap_level(void); + void assoc_exact_ok(jl_value_t *args1, jl_value_t **args, size_t n, int8_t offs, size_t world) { jl_typemap_level_t *cache = jl_new_typemap_level(); JL_GC_PUSH1(&cache); diff --git a/test/client.jl b/test/client.jl index 195743b1d6208..0649ab3241d62 100644 --- a/test/client.jl +++ b/test/client.jl @@ -12,7 +12,7 @@ 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 Stacktrace:.* """s @@ -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 Stacktrace:.* """s, sprint(show, excs)) end diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 0a03e60f6dd03..1d04926ef23af 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -31,6 +31,16 @@ function format_filename(s) return r end +# Returns true if the given command errors, but doesn't signal +function errors_not_signals(cmd::Cmd) + p = run(pipeline(ignorestatus(cmd); stdout=devnull, stderr=devnull)) + return errors_not_signals(p) +end +function errors_not_signals(p::Base.Process) + wait(p) + return process_exited(p) && !Base.process_signaled(p) && !success(p) +end + let fn = format_filename("a%d %p %i %L %l %u z") hd = withenv("HOME" => nothing) do @@ -50,6 +60,78 @@ let @test format_filename("%a%%b") == "a%b" end +@testset "julia_cmd" begin + julia_basic = Base.julia_cmd() + 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), + + ("-J$(unsafe_string(opts.image_file))", false), + + ("--depwarn=yes", false), + ("--depwarn=error", false), + ("--depwarn=no", true), + + ("--check-bounds=yes", false), + ("--check-bounds=no", false), + ("--check-bounds=auto", true), + + ("--inline=no", false), + ("--inline=yes", true), + + ("-O0", false), + ("-O1", false), + ("-O2", true), + ("-O3", false), + + ("--min-optlevel=0", true), + ("--min-optlevel=1", false), + ("--min-optlevel=2", false), + ("--min-optlevel=3", false), + + ("-g0", false), + ("-g1", false), + ("-g2", false), + + ("--compile=no", false), + ("--compile=all", false), + ("--compile=min", false), + ("--compile=yes", true), + + ("--code-coverage=@", false), + ("--code-coverage=user", false), + ("--code-coverage=all", false), + ("--code-coverage=none", true), + + ("--track-allocation=@", false), + ("--track-allocation=user", false), + ("--track-allocation=all", false), + ("--track-allocation=none", true), + + ("--color=yes", false), + ("--color=no", false), + + ("--startup-file=no", false), + ("--startup-file=yes", true), + + # ("--sysimage-native-code=no", false), # takes a lot longer (30s) + ("--sysimage-native-code=yes", true), + + ("--pkgimages=yes", true), + ("--pkgimages=no", false), + ) + @testset "$arg" begin + if default + @test !occursin(arg, get_julia_cmd(arg)) + else + @test occursin(arg, get_julia_cmd(arg)) + end + end + end +end + let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # tests for handling of ENV errors let v = writereadpipeline("println(\"REPL: \", @which(less), @isdefined(InteractiveUtils))", @@ -57,8 +139,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` "JULIA_LOAD_PATH" => "", "JULIA_DEPOT_PATH" => "", "HOME" => homedir())) - @test v[1] == "false\nREPL: InteractiveUtilstrue\n" - @test v[2] + @test v == ("false\nREPL: InteractiveUtilstrue\n", true) end let v = writereadpipeline("println(\"REPL: \", InteractiveUtils)", setenv(`$exename -i -e 'const InteractiveUtils = 3'`, @@ -66,10 +147,9 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` "JULIA_DEPOT_PATH" => ";;;:::", "HOME" => homedir())) # TODO: ideally, `@which`, etc. would still work, but Julia can't handle `using $InterativeUtils` - @test v[1] == "REPL: 3\n" - @test v[2] + @test v == ("REPL: 3\n", true) end - let v = readchomperrors(`$exename -i -e ' + @testset let v = readchomperrors(`$exename -i -e ' empty!(LOAD_PATH) @eval Sys STDLIB=mktempdir() Base.unreference_module(Base.PkgId(Base.UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils")) @@ -83,35 +163,37 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` real_threads = string(ccall(:jl_cpu_threads, Int32, ())) for nc in ("0", "-2", "x", "2x", " ", "") v = readchomperrors(setenv(`$exename -i -E 'Sys.CPU_THREADS'`, "JULIA_CPU_THREADS" => nc, "HOME" => homedir())) - @test v[1] - @test v[2] == real_threads - @test v[3] == "WARNING: couldn't parse `JULIA_CPU_THREADS` environment variable. Defaulting Sys.CPU_THREADS to $real_threads." + @test v == (true, real_threads, + "WARNING: couldn't parse `JULIA_CPU_THREADS` environment variable. Defaulting Sys.CPU_THREADS to $real_threads.") end for nc in ("1", " 1 ", " +1 ", " 0x1 ") - v = readchomperrors(setenv(`$exename -i -E 'Sys.CPU_THREADS'`, "JULIA_CPU_THREADS" => nc, "HOME" => homedir())) - @test v[1] - @test v[2] == "1" - @test isempty(v[3]) + @testset let v = readchomperrors(setenv(`$exename -i -E 'Sys.CPU_THREADS'`, "JULIA_CPU_THREADS" => nc, "HOME" => homedir())) + @test v[1] + @test v[2] == "1" + @test isempty(v[3]) + end end - let v = readchomperrors(setenv(`$exename -e 0`, "JULIA_LLVM_ARGS" => "-print-options", "HOME" => homedir())) + @testset let v = readchomperrors(setenv(`$exename -e 0`, "JULIA_LLVM_ARGS" => "-print-options", "HOME" => homedir())) @test v[1] @test contains(v[2], r"print-options + = 1") @test contains(v[2], r"combiner-store-merge-dependence-limit + = 4") @test contains(v[2], r"enable-tail-merge + = 2") @test isempty(v[3]) end - let v = readchomperrors(setenv(`$exename -e 0`, "JULIA_LLVM_ARGS" => "-print-options -enable-tail-merge=1 -combiner-store-merge-dependence-limit=6", "HOME" => homedir())) + @testset let v = readchomperrors(setenv(`$exename -e 0`, "JULIA_LLVM_ARGS" => "-print-options -enable-tail-merge=1 -combiner-store-merge-dependence-limit=6", "HOME" => homedir())) @test v[1] @test contains(v[2], r"print-options + = 1") @test contains(v[2], r"combiner-store-merge-dependence-limit + = 6") @test contains(v[2], r"enable-tail-merge + = 1") @test isempty(v[3]) end - let v = readchomperrors(setenv(`$exename -e 0`, "JULIA_LLVM_ARGS" => "-print-options -enable-tail-merge=1 -enable-tail-merge=1", "HOME" => homedir())) - @test !v[1] - @test isempty(v[2]) - @test v[3] == "julia: for the --enable-tail-merge option: may only occur zero or one times!" + if Base.libllvm_version < v"15" #LLVM over 15 doesn't care for multiple options + @testset let v = readchomperrors(setenv(`$exename -e 0`, "JULIA_LLVM_ARGS" => "-print-options -enable-tail-merge=1 -enable-tail-merge=1", "HOME" => homedir())) + @test !v[1] + @test isempty(v[2]) + @test v[3] == "julia: for the --enable-tail-merge option: may only occur zero or one times!" + end end end @@ -123,7 +205,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test read(`$exename -v`, String) == read(`$exename --version`, String) # --help - let header = "julia [switches] -- [programfile] [args...]" + let header = "\n julia [switches] -- [programfile] [args...]" @test startswith(read(`$exename -h`, String), header) @test startswith(read(`$exename --help`, String), header) end @@ -161,22 +243,22 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # --eval @test success(`$exename -e "exit(0)"`) - @test !success(`$exename -e "exit(1)"`) + @test errors_not_signals(`$exename -e "exit(1)"`) @test success(`$exename --eval="exit(0)"`) - @test !success(`$exename --eval="exit(1)"`) - @test !success(`$exename -e`) - @test !success(`$exename --eval`) + @test errors_not_signals(`$exename --eval="exit(1)"`) + @test errors_not_signals(`$exename -e`) + @test errors_not_signals(`$exename --eval`) # --eval --interactive (replaced --post-boot) @test success(`$exename -i -e "exit(0)"`) - @test !success(`$exename -i -e "exit(1)"`) + @test errors_not_signals(`$exename -i -e "exit(1)"`) # issue #34924 @test success(`$exename -e 'const LOAD_PATH=1'`) # --print @test read(`$exename -E "1+1"`, String) == "2\n" @test read(`$exename --print="1+1"`, String) == "2\n" - @test !success(`$exename -E`) - @test !success(`$exename --print`) + @test errors_not_signals(`$exename -E`) + @test errors_not_signals(`$exename --print`) # --load let testfile = tempname() @@ -209,16 +291,17 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` end end # -L, --load requires an argument - @test !success(`$exename -L`) - @test !success(`$exename --load`) + @test errors_not_signals(`$exename -L`) + @test errors_not_signals(`$exename --load`) # --cpu-target (requires LLVM enabled) - @test !success(`$exename -C invalidtarget`) - @test !success(`$exename --cpu-target=invalidtarget`) + # Strictly test for failed error, not a segfault, since we had a false positive with just `success()` before. + @test errors_not_signals(`$exename -C invalidtarget`) + @test errors_not_signals(`$exename --cpu-target=invalidtarget`) # -t, --threads - code = "print(Threads.nthreads())" - cpu_threads = ccall(:jl_cpu_threads, Int32, ()) + code = "print(Threads.threadpoolsize())" + cpu_threads = ccall(:jl_effective_threads, Int32, ()) @test string(cpu_threads) == read(`$exename --threads auto -e $code`, String) == read(`$exename --threads=auto -e $code`, String) == @@ -240,23 +323,44 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` withenv("JULIA_NUM_THREADS" => string(cpu_threads)) do @test read(`$exename -e $code`, String) == string(cpu_threads) end - @test !success(`$exename -t 0`) - @test !success(`$exename -t -1`) + @test errors_not_signals(`$exename -t 0`) + @test errors_not_signals(`$exename -t -1`) # Combining --threads and --procs: --threads does propagate withenv("JULIA_NUM_THREADS" => nothing) do - code = "print(sum(remotecall_fetch(Threads.nthreads, x) for x in procs()))" + code = "print(sum(remotecall_fetch(Threads.threadpoolsize, x) for x in procs()))" @test read(`$exename -p2 -t2 -e $code`, String) == "6" end + # Combining --threads and invalid -C should yield a decent error + @test errors_not_signals(`$exename -t 2 -C invalidtarget`) + # --procs @test readchomp(`$exename -q -p 2 -e "println(nworkers())"`) == "2" - @test !success(`$exename -p 0`) + @test errors_not_signals(`$exename -p 0`) let p = run(`$exename --procs=1.0`, wait=false) wait(p) @test p.exitcode == 1 && p.termsignal == 0 end + # --gcthreads + code = "print(Threads.ngcthreads())" + cpu_threads = ccall(:jl_effective_threads, Int32, ()) + @test (cpu_threads == 1 ? "1" : string(div(cpu_threads, 2))) == + read(`$exename --threads auto -e $code`, String) == + read(`$exename --threads=auto -e $code`, String) == + read(`$exename -tauto -e $code`, String) == + read(`$exename -t auto -e $code`, String) + for nt in (nothing, "1") + withenv("JULIA_NUM_GC_THREADS" => nt) do + @test read(`$exename --gcthreads=2 -e $code`, String) == "2" + end + end + + withenv("JULIA_NUM_GC_THREADS" => 2) do + @test read(`$exename -e $code`, String) == "2" + end + # --machine-file # this does not check that machine file works, # only that the filename gets correctly passed to the option struct @@ -278,14 +382,14 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # --color @test readchomp(`$exename --color=yes -E "Base.have_color"`) == "true" @test readchomp(`$exename --color=no -E "Base.have_color"`) == "false" - @test !success(`$exename --color=false`) + @test errors_not_signals(`$exename --color=false`) # --history-file @test readchomp(`$exename -E "Bool(Base.JLOptions().historyfile)" --history-file=yes`) == "true" @test readchomp(`$exename -E "Bool(Base.JLOptions().historyfile)" --history-file=no`) == "false" - @test !success(`$exename --history-file=false`) + @test errors_not_signals(`$exename --history-file=false`) # --code-coverage mktempdir() do dir @@ -326,6 +430,35 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` rm(covfile) @test occursin(expected, got) || (expected, got) @test_broken occursin(expected_good, got) + + # Ask for coverage in specific file + tfile = realpath(inputfile) + @test readchomp(`$exename -E "(Base.JLOptions().code_coverage, unsafe_string(Base.JLOptions().tracked_path))" -L $inputfile + --code-coverage=$covfile --code-coverage=@$tfile`) == "(3, $(repr(tfile)))" + @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 directory + tdir = dirname(realpath(inputfile)) + @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)))" + @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 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)))" + @test isfile(covfile) + got = read(covfile, String) + @test isempty(got) + rm(covfile) end # --track-allocation @@ -420,16 +553,16 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` --check-bounds=no`)) == JL_OPTIONS_CHECK_BOUNDS_OFF end # check-bounds takes yes/no as argument - @test !success(`$exename -E "exit(0)" --check-bounds=false`) + @test errors_not_signals(`$exename -E "exit(0)" --check-bounds=false`) # --depwarn @test readchomp(`$exename --depwarn=no -E "Base.JLOptions().depwarn"`) == "0" @test readchomp(`$exename --depwarn=yes -E "Base.JLOptions().depwarn"`) == "1" - @test !success(`$exename --depwarn=false`) + @test errors_not_signals(`$exename --depwarn=false`) # test deprecated syntax - @test !success(`$exename -e "foo (x::Int) = x * x" --depwarn=error`) + @test errors_not_signals(`$exename -e "foo (x::Int) = x * x" --depwarn=error`) # test deprecated method - @test !success(`$exename -e " + @test errors_not_signals(`$exename -e " foo() = :foo; bar() = :bar @deprecate foo() bar() foo() @@ -447,7 +580,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` Foo.Deprecated """ - @test !success(`$exename -E "$code" --depwarn=error`) + @test errors_not_signals(`$exename -E "$code" --depwarn=error`) @test readchomperrors(`$exename -E "$code" --depwarn=yes`) == (true, "true", "WARNING: Foo.Deprecated is deprecated, use NotDeprecated instead.\n likely near none:8") @@ -461,14 +594,14 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test readchomp(`$exename --inline=yes -E "Bool(Base.JLOptions().can_inline)"`) == "true" @test readchomp(`$exename --inline=no -E "Bool(Base.JLOptions().can_inline)"`) == "false" # --inline takes yes/no as argument - @test !success(`$exename --inline=false`) + @test errors_not_signals(`$exename --inline=false`) # --polly @test readchomp(`$exename -E "Bool(Base.JLOptions().polly)"`) == "true" @test readchomp(`$exename --polly=yes -E "Bool(Base.JLOptions().polly)"`) == "true" @test readchomp(`$exename --polly=no -E "Bool(Base.JLOptions().polly)"`) == "false" # --polly takes yes/no as argument - @test !success(`$exename --polly=false`) + @test errors_not_signals(`$exename --polly=false`) # --fast-math let JL_OPTIONS_FAST_MATH_DEFAULT = 0, @@ -481,12 +614,12 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` @test parse(Int,readchomp(`$exename --math-mode=ieee -E "Int(Base.JLOptions().fast_math)"`)) == JL_OPTIONS_FAST_MATH_OFF @test parse(Int,readchomp(`$exename --math-mode=fast -E - "Int(Base.JLOptions().fast_math)"`)) == JL_OPTIONS_FAST_MATH_ON + "Int(Base.JLOptions().fast_math)"`)) == JL_OPTIONS_FAST_MATH_DEFAULT end # --worker takes default / custom as argument (default/custom arguments # tested in test/parallel.jl) - @test !success(`$exename --worker=true`) + @test errors_not_signals(`$exename --worker=true`) # test passing arguments mktempdir() do dir @@ -502,19 +635,27 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` withenv("JULIA_DEPOT_PATH" => dir) do output = "[\"foo\", \"-bar\", \"--baz\"]" @test readchomp(`$exename $testfile foo -bar --baz`) == output - @test readchomp(`$exename $testfile -- foo -bar --baz`) == output + @test readchomp(`$exename -- $testfile foo -bar --baz`) == output @test readchomp(`$exename -L $testfile -e 'exit(0)' -- foo -bar --baz`) == output @test readchomp(`$exename --startup-file=yes -e 'exit(0)' -- foo -bar --baz`) == output + output = "[\"foo\", \"--\", \"-bar\", \"--baz\"]" + @test readchomp(`$exename $testfile foo -- -bar --baz`) == output + @test readchomp(`$exename -- $testfile foo -- -bar --baz`) == output + @test readchomp(`$exename -L $testfile -e 'exit(0)' foo -- -bar --baz`) == + output + @test readchomp(`$exename -L $testfile -e 'exit(0)' -- foo -- -bar --baz`) == + output + @test readchomp(`$exename --startup-file=yes -e 'exit(0)' foo -- -bar --baz`) == + output + output = "String[]\nString[]" @test readchomp(`$exename -L $testfile $testfile`) == output @test readchomp(`$exename --startup-file=yes $testfile`) == output - @test !success(`$exename --foo $testfile`) - @test readchomp(`$exename -L $testfile -e 'exit(0)' -- foo -bar -- baz`) == - "[\"foo\", \"-bar\", \"--\", \"baz\"]" + @test errors_not_signals(`$exename --foo $testfile`) end end @@ -576,6 +717,8 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` (false, "", "ERROR: option `--inline` is missing an argument") @test readchomperrors(`$exename --startup-file=no -e "@show ARGS" -now -- julia RUN.jl`) == (false, "", "ERROR: unknown option `-n`") + @test readchomperrors(`$exename --interactive=yes`) == + (false, "", "ERROR: option `-i/--interactive` does not accept an argument") # --compiled-modules={yes|no} @test readchomp(`$exename -E "Bool(Base.JLOptions().use_compiled_modules)"`) == "true" @@ -583,7 +726,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` "Bool(Base.JLOptions().use_compiled_modules)"`) == "true" @test readchomp(`$exename --compiled-modules=no -E "Bool(Base.JLOptions().use_compiled_modules)"`) == "false" - @test !success(`$exename --compiled-modules=foo -e "exit(0)"`) + @test errors_not_signals(`$exename --compiled-modules=foo -e "exit(0)"`) # issue #12671, starting from a non-directory # rm(dir) fails on windows with Permission denied @@ -603,14 +746,45 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` end end +# Object file with multiple cpu targets +@testset "Object file for multiple microarchitectures" begin + julia_path = joinpath(Sys.BINDIR, Base.julia_exename()) + outputo_file = tempname() + write(outputo_file, "1") + object_file = tempname() * ".o" + + # This is to test that even with `pkgimages=no`, we can create object file + # with multiple cpu-targets + # The cmd is checked for `--object-o` as soon as it is run. So, to avoid long + # testing times, intentionally don't pass `--sysimage`; when we reach the + # corresponding error, we know that `check_cmdline` has already passed + let v = readchomperrors(`$julia_path + --cpu-target='native;native' + --output-o=$object_file $outputo_file + --pkgimages=no`) + + @test v[1] == false + @test v[2] == "" + @test !contains(v[3], "More than one command line CPU targets specified") + @test v[3] == "ERROR: File \"boot.jl\" not found" + end + + # This is to test that with `pkgimages=yes`, multiple CPU targets are parsed. + # We intentionally fail fast due to a lack of an `--output-o` flag. + let v = readchomperrors(`$julia_path --cpu-target='native;native' --pkgimages=yes`) + @test v[1] == false + @test v[2] == "" + @test contains(v[3], "More than one command line CPU targets specified") + end +end # Find the path of libjulia (or libjulia-debug, as the case may be) # to use as a dummy shlib to open libjulia = if Base.DARWIN_FRAMEWORK abspath(Libdl.dlpath(Base.DARWIN_FRAMEWORK_NAME * - (ccall(:jl_is_debugbuild, Cint, ()) != 0 ? "_debug" : ""))) + (Base.isdebugbuild() ? "_debug" : ""))) else - abspath(Libdl.dlpath((ccall(:jl_is_debugbuild, Cint, ()) != 0) ? "libjulia-debug" : "libjulia")) + abspath(Libdl.dlpath(Base.isdebugbuild() ? "libjulia-debug" : "libjulia")) end @@ -629,8 +803,7 @@ let exename = `$(Base.julia_cmd().exec[1]) -t 1` @test !occursin("Segmentation fault", s) @test !occursin("EXCEPTION_ACCESS_VIOLATION", s) end - @test !success(p) - @test !Base.process_signaled(p) + @test errors_not_signals(p) @test p.exitcode == 1 end end @@ -640,8 +813,7 @@ let exename = `$(Base.julia_cmd().exec[1]) -t 1` let s = read(err, String) @test s == "ERROR: System image file failed consistency check: maybe opened the wrong version?\n" end - @test !success(p) - @test !Base.process_signaled(p) + @test errors_not_signals(p) @test p.exitcode == 1 end end @@ -659,7 +831,7 @@ let exename = Base.julia_cmd() @test parse(Int,readchomp(`$exename -E "Base.JLOptions().startupfile" --startup-file=no`)) == JL_OPTIONS_STARTUPFILE_OFF end - @test !success(`$exename --startup-file=false`) + @test errors_not_signals(`$exename --startup-file=false`) end # Make sure `julia --lisp` doesn't break @@ -780,4 +952,6 @@ end @test lines[3] == "foo" @test lines[4] == "bar" end +#heap-size-hint +@test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=500M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "524288000" end diff --git a/test/combinatorics.jl b/test/combinatorics.jl index 8a27ad6da9971..f8fe4e0bd0829 100644 --- a/test/combinatorics.jl +++ b/test/combinatorics.jl @@ -16,6 +16,16 @@ using Random: randcycle @test binomial(Int64(67), Int64(29)) == binomial(BigInt(67), BigInt(29)) == 7886597962249166160 @test binomial(Int128(131), Int128(62)) == binomial(BigInt(131), BigInt(62)) == 157311720980559117816198361912717812000 @test_throws OverflowError binomial(Int64(67), Int64(30)) + + #Issue 48072 + ∐ = parse(BigInt, "1" * "0"^13 * "666" * "0"^13 * "1") + @test binomial(∐, ∐ - 1) == ∐ + @test binomial(∐, ∐ - 2) == 500000000000066600000000002218280000000000033300000000000000 + @test binomial(∐, ∐ - 3) == binomial(∐, 3) + @test binomial(-big(2), ∐ - 3) == 1000000000000066599999999999999 + @test_throws OverflowError binomial(big(2)^65, big(2)^64) + @test_throws OverflowError binomial(-big(2)^65, big(2)^64) + @test binomial(∐, 2 * ∐) == BigInt(0) end @testset "permutations" begin @@ -34,6 +44,7 @@ end @test invperm((1,2)) == (1,2) @test invperm((2,1)) == (2,1) @test_throws ArgumentError invperm((1,3)) + @test_throws ArgumentError invperm((1,1)) push!(p, 1) @test !isperm(p) diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl new file mode 100644 index 0000000000000..0e94d42fa8866 --- /dev/null +++ b/test/compiler/AbstractInterpreter.jl @@ -0,0 +1,350 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test +const CC = Core.Compiler + +include("irutils.jl") +include("newinterp.jl") + +# OverlayMethodTable +# ================== + +import Base.Experimental: @MethodTable, @overlay + +@newinterp MTOverlayInterp +@MethodTable(OverlayedMT) +CC.method_table(interp::MTOverlayInterp) = CC.OverlayMethodTable(CC.get_world_counter(interp), OverlayedMT) + +function CC.add_remark!(interp::MTOverlayInterp, ::CC.InferenceState, remark) + if interp.meta !== nothing + # Core.println(remark) + push!(interp.meta, remark) + end + return nothing +end + +strangesin(x) = sin(x) +@overlay OverlayedMT strangesin(x::Float64) = iszero(x) ? nothing : cos(x) + +# inference should use the overlayed method table +@test Base.return_types((Float64,); interp=MTOverlayInterp()) do x + strangesin(x) +end |> only === Union{Float64,Nothing} +@test Base.return_types((Any,); interp=MTOverlayInterp()) do x + @invoke strangesin(x::Float64) +end |> only === Union{Float64,Nothing} + +# effect analysis should figure out that the overlayed method is used +@test Base.infer_effects((Float64,); interp=MTOverlayInterp()) do x + strangesin(x) +end |> !Core.Compiler.is_nonoverlayed +@test Base.infer_effects((Any,); interp=MTOverlayInterp()) do x + @invoke strangesin(x::Float64) +end |> !Core.Compiler.is_nonoverlayed + +# account for overlay possibility in unanalyzed matching method +callstrange(::Float64) = strangesin(x) +callstrange(::Nothing) = Core.compilerbarrier(:type, nothing) # trigger inference bail out +callstrange_entry(x) = callstrange(x) # needs to be defined here because of world age +let interp = MTOverlayInterp(Set{Any}()) + matches = Core.Compiler.findall(Tuple{typeof(callstrange),Any}, Core.Compiler.method_table(interp)).matches + @test Core.Compiler.length(matches) == 2 + if Core.Compiler.getindex(matches, 1).method == which(callstrange, (Nothing,)) + @test Base.infer_effects(callstrange_entry, (Any,); interp) |> !Core.Compiler.is_nonoverlayed + @test "Call inference reached maximally imprecise information. Bailing on." in interp.meta + else + @warn "`nonoverlayed` test for inference bailing out is skipped since the method match sort order is changed." + end +end + +# but it should never apply for the native compilation +@test Base.infer_effects((Float64,)) do x + strangesin(x) +end |> Core.Compiler.is_nonoverlayed +@test Base.infer_effects((Any,)) do x + @invoke strangesin(x::Float64) +end |> Core.Compiler.is_nonoverlayed + +# fallback to the internal method table +@test Base.return_types((Int,); interp=MTOverlayInterp()) do x + cos(x) +end |> only === Float64 +@test Base.return_types((Any,); interp=MTOverlayInterp()) do x + @invoke cos(x::Float64) +end |> only === Float64 + +# not fully covered overlay method match +overlay_match(::Any) = nothing +@overlay OverlayedMT overlay_match(::Int) = missing +@test Base.return_types((Any,); interp=MTOverlayInterp()) do x + overlay_match(x) +end |> only === Union{Nothing,Missing} + +# partial concrete evaluation +@test Base.return_types(; interp=MTOverlayInterp()) do + isbitstype(Int) ? nothing : missing +end |> only === Nothing +Base.@assume_effects :terminates_globally function issue41694(x) + res = 1 + 1 < x < 20 || throw("bad") + while x > 1 + res *= x + x -= 1 + end + return res +end +@test Base.return_types(; interp=MTOverlayInterp()) do + issue41694(3) == 6 ? nothing : missing +end |> only === Nothing + +# disable partial concrete evaluation when tainted by any overlayed call +Base.@assume_effects :total totalcall(f, args...) = f(args...) +@test Base.return_types(; interp=MTOverlayInterp()) do + if totalcall(strangesin, 1.0) == cos(1.0) + return nothing + else + return missing + end +end |> only === Nothing + +# GPUCompiler needs accurate inference through kwfunc with the overlay of `Core.throw_inexacterror` +# 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.InferenceParams(::Issue48097Interp) = CC.InferenceParams(; unoptimize_throw_blocks=false) +@overlay Issue48097MT @noinline Core.throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} = return +issue48097(; kwargs...) = return 42 +@test fully_eliminated(; interp=Issue48097Interp(), retval=42) do + issue48097(; a=1f0, b=1.0) +end + +# AbstractLattice +# =============== + +using Core: SlotNumber, Argument +using Core.Compiler: slot_id, tmerge_fast_path +import .CC: + AbstractLattice, BaseInferenceLattice, IPOResultLattice, InferenceLattice, OptimizerLattice, + widenlattice, is_valid_lattice_norec, typeinf_lattice, ipo_lattice, optimizer_lattice, + widenconst, tmeet, tmerge, ⊑, abstract_eval_special_value, widenreturn + +@newinterp TaintInterpreter +struct TaintLattice{PL<:AbstractLattice} <: CC.AbstractLattice + parent::PL +end +CC.widenlattice(𝕃::TaintLattice) = 𝕃.parent +CC.is_valid_lattice_norec(::TaintLattice, @nospecialize(elm)) = isa(elm, Taint) + +struct InterTaintLattice{PL<:AbstractLattice} <: CC.AbstractLattice + parent::PL +end +CC.widenlattice(𝕃::InterTaintLattice) = 𝕃.parent +CC.is_valid_lattice_norec(::InterTaintLattice, @nospecialize(elm)) = isa(elm, InterTaint) + +const AnyTaintLattice{L} = Union{TaintLattice{L},InterTaintLattice{L}} + +CC.typeinf_lattice(::TaintInterpreter) = InferenceLattice(TaintLattice(BaseInferenceLattice.instance)) +CC.ipo_lattice(::TaintInterpreter) = InferenceLattice(InterTaintLattice(IPOResultLattice.instance)) +CC.optimizer_lattice(::TaintInterpreter) = InterTaintLattice(OptimizerLattice()) + +struct Taint + typ + slots::BitSet + function Taint(@nospecialize(typ), slots::BitSet) + if typ isa Taint + slots = typ.slots ∪ slots + typ = typ.typ + end + return new(typ, slots) + end +end +Taint(@nospecialize(typ), id::Int) = Taint(typ, push!(BitSet(), id)) +function Base.:(==)(a::Taint, b::Taint) + return a.typ == b.typ && a.slots == b.slots +end + +struct InterTaint + typ + slots::BitSet + function InterTaint(@nospecialize(typ), slots::BitSet) + if typ isa InterTaint + slots = typ.slots ∪ slots + typ = typ.typ + end + return new(typ, slots) + end +end +InterTaint(@nospecialize(typ), id::Int) = InterTaint(typ, push!(BitSet(), id)) +function Base.:(==)(a::InterTaint, b::InterTaint) + return a.typ == b.typ && a.slots == b.slots +end + +const AnyTaint = Union{Taint, InterTaint} + +function CC.tmeet(𝕃::AnyTaintLattice, @nospecialize(v), @nospecialize(t::Type)) + T = isa(𝕃, TaintLattice) ? Taint : InterTaint + if isa(v, T) + v = v.typ + end + return tmeet(widenlattice(𝕃), v, t) +end +function CC.tmerge(𝕃::AnyTaintLattice, @nospecialize(typea), @nospecialize(typeb)) + r = tmerge_fast_path(𝕃, typea, typeb) + r !== nothing && return r + # type-lattice for Taint + T = isa(𝕃, TaintLattice) ? Taint : InterTaint + if isa(typea, T) + if isa(typeb, T) + return T( + tmerge(widenlattice(𝕃), typea.typ, typeb.typ), + typea.slots ∪ typeb.slots) + else + typea = typea.typ + end + elseif isa(typeb, T) + typeb = typeb.typ + end + return tmerge(widenlattice(𝕃), typea, typeb) +end +function CC.:⊑(𝕃::AnyTaintLattice, @nospecialize(typea), @nospecialize(typeb)) + T = isa(𝕃, TaintLattice) ? Taint : InterTaint + if isa(typea, T) + if isa(typeb, T) + typea.slots ⊆ typeb.slots || return false + return ⊑(widenlattice(𝕃), typea.typ, typeb.typ) + end + typea = typea.typ + elseif isa(typeb, T) + return false + end + return ⊑(widenlattice(𝕃), typea, typeb) +end +CC.widenconst(taint::AnyTaint) = widenconst(taint.typ) + +function CC.abstract_eval_special_value(interp::TaintInterpreter, + @nospecialize(e), vtypes::CC.VarTable, sv::CC.InferenceState) + ret = @invoke CC.abstract_eval_special_value(interp::CC.AbstractInterpreter, + e::Any, vtypes::CC.VarTable, sv::CC.InferenceState) + if isa(e, SlotNumber) || isa(e, Argument) + return Taint(ret, slot_id(e)) + end + return ret +end + +function CC.widenreturn(𝕃::InferenceLattice{<:InterTaintLattice}, @nospecialize(rt), @nospecialize(bestguess), nargs::Int, slottypes::Vector{Any}, changes::CC.VarTable) + if isa(rt, Taint) + return InterTaint(rt.typ, BitSet((id for id in rt.slots if id ≤ nargs))) + end + return CC.widenreturn(widenlattice(𝕃), rt, bestguess, nargs, slottypes, changes) +end + +@test CC.tmerge(typeinf_lattice(TaintInterpreter()), Taint(Int, 1), Taint(Int, 2)) == Taint(Int, BitSet(1:2)) + +# code_typed(ifelse, (Bool, Int, Int); interp=TaintInterpreter()) + +# External lattice without `Conditional` + +import .CC: + AbstractLattice, ConstsLattice, PartialsLattice, InferenceLattice, OptimizerLattice, + typeinf_lattice, ipo_lattice, optimizer_lattice + +@newinterp NonconditionalInterpreter +CC.typeinf_lattice(::NonconditionalInterpreter) = InferenceLattice(PartialsLattice(ConstsLattice())) +CC.ipo_lattice(::NonconditionalInterpreter) = InferenceLattice(PartialsLattice(ConstsLattice())) +CC.optimizer_lattice(::NonconditionalInterpreter) = OptimizerLattice(PartialsLattice(ConstsLattice())) + +@test Base.return_types((Any,); interp=NonconditionalInterpreter()) do x + c = isa(x, Int) || isa(x, Float64) + if c + return x + else + return nothing + end +end |> only === Any + +# CallInfo × inlining +# =================== + +@newinterp NoinlineInterpreter +noinline_modules(interp::NoinlineInterpreter) = interp.meta::Set{Module} + +import .CC: CallInfo + +struct NoinlineCallInfo <: CallInfo + info::CallInfo # wrapped call +end +CC.nsplit_impl(info::NoinlineCallInfo) = CC.nsplit(info.info) +CC.getsplit_impl(info::NoinlineCallInfo, idx::Int) = CC.getsplit(info.info, idx) +CC.getresult_impl(info::NoinlineCallInfo, idx::Int) = CC.getresult(info.info, idx) + +function CC.abstract_call(interp::NoinlineInterpreter, + arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Union{Int,Nothing}) + ret = @invoke CC.abstract_call(interp::CC.AbstractInterpreter, + arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Union{Int,Nothing}) + if sv.mod in noinline_modules(interp) + return CC.CallMeta(ret.rt, ret.effects, NoinlineCallInfo(ret.info)) + end + return ret +end +function CC.inlining_policy(interp::NoinlineInterpreter, + @nospecialize(src), @nospecialize(info::CallInfo), stmt_flag::UInt8, mi::MethodInstance, + argtypes::Vector{Any}) + if isa(info, NoinlineCallInfo) + return nothing + end + return @invoke CC.inlining_policy(interp::CC.AbstractInterpreter, + src::Any, info::CallInfo, stmt_flag::UInt8, mi::MethodInstance, + argtypes::Vector{Any}) +end + +@inline function inlined_usually(x, y, z) + return x * y + z +end + +# check if the inlining algorithm works as expected +let src = code_typed1((Float64,Float64,Float64)) do x, y, z + inlined_usually(x, y, z) + end + @test count(isinvoke(:inlined_usually), src.code) == 0 + @test count(iscall((src, inlined_usually)), src.code) == 0 +end +let NoinlineModule = Module() + interp = NoinlineInterpreter(Set((NoinlineModule,))) + + # this anonymous function's context is Main -- it should be inlined as usual + let src = code_typed1((Float64,Float64,Float64); interp) do x, y, z + inlined_usually(x, y, z) + end + @test count(isinvoke(:inlined_usually), src.code) == 0 + @test count(iscall((src, inlined_usually)), src.code) == 0 + end + + # 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) + let src = code_typed1((Float64,Float64,Float64); interp) do x, y, z + inlined_usually(x, y, z) + end + @test count(isinvoke(:inlined_usually), src.code) == 0 + @test count(iscall((src, inlined_usually)), src.code) == 0 + end + + # now the context module is `NoinlineModule` -- it should not be inlined + let src = @eval NoinlineModule $code_typed1((Float64,Float64,Float64); interp=$interp) do x, y, z + $inlined_usually(x, y, z) + end + @test count(isinvoke(:inlined_usually), src.code) == 1 + @test count(iscall((src, inlined_usually)), src.code) == 0 + end + + # the context module is totally irrelevant -- it should be inlined as usual + OtherModule = Module() + let src = @eval OtherModule $code_typed1((Float64,Float64,Float64); interp=$interp) do x, y, z + $inlined_usually(x, y, z) + end + @test count(isinvoke(:inlined_usually), src.code) == 0 + @test count(iscall((src, inlined_usually)), src.code) == 0 + end +end diff --git a/test/compiler/EscapeAnalysis/EAUtils.jl b/test/compiler/EscapeAnalysis/EAUtils.jl index 3ae9b41a0ddac..6894733e0fa45 100644 --- a/test/compiler/EscapeAnalysis/EAUtils.jl +++ b/test/compiler/EscapeAnalysis/EAUtils.jl @@ -41,17 +41,11 @@ function code_escapes(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world), debuginfo::Symbol = :none, optimize::Bool = true) - ft = Core.Typeof(f) - if isa(types, Type) - u = unwrap_unionall(types) - tt = rewrap_unionall(Tuple{ft, u.parameters...}, types) - else - tt = Tuple{ft, types...} - end + tt = Base.signature_type(f, types) interp = EscapeAnalyzer(interp, tt, optimize) results = Base.code_typed_by_type(tt; optimize=true, world, interp) isone(length(results)) || throw(ArgumentError("`code_escapes` only supports single analysis result")) - return EscapeResult(interp.ir, interp.state, interp.linfo, debuginfo===:source) + return EscapeResult(interp.ir, interp.state, interp.linfo, debuginfo === :source) end # in order to run a whole analysis from ground zero (e.g. for benchmarking, etc.) @@ -63,9 +57,7 @@ __clear_cache!() = empty!(GLOBAL_CODE_CACHE) # imports import .CC: AbstractInterpreter, NativeInterpreter, WorldView, WorldRange, - InferenceParams, OptimizationParams, get_world_counter, get_inference_cache, code_cache, - lock_mi_inference, unlock_mi_inference, add_remark!, - may_optimize, may_compress, may_discard_trees, verbose_stmt_info + InferenceParams, OptimizationParams, get_world_counter, get_inference_cache, code_cache # usings import Core: CodeInstance, MethodInstance, CodeInfo @@ -99,16 +91,6 @@ CC.InferenceParams(interp::EscapeAnalyzer) = InferenceParams(interp.native) CC.OptimizationParams(interp::EscapeAnalyzer) = OptimizationParams(interp.native) CC.get_world_counter(interp::EscapeAnalyzer) = get_world_counter(interp.native) -CC.lock_mi_inference(::EscapeAnalyzer, ::MethodInstance) = nothing -CC.unlock_mi_inference(::EscapeAnalyzer, ::MethodInstance) = nothing - -CC.add_remark!(interp::EscapeAnalyzer, sv, s) = add_remark!(interp.native, sv, s) - -CC.may_optimize(interp::EscapeAnalyzer) = may_optimize(interp.native) -CC.may_compress(interp::EscapeAnalyzer) = may_compress(interp.native) -CC.may_discard_trees(interp::EscapeAnalyzer) = may_discard_trees(interp.native) -CC.verbose_stmt_info(interp::EscapeAnalyzer) = verbose_stmt_info(interp.native) - CC.get_inference_cache(interp::EscapeAnalyzer) = get_inference_cache(interp.native) const GLOBAL_CODE_CACHE = IdDict{MethodInstance,CodeInstance}() @@ -159,16 +141,16 @@ function invalidate_cache!(replaced, max_world, depth = 0) end function CC.optimize(interp::EscapeAnalyzer, - opt::OptimizationState, params::OptimizationParams, caller::InferenceResult) + opt::OptimizationState, caller::InferenceResult) ir = run_passes_with_ea(interp, opt.src, opt, caller) - return CC.finish(interp, opt, params, ir, caller) + return CC.finish(interp, opt, ir, caller) end function CC.cache_result!(interp::EscapeAnalyzer, caller::InferenceResult) if haskey(interp.cache, caller) GLOBAL_ESCAPE_CACHE[caller.linfo] = interp.cache[caller] end - return Base.@invoke CC.cache_result!(interp::AbstractInterpreter, caller::InferenceResult) + return @invoke CC.cache_result!(interp::AbstractInterpreter, caller::InferenceResult) end const GLOBAL_ESCAPE_CACHE = IdDict{MethodInstance,EscapeCache}() @@ -213,7 +195,7 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati cache_escapes!(interp, caller, state, cccopy(ir)) end catch err - @error "error happened within [IPO EA], insepct `Main.ir` and `Main.nargs`" + @error "error happened within [IPO EA], inspect `Main.ir` and `Main.nargs`" @eval Main (ir = $ir; nargs = $nargs) rethrow(err) end @@ -224,14 +206,14 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati interp.state = state interp.linfo = sv.linfo end - @timeit "Inlining" ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds) + @timeit "Inlining" ir = ssa_inlining_pass!(ir, sv.inlining, ci.propagate_inbounds) # @timeit "verify 2" verify_ir(ir) @timeit "compact 2" ir = compact!(ir) if caller.linfo.specTypes === interp.entry_tt && interp.optimize try @timeit "[Local EA]" state = analyze_escapes(ir, nargs, true, get_escape_cache(interp)) catch err - @error "error happened within [Local EA], insepct `Main.ir` and `Main.nargs`" + @error "error happened within [Local EA], inspect `Main.ir` and `Main.nargs`" @eval Main (ir = $ir; nargs = $nargs) rethrow(err) end @@ -288,7 +270,7 @@ end function Base.show(io::IO, x::EscapeInfo) name, color = get_name_color(x) if isnothing(name) - Base.@invoke show(io::IO, x::Any) + @invoke show(io::IO, x::Any) else printstyled(io, name; color) end diff --git a/test/compiler/EscapeAnalysis/interprocedural.jl b/test/compiler/EscapeAnalysis/interprocedural.jl index 42a2505e03c08..756e5489ed637 100644 --- a/test/compiler/EscapeAnalysis/interprocedural.jl +++ b/test/compiler/EscapeAnalysis/interprocedural.jl @@ -79,12 +79,12 @@ let result = code_escapes((SafeRef{String},); optimize=false) do x end # InvokeCallInfo let result = code_escapes((SafeRef{String},); optimize=false) do x - return Base.@invoke noescape(x::Any) + return @invoke noescape(x::Any) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end let result = code_escapes((SafeRef{String},); optimize=false) do x - return Base.@invoke conditional_escape!(false::Any, x::Any) + return @invoke conditional_escape!(false::Any, x::Any) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end diff --git a/test/compiler/EscapeAnalysis/local.jl b/test/compiler/EscapeAnalysis/local.jl index e5d8f1bf2c940..dd324c3619dc7 100644 --- a/test/compiler/EscapeAnalysis/local.jl +++ b/test/compiler/EscapeAnalysis/local.jl @@ -1997,9 +1997,9 @@ let result = code_escapes((Int,String,)) do n,s i = only(findall(isarrayalloc, result.ir.stmts.inst)) r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[SSAValue(i)], r) - Base.JLOptions().check_bounds ≠ 0 && @test has_thrown_escape(result.state[SSAValue(i)]) + @test !has_thrown_escape(result.state[SSAValue(i)]) @test has_return_escape(result.state[Argument(3)], r) # s - Base.JLOptions().check_bounds ≠ 0 && @test has_thrown_escape(result.state[Argument(3)]) # s + @test !has_thrown_escape(result.state[Argument(3)]) # s end let result = code_escapes((Int,String,)) do n,s xs = String[] @@ -2011,9 +2011,9 @@ let result = code_escapes((Int,String,)) do n,s i = only(findall(isarrayalloc, result.ir.stmts.inst)) r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[SSAValue(i)], r) # xs - @test has_thrown_escape(result.state[SSAValue(i)]) # xs + @test !has_thrown_escape(result.state[SSAValue(i)]) # xs @test has_return_escape(result.state[Argument(3)], r) # s - @test has_thrown_escape(result.state[Argument(3)]) # s + @test !has_thrown_escape(result.state[Argument(3)]) # s end let result = code_escapes((String,String,String)) do s, t, u xs = String[] diff --git a/test/compiler/EscapeAnalysis/setup.jl b/test/compiler/EscapeAnalysis/setup.jl index 4e7d6fb5159aa..18221e5afc524 100644 --- a/test/compiler/EscapeAnalysis/setup.jl +++ b/test/compiler/EscapeAnalysis/setup.jl @@ -1,3 +1,4 @@ +include(normpath(@__DIR__, "..", "irutils.jl")) include(normpath(@__DIR__, "EAUtils.jl")) using Test, Core.Compiler.EscapeAnalysis, .EAUtils import Core: Argument, SSAValue, ReturnNode @@ -7,7 +8,6 @@ import .EA: ignore_argescape isT(T) = (@nospecialize x) -> x === T isreturn(@nospecialize x) = isa(x, Core.ReturnNode) && isdefined(x, :val) isthrow(@nospecialize x) = Meta.isexpr(x, :call) && Core.Compiler.is_throw_call(x) -isnew(@nospecialize x) = Meta.isexpr(x, :new) isϕ(@nospecialize x) = isa(x, Core.PhiNode) function with_normalized_name(@nospecialize(f), @nospecialize(x)) if Meta.isexpr(x, :foreigncall) @@ -20,19 +20,6 @@ end isarrayalloc(@nospecialize x) = with_normalized_name(nn->!isnothing(Core.Compiler.alloc_array_ndims(nn)), x) isarrayresize(@nospecialize x) = with_normalized_name(nn->!isnothing(EA.array_resize_info(nn)), x) isarraycopy(@nospecialize x) = with_normalized_name(nn->EA.is_array_copy(nn), x) -import Core.Compiler: argextype, singleton_type -iscall(y) = @nospecialize(x) -> iscall(y, x) -function iscall((ir, f), @nospecialize(x)) - return iscall(x) do @nospecialize x - singleton_type(Core.Compiler.argextype(x, ir, Any[])) === f - end -end -iscall(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :call) && pred(x.args[1]) - -# check if `x` is a statically-resolved call of a function whose name is `sym` -isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) -isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) -isinvoke(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :invoke) && pred(x.args[1]::Core.MethodInstance) """ is_load_forwardable(x::EscapeInfo) -> Bool diff --git a/test/compiler/codegen.jl b/test/compiler/codegen.jl index ec89ac9cd72a4..8a3949212ea16 100644 --- a/test/compiler/codegen.jl +++ b/test/compiler/codegen.jl @@ -10,11 +10,19 @@ const opt_level = Base.JLOptions().opt_level const coverage = (Base.JLOptions().code_coverage > 0) || (Base.JLOptions().malloc_log > 0) const Iptr = sizeof(Int) == 8 ? "i64" : "i32" -# `_dump_function` might be more efficient but it doesn't really matter here... -get_llvm(@nospecialize(f), @nospecialize(t), raw=true, dump_module=false, optimize=true) = - sprint(code_llvm, f, t, raw, dump_module, optimize) +const is_debug_build = Base.isdebugbuild() +function libjulia_codegen_name() + is_debug_build ? "libjulia-codegen-debug" : "libjulia-codegen" +end -if opt_level > 0 +# The tests below assume a certain format and safepoint_on_entry=true breaks that. +function get_llvm(@nospecialize(f), @nospecialize(t), raw=true, dump_module=false, optimize=true) + params = Base.CodegenParams(safepoint_on_entry=false) + d = InteractiveUtils._dump_function(f, t, false, false, !raw, dump_module, :att, optimize, :none, false, params) + sprint(print, d) +end + +if !is_debug_build && opt_level > 0 # Make sure getptls call is removed at IR level with optimization on @test !occursin(" call ", get_llvm(identity, Tuple{String})) end @@ -104,7 +112,7 @@ function test_jl_dump_llvm_opt() end end -if opt_level > 0 +if !is_debug_build && opt_level > 0 # Make sure `jl_string_ptr` is inlined @test !occursin(" call ", get_llvm(jl_string_ptr, Tuple{String})) # Make sure `Core.sizeof` call is inlined @@ -418,9 +426,15 @@ let src = get_llvm(f33829, Tuple{Float64}, true, true) @test !occursin(r"call [^(]*\{}", src) end +# Base.vect prior to PR 41696 +function oldvect(X...) + T = Base.promote_typeof(X...) + return copyto!(Vector{T}(undef, length(X)), X) +end + let io = IOBuffer() # Test for the f(args...) = g(args...) generic codegen optimization - code_llvm(io, Base.vect, Tuple{Vararg{Union{Float64, Int64}}}) + code_llvm(io, oldvect, Tuple{Vararg{Union{Float64, Int64}}}) @test !occursin("__apply", String(take!(io))) end @@ -490,8 +504,9 @@ function f37262(x) end @testset "#37262" begin str = "store volatile { i8, {}*, {}*, {}*, {}* } zeroinitializer, { i8, {}*, {}*, {}*, {}* }* %phic" + str_opaque = "store volatile { i8, ptr, ptr, ptr, ptr } zeroinitializer, ptr %phic" llvmstr = get_llvm(f37262, (Bool,), false, false, false) - @test contains(llvmstr, str) || llvmstr + @test (contains(llvmstr, str) || contains(llvmstr, str_opaque)) || llvmstr @test f37262(Base.inferencebarrier(true)) === nothing end @@ -634,7 +649,7 @@ end # issue #41157 f41157(a, b) = a[1] = b[1] -@test_throws BoundsError f41157(Tuple{Int}[], Tuple{Union{}}[]) +@test_throws BoundsError f41157(Tuple{Int}[], (NTuple{N,Union{}} where N)[]) # issue #41096 struct Modulate41096{M<:Union{Function, Val{true}, Val{false}}, id} @@ -668,17 +683,31 @@ U41096 = Term41096{:U}(Modulate41096(:U, false)) @test !newexpand41096((t=t41096, μ=μ41096, U=U41096), :U) + # test that we can start julia with libjulia-codegen removed; PR #41936 mktempdir() do pfx cp(dirname(Sys.BINDIR), pfx; force=true) - libpath = relpath(dirname(dlpath("libjulia-codegen")), dirname(Sys.BINDIR)) + libpath = relpath(dirname(dlpath(libjulia_codegen_name())), dirname(Sys.BINDIR)) libs_deleted = 0 - for f in filter(f -> startswith(f, "libjulia-codegen"), readdir(joinpath(pfx, libpath))) + libfiles = filter(f -> startswith(f, "libjulia-codegen"), readdir(joinpath(pfx, libpath))) + for f in libfiles rm(joinpath(pfx, libpath, f); force=true, recursive=true) libs_deleted += 1 end @test libs_deleted > 0 @test readchomp(`$pfx/bin/$(Base.julia_exename()) -e 'print("no codegen!\n")'`) == "no codegen!" + + # PR #47343 + libs_emptied = 0 + for f in libfiles + touch(joinpath(pfx, libpath, f)) + libs_emptied += 1 + end + + errfile = joinpath(pfx, "stderr.txt") + @test libs_emptied > 0 + @test_throws ProcessFailedException run(pipeline(`$pfx/bin/$(Base.julia_exename()) -e 'print("This should fail!\n")'`; stderr=errfile)) + @test contains(readline(errfile), "ERROR: Unable to load dependent library") end # issue #42645 @@ -702,6 +731,23 @@ function f42645() end @test ((f42645()::B42645).y::A42645{Int}).x +struct A44921{T} + x::T +end +function f44921(a) + if a === :x + A44921(_f) # _f purposefully undefined + elseif a === :p + g44921(a) + end +end +function g44921(a) + if !@isdefined _f # just needs to be some non constprop-able condition + A44921(()) + end +end +@test f44921(:p) isa A44921 + # issue #43123 @noinline cmp43123(a::Some, b::Some) = something(a) === something(b) @noinline cmp43123(a, b) = a[] === b[] @@ -716,4 +762,61 @@ end f_donotdelete_input(x) = Base.donotdelete(x+1) f_donotdelete_const() = Base.donotdelete(1+1) @test occursin("call void (...) @jl_f_donotdelete(i64", get_llvm(f_donotdelete_input, Tuple{Int64}, true, false, false)) -@test occursin("call void (...) @jl_f_donotdelete()", get_llvm(f_donotdelete_const, Tuple{}, true, false, false)) \ No newline at end of file +@test occursin("call void (...) @jl_f_donotdelete()", get_llvm(f_donotdelete_const, Tuple{}, true, false, false)) + +# Test 45476 fixes +struct MaybeTuple45476 + val::Union{Nothing, Tuple{Float32}} +end + +@test MaybeTuple45476((0,)).val[1] == 0f0 + +# Test int paths for getfield/isdefined +f_getfield_nospecialize(@nospecialize(x)) = getfield(x, 1) +f_isdefined_nospecialize(@nospecialize(x)) = isdefined(x, 1) + +@test !occursin("jl_box_int", get_llvm(f_getfield_nospecialize, Tuple{Any}, true, false, false)) +@test !occursin("jl_box_int", get_llvm(f_isdefined_nospecialize, Tuple{Any}, true, false, false)) + +# Test codegen for isa(::Any, Type) +f_isa_type(@nospecialize(x)) = isa(x, Type) +@test !occursin("jl_isa", get_llvm(f_isa_type, Tuple{Any}, true, false, false)) + +# Issue #47247 +f47247(a::Ref{Int}, b::Nothing) = setfield!(a, :x, b) +@test_throws TypeError f47247(Ref(5), nothing) + +f48085(@nospecialize x...) = length(x) +@test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Vararg{Int}}, Core.svec()) === nothing +@test Core.Compiler.get_compileable_sig(which(f48085, (Vararg{Any},)), Tuple{typeof(f48085), Int, Vararg{Int}}, Core.svec()) === Tuple{typeof(f48085), Any, Vararg{Any}} + +# Make sure that the bounds check is elided in tuple iteration +@test !occursin("call void @", get_llvm(iterate, Tuple{NTuple{4, Float64}, Int})) + +# issue #34459 +function f34459(args...) + Base.pointerset(args[1], 1, 1, 1) + return +end +@test !occursin("jl_f_tuple", get_llvm(f34459, Tuple{Ptr{Int}, Type{Int}}, true, false, false)) + +# issue #48394: incorrectly-inferred getproperty shouldn't introduce invalid cgval_t +# when dealing with unions of ghost values +struct X48394 + x::Nothing + y::Bool +end +struct Y48394 + x::Nothing + z::Missing +end +function F48394(a, b, i) + c = i ? a : b + c.y +end +@test F48394(X48394(nothing,true), Y48394(nothing, missing), true) +@test occursin("llvm.trap", get_llvm(F48394, Tuple{X48394, Y48394, Bool})) + +# issue 48917, hoisting load to above the parent +f48917(x, w) = (y = (a=1, b=x); z = (; a=(a=(1, w), b=(3, y)))) +@test f48917(1,2) == (a = (a = (1, 2), b = (3, (a = 1, b = 1))),) diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index e89b56e4bf6de..0e8fe27591a5e 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -1,12 +1,15 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Cassette +# ======== + module MiniCassette # A minimal demonstration of the cassette mechanism. Doesn't support all the # fancy features, but sufficient to exercise this code path in the compiler. - using Core.Compiler: method_instances, retrieve_code_info, CodeInfo, - MethodInstance, SSAValue, GotoNode, GotoIfNot, ReturnNode, Slot, SlotNumber, quoted, - signature_type + using Core.Compiler: retrieve_code_info, CodeInfo, + MethodInstance, SSAValue, GotoNode, GotoIfNot, ReturnNode, SlotNumber, quoted, + signature_type, anymap using Base: _methods_by_ftype using Base.Meta: isexpr using Test @@ -16,10 +19,11 @@ module MiniCassette struct Ctx; end # A no-op cassette-like transform - function transform_expr(expr, map_slot_number, map_ssa_value, sparams) - transform(expr) = transform_expr(expr, map_slot_number, map_ssa_value, sparams) + function transform_expr(expr, map_slot_number, map_ssa_value, sparams::Core.SimpleVector) + @nospecialize expr + transform(@nospecialize expr) = transform_expr(expr, map_slot_number, map_ssa_value, sparams) if isexpr(expr, :call) - return Expr(:call, overdub, SlotNumber(2), map(transform, expr.args)...) + return Expr(:call, overdub, SlotNumber(2), anymap(transform, expr.args)...) elseif isa(expr, GotoIfNot) return GotoIfNot(transform(expr.cond), map_ssa_value(SSAValue(expr.dest)).id) elseif isexpr(expr, :static_parameter) @@ -27,10 +31,10 @@ module MiniCassette elseif isa(expr, ReturnNode) return ReturnNode(transform(expr.val)) elseif isa(expr, Expr) - return Expr(expr.head, map(transform, expr.args)...) + return Expr(expr.head, anymap(transform, expr.args)...) elseif isa(expr, GotoNode) return GotoNode(map_ssa_value(SSAValue(expr.label)).id) - elseif isa(expr, Slot) + elseif isa(expr, SlotNumber) return map_slot_number(expr.id) elseif isa(expr, SSAValue) return map_ssa_value(expr) @@ -39,16 +43,16 @@ module MiniCassette end end - function transform!(ci, nargs, sparams) + function transform!(ci::CodeInfo, nargs::Int, sparams::Core.SimpleVector) code = ci.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, [Expr(:call, getfield, SlotNumber(4), i) for i = 1:nargs]) - prepend!(ci.codelocs, [0 for i = 1:nargs]) - prepend!(ci.ssaflags, [0x00 for i = 1:nargs]) + prepend!(code, Any[Expr(:call, getfield, SlotNumber(4), i) for i = 1:nargs]) + prepend!(ci.codelocs, fill(0, nargs)) + prepend!(ci.ssaflags, fill(0x00, nargs)) ci.ssavaluetypes += nargs - function map_slot_number(slot) + function map_slot_number(slot::Int) if slot == 1 # self in the original function is now `f` return SlotNumber(3) @@ -66,24 +70,28 @@ module MiniCassette end end - function overdub_generator(self, c, f, args) - if !isdefined(f, :instance) - return :(return f(args...)) + function overdub_generator(world::UInt, source, self, c, f, args) + @nospecialize + if !Base.issingletontype(f) + # (c, f, args..) -> f(args...) + code_info = :(return f(args...)) + return Core.GeneratedFunctionStub(identity, Core.svec(:overdub, :c, :f, :args), Core.svec())(world, source, code_info) end tt = Tuple{f, args...} - match = Base._which(tt, typemax(UInt)) + match = Base._which(tt; world) mi = Core.Compiler.specialize_method(match) # Unsupported in this mini-cassette @assert !mi.def.isva - code_info = retrieve_code_info(mi) + code_info = retrieve_code_info(mi, world) @assert isa(code_info, CodeInfo) code_info = copy(code_info) - if isdefined(code_info, :edges) - code_info.edges = MethodInstance[mi] - end + @assert code_info.edges === nothing + code_info.edges = MethodInstance[mi] transform!(code_info, length(args), match.sparams) - code_info + # 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 end @inline function overdub(c::Ctx, f::Union{Core.Builtin, Core.IntrinsicFunction}, args...) @@ -92,16 +100,7 @@ module MiniCassette @eval function overdub(c::Ctx, f, args...) $(Expr(:meta, :generated_only)) - $(Expr(:meta, - :generated, - Expr(:new, - Core.GeneratedFunctionStub, - :overdub_generator, - Any[:overdub, :ctx, :f, :args], - Any[], - @__LINE__, - QuoteNode(Symbol(@__FILE__)), - true))) + $(Expr(:meta, :generated, overdub_generator)) end end @@ -116,30 +115,13 @@ f() = 2 # Test that MiniCassette is at least somewhat capable by overdubbing gcd @test overdub(Ctx(), gcd, 10, 20) === gcd(10, 20) -# Test that pure propagates for Cassette -Base.@pure isbitstype(T) = Base.isbitstype(T) -f31012(T) = Val(isbitstype(T)) -@test @inferred(overdub(Ctx(), f31012, Int64)) == Val(true) - @generated bar(::Val{align}) where {align} = :(42) foo(i) = i+bar(Val(1)) @test @inferred(overdub(Ctx(), foo, 1)) == 43 -# Check that misbehaving pure functions propagate their error -Base.@pure func1() = 42 -Base.@pure func2() = (this_is_an_exception; func1()) - -let method = which(func2, ()) - mi = Core.Compiler.specialize_method(method, Tuple{typeof(func2)}, Core.svec()) - mi.inInference = true -end -func3() = func2() -@test_throws UndefVarError func3() - - - -## overlay method tables +# overlay method tables +# ===================== module OverlayModule @@ -157,7 +139,7 @@ end # parametric function def @overlay mt tan(x::T) where {T} = 3 -end +end # module OverlayModule methods = Base._methods_by_ftype(Tuple{typeof(sin), Float64}, nothing, 1, Base.get_world_counter()) @test only(methods).method.module === Base.Math @@ -210,8 +192,12 @@ try Baz = Base.require(Main, :Baz) @test length(Bar.mt) == 1 finally - rm(load_path, recursive=true, force=true) - rm(depot_path, recursive=true, force=true) filter!((≠)(load_path), LOAD_PATH) filter!((≠)(depot_path), DEPOT_PATH) + rm(load_path, recursive=true, force=true) + try + rm(depot_path, force=true, recursive=true) + catch err + @show err + end end diff --git a/test/compiler/datastructures.jl b/test/compiler/datastructures.jl new file mode 100644 index 0000000000000..8dbaee61503d0 --- /dev/null +++ b/test/compiler/datastructures.jl @@ -0,0 +1,100 @@ +using Test + +@testset "CachedMethodTable" begin + # cache result should be separated per `limit` and `sig` + # https://github.com/JuliaLang/julia/pull/46799 + interp = Core.Compiler.NativeInterpreter() + table = Core.Compiler.method_table(interp) + sig = Tuple{typeof(*), Any, Any} + result1 = Core.Compiler.findall(sig, table; limit=-1) + result2 = Core.Compiler.findall(sig, table; limit=Core.Compiler.InferenceParams().max_methods) + @test result1 !== nothing && !Core.Compiler.isempty(result1.matches) + @test result2 === nothing +end + +@testset "BitSetBoundedMinPrioritySet" begin + bsbmp = Core.Compiler.BitSetBoundedMinPrioritySet(5) + Core.Compiler.push!(bsbmp, 2) + Core.Compiler.push!(bsbmp, 2) + @test Core.Compiler.popfirst!(bsbmp) == 2 + Core.Compiler.push!(bsbmp, 1) + @test Core.Compiler.popfirst!(bsbmp) == 1 + @test Core.Compiler.isempty(bsbmp) +end + +@testset "basic heap functionality" begin + v = [2,3,1] + @test Core.Compiler.heapify!(v, Core.Compiler.Forward) === v + @test Core.Compiler.heappop!(v, Core.Compiler.Forward) === 1 + @test Core.Compiler.heappush!(v, 4, Core.Compiler.Forward) === v + @test Core.Compiler.heappop!(v, Core.Compiler.Forward) === 2 + @test Core.Compiler.heappop!(v, Core.Compiler.Forward) === 3 + @test Core.Compiler.heappop!(v, Core.Compiler.Forward) === 4 +end + +@testset "randomized heap correctness tests" begin + order = Core.Compiler.By(x -> -x[2]) + for i in 1:6 + heap = Tuple{Int, Int}[(rand(1:i), rand(1:i)) for _ in 1:2i] + mock = copy(heap) + @test Core.Compiler.heapify!(heap, order) === heap + sort!(mock, by=last) + + for _ in 1:6i + if rand() < .5 && !isempty(heap) + # The first entries may differ because heaps are not stable + @test last(Core.Compiler.heappop!(heap, order)) === last(pop!(mock)) + else + new = (rand(1:i), rand(1:i)) + Core.Compiler.heappush!(heap, new, order) + push!(mock, new) + sort!(mock, by=last) + end + end + end +end + +@testset "searchsorted" begin + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 0) === Core.Compiler.UnitRange(1, 0) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 1) === Core.Compiler.UnitRange(1, 2) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 2) === Core.Compiler.UnitRange(3, 4) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 4) === Core.Compiler.UnitRange(7, 6) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 2.5; lt=<) === Core.Compiler.UnitRange(5, 4) + + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 0) === Core.Compiler.UnitRange(1, 0) + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 1) === Core.Compiler.UnitRange(1, 1) + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 2) === Core.Compiler.UnitRange(2, 2) + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 4) === Core.Compiler.UnitRange(4, 3) + + @test Core.Compiler.searchsorted([1:10;], 1, by=(x -> x >= 5)) === Core.Compiler.UnitRange(1, 4) + @test Core.Compiler.searchsorted([1:10;], 10, by=(x -> x >= 5)) === Core.Compiler.UnitRange(5, 10) + @test Core.Compiler.searchsorted([1:5; 1:5; 1:5], 1, 6, 10, Core.Compiler.Forward) === Core.Compiler.UnitRange(6, 6) + @test Core.Compiler.searchsorted(fill(1, 15), 1, 6, 10, Core.Compiler.Forward) === Core.Compiler.UnitRange(6, 10) + + for (rg,I) in Any[(Core.Compiler.UnitRange(49, 57), 47:59), + (Core.Compiler.StepRange(1, 2, 17), -1:19)] + rg_r = Core.Compiler.reverse(rg) + rgv, rgv_r = Core.Compiler.collect(rg), Core.Compiler.collect(rg_r) + for i = I + @test Core.Compiler.searchsorted(rg,i) === Core.Compiler.searchsorted(rgv,i) + @test Core.Compiler.searchsorted(rg_r,i,rev=true) === Core.Compiler.searchsorted(rgv_r,i,rev=true) + end + end +end + +@testset "basic sort" begin + v = [3,1,2] + @test v == [3,1,2] + @test Core.Compiler.sort!(v) === v == [1,2,3] + @test Core.Compiler.sort!(v, by = x -> -x) === v == [3,2,1] + @test Core.Compiler.sort!(v, by = x -> -x, < = >) === v == [1,2,3] +end + +@testset "randomized sorting tests" begin + for n in [0, 1, 3, 10, 30, 100, 300], k in [0, 30, 2n] + v = rand(-1:k, n) + for by in [identity, x -> -x, x -> x^2 + .1x], lt in [<, >] + @test sort(v; by, lt) == Core.Compiler.sort!(copy(v); by, < = lt) + end + end +end diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl new file mode 100644 index 0000000000000..f809192d8d1ed --- /dev/null +++ b/test/compiler/effects.jl @@ -0,0 +1,984 @@ +using Test +include("irutils.jl") + +# Test that the Core._apply_iterate bail path taints effects +function f_apply_bail(f) + f(()...) + return nothing +end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(f_apply_bail)) +@test !fully_eliminated((Function,)) do f + f_apply_bail(f) + nothing +end + +# Test that effect modeling for return_type doesn't incorrectly pick +# up the effects of the function being analyzed +f_throws() = error() +@noinline function return_type_unused(x) + Core.Compiler.return_type(f_throws, Tuple{}) + return x+1 +end +@test Core.Compiler.is_removable_if_unused(Base.infer_effects(return_type_unused, (Int,))) +@test fully_eliminated((Int,)) do x + return_type_unused(x) + return nothing +end + +# Test that ambiguous calls don't accidentally get nothrow effect +ambig_effects_test(a::Int, b) = 1 +ambig_effects_test(a, b::Int) = 1 +ambig_effects_test(a, b) = 1 +@test !Core.Compiler.is_nothrow(Base.infer_effects(ambig_effects_test, (Int, Any))) +global ambig_unknown_type_global::Any = 1 +@noinline function conditionally_call_ambig(b::Bool, a) + if b + ambig_effects_test(a, ambig_unknown_type_global) + end + return 0 +end +@test !fully_eliminated((Bool,)) do b + conditionally_call_ambig(b, 1) + return nothing +end + +# Test that a missing methtable identification gets tainted +# appropriately +struct FCallback; f::Union{Nothing, Function}; end +f_invoke_callback(fc) = let f=fc.f; (f !== nothing && f(); nothing); end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(f_invoke_callback, (FCallback,))) +@test !fully_eliminated((FCallback,)) do fc + f_invoke_callback(fc) + return nothing +end + +# @assume_effects override +const ___CONST_DICT___ = Dict{Any,Any}(Symbol(c) => i for (i, c) in enumerate('a':'z')) +Base.@assume_effects :foldable concrete_eval( + f, args...; kwargs...) = f(args...; kwargs...) +@test fully_eliminated() do + concrete_eval(getindex, ___CONST_DICT___, :a) +end + +# :removable override +Base.@assume_effects :removable removable_call( + f, args...; kwargs...) = f(args...; kwargs...) +@test fully_eliminated() do + @noinline removable_call(getindex, ___CONST_DICT___, :a) + nothing +end + +# terminates_globally override +# https://github.com/JuliaLang/julia/issues/41694 +Base.@assume_effects :terminates_globally function issue41694(x) + res = 1 + 1 < x < 20 || throw("bad") + while x > 1 + res *= x + x -= 1 + end + return res +end +@test Core.Compiler.is_foldable(Base.infer_effects(issue41694, (Int,))) +@test fully_eliminated() do + issue41694(2) +end + +Base.@assume_effects :terminates_globally function recur_termination1(x) + x == 1 && return 1 + 1 < x < 20 || throw("bad") + return x * recur_termination1(x-1) +end +@test Core.Compiler.is_foldable(Base.infer_effects(recur_termination1, (Int,))) +@test fully_eliminated() do + recur_termination1(12) +end + +Base.@assume_effects :terminates_globally function recur_termination21(x) + x == 1 && return 1 + 1 < x < 20 || throw("bad") + return recur_termination22(x) +end +recur_termination22(x) = x * recur_termination21(x-1) +@test Core.Compiler.is_foldable(Base.infer_effects(recur_termination21, (Int,))) +@test Core.Compiler.is_foldable(Base.infer_effects(recur_termination22, (Int,))) +@test fully_eliminated() do + recur_termination21(12) + recur_termination22(12) +end + +# anonymous function support for `@assume_effects` +@test fully_eliminated() do + map((2,3,4)) do x + # this :terminates_locally allows this anonymous function to be constant-folded + Base.@assume_effects :terminates_locally + res = 1 + 1 < x < 20 || error("bad pow") + while x > 1 + res *= x + x -= 1 + end + return res + end +end + +# control flow backedge should taint `terminates` +@test Base.infer_effects((Int,)) do n + for i = 1:n; end +end |> !Core.Compiler.is_terminates + +# interprocedural-recursion should taint `terminates` **appropriately** +function sumrecur(a, x) + isempty(a) && return x + return sumrecur(Base.tail(a), x + first(a)) +end +@test Base.infer_effects(sumrecur, (Tuple{Int,Int,Int},Int)) |> Core.Compiler.is_terminates +@test Base.infer_effects(sumrecur, (Tuple{Int,Int,Int,Vararg{Int}},Int)) |> !Core.Compiler.is_terminates + +# https://github.com/JuliaLang/julia/issues/45781 +@test Base.infer_effects((Float32,)) do a + out1 = promote_type(Irrational{:π}, Bool) + out2 = sin(a) + out1, out2 +end |> Core.Compiler.is_terminates + +# refine :consistent-cy effect inference using the return type information +@test Base.infer_effects((Any,)) do x + taint = Ref{Any}(x) # taints :consistent-cy, but will be adjusted + throw(taint) +end |> Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + if x < 0 + taint = Ref(x) # taints :consistent-cy, but will be adjusted + throw(DomainError(x, taint)) + end + return nothing +end |> Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + if x < 0 + taint = Ref(x) # taints :consistent-cy, but will be adjusted + throw(DomainError(x, taint)) + end + return x == 0 ? nothing : x # should `Union` of isbitstype objects nicely +end |> Core.Compiler.is_consistent +@test Base.infer_effects((Symbol,Any)) do s, x + if s === :throw + taint = Ref{Any}(":throw option given") # taints :consistent-cy, but will be adjusted + throw(taint) + end + return s # should handle `Symbol` nicely +end |> Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + return Ref(x) +end |> !Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + return x < 0 ? Ref(x) : nothing +end |> !Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + if x < 0 + throw(DomainError(x, lazy"$x is negative")) + end + return nothing +end |> Core.Compiler.is_foldable + +# :the_exception expression should taint :consistent-cy +global inconsistent_var::Int = 42 +function throw_inconsistent() # this is still :consistent + throw(inconsistent_var) +end +function catch_inconsistent() + try + throw_inconsistent() + catch err + err + end +end +@test !Core.Compiler.is_consistent(Base.infer_effects(catch_inconsistent)) +cache_inconsistent() = catch_inconsistent() +function compare_inconsistent() + a = cache_inconsistent() + global inconsistent_var = 0 + b = cache_inconsistent() + global inconsistent_var = 42 + return a === b +end +@test !compare_inconsistent() +# return type information shouldn't be able to refine it also +function catch_inconsistent(x::T) where T + v = x + try + throw_inconsistent() + catch err + v = err::T + end + return v +end +@test !Core.Compiler.is_consistent(Base.infer_effects(catch_inconsistent, (Int,))) +cache_inconsistent(x) = catch_inconsistent(x) +function compare_inconsistent(x::T) where T + x = one(T) + a = cache_inconsistent(x) + global inconsistent_var = 0 + b = cache_inconsistent(x) + global inconsistent_var = 42 + return a === b +end +@test !compare_inconsistent(3) + +# Effect modeling for Core.compilerbarrier +@test Base.infer_effects(Base.inferencebarrier, Tuple{Any}) |> Core.Compiler.is_removable_if_unused + +# allocation/access of uninitialized fields should taint the :consistent-cy +struct Maybe{T} + x::T + Maybe{T}() where T = new{T}() + Maybe{T}(x) where T = new{T}(x) + Maybe(x::T) where T = new{T}(x) +end +Base.getindex(x::Maybe) = x.x + +struct SyntacticallyDefined{T} + x::T +end + +import Core.Compiler: Const, getfield_notundefined +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) + 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) + 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) +end +# should be conservative when object information isn't accurate +@test !getfield_notundefined(Any, Const(1)) +@test !getfield_notundefined(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) +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 Base.infer_effects() do + Maybe{Int}() +end |> !Core.Compiler.is_consistent +@test Base.infer_effects() do + Maybe{Int}()[] +end |> !Core.Compiler.is_consistent +@test !fully_eliminated() do + Maybe{Int}()[] +end +@test Base.infer_effects() do + Maybe{String}() +end |> Core.Compiler.is_consistent +@test Base.infer_effects() do + Maybe{String}()[] +end |> Core.Compiler.is_consistent +let f() = Maybe{String}()[] + @test Base.return_types() do + f() # this call should be concrete evaluated + end |> only === Union{} +end +@test Base.infer_effects() do + Ref{Int}() +end |> !Core.Compiler.is_consistent +@test Base.infer_effects() do + Ref{Int}()[] +end |> !Core.Compiler.is_consistent +@test !fully_eliminated() do + Ref{Int}()[] +end +@test Base.infer_effects() do + Ref{String}()[] +end |> Core.Compiler.is_consistent +let f() = Ref{String}()[] + @test Base.return_types() do + f() # this call should be concrete evaluated + end |> only === Union{} +end +@test Base.infer_effects((SyntacticallyDefined{Float64}, Symbol)) do w, s + getfield(w, s) +end |> Core.Compiler.is_foldable + +# effects propagation for `Core.invoke` calls +# https://github.com/JuliaLang/julia/issues/44763 +global x44763::Int = 0 +increase_x44763!(n) = (global x44763; x44763 += n) +invoke44763(x) = @invoke increase_x44763!(x) +@test Base.return_types() do + invoke44763(42) +end |> only === Int +@test x44763 == 0 + +# `@inbounds`/`@boundscheck` expression should taint :consistent-cy correctly +# https://github.com/JuliaLang/julia/issues/48099 +function A1_inbounds() + r = 0 + @inbounds begin + @boundscheck r += 1 + end + return r +end +@test !Core.Compiler.is_consistent(Base.infer_effects(A1_inbounds)) + +# Test that purity doesn't try to accidentally run unreachable code due to +# boundscheck elimination +function f_boundscheck_elim(n) + # Inbounds here assumes that this is only ever called with `n==0`, but of + # course the compiler has no way of knowing that, so it must not attempt + # to run the `@inbounds getfield(sin, 1)` that `ntuple` generates. + ntuple(x->(@inbounds ()[x]), n) +end +@test_broken !Core.Compiler.is_consistent(Base.infer_effects(f_boundscheck_elim, (Int,))) +@test Tuple{} <: only(Base.return_types(f_boundscheck_elim, (Int,))) + +# Test that purity modeling doesn't accidentally introduce new world age issues +f_redefine_me(x) = x+1 +f_call_redefine() = f_redefine_me(0) +f_mk_opaque() = Base.Experimental.@opaque ()->Base.inferencebarrier(f_call_redefine)() +const op_capture_world = f_mk_opaque() +f_redefine_me(x) = x+2 +@test op_capture_world() == 1 +@test f_mk_opaque()() == 2 + +# backedge insertion for Any-typed, effect-free frame +const CONST_DICT = let d = Dict() + for c in 'A':'z' + push!(d, c => Int(c)) + end + d +end +Base.@assume_effects :foldable getcharid(c) = CONST_DICT[c] +@noinline callf(f, args...) = f(args...) +function entry_to_be_invalidated(c) + return callf(getcharid, c) +end +@test Base.infer_effects((Char,)) do x + entry_to_be_invalidated(x) +end |> Core.Compiler.is_foldable +@test fully_eliminated(; retval=97) do + entry_to_be_invalidated('a') +end +getcharid(c) = CONST_DICT[c] # now this is not eligible for concrete evaluation +@test Base.infer_effects((Char,)) do x + entry_to_be_invalidated(x) +end |> !Core.Compiler.is_foldable +@test !fully_eliminated() do + entry_to_be_invalidated('a') +end + +@test !Core.Compiler.builtin_nothrow(Core.Compiler.fallback_lattice, Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any) + +# Nothrow for assignment to globals +global glob_assign_int::Int = 0 +f_glob_assign_int() = global glob_assign_int += 1 +let effects = Base.infer_effects(f_glob_assign_int, ()) + @test !Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_nothrow(effects) +end +# Nothrow for setglobal! +global SETGLOBAL!_NOTHROW::Int = 0 +let effects = Base.infer_effects() do + setglobal!(@__MODULE__, :SETGLOBAL!_NOTHROW, 42) + end + @test Core.Compiler.is_nothrow(effects) +end + +# we should taint `nothrow` if the binding doesn't exist and isn't fixed yet, +# as the cached effects can be easily wrong otherwise +# since the inference currently doesn't track "world-age" of global variables +@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42 +setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42) +let effects = Base.infer_effects() do + global_assignment_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + setglobal!_nothrow_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +global UNDEFINEDYET::String = "0" +let effects = Base.infer_effects() do + global_assignment_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + setglobal!_nothrow_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +@test_throws ErrorException setglobal!_nothrow_undefinedyet() + +# Nothrow for setfield! +mutable struct SetfieldNothrow + x::Int +end +f_setfield_nothrow() = SetfieldNothrow(0).x = 1 +let effects = Base.infer_effects(f_setfield_nothrow, ()) + # Technically effect free even though we use the heap, since the + # object doesn't escape, but the compiler doesn't know that. + #@test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_nothrow(effects) +end + +# even if 2-arg `getfield` may throw, it should be still `:consistent` +@test Core.Compiler.is_consistent(Base.infer_effects(getfield, (NTuple{5, Float64}, Int))) + +# SimpleVector allocation is consistent +@test Core.Compiler.is_consistent(Base.infer_effects(Core.svec)) +@test Base.infer_effects() do + Core.svec(nothing, 1, "foo") +end |> Core.Compiler.is_consistent + +# fastmath operations are inconsistent +@test !Core.Compiler.is_consistent(Base.infer_effects((a,b)->@fastmath(a+b), (Float64,Float64))) + +# 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} +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})) +end +@test Base.infer_effects((Bool,)) do c + obj = c ? Some{String}("foo") : Some{Symbol}(:bar) + return getfield(obj, :value) +end |> Core.Compiler.is_consistent + +# getfield is nothrow when bounds checking is turned off +@test Base.infer_effects((Tuple{Int,Int},Int)) do t, i + getfield(t, i, false) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Tuple{Int,Int},Symbol)) do t, i + getfield(t, i, false) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Tuple{Int,Int},String)) do t, i + getfield(t, i, false) # invalid name type +end |> !Core.Compiler.is_nothrow + +@test Core.Compiler.is_consistent(Base.infer_effects(setindex!, (Base.RefValue{Int}, Int))) + +# :inaccessiblememonly effect +const global constant_global::Int = 42 +const global ConstantType = Ref +global nonconstant_global::Int = 42 +const global constant_mutable_global = Ref(0) +const global constant_global_nonisbits = Some(:foo) +@test Base.infer_effects() do + constant_global +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConstantType +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConstantType{Any}() +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + constant_global_nonisbits +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :constant_global) +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + nonconstant_global +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :nonconstant_global) +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Symbol,)) do name + getglobal(@__MODULE__, name) +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Int,)) do v + global nonconstant_global = v +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Int,)) do v + setglobal!(@__MODULE__, :nonconstant_global, v) +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Int,)) do v + constant_mutable_global[] = v +end |> !Core.Compiler.is_inaccessiblememonly +module ConsistentModule +const global constant_global::Int = 42 +const global ConstantType = Ref +end # module +@test Base.infer_effects() do + ConsistentModule.constant_global +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConsistentModule.ConstantType +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConsistentModule.ConstantType{Any}() +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :ConsistentModule).constant_global +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :ConsistentModule).ConstantType +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :ConsistentModule).ConstantType{Any}() +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Module,)) do M + M.constant_global +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Module,)) do M + M.ConstantType +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do M + M.ConstantType{Any}() +end |> !Core.Compiler.is_inaccessiblememonly + +# the `:inaccessiblememonly` helper effect allows us to prove `:consistent`-cy of frames +# including `getfield` / `isdefined` accessing to local mutable object + +mutable struct SafeRef{T} + x::T +end +Base.getindex(x::SafeRef) = x.x; +Base.setindex!(x::SafeRef, v) = x.x = v; +Base.isassigned(x::SafeRef) = true; + +function mutable_consistent(s) + SafeRef(s)[] +end +@test Core.Compiler.is_inaccessiblememonly(Base.infer_effects(mutable_consistent, (Symbol,))) +@test fully_eliminated(; retval=:foo) do + mutable_consistent(:foo) +end + +function nested_mutable_consistent(s) + SafeRef(SafeRef(SafeRef(SafeRef(SafeRef(s)))))[][][][][] +end +@test Core.Compiler.is_inaccessiblememonly(Base.infer_effects(nested_mutable_consistent, (Symbol,))) +@test fully_eliminated(; retval=:foo) do + nested_mutable_consistent(:foo) +end + +const consistent_global = Some(:foo) +@test Base.infer_effects() do + consistent_global.value +end |> Core.Compiler.is_consistent +const inconsistent_global = SafeRef(:foo) +@test Base.infer_effects() do + inconsistent_global[] +end |> !Core.Compiler.is_consistent +const inconsistent_condition_ref = Ref{Bool}(false) +@test Base.infer_effects() do + if inconsistent_condition_ref[] + return 0 + else + return 1 + end +end |> !Core.Compiler.is_consistent + +# should handle va-method properly +callgetfield1(xs...) = getfield(getfield(xs, 1), 1) +@test !Core.Compiler.is_inaccessiblememonly(Base.infer_effects(callgetfield1, (Base.RefValue{Symbol},))) +const GLOBAL_XS = Ref(:julia) +global_getfield() = callgetfield1(GLOBAL_XS) +@test let + Base.Experimental.@force_compile + global_getfield() +end === :julia +GLOBAL_XS[] = :julia2 +@test let + Base.Experimental.@force_compile + global_getfield() +end === :julia2 + +# the `:inaccessiblememonly` helper effect allows us to prove `:effect_free`-ness of frames +# including `setfield!` modifying local mutable object + +const global_ref = Ref{Any}() +global const global_bit::Int = 42 +makeref() = Ref{Any}() +setref!(ref, @nospecialize v) = ref[] = v + +@noinline function removable_if_unused1() + x = makeref() + setref!(x, 42) + x +end +@noinline function removable_if_unused2() + x = makeref() + setref!(x, global_bit) + x +end +for f = Any[removable_if_unused1, removable_if_unused2] + effects = Base.infer_effects(f) + @test Core.Compiler.is_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_removable_if_unused(effects) + @test @eval fully_eliminated() do + $f() + nothing + end +end +@noinline function removable_if_unused3(v) + x = makeref() + setref!(x, v) + x +end +let effects = Base.infer_effects(removable_if_unused3, (Int,)) + @test Core.Compiler.is_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_removable_if_unused(effects) +end +@test fully_eliminated((Int,)) do v + removable_if_unused3(v) + nothing +end + +@noinline function unremovable_if_unused1!(x) + setref!(x, 42) +end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused1!, (typeof(global_ref),))) +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused1!, (Any,))) + +@noinline function unremovable_if_unused2!() + setref!(global_ref, 42) +end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused2!)) + +@noinline function unremovable_if_unused3!() + getfield(@__MODULE__, :global_ref)[] = nothing +end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused3!)) + +# array ops +# ========= + +# allocation +# ---------- + +# 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) + for dim = good_dims, N = Ns + 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 + 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 + end + @test_throws "invalid Array" @eval $construct_array(Int, $(dims...)) + end +end + +# high-level interfaces +# getindex +for safesig = Any[ + (Type{Int},) + (Type{Int}, Int) + (Type{Int}, Int, Int) + (Type{Number},) + (Type{Number}, Number) + (Type{Number}, Int) + (Type{Any},) + (Type{Any}, Any,) + (Type{Any}, Any, Any) + ] + let effects = Base.infer_effects(getindex, safesig) + @test Core.Compiler.is_consistent_if_notreturned(effects) + @test Core.Compiler.is_removable_if_unused(effects) + end +end +for unsafesig = Any[ + (Type{Int}, String) + (Type{Int}, Any) + (Type{Number}, AbstractString) + (Type{Number}, Any) + ] + let effects = Base.infer_effects(getindex, unsafesig) + @test !Core.Compiler.is_nothrow(effects) + end +end +# vect +for safesig = Any[ + () + (Int,) + (Int, Int) + ] + let effects = Base.infer_effects(Base.vect, safesig) + @test Core.Compiler.is_consistent_if_notreturned(effects) + @test Core.Compiler.is_removable_if_unused(effects) + end +end + +# arrayref +# -------- + +let effects = Base.infer_effects(Base.arrayref, (Vector{Any},Int)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end + +# arrayset +# -------- + +let effects = Base.infer_effects(Base.arrayset, (Vector{Any},Any,Int)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free_if_inaccessiblememonly(effects) + @test !Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end +# nothrow for arrayset +@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i + Base.arrayset(true, a, v, i) +end |> !Core.Compiler.is_nothrow +@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i + a[i] = 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) +end |> Core.Compiler.is_nothrow +@test Base.infer_effects((Vector{Number},Number,Int)) do a, v, i + Base.arrayset(false, a, v, i) +end |> Core.Compiler.is_nothrow + +# arraysize +# --------- + +let effects = Base.infer_effects(Base.arraysize, (Array,Int)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +end +# Test that arraysize has proper effect modeling +@test fully_eliminated(M->(size(M, 2); nothing), (Matrix{Float64},)) + +# arraylen +# -------- + +let effects = Base.infer_effects(Base.arraylen, (Vector{Any},)) + @test Core.Compiler.is_consistent_if_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_nothrow(effects) + @test Core.Compiler.is_terminates(effects) +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 + +# 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 + +# 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)) + +# Test that :new can be eliminated even if an sparam is unknown +struct SparamUnused{T} + x + SparamUnused(x::T) where {T} = new{T}(x) +end +mksparamunused(x) = (SparamUnused(x); nothing) +let src = code_typed1(mksparamunused, (Any,)) + @test count(isnew, src.code) == 0 +end + +struct WrapperOneField{T} + x::T +end + +# Effects for getfield of type instance +@test Base.infer_effects(Tuple{Nothing}) do x + WrapperOneField{typeof(x)}.instance +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 Base.infer_effects(Tuple{WrapperOneField{Symbol}, Symbol}) do w, s + getfield(w, s) +end |> Core.Compiler.is_foldable + +# Flow-sensitive consistenct for _typevar +@test Base.infer_effects() do + return WrapperOneField == (WrapperOneField{T} where T) +end |> Core.Compiler.is_foldable_nothrow + +# Test that dead `@inbounds` does not taint consistency +# https://github.com/JuliaLang/julia/issues/48243 +@test Base.infer_effects(Tuple{Int64}) do i + false && @inbounds (1,2,3)[i] + return 1 +end |> Core.Compiler.is_foldable_nothrow + +@test Base.infer_effects(Tuple{Int64}) do i + @inbounds (1,2,3)[i] +end |> !Core.Compiler.is_consistent + +@test Base.infer_effects(Tuple{Tuple{Int64}}) do x + @inbounds x[1] +end |> Core.Compiler.is_foldable_nothrow + +# Test that :new of non-concrete, but otherwise known type +# does not taint consistency. +@eval struct ImmutRef{T} + x::T + ImmutRef(x) = $(Expr(:new, :(ImmutRef{typeof(x)}), :x)) +end +@test Core.Compiler.is_foldable(Base.infer_effects(ImmutRef, Tuple{Any})) + +@test Core.Compiler.is_foldable_nothrow(Base.infer_effects(typejoin, ())) + +# nothrow-ness of subtyping operations +# https://github.com/JuliaLang/julia/pull/48566 +@test !Core.Compiler.is_nothrow(Base.infer_effects((A,B)->A<:B, (Any,Any))) +@test !Core.Compiler.is_nothrow(Base.infer_effects((A,B)->A>:B, (Any,Any))) + +# GotoIfNot should properly mark itself as throwing when given a non-Bool +# https://github.com/JuliaLang/julia/pull/48583 +gotoifnot_throw_check_48583(x) = x ? x : 0 +@test !Core.Compiler.is_nothrow(Base.infer_effects(gotoifnot_throw_check_48583, (Missing,))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(gotoifnot_throw_check_48583, (Any,))) +@test Core.Compiler.is_nothrow(Base.infer_effects(gotoifnot_throw_check_48583, (Bool,))) + +# unknown :static_parameter should taint :nothrow +# https://github.com/JuliaLang/julia/issues/46771 +unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = (T; nothing) +unknown_sparam_nothrow1(x::Ref{T}) where T = (T; nothing) +unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = (T; nothing) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{Int},))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{<:Integer},))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type,))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Nothing,))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Union{Type{Int},Nothing},))) +@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Any,))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow1, (Ref,))) +@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) + +# purely abstract recursion should not taint :terminates +# https://github.com/JuliaLang/julia/issues/48983 +abstractly_recursive1() = abstractly_recursive2() +abstractly_recursive2() = (Core.Compiler._return_type(abstractly_recursive1, Tuple{}); 1) +abstractly_recursive3() = abstractly_recursive2() +@test Core.Compiler.is_terminates(Base.infer_effects(abstractly_recursive3, ())) +actually_recursive1(x) = actually_recursive2(x) +actually_recursive2(x) = (x <= 0) ? 1 : actually_recursive1(x - 1) +actually_recursive3(x) = actually_recursive2(x) +@test !Core.Compiler.is_terminates(Base.infer_effects(actually_recursive3, (Int,))) + +# `isdefined` effects +struct MaybeSome{T} + value::T + MaybeSome(x::T) where T = new{T}(x) + MaybeSome{T}(x::T) where T = new{T}(x) + MaybeSome{T}() where T = new{T}() +end +const undefined_ref = Ref{String}() +const defined_ref = Ref{String}("julia") +const undefined_some = MaybeSome{String}() +const defined_some = MaybeSome{String}("julia") +let effects = Base.infer_effects() do + isdefined(undefined_ref, :x) + end + @test !Core.Compiler.is_consistent(effects) + @test Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + isdefined(defined_ref, :x) + end + @test Core.Compiler.is_consistent(effects) + @test Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + isdefined(undefined_some, :value) + end + @test Core.Compiler.is_consistent(effects) + @test Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + isdefined(defined_some, :value) + end + @test Core.Compiler.is_consistent(effects) + @test Core.Compiler.is_nothrow(effects) +end +# high-level interface test +isassigned_effects(s) = isassigned(Ref(s)) +@test Core.Compiler.is_consistent(Base.infer_effects(isassigned_effects, (Symbol,))) +@test fully_eliminated(; retval=true) do + isassigned_effects(:foo) +end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d4fbe0dbfbf6b..5987e10401bc8 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -7,6 +7,8 @@ isdispatchelem(@nospecialize x) = !isa(x, Type) || Core.Compiler.isdispatchelem( using Random, Core.IR using InteractiveUtils: code_llvm +include("irutils.jl") + f39082(x::Vararg{T}) where {T <: Number} = x[1] let ast = only(code_typed(f39082, Tuple{Vararg{Rational}}))[1] @test ast.slottypes == Any[Const(f39082), Tuple{Vararg{Rational}}] @@ -25,7 +27,6 @@ let comparison = Tuple{X, X} where X<:Tuple @test Core.Compiler.limit_type_size(sig, comparison, comparison, 100, 100) == Tuple{Tuple, Tuple} @test Core.Compiler.limit_type_size(sig, ref, comparison, 100, 100) == Tuple{Any, Any} @test Core.Compiler.limit_type_size(Tuple{sig}, Tuple{ref}, comparison, 100, 100) == Tuple{Tuple{Any, Any}} - @test Core.Compiler.limit_type_size(sig, ref, Tuple{comparison}, 100, 100) == Tuple{Tuple{X, X} where X<:Tuple, Tuple{X, X} where X<:Tuple} @test Core.Compiler.limit_type_size(ref, sig, Union{}, 100, 100) == ref end @@ -49,6 +50,13 @@ let va = ccall(:jl_type_intersection_with_env, Any, (Any, Any), Tuple{Tuple}, Tu @test Core.Compiler.__limit_type_size(Tuple, va, Core.svec(va, Union{}), 2, 2) === Tuple end +mutable struct TS14009{T}; end +let A = TS14009{TS14009{TS14009{TS14009{TS14009{T}}}}} where {T}, + B = Base.rewrap_unionall(TS14009{Base.unwrap_unionall(A)}, A) + + @test Core.Compiler.Compiler.limit_type_size(B, A, A, 2, 2) == TS14009 +end + # issue #42835 @test !Core.Compiler.type_more_complex(Int, Any, Core.svec(), 1, 1, 1) @test !Core.Compiler.type_more_complex(Int, Type{Int}, Core.svec(), 1, 1, 1) @@ -79,9 +87,12 @@ end @test !Core.Compiler.type_more_complex(Type{1}, Type{2}, Core.svec(), 1, 1, 1) @test Core.Compiler.type_more_complex(Type{Union{Float32,Float64}}, Union{Float32,Float64}, Core.svec(Union{Float32,Float64}), 1, 1, 1) @test !Core.Compiler.type_more_complex(Type{Union{Float32,Float64}}, Union{Float32,Float64}, Core.svec(Union{Float32,Float64}), 0, 1, 1) -@test_broken Core.Compiler.type_more_complex(Type{<:Union{Float32,Float64}}, Type{Union{Float32,Float64}}, Core.svec(Union{Float32,Float64}), 1, 1, 1) +@test Core.Compiler.type_more_complex(Type{<:Union{Float32,Float64}}, Type{Union{Float32,Float64}}, Core.svec(Union{Float32,Float64}), 1, 1, 1) @test Core.Compiler.type_more_complex(Type{<:Union{Float32,Float64}}, Any, Core.svec(Union{Float32,Float64}), 1, 1, 1) +# issue #49287 +@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) let # 40336 t = Type{Type{Int}} @@ -161,7 +172,7 @@ tmerge_test(Tuple{}, Tuple{Complex, Vararg{Union{ComplexF32, ComplexF64}}}, @test Core.Compiler.tmerge(Vector{Int}, Core.Compiler.tmerge(Vector{String}, Union{Vector{Bool}, Vector{Symbol}})) == Vector @test Core.Compiler.tmerge(Base.BitIntegerType, Union{}) === Base.BitIntegerType @test Core.Compiler.tmerge(Union{}, Base.BitIntegerType) === Base.BitIntegerType -@test Core.Compiler.tmerge(Core.Compiler.InterConditional(1, Int, Union{}), Core.Compiler.InterConditional(2, String, Union{})) === Core.Compiler.Const(true) +@test Core.Compiler.tmerge(Core.Compiler.fallback_ipo_lattice, Core.Compiler.InterConditional(1, Int, Union{}), Core.Compiler.InterConditional(2, String, Union{})) === Core.Compiler.Const(true) struct SomethingBits x::Base.BitIntegerType @@ -271,7 +282,7 @@ barTuple2() = fooTuple{tuple(:y)}() @test Base.return_types(barTuple1,Tuple{})[1] == Base.return_types(barTuple2,Tuple{})[1] == fooTuple{(:y,)} # issue #6050 -@test Core.Compiler.getfield_tfunc( +@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} @@ -377,7 +388,7 @@ struct A15259 x y end -# check that allocation was ellided +# check that allocation was elided @eval f15259(x,y) = (a = $(Expr(:new, :A15259, :x, :y)); (a.x, a.y, getfield(a,1), getfield(a, 2))) @test isempty(filter(x -> isa(x,Expr) && x.head === :(=) && isa(x.args[2], Expr) && x.args[2].head === :new, @@ -404,7 +415,7 @@ f11366(x::Type{Ref{T}}) where {T} = Ref{x} let f(T) = Type{T} - @test Base.return_types(f, Tuple{Type{Int}}) == [Type{Type{Int}}] + @test Base.return_types(f, Tuple{Type{Int}}) == Any[Type{Type{Int}}] end # issue #9222 @@ -427,7 +438,7 @@ function foo9222() end @test 0.0 == foo9222() -# branching based on inferrable conditions +# branching based on inferable conditions let f(x) = isa(x,Int) ? 1 : "" @test Base.return_types(f, Tuple{Int}) == [Int] end @@ -555,27 +566,6 @@ f18450() = ifelse(true, Tuple{Vararg{Int}}, Tuple{Vararg}) # issue #18569 @test !Core.Compiler.isconstType(Type{Tuple}) -# ensure pure attribute applies correctly to all signatures of fpure -Base.@pure function fpure(a=rand(); b=rand()) - # use the `rand` function since it is known to be `@inline` - # but would be too big to inline - return a + b + rand() -end -gpure() = fpure() -gpure(x::Irrational) = fpure(x) -@test which(fpure, ()).pure -@test which(fpure, (typeof(pi),)).pure -@test !which(gpure, ()).pure -@test !which(gpure, (typeof(pi),)).pure -@test code_typed(gpure, ())[1][1].pure -@test code_typed(gpure, (typeof(π),))[1][1].pure -@test gpure() == gpure() == gpure() -@test gpure(π) == gpure(π) == gpure(π) - -# Make sure @pure works for functions using the new syntax -Base.@pure (fpure2(x::T) where T) = T -@test which(fpure2, (Int64,)).pure - # issue #10880 function cat10880(a, b) Tuple{a.parameters..., b.parameters...} @@ -595,7 +585,6 @@ function is_typed_expr(e::Expr) end is_typed_expr(@nospecialize other) = false test_inferred_static(@nospecialize(other)) = true -test_inferred_static(slot::TypedSlot) = @test isdispatchelem(slot.typ) function test_inferred_static(expr::Expr) for a in expr.args test_inferred_static(a) @@ -652,17 +641,8 @@ for (codetype, all_ssa) in Any[ (code_typed(h18679, ())[1], true), (code_typed(g19348, (typeof((1, 2.0)),))[1], true)] code = codetype[1] - local notconst(@nospecialize(other)) = true - notconst(slot::TypedSlot) = @test isa(slot.typ, Type) - function notconst(expr::Expr) - for a in expr.args - notconst(a) - end - end local i - for i = 1:length(code.code) - e = code.code[i] - notconst(e) + for i = 1:length(code.ssavaluetypes) typ = code.ssavaluetypes[i] typ isa Core.Compiler.MaybeUndef && (typ = typ.typ) @test isa(typ, Type) || isa(typ, Const) || isa(typ, Conditional) || typ @@ -705,6 +685,7 @@ end # inference of `fieldtype` mutable struct UndefField__ x::Union{} + UndefField__() = new() end f_infer_undef_field() = fieldtype(UndefField__, :x) @test Base.return_types(f_infer_undef_field, ()) == Any[Type{Union{}}] @@ -715,8 +696,10 @@ mutable struct HasAbstractlyTypedField end f_infer_abstract_fieldtype() = fieldtype(HasAbstractlyTypedField, :x) @test Base.return_types(f_infer_abstract_fieldtype, ()) == Any[Type{Union{Int,String}}] -let fieldtype_tfunc = Core.Compiler.fieldtype_tfunc, - fieldtype_nothrow = Core.Compiler.fieldtype_nothrow +let fieldtype_tfunc(@nospecialize args...) = + Core.Compiler.fieldtype_tfunc(Core.Compiler.fallback_lattice, args...), + fieldtype_nothrow(@nospecialize(s0), @nospecialize(name)) = Core.Compiler.fieldtype_nothrow( + Core.Compiler.OptimizerLattice(), s0, name) @test fieldtype_tfunc(Union{}, :x) == Union{} @test fieldtype_tfunc(Union{Type{Int32}, Int32}, Const(:x)) == Union{} @test fieldtype_tfunc(Union{Type{Base.RefValue{T}}, Type{Int32}} where {T<:Array}, Const(:x)) == Type{<:Array} @@ -746,6 +729,19 @@ let fieldtype_tfunc = Core.Compiler.fieldtype_tfunc, @test TypeVar <: fieldtype_tfunc(Any, Any) end +import Core.Compiler: MaybeUndef, builtin_nothrow +let 𝕃ₒ = Core.Compiler.OptimizerLattice() + @test !builtin_nothrow(𝕃ₒ, setfield!, Any[Base.RefValue{String}, Core.Const(:x), MaybeUndef(String)], Any) + @test !builtin_nothrow(𝕃ₒ, setfield!, Any[Base.RefValue{String}, Core.Const(:x), MaybeUndef(String), Core.Const(:not_atomic)], Any) + @test !builtin_nothrow(𝕃ₒ, isdefined, Any[Any,MaybeUndef(Symbol)], Bool) + @test !builtin_nothrow(𝕃ₒ, fieldtype, Any[MaybeUndef(Any),Symbol], Any) + @test !builtin_nothrow(𝕃ₒ, isa, Any[Type,MaybeUndef(Type)], Any) + @test !builtin_nothrow(𝕃ₒ, <:, Any[MaybeUndef(Any),MaybeUndef(Any)], Any) + @test !builtin_nothrow(𝕃ₒ, Core.ifelse, Any[MaybeUndef(Bool),Any,Any], Any) + @test !builtin_nothrow(𝕃ₒ, typeassert, Any[MaybeUndef(Any),Type{Symbol}], Any) + @test !builtin_nothrow(𝕃ₒ, Core.get_binding_type, Any[Module,MaybeUndef(Symbol)], Any) +end + # issue #11480 @noinline f11480(x,y) = x let A = Ref @@ -896,35 +892,6 @@ end f20267(x::T20267{T}, y::T) where (T) = f20267(Any[1][1], x.inds) @test Base.return_types(f20267, (Any, Any)) == Any[Union{}] -# issue #20704 -f20704(::Int) = 1 -Base.@pure b20704(@nospecialize(x)) = f20704(x) -@test b20704(42) === 1 -@test_throws MethodError b20704(42.0) - -bb20704() = b20704(Any[1.0][1]) -@test_throws MethodError bb20704() - -v20704() = Val{b20704(Any[1.0][1])} -@test_throws MethodError v20704() -@test Base.return_types(v20704, ()) == Any[Type{Val{1}}] - -Base.@pure g20704(::Int) = 1 -h20704(@nospecialize(x)) = g20704(x) -@test g20704(1) === 1 -@test_throws MethodError h20704(1.2) - -Base.@pure c20704() = (f20704(1.0); 1) -d20704() = c20704() -@test_throws MethodError d20704() - -Base.@pure function a20704(x) - rand() - 42 -end -aa20704(x) = x(nothing) -@test code_typed(aa20704, (typeof(a20704),))[1][1].pure - #issue #21065, elision of _apply_iterate when splatted expression is not effect_free function f21065(x,y) println("x=$x, y=$y") @@ -954,7 +921,7 @@ err20033(x::Float64...) = prod(x) # Inference of constant svecs @eval fsvecinf() = $(QuoteNode(Core.svec(Tuple{Int,Int}, Int)))[1] -@test Core.Compiler.return_type(fsvecinf, Tuple{}) == Type{Tuple{Int,Int}} +@test only(Base.return_types(fsvecinf, Tuple{})) == Type{Tuple{Int,Int}} # nfields tfunc on `DataType` let f = ()->Val{nfields(DataType[Int][1])} @@ -982,7 +949,7 @@ end # issue #21410 f21410(::V, ::Pair{V,E}) where {V, E} = E -@test code_typed(f21410, Tuple{Ref, Pair{Ref{T},Ref{T}} where T<:Number})[1].second == +@test only(Base.return_types(f21410, Tuple{Ref, Pair{Ref{T},Ref{T}} where T<:Number})) == Type{E} where E <: (Ref{T} where T<:Number) # issue #21369 @@ -1057,7 +1024,7 @@ end g21771(T) = T f21771(::Val{U}) where {U} = Tuple{g21771(U)} @test @inferred(f21771(Val{Int}())) === Tuple{Int} -@test @inferred(f21771(Val{Union{}}())) === Tuple{Union{}} +@test_throws ErrorException @inferred(f21771(Val{Union{}}())) @test @inferred(f21771(Val{Integer}())) === Tuple{Integer} # PR #28284, check that constants propagate through calls to new @@ -1112,12 +1079,6 @@ let f(x) = isdefined(x, :NonExistentField) ? 1 : "" @test Base.return_types(f, (ComplexF32,)) == Any[String] @test Union{Int,String} <: Base.return_types(f, (AbstractArray,))[1] end -import Core.Compiler: isdefined_tfunc -@test isdefined_tfunc(ComplexF32, Const(())) === Union{} -@test isdefined_tfunc(ComplexF32, Const(1)) === Const(true) -@test isdefined_tfunc(ComplexF32, Const(2)) === Const(true) -@test isdefined_tfunc(ComplexF32, Const(3)) === Const(false) -@test isdefined_tfunc(ComplexF32, Const(0)) === Const(false) mutable struct SometimesDefined x function SometimesDefined() @@ -1128,36 +1089,64 @@ mutable struct SometimesDefined return v end end -@test isdefined_tfunc(SometimesDefined, Const(:x)) == Bool -@test isdefined_tfunc(SometimesDefined, Const(:y)) === Const(false) -@test isdefined_tfunc(Const(Base), Const(:length)) === Const(true) -@test isdefined_tfunc(Const(Base), Symbol) == Bool -@test isdefined_tfunc(Const(Base), Const(:NotCurrentlyDefinedButWhoKnows)) == Bool -@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) -@test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(2)) === Bool -@test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(3)) === Bool -@testset "isdefined check for `NamedTuple`s" begin - # concrete `NamedTuple`s - @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:x)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:y)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:z)) === Const(false) - # non-concrete `NamedTuple`s - @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:x)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:y)) === Const(true) - @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:z)) === Const(false) -end struct UnionIsdefinedA; x; end struct UnionIsdefinedB; x; end -@test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:x)) === Const(true) -@test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:y)) === Const(false) -@test isdefined_tfunc(Union{UnionIsdefinedA,Nothing}, Const(:x)) === Bool +let isdefined_tfunc(@nospecialize xs...) = + Core.Compiler.isdefined_tfunc(Core.Compiler.fallback_lattice, xs...) + @test isdefined_tfunc(typeof(NamedTuple()), Const(0)) === Const(false) + @test isdefined_tfunc(typeof(NamedTuple()), Const(1)) === Const(false) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(:a)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(:b)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(:c)) === Const(false) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(0)) === Const(false) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(1)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(2)) === Const(true) + @test isdefined_tfunc(typeof((a=1,b=2)), Const(3)) === Const(false) + @test isdefined_tfunc(NamedTuple, Const(1)) == Bool + @test isdefined_tfunc(NamedTuple, Symbol) == Bool + @test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:z)) + @test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(1)) + @test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(3)) + @test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:y)) + + @test isdefined_tfunc(ComplexF32, Const(())) === Union{} + @test isdefined_tfunc(ComplexF32, Const(1)) === Const(true) + @test isdefined_tfunc(ComplexF32, Const(2)) === Const(true) + @test isdefined_tfunc(ComplexF32, Const(3)) === Const(false) + @test isdefined_tfunc(ComplexF32, Const(0)) === Const(false) + @test isdefined_tfunc(SometimesDefined, Const(:x)) == Bool + @test isdefined_tfunc(SometimesDefined, Const(:y)) === Const(false) + @test isdefined_tfunc(Const(Base), Const(:length)) === Const(true) + @test isdefined_tfunc(Const(Base), Symbol) == Bool + @test isdefined_tfunc(Const(Base), Const(:NotCurrentlyDefinedButWhoKnows)) == Bool + @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) + @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(2)) === Bool + @test isdefined_tfunc(Tuple{Any,Vararg{Any}}, Const(3)) === Bool + @testset "isdefined check for `NamedTuple`s" begin + # concrete `NamedTuple`s + @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:x)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:y)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),Tuple{Int,Int}}, Const(:z)) === Const(false) + # non-concrete `NamedTuple`s + @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:x)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:y)) === Const(true) + @test isdefined_tfunc(NamedTuple{(:x,:y),<:Tuple{Int,Any}}, Const(:z)) === Const(false) + end + @test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:x)) === Const(true) + @test isdefined_tfunc(Union{UnionIsdefinedA,UnionIsdefinedB}, Const(:y)) === Const(false) + @test isdefined_tfunc(Union{UnionIsdefinedA,Nothing}, Const(:x)) === Bool +end + +# https://github.com/aviatesk/JET.jl/issues/379 +fJET379(x::Union{Complex{T}, T}) where T = isdefined(x, :im) +@test only(Base.return_types(fJET379)) === Bool @noinline map3_22347(f, t::Tuple{}) = () @noinline map3_22347(f, t::Tuple) = (f(t[1]), map3_22347(f, Base.tail(t))...) @@ -1171,23 +1160,17 @@ let niter = 0 end # issue #22875 - -typeargs = Tuple{Type{Int},} -@test Base.Core.Compiler.return_type((args...) -> one(args...), typeargs) === Int - -typeargs = Tuple{Type{Int},Type{Int},Type{Int},Type{Int},Type{Int},Type{Int}} -@test Base.Core.Compiler.return_type(promote_type, typeargs) === Type{Int} - -# demonstrate that inference must converge -# while doing constant propagation -Base.@pure plus1(x) = x + 1 -f21933(x::Val{T}) where {T} = f(Val(plus1(T))) -code_typed(f21933, (Val{1},)) -Base.return_types(f21933, (Val{1},)) +let typeargs = Tuple{Type{Int},} + @test only(Base.return_types((args...) -> one(args...), typeargs)) === Int +end +let typeargs = Tuple{Type{Int},Type{Int},Type{Int},Type{Int},Type{Int},Type{Int}} + @test only(Base.return_types(promote_type, typeargs)) === Type{Int} +end function count_specializations(method::Method) specs = method.specializations - n = count(i -> isassigned(specs, i), 1:length(specs)) + specs isa Core.MethodInstance && return 1 + n = count(!isnothing, specs::Core.SimpleVector) return n end @@ -1202,23 +1185,7 @@ copy_dims_pair(out) = () copy_dims_pair(out, dim::Int, tail...) = copy_dims_pair(out => dim, tail...) copy_dims_pair(out, dim::Colon, tail...) = copy_dims_pair(out => dim, tail...) @test Base.return_types(copy_dims_pair, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 5 < count_specializations(m) < 15, methods(copy_dims_pair)) # currently about 7 - -@test isdefined_tfunc(typeof(NamedTuple()), Const(0)) === Const(false) -@test isdefined_tfunc(typeof(NamedTuple()), Const(1)) === Const(false) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(:a)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(:b)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(:c)) === Const(false) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(0)) === Const(false) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(1)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(2)) === Const(true) -@test isdefined_tfunc(typeof((a=1,b=2)), Const(3)) === Const(false) -@test isdefined_tfunc(NamedTuple, Const(1)) == Bool -@test isdefined_tfunc(NamedTuple, Symbol) == Bool -@test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:z)) -@test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(1)) -@test Const(false) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(3)) -@test Const(true) ⊑ isdefined_tfunc(NamedTuple{(:x,:y)}, Const(:y)) +@test all(m -> 3 < count_specializations(m) < 15, methods(copy_dims_pair)) # currently about 5 # splatting an ::Any should still allow inference to use types of parameters preceding it f22364(::Int, ::Any...) = 0 @@ -1232,7 +1199,7 @@ function get_linfo(@nospecialize(f), @nospecialize(t)) throw(ArgumentError("argument is not a generic function")) end # get the MethodInstance for the method match - match = Base._which(Base.signature_type(f, t), Base.get_world_counter()) + match = Base._which(Base.signature_type(f, t)) precompile(match.spec_types) return Core.Compiler.specialize_method(match) end @@ -1328,7 +1295,8 @@ 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) -let isa_tfunc = Core.Compiler.isa_tfunc +let isa_tfunc(@nospecialize xs...) = + Core.Compiler.isa_tfunc(Core.Compiler.fallback_lattice, xs...) @test isa_tfunc(Array, Const(AbstractArray)) === Const(true) @test isa_tfunc(Array, Type{AbstractArray}) === Const(true) @test isa_tfunc(Array, Type{AbstractArray{Int}}) == Bool @@ -1348,7 +1316,7 @@ let isa_tfunc = Core.Compiler.isa_tfunc @test isa_tfunc(typeof(Union{}), Union{}) === Union{} # any result is ok @test isa_tfunc(typeof(Union{}), Type{typeof(Union{})}) === Const(true) @test isa_tfunc(typeof(Union{}), Const(typeof(Union{}))) === Const(true) - let c = Conditional(Core.SlotNumber(0), Const(Union{}), Const(Union{})) + let c = Conditional(0, Const(Union{}), Const(Union{})) @test isa_tfunc(c, Const(Bool)) === Const(true) @test isa_tfunc(c, Type{Bool}) === Const(true) @test isa_tfunc(c, Const(Real)) === Const(true) @@ -1367,7 +1335,8 @@ let isa_tfunc = Core.Compiler.isa_tfunc @test isa_tfunc(Union{Int64, Float64}, Type{AbstractArray}) === Const(false) end -let subtype_tfunc = Core.Compiler.subtype_tfunc +let subtype_tfunc(@nospecialize xs...) = + Core.Compiler.subtype_tfunc(Core.Compiler.fallback_lattice, xs...) @test subtype_tfunc(Type{<:Array}, Const(AbstractArray)) === Const(true) @test subtype_tfunc(Type{<:Array}, Type{AbstractArray}) === Const(true) @test subtype_tfunc(Type{<:Array}, Type{AbstractArray{Int}}) == Bool @@ -1399,7 +1368,7 @@ let subtype_tfunc = Core.Compiler.subtype_tfunc @test subtype_tfunc(Type{Union{}}, Any) === Const(true) # Union{} <: Any @test subtype_tfunc(Type{Union{}}, Union{Type{Int64}, Type{Float64}}) === Const(true) @test subtype_tfunc(Type{Union{}}, Union{Type{T}, Type{Float64}} where T) === Const(true) - let c = Conditional(Core.SlotNumber(0), Const(Union{}), Const(Union{})) + let c = Conditional(0, Const(Union{}), Const(Union{})) @test subtype_tfunc(c, Const(Bool)) === Const(true) # any result is ok end @test subtype_tfunc(Type{Val{1}}, Type{Val{T}} where T) === Bool @@ -1419,8 +1388,9 @@ end let egal_tfunc function egal_tfunc(a, b) - r = Core.Compiler.egal_tfunc(a, b) - @test r === Core.Compiler.egal_tfunc(b, a) + 𝕃 = Core.Compiler.fallback_lattice + r = Core.Compiler.egal_tfunc(𝕃, a, b) + @test r === Core.Compiler.egal_tfunc(𝕃, b, a) return r end @test egal_tfunc(Const(12345.12345), Const(12344.12345 + 1)) == Const(true) @@ -1442,7 +1412,7 @@ let egal_tfunc @test egal_tfunc(Type{Union{Float32, Float64}}, Type{Union{Float32, Float64}}) === Bool @test egal_tfunc(typeof(Union{}), typeof(Union{})) === Bool # could be improved @test egal_tfunc(Const(typeof(Union{})), Const(typeof(Union{}))) === Const(true) - let c = Conditional(Core.SlotNumber(0), Const(Union{}), Const(Union{})) + let c = Conditional(0, Const(Union{}), Const(Union{})) @test egal_tfunc(c, Const(Bool)) === Const(false) @test egal_tfunc(c, Type{Bool}) === Const(false) @test egal_tfunc(c, Const(Real)) === Const(false) @@ -1453,17 +1423,17 @@ let egal_tfunc @test egal_tfunc(c, Bool) === Bool @test egal_tfunc(c, Any) === Bool end - let c = Conditional(Core.SlotNumber(0), Union{}, Const(Union{})) # === Const(false) - @test egal_tfunc(c, Const(false)) === Conditional(c.var, c.elsetype, Union{}) - @test egal_tfunc(c, Const(true)) === Conditional(c.var, Union{}, c.elsetype) + let c = Conditional(0, Union{}, Const(Union{})) # === Const(false) + @test egal_tfunc(c, Const(false)) === Conditional(c.slot, c.elsetype, Union{}) + @test egal_tfunc(c, Const(true)) === Conditional(c.slot, Union{}, c.elsetype) @test egal_tfunc(c, Const(nothing)) === Const(false) @test egal_tfunc(c, Int) === Const(false) @test egal_tfunc(c, Bool) === Bool @test egal_tfunc(c, Any) === Bool end - let c = Conditional(Core.SlotNumber(0), Const(Union{}), Union{}) # === Const(true) - @test egal_tfunc(c, Const(false)) === Conditional(c.var, Union{}, c.vtype) - @test egal_tfunc(c, Const(true)) === Conditional(c.var, c.vtype, Union{}) + let c = Conditional(0, Const(Union{}), Union{}) # === Const(true) + @test egal_tfunc(c, Const(false)) === Conditional(c.slot, Union{}, c.thentype) + @test egal_tfunc(c, Const(true)) === Conditional(c.slot, c.thentype, Union{}) @test egal_tfunc(c, Const(nothing)) === Const(false) @test egal_tfunc(c, Int) === Const(false) @test egal_tfunc(c, Bool) === Bool @@ -1488,79 +1458,105 @@ egal_conditional_lattice3(x, y) = x === y + y ? "" : 1 @test Base.return_types(egal_conditional_lattice3, (Int64, Int64)) == Any[Union{Int, String}] @test Base.return_types(egal_conditional_lattice3, (Int32, Int64)) == Any[Int] -using Core.Compiler: PartialStruct, nfields_tfunc, sizeof_tfunc, sizeof_nothrow -@test sizeof_tfunc(Const(Ptr)) === sizeof_tfunc(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) === Const(Sys.WORD_SIZE ÷ 8) -@test sizeof_tfunc(Type{Ptr}) === Const(sizeof(Ptr)) -@test sizeof_nothrow(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) -@test sizeof_nothrow(Const(Ptr)) -@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{Union{Int, String}}) -@test sizeof_nothrow(String) -@test !sizeof_nothrow(Type{String}) -@test sizeof_tfunc(Type{Union{Int64, Int32}}) == Const(Core.sizeof(Union{Int64, Int32})) -let PT = PartialStruct(Tuple{Int64,UInt64}, Any[Const(10), UInt64]) - @test sizeof_tfunc(PT) === Const(16) - @test nfields_tfunc(PT) === Const(2) - @test sizeof_nothrow(PT) -end -@test nfields_tfunc(Type) === Int -@test nfields_tfunc(Number) === Int -@test nfields_tfunc(Int) === Const(0) -@test nfields_tfunc(Complex) === Const(2) -@test nfields_tfunc(Type{Type{Int}}) === Const(nfields(DataType)) -@test nfields_tfunc(UnionAll) === Const(2) -@test nfields_tfunc(DataType) === Const(nfields(DataType)) -@test nfields_tfunc(Type{Int}) === Const(nfields(DataType)) -@test nfields_tfunc(Type{Integer}) === Const(nfields(DataType)) -@test nfields_tfunc(Type{Complex}) === Int -@test nfields_tfunc(typeof(Union{})) === Const(0) -@test nfields_tfunc(Type{Union{}}) === Const(0) -@test nfields_tfunc(Tuple{Int, Vararg{Int}}) === Int -@test nfields_tfunc(Tuple{Int, Integer}) === Const(2) -@test nfields_tfunc(Union{Tuple{Int, Float64}, Tuple{Int, Int}}) === Const(2) - -using Core.Compiler: typeof_tfunc -@test typeof_tfunc(Tuple{Vararg{Int}}) == Type{Tuple{Vararg{Int,N}}} where N -@test typeof_tfunc(Tuple{Any}) == Type{<:Tuple{Any}} -@test typeof_tfunc(Type{Array}) === DataType -@test typeof_tfunc(Type{<:Array}) === DataType -@test typeof_tfunc(Array{Int}) == Type{Array{Int,N}} where N -@test typeof_tfunc(AbstractArray{Int}) == Type{<:AbstractArray{Int,N}} where N -@test typeof_tfunc(Union{<:T, <:Real} where T<:Complex) == Union{Type{Complex{T}} where T<:Real, Type{<:Real}} +let nfields_tfunc(@nospecialize xs...) = + Core.Compiler.nfields_tfunc(Core.Compiler.fallback_lattice, xs...) + sizeof_tfunc(@nospecialize xs...) = + Core.Compiler.sizeof_tfunc(Core.Compiler.fallback_lattice, xs...) + sizeof_nothrow(@nospecialize xs...) = + Core.Compiler.sizeof_nothrow(xs...) + @test sizeof_tfunc(Const(Ptr)) === sizeof_tfunc(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) === Const(Sys.WORD_SIZE ÷ 8) + @test sizeof_tfunc(Type{Ptr}) === Const(sizeof(Ptr)) + @test sizeof_nothrow(Union{Ptr, Int, Type{Ptr{Int8}}, Type{Int}}) + @test sizeof_nothrow(Const(Ptr)) + @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{Union{Int, String}}) + @test sizeof_nothrow(String) + @test !sizeof_nothrow(Type{String}) + @test sizeof_tfunc(Type{Union{Int64, Int32}}) == Const(Core.sizeof(Union{Int64, Int32})) + let PT = Core.Compiler.PartialStruct(Tuple{Int64,UInt64}, Any[Const(10), UInt64]) + @test sizeof_tfunc(PT) === Const(16) + @test nfields_tfunc(PT) === Const(2) + @test sizeof_nothrow(PT) + end + @test nfields_tfunc(Type) === Int + @test nfields_tfunc(Number) === Int + @test nfields_tfunc(Int) === Const(0) + @test nfields_tfunc(Complex) === Const(2) + @test nfields_tfunc(Type{Type{Int}}) === Const(nfields(DataType)) + @test nfields_tfunc(UnionAll) === Const(2) + @test nfields_tfunc(DataType) === Const(nfields(DataType)) + @test nfields_tfunc(Type{Int}) === Const(nfields(DataType)) + @test nfields_tfunc(Type{Integer}) === Const(nfields(DataType)) + @test nfields_tfunc(Type{Complex}) === Int + @test nfields_tfunc(typeof(Union{})) === Const(0) + @test nfields_tfunc(Type{Union{}}) === Const(0) + @test nfields_tfunc(Tuple{Int, Vararg{Int}}) === Int + @test nfields_tfunc(Tuple{Int, Integer}) === Const(2) + @test nfields_tfunc(Union{Tuple{Int, Float64}, Tuple{Int, Int}}) === Const(2) + @test nfields_tfunc(@NamedTuple{a::Int,b::Integer}) === Const(2) + @test nfields_tfunc(NamedTuple{(:a,:b),T} where T<:Tuple{Int,Integer}) === Const(2) + @test nfields_tfunc(NamedTuple{(:a,:b)}) === Const(2) + @test nfields_tfunc(NamedTuple{names,Tuple{Any,Any}} where names) === Const(2) + @test nfields_tfunc(Union{NamedTuple{(:a,:b)},NamedTuple{(:c,:d)}}) === Const(2) +end + +let typeof_tfunc(@nospecialize xs...) = + Core.Compiler.typeof_tfunc(Core.Compiler.fallback_lattice, xs...) + @test typeof_tfunc(Tuple{Vararg{Int}}) == Type{Tuple{Vararg{Int,N}}} where N + @test typeof_tfunc(Tuple{Any}) == Type{<:Tuple{Any}} + @test typeof_tfunc(Type{Array}) === DataType + @test typeof_tfunc(Type{<:Array}) === DataType + @test typeof_tfunc(Array{Int}) == Type{Array{Int,N}} where N + @test typeof_tfunc(AbstractArray{Int}) == Type{<:AbstractArray{Int,N}} where N + @test typeof_tfunc(Union{<:T, <:Real} where T<:Complex) == Union{Type{Complex{T}} where T<:Real, Type{<:Real}} +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, arrayref_tfunc, arrayset_tfunc, arraysize_tfunc -@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{} +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{} +end + +let tuple_tfunc(@nospecialize xs...) = + Core.Compiler.tuple_tfunc(Core.Compiler.fallback_lattice, Any[xs...]) + @test Core.Compiler.widenconst(tuple_tfunc(Type{Int})) === Tuple{DataType} + # https://github.com/JuliaLang/julia/issues/44705 + @test tuple_tfunc(Union{Type{Int32},Type{Int64}}) === Tuple{Type} + @test tuple_tfunc(DataType) === Tuple{DataType} + @test tuple_tfunc(UnionAll) === Tuple{UnionAll} +end function f23024(::Type{T}, ::Int) where T 1 + 1 @@ -1572,6 +1568,7 @@ g23024(TT::Tuple{DataType}) = f23024(TT[1], v23024) @test g23024((UInt8,)) === 2 @test !Core.Compiler.isconstType(Type{typeof(Union{})}) # could be Core.TypeofBottom or Type{Union{}} at runtime +@test !isa(Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, Type{Core.TypeofBottom}, Core.Compiler.Const(:name)), Core.Compiler.Const) @test Base.return_types(supertype, (Type{typeof(Union{})},)) == Any[Any] # issue #23685 @@ -1598,7 +1595,7 @@ gg13183(x::X...) where {X} = (_false13183 ? gg13183(x, x) : 0) let linfo = get_linfo(Base.convert, Tuple{Type{Int64}, Int32}), world = UInt(23) # some small-numbered world that should be valid interp = Core.Compiler.NativeInterpreter() - opt = Core.Compiler.OptimizationState(linfo, Core.Compiler.OptimizationParams(interp), interp) + opt = Core.Compiler.OptimizationState(linfo, interp) # make sure the state of the properties look reasonable @test opt.src !== linfo.def.source @test length(opt.src.slotflags) == linfo.def.nargs <= length(opt.src.slotnames) @@ -1630,44 +1627,48 @@ g_test_constant() = (f_constant(3) == 3 && f_constant(4) == 4 ? true : "BAD") f_pure_add() = (1 + 1 == 2) ? true : "FAIL" @test @inferred f_pure_add() -# inference of `T.mutable` -@test Core.Compiler.getfield_tfunc(Const(Int.name), Const(:flags)) == Const(0x4) -@test Core.Compiler.getfield_tfunc(Const(Vector{Int}.name), Const(:flags)) == Const(0x2) -@test Core.Compiler.getfield_tfunc(Core.TypeName, Const(:flags)) == UInt8 - -# getfield on abstract named tuples. issue #32698 -import Core.Compiler: getfield_tfunc, Const -@test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Const(:y)) == Union{Missing, Float64} -@test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Const(2)) == Union{Missing, Float64} -@test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Symbol) == Union{Missing, Float64, Int} -@test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Symbol) == Union{Missing, Float64, Int} -@test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Int) == Union{Missing, Float64, Int} -@test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, - Const(:x)) == Union{Missing, Float64, Int} - +import Core: Const mutable struct ARef{T} @atomic x::T end -@test getfield_tfunc(ARef{Int},Const(:x),Symbol) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Bool) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Symbol,Bool) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Symbol,Vararg{Symbol}) === Int # `Vararg{Symbol}` might be empty -@test getfield_tfunc(ARef{Int},Const(:x),Vararg{Symbol}) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Any,) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Any,Any) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Any,Vararg{Any}) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Vararg{Any}) === Int -@test getfield_tfunc(ARef{Int},Const(:x),Int) === Union{} -@test getfield_tfunc(ARef{Int},Const(:x),Bool,Symbol) === Union{} -@test getfield_tfunc(ARef{Int},Const(:x),Symbol,Symbol) === Union{} -@test getfield_tfunc(ARef{Int},Const(:x),Bool,Bool) === Union{} - -import Core.Compiler: setfield!_tfunc, setfield!_nothrow, Const +let getfield_tfunc(@nospecialize xs...) = + Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, xs...) + + # inference of `T.mutable` + @test getfield_tfunc(Const(Int.name), Const(:flags)) == Const(0x4) + @test getfield_tfunc(Const(Vector{Int}.name), Const(:flags)) == Const(0x2) + @test getfield_tfunc(Core.TypeName, Const(:flags)) == UInt8 + + # getfield on abstract named tuples. issue #32698 + @test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Const(:y)) == Union{Missing, Float64} + @test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Const(2)) == Union{Missing, Float64} + @test getfield_tfunc(NamedTuple{(:id, :y), T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Symbol) == Union{Missing, Float64, Int} + @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Symbol) == Union{Missing, Float64, Int} + @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Int) == Union{Missing, Float64, Int} + @test getfield_tfunc(NamedTuple{<:Any, T} where {T <: Tuple{Int, Union{Float64, Missing}}}, + Const(:x)) == Union{Missing, Float64, Int} + + @test getfield_tfunc(ARef{Int},Const(:x),Symbol) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Bool) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Symbol,Bool) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Symbol,Vararg{Symbol}) === Int # `Vararg{Symbol}` might be empty + @test getfield_tfunc(ARef{Int},Const(:x),Vararg{Symbol}) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Any,) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Any,Any) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Any,Vararg{Any}) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Vararg{Any}) === Int + @test getfield_tfunc(ARef{Int},Const(:x),Int) === Union{} + @test getfield_tfunc(ARef{Int},Const(:x),Bool,Symbol) === Union{} + @test getfield_tfunc(ARef{Int},Const(:x),Symbol,Symbol) === Union{} + @test getfield_tfunc(ARef{Int},Const(:x),Bool,Bool) === Union{} +end + +import Core.Compiler: Const mutable struct XY{X,Y} x::X y::Y @@ -1678,101 +1679,107 @@ mutable struct ABCDconst c const d::Union{Int,Nothing} end -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Int, Int) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int, Symbol) === Int -@test setfield!_tfunc(Base.RefValue{Any}, Int, Int) === Int -@test setfield!_tfunc(XY{Any,Any}, Const(1), Int) === Int -@test setfield!_tfunc(XY{Any,Any}, Const(2), Float64) === Float64 -@test setfield!_tfunc(XY{Int,Float64}, Const(1), Int) === Int -@test setfield!_tfunc(XY{Int,Float64}, Const(2), Float64) === Float64 -@test setfield!_tfunc(ABCDconst, Const(:c), Any) === Any -@test setfield!_tfunc(ABCDconst, Const(3), Any) === Any -@test setfield!_tfunc(ABCDconst, Symbol, Any) === Any -@test setfield!_tfunc(ABCDconst, Int, Any) === Any -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(:x), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(1), Int) === Int -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Symbol, Int) === Int -@test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Int, Int) === Int -@test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Int, Int) === Int -@test setfield!_tfunc(Any, Symbol, Int) === Int -@test setfield!_tfunc(Any, Int, Int) === Int -@test setfield!_tfunc(Any, Any, Int) === Int -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64, Symbol) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64, Symbol) === Union{} -@test setfield!_tfunc(Base.RefValue{Int}, Int, Float64) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int, Bool) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int, Bool) === Union{} -@test setfield!_tfunc(Base.RefValue{Any}, String, Int) === Union{} -@test setfield!_tfunc(Some{Any}, Const(:value), Int) === Union{} -@test setfield!_tfunc(Some, Const(:value), Int) === Union{} -@test setfield!_tfunc(Some{Any}, Const(1), Int) === Union{} -@test setfield!_tfunc(Some, Const(1), Int) === Union{} -@test setfield!_tfunc(Some{Any}, Symbol, Int) === Union{} -@test setfield!_tfunc(Some, Symbol, Int) === Union{} -@test setfield!_tfunc(Some{Any}, Int, Int) === Union{} -@test setfield!_tfunc(Some, Int, Int) === Union{} -@test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Union{} -@test setfield!_tfunc(Const(@__MODULE__), Int, Int) === Union{} -@test setfield!_tfunc(Module, Const(:v), Int) === Union{} -@test setfield!_tfunc(Union{Module,Base.RefValue{Any}}, Const(:v), Int) === Union{} -@test setfield!_tfunc(ABCDconst, Const(:a), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(:b), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(:d), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(1), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(2), Any) === Union{} -@test setfield!_tfunc(ABCDconst, Const(4), Any) === Union{} -@test setfield!_nothrow(Base.RefValue{Int}, Const(:x), Int) -@test setfield!_nothrow(Base.RefValue{Int}, Const(1), Int) -@test setfield!_nothrow(Base.RefValue{Any}, Const(:x), Int) -@test setfield!_nothrow(Base.RefValue{Any}, Const(1), Int) -@test setfield!_nothrow(XY{Any,Any}, Const(:x), Int) -@test setfield!_nothrow(XY{Any,Any}, Const(:x), Any) -@test setfield!_nothrow(XY{Int,Float64}, Const(:x), Int) -@test setfield!_nothrow(ABCDconst, Const(:c), Any) -@test setfield!_nothrow(ABCDconst, Const(3), Any) -@test !setfield!_nothrow(XY{Int,Float64}, Symbol, Any) -@test !setfield!_nothrow(XY{Int,Float64}, Int, Any) -@test !setfield!_nothrow(Base.RefValue{Int}, Const(:x), Any) -@test !setfield!_nothrow(Base.RefValue{Int}, Const(1), Any) -@test !setfield!_nothrow(Any[Base.RefValue{Any}, Const(:x), Int, Symbol]) -@test !setfield!_nothrow(Base.RefValue{Any}, Symbol, Int) -@test !setfield!_nothrow(Base.RefValue{Any}, Int, Int) -@test !setfield!_nothrow(XY{Int,Float64}, Const(:y), Int) -@test !setfield!_nothrow(XY{Int,Float64}, Symbol, Int) -@test !setfield!_nothrow(XY{Int,Float64}, Int, Int) -@test !setfield!_nothrow(ABCDconst, Const(:a), Any) -@test !setfield!_nothrow(ABCDconst, Const(:b), Any) -@test !setfield!_nothrow(ABCDconst, Const(:d), Any) -@test !setfield!_nothrow(ABCDconst, Symbol, Any) -@test !setfield!_nothrow(ABCDconst, Const(1), Any) -@test !setfield!_nothrow(ABCDconst, Const(2), Any) -@test !setfield!_nothrow(ABCDconst, Const(4), Any) -@test !setfield!_nothrow(ABCDconst, Int, Any) -@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) -@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(:x), Int) -@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) -@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(1), Int) -@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) -@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Symbol, Int) -@test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Int, Int) -@test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Int, Int) -@test !setfield!_nothrow(Any, Symbol, Int) -@test !setfield!_nothrow(Any, Int, Int) -@test !setfield!_nothrow(Any, Any, Int) +let setfield!_tfunc(@nospecialize xs...) = + Core.Compiler.setfield!_tfunc(Core.Compiler.fallback_lattice, xs...) + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Int, Int) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(:x), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Const(1), Int, Symbol) === Int + @test setfield!_tfunc(Base.RefValue{Any}, Int, Int) === Int + @test setfield!_tfunc(XY{Any,Any}, Const(1), Int) === Int + @test setfield!_tfunc(XY{Any,Any}, Const(2), Float64) === Float64 + @test setfield!_tfunc(XY{Int,Float64}, Const(1), Int) === Int + @test setfield!_tfunc(XY{Int,Float64}, Const(2), Float64) === Float64 + @test setfield!_tfunc(ABCDconst, Const(:c), Any) === Any + @test setfield!_tfunc(ABCDconst, Const(3), Any) === Any + @test setfield!_tfunc(ABCDconst, Symbol, Any) === Any + @test setfield!_tfunc(ABCDconst, Int, Any) === Any + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(:x), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Const(1), Int) === Int + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Symbol, Int) === Int + @test setfield!_tfunc(Union{Base.RefValue{Any},Some{Any}}, Int, Int) === Int + @test setfield!_tfunc(Union{Base.RefValue,Some{Any}}, Int, Int) === Int + @test setfield!_tfunc(Any, Symbol, Int) === Int + @test setfield!_tfunc(Any, Int, Int) === Int + @test setfield!_tfunc(Any, Any, Int) === Int + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Const(:x), Float64, Symbol) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Const(1), Float64, Symbol) === Union{} + @test setfield!_tfunc(Base.RefValue{Int}, Int, Float64) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(:y), Int, Bool) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, Const(2), Int, Bool) === Union{} + @test setfield!_tfunc(Base.RefValue{Any}, String, Int) === Union{} + @test setfield!_tfunc(Some{Any}, Const(:value), Int) === Union{} + @test setfield!_tfunc(Some, Const(:value), Int) === Union{} + @test setfield!_tfunc(Some{Any}, Const(1), Int) === Union{} + @test setfield!_tfunc(Some, Const(1), Int) === Union{} + @test setfield!_tfunc(Some{Any}, Symbol, Int) === Union{} + @test setfield!_tfunc(Some, Symbol, Int) === Union{} + @test setfield!_tfunc(Some{Any}, Int, Int) === Union{} + @test setfield!_tfunc(Some, Int, Int) === Union{} + @test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Union{} + @test setfield!_tfunc(Const(@__MODULE__), Int, Int) === Union{} + @test setfield!_tfunc(Module, Const(:v), Int) === Union{} + @test setfield!_tfunc(Union{Module,Base.RefValue{Any}}, Const(:v), Int) === Union{} + @test setfield!_tfunc(ABCDconst, Const(:a), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(:b), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(:d), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(1), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(2), Any) === Union{} + @test setfield!_tfunc(ABCDconst, Const(4), Any) === Union{} +end +let setfield!_nothrow(@nospecialize xs...) = + Core.Compiler.setfield!_nothrow(Core.Compiler.OptimizerLattice(), xs...) + @test setfield!_nothrow(Base.RefValue{Int}, Const(:x), Int) + @test setfield!_nothrow(Base.RefValue{Int}, Const(1), Int) + @test setfield!_nothrow(Base.RefValue{Any}, Const(:x), Int) + @test setfield!_nothrow(Base.RefValue{Any}, Const(1), Int) + @test setfield!_nothrow(XY{Any,Any}, Const(:x), Int) + @test setfield!_nothrow(XY{Any,Any}, Const(:x), Any) + @test setfield!_nothrow(XY{Int,Float64}, Const(:x), Int) + @test setfield!_nothrow(ABCDconst, Const(:c), Any) + @test setfield!_nothrow(ABCDconst, Const(3), Any) + @test !setfield!_nothrow(XY{Int,Float64}, Symbol, Any) + @test !setfield!_nothrow(XY{Int,Float64}, Int, Any) + @test !setfield!_nothrow(Base.RefValue{Int}, Const(:x), Any) + @test !setfield!_nothrow(Base.RefValue{Int}, Const(1), Any) + @test !setfield!_nothrow(Base.RefValue{Any}, Const(:x), Int, Symbol) + @test !setfield!_nothrow(Base.RefValue{Any}, Symbol, Int) + @test !setfield!_nothrow(Base.RefValue{Any}, Int, Int) + @test !setfield!_nothrow(XY{Int,Float64}, Const(:y), Int) + @test !setfield!_nothrow(XY{Int,Float64}, Symbol, Int) + @test !setfield!_nothrow(XY{Int,Float64}, Int, Int) + @test !setfield!_nothrow(ABCDconst, Const(:a), Any) + @test !setfield!_nothrow(ABCDconst, Const(:b), Any) + @test !setfield!_nothrow(ABCDconst, Const(:d), Any) + @test !setfield!_nothrow(ABCDconst, Symbol, Any) + @test !setfield!_nothrow(ABCDconst, Const(1), Any) + @test !setfield!_nothrow(ABCDconst, Const(2), Any) + @test !setfield!_nothrow(ABCDconst, Const(4), Any) + @test !setfield!_nothrow(ABCDconst, Int, Any) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(:x), Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(:x), Int) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Const(1), Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Const(1), Int) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Symbol, Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Symbol, Int) + @test !setfield!_nothrow(Union{Base.RefValue{Any},Some{Any}}, Int, Int) + @test !setfield!_nothrow(Union{Base.RefValue,Some{Any}}, Int, Int) + @test !setfield!_nothrow(Any, Symbol, Int) + @test !setfield!_nothrow(Any, Int, Int) + @test !setfield!_nothrow(Any, Any, Int) +end struct Foo_22708 x::Ptr{Foo_22708} @@ -1785,9 +1792,17 @@ bar_22708(x) = f_22708(x) @test bar_22708(1) == "x" +struct EarlyGeneratedFunctionStub + stub::Core.GeneratedFunctionStub +end +(stub::EarlyGeneratedFunctionStub)(args...) = (@nospecialize; stub.stub(args...)) + # mechanism for spoofing work-limiting heuristics and early generator expansion (#24852) -function _generated_stub(gen::Symbol, args::Vector{Any}, params::Vector{Any}, line, file, expand_early) - stub = Expr(:new, Core.GeneratedFunctionStub, gen, args, params, line, file, expand_early) +function _generated_stub(gen::Symbol, args::Core.SimpleVector, params::Core.SimpleVector, expand_early::Bool) + stub = Expr(:new, Core.GeneratedFunctionStub, gen, args, params) + if expand_early + stub = Expr(:new, EarlyGeneratedFunctionStub, stub) + end return Expr(:meta, :generated, stub) end @@ -1796,39 +1811,52 @@ f24852_kernel2(x, y::Tuple) = f24852_kernel1(x, (y,)) f24852_kernel3(x, y::Tuple) = f24852_kernel2(x, (y,)) f24852_kernel(x, y::Number) = f24852_kernel3(x, (y,)) -function f24852_kernel_cinfo(fsig::Type) - world = typemax(UInt) # FIXME - match = Base._methods_by_ftype(fsig, -1, world)[1] - isdefined(match.method, :source) || return (nothing, :(f(x, y))) +function f24852_kernel_cinfo(world::UInt, source, fsig::Type) + matches = Base._methods_by_ftype(fsig, -1, world) + if matches === nothing || length(matches) != 1 + match = nothing + else + match = matches[1] + if !isdefined(match.method, :source) + match = nothing + end + end + if match === nothing + code_info = :(f(x, y)) + code_info = Core.GeneratedFunctionStub(identity, Core.svec(:self, :f, :x, :y), Core.svec(:X, :Y))(world, source, code_info) + return (nothing, code_info) + end code_info = Base.uncompressed_ir(match.method) Meta.partially_inline!(code_info.code, Any[], match.spec_types, Any[match.sparams...], 1, 0, :propagate) if startswith(String(match.method.name), "f24852") for a in code_info.code - if a isa Expr && a.head == :(=) + if Meta.isexpr(a, :(=)) a = a.args[2] end - if a isa Expr && length(a.args) === 3 && a.head === :call + if Meta.isexpr(a, :call) && length(a.args) === 3 pushfirst!(a.args, Core.SlotNumber(1)) end end end pushfirst!(code_info.slotnames, Symbol("#self#")) pushfirst!(code_info.slotflags, 0x00) + # 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 match.method, code_info end -function f24852_gen_cinfo_uninflated(X, Y, _, f, x, y) - _, code_info = f24852_kernel_cinfo(Tuple{f, x, y}) +function f24852_gen_cinfo_uninflated(world::UInt, source, X, Y, _, f, x, y) + _, code_info = f24852_kernel_cinfo(world, source, Tuple{f, x, y}) return code_info end -function f24852_gen_cinfo_inflated(X, Y, _, f, x, y) - method, code_info = f24852_kernel_cinfo(Tuple{f, x, y}) +function f24852_gen_cinfo_inflated(world::UInt, source, X, Y, _, f, x, y) + method, code_info = f24852_kernel_cinfo(world, source, Tuple{f, x, y}) code_info.method_for_inference_limit_heuristics = method return code_info end -function f24852_gen_expr(X, Y, _, f, x, y) # deparse f(x::X, y::Y) where {X, Y} +function f24852_gen_expr(X, Y, _, f, x, y) # deparse of f(x::X, y::Y) where {X, Y} if f === typeof(f24852_kernel) f2 = :f24852_kernel3 elseif f === typeof(f24852_kernel3) @@ -1845,20 +1873,8 @@ end @eval begin function f24852_late_expr(f, x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_expr, Any[:self, :f, :x, :y], - Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), false)) - $(Expr(:meta, :generated_only)) - #= no body =# - end - function f24852_late_inflated(f, x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_inflated, Any[:self, :f, :x, :y], - Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), false)) - $(Expr(:meta, :generated_only)) - #= no body =# - end - function f24852_late_uninflated(f, x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_uninflated, Any[:self, :f, :x, :y], - Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), false)) + $(_generated_stub(:f24852_gen_expr, Core.svec(:self, :f, :x, :y), + Core.svec(:X, :Y), false)) $(Expr(:meta, :generated_only)) #= no body =# end @@ -1866,20 +1882,18 @@ end @eval begin function f24852_early_expr(f, x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_expr, Any[:self, :f, :x, :y], - Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), true)) + $(_generated_stub(:f24852_gen_expr, Core.svec(:self, :f, :x, :y), + Core.svec(:X, :Y), true)) $(Expr(:meta, :generated_only)) #= no body =# end function f24852_early_inflated(f, x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_inflated, Any[:self, :f, :x, :y], - Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), true)) + $(Expr(:meta, :generated, f24852_gen_cinfo_inflated)) $(Expr(:meta, :generated_only)) #= no body =# end function f24852_early_uninflated(f, x::X, y::Y) where {X, Y} - $(_generated_stub(:f24852_gen_cinfo_uninflated, Any[:self, :f, :x, :y], - Any[:X, :Y], @__LINE__, QuoteNode(Symbol(@__FILE__)), true)) + $(Expr(:meta, :generated, f24852_gen_cinfo_uninflated)) $(Expr(:meta, :generated_only)) #= no body =# end @@ -1890,10 +1904,6 @@ result = f24852_kernel(x, y) @test result === f24852_late_expr(f24852_kernel, x, y) @test Base.return_types(f24852_late_expr, typeof((f24852_kernel, x, y))) == Any[Any] -@test result === f24852_late_uninflated(f24852_kernel, x, y) -@test Base.return_types(f24852_late_uninflated, typeof((f24852_kernel, x, y))) == Any[Any] -@test result === f24852_late_uninflated(f24852_kernel, x, y) -@test Base.return_types(f24852_late_uninflated, typeof((f24852_kernel, x, y))) == Any[Any] @test result === f24852_early_expr(f24852_kernel, x, y) @test Base.return_types(f24852_early_expr, typeof((f24852_kernel, x, y))) == Any[Any] @@ -1901,7 +1911,6 @@ result = f24852_kernel(x, y) @test Base.return_types(f24852_early_uninflated, typeof((f24852_kernel, x, y))) == Any[Any] @test result === @inferred f24852_early_inflated(f24852_kernel, x, y) @test Base.return_types(f24852_early_inflated, typeof((f24852_kernel, x, y))) == Any[Float64] - # TODO: test that `expand_early = true` + inflated `method_for_inference_limit_heuristics` # can be used to tighten up some inference result. @@ -1927,19 +1936,22 @@ function foo25261() next = f25261(Core.getfield(next, 2)) end end -opt25261 = code_typed(foo25261, Tuple{}, optimize=false)[1].first.code -i = 1 -# Skip to after the branch -while !isa(opt25261[i], GotoIfNot); global i += 1; end -foundslot = false -for expr25261 in opt25261[i:end] - if expr25261 isa TypedSlot && expr25261.typ === Tuple{Int, Int} - # This should be the assignment to the SSAValue into the getfield - # call - make sure it's a TypedSlot - global foundslot = true +let opt25261 = code_typed(foo25261, Tuple{}, optimize=false)[1].first.code + i = 1 + # Skip to after the branch + while !isa(opt25261[i], GotoIfNot) + i += 1 + end + foundslot = false + for expr25261 in opt25261[i:end] + if expr25261 isa Core.Compiler.TypedSlot && expr25261.typ === Tuple{Int, Int} + # This should be the assignment to the SSAValue into the getfield + # call - make sure it's a TypedSlot + foundslot = true + end end + @test foundslot end -@test foundslot @testset "inter-procedural conditional constraint propagation" begin # simple cases @@ -2021,17 +2033,21 @@ end return nothing end == Any[Union{Nothing,Expr}] - # handle the edge case - let ts = @eval Module() begin - edgecase(_) = $(Core.Compiler.InterConditional(2, Int, Any)) - # create cache - Base.return_types(edgecase, (Any,)) - Base.return_types((Any,)) do x - edgecase(x) ? x : nothing # ::Any - end + # handle edge case + @test (@eval Module() begin + edgecase(_) = $(Core.Compiler.InterConditional(2, Int, Any)) + Base.return_types(edgecase, (Any,)) # create cache + Base.return_types((Any,)) do x + edgecase(x) end - @test ts == Any[Any] - end + end) == Any[Core.Compiler.InterConditional] + + # a tricky case: if constant inference derives `Const` while non-constant inference has + # derived `InterConditional`, we should not discard that constant information + iszero_simple(x) = x === 0 + @test Base.return_types() do + iszero_simple(0) ? nothing : missing + end |> only === Nothing end @testset "branching on conditional object" begin @@ -2060,7 +2076,7 @@ end end # https://github.com/JuliaLang/julia/issues/42090#issuecomment-911824851 -# `PartialStruct` shoudln't wrap `Conditional` +# `PartialStruct` shouldn't wrap `Conditional` let M = Module() @eval M begin struct BePartialStruct @@ -2075,13 +2091,23 @@ let M = Module() obj = $(Expr(:new, M.BePartialStruct, 42, :cond)) r1 = getfield(obj, :cond) ? 0 : a # r1::Union{Nothing,Int}, not r1::Int (because PartialStruct doesn't wrap Conditional) a = $(gensym(:anyvar))::Any - r2 = getfield(obj, :cond) ? a : nothing # r2::Any, not r2::Const(nothing) (we don't need to worry about constrait invalidation here) + r2 = getfield(obj, :cond) ? a : nothing # r2::Any, not r2::Const(nothing) (we don't need to worry about constraint invalidation here) return r1, r2 # ::Tuple{Union{Nothing,Int},Any} end |> only end @test rt == Tuple{Union{Nothing,Int},Any} end +# make sure we never form nested `Conditional` (https://github.com/JuliaLang/julia/issues/46207) +@test Base.return_types((Any,)) do a + c = isa(a, Integer) + 42 === c ? :a : "b" +end |> only === String +@test Base.return_types((Any,)) do a + c = isa(a, Integer) + c === 42 ? :a : "b" +end |> only === String + @testset "conditional constraint propagation from non-`Conditional` object" begin @test Base.return_types((Bool,)) do b if b @@ -2109,7 +2135,7 @@ end # `InterConditional` handling: `abstract_invoke` ispositive(a) = isa(a, Int) && a > 0 @test Base.return_types((Any,)) do a - if Base.@invoke ispositive(a::Any) + if @invoke ispositive(a::Any) return a end return 0 @@ -2129,6 +2155,303 @@ end end |> only === Int end +# type-based alias analysis +# ========================= +# `MustAlias` propagates constraints imposed on aliased fields + +struct AliasableField{T} + f::T +end +struct AliasableFields{S,T} + f1::S + f2::T +end +mutable struct AliasableConstField{S,T} + const f1::S + f2::T +end + +import Core.Compiler: + InferenceLattice, OptimizerLattice, MustAliasesLattice, InterMustAliasesLattice, + BaseInferenceLattice, IPOResultLattice, typeinf_lattice, ipo_lattice, optimizer_lattice + +include("newinterp.jl") +@newinterp MustAliasInterpreter +let CC = Core.Compiler + CC.typeinf_lattice(::MustAliasInterpreter) = InferenceLattice(MustAliasesLattice(BaseInferenceLattice.instance)) + CC.ipo_lattice(::MustAliasInterpreter) = InferenceLattice(InterMustAliasesLattice(IPOResultLattice.instance)) + CC.optimizer_lattice(::MustAliasInterpreter) = OptimizerLattice() +end + +# lattice +# ------- + +import Core.Compiler: MustAlias, Const, PartialStruct, ⊑, tmerge +let 𝕃ᵢ = InferenceLattice(MustAliasesLattice(BaseInferenceLattice.instance)) + ⊑(@nospecialize(a), @nospecialize(b)) = Core.Compiler.:⊑(𝕃ᵢ, a, b) + tmerge(@nospecialize(a), @nospecialize(b)) = Core.Compiler.tmerge(𝕃ᵢ, a, b) + isa_tfunc(@nospecialize xs...) = Core.Compiler.isa_tfunc(𝕃ᵢ, xs...) + ifelse_tfunc(@nospecialize xs...) = Core.Compiler.ifelse_tfunc(𝕃ᵢ, xs...) + + @test (MustAlias(2, AliasableField{Any}, 1, Int) ⊑ Int) + @test !(Int ⊑ MustAlias(2, AliasableField{Any}, 1, Int)) + @test (Int ⊑ MustAlias(2, AliasableField{Any}, 1, Any)) + @test (Const(42) ⊑ MustAlias(2, AliasableField{Any}, 1, Int)) + @test !(MustAlias(2, AliasableField{Any}, 1, Any) ⊑ Int) + @test tmerge(MustAlias(2, AliasableField{Any}, 1, Any), Const(nothing)) === Any + @test tmerge(MustAlias(2, AliasableField{Any}, 1, Int), Const(nothing)) === Union{Int,Nothing} + @test tmerge(Const(nothing), MustAlias(2, AliasableField{Any}, 1, Any)) === Any + @test tmerge(Const(nothing), MustAlias(2, AliasableField{Any}, 1, Int)) === Union{Int,Nothing} + @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Const(Bool)) === Const(true) + @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Type{Bool}) === Const(true) + @test isa_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Type{Bool}) === Const(false) + @test ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Bool), Int, Int) === Int + @test ifelse_tfunc(MustAlias(2, AliasableField{Any}, 1, Int), Int, Int) === Union{} +end + +maybeget_mustalias_tmerge(x::AliasableField) = x.f +maybeget_mustalias_tmerge(x) = x +@test Base.return_types((Union{Nothing,AliasableField{Any}},); interp=MustAliasInterpreter()) do x + isa(maybeget_mustalias_tmerge(x)#=::Any, not MustAlias=#, Int) && throw() + x +end |> only === Union{Nothing,AliasableField{Any}} + +# isa constraint +# -------------- + +# simple intra-procedural case +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do a + if isa(getfield(a, :f), Int) + return getfield(a, :f) + end + return 0 +end |> only === Int +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do a + if isa(getfield(a, 1), Int) + return getfield(a, 1) + end + return 0 +end |> only === Int +@test Base.return_types((AliasableField{Union{Some{Int},Nothing}},); interp=MustAliasInterpreter()) do a + if isa(getfield(a, 1), Some) + return getfield(a, 1) + end + throw() +end |> only === Some{Int} +@test Base.return_types((Tuple{Any},); interp=MustAliasInterpreter()) do t + if isa(getfield(t, 1), Int) + return getfield(t, 1) + end + return 0 +end |> only === Int +@test Base.return_types((Any,); interp=MustAliasInterpreter()) do a + x = AliasableFields(a, 0) # x::PartialStruct(AliasableFields, Any[Any, Const(0)]) + if isa(getfield(x, :f1), Int) # x::PartialStruct(AliasableFields, Any[Int, Const(0)]) + return getfield(x, :f1) + end + return 0 +end |> only === Int +@test Base.return_types((Any,Any); interp=MustAliasInterpreter()) do a, b + x = AliasableFields(a, b) # x::AliasableFields + if isa(getfield(x, :f1), Int) # x::PartialStruct(AliasableFields, Any[Int, Any]) + if isa(getfield(x, :f2), Int) # x::PartialStruct(AliasableFields, Any[Int, Int]) + return getfield(x, :f1), getfield(x, :f2) + end + end + return 0, 0 +end |> only === Tuple{Int,Int} +@test Base.return_types((Any,); interp=MustAliasInterpreter()) do a + x = AliasableConstField(a, 0) + if isa(getfield(x, :f1), Int) + return getfield(x, :f1) + end + return 0 +end |> only === Int + +# shouldn't use refinement information when not worthwhile +@test Base.return_types((AliasableField{Int},); interp=MustAliasInterpreter()) do a + if isa(getfield(a, :f), Any) + return getfield(a, :f) # shouldn't be ::Any + end + return 0 +end |> only === Int +# shouldn't assume anything about mutable field +@test Base.return_types((Any,Any); interp=MustAliasInterpreter()) do a, b + x = AliasableConstField{Any,Any}(a, b) + if isa(getfield(x, :f2), Int) + setfield!(x, :f2, z::Any) + return getfield(x, :f2) # shouldn't be ::Int + end + return 0 +end |> only === Any +# when abstract type, we shouldn't assume anything +@test Base.return_types((Any,); interp=MustAliasInterpreter()) do a + if isa(getfield(a, :mayexist), Int) + return getfield(a, :mayexist) + end + return 0 +end |> only === Any + +# works inter-procedurally +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do a + if isa(a.f, Int) + return a.f + end + return 0 +end |> only === Int +@test Base.return_types((Tuple{Any},); interp=MustAliasInterpreter()) do t + if isa(t[1], Int) + return t[1] + end + return 0 +end |> only === Int +@test Base.return_types((Any,); interp=MustAliasInterpreter()) do a + x = AliasableFields(a, 0) # x::PartialStruct(AliasableFields, Any[Any, Const(0)]) + if isa(x.f1, Int) # x::PartialStruct(AliasableFields, Any[Int, Const(0)]) + return x.f1 + end + return 0 +end |> only === Int +@test Base.return_types((Any,Any); interp=MustAliasInterpreter()) do a, b + x = AliasableFields(a, b) # x::AliasableFields + if isa(x.f1, Int) # x::PartialStruct(AliasableFields, Any[Int, Any]) + if isa(x.f2, Int) # x::PartialStruct(AliasableFields, Any[Int, Int]) + return x.f1, x.f2 + end + end + return 0, 0 +end |> only === Tuple{Int,Int} +@test Base.return_types((Any,); interp=MustAliasInterpreter()) do a + x = AliasableConstField(a, 0) + if isa(x.f1, Int) + return x.f1 + end + return 0 +end |> only === Int +getf(a) = a.f +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do a + if isa(getf(a), Int) + return getf(a) + end + return 0 +end |> only === Int + +# merge of same `MustAlias`s +merge_same_aliases(b, a) = b ? _merge_same_aliases1(a) : _merge_same_aliases2(a) # MustAlias(a, Const(:f1), Union{Int,Nothing}) +_merge_same_aliases1(a) = (@assert isa(a.f, Int); a.f) # ::MustAlias(a, Const(:f1), Int) +_merge_same_aliases2(a) = (@assert isa(a.f, Nothing); a.f) # ::MustAlias(a, Const(:f1), Nothing) +@test Base.return_types((Bool,AliasableField,); interp=MustAliasInterpreter()) do b, a + return merge_same_aliases(b, a) # ::Union{Int,Nothing} +end |> only === Union{Nothing,Int} + +# call-site refinement +isaint(a) = isa(a, Int) +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do a + if isaint(a.f) + return a.f + end + return 0 +end |> only === Int +# handle multiple call-site refinment targets +isasome(_) = true +isasome(::Nothing) = false +@test_broken Base.return_types((AliasableField{Union{Int,Nothing}},); interp=MustAliasInterpreter()) do a + if isasome(a.f) + return a.f + end + return 0 +end |> only === Int + +# appropriate lattice order +@test Base.return_types((AliasableField{Any},); interp=MustAliasInterpreter()) do x + v = x.f # ::MustAlias(2, AliasableField{Any}, 1, Any) + if isa(v, Int) # ::Conditional(3, Int, Any) + v = v # ::Int (∵ Int ⊑ MustAlias(2, AliasableField{Any}, 1, Any)) + else + v = 42 + end + return v +end |> only === Int + +# complicated callsite refinement cases +from_interconditional_check11(y::Int, ::AliasableField) = y > 0 +@test Base.return_types((AliasableField{Any},); interp=MustAliasInterpreter()) do x + if from_interconditional_check11(x.f, x) + return x.f + end + return 0 +end |> only === Int +from_interconditional_check12(::AliasableField, y::Int) = y > 0 +@test Base.return_types((AliasableField{Any},); interp=MustAliasInterpreter()) do x + if from_interconditional_check12(x, x.f) + return x.f + end + return 0 +end |> only === Int +from_interconditional_check21(y, ::Union{Int,String}) = isa(y, Int) +@test Base.return_types((AliasableField{Any},); interp=MustAliasInterpreter()) do x + if from_interconditional_check21(x.f, x.f) + return x.f + end + return 0 +end |> only === Int +from_interconditional_check22(::Union{Int,String}, y) = isa(y, Int) +@test Base.return_types((AliasableField{Any},); interp=MustAliasInterpreter()) do x + if from_interconditional_check22(x.f, x.f) + return x.f + end + return 0 +end |> only === Int + +# === constraint +# -------------- + +# simple symmetric tests +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do x + if x.f === 0 + return x.f + end + return 0 +end |> only === Int +@test Base.return_types((AliasableField,); interp=MustAliasInterpreter()) do x + if 0 === x.f + return x.f + end + return 0 +end |> only === Int +# NOTE we prioritize constraints on aliased field over those on slots themselves +@test Base.return_types((AliasableField,Int,); interp=MustAliasInterpreter()) do x, a + if x.f === a + return x.f + end + return 0 +end |> only === Int +@test Base.return_types((AliasableField,Int,); interp=MustAliasInterpreter()) do x, a + if a === x.f + return x.f + end + return 0 +end |> only === Int +@test_broken Base.return_types((AliasableField{Union{Nothing,Int}},); interp=MustAliasInterpreter()) do x + if !isnothing(x.f) + return x.f + end + return 0 +end |> only === Int +@test_broken Base.return_types((AliasableField{Union{Some{Int},Nothing}},); interp=MustAliasInterpreter()) do x + if !isnothing(x.f) + return x.f + end + throw() +end |> only === Some{Int} + +# handle the edge case +@eval intermustalias_edgecase(_) = $(Core.Compiler.InterMustAlias(2, Some{Any}, 1, Int)) +Base.return_types(intermustalias_edgecase, (Any,); interp=MustAliasInterpreter()) # create cache +@test Base.return_types((Any,); interp=MustAliasInterpreter()) do x + intermustalias_edgecase(x) +end |> only === Core.Compiler.InterMustAlias + function f25579(g) h = g[] t = (h === nothing) @@ -2171,6 +2494,14 @@ function conflicting_assignment_conditional() end @test @inferred(conflicting_assignment_conditional()) === 4 +# https://github.com/JuliaLang/julia/issues/45499 +@test Base.return_types((Vector{Int},Int,)) do xs, x + if (i = findfirst(==(x), xs)) !== nothing + return i + end + return 0 +end |> only === Int + # 26826 constant prop through varargs struct Foo26826{A,B} @@ -2230,78 +2561,125 @@ function _g_ifelse_isa_() end @test Base.return_types(_g_ifelse_isa_, ()) == [Int] -@testset "Conditional forwarding" begin - # forward `Conditional` if it conveys a constraint on any other argument - ifelselike(cnd, x, y) = cnd ? x : y +# Conditional forwarding +# ====================== - @test Base.return_types((Any,Int,)) do x, y - ifelselike(isa(x, Int), x, y) - end |> only == Int +# forward `Conditional` if it conveys a constraint on any other argument +ifelselike(cnd, x, y) = cnd ? x : y - # should work nicely with union-split - @test Base.return_types((Union{Int,Nothing},)) do x - ifelselike(isa(x, Int), x, 0) - end |> only == Int - - @test Base.return_types((Any,Int)) do x, y - ifelselike(!isa(x, Int), y, x) - end |> only == Int +@test Base.return_types((Any,Int,)) do x, y + ifelselike(isa(x, Int), x, y) +end |> only == Int - @test Base.return_types((Any,Int)) do x, y - a = ifelselike(x === 0, x, 0) # ::Const(0) - if a == 0 - return y - else - return nothing # dead branch - end - end |> only == Int +# should work nicely with union-split +@test Base.return_types((Union{Int,Nothing},)) do x + ifelselike(isa(x, Int), x, 0) +end |> only == Int - # pick up the first if there are multiple constrained arguments - @test Base.return_types((Any,)) do x - ifelselike(isa(x, Int), x, x) - end |> only == Any +@test Base.return_types((Any,Int)) do x, y + ifelselike(!isa(x, Int), y, x) +end |> only == Int - # just propagate multiple constraints - ifelselike2(cnd1, cnd2, x, y, z) = cnd1 ? x : cnd2 ? y : z - @test Base.return_types((Any,Any)) do x, y - ifelselike2(isa(x, Int), isa(y, Int), x, y, 0) - end |> only == Int +@test Base.return_types((Any,Int)) do x, y + a = ifelselike(x === 0, x, 0) # ::Const(0) + if a == 0 + return y + else + return nothing # dead branch + end +end |> only == Int - # work with `invoke` - @test Base.return_types((Any,Any)) do x, y - Base.@invoke ifelselike(isa(x, Int), x, y::Int) - end |> only == Int +# pick up the first if there are multiple constrained arguments +@test Base.return_types((Any,)) do x + ifelselike(isa(x, Int), x, x) +end |> only == Any - # don't be confused with vararg method - vacond(cnd, va...) = cnd ? va : 0 - @test Base.return_types((Any,)) do x - # at runtime we will see `va::Tuple{Tuple{Int,Int}, Tuple{Int,Int}}` - vacond(isa(x, Tuple{Int,Int}), x, x) - end |> only == Union{Int,Tuple{Any,Any}} +# just propagate multiple constraints +ifelselike2(cnd1, cnd2, x, y, z) = cnd1 ? x : cnd2 ? y : z +@test Base.return_types((Any,Any)) do x, y + ifelselike2(isa(x, Int), isa(y, Int), x, y, 0) +end |> only == Int - # demonstrate extra constraint propagation for Base.ifelse - @test Base.return_types((Any,Int,)) do x, y - ifelse(isa(x, Int), x, y) - end |> only == Int +# work with `invoke` +@test Base.return_types((Any,Any)) do x, y + @invoke ifelselike(isa(x, Int), x::Any, y::Int) +end |> only == Int - # slot as SSA - @test Base.return_types((Any,Vector{Any})) do x, y - z = x - ifelselike(isa(z, Int), z, length(y)) - end |> only === Int +# don't be confused with vararg method +vacond(cnd, va...) = cnd ? va : 0 +@test Base.return_types((Any,)) do x + # at runtime we will see `va::Tuple{Tuple{Int,Int}, Tuple{Int,Int}}` + vacond(isa(x, Tuple{Int,Int}), x, x) +end |> only == Union{Int,Tuple{Any,Any}} + +# https://github.com/JuliaLang/julia/issues/47435 +is_closed_ex(e::InvalidStateException) = true +is_closed_ex(e) = false +function issue47435() + try + catch e + println("caught $e: $(is_closed_ex(e))") + end end +@test only(Base.return_types(issue47435)) === Nothing + +# demonstrate extra constraint propagation for Base.ifelse +@test Base.return_types((Any,Int,)) do x, y + ifelse(isa(x, Int), x, y) +end |> only == Int + +# forward conditional information imposed on SSA that is alised to a slot +@test Base.return_types((Any,Vector{Any})) do x, y + z = x + ifelselike(isa(z, Int), z, length(y)) +end |> only === Int # Equivalence of Const(T.instance) and T for singleton types @test Const(nothing) ⊑ Nothing && Nothing ⊑ Const(nothing) -# Don't pessimize apply_type to anything worse than Type and yield Bottom for invalid Unions -@test Core.Compiler.return_type(Core.apply_type, Tuple{Type{Union}}) == Type{Union{}} -@test Core.Compiler.return_type(Core.apply_type, Tuple{Type{Union},Any}) == Type -@test Core.Compiler.return_type(Core.apply_type, Tuple{Type{Union},Any,Any}) == Type -@test Core.Compiler.return_type(Core.apply_type, Tuple{Type{Union},Int}) == Union{} -@test Core.Compiler.return_type(Core.apply_type, Tuple{Type{Union},Any,Int}) == Union{} -@test Core.Compiler.return_type(Core.apply_type, Tuple{Any}) == Any -@test Core.Compiler.return_type(Core.apply_type, Tuple{Any,Any}) == Any +# https://github.com/JuliaLang/julia/pull/47947 +# correct `apply_type` inference of `NamedTuple{(), <:Any}` +@test (() -> NamedTuple{(), <:Any})() isa UnionAll + +# Don't pessimize apply_type to anything worse than Type (or TypeVar) and yield Bottom for invalid Unions +@test only(Base.return_types(Core.apply_type, Tuple{Type{Union}})) == Type{Union{}} +@test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any})) == Union{Type,TypeVar} +@test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any,Any})) == Type +@test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Int})) == Union{} +@test only(Base.return_types(Core.apply_type, Tuple{Type{Union},Any,Int})) == Union{} +@test only(Base.return_types(Core.apply_type, Tuple{Any})) == Any +@test only(Base.return_types(Core.apply_type, Tuple{Any,Any})) == Any + +# `apply_type_tfunc` accuracy for constrained type construction +# https://github.com/JuliaLang/julia/issues/47089 +import Core: Const +import Core.Compiler: apply_type_tfunc +struct Issue47089{A<:Number,B<:Number} end +let 𝕃 = Core.Compiler.fallback_lattice + A = Type{<:Integer} + @test apply_type_tfunc(𝕃, Const(Issue47089), A, A) <: (Type{Issue47089{A,B}} where {A<:Integer, B<:Integer}) + @test apply_type_tfunc(𝕃, Const(Issue47089), Const(Int), Const(Int), Const(Int)) === Union{} + @test apply_type_tfunc(𝕃, Const(Issue47089), Const(String)) === Union{} + @test apply_type_tfunc(𝕃, Const(Issue47089), Const(AbstractString)) === Union{} + @test apply_type_tfunc(𝕃, Const(Issue47089), Type{Ptr}, Type{Ptr{T}} where T) === Base.rewrap_unionall(Type{Issue47089.body.body}, Issue47089) + # check complexity size limiting + @test apply_type_tfunc(𝕃, Const(Val), Type{Pair{Pair{Pair{Pair{A,B},C},D},E}} where {A,B,C,D,E}) == Type{Val{Pair{A, B}}} where {A, B} + @test apply_type_tfunc(𝕃, Const(Pair), Base.rewrap_unionall(Type{Pair.body.body},Pair), Type{Pair{Pair{Pair{Pair{A,B},C},D},E}} where {A,B,C,D,E}) == Type{Pair{Pair{A, B}, Pair{C, D}}} where {A, B, C, D} + @test apply_type_tfunc(𝕃, Const(Val), Type{Union{Int,Pair{Pair{Pair{Pair{A,B},C},D},E}}} where {A,B,C,D,E}) == Type{Val{_A}} where _A +end +@test only(Base.return_types(keys, (Dict{String},))) == Base.KeySet{String, T} where T<:(Dict{String}) +@test only(Base.return_types((r)->similar(Array{typeof(r[])}, 1), (Base.RefValue{Array{Int}},))) == Vector{<:Array{Int}} +@test only(Base.return_types((r)->similar(Array{typeof(r[])}, 1), (Base.RefValue{Array{<:Real}},))) == Vector{<:Array{<:Real}} +# test complexity limit on apply_type on a function capturing functions returning functions +@test only(Base.return_types(Base.afoldl, (typeof((m, n) -> () -> Returns(nothing)(m, n)), Function, Function, Vararg{Function}))) === Function + +let A = Tuple{A,B,C,D,E,F,G,H} where {A,B,C,D,E,F,G,H} + B = Core.Compiler.rename_unionall(A) + for i in 1:8 + @test A.var != B.var && (i == 1 ? A == B : A != B) + A, B = A.body, B.body + end +end # PR 27351, make sure optimized type intersection for method invalidation handles typevars @@ -2496,7 +2874,7 @@ Base.iterate(i::Iterator27434, ::Val{2}) = i.z, Val(3) Base.iterate(::Iterator27434, ::Any) = nothing @test @inferred(splat27434(Iterator27434(1, 2, 3))) == (1, 2, 3) @test @inferred((1, 2, 3) == (1, 2, 3)) -@test Core.Compiler.return_type(splat27434, Tuple{typeof(Iterators.repeated(1))}) == Union{} +@test only(Base.return_types(splat27434, Tuple{typeof(Iterators.repeated(1))})) == Union{} # issue #32465 let rt = Base.return_types(splat27434, (NamedTuple{(:x,), Tuple{T}} where T,)) @@ -2583,11 +2961,11 @@ end # issue #28356 # unit test to make sure countunionsplit overflows gracefully # we don't care what number is returned as long as it's large -@test Core.Compiler.unionsplitcost(Any[Union{Int32, Int64} for i=1:80]) > 100000 -@test Core.Compiler.unionsplitcost(Any[Union{Int8, Int16, Int32, Int64}]) == 2 -@test Core.Compiler.unionsplitcost(Any[Union{Int8, Int16, Int32, Int64}, Union{Int8, Int16, Int32, Int64}, Int8]) == 8 -@test Core.Compiler.unionsplitcost(Any[Union{Int8, Int16, Int32, Int64}, Union{Int8, Int16, Int32}, Int8]) == 6 -@test Core.Compiler.unionsplitcost(Any[Union{Int8, Int16, Int32}, Union{Int8, Int16, Int32, Int64}, Int8]) == 6 +@test Core.Compiler.unionsplitcost(Core.Compiler.JLTypeLattice(), Any[Union{Int32, Int64} for i=1:80]) > 100000 +@test Core.Compiler.unionsplitcost(Core.Compiler.JLTypeLattice(), Any[Union{Int8, Int16, Int32, Int64}]) == 2 +@test Core.Compiler.unionsplitcost(Core.Compiler.JLTypeLattice(), Any[Union{Int8, Int16, Int32, Int64}, Union{Int8, Int16, Int32, Int64}, Int8]) == 8 +@test Core.Compiler.unionsplitcost(Core.Compiler.JLTypeLattice(), Any[Union{Int8, Int16, Int32, Int64}, Union{Int8, Int16, Int32}, Int8]) == 6 +@test Core.Compiler.unionsplitcost(Core.Compiler.JLTypeLattice(), Any[Union{Int8, Int16, Int32}, Union{Int8, Int16, Int32, Int64}, Int8]) == 6 # make sure compiler doesn't hang in union splitting @@ -2773,7 +3151,7 @@ foo_inlining_apply(args...) = ccall(:jl_, Nothing, (Any,), args[1]) bar_inlining_apply() = Core._apply_iterate(iterate, Core._apply_iterate, (iterate,), (foo_inlining_apply,), ((1,),)) let ci = code_typed(bar_inlining_apply, Tuple{})[1].first @test length(ci.code) == 2 - @test ci.code[1].head == :foreigncall + @test ci.code[1].head === :foreigncall end # Test that inference can infer .instance of types @@ -2844,8 +3222,8 @@ f_with_Type_arg(::Type{T}) where {T} = T (N >= 0) || throw(ArgumentError(string("tuple length should be ≥0, got ", N))) if @generated quote - @Base.nexprs $N i -> t_i = f(i) - @Base.ncall $N tuple t + Base.@nexprs $N i -> t_i = f(i) + Base.@ncall $N tuple t end else Tuple(f(i) for i = 1:N) @@ -3012,9 +3390,12 @@ const DenseIdx = Union{IntRange,Integer} # Non uniformity in expressions with PartialTypeVar @test Core.Compiler.:⊑(Core.Compiler.PartialTypeVar(TypeVar(:N), true, true), TypeVar) let N = TypeVar(:N) - @test Core.Compiler.apply_type_nothrow([Core.Compiler.Const(NTuple), + 𝕃 = Core.Compiler.OptimizerLattice() + argtypes = Any[Core.Compiler.Const(NTuple), Core.Compiler.PartialTypeVar(N, true, true), - Core.Compiler.Const(Any)], Type{Tuple{Vararg{Any,N}}}) + Core.Compiler.Const(Any)] + rt = Type{Tuple{Vararg{Any,N}}} + @test Core.Compiler.apply_type_nothrow(𝕃, argtypes, rt) end # issue #33768 @@ -3249,10 +3630,10 @@ Base.iterate(::Itr41839_3 , i) = i < 16 ? (i, i + 1) : nothing # issue #32699 f32699(a) = (id = a[1],).id -@test Base.return_types(f32699, (Vector{Union{Int,Missing}},)) == Any[Union{Int,Missing}] +@test only(Base.return_types(f32699, (Vector{Union{Int,Missing}},))) == Union{Int,Missing} g32699(a) = Tuple{a} -@test Base.return_types(g32699, (Type{<:Integer},))[1] == Type{<:Tuple{Any}} -@test Base.return_types(g32699, (Type,))[1] == Type{<:Tuple} +@test only(Base.return_types(g32699, (Type{<:Integer},))) <: Type{<:Tuple{Any}} +@test only(Base.return_types(g32699, (Type,))) <: Type{<:Tuple} # Inference precision of union-split calls function f_apply_union_split(fs, x) @@ -3283,10 +3664,11 @@ f_generator_splat(t::Tuple) = tuple((identity(l) for l in t)...) # Issue #36710 - sizeof(::UnionAll) tfunc correctness @test (sizeof(Ptr),) == sizeof.((Ptr,)) == sizeof.((Ptr{Cvoid},)) -@test Core.Compiler.sizeof_tfunc(UnionAll) === Int +@test Core.Compiler.sizeof_tfunc(Core.Compiler.fallback_lattice, UnionAll) === Int @test !Core.Compiler.sizeof_nothrow(UnionAll) -@test Base.return_types(Expr) == Any[Expr] +@test only(Base.return_types(Core._expr)) === Expr +@test only(Base.return_types(Core.svec, (Any,))) === Core.SimpleVector # Use a global constant to rely less on unrelated constant propagation const const_int32_typename = Int32.name @@ -3310,44 +3692,46 @@ for badf in [getfield_const_typename_bad1, getfield_const_typename_bad2] @test_throws TypeError badf() end -@test Core.Compiler.return_type(apply26826, Tuple{typeof(sizeof), Vararg{DataType}}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(sizeof), DataType, Vararg}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(sizeof), DataType, Any, Vararg}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Any, Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Any, Any, Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(===), Any, Any, Any, Vararg}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Vararg{Symbol}}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Vararg{Symbol}}) == Symbol -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Vararg{Integer}}) == Integer -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Vararg}) == Integer -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Vararg}) == Integer -@test Core.Compiler.return_type(apply26826, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Any, Vararg}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(Core._expr), Vararg}) == Expr -@test Core.Compiler.return_type(apply26826, Tuple{typeof(Core._expr), Any, Vararg}) == Expr -@test Core.Compiler.return_type(apply26826, Tuple{typeof(Core._expr), Any, Any, Vararg}) == Expr -@test Core.Compiler.return_type(apply26826, Tuple{typeof(applicable), Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(applicable), Any, Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(applicable), Any, Any, Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(applicable), Any, Any, Any, Vararg}) == Bool -@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Vararg}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Any, Vararg}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Any, Any, Vararg}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Tuple{Int}, Any, Any, Any, Vararg}) == Int -@test Core.Compiler.return_type(apply26826, Tuple{typeof(getfield), Any, Any, Any, Any, Any, Vararg}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Vararg}) == Any -@test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Any, Vararg}) == Any -@test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Any, Any, Vararg}) == Any -@test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Any, Any, Any, Vararg}) == Any -@test Core.Compiler.return_type(apply26826, Tuple{typeof(fieldtype), Any, Any, Any, Any, Vararg}) == Union{} -@test Core.Compiler.return_type(apply26826, Tuple{typeof(Core.apply_type), Vararg}) == Any -@test Core.Compiler.return_type(apply26826, Tuple{typeof(Core.apply_type), Any, Vararg}) == Any -@test Core.Compiler.return_type(apply26826, Tuple{typeof(Core.apply_type), Any, Any, Vararg}) == Any +# tfuncs precision with vararg argument +apply_fargs(f, args...) = f(args...) +@test only(Base.return_types(apply_fargs, Tuple{typeof(sizeof), Vararg{DataType}})) == Int +@test only(Base.return_types(apply_fargs, Tuple{typeof(sizeof), DataType, Vararg})) == Int +@test only(Base.return_types(apply_fargs, Tuple{typeof(sizeof), DataType, Any, Vararg})) == Union{} +@test only(Base.return_types(apply_fargs, Tuple{typeof(===), Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(===), Any, Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(===), Any, Any, Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(===), Any, Any, Any, Vararg})) == Union{} +@test only(Base.return_types(apply_fargs, Tuple{typeof(setfield!), Vararg{Symbol}})) == Union{} +@test only(Base.return_types(apply_fargs, Tuple{typeof(setfield!), Any, Vararg{Symbol}})) == Symbol +@test only(Base.return_types(apply_fargs, Tuple{typeof(setfield!), Any, Symbol, Vararg{Integer}})) == Integer +@test only(Base.return_types(apply_fargs, Tuple{typeof(setfield!), Any, Symbol, Integer, Vararg})) == Integer +@test only(Base.return_types(apply_fargs, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Vararg})) == Integer +@test only(Base.return_types(apply_fargs, Tuple{typeof(setfield!), Any, Symbol, Integer, Any, Any, Vararg})) == Union{} +@test only(Base.return_types(apply_fargs, Tuple{typeof(Core._expr), Vararg})) == Expr +@test only(Base.return_types(apply_fargs, Tuple{typeof(Core._expr), Any, Vararg})) == Expr +@test only(Base.return_types(apply_fargs, Tuple{typeof(Core._expr), Any, Any, Vararg})) == Expr +@test only(Base.return_types(apply_fargs, Tuple{typeof(applicable), Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(applicable), Any, Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(applicable), Any, Any, Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(applicable), Any, Any, Any, Vararg})) == Bool +@test only(Base.return_types(apply_fargs, Tuple{typeof(getfield), Tuple{Int}, Vararg})) == Int +@test only(Base.return_types(apply_fargs, Tuple{typeof(getfield), Tuple{Int}, Any, Vararg})) == Int +@test only(Base.return_types(apply_fargs, Tuple{typeof(getfield), Tuple{Int}, Any, Any, Vararg})) == Int +@test only(Base.return_types(apply_fargs, Tuple{typeof(getfield), Tuple{Int}, Any, Any, Any, Vararg})) == Int +@test only(Base.return_types(apply_fargs, Tuple{typeof(getfield), Any, Any, Any, Any, Any, Vararg})) == Union{} +@test only(Base.return_types(apply_fargs, Tuple{typeof(fieldtype), Vararg})) == Any +@test only(Base.return_types(apply_fargs, Tuple{typeof(fieldtype), Any, Vararg})) == Any +@test only(Base.return_types(apply_fargs, Tuple{typeof(fieldtype), Any, Any, Vararg})) == Any +@test only(Base.return_types(apply_fargs, Tuple{typeof(fieldtype), Any, Any, Any, Vararg})) == Any +@test only(Base.return_types(apply_fargs, Tuple{typeof(fieldtype), Any, Any, Any, Any, Vararg})) == Union{} +@test only(Base.return_types(apply_fargs, Tuple{typeof(Core.apply_type), Vararg})) == Any +@test only(Base.return_types(apply_fargs, Tuple{typeof(Core.apply_type), Any, Vararg})) == Any +@test only(Base.return_types(apply_fargs, Tuple{typeof(Core.apply_type), Any, Any, Vararg})) == Any f_apply_cglobal(args...) = cglobal(args...) -@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Vararg{Type{Int}}}) == Ptr -@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Any, Vararg{Type{Int}}}) == Ptr -@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Any, Type{Int}, Vararg{Type{Int}}}) == Ptr{Int} -@test Core.Compiler.return_type(f_apply_cglobal, Tuple{Any, Type{Int}, Type{Int}, Vararg{Type{Int}}}) == Union{} +@test only(Base.return_types(f_apply_cglobal, Tuple{Vararg{Type{Int}}})) == Ptr +@test only(Base.return_types(f_apply_cglobal, Tuple{Any, Vararg{Type{Int}}})) == Ptr +@test only(Base.return_types(f_apply_cglobal, Tuple{Any, Type{Int}, Vararg{Type{Int}}})) == Ptr{Int} +@test only(Base.return_types(f_apply_cglobal, Tuple{Any, Type{Int}, Type{Int}, Vararg{Type{Int}}})) == Union{} # issue #37532 @test Core.Compiler.intrinsic_nothrow(Core.bitcast, Any[Type{Ptr{Int}}, Int]) @@ -3464,7 +3848,7 @@ end end # issue #37638 -@test isa(Core.Compiler.return_type(() -> (nothing, Any[]...)[2], Tuple{}), Type) +@test only(Base.return_types(() -> (nothing, Any[]...)[2])) isa Type # Issue #37943 f37943(x::Any, i::Int) = getfield((x::Pair{false, Int}), i) @@ -3494,25 +3878,51 @@ g38888() = S38888(Base.inferencebarrier(3), nothing) @test g38888() isa S38888 f_inf_error_bottom(x::Vector) = isempty(x) ? error(x[1]) : x -@test Core.Compiler.return_type(f_inf_error_bottom, Tuple{Vector{Any}}) == Vector{Any} - -# @constprop :aggressive -@noinline g_nonaggressive(y, x) = Val{x}() -@noinline Base.@constprop :aggressive g_aggressive(y, x) = Val{x}() - -f_nonaggressive(x) = g_nonaggressive(x, 1) -f_aggressive(x) = g_aggressive(x, 1) - -# The first test just makes sure that improvements to the compiler don't -# render the annotation effectless. -@test Base.return_types(f_nonaggressive, Tuple{Int})[1] == Val -@test Base.return_types(f_aggressive, Tuple{Int})[1] == Val{1} - -# @constprop :none -@noinline Base.@constprop :none g_noaggressive(flag::Bool) = flag ? 1 : 1.0 -ftrue_noaggressive() = g_noaggressive(true) -@test only(Base.return_types(ftrue_noaggressive, Tuple{})) == Union{Int,Float64} - +@test only(Base.return_types(f_inf_error_bottom, Tuple{Vector{Any}})) == Vector{Any} + +# @constprop annotation +@noinline f_constprop_simple(f, x) = (f(x); Val{x}()) +Base.@constprop :aggressive f_constprop_aggressive(f, x) = (f(x); Val{x}()) +Base.@constprop :aggressive @noinline f_constprop_aggressive_noinline(f, x) = (f(x); Val{x}()) +Base.@constprop :none f_constprop_none(f, x) = (f(x); Val{x}()) +Base.@constprop :none @inline f_constprop_none_inline(f, x) = (f(x); Val{x}()) + +@test !Core.Compiler.is_aggressive_constprop(only(methods(f_constprop_simple))) +@test !Core.Compiler.is_no_constprop(only(methods(f_constprop_simple))) +@test Core.Compiler.is_aggressive_constprop(only(methods(f_constprop_aggressive))) +@test !Core.Compiler.is_no_constprop(only(methods(f_constprop_aggressive))) +@test Core.Compiler.is_aggressive_constprop(only(methods(f_constprop_aggressive_noinline))) +@test !Core.Compiler.is_no_constprop(only(methods(f_constprop_aggressive_noinline))) +@test !Core.Compiler.is_aggressive_constprop(only(methods(f_constprop_none))) +@test Core.Compiler.is_no_constprop(only(methods(f_constprop_none))) +@test !Core.Compiler.is_aggressive_constprop(only(methods(f_constprop_none_inline))) +@test Core.Compiler.is_no_constprop(only(methods(f_constprop_none_inline))) + +# make sure that improvements to the compiler don't render the annotation effectless. +@test Base.return_types((Function,)) do f + f_constprop_simple(f, 1) +end |> only == Val +@test Base.return_types((Function,)) do f + f_constprop_aggressive(f, 1) +end |> only == Val{1} +@test Base.return_types((Function,)) do f + f_constprop_aggressive_noinline(f, 1) +end |> only == Val{1} +@test Base.return_types((Function,)) do f + f_constprop_none(f, 1) +end |> only == Val +@test Base.return_types((Function,)) do f + f_constprop_none_inline(f, 1) +end |> only == Val + +# anonymous function support for `@constprop` +@test Base.return_types((Function,)) do f + map((1,2,3)) do x + Base.@constprop :aggressive + f(x) + return Val{x}() + end +end |> only == Tuple{Val{1},Val{2},Val{3}} function splat_lotta_unions() a = Union{Tuple{Int},Tuple{String,Vararg{Int}},Tuple{Int,Vararg{Int}}}[(2,)][1] @@ -3520,7 +3930,7 @@ function splat_lotta_unions() c = Union{Int8,Int16,Int32,Int64,Int128}[1][1] (a...,b...,c...) end -@test Core.Compiler.return_type(splat_lotta_unions, Tuple{}) >: Tuple{Int,Int,Int} +@test only(Base.return_types(splat_lotta_unions, Tuple{})) >: Tuple{Int,Int,Int} # Bare Core.Argument in IR @eval f_bare_argument(x) = $(Core.Argument(2)) @@ -3556,13 +3966,13 @@ end # argtypes let - tunion = Core.Compiler.switchtupleunion(Any[Union{Int32,Int64}, Core.Const(nothing)]) + tunion = Core.Compiler.switchtupleunion(Core.Compiler.ConstsLattice(), Any[Union{Int32,Int64}, Core.Const(nothing)]) @test length(tunion) == 2 @test Any[Int32, Core.Const(nothing)] in tunion @test Any[Int64, Core.Const(nothing)] in tunion end let - tunion = Core.Compiler.switchtupleunion(Any[Union{Int32,Int64}, Union{Float32,Float64}, Core.Const(nothing)]) + tunion = Core.Compiler.switchtupleunion(Core.Compiler.ConstsLattice(), Any[Union{Int32,Int64}, Union{Float32,Float64}, Core.Const(nothing)]) @test length(tunion) == 4 @test Any[Int32, Float32, Core.Const(nothing)] in tunion @test Any[Int32, Float64, Core.Const(nothing)] in tunion @@ -3643,27 +4053,18 @@ end end end == [Union{Some{Float64}, Some{Int}, Some{UInt8}}] +# make sure inference on a recursive call graph with nested `Type`s terminates # https://github.com/JuliaLang/julia/issues/40336 -@testset "make sure a call with signatures with recursively nested Types terminates" begin - @test @eval Module() begin - f(@nospecialize(t)) = f(Type{t}) - - code_typed() do - f(Int) - end - true - end - - @test @eval Module() begin - f(@nospecialize(t)) = tdepth(t) == 10 ? t : f(Type{t}) - tdepth(@nospecialize(t)) = isempty(t.parameters) ? 1 : 1+tdepth(t.parameters[1]) +f40336(@nospecialize(t)) = f40336(Type{t}) +@test Base.return_types() do + f40336(Int) +end |> only === Union{} - code_typed() do - f(Int) - end - true - end -end +g40336(@nospecialize(t)) = tdepth(t) == 10 ? t : g40336(Type{t}) +tdepth(@nospecialize(t)) = (!isa(t, DataType) || isempty(t.parameters)) ? 1 : 1+tdepth(t.parameters[1]) +@test (Base.return_types() do + g40336(Int) +end |> only; true) # Make sure that const prop doesn't fall into cycles that aren't problematic # in the type domain @@ -3704,70 +4105,60 @@ function f_convert_me_to_ir(b, x) return a end -let - # Test the presence of PhiNodes in lowered IR by taking the above function, +let # Test the presence of PhiNodes in lowered IR by taking the above function, # running it through SSA conversion and then putting it into an opaque # closure. mi = Core.Compiler.specialize_method(first(methods(f_convert_me_to_ir)), Tuple{Bool, Float64}, Core.svec()) ci = Base.uncompressed_ast(mi.def) + ci.slottypes = Any[ Any for i = 1:length(ci.slotflags) ] ci.ssavaluetypes = Any[Any for i = 1:ci.ssavaluetypes] - sv = Core.Compiler.OptimizationState(mi, Core.Compiler.OptimizationParams(), - Core.Compiler.NativeInterpreter()) + sv = Core.Compiler.OptimizationState(mi, Core.Compiler.NativeInterpreter()) ir = Core.Compiler.convert_to_ircode(ci, sv) ir = Core.Compiler.slot2reg(ir, ci, sv) ir = Core.Compiler.compact!(ir) - Core.Compiler.replace_code_newstyle!(ci, ir, 4) - ci.ssavaluetypes = length(ci.code) + Core.Compiler.replace_code_newstyle!(ci, ir) + ci.ssavaluetypes = length(ci.ssavaluetypes) @test any(x->isa(x, Core.PhiNode), ci.code) oc = @eval b->$(Expr(:new_opaque_closure, Tuple{Bool, Float64}, Any, Any, Expr(:opaque_closure_method, nothing, 2, false, LineNumberNode(0, nothing), ci)))(b, 1.0) @test Base.return_types(oc, Tuple{Bool}) == Any[Float64] - oc = @eval ()->$(Expr(:new_opaque_closure, Tuple{Bool, Float64}, Any, Any, Expr(:opaque_closure_method, nothing, 2, false, LineNumberNode(0, nothing), ci)))(true, 1.0) @test Base.return_types(oc, Tuple{}) == Any[Float64] end -@testset "constant prop' on `invoke` calls" begin - m = Module() - - # simple cases - @eval m begin - f(a::Any, sym::Bool) = sym ? Any : :any - f(a::Number, sym::Bool) = sym ? Number : :number - end - @test (@eval m Base.return_types((Any,)) do a - Base.@invoke f(a::Any, true::Bool) - end) == Any[Type{Any}] - @test (@eval m Base.return_types((Any,)) do a - Base.@invoke f(a::Number, true::Bool) - end) == Any[Type{Number}] - @test (@eval m Base.return_types((Any,)) do a - Base.@invoke f(a::Any, false::Bool) - end) == Any[Symbol] - @test (@eval m Base.return_types((Any,)) do a - Base.@invoke f(a::Number, false::Bool) - end) == Any[Symbol] - - # https://github.com/JuliaLang/julia/issues/41024 - @eval m begin - # mixin, which expects common field `x::Int` - abstract type AbstractInterface end - Base.getproperty(x::AbstractInterface, sym::Symbol) = - sym === :x ? getfield(x, sym)::Int : - return getfield(x, sym) # fallback - - # extended mixin, which expects additional field `y::Rational{Int}` - abstract type AbstractInterfaceExtended <: AbstractInterface end - Base.getproperty(x::AbstractInterfaceExtended, sym::Symbol) = - sym === :y ? getfield(x, sym)::Rational{Int} : - return Base.@invoke getproperty(x::AbstractInterface, sym::Symbol) - end - @test (@eval m Base.return_types((AbstractInterfaceExtended,)) do x - x.x - end) == Any[Int] -end +# constant prop' on `invoke` calls +invoke_constprop(a::Any, typ::Bool) = typ ? Any : :any +invoke_constprop(a::Number, typ::Bool) = typ ? Number : :number +@test Base.return_types((Any,)) do a + @invoke invoke_constprop(a::Any, true::Bool) +end |> only === Type{Any} +@test Base.return_types((Any,)) do a + @invoke invoke_constprop(a::Number, true::Bool) +end |> only === Type{Number} +@test Base.return_types((Any,)) do a + @invoke invoke_constprop(a::Any, false::Bool) +end |> only === Symbol +@test Base.return_types((Any,)) do a + @invoke invoke_constprop(a::Number, false::Bool) +end |> only === Symbol + +# https://github.com/JuliaLang/julia/issues/41024 +abstract type Interface41024 end +Base.getproperty(x::Interface41024, sym::Symbol) = + sym === :x ? getfield(x, sym)::Int : + return getfield(x, sym) # fallback + +# extended mixin, which expects additional field `y::Rational{Int}` +abstract type Interface41024Extended <: Interface41024 end +Base.getproperty(x::Interface41024Extended, sym::Symbol) = + sym === :y ? getfield(x, sym)::Rational{Int} : + return @invoke getproperty(x::Interface41024, sym::Symbol) + +@test Base.return_types((Interface41024Extended,)) do x + x.x +end |> only === Int @testset "fieldtype for unions" begin # e.g. issue #40177 f40177(::Type{T}) where {T} = fieldtype(T, 1) @@ -3895,10 +4286,6 @@ end +(UnhandledVarargCond(false), xs...) end |> only === Int - @test (Base.return_types((Vector{Any},)) do xs - Core.kwfunc(xs...) - end; true) - @test Base.return_types((Vector{Vector{Int}},)) do xs Tuple(xs...) end |> only === Tuple{Vararg{Int}} @@ -3930,6 +4317,22 @@ end |> only == Tuple{Int,Int} s2.value.value end |> only == Int +# form PartialStruct for mutables with `const` field +import Core.Compiler: Const, ⊑ +mutable struct PartialMutable{S,T} + const s::S + t::T +end +@test Base.return_types((Int,)) do s + o = PartialMutable{Any,Any}(s, s) # form `PartialStruct(PartialMutable{Any,Any}, Any[Int,Any])` here + o.s +end |> only === Int +@test Const(nothing) ⊑ Base.return_types((Int,)) do s + o = PartialMutable{Any,Any}(s, s) # don't form `PartialStruct(PartialMutable{Any,Any}, Any[Int,Int])` here + o.t = nothing + o.t +end |> only + # issue #42986 @testset "narrow down `Union` using `isdefined` checks" begin # basic functionality @@ -3999,32 +4402,505 @@ end @test ⊑(a, c) @test ⊑(b, c) - @test @eval Module() begin - const ginit = Base.ImmutableDict{Any,Any}() - Base.return_types() do - g = ginit + init = Base.ImmutableDict{Number,Number}() + a = Const(init) + b = Core.Compiler.PartialStruct(typeof(init), Any[Const(init), Any, ComplexF64]) + c = Core.Compiler.tmerge(a, b) + @test ⊑(a, c) && ⊑(b, c) + @test c === typeof(init) + + a = Core.Compiler.PartialStruct(typeof(init), Any[Const(init), ComplexF64, ComplexF64]) + c = Core.Compiler.tmerge(a, b) + @test ⊑(a, c) && ⊑(b, c) + @test c.fields[2] === Any # or Number + @test c.fields[3] === ComplexF64 + + b = Core.Compiler.PartialStruct(typeof(init), Any[Const(init), ComplexF32, Union{ComplexF32,ComplexF64}]) + c = Core.Compiler.tmerge(a, b) + @test ⊑(a, c) + @test ⊑(b, c) + @test c.fields[2] === Complex + @test c.fields[3] === Complex + + global const ginit43784 = Base.ImmutableDict{Any,Any}() + @test Base.return_types() do + g = ginit43784 while true g = Base.ImmutableDict(g, 1=>2) end end |> only === Union{} +end + +# Test that a function-wise `@max_methods` works as expected +Base.Experimental.@max_methods 1 function f_max_methods end +f_max_methods(x::Int) = 1 +f_max_methods(x::Float64) = 2 +g_max_methods(x) = f_max_methods(x) +@test only(Base.return_types(g_max_methods, Tuple{Int})) === Int +@test only(Base.return_types(g_max_methods, Tuple{Any})) === Any + +# Test that a module-wise `@max_methods` works as expected +module Test43370 +using Test +Base.Experimental.@max_methods 1 +f_max_methods(x::Int) = 1 +f_max_methods(x::Float64) = 2 +g_max_methods(x) = f_max_methods(x) +@test only(Base.return_types(g_max_methods, Tuple{Int})) === Int +@test only(Base.return_types(g_max_methods, Tuple{Any})) === Any +end + +# Make sure return_type_tfunc doesn't accidentally cause bad inference if used +# at top level. +@test let + Base.Experimental.@force_compile + Core.Compiler.return_type(+, NTuple{2, Rational}) +end == Rational + +# vararg-tuple comparison within `Compiler.PartialStruct` +# https://github.com/JuliaLang/julia/issues/44965 +let 𝕃ᵢ = Core.Compiler.fallback_lattice + t = Core.Compiler.tuple_tfunc(𝕃ᵢ, Any[Const(42), Vararg{Any}]) + @test Core.Compiler.issimplertype(𝕃ᵢ, t, t) + + t = Core.Compiler.tuple_tfunc(𝕃ᵢ, Any[Const(42), Vararg{Union{}}]) + @test t === Const((42,)) + t = Core.Compiler.tuple_tfunc(𝕃ᵢ, Any[Const(42), Int, Vararg{Union{}}]) + @test t.typ === Tuple{Int, Int} + @test t.fields == Any[Const(42), Int] +end + +foo_empty_vararg(i...) = i[2] +bar_empty_vararg(i) = foo_empty_vararg(10, 20, 30, i...) +@test bar_empty_vararg(Union{}[]) === 20 + + +# check the inference convergence with an empty vartable: +# the inference state for the toplevel chunk below will have an empty vartable, +# and so we may fail to terminate (or optimize) it if we don't update vartables correctly +let # NOTE make sure this toplevel chunk doesn't contain any local binding + Base.Experimental.@force_compile + global xcond::Bool = false + while xcond end +end +@test !xcond + +struct Issue45780 + oc::Core.OpaqueClosure{Tuple{}} +end +f45780() = Val{Issue45780(Base.Experimental.@opaque ()->1).oc()}() +@test (@inferred f45780()) == Val{1}() + +# issue #45600 +@test only(code_typed() do + while true + x = try finally end + end +end)[2] == Union{} +@test only(code_typed() do + while true + @time 1 + end +end)[2] == Union{} + +# compilerbarrier builtin +import Core: compilerbarrier +# runtime semantics +for setting = (:type, :const, :conditional) + @test compilerbarrier(setting, 42) == 42 + @test compilerbarrier(setting, :sym) == :sym +end +@test_throws ErrorException compilerbarrier(:nonexisting, 42) +@test_throws TypeError compilerbarrier("badtype", 42) +@test_throws ArgumentError compilerbarrier(:nonexisting, 42, nothing) +# barrier on abstract interpretation +@test Base.return_types((Int,)) do a + x = compilerbarrier(:type, a) # `x` won't be inferred as `x::Int` + return x +end |> only === Any +@test Base.return_types() do + x = compilerbarrier(:const, 42) + if x == 42 # no constant information here, so inference also accounts for the else branch (leading to less accurate return type inference) + return x # but `x` is still inferred as `x::Int` at least here + else + return nothing + end +end |> only === Union{Int,Nothing} +@test Base.return_types((Union{Int,Nothing},)) do a + if compilerbarrier(:conditional, isa(a, Int)) + # the conditional information `a::Int` isn't available here (leading to less accurate return type inference) + return a + else + return nothing + end +end |> only === Union{Int,Nothing} +@test Base.return_types((Symbol,Int)) do setting, val + compilerbarrier(setting, val) +end |> only === Any # XXX we may want to have "compile-time" error for this instead +for setting = (:type, :const, :conditional) + # a successful barrier on abstract interpretation should be eliminated at the optimization + @test @eval fully_eliminated((Int,)) do a + compilerbarrier($(QuoteNode(setting)), 42) + end +end + +# https://github.com/JuliaLang/julia/issues/46426 +@noinline typebarrier() = Base.inferencebarrier(0.0) +@noinline constbarrier() = Base.compilerbarrier(:const, 0.0) +let src = code_typed1() do + typebarrier() + end + @test any(isinvoke(:typebarrier), src.code) + @test Base.return_types() do + typebarrier() + end |> only === Any +end +let src = code_typed1() do + constbarrier() + end + @test any(isinvoke(:constbarrier), src.code) + @test Base.return_types() do + constbarrier() + end |> only === Float64 +end + +# Test that Const ⊑ PartialStruct respects vararg +@test Const((1,2)) ⊑ PartialStruct(Tuple{Vararg{Int}}, [Const(1), Vararg{Int}]) + +# Test that semi-concrete interpretation doesn't break on functions with while loops in them. +Base.@assume_effects :consistent :effect_free :terminates_globally function pure_annotated_loop(x::Int, y::Int) + for i = 1:2 + x += y end + return y end +call_pure_annotated_loop(x) = Val{pure_annotated_loop(x, 1)}() +@test only(Base.return_types(call_pure_annotated_loop, Tuple{Int})) === Val{1} -# Test that purity modeling doesn't accidentally introduce new world age issues -f_redefine_me(x) = x+1 -f_call_redefine() = f_redefine_me(0) -f_mk_opaque() = @Base.Experimental.opaque ()->Base.inferencebarrier(f_call_redefine)() -const op_capture_world = f_mk_opaque() -f_redefine_me(x) = x+2 -@test op_capture_world() == 1 -@test f_mk_opaque()() == 2 +function isa_kindtype(T::Type{<:AbstractVector}) + if isa(T, DataType) + # `T` here should be inferred as `DataType` rather than `Type{<:AbstractVector}` + return T.name.name # should be inferred as ::Symbol + end + return nothing +end +@test only(Base.return_types(isa_kindtype)) === Union{Nothing,Symbol} -# Test that purity doesn't try to accidentally run unreachable code due to -# boundscheck elimination -function f_boundscheck_elim(n) - # Inbounds here assumes that this is only ever called with n==0, but of - # course the compiler has no way of knowing that, so it must not attempt - # to run the @inbounds `getfield(sin, 1)`` that ntuple generates. - ntuple(x->(@inbounds getfield(sin, x)), n) +invoke_concretized1(a::Int) = a > 0 ? :int : nothing +invoke_concretized1(a::Integer) = a > 0 ? "integer" : nothing +# check if `invoke(invoke_concretized1, Tuple{Integer}, ::Int)` is foldable +@test Base.infer_effects((Int,)) do a + @invoke invoke_concretized1(a::Integer) +end |> Core.Compiler.is_foldable +@test Base.return_types() do + @invoke invoke_concretized1(42::Integer) +end |> only === String + +invoke_concretized2(a::Int) = a > 0 ? :int : nothing +invoke_concretized2(a::Integer) = a > 0 ? :integer : nothing +# check if `invoke(invoke_concretized2, Tuple{Integer}, ::Int)` is foldable +@test Base.infer_effects((Int,)) do a + @invoke invoke_concretized2(a::Integer) +end |> Core.Compiler.is_foldable +@test let + Base.Experimental.@force_compile + @invoke invoke_concretized2(42::Integer) +end === :integer + +# Test that abstract_apply doesn't fail to fully infer if the result is unused +struct FiniteIteration + n::Int +end +Base.iterate(f::FiniteIteration, i::Int = 0) = i < f.n ? (i, i+1) : nothing +function unused_apply_iterate() + tuple(FiniteIteration(4)...) + return nothing +end +@test fully_eliminated(unused_apply_iterate, ()) + +@testset "#45956: non-linearized cglobal needs special treatment for stmt effects" begin + function foo() + cglobal((a, )) + ccall(0, Cvoid, (Nothing,), b) + end + @test only(code_typed() do + cglobal((a, )) + ccall(0, Cvoid, (Nothing,), b) + end)[2] === Nothing +end + +# singleton_type on slot wrappers +@test Base.return_types((Int,)) do x + c = isa(x, Int) # ::Conditional + c(false) # ::Union{} +end |> only === Union{} +@test Base.return_types((Tuple{typeof(typeof),Float64},)) do args + f = args[1] # ::MustAlias + v = args[2] # ::MustAlias + f(v) # ::Type{Float64} +end |> only === Type{Float64} + +# Issue #46839: `abstract_invoke` should handle incorrect call type +@test only(Base.return_types(()->invoke(BitSet, Any, x), ())) === Union{} +@test only(Base.return_types(()->invoke(BitSet, Union{Tuple{Int32},Tuple{Int64}}, 1), ())) === Union{} + +# Issue #47688: Abstract iteration should take into account `iterate` effects +global it_count47688 = 0 +struct CountsIterate47688{N}; end +function Base.iterate(::CountsIterate47688{N}, n=0) where N + global it_count47688 += 1 + n <= N ? (n, n+1) : nothing +end +foo47688() = tuple(CountsIterate47688{5}()...) +bar47688() = foo47688() +@test only(Base.return_types(bar47688)) == NTuple{6, Int} +@test it_count47688 == 0 +@test isa(bar47688(), NTuple{6, Int}) +@test it_count47688 == 7 +@test isa(foo47688(), NTuple{6, Int}) +@test it_count47688 == 14 + +# refine instantiation of partially-known NamedTuple that is known to be empty +function empty_nt_values(Tpl) + T = NamedTuple{(),Tpl} + nt = T(()) + values(nt) +end +function empty_nt_keys(Tpl) + T = NamedTuple{(),Tpl} + nt = T(()) + keys(nt) +end +@test Base.return_types(empty_nt_values, (Any,)) |> only === Tuple{} +@test Base.return_types(empty_nt_keys, (Any,)) |> only === Tuple{} +g() = empty_nt_values(Base.inferencebarrier(Tuple{})) +@test g() == () # Make sure to actually run this to test this in the inference world age + +# 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. +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 that inference doesn't give up if it can potentially refine effects, +# even if the return type is Any. +f_no_bail_effects_any(x::Any) = x +f_no_bail_effects_any(x::NamedTuple{(:x,), Tuple{Any}}) = getfield(x, 1) +g_no_bail_effects_any(x::Any) = f_no_bail_effects_any(x) +@test Core.Compiler.is_foldable_nothrow(Base.infer_effects(g_no_bail_effects_any, Tuple{Any})) + +# issue #48374 +@test (() -> Union{<:Nothing})() == Nothing + +# :static_parameter accuracy +unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = @isdefined(T) ? T::Type : nothing +unknown_sparam_nothrow1(x::Ref{T}) where T = @isdefined(T) ? T::Type : nothing +unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = @isdefined(T) ? T::Type : nothing +@test only(Base.return_types(unknown_sparam_throw, (Type{Int},))) == Type{Int} +@test only(Base.return_types(unknown_sparam_throw, (Type{<:Integer},))) == Type{<:Integer} +@test only(Base.return_types(unknown_sparam_throw, (Type,))) == Union{Nothing, Type} +@test_broken only(Base.return_types(unknown_sparam_throw, (Nothing,))) === Nothing +@test_broken only(Base.return_types(unknown_sparam_throw, (Union{Type{Int},Nothing},))) === Union{Nothing,Type{Int}} +@test only(Base.return_types(unknown_sparam_throw, (Any,))) === Union{Nothing,Type} +@test only(Base.return_types(unknown_sparam_nothrow1, (Ref,))) === Type +@test only(Base.return_types(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) === Type + +struct Issue49027{Ty<:Number} + x::Ty +end +function issue49027(::Type{<:Issue49027{Ty}}) where Ty + if @isdefined Ty # should be false when `Ty` is given as a free type var. + return Ty::DataType + end + return nothing +end +@test only(Base.return_types(issue49027, (Type{Issue49027{TypeVar(:Ty)}},))) >: Nothing +@test isnothing(issue49027(Issue49027{TypeVar(:Ty)})) +function issue49027_integer(::Type{<:Issue49027{Ty}}) where Ty<:Integer + if @isdefined Ty # should be false when `Ty` is given as a free type var. + return Ty::DataType + end + nothing +end +@test only(Base.return_types(issue49027_integer, (Type{Issue49027{TypeVar(:Ty,Int)}},))) >: Nothing +@test isnothing(issue49027_integer(Issue49027{TypeVar(:Ty,Int)})) + +function fapplicable end +gapplicable() = Val(applicable(fapplicable)) +gapplicable(x) = Val(applicable(fapplicable; x)) +@test only(Base.return_types(gapplicable, ())) === Val{false} +@test only(Base.return_types(gapplicable, (Int,))) === Val{false} +fapplicable() = 1 +@test only(Base.return_types(gapplicable, ())) === Val{true} +@test only(Base.return_types(gapplicable, (Int,))) === Val{false} +Base.delete_method(which(fapplicable, ())) +@test only(Base.return_types(gapplicable, ())) === Val{false} +@test only(Base.return_types(gapplicable, (Int,))) === Val{false} +fapplicable(; x) = x +@test only(Base.return_types(gapplicable, ())) === Val{true} +@test only(Base.return_types(gapplicable, (Int,))) === Val{true} +@test only(Base.return_types(()) do; applicable(); end) === Union{} +@test only(Base.return_types((Any,)) do x; Val(applicable(x...)); end) == Val +@test only(Base.return_types((Tuple{Vararg{Int}},)) do x; Val(applicable(+, 1, 2, x...)); end) == Val # could be improved to Val{true} +@test only(Base.return_types((Tuple{Vararg{Int}},)) do x; Val(applicable(+, 1, 2, 3, x...)); end) === Val{true} +@test only(Base.return_types((Int,)) do x; Val(applicable(+, 1, x)); end) === Val{true} +@test only(Base.return_types((Union{Int32,Int64},)) do x; Val(applicable(+, 1, x)); end) === Val{true} +@test only(Base.return_types((String,)) do x; Val(applicable(+, 1, x)); end) === Val{false} +fapplicable(::Int, ::Integer) = 2 +fapplicable(::Integer, ::Int32) = 3 +@test only(Base.return_types((Int32,)) do x; Val(applicable(fapplicable, 1, x)); end) === Val{false} +@test only(Base.return_types((Int64,)) do x; Val(applicable(fapplicable, 1, x)); end) === Val{true} +@test only(Base.return_types((Tuple{Vararg{Int}},)) do x; Val(applicable(tuple, x...)); end) === Val{true} +@test only(Base.return_types((Tuple{Vararg{Int}},)) do x; Val(applicable(sin, 1, x...)); end) == Val +@test only(Base.return_types((Tuple{Vararg{Int}},)) do x; Val(applicable(sin, 1, 2, x...)); end) === Val{false} + +function fhasmethod end +ghasmethod() = Val(hasmethod(fhasmethod, Tuple{})) +@test only(Base.return_types(ghasmethod, ())) === Val{false} +fhasmethod() = 1 +@test only(Base.return_types(ghasmethod, ())) === Val{true} +Base.delete_method(which(fhasmethod, ())) +@test only(Base.return_types(ghasmethod, ())) === Val{false} +@test only(Base.return_types(()) do; Core._hasmethod(); end) === Any +@test only(Base.return_types(()) do; Core._hasmethod(+, Tuple, 1); end) === Any +@test only(Base.return_types(()) do; Core._hasmethod(+, 1); end) === Bool +@test only(Base.return_types(()) do; Core._hasmethod(+, Tuple{1}); end) === Bool +@test only(Base.return_types((Any,)) do x; Val(hasmethod(x...)); end) == Val +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Int})); end) === Val{true} +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Int, Vararg{Int}})); end) === Val{false} +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Int, Int, Vararg{Int}})); end) === Val{true} +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Int})); end) === Val{true} +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Union{Int32,Int64}})); end) === Val{true} +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Union{Int,String}})); end) === Val{false} +@test only(Base.return_types(()) do; Val(hasmethod(+, Tuple{Int, Any})); end) === Val{false} +@test only(Base.return_types() do; Val(hasmethod(+, Tuple{Int, String})); end) === Val{false} +fhasmethod(::Int, ::Integer) = 2 +fhasmethod(::Integer, ::Int32) = 3 +@test only(Base.return_types(()) do; Val(hasmethod(fhasmethod, Tuple{Int, Int32})); end) === Val{false} +@test only(Base.return_types(()) do; Val(hasmethod(fhasmethod, Tuple{Int, Int64})); end) === Val{true} +@test only(Base.return_types(()) do; Val(hasmethod(tuple, Tuple{Vararg{Int}})); end) === Val{true} +@test only(Base.return_types(()) do; Val(hasmethod(sin, Tuple{Int, Vararg{Int}})); end) == Val{false} +@test only(Base.return_types(()) do; Val(hasmethod(sin, Tuple{Int, Int, Vararg{Int}})); end) === Val{false} + +# interprocedural call inference from irinterp +@noinline Base.@assume_effects :total issue48679_unknown_any(x) = Base.inferencebarrier(x) + +@noinline _issue48679(y::Union{Nothing,T}) where {T} = T::Type +Base.@constprop :aggressive function issue48679(x, b) + if b + x = issue48679_unknown_any(x) + end + return _issue48679(x) +end +@test Base.return_types((Float64,)) do x + issue48679(x, false) +end |> only == Type{Float64} + +Base.@constprop :aggressive @noinline _issue48679_const(b, y::Union{Nothing,T}) where {T} = b ? nothing : T::Type +Base.@constprop :aggressive function issue48679_const(x, b) + if b + x = issue48679_unknown_any(x) + end + return _issue48679_const(b, x) +end +@test Base.return_types((Float64,)) do x + issue48679_const(x, false) +end |> only == Type{Float64} + +# `invoke` call in irinterp +@noinline _irinterp_invoke(x::Any) = :any +@noinline _irinterp_invoke(x::T) where T = T +Base.@constprop :aggressive Base.@assume_effects :foldable function irinterp_invoke(x::T, b) where T + return @invoke _irinterp_invoke(x::(b ? T : Any)) +end +@test Base.return_types((Int,)) do x + irinterp_invoke(x, true) +end |> only == Type{Int} + +# recursion detection for semi-concrete interpretation +# avoid direct infinite loop via `concrete_eval_invoke` +Base.@assume_effects :foldable function recur_irinterp1(x, y) + if rand(Bool) + return x, y + end + return recur_irinterp1(x+1, y) +end +@test Base.return_types((Symbol,)) do y + recur_irinterp1(0, y) +end |> only === Tuple{Int,Symbol} +@test last(recur_irinterp1(0, :y)) === :y +# avoid indirect infinite loop via `concrete_eval_invoke` +Base.@assume_effects :foldable function recur_irinterp2(x, y) + if rand(Bool) + return x, y + end + return _recur_irinterp2(x+1, y) +end +Base.@assume_effects :foldable _recur_irinterp2(x, y) = @noinline recur_irinterp2(x, y) +@test Base.return_types((Symbol,)) do y + recur_irinterp2(0, y) +end |> only === Tuple{Int,Symbol} +@test last(recur_irinterp2(0, :y)) === :y + +# test Conditional Union splitting of info derived from fieldtype (e.g. in abstract setproperty! handling) +@test only(Base.return_types((Int, Pair{Int,Nothing}, Symbol)) do a, x, s + T = fieldtype(typeof(x), s) + if a isa T + throw(a) + else + return T + end +end) == Type{Nothing} + +# Test that Core.Compiler.return_type inference works for the 1-arg version +@test Base.return_types() do + Core.Compiler.return_type(Tuple{typeof(+), Int, Int}) +end |> only == Type{Int} + +# Test that NamedTuple abstract iteration works for PartialStruct/Const +function nt_splat_const() + nt = (; x=1, y=2) + Val{tuple(nt...)[2]}() +end +@test @inferred(nt_splat_const()) == Val{2}() + +function nt_splat_partial(x::Int) + nt = (; x, y=2) + Val{tuple(nt...)[2]}() +end +@test @inferred(nt_splat_partial(42)) == Val{2}() + +# Test that irinterp refines based on discovered errors +Base.@assume_effects :foldable Base.@constprop :aggressive function kill_error_edge(b1, b2, xs, x) + y = b1 ? "julia" : xs[] + if b2 + a = length(y) + else + a = sin(y) + end + a + x +end + +Base.@assume_effects :foldable Base.@constprop :aggressive function kill_error_edge(b1, b2, xs, ys, x) + y = b1 ? xs[] : ys[] + if b2 + a = length(y) + else + a = sin(y) + end + a + x +end + +let src = code_typed1((Bool,Base.RefValue{Any},Int,)) do b2, xs, x + kill_error_edge(true, b2, xs, x) + end + @test count(@nospecialize(x)->isa(x, Core.PhiNode), src.code) == 0 +end + +let src = code_typed1((Bool,Base.RefValue{String}, Base.RefValue{Any},Int,)) do b2, xs, ys, x + kill_error_edge(true, b2, xs, ys, x) + end + @test count(@nospecialize(x)->isa(x, Core.PhiNode), src.code) == 0 end -@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 9347acc83f13f..7920212537608 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -4,7 +4,8 @@ using Test using Base.Meta using Core: ReturnNode -include(normpath(@__DIR__, "irutils.jl")) +include("irutils.jl") +include("newinterp.jl") """ Helper to walk the AST and call a function on every node. @@ -28,7 +29,7 @@ function test_inlined_symbols(func, argtypes) ast = Expr(:block) ast.args = src.code walk(ast) do e - if isa(e, Core.Slot) + if isa(e, Core.SlotNumber) @test 1 <= e.id <= nl end if isa(e, Core.NewvarNode) @@ -120,16 +121,14 @@ f29083(;μ,σ) = μ + σ*randn() g29083() = f29083(μ=2.0,σ=0.1) let c = code_typed(g29083, ())[1][1].code # make sure no call to kwfunc remains - @test !any(e->(isa(e,Expr) && ((e.head === :invoke && e.args[1].def.name === :kwfunc) || - (e.head === :foreigncall && e.args[1] === QuoteNode(:jl_get_keyword_sorter)))), - c) + @test !any(e->(isa(e,Expr) && (e.head === :invoke && e.args[1].def.name === :kwfunc)), c) end @testset "issue #19122: [no]inline of short func. def. with return type annotation" begin exf19122 = @macroexpand(@inline f19122()::Bool = true) exg19122 = @macroexpand(@noinline g19122()::Bool = true) - @test exf19122.args[2].args[1].args[1] == :inline - @test exg19122.args[2].args[1].args[1] == :noinline + @test exf19122.args[2].args[1].args[1] === :inline + @test exg19122.args[2].args[1].args[1] === :noinline @inline f19122()::Bool = true @noinline g19122()::Bool = true @@ -213,7 +212,7 @@ function f_div(x) div(x, 1) return x end -@test fully_eliminated(f_div, (Int,)) == 1 +@test fully_eliminated(f_div, (Int,); retval=Core.Argument(2)) # ...unless we div by an unknown amount function f_div(x, y) div(x, y) @@ -275,11 +274,38 @@ f34900(x, y::Int) = y f34900(x::Int, y::Int) = invoke(f34900, Tuple{Int, Any}, x, y) @test fully_eliminated(f34900, Tuple{Int, Int}; retval=Core.Argument(2)) -@testset "check jl_ir_flag_inlineable for inline macro" begin - @test ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods(@inline x -> x)).source) - @test !ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods( x -> x)).source) - @test ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods(@inline function f(x) x end)).source) - @test !ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods(function f(x) x end)).source) +using Core.Compiler: is_declared_inline, is_declared_noinline + +@testset "is_declared_[no]inline" begin + @test is_declared_inline(only(methods(@inline x -> x))) + @test is_declared_inline(only(methods(x -> (@inline; x)))) + @test is_declared_inline(only(methods(@inline function f(x) x end))) + @test is_declared_inline(only(methods(function f(x) @inline; x end))) + @test is_declared_inline(only(methods() do x @inline; x end)) + @test is_declared_noinline(only(methods(@noinline x -> x))) + @test is_declared_noinline(only(methods(x -> (@noinline; x)))) + @test is_declared_noinline(only(methods(@noinline function f(x) x end))) + @test is_declared_noinline(only(methods(function f(x) @noinline; x end))) + @test is_declared_noinline(only(methods() do x @noinline; x end)) + @test !is_declared_inline(only(methods(x -> x))) + @test !is_declared_noinline(only(methods(x -> x))) + @test !is_declared_inline(only(methods(function f(x) x end))) + @test !is_declared_noinline(only(methods(function f(x) x end))) + @test !is_declared_inline(only(methods() do x x end)) + @test !is_declared_noinline(only(methods() do x x end)) +end + +using Core.Compiler: is_inlineable, set_inlineable! + +@testset "basic set_inlineable! functionality" begin + ci = code_typed1() do + x -> x + end + set_inlineable!(ci, true) + @test is_inlineable(ci) + set_inlineable!(ci, false) + @test !is_inlineable(ci) + @test_throws MethodError set_inlineable!(ci, 5) end const _a_global_array = [1] @@ -292,13 +318,11 @@ end f_29115(x) = (x...,) @test @allocated(f_29115(1)) == 0 @test @allocated(f_29115(1=>2)) == 0 -let ci = code_typed(f_29115, Tuple{Int64})[1].first - @test length(ci.code) == 2 && isexpr(ci.code[1], :call) && - ci.code[1].args[1] === GlobalRef(Core, :tuple) +let src = code_typed(f_29115, Tuple{Int64}) |> only |> first + @test iscall((src, tuple), src.code[end-1]) end -let ci = code_typed(f_29115, Tuple{Pair{Int64, Int64}})[1].first - @test length(ci.code) == 4 && isexpr(ci.code[1], :call) && - ci.code[end-1].args[1] === GlobalRef(Core, :tuple) +let src = code_typed(f_29115, Tuple{Pair{Int64, Int64}}) |> only |> first + @test iscall((src, tuple), src.code[end-1]) end # Issue #37182 & #37555 - Inlining of pending nodes @@ -313,7 +337,7 @@ struct NonIsBitsDims dims::NTuple{N, Int} where N end NonIsBitsDims() = NonIsBitsDims(()) -@test fully_eliminated(NonIsBitsDims, (); retval=QuoteNode(NonIsBitsDims())) +@test fully_eliminated(NonIsBitsDims, (); retval=NonIsBitsDims()) struct NonIsBitsDimsUndef dims::NTuple{N, Int} where N @@ -337,18 +361,6 @@ struct RealConstrained{T <: Real}; end @test !fully_eliminated(x->(RealConstrained{x}; nothing), Tuple{Int}) @test !fully_eliminated(x->(RealConstrained{x}; nothing), Tuple{Type{Vector{T}} where T}) -# Check that pure functions with non-inlineable results still get deleted -struct Big - x::NTuple{1024, Int} -end -@Base.pure Big() = Big(ntuple(identity, 1024)) -function pure_elim_full() - Big() - nothing -end - -@test fully_eliminated(pure_elim_full, Tuple{}) - # Union splitting of convert f_convert_missing(x) = convert(Int64, x) let ci = code_typed(f_convert_missing, Tuple{Union{Int64, Missing}})[1][1], @@ -367,26 +379,6 @@ using Base.Experimental: @opaque f_oc_getfield(x) = (@opaque ()->x)() @test fully_eliminated(f_oc_getfield, Tuple{Int}) -import Core.Compiler: argextype, singleton_type -const EMPTY_SPTYPES = Any[] - -code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::Core.CodeInfo -get_code(args...; kwargs...) = code_typed1(args...; kwargs...).code - -# check if `x` is a dynamic call of a given function -iscall(y) = @nospecialize(x) -> iscall(y, x) -function iscall((src, f)::Tuple{Core.CodeInfo,Base.Callable}, @nospecialize(x)) - return iscall(x) do @nospecialize x - singleton_type(argextype(x, src, EMPTY_SPTYPES)) === f - end -end -iscall(pred::Base.Callable, @nospecialize(x)) = Meta.isexpr(x, :call) && pred(x.args[1]) - -# check if `x` is a statically-resolved call of a function whose name is `sym` -isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) -isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) -isinvoke(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :invoke) && pred(x.args[1]::Core.MethodInstance) - @testset "@inline/@noinline annotation before definition" begin M = Module() @eval M begin @@ -604,7 +596,7 @@ g41299(f::Tf, args::Vararg{Any,N}) where {Tf,N} = f(args...) @test_throws TypeError g41299(>:, 1, 2) # https://github.com/JuliaLang/julia/issues/42078 -# idempotency of callsite inling +# idempotency of callsite inlining function getcache(mi::Core.MethodInstance) cache = Core.Compiler.code_cache(Core.Compiler.NativeInterpreter()) codeinf = Core.Compiler.get(cache, mi, nothing) @@ -625,10 +617,9 @@ let f42078(a) end let # make sure to discard the inferred source - specs = collect(only(methods(f42078)).specializations) - mi = specs[findfirst(!isnothing, specs)]::Core.MethodInstance + mi = only(methods(f42078)).specializations::Core.MethodInstance codeinf = getcache(mi)::Core.CodeInstance - codeinf.inferred = nothing + @atomic codeinf.inferred = nothing end let # inference should re-infer `f42078(::Int)` and we should get the same code @@ -641,7 +632,7 @@ let end begin - # more idempotency of callsite inling + # more idempotency of callsite inlining # ----------------------------------- # this test case requires forced constant propagation for callsite inlined function call, # particularly, in the following example, the inlinear will look up `+ₚ(::Point, ::Const(Point(2.25, 4.75)))` @@ -693,9 +684,9 @@ begin end # https://github.com/JuliaLang/julia/issues/42246 -@test mktempdir() do dir +mktempdir() do dir cd(dir) do - code = quote + code = """ issue42246() = @noinline IOBuffer("a") let ci, rt = only(code_typed(issue42246)) @@ -708,9 +699,9 @@ end exit(1) end end - end |> string + """ cmd = `$(Base.julia_cmd()) --code-coverage=tmp.info -e $code` - success(pipeline(Cmd(cmd); stdout=stdout, stderr=stderr)) + @test success(pipeline(cmd; stdout, stderr)) end end @@ -810,6 +801,103 @@ let @test invoke(Any[10]) === false end +# test union-split, non-dispatchtuple callsite inlining + +@constprop :none @noinline abstract_unionsplit(@nospecialize x::Any) = Base.inferencebarrier(:Any) +@constprop :none @noinline abstract_unionsplit(@nospecialize x::Number) = Base.inferencebarrier(:Number) +let src = code_typed1((Any,)) do x + abstract_unionsplit(x) + end + @test count(isinvoke(:abstract_unionsplit), src.code) == 2 + @test count(iscall((src, abstract_unionsplit)), src.code) == 0 # no fallback dispatch +end +let src = code_typed1((Union{Type,Number},)) do x + abstract_unionsplit(x) + end + @test count(isinvoke(:abstract_unionsplit), src.code) == 2 + @test count(iscall((src, abstract_unionsplit)), src.code) == 0 # no fallback dispatch +end + +@constprop :none @noinline abstract_unionsplit_fallback(@nospecialize x::Type) = Base.inferencebarrier(:Any) +@constprop :none @noinline abstract_unionsplit_fallback(@nospecialize x::Number) = Base.inferencebarrier(:Number) +let src = code_typed1((Any,)) do x + abstract_unionsplit_fallback(x) + end + @test count(isinvoke(:abstract_unionsplit_fallback), src.code) == 2 + @test count(iscall((src, abstract_unionsplit_fallback)), src.code) == 1 # fallback dispatch +end +let src = code_typed1((Union{Type,Number},)) do x + abstract_unionsplit_fallback(x) + end + @test count(isinvoke(:abstract_unionsplit_fallback), src.code) == 2 + @test count(iscall((src, abstract_unionsplit)), src.code) == 0 # no fallback dispatch +end + +@constprop :aggressive @inline abstract_unionsplit(c, @nospecialize x::Any) = (c && println("erase me"); typeof(x)) +@constprop :aggressive @inline abstract_unionsplit(c, @nospecialize x::Number) = (c && println("erase me"); typeof(x)) +let src = code_typed1((Any,)) do x + abstract_unionsplit(false, x) + end + @test count(iscall((src, typeof)), src.code) == 2 + @test count(isinvoke(:println), src.code) == 0 + @test count(iscall((src, println)), src.code) == 0 + @test count(iscall((src, abstract_unionsplit)), src.code) == 0 # no fallback dispatch +end +let src = code_typed1((Union{Type,Number},)) do x + abstract_unionsplit(false, x) + end + @test count(iscall((src, typeof)), src.code) == 2 + @test count(isinvoke(:println), src.code) == 0 + @test count(iscall((src, println)), src.code) == 0 + @test count(iscall((src, abstract_unionsplit)), src.code) == 0 # no fallback dispatch +end + +@constprop :aggressive @inline abstract_unionsplit_fallback(c, @nospecialize x::Type) = (c && println("erase me"); typeof(x)) +@constprop :aggressive @inline abstract_unionsplit_fallback(c, @nospecialize x::Number) = (c && println("erase me"); typeof(x)) +let src = code_typed1((Any,)) do x + abstract_unionsplit_fallback(false, x) + end + @test count(iscall((src, typeof)), src.code) == 2 + @test count(isinvoke(:println), src.code) == 0 + @test count(iscall((src, println)), src.code) == 0 + @test count(iscall((src, abstract_unionsplit_fallback)), src.code) == 1 # fallback dispatch +end +let src = code_typed1((Union{Type,Number},)) do x + abstract_unionsplit_fallback(false, x) + end + @test count(iscall((src, typeof)), src.code) == 2 + @test count(isinvoke(:println), src.code) == 0 + @test count(iscall((src, println)), src.code) == 0 + @test count(iscall((src, abstract_unionsplit)), src.code) == 0 # no fallback dispatch +end + +abstract_diagonal_dispatch(x::Int, y::Int) = 1 +abstract_diagonal_dispatch(x::Real, y::Int) = 2 +abstract_diagonal_dispatch(x::Int, y::Real) = 3 +function test_abstract_diagonal_dispatch(xs) + @test abstract_diagonal_dispatch(xs[1], xs[2]) == 1 + @test abstract_diagonal_dispatch(xs[3], xs[4]) == 3 + @test abstract_diagonal_dispatch(xs[5], xs[6]) == 2 + @test_throws MethodError abstract_diagonal_dispatch(xs[7], xs[8]) +end +test_abstract_diagonal_dispatch(Any[ + 1, 1, # => 1 + 1, 1.0, # => 3 + 1.0, 1, # => 2 + 1.0, 1.0 # => MethodError +]) + +constrained_dispatch(x::T, y::T) where T<:Real = 0 +let src = code_typed1((Real,Real,)) do x, y + constrained_dispatch(x, y) + end + @test any(iscall((src, constrained_dispatch)), src.code) # should account for MethodError +end +@test_throws MethodError let + x, y = 1.0, 1 + constrained_dispatch(x, y) +end + # issue 43104 @inline isGoodType(@nospecialize x::Type) = @@ -833,7 +921,7 @@ let # aggressive inlining of single, abstract method match (with constant-prop'e # both callsite should be inlined with constant-prop'ed result @test count(isinvoke(:isType), src.code) == 2 @test count(isinvoke(:has_free_typevars), src.code) == 0 - # `isGoodType(y::Any)` isn't fully convered, thus a runtime type check and fallback dynamic dispatch should be inserted + # `isGoodType(y::Any)` isn't fully covered, thus a runtime type check and fallback dynamic dispatch should be inserted @test count(iscall((src,isGoodType2)), src.code) == 1 end @@ -891,14 +979,7 @@ Base.@constprop :aggressive function conditional_escape!(cnd, x) return nothing end @test fully_eliminated((String,)) do x - Base.@invoke conditional_escape!(false::Any, x::Any) -end - -@testset "strides for ReshapedArray (PR#44027)" 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 fully_eliminated(f, Tuple{typeof(a)}) && f(a) == 1 + @invoke conditional_escape!(false::Any, x::Any) end @testset "elimination of `get_binding_type`" begin @@ -954,26 +1035,6 @@ struct FooTheRef x::Ref FooTheRef(v) = new(v === nothing ? THE_REF_NULL : THE_REF) end -let src = code_typed1() do - FooTheRef(nothing) - end - @test count(isnew, src.code) == 1 -end -let src = code_typed1() do - FooTheRef(0) - end - @test count(isnew, src.code) == 1 -end -let src = code_typed1() do - Base.@invoke FooTheRef(nothing::Any) - end - @test count(isnew, src.code) == 1 -end -let src = code_typed1() do - Base.@invoke FooTheRef(0::Any) - end - @test count(isnew, src.code) == 1 -end @test fully_eliminated() do FooTheRef(nothing) nothing @@ -983,25 +1044,14 @@ end nothing end @test fully_eliminated() do - Base.@invoke FooTheRef(nothing::Any) + @invoke FooTheRef(nothing::Any) nothing end @test fully_eliminated() do - Base.@invoke FooTheRef(0::Any) + @invoke FooTheRef(0::Any) nothing end -# Test that the Core._apply_iterate bail path taints effects -function f_apply_bail(f) - f(()...) - return nothing -end -f_call_apply_bail(f) = f_apply_bail(f) -@test !fully_eliminated(f_call_apply_bail, Tuple{Function}) - -# Test that arraysize has proper effect modeling -@test fully_eliminated(M->(size(M, 2); nothing), Tuple{Matrix{Float64}}) - # DCE of non-inlined callees @noinline noninlined_dce_simple(a) = identity(a) @test fully_eliminated((String,)) do s @@ -1023,49 +1073,970 @@ Base.setindex!(s::SafeRef, x) = setfield!(s, 1, x) noninlined_dce_new(s) nothing end -# should be resolved once we merge https://github.com/JuliaLang/julia/pull/43923 -@test_broken fully_eliminated((Union{Symbol,String},)) do s +@test fully_eliminated((Union{Symbol,String},)) do s noninlined_dce_new(s) nothing end -# Test that ambigous calls don't accidentally get nothrow effect -ambig_effect_test(a::Int, b) = 1 -ambig_effect_test(a, b::Int) = 1 -ambig_effect_test(a, b) = 1 -global ambig_unknown_type_global=1 -@noinline function conditionally_call_ambig(b::Bool, a) - if b - ambig_effect_test(a, ambig_unknown_type_global) - end - return 0 -end -function call_call_ambig(b::Bool) - conditionally_call_ambig(b, 1) - return 1 -end -@test !fully_eliminated(call_call_ambig, Tuple{Bool}) - -# Test that a missing methtable identification gets tainted -# appropriately -struct FCallback; f::Union{Nothing, Function}; end -f_invoke_callback(fc) = let f=fc.f; (f !== nothing && f(); nothing); end -function f_call_invoke_callback(f::FCallback) - f_invoke_callback(f) +# https://github.com/JuliaLang/julia/issues/44732 +struct Component44732 + v +end +struct Container44732 + x::Union{Nothing,Component44732} +end + +# NOTE make sure to prevent inference bail out +validate44732(::Component44732) = nothing +validate44732(::Any) = error("don't erase this error!") + +function issue44732(c::Container44732) + validate44732(c.x) return nothing end -@test !fully_eliminated(f_call_invoke_callback, Tuple{FCallback}) -# https://github.com/JuliaLang/julia/issues/41694 -Base.@assume_effects :terminates_globally function issue41694(x) - res = 1 - 1 < x < 20 || throw("bad") - while x > 1 - res *= x - x -= 1 +let src = code_typed1(issue44732, (Container44732,)) + @test any(isinvoke(:validate44732), src.code) +end +@test_throws ErrorException("don't erase this error!") issue44732(Container44732(nothing)) + +global x44200::Int = 0 +function f44200() + global x44200 = 0 + while x44200 < 10 + x44200 += 1 + end + x44200 +end +let src = code_typed1(f44200) + @test_broken count(x -> isa(x, Core.PiNode), src.code) == 0 +end + +# Test that peeling off one case from (::Any) doesn't introduce +# a dynamic dispatch. +@noinline f_peel(x::Int) = Base.inferencebarrier(1) +@noinline f_peel(@nospecialize(x::Any)) = Base.inferencebarrier(2) +g_call_peel(x) = f_peel(x) +let src = code_typed1(g_call_peel, Tuple{Any}) + @test count(isinvoke(:f_peel), src.code) == 2 +end + +const my_defined_var = 42 +@test fully_eliminated(; retval=42) do + getglobal(@__MODULE__, :my_defined_var, :monotonic) +end +@test !fully_eliminated() do + getglobal(@__MODULE__, :my_defined_var, :foo) +end + +# Test for deletion of value-dependent control flow that is apparent +# at inference time, but hard to delete later. +function maybe_error_int(x::Int) + if x > 2 + Base.donotdelete(Base.inferencebarrier(x)) + error() + end + return 1 +end +@test fully_eliminated() do + return maybe_error_int(1) +end + +# Test that inlining doesn't accidentally delete a bad return_type call +f_bad_return_type() = Core.Compiler.return_type(+, 1, 2) +@test_throws MethodError f_bad_return_type() + +# Test that inlining doesn't leave useless globalrefs around +f_ret_nothing(x) = (Base.donotdelete(x); return nothing) +let src = code_typed1(Tuple{Int}) do x + f_ret_nothing(x) + return 1 + end + @test count(x -> isa(x, Core.GlobalRef) && x.name === :nothing, src.code) == 0 +end + +# Test that we can inline a finalizer for a struct that does not otherwise escape +@noinline nothrow_side_effect(x) = + Base.@assume_effects :total !:effect_free @ccall jl_(x::Any)::Cvoid +@test Core.Compiler.is_finalizer_inlineable(Base.infer_effects(nothrow_side_effect, (Nothing,))) + +mutable struct DoAllocNoEscape + function DoAllocNoEscape() + finalizer(new()) do this + nothrow_side_effect(nothing) + end + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocNoEscape() + end + end + @test count(isnew, src.code) == 0 +end + +# Test that a case when `Core.finalizer` is registered interprocedurally, +# but still eligible for SROA after inlining +mutable struct DoAllocNoEscapeInter end + +let src = code_typed1() do + for i = 1:1000 + obj = DoAllocNoEscapeInter() + finalizer(obj) do this + nothrow_side_effect(nothing) + end + end + end + @test count(isnew, src.code) == 0 +end + +function register_finalizer!(obj) + finalizer(obj) do this + nothrow_side_effect(nothing) + end +end +let src = code_typed1() do + for i = 1:1000 + obj = DoAllocNoEscapeInter() + register_finalizer!(obj) + end + end + @test count(isnew, src.code) == 0 +end + +function genfinalizer(val) + return function (this) + nothrow_side_effect(val) + end +end +let src = code_typed1() do + for i = 1:1000 + obj = DoAllocNoEscapeInter() + finalizer(genfinalizer(nothing), obj) + end + end + @test count(isnew, src.code) == 0 +end + +# Test that we can inline a finalizer that just returns a constant value +mutable struct DoAllocConst + function DoAllocConst() + finalizer(new()) do this + return nothing + end + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocConst() + end + end + @test count(isnew, src.code) == 0 +end + +# Test that finalizer elision doesn't cause a throw to be inlined into a function +# that shouldn't have it +const finalizer_should_throw = Ref{Bool}(true) +mutable struct DoAllocFinalizerThrows + function DoAllocFinalizerThrows() + finalizer(new()) do this + finalizer_should_throw[] && error("Unexpected finalizer throw") + end + end +end + +function f_finalizer_throws() + prev = GC.enable(false) + for i = 1:100 + DoAllocFinalizerThrows() + end + finalizer_should_throw[] = false + GC.enable(prev) + GC.gc() + return true +end + +@test f_finalizer_throws() + +# Test finalizers with static parameters +mutable struct DoAllocNoEscapeSparam{T} + x + @inline function finalizer_sparam(d::DoAllocNoEscapeSparam{T}) where {T} + nothrow_side_effect(nothing) + nothrow_side_effect(T) + end + @inline function DoAllocNoEscapeSparam(x::T) where {T} + finalizer(finalizer_sparam, new{T}(x)) + end +end +let src = code_typed1(Tuple{Any}) do x + for i = 1:1000 + DoAllocNoEscapeSparam(x) + end + end + @test count(x->isexpr(x, :static_parameter), src.code) == 0 # A bad inline might leave left-over :static_parameter + nnothrow_invokes = count(isinvoke(:nothrow_side_effect), src.code) + @test count(iscall(f->!isa(singleton_type(argextype(f, src)), Core.Builtin)), src.code) == + count(iscall((src, nothrow_side_effect)), src.code) == 2 - nnothrow_invokes + # TODO: Our effect modeling is not yet strong enough to fully eliminate this + @test_broken count(isnew, src.code) == 0 +end + +# Test finalizer varargs +function varargs_finalizer(args...) + nothrow_side_effect(args[1]) +end +mutable struct DoAllocNoEscapeNoVarargs + function DoAllocNoEscapeNoInline() + finalizer(noinline_finalizer, new()) + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocNoEscapeNoInline() + end + end +end + +# Test noinline finalizer +@noinline function noinline_finalizer(d) + nothrow_side_effect(nothing) +end +mutable struct DoAllocNoEscapeNoInline + function DoAllocNoEscapeNoInline() + finalizer(noinline_finalizer, new()) + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocNoEscapeNoInline() + end + end + @test count(isnew, src.code) == 1 + @test count(isinvoke(:noinline_finalizer), src.code) == 1 +end + +# Test that we resolve a `finalizer` call that we don't handle currently +mutable struct DoAllocNoEscapeBranch + val::Int + function DoAllocNoEscapeBranch(val::Int) + finalizer(new(val)) do this + if this.val > 500 + nothrow_side_effect(this.val) + else + nothrow_side_effect(nothing) + end + end + end +end +let src = code_typed1() do + for i = 1:1000 + DoAllocNoEscapeBranch(i) + end + end + @test !any(iscall((src, Core.finalizer)), src.code) + @test !any(isinvoke(:finalizer), src.code) +end + +const FINALIZATION_COUNT = Ref(0) +init_finalization_count!() = FINALIZATION_COUNT[] = 0 +get_finalization_count() = FINALIZATION_COUNT[] +@noinline add_finalization_count!(x) = FINALIZATION_COUNT[] += x +@noinline Base.@assume_effects :nothrow safeprint(io::IO, x...) = (@nospecialize; print(io, x...)) +@test Core.Compiler.is_finalizer_inlineable(Base.infer_effects(add_finalization_count!, (Int,))) + +mutable struct DoAllocWithField + x::Int + function DoAllocWithField(x::Int) + finalizer(new(x)) do this + add_finalization_count!(this.x) + end + end +end +mutable struct DoAllocWithFieldInter + x::Int +end +function register_finalizer!(obj::DoAllocWithFieldInter) + finalizer(obj) do this + add_finalization_count!(this.x) + end +end + +function const_finalization(io) + for i = 1:1000 + o = DoAllocWithField(1) + safeprint(io, o.x) + end +end +let src = code_typed1(const_finalization, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + const_finalization(IOBuffer()) + @test get_finalization_count() == 1000 +end + +# Test that finalizers that don't do anything are just erased from the IR +function useless_finalizer() + x = Ref(1) + finalizer(x) do x + nothing + end + return x +end +let src = code_typed1(useless_finalizer, ()) + @test count(iscall((src, Core.finalizer)), src.code) == 0 + @test length(src.code) == 2 +end + +# tests finalizer inlining when def/uses involve control flow +function cfg_finalization1(io) + for i = -999:1000 + o = DoAllocWithField(i) + if i == 1000 + safeprint(io, o.x, '\n') + elseif i > 0 + safeprint(io, o.x) + end + end +end +let src = code_typed1(cfg_finalization1, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization1(IOBuffer()) + @test get_finalization_count() == 1000 +end + +function cfg_finalization2(io) + for i = -999:1000 + o = DoAllocWithField(1) + o.x = i # with `setfield!` + if i == 1000 + safeprint(io, o.x, '\n') + elseif i > 0 + safeprint(io, o.x) + end + end +end +let src = code_typed1(cfg_finalization2, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization2(IOBuffer()) + @test get_finalization_count() == 1000 +end + +function cfg_finalization3(io) + for i = -999:1000 + o = DoAllocWithFieldInter(i) + register_finalizer!(o) + if i == 1000 + safeprint(io, o.x, '\n') + elseif i > 0 + safeprint(io, o.x) + end end - return res end +let src = code_typed1(cfg_finalization3, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization3(IOBuffer()) + @test get_finalization_count() == 1000 +end + +function cfg_finalization4(io) + for i = -999:1000 + o = DoAllocWithFieldInter(1) + o.x = i # with `setfield!` + register_finalizer!(o) + if i == 1000 + safeprint(io, o.x, '\n') + elseif i > 0 + safeprint(io, o.x) + end + end +end +let src = code_typed1(cfg_finalization4, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization4(IOBuffer()) + @test get_finalization_count() == 1000 +end + +function cfg_finalization5(io) + for i = -999:1000 + o = DoAllocWithFieldInter(i) + if i == 1000 + safeprint(io, o.x, '\n') + elseif i > 0 + safeprint(io, o.x) + end + register_finalizer!(o) + end +end +let src = code_typed1(cfg_finalization5, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization5(IOBuffer()) + @test get_finalization_count() == 1000 +end + +function cfg_finalization6(io) + for i = -999:1000 + o = DoAllocWithField(0) + if i == 1000 + o.x = i # with `setfield!` + elseif i > 0 + safeprint(io, o.x, '\n') + end + end +end +let src = code_typed1(cfg_finalization6, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization6(IOBuffer()) + @test get_finalization_count() == 1000 +end + + +function cfg_finalization7(io) + for i = -999:1000 + o = DoAllocWithField(0) + o.x = 0 + if i == 1000 + o.x = i # with `setfield!` + end + o.x = i + if i == 999 + o.x = i + end + o.x = 0 + if i == 1000 + o.x = i + end + end +end +let src = code_typed1(cfg_finalization7, (IO,)) + @test count(isinvoke(:add_finalization_count!), src.code) == 1 +end +let + init_finalization_count!() + cfg_finalization7(IOBuffer()) + @test get_finalization_count() == 1000 +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) + 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) + end + @test count(iscall((src, $f)), src.code) == 0 + end + let xs = Any[] + $f(xs, :x, "y", 'z') + @test xs[1] === :x + @test xs[2] == "y" + @test xs[3] === 'z' + end + end +end + +using Core.Compiler: is_declared_inline, is_declared_noinline + +# https://github.com/JuliaLang/julia/issues/45050 +@testset "propagate :meta annotations to keyword sorter methods" begin + # @inline, @noinline, @constprop + let @inline f(::Any; x::Int=1) = 2x + @test is_declared_inline(only(methods(f))) + @test is_declared_inline(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) + end + let @noinline f(::Any; x::Int=1) = 2x + @test is_declared_noinline(only(methods(f))) + @test is_declared_noinline(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) + end + let Base.@constprop :aggressive f(::Any; x::Int=1) = 2x + @test Core.Compiler.is_aggressive_constprop(only(methods(f))) + @test Core.Compiler.is_aggressive_constprop(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) + end + let Base.@constprop :none f(::Any; x::Int=1) = 2x + @test Core.Compiler.is_no_constprop(only(methods(f))) + @test Core.Compiler.is_no_constprop(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) + end + # @nospecialize + let f(@nospecialize(A::Any); x::Int=1) = 2x + @test only(methods(f)).nospecialize == 1 + @test only(methods(Core.kwcall, (Any, typeof(f), Vararg))).nospecialize == 4 + end + let f(::Any; x::Int=1) = (@nospecialize; 2x) + @test only(methods(f)).nospecialize == -1 + @test only(methods(Core.kwcall, (Any, typeof(f), Vararg))).nospecialize == -1 + end + # Base.@assume_effects + let Base.@assume_effects :notaskstate f(::Any; x::Int=1) = 2x + @test Core.Compiler.decode_effects_override(only(methods(f)).purity).notaskstate + @test Core.Compiler.decode_effects_override(only(methods(Core.kwcall, (Any, typeof(f), Vararg))).purity).notaskstate + end + # propagate multiple metadata also + let @inline Base.@assume_effects :notaskstate Base.@constprop :aggressive f(::Any; x::Int=1) = (@nospecialize; 2x) + @test is_declared_inline(only(methods(f))) + @test Core.Compiler.is_aggressive_constprop(only(methods(f))) + @test is_declared_inline(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) + @test Core.Compiler.is_aggressive_constprop(only(methods(Core.kwcall, (Any, typeof(f), Vararg)))) + @test only(methods(f)).nospecialize == -1 + @test only(methods(Core.kwcall, (Any, typeof(f), Vararg))).nospecialize == -1 + @test Core.Compiler.decode_effects_override(only(methods(f)).purity).notaskstate + @test Core.Compiler.decode_effects_override(only(methods(Core.kwcall, (Any, typeof(f), Vararg))).purity).notaskstate + end +end + +# Test that one opaque closure capturing another gets inlined properly. +function oc_capture_oc(z) + oc1 = @opaque x->x + oc2 = @opaque y->oc1(y) + return oc2(z) +end +@test fully_eliminated(oc_capture_oc, (Int,)) + +@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() + f(x::OldVal{i}) where {i} = i + r = 0 + for i = 1:10000 + r += f(OldVal{i}()) + 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) + end +end + +function twice_sitofp(x::Int, y::Int) + x = Base.sitofp(Float64, x) + y = Base.sitofp(Float64, y) + return (x, y) +end + +# Test that semi-concrete eval can inline constant results +let src = code_typed1((Int,)) do x + twice_sitofp(x, 2) + end + @test count(iscall((src, Base.sitofp)), src.code) == 1 +end + +# `@noinline` annotations with semi-concrete eval +let src = code_typed1((Int,)) do x + @noinline twice_sitofp(x, 2) + end + @test count(isinvoke(:twice_sitofp), src.code) == 1 +end + +# `Base.@constprop :aggressive` forces semi-concrete eval, but it should still not be inlined +@noinline Base.@constprop :aggressive function twice_sitofp_noinline(x::Int, y::Int) + x = Base.sitofp(Float64, x) + y = Base.sitofp(Float64, y) + return (x, y) +end + +let src = code_typed1((Int,)) do x + twice_sitofp_noinline(x, 2) + end + @test count(isinvoke(:twice_sitofp_noinline), src.code) == 1 +end + +# Test getfield modeling of Type{Ref{_A}} where _A +let getfield_tfunc(@nospecialize xs...) = + Core.Compiler.getfield_tfunc(Core.Compiler.fallback_lattice, xs...) + @test getfield_tfunc(Type, Core.Const(:parameters)) !== Union{} + @test !isa(getfield_tfunc(Type{Tuple{Union{Int, Float64}, Int}}, Core.Const(:name)), Core.Const) +end +@test fully_eliminated(Base.ismutable, Tuple{Base.RefValue}) + +# TODO: Remove compute sparams for vararg_retrival +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}},)) + @test_broken count(iscall((src, Core._compute_sparams)), src.code) == 0 && + count(iscall((src, Core._svec_ref)), src.code) == 0 && + count(iscall((src, Core.nfields)), src.code) == 1 +end + +# Test effect annotation of declined inline unionsplit +f_union_unmatched(x::Union{Nothing, Type{T}}) where {T} = nothing +let src = code_typed1((Any,)) do x + if isa(x, Union{Nothing, Type}) + f_union_unmatched(x) + end + nothing + end + @test count(iscall((src, f_union_unmatched)), src.code) == 0 +end + +# modifyfield! handling +# ===================== + +isinvokemodify(y) = @nospecialize(x) -> isinvokemodify(y, x) +isinvokemodify(sym::Symbol, @nospecialize(x)) = isinvokemodify(mi->mi.def.name===sym, x) +isinvokemodify(pred::Function, @nospecialize(x)) = isexpr(x, :invoke_modify) && pred(x.args[1]::MethodInstance) + +mutable struct Atomic{T} + @atomic x::T +end +let src = code_typed1((Atomic{Int},)) do a + @atomic a.x + 1 + end + @test count(isinvokemodify(:+), src.code) == 1 +end +let src = code_typed1((Atomic{Int},)) do a + @atomic a.x += 1 + end + @test count(isinvokemodify(:+), src.code) == 1 +end +let src = code_typed1((Atomic{Int},)) do a + @atomic a.x max 10 + end + @test count(isinvokemodify(:max), src.code) == 1 +end +# simple union split handling +mymax(x::T, y::T) where T<:Real = max(x, y) +mymax(x::T, y::Real) where T<:Real = convert(T, max(x, y))::T +let src = code_typed1((Atomic{Int},Union{Int,Float64})) do a, b + @atomic a.x mymax b + end + @test count(isinvokemodify(:mymax), src.code) == 2 +end + +# apply `ssa_inlining_pass` multiple times +let 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.inst) + @test i !== nothing + + # ok, 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.inst) == 0 + @test count(iscall((ir, Core.Intrinsics.mul_int)), ir.stmts.inst) == 1 +end + +# Test special purpose inliner for Core.ifelse +f_ifelse_1(a, b) = Core.ifelse(true, a, b) +f_ifelse_2(a, b) = Core.ifelse(false, a, b) +f_ifelse_3(a, b) = Core.ifelse(a, true, b) + +@test fully_eliminated(f_ifelse_1, Tuple{Any, Any}; retval=Core.Argument(2)) +@test fully_eliminated(f_ifelse_2, Tuple{Any, Any}; retval=Core.Argument(3)) +@test !fully_eliminated(f_ifelse_3, Tuple{Any, Any}) + +# inline_splatnew for abstract `NamedTuple` +@eval construct_splatnew(T, fields) = $(Expr(:splatnew, :T, :fields)) +for tt = Any[(Int,Int), (Integer,Integer), (Any,Any)] + let src = code_typed1(tt) do a, b + construct_splatnew(NamedTuple{(:a,:b),typeof((a,b))}, (a,b)) + end + @test count(issplatnew, src.code) == 0 + @test count(isnew, src.code) == 1 + end +end + +# optimize away `NamedTuple`s used for handling `@nospecialize`d keyword-argument +# https://github.com/JuliaLang/julia/pull/47059 +abstract type CallInfo end +struct NewInstruction + stmt::Any + type::Any + info::CallInfo + line::Int32 + flag::UInt8 + function NewInstruction(@nospecialize(stmt), @nospecialize(type), @nospecialize(info::CallInfo), + line::Int32, flag::UInt8) + return new(stmt, type, info, line, flag) + end +end +@nospecialize +function NewInstruction(newinst::NewInstruction; + stmt=newinst.stmt, + type=newinst.type, + info::CallInfo=newinst.info, + line::Int32=newinst.line, + flag::UInt8=newinst.flag) + return NewInstruction(stmt, type, info, line, flag) +end +@specialize +let src = code_typed1((NewInstruction,Any,Any,CallInfo)) do newinst, stmt, type, info + NewInstruction(newinst; stmt, type, info) + end + @test count(issplatnew, src.code) == 0 + @test count(iscall((src,NamedTuple)), src.code) == 0 + @test count(isnew, src.code) == 1 +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) +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 +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 +end + +# inlineable but removable call should be eligible for DCE +Base.@assume_effects :removable @inline function inlineable_effect_free(a::Float64) + a == Inf && return zero(a) + return sin(a) + cos(a) +end +@test fully_eliminated((Float64,)) do a + b = inlineable_effect_free(a) + c = inlineable_effect_free(b) + nothing +end + +# https://github.com/JuliaLang/julia/issues/47374 +function f47374(x) + [f47374(i, x) for i in 1:1] +end +function f47374(i::Int, x) + return 1.0 +end +@test f47374(rand(1)) == Float64[1.0] + +# compiler should recognize effectful :static_parameter +# https://github.com/JuliaLang/julia/issues/45490 +issue45490_1(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = T +issue45490_2(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = (typeof(T); nothing) +for f = (issue45490_1, issue45490_2) + src = code_typed1(f, (Any,Any)) + @test any(src.code) do @nospecialize x + isexpr(x, :static_parameter) + end + @test_throws UndefVarError f(nothing, nothing) +end + +# inline effect-free :static_parameter, required for semi-concrete interpretation accuracy +# https://github.com/JuliaLang/julia/issues/47349 +function make_issue47349(::Val{N}) where {N} + pickargs(::Val{N}) where {N} = (@nospecialize(x::Tuple)) -> x[N] + return pickargs(Val{N-1}()) +end +let src = code_typed1(make_issue47349(Val{4}()), (Any,)) + @test !any(src.code) do @nospecialize x + isexpr(x, :static_parameter) + end + @test Base.return_types((Int,)) do x + make_issue47349(Val(4))((x,nothing,Int)) + end |> only === Type{Int} +end + +# Test that irinterp can make use of constant results even if they're big +# Check that pure functions with non-inlineable results still get deleted +struct BigSemi + x::NTuple{1024, Int} +end +@Base.assume_effects :total @noinline make_big_tuple(x::Int) = ntuple(x->x+1, 1024)::NTuple{1024, Int} +BigSemi(y::Int, x::Int) = BigSemi(make_big_tuple(x)) +function elim_full_ir(y) + bs = BigSemi(y, 10) + return Val{bs.x[1]}() +end + +@test fully_eliminated(elim_full_ir, Tuple{Int}) + +# union splitting should account for uncovered call signature +# https://github.com/JuliaLang/julia/issues/48397 +f48397(::Bool) = :ok +f48397(::Tuple{String,String}) = :ok +let src = code_typed1((Union{Bool,Tuple{String,Any}},)) do x + f48397(x) + end + @test any(iscall((src, f48397)), src.code) +end +g48397::Union{Bool,Tuple{String,Any}} = ("48397", 48397) +let res = @test_throws MethodError let + Base.Experimental.@force_compile + f48397(g48397) + end + err = res.value + @test err.f === f48397 && err.args === (g48397,) +end +let res = @test_throws MethodError let + Base.Experimental.@force_compile + convert(Union{Bool,Tuple{String,String}}, g48397) + end + err = res.value + @test err.f === convert && err.args === (Union{Bool,Tuple{String,String}}, g48397) +end + +# https://github.com/JuliaLang/julia/issues/49050 +abstract type Issue49050AbsTop{T,N} end +abstract type Issue49050Abs1{T, N} <: Issue49050AbsTop{T,N} end +abstract type Issue49050Abs2{T} <: Issue49050Abs1{T,3} end +struct Issue49050Concrete{T} <: Issue49050Abs2{T} + x::T +end +issue49074(::Type{Issue49050AbsTop{T,N}}) where {T,N} = Issue49050AbsTop{T,N} +Base.@assume_effects :foldable issue49074(::Type{C}) where {C<:Issue49050AbsTop} = issue49074(supertype(C)) +let src = code_typed1() do + issue49074(Issue49050Concrete) + end + @test any(isinvoke(:issue49074), src.code) +end +let result = @test_throws MethodError issue49074(Issue49050Concrete) + @test result.value.f === issue49074 + @test result.value.args === (Any,) +end + +# inlining of `TypeName` @test fully_eliminated() do - issue41694(2) + Ref.body.name +end + +# Regression for finalizer inlining with more complex control flow +global finalizer_escape::Int = 0 +mutable struct FinalizerEscapeTest + x::Int + function FinalizerEscapeTest() + this = new(0) + finalizer(this) do this + global finalizer_escape + finalizer_escape = this.x + end + return this + end +end + +function run_finalizer_escape_test1(b1, b2) + x = FinalizerEscapeTest() + x.x = 1 + if b1 + x.x = 2 + end + if b2 + Base.donotdelete(b2) + end + x.x = 3 + return nothing +end + +function run_finalizer_escape_test2(b1, b2) + x = FinalizerEscapeTest() + x.x = 1 + if b1 + x.x = 2 + end + x.x = 3 + return nothing +end + +for run_finalizer_escape_test in (run_finalizer_escape_test1, run_finalizer_escape_test2) + global finalizer_escape::Int = 0 + + let src = code_typed1(run_finalizer_escape_test, Tuple{Bool, Bool}) + @test any(x->isexpr(x, :(=)), src.code) + end + + let + run_finalizer_escape_test(true, true) + @test finalizer_escape == 3 + end +end + +# `compilesig_invokes` inlining option +@newinterp NoCompileSigInvokes +Core.Compiler.OptimizationParams(::NoCompileSigInvokes) = + Core.Compiler.OptimizationParams(; compilesig_invokes=false) +@noinline no_compile_sig_invokes(@nospecialize x) = (x !== Any && !Base.has_free_typevars(x)) +# test the single dispatch candidate case +let src = code_typed1((Type,)) do x + no_compile_sig_invokes(x) + end + @test count(src.code) do @nospecialize x + isinvoke(:no_compile_sig_invokes, x) && + (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Any} + end == 1 +end +let src = code_typed1((Type,); interp=NoCompileSigInvokes()) do x + no_compile_sig_invokes(x) + end + @test count(src.code) do @nospecialize x + isinvoke(:no_compile_sig_invokes, x) && + (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Type} + end == 1 +end +# test the union split case +let src = code_typed1((Union{DataType,UnionAll},)) do x + no_compile_sig_invokes(x) + end + @test count(src.code) do @nospecialize x + isinvoke(:no_compile_sig_invokes, x) && + (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Any} + end == 2 +end +let src = code_typed1((Union{DataType,UnionAll},); interp=NoCompileSigInvokes()) do x + no_compile_sig_invokes(x) + end + @test count(src.code) do @nospecialize x + isinvoke(:no_compile_sig_invokes, x) && + (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),DataType} + end == 1 + @test count(src.code) do @nospecialize x + isinvoke(:no_compile_sig_invokes, x) && + (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),UnionAll} + end == 1 end diff --git a/test/compiler/interpreter_exec.jl b/test/compiler/interpreter_exec.jl index 27143c17052cc..a310a2740131d 100644 --- a/test/compiler/interpreter_exec.jl +++ b/test/compiler/interpreter_exec.jl @@ -106,3 +106,17 @@ let m = Meta.@lower 1 + 1 global test29262 = false @test :b === @eval $m 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 new file mode 100644 index 0000000000000..20ab2483aa378 --- /dev/null +++ b/test/compiler/invalidation.jl @@ -0,0 +1,258 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# setup +# ----- + +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 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; + 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) + 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_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 + + +# basic functionality test +# ------------------------ + +basic_callee(x) = x +basic_caller(x) = basic_callee(x) + +# run inference and check that cache exist +@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 +end +@test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) + mi.def.name === :basic_caller +end + +# this redefinition below should invalidate the cache +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 +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 +end +@test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) + mi.def.name === :basic_caller +end + +# backedge optimization +# --------------------- + +const GLOBAL_BUFFER = IOBuffer() + +# test backedge optimization when the callee's type and effects information are maximized +begin take!(GLOBAL_BUFFER) + + pr48932_callee(x) = (print(GLOBAL_BUFFER, x); Base.inferencebarrier(x)) + pr48932_caller(x) = pr48932_callee(Base.inferencebarrier(x)) + + # assert that type and effects information inferred from `pr48932_callee(::Any)` are the top + let rt = only(Base.return_types(pr48932_callee, (Any,))) + @test rt === Any + effects = Base.infer_effects(pr48932_callee, (Any,)) + @test Core.Compiler.Effects(effects; noinbounds=false) == Core.Compiler.Effects() + end + + # run inference on both `pr48932_caller` and `pr48932_callee` + let (src, rt) = code_typed((Int,); interp=InvalidationTester()) do x + @inline pr48932_caller(x) + end |> only + @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 + end + @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) + mi.def.name === :pr48932_caller + 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 + end + @test isnothing(pr48932_caller(42)) + @test "42" == String(take!(GLOBAL_BUFFER)) +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); nothing) + pr48932_caller_unuse(x) = (pr48932_callee_inferrable(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,))) + @test rt === Nothing + effects = Base.infer_effects(pr48932_callee_inferrable, (Any,)) + @test Core.Compiler.Effects(effects; noinbounds=false) == Core.Compiler.Effects() + end + + # run inference on both `pr48932_caller` and `pr48932_callee`: + # we don't need to add backedge to `pr48932_callee` from `pr48932_caller` + # since the inference result of `pr48932_callee` is maximized and it's not inlined + let (src, rt) = code_typed((Int,); interp=InvalidationTester()) do x + @inline pr48932_caller_unuse(x) + end |> only + @test rt === Nothing + @test any(iscall((src, pr48932_callee_inferrable)), src.code) + end + @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 + 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 + end + @test isnothing(pr48932_caller_unuse(42)) + @test "foo" == String(take!(GLOBAL_BUFFER)) +end + +# we need to add backedge when the callee is inlined +begin take!(GLOBAL_BUFFER) + + @noinline pr48932_callee_inlined(@nospecialize x) = (print(GLOBAL_BUFFER, x); Base.inferencebarrier(x)) + pr48932_caller_inlined(x) = pr48932_callee_inlined(Base.inferencebarrier(x)) + + # assert that type and effects information inferred from `pr48932_callee(::Any)` are the top + let rt = only(Base.return_types(pr48932_callee_inlined, (Any,))) + @test rt === Any + effects = Base.infer_effects(pr48932_callee_inlined, (Any,)) + @test Core.Compiler.Effects(effects; noinbounds=false) == Core.Compiler.Effects() + end + + # run inference on `pr48932_caller_inlined` and `pr48932_callee_inlined` + let (src, rt) = code_typed((Int,); interp=InvalidationTester()) do x + @inline pr48932_caller_inlined(x) + end |> only + @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 + end + @test any(INVALIDATION_TESTER_CACHE.dict) do (mi, ci) + mi.def.name === :pr48932_caller_inlined + 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 + 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 128fd6cc84b7b..c704a8cf1c434 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -2,7 +2,9 @@ using Test using Base.Meta -using Core: PhiNode, SSAValue, GotoNode, PiNode, QuoteNode, ReturnNode, GotoIfNot +import Core: + CodeInfo, Argument, SSAValue, GotoNode, GotoIfNot, PiNode, PhiNode, + QuoteNode, ReturnNode include(normpath(@__DIR__, "irutils.jl")) @@ -12,7 +14,7 @@ include(normpath(@__DIR__, "irutils.jl")) ## Test that domsort doesn't mangle single-argument phis (#29262) let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 Expr(:call, :opaque), @@ -47,7 +49,7 @@ end # test that we don't stack-overflow in SNCA with large functions. let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo code = Any[] N = 2^15 for i in 1:2:N @@ -73,30 +75,87 @@ end # SROA # ==== +import Core.Compiler: widenconst + +is_load_forwarded(src::CodeInfo) = !any(iscall((src, getfield)), src.code) +is_scalar_replaced(src::CodeInfo) = + is_load_forwarded(src) && !any(iscall((src, setfield!)), src.code) && !any(isnew, src.code) + +function is_load_forwarded(@nospecialize(T), src::CodeInfo) + for i in 1:length(src.code) + x = src.code[i] + if iscall((src, getfield), x) + widenconst(argextype(x.args[1], src)) <: T && return false + end + end + return true +end +function is_scalar_replaced(@nospecialize(T), src::CodeInfo) + is_load_forwarded(T, src) || return false + for i in 1:length(src.code) + x = src.code[i] + if iscall((src, setfield!), x) + widenconst(argextype(x.args[1], src)) <: T && return false + elseif isnew(x) + widenconst(argextype(SSAValue(i), src)) <: T && return false + end + end + return true +end + struct ImmutableXYZ; x; y; z; end mutable struct MutableXYZ; x; y; z; end +struct ImmutableOuter{T}; x::T; y::T; z::T; end +mutable struct MutableOuter{T}; x::T; y::T; z::T; end +struct ImmutableRef{T}; x::T; end +Base.getindex(r::ImmutableRef) = r.x +mutable struct SafeRef{T}; x::T; end +Base.getindex(s::SafeRef) = getfield(s, 1) +Base.setindex!(s::SafeRef, x) = setfield!(s, 1, x) + +# simple immutability +# ------------------- -# should optimize away very basic cases let src = code_typed1((Any,Any,Any)) do x, y, z xyz = ImmutableXYZ(x, y, z) xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) + @test any(src.code) do @nospecialize x + iscall((src, tuple), x) && + x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=z=# Core.Argument(4)] + end end +let src = code_typed1((Any,Any,Any)) do x, y, z + xyz = (x, y, z) + xyz[1], xyz[2], xyz[3] + end + @test is_scalar_replaced(src) + @test any(src.code) do @nospecialize x + iscall((src, tuple), x) && + x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=z=# Core.Argument(4)] + end +end + +# simple mutability +# ----------------- + let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) + @test any(src.code) do @nospecialize x + iscall((src, tuple), x) && + x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=z=# Core.Argument(4)] + end end - -# should handle simple mutabilities let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) xyz.y = 42 xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) @test any(src.code) do @nospecialize x iscall((src, tuple), x) && x.args[2:end] == Any[#=x=# Core.Argument(2), 42, #=x=# Core.Argument(4)] @@ -107,19 +166,23 @@ let src = code_typed1((Any,Any,Any)) do x, y, z xyz.x, xyz.z = xyz.z, xyz.x xyz.x, xyz.y, xyz.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) @test any(src.code) do @nospecialize x iscall((src, tuple), x) && x.args[2:end] == Any[#=z=# Core.Argument(4), #=y=# Core.Argument(3), #=x=# Core.Argument(2)] end end -# circumvent uninitialized fields as far as there is a solid `setfield!` definition + +# uninitialized fields +# -------------------- + +# safe cases let src = code_typed1() do r = Ref{Any}() r[] = 42 return r[] end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Bool,)) do cond r = Ref{Any}() @@ -131,7 +194,7 @@ let src = code_typed1((Bool,)) do cond return r[] end end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Bool,)) do cond r = Ref{Any}() @@ -142,7 +205,7 @@ let src = code_typed1((Bool,)) do cond end return r[] end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z r = Ref{Any}() @@ -157,7 +220,16 @@ let src = code_typed1((Bool,Bool,Any,Any,Any)) do c1, c2, x, y, z end return r[] end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) +end + +# unsafe cases +let src = code_typed1() do + r = Ref{Any}() + return r[] + end + @test count(isnew, src.code) == 1 + @test count(iscall((src, getfield)), src.code) == 1 end let src = code_typed1((Bool,)) do cond r = Ref{Any}() @@ -167,7 +239,9 @@ let src = code_typed1((Bool,)) do cond return r[] end # N.B. `r` should be allocated since `cond` might be `false` and then it will be thrown - @test any(isnew, src.code) + @test count(isnew, src.code) == 1 + @test count(iscall((src, setfield!)), src.code) == 1 + @test count(iscall((src, getfield)), src.code) == 1 end let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y r = Ref{Any}() @@ -181,12 +255,16 @@ let src = code_typed1((Bool,Bool,Any,Any)) do c1, c2, x, y return r[] end # N.B. `r` should be allocated since `c2` might be `false` and then it will be thrown - @test any(isnew, src.code) + @test count(isnew, src.code) == 1 + @test count(iscall((src, setfield!)), src.code) == 2 + @test count(iscall((src, getfield)), src.code) == 1 end -# should include a simple alias analysis -struct ImmutableOuter{T}; x::T; y::T; z::T; end -mutable struct MutableOuter{T}; x::T; y::T; z::T; end +# aliased load forwarding +# ----------------------- +# TODO fix broken examples with EscapeAnalysis + +# OK: immutable(immutable(...)) case let src = code_typed1((Any,Any,Any)) do x, y, z xyz = ImmutableXYZ(x, y, z) outer = ImmutableOuter(xyz, xyz, xyz) @@ -214,22 +292,21 @@ let src = code_typed1((Any,Any,Any)) do x, y, z end end -# FIXME our analysis isn't yet so powerful at this moment: may be unable to handle nested objects well -# OK: mutable(immutable(...)) case +# OK (mostly): immutable(mutable(...)) case let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) t = (xyz,) v = t[1].x v, v, v end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) end let src = code_typed1((Any,Any,Any)) do x, y, z xyz = MutableXYZ(x, y, z) outer = ImmutableOuter(xyz, xyz, xyz) outer.x.x, outer.y.y, outer.z.z end - @test !any(isnew, src.code) + @test is_scalar_replaced(src) @test any(src.code) do @nospecialize x iscall((src, tuple), x) && x.args[2:end] == Any[#=x=# Core.Argument(2), #=y=# Core.Argument(3), #=y=# Core.Argument(4)] @@ -240,12 +317,27 @@ let # this is a simple end to end test case, which demonstrates allocation elimi # NOTE this test case isn't so robust and might be subject to future changes of the broadcasting implementation, # in that case you don't really need to stick to keeping this test case around simple_sroa(s) = broadcast(identity, Ref(s)) + let src = code_typed1(simple_sroa, (String,)) + @test is_scalar_replaced(src) + end s = Base.inferencebarrier("julia")::String simple_sroa(s) # NOTE don't hard-code `"julia"` in `@allocated` clause and make sure to execute the # compiled code for `simple_sroa`, otherwise everything can be folded even without SROA @test @allocated(simple_sroa(s)) == 0 end +let # FIXME: some nested example + src = code_typed1((Int,)) do x + Ref(Ref(x))[][] + end + @test_broken is_scalar_replaced(src) + + src = code_typed1((Int,)) do x + Ref(Ref(Ref(Ref(Ref(Ref(Ref(Ref(Ref(Ref((x)))))))))))[][][][][][][][][][] + end + @test_broken is_scalar_replaced(src) +end + # FIXME: immutable(mutable(...)) case let src = code_typed1((Any,Any,Any)) do x, y, z xyz = ImmutableXYZ(x, y, z) @@ -295,6 +387,22 @@ let # should work with constant globals @test count(isnew, src.code) == 0 end +# don't SROA statement that may throw +# https://github.com/JuliaLang/julia/issues/48067 +function issue48067(a::Int, b) + r = Ref(a) + try + setfield!(r, :x, b) + nothing + catch err + getfield(r, :x) + end +end +let src = code_typed1(issue48067, (Int,String)) + @test any(iscall((src, setfield!)), src.code) +end +@test issue48067(42, "julia") == 42 + # should work nicely with inlining to optimize away a complicated case # adapted from http://wiki.luajit.org/Allocation-Sinking-Optimization#implementation%5B struct Point @@ -314,6 +422,118 @@ let src = code_typed1(compute_points) @test !any(isnew, src.code) end +# preserve elimination +# -------------------- + +function ispreserved(@nospecialize(x)) + return function (@nospecialize(stmt),) + if Meta.isexpr(stmt, :foreigncall) + nccallargs = length(stmt.args[3]::Core.SimpleVector) + for pidx = (6+nccallargs):length(stmt.args) + if stmt.args[pidx] === x + return true + end + end + end + return false + end +end + +let src = code_typed1((String,)) do s + ccall(:some_ccall, Cint, (Ptr{String},), Ref(s)) + end + @test count(isnew, src.code) == 0 + @test any(ispreserved(#=s=#Core.Argument(2)), src.code) +end + +# if the mutable struct is directly used, we shouldn't eliminate it +let src = code_typed1() do + a = MutableXYZ(-512275808,882558299,-2133022131) + b = Int32(42) + ccall(:some_ccall, Cvoid, (MutableXYZ, Int32), a, b) + return a.x + end + @test count(isnew, src.code) == 1 +end + +# should eliminate allocation whose address isn't taked even if it has uninitialized field(s) +mutable struct BadRef + x::String + y::String + BadRef(x) = new(x) +end +Base.cconvert(::Type{Ptr{BadRef}}, a::String) = BadRef(a) +Base.unsafe_convert(::Type{Ptr{BadRef}}, ar::BadRef) = Ptr{BadRef}(pointer_from_objref(ar.x)) +let src = code_typed1((String,)) do s + ccall(:jl_breakpoint, Cvoid, (Ptr{BadRef},), s) + end + @test count(isnew, src.code) == 0 + @test any(ispreserved(#=s=#Core.Argument(2)), src.code) +end + +# isdefined elimination +# --------------------- + +let src = code_typed1((Any,)) do a + r = Ref{Any}() + r[] = a + if isassigned(r) + return r[] + end + return nothing + end + @test is_scalar_replaced(src) +end + +let src = code_typed1((Bool, Any,)) do cnd, a + r = Ref{Any}() + if cnd + r[] = a # this `setfield!` shouldn't be eliminated + end + return isassigned(r) + end + @test count(isnew, src.code) == 1 + @test count(iscall((src, setfield!)), src.code) == 1 +end + +callit(f, args...) = f(args...) +function isdefined_elim() + local arr::Vector{Any} + callit() do + arr = Any[] + end + return arr +end +let src = code_typed1(isdefined_elim) + @test is_scalar_replaced(src) +end +@test isdefined_elim() == Any[] + +function abmult(r::Int, x0) + if r < 0 + r = -r + end + f = x -> x * r + return @inline f(x0) +end +let src = code_typed1(abmult, (Int,Int)) + @test is_scalar_replaced(src) +end +@test abmult(-3, 3) == 9 + +function abmult2(r0::Int, x0) + r::Int = r0 + if r < 0 + r = -r + end + f = x -> x * r + return f(x0) +end +let src = code_typed1(abmult2, (Int,Int)) + @test is_scalar_replaced(src) +end +@test abmult2(-3, 3) == 9 + # comparison lifting # ================== @@ -454,7 +674,7 @@ end # A SSAValue after the compaction line let m = Meta.@lower 1 + 1 @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # block 1 nothing, @@ -488,9 +708,10 @@ let m = Meta.@lower 1 + 1 Any ] nstmts = length(src.code) - src.codelocs = fill(Int32(1), nstmts) - src.ssaflags = fill(Int32(0), nstmts) - ir = Core.Compiler.inflate_ir(src, Any[], Any[Any, Any]) + 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 ir = @test_nowarn Core.Compiler.sroa_pass!(ir) @test Core.Compiler.verify_ir(ir) === nothing @@ -517,7 +738,7 @@ end let m = Meta.@lower 1 + 1 # Test that CFG simplify combines redundant basic blocks @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ Core.Compiler.GotoNode(2), Core.Compiler.GotoNode(3), @@ -539,10 +760,98 @@ let m = Meta.@lower 1 + 1 @test length(ir.cfg.blocks) == 1 && Core.Compiler.length(ir.stmts) == 1 end +# Test cfg_simplify in complicated sequences of dropped and merged bbs +using Core.Compiler: Argument, IRCode, GotoNode, GotoIfNot, ReturnNode, NoCallInfo, BasicBlock, StmtRange, SSAValue +bb_term(ir, bb) = Core.Compiler.getindex(ir, SSAValue(Core.Compiler.last(ir.cfg.blocks[bb].stmts)))[:inst] + +function each_stmt_a_bb(stmts, preds, succs) + ir = IRCode() + empty!(ir.stmts.inst) + append!(ir.stmts.inst, 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.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)]) + Core.Compiler.verify_ir(ir) + return ir +end + +for gotoifnot in (false, true) + stmts = [ + # BB 1 + GotoIfNot(Argument(1), 8), + # BB 2 + GotoIfNot(Argument(2), 4), + # BB 3 + GotoNode(9), + # BB 4 + GotoIfNot(Argument(3), 10), + # BB 5 + GotoIfNot(Argument(4), 11), + # BB 6 + GotoIfNot(Argument(5), 12), + # BB 7 + GotoNode(13), + # BB 8 + ReturnNode(1), + # BB 9 + nothing, + # BB 10 + nothing, + # BB 11 + gotoifnot ? GotoIfNot(Argument(6), 13) : GotoNode(13), + # BB 12 + ReturnNode(2), + # BB 13 + ReturnNode(3), + ] + preds = Vector{Int}[Int[], [1], [2], [2], [4], [5], [6], [1], [3], [4, 9], [5, 10], gotoifnot ? [6,11] : [6], [7, 11]] + succs = Vector{Int}[[2, 8], [3, 4], [9], [5, 10], [6, 11], [7, 12], [13], Int[], [10], [11], gotoifnot ? [12, 13] : [13], Int[], Int[]] + ir = each_stmt_a_bb(stmts, preds, succs) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + + if gotoifnot + let term4 = bb_term(ir, 4), term5 = bb_term(ir, 5) + @test isa(term4, GotoIfNot) && bb_term(ir, term4.dest).val == 3 + @test isa(term5, ReturnNode) && term5.val == 2 + end + else + @test length(ir.cfg.blocks) == 10 + let term = bb_term(ir, 3) + @test isa(term, GotoNode) && bb_term(ir, term.label).val == 3 + end + end +end + +let stmts = [ + # BB 1 + GotoIfNot(Argument(1), 4), + # BB 2 + GotoIfNot(Argument(2), 5), + # BB 3 + GotoNode(5), + # BB 4 + ReturnNode(1), + # BB 5 + ReturnNode(2) + ] + preds = Vector{Int}[Int[], [1], [2], [1], [2, 3]] + succs = Vector{Int}[[2, 4], [3, 5], [5], Int[], Int[]] + ir = each_stmt_a_bb(stmts, preds, succs) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + + @test length(ir.cfg.blocks) == 4 + terms = map(i->bb_term(ir, i), 1:length(ir.cfg.blocks)) + @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]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ Core.Compiler.GotoIfNot(Core.Compiler.Argument(2), 3), Core.Compiler.GotoNode(4), @@ -572,7 +881,7 @@ let m = Meta.@lower 1 + 1 # Test that CFG simplify doesn't try to merge every block in a loop into # its predecessor @assert Meta.isexpr(m, :thunk) - src = m.args[1]::Core.CodeInfo + src = m.args[1]::CodeInfo src.code = Any[ # Block 1 Core.Compiler.GotoNode(2), @@ -592,6 +901,21 @@ let m = Meta.@lower 1 + 1 @test length(ir.cfg.blocks) == 1 end +# `cfg_simplify!` shouldn't error in a presence of `try/catch` block +let ir = Base.code_ircode(; optimize_until="slot2ssa") do + v = try + catch + end + v + end |> only |> first + Core.Compiler.verify_ir(ir) + nb = length(ir.cfg.blocks) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + na = length(ir.cfg.blocks) + @test na < nb +end + # Issue #29213 function f_29213() while true @@ -729,8 +1053,8 @@ let end |> only |> first end - refs = map(Core.SSAValue, findall(x->x isa Expr && x.head == :new, src.code)) - some_ccall = findfirst(x -> x isa Expr && x.head == :foreigncall && x.args[1] == :(:some_ccall), src.code) + refs = map(Core.SSAValue, findall(@nospecialize(x)->Meta.isexpr(x, :new), src.code)) + some_ccall = findfirst(@nospecialize(x) -> Meta.isexpr(x, :foreigncall) && x.args[1] == :(:some_ccall), src.code) @assert some_ccall !== nothing stmt = src.code[some_ccall] nccallargs = length(stmt.args[3]::Core.SimpleVector) @@ -746,36 +1070,40 @@ end let # effect-freeness computation for array allocation # should eliminate dead allocations - good_dims = (0, 2) - for dim in good_dims, N in 0:10 + good_dims = @static Int === Int64 ? (1:10) : (1:8) + Ns = @static Int === Int64 ? (1:10) : (1:8) + for dim = good_dims, N = Ns dims = ntuple(i->dim, N) - @eval @test fully_eliminated(()) do + @test @eval fully_eliminated() do Array{Int,$N}(undef, $(dims...)) nothing end end - # shouldn't eliminate errorneous dead allocations - bad_dims = [-1, # should keep "invalid Array dimensions" - typemax(Int)] # should keep "invalid Array size" + # shouldn't eliminate erroneous dead allocations + bad_dims = [-1, typemax(Int)] for dim in bad_dims, N in 1:10 dims = ntuple(i->dim, N) - @eval @test !fully_eliminated(()) do + @test @eval !fully_eliminated() do + Array{Int,$N}(undef, $(dims...)) + nothing + end + @test_throws "invalid Array" @eval let Array{Int,$N}(undef, $(dims...)) nothing end end # some high-level examples - @test fully_eliminated(()) do + @test fully_eliminated() do Int[] nothing end - @test fully_eliminated(()) do + @test fully_eliminated() do Matrix{Tuple{String,String}}(undef, 4, 4) nothing end - @test fully_eliminated(()) do + @test fully_eliminated() do IdDict{Any,Any}() nothing end @@ -831,3 +1159,89 @@ let ci = code_typed(foo_cfg_empty, Tuple{Bool}, optimize=true)[1][1] @test length(ir.cfg.blocks) <= 2 @test isa(ir.stmts[length(ir.stmts)][:inst], ReturnNode) end + +@test Core.Compiler.is_effect_free(Base.infer_effects(getfield, (Complex{Int}, Symbol))) +@test Core.Compiler.is_effect_free(Base.infer_effects(getglobal, (Module, Symbol))) + +# Test that UseRefIterator gets SROA'd inside of new_to_regular (#44557) +# expression and new_to_regular offset are arbitrary here, we just want to see the UseRefIterator erased +let e = Expr(:call, Core.GlobalRef(Base, :arrayset), false, Core.SSAValue(4), Core.SSAValue(9), Core.SSAValue(8)) + new_to_reg(expr) = Core.Compiler.new_to_regular(expr, 1) + @allocated new_to_reg(e) # warmup call + @test (@allocated new_to_reg(e)) == 0 +end + +# Test that SROA doesn't try to forward a previous iteration's SSA value +let sroa_no_forward() = begin + res = (0, 0) + for i in 1:5 + a = first(res) + a == 5 && error() + if i == 1 + res = (i, 2.0) + end + end + return res + end + @test sroa_no_forward() == (1, 2.0) +end + +@noinline function foo_defined_last_iter(n::Int) + local x + for i = 1:n + if i == 5 + x = 1 + end + end + if n > 2 + return x + n + end + return 0 +end +const_call_defined_last_iter() = foo_defined_last_iter(3) +@test foo_defined_last_iter(2) == 0 +@test_throws UndefVarError foo_defined_last_iter(3) +@test_throws UndefVarError const_call_defined_last_iter() +@test foo_defined_last_iter(6) == 7 + +let src = code_typed1(foo_defined_last_iter, Tuple{Int}) + for i = 1:length(src.code) + e = src.code[i] + if isexpr(e, :throw_undef_if_not) + @assert !isa(e.args[2], Bool) + end + end +end + +# Issue #47180, incorrect phi counts in CmdRedirect +function a47180(b; stdout ) + c = setenv(b, b.env) + if true + c = pipeline(c, stdout) + end + c +end +@test isa(a47180(``; stdout), Base.AbstractCmd) + +# Test that _compute_sparams can be eliminated for NamedTuple +named_tuple_elim(name::Symbol, result) = NamedTuple{(name,)}(result) +let src = code_typed1(named_tuple_elim, Tuple{Symbol, Tuple}) + @test count(iscall((src, Core._compute_sparams)), src.code) == 0 && + count(iscall((src, Core._svec_ref)), src.code) == 0 && + count(iscall(x->!isa(argextype(x, src).val, Core.Builtin)), src.code) == 0 +end + +# Test that sroa works if the struct type is a PartialStruct +mutable struct OneConstField + const a::Int + b::Int +end + +@eval function one_const_field_partial() + # Use explicit :new here to avoid inlining messing with the type + strct = $(Expr(:new, OneConstField, 1, 2)) + strct.b = 4 + strct.b = 5 + return strct.b +end +@test fully_eliminated(one_const_field_partial; retval=5) diff --git a/test/compiler/irutils.jl b/test/compiler/irutils.jl index 06d261720bdf8..95ac0d555ef88 100644 --- a/test/compiler/irutils.jl +++ b/test/compiler/irutils.jl @@ -1,23 +1,29 @@ import Core: CodeInfo, ReturnNode, MethodInstance -import Core.Compiler: argextype, singleton_type +import Core.Compiler: IRCode, IncrementalCompact, VarState, argextype, singleton_type import Base.Meta: isexpr -argextype(@nospecialize args...) = argextype(args..., Any[]) +argextype(@nospecialize args...) = argextype(args..., VarState[]) code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::CodeInfo get_code(args...; kwargs...) = code_typed1(args...; kwargs...).code # check if `x` is a statement with a given `head` isnew(@nospecialize x) = isexpr(x, :new) +issplatnew(@nospecialize x) = isexpr(x, :splatnew) isreturn(@nospecialize x) = isa(x, ReturnNode) # check if `x` is a dynamic call of a given function iscall(y) = @nospecialize(x) -> iscall(y, x) -function iscall((src, f)::Tuple{CodeInfo,Base.Callable}, @nospecialize(x)) +function iscall((src, f)::Tuple{IR,Base.Callable}, @nospecialize(x)) where IR<:Union{CodeInfo,IRCode,IncrementalCompact} return iscall(x) do @nospecialize x singleton_type(argextype(x, src)) === f end end -iscall(pred::Base.Callable, @nospecialize(x)) = isexpr(x, :call) && pred(x.args[1]) +function iscall(pred::Base.Callable, @nospecialize(x)) + if isexpr(x, :(=)) + x = x.args[2] + end + return isexpr(x, :call) && pred(x.args[1]) +end # check if `x` is a statically-resolved call of a function whose name is `sym` isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) @@ -27,7 +33,14 @@ isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1 function fully_eliminated(@nospecialize args...; retval=(@__FILE__), kwargs...) code = code_typed1(args...; kwargs...).code if retval !== (@__FILE__) - return length(code) == 1 && isreturn(code[1]) && code[1].val == retval + length(code) == 1 || return false + code1 = code[1] + isreturn(code1) || return false + val = code1.val + if val isa QuoteNode + val = val.value + end + return val == retval else return length(code) == 1 && isreturn(code[1]) end diff --git a/test/compiler/newinterp.jl b/test/compiler/newinterp.jl new file mode 100644 index 0000000000000..56a68f2a09545 --- /dev/null +++ b/test/compiler/newinterp.jl @@ -0,0 +1,45 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + @newinterp NewInterpreter + +Defines new `NewInterpreter <: AbstractInterpreter` whose cache is separated +from the native code cache, satisfying the minimum interface requirements. +""" +macro newinterp(InterpName) + InterpCacheName = esc(Symbol(string(InterpName, "Cache"))) + InterpName = esc(InterpName) + C = Core + CC = Core.Compiler + quote + struct $InterpCacheName + dict::IdDict{$C.MethodInstance,$C.CodeInstance} + end + $InterpCacheName() = $InterpCacheName(IdDict{$C.MethodInstance,$C.CodeInstance}()) + 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 + 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) + 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_cache(interp::$InterpName) = interp.inf_cache + $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 diff --git a/test/compiler/ssair.jl b/test/compiler/ssair.jl index ffb48a9de38e9..43f17d4ad69f2 100644 --- a/test/compiler/ssair.jl +++ b/test/compiler/ssair.jl @@ -3,7 +3,9 @@ using Base.Meta using Core.IR const Compiler = Core.Compiler -using .Compiler: CFG, BasicBlock +using .Compiler: CFG, BasicBlock, NewSSAValue + +include(normpath(@__DIR__, "irutils.jl")) make_bb(preds, succs) = BasicBlock(Compiler.StmtRange(0, 0), preds, succs) @@ -36,7 +38,7 @@ end # false, false, false, false # )) # -# NullLineInfo = Core.LineInfoNode(Main, Symbol(""), Symbol(""), 0, 0) +# NullLineInfo = Core.LineInfoNode(Main, Symbol(""), Symbol(""), Int32(0), Int32(0)) # Compiler.run_passes(ci, 1, [NullLineInfo]) # # XXX: missing @test #end @@ -69,8 +71,10 @@ let cfg = CFG(BasicBlock[ ], Int[]) dfs = Compiler.DFS(cfg.blocks) @test dfs.from_pre[dfs.to_parent_pre[dfs.to_pre[5]]] == 4 - let correct_idoms = Compiler.naive_idoms(cfg.blocks) + 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 # 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). @@ -82,6 +86,7 @@ let cfg = CFG(BasicBlock[ 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 end end end @@ -101,15 +106,6 @@ for compile in ("min", "yes") end end -# Issue #27104 -# Test whether meta nodes are still present after code optimization. -let - @noinline f(x, y) = x + y - @test any(code_typed(f)[1][1].code) do ex - Meta.isexpr(ex, :meta) - end -end - # PR #32145 # Make sure IncrementalCompact can handle blocks with predecessors of index 0 # while removing blocks with no predecessors. @@ -121,9 +117,9 @@ let cfg = CFG(BasicBlock[ make_bb([2, 3] , [] ), ], Int[]) insts = Compiler.InstructionStream([], [], Any[], Int32[], UInt8[]) - code = Compiler.IRCode(insts, cfg, LineInfoNode[], [], [], []) - compact = Compiler.IncrementalCompact(code, true) - @test length(compact.result_bbs) == 4 && 0 in compact.result_bbs[3].preds + ir = Compiler.IRCode(insts, cfg, Core.LineInfoNode[], 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 # Issue #32579 - Optimizer bug involving type constraints @@ -168,7 +164,17 @@ let ci = make_ci([ ]) ir = Core.Compiler.inflate_ir(ci) ir = Core.Compiler.compact!(ir, true) - @test Core.Compiler.verify_ir(ir) == nothing + @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([ + Expr(:call, GlobalRef(Main, :cglobal), + Expr(:call, Core.tuple, :(:c)), Nothing), + Core.Compiler.ReturnNode() + ]) + ir = Core.Compiler.inflate_ir(ci) + @test Core.Compiler.verify_ir(ir) === nothing end # Test that GlobalRef in value position is non-canonical @@ -205,7 +211,7 @@ let ci = make_ci([ # come after it. for i in 1:length(ir.stmts) s = ir.stmts[i] - if isa(s, Expr) && s.head == :call && s.args[1] == :something + if Meta.isexpr(s, :call) && s.args[1] === :something if isa(s.args[2], SSAValue) @test s.args[2].id <= i end @@ -315,8 +321,8 @@ end f_if_typecheck() = (if nothing; end; unsafe_load(Ptr{Int}(0))) @test_throws TypeError f_if_typecheck() -@test let # https://github.com/JuliaLang/julia/issues/42258 - code = quote +let # https://github.com/JuliaLang/julia/issues/42258 + code = """ function foo() a = @noinline rand(rand(0:10)) if isempty(a) @@ -329,8 +335,283 @@ f_if_typecheck() = (if nothing; end; unsafe_load(Ptr{Int}(0))) code_typed(foo; optimize=true) code_typed(Core.Compiler.setindex!, (Core.Compiler.UseRef,Core.Compiler.NewSSAValue); optimize=true) - end |> string + """ cmd = `$(Base.julia_cmd()) -g 2 -e $code` - stderr = IOBuffer() - success(pipeline(Cmd(cmd); stdout=stdout, stderr=stderr)) && isempty(String(take!(stderr))) + stderr = Base.BufferStream() + @test success(pipeline(Cmd(cmd); stdout, stderr)) + @test readchomp(stderr) == "" +end + +@testset "code_ircode" begin + @test first(only(Base.code_ircode(+, (Float64, Float64)))) isa Compiler.IRCode + @test first(only(Base.code_ircode(+, (Float64, Float64); optimize_until = 3))) isa + Compiler.IRCode + @test first(only(Base.code_ircode(+, (Float64, Float64); optimize_until = "SROA"))) isa + Compiler.IRCode + + function demo(f) + f() + f() + f() + end + @test first(only(Base.code_ircode(demo))) isa Compiler.IRCode + @test first(only(Base.code_ircode(demo; optimize_until = 3))) isa Compiler.IRCode + @test first(only(Base.code_ircode(demo; optimize_until = "SROA"))) isa Compiler.IRCode +end + +# slots after SSA conversion +function f_with_slots(a, b) + # `c` and `d` are local variables + c = a + b + d = c > 0 + return (c, d) +end +let # #self#, a, b, c, d + unopt = code_typed1(f_with_slots, (Int,Int); optimize=false) + @test length(unopt.slotnames) == length(unopt.slotflags) == length(unopt.slottypes) == 5 + ir_withslots = first(only(Base.code_ircode(f_with_slots, (Int,Int); optimize_until="convert"))) + @test length(ir_withslots.argtypes) == 5 + # #self#, a, b + opt = code_typed1(f_with_slots, (Int,Int); optimize=true) + @test length(opt.slotnames) == length(opt.slotflags) == length(opt.slottypes) == 3 + ir_ssa = first(only(Base.code_ircode(f_with_slots, (Int,Int); optimize_until="slot2reg"))) + @test length(ir_ssa.argtypes) == 3 +end + +let + function test_useref(stmt, v, op) + if isa(stmt, Expr) + @test stmt.args[op] === v + elseif isa(stmt, GotoIfNot) + @test stmt.cond === v + elseif isa(stmt, ReturnNode) || isa(stmt, UpsilonNode) + @test stmt.val === v + elseif isa(stmt, SSAValue) || isa(stmt, NewSSAValue) + @test stmt === v + elseif isa(stmt, PiNode) + @test stmt.val === v && stmt.typ === typeof(stmt) + elseif isa(stmt, PhiNode) || isa(stmt, PhiCNode) + @test stmt.values[op] === v + end + end + + function _test_userefs(@nospecialize stmt) + ex = Expr(:call, :+, Core.SSAValue(3), 1) + urs = Core.Compiler.userefs(stmt)::Core.Compiler.UseRefIterator + it = Core.Compiler.iterate(urs) + while it !== nothing + ur = getfield(it, 1)::Core.Compiler.UseRef + op = getfield(it, 2)::Int + v1 = Core.Compiler.getindex(ur) + # set to dummy expression and then back to itself to test `_useref_setindex!` + v2 = Core.Compiler.setindex!(ur, ex) + test_useref(v2, ex, op) + Core.Compiler.setindex!(ur, v1) + @test Core.Compiler.getindex(ur) === v1 + it = Core.Compiler.iterate(urs, op) + end + end + + function test_userefs(body) + for stmt in body + _test_userefs(stmt) + end + end + + # this isn't valid code, we just care about looking at a variety of IR nodes + body = Any[ + Expr(:enter, 11), + Expr(:call, :+, SSAValue(3), 1), + Expr(:throw_undef_if_not, :expected, false), + Expr(:leave, 1), + Expr(:(=), SSAValue(1), Expr(:call, :+, SSAValue(3), 1)), + UpsilonNode(), + UpsilonNode(SSAValue(2)), + PhiCNode(Any[SSAValue(5), SSAValue(7), SSAValue(9)]), + PhiCNode(Any[SSAValue(6)]), + PhiNode(Int32[8], Any[SSAValue(7)]), + PiNode(SSAValue(6), GotoNode), + GotoIfNot(SSAValue(3), 10), + GotoNode(5), + SSAValue(7), + NewSSAValue(9), + ReturnNode(SSAValue(11)), + ] + + test_userefs(body) +end + +let ir = Base.code_ircode((Bool,Any)) do c, x + println(x, 1) #1 + if c + println(x, 2) #2 + else + println(x, 3) #3 + end + println(x, 4) #4 + end |> only |> first + # IR legality check + @test length(ir.cfg.blocks) == 4 + for i = 1:4 + @test any(ir.cfg.blocks[i].stmts) do j + inst = ir.stmts[j][:inst] + iscall((ir, println), inst) && + inst.args[3] == i + end + end + # domination analysis + domtree = Core.Compiler.construct_domtree(ir.cfg.blocks) + @test Core.Compiler.dominates(domtree, 1, 2) + @test Core.Compiler.dominates(domtree, 1, 3) + @test Core.Compiler.dominates(domtree, 1, 4) + for i = 2:4 + for j = 1:4 + i == j && continue + @test !Core.Compiler.dominates(domtree, i, j) + end + end + # post domination analysis + post_domtree = Core.Compiler.construct_postdomtree(ir.cfg.blocks) + @test Core.Compiler.postdominates(post_domtree, 4, 1) + @test Core.Compiler.postdominates(post_domtree, 4, 2) + @test Core.Compiler.postdominates(post_domtree, 4, 3) + for i = 1:3 + for j = 1:4 + i == j && continue + @test !Core.Compiler.postdominates(post_domtree, i, j) + end + end +end + +@testset "issue #46967: undef stmts introduced by compaction" begin + # generate some IR + function foo(i) + j = i+42 + j == 1 ? 1 : 2 + end + ir = only(Base.code_ircode(foo, (Int,)))[1] + instructions = length(ir.stmts) + + # get the addition instruction + add_stmt = ir.stmts[1] + @test Meta.isexpr(add_stmt[:inst], :call) && add_stmt[:inst].args[3] == 42 + + # replace the addition with a slightly different one + inst = Core.Compiler.NewInstruction(Expr(:call, add_stmt[:inst].args[1], add_stmt[:inst].args[2], 999), Int) + node = Core.Compiler.insert_node!(ir, 1, inst) + Core.Compiler.setindex!(add_stmt, node, :inst) + + # perform compaction (not by calling compact! because with DCE the bug doesn't trigger) + compact = Core.Compiler.IncrementalCompact(ir) + state = Core.Compiler.iterate(compact) + while state !== nothing + state = Core.Compiler.iterate(compact, state[2]) + end + ir = Core.Compiler.complete(compact) + + # test that the inserted node was compacted + @test Core.Compiler.length(ir.new_nodes) == 0 + + # test that we performed copy propagation, but that the undef node was trimmed + @test length(ir.stmts) == instructions + + @test show(devnull, ir) === nothing +end + +@testset "IncrementalCompact statefulness" begin + foo(i) = i == 1 ? 1 : 2 + ir = only(Base.code_ircode(foo, (Int,)))[1] + compact = Core.Compiler.IncrementalCompact(ir) + + # set up first iterator + x = Core.Compiler.iterate(compact) + x = Core.Compiler.iterate(compact, x[2]) + + # set up second iterator + x = Core.Compiler.iterate(compact) + + # consume remainder + while x !== nothing + x = Core.Compiler.iterate(compact, x[2]) + end + + ir = Core.Compiler.complete(compact) + @test Core.Compiler.verify_ir(ir) === nothing +end + +# insert_node! operations +# ======================= + +import Core: SSAValue +import Core.Compiler: NewInstruction, insert_node! + +# insert_node! for pending 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][:inst], :invoke) + + newssa = insert_node!(ir, SSAValue(1), NewInstruction(Expr(:call, println, SSAValue(1)), 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][:inst], :invoke) + call1 = ir.stmts[2][:inst] + @test iscall((ir,println), call1) + @test call1.args[2] === SSAValue(1) + call2 = ir.stmts[3][:inst] + @test iscall((ir,println), call2) + @test call2.args[2] === SSAValue(2) +end + +# insert_node! with new instruction with flag computed +let ir = Base.code_ircode((Int,Int); optimize_until="inlining") do a, b + a^b + end |> only |> first + invoke_idx = findfirst(ir.stmts.inst) do @nospecialize(x) + Meta.isexpr(x, :invoke) + end + @test invoke_idx !== nothing + invoke_expr = ir.stmts.inst[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) + 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.inst) do @nospecialize(x) + x == invoke_expr + end + @test new_invoke_idx !== nothing + new_call_idx = findfirst(ir.stmts.inst) do @nospecialize(x) + iscall((ir,println), x) && x.args[2] === SSAValue(invoke_idx) + end + @test new_call_idx !== nothing + @test new_call_idx == new_invoke_idx+1 + end + + # 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) + state = Core.Compiler.iterate(compact) + while state !== nothing + state = Core.Compiler.iterate(compact, state[2]) + end + ir = Core.Compiler.finish(compact) + + ir = Core.Compiler.finish(compact) + new_invoke_idx = findfirst(ir.stmts.inst) do @nospecialize(x) + x == invoke_expr + end + @test new_invoke_idx !== nothing + new_call_idx = findfirst(ir.stmts.inst) do @nospecialize(x) + iscall((ir,Base.add_int), x) && x.args[2] === SSAValue(invoke_idx) + end + @test new_call_idx === nothing # should be deleted during the compaction + end end diff --git a/test/compiler/validation.jl b/test/compiler/validation.jl index ffa79ed1c823d..5fd074fee73ae 100644 --- a/test/compiler/validation.jl +++ b/test/compiler/validation.jl @@ -20,12 +20,11 @@ end msig = Tuple{typeof(f22938),Int,Int,Int,Int} world = Base.get_world_counter() -match = Base._methods_by_ftype(msig, -1, world)[] +match = only(Base._methods_by_ftype(msig, -1, world)) mi = Core.Compiler.specialize_method(match) -c0 = Core.Compiler.retrieve_code_info(mi) +c0 = Core.Compiler.retrieve_code_info(mi, world) -@test isempty(Core.Compiler.validate_code(mi)) -@test isempty(Core.Compiler.validate_code(c0)) +@test isempty(Core.Compiler.validate_code(mi, c0)) @testset "INVALID_EXPR_HEAD" begin c = copy(c0) @@ -116,7 +115,7 @@ end @testset "SIGNATURE_NARGS_MISMATCH" begin old_sig = mi.def.sig mi.def.sig = Tuple{1,2} - errors = Core.Compiler.validate_code(mi) + errors = Core.Compiler.validate_code(mi, nothing) mi.def.sig = old_sig @test length(errors) == 1 @test errors[1].kind === Core.Compiler.SIGNATURE_NARGS_MISMATCH @@ -132,7 +131,7 @@ end @testset "SLOTNAMES_NARGS_MISMATCH" begin mi.def.nargs += 20 - errors = Core.Compiler.validate_code(mi) + errors = Core.Compiler.validate_code(mi, c0) mi.def.nargs -= 20 @test length(errors) == 2 @test count(e.kind === Core.Compiler.SLOTNAMES_NARGS_MISMATCH for e in errors) == 1 diff --git a/test/complex.jl b/test/complex.jl index 20470dd5617e7..2b87655f1ebe0 100644 --- a/test/complex.jl +++ b/test/complex.jl @@ -44,7 +44,12 @@ end @testset for T in (Float16, Float32, Float64, BigFloat) t = true f = false - + @testset "equality" begin + @test isequal(T(0.0)*im, T(0.0)) + @test !isequal(T(0.0)*im, T(-0.0)) + @test isequal(Complex(T(-0.0), T(0.0)), T(-0.0)) + @test !isequal(T(-0.0)*im, T(-0.0)) + end @testset "add and subtract" begin @test isequal(T(+0.0) + im, Complex(T(+0.0), T(+1.0))) @test isequal(T(-0.0) + im, Complex(T(-0.0), T(+1.0))) @@ -935,6 +940,7 @@ end @test cispi(0.0+0.0im) == cispi(0) @test cispi(1.0+0.0im) == cispi(1) @test cispi(2.0+0.0im) == cispi(2) + @test cispi(5im) ≈ exp(-5pi) rtol=1e-10 # https://github.com/JuliaLang/julia/pull/45945 end @testset "exp2" begin diff --git a/test/copy.jl b/test/copy.jl index 28d34e4756a6b..633beee5f2af3 100644 --- a/test/copy.jl +++ b/test/copy.jl @@ -58,11 +58,18 @@ end @test B == A end let A = reshape(1:6, 3, 2), B = zeros(8,8) - RA = CartesianIndices(axes(A)) - copyto!(B, CartesianIndices((5:7,2:3)), A, RA) - @test B[5:7,2:3] == A - B[5:7,2:3] .= 0 - @test all(x->x==0, B) + RBs = Any[(5:7,2:3), (3:2:7,1:2:3), (6:-1:4,2:-1:1)] + RAs = Any[axes(A), reverse.(axes(A))] + for RB in RBs, RA in RAs + copyto!(B, CartesianIndices(RB), A, CartesianIndices(RA)) + @test B[RB...] == A[RA...] + B[RB...] .= 0 + @test all(iszero, B) + end + end + let A = [reshape(1:6, 3, 2);;] + copyto!(A, CartesianIndices((2:3,2)), A, CartesianIndices((2,2))) + @test A[2:3,:] == [1 4;2 5] end end @@ -238,3 +245,26 @@ end @testset "deepcopy_internal arrays" begin @test (@inferred Base.deepcopy_internal(zeros(), IdDict())) == zeros() end + +@testset "`copyto!`'s unaliasing" begin + a = view([1:3;], :) + @test copyto!(a, 2, a, 1, 2) == [1;1:2;] + a = [1:3;] + @test copyto!(a, 2:3, 1:1, a, 1:2, 1:1) == [1;1:2;] +end + +@testset "`deepcopy` a `GenericCondition`" begin + a = Base.GenericCondition(ReentrantLock()) + @test !islocked(a.lock) + lock(a.lock) + @test islocked(a.lock) + b = deepcopy(a) + @test typeof(a) === typeof(b) + @test a != b + @test a !== b + @test typeof(a.lock) === typeof(b.lock) + @test a.lock != b.lock + @test a.lock !== b.lock + @test islocked(a.lock) + @test !islocked(b.lock) +end diff --git a/test/core.jl b/test/core.jl index 43d6da062560b..f71baa843d25f 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,17 +14,33 @@ 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]), - (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :pure, :is_for_opaque_closure, :constprop=#]), - (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals]=#]), + (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), + (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.TypeMapLevel, []), (Core.TypeName, [:name, :module, :names, :atomicfields, :constfields, :wrapper, :mt, :hash, :n_uninitialized, :flags]), (DataType, [:name, :super, :parameters, :instance, :hash]), + (TypeVar, [:name, :ub, :lb]), ) @test Set((fieldname(T, i) for i in 1:fieldcount(T) if isconst(T, i))) == Set(c) 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.MethodInstance, [:uninferred, :cache, :precompiled]), + (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), + (Core.TypeMapEntry, [:next]), + (Core.TypeMapLevel, [:arg1, :targ, :name1, :tname, :list, :any]), + (Core.TypeName, [:cache, :linearcache]), + (DataType, [:types, :layout]), + ) + @test Set((fieldname(T, i) for i in 1:fieldcount(T) if Base.isfieldatomic(T, i))) == Set(c) +end @test_throws(ErrorException("setfield!: const field .name of type DataType cannot be changed"), setfield!(Int, :name, Int.name)) @@ -41,14 +57,14 @@ mutable struct ABCDconst c const d::Union{Int,Nothing} end -@test_throws(ErrorException("invalid redefinition of constant ABCDconst"), +@test_throws(ErrorException("invalid redefinition of constant $(nameof(curmod)).ABCDconst"), mutable struct ABCDconst const a const b::Int c d::Union{Int,Nothing} end) -@test_throws(ErrorException("invalid redefinition of constant ABCDconst"), +@test_throws(ErrorException("invalid redefinition of constant $(nameof(curmod)).ABCDconst"), mutable struct ABCDconst a b::Int @@ -92,6 +108,20 @@ let abcd = ABCDconst(1, 2, 3, 4) @test (1, 2, "not constant", 4) === (abcd.a, abcd.b, abcd.c, abcd.d) end +# test `===` handling null pointer in struct #44712 +struct N44712 + a::Some{Any} + b::Int + N44712() = new() +end +let a = Int[0, 1], b = Int[0, 2] + GC.@preserve a b begin + @test unsafe_load(Ptr{N44712}(pointer(a))) !== unsafe_load(Ptr{N44712}(pointer(b))) + end +end + +# another possible issue in #44712 +@test (("", 0),) !== (("", 1),) f47(x::Vector{Vector{T}}) where {T} = 0 @test_throws MethodError f47(Vector{Vector}()) @@ -245,6 +275,30 @@ let mi = T26321{3,NTuple{3,Int}}((1,2,3)), mf = T26321{3,NTuple{3,Float64}}((1.0 @test a isa Vector{<:T26321{3}} end +@test Base.return_types() do + typejoin(Int, UInt) +end |> only == Type{typejoin(Int, UInt)} +@test Base.return_types() do + typejoin(Int, UInt, Float64) +end |> only == Type{typejoin(Int, UInt, Float64)} + +let res = @test_throws TypeError let + Base.Experimental.@force_compile + typejoin(1, 2) + nothing + end + err = res.value + @test err.func === :<: +end +let res = @test_throws TypeError let + Base.Experimental.@force_compile + typejoin(1, 2, 3) + nothing + end + err = res.value + @test err.func === :<: +end + # promote_typejoin returns a Union only with Nothing/Missing combined with concrete types for T in (Nothing, Missing) @test Base.promote_typejoin(Int, Float64) === Real @@ -323,6 +377,15 @@ end #struct S22624{A,B,C} <: Ref{S22624{Int64,A}}; end @test_broken @isdefined S22624 +# issue #42297 +mutable struct Node42297{T, V} + value::V + next::Union{Node42297{T, T}, Node42297{T, Val{T}}, Nothing} + Node42297{T}(value) where {T} = new{T, typeof(value)}(value, nothing) +end +@test fieldtype(Node42297{Int,Val{Int}}, 1) === Val{Int} +@test fieldtype(Node42297{Int,Int}, 1) === Int + # issue #3890 mutable struct A3890{T1} x::Matrix{Complex{T1}} @@ -651,14 +714,14 @@ end f21900_cnt = 0 function f21900() for i = 1:1 - x = 0 + x_global_undefined_error = 0 end global f21900_cnt += 1 - x # should be global + x_global_undefined_error # should be global global f21900_cnt += -1000 nothing end -@test_throws UndefVarError(:x) f21900() +@test_throws UndefVarError(:x_global_undefined_error) f21900() @test f21900_cnt == 1 # use @eval so this runs as a toplevel scope block @@ -741,11 +804,15 @@ let @test isassigned(a,1) && !isassigned(a,2) a = Vector{Float64}(undef,1) @test isassigned(a,1) + @test isassigned(a,1,1) @test isassigned(a) @test !isassigned(a,2) a = Array{Float64}(undef, 2, 2, 2) @test isassigned(a,1) - @test isassigned(a) + @test isassigned(a,8) + @test isassigned(a,2,2,2) + @test isassigned(a,2,2,2,1) + @test !isassigned(a) @test !isassigned(a,9) a = Array{Float64}(undef, 1) @test isassigned(a,1) @@ -753,15 +820,22 @@ let @test !isassigned(a,2) a = Array{Float64}(undef, 2, 2, 2, 2) @test isassigned(a,1) - @test isassigned(a) + @test isassigned(a,2,2,2,2) + @test isassigned(a,2,2,2,2,1) + @test isassigned(a,16) + @test !isassigned(a) @test !isassigned(a,17) + @test !isassigned(a,3,1,1,1) + @test !isassigned(a,1,3,1,1) + @test !isassigned(a,1,1,3,1) + @test !isassigned(a,1,1,1,3) end # isassigned, issue #11167 mutable struct Type11167{T,N} end function count11167() let cache = Type11167.body.body.name.cache - return sum(i -> isassigned(cache, i), 0:length(cache)) + return count(!isnothing, cache) end end @test count11167() == 0 @@ -1384,6 +1458,7 @@ let @test occursin("is not properly aligned to $(sizeof(Int)) bytes", res.value.msg) res = @test_throws ArgumentError unsafe_wrap(Array, pointer(a) + 1, (1, 1)) @test occursin("is not properly aligned to $(sizeof(Int)) bytes", res.value.msg) + res = @test_throws MethodError unsafe_wrap(Vector{UInt8}, pointer(Int32[1]), (sizeof(Int32),)) end struct FooBar2515 @@ -1524,6 +1599,12 @@ let @test invoke(i2169, Tuple{Array}, Int8[1]) === Int8(-128) end +# issue #44227 +struct F{T} end +F{Int32}(; y=1) = 1 +F{Int64}(; y=1) = invoke(F{Int32}, Tuple{}; y) +@test F{Int64}() === 1 + # issue #2365 mutable struct B2365{T} v::Union{T, Nothing} @@ -1613,7 +1694,9 @@ end # issue #3221 let x = fill(nothing, 1) - @test_throws MethodError x[1] = 1 + @test_throws ErrorException("cannot convert a value to nothing for assignment") x[1] = 1 + x = Vector{Union{}}(undef, 1) + @test_throws ArgumentError("cannot convert a value to Union{} for assignment") x[1] = 1 end # issue #3220 @@ -1958,9 +2041,8 @@ mutable struct TupleParam{P} x::Bool end -function tupledispatch(a::TupleParam{(1,:a)}) - a.x -end +tupledispatch(a::TupleParam{(1,:a)}) = a.x +tupledispatch(a::TupleParam{(1,(:a,))}) = 42 # tuples can be used as type params let t1 = TupleParam{(1,:a)}(true), @@ -1972,6 +2054,10 @@ let t1 = TupleParam{(1,:a)}(true), # dispatch works properly @test tupledispatch(t1) == true @test_throws MethodError tupledispatch(t2) + + @test tupledispatch(TupleParam{(1,(:a,))}(true)) === 42 + @test_throws TypeError TupleParam{NamedTuple{(:a,), Tuple{Any}}((1,))} + @test_throws TypeError Val{NamedTuple{(:a,), Tuple{NamedTuple{<:Any,Tuple{Int}}}}(((x=2,),))} end # issue #5254 @@ -2809,10 +2895,10 @@ let f end end for m in methods(f10373) - @test m.name == :f10373 + @test m.name === :f10373 end for m in methods(g10373) - @test m.name == :g10373 + @test m.name === :g10373 end # issue #7221 @@ -3578,7 +3664,7 @@ let @test false catch err @test isa(err, TypeError) - @test err.func == :Vararg + @test err.func === :Vararg @test err.expected == Int @test err.got == Int end @@ -3588,7 +3674,7 @@ let @test false catch err @test isa(err, TypeError) - @test err.func == :Vararg + @test err.func === :Vararg @test err.expected == Int @test err.got == 0x1 end @@ -3799,7 +3885,8 @@ PossiblyInvalidUnion{T} = Union{T,Int} # issue #13007 call13007(::Type{Array{T,N}}) where {T,N} = 0 call13007(::Type{Array}) = 1 -@test length(Base._methods(call13007, Tuple{Type{x} where x<:Array}, 4, typemax(UInt))) == 2 +@test Base._methods(call13007, Tuple{Type{x} where x<:Array}, 4, typemax(UInt)) === nothing +@test length(Base._methods(call13007, Tuple{Type{x} where x<:Array}, 4, Base.get_world_counter())) == 2 # detecting cycles during type intersection, e.g. #1631 cycle_in_solve_tvar_constraints(::Type{Some{S}}, x::S) where {S} = 0 @@ -4200,7 +4287,7 @@ end let ex = quote $(if true; :(test); end) end - @test ex.args[2] == :test + @test ex.args[2] === :test end # issue #15180 @@ -4328,6 +4415,30 @@ let gc_enabled13995 = GC.enable(false) GC.enable(gc_enabled13995) end +# Ensure an independent GC frame +@noinline outlined(f) = f() + +@testset "finalizers must not change the sticky flag" begin + GC.enable(false) + try + outlined() do + local obj = Ref(0) + finalizer(obj) do _ + @async nothing + end + Base.donotdelete(obj) + end + task = Threads.@spawn begin + GC.enable(true) + GC.gc() + end + wait(task) + @test !task.sticky + finally + GC.enable(true) + end +end + # issue #15283 j15283 = 0 let @@ -4802,12 +4913,19 @@ let a = Any[] @test a == [10, 2] end +# issue 47209 +struct f47209 + x::Int + f47209()::Nothing = new(1) +end +@test_throws ErrorException("cannot convert a value to nothing for assignment") f47209() + # issue #12096 let a = Val{Val{TypeVar(:_, Int)}}, b = Val{Val{x} where x<:Int} - @test !isdefined(a, :instance) - @test isdefined(b, :instance) + @test !Base.issingletontype(a) + @test Base.issingletontype(b) @test Base.isconcretetype(b) end @@ -5152,10 +5270,10 @@ end GC.enable(true) # issue #18710 -bad_tvars() where {T} = 1 +@test_warn "declares type variable T but does not use it" @eval bad_tvars() where {T} = 1 @test isa(which(bad_tvars, ()), Method) @test bad_tvars() === 1 -bad_tvars2() where {T} = T +@test_warn "declares type variable T but does not use it" @eval bad_tvars2() where {T} = T @test_throws UndefVarError(:T) bad_tvars2() missing_tvar(::T...) where {T} = T @test_throws UndefVarError(:T) missing_tvar() @@ -5872,7 +5990,7 @@ module GlobalDef18933 global sincos nothing end - @test which(Main, :sincos) === Base.Math + @test which(@__MODULE__, :sincos) === Base.Math @test @isdefined sincos @test sincos === Base.sincos end @@ -6857,9 +6975,9 @@ g27209(x) = f27209(x ? nothing : 1.0) # Issue 27240 @inline function foo27240() if rand(Bool) - return foo_nonexistant_27240 + return foo_nonexistent_27240 else - return bar_nonexistant_27240 + return bar_nonexistent_27240 end end bar27240() = foo27240() @@ -7211,11 +7329,11 @@ struct sparse_t31649 end Base.convert(::Any, v::sparse_t31649) = copy(v.val) let spvec = sparse_t31649(zeros(Float64,5), Vector{Int64}()) - @test_throws MethodError repr(spvec) + @test_throws MethodError convert(Any, spvec) # Try manually putting the problematic method into the cache (in # the original issue compiling the showerror method caused this to happen) @test convert(Any, nothing) === nothing - @test_throws MethodError repr(spvec) + @test_throws MethodError convert(Any, spvec) end # Issue #31062 - Accidental recursion in jl_has_concrete_subtype @@ -7255,16 +7373,20 @@ end 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[3] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(2), Core.SlotNumber(2)) - @test code[4] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 2) - @test code[5] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(4), Core.SlotNumber(3)) - @test code[6] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 4) - @test code[7] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(6), Core.SlotNumber(5)) - @test code[8] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 5) - @test code[9] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(8), Core.SlotNumber(6)) - @test code[10] == Expr(:new, Core.SSAValue(1), Core.SSAValue(3), Core.SSAValue(5), Core.SlotNumber(4), Core.SSAValue(7), Core.SSAValue(9)) - @test code[11] == Core.ReturnNode(Core.SSAValue(10)) - end + @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)) +end # Issue #32820 function f32820(refs) @@ -7324,6 +7446,35 @@ end @test isbitstype(X41654) @test ('a'=>X41654(),)[1][2] isa X41654 +# issue #43411 +struct A43411{S, T} + x::NamedTuple{S, T} +end +@test isbitstype(A43411{(:a,), Tuple{Int}}) + +# issue #44614 +struct T44614_1{T} + m::T +end +struct T44614_2{L} + tuple::NTuple{3, Int64} + T44614_2{L}(t::NTuple{3, Int64}) where {L} = new{sum(t)}(t) +end +struct T44614_3{L, N} + a::Tuple{T44614_2{L}} + param::NTuple{N, T44614_1} + T44614_3(a::Tuple{T44614_2{L}}, pars::NTuple{N, T44614_1}) where {L, N} = new{L, N}(a, pars) +end +@test sizeof((T44614_2{L} where L).body) == 24 +let T = T44614_3{L,2} where L + # these values are computable, but we currently don't know how to compute them properly + ex = ErrorException("Argument is an incomplete T44614_3 type and does not have a definite size.") + @test_throws ex sizeof(T.body) + @test_throws ex sizeof(T) + @test_throws BoundsError fieldoffset(T.body, 2) + @test fieldoffset(T{1}, 2) == 24 +end + # Issue #34206/34207 function mre34206(a, n) va = view(a, :) @@ -7415,7 +7566,7 @@ end struct X36104; x::Int; end @test fieldtypes(X36104) == (Int,) primitive type P36104 8 end -@test_throws ErrorException("invalid redefinition of constant P36104") @eval(primitive type P36104 16 end) +@test_throws ErrorException("invalid redefinition of constant $(nameof(curmod)).P36104") @eval(primitive type P36104 16 end) # Malformed invoke f_bad_invoke(x::Int) = invoke(x, (Any,), x) @@ -7698,3 +7849,164 @@ end @test a == 1 @test b == Core.svec(2, 3) end + +@testset "setproperty! on modules" begin + m = Module() + @eval m global x::Int + + setglobal!(m, :x, 1) + @test m.x === 1 + setglobal!(m, :x, 2, :release) + @test m.x === 2 + @test_throws ConcurrencyViolationError setglobal!(m, :x, 3, :not_atomic) + @test_throws ErrorException setglobal!(m, :x, 4., :release) + + m.x = 1 + @test m.x === 1 + setproperty!(m, :x, 2, :release) + @test m.x === 2 + @test_throws ConcurrencyViolationError setproperty!(m, :x, 3, :not_atomic) + m.x = 4. + @test m.x === 4 +end + +# #45350 - Codegen for assignment to binding imported from module +module Foo45350 + global x45350::Int = 1 +end +import .Foo45350: x45350 +f45350() = (global x45350 = 2) +@test_throws ErrorException f45350() + +@testset "Error behavior of unsafe_convert for RefValue" begin + b = Base.RefValue{Int}() + @test Base.unsafe_convert(Ptr{Int}, b) !== C_NULL + b = Base.RefValue{Base.RefValue{Int}}() + # throws because we hit `b.x` + @test_throws Core.UndefRefError Base.unsafe_convert(Ptr{Base.RefValue{Int}}, b) + # throws because we hit `b.x` + b = Base.RefValue{Integer}() + @test_throws Core.UndefRefError Base.unsafe_convert(Ptr{Integer}, b) +end + +# #46503 - redefine `invoke`d methods +foo46503(@nospecialize(a), b::Union{Vector{Any}, Float64, Nothing}) = rand() +foo46503(a::Int, b::Nothing) = @invoke foo46503(a::Any, b) +@test 0 <= foo46503(1, nothing) <= 1 +foo46503(@nospecialize(a), b::Union{Nothing, Float64}) = rand() + 10 +@test 10 <= foo46503(1, nothing) <= 11 + +@testset "effect override on Symbol(::String)" begin + @test Core.Compiler.is_foldable(Base.infer_effects(Symbol, (String,))) +end + +@testset "error message for getfield with bad integer type" begin + @test_throws "expected Union{$Int, Symbol}" getfield((1,2), Int8(1)) +end + +# Correct isdefined error for isdefined of Module of Int fld +f_isdefined_one(@nospecialize(x)) = isdefined(x, 1) +@test (try; f_isdefined_one(@__MODULE__); catch err; err; end).got === 1 + +# Unspecialized retrieval of vararg length +fvarargN(x::Tuple{Vararg{Int, N}}) where {N} = N +fvarargN(args...) = fvarargN(args) +finvokevarargN() = Base.inferencebarrier(fvarargN)(1, 2, 3) +@test finvokevarargN() == 3 + +# Make sure that @specialize actually overrides a module annotation +module SpecializeModuleTest + @nospecialize + f(@specialize(x), y) = 2 + @specialize +end +@test methods(SpecializeModuleTest.f)[1].nospecialize & 0b11 == 0b10 + +let # https://github.com/JuliaLang/julia/issues/46918 + # jl_get_binding_type shouldn't be unstable + code = quote + res1 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) + + stderr + + res2 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) + + res3 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) + + print(stdout, res1, " ", res2, " ", res3) + end |> x->join(x.args, ';') + cmd = `$(Base.julia_cmd()) -e $code` # N.B make sure not to pass this code as `:block` + stdout = IOBuffer() + stderr = IOBuffer() + @test success(pipeline(Cmd(cmd); stdout, stderr)) + @test isempty(String(take!(stderr))) # make sure no error has happened + @test String(take!(stdout)) == "nothing IO IO" +end + +# Modules allowed as type parameters and usable in generated functions +module ModTparamTest + foo_test_mod_tparam() = 1 +end +foo_test_mod_tparam() = 2 + +struct ModTparamTestStruct{M}; end +@generated function ModTparamTestStruct{M}() where {M} + return :($(GlobalRef(M, :foo_test_mod_tparam))()) +end +@test ModTparamTestStruct{@__MODULE__}() == 2 +@test ModTparamTestStruct{ModTparamTest}() == 1 + +# issue #47476 +f47476(::Union{Int, NTuple{N,Int}}...) where {N} = N +# force it to populate the MethodInstance specializations cache +# with the correct sparams +code_typed(f47476, (Vararg{Union{Int, NTuple{2,Int}}},)); +code_typed(f47476, (Int, Vararg{Union{Int, NTuple{2,Int}}},)); +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) + +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 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) + @test any(mi -> mi !== nothing && Base.isvatuple(mi.specTypes), spec) + @test all(mi -> mi === nothing || !Base.has_free_typevars(mi.specTypes), spec) +end + +f48950(::Union{Int,d}, ::Union{c,Nothing}...) where {c,d} = 1 +@test f48950(1, 1, 1) == 1 + +# Module as tparam in unionall +struct ModTParamUnionAll{A, B}; end +@test isa(objectid(ModTParamUnionAll{Base}), UInt) + +# effects for objectid +for T in (Int, String, Symbol, Module) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (T,))) + @test Core.Compiler.is_foldable(Base.infer_effects(hash, (T,))) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Some{T},))) + @test Core.Compiler.is_foldable(Base.infer_effects(hash, (Some{T},))) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Some{Some{T}},))) + @test Core.Compiler.is_foldable(Base.infer_effects(hash, (Some{Some{T}},))) + @test Core.Compiler.is_foldable(Base.infer_effects(objectid, (Tuple{T},))) + @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},))) +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}},))) + +# donotdelete should not taint consistency of the containing function +f_donotdete(x) = (Core.Compiler.donotdelete(x); 1) +@test Core.Compiler.is_consistent(Base.infer_effects(f_donotdete, (Tuple{Float64},))) diff --git a/test/corelogging.jl b/test/corelogging.jl index 9c5102d848013..9626f48e4b407 100644 --- a/test/corelogging.jl +++ b/test/corelogging.jl @@ -1,5 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Make a copy of the original environment +original_env = copy(ENV) + using Test, Base.CoreLogging import Base.CoreLogging: BelowMinLevel, Debug, Info, Warn, Error, handle_message, shouldlog, min_enabled_level, catch_exceptions @@ -120,8 +123,8 @@ end @test length(logger.logs) == 1 record = logger.logs[1] @test record._module == Base.Core - @test record.group == :somegroup - @test record.id == :asdf + @test record.group === :somegroup + @test record.id === :asdf @test record.file == "/a/file" @test record.line == -10 # Test consistency with shouldlog() function arguments @@ -151,6 +154,21 @@ end @test_throws MethodError @macrocall(@error) end +@testset "Any type" begin + @test_logs (:info, sum) @info sum + # TODO: make this work (here we want `@test_logs` to fail) + # @test_fails @test_logs (:info, "sum") @info sum # `sum` works, `"sum"` does not + + # check that the message delivered to the user works + mktempdir() do dir + path_stdout = joinpath(dir, "stdout.txt") + path_stderr = joinpath(dir, "stderr.txt") + redirect_stdio(stdout=path_stdout, stderr=path_stderr) do + @info sum + end + @test occursin("Info: sum", read(path_stderr, String)) + end +end #------------------------------------------------------------------------------- # Early log level filtering @@ -420,11 +438,32 @@ end (record,), _ = collect_test_logs() do @info "test" end - @test record.group == :corelogging # name of this file + @test record.group === :corelogging # name of this file end @testset "complicated kwargs logging macro" begin @test_logs (:warn, "foo") @warn "foo" argvals=:((DoNotCare{$(Expr(:escape, :Any))}(),)) end +@testset "stdlib path" begin + logger = TestLogger() + with_logger(logger) do + @info "foo" _file=joinpath(Sys.BUILD_STDLIB_PATH, "InteractiveUtils", "src", "InteractiveUtils.jl") + end + logs = logger.logs + @test length(logs) == 1 + record = logs[1] + @test isfile(record.file) +end + +end + +# Restore the original environment +for k in keys(ENV) + if !haskey(original_env, k) + delete!(ENV, k) + end +end +for (k, v) in pairs(original_env) + ENV[k] = v end diff --git a/test/deprecation_exec.jl b/test/deprecation_exec.jl index efbb251daa1e0..5b465e05f0a12 100644 --- a/test/deprecation_exec.jl +++ b/test/deprecation_exec.jl @@ -26,9 +26,21 @@ module DeprecationTests # to test @deprecate struct A{T} end @deprecate A{T}(x::S) where {T, S} f() + module Sub + f1() = true + function f2 end + end + @deprecate Sub.f1() f() false + @deprecate Sub.f2 f false + # test that @deprecate_moved can be overridden by an import Base.@deprecate_moved foo1234 "Foo" Base.@deprecate_moved bar "Bar" false + + # test that positional and keyword arguments are forwarded when + # there is no explicit type annotation + new_return_args(args...; kwargs...) = args, NamedTuple(kwargs) + @deprecate old_return_args new_return_args end # module module Foo1234 export foo1234 @@ -43,7 +55,12 @@ struct T21972 end end -@testset "@deprecate" begin +# Create a consistent call frame for nowarn tests +@noinline call(f, args...) = @noinline f(args...) + +# Given this is a sub-processed test file, not using @testsets avoids +# leaking the report print into the Base test runner report +begin # @deprecate using .DeprecationTests using .Foo1234 @test foo1234(3) == 4 @@ -55,39 +72,59 @@ end @test_warn "importing deprecated binding" eval(ex) @test @test_nowarn(DeprecationTests.bar(4)) == 7 - # enable when issue #22043 is fixed - # @test @test_warn "f1 is deprecated, use f instead." f1() - # @test @test_nowarn f1() + @test @test_warn "`f1` is deprecated, use `f` instead." f1() + + @test_throws UndefVarError f2() # not exported + @test @test_warn "`f2` is deprecated, use `f` instead." DeprecationTests.f2() - # @test_throws UndefVarError f2() # not exported - # @test @test_warn "f2 is deprecated, use f instead." DeprecationTests.f2() - # @test @test_nowarn DeprecationTests.f2() + @test @test_warn "`f3()` is deprecated, use `f()` instead." f3() - # @test @test_warn "f3() is deprecated, use f() instead." f3() - # @test @test_nowarn f3() + @test_throws UndefVarError f4() # not exported + @test @test_warn "`f4()` is deprecated, use `f()` instead." DeprecationTests.f4() - # @test_throws UndefVarError f4() # not exported - # @test @test_warn "f4() is deprecated, use f() instead." DeprecationTests.f4() - # @test @test_nowarn DeprecationTests.f4() + @test @test_warn "`f5(x::T) where T` is deprecated, use `f()` instead." f5(1) - # @test @test_warn "f5(x::T) where T is deprecated, use f() instead." f5(1) - # @test @test_nowarn f5(1) + @test @test_warn "`A{T}(x::S) where {T, S}` is deprecated, use `f()` instead." A{Int}(1.) - # @test @test_warn "A{T}(x::S) where {T, S} is deprecated, use f() instead." A{Int}(1.) - # @test @test_nowarn A{Int}(1.) + @test @test_warn "`Sub.f1()` is deprecated, use `f()` instead." DeprecationTests.Sub.f1() + + redirect_stderr(devnull) do + @test call(f1) + @test call(DeprecationTests.f2) + @test call(f3) + @test call(DeprecationTests.f4) + @test call(f5, 1) + @test call(A{Int}, 1.) + @test call(DeprecationTests.Sub.f1) + @test call(DeprecationTests.Sub.f2) + end + + @test @test_nowarn call(f1) + @test @test_nowarn call(DeprecationTests.f2) + @test @test_nowarn call(f3) + @test @test_nowarn call(DeprecationTests.f4) + @test @test_nowarn call(f5, 1) + @test @test_nowarn call(A{Int}, 1.) + @test @test_nowarn call(DeprecationTests.Sub.f1) + @test @test_nowarn call(DeprecationTests.Sub.f2) # issue #21972 @noinline function f21972() T21972() end @test_deprecated "something" f21972() + + # test that positional and keyword arguments are forwarded when + # there is no explicit type annotation + @test_logs (:warn,) @test DeprecationTests.old_return_args(1, 2, 3) == ((1, 2, 3),(;)) + @test_logs (:warn,) @test DeprecationTests.old_return_args(1, 2, 3; a = 4, b = 5) == ((1, 2, 3), (a = 4, b = 5)) end f24658() = depwarn24658() depwarn24658() = Base.firstcaller(backtrace(), :_func_not_found_) -@testset "firstcaller" begin +begin # firstcaller # issue #24658 @test eval(:(if true; f24658(); end)) == (Ptr{Cvoid}(0),StackTraces.UNKNOWN) end @@ -105,7 +142,7 @@ f25130() testlogs = testlogger.logs @test length(testlogs) == 2 @test testlogs[1].id != testlogs[2].id -@test testlogs[1].kwargs[:caller].func == Symbol("top-level scope") +@test testlogs[1].kwargs[:caller].func === Symbol("top-level scope") @test all(l.message == "f25130 message" for l in testlogs) global_logger(prev_logger) @@ -113,7 +150,7 @@ global_logger(prev_logger) #------------------------------------------------------------------------------- # BEGIN 0.7 deprecations -@testset "parser syntax deprecations" begin +begin # parser syntax deprecations # #15524 # @test (@test_deprecated Meta.parse("for a=b f() end")) == :(for a=b; f() end) @test_broken length(Test.collect_test_logs(()->Meta.parse("for a=b f() end"))[1]) > 0 @@ -121,10 +158,32 @@ end # END 0.7 deprecations -@testset "tuple indexed by float deprecation" begin +begin # tuple indexed by float deprecation @test_deprecated getindex((1,), 1.0) === 1 @test_deprecated getindex((1,2), 2.0) === 2 - @test_throws Exception getindex((), 1.0) - @test_throws Exception getindex((1,2), 0.0) - @test_throws Exception getindex((1,2), -1.0) + @test Base.JLOptions().depwarn == 1 + @test_throws Exception @test_warn r"`getindex(t::Tuple, i::Real)` is deprecated" getindex((), 1.0) + @test_throws Exception @test_warn r"`getindex(t::Tuple, i::Real)` is deprecated" getindex((1,2), 0.0) + @test_throws Exception @test_warn r"`getindex(t::Tuple, i::Real)` is deprecated" getindex((1,2), -1.0) +end + +begin #@deprecated error message + @test_throws( + "if the third `export_old` argument is not specified or `true`,", + @eval @deprecate M.f() g() + ) + @test_throws( + "if the third `export_old` argument is not specified or `true`,", + @eval @deprecate M.f() g() true + ) + + # Given `@deprecated Old{T} where {...} new`, it is unclear if we should generate + # `Old{T}(args...) where {...} = new(args...)` or + # `(Old{T} where {...})(args...) = new(args...)`. + # Since nobody has requested this feature yet, make sure that it throws, until we + # consciously define + @test_throws( + "invalid usage of @deprecate", + @eval @deprecate Foo{T} where {T <: Int} g true + ) end diff --git a/test/dict.jl b/test/dict.jl index cbbb475c993fd..6a47c3c6eea8b 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -164,6 +164,11 @@ end @test Dict(t[1]=>t[2] for t in zip((1,"2"), (2,"2"))) == Dict{Any,Any}(1=>2, "2"=>"2") end +@testset "empty tuple ctor" begin + h = Dict(()) + @test length(h) == 0 +end + @testset "type of Dict constructed from varargs of Pairs" begin @test Dict(1=>1, 2=>2.0) isa Dict{Int,Real} @test Dict(1=>1, 2.0=>2) isa Dict{Real,Int} @@ -191,7 +196,7 @@ end bestkey(d, key) = key bestkey(d::AbstractDict{K,V}, key) where {K<:AbstractString,V} = string(key) bar(x) = bestkey(x, :y) - @test bar(Dict(:x => [1,2,5])) == :y + @test bar(Dict(:x => [1,2,5])) === :y @test bar(Dict("x" => [1,2,5])) == "y" end @@ -363,6 +368,110 @@ end close(io) end + +struct RainbowString + s::String + bold::Bool + other::Bool + valid::Bool + offset::Int +end +RainbowString(s, bold=false, other=false, valid=true) = RainbowString(s, bold, other, valid, 0) + +function Base.show(io::IO, rbs::RainbowString) + for (i, s) in enumerate(rbs.s) + if i ≤ rbs.offset + print(io, s) + continue + end + color = rbs.other ? string("\033[4", rand(1:7), 'm') : Base.text_colors[rand(0:255)] + if rbs.bold + printstyled(io, color, s; bold=true) + else + print(io, color, s) + end + if rbs.valid + print(io, '\033', '[', rbs.other ? "0" : "39", 'm') # end of color marker + end + end +end + +@testset "Display with colors" begin + d = Dict([randstring(8) => [RainbowString(randstring(8)) for i in 1:10] for j in 1:5]...) + str = sprint(io -> show(io, MIME("text/plain"), d); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + lines = split(str, '\n') + @test all(endswith("\033[0m…"), lines[2:end]) + @test all(x -> length(x) > 100, lines[2:end]) + + d2 = Dict(:foo => RainbowString("bar")) + str2 = sprint(io -> show(io, MIME("text/plain"), d2); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + @test !occursin('…', str2) + @test endswith(str2, "\033[0m") + + d3 = Dict(:foo => RainbowString("bar", true)) + str3 = sprint(io -> show(io, MIME("text/plain"), d3); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + @test !occursin('…', str3) + @test endswith(str3, "\033[0m") + + d4 = Dict(RainbowString(randstring(8), true) => nothing) + str4 = sprint(io -> show(io, MIME("text/plain"), d4); context = (:displaysize=>(30,20), :color=>true, :limit=>true)) + @test endswith(str4, "\033[0m… => nothing") + + d5 = Dict(RainbowString(randstring(30), false, true, false) => nothing) + str5 = sprint(io -> show(io, MIME("text/plain"), d5); context = (:displaysize=>(30,30), :color=>true, :limit=>true)) + @test endswith(str5, "\033[0m… => nothing") + + d6 = Dict(randstring(8) => RainbowString(randstring(30), true, true, false) for _ in 1:3) + str6 = sprint(io -> show(io, MIME("text/plain"), d6); context = (:displaysize=>(30,30), :color=>true, :limit=>true)) + lines6 = split(str6, '\n') + @test all(endswith("\033[0m…"), lines6[2:end]) + @test all(x -> length(x) > 100, lines6[2:end]) + str6_long = sprint(io -> show(io, MIME("text/plain"), d6); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + lines6_long = split(str6_long, '\n') + @test all(endswith("\033[0m"), lines6_long[2:end]) + + d7 = Dict(randstring(8) => RainbowString(randstring(30))) + str7 = sprint(io -> show(io, MIME("text/plain"), d7); context = (:displaysize=>(30,20), :color=>true, :limit=>true)) + line7 = split(str7, '\n')[2] + @test endswith(line7, "\033[0m…") + @test length(line7) > 100 + + d8 = Dict(:x => RainbowString(randstring(10), false, false, false, 6)) + str8 = sprint(io -> show(io, MIME("text/plain"), d8); context = (:displaysize=>(30,14), :color=>true, :limit=>true)) + line8 = split(str8, '\n')[2] + @test !occursin("\033[", line8) + @test length(line8) == 14 + str8_long = sprint(io -> show(io, MIME("text/plain"), d8); context = (:displaysize=>(30,16), :color=>true, :limit=>true)) + line8_long = split(str8_long, '\n')[2] + @test endswith(line8_long, "\033[0m…") + @test length(line8_long) > 20 + + d9 = Dict(:x => RainbowString(repeat('苹', 5), false, true, false)) + str9 = sprint(io -> show(io, MIME("text/plain"), d9); context = (:displaysize=>(30,15), :color=>true, :limit=>true)) + @test endswith(str9, "\033[0m…") + @test count('苹', str9) == 3 + + d10 = Dict(:xy => RainbowString(repeat('苹', 5), false, true, false)) + str10 = sprint(io -> show(io, MIME("text/plain"), d10); context = (:displaysize=>(30,15), :color=>true, :limit=>true)) + @test endswith(str10, "\033[0m…") + @test count('苹', str10) == 2 + + d11 = Dict(RainbowString("abcdefgh", false, true, false) => 0, "123456" => 1) + str11 = sprint(io -> show(io, MIME("text/plain"), d11); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + _, line11_a, line11_b = split(str11, '\n') + @test endswith(line11_a, "h\033[0m => 0") || endswith(line11_b, "h\033[0m => 0") + @test endswith(line11_a, "6\" => 1") || endswith(line11_b, "6\" => 1") + + d12 = Dict(RainbowString(repeat(Char(48+i), 4), (i&1)==1, (i&2)==2, (i&4)==4) => i for i in 1:8) + str12 = sprint(io -> show(io, MIME("text/plain"), d12); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + @test !occursin('…', str12) + + d13 = Dict(RainbowString("foo\nbar") => 74) + str13 = sprint(io -> show(io, MIME("text/plain"), d13); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) + @test count('\n', str13) == 1 + @test occursin('…', str13) +end + @testset "Issue #15739" begin # Compact REPL printouts of an `AbstractDict` use brackets when appropriate d = Dict((1=>2) => (3=>45), (3=>10) => (10=>11)) buf = IOBuffer() @@ -1125,7 +1234,7 @@ end @test isempty(findall(isequal(1), Dict())) @test isempty(findall(isequal(1), Dict(:a=>2, :b=>3))) - @test findfirst(isequal(1), Dict(:a=>1, :b=>2)) == :a + @test findfirst(isequal(1), Dict(:a=>1, :b=>2)) === :a @test findfirst(isequal(1), Dict(:a=>1, :b=>1, :c=>3)) in (:a, :b) @test findfirst(isequal(1), Dict()) === nothing @test findfirst(isequal(1), Dict(:a=>2, :b=>3)) === nothing @@ -1179,6 +1288,8 @@ end @test s === copy!(s, Base.ImmutableDict(a[])) == Dict(a[]) end end + s2 = copy(s) + @test copy!(s, s) == s2 end @testset "map!(f, values(dict))" begin @@ -1197,6 +1308,7 @@ end map!(v->v-1, values(testdict)) @test testdict[:a] == 0 @test testdict[:b] == 1 + @test sizehint!(testdict, 1) === testdict end @testset "Dict" begin testdict = Dict(:a=>1, :b=>2) @@ -1244,3 +1356,18 @@ end let c = bar() @test c === missing || c == ComparesWithGC38727(1) end + +@testset "shrinking" begin + d = Dict(i => i for i = 1:1000) + filter!(x -> x.first < 10, d) + sizehint!(d, 10) + @test length(d.slots) < 100 +end + +# getindex is :effect_free and :terminates but not :consistent +for T in (Int, Float64, String, Symbol) + @test !Core.Compiler.is_consistent(Base.infer_effects(getindex, (Dict{T,Any}, T))) + @test Core.Compiler.is_effect_free(Base.infer_effects(getindex, (Dict{T,Any}, T))) + @test !Core.Compiler.is_nothrow(Base.infer_effects(getindex, (Dict{T,Any}, T))) + @test Core.Compiler.is_terminates(Base.infer_effects(getindex, (Dict{T,Any}, T))) +end diff --git a/test/docs.jl b/test/docs.jl index 762a481ee4801..6707278c53847 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -12,26 +12,19 @@ using InteractiveUtils: apropos include("testenv.jl") # Test helpers. -function docstrings_equal(d1, d2) +function docstrings_equal(d1, d2; debug=true) io1 = IOBuffer() io2 = IOBuffer() show(io1, MIME"text/markdown"(), d1) show(io2, MIME"text/markdown"(), d2) s1 = String(take!(io1)) s2 = String(take!(io2)) - #if s1 != s2 # for debugging - # e1 = eachline(IOBuffer(s1)) - # e2 = eachline(IOBuffer(s2)) - # for (l1, l2) in zip(e1, e2) - # l1 == l2 || println(l1, "\n", l2, "\n") - # end - # for l1 in e1 - # println(l1, "\n[missing]\n") - # end - # for l2 in e2 - # println("[missing]\n", l2, "\n") - # end - #end + if debug && s1 != s2 + print(s1) + println("--------------------------------------------------------------------------------") + print(s2) + println("================================================================================") + end return s1 == s2 end docstrings_equal(d1::DocStr, d2) = docstrings_equal(parsedoc(d1), d2) @@ -177,7 +170,7 @@ t(::AbstractString) "t-2" t(::Int, ::Any) "t-3" -t{S <: Integer}(::S) +t(::S) where {S <: Integer} # Docstrings to parametric methods after definition using where syntax (#32960): tw(x::T) where T = nothing @@ -357,7 +350,7 @@ let d1 = @doc(DocsTest.t(::Int, ::Any)), @test docstrings_equal(d1,d2) end -let d1 = @doc(DocsTest.t{S <: Integer}(::S)), +let d1 = @doc(DocsTest.t(::S) where {S <: Integer}), d2 = doc"t-3" @test docstrings_equal(d1,d2) end @@ -655,7 +648,7 @@ end @doc "This should document @m1... since its the result of expansion" @m2_11993 @test (@doc @m1_11993) !== nothing let d = (@doc :@m2_11993), - macro_doc = Markdown.parse("`$(curmod_prefix)@m2_11993` is a macro.") + macro_doc = Markdown.parse("`$(curmod_prefix == "Main." ? "" : curmod_prefix)@m2_11993` is a macro.") @test docstring_startswith(d, doc""" No documentation found. @@ -723,7 +716,7 @@ f12593_2() = 1 # crude test to make sure we sort docstring output by method specificity @test !docstrings_equal(Docs.doc(getindex, Tuple{Dict{Int,Int},Int}), - Docs.doc(getindex, Tuple{Type{Int64},Int})) + Docs.doc(getindex, Tuple{Type{Int64},Int}); debug=false) # test that macro documentation works @test (@repl :@assert) !== nothing @@ -794,7 +787,7 @@ end # Issue #13905. let err = try; @macroexpand(@doc "" f() = @x); false; catch ex; ex; end err::UndefVarError - @test err.var == Symbol("@x") + @test err.var === Symbol("@x") end @@ -977,6 +970,7 @@ abstract type $(curmod_prefix)Undocumented.at1{T>:Integer, N} ``` $(curmod_prefix)Undocumented.mt6{Integer, N} +$(curmod_prefix)Undocumented.st5{T>:Integer, N} ``` # Supertype Hierarchy @@ -1209,11 +1203,11 @@ end import Base.Docs: @var, Binding, defined -let x = Binding(Base, Symbol("@time")) +let x = Binding(Base, Symbol("@inline")) @test defined(x) == true - @test @var(@time) == x - @test @var(Base.@time) == x - @test @var(Base.Iterators.@time) == x + @test @var(@inline) == x + @test @var(Base.@inline) == x + @test @var(Base.Iterators.@inline) == x end let x = Binding(Iterators, :enumerate) @@ -1302,9 +1296,9 @@ dynamic_test.x = "test 2" function striptrimdocs(expr) if Meta.isexpr(expr, :call) fex = expr.args[1] - if Meta.isexpr(fex, :.) && fex.args[1] == :REPL + if Meta.isexpr(fex, :.) && fex.args[1] === :REPL fmex = fex.args[2] - if isa(fmex, QuoteNode) && fmex.value == :trimdocs + if isa(fmex, QuoteNode) && fmex.value === :trimdocs expr = expr.args[2] end end @@ -1316,28 +1310,28 @@ let dt1 = striptrimdocs(_repl(:(dynamic_test(1.0)))) @test dt1 isa Expr @test dt1.args[1] isa Expr @test dt1.args[1].head === :macrocall - @test dt1.args[1].args[1] == Symbol("@doc") + @test dt1.args[1].args[1] === Symbol("@doc") @test dt1.args[1].args[3] == :(dynamic_test(::typeof(1.0))) end let dt2 = striptrimdocs(_repl(:(dynamic_test(::String)))) @test dt2 isa Expr @test dt2.args[1] isa Expr @test dt2.args[1].head === :macrocall - @test dt2.args[1].args[1] == Symbol("@doc") + @test dt2.args[1].args[1] === Symbol("@doc") @test dt2.args[1].args[3] == :(dynamic_test(::String)) end let dt3 = striptrimdocs(_repl(:(dynamic_test(a)))) @test dt3 isa Expr @test dt3.args[1] isa Expr @test dt3.args[1].head === :macrocall - @test dt3.args[1].args[1] == Symbol("@doc") - @test dt3.args[1].args[3].args[2].head == :(::) # can't test equality due to line numbers + @test dt3.args[1].args[1] === Symbol("@doc") + @test dt3.args[1].args[3].args[2].head === :(::) # can't test equality due to line numbers end let dt4 = striptrimdocs(_repl(:(dynamic_test(1.0,u=2.0)))) @test dt4 isa Expr @test dt4.args[1] isa Expr @test dt4.args[1].head === :macrocall - @test dt4.args[1].args[1] == Symbol("@doc") + @test dt4.args[1].args[1] === Symbol("@doc") @test dt4.args[1].args[3] == :(dynamic_test(::typeof(1.0); u::typeof(2.0)=2.0)) end @@ -1441,27 +1435,36 @@ end struct t_docs_abc end @test "t_docs_abc" in accessible(@__MODULE__) -# Call overloading issue #20087 +# Call overloading issues #20087 and #44889 """ Docs for `MyFunc` struct. """ -mutable struct MyFunc - x -end +mutable struct MyFunc x end +""" +Docs for `MyParametricFunc{T}` struct. +""" +struct MyParametricFunc{T} end """ Docs for calling `f::MyFunc`. """ -function (f::MyFunc)(x) - f.x = x - return f -end +(f::MyFunc)(x) = f -@test docstrings_equal(@doc(MyFunc(2)), +""" +Docs for calling `f::MyParametricFunc{T}`. +""" +(f::MyParametricFunc{T})(x) where T = f + +@test docstrings_equal(@doc((::MyFunc)(2)), doc""" Docs for calling `f::MyFunc`. """) +@test docstrings_equal(@doc((::MyParametricFunc{Int})(44889)), +doc""" +Docs for calling `f::MyParametricFunc{T}`. +""") + struct A_20087 end """a""" diff --git a/test/embedding/embedding-test.jl b/test/embedding/embedding-test.jl index 797f6dabd9a89..c10cc6a16fee8 100644 --- a/test/embedding/embedding-test.jl +++ b/test/embedding/embedding-test.jl @@ -23,10 +23,13 @@ end @test readline(err) == "MethodError: no method matching this_function_has_no_methods()" @test success(p) lines = fetch(out_task) - @test length(lines) == 10 + @test length(lines) == 11 @test parse(Float64, lines[1]) ≈ sqrt(2) - @test lines[8] == "called bar" - @test lines[9] == "calling new bar" - @test lines[10] == " From worker 2:\tTaking over the world..." + @test lines[2] == "sqrt(2.0) in C: 1.414214e+00" + @test lines[3] == "sqrt(2.0) in C: 1.414214e+00" + @test lines[4] == "sqrt(2.0) in C: 1.414214e+00" + @test lines[9] == "called bar" + @test lines[10] == "calling new bar" + @test lines[11] == " From worker 2:\tTaking over the world..." @test readline(err) == "exception caught from C" end diff --git a/test/embedding/embedding.c b/test/embedding/embedding.c index d082366c908de..1294d4cdafb45 100644 --- a/test/embedding/embedding.c +++ b/test/embedding/embedding.c @@ -69,6 +69,14 @@ int main() fflush(stdout); } + { + // Same as above but using `@cfunction` + double (*sqrt_jl)(double) = jl_unbox_voidpointer(jl_eval_string("@cfunction(sqrt, Float64, (Float64,))")); + double retDouble = sqrt_jl(2.0); + printf("sqrt(2.0) in C: %e\n", retDouble); + fflush(stdout); + } + { // 1D arrays diff --git a/test/enums.jl b/test/enums.jl index 5a83e1b4dfa42..c7e3e3bf2abdb 100644 --- a/test/enums.jl +++ b/test/enums.jl @@ -94,18 +94,18 @@ end # other Integer types of enum members @enum Test3::UInt8 _one_Test3=0x01 _two_Test3=0x02 _three_Test3=0x03 -@test Test3.size == 1 +@test Core.sizeof(Test3) == 1 @test UInt8(_one_Test3) === 0x01 @test length(instances(Test3)) == 3 @enum Test4::UInt16 _one_Test4=0x01 _two_Test4=0x0002 _three_Test4=0x03 -@test Test4.size == 2 +@test Core.sizeof(Test4) == 2 @enum Test5::UInt32 _one_Test5=0x01 _two_Test5=0x00000002 _three_Test5=0x00000003 -@test Test5.size == 4 +@test Core.sizeof(Test5) == 4 @enum Test6::UInt128 _one_Test6=0x00000000000000000000000000000001 _two_Test6=0x00000000000000000000000000000002 -@test Test6.size == 16 +@test Core.sizeof(Test6) == 16 @test typeof(Integer(_one_Test6)) == UInt128 # enum values must be integers @@ -175,6 +175,10 @@ end end @test Int(haggis) == 4 +@enum HashEnum1 Enum1_a=1 +@enum HashEnum2 Enum2_a=1 +@test hash(Enum1_a) != hash(Enum2_a) + @test (Vector{Fruit}(undef, 3) .= apple) == [apple, apple, apple] # long, discongruous diff --git a/test/env.jl b/test/env.jl index 644d956af8fd4..de5cf92d9edb9 100644 --- a/test/env.jl +++ b/test/env.jl @@ -1,5 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# Make a copy of the original environment +original_env = copy(ENV) + using Random @test !("f=a=k=e=n=a=m=e" ∈ keys(ENV)) @@ -118,3 +121,58 @@ if Sys.iswindows() end end end + +@testset "get_bool_env" begin + @testset "truthy" begin + for v in ("t", "true", "y", "yes", "1") + 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("testing_gbe", true) == true + end + end + end + @testset "falsy" begin + for v in ("f", "false", "n", "no", "0") + 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("testing_gbe", false) == false + end + end + end + @testset "empty" begin + ENV["testing_gbe"] = "" + @test Base.get_bool_env("testing_gbe", true) == true + @test Base.get_bool_env("testing_gbe", false) == 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("testing_gbe", false) == false + end + @testset "unrecognized" begin + for v in ("truw", "falls") + ENV["testing_gbe"] = v + @test Base.get_bool_env("testing_gbe", true) === nothing + @test Base.get_bool_env("testing_gbe", false) === nothing + end + end + + # the "default" arg shouldn't have a default val, for clarity. + @test_throws MethodError Base.get_bool_env("testing_gbe") + + delete!(ENV, "testing_gbe") + @test !haskey(ENV, "testing_gbe") +end + +# Restore the original environment +for k in keys(ENV) + if !haskey(original_env, k) + delete!(ENV, k) + end +end +for (k, v) in pairs(original_env) + ENV[k] = v +end diff --git a/test/error.jl b/test/error.jl index e9d011e382a61..e9cdfa100bc81 100644 --- a/test/error.jl +++ b/test/error.jl @@ -1,16 +1,19 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# for curmod_str +include("testenv.jl") + @testset "ExponentialBackOff" begin @test length(ExponentialBackOff(n=10)) == 10 @test collect(ExponentialBackOff(n=10, first_delay=0.01))[1] == 0.01 @test maximum(ExponentialBackOff(n=10, max_delay=0.06)) == 0.06 ratio(x) = x[2:end]./x[1:end-1] @test all(x->x ≈ 10.0, ratio(collect(ExponentialBackOff(n=10, max_delay=Inf, factor=10, jitter=0.0)))) - Test.guardseed(12345) do - x = ratio(collect(ExponentialBackOff(n=100, max_delay=Inf, factor=1, jitter=0.1))) - xm = sum(x) / length(x) - @test abs(xm - 1.0) < 0.01 - end + Libc.srand(12345) + x = ratio(collect(ExponentialBackOff(n=100, max_delay=Inf, factor=1, jitter=0.1))) + xm = sum(x) / length(x) + @test abs(xm - 1.0) < 0.01 + Libc.srand() end @testset "retrying after errors" begin function foo_error(c, n) @@ -86,3 +89,37 @@ end e = SystemError("fail") @test e.extrainfo === nothing end + +@testset "MethodError for methods without line numbers" begin + try + eval(Expr(:function, :(f44319()), 0)) + 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" + end +end + +@testset "All types ending with Exception or Error subtype Exception" begin + function test_exceptions(mod, visited=Set{Module}()) + if mod ∉ visited + push!(visited, mod) + for name in names(mod, all=true) + isdefined(mod, name) || continue + value = getfield(mod, name) + + if value isa Module + test_exceptions(value, visited) + elseif value isa Type + str = string(value) + if endswith(str, "Exception") || endswith(str, "Error") + @test value <: Exception + end + end + end + end + visited + end + visited = test_exceptions(Base) + test_exceptions(Core, visited) +end diff --git a/test/errorshow.jl b/test/errorshow.jl index 72a2ebb1e9cbe..94722b803865f 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -49,28 +49,34 @@ include("testenv.jl") end file = @__FILE__ +sep = Base.Filesystem.path_separator +modul = @__MODULE__ Base.stacktrace_contract_userdir() && (file = Base.contractuser(file)) -cfile = " at $file:" +fname = basename(file) +dname = dirname(file) +cmod = "\n @ $modul" +cfile = " $file:" c1line = @__LINE__() + 1 method_c1(x::Float64, s::AbstractString...) = true buf = IOBuffer() Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, ""))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cfile$c1line" +@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf))) @test length(methods(method_c1)) <= 3 # because of '...' in candidate printing Base.show_method_candidates(IOContext(buf, :color => true), Base.MethodError(method_c1,(1, 1, ""))) -@test String(take!(buf)) == "\n\e[0mClosest candidates are:\n\e[0m method_c1(\e[91m::Float64\e[39m, \e[91m::AbstractString...\e[39m)$cfile$c1line" +mod_col = Base.text_colors[Base.STACKTRACE_FIXEDCOLORS[modul]] +@test occursin("\n\n\e[0mClosest candidates are:\n\e[0m method_c1(\e[91m::Float64\e[39m, \e[91m::AbstractString...\e[39m)\n\e[0m\e[90m @\e[39m $mod_col$modul\e[39m \e[90m$dname$sep\e[39m\e[90m\e[4m$fname:$c1line\e[24m\e[39m\n", String(take!(buf))) Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, "", ""))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c1(!Matched::Float64, ::AbstractString...)$cfile$c1line" +@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, ::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf))) # should match Base.show_method_candidates(buf, Base.MethodError(method_c1,(1., "", ""))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c1(::Float64, ::AbstractString...)$cfile$c1line" +@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 Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, 1))) -@test String(take!(buf)) == "" +@test isempty(String(take!(buf))) # matches the implicit constructor -> convert method Base.show_method_candidates(buf, Base.MethodError(Tuple{}, (1, 1, 1))) @@ -86,28 +92,35 @@ method_c2(x::Int32, y::Float64) = true method_c2(x::Int32, y::Int32, z::Int32) = true method_c2(x::T, y::T, z::T) where {T<:Real} = true -Base.show_method_candidates(buf, Base.MethodError(method_c2,(1., 1., 2))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cfile$(c2line+2)\n method_c2(::T, ::T, !Matched::T) where T<:Real$cfile$(c2line+5)\n method_c2(!Matched::Int32, ::Any...)$cfile$(c2line+1)\n ..." +let s + Base.show_method_candidates(buf, Base.MethodError(method_c2, (1., 1., 2))) + s = String(take!(buf)) + @test occursin("\n\nClosest candidates are:\n ", s) + @test occursin("\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cmod$cfile$(c2line+2)\n ", s) + @test occursin("\n method_c2(::T, ::T, !Matched::T) where T<:Real$cmod$cfile$(c2line+5)\n ", s) + @test occursin("\n method_c2(!Matched::Int32, ::Any...)$cmod$cfile$(c2line+1)\n ", s) + @test occursin("\n ...\n", s) +end c3line = @__LINE__() + 1 method_c3(x::Float64, y::Float64) = true Base.show_method_candidates(buf, Base.MethodError(method_c3,(1.,))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c3(::Float64, !Matched::Float64)$cfile$c3line" +@test occursin( "\n\nClosest candidates are:\n method_c3(::Float64, !Matched::Float64)$cmod$cfile$c3line\n", String(take!(buf))) # Test for the method error in issue #8651 c4line = @__LINE__ method_c4() = true method_c4(x::AbstractString) = false Base.show_method_candidates(buf, MethodError(method_c4,("",))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c4(::AbstractString)$cfile$(c4line+2)\n method_c4()$cfile$(c4line+1)" +@test occursin("\n\nClosest candidates are:\n method_c4(::AbstractString)$cmod$cfile$(c4line+2)\n method_c4()$cmod$cfile$(c4line+1)\n", String(take!(buf))) c5line = @__LINE__() + 1 method_c5(::Type{Float64}) = true Base.show_method_candidates(buf, MethodError(method_c5,(Float64,))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c5(::Type{Float64})$cfile$c5line" +@test occursin("\nClosest candidates are:\n method_c5(::Type{Float64})$cmod$cfile$c5line", String(take!(buf))) Base.show_method_candidates(buf, MethodError(method_c5,(Int32,))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c5(!Matched::Type{Float64})$cfile$c5line" +@test occursin("\nClosest candidates are:\n method_c5(!Matched::Type{Float64})$cmod$cfile$c5line", String(take!(buf))) mutable struct Test_type end test_type = Test_type() @@ -125,13 +138,13 @@ PR16155line2 = @__LINE__() + 1 (::Type{T})(arg::Any) where {T<:PR16155} = "replace call-to-convert method from sysimg" Base.show_method_candidates(buf, MethodError(PR16155,(1.0, 2.0, Int64(3)))) -@test String(take!(buf)) == "\nClosest candidates are:\n $(curmod_prefix)PR16155(::Any, ::Any)$cfile$PR16155line\n $(curmod_prefix)PR16155(!Matched::Int64, ::Any)$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cfile$PR16155line2" +@test occursin("\nClosest candidates are:\n $(curmod_prefix)PR16155(::Any, ::Any)$cmod$cfile$PR16155line\n $(curmod_prefix)PR16155(!Matched::Int64, ::Any)$cmod$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cmod$cfile$PR16155line2", String(take!(buf))) Base.show_method_candidates(buf, MethodError(PR16155,(Int64(3), 2.0, Int64(3)))) -@test String(take!(buf)) == "\nClosest candidates are:\n $(curmod_prefix)PR16155(::Int64, ::Any)$cfile$PR16155line\n $(curmod_prefix)PR16155(::Any, ::Any)$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cfile$PR16155line2" +@test occursin("\nClosest candidates are:\n $(curmod_prefix)PR16155(::Int64, ::Any)$cmod$cfile$PR16155line\n $(curmod_prefix)PR16155(::Any, ::Any)$cmod$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cmod$cfile$PR16155line2", String(take!(buf))) Base.show_method_candidates(buf, MethodError(Complex{T} where T<:Integer, (1.2,))) -@test startswith(String(take!(buf)), "\nClosest candidates are:\n (::Type{T})(::T) where T<:Number") +@test startswith(String(take!(buf)), "\n\nClosest candidates are:\n (::Type{T})(::T) where T<:Number") c6line = @__LINE__ method_c6(; x=1) = x @@ -155,27 +168,27 @@ m_error = try TestKWError.method_c6_in_module(1, x=1) catch e; e; end showerror(buf, m_error) error_out3 = String(take!(buf)) -@test occursin("method_c6(; x)$cfile$(c6line + 1) got unsupported keyword argument \"y\"", error_out) -@test occursin("method_c6(!Matched::Any; y)$cfile$(c6line + 2)", error_out) -@test occursin("method_c6(::Any; y)$cfile$(c6line + 2) got unsupported keyword argument \"x\"", error_out1) -@test occursin("method_c6_in_module(; x)$cfile$(c6mline + 2) got unsupported keyword argument \"y\"", error_out2) -@test occursin("method_c6_in_module(!Matched::Any; y)$cfile$(c6mline + 3)", error_out2) -@test occursin("method_c6_in_module(::Any; y)$cfile$(c6mline + 3) got unsupported keyword argument \"x\"", error_out3) +@test occursin("method_c6(; x) got unsupported keyword argument \"y\"$cmod$cfile$(c6line + 1)", error_out) +@test occursin("method_c6(!Matched::Any; y)$cmod$cfile$(c6line + 2)", error_out) +@test occursin("method_c6(::Any; y) got unsupported keyword argument \"x\"$cmod$cfile$(c6line + 2)", error_out1) +@test occursin("method_c6_in_module(; x) got unsupported keyword argument \"y\"$cmod$cfile$(c6mline + 2)", error_out2) +@test occursin("method_c6_in_module(!Matched::Any; y)$cmod$cfile$(c6mline + 3)", error_out2) +@test occursin("method_c6_in_module(::Any; y) got unsupported keyword argument \"x\"$cmod$cfile$(c6mline + 3)", error_out3) c7line = @__LINE__() + 1 method_c7(a, b; kargs...) = a Base.show_method_candidates(buf, MethodError(method_c7, (1, 1)), pairs((x = 1, y = 2))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cfile$c7line" +@test occursin("\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cmod$cfile$c7line", String(take!(buf))) c8line = @__LINE__() + 1 method_c8(a, b; y=1, w=1) = a Base.show_method_candidates(buf, MethodError(method_c8, (1, 1)), pairs((x = 1, y = 2, z = 1, w = 1))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c8(::Any, ::Any; y, w)$cfile$c8line got unsupported keyword arguments \"x\", \"z\"" +@test occursin("\nClosest candidates are:\n method_c8(::Any, ::Any; y, w) got unsupported keyword arguments \"x\", \"z\"$cmod$cfile$c8line", String(take!(buf))) let no_kwsorter_match, e no_kwsorter_match() = 0 no_kwsorter_match(a;y=1) = y e = try no_kwsorter_match(y=1) catch ex; ex; end - @test occursin(r"no method matching.+\(; y=1\)", sprint(showerror, e)) + @test occursin(Regex("no method matching.+\\(; y::$(Int)\\)"), sprint(showerror, e)) end ac15639line = @__LINE__ @@ -183,7 +196,7 @@ addConstraint_15639(c::Int32) = c addConstraint_15639(c::Int64; uncset=nothing) = addConstraint_15639(Int32(c), uncset=uncset) Base.show_method_candidates(buf, MethodError(addConstraint_15639, (Int32(1),)), pairs((uncset = nothing,))) -@test String(take!(buf)) == "\nClosest candidates are:\n addConstraint_15639(::Int32)$cfile$(ac15639line + 1) got unsupported keyword argument \"uncset\"\n addConstraint_15639(!Matched::Int64; uncset)$cfile$(ac15639line + 2)" +@test occursin("\nClosest candidates are:\n addConstraint_15639(::Int32) got unsupported keyword argument \"uncset\"$cmod$cfile$(ac15639line + 1)\n addConstraint_15639(!Matched::Int64; uncset)$cmod$cfile$(ac15639line + 2)", String(take!(buf))) # Busted Vararg method definitions bad_vararg_decl(x::Int, y::Vararg) = 1 # don't do this, instead use (x::Int, y...) @@ -337,7 +350,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" err_str = @except_str read(IOBuffer(), UInt8) EOFError @test err_str == "EOFError: read end of file" err_str = @except_str Dict()[:doesnotexist] KeyError @@ -422,27 +435,28 @@ let err_str, j = reinterpret(EightBitTypeT{Int32}, 0x54), sp = Base.source_path() sn = basename(sp) + Base.stacktrace_contract_userdir() && (sp = Base.contractuser(sp)) @test sprint(show, which(String, Tuple{})) == - "String() in $curmod_str at $sp:$(method_defs_lineno + 0)" + "String() @ $curmod_str $sp:$(method_defs_lineno + 0)" @test sprint(show, which("a", Tuple{})) == - "(::String)() in $curmod_str at $sp:$(method_defs_lineno + 1)" + "(::String)() @ $curmod_str $sp:$(method_defs_lineno + 1)" @test sprint(show, which(EightBitType, Tuple{})) == - "$(curmod_prefix)EightBitType() in $curmod_str at $sp:$(method_defs_lineno + 2)" + "$(curmod_prefix)EightBitType() @ $curmod_str $sp:$(method_defs_lineno + 2)" @test sprint(show, which(reinterpret(EightBitType, 0x54), Tuple{})) == - "(::$(curmod_prefix)EightBitType)() in $curmod_str at $sp:$(method_defs_lineno + 3)" + "(::$(curmod_prefix)EightBitType)() @ $curmod_str $sp:$(method_defs_lineno + 3)" @test sprint(show, which(EightBitTypeT, Tuple{})) == - "$(curmod_prefix)EightBitTypeT() in $curmod_str at $sp:$(method_defs_lineno + 4)" + "$(curmod_prefix)EightBitTypeT() @ $curmod_str $sp:$(method_defs_lineno + 4)" @test sprint(show, which(EightBitTypeT{Int32}, Tuple{})) == - "$(curmod_prefix)EightBitTypeT{T}() where T in $curmod_str at $sp:$(method_defs_lineno + 5)" + "$(curmod_prefix)EightBitTypeT{T}() where T @ $curmod_str $sp:$(method_defs_lineno + 5)" @test sprint(show, which(reinterpret(EightBitTypeT{Int32}, 0x54), Tuple{})) == - "(::$(curmod_prefix)EightBitTypeT)() in $curmod_str at $sp:$(method_defs_lineno + 6)" + "(::$(curmod_prefix)EightBitTypeT)() @ $curmod_str $sp:$(method_defs_lineno + 6)" @test startswith(sprint(show, which(Complex{Int}, Tuple{Int})), "Complex{T}(") @test startswith(sprint(show, which(getfield(Base, Symbol("@doc")), Tuple{LineNumberNode, Module, Vararg{Any}})), - "var\"@doc\"(__source__::LineNumberNode, __module__::Module, x...) in Core at boot.jl:") + "var\"@doc\"(__source__::LineNumberNode, __module__::Module, x...) @ Core boot.jl:") @test startswith(sprint(show, which(FunctionLike(), Tuple{})), - "(::$(curmod_prefix)FunctionLike)() in $curmod_str at $sp:$(method_defs_lineno + 7)") + "(::$(curmod_prefix)FunctionLike)() @ $curmod_str $sp:$(method_defs_lineno + 7)") @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)" @@ -623,7 +637,7 @@ let err_str @test occursin(r"MethodError: no method matching one\(::.*HasNoOne\)", err_str) @test occursin("HasNoOne does not support `one`; did you mean `oneunit`?", err_str) err_str = @except_str one(HasNoOne(); value=2) MethodError - @test occursin(r"MethodError: no method matching one\(::.*HasNoOne; value=2\)", err_str) + @test occursin(Regex("MethodError: no method matching one\\(::.*HasNoOne; value::$(Int)\\)"), err_str) @test occursin("`one` doesn't take keyword arguments, that would be silly", err_str) end pop!(Base.Experimental._hint_handlers[MethodError]) # order is undefined, don't copy this @@ -673,7 +687,7 @@ end getbt() = backtrace() bt = getbt() Base.update_stackframes_callback[] = function(list) - modify((sf, n)) = sf.func == :getbt ? (StackTraces.StackFrame(sf.func, sf.file, sf.line+2, sf.linfo, sf.from_c, sf.inlined, sf.pointer), n) : (sf, n) + modify((sf, n)) = sf.func === :getbt ? (StackTraces.StackFrame(sf.func, sf.file, sf.line+2, sf.linfo, sf.from_c, sf.inlined, sf.pointer), n) : (sf, n) map!(modify, list, list) end io = IOBuffer() @@ -903,3 +917,89 @@ end @test contains(err_str, "maybe you meant `import/using .Bar`") end end + +for (expr, errmsg) in + [ + (:(struct Foo <: 1 end), "can only subtype data types"), + (:(struct Foo <: Float64 end), "can only subtype abstract types"), + (:(struct Foo <: Foo end), "a type cannot subtype itself"), + (:(struct Foo <: Tuple{Float64} end), "cannot subtype a tuple type"), + (:(struct Foo <: NamedTuple{(:a,), Tuple{Int64}} end), "cannot subtype a named tuple type"), + (:(struct Foo <: Type{Float64} end), "cannot add subtypes to Type"), + (:(struct Foo <: Type{Float64} end), "cannot add subtypes to Type"), + (:(struct Foo <: typeof(Core.apply_type) end), "cannot add subtypes to Core.Builtin"), + ] + err = try @eval $expr + catch e + e + end + @test contains(sprint(showerror, err), errmsg) +end + +let err_str + err_str = @except_str "a" + "b" MethodError + @test occursin("String concatenation is performed with *", err_str) +end + +@testset "unused argument names" begin + g(::Int) = backtrace() + bt = g(1) + @test !contains(sprint(Base.show_backtrace, bt), "#unused#") +end + +# issue #49002 +let buf = IOBuffer() + Base.show_method_candidates(buf, Base.MethodError(typeof, (17,)), pairs((foo = :bar,))) + @test isempty(take!(buf)) + Base.show_method_candidates(buf, Base.MethodError(isa, ()), pairs((a = 5,))) + @test isempty(take!(buf)) +end + +f_internal_wrap(g, a; kw...) = error(); +@inline f_internal_wrap(a; kw...) = f_internal_wrap(identity, a; kw...); +bt = try + f_internal_wrap(1) +catch + catch_backtrace() +end +@test !occursin("#f_internal_wrap#", sprint(Base.show_backtrace, bt)) + +g_collapse_pos(x, y=1.0, z=2.0) = error() +bt = try + g_collapse_pos(1.0) +catch + catch_backtrace() +end +bt_str = sprint(Base.show_backtrace, bt) +@test occursin("g_collapse_pos(x::Float64, y::Float64, z::Float64)", bt_str) +@test !occursin("g_collapse_pos(x::Float64)", bt_str) + +g_collapse_kw(x; y=2.0) = error() +bt = try + g_collapse_kw(1.0) +catch + catch_backtrace() +end +bt_str = sprint(Base.show_backtrace, bt) +@test occursin("g_collapse_kw(x::Float64; y::Float64)", bt_str) +@test !occursin("g_collapse_kw(x::Float64)", bt_str) + +g_collapse_pos_kw(x, y=1.0; z=2.0) = error() +bt = try + g_collapse_pos_kw(1.0) +catch + catch_backtrace() +end +bt_str = sprint(Base.show_backtrace, bt) +@test occursin("g_collapse_pos_kw(x::Float64, y::Float64; z::Float64)", bt_str) +@test !occursin("g_collapse_pos_kw(x::Float64, y::Float64)", bt_str) +@test !occursin("g_collapse_pos_kw(x::Float64)", bt_str) + +# Test Base.print_with_compare in convert MethodErrors +struct TypeCompareError{A,B} <: Exception end +let e = @test_throws MethodError convert(TypeCompareError{Float64,1}, TypeCompareError{Float64,2}()) + str = sprint(Base.showerror, e.value) + @test occursin("TypeCompareError{Float64,2}", str) + @test occursin("TypeCompareError{Float64,1}", str) + @test !occursin("TypeCompareError{Float64{},2}", str) # No {...} for types without params +end diff --git a/test/exceptions.jl b/test/exceptions.jl index d8d1e7b45b8b5..eb0bbaec35090 100644 --- a/test/exceptions.jl +++ b/test/exceptions.jl @@ -276,7 +276,7 @@ end exc end yield(t) - @test t.state == :done + @test t.state === :done @test t.result == ErrorException("B") # Task exception state is preserved around task switches @test length(current_exceptions()) == 1 @@ -296,7 +296,7 @@ end exc end yield(t) - @test t.state == :done + @test t.state === :done @test t.result == ErrorException("B") @test bt == catch_backtrace() rethrow() @@ -318,7 +318,7 @@ end exc end yield(t) - @test t.state == :done + @test t.state === :done @test t.result == ErrorException("B") bt = catch_backtrace() rethrow(ErrorException("C")) @@ -335,7 +335,7 @@ end error("B") end yield(t) - @test t.state == :failed + @test t.state === :failed @test t.result == ErrorException("B") @test current_exceptions(t, backtrace=false) == [ (exception=ErrorException("A"),backtrace=nothing), diff --git a/test/fastmath.jl b/test/fastmath.jl index e93fb93330b4f..8755e727db092 100644 --- a/test/fastmath.jl +++ b/test/fastmath.jl @@ -207,6 +207,31 @@ end @test @fastmath(cis(third)) ≈ cis(third) end end + +@testset "reductions" begin + @test @fastmath(maximum([1,2,3])) == 3 + @test @fastmath(minimum([1,2,3])) == 1 + @test @fastmath(maximum(abs2, [1,2,3+0im])) == 9 + @test @fastmath(minimum(sqrt, [1,2,3])) == 1 + @test @fastmath(maximum(Float32[4 5 6; 7 8 9])) == 9.0f0 + @test @fastmath(minimum(Float32[4 5 6; 7 8 9])) == 4.0f0 + + @test @fastmath(maximum(Float32[4 5 6; 7 8 9]; dims=1)) == Float32[7.0 8.0 9.0] + @test @fastmath(minimum(Float32[4 5 6; 7 8 9]; dims=2)) == Float32[4.0; 7.0;;] + @test @fastmath(maximum(abs, [4+im -5 6-im; -7 8 -9]; dims=1)) == [7.0 8.0 9.0] + @test @fastmath(minimum(cbrt, [4 -5 6; -7 8 -9]; dims=2)) == cbrt.([-5; -9;;]) + + x = randn(3,4,5) + x1 = sum(x; dims=1) + x23 = sum(x; dims=(2,3)) + @test @fastmath(maximum!(x1, x)) ≈ maximum(x; dims=1) + @test x1 ≈ maximum(x; dims=1) + @test @fastmath(minimum!(x23, x)) ≈ minimum(x; dims=(2,3)) + @test x23 ≈ minimum(x; dims=(2,3)) + @test @fastmath(maximum!(abs, x23, x .+ im)) ≈ maximum(abs, x .+ im; dims=(2,3)) + @test @fastmath(minimum!(abs2, x1, x .+ im)) ≈ minimum(abs2, x .+ im; dims=1) +end + @testset "issue #10544" begin a = fill(1.,2,2) b = fill(1.,2,2) diff --git a/test/file.jl b/test/file.jl index a7c0b6dca125d..1d2ac4c6f9132 100644 --- a/test/file.jl +++ b/test/file.jl @@ -42,7 +42,7 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER # creation of symlink to directory that does not yet exist new_dir = joinpath(subdir, "new_dir") foo_file = joinpath(subdir, "new_dir", "foo") - nedlink = joinpath(subdir, "non_existant_dirlink") + nedlink = joinpath(subdir, "nonexistent_dirlink") symlink("new_dir", nedlink; dir_target=true) try readdir(nedlink) @@ -193,7 +193,7 @@ end t = i % 2 == 0 ? mktempfile() : mktempdir() push!(temps, t) @test ispath(t) - @test length(TEMP_CLEANUP) == i  + @test length(TEMP_CLEANUP) == i @test TEMP_CLEANUP_MAX[] == n # delete 1/3 of the temp paths i % 3 == 0 && rm(t, recursive=true, force=true) @@ -598,13 +598,25 @@ close(s) # This section tests temporary file and directory creation. # ####################################################################### +@testset "invalid read/write flags" begin + @test try + open("this file is not expected to exist", read=false, write=false) + false + catch e + isa(e, SystemError) || rethrow() + @test endswith(sprint(showerror, e), "Invalid argument") + true + end +end + @testset "quoting filenames" begin @test try open("this file is not expected to exist") false catch e isa(e, SystemError) || rethrow() - @test sprint(showerror, e) == "SystemError: opening file \"this file is not expected to exist\": No such file or directory" + @test e.errnum == 2 + @test startswith(sprint(showerror, e), "SystemError: opening file \"this file is not expected to exist\"") true end end @@ -770,13 +782,13 @@ end mktempdir() do tmpdir # rename file file = joinpath(tmpdir, "afile.txt") - files_stat = stat(file) close(open(file, "w")) # like touch, but lets the operating system update + files_stat = stat(file) # the timestamp for greater precision on some platforms (windows) newfile = joinpath(tmpdir, "bfile.txt") mv(file, newfile) - newfile_stat = stat(file) + newfile_stat = stat(newfile) @test !ispath(file) @test isfile(newfile) @@ -1252,7 +1264,7 @@ let f = open(file, "w") if Sys.iswindows() f = RawFD(ccall(:_open, Cint, (Cstring, Cint), file, Base.Filesystem.JL_O_RDONLY)) else - f = RawFD(ccall(:open, Cint, (Cstring, Cint), file, Base.Filesystem.JL_O_RDONLY)) + f = RawFD(ccall(:open, Cint, (Cstring, Cint, UInt32...), file, Base.Filesystem.JL_O_RDONLY)) end test_LibcFILE(Libc.FILE(f, Libc.modestr(true, false))) end @@ -1452,7 +1464,7 @@ rm(dir) #################### mktempdir() do dir name1 = joinpath(dir, "apples") - name2 = joinpath(dir, "bannanas") + name2 = joinpath(dir, "bananas") @test !ispath(name1) @test touch(name1) == name1 @test isfile(name1) @@ -1519,11 +1531,11 @@ if !Sys.iswindows() chmod(joinpath(d, "empty_outer", "empty_inner"), 0o333) # Test that an empty directory, even when we can't read its contents, is deletable - rm(joinpath(d, "empty_outer"); recursive=true, force=true) + rm(joinpath(d, "empty_outer"); recursive=true) @test !isdir(joinpath(d, "empty_outer")) # But a non-empty directory is not - @test_throws Base.IOError rm(joinpath(d, "nonempty"); recursive=true, force=true) + @test_throws Base.IOError rm(joinpath(d, "nonempty"); recursive=true) chmod(joinpath(d, "nonempty"), 0o777) rm(joinpath(d, "nonempty"); recursive=true, force=true) @test !isdir(joinpath(d, "nonempty")) @@ -1650,7 +1662,7 @@ end if Sys.iswindows() @testset "mkdir/rm permissions" begin - # test delete permission in system folders (i.e. impliclty test chmod permissions) + # test delete permission in system folders (i.e. implicitly test chmod permissions) # issue #38433 @test withenv("TMP" => "C:\\") do mktempdir() do dir end diff --git a/test/functional.jl b/test/functional.jl index c9b0b270baeb7..19355d13ff335 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -139,6 +139,13 @@ end @test findall(!iszero, x^2 for x in -1:0.5:1) == [1, 2, 4, 5] @test argmin(x^2 for x in -1:0.5:1) == 3 +# findall return type, see #45495 +let gen = (i for i in 1:3); + @test @inferred(findall(x -> true, gen))::Vector{Int} == [1, 2, 3] + @test @inferred(findall(x -> false, gen))::Vector{Int} == Int[] + @test @inferred(findall(x -> x < 0, gen))::Vector{Int} == Int[] +end + # inference on vararg generator of a type (see #22907 comments) let f(x) = collect(Base.Generator(=>, x, x)) @test @inferred(f((1,2))) == [1=>1, 2=>2] diff --git a/test/gc.jl b/test/gc.jl new file mode 100644 index 0000000000000..ecf71fe51f6ad --- /dev/null +++ b/test/gc.jl @@ -0,0 +1,25 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test + +function run_gctest(file) + let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no $file` + @testset for test_nthreads in (1, 2, 4) + new_env = copy(ENV) + new_env["JULIA_NUM_THREADS"] = string(test_nthreads) + new_env["JULIA_NUM_GC_THREADS"] = string(test_nthreads) + @test success(run(pipeline(setenv(cmd, new_env), stdout = stdout, stderr = stderr))) + end + end +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 +# than that. +@testset "GC threads" begin + run_gctest("gc/binarytree.jl") + run_gctest("gc/linkedlist.jl") + run_gctest("gc/objarray.jl") + run_gctest("gc/chunks.jl") +end diff --git a/test/gc/binarytree.jl b/test/gc/binarytree.jl new file mode 100644 index 0000000000000..896f47fa4c9c7 --- /dev/null +++ b/test/gc/binarytree.jl @@ -0,0 +1,54 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module BinaryTreeMutable + +# Adopted from +# https://benchmarksgame-team.pages.debian.net/benchmarksgame/description/binarytrees.html#binarytrees + +using Base.Threads +using Printf + +mutable struct Node + l::Union{Nothing, Node} + r::Union{Nothing, Node} +end + +function make(n::Int) + return n === 0 ? Node(nothing, nothing) : Node(make(n-1), make(n-1)) +end + +function check(node::Node) + return 1 + (node.l === nothing ? 0 : check(node.l) + check(node.r)) +end + +function binary_trees(io, n::Int) + @printf io "stretch tree of depth %jd\t check: %jd\n" n+1 check(make(n+1)) + + long_tree = make(n) + minDepth = 4 + resultSize = div((n - minDepth), 2) + 1 + results = Vector{String}(undef, resultSize) + Threads.@threads for depth in minDepth:2:n + c = 0 + niter = 1 << (n - depth + minDepth) + for _ in 1:niter + c += check(make(depth)) + end + index = div((depth - minDepth),2) + 1 + results[index] = @sprintf "%jd\t trees of depth %jd\t check: %jd\n" niter depth c + end + + for i in results + write(io, i) + end + + @printf io "long lived tree of depth %jd\t check: %jd\n" n check(long_tree) +end + +end #module + +using .BinaryTreeMutable + +# Memory usage is 466MB +BinaryTreeMutable.binary_trees(devnull, 16) +GC.gc() diff --git a/test/gc/chunks.jl b/test/gc/chunks.jl new file mode 100644 index 0000000000000..08af59ecbf973 --- /dev/null +++ b/test/gc/chunks.jl @@ -0,0 +1,17 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# MWE from https://github.com/JuliaLang/julia/issues/49501 +N = 1_000_000 # or larger +T = BigFloat + +struct Q{T} + a::T + b::T +end + +# Memoy use is ~512MB +let + A = [Q(rand(T), rand(T)) for _ in 1:N] +end + +GC.gc() diff --git a/test/gc/linkedlist.jl b/test/gc/linkedlist.jl new file mode 100644 index 0000000000000..669e5f8ec21d9 --- /dev/null +++ b/test/gc/linkedlist.jl @@ -0,0 +1,23 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +mutable struct ListNode + key::Int64 + next::ListNode + ListNode() = new() + ListNode(x)= new(x) + ListNode(x,y) = new(x,y); +end + +function list(N=16*1024^2) + start::ListNode = ListNode(1) + current::ListNode = start + for i = 2:N + current = ListNode(i,current) + end + return current.key +end + +# Memory use is 512 MB +_ = list() + +GC.gc() diff --git a/test/gc/objarray.jl b/test/gc/objarray.jl new file mode 100644 index 0000000000000..d36fcedef71a4 --- /dev/null +++ b/test/gc/objarray.jl @@ -0,0 +1,36 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Random: seed! +seed!(1) + +abstract type Cell end + +struct CellA<:Cell + a::Ref{Int} +end + +struct CellB<:Cell + b::String +end + +function fillcells!(mc::Array{Cell}) + for ind in eachindex(mc) + mc[ind] = ifelse(rand() > 0.5, CellA(ind), CellB(string(ind))) + end + return mc +end + +function work(size) + mcells = Array{Cell}(undef, size, size) + fillcells!(mcells) +end + +function run(maxsize) + Threads.@threads for i in 1:maxsize + work(i*375) + end +end + +# Memory usage 581 MB +run(4) +GC.gc() diff --git a/test/gcext/.gitignore b/test/gcext/.gitignore index 0f8c848e5cea6..829c3297dfa2c 100644 --- a/test/gcext/.gitignore +++ b/test/gcext/.gitignore @@ -1,2 +1,3 @@ /gcext /gcext-debug +/Foreign/deps diff --git a/test/gcext/DependsOnForeign/Manifest.toml b/test/gcext/DependsOnForeign/Manifest.toml new file mode 100644 index 0000000000000..d830116bb54ca --- /dev/null +++ b/test/gcext/DependsOnForeign/Manifest.toml @@ -0,0 +1,14 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.3" +manifest_format = "2.0" +project_hash = "e7199d961a5f4ebad68a3deaf5beaa7406a0afcb" + +[[deps.Foreign]] +deps = ["Libdl"] +path = "../Foreign" +uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" +version = "0.1.0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/DependsOnForeign/Project.toml b/test/gcext/DependsOnForeign/Project.toml new file mode 100644 index 0000000000000..b2bee1338c2b7 --- /dev/null +++ b/test/gcext/DependsOnForeign/Project.toml @@ -0,0 +1,6 @@ +name = "DependsOnForeign" +uuid = "4b0716e0-dfb5-4e00-8b44-e2685a41517f" +version = "0.1.0" + +[deps] +Foreign = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" diff --git a/test/gcext/DependsOnForeign/src/DependsOnForeign.jl b/test/gcext/DependsOnForeign/src/DependsOnForeign.jl new file mode 100644 index 0000000000000..cdf31774956e1 --- /dev/null +++ b/test/gcext/DependsOnForeign/src/DependsOnForeign.jl @@ -0,0 +1,14 @@ +module DependsOnForeign + +using Foreign + +f(obj::FObj) = Base.pointer_from_objref(obj) +precompile(f, (FObj,)) + +const FObjRef = Ref{FObj}() + +function __init__() + FObjRef[] = FObj() +end + +end # module DependsOnForeign diff --git a/test/gcext/Foreign/Manifest.toml b/test/gcext/Foreign/Manifest.toml new file mode 100644 index 0000000000000..25cf111aa50ba --- /dev/null +++ b/test/gcext/Foreign/Manifest.toml @@ -0,0 +1,8 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0-DEV" +manifest_format = "2.0" +project_hash = "7b70172a2edbdc772ed789e79d4411d7528eae86" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/Foreign/Project.toml b/test/gcext/Foreign/Project.toml new file mode 100644 index 0000000000000..819f64beee442 --- /dev/null +++ b/test/gcext/Foreign/Project.toml @@ -0,0 +1,6 @@ +name = "Foreign" +uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" +version = "0.1.0" + +[deps] +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/Foreign/deps/foreignlib.c b/test/gcext/Foreign/deps/foreignlib.c new file mode 100644 index 0000000000000..72e02e9bef0cf --- /dev/null +++ b/test/gcext/Foreign/deps/foreignlib.c @@ -0,0 +1,56 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include "julia.h" +#include "julia_gcext.h" + +// TODO make these atomics +int nmarks = 0; +int nsweeps = 0; + +uintptr_t mark(jl_ptls_t ptls, jl_value_t *p) +{ + nmarks += 1; + return 0; +} + +void sweep(jl_value_t *p) +{ + nsweeps++; +} + +JL_DLLEXPORT jl_datatype_t *declare_foreign(jl_sym_t* name, jl_module_t *module, jl_datatype_t *parent) +{ + return jl_new_foreign_type(name, module, parent, mark, sweep, 1, 0); +} + +// #define GC_MAX_SZCLASS (2032 - sizeof(void *)) + +JL_DLLEXPORT int reinit_foreign(jl_datatype_t *dt) +{ + int ret = jl_reinit_foreign_type(dt, mark, sweep); + nmarks = nsweeps = 0; + if (ret == 0) + return 0; + if (dt->layout->npointers != 1) + return -1; + if (dt->layout->size != 0) + return -2; + return ret; +} + +JL_DLLEXPORT jl_value_t *allocate_foreign(jl_ptls_t ptls, size_t sz, jl_datatype_t *dt) +{ + jl_value_t* obj = jl_gc_alloc_typed(ptls, sz, dt); + jl_gc_schedule_foreign_sweepfunc(ptls, obj); + return obj; +} + +JL_DLLEXPORT int nmark_counter() +{ + return nmarks; +} + +JL_DLLEXPORT int nsweep_counter() +{ + return nsweeps; +} diff --git a/test/gcext/Foreign/src/Foreign.jl b/test/gcext/Foreign/src/Foreign.jl new file mode 100644 index 0000000000000..a1ab79fab586a --- /dev/null +++ b/test/gcext/Foreign/src/Foreign.jl @@ -0,0 +1,29 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module Foreign + +using Libdl + +const foreignlib = joinpath(ENV["BINDIR"], "foreignlib.$(dlext)") + +const FObj = ccall((:declare_foreign, foreignlib), Any, (Any, Any, Any), :FObj, @__MODULE__, Any) +FObj() = ccall((:allocate_foreign, foreignlib), Any, (Ptr{Cvoid}, Csize_t, Any,), Core.getptls(), sizeof(Ptr{Cvoid}), FObj)::FObj + +export FObj + +get_nmark() = ccall((:nmark_counter, foreignlib), Cint, ()) +get_nsweep() = ccall((:nsweep_counter, foreignlib), Cint, ()) + +function __init__() + @assert ccall((:reinit_foreign, foreignlib), Cint, (Any,), FObj) == 1 +end + +allocs(N) = [Foreign.FObj() for _ in 1:N] + +function test(N) + x = allocs(N) + Core.donotdelete(x) + x = nothing +end + +end # module Foreign diff --git a/test/gcext/ForeignObjSerialization/Manifest.toml b/test/gcext/ForeignObjSerialization/Manifest.toml new file mode 100644 index 0000000000000..d830116bb54ca --- /dev/null +++ b/test/gcext/ForeignObjSerialization/Manifest.toml @@ -0,0 +1,14 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.3" +manifest_format = "2.0" +project_hash = "e7199d961a5f4ebad68a3deaf5beaa7406a0afcb" + +[[deps.Foreign]] +deps = ["Libdl"] +path = "../Foreign" +uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" +version = "0.1.0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/test/gcext/ForeignObjSerialization/Project.toml b/test/gcext/ForeignObjSerialization/Project.toml new file mode 100644 index 0000000000000..1a26ff7884481 --- /dev/null +++ b/test/gcext/ForeignObjSerialization/Project.toml @@ -0,0 +1,6 @@ +name = "ForeignObjSerialization" +uuid = "2c015d96-a6ca-42f0-bc68-f9090de6bc2c" +version = "0.1.0" + +[deps] +Foreign = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4" diff --git a/test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl b/test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl new file mode 100644 index 0000000000000..e32753aecb3b4 --- /dev/null +++ b/test/gcext/ForeignObjSerialization/src/ForeignObjSerialization.jl @@ -0,0 +1,6 @@ +module ForeignObjSerialization + +using Foreign +const FObjRef = Ref{FObj}(FObj()) + +end # module ForeignObjSerialization diff --git a/test/gcext/LocalTest.jl b/test/gcext/LocalTest.jl index f73b4b47e8023..e2ee94e765321 100644 --- a/test/gcext/LocalTest.jl +++ b/test/gcext/LocalTest.jl @@ -54,13 +54,13 @@ function set_aux_root(n :: Int, x :: String) return ccall(:set_aux_root, Nothing, (UInt, String), n, x) end -function internal_obj_scan(p :: Any) - if ccall(:internal_obj_scan, Cint, (Any,), p) == 0 - global internal_obj_scan_failures += 1 - end -end +# function internal_obj_scan(p :: Any) +# if ccall(:internal_obj_scan, Cint, (Any,), p) == 0 +# global internal_obj_scan_failures += 1 +# end +# end -global internal_obj_scan_failures = 0 +# global internal_obj_scan_failures = 0 for i in 0:1000 set_aux_root(i, string(i)) @@ -70,12 +70,12 @@ function test() local stack = make() for i in 1:100000 push(stack, string(i, base=2)) - internal_obj_scan(top(stack)) + # internal_obj_scan(top(stack)) end for i in 1:1000 local stack2 = make() - internal_obj_scan(stack2) - internal_obj_scan(blob(stack2)) + # internal_obj_scan(stack2) + # internal_obj_scan(blob(stack2)) while !empty(stack) push(stack2, pop(stack)) end @@ -98,5 +98,5 @@ end print(gc_counter_full(), " full collections.\n") print(gc_counter_inc(), " partial collections.\n") print(num_obj_sweeps(), " object sweeps.\n") -print(internal_obj_scan_failures, " internal object scan failures.\n") +# print(internal_obj_scan_failures, " internal object scan failures.\n") print(corrupted_roots, " corrupted auxiliary roots.\n") diff --git a/test/gcext/Makefile b/test/gcext/Makefile index 7cb602572e3c5..2a77b76ede50d 100644 --- a/test/gcext/Makefile +++ b/test/gcext/Makefile @@ -19,18 +19,26 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) # get the executable suffix, if any EXE := $(suffix $(abspath $(JULIA))) +OS := $(shell uname) +ifeq ($(OS), Darwin) + DYLIB := .dylib +else + DYLIB := .so +endif + # get compiler and linker flags. (see: `contrib/julia-config.jl`) JULIA_CONFIG := $(JULIA) -e 'include(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "julia-config.jl"))' -- CPPFLAGS_ADD := CFLAGS_ADD = $(shell $(JULIA_CONFIG) --cflags) LDFLAGS_ADD = -lm $(shell $(JULIA_CONFIG) --ldflags --ldlibs) +DYLIBFLAGS := --shared -fPIC DEBUGFLAGS += -g #============================================================================= -release: $(BIN)/gcext$(EXE) -debug: $(BIN)/gcext-debug$(EXE) +release: $(BIN)/gcext$(EXE) $(BIN)/Foreign/deps/foreignlib$(DYLIB) +debug: $(BIN)/gcext-debug$(EXE) $(BIN)/Foreign/deps/foreignlib-debug$(DYLIB) $(BIN)/gcext$(EXE): $(SRCDIR)/gcext.c $(CC) $^ -o $@ $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) @@ -38,19 +46,27 @@ $(BIN)/gcext$(EXE): $(SRCDIR)/gcext.c $(BIN)/gcext-debug$(EXE): $(SRCDIR)/gcext.c $(CC) $^ -o $@ $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(DEBUGFLAGS) +$(BIN)/foreignlib$(DYLIB): $(SRCDIR)/Foreign/deps/foreignlib.c + $(CC) $^ -o $@ $(DYLIBFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) + +$(BIN)/foreignlib-debug$(DYLIB): $(SRCDIR)/Foreign/deps/foreignlib.c + $(CC) $^ -o $@ $(DYLIBFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(DEBUGFLAGS) + ifneq ($(abspath $(BIN)),$(abspath $(SRCDIR))) # for demonstration purposes, our demo code is also installed # in $BIN, although this would likely not be typical -$(BIN)/LocalModule.jl: $(SRCDIR)/LocalModule.jl +$(BIN)/LocalTest.jl: $(SRCDIR)/LocalTest.jl cp $< $@ endif -check: $(BIN)/gcext$(EXE) $(BIN)/LocalTest.jl - $(JULIA) --depwarn=error $(SRCDIR)/gcext-test.jl $< +check: $(BIN)/gcext$(EXE) $(BIN)/LocalTest.jl $(BIN)/foreignlib$(DYLIB) + BINDIR=$(BIN) $(JULIA) --depwarn=error $(SRCDIR)/gcext-test.jl $< @echo SUCCESS clean: -rm -f $(BIN)/gcext-debug$(EXE) $(BIN)/gcext$(EXE) + -rm -f $(BIN)/foreignlib$(DYLIB) + -rm -f $(BIN)/foreignlib-debug$(DYLIB) .PHONY: release debug clean check diff --git a/test/gcext/gcext-test.jl b/test/gcext/gcext-test.jl index e6f3e3663ff0e..81637392e3c5d 100644 --- a/test/gcext/gcext-test.jl +++ b/test/gcext/gcext-test.jl @@ -2,6 +2,7 @@ # tests the output of the embedding example is correct using Test +using Pkg if Sys.iswindows() # libjulia needs to be in the same directory as the embedding executable or in path @@ -31,12 +32,47 @@ end errlines = fetch(err_task) lines = fetch(out_task) @test length(errlines) == 0 - @test length(lines) == 6 + # @test length(lines) == 6 + @test length(lines) == 5 @test checknum(lines[2], r"([0-9]+) full collections", n -> n >= 10) @test checknum(lines[3], r"([0-9]+) partial collections", n -> n > 0) @test checknum(lines[4], r"([0-9]+) object sweeps", n -> n > 0) - @test checknum(lines[5], r"([0-9]+) internal object scan failures", - n -> n == 0) - @test checknum(lines[6], r"([0-9]+) corrupted auxiliary roots", + # @test checknum(lines[5], r"([0-9]+) internal object scan failures", + # n -> n == 0) + # @test checknum(lines[6], r"([0-9]+) corrupted auxiliary roots", + # n -> n == 0) + @test checknum(lines[5], r"([0-9]+) corrupted auxiliary roots", n -> n == 0) end + +@testset "Package with foreign type" begin + load_path = copy(LOAD_PATH) + push!(LOAD_PATH, joinpath(@__DIR__, "Foreign")) + push!(LOAD_PATH, joinpath(@__DIR__, "DependsOnForeign")) + try + # Force recaching + Base.compilecache(Base.identify_package("Foreign")) + Base.compilecache(Base.identify_package("DependsOnForeign")) + + push!(LOAD_PATH, joinpath(@__DIR__, "ForeignObjSerialization")) + @test_throws ErrorException Base.compilecache(Base.identify_package("ForeignObjSerialization"), Base.DevNull()) + pop!(LOAD_PATH) + + (@eval (using Foreign)) + @test Base.invokelatest(Foreign.get_nmark) == 0 + @test Base.invokelatest(Foreign.get_nsweep) == 0 + + obj = Base.invokelatest(Foreign.FObj) + GC.@preserve obj begin + GC.gc(true) + end + @test Base.invokelatest(Foreign.get_nmark) > 0 + @time Base.invokelatest(Foreign.test, 10) + GC.gc(true) + @test Base.invokelatest(Foreign.get_nsweep) > 0 + (@eval (using DependsOnForeign)) + Base.invokelatest(DependsOnForeign.f, obj) + finally + copy!(LOAD_PATH, load_path) + end +end diff --git a/test/gcext/gcext.c b/test/gcext/gcext.c index 2b380c43feccb..90b5ee82d80b5 100644 --- a/test/gcext/gcext.c +++ b/test/gcext/gcext.c @@ -307,6 +307,7 @@ static size_t gc_alloc_size(jl_value_t *val) int internal_obj_scan(jl_value_t *val) { + // FIXME: `jl_gc_internal_obj_base_ptr` is not allowed to be called from outside GC if (jl_gc_internal_obj_base_ptr(val) == val) { size_t size = gc_alloc_size(val); char *addr = (char *)val; @@ -491,7 +492,7 @@ void task_scanner(jl_task_t *task, int root_task) jl_active_task_stack(task, &start_stack, &end_stack, &total_start_stack, &total_end_stack); // this is the live stack of a thread. Is it ours? - if (start_stack && task == (jl_task_t *)jl_get_current_task()) { + if (start_stack && task == (jl_task_t*)jl_get_current_task()) { if (!(lt_ptr(start_stack, &var_on_frame) && lt_ptr(&var_on_frame, end_stack))) { // error, current stack frame must be on the live stack. jl_error("stack frame not part of the current task"); @@ -561,8 +562,10 @@ void sweep_stack_data(jl_value_t *p) { obj_sweeps++; dynstack_t *stk = (dynstack_t *)p; - if (stk->size > stk->capacity) - jl_error("internal error during sweeping"); + if (stk->size > stk->capacity) { + assert(0 && "internal error during sweeping"); + abort(); + } } // Safely execute Julia code @@ -609,8 +612,7 @@ int main() jl_gc_set_cb_root_scanner(abort_with_error, 1); jl_gc_set_cb_root_scanner(abort_with_error, 0); // Create module to store types in. - module = jl_new_module(jl_symbol("TestGCExt")); - module->parent = jl_main_module; + module = jl_new_module(jl_symbol("TestGCExt"), jl_main_module); jl_set_const(jl_main_module, jl_symbol("TestGCExt"), (jl_value_t *)module); // Define Julia types for our stack implementation. datatype_stack = jl_new_foreign_type( diff --git a/test/gmp.jl b/test/gmp.jl index 2eb1e9faf47da..8f6be13c38054 100644 --- a/test/gmp.jl +++ b/test/gmp.jl @@ -227,6 +227,7 @@ let a, b @test 0 == sum(BigInt[]) isa BigInt @test prod(b) == foldl(*, b) @test 1 == prod(BigInt[]) isa BigInt + @test prod(BigInt[0, 0, 0]) == 0 # issue #46665 end @testset "Iterated arithmetic" begin @@ -335,11 +336,13 @@ end @testset "digits" begin n = Int64(2080310129088201558) N = big(n) - for base in (2,7,10,11,16,30,50,62,64,100), pad in (0,1,10,100) - @test digits(n; base, pad) == digits(N; base, pad) + for base in (2,7,10,11,16,30,50,62,64,100,128), pad in (0,1,10,100) + @test digits(n; base, pad) == digits(N; base, pad) == digits(UInt8, N; base, pad) @test digits(-n; base, pad) == digits(-N; base, pad) @test digits!(Vector{Int}(undef, pad), n; base) == digits!(Vector{Int}(undef, pad), N; base) end + @test digits(UInt8, n; base=1<<8) == digits(UInt8, N; base=1<<8) + @test digits(UInt16, n; base=1<<16) == digits(UInt16, N; base=1<<16) end # serialization (#5133) @@ -542,3 +545,164 @@ end @test T(big"2"^(n+1) - big"2"^(n-precision(T)) - 1) === floatmax(T) end end + +a = Rational{BigInt}(12345678901234567890123456789, 987654321987654320) +b = Rational{BigInt}(12345678902222222212111111109, 987654321987654320) +c = Rational{BigInt}(24691357802469135780246913578, 987654321987654320) +d = Rational{BigInt}(- 12345678901234567890123456789, 493827160993827160) +e = Rational{BigInt}(12345678901234567890123456789, 12345678902222222212111111109) +@testset "big rational basics" begin + @test a+BigInt(1) == b + @test typeof(a+1) == Rational{BigInt} + @test a+1 == b + @test isequal(a+1, b) + @test b == a+1 + @test !(b == a) + @test b > a + @test b >= a + @test !(b < a) + @test !(b <= a) + + @test typeof(a * 2) == Rational{BigInt} + @test a*2 == c + @test c-a == a + @test c == a + a + @test c+1 == a+b + + @test typeof(d) == Rational{BigInt} + @test d == -c + + + @test e == a // b + + @testset "gmp cmp" begin + @test Base.GMP.MPQ.cmp(b, a) == 1 + @test Base.GMP.MPQ.cmp(a, b) == -1 + @test Base.GMP.MPQ.cmp(a, a) == 0 + end + + @testset "division errors" begin + oz = Rational{BigInt}(0, 1) + zo = Rational{BigInt}(1, 0) + + @test oz + oz == 3 * oz == oz + @test oz // zo == oz + @test zo // oz == zo + + @test_throws DivideError() zo - zo + @test_throws DivideError() zo + (-zo) + @test_throws DivideError() zo * oz + @test_throws DivideError() oz // oz + @test_throws DivideError() zo // zo + end + + @testset "big infinities" begin + oz = Rational{BigInt}(1, 0) + zo = Rational{BigInt}(0, 1) + o = Rational{BigInt}(1, 1) + + @test oz + zo == oz + @test zo - oz == -oz + @test zo + (-oz) == -oz + @test -oz + zo == -oz + + @test (-oz) * (-oz) == oz + @test (-oz) * oz == -oz + + @test o // zo == oz + @test (-o) // zo == -oz + + @test Rational{BigInt}(-1, 0) == -1//0 + @test Rational{BigInt}(1, 0) == 1//0 + end +end + + +aa = 1//2 +bb = -1//3 +cc = 3//2 +a = Rational{BigInt}(aa) +b = Rational{BigInt}(bb) +c = Rational{BigInt}(cc) +t = Rational{BigInt}(0, 1) +@testset "big rational inplace" begin + @test Base.GMP.MPQ.add!(t, a, b) == 1//6 + @test t == 1//6 + @test Base.GMP.MPQ.add!(t, t) == 1//3 + @test t == 1//3 + + @test iszero(Base.GMP.MPQ.sub!(t, t)) + @test iszero(t) + @test Base.GMP.MPQ.sub!(t, b, c) == -11//6 + @test t == -11//6 + + @test Base.GMP.MPQ.mul!(t, a, b) == -1//6 + @test t == -1//6 + @test Base.GMP.MPQ.mul!(t, t) == 1//36 + @test t == 1//36 + @test iszero(Base.GMP.MPQ.mul!(t, Rational{BigInt}(0))) + + @test Base.GMP.MPQ.div!(t, a, b) == -3//2 + @test t == -3//2 + @test Base.GMP.MPQ.div!(t, a) == -3//1 + @test t == -3//1 + + @test aa == a && bb == b && cc == c + + @testset "set" begin + @test Base.GMP.MPQ.set!(a, b) == b + @test a == b == bb + + Base.GMP.MPQ.add!(a, b, c) + @test b == bb + + @test Base.GMP.MPQ.set_z!(a, BigInt(0)) == 0 + @test iszero(a) + @test Base.GMP.MPQ.set_z!(a, BigInt(3)) == 3 + @test a == BigInt(3) + + @test Base.GMP.MPQ.set_ui(1, 2) == 1//2 + @test Base.GMP.MPQ.set_ui(0, 1) == 0//1 + @test Base.GMP.MPQ.set_ui!(a, 1, 2) == 1//2 + @test a == 1//2 + + @test Base.GMP.MPQ.set_si(1, 2) == 1//2 + @test Base.GMP.MPQ.set_si(-1, 2) == -1//2 + @test Base.GMP.MPQ.set_si!(a, -1, 2) == -1//2 + @test a == -1//2 + end + + @testset "infinities" begin + oz = Rational{BigInt}(1, 0) + zo = Rational{BigInt}(0, 1) + oo = Rational{BigInt}(1, 1) + + @test Base.GMP.MPQ.add!(zo, oz) == oz + @test zo == oz + zo = Rational{BigInt}(0, 1) + + @test Base.GMP.MPQ.sub!(zo, oz) == -oz + @test zo == -oz + zo = Rational{BigInt}(0, 1) + + @test Base.GMP.MPQ.add!(zo, -oz) == -oz + @test zo == -oz + zo = Rational{BigInt}(0, 1) + + @test Base.GMP.MPQ.sub!(zo, -oz) == oz + @test zo == oz + zo = Rational{BigInt}(0, 1) + + @test Base.GMP.MPQ.mul!(-oz, -oz) == oz + @test Base.GMP.MPQ.mul!(-oz, oz) == -oz + @test Base.GMP.MPQ.mul!(oz, -oz) == -1//0 + @test oz == -1//0 + oz = Rational{BigInt}(1, 0) + + @test Base.GMP.MPQ.div!(oo, zo) == oz + @test oo == oz + oo = Rational{BigInt}(1, 1) + + @test Base.GMP.MPQ.div!(-oo, zo) == -oz + end +end diff --git a/test/hashing.jl b/test/hashing.jl index 9bd076554962f..943109924f280 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -60,6 +60,9 @@ end @test hash(nextfloat(2.0^63)) == hash(UInt64(nextfloat(2.0^63))) @test hash(prevfloat(2.0^64)) == hash(UInt64(prevfloat(2.0^64))) +# issue #48744 +@test hash(typemin(Int)//1) === hash(big(typemin(Int)//1)) + # issue #9264 @test hash(1//6,zero(UInt)) == invoke(hash, Tuple{Real, UInt}, 1//6, zero(UInt)) @test hash(1//6) == hash(big(1)//big(6)) @@ -201,9 +204,9 @@ let a = QuoteNode(1), b = QuoteNode(1.0) @test (hash(a)==hash(b)) == (a==b) end -let a = Expr(:block, Core.TypedSlot(1, Any)), - b = Expr(:block, Core.TypedSlot(1, Any)), - c = Expr(:block, Core.TypedSlot(3, Any)) +let a = Expr(:block, Core.SlotNumber(1)), + b = Expr(:block, Core.SlotNumber(1)), + c = Expr(:block, Core.SlotNumber(3)) @test a == b && hash(a) == hash(b) @test a != c && hash(a) != hash(c) @test b != c && hash(b) != hash(c) @@ -284,3 +287,18 @@ end end end end + +if Sys.WORD_SIZE >= 64 + @testset "very large string" begin + N = 2^31+1 + s = String('\0'^N); + objectid(s) + end +end + +# Issue #49620 +let t1 = Tuple{AbstractVector,AbstractVector{<:Integer},UnitRange{<:Integer}}, + t2 = Tuple{AbstractVector,AbstractVector{<:Integer},UnitRange{<:Integer}} + @test hash(t1) == hash(t2) + @test length(Set{Type}([t1, t2])) == 1 +end diff --git a/test/int.jl b/test/int.jl index b75337c405767..f79bc5a9781d0 100644 --- a/test/int.jl +++ b/test/int.jl @@ -55,37 +55,38 @@ using Random end end @testset "signed and unsigned" begin - @test signed(3) == 3 - @test signed(UInt(3)) == 3 + @test signed(3) === 3 + @test signed(UInt(3)) === 3 @test isa(signed(UInt(3)), Int) - @test signed(UInt(0) - 1) == -1 + @test signed(UInt(0) - 1) === -1 @test_throws InexactError signed(UInt(-3)) - @test signed(true) == 1 + @test signed(true) === 1 @test unsigned(true) isa Unsigned - @test unsigned(true) == unsigned(1) + @test unsigned(true) === unsigned(1) - @test signed(Bool) == Int - @test signed(Bool) == typeof(signed(true)) - @test unsigned(Bool) == UInt - @test unsigned(Bool) == typeof(unsigned(true)) + @test signed(Bool) === Int + @test signed(Bool) === typeof(signed(true)) + @test unsigned(Bool) === UInt + @test unsigned(Bool) === typeof(unsigned(true)) end @testset "bswap" begin + @test bswap(true) == true @test bswap(Int8(3)) == 3 - @test bswap(UInt8(3)) == 3 + @test bswap(UInt8(3)) === 0x3 @test bswap(Int16(3)) == 256*3 @test bswap(Int16(256)) == 1 @test bswap(Int16(257)) == 257 @test bswap(Int32(1)) == 2^(3*8) @test bswap(Int32(2)^(3*8)) == 1 - @test bswap(Int64(1)) == Int64(2)^(7*8) + @test bswap(Int64(1)) === Int64(2)^(7*8) @test bswap(Int64(2)^(7*8)) == 1 - @test bswap(Int128(1)) == Int128(2)^(15*8) - @test bswap(Int128(2)^(15*8)) == Int128(1) - @test bswap(UInt128(2)^(15*8)) == UInt128(1) + @test bswap(Int128(1)) === Int128(2)^(15*8) + @test bswap(Int128(2)^(15*8)) === Int128(1) + @test bswap(UInt128(2)^(15*8)) === UInt128(1) end @testset "count_zeros" begin - @test count_zeros(10) == Sys.WORD_SIZE - 2 - @test count_zeros(UInt8(10)) == 6 + @test count_zeros(10) === Sys.WORD_SIZE - 2 + @test count_zeros(UInt8(10)) === 6 end @testset "Conversions" begin @test convert(Signed, UInt128(3)) === Int128(3) @@ -104,11 +105,11 @@ end end @testset "trunc, floor, ceil" begin - @test trunc(3) == 3 - @test trunc(Integer, 3) == 3 + @test trunc(3) === 3 + @test trunc(Integer, 3) === 3 - @test floor(3) == 3 - @test ceil(3) == 3 + @test floor(3) === 3 + @test ceil(3) === 3 end @testset "big" begin @@ -120,10 +121,11 @@ end end @test round(UInt8, 123) == 123 -@test mod(123, UInt8) == 0x7b +@test mod(123, UInt8) === 0x7b -primitive type MyBitsType <: Integer 8 end +primitive type MyBitsType <: Signed 8 end @test_throws MethodError ~reinterpret(MyBitsType, 0x7b) +@test signed(MyBitsType) === MyBitsType UItypes = Base.BitUnsigned_types SItypes = Base.BitSigned_types @@ -197,6 +199,17 @@ end @test val >> -scount === val << ucount end end + for T2 in Base.BitInteger_types + for op in (>>, <<, >>>) + if sizeof(T2)==sizeof(Int) || T <: Signed || (op==>>>) || T2 <: Unsigned + @test Core.Compiler.is_foldable_nothrow(Base.infer_effects(op, (T, T2))) + else + @test Core.Compiler.is_foldable(Base.infer_effects(op, (T, T2))) + # #47835, TODO implement interval arithmetic analysis + @test_broken Core.Compiler.is_nothrow(Base.infer_effects(op, (T, T2))) + end + end + end end end @@ -211,8 +224,8 @@ end end val2 = 0xabcd - @test 0x5e6d == bitrotate(val2, 3) - @test 0xb579 == bitrotate(val2, -3) + @test 0x5e6d === bitrotate(val2, 3) + @test 0xb579 === bitrotate(val2, -3) end @testset "widen/widemul" begin @@ -240,12 +253,12 @@ end @test typeof(widen(Int64(-3))) == Int128 @test typeof(widen(Int128(-3))) == BigInt - @test widemul(false, false) == false - @test widemul(false, 3) == 0 - @test widemul(3, true) == widemul(true, 3) == 3 + @test widemul(false, false) === false + @test widemul(false, 3) === 0 + @test widemul(3, true) === widemul(true, 3) === 3 let i=Int64(2)^63-1, k=widemul(i,i) - @test widemul(i,i)==85070591730234615847396907784232501249 + @test widemul(i,i)===85070591730234615847396907784232501249 j=div(k,2) @test div(k,j)==2 j=div(k,5) @@ -287,6 +300,29 @@ end end end +@testset "typemin typemax" begin + @test typemin(Int8 ) === Int8(-128) + @test typemax(Int8 ) === Int8(127) + @test typemin(UInt8 ) === UInt8(0) + @test typemax(UInt8 ) === UInt8(255) + @test typemin(Int16 ) === Int16(-32768) + @test typemax(Int16 ) === Int16(32767) + @test typemin(UInt16 ) === UInt16(0) + @test typemax(UInt16 ) === UInt16(65535) + @test typemin(Int32 ) === Int32(-2147483648) + @test typemax(Int32 ) === Int32(2147483647) + @test typemin(UInt32 ) === UInt32(0) + @test typemax(UInt32 ) === UInt32(4294967295) + @test typemin(Int64 ) === Int64(-9223372036854775808) + @test typemax(Int64 ) === Int64(9223372036854775807) + @test typemin(UInt64 ) === UInt64(0) + @test typemax(UInt64 ) === UInt64(0xffff_ffff_ffff_ffff) + @test typemin(UInt128) === UInt128(0) + @test typemax(UInt128) === UInt128(0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff) + @test typemin(Int128 ) === Int128(-170141183460469231731687303715884105728) + @test typemax(Int128 ) === Int128(170141183460469231731687303715884105727) +end + @testset "issue #15489" begin @test 0x00007ffea27edaa0 + (-40) === (-40) + 0x00007ffea27edaa0 === 0x00007ffea27eda78 @test UInt64(1) * Int64(-1) === typemax(UInt64) @@ -351,25 +387,28 @@ end @testset "rounding division" begin for x = -100:100 for y = 1:100 - for rnd in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp) + for rnd in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundFromZero) @test div(x,y,rnd) == round(x/y,rnd) @test div(x,-y,rnd) == round(x/-y,rnd) end + @test divrem(x,y,RoundFromZero) == (div(x,y,RoundFromZero), rem(x,y,RoundFromZero)) + @test divrem(x,-y,RoundFromZero) == (div(x,-y,RoundFromZero), rem(x,-y,RoundFromZero)) end end - for (a, b, nearest, away, up) in ( - (3, 2, 2, 2, 2), - (5, 3, 2, 2, 2), - (-3, 2, -2, -2, -1), - (5, 2, 2, 3, 3), - (-5, 2, -2, -3, -2), - (-5, 3, -2, -2, -2), - (5, -3, -2, -2, -2)) + for (a, b, nearest, away, up, from_zero) in ( + (3, 2, 2, 2, 2, 2), + (5, 3, 2, 2, 2, 2), + (-3, 2, -2, -2, -1, -2), + (5, 2, 2, 3, 3, 3), + (-5, 2, -2, -3, -2, -3), + (-5, 3, -2, -2, -2, -2), + (5, -3, -2, -2, -2, -2)) for sign in (+1, -1) (a, b) = (a*sign, b*sign) - @test div(a, b, RoundNearest) == nearest - @test div(a, b, RoundNearestTiesAway) == away - @test div(a, b, RoundNearestTiesUp) == up + @test div(a, b, RoundNearest) === nearest + @test div(a, b, RoundNearestTiesAway) === away + @test div(a, b, RoundNearestTiesUp) === up + @test div(a, b, RoundFromZero) === from_zero end end @@ -377,10 +416,10 @@ end @test div(-typemax(Int64), typemax(Int64)-1, RoundNearest) == -1 @test div(typemax(Int64), 2, RoundNearest) == 4611686018427387904 @test div(-typemax(Int64), 2, RoundNearestTiesUp) == -4611686018427387903 - @test div(typemax(Int)-2, typemax(Int), RoundNearest) == 1 + @test div(typemax(Int)-2, typemax(Int), RoundNearest) === 1 # Exhaustively test (U)Int8 to catch any overflow-style issues - for r in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp) + for r in (RoundNearest, RoundNearestTiesAway, RoundNearestTiesUp, RoundFromZero) for T in (UInt8, Int8) for x in typemin(T):typemax(T) for y in typemin(T):typemax(T) @@ -406,26 +445,9 @@ end @test bitreverse(Int32(456618293)) === Int32(-1399919400) end -@testset "min/max of datatype" begin - @test typemin(Int8) == Int8(-128) - @test typemin(UInt8) == UInt8(0) - @test typemin(Int16) == Int16(-32768) - @test typemin(UInt16) == UInt16(0) - @test typemin(Int32) == Int32(-2147483648) - @test typemin(UInt32) == UInt32(0) - @test typemin(Int64) == Int64(-9223372036854775808) - @test typemin(UInt64) == UInt64(0) - @test typemin(Int128) == Int128(-170141183460469231731687303715884105728) - @test typemin(UInt128) == UInt128(0) - - @test typemax(Int8) == Int8(127) - @test typemax(UInt8) == UInt8(255) - @test typemax(Int16) == Int16(32767) - @test typemax(UInt16) == UInt16(65535) - @test typemax(Int32) == Int32(2147483647) - @test typemax(UInt32) == UInt32(4294967295) - @test typemax(Int64) == Int64(9223372036854775807) - @test typemax(UInt64) == UInt64(0xffffffffffffffff) - @test typemax(Int128) == Int128(170141183460469231731687303715884105727) - @test typemax(UInt128) == UInt128(0xffffffffffffffffffffffffffffffff) +@testset "BitIntegerType" begin + @test Int isa Base.BitIntegerType + @test Base.BitIntegerType === Union{ + Type{ Int8}, Type{ Int16}, Type{ Int32}, Type{ Int64}, Type{ Int128}, + Type{UInt8}, Type{UInt16}, Type{UInt32}, Type{UInt64}, Type{UInt128}} end diff --git a/test/intfuncs.jl b/test/intfuncs.jl index 4fc21c3bcf1b2..ceaac235a3da9 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -2,6 +2,8 @@ using Random +is_effect_free(args...) = Core.Compiler.is_effect_free(Base.infer_effects(args...)) + @testset "gcd/lcm" begin # All Integer data types take different code paths -- test all # TODO: Test gcd and lcm for BigInt. @@ -146,6 +148,11 @@ using Random @test gcd(0xf, 20) == 5 @test gcd(UInt32(6), Int8(-50)) == 2 @test gcd(typemax(UInt), -16) == 1 + + @testset "effects" begin + @test is_effect_free(gcd, Tuple{Int,Int}) + @test is_effect_free(lcm, Tuple{Int,Int}) + end end @testset "gcd/lcm for arrays" begin @@ -204,6 +211,14 @@ end @test gcd(MyRational(2//3), 3) == gcd(2//3, 3) == gcd(Real[MyRational(2//3), 3]) @test lcm(MyRational(2//3), 3) == lcm(2//3, 3) == lcm(Real[MyRational(2//3), 3]) @test gcdx(MyRational(2//3), 3) == gcdx(2//3, 3) + + # test error path + struct MyOtherRational <: Real + val::Rational{Int} + end + @test_throws MethodError gcd(MyOtherRational(2//3), MyOtherRational(3//4)) + @test_throws MethodError lcm(MyOtherRational(2//3), MyOtherRational(3//4)) + @test_throws MethodError gcdx(MyOtherRational(2//3), MyOtherRational(3//4)) end @testset "invmod" begin @@ -252,6 +267,14 @@ end @test powermod(2, -2, 5) == 4 @test powermod(2, -1, -5) == -2 @test powermod(2, -2, -5) == -1 + + @test powermod(2, typemin(Int128), 5) == 1 + @test powermod(2, typemin(Int128), -5) == -4 + + @test powermod(2, big(3), 5) == 3 + @test powermod(2, big(3), -5) == -2 + @inferred powermod(2, -2, -5) + @inferred powermod(big(2), -2, UInt(5)) end @testset "nextpow/prevpow" begin @@ -426,12 +449,42 @@ end end end -@testset "leading_ones and count_zeros" begin +@testset "leading_ones, count_zeros, etc." begin @test leading_ones(UInt32(Int64(2) ^ 32 - 2)) == 31 @test leading_ones(1) == 0 @test leading_zeros(Int32(1)) == 31 @test leading_zeros(UInt32(Int64(2) ^ 32 - 2)) == 0 + @test Base.top_set_bit(3) == 2 + @test Base.top_set_bit(-Int64(17)) == 64 + @test Base.top_set_bit(big(15)) != Base.top_set_bit(big(16)) == Base.top_set_bit(big(17)) == 5 + @test_throws DomainError Base.top_set_bit(big(-17)) + + struct MyInt <: Integer + x::Int + end + MyInt(x::MyInt) = x + Base.:+(a::MyInt, b::MyInt) = a.x + b.x + + for n in 0:100 + x = ceil(Int, log2(n + 1)) + @test x == Base.top_set_bit(Int128(n)) == Base.top_set_bit(unsigned(Int128(n))) + @test x == Base.top_set_bit(Int32(n)) == Base.top_set_bit(unsigned(Int64(n))) + @test x == Base.top_set_bit(Int8(n)) == Base.top_set_bit(unsigned(Int8(n))) + @test x == Base.top_set_bit(big(n)) # BigInt fallback + @test x == Base.top_set_bit(MyInt(n)) # generic fallback + end + + for n in -10:-1 + @test 128 == Base.top_set_bit(Int128(n)) == Base.top_set_bit(unsigned(Int128(n))) + @test 32 == Base.top_set_bit(Int32(n)) == Base.top_set_bit(unsigned(Int32(n))) + @test 8 == Base.top_set_bit(Int8(n)) == Base.top_set_bit(unsigned(Int8(n))) + @test_throws DomainError Base.top_set_bit(big(n)) + # This error message should never be exposed to the end user anyway. + err = n == -1 ? InexactError : DomainError + @test_throws err Base.top_set_bit(MyInt(n)) + end + @test count_zeros(Int64(1)) == 63 end @@ -503,4 +556,18 @@ end for x in ((false,false), (false,true), (true,false), (true,true)) @test binomial(x...) == (x != (false,true)) end + + # binomial(x,k) for non-integer x + @test @inferred(binomial(10.0,3)) === 120.0 + @test @inferred(binomial(10//1,3)) === 120//1 + @test binomial(2.5,3) ≈ 5//16 === binomial(5//2,3) + @test binomial(2.5,0) == 1.0 + @test binomial(35.0, 30) ≈ binomial(35, 30) # naive method overflows + @test binomial(2.5,-1) == 0.0 end + +# concrete-foldability +@test Base.infer_effects(gcd, (Int,Int)) |> Core.Compiler.is_foldable +@test Base.infer_effects(gcdx, (Int,Int)) |> Core.Compiler.is_foldable +@test Base.infer_effects(invmod, (Int,Int)) |> Core.Compiler.is_foldable +@test Base.infer_effects(binomial, (Int,Int)) |> Core.Compiler.is_foldable diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 2f2ef0cd505d5..aa2a9649857c4 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -9,6 +9,11 @@ include("testenv.jl") @test isa((() -> Core.Intrinsics.bitcast(Ptr{Int8}, 0))(), Ptr{Int8}) @test isa(convert(Char, 65), Char) +truncbool(u) = reinterpret(UInt8, reinterpret(Bool, u)) +@test truncbool(0x01) == 0x01 +@test truncbool(0x02) == 0x00 +@test truncbool(0x03) == 0x01 + # runtime intrinsics @testset "runtime intrinsics" begin @test Core.Intrinsics.add_int(1, 1) == 2 @@ -143,7 +148,6 @@ end @test_intrinsic Core.Intrinsics.sub_float Float16(3.3) Float16(2) Float16(1.301) @test_intrinsic Core.Intrinsics.mul_float Float16(3.3) Float16(2) Float16(6.6) @test_intrinsic Core.Intrinsics.div_float Float16(3.3) Float16(2) Float16(1.65) - @test_intrinsic Core.Intrinsics.rem_float Float16(3.3) Float16(2) Float16(1.301) # ternary @test_intrinsic Core.Intrinsics.fma_float Float16(3.3) Float16(4.4) Float16(5.5) Float16(20.02) @@ -164,6 +168,30 @@ 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 + @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 @test_throws ConcurrencyViolationError("invalid atomic ordering") (@force_compile; Core.Intrinsics.atomic_fence(:u)) === nothing @test_throws ConcurrencyViolationError("invalid atomic ordering") (@force_compile; Core.Intrinsics.atomic_fence(Symbol("u", "x"))) === nothing diff --git a/test/iobuffer.jl b/test/iobuffer.jl index d8211aa7086b3..ec77903b4a5b8 100644 --- a/test/iobuffer.jl +++ b/test/iobuffer.jl @@ -348,3 +348,12 @@ end @testset "bytesavailable devnull" begin @test bytesavailable(devnull) == 0 end + +@testset "#48188 read_sub for non Array AbstractArray" begin + a = [0,0,0] + v = @view a[1:2] + io = IOBuffer() + write(io,1) + seek(io,0) + @test Base.read_sub(io,v,1,1) == [1,0] +end diff --git a/test/iterators.jl b/test/iterators.jl index 1b2498fb1f905..59588bdac9684 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -102,6 +102,7 @@ end @test length(zip(cycle(1:3), 1:7, cycle(1:3))) == 7 @test length(zip(1:3,product(1:7,cycle(1:3)))) == 3 @test length(zip(1:3,product(1:7,cycle(1:3)),8)) == 1 +@test_throws ArgumentError length(zip()) # length of zip of empty tuple # map # ---- @@ -186,8 +187,17 @@ end @test isempty(collect(drop(0:2:10, 100))) @test_throws ArgumentError drop(0:2:8, -1) @test length(drop(1:3,typemax(Int))) == 0 +@test length(drop(UInt(1):2, 3)) == 0 +@test length(drop(StepRangeLen(1, 1, UInt(2)), 3)) == 0 @test Base.IteratorSize(drop(countfrom(1),3)) == Base.IsInfinite() @test_throws MethodError length(drop(countfrom(1), 3)) +@test Base.IteratorSize(Iterators.drop(Iterators.filter(i -> i>0, 1:10), 2)) == Base.SizeUnknown() + +let x = Iterators.drop(Iterators.Stateful("abc"), 2) + @test !Base.isdone(x, nothing) + iterate(x) + @test Base.isdone(x, nothing) +end # double take # and take/drop canonicalization @@ -236,6 +246,8 @@ let i = 0 i += 1 i <= 10 || break end + @test Base.isdone(cycle(0:3)) === Base.isdone(0:3) === missing + @test !Base.isdone(cycle(0:3), 1) end # repeated @@ -314,6 +326,8 @@ let itr @test collect(itr) == Int[] # Stateful do not preserve shape itr = (i-1 for i in Base.Stateful(zeros(Int, 0, 0))) @test collect(itr) == Int[] # Stateful do not preserve shape + itr = Iterators.Stateful(Iterators.Stateful(1:1)) + @test collect(itr) == [1] end # with 1D inputs @@ -322,21 +336,25 @@ let a = 1:2, c = Int32(1):Int32(0) # length + @test length(product()) == 1 @test length(product(a)) == 2 @test length(product(a, b)) == 20 @test length(product(a, b, c)) == 0 # size + @test size(product()) == tuple() @test size(product(a)) == (2,) @test size(product(a, b)) == (2, 10) @test size(product(a, b, c)) == (2, 10, 0) # eltype + @test eltype(product()) == Tuple{} @test eltype(product(a)) == Tuple{Int} @test eltype(product(a, b)) == Tuple{Int, Float64} @test eltype(product(a, b, c)) == Tuple{Int, Float64, Int32} # ndims + @test ndims(product()) == 0 @test ndims(product(a)) == 1 @test ndims(product(a, b)) == 2 @test ndims(product(a, b, c)) == 3 @@ -411,6 +429,8 @@ let a = 1:2, @test_throws ArgumentError size(product(itr)) @test_throws ArgumentError ndims(product(itr)) end + + @test_throws OverflowError length(product(1:typemax(Int), 1:typemax(Int))) end # IteratorSize trait business @@ -430,6 +450,10 @@ end @test Base.IteratorSize(product(take(1:2, 1), take(1:2, 1))) == Base.HasShape{2}() @test Base.IteratorSize(product(take(1:2, 2))) == Base.HasShape{1}() @test Base.IteratorSize(product([1 2; 3 4])) == Base.HasShape{2}() +@test Base.IteratorSize(product((1,2,3,4), (5, 6, 7, 8))) == Base.HasShape{2}() # product of ::HasLength and ::HasLength +@test Base.IteratorSize(product(1:2, 3:5, 5:6)) == Base.HasShape{3}() # product of 3 iterators +@test Base.IteratorSize(product([1 2; 3 4], 1:4)) == Base.HasShape{3}() # product of ::HasShape{2} with ::HasShape{1} +@test Base.IteratorSize(product([1 2; 3 4], (1,2))) == Base.HasShape{3}() # product of ::HasShape{2} with ::HasLength # IteratorEltype trait business let f1 = Iterators.filter(i->i>0, 1:10) @@ -447,12 +471,20 @@ end @test Base.IteratorEltype(product(take(1:2, 1), take(1:2, 1))) == Base.HasEltype() @test Base.IteratorEltype(product(take(1:2, 2))) == Base.HasEltype() @test Base.IteratorEltype(product([1 2; 3 4])) == Base.HasEltype() +@test Base.IteratorEltype(product()) == Base.HasEltype() @test collect(product(1:2,3:4)) == [(1,3) (1,4); (2,3) (2,4)] @test isempty(collect(product(1:0,1:2))) @test length(product(1:2,1:10,4:6)) == 60 @test Base.IteratorSize(product(1:2, countfrom(1))) == Base.IsInfinite() +@test Base.iterate(product()) == ((), true) +@test Base.iterate(product(), 1) == nothing + +# intersection +@test intersect(product(1:3, 4:6), product(2:4, 3:5)) == Iterators.ProductIterator((2:3, 4:5)) +@test intersect(product(1:3, [4 5 ; 6 7]), product(2:4, [7 6 ; 5 4])).iterators == (2:3, [4, 6, 5, 7]) + # flatten # ------- @test collect(flatten(Any[1:2, 4:5])) == Any[1,2,4,5] @@ -468,17 +500,46 @@ end @test_throws ArgumentError length(flatten(NTuple[(1,), ()])) # #16680 @test_throws ArgumentError length(flatten([[1], [1]])) +@testset "IteratorSize trait for flatten" begin + @test Base.IteratorSize(Base.Flatten((i for i=1:2) for j=1:1)) == Base.SizeUnknown() + @test Base.IteratorSize(Base.Flatten((1,2))) == Base.HasLength() + @test Base.IteratorSize(Base.Flatten(1:2:4)) == Base.HasLength() +end + @test Base.IteratorEltype(Base.Flatten((i for i=1:2) for j=1:1)) == Base.EltypeUnknown() # see #29112, #29464, #29548 @test Base.return_types(Base.IteratorEltype, Tuple{Array}) == [Base.HasEltype] +# flatmap +# ------- +@test flatmap(1:3) do j flatmap(1:3) do k + j!=k ? ((j,k),) : () +end end |> collect == [(j,k) for j in 1:3 for k in 1:3 if j!=k] +# Test inspired by the monad associativity law +fmf(x) = x<0 ? () : (x^2,) +fmg(x) = x<1 ? () : (x/2,) +fmdata = -2:0.75:2 +fmv1 = flatmap(tuple.(fmdata)) do h + flatmap(h) do x + gx = fmg(x) + flatmap(gx) do x + fmf(x) + end + end +end +fmv2 = flatmap(tuple.(fmdata)) do h + gh = flatmap(h) do x fmg(x) end + flatmap(gh) do x fmf(x) end +end +@test all(fmv1 .== fmv2) + # partition(c, n) let v = collect(partition([1,2,3,4,5], 1)) @test all(i->v[i][1] == i, v) end let v1 = collect(partition([1,2,3,4,5], 2)), - v2 = collect(partition(flatten([[1,2],[3,4],5]), 2)) # collecting partition with SizeUnkown + v2 = collect(partition(flatten([[1,2],[3,4],5]), 2)) # collecting partition with SizeUnknown @test v1[1] == v2[1] == [1,2] @test v1[2] == v2[2] == [3,4] @test v1[3] == v2[3] == [5] @@ -550,12 +611,15 @@ end (1,1), (8,8), (11, 13), (1,1,1), (8, 4, 2), (11, 13, 17)), part in (1, 7, 8, 11, 63, 64, 65, 142, 143, 144) - P = partition(CartesianIndices(dims), part) - for I in P - @test length(I) == iterate_length(I) == simd_iterate_length(I) == simd_trip_count(I) - @test collect(I) == iterate_elements(I) == simd_iterate_elements(I) == index_elements(I) + for fun in (i -> 1:i, i -> 1:2:2i, i -> Base.IdentityUnitRange(-i:i)) + iter = CartesianIndices(map(fun, dims)) + P = partition(iter, part) + for I in P + @test length(I) == iterate_length(I) == simd_iterate_length(I) == simd_trip_count(I) + @test collect(I) == iterate_elements(I) == simd_iterate_elements(I) == index_elements(I) + end + @test all(Base.splat(==), zip(Iterators.flatten(map(collect, P)), iter)) end - @test all(Base.splat(==), zip(Iterators.flatten(map(collect, P)), CartesianIndices(dims))) end @testset "empty/invalid partitions" begin @test_throws ArgumentError partition(1:10, 0) @@ -716,6 +780,7 @@ end @test popfirst!(a) == 'a' @test collect(Iterators.take(a, 3)) == ['b','c','d'] @test collect(a) == ['e', 'f'] + @test_throws EOFError popfirst!(a) # trying to pop from an empty stateful iterator. end let a = @inferred(Iterators.Stateful([1, 1, 1, 2, 3, 4])) for x in a; x == 1 || break; end @@ -784,6 +849,8 @@ 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 end @testset "pair for Svec" begin @@ -830,6 +897,8 @@ end @test_throws ArgumentError only([]) @test_throws ArgumentError only([3, 2]) + @test only(fill(42)) === 42 # zero dimensional array containing a single value. + @test @inferred(only((3,))) === 3 @test_throws ArgumentError only(()) @test_throws ArgumentError only((3, 2)) @@ -883,7 +952,24 @@ end @test accumulate(+, (x^2 for x in 1:3); init=100) == [101, 105, 114] end -@testset "proper patition for non-1-indexed vector" begin + +@testset "Iterators.tail_if_any" begin + @test Iterators.tail_if_any(()) == () + @test Iterators.tail_if_any((1, 2)) == (2,) + @test Iterators.tail_if_any((1,)) == () +end + +@testset "IteratorSize trait for zip" begin + @test Base.IteratorSize(zip()) == Base.IsInfinite() # for zip of empty tuple + @test Base.IteratorSize(zip((1,2,3), repeated(0))) == Base.HasLength() # for zip of ::HasLength and ::IsInfinite + @test Base.IteratorSize(zip( 1:5, repeated(0) )) == Base.HasLength() # for zip of ::HasShape and ::IsInfinite + @test Base.IteratorSize(zip(repeated(0), (1,2,3))) == Base.HasLength() # for zip of ::IsInfinite and ::HasLength + @test Base.IteratorSize(zip(repeated(0), 1:5 )) == Base.HasLength() # for zip of ::IsInfinite and ::HasShape + @test Base.IteratorSize(zip((1,2,3), 1:5) ) == Base.HasLength() # for zip of ::HasLength and ::HasShape + @test Base.IteratorSize(zip(1:5, (1,2,3)) ) == Base.HasLength() # for zip of ::HasShape and ::HasLength +end + +@testset "proper partition for non-1-indexed vector" begin @test partition(IdentityUnitRange(11:19), 5) |> collect == [11:15,16:19] # IdentityUnitRange end @@ -899,3 +985,19 @@ end @test last(Iterators.map(identity, 1:3)) == 3 @test last(Iterators.filter(iseven, (Iterators.map(identity, 1:3)))) == 2 end + +@testset "isempty and isdone for Generators" begin + itr = eachline(IOBuffer("foo\n")) + gen = (x for x in itr) + @test !isempty(gen) + @test !Base.isdone(gen) + @test collect(gen) == ["foo"] +end + +@testset "empty product iterators" begin + v = nothing + for (z,) in zip(Iterators.product()) + v = z + end + @test v == () +end diff --git a/test/keywordargs.jl b/test/keywordargs.jl index 9cbae2b1a0b19..0aed0544b7e2e 100644 --- a/test/keywordargs.jl +++ b/test/keywordargs.jl @@ -181,7 +181,7 @@ end @test test4538_2(x=2) == 2 # that, but in a module - @Foo4538.TEST() + Foo4538.@TEST() @test test4538_foo_2() == 1 @test test4538_foo_2(x=2) == 2 @@ -288,7 +288,7 @@ end end @testset "issue #21510" begin f21510(; @nospecialize a = 2) = a - @test f21510(a=:b) == :b + @test f21510(a=:b) === :b @test f21510() == 2 end @testset "issue #34516" begin @@ -297,7 +297,7 @@ end @test_throws UndefKeywordError f34516() @test_throws UndefKeywordError f34516(1) g34516(@nospecialize(x); k=0) = 0 - @test first(methods(Core.kwfunc(g34516))).nospecialize != 0 + @test only(methods(Core.kwcall, (Any, typeof(g34516), Vararg))).nospecialize != 0 end @testset "issue #21518" begin a = 0 @@ -387,3 +387,10 @@ f41416(a...="a"; b=true) = (b, a) @test f41416(;b=false) === (false, ("a",)) @test f41416(33) === (true, (33,)) @test f41416(3; b=false) === (false, (3,)) + +Core.kwcall(i::Int) = "hi $i" +let m = first(methods(Core.kwcall, (NamedTuple,typeof(kwf1),Vararg))) + @test m.name === :kwf1 + @test Core.kwcall(1) == "hi 1" + @test which(Core.kwcall, (Int,)).name === :kwcall +end diff --git a/test/llvmcall.jl b/test/llvmcall.jl index b7f78205ec856..f7f6b44b29e62 100644 --- a/test/llvmcall.jl +++ b/test/llvmcall.jl @@ -205,6 +205,23 @@ module CcallableRetTypeTest @test do_the_call() === 42.0 end +# Issue #48093 - test that non-external globals are not deduplicated +function kernel() + Base.llvmcall((""" + @shmem = internal global i8 0, align 8 + define void @entry() { + store i8 1, i8* @shmem + ret void + }""", "entry"), Cvoid, Tuple{}) + Base.llvmcall((""" + @shmem = internal global i8 0, align 8 + define i8 @entry() { + %1 = load i8, i8* @shmem + ret i8 %1 + }""", "entry"), UInt8, Tuple{}) +end +@test kernel() == 0x00 + # If this test breaks, you've probably broken Cxx.jl - please check module LLVMCallFunctionTest using Base: llvmcall @@ -247,5 +264,7 @@ MyStruct(kern) = MyStruct(kern, reinterpret(Core.LLVMPtr{UInt8,1}, 0)) MyStruct() = MyStruct(0) s = MyStruct() +# ensure LLVMPtr properly subtypes +@test eltype(supertype(Core.LLVMPtr{UInt8,1})) <: UInt8 @test s.kern == 0 @test reinterpret(Int, s.ptr) == 0 diff --git a/test/llvmcall2.jl b/test/llvmcall2.jl index cfd20d210bfd7..8926b962a35c6 100644 --- a/test/llvmcall2.jl +++ b/test/llvmcall2.jl @@ -37,10 +37,26 @@ function ceilfloor(x::Float64) end @test ceilfloor(7.4) == 8.0 -# support for calling external functions -begin - f() = ccall("time", llvmcall, Cvoid, (Ptr{Cvoid},), C_NULL) - @test_throws ErrorException f() +let err = ErrorException("llvmcall only supports intrinsic calls") + # support for calling external functions + @test_throws err @eval ccall("time", llvmcall, Cvoid, (Ptr{Cvoid},), C_NULL) g() = ccall("extern time", llvmcall, Cvoid, (Ptr{Cvoid},), C_NULL) g() + @test_throws err @eval ccall("extern llvm.floor", llvmcall, Float64, (Float64,), 0.0) + + # support for mangling + @test (@eval ccall("llvm.floor.f64", llvmcall, Float64, (Float64,), 0.0)) === 0.0 + @test (@eval ccall("llvm.floor", llvmcall, Float64, (Float64,), 0.0), + ccall("llvm.floor", llvmcall, Float32, (Float32,), 0.0)) === (0.0, 0.0f0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float32, (Float64,), 0.0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float32, (Float32,), 0.0f0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float64, (Float32,), 0.0f0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Float64, (Int,), 0) + @test_throws err @eval ccall("llvm.floor.f64", llvmcall, Int, (Int,), 0) + @test_throws err @eval ccall("llvm.floor", llvmcall, Float64, (Float32,), 0.0f0) + @test_throws err @eval ccall("llvm.floor", llvmcall, Float64, (Int,), 0) + @test_throws err @eval ccall("llvm.floor", llvmcall, Int, (Int,), 0) + + @test_throws err (@eval ccall("llvm.floor.f64", llvmcall, Float64, (Float64, Float64...,), 0.0)) === 0.0 + @test_throws err (@eval ccall("llvm.floor", llvmcall, Float64, (Float64, Float64...,), 0.0)) === 0.0 end diff --git a/test/llvmpasses/.gitignore b/test/llvmpasses/.gitignore index aa144c71f85f8..4b99de76c491b 100644 --- a/test/llvmpasses/.gitignore +++ b/test/llvmpasses/.gitignore @@ -1 +1,2 @@ /Output/ +.lit_test_times.txt \ No newline at end of file diff --git a/test/llvmpasses/Makefile b/test/llvmpasses/Makefile index a0b9cf977ede8..ec0333178c225 100644 --- a/test/llvmpasses/Makefile +++ b/test/llvmpasses/Makefile @@ -4,11 +4,29 @@ include $(JULIAHOME)/Make.inc check: . -TESTS = $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.jl $(SRCDIR)/*.ll)) +TESTS_ll := $(filter-out update-%,$(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.ll))) +TESTS_jl := $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.jl)) +TESTS := $(TESTS_ll) $(TESTS_jl) . $(TESTS): PATH=$(build_bindir):$(build_depsbindir):$$PATH \ LD_LIBRARY_PATH=${build_libdir}:$$LD_LIBRARY_PATH \ - $(build_depsbindir)/lit/lit.py -v $(addprefix $(SRCDIR)/,$@) + $(build_depsbindir)/lit/lit.py -v "$(addprefix $(SRCDIR)/,$@)" -.PHONY: $(TESTS) check all . +$(addprefix update-,$(TESTS_ll)): + @echo 'NOTE: This requires a llvm source files locally, such as via `make -C deps USE_BINARYBUILDER_LLVM=0 DEPS_GIT=llvm checkout-llvm`' + @read -p "$$(printf $(WARNCOLOR)'This will directly modify %s, are you sure you want to proceed? '$(ENDCOLOR) '$@')" REPLY && [ yy = "y$$REPLY" ] + sed -e 's/%shlibext/.$(SHLIB_EXT)/g' < "$(@:update-%=$(SRCDIR)/%)" > "$@" + PATH=$(build_bindir):$(build_depsbindir):$$PATH \ + LD_LIBRARY_PATH=${build_libdir}:$$LD_LIBRARY_PATH \ + $(JULIAHOME)/deps/srccache/llvm/llvm/utils/update_test_checks.py "$@" \ + --preserve-names + mv "$@" "$(@:update-%=$(SRCDIR)/%)" + +update-help: + PATH=$(build_bindir):$(build_depsbindir):$$PATH \ + LD_LIBRARY_PATH=${build_libdir}:$$LD_LIBRARY_PATH \ + $(JULIAHOME)/deps/srccache/llvm/llvm/utils/update_test_checks.py \ + --help + +.PHONY: $(TESTS) $(addprefix update-,$(TESTS_ll)) check all . diff --git a/test/llvmpasses/aliasscopes.jl b/test/llvmpasses/aliasscopes.jl index 31b78cae922b5..751e351dfad1e 100644 --- a/test/llvmpasses/aliasscopes.jl +++ b/test/llvmpasses/aliasscopes.jl @@ -18,8 +18,8 @@ import Base.Experimental: Const, @aliasscope function simple(A, B) @aliasscope @inbounds for I in eachindex(A, B) A[I] = Const(B)[I] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE:![0-9]+]] -# CHECK: store double {{.*}} !noalias [[SCOPE]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE_LD:![0-9]+]] +# CHECK: store double {{.*}} !noalias [[SCOPE_ST:![0-9]+]] end return 0 # return nothing causes japi1 end @@ -28,8 +28,8 @@ end function constargs(A, B::Const) @aliasscope @inbounds for I in eachindex(A, B) A[I] = B[I] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE2:![0-9]+]] -# CHECK: store double {{.*}} !noalias [[SCOPE2]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE2_LD:![0-9]+]] +# CHECK: store double {{.*}} !noalias [[SCOPE2_ST:![0-9]+]] end return 0 end @@ -40,10 +40,10 @@ function micro_ker!(AB, Ac, Bc, kc, offSetA, offSetB) @inbounds @aliasscope for k in 1:kc for j in 1:NR, i in 1:MR AB[i+(j-1)*MR] = muladd(Const(Ac)[offSetA+i], Const(Bc)[offSetB+j], Const(AB)[i+(j-1)*MR]) -# CHECK: load double, {{.*}} !alias.scope [[SCOPE3:![0-9]+]] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE3]] -# CHECK: load double, {{.*}} !alias.scope [[SCOPE3]] -# CHECK: store double {{.*}} !noalias [[SCOPE3]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE3_LD:![0-9]+]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE3_LD]] +# CHECK: load double, {{.*}} !alias.scope [[SCOPE3_LD]] +# CHECK: store double {{.*}} !noalias [[SCOPE3_ST:![0-9]+]] end offSetA += MR offSetB += NR @@ -51,11 +51,15 @@ function micro_ker!(AB, Ac, Bc, kc, offSetA, offSetB) return end -# CHECK: [[SCOPE]] = !{[[ALIASSCOPE:![0-9]+]]} -# CHECK: [[ALIASSCOPE]] = !{!"aliasscope", [[MDNODE:![0-9]+]]} -# CHECK: [[MDNODE]] = !{!"simple"} +# CHECK-DAG: [[SCOPE_LD]] = !{[[ALIASSCOPE:![0-9]+]] +# CHECK-DAG: [[SCOPE_ST]] = !{[[ALIASSCOPE]] +# CHECK-DAG: [[SCOPE2_LD]] = !{[[ALIASSCOPE2:![0-9]+]] +# CHECK-DAG: [[SCOPE2_ST]] = !{[[ALIASSCOPE2]] +# CHECK-DAG: [[SCOPE3_LD]] = !{[[ALIASSCOPE3:![0-9]+]] +# CHECK-DAG: [[SCOPE3_ST]] = !{[[ALIASSCOPE3]] +# CHECK-DAG: [[ALIASSCOPE]] = !{!"aliasscope", [[MDNODE:![0-9]+]]} +# CHECK-DAG: [[MDNODE]] = !{!"simple"} emit(simple, Vector{Float64}, Vector{Float64}) emit(constargs, Vector{Float64}, Const{Float64, 1}) emit(micro_ker!, Matrix{Float64}, Vector{Float64}, Vector{Float64}, Int64, Int64, Int64) - diff --git a/test/llvmpasses/alloc-opt-gcframe-addrspaces.jl b/test/llvmpasses/alloc-opt-gcframe-addrspaces.jl new file mode 100644 index 0000000000000..093c062deca64 --- /dev/null +++ b/test/llvmpasses/alloc-opt-gcframe-addrspaces.jl @@ -0,0 +1,40 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# RUN: julia --startup-file=no %s | opt -enable-new-pm=0 -load libjulia-codegen%shlibext -AllocOpt -S - | FileCheck %s +# RUN: julia --startup-file=no %s | opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(AllocOpt)' -S - | FileCheck %s + +isz = sizeof(UInt) == 8 ? "i64" : "i32" + +println(""" +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" + +@tag = external addrspace(10) global {} + +declare {}*** @julia.ptls_states() +declare {}*** @julia.get_pgcstack() +declare noalias {} addrspace(10)* @julia.gc_alloc_obj(i8*, $isz, {} addrspace(10)*) +declare {}* @julia.pointer_from_objref({} addrspace(11)*) +""") + +# Test that non-0 addrspace allocas are properly emitted and handled + +# CHECK-LABEL: @non_zero_addrspace +# CHECK: %1 = alloca i32, align 8, addrspace(5) +# CHECK: %2 = bitcast i32 addrspace(5)* %1 to i8 addrspace(5)* +# CHECK: %3 = bitcast i8 addrspace(5)* %2 to {} addrspace(5)* +# CHECK: %var1 = addrspacecast {} addrspace(5)* %3 to {} addrspace(10)* +# CHECK: call void @llvm.lifetime.start.p5i8(i64 4, i8 addrspace(5)* %2) +# CHECK: ret void +println(""" +define void @non_zero_addrspace() { + %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, $isz 4, {} addrspace(10)* @tag) + %var2 = addrspacecast {} addrspace(10)* %var1 to {} addrspace(11)* + %var3 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var2) + ret void +} +""") +# CHECK-LABEL: }{{$}} diff --git a/test/llvmpasses/alloc-opt-gcframe.jl b/test/llvmpasses/alloc-opt-gcframe.jl index 8e3de7645595e..e7ddf12d79bc7 100644 --- a/test/llvmpasses/alloc-opt-gcframe.jl +++ b/test/llvmpasses/alloc-opt-gcframe.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # RUN: julia --startup-file=no %s | opt -enable-new-pm=0 -load libjulia-codegen%shlibext -AllocOpt -LateLowerGCFrame -FinalLowerGC -S - | FileCheck %s +# RUN: julia --startup-file=no %s | opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(AllocOpt,LateLowerGCFrame),FinalLowerGC' -S - | FileCheck %s isz = sizeof(UInt) == 8 ? "i64" : "i32" @@ -13,11 +14,11 @@ target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" # CHECK-LABEL: @return_obj # CHECK-NOT: @julia.gc_alloc_obj # CHECK: %current_task = getelementptr inbounds {}*, {}** %gcstack, i64 -12 -# CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 14 +# CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 16 # CHECK-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 # CHECK-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** # CHECK-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* -# CHECK-NEXT: %v = call noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc(i8* [[ptls_i8]], i32 [[SIZE_T:[0-9]+]], i32 16) +# CHECK-NEXT: %v = call noalias nonnull dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc(i8* [[ptls_i8]], i32 [[SIZE_T:[0-9]+]], i32 16) # CHECK: store atomic {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* {{.*}} unordered, align 8, !tbaa !4 println(""" define {} addrspace(10)* @return_obj() { diff --git a/test/llvmpasses/alloc-opt-pass.jl b/test/llvmpasses/alloc-opt-pass.ll similarity index 55% rename from test/llvmpasses/alloc-opt-pass.jl rename to test/llvmpasses/alloc-opt-pass.ll index 3f2b09ebabb4a..4ce152669246f 100644 --- a/test/llvmpasses/alloc-opt-pass.jl +++ b/test/llvmpasses/alloc-opt-pass.ll @@ -1,28 +1,24 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license +; This file is a part of Julia. License is MIT: https://julialang.org/license -# RUN: julia --startup-file=no %s | opt -enable-new-pm=0 -load libjulia-codegen%shlibext -AllocOpt -S - | FileCheck %s +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -AllocOpt -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(AllocOpt)' -S %s | FileCheck %s -isz = sizeof(UInt) == 8 ? "i64" : "i32" - -println(""" @tag = external addrspace(10) global {} -""") -# Test that the gc_preserve intrinsics are deleted directly. +; Test that the gc_preserve intrinsics are deleted directly. -# CHECK-LABEL: @preserve_branches -# CHECK: call {}*** @julia.ptls_states() -# CHECK: L1: -# CHECK-NOT: @llvm.julia.gc_preserve_begin -# CHECK-NEXT: @external_function() -# CHECK-NEXT: br i1 %b2, label %L2, label %L3 +; CHECK-LABEL: @preserve_branches +; CHECK: call {}*** @julia.ptls_states() +; CHECK: L1: +; CHECK-NOT: @llvm.julia.gc_preserve_begin +; CHECK-NEXT: @external_function() +; CHECK-NEXT: br i1 %b2, label %L2, label %L3 -# CHECK: L2: -# CHECK: @external_function() -# CHECK-NEXT: br label %L3 +; CHECK: L2: +; CHECK: @external_function() +; CHECK-NEXT: br label %L3 -# CHECK: L3: -println(""" +; CHECK: L3: define void @preserve_branches(i8* %fptr, i1 %b, i1 %b2) { %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() @@ -30,8 +26,8 @@ define void @preserve_branches(i8* %fptr, i1 %b, i1 %b2) { br i1 %b, label %L1, label %L3 L1: - %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, $isz 8, {} addrspace(10)* @tag) - %tok = call token (...) @llvm.julia.gc_preserve_begin({} addrspace(10)* %v) + %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 8, {} addrspace(10)* @tag) + %tok = call token (...) @llvm.julia.gc_preserve_begin({} addrspace(10)* nonnull %v) call void @external_function() br i1 %b2, label %L2, label %L3 @@ -42,22 +38,20 @@ L2: L3: ret void } -""") -# CHECK-LABEL: }{{$}} - -# CHECK-LABEL: @preserve_branches2 -# CHECK: call {}*** @julia.ptls_states() -# CHECK: L1: -# CHECK-NEXT: @llvm.julia.gc_preserve_begin{{.*}}{} addrspace(10)* %v2 -# CHECK-NEXT: @external_function() -# CHECK-NEXT: br i1 %b2, label %L2, label %L3 - -# CHECK: L2: -# CHECK: @external_function() -# CHECK-NEXT: br label %L3 - -# CHECK: L3: -println(""" +; CHECK-LABEL: }{{$}} + +; CHECK-LABEL: @preserve_branches2 +; CHECK: call {}*** @julia.ptls_states() +; CHECK: L1: +; CHECK-NEXT: @llvm.julia.gc_preserve_begin{{.*}}{} addrspace(10)* %v2 +; CHECK-NEXT: @external_function() +; CHECK-NEXT: br i1 %b2, label %L2, label %L3 + +; CHECK: L2: +; CHECK: @external_function() +; CHECK-NEXT: br label %L3 + +; CHECK: L3: define void @preserve_branches2(i8* %fptr, i1 %b, i1 %b2) { %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() @@ -66,8 +60,8 @@ define void @preserve_branches2(i8* %fptr, i1 %b, i1 %b2) { br i1 %b, label %L1, label %L3 L1: - %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, $isz 8, {} addrspace(10)* @tag) - %tok = call token (...) @llvm.julia.gc_preserve_begin({} addrspace(10)* %v, {} addrspace(10)* %v2) + %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 8, {} addrspace(10)* @tag) + %tok = call token (...) @llvm.julia.gc_preserve_begin({} addrspace(10)* %v, {} addrspace(10)* nonnull %v2) call void @external_function() br i1 %b2, label %L2, label %L3 @@ -78,57 +72,50 @@ L2: L3: ret void } -""") -# CHECK-LABEL: }{{$}} - -# CHECK-LABEL: @legal_int_types -# CHECK: alloca [12 x i8] -# CHECK-NOT: alloca i96 -# CHECK: ret void -println(""" +; CHECK-LABEL: }{{$}} + +; CHECK-LABEL: @legal_int_types +; CHECK: alloca [12 x i8] +; CHECK-NOT: alloca i96 +; CHECK: ret void define void @legal_int_types() { %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, $isz 12, {} addrspace(10)* @tag) + %var1 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 12, {} addrspace(10)* @tag) %var2 = addrspacecast {} addrspace(10)* %var1 to {} addrspace(11)* %var3 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var2) ret void } -""") -# CHECK-LABEL: }{{$}} - +; CHECK-LABEL: }{{$}} -println(""" declare void @external_function() declare {} addrspace(10)* @external_function2() declare {}*** @julia.ptls_states() declare {}*** @julia.get_pgcstack() -declare noalias {} addrspace(10)* @julia.gc_alloc_obj(i8*, $isz, {} addrspace(10)*) +declare noalias {} addrspace(10)* @julia.gc_alloc_obj(i8*, i64, {} addrspace(10)*) declare {}* @julia.pointer_from_objref({} addrspace(11)*) declare void @llvm.memcpy.p11i8.p0i8.i64(i8 addrspace(11)* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) declare token @llvm.julia.gc_preserve_begin(...) declare void @llvm.julia.gc_preserve_end(token) -""") - -# CHECK-LABEL: @memref_collision -# CHECK: call {}*** @julia.ptls_states() -# CHECK-NOT: store {} -# CHECK: store i -# CHECK-NOT: store {} -# CHECK: L1: -# CHECK: load {} -# CHECK: L2: -# CHECK: load i -println(""" -define void @memref_collision($isz %x) { + +; CHECK-LABEL: @memref_collision +; CHECK: call {}*** @julia.ptls_states() +; CHECK-NOT: store {} +; CHECK: store i +; CHECK-NOT: store {} +; CHECK: L1: +; CHECK: load {} +; CHECK: L2: +; CHECK: load i +define void @memref_collision(i64 %x) { %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() %ptls_i8 = bitcast {}*** %ptls to i8* - %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, $isz 8, {} addrspace(10)* @tag) - %v_p = bitcast {} addrspace(10)* %v to $isz addrspace(10)* - store $isz %x, $isz addrspace(10)* %v_p + %v = call noalias {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 8, {} addrspace(10)* @tag) + %v_p = bitcast {} addrspace(10)* %v to i64 addrspace(10)* + store i64 %x, i64 addrspace(10)* %v_p br i1 0, label %L1, label %L2 L1: @@ -137,9 +124,8 @@ L1: ret void L2: - %v2 = bitcast {} addrspace(10)* %v to $isz addrspace(10)* + %v2 = bitcast {} addrspace(10)* %v to i64 addrspace(10)* %v2_x = load i64, i64 addrspace(10)* %v2 ret void } -""") -# CHECK-LABEL: }{{$}} +; CHECK-LABEL: }{{$}} diff --git a/test/llvmpasses/alloc-opt-unsized.ll b/test/llvmpasses/alloc-opt-unsized.ll new file mode 100644 index 0000000000000..8a21091ce558c --- /dev/null +++ b/test/llvmpasses/alloc-opt-unsized.ll @@ -0,0 +1,37 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -AllocOpt -S %s | FileCheck %s + +source_filename = "text" +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 {}*** @julia.get_pgcstack() + +declare {} addrspace(10)* @julia.gc_alloc_obj({}**, i64, {} addrspace(10)*) + +declare void @julia.write_barrier({} addrspace(10)*, ...) + +define void @diffejulia_objective__1864_inner_1wrap({} addrspace(10)* %arg, i64 %iv.i) { +entry: + %i5 = call {}*** @julia.get_pgcstack() + %i13 = bitcast {}*** %i5 to {}** + %i14 = getelementptr inbounds {}*, {}** %i13, i64 -12 + %i18 = call noalias nonnull dereferenceable(8000) dereferenceable_or_null(8000) {} addrspace(10)* @julia.gc_alloc_obj({}** %i14, i64 8000, {} addrspace(10)* addrspacecast ({}* inttoptr (i64 139756155247504 to {}*) to {} addrspace(10)*)) + %_malloccache.i = bitcast {} addrspace(10)* %i18 to {} addrspace(10)* addrspace(10)* + %i23 = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %_malloccache.i, i64 %iv.i + store {} addrspace(10)* %arg, {} addrspace(10)* addrspace(10)* %i23, align 8 + %i24 = bitcast {} addrspace(10)* addrspace(10)* %_malloccache.i to {} addrspace(10)* + call void ({} addrspace(10)*, ...) @julia.write_barrier({} addrspace(10)* %i24, {} addrspace(10)* %arg) + %l = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %i23 + ret void +} + +; CHECK: %[[i0:.+]] = alloca {} addrspace(10)*, i64 1000, align 16 +; CHECK: %[[i1:.+]] = bitcast {} addrspace(10)** %[[i0]] to i8* +; CHECK: %i18 = bitcast i8* %[[i1]] to {}* +; CHECK: %_malloccache.i = bitcast {}* %i18 to {} addrspace(10)** +; CHECK: %i23 = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)** %_malloccache.i, i64 %iv.i +; CHECK: store {} addrspace(10)* %arg, {} addrspace(10)** %i23, align 8 +; CHECK: %i24 = bitcast {} addrspace(10)** %_malloccache.i to {}* +; CHECK: %l = load {} addrspace(10)*, {} addrspace(10)** %i23, align 8 diff --git a/test/llvmpasses/cpu-features.ll b/test/llvmpasses/cpu-features.ll index 42a615a838e6b..1a04db5749b39 100644 --- a/test/llvmpasses/cpu-features.ll +++ b/test/llvmpasses/cpu-features.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -CPUFeatures -simplifycfg -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='CPUFeatures,simplifycfg' -S %s | FileCheck %s 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-addrspaces.ll b/test/llvmpasses/final-lower-gc-addrspaces.ll new file mode 100644 index 0000000000000..61e9e33875078 --- /dev/null +++ b/test/llvmpasses/final-lower-gc-addrspaces.ll @@ -0,0 +1,43 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -FinalLowerGC -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='FinalLowerGC' -S %s | FileCheck %s + +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" + +@tag = external addrspace(10) global {} + +declare void @boxed_simple({} addrspace(10)*, {} addrspace(10)*) +declare {} addrspace(10)* @ijl_box_int64(i64) +declare {}*** @julia.ptls_states() +declare {}*** @julia.get_pgcstack() + +declare noalias nonnull {} addrspace(10)** @julia.new_gc_frame(i32) +declare void @julia.push_gc_frame({} addrspace(10)**, i32) +declare {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)**, i32) +declare void @julia.pop_gc_frame({} addrspace(10)**) +declare noalias nonnull {} addrspace(10)* @julia.gc_alloc_bytes(i8*, i64) #0 + +attributes #0 = { allocsize(1) } + +define void @gc_frame_addrspace(i64 %a, i64 %b) { +top: +; CHECK-LABEL: @gc_frame_addrspace +; CHECK: %0 = alloca {} addrspace(10)*, i32 4, align 16, addrspace(5) +; CHECK: %gcframe = addrspacecast {} addrspace(10)* addrspace(5)* %0 to {} addrspace(10)** +; CHECK: %1 = bitcast {} addrspace(10)** %gcframe to i8* + %gcframe = call {} addrspace(10)** @julia.new_gc_frame(i32 2) + %pgcstack = call {}*** @julia.get_pgcstack() + call void @julia.push_gc_frame({} addrspace(10)** %gcframe, i32 2) + %aboxed = call {} addrspace(10)* @ijl_box_int64(i64 signext %a) + %frame_slot_1 = call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 1) + store {} addrspace(10)* %aboxed, {} addrspace(10)** %frame_slot_1, align 8 + %bboxed = call {} addrspace(10)* @ijl_box_int64(i64 signext %b) + %frame_slot_2 = call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 0) + store {} addrspace(10)* %bboxed, {} addrspace(10)** %frame_slot_2, align 8 + call void @boxed_simple({} addrspace(10)* %aboxed, {} addrspace(10)* %bboxed) + call void @julia.pop_gc_frame({} addrspace(10)** %gcframe) +; CHECK: ret void + ret void +} diff --git a/test/llvmpasses/final-lower-gc.ll b/test/llvmpasses/final-lower-gc.ll index 5e4a23770c4e0..6f1be3d240ae4 100644 --- a/test/llvmpasses/final-lower-gc.ll +++ b/test/llvmpasses/final-lower-gc.ll @@ -1,4 +1,8 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -FinalLowerGC -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='FinalLowerGC' -S %s | FileCheck %s + @tag = external addrspace(10) global {} @@ -57,7 +61,7 @@ top: %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() %ptls_i8 = bitcast {}*** %ptls to i8* -; CHECK: %v = call noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc +; CHECK: %v = call noalias nonnull dereferenceable({{[0-9]+}}) {} addrspace(10)* @ijl_gc_pool_alloc %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* %ptls_i8, i64 8) %0 = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* %1 = getelementptr {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %0, i64 -1 @@ -65,6 +69,21 @@ top: ret {} addrspace(10)* %v } +define {} addrspace(10)* @gc_alloc_lowering_var(i64 %size) { +top: +; CHECK-LABEL: @gc_alloc_lowering_var + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %ptls_i8 = bitcast {}*** %ptls to i8* +; CHECK: %0 = add i64 %size, 8 +; CHECK: %v = call noalias nonnull dereferenceable(8) {} addrspace(10)* @ijl_gc_alloc_typed(i8* %ptls_i8, i64 %0, i8* null) + %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* %ptls_i8, i64 %size) + %0 = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* + %1 = getelementptr {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %0, i64 -1 + store {} addrspace(10)* @tag, {} addrspace(10)* addrspace(10)* %1, align 8, !tbaa !0 + ret {} addrspace(10)* %v +} + !0 = !{!1, !1, i64 0} !1 = !{!"jtbaa_gcframe", !2, i64 0} !2 = !{!"jtbaa"} diff --git a/test/llvmpasses/float16.ll b/test/llvmpasses/float16.ll new file mode 100644 index 0000000000000..668c6ff3dd261 --- /dev/null +++ b/test/llvmpasses/float16.ll @@ -0,0 +1,104 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: -p +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -DemoteFloat16 -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='DemoteFloat16' -S %s | FileCheck %s + +define half @demotehalf_test(half %a, half %b) #0 { +top: +; CHECK-LABEL: @demotehalf_test( +; CHECK-NEXT: top: +; CHECK-NEXT: %0 = fpext half %a to float +; CHECK-NEXT: %1 = fpext half %b to float +; CHECK-NEXT: %2 = fadd float %0, %1 +; CHECK-NEXT: %3 = fptrunc float %2 to half +; CHECK-NEXT: %4 = fpext half %3 to float +; CHECK-NEXT: %5 = fpext half %b to float +; CHECK-NEXT: %6 = fadd float %4, %5 +; CHECK-NEXT: %7 = fptrunc float %6 to half +; CHECK-NEXT: %8 = fpext half %7 to float +; CHECK-NEXT: %9 = fpext half %b to float +; CHECK-NEXT: %10 = fadd float %8, %9 +; CHECK-NEXT: %11 = fptrunc float %10 to half +; CHECK-NEXT: %12 = fpext half %11 to float +; CHECK-NEXT: %13 = fpext half %b to float +; CHECK-NEXT: %14 = fmul float %12, %13 +; CHECK-NEXT: %15 = fptrunc float %14 to half +; CHECK-NEXT: %16 = fpext half %15 to float +; CHECK-NEXT: %17 = fpext half %b to float +; CHECK-NEXT: %18 = fdiv float %16, %17 +; CHECK-NEXT: %19 = fptrunc float %18 to half +; CHECK-NEXT: %20 = insertelement <2 x half> undef, half %a, i32 0 +; CHECK-NEXT: %21 = insertelement <2 x half> %20, half %b, i32 1 +; CHECK-NEXT: %22 = insertelement <2 x half> undef, half %b, i32 0 +; CHECK-NEXT: %23 = insertelement <2 x half> %22, half %b, i32 1 +; CHECK-NEXT: %24 = fpext <2 x half> %21 to <2 x float> +; CHECK-NEXT: %25 = fpext <2 x half> %23 to <2 x float> +; CHECK-NEXT: %26 = fadd <2 x float> %24, %25 +; CHECK-NEXT: %27 = fptrunc <2 x float> %26 to <2 x half> +; CHECK-NEXT: %28 = extractelement <2 x half> %27, i32 0 +; CHECK-NEXT: %29 = extractelement <2 x half> %27, i32 1 +; CHECK-NEXT: %30 = fpext half %28 to float +; CHECK-NEXT: %31 = fpext half %29 to float +; CHECK-NEXT: %32 = fadd float %30, %31 +; CHECK-NEXT: %33 = fptrunc float %32 to half +; CHECK-NEXT: %34 = fpext half %33 to float +; CHECK-NEXT: %35 = fpext half %19 to float +; CHECK-NEXT: %36 = fadd float %34, %35 +; CHECK-NEXT: %37 = fptrunc float %36 to half +; CHECK-NEXT: ret half %37 +; + %0 = fadd half %a, %b + %1 = fadd half %0, %b + %2 = fadd half %1, %b + %3 = fmul half %2, %b + %4 = fdiv half %3, %b + %5 = insertelement <2 x half> undef, half %a, i32 0 + %6 = insertelement <2 x half> %5, half %b, i32 1 + %7 = insertelement <2 x half> undef, half %b, i32 0 + %8 = insertelement <2 x half> %7, half %b, i32 1 + %9 = fadd <2 x half> %6, %8 + %10 = extractelement <2 x half> %9, i32 0 + %11 = extractelement <2 x half> %9, i32 1 + %12 = fadd half %10, %11 + %13 = fadd half %12, %4 + ret half %13 +} + +define half @native_half_test(half %a, half %b) #1 { +; CHECK-LABEL: @native_half_test( +; CHECK-NEXT top: +; CHECK-NEXT %0 = fadd half %a, %b +; CHECK-NEXT %1 = fadd half %0, %b +; CHECK-NEXT %2 = fadd half %1, %b +; CHECK-NEXT %3 = fmul half %2, %b +; CHECK-NEXT %4 = fdiv half %3, %b +; CHECK-NEXT %5 = insertelement <2 x half> undef, half %a, i32 0 +; CHECK-NEXT %6 = insertelement <2 x half> %5, half %b, i32 1 +; CHECK-NEXT %7 = insertelement <2 x half> undef, half %b, i32 0 +; CHECK-NEXT %8 = insertelement <2 x half> %7, half %b, i32 1 +; CHECK-NEXT %9 = fadd <2 x half> %6, %8 +; CHECK-NEXT %10 = extractelement <2 x half> %9, i32 0 +; CHECK-NEXT %11 = extractelement <2 x half> %9, i32 1 +; CHECK-NEXT %12 = fadd half %10, %11 +; CHECK-NEXT %13 = fadd half %12, %4 +; CHECK-NEXT ret half %13 +; +top: + %0 = fadd half %a, %b + %1 = fadd half %0, %b + %2 = fadd half %1, %b + %3 = fmul half %2, %b + %4 = fdiv half %3, %b + %5 = insertelement <2 x half> undef, half %a, i32 0 + %6 = insertelement <2 x half> %5, half %b, i32 1 + %7 = insertelement <2 x half> undef, half %b, i32 0 + %8 = insertelement <2 x half> %7, half %b, i32 1 + %9 = fadd <2 x half> %6, %8 + %10 = extractelement <2 x half> %9, i32 0 + %11 = extractelement <2 x half> %9, i32 1 + %12 = fadd half %10, %11 + %13 = fadd half %12, %4 + ret half %13 +} + +attributes #0 = { "target-features"="-avx512fp16" } +attributes #1 = { "target-features"="+avx512fp16" } diff --git a/test/llvmpasses/gcroots.ll b/test/llvmpasses/gcroots.ll index 29cb8683244d1..eefd847bf68fa 100644 --- a/test/llvmpasses/gcroots.ll +++ b/test/llvmpasses/gcroots.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LateLowerGCFrame -FinalLowerGC -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame),FinalLowerGC' -S %s | FileCheck %s declare void @boxed_simple({} addrspace(10)*, {} addrspace(10)*) diff --git a/test/llvmpasses/julia-licm-fail.ll b/test/llvmpasses/julia-licm-fail.ll new file mode 100644 index 0000000000000..250ad620b05e6 --- /dev/null +++ b/test/llvmpasses/julia-licm-fail.ll @@ -0,0 +1,96 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -JuliaLICM -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaLICM' -S %s | FileCheck %s + +; COM: This file contains functions that should not trigger allocations to be hoisted out of loops + +@tag = external addrspace(10) global {}, align 16 + +; COM: Tests that an escape in a loop prevents hoisting of the allocation +; CHECK-LABEL: @julia_escape_alloc +define void @julia_escape_alloc(i1 %ret) { +top: + %pgcstack = call {}*** @julia.get_pgcstack() + %current_task = bitcast {}*** %pgcstack to {}** +; CHECK: br label %preheader + br label %preheader +; CHECK: preheader: +preheader: +; CHECK-NOT: julia.gc_alloc_obj +; CHECK-NEXT: br label %loop + br label %loop +; CHECK: loop: +loop: +; CHECK-NEXT: %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) + %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) +; CHECK-NEXT: %ignore = call {} addrspace(10)* @escape({} addrspace(10)* %alloc) + %ignore = call {} addrspace(10)* @escape({} addrspace(10)* %alloc) + br i1 %ret, label %return, label %loop +return: + ret void +} + +; COM: Tests that addrescape in a loop prevents hoisting of the allocation +; CHECK-LABEL: @julia_addrescape_alloc +define void @julia_addrescape_alloc(i1 %ret) { +top: + %pgcstack = call {}*** @julia.get_pgcstack() + %current_task = bitcast {}*** %pgcstack to {}** +; CHECK: br label %preheader + br label %preheader +; CHECK: preheader: +preheader: +; CHECK-NOT: julia.gc_alloc_obj +; CHECK-NEXT: br label %loop + br label %loop +; CHECK: loop: +loop: +; CHECK-NEXT: %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) + %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) +; CHECK-NEXT: %cast = addrspacecast {} addrspace(10)* %alloc to {} addrspace(11)* + %cast = addrspacecast {} addrspace(10)* %alloc to {} addrspace(11)* +; CHECK-NEXT: %ptr = call nonnull {}* @julia.pointer_from_objref({} addrspace(11)* %cast) + %ptr = call nonnull {}* @julia.pointer_from_objref({} addrspace(11)* %cast) + br i1 %ret, label %return, label %loop +return: + ret void +} + +declare void @julia.write_barrier({}*, ...) + +declare {}*** @julia.get_pgcstack() + +; Function Attrs: allocsize(1) +declare noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}**, i64, {} addrspace(10)*) #1 + +; Function Attrs: argmemonly nofree nosync nounwind willreturn +declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #2 + +; Function Attrs: argmemonly nofree nosync nounwind willreturn +declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #2 + +; Function Attrs: inaccessiblemem_or_argmemonly +declare void @ijl_gc_queue_root({} addrspace(10)*) #3 + +; Function Attrs: allocsize(1) +declare noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc(i8*, i32, i32) #1 + +; Function Attrs: allocsize(1) +declare noalias nonnull {} addrspace(10)* @ijl_gc_big_alloc(i8*, i64) #1 + +; COM: escape to make it easy to find +declare nonnull {} addrspace(10)* @escape({} addrspace(10)*) + +; COM: addrescape function +declare nonnull {}* @julia.pointer_from_objref({} addrspace(11)*) + +attributes #0 = { "probe-stack"="inline-asm" } +attributes #1 = { allocsize(1) } +attributes #2 = { argmemonly nofree nosync nounwind willreturn } +attributes #3 = { inaccessiblemem_or_argmemonly } + +!llvm.module.flags = !{!0, !1} + +!0 = !{i32 2, !"Dwarf Version", i32 4} +!1 = !{i32 2, !"Debug Info Version", i32 3} diff --git a/test/llvmpasses/julia-licm-missed.ll b/test/llvmpasses/julia-licm-missed.ll new file mode 100644 index 0000000000000..977b8e2a787f9 --- /dev/null +++ b/test/llvmpasses/julia-licm-missed.ll @@ -0,0 +1,109 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -JuliaLICM -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaLICM' -S %s | FileCheck %s + +; COM: This file contains functions that currently do not trigger allocations to be hoisted out of loops +; COM: i.e. they are missed optimizations +; COM: Better optimization could potentially enable allocations to be hoisted out of these loops + +@tag = external addrspace(10) global {}, align 16 + +; COM: Currently we don't hoist allocations that have references stored into them out of loops +; COM: This is because we need to insert write barriers for the stores when the storee does not +; COM: dominate the allocation after it has been moved out of the loop +; CHECK-LABEL: @julia_refstore +define void @julia_refstore({} addrspace(10)* %obj, i1 %ret) { +top: + %pgcstack = call {}*** @julia.get_pgcstack() + %current_task = bitcast {}*** %pgcstack to {}** +; CHECK: br label %preheader + br label %preheader +; CHECK: preheader: +preheader: +; CHECK-NOT: julia.gc_alloc_obj +; CHECK-NEXT: br label %loop + br label %loop +; CHECK: loop: +loop: +; CHECK-NEXT: %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) + %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) +; CHECK-NEXT: %derived = addrspacecast {} addrspace(10)* %alloc to {} addrspace(11)* + %derived = addrspacecast {} addrspace(10)* %alloc to {} addrspace(11)* +; CHECK-NEXT: %ptr = bitcast {} addrspace(11)* %derived to {} addrspace(10)* addrspace(11)* + %ptr = bitcast {} addrspace(11)* %derived to {} addrspace(10)* addrspace(11)* +; CHECK-NEXT: store {} addrspace(10)* %obj, {} addrspace(10)* addrspace(11)* %ptr, align 8 + store {} addrspace(10)* %obj, {} addrspace(10)* addrspace(11)* %ptr, align 8 + br i1 %ret, label %return, label %loop +return: + ret void +} + +; COM: Currently our LLVM-level escape analysis doesn't handle phi nodes at all +; COM: so this allocation is counted as 'escaping' despite the fact that it's +; COM: clearly dead +; CHECK-LABEL: @julia_phi +define void @julia_phi({} addrspace(10)* %obj, i1 %ret) { +top: + %pgcstack = call {}*** @julia.get_pgcstack() + %current_task = bitcast {}*** %pgcstack to {}** +; CHECK: br label %preheader + br label %preheader +; CHECK: preheader: +preheader: +; CHECK-NOT: julia.gc_alloc_obj +; CHECK-NEXT: br label %loop + br label %loop +; CHECK: loop: +loop: +; CHECK-NEXT: %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) + %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) + br label %other +; CHECK: other: +other: +; CHECK-NEXT: %phi = phi {} addrspace(10)* [ %alloc, %loop ] + %phi = phi {} addrspace(10)* [ %alloc, %loop ] + br i1 %ret, label %return, label %loop +return: + ret void +} + + + +declare void @julia.write_barrier({}*, ...) + +declare {}*** @julia.get_pgcstack() + +; Function Attrs: allocsize(1) +declare noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}**, i64, {} addrspace(10)*) #1 + +; Function Attrs: argmemonly nofree nosync nounwind willreturn +declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #2 + +; Function Attrs: argmemonly nofree nosync nounwind willreturn +declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #2 + +; Function Attrs: inaccessiblemem_or_argmemonly +declare void @ijl_gc_queue_root({} addrspace(10)*) #3 + +; Function Attrs: allocsize(1) +declare noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc(i8*, i32, i32) #1 + +; Function Attrs: allocsize(1) +declare noalias nonnull {} addrspace(10)* @ijl_gc_big_alloc(i8*, i64) #1 + +; COM: escape to make it easy to find +declare nonnull {} addrspace(10)* @escape({} addrspace(10)*) + +; COM: addrescape function +declare nonnull {}* @julia.pointer_from_objref({} addrspace(11)*) + +attributes #0 = { "probe-stack"="inline-asm" } +attributes #1 = { allocsize(1) } +attributes #2 = { argmemonly nofree nosync nounwind willreturn } +attributes #3 = { inaccessiblemem_or_argmemonly } + +!llvm.module.flags = !{!0, !1} + +!0 = !{i32 2, !"Dwarf Version", i32 4} +!1 = !{i32 2, !"Debug Info Version", i32 3} diff --git a/test/llvmpasses/julia-licm.ll b/test/llvmpasses/julia-licm.ll index 2ffc7f45ca787..6fc6f85de7c26 100644 --- a/test/llvmpasses/julia-licm.ll +++ b/test/llvmpasses/julia-licm.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -JuliaLICM -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaLICM' -S %s | FileCheck %s @tag = external addrspace(10) global {}, align 16 @@ -6,6 +9,8 @@ declare void @julia.write_barrier({}*, ...) declare {}*** @julia.get_pgcstack() +; COM: check basic allocation hoisting functionality +; CHECK-LABEL: @julia_allocation_hoist define nonnull {} addrspace(10)* @julia_allocation_hoist(i64 signext %0) #0 { top: %1 = call {}*** @julia.get_pgcstack() @@ -24,14 +29,16 @@ L4: ; preds = %top %current_task112 = getelementptr inbounds {}**, {}*** %1, i64 -12 %current_task1 = bitcast {}*** %current_task112 to {}** ; CHECK: %3 = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task1, i64 8, {} addrspace(10)* @tag) + ; CHECK-NEXT: %4 = bitcast {} addrspace(10)* %3 to i8 addrspace(10)* + ; CHECK-NEXT: call void @llvm.memset.p10i8.i64(i8 addrspace(10)* align {{[0-9]+}} %4, i8 0, i64 8, i1 false) ; CHECK-NEXT: br label %L22 br label %L22 L22: ; preds = %L4, %L22 %value_phi5 = phi i64 [ 1, %L4 ], [ %5, %L22 ] - ; CHECK: %value_phi5 = phi i64 [ 1, %L4 ], [ %5, %L22 ] - ; CHECK-NEXT %4 = bitcast {} addrspace(10)* %3 to i64 addrspace(10)* - %3 = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task1, i64 8, {} addrspace(10)* @tag) #2 + ; CHECK: %value_phi5 = phi i64 [ 1, %L4 ], [ %6, %L22 ] + ; CHECK-NEXT %5 = bitcast {} addrspace(10)* %3 to i64 addrspace(10)* + %3 = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task1, i64 8, {} addrspace(10)* @tag) #1 %4 = bitcast {} addrspace(10)* %3 to i64 addrspace(10)* store i64 %value_phi5, i64 addrspace(10)* %4, align 8, !tbaa !2 %.not = icmp eq i64 %value_phi5, %0 @@ -39,29 +46,50 @@ L22: ; preds = %L4, %L22 br i1 %.not, label %L3.loopexit, label %L22 } +; COM: check that we hoist the allocation out of the loop despite returning the allocation +; CHECK-LABEL: @julia_hoist_returned +define nonnull {} addrspace(10)* @julia_hoist_returned(i64 signext %n, i1 zeroext %ret) { +top: + %pgcstack = call {}*** @julia.get_pgcstack() + %current_task = bitcast {}*** %pgcstack to {}** +; CHECK: br label %preheader + br label %preheader +; CHECK: preheader: +preheader: +; CHECK-NEXT: %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) +; CHECK-NEXT: [[casted:%.*]] = bitcast {} addrspace(10)* %alloc to i8 addrspace(10)* +; CHECK-NEXT: call void @llvm.memset.p10i8.i64(i8 addrspace(10)* align {{[0-9]+}} [[casted]], i8 0, i64 8, i1 false) +; CHECK-NEXT: br label %loop + br label %loop +loop: + %alloc = call noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}** nonnull %current_task, i64 8, {} addrspace(10)* @tag) + br i1 %ret, label %return, label %loop +return: + ret {} addrspace(10)* %alloc +} + ; Function Attrs: allocsize(1) -declare noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}**, i64, {} addrspace(10)*) #2 +declare noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}**, i64, {} addrspace(10)*) #1 ; Function Attrs: argmemonly nofree nosync nounwind willreturn -declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #3 +declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #2 ; Function Attrs: argmemonly nofree nosync nounwind willreturn -declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #3 +declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #2 ; Function Attrs: inaccessiblemem_or_argmemonly -declare void @ijl_gc_queue_root({} addrspace(10)*) #4 +declare void @ijl_gc_queue_root({} addrspace(10)*) #3 ; Function Attrs: allocsize(1) -declare noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc(i8*, i32, i32) #2 +declare noalias nonnull {} addrspace(10)* @ijl_gc_pool_alloc(i8*, i32, i32) #1 ; Function Attrs: allocsize(1) -declare noalias nonnull {} addrspace(10)* @ijl_gc_big_alloc(i8*, i64) #2 +declare noalias nonnull {} addrspace(10)* @ijl_gc_big_alloc(i8*, i64) #1 attributes #0 = { "probe-stack"="inline-asm" } -attributes #1 = { "probe-stack"="inline-asm" "thunk" } -attributes #2 = { allocsize(1) } -attributes #3 = { argmemonly nofree nosync nounwind willreturn } -attributes #4 = { inaccessiblemem_or_argmemonly } +attributes #1 = { allocsize(1) } +attributes #2 = { argmemonly nofree nosync nounwind willreturn } +attributes #3 = { inaccessiblemem_or_argmemonly } !llvm.module.flags = !{!0, !1} @@ -72,4 +100,4 @@ attributes #4 = { inaccessiblemem_or_argmemonly } !4 = !{!"jtbaa_value", !5, i64 0} !5 = !{!"jtbaa_data", !6, i64 0} !6 = !{!"jtbaa", !7, i64 0} -!7 = !{!"jtbaa"} \ No newline at end of file +!7 = !{!"jtbaa"} diff --git a/test/llvmpasses/late-lower-gc-addrspaces.ll b/test/llvmpasses/late-lower-gc-addrspaces.ll new file mode 100644 index 0000000000000..84a6da9f2554d --- /dev/null +++ b/test/llvmpasses/late-lower-gc-addrspaces.ll @@ -0,0 +1,155 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LateLowerGCFrame -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame)' -S %s | FileCheck %s + +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" + +@tag = external addrspace(10) global {}, align 16 + +declare void @boxed_simple({} addrspace(10)*, {} addrspace(10)*) +declare {} addrspace(10)* @jl_box_int64(i64) +declare {}*** @julia.get_pgcstack() +declare void @jl_safepoint() +declare {} addrspace(10)* @jl_apply_generic({} addrspace(10)*, {} addrspace(10)**, i32) +declare noalias nonnull {} addrspace(10)* @julia.gc_alloc_obj({}**, i64, {} addrspace(10)*) +declare i32 @rooting_callee({} addrspace(12)*, {} addrspace(12)*) + +define void @gc_frame_lowering(i64 %a, i64 %b) { +top: +; CHECK-LABEL: @gc_frame_lowering +; CHECK: %gcframe = call {} addrspace(10)** @julia.new_gc_frame(i32 2) +; CHECK: %pgcstack = call {}*** @julia.get_pgcstack() + %pgcstack = call {}*** @julia.get_pgcstack() +; CHECK-NEXT: call void @julia.push_gc_frame({} addrspace(10)** %gcframe, i32 2) +; CHECK-NEXT: call {} addrspace(10)* @jl_box_int64 + %aboxed = call {} addrspace(10)* @jl_box_int64(i64 signext %a) +; CHECK: [[GEP0:%.*]] = call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 [[GEPSLOT0:[0-9]+]]) +; CHECK-NEXT: store {} addrspace(10)* %aboxed, {} addrspace(10)** [[GEP0]] + %bboxed = call {} addrspace(10)* @jl_box_int64(i64 signext %b) +; CHECK-NEXT: %bboxed = +; Make sure the same gc slot isn't re-used +; CHECK-NOT: call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 [[GEPSLOT0]]) +; CHECK: [[GEP1:%.*]] = call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 [[GEPSLOT1:[0-9]+]]) +; CHECK-NEXT: store {} addrspace(10)* %bboxed, {} addrspace(10)** [[GEP1]] +; CHECK-NEXT: call void @boxed_simple + call void @boxed_simple({} addrspace(10)* %aboxed, + {} addrspace(10)* %bboxed) +; CHECK-NEXT: call void @julia.pop_gc_frame({} addrspace(10)** %gcframe) + ret void +} + +define {} addrspace(10)* @gc_alloc_lowering() { +top: +; CHECK-LABEL: @gc_alloc_lowering + %pgcstack = call {}*** @julia.get_pgcstack() + %0 = bitcast {}*** %pgcstack to {}** + %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 +; CHECK: %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 +; CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 16 +; CHECK-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 +; CHECK-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** +; CHECK-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* +; CHECK-NEXT: %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8) +; CHECK-NEXT: [[V2:%.*]] = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* +; CHECK-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* [[V2]], i64 -1 +; CHECK-NEXT: store atomic {} addrspace(10)* @tag, {} addrspace(10)* 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) +; CHECK-NEXT: ret {} addrspace(10)* %v + ret {} addrspace(10)* %v +} + +; Confirm that loadedval instruction does not contain invariant.load metadata +; after the gc placement pass, but still contains the range metadata. +; Since loadedval is marked invariant, passes are allowed to move the use. +; But after the placement pass, must ensure it won't be relocated after our +; last gc-root use +define void @gc_drop_aliasing() { +top: +; CHECK-LABEL: @gc_drop_aliasing + %pgcstack = call {}*** @julia.get_pgcstack() + %0 = bitcast {}*** %pgcstack to {}** + %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 +; CHECK: %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 +; CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 16 +; CHECK-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 +; CHECK-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** +; CHECK-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* +; CHECK-NEXT: %v = call {} addrspace(10)* @julia.gc_alloc_bytes(i8* [[ptls_i8]], [[SIZE_T:i.[0-9]+]] 8) +; CHECK-NEXT: [[V2:%.*]] = bitcast {} addrspace(10)* %v to {} addrspace(10)* addrspace(10)* +; CHECK-NEXT: [[V_HEADROOM:%.*]] = getelementptr inbounds {} addrspace(10)*, {} addrspace(10)* addrspace(10)* [[V2]], i64 -1 +; CHECK-NEXT: store atomic {} addrspace(10)* @tag, {} addrspace(10)* 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) +; CHECK-NEXT: %v64 = bitcast {} addrspace(10)* %v to i64 addrspace(10)* + %v64 = bitcast {} addrspace(10)* %v to i64 addrspace(10)* +; CHECK-NEXT: %loadedval = load i64, i64 addrspace(10)* %v64, align 8, !range !7 + %loadedval = load i64, i64 addrspace(10)* %v64, align 8, !range !0, !invariant.load !1 +; CHECK-NEXT: store i64 %loadedval, i64 addrspace(10)* %v64, align 8, !noalias !8 + store i64 %loadedval, i64 addrspace(10)* %v64, align 8, !noalias !2 +; CHECK-NEXT: %lv2 = load i64, i64 addrspace(10)* %v64, align 8, !tbaa !11, !range !7 + %lv2 = load i64, i64 addrspace(10)* %v64, align 8, !range !0, !tbaa !4 +; CHECK-NEXT: ret void + ret void +} + +define i32 @callee_root({} addrspace(10)* %v0, {} addrspace(10)* %v1) { +top: +; CHECK-LABEL: @callee_root +; CHECK-NOT: @julia.new_gc_frame + %v2 = call {}*** @julia.get_pgcstack() + %v3 = bitcast {} addrspace(10)* %v0 to {} addrspace(10)* addrspace(10)* + %v4 = addrspacecast {} addrspace(10)* addrspace(10)* %v3 to {} addrspace(10)* addrspace(11)* + %v5 = load atomic {} addrspace(10)*, {} addrspace(10)* addrspace(11)* %v4 unordered, align 8 + %v6 = bitcast {} addrspace(10)* %v1 to {} addrspace(10)* addrspace(10)* + %v7 = addrspacecast {} addrspace(10)* addrspace(10)* %v6 to {} addrspace(10)* addrspace(11)* + %v8 = load atomic {} addrspace(10)*, {} addrspace(10)* addrspace(11)* %v7 unordered, align 8 + %v9 = addrspacecast {} addrspace(10)* %v5 to {} addrspace(12)* + %v10 = addrspacecast {} addrspace(10)* %v8 to {} addrspace(12)* + %v11 = call i32 @rooting_callee({} addrspace(12)* %v9, {} addrspace(12)* %v10) + ret i32 %v11 +; CHECK: ret i32 +} + +define i32 @freeze({} addrspace(10)* %v0, {} addrspace(10)* %v1) { +top: +; CHECK-LABEL: @freeze +; CHECK-NOT: @julia.new_gc_frame + %v2 = call {}*** @julia.get_pgcstack() + %v3 = bitcast {} addrspace(10)* %v0 to {} addrspace(10)* addrspace(10)* + %v4 = addrspacecast {} addrspace(10)* addrspace(10)* %v3 to {} addrspace(10)* addrspace(11)* + %v5 = load atomic {} addrspace(10)*, {} addrspace(10)* addrspace(11)* %v4 unordered, align 8 + %v6 = bitcast {} addrspace(10)* %v1 to {} addrspace(10)* addrspace(10)* + %v7 = addrspacecast {} addrspace(10)* addrspace(10)* %v6 to {} addrspace(10)* addrspace(11)* + %v8 = load atomic {} addrspace(10)*, {} addrspace(10)* addrspace(11)* %v7 unordered, align 8 + %fv8 = freeze {} addrspace(10)* %v8 + %v9 = addrspacecast {} addrspace(10)* %v5 to {} addrspace(12)* + %v10 = addrspacecast {} addrspace(10)* %fv8 to {} addrspace(12)* + %v11 = call i32 @rooting_callee({} addrspace(12)* %v9, {} addrspace(12)* %v10) + ret i32 %v11 +; CHECK: ret i32 +} + +!0 = !{i64 0, i64 23} +!1 = !{!1} +!2 = !{!7} ; scope list +!3 = !{!4, !4, i64 0, i64 1} +!4 = !{!"jtbaa_const", !5} +!5 = !{!"jtbaa"} +!6 = distinct !{!6} ; alias domain +!7 = distinct !{!7, !6} ; alias scope + + +; CHECK: !0 = !{!1, !1, i64 0} +; CHECK-NEXT: !1 = !{!"jtbaa_gcframe", !2, i64 0} +; CHECK-NEXT: !2 = !{!"jtbaa", !3, i64 0} +; CHECK-NEXT: !3 = !{!"jtbaa"} +; CHECK-NEXT: !4 = !{!5, !5, i64 0} +; CHECK-NEXT: !5 = !{!"jtbaa_tag", !6, i64 0} +; CHECK-NEXT: !6 = !{!"jtbaa_data", !2, i64 0} +; CHECK-NEXT: !7 = !{i64 0, i64 23} +; CHECK-NEXT: !8 = !{!9} +; CHECK-NEXT: !9 = distinct !{!9, !10} +; CHECK-NEXT: !10 = distinct !{!10} +; CHECK-NEXT: !11 = !{!12, !12, i64 0} +; CHECK-NEXT: !12 = !{!"jtbaa_const", !3} diff --git a/test/llvmpasses/late-lower-gc.ll b/test/llvmpasses/late-lower-gc.ll index 22cb558c54158..98c472771aaf9 100644 --- a/test/llvmpasses/late-lower-gc.ll +++ b/test/llvmpasses/late-lower-gc.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LateLowerGCFrame -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame)' -S %s | FileCheck %s @tag = external addrspace(10) global {}, align 16 @@ -41,7 +44,7 @@ top: %0 = bitcast {}*** %pgcstack to {}** %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 ; CHECK: %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 -; CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 14 +; CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 16 ; CHECK-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; CHECK-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; CHECK-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* @@ -66,7 +69,7 @@ top: %0 = bitcast {}*** %pgcstack to {}** %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 ; CHECK: %current_task = getelementptr inbounds {}*, {}** %0, i64 -12 -; CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 14 +; CHECK-NEXT: [[ptls_field:%.*]] = getelementptr inbounds {}*, {}** %current_task, i64 16 ; CHECK-NEXT: [[ptls_load:%.*]] = load {}*, {}** [[ptls_field]], align 8, !tbaa !0 ; CHECK-NEXT: [[ppjl_ptls:%.*]] = bitcast {}* [[ptls_load]] to {}** ; CHECK-NEXT: [[ptls_i8:%.*]] = bitcast {}** [[ppjl_ptls]] to i8* @@ -81,7 +84,7 @@ top: %loadedval = load i64, i64 addrspace(10)* %v64, align 8, !range !0, !invariant.load !1 ; CHECK-NEXT: store i64 %loadedval, i64 addrspace(10)* %v64, align 8, !noalias !8 store i64 %loadedval, i64 addrspace(10)* %v64, align 8, !noalias !2 -; CHECK-NEXT: %lv2 = load i64, i64 addrspace(10)* %v64, align 8, !tbaa !9, !range !7 +; CHECK-NEXT: %lv2 = load i64, i64 addrspace(10)* %v64, align 8, !tbaa !11, !range !7 %lv2 = load i64, i64 addrspace(10)* %v64, align 8, !range !0, !tbaa !4 ; CHECK-NEXT: ret void ret void @@ -124,12 +127,57 @@ top: ; CHECK: ret i32 } +; COM: the bugs here may be caught by death-by-verify-assertion +define {} addrspace(10)* @gclift_switch({} addrspace(13)* addrspace(10)* %input, i1 %unpredictable) { + top: + %0 = call {}*** @julia.get_pgcstack() + br i1 %unpredictable, label %mid1, label %mid2 + mid1: + br label %mid2 + mid2: + %root = phi {} addrspace(13)* addrspace(10)* [ %input, %top ], [ %input, %mid1 ] + %unrelated = phi i1 [ %unpredictable, %top ], [ %unpredictable, %mid1 ] + %1 = addrspacecast {} addrspace(13)* addrspace(10)* %root to {} addrspace(13)* addrspace(11)* + %2 = bitcast {} addrspace(13)* addrspace(11)* %1 to {} addrspace(11)* + switch i1 %unpredictable, label %end [ + i1 1, label %end + i1 0, label %end + ] + end: + %phi = phi {} addrspace(11)* [ %2, %mid2 ], [ %2, %mid2 ], [ %2, %mid2 ] + %ret = bitcast {} addrspace(13)* addrspace(10)* %input to {} addrspace(10)* + ; CHECK: %gclift + ret {} addrspace(10)* %ret +} + +define void @decayar([2 x {} addrspace(10)* addrspace(11)*] %ar) { + %v2 = call {}*** @julia.get_pgcstack() + %e0 = extractvalue [2 x {} addrspace(10)* addrspace(11)*] %ar, 0 + %l0 = load {} addrspace(10)*, {} addrspace(10)* addrspace(11)* %e0 + %e1 = extractvalue [2 x {} addrspace(10)* addrspace(11)*] %ar, 1 + %l1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(11)* %e1 + %r = call i32 @callee_root({} addrspace(10)* %l0, {} addrspace(10)* %l1) + ret void +} + +; CHECK-LABEL: @decayar +; CHECK: %gcframe = call {} addrspace(10)** @julia.new_gc_frame(i32 2) +; CHECK: %1 = call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 1) +; CHECK: store {} addrspace(10)* %l0, {} addrspace(10)** %1, align 8 +; CHECK: %2 = call {} addrspace(10)** @julia.get_gc_frame_slot({} addrspace(10)** %gcframe, i32 0) +; CHECK: store {} addrspace(10)* %l1, {} addrspace(10)** %2, align 8 +; CHECK: %r = call i32 @callee_root({} addrspace(10)* %l0, {} addrspace(10)* %l1) +; CHECK: call void @julia.pop_gc_frame({} addrspace(10)** %gcframe) + !0 = !{i64 0, i64 23} -!1 = !{} -!2 = distinct !{!2} +!1 = !{!1} +!2 = !{!7} ; scope list !3 = !{!4, !4, i64 0, i64 1} !4 = !{!"jtbaa_const", !5} !5 = !{!"jtbaa"} +!6 = distinct !{!6} ; alias domain +!7 = distinct !{!7, !6} ; alias scope + ; CHECK: !0 = !{!1, !1, i64 0} ; CHECK-NEXT: !1 = !{!"jtbaa_gcframe", !2, i64 0} @@ -139,4 +187,8 @@ top: ; CHECK-NEXT: !5 = !{!"jtbaa_tag", !6, i64 0} ; CHECK-NEXT: !6 = !{!"jtbaa_data", !2, i64 0} ; CHECK-NEXT: !7 = !{i64 0, i64 23} -; CHECK-NEXT: !8 = distinct !{!8} +; CHECK-NEXT: !8 = !{!9} +; CHECK-NEXT: !9 = distinct !{!9, !10} +; CHECK-NEXT: !10 = distinct !{!10} +; CHECK-NEXT: !11 = !{!12, !12, i64 0} +; CHECK-NEXT: !12 = !{!"jtbaa_const", !3} diff --git a/test/llvmpasses/loopinfo.jl b/test/llvmpasses/loopinfo.jl index 508127066ee33..c970e07f8a125 100644 --- a/test/llvmpasses/loopinfo.jl +++ b/test/llvmpasses/loopinfo.jl @@ -3,6 +3,7 @@ # RUN: julia --startup-file=no %s %t && llvm-link -S %t/* -o %t/module.ll # RUN: cat %t/module.ll | FileCheck %s # RUN: cat %t/module.ll | opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LowerSIMDLoop -S - | FileCheck %s -check-prefix=LOWER +# RUN: cat %t/module.ll | opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='LowerSIMDLoop' -S - | FileCheck %s -check-prefix=LOWER # RUN: julia --startup-file=no %s %t -O && llvm-link -S %t/* -o %t/module.ll # RUN: cat %t/module.ll | FileCheck %s -check-prefix=FINAL @@ -31,7 +32,7 @@ function simdf(X) # LOWER: fadd fast double # LOWER-NOT: call void @julia.loopinfo_marker() # LOWER: br {{.*}}, !llvm.loop [[LOOPID:![0-9]+]] -# FINAL: fadd fast <{{[0-9]+}} x double> +# FINAL: fadd fast <{{(vscale x )?}}{{[0-9]+}} x double> end acc end diff --git a/test/llvmpasses/lower-handlers-addrspaces.ll b/test/llvmpasses/lower-handlers-addrspaces.ll new file mode 100644 index 0000000000000..fcc4dc0114c21 --- /dev/null +++ b/test/llvmpasses/lower-handlers-addrspaces.ll @@ -0,0 +1,32 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LowerExcHandlers -print-before-all -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LowerExcHandlers)' -S %s | FileCheck %s + +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 i8**** @julia.ptls_states() +declare i8**** @julia.get_pgcstack() + +define void @simple() { +top: + %pgcstack = call i8**** @julia.get_pgcstack() +; CHECK: call void @llvm.lifetime.start +; CHECK: call void @ijl_enter_handler +; CHECK: setjmp + %r = call i32 @julia.except_enter() + %cmp = icmp eq i32 %r, 0 + br i1 %cmp, label %try, label %catch +try: + br label %after +catch: + br label %after +after: + call void @ijl_pop_handler(i32 1) +; CHECK: llvm.lifetime.end + ret void +} diff --git a/test/llvmpasses/lower-handlers.ll b/test/llvmpasses/lower-handlers.ll index 80b6be683c024..c3d51f2e94c30 100644 --- a/test/llvmpasses/lower-handlers.ll +++ b/test/llvmpasses/lower-handlers.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LowerExcHandlers -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LowerExcHandlers)' -S %s | FileCheck %s attributes #1 = { returns_twice } declare i32 @julia.except_enter() #1 diff --git a/test/llvmpasses/muladd.ll b/test/llvmpasses/muladd.ll index 42a6bcff96b96..f93940db392af 100644 --- a/test/llvmpasses/muladd.ll +++ b/test/llvmpasses/muladd.ll @@ -1,5 +1,10 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -CombineMulAdd -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='CombineMulAdd' -S %s | FileCheck %s + +; CHECK-LABEL: @fast_muladd1 define double @fast_muladd1(double %a, double %b, double %c) { top: ; CHECK: {{contract|fmuladd}} @@ -9,6 +14,7 @@ top: ret double %v2 } +; CHECK-LABEL: @fast_mulsub1 define double @fast_mulsub1(double %a, double %b, double %c) { top: ; CHECK: {{contract|fmuladd}} @@ -18,6 +24,7 @@ top: ret double %v2 } +; CHECK-LABEL: @fast_mulsub_vec1 define <2 x double> @fast_mulsub_vec1(<2 x double> %a, <2 x double> %b, <2 x double> %c) { top: ; CHECK: {{contract|fmuladd}} @@ -26,3 +33,31 @@ top: ; CHECK: ret <2 x double> ret <2 x double> %v2 } + +; COM: Should not mark fmul as contract when multiple uses of fmul exist +; CHECK-LABEL: @slow_muladd1 +define double @slow_muladd1(double %a, double %b, double %c) { +top: +; CHECK: %v1 = fmul double %a, %b + %v1 = fmul double %a, %b +; CHECK: %v2 = fadd fast double %v1, %c + %v2 = fadd fast double %v1, %c +; CHECK: %v3 = fadd fast double %v1, %b + %v3 = fadd fast double %v1, %b +; CHECK: %v4 = fadd fast double %v3, %v2 + %v4 = fadd fast double %v3, %v2 +; CHECK: ret double %v4 + ret double %v4 +} + +; COM: Should not mark fadd->fadd fast as contract +; CHECK-LABEL: @slow_addadd1 +define double @slow_addadd1(double %a, double %b, double %c) { +top: +; CHECK: %v1 = fadd double %a, %b + %v1 = fadd double %a, %b +; CHECK: %v2 = fadd fast double %v1, %c + %v2 = fadd fast double %v1, %c +; CHECK: ret double %v2 + ret double %v2 +} diff --git a/test/llvmpasses/multiversioning-annotate-only.ll b/test/llvmpasses/multiversioning-annotate-only.ll new file mode 100644 index 0000000000000..ababb4fc74b8a --- /dev/null +++ b/test/llvmpasses/multiversioning-annotate-only.ll @@ -0,0 +1,219 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -JuliaMultiVersioning -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning' -S %s | FileCheck %s + +; COM: This test checks that multiversioning correctly picks up on features that should trigger cloning +; COM: Note that for annotations alone, we don't need jl_fvars or jl_gvars + +; COM: Copied from src/processor.h +; COM: JL_TARGET_VEC_CALL = 1 << 0, +; COM: // Clone all functions +; COM: JL_TARGET_CLONE_ALL = 1 << 1, +; COM: // Clone when there's scalar math operations that can benefit from target-specific +; COM: // optimizations. This includes `muladd`, `fma`, `fast`/`contract` flags. +; COM: JL_TARGET_CLONE_MATH = 1 << 2, +; COM: // Clone when the function has a loop +; COM: JL_TARGET_CLONE_LOOP = 1 << 3, +; COM: // Clone when the function uses any vectors +; COM: // When this is specified, the cloning pass should also record if any of the cloned functions +; COM: // used this in any function call (including the signature of the function itself) +; COM: JL_TARGET_CLONE_SIMD = 1 << 4, +; COM: // The CPU name is unknown +; COM: JL_TARGET_UNKNOWN_NAME = 1 << 5, +; COM: // Optimize for size for this target +; COM: JL_TARGET_OPTSIZE = 1 << 6, +; COM: // Only optimize for size for this target +; COM: JL_TARGET_MINSIZE = 1 << 7, +; COM: // Clone when the function queries CPU features +; COM: JL_TARGET_CLONE_CPU = 1 << 8, +; COM: // Clone when the function uses fp16 +; COM: JL_TARGET_CLONE_FLOAT16 = 1 << 9, + +; COM: start with the basics, just one feature per function + +; COM: boring should only be cloned if clone_all is enabled on the target +; CHECK: @boring{{.*}}#[[BORING_ATTRS:[0-9]+]] +define noundef i32 @boring(i32 noundef %0) { + ret i32 %0 +} + +; CHECK: @fastmath_test{{.*}}#[[FASTMATH_TEST_ATTRS:[0-9]+]] +define noundef float @fastmath_test(float noundef %0, float noundef %1) { + %3 = fadd fast float %0, %1 + ret float %3 +} + +; CHECK: @loop_test{{.*}}#[[LOOP_TEST_ATTRS:[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 !9 +} + +; CHECK: @simd_test{{.*}}#[[SIMD_TEST_ATTRS:[0-9]+]] +define noundef i32 @simd_test(<4 x i32> noundef %0) { + %2 = extractelement <4 x i32> %0, i64 0 + ret i32 %2 +} + +; COM: now check all the combinations + +; CHECK: @simd_fastmath_test{{.*}}#[[SIMD_FASTMATH_TEST_ATTRS:[0-9]+]] +define noundef float @simd_fastmath_test(<4 x float> noundef %0) { + %2 = extractelement <4 x float> %0, i64 0 + %3 = extractelement <4 x float> %0, i64 1 + %4 = fadd fast float %2, %3 + ret float %4 +} + +; CHECK: @loop_fastmath_test{{.*}}#[[LOOP_FASTMATH_TEST_ATTRS:[0-9]+]] +define noundef i32 @loop_fastmath_test(i32 noundef %0) { + %2 = icmp sgt i32 %0, 0 + br i1 %2, label %7, label %5 + +3: ; preds = %7 + %4 = fptosi float %12 to i32 + br label %5 + +5: ; preds = %3, %1 + %6 = phi i32 [ 0, %1 ], [ %4, %3 ] + ret i32 %6 + +7: ; preds = %1, %7 + %8 = phi i32 [ %13, %7 ], [ 0, %1 ] + %9 = phi float [ %12, %7 ], [ 0.000000e+00, %1 ] + %10 = lshr i32 %8, 1 + %11 = sitofp i32 %10 to float + %12 = fadd fast float %9, %11 + %13 = add nuw nsw i32 %8, 1 + %14 = icmp eq i32 %13, %0 + br i1 %14, label %3, label %7, !llvm.loop !9 +} + +; CHECK: @simd_loop_test{{.*}}#[[SIMD_LOOP_TEST_ATTRS:[0-9]+]] +define dso_local noundef i32 @simd_loop_test(<4 x i32> noundef %0) { + %2 = extractelement <4 x i32> %0, i64 0 + %3 = icmp sgt i32 %2, 0 + br i1 %3, label %6, label %4 + +4: ; preds = %6, %1 + %5 = phi i32 [ 0, %1 ], [ %10, %6 ] + ret i32 %5 + +6: ; preds = %1, %6 + %7 = phi i32 [ %11, %6 ], [ 0, %1 ] + %8 = phi i32 [ %10, %6 ], [ 0, %1 ] + %9 = lshr i32 %7, 1 + %10 = add nuw nsw i32 %9, %8 + %11 = add nuw nsw i32 %7, 1 + %12 = icmp eq i32 %11, %2 + br i1 %12, label %4, label %6, !llvm.loop !9 +} + +; CHECK: @simd_loop_fastmath_test{{.*}}#[[SIMD_LOOP_FASTMATH_TEST_ATTRS:[0-9]+]] +define noundef i32 @simd_loop_fastmath_test(<4 x i32> noundef %0) { + %2 = extractelement <4 x i32> %0, i64 0 + %3 = icmp sgt i32 %2, 0 + br i1 %3, label %8, label %6 + +4: ; preds = %8 + %5 = fptosi float %13 to i32 + br label %6 + +6: ; preds = %4, %1 + %7 = phi i32 [ 0, %1 ], [ %5, %4 ] + ret i32 %7 + +8: ; preds = %1, %8 + %9 = phi i32 [ %14, %8 ], [ 0, %1 ] + %10 = phi float [ %13, %8 ], [ 0.000000e+00, %1 ] + %11 = lshr i32 %9, 1 + %12 = sitofp i32 %11 to float + %13 = fadd fast float %10, %12 + %14 = add nuw nsw i32 %9, 1 + %15 = icmp eq i32 %14, %2 + br i1 %15, label %4, label %8, !llvm.loop !9 +} + +; COM: check for fvar and reloc annotations on functions used by other globals + +@func_gv = global i32 (i32)* @func_in_gv, align 8 + +; CHECK: @func_in_gv{{.*}}#[[FUNC_IN_GV_ATTRS:[0-9]+]] +define noundef i32 @func_in_gv(i32 noundef returned %0) { + ret i32 %0 +} + +@aliaser = alias i32 (i32)*, bitcast (i32 (i32)* @aliasee to i32 (i32)**) + +; CHECK: @aliasee{{.*}}#[[ALIASEE_ATTRS:[0-9]+]] +define i32 @aliasee(i32 noundef returned %0) { + ret i32 %0 +} + +; COM: check for reloc annotations on functions used by other functions +; CHECK: @cloned{{.*}}#[[CLONED_RELOC_ATTRS:[0-9]+]] +define noundef float @cloned(float noundef %0, float noundef %1) { + %3 = fadd fast float %0, %1 + ret float %3 +} + +define noundef i32 @uncloned(i32 noundef %0) { + %2 = sitofp i32 %0 to float + %3 = call noundef float @cloned(float noundef %2, float noundef %2) + %4 = fptosi float %3 to i32 + ret i32 %4 +} + +; COM: Note that these strings are hex-encoded bits of the target indices that will be cloned +; CHECK-DAG: attributes #[[BORING_ATTRS]] = { "julia.mv.clones"="2" } +; CHECK-DAG: attributes #[[FASTMATH_TEST_ATTRS]] = { "julia.mv.clones"="6" } +; CHECK-DAG: attributes #[[LOOP_TEST_ATTRS]] = { "julia.mv.clones"="A" } +; CHECK-DAG: attributes #[[SIMD_TEST_ATTRS]] = { "julia.mv.clones"="12" } +; CHECK-DAG: attributes #[[SIMD_FASTMATH_TEST_ATTRS]] = { "julia.mv.clones"="16" } +; CHECK-DAG: attributes #[[LOOP_FASTMATH_TEST_ATTRS]] = { "julia.mv.clones"="E" } +; CHECK-DAG: attributes #[[SIMD_LOOP_TEST_ATTRS]] = { "julia.mv.clones"="1A" } +; CHECK-DAG: attributes #[[SIMD_LOOP_FASTMATH_TEST_ATTRS]] = { "julia.mv.clones"="1E" } +; CHECK-DAG: attributes #[[FUNC_IN_GV_ATTRS]] +; CHECK-SAME: "julia.mv.clones"="2" +; CHECK-SAME: "julia.mv.fvar" +; CHECK-DAG: attributes #[[ALIASEE_ATTRS]] +; CHECK-SAME: "julia.mv.clones"="2" +; CHECK-SAME: "julia.mv.reloc" +; CHECK-DAG: attributes #[[CLONED_RELOC_ATTRS]] +; CHECK-SAME: "julia.mv.clones"="6" +; CHECK-SAME: "julia.mv.reloc" + +; CHECK-LABEL: !llvm.module.flags + +!llvm.module.flags = !{!0, !1, !2} + +; CHECK-DAG: julia.mv.enable +; CHECK-DAG: julia.mv.skipcloning +; CHECK-DAG: julia.mv.specs +; CHECK-DAG: julia.mv.annotated +; CHECK-DAG: julia.mv.veccall + +!0 = !{i32 1, !"julia.mv.enable", i32 1} +!1 = !{i32 1, !"julia.mv.skipcloning", i32 1} +!2 = !{i32 1, !"julia.mv.specs", !3} +!3 = !{!4, !5, !6, !7, !8} +!4 = !{!"cpubase", !"nofeatures", i32 0, i32 2} +!5 = !{!"cpucloneall", !"cloneall", i32 0, i32 2} +!6 = !{!"cpufastmath", !"fastmathclone", i32 0, i32 4} +!7 = !{!"cpuloop", !"loopclone", i32 0, i32 8} +!8 = !{!"cpusimd", !"simdclone", i32 0, i32 16} +!9 = !{!9} diff --git a/test/llvmpasses/multiversioning-clone-only.ll b/test/llvmpasses/multiversioning-clone-only.ll new file mode 100644 index 0000000000000..897652700c335 --- /dev/null +++ b/test/llvmpasses/multiversioning-clone-only.ll @@ -0,0 +1,215 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -JuliaMultiVersioning -S %s | FileCheck %s --allow-unused-prefixes=false +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='JuliaMultiVersioning' -S %s | FileCheck %s --allow-unused-prefixes=false + +; CHECK: @jl_fvar_idxs = hidden constant [1 x i32] zeroinitializer +; CHECK: @jl_gvar_idxs = hidden constant [0 x i32] zeroinitializer +; CHECK: @subtarget_cloned_gv = hidden global i64* null +; CHECK: @subtarget_cloned.reloc_slot = hidden global i32 (i32)* 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_clone_slots = hidden constant [5 x i32] +; CHECK-SAME: i32 2, i32 0, {{.*}} sub {{.*}}@subtarget_cloned.reloc_slot{{.*}}@jl_gvar_base +; 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 + +@subtarget_cloned_aliased = alias i32 (i32), i32 (i32)* @subtarget_cloned + +; CHECK: define{{.*}}@boring({{.*}}#[[BORING_DEFAULT_ATTRS:[0-9]+]] +; CHECK-NEXT: ret i32 %0 +define noundef i32 @boring(i32 noundef %0) #0 { + ret i32 %0 +} + +; CHECK: declare{{.*}}@declaration({{.*}}#[[DECLARATION_DEFAULT_ATTRS:[0-9]+]] +declare i32 @declaration(i32 %0) #1 + +; CHECK: define{{.*}}@call_boring({{.*}}#[[BORING_DEFAULT_ATTRS]] +; CHECK-NEXT: %2 = call noundef i32 @boring(i32 noundef %0) +define noundef i32 @call_boring(i32 noundef %0) #0 { + %2 = call noundef i32 @boring(i32 noundef %0) + ret i32 %2 +} + +; CHECK: define{{.*}}@call_declaration({{.*}}#[[DECLARATION_DEFAULT_ATTRS]] +; CHECK-NEXT: %2 = call noundef i32 @declaration(i32 noundef %0) +define noundef i32 @call_declaration(i32 noundef %0) #1 { + %2 = call noundef i32 @declaration(i32 noundef %0) + ret i32 %2 +} + +; CHECK: define{{.*}}@subtarget_cloned({{.*}}#[[SUBTARGET_CLONED_DEFAULT_ATTRS:[0-9]+]] +; CHECK-NEXT: ret i32 0 +define noundef i32 @subtarget_cloned(i32 noundef %0) #2 { + ret i32 0 +} + +; COM: should fixup this callsite since 2 is cloned for a subtarget +; CHECK: define{{.*}}@call_subtarget_cloned({{.*}}#[[CALL_SUBTARGET_CLONED_DEFAULT_ATTRS:[0-9]+]] +; CHECK-NEXT: [[FUNC_PTR:%[0-9]+]] = load{{.*}}@subtarget_cloned.reloc_slot{{.*}}!tbaa ![[TBAA_CONST_METADATA:[0-9]+]], !invariant.load +; CHECK-NEXT: call{{.*}}[[FUNC_PTR]] +; CHECK: ret i32 +define noundef i32 @call_subtarget_cloned(i32 noundef %0) #3 { + %2 = call noundef i32 @subtarget_cloned(i32 noundef %0) + ret i32 %2 +} + +; CHECK: define{{.*}}@call_subtarget_cloned_but_not_cloned({{.*}}#[[BORING_DEFAULT_ATTRS]] +; CHECK-NEXT: [[FUNC_PTR:%[0-9]+]] = load{{.*}}@subtarget_cloned.reloc_slot{{.*}}!tbaa ![[TBAA_CONST_METADATA]], !invariant.load +; CHECK-NEXT: call{{.*}}[[FUNC_PTR]] +; CHECK: ret i32 +define noundef i32 @call_subtarget_cloned_but_not_cloned(i32 noundef %0) #0 { + %2 = call noundef i32 @subtarget_cloned(i32 noundef %0) + ret i32 %2 +} + +; CHECK: define{{.*}}@boring.1({{.*}}#[[BORING_CLONEALL_ATTRS:[0-9]+]] +; CHECK-NEXT: ret i32 %0 + +; CHECK: declare{{.*}}@declaration.1({{.*}}#[[DECLARATION_CLONEALL_ATTRS:[0-9]+]] + +; COM: should not fixup this callsite since boring is not cloned for a subtarget +; COM: also should call boring.1 instead of boring +; CHECK: define{{.*}}@call_boring.1({{.*}}#[[BORING_CLONEALL_ATTRS]] +; CHECK-NEXT: %2 = call noundef i32 @boring.1(i32 noundef %0) + +; CHECK: define{{.*}}@call_declaration.1({{.*}}#[[DECLARATION_CLONEALL_ATTRS]] +; CHECK-NEXT: %2 = call noundef i32 @declaration.1(i32 noundef %0) + +; CHECK: define{{.*}}@subtarget_cloned.1({{.*}}#[[SUBTARGET_CLONED_CLONEALL_ATTRS:[0-9]+]] +; CHECK-NEXT: ret i32 0 + +; CHECK: define{{.*}}@subtarget_cloned.2({{.*}}#[[SUBTARGET_CLONED_FASTMATH_ATTRS:[0-9]+]] +; CHECK-NEXT: ret i32 0 + +; COM: should *NOT* fixup this callsite since subtarget_cloned is not cloned for a subtarget of the cloneall +; CHECK: define{{.*}}@call_subtarget_cloned.1({{.*}}#[[CALL_SUBTARGET_CLONED_CLONEALL_ATTRS:[0-9]+]] +; CHECK-NEXT: %2 = call noundef i32 @subtarget_cloned.1(i32 noundef %0) + +; CHECK: define {{.*}}@call_subtarget_cloned.2({{.*}}#[[CALL_SUBTARGET_CLONED_FASTMATH_ATTRS:[0-9]+]] +; CHECK-NEXT: %2 = call noundef i32 @subtarget_cloned.2(i32 noundef %0) + +; CHECK: define{{.*}}@call_subtarget_cloned_but_not_cloned.1({{.*}}#[[BORING_CLONEALL_ATTRS]] +; CHECK-NEXT: %2 = call noundef i32 @subtarget_cloned.1(i32 noundef %0) + +; COM: should not have cloned for fastmath +; CHECK-NOT: @subtarget_cloned_but_not_cloned.2 + +; COM: check for alias being rewritten to a function trampoline +; CHECK: define{{.*}}@subtarget_cloned_aliased{{.*}}#[[SUBTARGET_ALIASED_ATTRS:[0-9]+]] +; CHECK-NOT: } +; CHECK: [[FUNC_PTR:%[0-9]+]] = load{{.*}}@subtarget_cloned.reloc_slot{{.*}}!tbaa ![[TBAA_CONST_METADATA]], !invariant.load +; CHECK-NEXT: call{{.*}}[[FUNC_PTR]] +; CHECK: ret i32 + +; CHECK: attributes #[[BORING_DEFAULT_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="2" +; CHECK-DAG: "julia.mv.clone"="0" +; CHECK-DAG: "target-cpu"="cpubase" +; CHECK-DAG: "target-features"="nofeatures" +; CHECK-SAME: } +; CHECK: attributes #[[DECLARATION_DEFAULT_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="2" +; CHECK-DAG: "julia.mv.clone"="0" +; CHECK-DAG: "target-cpu"="cpubase" +; CHECK-DAG: "target-features"="nofeatures" +; CHECK-SAME: } +; CHECK: attributes #[[SUBTARGET_CLONED_DEFAULT_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="6" +; CHECK-DAG: "julia.mv.clone"="0" +; CHECK-DAG: "target-cpu"="cpubase" +; CHECK-DAG: "target-features"="nofeatures" +; CHECK-DAG: "julia.mv.reloc" +; CHECK-SAME: } +; CHECK: attributes #[[CALL_SUBTARGET_CLONED_DEFAULT_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="6" +; CHECK-DAG: "julia.mv.clone"="0" +; CHECK-DAG: "target-cpu"="cpubase" +; CHECK-DAG: "target-features"="nofeatures" +; CHECK-SAME: } +; CHECK: attributes #[[BORING_CLONEALL_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="2" +; CHECK-DAG: "julia.mv.clone"="1" +; CHECK-DAG: "target-cpu"="cpucloneall" +; CHECK-DAG: "target-features"="cloneall" +; CHECK-SAME: } +; CHECK: attributes #[[DECLARATION_CLONEALL_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="2" +; CHECK-DAG: "julia.mv.clone"="1" +; CHECK-DAG: "target-cpu"="cpucloneall" +; CHECK-DAG: "target-features"="cloneall" +; CHECK-SAME: } +; CHECK: attributes #[[SUBTARGET_CLONED_CLONEALL_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="6" +; CHECK-DAG: "julia.mv.clone"="1" +; CHECK-DAG: "target-cpu"="cpucloneall" +; CHECK-DAG: "target-features"="cloneall" +; CHECK-DAG: "julia.mv.reloc" +; CHECK-SAME: } +; CHECK: attributes #[[SUBTARGET_CLONED_FASTMATH_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="6" +; CHECK-DAG: "julia.mv.clone"="2" +; CHECK-DAG: "target-cpu"="cpufastmath" +; CHECK-DAG: "target-features"="fastmathclone" +; CHECK-DAG: "julia.mv.reloc" +; CHECK-SAME: } +; CHECK: attributes #[[CALL_SUBTARGET_CLONED_CLONEALL_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="6" +; CHECK-DAG: "julia.mv.clone"="1" +; CHECK-DAG: "target-cpu"="cpucloneall" +; CHECK-DAG: "target-features"="cloneall" +; CHECK-SAME: } +; CHECK: attributes #[[CALL_SUBTARGET_CLONED_FASTMATH_ATTRS]] +; CHECK-SAME: { +; CHECK-DAG: "julia.mv.clones"="6" +; CHECK-DAG: "julia.mv.clone"="2" +; CHECK-DAG: "target-cpu"="cpufastmath" +; CHECK-DAG: "target-features"="fastmathclone" +; CHECK-SAME: } +; CHECK: attributes #[[SUBTARGET_ALIASED_ATTRS]] +; CHECK-SAME: { +; CHECK-SAME: "julia.mv.alias" +; CHECK-SAME: } +attributes #0 = {"julia.mv.clones"="2"} +attributes #1 = {"julia.mv.clones"="2" "test.unique"="1"} +attributes #2 = {"julia.mv.clones"="6" "julia.mv.reloc"} +attributes #3 = {"julia.mv.clones"="6"} + +!llvm.module.flags = !{!0, !1, !2} + +!0 = !{i32 1, !"julia.mv.enable", i32 1} +!1 = !{i32 1, !"julia.mv.annotated", i32 1} +!2 = !{i32 1, !"julia.mv.specs", !3} +!3 = !{!4, !5, !6, !7, !8} +!4 = !{!"cpubase", !"nofeatures", i32 0, i32 2} +!5 = !{!"cpucloneall", !"cloneall", i32 0, i32 2} +!6 = !{!"cpufastmath", !"fastmathclone", i32 0, i32 4} +!7 = !{!"cpuloop", !"loopclone", i32 0, i32 8} +!8 = !{!"cpusimd", !"simdclone", i32 0, i32 16} +; CHECK-DAG: ![[TBAA_CONST_METADATA]] = !{![[JTBAA_CONST_METADATA:[0-9]+]], ![[JTBAA_CONST_METADATA]] +; CHECK-DAG: ![[JTBAA_CONST_METADATA]] = !{!"jtbaa_const" diff --git a/test/llvmpasses/pipeline-o0.jl b/test/llvmpasses/pipeline-o0.jl new file mode 100644 index 0000000000000..1b5d1df3c9f36 --- /dev/null +++ b/test/llvmpasses/pipeline-o0.jl @@ -0,0 +1,32 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# RUN: julia --startup-file=no -O0 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s +# RUN: julia --startup-file=no -O1 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s +# RUN: julia --startup-file=no -O2 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s +# RUN: julia --startup-file=no -O3 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s + +include(joinpath("..", "testhelpers", "llvmpasses.jl")) + +# CHECK-LABEL: @julia_simple +# CHECK-NOT: julia.get_pgcstack +# CHECK: asm +# CHECK-NOT: julia.gc_alloc_obj +# CHECK: ijl_gc_pool_alloc +# COM: we want something vaguely along the lines of asm load from the fs register -> allocate bytes +function simple() + Ref(0) +end + +# CHECK-LABEL: @julia_buildarray +# CHECK-NOT: julia.write_barrier +# CHECK: gc_queue_root +function buildarray() + out = [] + for i in 1:100 + push!(out, Ref(0)) + end + out +end + +emit(simple) +emit(buildarray) diff --git a/test/llvmpasses/pipeline-o2-allocs.jl b/test/llvmpasses/pipeline-o2-allocs.jl new file mode 100644 index 0000000000000..86e1ded3f11e5 --- /dev/null +++ b/test/llvmpasses/pipeline-o2-allocs.jl @@ -0,0 +1,67 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# RUN: julia --startup-file=no -O2 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s +# RUN: julia --startup-file=no -O3 --check-bounds=yes %s %t -O && llvm-link -S %t/* | FileCheck %s + +include(joinpath("..", "testhelpers", "llvmpasses.jl")) + +# COM: This tests that simplifycfg is still hoisting allocations in different basic blocks +# COM: into the parent basic block, and deduplicating them in the process +# CHECK-LABEL: @julia_split +# CHECK: alloc +# CHECK-NOT: alloc +# CHECK: ret +function split(maybe) + if maybe + Ref(1) + else + Ref(2) + end +end + +# COM: This tests that irrespective of the condition outside the loop +# COM: allocations inside the loop are hoisted and the loop is deleted +# CHECK-LABEL: @julia_loop_alloc +# CHECK: phi +# CHECK-NOT: phi +function loop_alloc(N) + ref = Ref(zero(typeof(N))) + N <= zero(typeof(N)) && return ref + for i in one(typeof(N)):N + ref = Ref(i) + end + ref +end + +# COM: This tests that even with the allocation LLVM will recognize +# COM: that the loop is meaningless and delete it +# CHECK-LABEL: @julia_loop_const +# CHECK-NOT: br +function loop_const() + ref = Ref(0) + for i in 1:1000 + ref = Ref(0) + end + ref +end + +# COM: This tests that the GC.@preserve macro is being ignored since ref +# COM: is not used anywhere else +# CHECK-LABEL: @julia_nopreserve +# CHECK-NOT: alloc +# CHECK-NOT: julia.gc_preserve_begin +# CHECK-NOT: julia.gc_preserve_end +function nopreserve() + ref = Ref(0) + GC.@preserve ref begin + end +end + +# COM: this cordons off the attributes/function declarations from the actual +# COM: IR that we really want to check +# CHECK: attributes + +emit(split, Bool) +emit(loop_alloc, Int64) +emit(loop_const) +emit(nopreserve) diff --git a/test/llvmpasses/pipeline-o2-broadcast.jl b/test/llvmpasses/pipeline-o2-broadcast.jl new file mode 100644 index 0000000000000..584e8855f0f8c --- /dev/null +++ b/test/llvmpasses/pipeline-o2-broadcast.jl @@ -0,0 +1,123 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# RUN: julia --startup-file=no -O2 --check-bounds=auto %s %t -O && llvm-link -S %t/* | FileCheck %s +# RUN: julia --startup-file=no -O3 --check-bounds=auto %s %t -O && llvm-link -S %t/* | FileCheck %s + +include(joinpath("..", "testhelpers", "llvmpasses.jl")) + +# COM: Check broadcasted outer product is vectorized + +# COM: Float32 +# CHECK: @japi1_prod_v_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x float> +# COM: fmul <[[VSCALE]][[VEC_FACTOR]] x float> +# CHECK: fmul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x float> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x float> + +# COM: Float64 +# CHECK: @japi1_prod_v_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x double> +# COM: fmul <[[VSCALE]][[VEC_FACTOR]] x double> +# CHECK: fmul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x double> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x double> + +# COM: Int32 +# CHECK: @japi1_prod_v_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i32> +# COM: mul <[[VSCALE]][[VEC_FACTOR]] x i32> +# CHECK: mul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i32> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x i32> + +# COM: Int64 +# CHECK: @japi1_prod_v_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i64> +# COM: mul <[[VSCALE]][[VEC_FACTOR]] x i64> +# CHECK: mul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i64> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x i64> + +function prod_v_vT(R, x, y) + R .= x .* y' +end + +# COM: Check broadcasted inner product is vectorized + +# COM: Float32 +# CHECK: @japi1_prod_vT_v +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x float> +# COM: fmul <[[VSCALE]][[VEC_FACTOR]] x float> +# CHECK: fmul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x float> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x float> + +# COM: Float64 +# CHECK: @japi1_prod_vT_v +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x double> +# COM: fmul <[[VSCALE]][[VEC_FACTOR]] x double> +# CHECK: fmul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x double> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x double> + +# COM: Int32 +# CHECK: @japi1_prod_vT_v +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i32> +# COM: mul <[[VSCALE]][[VEC_FACTOR]] x i32> +# CHECK: mul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i32> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x i32> + +# COM: Int64 +# CHECK: @japi1_prod_vT_v +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i64> +# COM: mul <[[VSCALE]][[VEC_FACTOR]] x i64> +# CHECK: mul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i64> +# CHECK: store <[[VSCALE]][[VEC_FACTOR]] x i64> + +function prod_vT_v(R, x, y) + R .= x' .* y +end + +# COM: Check broadcasted multiplications are vectorized + +# COM: Float32 +# CHECK: @japi1_prod_v_M_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x float> +# COM: fmul <[[VSCALE]][[VEC_FACTOR]] x float> +# XFAIL-CHECK: fmul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x float> +# XFAIL-CHECK: store <[[VSCALE]][[VEC_FACTOR]] x float> + +# COM: Float64 +# CHECK: @japi1_prod_v_M_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x double> +# COM: fmul <[[VSCALE]][[VEC_FACTOR]] x double> +# XFAIL-CHECK: fmul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x double> +# XFAIL-CHECK: store <[[VSCALE]][[VEC_FACTOR]] x double> + +# COM: Int32 +# CHECK: @japi1_prod_v_M_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i32> +# COM: mul <[[VSCALE]][[VEC_FACTOR]] x i32> +# XFAIL-CHECK: mul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i32> +# XFAIL-CHECK: store <[[VSCALE]][[VEC_FACTOR]] x i32> + +# COM: Int64 +# CHECK: @japi1_prod_v_M_vT +# COM: load <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i64> +# COM: mul <[[VSCALE]][[VEC_FACTOR]] x i64> +# XFAIL-CHECK: mul <[[VSCALE:(vscale x )?]][[VEC_FACTOR:[0-9]+]] x i64> +# XFAIL-CHECK: store <[[VSCALE]][[VEC_FACTOR]] x i64> + +function prod_v_M_vT(R, x, M, y) + R .= x .* M .* y' +end + +emit(prod_v_vT, Matrix{Float32}, Vector{Float32}, Vector{Float32}) +emit(prod_v_vT, Matrix{Float64}, Vector{Float64}, Vector{Float64}) +emit(prod_v_vT, Matrix{Int32}, Vector{Int32}, Vector{Int32}) +emit(prod_v_vT, Matrix{Int64}, Vector{Int64}, Vector{Int64}) + +emit(prod_vT_v, Matrix{Float32}, Vector{Float32}, Vector{Float32}) +emit(prod_vT_v, Matrix{Float64}, Vector{Float64}, Vector{Float64}) +emit(prod_vT_v, Matrix{Int32}, Vector{Int32}, Vector{Int32}) +emit(prod_vT_v, Matrix{Int64}, Vector{Int64}, Vector{Int64}) + +emit(prod_v_M_vT, Matrix{Float32}, Vector{Float32}, Matrix{Float32}, Vector{Float32}) +emit(prod_v_M_vT, Matrix{Float64}, Vector{Float64}, Matrix{Float64}, Vector{Float64}) +emit(prod_v_M_vT, Matrix{Int32}, Vector{Int32}, Matrix{Int32}, Vector{Int32}) +emit(prod_v_M_vT, Matrix{Int64}, Vector{Int64}, Matrix{Int64}, Vector{Int64}) diff --git a/test/llvmpasses/pipeline-o2.jl b/test/llvmpasses/pipeline-o2.jl new file mode 100644 index 0000000000000..fcb2161de7614 --- /dev/null +++ b/test/llvmpasses/pipeline-o2.jl @@ -0,0 +1,152 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# 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 + +# 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 + +# RUN: julia --startup-file=no -O2 --check-bounds=auto %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL,BC_AUTO +# RUN: julia --startup-file=no -O3 --check-bounds=auto %s %t -O && llvm-link -S %t/* | FileCheck %s --check-prefixes=ALL,BC_AUTO + +include(joinpath("..", "testhelpers", "llvmpasses.jl")) + +# COM: Ensure safe iteration over one array is not boundschecked and is vectorized + +# ALL-LABEL: @julia_iterate_read +# ALL-NOT: bounds_error +# ALL: vector.body +function iterate_read(arr) + total = zero(eltype(arr)) + for i in eachindex(arr) + total += arr[i] + end + total +end + +# ALL-LABEL: @julia_iterate_write +# ALL-NOT: bounds_error +# ALL: vector.body +function iterate_write(arr, out) + for i in eachindex(arr, out) + out[i] = arr[i] + end +end + +# ALL-LABEL: @"julia_iterate_write! +# ALL-NOT: bounds_error +# ALL: vector.body +function iterate_write!(arr) + for i in eachindex(arr) + arr[i] *= 2 + end +end + +# COM: Ensure safe iteration over multiple arrays is not boundschecked and is vectorized + +# ALL-LABEL: @julia_multiiterate_read +# ALL-NOT: bounds_error +# ALL: vector.body +function multiiterate_read(arr1, arr2) + total = zero(eltype(arr1)) + for i in eachindex(arr1, arr2) + total += arr1[i] + total += arr2[i] + end + total +end + +# ALL-LABEL: @japi1_multiiterate_write +# ALL-NOT: bounds_error +# ALL: vector.body +function multiiterate_write(arr1, arr2, arr3) + for i in eachindex(arr1, arr2, arr3) + arr3[i] += arr1[i] + arr3[i] += arr2[i] + end +end + +# ALL-LABEL: @"julia_multiiterate_write! +# ALL-NOT: bounds_error +# ALL: vector.body +function multiiterate_write!(arr1, arr2) + for i in eachindex(arr1, arr2) + arr1[i] += arr2[i] + end +end + +# COM: memset checks + +# COM: INT64 +# ALL: define {{.*}} @julia_zeros +# ALL-NOT: bounds_error +# COM: memset is not used with bounds checks on (too late in the pipeline) +# BC_OFF: llvm.memset +# BC_AUTO: llvm.memset + +# COM: INT32 +# ALL: define {{.*}} @julia_zeros +# ALL-NOT: bounds_error +# COM: memset is not used with bounds checks on (too late in the pipeline) +# BC_OFF: llvm.memset +# BC_AUTO: llvm.memset + +# COM: INT16 +# ALL: define {{.*}} @julia_zeros +# ALL-NOT: bounds_error +# COM: memset is not used with bounds checks on (too late in the pipeline) +# BC_OFF: llvm.memset +# BC_AUTO: llvm.memset + +# COM: check reductive indvars/vectorization + +# ALL-LABEL: @julia_sumloop +# ALL: mul +function sumloop(N) + total = zero(typeof(N)) + for i in one(typeof(N)):N + total += i + end + total +end +# ALL-LABEL: @julia_simd_sumloop +# ALL: vector.body +function simd_sumloop(N) + total = zero(typeof(N)) + @simd for i in one(typeof(N)):N + total += i + end + total +end + +# COM: check hoisting and loop deletion functionality + +# ALL-LABEL: @julia_loopedlength +# ALL-NOT: br +# ALL: ret +function loopedlength(arr) + len = length(arr) + for i in 1:length(arr) + len = length(arr) + end + len +end + +emit(iterate_read, Vector{Int64}) +emit(iterate_write, Vector{Int64}, Vector{Int64}) +emit(iterate_write!, Vector{Int64}) + +emit(multiiterate_read, Vector{Int64}, Vector{Int64}) +emit(multiiterate_write, Vector{Int64}, Vector{Int64}, Vector{Int64}) +emit(multiiterate_write!, Vector{Int64}, Vector{Int64}) + +emit(zeros, Type{Int64}, Int64) +emit(zeros, Type{Int32}, Int64) +emit(zeros, Type{Int16}, Int64) +# COM: Int8 is hardcoded to memset anyways + +emit(sumloop, Int64) +# COM: Float64 doesn't vectorize for some reason +emit(simd_sumloop, Float32) + +emit(loopedlength, Vector{Int64}) diff --git a/test/llvmpasses/propagate-addrspace-non-zero.ll b/test/llvmpasses/propagate-addrspace-non-zero.ll new file mode 100644 index 0000000000000..c1ba2069102ac --- /dev/null +++ b/test/llvmpasses/propagate-addrspace-non-zero.ll @@ -0,0 +1,66 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + +; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -PropagateJuliaAddrspaces -dce -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='PropagateJuliaAddrspaces,dce' -S %s | FileCheck %s + +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" + +define i64 @simple() { +; CHECK-LABEL: @simple +; CHECK-NOT: addrspace(11) + %stack = alloca i64, addrspace(5) + %casted = addrspacecast i64 addrspace(5)*%stack to i64 addrspace(11)* + %loaded = load i64, i64 addrspace(11)* %casted + ret i64 %loaded +} + +define i64 @twogeps() { +; CHECK-LABEL: @twogeps +; CHECK-NOT: addrspace(11) + %stack = alloca i64, addrspace(5) + %casted = addrspacecast i64 addrspace(5)*%stack to i64 addrspace(11)* + %gep1 = getelementptr i64, i64 addrspace(11)* %casted, i64 1 + %gep2 = getelementptr i64, i64 addrspace(11)* %gep1, i64 1 + %loaded = load i64, i64 addrspace(11)* %gep2 + ret i64 %loaded +} + +define i64 @phi(i1 %cond) { +; CHECK-LABEL: @phi +; CHECK-NOT: addrspace(11) +top: + %stack1 = alloca i64, addrspace(5) + %stack2 = alloca i64, addrspace(5) + %stack1_casted = addrspacecast i64 addrspace(5)*%stack1 to i64 addrspace(11)* + %stack2_casted = addrspacecast i64 addrspace(5)*%stack2 to i64 addrspace(11)* + br i1 %cond, label %A, label %B +A: + br label %B +B: + %phi = phi i64 addrspace(11)* [ %stack1_casted, %top ], [ %stack2_casted, %A ] + %load = load i64, i64 addrspace(11)* %phi + ret i64 %load +} + + +define i64 @select(i1 %cond) { +; CHECK-LABEL: @select +; CHECK-NOT: addrspace(11) +top: + %stack1 = alloca i64, addrspace(5) + %stack2 = alloca i64, addrspace(5) + %stack1_casted = addrspacecast i64 addrspace(5)*%stack1 to i64 addrspace(11)* + %stack2_casted = addrspacecast i64 addrspace(5)*%stack2 to i64 addrspace(11)* + %select = select i1 %cond, i64 addrspace(11)* %stack1_casted, i64 addrspace(11)* %stack2_casted + %load = load i64, i64 addrspace(11)* %select + ret i64 %load +} + +define i64 @nullptr() { +; CHECK-LABEL: @nullptr +; CHECK-NOT: addrspace(11) + %casted = addrspacecast i64 addrspace(5)*null to i64 addrspace(11)* + %load = load i64, i64 addrspace(11)* %casted + ret i64 %load +} diff --git a/test/llvmpasses/propagate-addrspace.ll b/test/llvmpasses/propagate-addrspace.ll index 4fc357e03a7f1..92bf68578477f 100644 --- a/test/llvmpasses/propagate-addrspace.ll +++ b/test/llvmpasses/propagate-addrspace.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -PropagateJuliaAddrspaces -dce -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='PropagateJuliaAddrspaces,dce' -S %s | FileCheck %s define i64 @simple() { ; CHECK-LABEL: @simple diff --git a/test/llvmpasses/refinements.ll b/test/llvmpasses/refinements.ll index 0da965e538db8..3600fb76804ab 100644 --- a/test/llvmpasses/refinements.ll +++ b/test/llvmpasses/refinements.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LateLowerGCFrame -FinalLowerGC -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame),FinalLowerGC' -S %s | FileCheck %s declare {}*** @julia.ptls_states() @@ -6,6 +9,7 @@ declare {}*** @julia.get_pgcstack() declare void @jl_safepoint() declare void @one_arg_boxed({} addrspace(10)*) declare {} addrspace(10)* @ijl_box_int64(i64) +declare {} addrspace(10)* @allocate_some_value() define void @argument_refinement({} addrspace(10)* %a) { ; CHECK-LABEL: @argument_refinement @@ -53,8 +57,6 @@ define void @heap_refinement2(i64 %a) { ret void } -declare {} addrspace(10)* @allocate_some_value() - ; Check that the way we compute rooting is compatible with refinements define void @issue22770() { ; CHECK-LABEL: @issue22770 diff --git a/test/llvmpasses/remove-addrspaces.ll b/test/llvmpasses/remove-addrspaces.ll index 80eab21a51467..4710f9bd6c4d6 100644 --- a/test/llvmpasses/remove-addrspaces.ll +++ b/test/llvmpasses/remove-addrspaces.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -RemoveJuliaAddrspaces -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='RemoveJuliaAddrspaces' -S %s | FileCheck %s define i64 @getindex({} addrspace(10)* nonnull align 16 dereferenceable(40)) { @@ -46,7 +49,7 @@ top: %list = type { i64, %list* } ; COM: There's nothing to remove in this function; but remove-addrspaces shouldn't crash. -define i64 @sum.linked.list() #0 { +define i64 @sum.linked.list() { ; CHECK-LABEL: @sum.linked.list top: %a = alloca %list @@ -108,3 +111,9 @@ define void @byval_type([1 x {} addrspace(10)*] addrspace(11)* byval([1 x {} add ; CHECK: define void @byval_type([1 x {}*]* byval([1 x {}*]) %0) ret void } + + +; COM: check that function attributes are preserved on declarations too +declare void @convergent_function() #0 +attributes #0 = { convergent } +; CHECK: attributes #0 = { convergent } diff --git a/test/llvmpasses/returnstwicegc.ll b/test/llvmpasses/returnstwicegc.ll index ebc6127813876..404330ac3f7e1 100644 --- a/test/llvmpasses/returnstwicegc.ll +++ b/test/llvmpasses/returnstwicegc.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LateLowerGCFrame -FinalLowerGC -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame),FinalLowerGC' -S %s | FileCheck %s declare void @boxed_simple({} addrspace(10)*, {} addrspace(10)*) diff --git a/test/llvmpasses/safepoint_stress.jl b/test/llvmpasses/safepoint_stress.jl index 7ecf01a194851..dc6752e76d595 100644 --- a/test/llvmpasses/safepoint_stress.jl +++ b/test/llvmpasses/safepoint_stress.jl @@ -1,6 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # RUN: julia --startup-file=no %s | opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LateLowerGCFrame -FinalLowerGC -S - | FileCheck %s +# RUN: julia --startup-file=no %s | opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='function(LateLowerGCFrame),FinalLowerGC' -S - | FileCheck %s + println(""" declare {} addrspace(10)* @alloc() diff --git a/test/llvmpasses/simdloop.ll b/test/llvmpasses/simdloop.ll index ac3c92092b3ce..142250212984e 100644 --- a/test/llvmpasses/simdloop.ll +++ b/test/llvmpasses/simdloop.ll @@ -1,4 +1,7 @@ +; This file is a part of Julia. License is MIT: https://julialang.org/license + ; RUN: opt -enable-new-pm=0 -load libjulia-codegen%shlibext -LowerSIMDLoop -S %s | FileCheck %s +; RUN: opt -enable-new-pm=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='LowerSIMDLoop' -S %s | FileCheck %s declare void @julia.loopinfo_marker() diff --git a/test/loading.jl b/test/loading.jl index dc8a9103fbbe5..ea544c2635dbc 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1,16 +1,18 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +original_depot_path = copy(Base.DEPOT_PATH) + using Test # Tests for @__LINE__ inside and outside of macros -@test (@__LINE__) == 6 +@test (@__LINE__) == 8 macro macro_caller_lineno() - @test 9 == (@__LINE__) != __source__.line > 12 + @test 11 == (@__LINE__) != __source__.line > 14 return __source__.line end -@test @macro_caller_lineno() == (@__LINE__) > 12 +@test @macro_caller_lineno() == (@__LINE__) > 14 # @__LINE__ in a macro expands to the location of the macrocall in the source # while __source__.line is the location of the macro caller @@ -40,6 +42,7 @@ thefname = "the fname!//\\&\1*" include_string_test_func = include_string(@__MODULE__, "include_string_test() = @__FILE__", thefname) @test include_string_test_func() == thefname @test include_string(@__MODULE__, "Base.source_path()", thefname) == Base.source_path() +@test isdir(Base.source_dir()) @test basename(@__FILE__) == "loading.jl" @test isabspath(@__FILE__) @@ -124,6 +127,7 @@ let uuidstr = "ab"^4 * "-" * "ab"^2 * "-" * "ab"^2 * "-" * "ab"^2 * "-" * "ab"^6 @test string(uuid) == uuidstr == sprint(print, uuid) @test "check $uuid" == "check $uuidstr" @test UUID(UInt128(uuid)) == uuid + @test UUID(uuid) === uuid @test UUID(convert(NTuple{2, UInt64}, uuid)) == uuid @test UUID(convert(NTuple{4, UInt32}, uuid)) == uuid @@ -234,6 +238,7 @@ append!(empty!(DEPOT_PATH), [mktempdir(), joinpath(@__DIR__, "depot")]) @test watcher_counter[] == 0 @test_logs (:error, r"active project callback .* failed") Base.set_active_project(nothing) @test watcher_counter[] == 1 +pop!(Base.active_project_callbacks) @test load_path() == [joinpath(@__DIR__, "project", "Project.toml")] @@ -357,6 +362,13 @@ module NotPkgModule; end @test pkgdir(NotPkgModule, "src") === nothing end + @testset "pkgversion" begin + @test pkgversion(Foo) == v"1.2.3" + @test pkgversion(Foo.SubFoo1) == v"1.2.3" + @test pkgversion(Foo.SubFoo2) == v"1.2.3" + @test pkgversion(NotPkgModule) === nothing + end + end ## systematic generation of test environments ## @@ -651,6 +663,7 @@ finally Base.set_active_project(old_act_proj) popfirst!(LOAD_PATH) end +@test pkgversion(TestPkg) == v"1.2.3" @testset "--project and JULIA_PROJECT paths should be absolutified" begin mktempdir() do dir; cd(dir) do @@ -661,10 +674,10 @@ end cd("foo") @test Base.active_project() == old """ - @test success(`$(Base.julia_cmd()) --startup-file=no --project=foo -e $(script)`) - withenv("JULIA_PROJECT" => "foo") do - @test success(`$(Base.julia_cmd()) --startup-file=no -e $(script)`) - end + cmd = `$(Base.julia_cmd()) --startup-file=no -e $(script)` + cmd = addenv(cmd, "JULIA_PROJECT" => "foo") + cmd = pipeline(cmd; stdout, stderr) + @test success(cmd) end; end end @@ -677,15 +690,16 @@ mktempdir() do dir vdir = vdir[2:end] # remove @ vpath = joinpath(dir, "environments", vdir) mkpath(vpath) - withenv("JULIA_DEPOT_PATH" => dir) do - script = "@assert startswith(Base.active_project(), $(repr(vpath)))" - @test success(`$(Base.julia_cmd()) --startup-file=no -e $(script)`) - end + script = "@assert startswith(Base.active_project(), $(repr(vpath)))" + cmd = `$(Base.julia_cmd()) --startup-file=no -e $(script)` + cmd = addenv(cmd, "JULIA_DEPOT_PATH" => dir) + cmd = pipeline(cmd; stdout, stderr) + @test success(cmd) end @testset "expansion of JULIA_LOAD_PATH" begin s = Sys.iswindows() ? ';' : ':' - tmp = "/foo/bar" + tmp = "/this/does/not/exist" cases = Dict{Any,Vector{String}}( nothing => Base.DEFAULT_LOAD_PATH, "" => [], @@ -694,16 +708,17 @@ end "$s$tmp" => [Base.DEFAULT_LOAD_PATH; tmp], ) for (env, result) in pairs(cases) - withenv("JULIA_LOAD_PATH" => env) do - script = "LOAD_PATH == $(repr(result)) || error()" - @test success(`$(Base.julia_cmd()) --startup-file=no -e $script`) - end + script = "LOAD_PATH == $(repr(result)) || error()" + cmd = `$(Base.julia_cmd()) --startup-file=no -e $script` + cmd = addenv(cmd, "JULIA_LOAD_PATH" => env) + cmd = pipeline(cmd; stdout, stderr) + @test success(cmd) end end @testset "expansion of JULIA_DEPOT_PATH" begin s = Sys.iswindows() ? ';' : ':' - tmp = "/foo/bar" + tmp = "/this/does/not/exist" DEFAULT = Base.append_default_depot_path!(String[]) cases = Dict{Any,Vector{String}}( nothing => DEFAULT, @@ -713,32 +728,45 @@ end "$s$tmp" => [DEFAULT; tmp], ) for (env, result) in pairs(cases) - withenv("JULIA_DEPOT_PATH" => env) do - script = "DEPOT_PATH == $(repr(result)) || error()" - @test success(`$(Base.julia_cmd()) --startup-file=no -e $script`) - end + script = "DEPOT_PATH == $(repr(result)) || error()" + cmd = `$(Base.julia_cmd()) --startup-file=no -e $script` + cmd = addenv(cmd, "JULIA_DEPOT_PATH" => env) + cmd = pipeline(cmd; stdout, stderr) + @test success(cmd) end end +@testset "Issue #25719" begin + empty!(LOAD_PATH) + @test Base.root_module(Core, :Core) == Core + push!(LOAD_PATH, "@stdlib") + @test Base.root_module(Base, :Test) == Test + @test_throws KeyError(:SomeNonExistentPackage) Base.root_module(Base, :SomeNonExistentPackage) +end + ## cleanup after tests ## for env in keys(envs) rm(env, force=true, recursive=true) end for depot in depots - rm(depot, force=true, recursive=true) + try + rm(depot, force=true, recursive=true) + catch err + @show err + end end append!(empty!(LOAD_PATH), saved_load_path) append!(empty!(DEPOT_PATH), saved_depot_path) -for _ = 1:2 pop!(Base.active_project_callbacks) end +pop!(Base.active_project_callbacks) Base.set_active_project(saved_active_project) @test watcher_counter[] == 3 # issue #28190 -module Foo; import Libdl; end -import .Foo.Libdl; import Libdl -@test Foo.Libdl === Libdl +module Foo28190; import Libdl; end +import .Foo28190.Libdl; import Libdl +@test Foo28190.Libdl === Libdl @testset "include with mapexpr" begin let exprs = Any[] @@ -801,8 +829,10 @@ end try push!(LOAD_PATH, tmp) write(joinpath(tmp, "BadCase.jl"), "module badcase end") - @test_throws ErrorException("package `BadCase` did not define the expected module `BadCase`, \ - check for typos in package module name") (@eval using BadCase) + @test_logs (:warn, r"The call to compilecache failed.*") match_mode=:any begin + @test_throws ErrorException("package `BadCase` did not define the expected module `BadCase`, \ + check for typos in package module name") (@eval using BadCase) + end finally copy!(LOAD_PATH, old_loadpath) end @@ -910,3 +940,244 @@ end end end end + + +@testset "Loading with incomplete manifest/depot #45977" begin + mktempdir() do tmp + # Set up a stacked env. + cp(joinpath(@__DIR__, "depot"), joinpath(tmp, "depot")) + + mkdir(joinpath(tmp, "Env1")) + mkdir(joinpath(tmp, "Global")) + + for env in ["Env1", "Global"] + write(joinpath(tmp, env, "Project.toml"), """ + [deps] + Baz = "6801f525-dc68-44e8-a4e8-cabd286279e7" + """) + end + + write(joinpath(tmp, "Global", "Manifest.toml"), """ + [[Baz]] + uuid = "6801f525-dc68-44e8-a4e8-cabd286279e7" + git-tree-sha1 = "efc7e24c53d6a328011975294a2c75fed2f9800a" + """) + + # This SHA does not exist in the depot. + write(joinpath(tmp, "Env1", "Manifest.toml"), """ + [[Baz]] + uuid = "6801f525-dc68-44e8-a4e8-cabd286279e7" + git-tree-sha1 = "5f2f6e72d001b014b48b26ec462f3714c342e167" + """) + + + old_load_path = copy(LOAD_PATH) + old_depot_path = copy(DEPOT_PATH) + try + empty!(LOAD_PATH) + push!(empty!(DEPOT_PATH), joinpath(tmp, "depot")) + + push!(LOAD_PATH, joinpath(tmp, "Global")) + + pkg = Base.identify_package("Baz") + # Package in manifest in current env not present in depot + @test Base.locate_package(pkg) !== nothing + + @test Base.find_package("Baz") !== nothing # coverage + + pushfirst!(LOAD_PATH, joinpath(tmp, "Env1")) + + @test Base.locate_package(pkg) === nothing + + write(joinpath(tmp, "Env1", "Manifest.toml"), """ + """) + # Package in current env not present in manifest + pkg, env = Base.identify_package_env("Baz") + @test Base.locate_package(pkg, env) === nothing + finally + copy!(LOAD_PATH, old_load_path) + copy!(DEPOT_PATH, old_depot_path) + end + end +end + +@testset "Extensions" begin + depot_path = mktempdir() + try + proj = joinpath(@__DIR__, "project", "Extensions", "HasDepWithExtensions.jl") + + function gen_extension_cmd(compile, distr=false) + load_distr = distr ? "using Distributed; addprocs(1)" : "" + ew = distr ? "@everywhere" : "" + cmd = """ + $load_distr + begin + $ew push!(empty!(DEPOT_PATH), $(repr(depot_path))) + using HasExtensions + $ew using HasExtensions + $ew Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly got an extension") + $ew HasExtensions.ext_loaded && error("ext_loaded set") + using HasDepWithExtensions + $ew using HasDepWithExtensions + $ew Base.get_extension(HasExtensions, :Extension).extvar == 1 || error("extvar in Extension not set") + $ew HasExtensions.ext_loaded || error("ext_loaded not set") + $ew HasExtensions.ext_folder_loaded && error("ext_folder_loaded set") + $ew HasDepWithExtensions.do_something() || error("do_something errored") + using ExtDep2 + $ew using ExtDep2 + $ew HasExtensions.ext_folder_loaded || error("ext_folder_loaded not set") + end + """ + return `$(Base.julia_cmd()) $compile --startup-file=no -e $cmd` + end + + for compile in (`--compiled-modules=no`, ``, ``) # Once when requiring precompilation, once where it is already precompiled + cmd = gen_extension_cmd(compile) + cmd = addenv(cmd, "JULIA_LOAD_PATH" => proj) + cmd = pipeline(cmd; stdout, stderr) + @test success(cmd) + end + + sep = Sys.iswindows() ? ';' : ':' + + cmd = gen_extension_cmd(``, true) + cmd = addenv(cmd, "JULIA_LOAD_PATH" => join([proj, "@stdlib"], sep)) + str = read(cmd, String) + @test !occursin("Error during loading of extension", str) + @test !occursin("ConcurrencyViolationError", str) + + # 48351 + cmd = gen_extension_cmd(``) + cmd = addenv(cmd, "JULIA_LOAD_PATH" => join([mktempdir(), proj], sep)) + cmd = pipeline(cmd; stdout, stderr) + @test success(cmd) + + # Only load env from where package is loaded + envs = [joinpath(@__DIR__, "project", "Extensions", "EnvWithHasExtensionsv2"), joinpath(@__DIR__, "project", "Extensions", "EnvWithHasExtensions")] + cmd = addenv(```$(Base.julia_cmd()) --startup-file=no -e ' + begin + push!(empty!(DEPOT_PATH), '$(repr(depot_path))') + using HasExtensions + using ExtDep + Base.get_extension(HasExtensions, :Extension) === nothing || error("unexpectedly loaded ext from other env") + Base.get_extension(HasExtensions, :Extension2) === nothing && error("did not load ext from active env") + end + ' + ```, "JULIA_LOAD_PATH" => join(envs, sep)) + @test success(cmd) + + test_ext_proj = """ + begin + using HasExtensions + using ExtDep + 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") + end + """ + for compile in (`--compiled-modules=no`, ``) + cmd_proj_ext = `$(Base.julia_cmd()) $compile --startup-file=no -e $test_ext_proj` + proj = joinpath(@__DIR__, "project", "Extensions") + cmd_proj_ext = addenv(cmd_proj_ext, "JULIA_LOAD_PATH" => join([joinpath(proj, "HasExtensions.jl"), joinpath(proj, "EnvWithDeps")], sep)) + run(cmd_proj_ext) + end + finally + try + rm(depot_path, force=true, recursive=true) + catch err + @show err + end + end +end + +pkgimage(val) = val == 1 ? `--pkgimage=yes` : `--pkgimage=no` +opt_level(val) = `-O$val` +debug_level(val) = `-g$val` +inline(val) = val == 1 ? `--inline=yes` : `--inline=no` +check_bounds(val) = if val == 0 + `--check-bounds=auto` +elseif val == 1 + `--check-bounds=yes` +elseif val == 2 + `--check-bounds=no` +end + +@testset "CacheFlags" begin + cf = Base.CacheFlags() + opts = Base.JLOptions() + @test cf.use_pkgimages == opts.use_pkgimages + @test cf.debug_level == opts.debug_level + @test cf.check_bounds == opts.check_bounds + @test cf.inline == opts.can_inline + @test cf.opt_level == opts.opt_level + + # OOICCDDP + for (P, D, C, I, O) in Iterators.product(0:1, 0:2, 0:2, 0:1, 0:3) + julia = joinpath(Sys.BINDIR, Base.julia_exename()) + script = """ + let + cf = Base.CacheFlags() + opts = Base.JLOptions() + cf.use_pkgimages == opts.use_pkgimages == $P || error("use_pkgimages") + cf.debug_level == opts.debug_level == $D || error("debug_level") + cf.check_bounds == opts.check_bounds == $C || error("check_bounds") + cf.inline == opts.can_inline == $I || error("inline") + cf.opt_level == opts.opt_level == $O || error("opt_level") + end + """ + cmd = `$julia $(pkgimage(P)) $(opt_level(O)) $(debug_level(D)) $(check_bounds(C)) $(inline(I)) -e $script` + @test success(pipeline(cmd; stdout, stderr)) + end + + cf = Base.CacheFlags(255) + @test cf.use_pkgimages + @test cf.debug_level == 3 + @test cf.check_bounds == 3 + @test cf.inline + @test cf.opt_level == 3 + + io = PipeBuffer() + show(io, cf) + @test read(io, String) == "use_pkgimages = true, debug_level = 3, check_bounds = 3, inline = true, opt_level = 3" +end + +empty!(Base.DEPOT_PATH) +append!(Base.DEPOT_PATH, original_depot_path) + +@testset "loading deadlock detector" begin + pkid1 = Base.PkgId("pkgid1") + pkid2 = Base.PkgId("pkgid2") + pkid3 = Base.PkgId("pkgid3") + pkid4 = Base.PkgId("pkgid4") + e = Base.Event() + @test nothing === @lock Base.require_lock Base.start_loading(pkid4) # module pkgid4 + @test nothing === @lock Base.require_lock Base.start_loading(pkid1) # module pkgid1 + t1 = @async begin + @test nothing === @lock Base.require_lock Base.start_loading(pkid2) # @async module pkgid2; using pkgid1; end + notify(e) + @test "loaded_pkgid1" == @lock Base.require_lock Base.start_loading(pkid1) + @lock Base.require_lock Base.end_loading(pkid2, "loaded_pkgid2") + end + wait(e) + reset(e) + t2 = @async begin + @test nothing === @lock Base.require_lock Base.start_loading(pkid3) # @async module pkgid3; using pkgid2; end + notify(e) + @test "loaded_pkgid2" == @lock Base.require_lock Base.start_loading(pkid2) + @lock Base.require_lock Base.end_loading(pkid3, "loaded_pkgid3") + end + wait(e) + reset(e) + @test_throws(ConcurrencyViolationError("deadlock detected in loading pkgid3 -> pkgid2 -> pkgid1 -> pkgid3 && pkgid4"), + @lock Base.require_lock Base.start_loading(pkid3)).value # try using pkgid3 + @test_throws(ConcurrencyViolationError("deadlock detected in loading pkgid4 -> pkgid4 && pkgid1"), + @lock Base.require_lock Base.start_loading(pkid4)).value # try using pkgid4 + @lock Base.require_lock Base.end_loading(pkid1, "loaded_pkgid1") # end + @lock Base.require_lock Base.end_loading(pkid4, "loaded_pkgid4") # end + wait(t2) + wait(t1) +end + +@testset "Upgradable stdlibs" begin + @test success(`$(Base.julia_cmd()) --startup-file=no -e 'using DelimitedFiles'`) +end diff --git a/test/math.jl b/test/math.jl index af280066f2f22..19d9f7893a496 100644 --- a/test/math.jl +++ b/test/math.jl @@ -8,6 +8,20 @@ function isnan_type(::Type{T}, x) where T isa(x, T) && isnan(x) end +# has_fma has no runtime support. +# So we need function wrappers to make this work. +has_fma_Int() = Core.Compiler.have_fma(Int) +has_fma_Float32() = Core.Compiler.have_fma(Float32) +has_fma_Float64() = Core.Compiler.have_fma(Float64) + +has_fma = Dict( + Int => has_fma_Int(), + Rational{Int} => has_fma_Int(), + Float32 => has_fma_Float32(), + Float64 => has_fma_Float64(), + BigFloat => true, +) + @testset "clamp" begin @test clamp(0, 1, 3) == 1 @test clamp(1, 1, 3) == 1 @@ -55,8 +69,9 @@ end @test repr(Any[pi ℯ; ℯ pi]) == "Any[π ℯ; ℯ π]" @test string(pi) == "π" - @test sin(π) === sinpi(1) == tan(π) == sinpi(1 // 1) == 0 - @test cos(π) === cospi(1) == sec(π) == cospi(1 // 1) == -1 + @test sin(π) == sind(180) === sinpi(1) === sinpi(1//1) == tan(π) == 0 + @test tan(π) == tand(180) === tanpi(1) === tanpi(1//1) === -0.0 + @test cos(π) == cosd(180) === cospi(1) === cospi(1//1) == sec(π) == -1 @test csc(π) == 1/0 && cot(π) == -1/0 @test sincos(π) === sincospi(1) == (0, -1) end @@ -155,8 +170,6 @@ end @test x^y === T(big(x)^big(y)) @test x^1 === x @test x^yi === T(big(x)^yi) - # test for large negative exponent where error compensation matters - @test 0.9999999955206014^-1.0e8 == 1.565084574870928 @test (-x)^yi == x^yi @test (-x)^(yi+1) == -(x^(yi+1)) @test acos(x) ≈ acos(big(x)) @@ -167,8 +180,10 @@ end @test atan(x,y) ≈ atan(big(x),big(y)) @test atanh(x) ≈ atanh(big(x)) @test cbrt(x) ≈ cbrt(big(x)) + @test fourthroot(x) ≈ fourthroot(big(x)) @test cos(x) ≈ cos(big(x)) @test cosh(x) ≈ cosh(big(x)) + @test cospi(x) ≈ cospi(big(x)) @test exp(x) ≈ exp(big(x)) @test exp10(x) ≈ exp10(big(x)) @test exp2(x) ≈ exp2(big(x)) @@ -182,9 +197,11 @@ end @test log2(x) ≈ log2(big(x)) @test sin(x) ≈ sin(big(x)) @test sinh(x) ≈ sinh(big(x)) + @test sinpi(x) ≈ sinpi(big(x)) @test sqrt(x) ≈ sqrt(big(x)) @test tan(x) ≈ tan(big(x)) @test tanh(x) ≈ tanh(big(x)) + @test tanpi(x) ≈ tanpi(big(x)) @test sec(x) ≈ sec(big(x)) @test csc(x) ≈ csc(big(x)) @test secd(x) ≈ secd(big(x)) @@ -203,6 +220,9 @@ end @test isequal(cbrt(T(0)), T(0)) @test isequal(cbrt(T(1)), T(1)) @test isequal(cbrt(T(1000000000))^3, T(1000)^3) + @test isequal(fourthroot(T(0)), T(0)) + @test isequal(fourthroot(T(1)), T(1)) + @test isequal(fourthroot(T(100000000))^4, T(100)^4) @test isequal(cos(T(0)), T(1)) @test cos(T(pi)/2) ≈ T(0) atol=eps(T) @test isequal(cos(T(pi)), T(-1)) @@ -255,6 +275,8 @@ end @test asin(sin(x)) ≈ x @test cbrt(x)^3 ≈ x @test cbrt(x^3) ≈ x + @test fourthroot(x)^4 ≈ x + @test fourthroot(x^4) ≈ x @test asinh(sinh(x)) ≈ x @test atan(tan(x)) ≈ x @test atan(x,y) ≈ atan(x/y) @@ -413,47 +435,51 @@ end @test rad2deg(pi + (pi/3)*im) ≈ 180 + 60im end +# ensure zeros are signed the same +⩲(x,y) = typeof(x) == typeof(y) && x == y && signbit(x) == signbit(y) +⩲(x::Tuple, y::Tuple) = length(x) == length(y) && all(map(⩲,x,y)) + @testset "degree-based trig functions" begin - @testset "$T" for T = (Float32,Float64,Rational{Int}) + @testset "$T" for T = (Float32,Float64,Rational{Int},BigFloat) fT = typeof(float(one(T))) fTsc = typeof( (float(one(T)), float(one(T))) ) for x = -400:40:400 - @test sind(convert(T,x))::fT ≈ convert(fT,sin(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) - @test cosd(convert(T,x))::fT ≈ convert(fT,cos(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) + @test sind(convert(T,x))::fT ≈ sin(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) + @test cosd(convert(T,x))::fT ≈ cos(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) s,c = sincosd(convert(T,x)) - @test s::fT ≈ convert(fT,sin(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) - @test c::fT ≈ convert(fT,cos(pi/180*x)) atol=eps(deg2rad(convert(fT,x))) + @test s::fT ≈ sin(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) + @test c::fT ≈ cos(pi*convert(fT,x)/180) atol=eps(deg2rad(convert(fT,x))) end @testset "sind" begin - @test sind(convert(T,0.0))::fT === zero(fT) - @test sind(convert(T,180.0))::fT === zero(fT) - @test sind(convert(T,360.0))::fT === zero(fT) - T != Rational{Int} && @test sind(convert(T,-0.0))::fT === -zero(fT) - @test sind(convert(T,-180.0))::fT === -zero(fT) - @test sind(convert(T,-360.0))::fT === -zero(fT) + @test sind(convert(T,0.0))::fT ⩲ zero(fT) + @test sind(convert(T,180.0))::fT ⩲ zero(fT) + @test sind(convert(T,360.0))::fT ⩲ zero(fT) + T != Rational{Int} && @test sind(convert(T,-0.0))::fT ⩲ -zero(fT) + @test sind(convert(T,-180.0))::fT ⩲ -zero(fT) + @test sind(convert(T,-360.0))::fT ⩲ -zero(fT) if T <: AbstractFloat @test isnan(sind(T(NaN))) end end @testset "cosd" begin - @test cosd(convert(T,90))::fT === zero(fT) - @test cosd(convert(T,270))::fT === zero(fT) - @test cosd(convert(T,-90))::fT === zero(fT) - @test cosd(convert(T,-270))::fT === zero(fT) + @test cosd(convert(T,90))::fT ⩲ zero(fT) + @test cosd(convert(T,270))::fT ⩲ zero(fT) + @test cosd(convert(T,-90))::fT ⩲ zero(fT) + @test cosd(convert(T,-270))::fT ⩲ zero(fT) if T <: AbstractFloat @test isnan(cosd(T(NaN))) end end @testset "sincosd" begin - @test sincosd(convert(T,-360))::fTsc === ( -zero(fT), one(fT) ) - @test sincosd(convert(T,-270))::fTsc === ( one(fT), zero(fT) ) - @test sincosd(convert(T,-180))::fTsc === ( -zero(fT), -one(fT) ) - @test sincosd(convert(T, -90))::fTsc === ( -one(fT), zero(fT) ) - @test sincosd(convert(T, 0))::fTsc === ( zero(fT), one(fT) ) - @test sincosd(convert(T, 90))::fTsc === ( one(fT), zero(fT) ) - @test sincosd(convert(T, 180))::fTsc === ( zero(fT), -one(fT) ) - @test sincosd(convert(T, 270))::fTsc === ( -one(fT), zero(fT) ) + @test sincosd(convert(T,-360))::fTsc ⩲ ( -zero(fT), one(fT) ) + @test sincosd(convert(T,-270))::fTsc ⩲ ( one(fT), zero(fT) ) + @test sincosd(convert(T,-180))::fTsc ⩲ ( -zero(fT), -one(fT) ) + @test sincosd(convert(T, -90))::fTsc ⩲ ( -one(fT), zero(fT) ) + @test sincosd(convert(T, 0))::fTsc ⩲ ( zero(fT), one(fT) ) + @test sincosd(convert(T, 90))::fTsc ⩲ ( one(fT), zero(fT) ) + @test sincosd(convert(T, 180))::fTsc ⩲ ( zero(fT), -one(fT) ) + @test sincosd(convert(T, 270))::fTsc ⩲ ( -one(fT), zero(fT) ) if T <: AbstractFloat @test_throws DomainError sincosd(T(Inf)) @test all(isnan.(sincosd(T(NaN)))) @@ -465,36 +491,63 @@ end "sincospi" => (x->sincospi(x)[1], x->sincospi(x)[2]) ) @testset "pi * $x" for x = -3:0.3:3 - @test sinpi(convert(T,x))::fT ≈ convert(fT,sin(pi*x)) atol=eps(pi*convert(fT,x)) - @test cospi(convert(T,x))::fT ≈ convert(fT,cos(pi*x)) atol=eps(pi*convert(fT,x)) + @test sinpi(convert(T,x))::fT ≈ sin(pi*convert(fT,x)) atol=eps(pi*convert(fT,x)) + @test cospi(convert(T,x))::fT ≈ cos(pi*convert(fT,x)) atol=eps(pi*convert(fT,x)) end - @test sinpi(convert(T,0.0))::fT === zero(fT) - @test sinpi(convert(T,1.0))::fT === zero(fT) - @test sinpi(convert(T,2.0))::fT === zero(fT) - T != Rational{Int} && @test sinpi(convert(T,-0.0))::fT === -zero(fT) - @test sinpi(convert(T,-1.0))::fT === -zero(fT) - @test sinpi(convert(T,-2.0))::fT === -zero(fT) + @test sinpi(convert(T,0.0))::fT ⩲ zero(fT) + @test sinpi(convert(T,1.0))::fT ⩲ zero(fT) + @test sinpi(convert(T,2.0))::fT ⩲ zero(fT) + T != Rational{Int} && @test sinpi(convert(T,-0.0))::fT ⩲ -zero(fT) + @test sinpi(convert(T,-1.0))::fT ⩲ -zero(fT) + @test sinpi(convert(T,-2.0))::fT ⩲ -zero(fT) @test_throws DomainError sinpi(convert(T,Inf)) - @test cospi(convert(T,0.5))::fT === zero(fT) - @test cospi(convert(T,1.5))::fT === zero(fT) - @test cospi(convert(T,-0.5))::fT === zero(fT) - @test cospi(convert(T,-1.5))::fT === zero(fT) + @test cospi(convert(T,0.5))::fT ⩲ zero(fT) + @test cospi(convert(T,1.5))::fT ⩲ zero(fT) + @test cospi(convert(T,-0.5))::fT ⩲ zero(fT) + @test cospi(convert(T,-1.5))::fT ⩲ zero(fT) @test_throws DomainError cospi(convert(T,Inf)) end - @testset "Check exact values" begin - @test sind(convert(T,30)) == 0.5 - @test cosd(convert(T,60)) == 0.5 - @test sind(convert(T,150)) == 0.5 - @test sinpi(one(T)/convert(T,6)) == 0.5 - @test sincospi(one(T)/convert(T,6))[1] == 0.5 - @test_throws DomainError sind(convert(T,Inf)) - @test_throws DomainError cosd(convert(T,Inf)) - T != Float32 && @test cospi(one(T)/convert(T,3)) == 0.5 - T != Float32 && @test sincospi(one(T)/convert(T,3))[2] == 0.5 - T == Rational{Int} && @test sinpi(5//6) == 0.5 - T == Rational{Int} && @test sincospi(5//6)[1] == 0.5 + @testset "trig pi functions accuracy" for numerator in -20:1:20 + for func in (sinpi, cospi, tanpi, + x -> sincospi(x)[1], + x -> sincospi(x)[2]) + x = numerator // 20 + # Check that rational function works + @test func(x) ≈ func(BigFloat(x)) + # Use short value so that wider values will be exactly equal + shortx = Float16(x) + # Compare to BigFloat value + bigvalue = func(BigFloat(shortx)) + for T in (Float16,Float32,Float64) + @test func(T(shortx)) ≈ T(bigvalue) + end + end + end + @testset begin + # If the machine supports fma (fused multiply add), we require exact equality. + # Otherwise, we only require approximate equality. + if has_fma[T] + my_eq = (==) + @debug "On this machine, FMA is supported for $(T), so we will test for exact equality" my_eq + else + my_eq = isapprox + @debug "On this machine, FMA is not supported for $(T), so we will test for approximate equality" my_eq + end + @testset let context=(T, has_fma[T], my_eq) + @test sind(convert(T,30)) == 0.5 + @test cosd(convert(T,60)) == 0.5 + @test sind(convert(T,150)) == 0.5 + @test my_eq(sinpi(one(T)/convert(T,6)), 0.5) + @test my_eq(sincospi(one(T)/convert(T,6))[1], 0.5) + @test_throws DomainError sind(convert(T,Inf)) + @test_throws DomainError cosd(convert(T,Inf)) + fT == Float64 && @test my_eq(cospi(one(T)/convert(T,3)), 0.5) + fT == Float64 && @test my_eq(sincospi(one(T)/convert(T,3))[2], 0.5) + T == Rational{Int} && @test my_eq(sinpi(5//6), 0.5) + T == Rational{Int} && @test my_eq(sincospi(5//6)[1], 0.5) + end end end scdm = sincosd(missing) @@ -502,14 +555,18 @@ end @test ismissing(scdm[2]) end -@testset "Integer and Inf args for sinpi/cospi/sinc/cosc" begin +@testset "Integer and Inf args for sinpi/cospi/tanpi/sinc/cosc" begin for (sinpi, cospi) in ((sinpi, cospi), (x->sincospi(x)[1], x->sincospi(x)[2])) - @test sinpi(1) == 0 - @test sinpi(-1) == -0 + @test sinpi(1) === 0.0 + @test sinpi(-1) === -0.0 @test cospi(1) == -1 @test cospi(2) == 1 end + @test tanpi(1) === -0.0 + @test tanpi(-1) === 0.0 + @test tanpi(2) === 0.0 + @test tanpi(-2) === -0.0 @test sinc(1) == 0 @test sinc(complex(1,0)) == 0 @test sinc(0) == 1 @@ -540,15 +597,19 @@ end end end end - @test @inferred(sinc(0//1)) === 1.0 - @test @inferred(cosc(0//1)) === -0.0 + @test @inferred(sinc(0//1)) ⩲ 1.0 + @test @inferred(cosc(0//1)) ⩲ -0.0 # test right before/after thresholds of Taylor series @test sinc(0.001) ≈ 0.999998355066745 rtol=1e-15 @test sinc(0.00099) ≈ 0.9999983878009009 rtol=1e-15 @test sinc(0.05f0) ≈ 0.9958927352435614 rtol=1e-7 @test sinc(0.0499f0) ≈ 0.9959091277049384 rtol=1e-7 - @test cosc(0.14) ≈ -0.4517331883801308 rtol=1e-15 + if has_fma[Float64] + @test cosc(0.14) ≈ -0.4517331883801308 rtol=1e-15 + else + @test cosc(0.14) ≈ -0.4517331883801308 rtol=1e-14 + end @test cosc(0.1399) ≈ -0.45142306168781854 rtol=1e-14 @test cosc(0.26f0) ≈ -0.7996401373462212 rtol=5e-7 @test cosc(0.2599f0) ≈ -0.7993744054401625 rtol=5e-7 @@ -558,7 +619,7 @@ end end end -@testset "Irrational args to sinpi/cospi/sinc/cosc" begin +@testset "Irrational args to sinpi/cospi/tanpi/sinc/cosc" begin for x in (pi, ℯ, Base.MathConstants.golden) for (sinpi, cospi) in ((sinpi, cospi), (x->sincospi(x)[1], x->sincospi(x)[2])) @test sinpi(x) ≈ Float64(sinpi(big(x))) @@ -566,6 +627,7 @@ end @test sinpi(complex(x, x)) ≈ ComplexF64(sinpi(complex(big(x), big(x)))) @test cospi(complex(x, x)) ≈ ComplexF64(cospi(complex(big(x), big(x)))) end + @test tanpi(x) ≈ Float64(tanpi(big(x))) @test sinc(x) ≈ Float64(sinc(big(x))) @test cosc(x) ≈ Float64(cosc(big(x))) @test sinc(complex(x, x)) ≈ ComplexF64(sinc(complex(big(x), big(x)))) @@ -595,7 +657,7 @@ end end @testset "trig function type stability" begin - @testset "$T $f" for T = (Float32,Float64,BigFloat,Rational{Int16},Complex{Int32},ComplexF16), f = (sind,cosd,sinpi,cospi) + @testset "$T $f" for T = (Float32,Float64,BigFloat,Rational{Int16},Complex{Int32},ComplexF16), f = (sind,cosd,sinpi,cospi,tanpi) @test Base.return_types(f,Tuple{T}) == [float(T)] end @testset "$T sincospi" for T = (Float32,Float64,BigFloat,Rational{Int16},Complex{Int32},ComplexF16) @@ -1199,6 +1261,22 @@ end end end +@testset "fourthroot" begin + for T in (Float32, Float64) + @test fourthroot(zero(T)) === zero(T) + @test fourthroot(one(T)) === one(T) + @test fourthroot(T(Inf)) === T(Inf) + @test isnan_type(T, fourthroot(T(NaN))) + for x in (pcnfloat(nextfloat(nextfloat(zero(T))))..., + 0.45, 0.6, 0.98, + map(x->x^3, 1.0:1.0:1024.0)..., + prevfloat(T(Inf))) + by = fourthroot(big(T(x))) + @test fourthroot(T(x)) ≈ by rtol=eps(T) + end + end +end + @testset "hypot" begin @test hypot(0, 0) == 0.0 @test hypot(3, 4) == 5.0 @@ -1282,7 +1360,7 @@ struct BadFloatWrapper <: AbstractFloat x::Float64 end -@testset "not impelemented errors" begin +@testset "not implemented errors" begin x = BadFloatWrapper(1.9) for f in (sin, cos, tan, sinh, cosh, tanh, atan, acos, asin, asinh, acosh, atanh, exp, log1p, expm1, log) #exp2, exp10 broken for now @test_throws MethodError f(x) @@ -1319,6 +1397,59 @@ end end end +@testset "pow" begin + # tolerance by type for regular powers + POW_TOLS = Dict(Float16=>[.51, .51, .51, 2.0, 1.5], + Float32=>[.51, .51, .51, 2.0, 1.5], + Float64=>[.55, 0.8, 1.5, 2.0, 1.5]) + for T in (Float16, Float32, Float64) + for x in (0.0, -0.0, 1.0, 10.0, 2.0, Inf, NaN, -Inf, -NaN) + for y in (0.0, -0.0, 1.0, -3.0,-10.0 , Inf, NaN, -Inf, -NaN) + got, expected = T(x)^T(y), T(big(x)^T(y)) + if isnan(expected) + @test isnan_type(T, got) || T.((x,y)) + else + @test got == expected || T.((x,y)) + end + end + end + for _ in 1:2^16 + # note x won't be subnormal here + x=rand(T)*100; y=rand(T)*200-100 + got, expected = x^y, widen(x)^y + if isfinite(eps(T(expected))) + if y == T(-2) # unfortunately x^-2 is less accurate for performance reasons. + @test abs(expected-got) <= POW_TOLS[T][4]*eps(T(expected)) || (x,y) + elseif y == T(3) # unfortunately x^3 is less accurate for performance reasons. + @test abs(expected-got) <= POW_TOLS[T][5]*eps(T(expected)) || (x,y) + elseif issubnormal(got) + @test abs(expected-got) <= POW_TOLS[T][2]*eps(T(expected)) || (x,y) + else + @test abs(expected-got) <= POW_TOLS[T][1]*eps(T(expected)) || (x,y) + end + end + end + for _ in 1:2^14 + # test subnormal(x), y in -1.2, 1.8 since anything larger just overflows. + x=rand(T)*floatmin(T); y=rand(T)*3-T(1.2) + got, expected = x^y, widen(x)^y + if isfinite(eps(T(expected))) + @test abs(expected-got) <= POW_TOLS[T][3]*eps(T(expected)) || (x,y) + end + end + # test (-x)^y for y larger than typemax(Int) + @test T(-1)^floatmax(T) === T(1) + @test prevfloat(T(-1))^floatmax(T) === T(Inf) + @test nextfloat(T(-1))^floatmax(T) === T(0.0) + end + # test for large negative exponent where error compensation matters + @test 0.9999999955206014^-1.0e8 == 1.565084574870928 + @test 3e18^20 == Inf + # 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 +end + # Test that sqrt behaves correctly and doesn't exhibit fp80 double rounding. # This happened on old glibc versions. # Test case from https://sourceware.org/bugzilla/show_bug.cgi?id=14032. @@ -1399,3 +1530,31 @@ end # the compiler ever gets good enough to figure # that out by itself, move this to inference). @test code_typed(x->Val{x^0.0}(), Tuple{Float64})[1][2] == Val{1.0} + +function f44336() + as = ntuple(_ -> rand(), Val(32)) + @inline hypot(as...) +end +@testset "Issue #44336" begin + f44336() + @test (@allocated f44336()) == 0 +end + +# test constant-foldability +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) + f = getfield(@__MODULE__, fn) + eff = Base.infer_effects(f, (T,)) + @test Core.Compiler.is_foldable(eff) + end +end +for T in (Float16, Float32, Float64) + for f in (exp, exp2, exp10) + @test Core.Compiler.is_removable_if_unused(Base.infer_effects(f, (T,))) + end + @test Core.Compiler.is_foldable(Base.infer_effects(^, (T,Int))) + @test Core.Compiler.is_foldable(Base.infer_effects(^, (T,T))) +end diff --git a/test/meta.jl b/test/meta.jl index 5bdb988f41b6d..399e106684a81 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -144,8 +144,8 @@ baremodule B x = 1 module M; x = 2; end import Base - @Base.eval x = 3 - @Base.eval M x = 4 + Base.@eval x = 3 + Base.@eval M x = 4 end @test B.x == 3 @test B.M.x == 4 @@ -221,8 +221,8 @@ let a = 1 @test @macroexpand @is_dollar_expr $a end -@test Meta.parseatom("@foo", 1, filename=:bar)[1].args[2].file == :bar -@test Meta.parseall("@foo", filename=:bar).args[1].file == :bar +@test Meta.parseatom("@foo", 1, filename=:bar)[1].args[2].file === :bar +@test Meta.parseall("@foo", filename=:bar).args[1].file === :bar _lower(m::Module, ex, world::UInt) = ccall(:jl_expand_in_world, Any, (Any, Ref{Module}, Cstring, Cint, Csize_t), ex, m, "none", 0, world) diff --git a/test/misc.jl b/test/misc.jl index 9a92d0fda0076..79b684badf1e0 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -149,7 +149,7 @@ for l in (Threads.SpinLock(), ReentrantLock()) @test get_finalizers_inhibited() == 1 GC.enable_finalizers(true) @test get_finalizers_inhibited() == 0 - if ccall(:jl_is_debugbuild, Cint, ()) != 0 + if Base.isdebugbuild() # Note this warning only exists in debug builds @test_warn "WARNING: GC finalizers already enabled on this thread." GC.enable_finalizers(true) end @@ -191,7 +191,7 @@ end sleep(rand(0:0.01:0.1)) history[Threads.atomic_add!(clock, 1)] = Threads.atomic_sub!(occupied, 1) - 1 return :resultvalue - end == :resultvalue + end === :resultvalue end end @test all(<=(sem_size), history) @@ -237,14 +237,16 @@ end # test that @sync is lexical (PR #27164) const x27164 = Ref(0) -do_something_async_27164() = @async(begin sleep(1); x27164[] = 2; end) +const c27164 = Base.Event() +do_something_async_27164() = @async(begin wait(c27164); x27164[] = 2; end) let t = nothing @sync begin + @async (sleep(0.1); x27164[] = 1) t = do_something_async_27164() - @async (sleep(0.05); x27164[] = 1) end @test x27164[] == 1 + notify(c27164) fetch(t) @test x27164[] == 2 end @@ -283,6 +285,7 @@ v11801, t11801 = @timed sin(1) @test names(@__MODULE__, all = true) == names_before_timing +redirect_stdout(devnull) do # suppress time prints # Accepted @time argument formats @test @time true @test @time "message" true @@ -333,21 +336,160 @@ function timev_macro_scope() end @test timev_macro_scope() == 1 -before = Base.cumulative_compile_time_ns_before(); +before_comp, before_recomp = Base.cumulative_compile_time_ns() # no need to turn timing on, @time will do that # exercise concurrent calls to `@time` for reentrant compilation time measurement. -t1 = @async @time begin - sleep(2) - @eval module M ; f(x,y) = x+y ; end - @eval M.f(2,3) +@sync begin + t1 = @async @time begin + sleep(2) + @eval module M ; f(x,y) = x+y ; end + @eval M.f(2,3) + end + t2 = @async begin + sleep(1) + @time 2 + 2 + end end -t2 = @async begin - sleep(1) - @time 2 + 2 + +after_comp, after_recomp = Base.cumulative_compile_time_ns() # no need to turn timing off, @time will do that +@test after_comp >= before_comp; +@test after_recomp >= before_recomp; +@test after_recomp - before_recomp <= after_comp - before_comp; + +# should be approximately 60,000,000 ns, we definitely shouldn't exceed 100x that value +# failing this probably means an uninitialized variable somewhere +@test after_comp - before_comp < 6_000_000_000; + +end # redirect_stdout + +# issue #48024, avoid overcounting timers +begin + double(x::Real) = 2x; + calldouble(container) = double(container[1]); + calldouble2(container) = calldouble(container); + + Base.Experimental.@force_compile; + local elapsed = Base.time_ns(); + Base.cumulative_compile_timing(true); + local compiles = Base.cumulative_compile_time_ns(); + @eval calldouble([1.0]); + Base.cumulative_compile_timing(false); + compiles = Base.cumulative_compile_time_ns() .- compiles; + elapsed = Base.time_ns() - elapsed; + + # compile time should be at most total time + @test compiles[1] <= elapsed + # recompile time should be at most compile time + @test compiles[2] <= compiles[1] + + elapsed = Base.time_ns(); + Base.cumulative_compile_timing(true); + compiles = Base.cumulative_compile_time_ns(); + @eval calldouble(1.0); + Base.cumulative_compile_timing(false); + compiles = Base.cumulative_compile_time_ns() .- compiles; + elapsed = Base.time_ns() - elapsed; + + # compile time should be at most total time + @test compiles[1] <= elapsed + # recompile time should be at most compile time + @test compiles[2] <= compiles[1] +end + +macro capture_stdout(ex) + quote + mktemp() do fname, f + redirect_stdout(f) do + $(esc(ex)) + end + seekstart(f) + read(f, String) + end + end end -after = Base.cumulative_compile_time_ns_after(); -@test after >= before; +# issue #48024, but with the time macro itself +begin + double(x::Real) = 2x; + calldouble(container) = double(container[1]); + calldouble2(container) = calldouble(container); + + local first = @capture_stdout @time @eval calldouble([1.0]) + local second = @capture_stdout @time @eval calldouble2(1.0) + + # these functions were not recompiled + local matches = collect(eachmatch(r"(\d+(?:\.\d+)?)%", first)) + @test length(matches) == 1 + @test parse(Float64, matches[1][1]) > 0.0 + @test parse(Float64, matches[1][1]) <= 100.0 + + matches = collect(eachmatch(r"(\d+(?:\.\d+)?)%", second)) + @test length(matches) == 1 + @test parse(Float64, matches[1][1]) > 0.0 + @test parse(Float64, matches[1][1]) <= 100.0 +end + +# compilation reports in @time, @timev +let f = gensym("f"), callf = gensym("callf"), call2f = gensym("call2f") + @eval begin + $f(::Real) = 1 + $callf(container) = $f(container[1]) + $call2f(container) = $callf(container) + c64 = [1.0] + c32 = [1.0f0] + cabs = AbstractFloat[1.0] + + out = @capture_stdout @time $call2f(c64) + @test occursin("% compilation time", out) + out = @capture_stdout @time $call2f(c64) + @test occursin("% compilation time", out) == false + + out = @capture_stdout @time $call2f(c32) + @test occursin("% compilation time", out) + out = @capture_stdout @time $call2f(c32) + @test occursin("% compilation time", out) == false + + out = @capture_stdout @time $call2f(cabs) + @test occursin("% compilation time", out) + out = @capture_stdout @time $call2f(cabs) + @test occursin("% compilation time", out) == false + + $f(::Float64) = 2 + out = @capture_stdout @time $call2f(c64) + @test occursin("% compilation time:", out) + @test occursin("% of which was recompilation", out) + end +end +let f = gensym("f"), callf = gensym("callf"), call2f = gensym("call2f") + @eval begin + $f(::Real) = 1 + $callf(container) = $f(container[1]) + $call2f(container) = $callf(container) + c64 = [1.0] + c32 = [1.0f0] + cabs = AbstractFloat[1.0] + + out = @capture_stdout @timev $call2f(c64) + @test occursin("% compilation time", out) + out = @capture_stdout @timev $call2f(c64) + @test occursin("% compilation time", out) == false + + out = @capture_stdout @timev $call2f(c32) + @test occursin("% compilation time", out) + out = @capture_stdout @timev $call2f(c32) + @test occursin("% compilation time", out) == false + + out = @capture_stdout @timev $call2f(cabs) + @test occursin("% compilation time", out) + out = @capture_stdout @timev $call2f(cabs) + @test occursin("% compilation time", out) == false + + $f(::Float64) = 2 + out = @capture_stdout @timev $call2f(c64) + @test occursin("% compilation time:", out) + @test occursin("% of which was recompilation", out) + end +end # interactive utilities @@ -393,15 +535,8 @@ let s = Set(1:100) @test summarysize([s]) > summarysize(s) end -# issue #13021 -let ex = try - Main.x13021 = 0 - nothing -catch ex - ex -end - @test isa(ex, ErrorException) && ex.msg == "cannot assign variables in other modules" -end +# issue #44780 +@test summarysize(BigInt(2)^1000) > summarysize(BigInt(2)) ## test conversion from UTF-8 to UTF-16 (for Windows APIs) @@ -433,10 +568,10 @@ V8 = [ ([0xe1,0x88,0xb4],[0x1234]) ([0xea,0xaf,0x8d],[0xabcd]) ([0xed,0x9f,0xbf],[0xd7ff]) - ([0xed,0xa0,0x80],[0xd800]) # invalid code point – high surrogate - ([0xed,0xaf,0xbf],[0xdbff]) # invalid code point – high surrogate - ([0xed,0xb0,0x80],[0xdc00]) # invalid code point – low surrogate - ([0xed,0xbf,0xbf],[0xdfff]) # invalid code point – low surrogate + ([0xed,0xa0,0x80],[0xd800]) # invalid code point – high surrogate + ([0xed,0xaf,0xbf],[0xdbff]) # invalid code point – high surrogate + ([0xed,0xb0,0x80],[0xdc00]) # invalid code point – low surrogate + ([0xed,0xbf,0xbf],[0xdfff]) # invalid code point – low surrogate ([0xee,0x80,0x80],[0xe000]) ([0xef,0xbf,0xbf],[0xffff]) # 4-byte @@ -586,7 +721,7 @@ end let optstring = repr("text/plain", Base.JLOptions()) @test startswith(optstring, "JLOptions(\n") - @test !occursin("Ptr", optstring) + @test !occursin("Ptr{UInt8}", optstring) @test endswith(optstring, "\n)") @test occursin(" = \"", optstring) end @@ -594,7 +729,7 @@ let optstring = repr(Base.JLOptions()) @test startswith(optstring, "JLOptions(") @test endswith(optstring, ")") @test !occursin("\n", optstring) - @test !occursin("Ptr", optstring) + @test !occursin("Ptr{UInt8}", optstring) @test occursin(" = \"", optstring) end @@ -724,6 +859,10 @@ let buf = IOBuffer() printstyled(buf_color, "foo"; bold=true, color=:red) @test String(take!(buf)) == "\e[31m\e[1mfoo\e[22m\e[39m" + # Check that italic is turned off + printstyled(buf_color, "foo"; italic=true, color=:red) + @test String(take!(buf)) == "\e[31m\e[3mfoo\e[23m\e[39m" + # Check that underline is turned off printstyled(buf_color, "foo"; color = :red, underline = true) @test String(take!(buf)) == "\e[31m\e[4mfoo\e[24m\e[39m" @@ -741,8 +880,8 @@ let buf = IOBuffer() @test String(take!(buf)) == "\e[31m\e[8mfoo\e[28m\e[39m" # Check that all options can be turned on simultaneously - printstyled(buf_color, "foo"; color = :red, bold = true, underline = true, blink = true, reverse = true, hidden = true) - @test String(take!(buf)) == "\e[31m\e[1m\e[4m\e[5m\e[7m\e[8mfoo\e[28m\e[27m\e[25m\e[24m\e[22m\e[39m" + printstyled(buf_color, "foo"; color = :red, bold = true, italic = true, underline = true, blink = true, reverse = true, hidden = true) + @test String(take!(buf)) == "\e[31m\e[1m\e[3m\e[4m\e[5m\e[7m\e[8mfoo\e[28m\e[27m\e[25m\e[24m\e[22m\e[23m\e[39m" end abstract type DA_19281{T, N} <: AbstractArray{T, N} end @@ -771,7 +910,7 @@ mutable struct Demo_20254 end # these cause stack overflows and are a little flaky on CI, ref #20256 -if Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) +if Base.get_bool_env("JULIA_TESTFULL", false) function Demo_20254(arr::AbstractArray=Any[]) Demo_20254(string.(arr)) end @@ -828,65 +967,132 @@ end module atinvokelatest f(x) = 1 g(x, y; z=0) = x * y + z +mutable struct X; x; end +Base.getproperty(::X, ::Any) = error("overload me") +Base.setproperty!(::X, ::Any, ::Any) = error("overload me") +struct Xs + xs::Vector{Any} end - -let foo() = begin - @eval atinvokelatest.f(x::Int) = 3 - return Base.@invokelatest atinvokelatest.f(0) - end - @test foo() == 3 +Base.getindex(::Xs, ::Any) = error("overload me") +Base.setindex!(::Xs, ::Any, ::Any) = error("overload me") end -let foo() = begin +let call_test() = begin @eval atinvokelatest.f(x::Int) = 3 - return Base.@invokelatest atinvokelatest.f(0) + return @invokelatest atinvokelatest.f(0) end - @test foo() == 3 + @test call_test() == 3 - bar() = begin + call_with_kws_test() = begin @eval atinvokelatest.g(x::Int, y::Int; z=3) = z - return Base.@invokelatest atinvokelatest.g(2, 3; z=1) + return @invokelatest atinvokelatest.g(2, 3; z=1) + end + @test call_with_kws_test() == 1 + + getproperty_test() = begin + @eval Base.getproperty(x::atinvokelatest.X, f::Symbol) = getfield(x, f) + x = atinvokelatest.X(nothing) + return @invokelatest x.x + end + @test isnothing(getproperty_test()) + + setproperty!_test() = begin + @eval Base.setproperty!(x::atinvokelatest.X, f::Symbol, @nospecialize(v)) = setfield!(x, f, v) + x = atinvokelatest.X(nothing) + @invokelatest x.x = 1 + return x + end + x = setproperty!_test() + @test getfield(x, :x) == 1 + + getindex_test() = begin + @eval Base.getindex(xs::atinvokelatest.Xs, idx::Int) = xs.xs[idx] + xs = atinvokelatest.Xs(Any[nothing]) + return @invokelatest xs[1] + end + @test isnothing(getindex_test()) + + setindex!_test() = begin + @eval function Base.setindex!(xs::atinvokelatest.Xs, @nospecialize(v), idx::Int) + xs.xs[idx] = v + end + xs = atinvokelatest.Xs(Any[nothing]) + @invokelatest xs[1] = 1 + return xs end - @test bar() == 1 + xs = setindex!_test() + @test xs.xs[1] == 1 end +abstract type InvokeX end +Base.getproperty(::InvokeX, ::Symbol) = error("overload InvokeX") +Base.setproperty!(::InvokeX, ::Symbol, @nospecialize(v::Any)) = error("overload InvokeX") +mutable struct InvokeX2 <: InvokeX; x; end +Base.getproperty(x::InvokeX2, f::Symbol) = getfield(x, f) +Base.setproperty!(x::InvokeX2, f::Symbol, @nospecialize(v::Any)) = setfield!(x, f, v) + +abstract type InvokeXs end +Base.getindex(::InvokeXs, ::Int) = error("overload InvokeXs") +Base.setindex!(::InvokeXs, @nospecialize(v::Any), ::Int) = error("overload InvokeXs") +struct InvokeXs2 <: InvokeXs + xs::Vector{Any} +end +Base.getindex(xs::InvokeXs2, idx::Int) = xs.xs[idx] +Base.setindex!(xs::InvokeXs2, @nospecialize(v::Any), idx::Int) = xs.xs[idx] = v + @testset "@invoke macro" begin # test against `invoke` doc example - let - f(x::Real) = x^2 - f(x::Integer) = 1 + Base.@invoke f(x::Real) + let f(x::Real) = x^2 + f(x::Integer) = 1 + @invoke f(x::Real) @test f(2) == 5 end - let - f1(::Integer) = Integer + let f1(::Integer) = Integer f1(::Real) = Real; f2(x::Real) = _f2(x) _f2(::Integer) = Integer _f2(_) = Real @test f1(1) === Integer @test f2(1) === Integer - @test Base.@invoke(f1(1::Real)) === Real - @test Base.@invoke(f2(1::Real)) === Integer + @test @invoke(f1(1::Real)) === Real + @test @invoke(f2(1::Real)) === Integer end - # when argment's type annotation is omitted, it should be specified as `Any` - let - f(_) = Any + # when argment's type annotation is omitted, it should be specified as `Core.Typeof(x)` + let f(_) = Any f(x::Integer) = Integer @test f(1) === Integer - @test Base.@invoke(f(1::Any)) === Any - @test Base.@invoke(f(1)) === Any + @test @invoke(f(1::Any)) === Any + @test @invoke(f(1)) === Integer + + 😎(x, y) = 1 + 😎(x, ::Type{Int}) = 2 + # Without `Core.Typeof`, the first method would be called + @test @invoke(😎(1, Int)) == 2 end # handle keyword arguments correctly - let - f(a; kw1 = nothing, kw2 = nothing) = a + max(kw1, kw2) + let f(a; kw1 = nothing, kw2 = nothing) = a + max(kw1, kw2) f(::Integer; kwargs...) = error("don't call me") @test_throws Exception f(1; kw1 = 1, kw2 = 2) - @test 3 == Base.@invoke f(1::Any; kw1 = 1, kw2 = 2) - @test 3 == Base.@invoke f(1; kw1 = 1, kw2 = 2) + @test 3 == @invoke f(1::Any; kw1 = 1, kw2 = 2) + end + + # additional syntax test + let x = InvokeX2(nothing) + @test_throws "overload InvokeX" @invoke (x::InvokeX).x + @test isnothing(@invoke x.x) + @test_throws "overload InvokeX" @invoke (x::InvokeX).x = 42 + @invoke x.x = 42 + @test 42 == x.x + + xs = InvokeXs2(Any[nothing]) + @test_throws "overload InvokeXs" @invoke (xs::InvokeXs)[1] + @test isnothing(@invoke xs[1]) + @test_throws "overload InvokeXs" @invoke (xs::InvokeXs)[1] = 42 + @invoke xs[1] = 42 + @test 42 == xs.xs[1] end end @@ -940,19 +1146,28 @@ end @test_nowarn Core.eval(Main, :(import ....Main)) # issue #27239 +using Base.BinaryPlatforms: HostPlatform, libc @testset "strftime tests issue #27239" begin - # change to non-Unicode Korean + # change to non-Unicode Korean to test that it is properly transcoded into valid UTF-8 korloc = ["ko_KR.EUC-KR", "ko_KR.CP949", "ko_KR.949", "Korean_Korea.949"] - timestrs = String[] - withlocales(korloc) do - # system dependent formats - push!(timestrs, Libc.strftime(0.0)) - push!(timestrs, Libc.strftime("%a %A %b %B %p %Z", 0)) + at_least_one_locale_found = false + withlocales(korloc) do locale + at_least_one_locale_found = true + # Test both the default format and a custom formatting string + for s in (Libc.strftime(0.0), Libc.strftime("%a %A %b %B %p %Z", 0)) + # Ensure that we always get valid UTF-8 back + @test isvalid(s) + + # On `musl` it is impossible for `setlocale` to fail, it just falls back to + # the default system locale, which on our buildbots is en_US.UTF-8. We'll + # assert that what we get does _not_ start with `Thu`, as that's what all + # en_US.UTF-8 encodings would start with. + # X-ref: https://musl.openwall.narkive.com/kO1vpTWJ/setlocale-behavior-with-missing-locales + @test !startswith(s, "Thu") broken=(libc(HostPlatform()) == "musl") + end end - # tests - isempty(timestrs) && @warn "skipping stftime tests: no locale found for testing" - for s in timestrs - @test isvalid(s) + if !at_least_one_locale_found + @warn "skipping stftime tests: no locale found for testing" end end @@ -1018,13 +1233,64 @@ const outsidevar = 7 end @test TestOutsideVar() == TestOutsideVar(7) +@kwdef mutable struct Test_kwdef_const_atomic + a + b::Int + c::Int = 1 + const d + const e::Int + const f = 1 + const g::Int = 1 + @atomic h::Int +end + +@testset "const and @atomic fields in @kwdef" begin + x = Test_kwdef_const_atomic(a = 1, b = 1, d = 1, e = 1, h = 1) + for f in fieldnames(Test_kwdef_const_atomic) + @test getfield(x, f) == 1 + end + @testset "const fields" begin + @test_throws ErrorException x.d = 2 + @test_throws ErrorException x.e = 2 + @test_throws MethodError x.e = "2" + @test_throws ErrorException x.f = 2 + @test_throws ErrorException x.g = 2 + end + @testset "atomic fields" begin + @test_throws ConcurrencyViolationError x.h = 1 + @atomic x.h = 1 + @test @atomic(x.h) == 1 + @atomic x.h = 2 + @test @atomic(x.h) == 2 + end +end + +@kwdef struct Test_kwdef_lineinfo + a::String +end +@testset "@kwdef constructor line info" begin + for method in methods(Test_kwdef_lineinfo) + @test method.file === Symbol(@__FILE__) + @test ((@__LINE__)-6) ≤ method.line ≤ ((@__LINE__)-5) + end +end +@kwdef struct Test_kwdef_lineinfo_sparam{S<:AbstractString} + a::S +end +@testset "@kwdef constructor line info with static parameter" begin + for method in methods(Test_kwdef_lineinfo_sparam) + @test method.file === Symbol(@__FILE__) + @test ((@__LINE__)-6) ≤ method.line ≤ ((@__LINE__)-5) + end +end @testset "exports of modules" begin for (_, mod) in Base.loaded_modules - for v in names(mod) - @test isdefined(mod, v) - end - end + mod === Main && continue # Main exports everything + for v in names(mod) + @test isdefined(mod, v) + end + end end @testset "ordering UUIDs" begin @@ -1065,9 +1331,16 @@ end GC.safepoint() - GC.enable_logging(true) - GC.gc() - GC.enable_logging(false) + mktemp() do tmppath, _ + open(tmppath, "w") do tmpio + redirect_stderr(tmpio) do + GC.enable_logging(true) + GC.gc() + GC.enable_logging(false) + end + end + @test occursin("GC: pause", read(tmppath, String)) + end end @testset "fieldtypes Module" begin @@ -1108,4 +1381,24 @@ 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 +end + +@testset "in_finalizer" begin + @test !GC.in_finalizer() + + in_fin = Ref{Any}() + wait(@async begin + r = Ref(1) + finalizer(r) do _ + in_fin[] = GC.in_finalizer() + end + nothing + end) + GC.gc(true); yield() + @test in_fin[] end diff --git a/test/missing.jl b/test/missing.jl index 13ed684f1fc05..f06d1aad7a6b1 100644 --- a/test/missing.jl +++ b/test/missing.jl @@ -21,8 +21,8 @@ end @test convert(Union{Nothing, Missing}, nothing) === nothing @test convert(Union{Missing, Nothing, Float64}, 1) === 1.0 - @test_throws MethodError convert(Missing, 1) - @test_throws MethodError convert(Union{Nothing, Missing}, 1) + @test_throws ErrorException("cannot convert a value to missing for assignment") convert(Missing, 1) + @test_throws ErrorException("cannot convert a value to missing for assignment") convert(Union{Nothing, Missing}, 1) @test_throws MethodError convert(Union{Int, Missing}, "a") end @@ -66,6 +66,7 @@ end @test isequal(missing, missing) @test !isequal(1, missing) @test !isequal(missing, 1) + @test !isequal('c', missing) @test (missing < missing) === missing @test (missing < 1) === missing @test (1 < missing) === missing @@ -529,7 +530,7 @@ end @test mapreduce(cos, *, collect(skipmissing(A))) ≈ mapreduce(cos, *, skipmissing(A)) end - # Patterns that exercize code paths for inputs with 1 or 2 non-missing values + # Patterns that exercise code paths for inputs with 1 or 2 non-missing values @test sum(skipmissing([1, missing, missing, missing])) === 1 @test sum(skipmissing([missing, missing, missing, 1])) === 1 @test sum(skipmissing([1, missing, missing, missing, 2])) === 3 diff --git a/test/mpfr.jl b/test/mpfr.jl index a1039a7c5a810..1a0a0041bf94e 100644 --- a/test/mpfr.jl +++ b/test/mpfr.jl @@ -653,6 +653,10 @@ end @test typeof(round(Int64, x)) == Int64 && round(Int64, x) == 42 @test typeof(round(Int, x)) == Int && round(Int, x) == 42 @test typeof(round(UInt, x)) == UInt && round(UInt, x) == 0x2a + + # Issue #44662 + @test_throws InexactError round(Integer, big(Inf)) + @test_throws InexactError round(Integer, big(NaN)) end @testset "string representation" begin str = "1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012" diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 3b571b3c7d612..ea3a5cdbb8ee4 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -26,6 +26,7 @@ @test (x=4, y=5, z=6)[[:x, :y]] == (x=4, y=5) @test (x=4, y=5, z=6)[[:x]] == (x=4,) @test (x=4, y=5, z=6)[()] == NamedTuple() +@test (x=4, y=5, z=6)[:] == (x=4, y=5, z=6) @test NamedTuple()[()] == NamedTuple() @test_throws ErrorException (x=4, y=5, z=6).a @test_throws BoundsError (a=2,)[0] @@ -75,6 +76,26 @@ let NT = NamedTuple{(:a,:b),Tuple{Int8,Int16}}, nt = (x=3,y=4) @test_throws MethodError convert(NT, nt) end +@testset "convert NamedTuple" begin + conv1 = convert(NamedTuple{(:a,),Tuple{I}} where I, (;a=1)) + @test conv1 === (a = 1,) + + conv2 = convert(NamedTuple{(:a,),Tuple{Any}}, (;a=1)) + @test conv2 === NamedTuple{(:a,), Tuple{Any}}((1,)) + + conv3 = convert(NamedTuple{(:a,),}, (;a=1)) + @test conv3 === (a = 1,) + + conv4 = convert(NamedTuple{(:a,),Tuple{I}} where I<:Unsigned, (;a=1)) + @test conv4 === NamedTuple{(:a,), Tuple{Unsigned}}((1,)) + + conv5 = convert(NamedTuple, (;a=1)) + @test conv1 === (a = 1,) + + 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) +end + @test NamedTuple{(:a,:c)}((b=1,z=2,c=3,aa=4,a=5)) === (a=5, c=3) @test NamedTuple{(:a,)}(NamedTuple{(:b, :a), Tuple{Int, Union{Int,Nothing}}}((1, 2))) === NamedTuple{(:a,), Tuple{Union{Int,Nothing}}}((2,)) @@ -83,6 +104,9 @@ end @test eltype(NamedTuple{(:x, :y),Tuple{Union{Missing, Int},Union{Missing, Float64}}}( (missing, missing))) === Union{Real, Missing} +@test valtype((a=[1,2], b=[3,4])) === Vector{Int} +@test keytype((a=[1,2], b=[3,4])) === Symbol + @test Tuple((a=[1,2], b=[3,4])) == ([1,2], [3,4]) @test Tuple(NamedTuple()) === () @test Tuple((x=4, y=5, z=6)) == (4,5,6) @@ -120,7 +144,7 @@ end let nt = merge(NamedTuple{(:a,:b),Tuple{Int32,Union{Int32,Nothing}}}((1,Int32(2))), NamedTuple{(:a,:c),Tuple{Union{Int8,Nothing},Float64}}((nothing,1.0))) @test typeof(nt) == NamedTuple{(:a,:b,:c),Tuple{Union{Int8,Nothing},Union{Int32,Nothing},Float64}} - @test repr(nt) == "NamedTuple{(:a, :b, :c), Tuple{Union{Nothing, Int8}, Union{Nothing, Int32}, Float64}}((nothing, 2, 1.0))" + @test repr(nt) == "@NamedTuple{a::Union{Nothing, Int8}, b::Union{Nothing, Int32}, c::Float64}((nothing, 2, 1.0))" end @test merge(NamedTuple(), [:a=>1, :b=>2, :c=>3, :a=>4, :c=>5]) == (a=4, b=2, c=5) @@ -147,6 +171,8 @@ end @test Base.front((a = 1, )) ≡ NamedTuple() @test_throws ArgumentError Base.tail(NamedTuple()) @test_throws ArgumentError Base.front(NamedTuple()) +@test @inferred(reverse((a=1,))) === (a=1,) +@test @inferred(reverse((a=1, b=:c))) === (b=:c, a=1) # syntax errors @@ -257,10 +283,10 @@ abstr_nt_22194_3() @test findall(isequal(1), (a=1, b=1)) == [:a, :b] @test isempty(findall(isequal(1), NamedTuple())) @test isempty(findall(isequal(1), (a=2, b=3))) -@test findfirst(isequal(1), (a=1, b=2)) == :a -@test findlast(isequal(1), (a=1, b=2)) == :a -@test findfirst(isequal(1), (a=1, b=1)) == :a -@test findlast(isequal(1), (a=1, b=1)) == :b +@test findfirst(isequal(1), (a=1, b=2)) === :a +@test findlast(isequal(1), (a=1, b=2)) === :a +@test findfirst(isequal(1), (a=1, b=1)) === :a +@test findlast(isequal(1), (a=1, b=1)) === :b @test findfirst(isequal(1), ()) === nothing @test findlast(isequal(1), ()) === nothing @test findfirst(isequal(1), (a=2, b=3)) === nothing @@ -336,3 +362,29 @@ end # issue #44086 @test NamedTuple{(:x, :y, :z), Tuple{Int8, Int16, Int32}}((z=1, x=2, y=3)) === (x = Int8(2), y = Int16(3), z = Int32(1)) + +@testset "mapfoldl" begin + A1 = (;a=1, b=2, c=3, d=4) + A2 = (;a=-1, b=-2, c=-3, d=-4) + @test (((1=>2)=>3)=>4) == foldl(=>, A1) == + mapfoldl(identity, =>, A1) == mapfoldl(abs, =>, A2) + @test mapfoldl(abs, =>, A2, init=-10) == ((((-10=>1)=>2)=>3)=>4) + @test mapfoldl(abs, =>, (;), init=-10) == -10 + @test mapfoldl(abs, Pair{Any,Any}, NamedTuple(Symbol(:x,i) => i for i in 1:30)) == mapfoldl(abs, Pair{Any,Any}, [1:30;]) + @test_throws "reducing over an empty collection" mapfoldl(abs, =>, (;)) +end + +# Test effect/inference for merge/diff of unknown NamedTuples +for f in (Base.merge, Base.structdiff) + let eff = Base.infer_effects(f, Tuple{NamedTuple, NamedTuple}) + @test Core.Compiler.is_foldable(eff) && eff.nonoverlayed + end + @test Core.Compiler.return_type(f, Tuple{NamedTuple, NamedTuple}) == NamedTuple +end +@test Core.Compiler.is_foldable(Base.infer_effects(pairs, Tuple{NamedTuple})) + +# Test that merge/diff preserves nt field types +let a = Base.NamedTuple{(:a, :b), Tuple{Any, Any}}((1, 2)), b = Base.NamedTuple{(:b,), Tuple{Float64}}(3) + @test typeof(Base.merge(a, b)) == Base.NamedTuple{(:a, :b), Tuple{Any, Float64}} + @test typeof(Base.structdiff(a, b)) == Base.NamedTuple{(:a,), Tuple{Any}} +end diff --git a/test/numbers.jl b/test/numbers.jl index 4875de7fc3bb2..efb2702aff1c2 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -95,34 +95,68 @@ end @test max(1) === 1 @test minmax(1) === (1, 1) @test minmax(5, 3) == (3, 5) - @test minmax(3., 5.) == (3., 5.) - @test minmax(5., 3.) == (3., 5.) - @test minmax(3., NaN) ≣ (NaN, NaN) - @test minmax(NaN, 3) ≣ (NaN, NaN) - @test minmax(Inf, NaN) ≣ (NaN, NaN) - @test minmax(NaN, Inf) ≣ (NaN, NaN) - @test minmax(-Inf, NaN) ≣ (NaN, NaN) - @test minmax(NaN, -Inf) ≣ (NaN, NaN) - @test minmax(NaN, NaN) ≣ (NaN, NaN) - @test min(-0.0,0.0) === min(0.0,-0.0) - @test max(-0.0,0.0) === max(0.0,-0.0) - @test minmax(-0.0,0.0) === minmax(0.0,-0.0) - @test max(-3.2, 5.1) == max(5.1, -3.2) == 5.1 - @test min(-3.2, 5.1) == min(5.1, -3.2) == -3.2 - @test max(-3.2, Inf) == max(Inf, -3.2) == Inf - @test max(-3.2, NaN) ≣ max(NaN, -3.2) ≣ NaN - @test min(5.1, Inf) == min(Inf, 5.1) == 5.1 - @test min(5.1, -Inf) == min(-Inf, 5.1) == -Inf - @test min(5.1, NaN) ≣ min(NaN, 5.1) ≣ NaN - @test min(5.1, -NaN) ≣ min(-NaN, 5.1) ≣ NaN - @test minmax(-3.2, 5.1) == (min(-3.2, 5.1), max(-3.2, 5.1)) - @test minmax(-3.2, Inf) == (min(-3.2, Inf), max(-3.2, Inf)) - @test minmax(-3.2, NaN) ≣ (min(-3.2, NaN), max(-3.2, NaN)) - @test (max(Inf,NaN), max(-Inf,NaN), max(Inf,-NaN), max(-Inf,-NaN)) ≣ (NaN,NaN,NaN,NaN) - @test (max(NaN,Inf), max(NaN,-Inf), max(-NaN,Inf), max(-NaN,-Inf)) ≣ (NaN,NaN,NaN,NaN) - @test (min(Inf,NaN), min(-Inf,NaN), min(Inf,-NaN), min(-Inf,-NaN)) ≣ (NaN,NaN,NaN,NaN) - @test (min(NaN,Inf), min(NaN,-Inf), min(-NaN,Inf), min(-NaN,-Inf)) ≣ (NaN,NaN,NaN,NaN) - @test minmax(-Inf,NaN) ≣ (min(-Inf,NaN), max(-Inf,NaN)) + Top(T, op, x, y) = op(T.(x), T.(y)) + Top(T, op) = (x, y) -> Top(T, op, x, y) + _compare(x, y) = x == y + for T in (Float16, Float32, Float64, BigFloat) + minmax = Top(T,Base.minmax) + min = Top(T,Base.min) + max = Top(T,Base.max) + (==) = Top(T,_compare) + (===) = Top(T,Base.isequal) # we only use === to compare -0.0/0.0, `isequal` should be equivalent + @test minmax(3., 5.) == (3., 5.) + @test minmax(5., 3.) == (3., 5.) + @test minmax(3., NaN) ≣ (NaN, NaN) + @test minmax(NaN, 3) ≣ (NaN, NaN) + @test minmax(Inf, NaN) ≣ (NaN, NaN) + @test minmax(NaN, Inf) ≣ (NaN, NaN) + @test minmax(-Inf, NaN) ≣ (NaN, NaN) + @test minmax(NaN, -Inf) ≣ (NaN, NaN) + @test minmax(NaN, NaN) ≣ (NaN, NaN) + @test min(-0.0,0.0) === min(0.0,-0.0) + @test max(-0.0,0.0) === max(0.0,-0.0) + @test minmax(-0.0,0.0) === minmax(0.0,-0.0) + @test max(-3.2, 5.1) == max(5.1, -3.2) == 5.1 + @test min(-3.2, 5.1) == min(5.1, -3.2) == -3.2 + @test max(-3.2, Inf) == max(Inf, -3.2) == Inf + @test max(-3.2, NaN) ≣ max(NaN, -3.2) ≣ NaN + @test min(5.1, Inf) == min(Inf, 5.1) == 5.1 + @test min(5.1, -Inf) == min(-Inf, 5.1) == -Inf + @test min(5.1, NaN) ≣ min(NaN, 5.1) ≣ NaN + @test min(5.1, -NaN) ≣ min(-NaN, 5.1) ≣ NaN + @test minmax(-3.2, 5.1) == (min(-3.2, 5.1), max(-3.2, 5.1)) + @test minmax(-3.2, Inf) == (min(-3.2, Inf), max(-3.2, Inf)) + @test minmax(-3.2, NaN) ≣ (min(-3.2, NaN), max(-3.2, NaN)) + @test (max(Inf,NaN), max(-Inf,NaN), max(Inf,-NaN), max(-Inf,-NaN)) ≣ (NaN,NaN,NaN,NaN) + @test (max(NaN,Inf), max(NaN,-Inf), max(-NaN,Inf), max(-NaN,-Inf)) ≣ (NaN,NaN,NaN,NaN) + @test (min(Inf,NaN), min(-Inf,NaN), min(Inf,-NaN), min(-Inf,-NaN)) ≣ (NaN,NaN,NaN,NaN) + @test (min(NaN,Inf), min(NaN,-Inf), min(-NaN,Inf), min(-NaN,-Inf)) ≣ (NaN,NaN,NaN,NaN) + @test minmax(-Inf,NaN) ≣ (min(-Inf,NaN), max(-Inf,NaN)) + end +end +@testset "Base._extrema_rf for float" begin + for T in (Float16, Float32, Float64, BigFloat) + ordered = T[-Inf, -5, -0.0, 0.0, 3, Inf] + unorded = T[NaN, -NaN] + for i1 in 1:6, i2 in 1:6, j1 in 1:6, j2 in 1:6 + x = ordered[i1], ordered[i2] + y = ordered[j1], ordered[j2] + z = ordered[min(i1,j1)], ordered[max(i2,j2)] + @test Base._extrema_rf(x, y) === z + end + for i in 1:2, j1 in 1:6, j2 in 1:6 # unordered test (only 1 NaN) + x = unorded[i] , unorded[i] + y = ordered[j1], ordered[j2] + @test Base._extrema_rf(x, y) === x + @test Base._extrema_rf(y, x) === x + end + for i in 1:2, j in 1:2 # unordered test (2 NaNs) + x = unorded[i], unorded[i] + y = unorded[j], unorded[j] + z = Base._extrema_rf(x, y) + @test z === x || z === y + end + end end @testset "fma" begin let x = Int64(7)^7 @@ -490,6 +524,8 @@ end @test isa(sign(2//3), Rational{Int}) @test isa(2//3 + 2//3im, Complex{Rational{Int}}) @test isa(sign(2//3 + 2//3im), ComplexF64) + @test sign(pi) === 1.0 + @test sign(pi) === -sign(-pi) @test sign(one(UInt)) == 1 @test sign(zero(UInt)) == 0 @@ -1035,6 +1071,15 @@ end @test Float64(10633823966279328163822077199654060033) == 1.063382396627933e37 #nextfloat(0x1p123) @test Float64(-10633823966279328163822077199654060032) == -1.0633823966279327e37 @test Float64(-10633823966279328163822077199654060033) == -1.063382396627933e37 + + # Test lsb/msb gaps of 54 (won't fit in 64 bit mantissa) + @test Float64(Int128(9007199254740993)) == 9.007199254740992e15 + @test Float64(UInt128(9007199254740993)) == 9.007199254740992e15 + # Test 2^104-1 and 2^104 (2^104 is cutoff for which case is run in the conversion algorithm) + @test Float64(Int128(20282409603651670423947251286015)) == 2.028240960365167e31 + @test Float64(Int128(20282409603651670423947251286016)) == 2.028240960365167e31 + @test Float64(UInt128(20282409603651670423947251286015)) == 2.028240960365167e31 + @test Float64(UInt128(20282409603651670423947251286016)) == 2.028240960365167e31 end @testset "Float vs Int128 comparisons" begin @test Int128(1e30) == 1e30 @@ -1115,11 +1160,13 @@ end @test sqrt(2) == 1.4142135623730951 end +Base.@irrational i46051 4863.185427757 1548big(pi) @testset "Irrational printing" begin @test sprint(show, "text/plain", π) == "π = 3.1415926535897..." @test sprint(show, "text/plain", π, context=:compact => true) == "π" @test sprint(show, π) == "π" - + # issue #46051 + @test sprint(show, "text/plain", i46051) == "i46051 = 4863.185427757..." end @testset "issue #6365" begin for T in (Float32, Float64) @@ -1648,8 +1695,13 @@ end @test rem(prevfloat(1.0),1.0) == prevfloat(1.0) @test mod(prevfloat(1.0),1.0) == prevfloat(1.0) end - # issue #3046 - @test mod(Int64(2),typemax(Int64)) == 2 + @test mod(Int64(2), typemax(Int64)) == 2 # issue #3046 + @testset "issue #45875" begin + @test cld(+1.1, 0.1) == div(+1.1, 0.1, RoundUp) == ceil(big(+1.1)/big(0.1)) == +12.0 + @test fld(+1.1, 0.1) == div(+1.1, 0.1, RoundDown) == floor(big(+1.1)/big(0.1)) == +11.0 + @test cld(-1.1, 0.1) == div(-1.1, 0.1, RoundUp) == ceil(big(-1.1)/big(0.1)) == -11.0 + @test fld(-1.1, 0.1) == div(-1.1, 0.1, RoundDown) == floor(big(-1.1)/big(0.1)) == -12.0 + end end @testset "return types" begin for T in (Int8,Int16,Int32,Int64,Int128, UInt8,UInt16,UInt32,UInt64,UInt128) @@ -2007,8 +2059,11 @@ end end @test nextpow(2, 56789) == 65536 @test_throws DomainError nextpow(2, -56789) + @test_throws DomainError nextpow(Int8(4), 128) @test prevpow(2, 56789) == 32768 @test_throws DomainError prevpow(2, -56789) + @test_throws DomainError prevpow(Int8(4), 128) + @test_throws OverflowError nextpow(Int8(4), 65) for i = 1:100 @test nextpow(2, i) == nextpow(2, big(i)) @test prevpow(2, i) == prevpow(2, big(i)) @@ -2017,6 +2072,14 @@ end @test nextpow(2, T(42)) === T(64) @test prevpow(2, T(42)) === T(32) end + for T in (Float16, Float32, Float64) + @test prevpow(2, prevfloat(T(1024.0))) == T(512.0) + @test nextpow(2, nextfloat(T(1024.0))) == T(2048.0) + @test prevpow(T(2.0), prevfloat(T(1024.0))) == T(512.0) + @test nextpow(T(2.0), nextfloat(T(1024.0))) == T(2048.0) + @test prevpow(T(2.0), prevfloat(T(Inf))) < T(Inf) + @test nextpow(T(2.0), prevfloat(T(Inf))) == T(Inf) + end end @testset "ispow2" begin @test ispow2(64) @@ -2291,12 +2354,6 @@ end end end @testset "getindex error throwing" begin - #getindex(x::Number,-1) throws BoundsError - #getindex(x::Number,0) throws BoundsError - #getindex(x::Number,2) throws BoundsError - #getindex(x::Array,-1) throws BoundsError - #getindex(x::Array,0 throws BoundsError - #getindex(x::Array,length(x::Array)+1) throws BoundsError for x in [1.23, 7, ℯ, 4//5] #[FP, Int, Irrational, Rat] @test_throws BoundsError getindex(x,-1) @test_throws BoundsError getindex(x,0) @@ -2414,23 +2471,44 @@ zero(::Type{TestNumber{Inner}}) where {Inner} = TestNumber(zero(Inner)) big(test_number::TestNumber) = TestNumber(big(test_number.inner)) @test big(TestNumber{Int}) == TestNumber{BigInt} +# abstract abs2 +Base.:*(x::TestNumber, y::TestNumber) = TestNumber(x.inner*y.inner) +Base.:(==)(x::TestNumber, y::TestNumber) = x.inner == y.inner +Base.abs(x::TestNumber) = TestNumber(abs(x.inner)) +@test abs2(TestNumber(3+4im)) == TestNumber(25) + @testset "multiplicative inverses" begin function testmi(numrange, denrange) for d in denrange d == 0 && continue fastd = Base.multiplicativeinverse(d) for n in numrange + d == -1 && n == typemin(typeof(n)) && continue @test div(n,d) == div(n,fastd) end end end testmi(-1000:1000, -100:100) - testmi(typemax(Int)-1000:typemax(Int), -100:100) - testmi(typemin(Int)+1:typemin(Int)+1000, -100:100) @test_throws ArgumentError Base.multiplicativeinverse(0) - testmi(map(UInt32, 0:1000), map(UInt32, 1:100)) - testmi(typemax(UInt32)-UInt32(1000):typemax(UInt32), map(UInt32, 1:100)) + for T in [Int8, Int16, Int32, Int64, Int128] + testmi(map(T, typemin(T)+1:typemin(T)+100), map(T, -50:50)) + end + for T in [UInt8, UInt16, UInt32, UInt64, UInt128, Int8, Int16, Int32, Int64, Int128] + testmi(map(T, typemax(T)-50:typemax(T)), map(T, 1:50)) + testmi(rand(T, 50), rand(T, 50)) + @test_throws ArgumentError Base.multiplicativeinverse(T(0)) + end + + # Division overflow is not handled + T = Int8 + fastd = Base.multiplicativeinverse(T(-1)) + @test_throws DivideError div(typemin(T), T(-1)) + # does not throw: + # @test_throws div(typemin(T), fastd) + # test broadcasting works. + @test div.(3, Base.multiplicativeinverse(3)) == 1 end + @testset "ndims/indices/size/length" begin @test ndims(1) == 0 @test ndims(Integer) == 0 @@ -2511,29 +2589,33 @@ end @test rem(T(1), T(2), RoundNearest) == 1 @test rem(T(1), T(2), RoundDown) == 1 @test rem(T(1), T(2), RoundUp) == -1 + @test rem(T(1), T(2), RoundFromZero) == -1 @test rem(T(1.5), T(2), RoundToZero) == 1.5 @test rem(T(1.5), T(2), RoundNearest) == -0.5 @test rem(T(1.5), T(2), RoundDown) == 1.5 @test rem(T(1.5), T(2), RoundUp) == -0.5 + @test rem(T(1.5), T(2), RoundFromZero) == -0.5 @test rem(T(-1), T(2), RoundToZero) == -1 @test rem(T(-1), T(2), RoundNearest) == -1 @test rem(T(-1), T(2), RoundDown) == 1 @test rem(T(-1), T(2), RoundUp) == -1 + @test rem(T(-1), T(2), RoundFromZero) == 1 @test rem(T(-1.5), T(2), RoundToZero) == -1.5 @test rem(T(-1.5), T(2), RoundNearest) == 0.5 @test rem(T(-1.5), T(2), RoundDown) == 0.5 @test rem(T(-1.5), T(2), RoundUp) == -1.5 - for mode in [RoundToZero, RoundNearest, RoundDown, RoundUp] + @test rem(T(-1.5), T(2), RoundFromZero) == 0.5 + for mode in [RoundToZero, RoundNearest, RoundDown, RoundUp, RoundFromZero] @test isnan(rem(T(1), T(0), mode)) @test isnan(rem(T(Inf), T(2), mode)) @test isnan(rem(T(1), T(NaN), mode)) - # FIXME: The broken case erroneously returns -Inf - @test rem(T(4), floatmin(T) * 2, mode) == 0 broken=(T == BigFloat && mode == RoundUp) + @test rem(T(4), floatmin(T) * 2, mode) == 0 end @test isequal(rem(nextfloat(typemin(T)), T(2), RoundToZero), -0.0) @test isequal(rem(nextfloat(typemin(T)), T(2), RoundNearest), -0.0) - @test isequal(rem(nextfloat(typemin(T)), T(2), RoundDown), 0.0) - @test isequal(rem(nextfloat(typemin(T)), T(2), RoundUp), 0.0) + @test isequal(rem(nextfloat(typemin(T)), T(2), RoundDown), 0.0) + @test isequal(rem(nextfloat(typemin(T)), T(2), RoundUp), -0.0) + @test isequal(rem(nextfloat(typemin(T)), T(2), RoundFromZero), 0.0) end @testset "rem for $T RoundNearest" for T in (Int8, Int16, Int32, Int64, Int128) @@ -2609,6 +2691,37 @@ end @test rem2pi(T(-8), RoundNearest) ≈ -8+2pi @test rem2pi(T(-8), RoundDown) ≈ -8+4pi @test rem2pi(T(-8), RoundUp) ≈ -8+2pi + # to hit n is even and n % 4 == 2 condition + @test rem2pi(T(3), RoundToZero) == 3 + @test rem2pi(T(3), RoundNearest) == 3 + @test rem2pi(T(3), RoundDown) == 3 + @test rem2pi(T(3), RoundUp) ≈ 3 - 2π + @test rem2pi(T(-3), RoundToZero) == -3 + @test rem2pi(T(-3), RoundNearest) == -3 + @test rem2pi(T(-3), RoundDown) ≈ -3 + 2π + @test rem2pi(T(-3), RoundUp) == -3 + # to hit even n condition and n % 4 != 2 condition + @test rem2pi(T(13), RoundToZero) ≈ 13-4π + @test rem2pi(T(13), RoundNearest) ≈ 13-4π + @test rem2pi(T(13), RoundDown) ≈ 13-4π + @test rem2pi(T(13), RoundUp) ≈ 13-6π + @test rem2pi(T(-13), RoundToZero) ≈ -13+4π + @test rem2pi(T(-13), RoundNearest) ≈ -13+4π + @test rem2pi(T(-13), RoundDown) ≈ -13+6π + @test rem2pi(T(-13), RoundUp) ≈ -13+4π +end + +@testset "PR #36420 $T" for T in (Float16, Float32, Float64, BigFloat) + nan = reinterpret(Float64, reinterpret(UInt64, NaN) | rand(UInt64)) + for r in (RoundToZero, RoundNearest, RoundDown, RoundUp) + for x in (Inf, -Inf, NaN, -NaN, nan) + @test isnan(rem2pi(T(x), r)) + @test rem2pi(T(x), r) isa T + if isnan(x) && T !== BigFloat + @test rem2pi(T(x), r) === T(x) + end + end + end end import Base.^ @@ -2792,7 +2905,7 @@ end @testset "constructor inferability for BigFloat" begin T = BigFloat @test_broken all(R -> R<:T, Base.return_types(T)) - @test all(m -> m.file == Symbol("deprecated.jl"), + @test all(m -> m.file === Symbol("deprecated.jl"), collect(methods(T))[findall(R -> !(R<:T), Base.return_types(T))]) end @@ -2811,3 +2924,182 @@ end @test_throws MethodError fld(a, b) @test_throws MethodError cld(a, b) end + +@testset "Bool rounding (#25074)" begin + @testset "round Bool" begin + @test_throws InexactError round(Bool, -4.1) + @test_throws InexactError round(Bool, 1.5) + @test true == round(Bool, 1.0) + @test false == round(Bool, 0.0) + @test true == round(Bool, 0.6) + @test false == round(Bool, 0.4) + @test false == round(Bool, 0.5) + @test false == round(Bool, -0.5) + end + + @testset "trunc Bool" begin + @test_throws InexactError trunc(Bool, -4.1) + @test_throws InexactError trunc(Bool, 2.5) + @test true == trunc(Bool, 1.0) + @test false == trunc(Bool, 0.0) + @test false == trunc(Bool, 0.6) + @test false == trunc(Bool, 0.4) + @test true == trunc(Bool, 1.8) + @test false == trunc(Bool, -0.5) + end + + @testset "floor Bool" begin + @test_throws InexactError floor(Bool, -0.1) + @test_throws InexactError floor(Bool, 2.5) + @test true == floor(Bool, 1.0) + @test false == floor(Bool, 0.0) + @test false == floor(Bool, 0.6) + @test true == floor(Bool, 1.8) + end + + @testset "ceil Bool" begin + @test_throws InexactError ceil(Bool, -1.4) + @test_throws InexactError ceil(Bool, 1.5) + @test true == ceil(Bool, 1.0) + @test false == ceil(Bool, 0.0) + @test true == ceil(Bool, 0.6) + @test false == ceil(Bool, -0.7) + end +end + +Base.@irrational irrational_1548_pi 4863.185427757 1548big(pi) +Base.@irrational irrational_inv_1548_pi 1/big(irrational_1548_pi) +@testset "@irrational" begin + @test irrational_1548_pi ≈ 1548big(pi) + @test Float64(irrational_1548_pi) == 1548π + @test irrational_1548_pi ≈ 1548pi + @test irrational_1548_pi != 1548pi + + @test irrational_inv_1548_pi ≈ inv(1548big(pi)) + @test Float64(irrational_inv_1548_pi) == 1/(1548π) + @test irrational_inv_1548_pi ≈ inv(1548pi) + @test irrational_inv_1548_pi != inv(1548pi) +end + +@testset "modf" begin + @testset "remd" begin + denorm_min = nextfloat(0.0) + minfloat = floatmin(Float64) + maxfloat = floatmax(Float64) + values = [3.0,denorm_min,-denorm_min, minfloat, + -minfloat, maxfloat, -maxfloat] + # rem (0, y) == 0 for y != 0. + for val in values + @test isequal(rem(0.0, val), 0.0) + end + # rem (-0, y) == -0 for y != 0. + for val in values + @test isequal(rem(-0.0, val), -0.0) + end + # rem (+Inf, y) == NaN + values2 = [3.0,-1.1,0.0,-0.0,denorm_min,minfloat, + maxfloat,Inf,-Inf] + for val in values2 + @test isequal(rem(Inf, val), NaN) + end + # rem (-Inf, y) == NaN + for val in values2 + @test isequal(rem(-Inf, val), NaN) + end + # rem (x, +0) == NaN + values3 = values2[begin:end-2] + for val in values3 + @test isequal(rem(val, 0.0), NaN) + end + # rem (x, -0) == NaN + for val in values3 + @test isequal(rem(val, -0.0), NaN) + end + # rem (x, +Inf) == x for x not infinite. + @test isequal(rem(0.0, Inf), 0.0) + @test isequal(rem(-0.0, Inf), -0.0) + @test isequal(rem(denorm_min, Inf), denorm_min) + @test isequal(rem(minfloat, Inf), minfloat) + @test isequal(rem(maxfloat, Inf), maxfloat) + @test isequal(rem(3.0, Inf), 3.0) + # rem (x, -Inf) == x for x not infinite. + @test isequal(rem(0.0, -Inf), 0.0) + @test isequal(rem(-0.0, -Inf), -0.0) + @test isequal(rem(denorm_min, -Inf), denorm_min) + @test isequal(rem(minfloat, -Inf), minfloat) + @test isequal(rem(maxfloat, -Inf), maxfloat) + @test isequal(rem(3.0, -Inf), 3.0) + #NaN tests + @test isequal(rem(0.0, NaN), NaN) + @test isequal(rem(1.0, NaN), NaN) + @test isequal(rem(Inf, NaN), NaN) + @test isequal(rem(NaN, 0.0), NaN) + @test isequal(rem(NaN, 1.0), NaN) + @test isequal(rem(NaN, Inf), NaN) + @test isequal(rem(NaN, NaN), NaN) + #Sign tests + @test isequal(rem(6.5, 2.25), 2.0) + @test isequal(rem(-6.5, 2.25), -2.0) + @test isequal(rem(6.5, -2.25), 2.0) + @test isequal(rem(-6.5, -2.25), -2.0) + values4 = [maxfloat,-maxfloat,minfloat,-minfloat, + denorm_min, -denorm_min] + for val in values4 + @test isequal(rem(maxfloat,val), 0.0) + end + for val in values4 + @test isequal(rem(-maxfloat,val), -0.0) + end + @test isequal(rem(minfloat, maxfloat), minfloat) + @test isequal(rem(minfloat, -maxfloat), minfloat) + values5 = values4[begin+2:end] + for val in values5 + @test isequal(rem(minfloat,val), 0.0) + end + @test isequal(rem(-minfloat, maxfloat), -minfloat) + @test isequal(rem(-minfloat, -maxfloat), -minfloat) + for val in values5 + @test isequal(rem(-minfloat,val), -0.0) + end + values6 = values4[begin:end-2] + for val in values6 + @test isequal(rem(denorm_min,val), denorm_min) + end + @test isequal(rem(denorm_min, denorm_min), 0.0) + @test isequal(rem(denorm_min, -denorm_min), 0.0) + for val in values6 + @test isequal(rem(-denorm_min,val), -denorm_min) + end + @test isequal(rem(-denorm_min, denorm_min), -0.0) + @test isequal(rem(-denorm_min, -denorm_min), -0.0) + #Max value tests + values7 = [0x3p-1074,-0x3p-1074,0x3p-1073,-0x3p-1073] + for val in values7 + @test isequal(rem(0x1p1023,val), 0x1p-1073) + end + @test isequal(rem(0x1p1023, 0x3p-1022), 0x1p-1021) + @test isequal(rem(0x1p1023, -0x3p-1022), 0x1p-1021) + for val in values7 + @test isequal(rem(-0x1p1023,val), -0x1p-1073) + end + @test isequal(rem(-0x1p1023, 0x3p-1022), -0x1p-1021) + @test isequal(rem(-0x1p1023, -0x3p-1022), -0x1p-1021) + + end + + @testset "remf" begin + @test isequal(rem(Float32(0x1p127), Float32(0x3p-149)), Float32(0x1p-149)) + @test isequal(rem(Float32(0x1p127), -Float32(0x3p-149)), Float32(0x1p-149)) + @test isequal(rem(Float32(0x1p127), Float32(0x3p-148)), Float32(0x1p-147)) + @test isequal(rem(Float32(0x1p127), -Float32(0x3p-148)), Float32(0x1p-147)) + @test isequal(rem(Float32(0x1p127), Float32(0x3p-126)), Float32(0x1p-125)) + @test isequal(rem(Float32(0x1p127), -Float32(0x3p-126)), Float32(0x1p-125)) + @test isequal(rem(-Float32(0x1p127), Float32(0x3p-149)), -Float32(0x1p-149)) + @test isequal(rem(-Float32(0x1p127), -Float32(0x3p-149)), -Float32(0x1p-149)) + @test isequal(rem(-Float32(0x1p127), Float32(0x3p-148)), -Float32(0x1p-147)) + @test isequal(rem(-Float32(0x1p127), -Float32(0x3p-148)), -Float32(0x1p-147)) + @test isequal(rem(-Float32(0x1p127), Float32(0x3p-126)), -Float32(0x1p-125)) + @test isequal(rem(-Float32(0x1p127), -Float32(0x3p-126)), -Float32(0x1p-125)) + end + +end diff --git a/test/offsetarray.jl b/test/offsetarray.jl index 7621e14013627..c447c6d420f2a 100644 --- a/test/offsetarray.jl +++ b/test/offsetarray.jl @@ -3,11 +3,10 @@ isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl") using .Main.OffsetArrays import .Main.OffsetArrays: IdOffsetRange -using DelimitedFiles using Random using LinearAlgebra -using Statistics using Base: IdentityUnitRange +using Test if !isdefined(@__MODULE__, :T24Linear) include("testhelpers/arrayindexingtypes.jl") @@ -244,7 +243,7 @@ targets2 = ["(fill(1.0), fill(1.0))", end P = OffsetArray(rand(8,8), (1,1)) PV = view(P, 2:3, :) -@test endswith(summary(PV), "with indices Base.OneTo(2)×OffsetArrays.IdOffsetRange(2:9)") +@test endswith(summary(PV), "with indices Base.OneTo(2)×$(repr(axes(P,2)))") # Similar B = similar(A, Float32) @@ -415,6 +414,23 @@ rv = reverse(v) cv = copy(v) @test reverse!(cv) == rv +@testset "reverse! (issue #45870)" begin + @testset for n in [4,5] + offset = typemax(Int)-n + vo = OffsetArray([1:n;], offset) + vo2 = OffsetArray([1:n;], offset) + @test reverse!(vo) == OffsetArray(n:-1:1, offset) + @test reverse!(vo) == vo2 + @test_throws BoundsError reverse!(vo, firstindex(vo)-1, firstindex(vo)) + @test reverse!(vo, firstindex(vo), firstindex(vo)-1) == vo2 + @test reverse!(vo, firstindex(vo), firstindex(vo)) == vo2 + @test reverse!(vo, lastindex(vo), lastindex(vo)) == vo2 + @test reverse!(vo, lastindex(vo), lastindex(vo)+1) == vo2 # overflow in stop + @test reverse!(vo, firstindex(vo)+1) == OffsetArray([1;n:-1:2], offset) + @test reverse!(vo2, firstindex(vo)+1, lastindex(vo)-1) == OffsetArray([1;n-1:-1:2;n], offset) + end +end + A = OffsetArray(rand(4,4), (-3,5)) @test lastindex(A) == 16 @test lastindex(A, 1) == 1 @@ -458,13 +474,10 @@ I = findall(!iszero, z) @test findall(x->x>0, h) == [-1,1] @test findall(x->x<0, h) == [-2,0] @test findall(x->x==0, h) == [2] -@test mean(A_3_3) == median(A_3_3) == 5 -@test mean(x->2x, A_3_3) == 10 -@test mean(A_3_3, dims=1) == median(A_3_3, dims=1) == OffsetArray([2 5 8], A_3_3.offsets) -@test mean(A_3_3, dims=2) == median(A_3_3, dims=2) == OffsetArray(reshape([4,5,6],(3,1)), A_3_3.offsets) -@test var(A_3_3) == 7.5 -@test std(A_3_3, dims=1) == OffsetArray([1 1 1], A_3_3.offsets) -@test std(A_3_3, dims=2) == OffsetArray(reshape([3,3,3], (3,1)), A_3_3.offsets) +@test sum(A_3_3) == 45 +@test sum(x->2x, A_3_3) == 90 +@test sum(A_3_3, dims=1) == OffsetArray([6 15 24], A_3_3.offsets) +@test sum(A_3_3, dims=2) == OffsetArray(reshape([12,15,18],(3,1)), A_3_3.offsets) @test sum(OffsetArray(fill(1,3000), -1000)) == 3000 # https://github.com/JuliaArrays/OffsetArrays.jl/issues/92 @@ -494,11 +507,6 @@ B92 = view(A92, :, :, Base.IdentityUnitRange(-1:0)) end end -io = IOBuffer() -writedlm(io, A) -seek(io, 0) -@test readdlm(io, eltype(A)) == parent(A) - amin, amax = extrema(parent(A)) @test clamp.(A, (amax+amin)/2, amax).parent == clamp.(parent(A), (amax+amin)/2, amax) @@ -650,6 +658,14 @@ end @test last(v, 100) == v0 @test last(v, 100) !== v @test last(v, 1) == [v[end]] + + @testset "overflow (issue #45842)" begin + a = [2,3,4] + b = OffsetArray(a, 2:4) + @test first(a, typemax(Int)) == first(b, typemax(Int)) + b = OffsetArray(a, typemin(Int)) + @test last(a, 100) == last(b, 100) + end end @testset "Resizing OffsetVectors" begin @@ -788,7 +804,7 @@ end end end -@testset "proper patition for non-1-indexed vector" begin +@testset "proper partition for non-1-indexed vector" begin @test Iterators.partition(OffsetArray(1:10,10), 5) |> collect == [1:5,6:10] # OffsetVector @test Iterators.partition(OffsetArray(collect(1:10),10), 5) |> collect == [1:5,6:10] # OffsetVector @test Iterators.partition(OffsetArray(reshape(1:9,3,3), (3,3)), 5) |> collect == [1:5,6:9] #OffsetMatrix @@ -802,6 +818,22 @@ end @test reshape(a, (:,)) === a end +@testset "stack" begin + nought = OffsetArray([0, 0.1, 0.01], 0:2) + ten = OffsetArray([1,10,100,1000], 10:13) + + @test stack(ten) == ten + @test stack(ten .+ nought') == ten .+ nought' + @test stack(x^2 for x in ten) == ten.^2 + + @test axes(stack(nought for _ in ten)) == (0:2, 10:13) + @test axes(stack([nought for _ in ten])) == (0:2, 10:13) + @test axes(stack(nought for _ in ten; dims=1)) == (10:13, 0:2) + @test axes(stack((x, x^2) for x in nought)) == (1:2, 0:2) + @test axes(stack(x -> x[end-1:end], ten for _ in nought, _ in nought)) == (1:2, 0:2, 0:2) + @test axes(stack([ten[end-1:end] for _ in nought, _ in nought])) == (1:2, 0:2, 0:2) +end + @testset "issue #41630: replace_ref_begin_end!/@view on offset-like arrays" begin x = OffsetArray([1 2; 3 4], -10:-9, 9:10) # 2×2 OffsetArray{...} with indices -10:-9×9:10 @@ -822,3 +854,12 @@ end @test (@view x[end, -y[end]])[] == 3 @test (@view x[y[end], end])[] == 4 end + +@testset "CartesianIndices (issue #40035)" begin + A = OffsetArray(big(1):big(2), 0); + B = OffsetArray(1:2, 0); + # axes of an OffsetArray may be converted to an AbstractUnitRange, + # but the conversion to an OrdinalRange was not defined. + # this is fixed in #40038, so the evaluation of its CartesianIndices should work + @test CartesianIndices(A) == CartesianIndices(B) +end diff --git a/test/opaque_closure.jl b/test/opaque_closure.jl index 13cf5395ce1a2..e6490f5e9d345 100644 --- a/test/opaque_closure.jl +++ b/test/opaque_closure.jl @@ -1,7 +1,10 @@ using Test using InteractiveUtils +using Core: OpaqueClosure +using Base.Experimental: @opaque const_int() = 1 +const_int_barrier() = Base.inferencebarrier(1)::typeof(1) const lno = LineNumberNode(1, :none) @@ -11,7 +14,7 @@ let ci = @code_lowered const_int() Expr(:opaque_closure_method, nothing, 0, false, lno, ci))) end end -@test isa(oc_trivial(), Core.OpaqueClosure{Tuple{}, Any}) +@test isa(oc_trivial(), OpaqueClosure{Tuple{}, Any}) @test oc_trivial()() == 1 let ci = @code_lowered const_int() @@ -20,7 +23,7 @@ let ci = @code_lowered const_int() Expr(:opaque_closure_method, nothing, 0, false, lno, ci))) end end -@test isa(oc_simple_inf(), Core.OpaqueClosure{Tuple{}, Int}) +@test isa(oc_simple_inf(), OpaqueClosure{Tuple{}, Int}) @test oc_simple_inf()() == 1 struct OcClos2Int @@ -71,8 +74,8 @@ let ci = @code_lowered OcClos1Any(1)() :x)) end end -@test isa(oc_infer_pass_clos(1), Core.OpaqueClosure{Tuple{}, typeof(1)}) -@test isa(oc_infer_pass_clos("a"), Core.OpaqueClosure{Tuple{}, typeof("a")}) +@test isa(oc_infer_pass_clos(1), OpaqueClosure{Tuple{}, typeof(1)}) +@test isa(oc_infer_pass_clos("a"), OpaqueClosure{Tuple{}, typeof("a")}) @test oc_infer_pass_clos(1)() == 1 @test oc_infer_pass_clos("a")() == "a" @@ -114,8 +117,6 @@ let A = [1 2] end end -using Base.Experimental: @opaque - @test @opaque(x->2x)(8) == 16 let f = @opaque (x::Int, y::Float64)->(2x, 3y) @test_throws TypeError f(1, 1) @@ -127,18 +128,26 @@ end @test uses_frontend_opaque(10)(8) == 18 # World age mechanism +module test_world_age + +using Test +using Core: OpaqueClosure +using Base.Experimental: @opaque + function test_oc_world_age end mk_oc_world_age() = @opaque ()->test_oc_world_age() g_world_age = @opaque ()->test_oc_world_age() h_world_age = mk_oc_world_age() -@test isa(h_world_age, Core.OpaqueClosure{Tuple{}, Union{}}) +@test isa(h_world_age, OpaqueClosure{Tuple{}, Union{}}) test_oc_world_age() = 1 @test_throws MethodError g_world_age() @test_throws MethodError h_world_age() @test mk_oc_world_age()() == 1 g_world_age = @opaque ()->test_oc_world_age() @test g_world_age() == 1 -@test isa(mk_oc_world_age(), Core.OpaqueClosure{Tuple{}, Int}) +@test isa(mk_oc_world_age(), OpaqueClosure{Tuple{}, Int}) + +end # module test_world_age function maybe_vararg(isva::Bool) T = isva ? Vararg{Int} : Int @@ -169,33 +178,28 @@ mk_va_opaque() = @opaque (x...)->x @test mk_va_opaque()(1,2) == (1,2) # OpaqueClosure show method -@test repr(@opaque x->1) == "(::Any)::Any->◌" +@test repr(@opaque x->Base.inferencebarrier(1)) == "(::Any)::Any->◌" # Opaque closure in CodeInfo returned from generated functions -function mk_ocg(args...) - ci = @code_lowered const_int() - cig = Meta.lower(@__MODULE__, Expr(:new_opaque_closure, Tuple{}, Any, Any, - Expr(:opaque_closure_method, nothing, 0, false, lno, ci))).args[1] - cig.slotnames = Symbol[Symbol("#self#")] - cig.slottypes = Any[Any] - cig.slotflags = UInt8[0x00] - cig +let ci = @code_lowered const_int() + global function mk_ocg(world::UInt, source, args...) + @nospecialize + cig = Meta.lower(@__MODULE__, Expr(:new_opaque_closure, Tuple{}, Any, Any, + Expr(:opaque_closure_method, nothing, 0, false, lno, ci))).args[1] + 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 @eval function oc_trivial_generated() $(Expr(:meta, :generated_only)) - $(Expr(:meta, - :generated, - Expr(:new, - Core.GeneratedFunctionStub, - :mk_ocg, - Any[:oc_trivial_generated], - Any[], - @__LINE__, - QuoteNode(Symbol(@__FILE__)), - true))) -end -@test isa(oc_trivial_generated(), Core.OpaqueClosure{Tuple{}, Any}) + $(Expr(:meta, :generated, mk_ocg)) +end +@test isa(oc_trivial_generated(), OpaqueClosure{Tuple{}, Any}) @test oc_trivial_generated()() == 1 # Constprop through varargs OpaqueClosure @@ -227,6 +231,69 @@ const GLOBAL_OPAQUE_CLOSURE = @opaque () -> 123 call_global_opaque_closure() = GLOBAL_OPAQUE_CLOSURE() @test call_global_opaque_closure() == 123 +let foo::Int = 42 + Base.Experimental.@force_compile + oc = Base.Experimental.@opaque a::Int->sin(a) + cos(foo) + + @test only(Base.return_types(oc, (Int,))) === Float64 + code, rt = first(code_typed(oc, (Int,))) + @test rt === Float64 +end + let oc = @opaque a->sin(a) @test length(code_typed(oc, (Int,))) == 1 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 + oc = OpaqueClosure(ir) + @test oc(40, 2) == 42 + @test isa(oc, OpaqueClosure{Tuple{Int,Int}, Int}) + @test_throws TypeError oc("40", 2) + @test OpaqueClosure(ir)(40, 2) == 42 # the `OpaqueClosure(::IRCode)` constructor should be non-destructive +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 + 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 +end + +# variadic arguments +let src = code_typed((Int,Int)) do x, y... + return (x, y) + end |> only |> first + let oc = OpaqueClosure(src) + @test oc(1,2) === (1,(2,)) + @test_throws MethodError oc(1,2,3) + end + ir = Core.Compiler.inflate_ir(src) + let oc = OpaqueClosure(ir; isva=true) + @test oc(1,2) === (1,(2,)) + @test_throws MethodError oc(1,2,3) + end +end + +# Check for correct handling in case of broken return type. +eval_oc_dyn(oc) = Base.inferencebarrier(oc)() +eval_oc_spec(oc) = oc() +for f in (const_int, const_int_barrier) + ci = code_lowered(f, Tuple{})[1] + for compiled in (true, false) + oc_expr = Expr(:new_opaque_closure, Tuple{}, Union{}, Float64, + Expr(:opaque_closure_method, nothing, 0, false, lno, ci)) + oc_mismatch = let ci = code_lowered(f, Tuple{})[1] + if compiled + eval(:((()->$oc_expr)())) + else + eval(oc_expr) + end + end + @test isa(oc_mismatch, OpaqueClosure{Tuple{}, Union{}}) + @test_throws TypeError eval_oc_dyn(oc_mismatch) + @test_throws TypeError eval_oc_spec(oc_mismatch) + end +end diff --git a/test/operators.jl b/test/operators.jl index 97edebc0ea6f3..46cf6c7526299 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -2,6 +2,8 @@ using Random: randstring +include("compiler/irutils.jl") + @testset "ifelse" begin @test ifelse(true, 1, 2) == 1 @test ifelse(false, 1, 2) == 2 @@ -44,11 +46,11 @@ end p = 1=>:foo @test first(p) == 1 - @test last(p) == :foo - @test first(reverse(p)) == :foo + @test last(p) === :foo + @test first(reverse(p)) === :foo @test last(reverse(p)) == 1 @test lastindex(p) == 2 - @test p[lastindex(p)] == p[end] == p[2] == :foo + @test p[lastindex(p)] == p[end] == p[2] === :foo end # Infix `isa` @@ -91,6 +93,23 @@ end @test isless('a','b') +@testset "isless on pairs of integers (because there is a fastpath)" begin + @test isless((1,2), (1,3)) + @test isless((0,-2), (0,2)) + @test isless((-1,2), (1,2)) + @test isless((-1,-2), (1,2)) + @test !isless((1,3), (1,2)) + @test !isless((0,2), (0,-2)) + @test !isless((1,2), (-1,2)) + @test !isless((1,2), (-1,-2)) + @test !isless((-1,-2), (-1,-2)) + + @test isless((typemin(Int), typemin(Int)), (0,0)) + @test isless((1, 1), (Int8(2), Int8(2))) + @test !isless((UInt8(200),Int8(-1)), (UInt8(200),Int8(-1))) + @test isless((1, 1), (1, unsigned(2))) +end + @testset "isgreater" begin # isgreater should be compatible with min. min1(a, b) = Base.isgreater(a, b) ? b : a @@ -175,10 +194,30 @@ Base.promote_rule(::Type{T19714}, ::Type{Int}) = T19714 end +@testset "Nested ComposedFunction's stability" begin + f(x) = (1, 1, x...) + g = (f ∘ (f ∘ f)) ∘ (f ∘ f ∘ f) + @test (@inferred (g∘g)(1)) == ntuple(Returns(1), 25) + @test (@inferred g(1)) == ntuple(Returns(1), 13) + h = (-) ∘ (-) ∘ (-) ∘ (-) ∘ (-) ∘ (-) ∘ sum + @test (@inferred h((1, 2, 3); init = 0.0)) == 6.0 + issue_45877 = reduce(∘, fill(sin, 50)) + @test Core.Compiler.is_foldable(Base.infer_effects(Base.unwrap_composed, (typeof(issue_45877),))) + @test fully_eliminated() do + issue_45877(1.0) + end +end + @testset "function negation" begin str = randstring(20) @test filter(!isuppercase, str) == replace(str, r"[A-Z]" => "") @test filter(!islowercase, str) == replace(str, r"[a-z]" => "") + @test !!isnan === isnan + @test repr(!isnan) == "!isnan" + @test repr((-) ∘ sin) == "(-) ∘ sin" + @test repr(cos ∘ (sin ∘ tan)) == "cos ∘ (sin ∘ tan)" + @test repr(!(cos ∘ !sin)) == "!(cos ∘ !sin)" + @test repr(cos ∘ sin ∘ tan) == "cos ∘ sin ∘ tan" == repr((cos ∘ sin) ∘ tan) end # issue #19891 @@ -252,6 +291,9 @@ end end @test fldmod1(4.0, 3) == fldmod1(4, 3) + + # issue 28973 + @test fld1(0.4, 0.9) == fld1(nextfloat(0.4), 0.9) == 1.0 end @testset "Fix12" begin @@ -302,4 +344,18 @@ end val = [1,2,3] @test Returns(val)(1) === val @test sprint(show, Returns(1.0)) == "Returns{Float64}(1.0)" + + illtype = Vector{Core.TypeVar(:T)} + @test Returns(illtype) == Returns{DataType}(illtype) +end + +@testset "<= (issue #46327)" begin + struct A46327 <: Real end + Base.:(==)(::A46327, ::A46327) = false + Base.:(<)(::A46327, ::A46327) = false + @test !(A46327() <= A46327()) + struct B46327 <: Real end + Base.:(==)(::B46327, ::B46327) = true + Base.:(<)(::B46327, ::B46327) = false + @test B46327() <= B46327() end diff --git a/test/osutils.jl b/test/osutils.jl index 5f597292c5cc9..5e72675279cbc 100644 --- a/test/osutils.jl +++ b/test/osutils.jl @@ -1,4 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using Libdl @testset "Operating system predicates" begin @test !Sys.isunix(:Windows) @@ -50,9 +51,21 @@ end if Sys.iswindows() @testset "path variables use correct path delimiters on windows" begin for path in (Base.SYSCONFDIR, Base.DATAROOTDIR, Base.DOCDIR, - Base.LIBDIR, Base.PRIVATE_LIBDIR, Base.INCLUDEDIR, Base.LIBEXECDIR) + Base.LIBDIR, Base.PRIVATE_LIBDIR, Base.INCLUDEDIR, Base.LIBEXECDIR, Base.PRIVATE_LIBEXECDIR) @test !occursin("/", path) @test !occursin("\\\\", path) end end end + +if Sys.islinux() && Sys.which("readelf") !== nothing + @testset "stack is not marked as executable" begin + for f in intersect(dllist(), + [readdir(joinpath(Sys.BINDIR, Base.LIBDIR), join=true); + readdir(joinpath(Sys.BINDIR, Base.LIBDIR, "julia"), join=true)]) + for l in eachline(open(`readelf -l $f`)) + @test !(contains(l, "GNU_STACK") && contains(l, 'E')) + end + end + end +end diff --git a/test/parse.jl b/test/parse.jl index ae07936b3a18e..69092b2c4188d 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -41,6 +41,16 @@ Base.iterate(::Issue29451String, i::Integer=1) = i == 1 ? ('0', 2) : nothing @test Issue29451String() == "0" @test parse(Int, Issue29451String()) == 0 +# https://github.com/JuliaStrings/InlineStrings.jl/issues/57 +struct InlineStringIssue57 <: AbstractString end +Base.ncodeunits(::InlineStringIssue57) = 4 +Base.lastindex(::InlineStringIssue57) = 4 +Base.isvalid(::InlineStringIssue57, i::Integer) = 0 < i < 5 +Base.iterate(::InlineStringIssue57, i::Integer=1) = i == 1 ? ('t', 2) : i == 2 ? ('r', 3) : i == 3 ? ('u', 4) : i == 4 ? ('e', 5) : nothing +Base.:(==)(::SubString{InlineStringIssue57}, x::String) = x == "true" + +@test parse(Bool, InlineStringIssue57()) + @testset "Issue 20587, T=$T" for T in Any[BigInt, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8] T === BigInt && continue # TODO: make BigInt pass this test for s in ["", " ", " "] @@ -300,7 +310,7 @@ end @test eltype([tryparse(Complex{Int}, s) for s in String[]]) == Union{Nothing, Complex{Int}} end -@testset "isssue #29980" begin +@testset "issue #29980" begin @test parse(Bool, "1") === true @test parse(Bool, "01") === true @test parse(Bool, "0") === false diff --git a/test/path.jl b/test/path.jl index 31de4baffd1a0..2f4f2d0983a58 100644 --- a/test/path.jl +++ b/test/path.jl @@ -34,11 +34,11 @@ @test expanduser(S("x")) == "x" @test expanduser(S("~")) == (Sys.iswindows() ? "~" : homedir()) end - @testset "Base.contractuser" begin - @test Base.contractuser(S(homedir())) == (Sys.iswindows() ? homedir() : "~") - @test Base.contractuser(S(joinpath(homedir(), "x"))) == + @testset "contractuser" begin + @test contractuser(S(homedir())) == (Sys.iswindows() ? homedir() : "~") + @test contractuser(S(joinpath(homedir(), "x"))) == (Sys.iswindows() ? joinpath(homedir(), "x") : "~$(sep)x") - @test Base.contractuser(S("/foo/bar")) == "/foo/bar" + @test contractuser(S("/foo/bar")) == "/foo/bar" end @testset "isdirpath" begin @test !isdirpath(S("foo")) @@ -171,6 +171,9 @@ @test string(splitdrive(S(homedir()))...) == homedir() @test splitdrive("a\nb") == ("", "a\nb") + @test splitdir("a/\xfe/\n/b/c.ext") == ("a/\xfe/\n/b", "c.ext") + @test splitext("a/\xfe/\n/b/c.ext") == ("a/\xfe/\n/b/c", ".ext") + if Sys.iswindows() @test splitdrive(S("\\\\servername\\hello.world\\filename.ext")) == ("\\\\servername\\hello.world","\\filename.ext") diff --git a/test/precompile.jl b/test/precompile.jl index 411267705622d..606ee1087e51e 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1,5 +1,8 @@ # 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 Foo_module = :Foo4b3a94a1a081a8cb @@ -25,8 +28,18 @@ function precompile_test_harness(@nospecialize(f), separate::Bool) pushfirst!(DEPOT_PATH, load_cache_path) f(load_path) finally - rm(load_path, recursive=true, force=true) - separate && rm(load_cache_path, recursive=true, force=true) + 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 @@ -35,7 +48,7 @@ end # method root provenance -rootid(m::Module) = ccall(:jl_module_build_id, UInt64, (Any,), Base.parentmodule(m)) +rootid(m::Module) = Base.module_build_id(Base.parentmodule(m)) % UInt64 rootid(m::Method) = rootid(m.module) function root_provenance(m::Method, i::Int) @@ -83,7 +96,6 @@ function group_roots(iter::RLEIterator) return rootsby end - precompile_test_harness("basic precompile functionality") do dir2 precompile_test_harness(false) do dir Foo_file = joinpath(dir, "$Foo_module.jl") @@ -107,16 +119,18 @@ precompile_test_harness(false) do dir write(Foo2_file, """ module $Foo2_module - export override + export override, overridenc override(x::Integer) = 2 override(x::AbstractFloat) = Float64(override(1)) + overridenc(x::Integer) = rand()+1 + overridenc(x::AbstractFloat) = Float64(overridenc(1)) end """) write(Foo_file, """ module $Foo_module import $FooBase_module, $FooBase_module.typeA - import $Foo2_module: $Foo2_module, override + import $Foo2_module: $Foo2_module, override, overridenc import $FooBase_module.hash import Test module Inner @@ -156,10 +170,9 @@ precompile_test_harness(false) do dir # issue 16529 (adding a method to a type with no instances) (::Task)(::UInt8, ::UInt16, ::UInt32) = 2 - # issue 16471 (capturing references to a kwfunc) - Test.@test !isdefined(typeof(sin).name.mt, :kwsorter) + # issue 16471 Base.sin(::UInt8, ::UInt16, ::UInt32; x = 52) = x - const sinkw = Core.kwfunc(Base.sin) + const sinkw = Core.kwcall # issue 16908 (some complicated types and external method definitions) abstract type CategoricalPool{T, R <: Integer, V} end @@ -222,6 +235,8 @@ precompile_test_harness(false) do dir g() = override(1.0) Test.@test g() === 2.0 # compile this + gnc() = overridenc(1.0) + Test.@test 1 < gnc() < 5 # compile this const abigfloat_f() = big"12.34" const abigfloat_x = big"43.21" @@ -246,11 +261,12 @@ precompile_test_harness(false) do dir # check that @ccallable works from precompiled modules Base.@ccallable Cint f35014(x::Cint) = x+Cint(1) + + # check that Tasks work from serialized state + ch1 = Channel(x -> nothing) + ch2 = Channel(x -> (push!(x, 2); nothing), Inf) end """) - # make sure `sin` didn't have a kwfunc (which would invalidate the attempted test) - @test !isdefined(typeof(sin).name.mt, :kwsorter) - # Issue #12623 @test __precompile__(false) === nothing @@ -258,6 +274,8 @@ precompile_test_harness(false) do dir Foo2 = Base.require(Main, Foo2_module) @eval $Foo2.override(::Int) = 'a' @eval $Foo2.override(::Float32) = 'b' + @eval $Foo2.overridenc(::Int) = rand() + 97.0 + @eval $Foo2.overridenc(::Float32) = rand() + 100.0 Foo = Base.require(Main, Foo_module) Base.invokelatest() do # use invokelatest to see the results of loading the compile @@ -266,9 +284,13 @@ precompile_test_harness(false) do dir # Issue #21307 @test Foo.g() === 97.0 + @test 96 < Foo.gnc() < 99 @test Foo.override(1.0e0) == Float64('a') @test Foo.override(1.0f0) == 'b' @test Foo.override(UInt(1)) == 2 + @test 96 < Foo.overridenc(1.0e0) < 99 + @test 99 < Foo.overridenc(1.0f0) < 102 + @test 0 < Foo.overridenc(UInt(1)) < 3 # Issue #15722 @test Foo.abigfloat_f()::BigFloat == big"12.34" @@ -292,6 +314,13 @@ precompile_test_harness(false) do dir @test Foo.layout2 == Any[Ptr{Int8}(0), Ptr{Int16}(0), Ptr{Int32}(-1)] @test typeof.(Foo.layout2) == [Ptr{Int8}, Ptr{Int16}, Ptr{Int32}] @test Foo.layout3 == ["ab", "cd", "ef", "gh", "ij"] + + @test !isopen(Foo.ch1) + @test !isopen(Foo.ch2) + @test !isready(Foo.ch1) + @test isready(Foo.ch2) + @test take!(Foo.ch2) === 2 + @test !isready(Foo.ch2) end @eval begin function ccallable_test() @@ -310,12 +339,17 @@ precompile_test_harness(false) do dir cachedir = joinpath(dir, "compiled", "v$(VERSION.major).$(VERSION.minor)") cachedir2 = joinpath(dir2, "compiled", "v$(VERSION.major).$(VERSION.minor)") cachefile = joinpath(cachedir, "$Foo_module.ji") + if Base.JLOptions().use_pkgimages == 1 + ocachefile = Base.ocachefile_from_cachefile(cachefile) + else + ocachefile = nothing + end # 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 - ms = Base._require_from_serialized(cachefile) - @test isa(ms, Array{Any,1}) + m = Base._require_from_serialized(Base.PkgId(Foo), cachefile, ocachefile) + @test isa(m, Module) end end @@ -335,9 +369,9 @@ 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) + modules, (deps, requires), required_modules, _... = Base.parse_cache_header(cachefile) discard_module = mod_fl_mt -> (mod_fl_mt.filename, mod_fl_mt.mtime) - @test modules == [ Base.PkgId(Foo) => Base.module_build_id(Foo) ] + @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") ] @test requires == [ Base.PkgId(Foo) => Base.PkgId(string(FooBase_module)), Base.PkgId(Foo) => Base.PkgId(Foo2), @@ -360,23 +394,23 @@ precompile_test_harness(false) do dir Dict(let m = Base.root_module(Base, s) Base.PkgId(m) => Base.module_build_id(m) end for s in - [:ArgTools, :Artifacts, :Base64, :CRC32c, :Dates, :DelimitedFiles, - :Distributed, :Downloads, :FileWatching, :Future, :InteractiveUtils, - :LazyArtifacts, :LibCURL, :LibCURL_jll, :LibGit2, :Libdl, :LinearAlgebra, - :Logging, :Markdown, :Mmap, :MozillaCACerts_jll, :NetworkOptions, :Pkg, :Printf, - :Profile, :p7zip_jll, :REPL, :Random, :SHA, :Serialization, :SharedArrays, :Sockets, - :SparseArrays, :Statistics, :SuiteSparse, :TOML, :Tar, :Test, :UUIDs, :Unicode, + [:ArgTools, :Artifacts, :Base64, :CompilerSupportLibraries_jll, :CRC32c, :Dates, + :Downloads, :FileWatching, :Future, :InteractiveUtils, :libblastrampoline_jll, + :LibCURL, :LibCURL_jll, :LibGit2, :Libdl, :LinearAlgebra, + :Logging, :Markdown, :Mmap, :MozillaCACerts_jll, :NetworkOptions, :OpenBLAS_jll, :Pkg, :Printf, + :p7zip_jll, :REPL, :Random, :SHA, :Serialization, :Sockets, + :TOML, :Tar, :Test, :UUIDs, :Unicode, :nghttp2_jll] ), ) @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; srcfiles_only=true) @test map(x -> x.filename, deps) == [Foo_file] @test current_task()(0x01, 0x4000, 0x30031234) == 2 @test sin(0x01, 0x4000, 0x30031234) == 52 @test sin(0x01, 0x4000, 0x30031234; x = 9142) == 9142 - @test Foo.sinkw === Core.kwfunc(Base.sin) + @test Foo.sinkw === Core.kwcall @test Foo.NominalValue() == 1 @test Foo.OrdinalValue() == 1 @@ -433,7 +467,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 @@ -455,7 +489,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") @@ -489,18 +523,19 @@ precompile_test_harness(false) do dir end """) - cachefile = Base.compilecache(Base.PkgId("FooBar")) + cachefile, _ = 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")) - @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Vector + Tsc = Bool(Base.JLOptions().use_pkgimages) ? Tuple{<:Vector, String} : Tuple{<:Vector, Nothing} + @test Base.stale_cachefile(FooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @test !isdefined(Main, :FooBar) @test !isdefined(Main, :FooBar1) relFooBar_file = joinpath(dir, "subfolder", "..", "FooBar.jl") - @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa (Sys.iswindows() ? Vector : Bool) # `..` is not a symlink on Windows + @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa (Sys.iswindows() ? Tuple{<:Vector, String} : Bool) # `..` is not a symlink on Windows mkdir(joinpath(dir, "subfolder")) - @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa Vector + @test Base.stale_cachefile(relFooBar_file, joinpath(cachedir, "FooBar.ji")) isa Tsc @eval using FooBar fb_uuid = Base.module_build_id(FooBar) @@ -512,7 +547,7 @@ precompile_test_harness(false) do dir @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(FooBar1_file, joinpath(cachedir2, "FooBar1.ji")) isa Vector + @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) @test fb_uuid != fb_uuid1 @@ -585,11 +620,11 @@ precompile_test_harness(false) do dir end end -# method root provenance -# setindex!(::Dict{K,V}, ::Any, ::K) adds both compression and codegen roots +# method root provenance & external code caching precompile_test_harness("code caching") do dir Bid = rootid(Base) Cache_module = :Cacheb8321416e8a3e2f1 + # Note: calling setindex!(::Dict{K,V}, ::Any, ::K) adds both compression and codegen roots write(joinpath(dir, "$Cache_module.jl"), """ module $Cache_module @@ -619,33 +654,56 @@ precompile_test_harness("code caching") do dir Base.compilecache(Base.PkgId(string(Cache_module))) @eval using $Cache_module M = getfield(@__MODULE__, Cache_module) + # Test that this cache file "owns" all the roots Mid = rootid(M) for name in (:f, :fpush, :callboth) func = getfield(M, name) m = only(collect(methods(func))) @test all(i -> root_provenance(m, i) == Mid, 1:length(m.roots)) end + # Check that we can cache external CodeInstances: + # length(::Vector) has an inferred specialization for `Vector{X}` + msize = which(length, (Vector{<:Any},)) + hasspec = false + for mi in Base.specializations(msize) + if mi.specTypes == Tuple{typeof(length),Vector{Cacheb8321416e8a3e2f1.X}} + if (isdefined(mi, :cache) && isa(mi.cache, Core.CodeInstance) && + mi.cache.max_world == typemax(UInt) && mi.cache.inferred !== nothing) + hasspec = true + break + end + end + end + @test hasspec + # Test that compilation adds to method roots with appropriate provenance m = which(setindex!, (Dict{M.X,Any}, Any, M.X)) - @test_broken M.X ∈ m.roots # requires caching external compilation results + @test 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 groups = group_roots(m) - @test_broken M.X ∈ groups[Mid] # requires caching external compilation results - @test M.X2 ∈ groups[rootid(@__MODULE__)] + @test M.X ∈ groups[Mid] # attributed to M + @test 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,)) - mi = minternal.specializations[1] + mi = minternal.specializations::Core.MethodInstance + @test mi.specTypes == Tuple{typeof(M.getelsize),Vector{Int32}} ci = mi.cache @test ci.relocatability == 1 + @test ci.inferred !== nothing + # ...and that we can add "untracked" roots & non-relocatable CodeInstances to them too Base.invokelatest() do M.getelsize(M.X2[]) end - mi = minternal.specializations[2] + mispecs = minternal.specializations::Core.SimpleVector + @test mispecs[1] === mi + mi = mispecs[2]::Core.MethodInstance ci = mi.cache @test ci.relocatability == 0 - # PkgA loads PkgB, and both add roots to the same method (both before and after loading B) + # PkgA loads PkgB, and both add roots to the same `push!` method (both before and after loading B) Cache_module2 = :Cachea1544c83560f0c99 write(joinpath(dir, "$Cache_module2.jl"), """ @@ -677,11 +735,388 @@ precompile_test_harness("code caching") do dir end mT = which(push!, (Vector{T} where T, Any)) groups = group_roots(mT) - # all below require caching external CodeInstances - @test_broken M2.Y ∈ groups[M2id] - @test_broken M2.Z ∈ groups[M2id] - @test_broken M.X ∈ groups[Mid] - @test_broken M.X ∉ groups[M2id] + @test M2.Y ∈ groups[M2id] + @test M2.Z ∈ groups[M2id] + @test M.X ∈ groups[Mid] + @test 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? + RootModule = :Root_0xab07d60518763a7e + write(joinpath(dir, "$RootModule.jl"), + """ + module $RootModule + function f(x) + while x < 10 + x += oftype(x, 1) + end + return x + end + g1() = f(Int16(9)) + g2() = f(Int16(9)) + # all deliberately uncompiled + end + """) + RootA = :RootA_0xab07d60518763a7e + write(joinpath(dir, "$RootA.jl"), + """ + module $RootA + using $RootModule + fA() = $RootModule.f(Int8(4)) + fA() + $RootModule.g1() + end + """) + RootB = :RootB_0xab07d60518763a7e + write(joinpath(dir, "$RootB.jl"), + """ + module $RootB + using $RootModule + fB() = $RootModule.f(Int8(4)) + fB() + $RootModule.g2() + end + """) + Base.compilecache(Base.PkgId(string(RootA))) + Base.compilecache(Base.PkgId(string(RootB))) + @eval using $RootA + @eval using $RootB + MA = getfield(@__MODULE__, RootA) + MB = getfield(@__MODULE__, RootB) + M = getfield(MA, RootModule) + m = which(M.f, (Any,)) + for mi in Base.specializations(m) + mi === nothing && continue + mi = mi::Core.MethodInstance + if mi.specTypes.parameters[2] === Int8 + # external callers + mods = Module[] + for be in mi.backedges + push!(mods, be.def.module) + end + @test MA ∈ mods + @test MB ∈ mods + @test length(mods) == 2 + elseif mi.specTypes.parameters[2] === Int16 + # internal callers + meths = Method[] + for be in mi.backedges + push!(meths, be.def) + end + @test which(M.g1, ()) ∈ meths + @test which(M.g2, ()) ∈ meths + @test length(meths) == 2 + end + end + + # Invalidations (this test is adapted from SnoopCompile) + function hasvalid(mi, world) + isdefined(mi, :cache) || return false + ci = mi.cache + while true + ci.max_world >= world && return true + isdefined(ci, :next) || return false + ci = ci.next + end + end + + StaleA = :StaleA_0xab07d60518763a7e + StaleB = :StaleB_0xab07d60518763a7e + StaleC = :StaleC_0xab07d60518763a7e + write(joinpath(dir, "$StaleA.jl"), + """ + module $StaleA + + stale(x) = rand(1:8) + stale(x::Int) = length(digits(x)) + + not_stale(x::String) = first(x) + + use_stale(c) = stale(c[1]) + not_stale("hello") + build_stale(x) = use_stale(Any[x]) + + # force precompilation + build_stale(37) + stale('c') + + ## Reporting tests (unrelated to the above) + nbits(::Int8) = 8 + nbits(::Int16) = 16 + + end + """ + ) + write(joinpath(dir, "$StaleB.jl"), + """ + module $StaleB + + # StaleB does not know about StaleC when it is being built. + # However, if StaleC is loaded first, we get `"jl_insert_method_instance"` + # invalidations. + using $StaleA + + # This will be invalidated if StaleC is loaded + useA() = $StaleA.stale("hello") + useA2() = useA() + + # force precompilation + begin + Base.Experimental.@force_compile + useA2() + end + + ## Reporting tests + call_nbits(x::Integer) = $StaleA.nbits(x) + map_nbits() = map(call_nbits, Integer[Int8(1), Int16(1)]) + map_nbits() + + end + """ + ) + write(joinpath(dir, "$StaleC.jl"), + """ + module $StaleC + + using $StaleA + + $StaleA.stale(x::String) = length(x) + call_buildstale(x) = $StaleA.build_stale(x) + + call_buildstale("hey") + + end # module + """ + ) + for pkg in (StaleA, StaleB, StaleC) + Base.compilecache(Base.PkgId(string(pkg))) + end + @eval using $StaleA + MA = getfield(@__MODULE__, StaleA) + Base.eval(MA, :(nbits(::UInt8) = 8)) + @eval using $StaleC + invalidations = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1) + @eval using $StaleB + ccall(:jl_debug_method_invalidation, Any, (Cint,), 0) + MB = getfield(@__MODULE__, StaleB) + MC = getfield(@__MODULE__, StaleC) + world = Base.get_world_counter() + m = only(methods(MA.use_stale)) + mi = m.specializations::Core.MethodInstance + @test hasvalid(mi, world) # it was re-inferred by StaleC + m = only(methods(MA.build_stale)) + mis = filter(!isnothing, collect(m.specializations::Core.SimpleVector)) + @test length(mis) == 2 + for mi in mis + mi = mi::Core.MethodInstance + if mi.specTypes.parameters[2] == Int + @test mi.cache.max_world < world + else + # The variant for String got "healed" by recompilation in StaleC + @test mi.specTypes.parameters[2] == String + @test mi.cache.max_world == typemax(UInt) + end + end + m = only(methods(MB.useA)) + mi = m.specializations::Core.MethodInstance + @test !hasvalid(mi, world) # invalidated by the stale(x::String) method in StaleC + m = only(methods(MC.call_buildstale)) + mi = m.specializations::Core.MethodInstance + @test hasvalid(mi, world) # was compiled with the new method + + # Reporting test (ensure SnoopCompile works) + @test all(i -> isassigned(invalidations, i), eachindex(invalidations)) + m = only(methods(MB.call_nbits)) + for mi in Base.specializations(m) + hv = hasvalid(mi, world) + @test mi.specTypes.parameters[end] === Integer ? !hv : hv + end + + setglobal!(Main, :inval, invalidations) + idxs = findall(==("verify_methods"), invalidations) + idxsbits = filter(idxs) do i + mi = invalidations[i-1] + mi.def == m + end + idx = only(idxsbits) + tagbad = invalidations[idx+1] + @test isa(tagbad, Int32) + j = findfirst(==(tagbad), invalidations) + @test invalidations[j-1] == "insert_backedges_callee" + @test isa(invalidations[j-2], Type) + @test isa(invalidations[j+1], Vector{Any}) # [nbits(::UInt8)] + m = only(methods(MB.useA2)) + mi = only(Base.specializations(m)) + @test !hasvalid(mi, world) + @test mi ∈ invalidations + + m = only(methods(MB.map_nbits)) + @test !hasvalid(m.specializations::Core.MethodInstance, world+1) # insert_backedges invalidations also trigger their backedges +end + +precompile_test_harness("invoke") do dir + InvokeModule = :Invoke0x030e7e97c2365aad + CallerModule = :Caller0x030e7e97c2365aad + write(joinpath(dir, "$InvokeModule.jl"), + """ + module $InvokeModule + export f, g, h, q, fnc, gnc, hnc, qnc # nc variants do not infer to a Const + export f44320, g44320 + export getlast + # f is for testing invoke that occurs within a dependency + f(x::Real) = 0 + f(x::Int) = x < 5 ? 1 : invoke(f, Tuple{Real}, x) + fnc(x::Real) = rand()-1 + fnc(x::Int) = x < 5 ? rand()+1 : invoke(fnc, Tuple{Real}, x) + # g is for testing invoke that occurs from a dependent + g(x::Real) = 0 + g(x::Int) = 1 + gnc(x::Real) = rand()-1 + gnc(x::Int) = rand()+1 + # h will be entirely superseded by a new method (full invalidation) + h(x::Real) = 0 + h(x::Int) = x < 5 ? 1 : invoke(h, Tuple{Integer}, x) + hnc(x::Real) = rand()-1 + hnc(x::Int) = x < 5 ? rand()+1 : invoke(hnc, Tuple{Integer}, x) + # q will have some callers invalidated + q(x::Integer) = 0 + qnc(x::Integer) = rand()-1 + # Issue #44320 + f44320(::Int) = 1 + f44320(::Any) = 2 + g44320() = invoke(f44320, Tuple{Any}, 0) + g44320() + + # Adding new specializations should not invalidate `invoke`s + function getlast(itr) + x = nothing + for y in itr + x = y + end + return x + end + getlast(a::AbstractArray) = invoke(getlast, Tuple{Any}, a) + end + """) + write(joinpath(dir, "$CallerModule.jl"), + """ + module $CallerModule + using $InvokeModule + # involving external modules + callf(x) = f(x) + callg(x) = x < 5 ? g(x) : invoke(g, Tuple{Real}, x) + callh(x) = h(x) + callq(x) = q(x) + callqi(x) = invoke(q, Tuple{Integer}, x) + callfnc(x) = fnc(x) + callgnc(x) = x < 5 ? gnc(x) : invoke(gnc, Tuple{Real}, x) + callhnc(x) = hnc(x) + callqnc(x) = qnc(x) + callqnci(x) = invoke(qnc, Tuple{Integer}, x) + + # Purely internal + internal(x::Real) = 0 + internal(x::Int) = x < 5 ? 1 : invoke(internal, Tuple{Real}, x) + internalnc(x::Real) = rand()-1 + internalnc(x::Int) = x < 5 ? rand()+1 : invoke(internalnc, Tuple{Real}, x) + + # Issue #44320 + f44320(::Real) = 3 + + call_getlast(x) = getlast(x) + + # force precompilation + begin + Base.Experimental.@force_compile + callf(3) + callg(3) + callh(3) + callq(3) + callqi(3) + callfnc(3) + callgnc(3) + callhnc(3) + callqnc(3) + callqnci(3) + internal(3) + internalnc(3) + call_getlast([1,2,3]) + end + + # Now that we've precompiled, invalidate with a new method that overrides the `invoke` dispatch + $InvokeModule.h(x::Integer) = -1 + $InvokeModule.hnc(x::Integer) = rand() - 20 + # ...and for q, override with a more specialized method that should leave only the invoked version still valid + $InvokeModule.q(x::Int) = -1 + $InvokeModule.qnc(x::Int) = rand()+1 + end + """) + Base.compilecache(Base.PkgId(string(CallerModule))) + @eval using $InvokeModule: $InvokeModule + MI = getfield(@__MODULE__, InvokeModule) + @eval $MI.getlast(a::UnitRange) = a.stop + @eval using $CallerModule + M = getfield(@__MODULE__, CallerModule) + + function get_method_for_type(func, @nospecialize(T)) # return the method func(::T) + for m in methods(func) + m.sig.parameters[end] === T && return m + end + error("no ::Real method found for $func") + end + function nvalid(mi::Core.MethodInstance) + isdefined(mi, :cache) || return 0 + ci = mi.cache + n = Int(ci.max_world == typemax(UInt)) + while isdefined(ci, :next) + ci = ci.next + n += ci.max_world == typemax(UInt) + end + return n + end + + for func in (M.f, M.g, M.internal, M.fnc, M.gnc, M.internalnc) + m = get_method_for_type(func, Real) + mi = m.specializations::Core.MethodInstance + @test length(mi.backedges) == 2 + @test mi.backedges[1] === Tuple{typeof(func), Real} + @test isa(mi.backedges[2], Core.MethodInstance) + @test mi.cache.max_world == typemax(mi.cache.max_world) + end + for func in (M.q, M.qnc) + m = get_method_for_type(func, Integer) + mi = m.specializations::Core.MethodInstance + @test length(mi.backedges) == 2 + @test mi.backedges[1] === Tuple{typeof(func), Integer} + @test isa(mi.backedges[2], Core.MethodInstance) + @test mi.cache.max_world == typemax(mi.cache.max_world) + end + + m = get_method_for_type(M.h, Real) + @test isempty(Base.specializations(m)) + m = get_method_for_type(M.hnc, Real) + @test isempty(Base.specializations(m)) + m = only(methods(M.callq)) + @test isempty(Base.specializations(m)) || nvalid(m.specializations::Core.MethodInstance) == 0 + m = only(methods(M.callqnc)) + @test isempty(Base.specializations(m)) || nvalid(m.specializations::Core.MethodInstance) == 0 + m = only(methods(M.callqi)) + @test (m.specializations::Core.MethodInstance).specTypes == Tuple{typeof(M.callqi), Int} + m = only(methods(M.callqnci)) + @test (m.specializations::Core.MethodInstance).specTypes == Tuple{typeof(M.callqnci), Int} + + m = only(methods(M.g44320)) + @test (m.specializations::Core.MethodInstance).cache.max_world == typemax(UInt) + + m = which(MI.getlast, (Any,)) + @test (m.specializations::Core.MethodInstance).cache.max_world == typemax(UInt) + + # Precompile specific methods for arbitrary arg types + invokeme(x) = 1 + invokeme(::Int) = 2 + m_any, m_int = sort(collect(methods(invokeme)); by=m->(m.file,m.line)) + @test precompile(invokeme, (Int,), m_any) + @test (m_any.specializations::Core.MethodInstance).specTypes === Tuple{typeof(invokeme), Int} + @test isempty(Base.specializations(m_int)) end # test --compiled-modules=no command line option @@ -863,7 +1298,11 @@ end end finally cd(save_cwd) - rm(temp_path, recursive=true) + try + rm(temp_path, recursive=true) + catch err + @show err + end pop!(test_workers) # remove myid rmprocs(test_workers) end @@ -880,14 +1319,22 @@ precompile_test_harness("delete_method") do dir """ module $A_module - export apc, anopc + export apc, anopc, apcnc, anopcnc + # Infer to a const apc(::Int, ::Int) = 1 apc(::Any, ::Any) = 2 anopc(::Int, ::Int) = 1 anopc(::Any, ::Any) = 2 + # Do not infer to a const + apcnc(::Int, ::Int) = rand() - 1 + apcnc(::Any, ::Any) = rand() + 1 + + anopcnc(::Int, ::Int) = rand() - 1 + anopcnc(::Any, ::Any) = rand() + 1 + end """) write(B_file, @@ -898,19 +1345,26 @@ precompile_test_harness("delete_method") do dir bpc(x) = apc(x, x) bnopc(x) = anopc(x, x) + bpcnc(x) = apcnc(x, x) + bnopcnc(x) = anopcnc(x, x) precompile(bpc, (Int,)) precompile(bpc, (Float64,)) + precompile(bpcnc, (Int,)) + precompile(bpcnc, (Float64,)) end """) A = Base.require(Main, A_module) - for mths in (collect(methods(A.apc)), collect(methods(A.anopc))) - Base.delete_method(mths[1]) + for mths in (collect(methods(A.apc)), collect(methods(A.anopc)), collect(methods(A.apcnc)), collect(methods(A.anopcnc))) + idx = findfirst(m -> m.sig.parameters[end] === Int, mths) + Base.delete_method(mths[idx]) end B = Base.require(Main, B_module) - @test Base.invokelatest(B.bpc, 1) == Base.invokelatest(B.bpc, 1.0) == 2 - @test Base.invokelatest(B.bnopc, 1) == Base.invokelatest(B.bnopc, 1.0) == 2 + for f in (B.bpc, B.bnopc, B.bpcnc, B.bnopcnc) + @test Base.invokelatest(f, 1) > 1 + @test Base.invokelatest(f, 1.0) > 1 + end end precompile_test_harness("Issues #19030 and #25279") do load_path @@ -988,13 +1442,13 @@ precompile_test_harness("Issue #25971") do load_path sourcefile = joinpath(load_path, "Foo25971.jl") write(sourcefile, "module Foo25971 end") chmod(sourcefile, 0o666) - cachefile = Base.compilecache(Base.PkgId("Foo25971")) + cachefile, _ = Base.compilecache(Base.PkgId("Foo25971")) @test filemode(sourcefile) == filemode(cachefile) chmod(sourcefile, 0o600) - cachefile = Base.compilecache(Base.PkgId("Foo25971")) + cachefile, _ = Base.compilecache(Base.PkgId("Foo25971")) @test filemode(sourcefile) == filemode(cachefile) chmod(sourcefile, 0o444) - cachefile = Base.compilecache(Base.PkgId("Foo25971")) + cachefile, _ = Base.compilecache(Base.PkgId("Foo25971")) # Check writable @test touch(cachefile) == cachefile end @@ -1067,10 +1521,10 @@ precompile_test_harness("No external edges") do load_path Base.compilecache(Base.PkgId("NoExternalEdges")) @eval begin using NoExternalEdges - @test only(methods(NoExternalEdges.foo1)).specializations[1].cache.max_world != 0 - @test only(methods(NoExternalEdges.foo2)).specializations[1].cache.max_world != 0 - @test only(methods(NoExternalEdges.foo3)).specializations[1].cache.max_world != 0 - @test only(methods(NoExternalEdges.foo4)).specializations[1].cache.max_world != 0 + @test (only(methods(NoExternalEdges.foo1)).specializations::Core.MethodInstance).cache.max_world != 0 + @test (only(methods(NoExternalEdges.foo2)).specializations::Core.MethodInstance).cache.max_world != 0 + @test (only(methods(NoExternalEdges.foo3)).specializations::Core.MethodInstance).cache.max_world != 0 + @test (only(methods(NoExternalEdges.foo4)).specializations::Core.MethodInstance).cache.max_world != 0 end end @@ -1081,11 +1535,236 @@ end f(x, y) = x + y f(x::Int, y) = 2x + y end - precompile(M.f, (Int, Any)) - precompile(M.f, (AbstractFloat, Any)) + @test precompile(M.f, (Int, Any)) + @test precompile(M.f, (AbstractFloat, Any)) mis = map(methods(M.f)) do m - m.specializations[1] + m.specializations::Core.MethodInstance end @test any(mi -> mi.specTypes.parameters[2] === Any, mis) @test all(mi -> isa(mi.cache, Core.CodeInstance), mis) end + +# Test that the cachepath is available in pkgorigins during the +# __init__ callback +precompile_test_harness("__init__ cachepath") do load_path + write(joinpath(load_path, "InitCachePath.jl"), + """ + module InitCachePath + __init__() = Base.pkgorigins[Base.PkgId(InitCachePath)] + end + """) + @test isa((@eval (using InitCachePath; InitCachePath)), Module) +end + +# Test that precompilation can handle invalidated methods created from `precompile`, +# not via backedges. +precompile_test_harness("Issue #46558") do load_path + write(joinpath(load_path, "Foo46558.jl"), + """ + module Foo46558 + foo(x::Real) = 1 + end + """) + write(joinpath(load_path, "Bar46558.jl"), + """ + module Bar46558 + using Foo46558 + precompile(Foo46558.foo, (Int,)) + end + """) + Base.compilecache(Base.PkgId("Foo46558")) + Base.compilecache(Base.PkgId("Bar46558")) + Foo = (@eval (using Foo46558; Foo46558)) + @eval ($Foo.foo)(x::Int) = 2 + Bar = (@eval (using Bar46558; Bar46558)) + @test (@eval $Foo.foo(1)) == 2 +end + +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) + + __init__() = @assert ci isa Core.CodeInstance + + end + """) + Base.compilecache(Base.PkgId("CodeInstancePrecompile")) + (@eval (using CodeInstancePrecompile)) +end + +precompile_test_harness("Recursive types") do load_path + write(joinpath(load_path, "RecursiveTypeDef.jl"), + """ + module RecursiveTypeDef + + struct C{T,O} end + struct A{T,N,O} <: AbstractArray{C{T,A{T,N,O}},N} + sz::NTuple{N,Int} + end + + end + """) + Base.compilecache(Base.PkgId("RecursiveTypeDef")) + (@eval (using RecursiveTypeDef)) + a = Base.invokelatest(RecursiveTypeDef.A{Float64,2,String}, (3, 3)) + @test isa(a, AbstractArray) +end + +@testset "issue 46778" begin + f46778(::Any, ::Type{Int}) = 1 + f46778(::Any, ::DataType) = 2 + @test precompile(Tuple{typeof(f46778), Int, DataType}) + @test (which(f46778, Tuple{Any,DataType}).specializations::Core.MethodInstance).cache.invoke != C_NULL +end + + +precompile_test_harness("Module tparams") do load_path + write(joinpath(load_path, "ModuleTparams.jl"), + """ + module ModuleTparams + module TheTParam + end + + struct ParamStruct{T}; end + const the_struct = ParamStruct{TheTParam}() + end + """) + Base.compilecache(Base.PkgId("ModuleTparams")) + (@eval (using ModuleTparams)) + @test ModuleTparams.the_struct === Base.invokelatest(ModuleTparams.ParamStruct{ModuleTparams.TheTParam}) +end + +precompile_test_harness("PkgCacheInspector") do load_path + # Test functionality needed by PkgCacheInspector.jl + write(joinpath(load_path, "PCI.jl"), + """ + module PCI + Base.repl_cmd() = 55 # external method + f() = Base.repl_cmd(7, "hello") # external specialization (should never exist otherwise) + try + f() + catch + end + end + """) + cachefile, ocachefile = Base.compilecache(Base.PkgId("PCI")) + + # Get the depmods + local depmods + @lock Base.require_lock begin + local depmodnames + io = open(cachefile, "r") + 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] + Base.isvalid_file_crc(io) || throw(ArgumentError("Invalid checksum in cache file $cachefile.")) + finally + close(io) + end + ndeps = length(depmodnames) + depmods = Vector{Any}(undef, ndeps) + for i in 1:ndeps + modkey, build_id = depmodnames[i] + dep = Base._tryrequire_from_serialized(modkey, build_id) + if !isa(dep, Module) + return dep + end + depmods[i] = dep + end + end + + if ocachefile !== nothing + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring), ocachefile, depmods, true, "PCI") + 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) + @test m.name == :repl_cmd && m.nargs < 2 + @test any(new_specializations) do ci + mi = ci.def + mi.specTypes == Tuple{typeof(Base.repl_cmd), Int, String} + end +end + +precompile_test_harness("DynamicExpressions") do load_path + # https://github.com/JuliaLang/julia/pull/47184#issuecomment-1364716312 + write(joinpath(load_path, "Float16MWE.jl"), + """ + module Float16MWE + struct Node{T} + val::T + end + doconvert(::Type{<:Node}, val) = convert(Float16, val) + precompile(Tuple{typeof(doconvert), Type{Node{Float16}}, Float64}) + end # module Float16MWE + """) + Base.compilecache(Base.PkgId("Float16MWE")) + @eval using Float16MWE + @test @invokelatest(Float16MWE.doconvert(Float16MWE.Node{Float16}, -1.2)) === Float16(-1.2) +end + +precompile_test_harness("BadInvalidations") do load_path + write(joinpath(load_path, "BadInvalidations.jl"), + """ + module BadInvalidations + Base.Experimental.@compiler_options compile=min optimize=1 + getval() = Base.a_method_to_overwrite_in_test() + getval() + end # module BadInvalidations + """) + Base.compilecache(Base.PkgId("BadInvalidations")) + @eval Base a_method_to_overwrite_in_test() = inferencebarrier(2) + @eval using BadInvalidations + @test Base.invokelatest(BadInvalidations.getval) === 2 +end + +# https://github.com/JuliaLang/julia/issues/48074 +precompile_test_harness("WindowsCacheOverwrite") do load_path + # https://github.com/JuliaLang/julia/pull/47184#issuecomment-1364716312 + write(joinpath(load_path, "WindowsCacheOverwrite.jl"), + """ + module WindowsCacheOverwrite + end # module + """) + ji, ofile = Base.compilecache(Base.PkgId("WindowsCacheOverwrite")) + @eval using WindowsCacheOverwrite + + write(joinpath(load_path, "WindowsCacheOverwrite.jl"), + """ + module WindowsCacheOverwrite + f() = "something new" + end # module + """) + + ji_2, ofile_2 = Base.compilecache(Base.PkgId("WindowsCacheOverwrite")) + @test ofile_2 == Base.ocachefile_from_cachefile(ji_2) +end + +precompile_test_harness("Issue #48391") do load_path + write(joinpath(load_path, "I48391.jl"), + """ + module I48391 + struct SurrealFinite <: Real end + precompile(Tuple{typeof(Base.isless), SurrealFinite, SurrealFinite}) + Base.:(<)(x::SurrealFinite, y::SurrealFinite) = "good" + end + """) + ji, ofile = Base.compilecache(Base.PkgId("I48391")) + @eval using I48391 + x = Base.invokelatest(I48391.SurrealFinite) + @test Base.invokelatest(isless, x, x) === "good" + @test_throws ErrorException isless(x, x) +end + +empty!(Base.DEPOT_PATH) +append!(Base.DEPOT_PATH, original_depot_path) +empty!(Base.LOAD_PATH) +append!(Base.LOAD_PATH, original_load_path) diff --git a/test/project/Extensions/EnvWithDeps/Manifest.toml b/test/project/Extensions/EnvWithDeps/Manifest.toml new file mode 100644 index 0000000000000..85ff259f0a4d5 --- /dev/null +++ b/test/project/Extensions/EnvWithDeps/Manifest.toml @@ -0,0 +1,21 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0-rc3" +manifest_format = "2.0" +project_hash = "ec25ff8df3a5e2212a173c3de2c7d716cc47cd36" + +[[deps.ExtDep]] +deps = ["SomePackage"] +path = "../ExtDep.jl" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" + +[[deps.ExtDep2]] +path = "../ExtDep2" +uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +version = "0.1.0" + +[[deps.SomePackage]] +path = "../SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +version = "0.1.0" diff --git a/test/project/Extensions/EnvWithDeps/Project.toml b/test/project/Extensions/EnvWithDeps/Project.toml new file mode 100644 index 0000000000000..cf020b56fc2e8 --- /dev/null +++ b/test/project/Extensions/EnvWithDeps/Project.toml @@ -0,0 +1,4 @@ +[deps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +SomePackage = "678608ae-7bb3-42c7-98b1-82102067a3d8" diff --git a/test/project/Extensions/EnvWithHasExtensions/Manifest.toml b/test/project/Extensions/EnvWithHasExtensions/Manifest.toml new file mode 100644 index 0000000000000..8ac961fa1a9a9 --- /dev/null +++ b/test/project/Extensions/EnvWithHasExtensions/Manifest.toml @@ -0,0 +1,29 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0-beta4" +manifest_format = "2.0" +project_hash = "caa716752e6dff3d77c3de929ebbb5d2024d04ef" + +[[deps.ExtDep]] +deps = ["SomePackage"] +path = "../ExtDep.jl" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" + +[[deps.HasExtensions]] +path = "../HasExtensions.jl" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.1.0" + + [deps.HasExtensions.extensions] + Extension = "ExtDep" + ExtensionFolder = ["ExtDep", "ExtDep2"] + + [deps.HasExtensions.weakdeps] + ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" + ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" + +[[deps.SomePackage]] +path = "../SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +version = "0.1.0" diff --git a/test/project/Extensions/EnvWithHasExtensions/Project.toml b/test/project/Extensions/EnvWithHasExtensions/Project.toml new file mode 100644 index 0000000000000..8639881ae95c0 --- /dev/null +++ b/test/project/Extensions/EnvWithHasExtensions/Project.toml @@ -0,0 +1,4 @@ +[deps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +HasExtensions = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +SomePackage = "678608ae-7bb3-42c7-98b1-82102067a3d8" diff --git a/test/project/Extensions/EnvWithHasExtensionsv2/Manifest.toml b/test/project/Extensions/EnvWithHasExtensionsv2/Manifest.toml new file mode 100644 index 0000000000000..66781a5701363 --- /dev/null +++ b/test/project/Extensions/EnvWithHasExtensionsv2/Manifest.toml @@ -0,0 +1,25 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.0-DEV" +manifest_format = "2.0" +project_hash = "caa716752e6dff3d77c3de929ebbb5d2024d04ef" + +[[deps.ExtDep]] +deps = ["SomePackage"] +path = "../ExtDep.jl" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" + +[[deps.HasExtensions]] +path = "../HasExtensions_v2.jl" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.2.0" +weakdeps = ["ExtDep"] + + [deps.HasExtensions.extensions] + Extension2 = "ExtDep" + +[[deps.SomePackage]] +path = "../SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +version = "0.1.0" diff --git a/test/project/Extensions/EnvWithHasExtensionsv2/Project.toml b/test/project/Extensions/EnvWithHasExtensionsv2/Project.toml new file mode 100644 index 0000000000000..8639881ae95c0 --- /dev/null +++ b/test/project/Extensions/EnvWithHasExtensionsv2/Project.toml @@ -0,0 +1,4 @@ +[deps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +HasExtensions = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +SomePackage = "678608ae-7bb3-42c7-98b1-82102067a3d8" diff --git a/test/project/Extensions/ExtDep.jl/Project.toml b/test/project/Extensions/ExtDep.jl/Project.toml new file mode 100644 index 0000000000000..d246934b7f958 --- /dev/null +++ b/test/project/Extensions/ExtDep.jl/Project.toml @@ -0,0 +1,6 @@ +name = "ExtDep" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" + +[deps] +SomePackage = "678608ae-7bb3-42c7-98b1-82102067a3d8" diff --git a/test/project/Extensions/ExtDep.jl/src/ExtDep.jl b/test/project/Extensions/ExtDep.jl/src/ExtDep.jl new file mode 100644 index 0000000000000..1c0022d879f51 --- /dev/null +++ b/test/project/Extensions/ExtDep.jl/src/ExtDep.jl @@ -0,0 +1,9 @@ +module ExtDep + +# loading this package makes the check for loading extensions trigger +# which tests #47921 +using SomePackage + +struct ExtDepStruct end + +end # module ExtDep diff --git a/test/project/Extensions/ExtDep2/Project.toml b/test/project/Extensions/ExtDep2/Project.toml new file mode 100644 index 0000000000000..b25b99615b185 --- /dev/null +++ b/test/project/Extensions/ExtDep2/Project.toml @@ -0,0 +1,3 @@ +name = "ExtDep2" +uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +version = "0.1.0" diff --git a/test/project/Extensions/ExtDep2/src/ExtDep2.jl b/test/project/Extensions/ExtDep2/src/ExtDep2.jl new file mode 100644 index 0000000000000..969905e25992f --- /dev/null +++ b/test/project/Extensions/ExtDep2/src/ExtDep2.jl @@ -0,0 +1,5 @@ +module ExtDep2 + +greet() = print("Hello World!") + +end # module ExtDep2 diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml new file mode 100644 index 0000000000000..52542fc822094 --- /dev/null +++ b/test/project/Extensions/HasDepWithExtensions.jl/Manifest.toml @@ -0,0 +1,31 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.0-DEV" +manifest_format = "2.0" +project_hash = "d523b3401f72a1ed34b7b43749fd2655c6b78542" + +[[deps.ExtDep]] +deps = ["SomePackage"] +path = "../ExtDep.jl" +uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c" +version = "0.1.0" + +[[deps.ExtDep2]] +path = "../ExtDep2" +uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +version = "0.1.0" + +[[deps.HasExtensions]] +path = "../HasExtensions.jl" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.1.0" +weakdeps = ["ExtDep", "ExtDep2"] + + [deps.HasExtensions.extensions] + Extension = "ExtDep" + ExtensionFolder = ["ExtDep", "ExtDep2"] + +[[deps.SomePackage]] +path = "../SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +version = "0.1.0" diff --git a/test/project/Extensions/HasDepWithExtensions.jl/Project.toml b/test/project/Extensions/HasDepWithExtensions.jl/Project.toml new file mode 100644 index 0000000000000..8f308a9fbee72 --- /dev/null +++ b/test/project/Extensions/HasDepWithExtensions.jl/Project.toml @@ -0,0 +1,8 @@ +name = "HasDepWithExtensions" +uuid = "d4ef3d4a-8e22-4710-85d8-c6cf2eb9efca" +version = "0.1.0" + +[deps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" +HasExtensions = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" diff --git a/test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl b/test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl new file mode 100644 index 0000000000000..5c1f2d1f301aa --- /dev/null +++ b/test/project/Extensions/HasDepWithExtensions.jl/src/HasDepWithExtensions.jl @@ -0,0 +1,21 @@ +module HasDepWithExtensions + +using HasExtensions: HasExtensions, HasExtensionsStruct +using ExtDep: ExtDepStruct +# Loading ExtDep makes the extension "Extension" load + +const m = Base.get_extension(HasExtensions, :Extension) +m isa Module || error("extension not loaded during precompilation") + +function do_something() + HasExtensions.foo(HasExtensionsStruct()) == 1 || error() + HasExtensions.foo(ExtDepStruct()) == 2 || error() + return true +end + +function __init__() + m = Base.get_extension(HasExtensions, :Extension) + m isa Module || error("extension not loaded during __init__") +end + +end # module diff --git a/test/project/Extensions/HasExtensions.jl/Manifest.toml b/test/project/Extensions/HasExtensions.jl/Manifest.toml new file mode 100644 index 0000000000000..55f7958701a75 --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/Manifest.toml @@ -0,0 +1,7 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.0-DEV" +manifest_format = "2.0" +project_hash = "c87947f1f1f070eea848950c304d668a112dec3d" + +[deps] diff --git a/test/project/Extensions/HasExtensions.jl/Project.toml b/test/project/Extensions/HasExtensions.jl/Project.toml new file mode 100644 index 0000000000000..72577de36d65d --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/Project.toml @@ -0,0 +1,11 @@ +name = "HasExtensions" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.1.0" + +[weakdeps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" +ExtDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d" + +[extensions] +Extension = "ExtDep" +ExtensionFolder = ["ExtDep", "ExtDep2"] diff --git a/test/project/Extensions/HasExtensions.jl/ext/Extension.jl b/test/project/Extensions/HasExtensions.jl/ext/Extension.jl new file mode 100644 index 0000000000000..9216c403a485a --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/ext/Extension.jl @@ -0,0 +1,13 @@ +module Extension + +using HasExtensions, ExtDep + +HasExtensions.foo(::ExtDep.ExtDepStruct) = 2 + +function __init__() + HasExtensions.ext_loaded = true +end + +const extvar = 1 + +end diff --git a/test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl b/test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl new file mode 100644 index 0000000000000..1fb90d7989ca9 --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/ext/ExtensionFolder/ExtensionFolder.jl @@ -0,0 +1,9 @@ +module ExtensionFolder + +using ExtDep, ExtDep2, HasExtensions + +function __init__() + HasExtensions.ext_folder_loaded = true +end + +end diff --git a/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl b/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl new file mode 100644 index 0000000000000..dbfaeec4f8812 --- /dev/null +++ b/test/project/Extensions/HasExtensions.jl/src/HasExtensions.jl @@ -0,0 +1,10 @@ +module HasExtensions + +struct HasExtensionsStruct end + +foo(::HasExtensionsStruct) = 1 + +ext_loaded = false +ext_folder_loaded = false + +end # module diff --git a/test/project/Extensions/HasExtensions_v2.jl/Project.toml b/test/project/Extensions/HasExtensions_v2.jl/Project.toml new file mode 100644 index 0000000000000..5d92a4b138058 --- /dev/null +++ b/test/project/Extensions/HasExtensions_v2.jl/Project.toml @@ -0,0 +1,9 @@ +name = "HasExtensions" +uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8" +version = "0.2.0" + +[weakdeps] +ExtDep = "fa069be4-f60b-4d4c-8b95-f8008775090c" + +[extensions] +Extension2 = "ExtDep" diff --git a/test/project/Extensions/HasExtensions_v2.jl/ext/Extension2.jl b/test/project/Extensions/HasExtensions_v2.jl/ext/Extension2.jl new file mode 100644 index 0000000000000..d027adec9c223 --- /dev/null +++ b/test/project/Extensions/HasExtensions_v2.jl/ext/Extension2.jl @@ -0,0 +1,3 @@ +module Extension2 + +end diff --git a/test/project/Extensions/HasExtensions_v2.jl/src/HasExtensions.jl b/test/project/Extensions/HasExtensions_v2.jl/src/HasExtensions.jl new file mode 100644 index 0000000000000..dbfaeec4f8812 --- /dev/null +++ b/test/project/Extensions/HasExtensions_v2.jl/src/HasExtensions.jl @@ -0,0 +1,10 @@ +module HasExtensions + +struct HasExtensionsStruct end + +foo(::HasExtensionsStruct) = 1 + +ext_loaded = false +ext_folder_loaded = false + +end # module diff --git a/test/project/Extensions/SomePackage/Project.toml b/test/project/Extensions/SomePackage/Project.toml new file mode 100644 index 0000000000000..b2d43340b39a8 --- /dev/null +++ b/test/project/Extensions/SomePackage/Project.toml @@ -0,0 +1,4 @@ +name = "SomePackage" +uuid = "678608ae-7bb3-42c7-98b1-82102067a3d8" +authors = ["Kristoffer <kcarlsson89@gmail.com>"] +version = "0.1.0" diff --git a/test/project/Extensions/SomePackage/src/SomePackage.jl b/test/project/Extensions/SomePackage/src/SomePackage.jl new file mode 100644 index 0000000000000..a41e0b7482bae --- /dev/null +++ b/test/project/Extensions/SomePackage/src/SomePackage.jl @@ -0,0 +1,5 @@ +module SomePackage + +greet() = print("Hello World!") + +end # module SomePackage diff --git a/test/project/deps/Foo1/Project.toml b/test/project/deps/Foo1/Project.toml new file mode 100644 index 0000000000000..b15bdfc656a64 --- /dev/null +++ b/test/project/deps/Foo1/Project.toml @@ -0,0 +1,3 @@ +name = "Foo" +uuid = "1a6589dc-c33c-4d54-9a54-f7fc4b3ff616" +version = "1.2.3" diff --git a/test/ranges.jl b/test/ranges.jl index a7f26c7efa629..ec69c57fc0a8f 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Base.Checked: checked_length +using InteractiveUtils: code_llvm @testset "range construction" begin @test_throws ArgumentError range(start=1, step=1, stop=2, length=10) @@ -254,6 +255,45 @@ end @test x.hi/2 === PhysQuantity{1}(2.0) @test_throws ErrorException("Int is incommensurate with PhysQuantity") x/2 @test zero(typeof(x)) === Base.TwicePrecision(PhysQuantity{1}(0.0)) + + function twiceprecision_roundtrip_is_not_lossy( + ::Type{S}, + x::T, + ) where {S<:Number, T<:Union{Number,Base.TwicePrecision}} + tw = Base.TwicePrecision{S}(x) + @test x == T(tw) + end + + function twiceprecision_is_normalized(tw::Tw) where {Tw<:Base.TwicePrecision} + (hi, lo) = (tw.hi, tw.lo) + normalized = Tw(Base.canonicalize2(hi, lo)...) + @test (abs(lo) ≤ abs(hi)) & (tw == normalized) + end + + rand_twiceprecision(::Type{T}) where {T<:Number} = Base.TwicePrecision{T}(rand(widen(T))) + + rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = @test !iszero(rand_twiceprecision(T).lo) + + # For this test the `BigFloat` mantissa needs to be just a bit + # larger than the `Float64` mantissa + setprecision(BigFloat, 70) do + n = 10 + @testset "rand twiceprecision is ok" for T ∈ (Float32, Float64), i ∈ 1:n + rand_twiceprecision_is_ok(T) + end + @testset "twiceprecision roundtrip is not lossy 1" for i ∈ 1:n + twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) + end + @testset "twiceprecision roundtrip is not lossy 2" for i ∈ 1:n + twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) + end + @testset "twiceprecision normalization 1: Float64 to Float32" for i ∈ 1:n + twiceprecision_is_normalized(Base.TwicePrecision{Float32}(rand_twiceprecision(Float64))) + end + @testset "twiceprecision normalization 2: Float32 to Float64" for i ∈ 1:n + twiceprecision_is_normalized(Base.TwicePrecision{Float64}(rand_twiceprecision(Float32))) + end + end end @testset "ranges" begin @test size(10:1:0) == (0,) @@ -518,8 +558,10 @@ end @test !(3.5 in 1:5) @test (3 in 1:5) @test (3 in 5:-1:1) - #@test (3 in 3+0*(1:5)) - #@test !(4 in 3+0*(1:5)) + @test (3 in 3 .+ 0*(1:5)) + @test !(4 in 3 .+ 0*(1:5)) + @test 0. in (0. .* (1:10)) + @test !(0.1 in (0. .* (1:10))) let r = 0.0:0.01:1.0 @test (r[30] in r) @@ -536,8 +578,17 @@ end x = (NaN16, Inf32, -Inf64, 1//0, -1//0) @test !(x in r) end + + @test 1e40 ∉ 0:1.0 # Issue #45747 + @test 1e20 ∉ 0:1e-20:1e-20 + @test 1e20 ∉ 0:1e-20 + @test 1.0 ∉ 0:1e-20:1e-20 + @test 0.5 ∉ 0:1e-20:1e-20 + @test 1 ∉ 0:1e-20:1e-20 + + @test_broken 17.0 ∈ 0:1e40 # Don't support really long ranges end - @testset "in() works across types, including non-numeric types (#21728)" begin + @testset "in() works across types, including non-numeric types (#21728 and #45646)" begin @test 1//1 in 1:3 @test 1//1 in 1.0:3.0 @test !(5//1 in 1:3) @@ -558,6 +609,22 @@ end @test !(Complex(1, 0) in Date(2017, 01, 01):Dates.Day(1):Date(2017, 01, 05)) @test !(π in Date(2017, 01, 01):Dates.Day(1):Date(2017, 01, 05)) @test !("a" in Date(2017, 01, 01):Dates.Day(1):Date(2017, 01, 05)) + + # We use Ducks because of their propensity to stand in a row and because we know + # that no additional methods (e.g. isfinite) are defined specifically for Ducks. + struct Duck + location::Int + end + Base.:+(x::Duck, y::Int) = Duck(x.location + y) + Base.:-(x::Duck, y::Int) = Duck(x.location - y) + Base.:-(x::Duck, y::Duck) = x.location - y.location + Base.isless(x::Duck, y::Duck) = isless(x.location, y.location) + + @test Duck(3) ∈ Duck(1):2:Duck(5) + @test Duck(3) ∈ Duck(5):-2:Duck(2) + @test Duck(4) ∉ Duck(5):-2:Duck(1) + @test Duck(4) ∈ Duck(1):Duck(5) + @test Duck(0) ∉ Duck(1):Duck(5) end end @testset "indexing range with empty range (#4309)" begin @@ -860,7 +927,15 @@ function range_fuzztests(::Type{T}, niter, nrange) where {T} @test m == length(r) @test strt == first(r) @test Δ == step(r) - @test_skip stop ≈ last(r) + # potential floating point error: + # stop = strt + (n-1)*Δ + # * error <= eps((n-1)*Δ)/2 <= abs((n-1)*Δ)/2 * eps(T) + # + error <= eps(stop)/2 <= abs(stop)/2 * eps(T) + # last(r) + # rat(strt) error <= eps(strt)/2 <= abs(strt)/2 * eps(T) + # rat(Δ) error <= (n-1)*eps(Δ)/2 <= abs((n-1)*Δ)/2 * eps(T) + # T(...) error <= eps(last(r))/2 <= abs(stop)/2 * eps(T) + @test stop ≈ last(r) atol = (abs(strt)/2 + (n-1)*abs(Δ) + abs(stop)) * eps(T) l = range(strt, stop=stop, length=n) @test n == length(l) @test strt == first(l) @@ -874,7 +949,6 @@ end @testset "Inexact errors on 32 bit architectures. #22613" begin @test first(range(log(0.2), stop=log(10.0), length=10)) == log(0.2) @test last(range(log(0.2), stop=log(10.0), length=10)) == log(10.0) - @test length(Base.floatrange(-3e9, 1.0, 1, 1.0)) == 1 end @testset "ranges with very small endpoints for type $T" for T = (Float32, Float64) @@ -1197,7 +1271,7 @@ end @test replrepr(1:4) == "1:4" @test repr("text/plain", 1:4) == "1:4" @test repr("text/plain", range(1, stop=5, length=7)) == "1.0:0.6666666666666666:5.0" - @test repr("text/plain", LinRange{Float64}(1,5,7)) == "7-element LinRange{Float64, Int$nb}:\n 1.0,1.66667,2.33333,3.0,3.66667,4.33333,5.0" + @test repr("text/plain", LinRange{Float64}(1,5,7)) == "7-element LinRange{Float64, Int$nb}:\n 1.0, 1.66667, 2.33333, 3.0, 3.66667, 4.33333, 5.0" @test repr(range(1, stop=5, length=7)) == "1.0:0.6666666666666666:5.0" @test repr(LinRange{Float64}(1,5,7)) == "LinRange{Float64}(1.0, 5.0, 7)" @test replrepr(0:100.) == "0.0:1.0:100.0" @@ -1205,7 +1279,7 @@ end # only examines spacing of the left and right edges of the range, sufficient # to cover the designated screen size. @test replrepr(range(0, stop=100, length=10000)) == "0.0:0.010001000100010001:100.0" - @test replrepr(LinRange{Float64}(0,100, 10000)) == "10000-element LinRange{Float64, Int$nb}:\n 0.0,0.010001,0.020002,0.030003,0.040004,…,99.95,99.96,99.97,99.98,99.99,100.0" + @test replrepr(LinRange{Float64}(0,100, 10000)) == "10000-element LinRange{Float64, Int$nb}:\n 0.0, 0.010001, 0.020002, 0.030003, …, 99.96, 99.97, 99.98, 99.99, 100.0" @test sprint(show, UnitRange(1, 2)) == "1:2" @test sprint(show, StepRange(1, 2, 5)) == "1:2:5" @@ -1612,6 +1686,60 @@ end @test x isa StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} end +@testset "Issue #44292" begin + let x = @inferred range(0, step=0.2, length=5) + @test x isa StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} + @test x == [0.0, 0.2, 0.4, 0.6, 0.8] + end + + let x = @inferred range(0.0, step=2, length=5) + @test x isa StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} + @test x == [0.0, 2.0, 4.0, 6.0, 8.0] + @test x === range(0.0, step=2.0, length=5) + @test x === range(0.0f0, step=2e0, length=5) + @test x === range(0e0, step=2.0f0, length=5) + end + + # start::IEEEFloat and step::Complex + let x = @inferred range(2.0, step=1im, length=3) + @test typeof(x) === StepRangeLen{ComplexF64, Float64, Complex{Int}, Int} + @test x == range(2, step=1im, length=3) # compare with integer range + @test x == 2.0 .+ [0im, 1im, 2im] + end + + # start::Complex and step::IEEEFloat + let x = @inferred range(2im, step=1.0, length=3) + @test typeof(x) === StepRangeLen{ComplexF64, Complex{Int}, Float64, Int} + @test x == range(2im, step=1, length=3) # compare with integer range + end + + # stop::IEEEFloat and step::Complex + let x = @inferred range(stop=2.0, step=1im, length=3) + @test typeof(x) === StepRangeLen{ComplexF64, ComplexF64, Complex{Int}, Int} + @test x == range(stop=2, step=1im, length=3) # compare with integer range + @test x == 2.0 .- [2im, 1im, 0im] + end + + # stop::Complex and step::IEEEFloat + let x = @inferred range(stop=2im, step=1.0, length=3) + @test typeof(x) === StepRangeLen{ComplexF64, ComplexF64, Float64, Int} + @test x == range(stop=2im, step=1, length=3) # compare with integer range + end + + let x = @inferred range(stop=10, step=2.0, length=5) + @test x isa StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} + @test x === @inferred range(stop=10.0, step=2.0, length=5) + @test x === @inferred range(stop=10f0, step=2.0, length=5) + @test x === @inferred range(stop=10e0, step=2.0f0, length=5) + @test x == [2, 4, 6, 8, 10] + end + + let x = @inferred range(stop=10.0, step=2, length=4) + @test x isa StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} + @test x == [4.0, 6.0, 8.0, 10.0] + end +end + @testset "Views of ranges" begin @test view(Base.OneTo(10), Base.OneTo(5)) === Base.OneTo(5) @test view(1:10, 1:5) === 1:5 @@ -1977,6 +2105,20 @@ end @test typeof(step(r)) === Int8 end +@testset "length(StepRange()) type stability" begin + for SR in (StepRange{Int,Int128}, StepRange{Int8,Int128}) + r1, r2 = SR(1, 1, 1), SR(1, 1, 0) + @test typeof(length(r1)) == typeof(checked_length(r1)) == + typeof(length(r2)) == typeof(checked_length(r2)) + end + SR = StepRange{Union{Int64,Int128},Int} + test_length(r, l) = length(r) === checked_length(r) === l + @test test_length(SR(Int64(1), 1, Int128(1)), Int128(1)) + @test test_length(SR(Int64(1), 1, Int128(0)), Int128(0)) + @test test_length(SR(Int64(1), 1, Int64(1)), Int64(1)) + @test test_length(SR(Int64(1), 1, Int64(0)), Int64(0)) +end + @testset "LinRange eltype for element types that wrap integers" begin struct RealWrapper{T <: Real} <: Real x :: T @@ -2288,3 +2430,50 @@ end @test isempty(range(typemax(Int), length=0, step=UInt(2))) @test length(range(1, length=typemax(Int128))) === typemax(Int128) + +@testset "firstindex(::StepRange{<:Base.BitInteger})" begin + test_firstindex(x) = firstindex(x) === first(Base.axes1(x)) + for T in Base.BitInteger_types, S in Base.BitInteger_types + @test test_firstindex(StepRange{T,S}(1, 1, 1)) + @test test_firstindex(StepRange{T,S}(1, 1, 0)) + end + @test test_firstindex(StepRange{Union{Int64,Int128},Int}(Int64(1), 1, Int128(1))) + @test test_firstindex(StepRange{Union{Int64,Int128},Int}(Int64(1), 1, Int128(0))) +end + +@testset "PR 49516" begin + struct PR49516 <: Signed + n::Int + end + PR49516(f::PR49516) = f + Base.:*(x::Integer, f::PR49516) = PR49516(*(x, f.n)) + Base.:+(f1::PR49516, f2::PR49516) = PR49516(+(f1.n, f2.n)) + Base.show(io::IO, f::PR49516) = print(io, "PR49516(", f.n, ")") + + srl = StepRangeLen(PR49516(1), PR49516(2), 10) + @test sprint(show, srl) == "PR49516(1):PR49516(2):PR49516(19)" +end + +@testset "Inline StepRange Construction #49270" begin + x = rand(Float32, 80) + a = rand(round(Int, length(x) / 2):length(x), 10^6) + + function test(x, a) + c = zero(Float32) + + @inbounds for j in a + for i in 1:8:j + c += x[i] + end + end + + return c + end + + llvm_ir(f, args) = sprint((io, args...) -> code_llvm(io, args...; debuginfo=:none), f, Base.typesof(args...)) + + ir = llvm_ir(test, (x, a)) + @test !occursin("steprange_last", ir) + @test !occursin("_colon", ir) + @test !occursin("StepRange", ir) +end diff --git a/test/rational.jl b/test/rational.jl index 1618156212af7..a1af6eda64516 100644 --- a/test/rational.jl +++ b/test/rational.jl @@ -253,6 +253,10 @@ end rational2 = Rational(-4500, 9000) @test sprint(show, rational1) == "1465//8593" @test sprint(show, rational2) == "-1//2" + @test sprint(show, -2//2) == "-1//1" + @test sprint(show, [-2//2,]) == "Rational{$Int}[-1]" + @test sprint(show, MIME"text/plain"(), Union{Int, Rational{Int}}[7 3//6; 6//3 2]) == + "2×2 Matrix{Union{Rational{$Int}, $Int}}:\n 7 1//2\n 2//1 2" let io1 = IOBuffer() write(io1, rational1) @@ -265,6 +269,91 @@ end @test read(io2, typeof(rational2)) == rational2 end end +@testset "abs overflow for Rational" begin + @test_throws OverflowError abs(typemin(Int) // 1) +end +@testset "parse" begin + # Non-negative Int in which parsing is expected to work + @test parse(Rational{Int}, string(10)) == 10 // 1 + @test parse(Rational{Int}, "100/10" ) == 10 // 1 + @test parse(Rational{Int}, "100 / 10") == 10 // 1 + @test parse(Rational{Int}, "0 / 10") == 0 // 1 + @test parse(Rational{Int}, "100//10" ) == 10 // 1 + @test parse(Rational{Int}, "100 // 10") == 10 // 1 + @test parse(Rational{Int}, "0 // 10") == 0 // 1 + + # Variations of the separator that should throw errors + @test_throws ArgumentError parse(Rational{Int}, "100\\10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 \\ 10") + @test_throws ArgumentError parse(Rational{Int}, "100\\\\10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 \\\\ 10") + @test_throws ArgumentError parse(Rational{Int}, "100/ /10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 / / 10") + @test_throws ArgumentError parse(Rational{Int}, "100// /10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 // / 10") + @test_throws ArgumentError parse(Rational{Int}, "100///10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 /// 10") + @test_throws ArgumentError parse(Rational{Int}, "100÷10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 ÷ 10") + @test_throws ArgumentError parse(Rational{Int}, "100 10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 10") + + # Zero denominator, negative denominator, and double negative + @test_throws ArgumentError parse(Rational{Int}, "0//0") + @test parse(Rational{Int}, "1000//-100") == -10 // 1 + @test parse(Rational{Int}, "-1000//-100") == 10 // 1 + + # Negative Int tests in which parsing is expected to work + @test parse(Rational{Int}, string(-10)) == -10 // 1 + @test parse(Rational{Int}, "-100/10" ) == -10 // 1 + @test parse(Rational{Int}, "-100 / 10") == -10 // 1 + @test parse(Rational{Int}, "-100//10" ) == -10 // 1 + + # Variations of the separator that should throw errors (negative version) + @test_throws ArgumentError parse(Rational{Int}, "-100\\10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 \\ 10") + @test_throws ArgumentError parse(Rational{Int}, "-100\\\\10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 \\\\ 10") + @test_throws ArgumentError parse(Rational{Int}, "-100/ /10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 / / 10") + @test_throws ArgumentError parse(Rational{Int}, "-100// /10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 // / 10") + @test_throws ArgumentError parse(Rational{Int}, "-100///10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 /// 10") + @test_throws ArgumentError parse(Rational{Int}, "-100÷10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 ÷ 10") + @test_throws ArgumentError parse(Rational{Int}, "-100 10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 10") + @test_throws ArgumentError parse(Rational{Int}, "-100 -10" ) + @test_throws ArgumentError parse(Rational{Int}, "-100 -10") + @test_throws ArgumentError parse(Rational{Int}, "100 -10" ) + @test_throws ArgumentError parse(Rational{Int}, "100 -10") + try # issue 44570 + parse(Rational{BigInt}, "100 10") + @test_broken false + catch + @test_broken true + end + + # A few tests for other Integer types + @test parse(Rational{Bool}, "true") == true // true + @test parse(Rational{UInt8}, "0xff/0xf") == UInt8(17) // UInt8(1) + @test parse(Rational{Int8}, "-0x7e/0xf") == Int8(-126) // Int8(15) + @test parse(Rational{BigInt}, "$(big(typemax(Int))*16)/8") == (big(typemax(Int))*2) // big(1) + # Mixed notations + @test parse(Rational{UInt8}, "0x64//28") == UInt8(25) // UInt8(7) + @test parse(Rational{UInt8}, "100//0x1c") == UInt8(25) // UInt8(7) + + # Out of the bounds tests + # 0x100 is 256, Int test works for both Int32 and Int64 + # The error must be throw even if the canonicalized fraction fits + # (i.e., would be less than typemax after divided by 2 in examples below, + # both over typemax values are even). + @test_throws OverflowError parse(Rational{UInt8}, "0x100/0x1") + @test_throws OverflowError parse(Rational{UInt8}, "0x100/0x2") + @test_throws OverflowError parse(Rational{Int}, "$(big(typemax(Int)) + 1)/1") + @test_throws OverflowError parse(Rational{Int}, "$(big(typemax(Int)) + 1)/2") +end # parse @testset "round" begin @test round(11//2) == round(11//2, RoundNearest) == 6//1 # rounds to closest _even_ integer diff --git a/test/read.jl b/test/read.jl index 91b5043ae2a55..b8060a023333f 100644 --- a/test/read.jl +++ b/test/read.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using DelimitedFiles, Random, Sockets +using Random, Sockets mktempdir() do dir @@ -312,12 +312,6 @@ for (name, f) in l verbose && println("$name countlines...") @test countlines(io()) == countlines(IOBuffer(text)) - - verbose && println("$name readdlm...") - @test readdlm(io(), ',') == readdlm(IOBuffer(text), ',') - @test readdlm(io(), ',') == readdlm(filename, ',') - - cleanup() end text = old_text @@ -658,4 +652,3 @@ end @test isempty(r) && isempty(collect(r)) end end - diff --git a/test/reduce.jl b/test/reduce.jl index 0e1568b0af901..4c05b179edcff 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -33,8 +33,12 @@ using .Main.OffsetArrays @test Base.mapfoldr(abs2, -, 2:5) == -14 @test Base.mapfoldr(abs2, -, 2:5; init=10) == -4 -@test @inferred(mapfoldr(x -> x + 1, (x, y) -> (x, y...), (1, 2.0, '3'); - init = ())) == (2, 3.0, '4') +for t in Any[(1, 2.0, '3'), (;a = 1, b = 2.0, c = '3')] + @test @inferred(mapfoldr(x -> x + 1, (x, y) -> (x, y...), t; + init = ())) == (2, 3.0, '4') + @test @inferred(mapfoldl(x -> x + 1, (x, y) -> (x..., y), t; + init = ())) == (2, 3.0, '4') +end @test foldr((x, y) -> ('⟨' * x * '|' * y * '⟩'), "λ 🐨.α") == "⟨λ|⟨ |⟨🐨|⟨.|α⟩⟩⟩⟩" # issue #31780 let x = rand(10) @@ -160,12 +164,14 @@ plus(x,y) = x + y sum3(A) = reduce(plus, A) sum4(itr) = invoke(reduce, Tuple{Function, Any}, plus, itr) sum5(A) = reduce(plus, A; init=0) -sum6(itr) = invoke(Core.kwfunc(reduce), Tuple{NamedTuple{(:init,), Tuple{Int}}, typeof(reduce), Function, Any}, (init=0,), reduce, plus, itr) +sum6(itr) = invoke(Core.kwcall, Tuple{NamedTuple{(:init,), Tuple{Int}}, typeof(reduce), Function, Any}, (init=0,), reduce, plus, itr) +sum61(itr) = invoke(reduce, Tuple{Function, Any}, init=0, plus, itr) sum7(A) = mapreduce(x->x, plus, A) sum8(itr) = invoke(mapreduce, Tuple{Function, Function, Any}, x->x, plus, itr) sum9(A) = mapreduce(x->x, plus, A; init=0) -sum10(itr) = invoke(Core.kwfunc(mapreduce), Tuple{NamedTuple{(:init,),Tuple{Int}}, typeof(mapreduce), Function, Function, Any}, (init=0,), mapreduce, x->x, plus, itr) -for f in (sum2, sum5, sum6, sum9, sum10) +sum10(itr) = invoke(Core.kwcall, Tuple{NamedTuple{(:init,),Tuple{Int}}, typeof(mapreduce), Function, Function, Any}, (init=0,), mapreduce, x->x, plus, itr) +sum11(itr) = invoke(mapreduce, Tuple{Function, Function, Any}, init=0, x->x, plus, itr) +for f in (sum2, sum5, sum6, sum61, sum9, sum10, sum11) @test sum(z) == f(z) @test sum(Int[]) == f(Int[]) == 0 @test sum(Int[7]) == f(Int[7]) == 7 @@ -263,7 +269,6 @@ let x = [4,3,5,2] @test maximum(x) == 5 @test minimum(x) == 2 @test extrema(x) == (2, 5) - @test Core.Compiler.extrema(x) == (2, 5) @test maximum(abs2, x) == 25 @test minimum(abs2, x) == 4 @@ -630,14 +635,14 @@ test18695(r) = sum( t^2 for t in r ) @test prod(Char['a','b']) == "ab" @testset "optimized reduce(vcat/hcat, A) for arrays" begin - for args in ([1:2], [[1, 2]], [1:2, 3:4], [[3, 4, 5], 1:2], [1:2, [3.5, 4.5]], + for args in ([1:2], [[1, 2]], [1:2, 3:4], AbstractVector{Int}[[3, 4, 5], 1:2], AbstractVector[1:2, [3.5, 4.5]], [[1 2], [3 4; 5 6]], [reshape([1, 2], 2, 1), 3:4]) X = reduce(vcat, args) Y = vcat(args...) @test X == Y @test typeof(X) === typeof(Y) end - for args in ([1:2], [[1, 2]], [1:2, 3:4], [[3, 4, 5], 1:3], [1:2, [3.5, 4.5]], + for args in ([1:2], [[1, 2]], [1:2, 3:4], AbstractVector{Int}[[3, 4, 5], 1:3], AbstractVector[1:2, [3.5, 4.5]], [[1 2; 3 4], [5 6; 7 8]], [1:2, [5 6; 7 8]], [[5 6; 7 8], [1, 2]]) X = reduce(hcat, args) Y = hcat(args...) @@ -671,9 +676,32 @@ end # issue #38627 @testset "overflow in mapreduce" begin # at len = 16 and len = 1025 there is a change in codepath - for len in [0, 1, 15, 16, 1024, 1025, 2048, 2049] + for len in [1, 15, 16, 1024, 1025, 2048, 2049] oa = OffsetArray(repeat([1], len), typemax(Int)-len) @test sum(oa) == reduce(+, oa) == len @test mapreduce(+, +, oa, oa) == 2len end end + +# issue #45748 +@testset "foldl's stability for nested Iterators" begin + a = Iterators.flatten((1:3, 1:3)) + b = (2i for i in a if i > 0) + c = Base.Generator(Float64, b) + d = (sin(i) for i in c if i > 0) + @test @inferred(sum(d)) == sum(collect(d)) + @test @inferred(extrema(d)) == extrema(collect(d)) + @test @inferred(maximum(c)) == maximum(collect(c)) + @test @inferred(prod(b)) == prod(collect(b)) + @test @inferred(minimum(a)) == minimum(collect(a)) +end + +function fold_alloc(a) + sum(a) + foldr(+, a) + max(@allocated(sum(a)), @allocated(foldr(+, a))) +end +let a = NamedTuple(Symbol(:x,i) => i for i in 1:33), + b = (a...,) + @test fold_alloc(a) == fold_alloc(b) == 0 +end diff --git a/test/reducedim.jl b/test/reducedim.jl index 512c94d1e2f02..daa0a3fbe1f92 100644 --- a/test/reducedim.jl +++ b/test/reducedim.jl @@ -6,7 +6,16 @@ using Random # issue #35800 # tested very early since it can be state-dependent -@test @inferred(mapreduce(x->count(!iszero,x), +, [rand(1)]; init = 0.)) == 1.0 + +function my_simple_count(pred, g::Vector{T}) where {T} + n::T = zero(T) + for x in g + n += pred(x) + end + return n +end + +@test @inferred(mapreduce(x->my_simple_count(!iszero,x), +, [rand(1)]; init = 0.)) == 1.0 function safe_mapslices(op, A, region) newregion = intersect(region, 1:ndims(A)) @@ -197,6 +206,10 @@ end @test isequal(f(A, dims=2), (zeros(Int, 0, 1), zeros(Int, 0, 1))) @test_throws ArgumentError f(A, dims=(1, 2)) @test isequal(f(A, dims=3), (zeros(Int, 0, 1), zeros(Int, 0, 1))) + @test_throws ArgumentError f(abs2, A, dims=1) + @test isequal(f(abs2, A, dims=2), (zeros(Int, 0, 1), zeros(Int, 0, 1))) + @test_throws ArgumentError f(abs2, A, dims=(1, 2)) + @test isequal(f(abs2, A, dims=3), (zeros(Int, 0, 1), zeros(Int, 0, 1))) end end @@ -225,15 +238,93 @@ for (tup, rval, rind) in [((1,), [5.0 5.0 6.0], [CartesianIndex(2,1) CartesianIn @test isequal(maximum!(copy(rval), A, init=false), rval) end +@testset "findmin/findmax transformed arguments, numeric values" begin + A = [1.0 -5.0 -6.0; + -5.0 2.0 4.0] + TA = [((1,), [1.0 2.0 4.0], [CartesianIndex(1,1) CartesianIndex(2,2) CartesianIndex(2,3)]), + ((2,), reshape([1.0, 2.0], 2, 1), reshape([CartesianIndex(1,1), CartesianIndex(2,2)], 2, 1)), + ((1,2), fill(1.0,1,1), fill(CartesianIndex(1,1),1,1))] + TA2 = [((1,), [1.0 4.0 16.0], [CartesianIndex(1,1) CartesianIndex(2,2) CartesianIndex(2,3)]), + ((2,), reshape([1.0, 4.0], 2, 1), reshape([CartesianIndex(1,1), CartesianIndex(2,2)], 2, 1)), + ((1,2), fill(1.0,1,1), fill(CartesianIndex(1,1),1,1))] + TAc = [((1,), [0.28366218546322625 -0.4161468365471424 -0.6536436208636119], [CartesianIndex(2,1) CartesianIndex(2,2) CartesianIndex(2,3)]), + ((2,), reshape([0.28366218546322625, -0.6536436208636119], 2, 1), reshape([CartesianIndex(1,2), CartesianIndex(2,3)], 2, 1)), + ((1,2), fill(-0.6536436208636119,1,1), fill(CartesianIndex(2,3),1,1))] + for (f, At) in ((abs, TA), (abs2, TA2), (cos, TAc)) + A′ = map(f, A) + for (tup, rval, rind) in At + (rval′, rind′) = findmin(f, A, dims=tup) + @test all(rval′ .≈ rval) + @test rind′ == rind + @test findmin(f, A, dims=tup) == (rval, rind) + @test (rval′, rind′) == findmin(A′, dims=tup) + end + end + + TA = [((1,), [5.0 5.0 6.0], [CartesianIndex(2,1) CartesianIndex(1,2) CartesianIndex(1,3)]), + ((2,), reshape([6.0,5.0], 2, 1), reshape([CartesianIndex(1,3), CartesianIndex(2,1)], 2, 1)), + ((1,2), fill(6.0,1,1),fill(CartesianIndex(1,3),1,1))] + TA2 = [((1,), [25.0 25.0 36.0], [CartesianIndex(2,1) CartesianIndex(1,2) CartesianIndex(1,3)]), + ((2,), reshape([36.0, 25.0], 2, 1), reshape([CartesianIndex(1,3), CartesianIndex(2,1)], 2, 1)), + ((1,2), fill(36.0,1,1), fill(CartesianIndex(1,3),1,1))] + TAc = [((1,), [0.5403023058681398 0.28366218546322625 0.960170286650366], [CartesianIndex(1,1) CartesianIndex(1,2) CartesianIndex(1,3)]), + ((2,), reshape([0.960170286650366, 0.28366218546322625], 2, 1), reshape([CartesianIndex(1,3), CartesianIndex(2,1)], 2, 1)), + ((1,2), fill(0.960170286650366,1,1), fill(CartesianIndex(1,3),1,1))] + for (f, At) in ((abs, TA), (abs2, TA2), (cos, TAc)) + A′ = map(f, A) + for (tup, rval, rind) in At + (rval′, rind′) = findmax(f, A, dims=tup) + @test all(rval′ .≈ rval) + @test rind′ == rind + @test findmax(f, A, dims=tup) == (rval, rind) + @test (rval′, rind′) == findmax(A′, dims=tup) + end + end +end + +# findmin/findmax function arguments: output type inference +@testset "findmin/findmax output type inference" begin + A = ["1" "22"; "333" "4444"] + for (tup, rval, rind) in [((1,), [1 2], [CartesianIndex(1, 1) CartesianIndex(1, 2)]), + ((2,), reshape([1, 3], 2, 1), reshape([CartesianIndex(1, 1), CartesianIndex(2, 1)], 2, 1)), + ((1,2), fill(1,1,1), fill(CartesianIndex(1,1),1,1))] + rval′, rind′ = findmin(length, A, dims=tup) + @test (rval, rind) == (rval′, rind′) + @test typeof(rval′) == Matrix{Int} + end + for (tup, rval, rind) in [((1,), [3 4], [CartesianIndex(2, 1) CartesianIndex(2, 2)]), + ((2,), reshape([2, 4], 2, 1), reshape([CartesianIndex(1, 2), CartesianIndex(2, 2)], 2, 1)), + ((1,2), fill(4,1,1), fill(CartesianIndex(2,2),1,1))] + rval′, rind′ = findmax(length, A, dims=tup) + @test (rval, rind) == (rval′, rind′) + @test typeof(rval) == Matrix{Int} + end + B = [1.5 1.0; 5.5 6.0] + for (tup, rval, rind) in [((1,), [3//2 1//1], [CartesianIndex(1, 1) CartesianIndex(1, 2)]), + ((2,), reshape([1//1, 11//2], 2, 1), reshape([CartesianIndex(1, 2), CartesianIndex(2, 1)], 2, 1)), + ((1,2), fill(1//1,1,1), fill(CartesianIndex(1,2),1,1))] + rval′, rind′ = findmin(Rational, B, dims=tup) + @test (rval, rind) == (rval′, rind′) + @test typeof(rval) == Matrix{Rational{Int}} + rval′, rind′ = findmin(Rational ∘ abs ∘ complex, B, dims=tup) + @test (rval, rind) == (rval′, rind′) + @test typeof(rval) == Matrix{Rational{Int}} + end +end + + @testset "missing in findmin/findmax" begin B = [1.0 missing NaN; 5.0 NaN missing] + B′ = [1.0 missing -NaN; + -5.0 NaN missing] for (tup, rval, rind) in [(1, [5.0 missing missing], [CartesianIndex(2, 1) CartesianIndex(1, 2) CartesianIndex(2, 3)]), (2, [missing; missing], [CartesianIndex(1, 2) CartesianIndex(2, 3)] |> permutedims)] (rval′, rind′) = findmax(B, dims=tup) @test all(rval′ .=== rval) @test all(rind′ .== rind) @test all(maximum(B, dims=tup) .=== rval) + @test isequal(findmax(abs, B′, dims=tup), (rval′, rind′)) end for (tup, rval, rind) in [(1, [1.0 missing missing], [CartesianIndex(1, 1) CartesianIndex(1, 2) CartesianIndex(2, 3)]), @@ -242,6 +333,7 @@ end @test all(rval′ .=== rval) @test all(rind′ .== rind) @test all(minimum(B, dims=tup) .=== rval) + @test isequal(findmin(abs, B′, dims=tup), (rval′, rind′)) end end @@ -266,6 +358,7 @@ for (tup, rval, rind) in [((1,), [NaN 2.0 4.0], [CartesianIndex(2,1) CartesianIn ((2,), reshape([1.0, NaN], 2, 1), reshape([CartesianIndex(1,1),CartesianIndex(2,1)], 2, 1)), ((1,2), fill(NaN,1,1),fill(CartesianIndex(2,1),1,1))] @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(abs, A, dims=tup), (rval, rind)) @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) @test isequal(minimum(A, dims=tup), rval) @test isequal(minimum!(similar(rval), A), rval) @@ -277,6 +370,7 @@ for (tup, rval, rind) in [((1,), [NaN 3.0 6.0], [CartesianIndex(2,1) CartesianIn ((2,), reshape([6.0, NaN], 2, 1), reshape([CartesianIndex(1,3),CartesianIndex(2,1)], 2, 1)), ((1,2), fill(NaN,1,1),fill(CartesianIndex(2,1),1,1))] @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(abs, A, dims=tup), (rval, rind)) @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) @test isequal(maximum(A, dims=tup), rval) @test isequal(maximum!(similar(rval), A), rval) @@ -286,125 +380,153 @@ end # issue #28320 @testset "reducedim issue with abstract complex arrays" begin -let A = Complex[1.5 0.5] - @test mapreduce(abs2, +, A, dims=2) == reshape([2.5], 1, 1) - @test sum(abs2, A, dims=2) == reshape([2.5], 1, 1) - @test prod(abs2, A, dims=2) == reshape([0.5625], 1, 1) - @test maximum(abs2, A, dims=2) == reshape([2.25], 1, 1) - @test minimum(abs2, A, dims=2) == reshape([0.25], 1, 1) -end + let A = Complex[1.5 0.5] + @test mapreduce(abs2, +, A, dims=2) == reshape([2.5], 1, 1) + @test sum(abs2, A, dims=2) == reshape([2.5], 1, 1) + @test prod(abs2, A, dims=2) == reshape([0.5625], 1, 1) + @test maximum(abs2, A, dims=2) == reshape([2.25], 1, 1) + @test minimum(abs2, A, dims=2) == reshape([0.25], 1, 1) + @test findmin(abs2, A, dims=2) == (fill(0.25, 1, 1), fill(CartesianIndex(1, 2), 1, 1)) + @test findmax(abs2, A, dims=2) == (fill(2.25, 1, 1), fill(CartesianIndex(1, 1), 1, 1)) + end end -A = [1.0 NaN 6.0; - NaN 2.0 4.0] -for (tup, rval, rind) in [((1,), [NaN NaN 4.0], [CartesianIndex(2,1) CartesianIndex(1,2) CartesianIndex(2,3)]), - ((2,), reshape([NaN, NaN], 2, 1), reshape([CartesianIndex(1,2),CartesianIndex(2,1)], 2, 1)), - ((1,2), fill(NaN,1,1),fill(CartesianIndex(2,1),1,1))] - @test isequal(findmin(A, dims=tup), (rval, rind)) - @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(minimum(A, dims=tup), rval) - @test isequal(minimum!(similar(rval), A), rval) - @test isequal(minimum!(copy(rval), A, init=false), rval) -end +@testset "NaN in findmin/findmax/minimum/maximum" begin + A = [1.0 NaN 6.0; + NaN 2.0 4.0] + A′ = [-1.0 NaN -6.0; + NaN -2.0 4.0] + for (tup, rval, rind) in [((1,), [NaN NaN 4.0], [CartesianIndex(2,1) CartesianIndex(1,2) CartesianIndex(2,3)]), + ((2,), reshape([NaN, NaN], 2, 1), reshape([CartesianIndex(1,2),CartesianIndex(2,1)], 2, 1)), + ((1,2), fill(NaN,1,1),fill(CartesianIndex(2,1),1,1))] + @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(abs, A′, dims=tup), (rval, rind)) + @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(minimum(A, dims=tup), rval) + @test isequal(minimum!(similar(rval), A), rval) + @test isequal(minimum!(copy(rval), A, init=false), rval) + end -for (tup, rval, rind) in [((1,), [NaN NaN 6.0], [CartesianIndex(2,1) CartesianIndex(1,2) CartesianIndex(1,3)]), - ((2,), reshape([NaN, NaN], 2, 1), reshape([CartesianIndex(1,2),CartesianIndex(2,1)], 2, 1)), - ((1,2), fill(NaN,1,1),fill(CartesianIndex(2,1),1,1))] - @test isequal(findmax(A, dims=tup), (rval, rind)) - @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(maximum(A, dims=tup), rval) - @test isequal(maximum!(similar(rval), A), rval) - @test isequal(maximum!(copy(rval), A, init=false), rval) + for (tup, rval, rind) in [((1,), [NaN NaN 6.0], [CartesianIndex(2,1) CartesianIndex(1,2) CartesianIndex(1,3)]), + ((2,), reshape([NaN, NaN], 2, 1), reshape([CartesianIndex(1,2),CartesianIndex(2,1)], 2, 1)), + ((1,2), fill(NaN,1,1),fill(CartesianIndex(2,1),1,1))] + @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(abs, A′, dims=tup), (rval, rind)) + @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(maximum(A, dims=tup), rval) + @test isequal(maximum!(similar(rval), A), rval) + @test isequal(maximum!(copy(rval), A, init=false), rval) + end end -A = [Inf -Inf Inf -Inf; - Inf Inf -Inf -Inf] -for (tup, rval, rind) in [((1,), [Inf -Inf -Inf -Inf], [CartesianIndex(1,1) CartesianIndex(1,2) CartesianIndex(2,3) CartesianIndex(1,4)]), - ((2,), reshape([-Inf -Inf], 2, 1), reshape([CartesianIndex(1,2),CartesianIndex(2,3)], 2, 1)), - ((1,2), fill(-Inf,1,1),fill(CartesianIndex(1,2),1,1))] - @test isequal(findmin(A, dims=tup), (rval, rind)) - @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(minimum(A, dims=tup), rval) - @test isequal(minimum!(similar(rval), A), rval) - @test isequal(minimum!(copy(rval), A, init=false), rval) -end +@testset "+/-Inf in findmin/findmax/minimum/maximum" begin + A = [Inf -Inf Inf -Inf; + Inf Inf -Inf -Inf] + A′ = [1 0 1 0; + 1 1 0 0] + for (tup, rval, rind) in [((1,), [Inf -Inf -Inf -Inf], [CartesianIndex(1,1) CartesianIndex(1,2) CartesianIndex(2,3) CartesianIndex(1,4)]), + ((2,), reshape([-Inf -Inf], 2, 1), reshape([CartesianIndex(1,2),CartesianIndex(2,3)], 2, 1)), + ((1,2), fill(-Inf,1,1),fill(CartesianIndex(1,2),1,1))] + @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(x -> x == 1 ? Inf : -Inf, A′, dims=tup), (rval, rind)) + @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(minimum(A, dims=tup), rval) + @test isequal(minimum!(similar(rval), A), rval) + @test isequal(minimum!(copy(rval), A, init=false), rval) + end -for (tup, rval, rind) in [((1,), [Inf Inf Inf -Inf], [CartesianIndex(1,1) CartesianIndex(2,2) CartesianIndex(1,3) CartesianIndex(1,4)]), - ((2,), reshape([Inf Inf], 2, 1), reshape([CartesianIndex(1,1),CartesianIndex(2,1)], 2, 1)), - ((1,2), fill(Inf,1,1),fill(CartesianIndex(1,1),1,1))] - @test isequal(findmax(A, dims=tup), (rval, rind)) - @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(maximum(A, dims=tup), rval) - @test isequal(maximum!(similar(rval), A), rval) - @test isequal(maximum!(copy(rval), A, init=false), rval) + for (tup, rval, rind) in [((1,), [Inf Inf Inf -Inf], [CartesianIndex(1,1) CartesianIndex(2,2) CartesianIndex(1,3) CartesianIndex(1,4)]), + ((2,), reshape([Inf Inf], 2, 1), reshape([CartesianIndex(1,1),CartesianIndex(2,1)], 2, 1)), + ((1,2), fill(Inf,1,1),fill(CartesianIndex(1,1),1,1))] + @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(x -> x == 1 ? Inf : -Inf, A′, dims=tup), (rval, rind)) + @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(maximum(A, dims=tup), rval) + @test isequal(maximum!(similar(rval), A), rval) + @test isequal(maximum!(copy(rval), A, init=false), rval) + end end -A = [BigInt(10)] -for (tup, rval, rind) in [((2,), [BigInt(10)], [1])] - @test isequal(findmin(A, dims=tup), (rval, rind)) - @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(minimum(A, dims=tup), rval) - @test isequal(minimum!(similar(rval), A), rval) - @test isequal(minimum!(copy(rval), A, init=false), rval) -end +@testset "BigInt in findmin/findmax/minimum/maximum" begin + A = [BigInt(10)] + A′ = [BigInt(1)] + for (tup, rval, rind) in [((2,), [BigInt(10)], [1])] + @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(x -> 10^x, A′, dims=tup), (rval, rind)) + @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(minimum(A, dims=tup), rval) + @test isequal(minimum!(similar(rval), A), rval) + @test isequal(minimum!(copy(rval), A, init=false), rval) + end -for (tup, rval, rind) in [((2,), [BigInt(10)], [1])] - @test isequal(findmax(A, dims=tup), (rval, rind)) - @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(maximum(A, dims=tup), rval) - @test isequal(maximum!(similar(rval), A), rval) - @test isequal(maximum!(copy(rval), A, init=false), rval) -end + for (tup, rval, rind) in [((2,), [BigInt(10)], [1])] + @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(x -> 10^x, A′, dims=tup), (rval, rind)) + @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(maximum(A, dims=tup), rval) + @test isequal(maximum!(similar(rval), A), rval) + @test isequal(maximum!(copy(rval), A, init=false), rval) + end -A = [BigInt(-10)] -for (tup, rval, rind) in [((2,), [BigInt(-10)], [1])] - @test isequal(findmin(A, dims=tup), (rval, rind)) - @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(minimum(A, dims=tup), rval) - @test isequal(minimum!(similar(rval), A), rval) - @test isequal(minimum!(copy(rval), A, init=false), rval) -end + A = [BigInt(-10)] + for (tup, rval, rind) in [((2,), [BigInt(-10)], [1])] + @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(x -> -(x + 20), A, dims=tup), (rval, rind)) + @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(minimum(A, dims=tup), rval) + @test isequal(minimum!(similar(rval), A), rval) + @test isequal(minimum!(copy(rval), A, init=false), rval) + end -for (tup, rval, rind) in [((2,), [BigInt(-10)], [1])] - @test isequal(findmax(A, dims=tup), (rval, rind)) - @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(maximum(A, dims=tup), rval) - @test isequal(maximum!(similar(rval), A), rval) - @test isequal(maximum!(copy(rval), A, init=false), rval) -end + for (tup, rval, rind) in [((2,), [BigInt(-10)], [1])] + @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(x -> -(x + 20), A, dims=tup), (rval, rind)) + @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(maximum(A, dims=tup), rval) + @test isequal(maximum!(similar(rval), A), rval) + @test isequal(maximum!(copy(rval), A, init=false), rval) + end -A = [BigInt(10) BigInt(-10)] -for (tup, rval, rind) in [((2,), reshape([BigInt(-10)], 1, 1), reshape([CartesianIndex(1,2)], 1, 1))] - @test isequal(findmin(A, dims=tup), (rval, rind)) - @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(minimum(A, dims=tup), rval) - @test isequal(minimum!(similar(rval), A), rval) - @test isequal(minimum!(copy(rval), A, init=false), rval) -end + A = [BigInt(10) BigInt(-10)] + A′ = [BigInt(1) BigInt(10)] + for (tup, rval, rind) in [((2,), reshape([BigInt(-10)], 1, 1), reshape([CartesianIndex(1,2)], 1, 1))] + @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(x -> x == 1 ? 10^x : x - 20, A′, dims=tup), (rval, rind)) + @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(minimum(A, dims=tup), rval) + @test isequal(minimum!(similar(rval), A), rval) + @test isequal(minimum!(copy(rval), A, init=false), rval) + end -for (tup, rval, rind) in [((2,), reshape([BigInt(10)], 1, 1), reshape([CartesianIndex(1,1)], 1, 1))] - @test isequal(findmax(A, dims=tup), (rval, rind)) - @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(maximum(A, dims=tup), rval) - @test isequal(maximum!(similar(rval), A), rval) - @test isequal(maximum!(copy(rval), A, init=false), rval) + for (tup, rval, rind) in [((2,), reshape([BigInt(10)], 1, 1), reshape([CartesianIndex(1,1)], 1, 1))] + @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(x -> x == 1 ? 10^x : x - 20, A′, dims=tup), (rval, rind)) + @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(maximum(A, dims=tup), rval) + @test isequal(maximum!(similar(rval), A), rval) + @test isequal(maximum!(copy(rval), A, init=false), rval) + end end -A = ["a", "b"] -for (tup, rval, rind) in [((1,), ["a"], [1])] - @test isequal(findmin(A, dims=tup), (rval, rind)) - @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(minimum(A, dims=tup), rval) - @test isequal(minimum!(similar(rval), A), rval) - @test isequal(minimum!(copy(rval), A, init=false), rval) -end +@testset "String in findmin/findmax/minimum/maximum" begin + A = ["a", "b"] + for (tup, rval, rind) in [((1,), ["a"], [1])] + @test isequal(findmin(A, dims=tup), (rval, rind)) + @test isequal(findmin(x -> (x^2)[1:1], A, dims=tup), (rval, rind)) + @test isequal(findmin!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(minimum(A, dims=tup), rval) + @test isequal(minimum!(similar(rval), A), rval) + @test isequal(minimum!(copy(rval), A, init=false), rval) + end -for (tup, rval, rind) in [((1,), ["b"], [2])] - @test isequal(findmax(A, dims=tup), (rval, rind)) - @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) - @test isequal(maximum(A, dims=tup), rval) - @test isequal(maximum!(similar(rval), A), rval) - @test isequal(maximum!(copy(rval), A, init=false), rval) + for (tup, rval, rind) in [((1,), ["b"], [2])] + @test isequal(findmax(A, dims=tup), (rval, rind)) + @test isequal(findmax(x -> (x^2)[1:1], A, dims=tup), (rval, rind)) + @test isequal(findmax!(similar(rval), similar(rind), A), (rval, rind)) + @test isequal(maximum(A, dims=tup), rval) + @test isequal(maximum!(similar(rval), A), rval) + @test isequal(maximum!(copy(rval), A, init=false), rval) + end end # issue #6672 diff --git a/test/reflection.jl b/test/reflection.jl index b1a5b6eb822a3..0ae8cb3f9d393 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -2,6 +2,8 @@ using Test +include("compiler/irutils.jl") + # code_native / code_llvm (issue #8239) # It's hard to really test these, but just running them should be # sufficient to catch segfault bugs. @@ -66,6 +68,7 @@ end # module ReflectionTest @test isbits((1,2)) @test !isbits([1]) @test isbits(nothing) +@test fully_eliminated(isbits, (Int,)) # issue #16670 @test isconcretetype(Int) @@ -81,7 +84,6 @@ end # module ReflectionTest @test isconcretetype(DataType) @test isconcretetype(Union) @test !isconcretetype(Union{}) -@test isconcretetype(Tuple{Union{}}) @test !isconcretetype(Complex) @test !isconcretetype(Complex.body) @test !isconcretetype(AbstractArray{Int,1}) @@ -147,7 +149,7 @@ module TestModSub9475 let @test Base.binding_module(@__MODULE__, :a9475) == @__MODULE__ @test Base.binding_module(@__MODULE__, :c7648) == TestMod7648 - @test Base.nameof(@__MODULE__) == :TestModSub9475 + @test Base.nameof(@__MODULE__) === :TestModSub9475 @test Base.fullname(@__MODULE__) == (curmod_name..., :TestMod7648, :TestModSub9475) @test Base.parentmodule(@__MODULE__) == TestMod7648 end @@ -158,7 +160,7 @@ using .TestModSub9475 let @test Base.binding_module(@__MODULE__, :d7648) == @__MODULE__ @test Base.binding_module(@__MODULE__, :a9475) == TestModSub9475 - @test Base.nameof(@__MODULE__) == :TestMod7648 + @test Base.nameof(@__MODULE__) === :TestMod7648 @test Base.parentmodule(@__MODULE__) == curmod end end # module TestMod7648 @@ -183,14 +185,16 @@ let using .TestMod7648 @test Base.binding_module(@__MODULE__, :a9475) == TestMod7648.TestModSub9475 @test Base.binding_module(@__MODULE__, :c7648) == TestMod7648 - @test nameof(foo7648) == :foo7648 + @test nameof(foo7648) === :foo7648 @test parentmodule(foo7648, (Any,)) == TestMod7648 @test parentmodule(foo7648) == TestMod7648 @test parentmodule(foo7648_nomethods) == TestMod7648 @test parentmodule(foo9475, (Any,)) == TestMod7648.TestModSub9475 @test parentmodule(foo9475) == TestMod7648.TestModSub9475 @test parentmodule(Foo7648) == TestMod7648 - @test nameof(Foo7648) == :Foo7648 + @test parentmodule(first(methods(foo9475))) == TestMod7648.TestModSub9475 + @test parentmodule(first(methods(foo7648))) == TestMod7648 + @test nameof(Foo7648) === :Foo7648 @test basename(functionloc(foo7648, (Any,))[1]) == "reflection.jl" @test first(methods(TestMod7648.TestModSub9475.foo7648)) == which(foo7648, (Int,)) @test TestMod7648 == which(@__MODULE__, :foo7648) @@ -199,7 +203,7 @@ end @test which(===, Tuple{Int, Int}) isa Method @test length(code_typed(===, Tuple{Int, Int})) === 1 -@test only(Base.return_types(===, Tuple{Int, Int})) === Any +@test only(Base.return_types(===, Tuple{Int, Int})) === Bool module TestingExported using Test @@ -224,7 +228,7 @@ let ex = :(a + b) end foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N} = nothing @test startswith(string(first(methods(foo13825))), - "foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N} in") + "foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N}") mutable struct TLayout x::Int8 @@ -425,10 +429,10 @@ let li = typeof(fieldtype).name.mt.cache.func::Core.MethodInstance, mmime = repr("text/plain", li.def) @test lrepr == lmime == "MethodInstance for fieldtype(...)" - @test mrepr == mmime == "fieldtype(...) in Core" + @test mrepr == "fieldtype(...) @ Core none:0" # simple print + @test mmime == "fieldtype(...)\n @ Core none:0" # verbose print end - # Linfo Tracing test function tracefoo end # Method Tracing test @@ -527,7 +531,7 @@ let ft = typeof(f18888) code_typed(f18888, Tuple{}; optimize=false) - @test !isempty(m.specializations) # uncached, but creates the specializations entry + @test m.specializations !== Core.svec() # uncached, but creates the specializations entry mi = Core.Compiler.specialize_method(m, Tuple{ft}, Core.svec()) interp = Core.Compiler.NativeInterpreter(world) @test !Core.Compiler.haskey(Core.Compiler.code_cache(interp), mi) @@ -543,7 +547,7 @@ let end # code_typed_by_type -@test Base.code_typed_by_type(Tuple{Type{<:Val}})[1][2] == Val +@test Base.code_typed_by_type(Tuple{Type{<:Val}})[2][2] == Val @test Base.code_typed_by_type(Tuple{typeof(sin), Float64})[1][2] === Float64 # New reflection methods in 0.6 @@ -643,7 +647,7 @@ let world = Core.Compiler.get_world_counter() match = Base._methods_by_ftype(T22979, -1, world)[1] instance = Core.Compiler.specialize_method(match) - cinfo_generated = Core.Compiler.get_staged(instance) + cinfo_generated = Core.Compiler.get_staged(instance, world) @test_throws ErrorException Base.uncompressed_ir(match.method) test_similar_codeinfo(code_lowered(f22979, typeof(x22979))[1], cinfo_generated) @@ -721,10 +725,35 @@ Base.delete_method(m) @test faz4(1) == 1 @test faz4(1.0) == 1 +# Deletion & invoke (issue #48802) +function f48802!(log, x::Integer) + log[] = "default" + return x + 1 +end +function addmethod_48802() + @eval function f48802!(log, x::Int) + ret = invoke(f48802!, Tuple{Any, Integer}, log, x) + log[] = "specialized" + return ret + end +end +log = Ref{String}() +@test f48802!(log, 1) == 2 +@test log[] == "default" +addmethod_48802() +@test f48802!(log, 1) == 2 +@test log[] == "specialized" +Base.delete_method(which(f48802!, Tuple{Any, Int})) +@test f48802!(log, 1) == 2 +@test log[] == "default" +addmethod_48802() +@test f48802!(log, 1) == 2 +@test log[] == "specialized" + # Methods with keyword arguments fookw(x; direction=:up) = direction fookw(y::Int) = 2 -@test fookw("string") == :up +@test fookw("string") === :up @test fookw(1) == 2 m = collect(methods(fookw))[2] Base.delete_method(m) @@ -964,3 +993,68 @@ end @eval m f4(a) = return @test Base.default_tt(m.f4) == Tuple end + +Base.@assume_effects :terminates_locally function issue41694(x::Int) + res = 1 + 1 < x < 20 || throw("bad") + while x > 1 + res *= x + x -= 1 + end + return res +end +maybe_effectful(x::Int) = 42 +maybe_effectful(x::Any) = unknown_operation() +function f_no_methods end +ambig_effects_test(a::Int, b) = 1 +ambig_effects_test(a, b::Int) = 1 +ambig_effects_test(a, b) = 1 + +@testset "infer_effects" begin + # generic functions + @test Base.infer_effects(issue41694, (Int,)) |> Core.Compiler.is_terminates + @test Base.infer_effects((Int,)) do x + issue41694(x) + end |> Core.Compiler.is_terminates + @test Base.infer_effects(issue41694) |> Core.Compiler.is_terminates # use `default_tt` + let effects = Base.infer_effects(maybe_effectful, (Any,)) # union split + @test !Core.Compiler.is_consistent(effects) + @test !Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_nothrow(effects) + @test !Core.Compiler.is_terminates(effects) + @test !Core.Compiler.is_nonoverlayed(effects) + end + # should account for MethodError + @test Base.infer_effects(issue41694, (Float64,)) |> !Core.Compiler.is_nothrow # definitive dispatch error + @test Base.infer_effects(issue41694, (Integer,)) |> !Core.Compiler.is_nothrow # possible dispatch error + @test Base.infer_effects(f_no_methods) |> !Core.Compiler.is_nothrow # no possible matching methods + @test Base.infer_effects(ambig_effects_test, (Int,Int)) |> !Core.Compiler.is_nothrow # ambiguity error + @test Base.infer_effects(ambig_effects_test, (Int,Any)) |> !Core.Compiler.is_nothrow # ambiguity error + # builtins + @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` +end + +@test Base._methods_by_ftype(Tuple{}, -1, Base.get_world_counter()) == Any[] +@test length(methods(Base.Broadcast.broadcasted, Tuple{Any, Any, Vararg})) > + length(methods(Base.Broadcast.broadcasted, Tuple{Base.Broadcast.BroadcastStyle, Any, Vararg})) >= + length(methods(Base.Broadcast.broadcasted, Tuple{Base.Broadcast.DefaultArrayStyle{1}, Any, Vararg})) >= + 10 + +@testset "specializations" begin + f(x) = 1 + f(1) + f("hello") + @test length(Base.specializations(only(methods(f)))) == 2 +end + +# https://github.com/JuliaLang/julia/issues/48856 +@test !Base.ismutationfree(Vector{Any}) +@test !Base.ismutationfree(Vector{Symbol}) +@test !Base.ismutationfree(Vector{UInt8}) +@test !Base.ismutationfree(Vector{Int32}) +@test !Base.ismutationfree(Vector{UInt64}) + +@test Base.ismutationfree(Type{Union{}}) diff --git a/test/regex.jl b/test/regex.jl index 0202dc4758e2f..e5f1428527512 100644 --- a/test/regex.jl +++ b/test/regex.jl @@ -59,6 +59,11 @@ @test repr(r"\\\"") == raw"r\"\\\\\\\"\"" @test repr(s"\\\"\\") == raw"s\"\\\\\\\"\\\\\"" + @test repr(r""a) == "r\"\"a" + @test repr(r""imsxa) == "r\"\"imsxa" + @test repr(Regex("", Base.DEFAULT_COMPILER_OPTS, UInt32(0))) == """Regex("", $(repr(Base.DEFAULT_COMPILER_OPTS)), $(repr(UInt32(0))))""" + @test repr(Regex("", UInt32(0), Base.DEFAULT_MATCH_OPTS)) == """Regex("", $(repr(UInt32(0))), $(repr(Base.DEFAULT_MATCH_OPTS)))""" + # findall @test findall(r"\w+", "foo bar") == [1:3, 5:7] @test findall(r"\w+", "foo bar", overlap=true) == [1:3, 2:3, 3:3, 5:7, 6:7, 7:7] @@ -69,6 +74,14 @@ @test findall('→', "OH⁻ + H₃CBr → HOH₃CBr⁻ → HOCH₃ + Br⁻") == [17, 35] @test findall('a', "") == Int[] @test findall('c', "batman") == Int[] + @test findall([0x52, 0x62], [0x40, 0x52, 0x62, 0x63]) == [2:3] + @test findall([0x52, 0x62], [0x40, 0x52, 0x62, 0x63, 0x52, 0x62]) == [2:3, 5:6] + @test findall([0x01, 0x01], [0x01, 0x01, 0x01, 0x01]) == [1:2, 3:4] + @test findall([0x01, 0x01], [0x01, 0x01, 0x01, 0x01]; overlap=true) == [1:2, 2:3, 3:4] + + # findnext + @test findnext(r"z", "zabcz", 2) == 5:5 + @test_throws BoundsError findnext(r"z", "zabcz", 7) # count @test count(r"\w+", "foo bar") == 2 @@ -114,14 +127,24 @@ # Backcapture reference in substitution string @test replace("abcde", r"(..)(?P<byname>d)" => s"\g<byname>xy\\\1") == "adxy\\bce" - @test_throws ErrorException replace("a", r"(?P<x>)" => s"\g<y>") + @test_throws(ErrorException("Bad replacement string: Group y not found in regex r\"(?P<x>)\""), + replace("a", r"(?P<x>)" => s"\g<y>")) + # test replace with invalid substitution group pattern + @test_throws(ErrorException("Bad replacement string: \\gg1>"), + replace("s", r"(?<g1>.)" => s"\gg1>")) + # test replace with 2-digit substitution group + @test replace(("0" ^ 9) * "1", Regex(("(0)" ^ 9) * "(1)") => s"10th group: \10") == "10th group: 1" # Proper unicode handling @test match(r"∀∀", "∀x∀∀∀").match == "∀∀" - # 'a' flag to disable UCP + # 'a' flag to disable UCP and UTF @test match(r"\w+", "Düsseldorf").match == "Düsseldorf" @test match(r"\w+"a, "Düsseldorf").match == "D" + @test match(r".+"a, "Düsseldorf").match == "Düsseldorf" + @test match(r".+"a, "Dü\xefsseldorf").match == "Dü\xefsseldorf" + @test_throws(ErrorException("PCRE.exec error: $(Base.PCRE.err_message(Base.PCRE.ERROR_UTF8_ERR6))"), + match(r"(*UTF).+"a, "Dü\xefsseldorf")) # Regex behaves like a scalar in broadcasting @test occursin.(r"Hello", ["Hello", "World"]) == [true, false] @@ -137,6 +160,8 @@ @test startswith("abc", r"A"i) @test !endswith("abc", r"C") @test endswith("abc", r"C"i) + # test with substring + @test endswith((@views "abc"[2:3]), r"C"i) @testset "multiplication & exponentiation" begin @test *(r"a") == r"a" @@ -197,9 +222,26 @@ end # Test that PCRE throws the correct kind of error - # TODO: Uncomment this once the corresponding change has propagated to CI - #@test_throws ErrorException Base.PCRE.info(C_NULL, Base.PCRE.INFO_NAMECOUNT, UInt32) + @test_throws ErrorException("PCRE error: NULL regex object") Base.PCRE.info(C_NULL, Base.PCRE.INFO_NAMECOUNT, UInt32) # test that we can get the error message of negative error codes @test Base.PCRE.err_message(Base.PCRE.ERROR_NOMEMORY) isa String + + # test failure cases for invalid integer flags + @test_throws ArgumentError Regex("test", typemax(Int32), 0) + @test_throws ArgumentError Regex("test", 0, typemax(Int32)) + + # hash + @test hash(r"123"i, zero(UInt)) == hash(Regex("123", "i"), zero(UInt)) +end + +@testset "#47936" begin + tests = (r"a+[bc]+c", + r"a+[bc]{1,2}c", + r"(a)+[bc]+c", + r"a{1,2}[bc]+c", + r"(a+)[bc]+c") + for re in tests + @test match(re, "ababc").match === SubString("ababc", 3:5) + end end diff --git a/test/reinterpretarray.jl b/test/reinterpretarray.jl index e623b407f70a6..fae4c6434e00d 100644 --- a/test/reinterpretarray.jl +++ b/test/reinterpretarray.jl @@ -180,7 +180,7 @@ end else @test_throws "Parent's strides" strides(reinterpret(Int64, view(A, 1:8, viewax2))) end - # non-integer-multipled classified + # non-integer-multiplied classified if mod(step(viewax2), 3) == 0 @test check_strides(reinterpret(NTuple{3,Int16}, view(A, 2:7, viewax2))) else @@ -197,6 +197,9 @@ end end @test check_strides(reinterpret(Float32, view(A, 8:-1:1, viewax2))) end + # issue 46113 + A = reinterpret(Int8, reinterpret(reshape, Int16, rand(Int8, 2, 3, 3))) + @test check_strides(A) end @testset "strides for ReshapedReinterpretArray" begin @@ -326,8 +329,8 @@ let a = [0.1 0.2; 0.3 0.4], at = reshape([(i,i+1) for i = 1:2:8], 2, 2) @test r[1,2] === reinterpret(Int64, v[1,2]) @test r[0,3] === reinterpret(Int64, v[0,3]) @test r[1,3] === reinterpret(Int64, v[1,3]) - @test_throws ArgumentError("cannot reinterpret a `Float64` array to `UInt32` when the first axis is OffsetArrays.IdOffsetRange(0:1). Try reshaping first.") reinterpret(UInt32, v) - @test_throws ArgumentError("`reinterpret(reshape, Tuple{Float64, Float64}, a)` where `eltype(a)` is Float64 requires that `axes(a, 1)` (got OffsetArrays.IdOffsetRange(0:1)) be equal to 1:2 (from the ratio of element sizes)") reinterpret(reshape, Tuple{Float64,Float64}, v) + @test_throws ArgumentError("cannot reinterpret a `Float64` array to `UInt32` when the first axis is $(repr(axes(v,1))). Try reshaping first.") reinterpret(UInt32, v) + @test_throws ArgumentError("`reinterpret(reshape, Tuple{Float64, Float64}, a)` where `eltype(a)` is Float64 requires that `axes(a, 1)` (got $(repr(axes(v,1)))) be equal to 1:2 (from the ratio of element sizes)") reinterpret(reshape, Tuple{Float64,Float64}, v) v = OffsetArray(a, (0, 1)) @test axes(reinterpret(reshape, Tuple{Float64,Float64}, v)) === (OffsetArrays.IdOffsetRange(Base.OneTo(2), 1),) r = reinterpret(UInt32, v) @@ -347,7 +350,7 @@ let a = [0.1 0.2; 0.3 0.4], at = reshape([(i,i+1) for i = 1:2:8], 2, 2) offsetvt = (-2, 4) vt = OffsetArray(at, offsetvt) istr = string(Int) - @test_throws ArgumentError("cannot reinterpret a `Tuple{$istr, $istr}` array to `$istr` when the first axis is OffsetArrays.IdOffsetRange(-1:0). Try reshaping first.") reinterpret(Int, vt) + @test_throws ArgumentError("cannot reinterpret a `Tuple{$istr, $istr}` array to `$istr` when the first axis is $(repr(axes(vt,1))). Try reshaping first.") reinterpret(Int, vt) vt = reshape(vt, 1:1, axes(vt)...) r = reinterpret(Int, vt) @test r == OffsetArray(reshape(1:8, 2, 2, 2), (0, offsetvt...)) @@ -465,9 +468,11 @@ end @test_throws ArgumentError reinterpret(Nothing, 1:6) @test_throws ArgumentError reinterpret(reshape, Missing, [0.0]) - # reintepret of empty array with reshape - @test reinterpret(reshape, Nothing, fill(missing, (0,0,0))) == fill(nothing, (0,0,0)) + # reinterpret of empty array + @test reinterpret(reshape, Nothing, fill(missing, (1,0,3))) == fill(nothing, (1,0,3)) + @test reinterpret(reshape, Missing, fill((), (0,))) == fill(missing, (0,)) @test_throws ArgumentError reinterpret(reshape, Nothing, fill(3.2, (0,0))) + @test_throws ArgumentError reinterpret(Missing, fill(77, (0,1))) @test_throws ArgumentError reinterpret(reshape, Float64, fill(nothing, 0)) # reinterpret of 0-dimensional array diff --git a/test/rounding.jl b/test/rounding.jl index e4c51212e81fa..508a68032e083 100644 --- a/test/rounding.jl +++ b/test/rounding.jl @@ -128,6 +128,16 @@ end else @test u === r end + + r = round(u, RoundFromZero) + if isfinite(u) + @test isfinite(r) + @test isinteger(r) + @test signbit(u) ? (r == floor(u)) : (r == ceil(u)) + @test signbit(u) == signbit(r) + else + @test u === r + end end end end @@ -171,6 +181,7 @@ end @test round.(y) ≈ t[(i+1+isodd(i>>2))>>2 for i in r] @test broadcast(x -> round(x, RoundNearestTiesAway), y) ≈ t[(i+1+(i>=0))>>2 for i in r] @test broadcast(x -> round(x, RoundNearestTiesUp), y) ≈ t[(i+2)>>2 for i in r] + @test broadcast(x -> round(x, RoundFromZero), y) ≈ t[(i+3*(i>=0))>>2 for i in r] end end end @@ -190,6 +201,10 @@ end @test round(Int,-2.5,RoundNearestTiesUp) == -2 @test round(Int,-1.5,RoundNearestTiesUp) == -1 @test round(Int,-1.9) == -2 + @test round(Int,nextfloat(1.0),RoundFromZero) == 2 + @test round(Int,-nextfloat(1.0),RoundFromZero) == -2 + @test round(Int,prevfloat(1.0),RoundFromZero) == 1 + @test round(Int,-prevfloat(1.0),RoundFromZero) == -1 @test_throws InexactError round(Int64, 9.223372036854776e18) @test round(Int64, 9.223372036854775e18) == 9223372036854774784 @test_throws InexactError round(Int64, -9.223372036854778e18) @@ -326,3 +341,13 @@ end @test f.(a, digits=9, base = 2) == map(x->f(x, digits=9, base = 2), a) end end + +@testset "rounding for F32/F64" begin + for T in [Float32, Float64] + old = rounding(T) + Base.Rounding.setrounding_raw(T, Base.Rounding.JL_FE_TOWARDZERO) + @test rounding(T) == RoundToZero + @test round(T(2.7)) == T(2.0) + Base.Rounding.setrounding_raw(T, Base.Rounding.to_fenv(old)) + end +end diff --git a/test/runtests.jl b/test/runtests.jl index aa9e101fa2182..16f60ddcf6764 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -75,6 +75,7 @@ move_to_node1("precompile") move_to_node1("SharedArrays") move_to_node1("threads") move_to_node1("Distributed") +move_to_node1("gc") # Ensure things like consuming all kernel pipe memory doesn't interfere with other tests move_to_node1("stress") @@ -102,11 +103,7 @@ cd(@__DIR__) do # * https://github.com/JuliaLang/julia/pull/29384 # * https://github.com/JuliaLang/julia/pull/40348 n = 1 - JULIA_TEST_USE_MULTIPLE_WORKERS = get(ENV, "JULIA_TEST_USE_MULTIPLE_WORKERS", "") |> - strip |> - lowercase |> - s -> tryparse(Bool, s) |> - x -> x === true + JULIA_TEST_USE_MULTIPLE_WORKERS = Base.get_bool_env("JULIA_TEST_USE_MULTIPLE_WORKERS", false) # If the `JULIA_TEST_USE_MULTIPLE_WORKERS` environment variable is set to `true`, we use # multiple worker processes regardless of the value of `net_on`. # Otherwise, we use multiple worker processes if and only if `net_on` is true. @@ -124,6 +121,15 @@ cd(@__DIR__) do Distributed.remotecall_eval(Main, workers(), revise_init_expr) end + println(""" + Running parallel tests with: + nworkers() = $(nworkers()) + nthreads() = $(Threads.threadpoolsize()) + Sys.CPU_THREADS = $(Sys.CPU_THREADS) + Sys.total_memory() = $(Base.format_bytes(Sys.total_memory())) + Sys.free_memory() = $(Base.format_bytes(Sys.free_memory())) + """) + #pretty print the information about gc and mem usage testgroupheader = "Test" workerheader = "(Worker)" @@ -238,7 +244,7 @@ cd(@__DIR__) do end end end - o_ts_duration = @elapsed @Experimental.sync begin + o_ts_duration = @elapsed Experimental.@sync begin for p in workers() @async begin push!(all_tasks, current_task()) diff --git a/test/ryu.jl b/test/ryu.jl index cf60e4867e236..0b10bd7e49ba5 100644 --- a/test/ryu.jl +++ b/test/ryu.jl @@ -52,6 +52,11 @@ end @test "2.305843009213694e40" == Ryu.writeshortest(Core.bitcast(Float64, 0x4850F0CF064DD592)) end +@testset "pow5 overflow (#47464)" begin + @test "4.6458339e+63" == Ryu.writeexp(4.645833859177319e63, 7) + @test "4.190673780e+40" == Ryu.writeexp(4.190673779576499e40, 9) +end + @testset "OutputLength" begin @test "1.0" == Ryu.writeshortest(1.0) # already tested in Basic @test "1.2" == Ryu.writeshortest(1.2) diff --git a/test/secretbuffer.jl b/test/secretbuffer.jl index aea2a662766c9..976c757deea57 100644 --- a/test/secretbuffer.jl +++ b/test/secretbuffer.jl @@ -99,6 +99,7 @@ using Test @test position(sb) == 0 skip(sb, sb.size) @test position(sb) == sb.size + shred!(sb) end @testset "seekend" begin sb = SecretBuffer("hello") @@ -108,7 +109,6 @@ using Test end @testset "position" begin sb = SecretBuffer("Julia") - println("testing position") initial_pos = (position(sb)) seek(sb,2) mid_pos = position(sb) @@ -120,5 +120,13 @@ using Test sb1 = SecretBuffer("hello") sb2 = SecretBuffer("juliaisawesome") @test hash(sb1, UInt(5)) === hash(sb2, UInt(5)) + shred!(sb1); shred!(sb2) + end + @testset "NULL initialization" begin + null_ptr = Cstring(C_NULL) + @test_throws ArgumentError Base.unsafe_SecretBuffer!(null_ptr) + null_ptr = Ptr{UInt8}(C_NULL) + @test_throws ArgumentError Base.unsafe_SecretBuffer!(null_ptr) + @test_throws ArgumentError Base.unsafe_SecretBuffer!(null_ptr, 0) end end diff --git a/test/sets.jl b/test/sets.jl index 43ba433e780d7..65444153c90d9 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -115,7 +115,7 @@ end @test in(2,s) @test length(s) == 2 @test_throws KeyError pop!(s,1) - @test pop!(s,1,:foo) == :foo + @test pop!(s,1,:foo) === :foo @test length(delete!(s,2)) == 1 @test !in(1,s) @test !in(2,s) @@ -151,6 +151,9 @@ end @test s === copy!(s, BitSet(a)) == S(a) end end + s = Set([1, 2]) + s2 = copy(s) + @test copy!(s, s) == s2 end @testset "sizehint, empty" begin @@ -390,9 +393,10 @@ end @test symdiff(Set([1]), BitSet()) isa Set{Int} @test symdiff(BitSet([1]), Set{Int}()) isa BitSet @test symdiff([1], BitSet()) isa Vector{Int} - # symdiff must NOT uniquify - @test symdiff([1, 2, 1]) == symdiff!([1, 2, 1]) == [2] - @test symdiff([1, 2, 1], [2, 2]) == symdiff!([1, 2, 1], [2, 2]) == [2] + #symdiff does uniquify + @test symdiff([1, 2, 1]) == symdiff!([1, 2, 1]) == [1,2] + @test symdiff([1, 2, 1], [2, 2]) == symdiff!([1, 2, 1], [2, 2]) == [1] + @test symdiff([1, 2, 1], [2, 2]) == symdiff!([1, 2, 1], [2, 2]) == [1] # Base.hasfastin @test all(Base.hasfastin, Any[Dict(1=>2), Set(1), BitSet(1), 1:9, 1:2:9, @@ -416,6 +420,48 @@ end @test issubset(Set(Bool[]), rand(Bool, 100)) == true # neither has a fast in, right doesn't have a length @test isdisjoint([1, 3, 5, 7, 9], Iterators.filter(iseven, 1:10)) + + # range fast-path + for (truth, a, b) in ( + # Integers + (true, 1:10, 11:20), # not overlapping + (false, 1:10, 5:20), # partial overlap + (false, 5:9, 1:10), # complete overlap + # complete overlap, unequal steps + (false, 3:6:60, 9:9:60), + (true, 4:6:60, 9:9:60), + (true, 0:6:12, 9:9:60), + (false, 6:6:18, 9:9:60), + (false, 12:6:18, 9:9:60), + (false, 18:6:18, 9:9:60), + (true, 1:2:3, 2:3:5), + (true, 1:4:5, 2:1:4), + (false, 4:12:124, 1:1:8), + # potential overflow + (false, 0x1:0x3:0x4, 0x4:0x3:0x4), + (true, 0x3:0x3:0x6, 0x4:0x3:0x4), + (false, typemax(Int8):Int8(3):typemax(Int8), typemin(Int8):Int8(3):typemax(Int8)), + # Chars + (true, 'a':'l', 'o':'p'), # not overlapping + (false, 'a':'l', 'h':'p'), # partial overlap + (false, 'a':'l', 'c':'e'), # complete overlap + # Floats + (true, 1.:10., 11.:20.), # not overlapping + (false, 1.:10., 5.:20.), # partial overlap + (false, 5.:9., 1.:10.), # complete overlap + # Inputs that may hang + (false, -6011687643038262928:3545293653953105048, -6446834672754204848:3271267329311042532), + ) + @test isdisjoint(a, b) == truth + @test isdisjoint(b, a) == truth + @test isdisjoint(a, reverse(b)) == truth + @test isdisjoint(reverse(a), b) == truth + @test isdisjoint(b, reverse(a)) == truth + @test isdisjoint(reverse(b), a) == truth + end + @test isdisjoint(10:9, 1:10) # empty range + @test !isdisjoint(1e-100:.1:1, 0:.1:1) + @test !isdisjoint(eps()/4:.1:.71, 0:.1:1) end @testset "unique" begin @@ -423,6 +469,9 @@ end @test in(1, u) @test in(2, u) @test length(u) == 2 + @test unique(iseven, []) == [] + # type promotion + @test unique(x -> x^2, [1, 3.]) == [1, 3.] @test @inferred(unique(iseven, [5, 1, 8, 9, 3, 4, 10, 7, 2, 6])) == [5, 8] @test @inferred(unique(x->x^2, Integer[3, -4, 5, 4])) == Integer[3, -4, 5] @test @inferred(unique(iseven, Integer[3, -4, 5, 4]; seen=Set{Bool}())) == Integer[3, -4] @@ -445,6 +494,8 @@ end end @testset "unique!" begin + u = [] + @test unique!(u) === u u = [1,1,3,2,1] @inferred(unique!(u)) @test u == [1,3,2] @@ -491,10 +542,23 @@ end @test allunique([]) @test allunique(Set()) @test allunique([1,2,3]) + @test allunique([1 2; 3 4]) @test allunique([:a,:b,:c]) @test allunique(Set([1,2,3])) @test !allunique([1,1,2]) @test !allunique([:a,:b,:c,:a]) + @test allunique(unique(randn(100))) # 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 + @test allunique(eachrow(hcat(1:10, 1:10))) + @test allunique(x for x in 'A':'Z' if randn()>0) + @test !allunique(x for x in repeat(1:2000, 3) if true) + @test allunique([0.0, -0.0]) + @test allunique(x for x in [0.0, -0.0] if true) + @test !allunique([NaN, NaN]) + @test !allunique(x for x in [NaN, NaN] if true) + # ranges @test allunique(4:7) @test allunique(1:1) @test allunique(4.0:0.3:7.0) @@ -503,6 +567,7 @@ end @test allunique(Date(2018, 8, 7):Day(1):Date(2018, 8, 11)) # JuliaCon 2018 @test allunique(DateTime(2018, 8, 7):Hour(1):DateTime(2018, 8, 11)) @test allunique(('a':1:'c')[1:2]) == true + @test allunique(collect(1:1001)) for r = (Base.OneTo(-1), Base.OneTo(0), Base.OneTo(1), Base.OneTo(5), 1:0, 1:1, 1:2, 1:10, 1:.5:.5, 1:.5:1, 1:.5:10, 3:-2:5, 3:-2:3, 3:-2:1, StepRangeLen(1.0, 2.0, 0), StepRangeLen(1.0, 2.0, 2), StepRangeLen(1.0, 2.0, 3), @@ -510,6 +575,13 @@ end LinRange(1, 2, 3), LinRange(1, 1, 0), LinRange(1, 1, 1), LinRange(1, 1, 10)) @test allunique(r) == invoke(allunique, Tuple{Any}, r) end + # tuples + @test allunique(()) + @test allunique((1,2,3)) + @test allunique(ntuple(identity, 40)) + @test !allunique((1,2,3,4,3)) + @test allunique((0.0, -0.0)) + @test !allunique((NaN, NaN)) end @testset "allequal" begin @@ -704,8 +776,7 @@ end x = @inferred replace([1, 2], 2=>missing) @test isequal(x, [1, missing]) && x isa Vector{Union{Int, Missing}} - @test_broken @inferred replace([1, missing], missing=>2) - x = replace([1, missing], missing=>2) + x = @inferred replace([1, missing], missing=>2) @test x == [1, 2] && x isa Vector{Int} x = @inferred replace([1, missing], missing=>2, count=1) @test x == [1, 2] && x isa Vector{Union{Int, Missing}} @@ -850,3 +921,14 @@ end @test !((1, 2) ⊊ (1, 2, 2)) @test !((1, 2, 2) ⊋ (1, 2)) end + +@testset "AbstractSet & Fallback" begin + mutable struct TestSet{T} <: AbstractSet{T} + set::Set{T} + function TestSet{T}() where T + new{T}(Set{T}()) + end + end + set = TestSet{Any}() + @test sizehint!(set, 1) === set +end diff --git a/test/show.jl b/test/show.jl index ba9f227e53e52..76772c649a838 100644 --- a/test/show.jl +++ b/test/show.jl @@ -268,7 +268,6 @@ end @test repr(Expr(:import, :Foo)) == ":(\$(Expr(:import, :Foo)))" @test repr(Expr(:import, Expr(:(.), ))) == ":(\$(Expr(:import, :(\$(Expr(:.))))))" - @test repr(Expr(:using, Expr(:(.), :A))) == ":(using A)" @test repr(Expr(:using, Expr(:(.), :A), Expr(:(.), :B))) == ":(using A, B)" @@ -286,6 +285,10 @@ end @test repr(Expr(:import, Expr(:(.), :A, :B), Expr(:(.), :C, :D))) == ":(import A.B, C.D)" +# https://github.com/JuliaLang/julia/issues/49168 +@test repr(:(using A: (..))) == ":(using A: (..))" +@test repr(:(using A: (..) as twodots)) == ":(using A: (..) as twodots)" + # range syntax @test_repr "1:2" @test_repr "3:4:5" @@ -528,7 +531,7 @@ module M1 var"#foo#"() = 2 end module var"#43932#" end @test endswith(sprint(show, var"#43932#"), ".var\"#43932#\"") -# issue #12477 +# issue #12477 @test sprint(show, Union{Int64, Int32, Int16, Int8, Float64}) == "Union{Float64, Int16, Int32, Int64, Int8}" # Function and array reference precedence @@ -603,7 +606,7 @@ let q1 = Meta.parse(repr(:("$(a)b"))), @test q1.args[1].args == [:a, "b"] @test isa(q2, Expr) - @test q2.args[1].head == :string + @test q2.args[1].head === :string @test q2.args[1].args == [:ab,] end @@ -769,12 +772,33 @@ let repr = sprint(show, "text/html", methods(f16580)) @test occursin("f16580(x, y...; <i>z, w, q...</i>)", repr) end +# Just check it doesn't error +f46594(::Vararg{T, 2}) where T = 1 +let repr = sprint(show, "text/html", first(methods(f46594))) + @test occursin("f46594(::Vararg{T, 2}) where T", replace(repr, r"</?[A-Za-z]>"=>"")) +end + function triangular_methodshow(x::T1, y::T2) where {T2<:Integer, T1<:T2} end let repr = sprint(show, "text/plain", methods(triangular_methodshow)) @test occursin("where {T2<:Integer, T1<:T2}", repr) end +struct S45879{P} end +let ms = methods(S45879) + @test ms isa Base.MethodList + @test length(ms) == 0 + @test sprint(show, Base.MethodList(Method[], typeof(S45879).name.mt)) isa String +end + +function f49475(a=12.0; b) end +let ms = methods(f49475) + @test length(ms) == 2 + repr1 = sprint(show, "text/plain", ms[1]) + repr2 = sprint(show, "text/plain", ms[2]) + @test occursin("f49475(; ...)", repr1) || occursin("f49475(; ...)", repr2) +end + if isempty(Base.GIT_VERSION_INFO.commit) @test occursin("https://github.com/JuliaLang/julia/tree/v$VERSION/base/special/trig.jl#L", Base.url(which(sin, (Float64,)))) else @@ -843,7 +867,7 @@ end end lower = length("\"\" ⋯ $(ncodeunits(str)) bytes ⋯ \"\"") limit = max(limit, lower) - if length(str) + 2 ≤ limit + if length(str) + 2 ≤ limit @test eval(Meta.parse(out)) == str else @test limit-!isascii(str) <= length(out) <= limit @@ -1262,12 +1286,6 @@ end let repr = sprint(dump, Core.svec()) @test repr == "empty SimpleVector\n" end -let sv = Core.svec(:a, :b, :c) - # unsafe replacement of :c with #undef to test handling of incomplete SimpleVectors - unsafe_store!(convert(Ptr{Ptr{Cvoid}}, Base.pointer_from_objref(sv)) + 3 * sizeof(Ptr), C_NULL) - repr = sprint(dump, sv) - @test repr == "SimpleVector\n 1: Symbol a\n 2: Symbol b\n 3: #undef\n" -end let repr = sprint(dump, sin) @test repr == "sin (function of type typeof(sin))\n" end @@ -1338,6 +1356,16 @@ test_repr("(:).a") @test repr(NTuple{7,Int64}) == "NTuple{7, Int64}" @test repr(Tuple{Float64, Float64, Float64, Float64}) == "NTuple{4, Float64}" @test repr(Tuple{Float32, Float32, Float32}) == "Tuple{Float32, Float32, Float32}" +@test repr(Tuple{String, Int64, Int64, Int64}) == "Tuple{String, Int64, Int64, Int64}" +@test repr(Tuple{String, Int64, Int64, Int64, Int64}) == "Tuple{String, Vararg{Int64, 4}}" + +# Test printing of NamedTuples using the macro syntax +@test repr(@NamedTuple{kw::Int64}) == "@NamedTuple{kw::Int64}" +@test repr(@NamedTuple{kw::Union{Float64, Int64}, kw2::Int64}) == "@NamedTuple{kw::Union{Float64, Int64}, kw2::Int64}" +@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}" + @testset "issue #42931" begin @test repr(NTuple{4, :A}) == "NTuple{4, :A}" @@ -1429,7 +1457,7 @@ struct var"#X#" end var"#f#"() = 2 struct var"%X%" end # Invalid name without '#' -# (Just to make this test more sustainable,) we don't necesssarily need to test the exact +# (Just to make this test more sustainable,) we don't necessarily need to test the exact # output format, just ensure that it prints at least the parts we expect: @test occursin(".var\"#X#\"", static_shown(var"#X#")) # Leading `.` tests it printed a module name. @test occursin(r"Set{var\"[^\"]+\"} where var\"[^\"]+\"", static_shown(Set{<:Any})) @@ -1453,6 +1481,9 @@ struct var"%X%" end # Invalid name without '#' end end +# Test that static show prints something reasonable for `<:Function` types +@test static_shown(:) == "Base.Colon()" + # Test @show let fname = tempname() try @@ -1815,8 +1846,8 @@ end # issue #27747 let t = (x = Integer[1, 2],) v = [t, t] - @test showstr(v) == "NamedTuple{(:x,), Tuple{Vector{Integer}}}[(x = [1, 2],), (x = [1, 2],)]" - @test replstr(v) == "2-element Vector{NamedTuple{(:x,), Tuple{Vector{Integer}}}}:\n (x = [1, 2],)\n (x = [1, 2],)" + @test showstr(v) == "@NamedTuple{x::Vector{Integer}}[(x = [1, 2],), (x = [1, 2],)]" + @test replstr(v) == "2-element Vector{@NamedTuple{x::Vector{Integer}}}:\n (x = [1, 2],)\n (x = [1, 2],)" end # issue #25857 @@ -1855,6 +1886,10 @@ end @test replstr((; var"#var#"=1)) == """(var"#var#" = 1,)""" @test replstr((; var"a"=1, b=2)) == "(a = 1, b = 2)" @test replstr((; a=1, b=2)) == "(a = 1, b = 2)" + + # issue 48828, typeinfo missing for arrays with >2 dimensions + @test showstr(Float16[1.0 3.0; 2.0 4.0;;; 5.0 7.0; 6.0 8.0]) == + "Float16[1.0 3.0; 2.0 4.0;;; 5.0 7.0; 6.0 8.0]" end @testset "#14684: `display` should print associative types in full" begin @@ -1886,16 +1921,16 @@ function _methodsstr(@nospecialize f) end @testset "show function methods" begin - @test occursin("methods for generic function \"sin\":\n", _methodsstr(sin)) + @test occursin("methods for generic function \"sin\" from Base:\n", _methodsstr(sin)) end @testset "show macro methods" begin - @test startswith(_methodsstr(getfield(Base,Symbol("@show"))), "# 1 method for macro \"@show\":\n") + @test startswith(_methodsstr(getfield(Base,Symbol("@show"))), "# 1 method for macro \"@show\" from Base:\n") end @testset "show constructor methods" begin @test occursin(" methods for type constructor:\n", _methodsstr(Vector)) end @testset "show builtin methods" begin - @test startswith(_methodsstr(typeof), "# 1 method for builtin function \"typeof\":\n") + @test startswith(_methodsstr(typeof), "# 1 method for builtin function \"typeof\" from Core:\n") end @testset "show callable object methods" begin @test occursin("methods for callable object:\n", _methodsstr(:)) @@ -2024,21 +2059,17 @@ let src = code_typed(my_fun28173, (Int,), debuginfo=:source)[1][1] lines2 = split(repr(ir), '\n') @test all(isspace, pop!(lines2)) @test popfirst!(lines2) == "2 1 ── $(QuoteNode(1))" - @test popfirst!(lines2) == " │ $(QuoteNode(2))" # TODO: this should print after the next statement let line1 = popfirst!(lines1) line2 = popfirst!(lines2) @test startswith(line1, "2 1 ── ") @test startswith(line2, " │ ") @test line2[12:end] == line2[12:end] end - let line1 = pop!(lines1) - line2 = pop!(lines2) - @test startswith(line1, "17 ") - @test startswith(line2, " ") - @test line1[3:end] == line2[3:end] - end - @test pop!(lines2) == " │ \$(QuoteNode(4))" - @test pop!(lines2) == "17 │ \$(QuoteNode(3))" # TODO: this should print after the next statement + @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 lines1 == lines2 # verbose linetable @@ -2046,6 +2077,13 @@ let src = code_typed(my_fun28173, (Int,), debuginfo=:source)[1][1] Base.IRShow.show_ir(io, ir, Base.IRShow.default_config(ir; verbose_linetable=true)) seekstart(io) @test count(contains(r"@ a{80}:\d+ within `my_fun28173"), eachline(io)) == 10 + + # Test that a bad :invoke doesn't cause an error during printing + Core.Compiler.insert_node!(ir, 1, Core.Compiler.NewInstruction(Expr(:invoke, nothing, sin), Any), false) + io = IOBuffer() + Base.IRShow.show_ir(io, ir) + seekstart(io) + @test contains(String(take!(io)), "Expr(:invoke, nothing") end # Verify that extra instructions at the end of the IR @@ -2287,6 +2325,8 @@ end @eval f1(var"a.b") = 3 @test occursin("f1(var\"a.b\")", sprint(_show, methods(f1))) + @test sprint(_show, Method[]) == "0-element Vector{Method}" + italic(s) = mime == MIME("text/html") ? "<i>$s</i>" : s @eval f2(; var"123") = 5 @@ -2355,3 +2395,233 @@ end @test sprint(show, setenv(setcpuaffinity(`true`, [1, 2]), "A" => "B")) == """setenv(setcpuaffinity(`true`, [1, 2]),["A=B"])""" end + +# Test that alignment takes into account unicode and computes alignment without +# color/formatting. + +struct ColoredLetter; end +Base.show(io::IO, ces::ColoredLetter) = Base.printstyled(io, 'A'; color=:red) + +struct ⛵; end +Base.show(io::IO, ces::⛵) = Base.print(io, '⛵') + +@test Base.alignment(stdout, ⛵()) == (0, 2) +@test Base.alignment(IOContext(IOBuffer(), :color=>true), ColoredLetter()) == (0, 1) +@test Base.alignment(IOContext(IOBuffer(), :color=>false), ColoredLetter()) == (0, 1) + +# spacing around dots in Diagonal, etc: +redminusthree = sprint((io, x) -> printstyled(io, x, color=:red), "-3", context=stdout) +@test Base.replace_with_centered_mark(redminusthree) == Base.replace_with_centered_mark("-3") + +# `show` implementations for `Method` +let buf = IOBuffer() + + # single line printing by default + show(buf, only(methods(sin, (Float64,)))) + @test !occursin('\n', String(take!(buf))) + + # two-line printing for rich display + show(buf, MIME("text/plain"), only(methods(sin, (Float64,)))) + @test occursin('\n', String(take!(buf))) +end + +@testset "basic `show_ir` functionality tests" begin + mktemp() do f, io + redirect_stdout(io) do + let io = IOBuffer() + for i = 1:10 + # make sure we don't error on printing IRs at any optimization level + ir = only(Base.code_ircode(sin, (Float64,); optimize_until=i))[1] + @test try; show(io, ir); true; catch; false; end + compact = Core.Compiler.IncrementalCompact(ir) + @test try; show(io, compact); true; catch; false; end + end + end + end + close(io) + @test isempty(read(f, String)) # make sure we don't unnecessarily lean anything into `stdout` + end +end + +@testset "IRCode: fix coloring of invalid SSA values" begin + # get some ir + function foo(i) + j = i+42 + j == 1 ? 1 : 2 + end + ir = only(Base.code_ircode(foo, (Int,)))[1] + + # replace an instruction + add_stmt = ir.stmts[1] + inst = Core.Compiler.NewInstruction(Expr(:call, add_stmt[:inst].args[1], add_stmt[:inst].args[2], 999), Int) + node = Core.Compiler.insert_node!(ir, 1, inst) + Core.Compiler.setindex!(add_stmt, node, :inst) + + # the new node should be colored green (as it's uncompacted IR), + # and its uses shouldn't be colored at all (since they're just plain valid references) + str = sprint(; context=:color=>true) do io + show(io, ir) + end + @test contains(str, "\e[32m%6 =") + @test contains(str, "%1 = %6") + + # if we insert an invalid node, it should be colored appropriately + Core.Compiler.setindex!(add_stmt, Core.Compiler.SSAValue(node.id+1), :inst) + str = sprint(; context=:color=>true) do io + show(io, ir) + end + @test contains(str, "%1 = \e[31m%7") +end + +@testset "issue #46947: IncrementalCompact double display of just-compacted nodes" begin + # get some IR + foo(i) = i == 1 ? 1 : 2 + ir = only(Base.code_ircode(foo, (Int,)))[1] + + instructions = length(ir.stmts) + lines_shown(obj) = length(findall('\n', sprint(io->show(io, obj)))) + @test lines_shown(ir) == instructions + + # insert a couple of instructions + let inst = Core.Compiler.NewInstruction(Expr(:identity, 1), Nothing) + Core.Compiler.insert_node!(ir, 2, inst) + end + let inst = Core.Compiler.NewInstruction(Expr(:identity, 2), Nothing) + Core.Compiler.insert_node!(ir, 2, inst) + end + let inst = Core.Compiler.NewInstruction(Expr(:identity, 3), Nothing) + Core.Compiler.insert_node!(ir, 4, inst) + end + instructions += 3 + @test lines_shown(ir) == instructions + + # compact the IR, ensuring we always show the same number of lines + # (the instructions + a separator line) + compact = Core.Compiler.IncrementalCompact(ir) + @test lines_shown(compact) == instructions + 1 + state = Core.Compiler.iterate(compact) + while state !== nothing + @test lines_shown(compact) == instructions + 1 + state = Core.Compiler.iterate(compact, state[2]) + end + @test lines_shown(compact) == instructions + 1 + + ir = Core.Compiler.complete(compact) + @test lines_shown(compact) == instructions + 1 +end + +@testset "#46424: IncrementalCompact displays wrong basic-block boundaries" begin + # get some cfg + function foo(i) + j = i+42 + j == 1 ? 1 : 2 + end + ir = only(Base.code_ircode(foo, (Int,)))[1] + + # at every point we should be able to observe these three basic blocks + function verify_display(ir) + str = sprint(io->show(io, ir)) + @test contains(str, "1 ─ %1 = ") + @test contains(str, r"2 ─ \s+ return 1") + @test contains(str, r"3 ─ \s+ return 2") + end + verify_display(ir) + + # insert some instructions + for i in 1:3 + inst = Core.Compiler.NewInstruction(Expr(:call, :identity, i), Int) + Core.Compiler.insert_node!(ir, 2, inst) + end + + # compact + compact = Core.Compiler.IncrementalCompact(ir) + verify_display(compact) + + # Compact the first instruction + state = Core.Compiler.iterate(compact) + + # Insert some instructions here + for i in 1:2 + inst = Core.Compiler.NewInstruction(Expr(:call, :identity, i), Int, Int32(1)) + Core.Compiler.insert_node_here!(compact, inst) + verify_display(compact) + end + + while state !== nothing + state = Core.Compiler.iterate(compact, state[2]) + verify_display(compact) + end + + # complete + ir = Core.Compiler.complete(compact) + verify_display(ir) +end + +@testset "IRCode: CFG display" begin + # get a cfg + function foo(i) + j = i+42 + j == 1 ? 1 : 2 + end + ir = only(Base.code_ircode(foo, (Int,)))[1] + cfg = ir.cfg + + str = sprint(io->show(io, cfg)) + @test contains(str, r"CFG with \d+ blocks") + @test contains(str, r"bb 1 \(stmt.+\) → bb.*") +end + +@testset "IncrementalCompact: correctly display attach-after nodes" begin + # set some IR + function foo(i) + j = i+42 + return j + end + ir = only(Base.code_ircode(foo, (Int,)))[1] + + # insert a bunch of nodes, inserting both before and after instruction 1 + inst = Core.Compiler.NewInstruction(Expr(:call, :identity, 1), Int) + Core.Compiler.insert_node!(ir, 1, inst) + inst = Core.Compiler.NewInstruction(Expr(:call, :identity, 2), Int) + Core.Compiler.insert_node!(ir, 1, inst) + inst = Core.Compiler.NewInstruction(Expr(:call, :identity, 3), Int) + Core.Compiler.insert_node!(ir, 1, inst, true) + inst = Core.Compiler.NewInstruction(Expr(:call, :identity, 4), Int) + Core.Compiler.insert_node!(ir, 1, inst, true) + + # at every point we should be able to observe these instructions (in order) + function verify_display(ir) + str = sprint(io->show(io, ir)) + lines = split(str, '\n') + patterns = ["identity(1)", + "identity(2)", + "add_int", + "identity(3)", + "identity(4)", + "return"] + line_idx = 1 + pattern_idx = 1 + while pattern_idx <= length(patterns) && line_idx <= length(lines) + # we test pattern-per-pattern, in order, + # so that we skip e.g. the compaction boundary + if contains(lines[line_idx], patterns[pattern_idx]) + pattern_idx += 1 + end + line_idx += 1 + end + @test pattern_idx > length(patterns) + end + verify_display(ir) + + compact = Core.Compiler.IncrementalCompact(ir) + verify_display(compact) + + state = Core.Compiler.iterate(compact) + while state !== nothing + verify_display(compact) + state = Core.Compiler.iterate(compact, state[2]) + end + + ir = Core.Compiler.complete(compact) + verify_display(ir) +end diff --git a/test/some.jl b/test/some.jl index 27d50ca354a49..e49fc586a3a6e 100644 --- a/test/some.jl +++ b/test/some.jl @@ -33,7 +33,7 @@ @test convert(Union{Int, Nothing}, 1) === 1 @test convert(Union{Int, Nothing}, 1.0) === 1 @test convert(Nothing, nothing) === nothing -@test_throws MethodError convert(Nothing, 1) +@test_throws ErrorException("cannot convert a value to nothing for assignment") convert(Nothing, 1) ## show() diff --git a/test/sorting.jl b/test/sorting.jl index 86479eca6cc78..ec1666dabb2fb 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -47,9 +47,25 @@ end @test r == [3,1,2] @test r === s end - @test_throws ArgumentError sortperm!(view([1,2,3,4], 1:4), [2,3,1]) - @test sortperm(OffsetVector([8.0,-2.0,0.5], -4)) == OffsetVector([-2, -1, -3], -4) - @test sortperm!(Int32[1,2], [2.0, 1.0]) == Int32[2, 1] + @test_throws ArgumentError sortperm!(view([1, 2, 3, 4], 1:4), [2, 3, 1]) + @test sortperm(OffsetVector([8.0, -2.0, 0.5], -4)) == OffsetVector([-2, -1, -3], -4) + @test sortperm!(Int32[1, 2], [2.0, 1.0]) == Int32[2, 1] + @test_throws ArgumentError sortperm!(Int32[1, 2], [2.0, 1.0]; dims=1) + let A = rand(4, 4, 4) + for dims = 1:3 + perm = sortperm(A; dims) + sorted = sort(A; dims) + @test A[perm] == sorted + + perm_idx = similar(Array{Int}, axes(A)) + sortperm!(perm_idx, A; dims) + @test perm_idx == perm + end + end + @test_throws ArgumentError sortperm!(zeros(Int, 3, 3), rand(3, 3);) + @test_throws ArgumentError sortperm!(zeros(Int, 3, 3), rand(3, 3); dims=3) + @test_throws ArgumentError sortperm!(zeros(Int, 3, 4), rand(4, 4); dims=1) + @test_throws ArgumentError sortperm!(OffsetArray(zeros(Int, 4, 4), -4:-1, 1:4), rand(4, 4); dims=1) end @testset "misc sorting" begin @@ -59,6 +75,17 @@ end @test sum(randperm(6)) == 21 @test length(reverse(0x1:0x2)) == 2 @test issorted(sort(rand(UInt64(1):UInt64(2), 7); rev=true); rev=true) # issue #43034 + @test sort(Union{}[]) == Union{}[] # issue #45280 +end + +@testset "stability" begin + for Alg in [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.DEFAULT_STABLE, + Base.Sort.ScratchQuickSort(missing, 1729), Base.Sort.ScratchQuickSort(1729, missing)] + @test issorted(sort(1:2000, alg=Alg, by=x->0)) + @test issorted(sort(1:2000, alg=Alg, by=x->x÷100)) + end + @test sort(1:2000, by=x->x÷100, rev=true) == sort(1:2000, by=x->-x÷100) == + vcat(2000, (x:x+99 for x in 1900:-100:100)..., 1:99) end @testset "partialsort" begin @@ -83,166 +110,6 @@ end @test_throws ArgumentError partialsortperm!([1,2], [2,3,1], 1:2) end -@testset "searchsorted" begin - numTypes = [ Int8, Int16, Int32, Int64, Int128, - UInt8, UInt16, UInt32, UInt64, UInt128, - Float16, Float32, Float64, BigInt, BigFloat] - - @test searchsorted([1:10;], 1, by=(x -> x >= 5)) == 1:4 - @test searchsorted([1:10;], 10, by=(x -> x >= 5)) == 5:10 - @test searchsorted([1:5; 1:5; 1:5], 1, 6, 10, Forward) == 6:6 - @test searchsorted(fill(1, 15), 1, 6, 10, Forward) == 6:10 - - for R in numTypes, T in numTypes - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(0)) === 1:0 - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(1)) == 1:2 - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(2)) == 3:4 - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(4)) === 7:6 - @test searchsorted(R[1, 1, 2, 2, 3, 3], 2.5) === 5:4 - - @test searchsorted(1:3, T(0)) === 1:0 - @test searchsorted(1:3, T(1)) == 1:1 - @test searchsorted(1:3, T(2)) == 2:2 - @test searchsorted(1:3, T(4)) === 4:3 - - @test searchsorted(R[1:10;], T(1), by=(x -> x >= 5)) == 1:4 - @test searchsorted(R[1:10;], T(10), by=(x -> x >= 5)) == 5:10 - @test searchsorted(R[1:5; 1:5; 1:5], T(1), 6, 10, Forward) == 6:6 - @test searchsorted(fill(R(1), 15), T(1), 6, 10, Forward) == 6:10 - end - - for (rg,I) in Any[(49:57,47:59), (1:2:17,-1:19), (-3:0.5:2,-5:.5:4)] - rg_r = reverse(rg) - rgv, rgv_r = [rg;], [rg_r;] - for i = I - @test searchsorted(rg,i) === searchsorted(rgv,i) - @test searchsorted(rg_r,i,rev=true) === searchsorted(rgv_r,i,rev=true) - end - end - - rg = 0.0:0.01:1.0 - for i = 2:101 - @test searchsorted(rg, rg[i]) == i:i - @test searchsorted(rg, prevfloat(rg[i])) === i:i-1 - @test searchsorted(rg, nextfloat(rg[i])) === i+1:i - end - - rg_r = reverse(rg) - for i = 1:100 - @test searchsorted(rg_r, rg_r[i], rev=true) == i:i - @test searchsorted(rg_r, prevfloat(rg_r[i]), rev=true) === i+1:i - @test searchsorted(rg_r, nextfloat(rg_r[i]), rev=true) === i:i-1 - end - - @test searchsorted(1:10, 1, by=(x -> x >= 5)) == searchsorted([1:10;], 1, by=(x -> x >= 5)) - @test searchsorted(1:10, 10, by=(x -> x >= 5)) == searchsorted([1:10;], 10, by=(x -> x >= 5)) - - @test searchsorted([], 0) === 1:0 - @test searchsorted([1,2,3], 0) === 1:0 - @test searchsorted([1,2,3], 4) === 4:3 - - @testset "issue 8866" begin - @test searchsortedfirst(500:1.0:600, -1.0e20) == 1 - @test searchsortedfirst(500:1.0:600, 1.0e20) == 102 - @test searchsortedlast(500:1.0:600, -1.0e20) == 0 - @test searchsortedlast(500:1.0:600, 1.0e20) == 101 - end - - @testset "issue 10966" begin - for R in numTypes, T in numTypes - @test searchsortedfirst(R(2):R(2), T(0)) == 1 - @test searchsortedfirst(R(2):R(2), T(2)) == 1 - @test searchsortedfirst(R(2):R(2), T(3)) == 2 - @test searchsortedfirst(R(1):1//2:R(5), T(0)) == 1 - @test searchsortedfirst(R(1):1//2:R(5), T(2)) == 3 - @test searchsortedfirst(R(1):1//2:R(5), T(6)) == 10 - @test searchsortedlast(R(2):R(2), T(0)) == 0 - @test searchsortedlast(R(2):R(2), T(2)) == 1 - @test searchsortedlast(R(2):R(2), T(3)) == 1 - @test searchsortedlast(R(1):1//2:R(5), T(0)) == 0 - @test searchsortedlast(R(1):1//2:R(5), T(2)) == 3 - @test searchsortedlast(R(1):1//2:R(5), T(6)) == 9 - @test searchsorted(R(2):R(2), T(0)) === 1:0 - @test searchsorted(R(2):R(2), T(2)) == 1:1 - @test searchsorted(R(2):R(2), T(3)) === 2:1 - end - end - - @testset "issue 32568" begin - for R in numTypes, T in numTypes - for arr in Any[R[1:5;], R(1):R(5), R(1):2:R(5)] - @test eltype(searchsorted(arr, T(2))) == keytype(arr) - @test eltype(searchsorted(arr, T(2), big(1), big(4), Forward)) == keytype(arr) - @test searchsortedfirst(arr, T(2)) isa keytype(arr) - @test searchsortedfirst(arr, T(2), big(1), big(4), Forward) isa keytype(arr) - @test searchsortedlast(arr, T(2)) isa keytype(arr) - @test searchsortedlast(arr, T(2), big(1), big(4), Forward) isa keytype(arr) - end - end - end - - @testset "issue #34157" begin - @test searchsorted(1:2.0, -Inf) === 1:0 - @test searchsorted([1,2], -Inf) === 1:0 - @test searchsorted(1:2, -Inf) === 1:0 - - @test searchsorted(1:2.0, Inf) === 3:2 - @test searchsorted([1,2], Inf) === 3:2 - @test searchsorted(1:2, Inf) === 3:2 - - for coll in Any[ - Base.OneTo(10), - 1:2, - 0x01:0x02, - -4:6, - 5:2:10, - [1,2], - 1.0:4, - [10.0,20.0], - ] - for huge in Any[Inf, 1e300, typemax(Int64), typemax(UInt64)] - @test searchsortedfirst(coll, huge) === lastindex(coll) + 1 - @test searchsortedlast(coll, huge) === lastindex(coll) - @test searchsorted(coll, huge) === lastindex(coll)+1 : lastindex(coll) - if !(eltype(coll) <: Unsigned) - @test searchsortedfirst(reverse(coll), huge, rev=true) === firstindex(coll) - @test searchsortedlast(reverse(coll), huge, rev=true) === firstindex(coll) - 1 - @test searchsorted(reverse(coll), huge, rev=true) === firstindex(coll):firstindex(coll) - 1 - end - - if !(huge isa Unsigned) - @test searchsortedfirst(coll, -huge)=== firstindex(coll) - @test searchsortedlast(coll, -huge) === firstindex(coll) - 1 - @test searchsorted(coll, -huge) === firstindex(coll) : firstindex(coll) - 1 - if !(eltype(coll) <: Unsigned) - @test searchsortedfirst(reverse(coll), -huge, rev=true) === lastindex(coll) + 1 - @test searchsortedlast(reverse(coll), -huge, rev=true) === lastindex(coll) - @test searchsorted(reverse(coll), -huge, rev=true) === lastindex(coll)+1:lastindex(coll) - end - end - end - end - - @testset "issue #34408" begin - r = 1f8-10:1f8 - # collect(r) = Float32[9.999999e7, 9.999999e7, 9.999999e7, 9.999999e7, 1.0e8, 1.0e8, 1.0e8, 1.0e8, 1.0e8] - for i in r - @test_broken searchsorted(collect(r), i) == searchsorted(r, i) - end - end - end - @testset "issue #35272" begin - for v0 = (3:-1:1, 3.0:-1.0:1.0), v = (v0, collect(v0)) - @test searchsorted(v, 3, rev=true) == 1:1 - @test searchsorted(v, 3.0, rev=true) == 1:1 - @test searchsorted(v, 2.5, rev=true) === 2:1 - @test searchsorted(v, 2, rev=true) == 2:2 - @test searchsorted(v, 1.2, rev=true) === 3:2 - @test searchsorted(v, 1, rev=true) == 3:3 - @test searchsorted(v, 0.1, rev=true) === 4:3 - end - end -end # exercise the codepath in searchsorted* methods for ranges that check for zero step range struct ConstantRange{T} <: AbstractRange{T} val::T @@ -263,9 +130,12 @@ Base.step(r::ConstantRange) = 0 @test searchsortedlast(r, 1.0, Forward) == 5 @test searchsortedlast(r, 1, Forward) == 5 @test searchsortedlast(r, UInt(1), Forward) == 5 +end +@testset "Each sorting algorithm individually" begin a = rand(1:10000, 1000) - for alg in [InsertionSort, MergeSort] + for alg in [InsertionSort, MergeSort, QuickSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + b = sort(a, alg=alg) @test issorted(b) @@ -329,14 +199,14 @@ Base.step(r::ConstantRange) = 0 @test b == c end - @testset "unstable algorithms" begin - b = sort(a, alg=QuickSort) + @testset "PartialQuickSort" begin + b = sort(a) @test issorted(b) @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)))) - b = sort(a, alg=QuickSort, rev=true) + b = sort(a, rev=true) @test issorted(b, rev=true) @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)), rev=true)) - b = sort(a, alg=QuickSort, by=x->1/x) + b = sort(a, by=x->1/x) @test issorted(b, by=x->1/x) @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)), by=x->1/x)) end @@ -399,8 +269,8 @@ end @testset "PartialQuickSort" begin a = rand(1:10000, 1000) # test PartialQuickSort only does a partial sort - let alg = PartialQuickSort(1:div(length(a), 10)) - k = alg.k + let k = 1:div(length(a), 10) + alg = PartialQuickSort(k) b = sort(a, alg=alg) c = sort(a, alg=alg, by=x->1/x) d = sort(a, alg=alg, rev=true) @@ -411,8 +281,8 @@ end @test !issorted(c, by=x->1/x) @test !issorted(d, rev=true) end - let alg = PartialQuickSort(div(length(a), 10)) - k = alg.k + let k = div(length(a), 10) + alg = PartialQuickSort(k) b = sort(a, alg=alg) c = sort(a, alg=alg, by=x->1/x) d = sort(a, alg=alg, rev=true) @@ -429,6 +299,7 @@ end @test partialsortperm([3,6,30,1,9], 2, rev=true) == 5 @test partialsortperm([3,6,30,1,9], 2, by=x->1/x) == 5 end + ## more advanced sorting tests ## randnans(n) = reinterpret(Float64,[rand(UInt64)|0x7ff8000000000000 for i=1:n]) @@ -464,7 +335,7 @@ end @test c == v # stable algorithms - for alg in [MergeSort] + for alg in [MergeSort, Base.Sort.ScratchQuickSort(), Base.Sort.ScratchQuickSort(1:n), Base.DEFAULT_STABLE] p = sortperm(v, alg=alg, rev=rev) p2 = sortperm(float(v), alg=alg, rev=rev) @test p == p2 @@ -474,10 +345,14 @@ end @test s == si invpermute!(s, p) @test s == v + + # Ensure stability, even with reverse short circuit + @test all(sort!(Real[fill(2.0, 15); fill(2, 15); fill(1.0, 15); fill(1, 15)]) + .=== Real[fill(1.0, 15); fill(1, 15); fill(2.0, 15); fill(2, 15)]) end # unstable algorithms - for alg in [QuickSort, PartialQuickSort(1:n)] + for alg in [QuickSort, PartialQuickSort(1:n), Base.DEFAULT_UNSTABLE] p = sortperm(v, alg=alg, rev=rev) p2 = sortperm(float(v), alg=alg, rev=rev) @test p == p2 @@ -508,9 +383,9 @@ end end v = randn_with_nans(n,0.1) - # TODO: alg = PartialQuickSort(n) fails here - for alg in [InsertionSort, QuickSort, MergeSort], + for alg in [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.Sort.ScratchQuickSort(1, n), Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], rev in [false,true] + alg === InsertionSort && n >= 3000 && continue # test float sorting with NaNs s = sort(v, alg=alg, rev=rev) @test issorted(s, rev=rev) @@ -570,7 +445,7 @@ end @test all(issorted, [sp[inds.==x] for x in 1:200]) end - for alg in [InsertionSort, MergeSort] + for alg in [InsertionSort, MergeSort, QuickSort, Base.DEFAULT_STABLE] sp = sortperm(inds, alg=alg) @test all(issorted, [sp[inds.==x] for x in 1:200]) end @@ -661,14 +536,24 @@ end @test issorted(a) a = view([9:-1:0;], :)::SubArray - Base.Sort.sort_int_range!(a, 10, 0, identity) # test it supports non-Vector + Base.Sort._sort!(a, Base.Sort.CountingSort(), Base.Forward, (; mn=0, mx=9)) # test it supports non-Vector @test issorted(a) a = OffsetArray([9:-1:0;], -5) - Base.Sort.sort_int_range!(a, 10, 0, identity) + Base.Sort._sort!(a, Base.Sort.CountingSort(), Base.Forward, (; mn=0, mx=9)) @test issorted(a) end +@testset "sort!(::OffsetVector)" begin + for length in vcat(0:5, [10, 300, 500, 1000]) + for offset in [-100000, -10, -1, 0, 1, 17, 1729] + x = OffsetVector(rand(length), offset) + sort!(x) + @test issorted(x) + end + end +end + @testset "sort!(::OffsetMatrix; dims)" begin x = OffsetMatrix(rand(5,5), 5, -5) sort!(x; dims=1) @@ -677,6 +562,13 @@ end end end +@testset "Offset with missing (#48862)" begin + v = [-1.0, missing, 1.0, 0.0, missing, -0.5, 0.5, 1.0, -0.5, missing, 0.5, -0.8, 1.5, NaN] + vo = OffsetArray(v, (firstindex(v):lastindex(v)).+100) + @test issorted(sort!(vo)) + @test issorted(v) +end + @testset "searchsortedfirst/last with generalized indexing" begin o = OffsetVector(1:3, -2) @test searchsortedfirst(o, 4) == lastindex(o) + 1 @@ -685,4 +577,570 @@ end @test searchsortedlast(o, 1.5) == -1 end +function adaptive_sort_test(v; trusted=InsertionSort, kw...) + sm = sum(hash.(v)) + truth = sort!(deepcopy(v); alg=trusted, kw...) + return ( + v === sort!(v; kw...) && + issorted(v; kw...) && + sum(hash.(v)) == sm && + all(v .=== truth)) +end +@testset "AdaptiveSort" begin + len = 70 + + @testset "Bool" begin + @test sort([false, true, false]) == [false, false, true] + @test sort([false, true, false], by=x->0) == [false, true, false] + @test sort([false, true, false], rev=true) == [true, false, false] + end + + @testset "fallback" begin + @test adaptive_sort_test(rand(1:typemax(Int32), len), by=x->x^2)# fallback + @test adaptive_sort_test(rand(Int, len), by=x->0, trusted=Base.Sort.ScratchQuickSort()) + end + + @test adaptive_sort_test(rand(Int, 20)) # InsertionSort + + @testset "large eltype" begin + for rev in [true, false] + @test adaptive_sort_test(rand(Int128, len), rev=rev) # direct ordered int + @test adaptive_sort_test(fill(rand(UInt128), len), rev=rev) # all same + @test adaptive_sort_test(rand(Int128.(1:len), len), rev=rev) # short int range + end + end + + @test adaptive_sort_test(fill(rand(), len)) # All same + + @testset "count sort" begin + @test adaptive_sort_test(rand(1:20, len)) + @test adaptive_sort_test(rand(1:20, len), rev=true) + end + + @testset "post-serialization count sort" begin + v = reinterpret(Float64, rand(1:20, len)) + @test adaptive_sort_test(copy(v)) + @test adaptive_sort_test(copy(v), rev=true) + end + + @testset "presorted" begin + @test adaptive_sort_test(sort!(rand(len))) + @test adaptive_sort_test(sort!(rand(Float32, len), rev=true)) + @test adaptive_sort_test(vcat(sort!(rand(Int16, len)), Int16(0))) + @test adaptive_sort_test(vcat(sort!(rand(UInt64, len), rev=true), 0)) + end + + @testset "lenm1 < 3bits fallback" begin + @test adaptive_sort_test(rand(len)) # InsertionSort + @test adaptive_sort_test(rand(130)) # QuickSort + end + + @test adaptive_sort_test(rand(1000)) # RadixSort +end + +@testset "uint mappings" begin + + #Construct value lists + floats = [reinterpret(U, vcat(T[-π, -1.0, -1/π, 1/π, 1.0, π, -0.0, 0.0, Inf, -Inf, NaN, -NaN, + prevfloat(T(0)), nextfloat(T(0)), prevfloat(T(Inf)), nextfloat(T(-Inf))], randnans(4))) + for (U, T) in [(UInt16, Float16), (UInt32, Float32), (UInt64, Float64)]] + + ints = [T[17, -T(17), 0, -one(T), 1, typemax(T), typemin(T), typemax(T)-1, typemin(T)+1] + for T in Base.BitInteger_types] + + char = Char['\n', ' ', Char(0), Char(8), Char(17), typemax(Char)] + + vals = vcat(floats, ints, [char]) + + #Add random values + UIntN(::Val{1}) = UInt8 + UIntN(::Val{2}) = UInt16 + UIntN(::Val{4}) = UInt32 + UIntN(::Val{8}) = UInt64 + UIntN(::Val{16}) = UInt128 + map(vals) do x + x isa Base.ReinterpretArray && return + T = eltype(x) + U = UIntN(Val(sizeof(T))) + append!(x, rand(T, 4)) + append!(x, reinterpret.(T, rand(U, 4))) + end + + for x in vals + T = eltype(x) + U = UIntN(Val(sizeof(T))) + for order in [Forward, Reverse, By(Forward, identity)] + if order isa Base.Order.By + @test Base.Sort.UIntMappable(T, order) === nothing + continue + end + + @test Base.Sort.UIntMappable(T, order) === U + x2 = deepcopy(x) + u = Base.Sort.uint_map!(x2, 1, length(x), order) + @test eltype(u) === U + @test all(Base.Sort.uint_map.(x, (order,)) .=== u) + mn = rand(U) + u .-= mn + @test x2 === Base.Sort.uint_unmap!(x2, u, 1, length(x), order, mn) + @test all(x2 .=== x) + + for a in x + for b in x + @test Base.Order.lt(order, a, b) === Base.Order.lt(Forward, Base.Sort.uint_map(a, order), Base.Sort.uint_map(b, order)) + end + end + end + end + + @test Base.Sort.UIntMappable(Union{Int, UInt}, Base.Forward) === nothing # issue #45280 +end + +@testset "invalid lt (#11429)" begin + # lt must be a total linear order (e.g. < not <=) so this usage is + # not allowed. Consequently, none of the behavior tested in this + # testset is guaranteed to work in future minor versions of Julia. + + safe_algs = [InsertionSort, MergeSort, Base.Sort.ScratchQuickSort(), Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + + n = 1000 + v = rand(1:5, n); + s = sort(v); + + # Nevertheless, it still works... + for alg in safe_algs + @test sort(v, alg=alg, lt = <=) == s + end + @test partialsort(v, 172, lt = <=) == s[172] + @test partialsort(v, 315:415, lt = <=) == s[315:415] + + # ...and it is consistently reverse stable. All these algorithms swap v[i] and v[j] + # where i < j if and only if lt(o, v[j], v[i]). This invariant holds even for + # this invalid lt order. + perm = reverse(sortperm(v, rev=true)) + 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] + + # lt can be very poorly behaved and sort will still permute its input in some way. + for alg in safe_algs + @test sort!(sort(v, alg=alg, lt = (x,y) -> rand([false, true]))) == s + end + @test partialsort(v, 172, lt = (x,y) -> rand([false, true])) ∈ 1:5 + @test all(partialsort(v, 315:415, lt = (x,y) -> rand([false, true])) .∈ (1:5,)) + + # issue #32675 + k = [38, 18, 38, 38, 3, 37, 26, 26, 6, 29, 38, 36, 38, 1, 38, 36, 38, 38, 38, 36, 36, + 36, 28, 34, 35, 38, 25, 20, 38, 1, 1, 5, 38, 38, 3, 34, 16, 38, 4, 10, 35, 37, 38, + 38, 2, 38, 25, 35, 38, 1, 35, 36, 20, 33, 36, 18, 38, 1, 24, 4, 38, 18, 12, 38, 34, + 35, 36, 38, 26, 31, 36, 38, 38, 30, 36, 35, 35, 7, 22, 35, 38, 35, 30, 21, 37] + idx = sortperm(k; lt=!isless) + @test issorted(k[idx], rev=true) +end + +@testset "sort(x; scratch)" begin + for n in [1,10,100,1000] + v = rand(n) + scratch = [0.0] + @test sort(v) == sort(v; scratch) + @test sort!(copy(v)) == sort!(copy(v); scratch) + @test sortperm(v) == sortperm(v; scratch=[4]) + @test sortperm!(Vector{Int}(undef, n), v) == sortperm!(Vector{Int}(undef, n), v; scratch=[4]) + + n > 100 && continue + M = rand(n, n) + @test sort(M; dims=2) == sort(M; dims=2, scratch) + @test sort!(copy(M); dims=1) == sort!(copy(M); dims=1, scratch) + end +end + +@testset "sorting preserves identity" begin + a = BigInt.([2, 2, 2, 1, 1, 1]) # issue #39620 + sort!(a) + @test length(IdDict(a .=> a)) == 6 + + for v in [BigInt.(rand(1:5, 40)), BigInt.(rand(Int, 70)), BigFloat.(rand(52))] + hashes = Set(hash.(v)) + ids = Set(objectid.(v)) + sort!(v) + @test hashes == Set(hash.(v)) + @test ids == Set(objectid.(v)) + end +end + +@testset "Unions with missing" begin + @test issorted(sort(shuffle!(vcat(fill(missing, 10), rand(Int, 100))))) + @test issorted(sort(vcat(rand(Int8, 600), [missing]))) + + # Because we define defalg(::AbstractArray{Missing}) + @test all(fill(missing, 10) .=== sort(fill(missing, 10))) + + # Unit tests for WithoutMissingVector + a = [1,7,missing,4] + @test_throws ArgumentError Base.Sort.WithoutMissingVector(a) + @test eltype(a[[1,2,4]]) == eltype(a) + @test eltype(Base.Sort.WithoutMissingVector(a[[1,2,4]])) == Int + am = Base.Sort.WithoutMissingVector(a, unsafe=true) + @test am[2] == 7 + @test eltype(am) == Int +end + +@testset "Specific algorithms" begin + 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)} + + function test_alg(kw, alg, float=true) + for order in [Base.Forward, Base.Reverse, Base.By(x -> x^2)] + order isa Base.By && alg isa requires_uint_mappable && continue + for n in [1,7,179,1312] + + n == 1 && alg isa Base.Sort.RadixSort && continue + + x = rand(1:n+1, n) + y = sort(x; order) + @test Base.Sort._sort!(x, alg, order, (;kw(y)...)) !== x + @test all(y .=== x) + + alg isa requires_uint_mappable && continue + + x = randn(n) + y = sort(x; order) + @test Base.Sort._sort!(x, alg, order, (;kw(y)...)) !== x + @test all(y .=== x) + end + end + end + test_alg(alg) = test_alg(x -> (), alg) + + function test_alg_rec(alg, extrema=false) + if extrema + test_alg(alg) do y + (;mn=first(y),mx=last(y)) + end + else + test_alg(alg) + end + extrema |= alg isa Base.Sort.ComputeExtrema + for name in fieldnames(typeof(alg)) + a = getfield(alg, name) + a isa Base.Sort.Algorithm && test_alg_rec(a, extrema) + end + end + + test_alg_rec(Base.DEFAULT_STABLE) + end +end + +@testset "show(::Algorithm)" begin + @test eval(Meta.parse(string(Base.DEFAULT_STABLE))) === Base.DEFAULT_STABLE + lines = split(string(Base.DEFAULT_STABLE), '\n') + @test 10 < maximum(length, lines) < 100 + @test 1 < length(lines) < 30 +end + +@testset "Extensibility" begin + # Defining new algorithms & backwards compatibility with packages that use sorting internals + + struct MyFirstAlg <: Base.Sort.Algorithm end + + @test_throws ArgumentError sort([1,2,3], alg=MyFirstAlg()) # not a stack overflow error + + v = shuffle(vcat(fill(missing, 10), rand(Int, 100))) + + # The pre 1.9 dispatch method + function Base.sort!(v::AbstractVector{Int}, lo::Integer, hi::Integer, ::MyFirstAlg, o::Base.Order.Ordering) + v[lo:hi] .= 7 + end + @test sort([1,2,3], alg=MyFirstAlg()) == [7,7,7] + @test all(sort(v, alg=Base.Sort.InitialOptimizations(MyFirstAlg())) .=== vcat(fill(7, 100), fill(missing, 10))) + + # Using the old hook with old entry-point + @test sort!([3,1,2], MyFirstAlg(), Base.Forward) == [7,7,7] + @test sort!([3,1,2], 1, 3, MyFirstAlg(), Base.Forward) == [7,7,7] + + # Use the pre 1.9 entry-point into the internals + function Base.sort!(v::AbstractVector{Int}, lo::Integer, hi::Integer, ::MyFirstAlg, o::Base.Order.Ordering) + sort!(v, lo, hi, Base.DEFAULT_STABLE, o) + end + @test sort([3,1,2], alg=MyFirstAlg()) == [1,2,3] + @test issorted(sort(v, alg=Base.Sort.InitialOptimizations(MyFirstAlg()))) + + # Another pre 1.9 entry-point into the internals + @test issorted(sort!(rand(100), InsertionSort, Base.Order.Forward)) + + struct MySecondAlg <: Base.Sort.Algorithm end + # A new dispatch method + function Base.Sort._sort!(v::AbstractVector, ::MySecondAlg, o::Base.Order.Ordering, kw) + Base.Sort.@getkw lo hi + v[lo:hi] .= 9 + end + @test sort([1,2,3], alg=MySecondAlg()) == [9,9,9] + @test all(sort(v, alg=Base.Sort.InitialOptimizations(MySecondAlg())) .=== vcat(fill(9, 100), fill(missing, 10))) +end + +@testset "sort!(v, lo, hi, alg, order)" begin + v = Vector{Float64}(undef, 4000) + for alg in [MergeSort, QuickSort, InsertionSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + rand!(v) + sort!(v, 1, 2000, alg, Base.Forward) + @test issorted(v[1:2000]) + @test !issorted(v) + + sort!(v, 2001, 4000, alg, Base.Forward) + @test issorted(v[1:2000]) + @test issorted(v[2001:4000]) + @test !issorted(v) + + sort!(v, 1001, 3000, alg, Base.Forward) + @test issorted(v[1:1000]) + @test issorted(v[1001:3000]) + @test issorted(v[3001:4000]) + @test !issorted(v[1:2000]) + @test !issorted(v[2001:4000]) + @test !issorted(v) + end +end + +@testset "IEEEFloatOptimization with -0.0" begin + x = vcat(round.(100 .* randn(1000)) ./ 100) # Also test lots of duplicates + x[rand(1:1000, 5)] .= 0.0 + x[rand(1:1000, 5)] .= -0.0 # To be sure that -0.0 is present + @test issorted(sort!(x)) +end + +@testset "Count sort near the edge of its range" begin + @test issorted(sort(rand(typemin(Int):typemin(Int)+100, 1000))) + @test issorted(sort(rand(typemax(Int)-100:typemax(Int), 1000))) + @test issorted(sort(rand(Int8, 600))) +end + +@testset "ScratchQuickSort API" begin + bsqs = Base.Sort.ScratchQuickSort + @test bsqs(1, 2, MergeSort) === bsqs(1, 2, MergeSort) + @test bsqs(missing, 2, MergeSort) === bsqs(missing, 2, MergeSort) + @test bsqs(1, missing, MergeSort) === bsqs(1, missing, MergeSort) + @test bsqs(missing, missing, MergeSort) === bsqs(missing, missing, MergeSort) + @test bsqs(1, MergeSort) === bsqs(1, 1, MergeSort) + @test bsqs(missing, MergeSort) === bsqs(missing, missing, MergeSort) + @test bsqs(MergeSort) === bsqs(missing, missing, MergeSort) + + @test bsqs(1, 2) === bsqs(1, 2, InsertionSort) + @test bsqs(missing, 2) === bsqs(missing, 2, InsertionSort) + @test bsqs(1, missing) === bsqs(1, missing, InsertionSort) + @test bsqs(missing, missing) === bsqs(missing, missing, InsertionSort) + @test bsqs(1) === bsqs(1, 1, InsertionSort) + @test bsqs(missing) === bsqs(missing, missing, InsertionSort) + @test bsqs() === bsqs(missing, missing, InsertionSort) +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()) + # 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 + +function test_allocs() + v = rand(10) + i = randperm(length(v)) + @test 1 == @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 0 == @allocations sortperm!(i, v, order=Base.Reverse) + @test 1 == @allocations sortperm(v) + @test 1 == @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) + rand!(i) + @test 0 == @allocations sort!(i, order=Base.Reverse) +end +@testset "Small calls do not unnecessarily allocate" begin + test_allocs() +end + +@testset "Presorted and reverse-presorted" begin + for len in [7, 92, 412, 780] + x = sort(randn(len)) + for _ in 1:2 + @test issorted(sort(x)) + @test issorted(sort(x), by=x -> x+7) + reverse!(x) + 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, + UInt8, UInt16, UInt32, UInt64, UInt128, + Float16, Float32, Float64, BigInt, BigFloat] + + @test searchsorted([1:10;], 1, by=(x -> x >= 5)) == 1:4 + @test searchsorted([1:10;], 10, by=(x -> x >= 5)) == 5:10 + @test searchsorted([1:5; 1:5; 1:5], 1, 6, 10, Forward) == 6:6 + @test searchsorted(fill(1, 15), 1, 6, 10, Forward) == 6:10 + + for R in numTypes, T in numTypes + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(0)) === 1:0 + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(1)) == 1:2 + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(2)) == 3:4 + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(4)) === 7:6 + @test searchsorted(R[1, 1, 2, 2, 3, 3], 2.5) === 5:4 + + @test searchsorted(1:3, T(0)) === 1:0 + @test searchsorted(1:3, T(1)) == 1:1 + @test searchsorted(1:3, T(2)) == 2:2 + @test searchsorted(1:3, T(4)) === 4:3 + + @test searchsorted(R[1:10;], T(1), by=(x -> x >= 5)) == 1:4 + @test searchsorted(R[1:10;], T(10), by=(x -> x >= 5)) == 5:10 + @test searchsorted(R[1:5; 1:5; 1:5], T(1), 6, 10, Forward) == 6:6 + @test searchsorted(fill(R(1), 15), T(1), 6, 10, Forward) == 6:10 + end + + for (rg,I) in Any[(49:57,47:59), (1:2:17,-1:19), (-3:0.5:2,-5:.5:4)] + rg_r = reverse(rg) + rgv, rgv_r = [rg;], [rg_r;] + for i = I + @test searchsorted(rg,i) === searchsorted(rgv,i) + @test searchsorted(rg_r,i,rev=true) === searchsorted(rgv_r,i,rev=true) + end + end + + rg = 0.0:0.01:1.0 + for i = 2:101 + @test searchsorted(rg, rg[i]) == i:i + @test searchsorted(rg, prevfloat(rg[i])) === i:i-1 + @test searchsorted(rg, nextfloat(rg[i])) === i+1:i + end + + rg_r = reverse(rg) + for i = 1:100 + @test searchsorted(rg_r, rg_r[i], rev=true) == i:i + @test searchsorted(rg_r, prevfloat(rg_r[i]), rev=true) === i+1:i + @test searchsorted(rg_r, nextfloat(rg_r[i]), rev=true) === i:i-1 + end + + @test searchsorted(1:10, 1, by=(x -> x >= 5)) == searchsorted([1:10;], 1, by=(x -> x >= 5)) + @test searchsorted(1:10, 10, by=(x -> x >= 5)) == searchsorted([1:10;], 10, by=(x -> x >= 5)) + + @test searchsorted([], 0) === 1:0 + @test searchsorted([1,2,3], 0) === 1:0 + @test searchsorted([1,2,3], 4) === 4:3 + + @testset "issue 8866" begin + @test searchsortedfirst(500:1.0:600, -1.0e20) == 1 + @test searchsortedfirst(500:1.0:600, 1.0e20) == 102 + @test searchsortedlast(500:1.0:600, -1.0e20) == 0 + @test searchsortedlast(500:1.0:600, 1.0e20) == 101 + end + + @testset "issue 10966" begin + for R in numTypes, T in numTypes + @test searchsortedfirst(R(2):R(2), T(0)) == 1 + @test searchsortedfirst(R(2):R(2), T(2)) == 1 + @test searchsortedfirst(R(2):R(2), T(3)) == 2 + @test searchsortedfirst(R(1):1//2:R(5), T(0)) == 1 + @test searchsortedfirst(R(1):1//2:R(5), T(2)) == 3 + @test searchsortedfirst(R(1):1//2:R(5), T(6)) == 10 + @test searchsortedlast(R(2):R(2), T(0)) == 0 + @test searchsortedlast(R(2):R(2), T(2)) == 1 + @test searchsortedlast(R(2):R(2), T(3)) == 1 + @test searchsortedlast(R(1):1//2:R(5), T(0)) == 0 + @test searchsortedlast(R(1):1//2:R(5), T(2)) == 3 + @test searchsortedlast(R(1):1//2:R(5), T(6)) == 9 + @test searchsorted(R(2):R(2), T(0)) === 1:0 + @test searchsorted(R(2):R(2), T(2)) == 1:1 + @test searchsorted(R(2):R(2), T(3)) === 2:1 + end + end + + @testset "issue 32568" begin + for R in numTypes, T in numTypes + for arr in Any[R[1:5;], R(1):R(5), R(1):2:R(5)] + @test eltype(searchsorted(arr, T(2))) == keytype(arr) + @test eltype(searchsorted(arr, T(2), big(1), big(4), Forward)) == keytype(arr) + @test searchsortedfirst(arr, T(2)) isa keytype(arr) + @test searchsortedfirst(arr, T(2), big(1), big(4), Forward) isa keytype(arr) + @test searchsortedlast(arr, T(2)) isa keytype(arr) + @test searchsortedlast(arr, T(2), big(1), big(4), Forward) isa keytype(arr) + end + end + end + + @testset "issue #34157" begin + @test searchsorted(1:2.0, -Inf) === 1:0 + @test searchsorted([1,2], -Inf) === 1:0 + @test searchsorted(1:2, -Inf) === 1:0 + + @test searchsorted(1:2.0, Inf) === 3:2 + @test searchsorted([1,2], Inf) === 3:2 + @test searchsorted(1:2, Inf) === 3:2 + + for coll in Any[ + Base.OneTo(10), + 1:2, + 0x01:0x02, + -4:6, + 5:2:10, + [1,2], + 1.0:4, + [10.0,20.0], + ] + for huge in Any[Inf, 1e300, typemax(Int64), typemax(UInt64)] + @test searchsortedfirst(coll, huge) === lastindex(coll) + 1 + @test searchsortedlast(coll, huge) === lastindex(coll) + @test searchsorted(coll, huge) === lastindex(coll)+1 : lastindex(coll) + if !(eltype(coll) <: Unsigned) + @test searchsortedfirst(reverse(coll), huge, rev=true) === firstindex(coll) + @test searchsortedlast(reverse(coll), huge, rev=true) === firstindex(coll) - 1 + @test searchsorted(reverse(coll), huge, rev=true) === firstindex(coll):firstindex(coll) - 1 + end + + if !(huge isa Unsigned) + @test searchsortedfirst(coll, -huge)=== firstindex(coll) + @test searchsortedlast(coll, -huge) === firstindex(coll) - 1 + @test searchsorted(coll, -huge) === firstindex(coll) : firstindex(coll) - 1 + if !(eltype(coll) <: Unsigned) + @test searchsortedfirst(reverse(coll), -huge, rev=true) === lastindex(coll) + 1 + @test searchsortedlast(reverse(coll), -huge, rev=true) === lastindex(coll) + @test searchsorted(reverse(coll), -huge, rev=true) === lastindex(coll)+1:lastindex(coll) + end + end + end + end + + @testset "issue #34408" begin + r = 1f8-10:1f8 + @test collect(r) == Float32[9.999999e7, 9.999999e7, 9.999999e7, 9.999999e7, 1.0e8, 1.0e8, 1.0e8, 1.0e8, 1.0e8] + for i in r + @test_broken searchsorted(collect(r), i) == searchsorted(r, i) + end + end + end + @testset "issue #35272" begin + for v0 = (3:-1:1, 3.0:-1.0:1.0), v = (v0, collect(v0)) + @test searchsorted(v, 3, rev=true) == 1:1 + @test searchsorted(v, 3.0, rev=true) == 1:1 + @test searchsorted(v, 2.5, rev=true) === 2:1 + @test searchsorted(v, 2, rev=true) == 2:2 + @test searchsorted(v, 1.2, rev=true) === 3:2 + @test searchsorted(v, 1, rev=true) == 3:3 + @test searchsorted(v, 0.1, rev=true) === 4:3 + end + end +end +# The "searchsorted" testset is at the end of the file because it is slow. + end diff --git a/test/spawn.jl b/test/spawn.jl index 92232ba5d70f6..0241c65573886 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -5,7 +5,7 @@ ################################### using Random, Sockets -using Downloads: download +using Downloads: Downloads, download valgrind_off = ccall(:jl_running_on_valgrind, Cint, ()) == 0 @@ -20,8 +20,33 @@ shcmd = `sh` sleepcmd = `sleep` lscmd = `ls` havebb = false + +function _tryonce_download_from_cache(desired_url::AbstractString) + cache_url = "https://cache.julialang.org/foo/$(desired_url)" + cache_output_filename = joinpath(mktempdir(), "myfile") + cache_response = Downloads.request( + cache_url; + output = cache_output_filename, + throw = false, + timeout = 60, + ) + if cache_response isa Downloads.Response + if Downloads.status_ok(cache_response.proto, cache_response.status) + return cache_output_filename + end + end + return Downloads.download(desired_url; timeout = 60) +end + +function download_from_cache(desired_url::AbstractString) + f = () -> _tryonce_download_from_cache(desired_url) + delays = Float64[30, 30, 60, 60, 60] + g = retry(f; delays) + return g() +end + if Sys.iswindows() - busybox = download("https://cache.julialang.org/https://frippery.org/files/busybox/busybox.exe", joinpath(tempdir(), "busybox.exe")) + busybox = download_from_cache("https://frippery.org/files/busybox/busybox.exe") havebb = try # use busybox-w32 on windows, if available success(`$busybox`) true @@ -826,6 +851,12 @@ end dir = joinpath(pwd(), "dir") cmd = addenv(setenv(`julia`; dir=dir), Dict()) @test cmd.dir == dir + + @test addenv(``, ["a=b=c"], inherit=false).env == ["a=b=c"] + cmd = addenv(``, "a"=>"b=c", inherit=false) + @test cmd.env == ["a=b=c"] + cmd = addenv(cmd, "b"=>"b") + @test issetequal(cmd.env, ["b=b", "a=b=c"]) end @testset "setenv with dir (with tests for #42131)" begin diff --git a/test/specificity.jl b/test/specificity.jl index de65c289be02a..9b605444bad42 100644 --- a/test/specificity.jl +++ b/test/specificity.jl @@ -90,7 +90,12 @@ begin @test f((1,2,3), A) == 3 @test f((1,2), A) == 2 @test f((), reshape([1])) == 1 + + oldstderr = stderr + newstderr = redirect_stderr() # redirect stderr to avoid method definition overwrite warning f(dims::NTuple{N,Int}, A::AbstractArray{T,N}) where {T,N} = 4 + redirect_stderr(oldstderr) + @test f((1,2), A) == 4 @test f((1,2,3), A) == 3 end @@ -209,7 +214,7 @@ f27361(::M) where M <: Tuple{3} = nothing @test length(methods(f27361)) == 2 # specificity of TypeofBottom -@test args_morespecific(Tuple{Core.TypeofBottom}, Tuple{DataType}) +@test !args_morespecific(Tuple{DataType}, Tuple{Core.TypeofBottom}) @test args_morespecific(Tuple{Core.TypeofBottom}, Tuple{Type{<:Tuple}}) @test args_morespecific(Tuple{Type{Any}, Type}, Tuple{Type{T}, Type{T}} where T) @@ -306,3 +311,8 @@ let A = Tuple{Type{SubString{S}},AbstractString} where S<:AbstractString, @test args_morespecific(B, C) @test args_morespecific(A, C) end + +@test args_morespecific(Tuple{Type{Union{}}, Any}, Tuple{Any, Type{Union{}}}) +@test args_morespecific(Tuple{typeof(Union{}), Any}, Tuple{Any, Type{Union{}}}) +@test args_morespecific(Tuple{Type{Union{}}, Type{Union{}}, Any}, Tuple{Type{Union{}}, Any, Type{Union{}}}) +@test args_morespecific(Tuple{Type{Union{}}, Type{Union{}}, Any, Type{Union{}}}, Tuple{Type{Union{}}, Any, Type{Union{}}, Type{Union{}}}) diff --git a/test/stack_overflow.jl b/test/stack_overflow.jl index 9f4bae6f3f5b3..297186c8a4d3a 100644 --- a/test/stack_overflow.jl +++ b/test/stack_overflow.jl @@ -17,3 +17,20 @@ let exename = Base.julia_cmd() @show readchomperrors(`$exename -e "f() = f(); f()"`) @show readchomperrors(`$exename -e "f() = f(); fetch(@async f())"`) end + +# Issue #49507: stackoverflow in type inference caused by close(::Channel, ::Exception) +@testset "close(::Channel, ::StackOverflowError)" begin + ch = let result = Channel() + foo() = try + foo() + catch e; + close(result, e) + end + + foo() # This shouldn't fail with an internal stackoverflow error in inference. + + result + end + + @test (try take!(ch) catch e; e; end) isa StackOverflowError +end diff --git a/test/stacktraces.jl b/test/stacktraces.jl index cbb07a60e456b..96393b124f70e 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -91,8 +91,9 @@ trace = (try; f(3); catch; stacktrace(catch_backtrace()); end)[1:3] 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 - #@test get(frame.linfo).def === which(func, (Any,)).func - #@test get(frame.linfo).specTypes === Tuple{typeof(func), Int} + @test frame.linfo.def.module === which(func, (Any,)).module + @test frame.linfo.def === which(func, (Any,)) + @test frame.linfo.specTypes === Tuple{typeof(func), Int} # line @test frame.file === Symbol(@__FILE__) @test !frame.from_c @@ -104,7 +105,7 @@ let src = Meta.lower(Main, quote let x = 1 end end).args[1]::Core.CodeInfo, li = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ()), sf - li.uninferred = src + setfield!(li, :uninferred, src, :monotonic) li.specTypes = Tuple{} li.def = @__MODULE__ sf = StackFrame(:a, :b, 3, li, false, false, 0) diff --git a/test/staged.jl b/test/staged.jl index b99ef46a2bc1e..0fa8ecb182cff 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -196,12 +196,11 @@ let gf_err2 return nothing end Expected = ErrorException("code reflection cannot be used from generated functions") + @test_throws Expected gf_err2(code_lowered) @test_throws Expected gf_err2(code_typed) @test_throws Expected gf_err2(code_llvm) @test_throws Expected gf_err2(code_native) - @test gf_err_ref[] == 66 - @test gf_err2(code_lowered) === nothing - @test gf_err_ref[] == 1077 + @test gf_err_ref[] == 88 end # issue #15043 @@ -246,12 +245,18 @@ f22440kernel(x::AbstractFloat) = x * x f22440kernel(::Type{T}) where {T} = one(T) f22440kernel(::Type{T}) where {T<:AbstractFloat} = zero(T) -@generated function f22440(y) - match = Base._methods_by_ftype(Tuple{typeof(f22440kernel),y}, -1, typemax(UInt))[1] +function f22440_gen(world::UInt, source, _, y) + match = only(Base._methods_by_ftype(Tuple{typeof(f22440kernel),y}, -1, world)) code_info = Base.uncompressed_ir(match.method) Meta.partially_inline!(code_info.code, Any[], match.spec_types, Any[match.sparams...], 0, 0, :propagate) + # 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 end +@eval function f22440(y) + $(Expr(:meta, :generated, f22440_gen)) + $(Expr(:meta, :generated_only)) +end @test f22440(Int) === f22440kernel(Int) @test f22440(Float64) === f22440kernel(Float64) @@ -305,3 +310,37 @@ end end @test f33243() === 2 @test x33243 === 2 + +# https://github.com/JuliaDebug/CassetteOverlay.jl/issues/12 +# generated function with varargs and unfortunately placed unused slot +@generated function f_vararg_generated(args...) + local unusedslot4 + local unusedslot5 + local unusedslot6 + :($args) +end +g_vararg_generated() = f_vararg_generated((;), (;), Base.inferencebarrier((;))) +let tup = g_vararg_generated() + @test all(==(typeof((;))), tup) + # This is just to make sure that the test is actually testing what we want: + # the test only works if there is an unused that matches the position of + # the inferencebarrier argument above (N.B. the generator function itself + # shifts everything over by 1) + @test_broken only(code_lowered(only(methods(f_vararg_generated)).generator.gen)).slotflags[5] == 0x00 +end + +# respect a given linetable in code generation +# https://github.com/JuliaLang/julia/pull/47750 +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) + @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 sin_generated(42) == sin(42) +end diff --git a/test/strings/basic.jl b/test/strings/basic.jl index c1df87420d7da..602c38551f6d8 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -164,6 +164,12 @@ end @test endswith(y)(y) @test endswith(z, z) @test endswith(z)(z) + #40616 startswith for IO objects + let s = "JuliaLang", io = IOBuffer(s) + for prefix in ("Julia", "July", s^2, "Ju", 'J', 'x', ('j','J')) + @test startswith(io, prefix) == startswith(s, prefix) + end + end end @testset "SubStrings and Views" begin @@ -418,7 +424,7 @@ end end @test nextind("fóobar", 0, 3) == 4 - @test Symbol(gstr) == Symbol("12") + @test Symbol(gstr) === Symbol("12") @test sizeof(gstr) == 2 @test ncodeunits(gstr) == 2 @@ -435,6 +441,9 @@ end @test all(x -> x == "12", svec) @test svec isa Vector{AbstractString} end + # test startswith and endswith for AbstractString + @test endswith(GenericString("abcd"), GenericString("cd")) + @test startswith(GenericString("abcd"), GenericString("ab")) end @testset "issue #10307" begin @@ -680,6 +689,7 @@ end Base.iterate(x::CharStr) = iterate(x.chars) Base.iterate(x::CharStr, i::Int) = iterate(x.chars, i) Base.lastindex(x::CharStr) = lastindex(x.chars) +Base.length(x::CharStr) = length(x.chars) @testset "cmp without UTF-8 indexing" begin # Simple case, with just ANSI Latin 1 characters @test "áB" != CharStr("áá") # returns false with bug @@ -723,6 +733,11 @@ end @test_throws ArgumentError "abc"[BitArray([true, false, true])] end +@testset "issue #46039 enhance StringIndexError display" begin + @test sprint(showerror, StringIndexError("αn", 2)) == "StringIndexError: invalid index [2], valid nearby indices [1]=>'α', [3]=>'n'" + @test sprint(showerror, StringIndexError("α\n", 2)) == "StringIndexError: invalid index [2], valid nearby indices [1]=>'α', [3]=>'\\n'" +end + @testset "concatenation" begin @test "ab" * "cd" == "abcd" @test 'a' * "bc" == "abc" @@ -854,7 +869,7 @@ end p = prevind(s, p) @test prevind(s, x, j) == p end - if n ≤ ncodeunits(s) + if n ≤ ncodeunits(s) n = nextind(s, n) @test nextind(s, x, j) == n end @@ -929,6 +944,21 @@ end end end +@testset "Conversion to Type{Union{String, SubString{String}}}" begin + str = "abc" + substr = SubString(str) + for T in [String, SubString{String}] + conv_str = convert(T, str) + conv_substr = convert(T, substr) + + if T == String + @test conv_str === conv_substr === str + elseif T == SubString{String} + @test conv_str === conv_substr === substr + end + end +end + @test unsafe_wrap(Vector{UInt8},"\xcc\xdd\xee\xff\x80") == [0xcc,0xdd,0xee,0xff,0x80] @test iterate("a", 1)[2] == 2 @@ -1095,9 +1125,264 @@ end @test sprint(summary, "") == "empty String" end +@testset "isascii" begin + N = 1 + @test isascii("S"^N) == true + @test isascii("S"^(N - 1)) == true + @test isascii("S"^(N + 1)) == true + + @test isascii("λ" * ("S"^(N))) == false + @test isascii(("S"^(N)) * "λ") == false + + for p = 1:16 + N = 2^p + @test isascii("S"^N) == true + @test isascii("S"^(N - 1)) == true + @test isascii("S"^(N + 1)) == true + + @test isascii("λ" * ("S"^(N))) == false + @test isascii(("S"^(N)) * "λ") == false + @test isascii("λ"*("S"^(N - 1))) == false + @test isascii(("S"^(N - 1)) * "λ") == false + if N > 4 + @test isascii("λ" * ("S"^(N - 3))) == false + @test isascii(("S"^(N - 3)) * "λ") == false + end + end +end + +@testset "Plug holes in test coverage" begin + @test_throws MethodError checkbounds(Bool, "abc", [1.0, 2.0]) + + apple_uint8 = Vector{UInt8}("Apple") + @test apple_uint8 == [0x41, 0x70, 0x70, 0x6c, 0x65] + + Base.String(::tstStringType) = "Test" + abstract_apple = tstStringType(apple_uint8) + @test hash(abstract_apple, UInt(1)) == hash("Test", UInt(1)) + + @test length("abc", 1, 3) == length("abc", UInt(1), UInt(3)) + + @test isascii(GenericString("abc")) + + 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) +end + @testset "LazyString" begin @test repr(lazy"$(1+2) is 3") == "\"3 is 3\"" let d = Dict(lazy"$(1+2) is 3" => 3) @test d["3 is 3"] == 3 end + l = lazy"1+2" + @test isequal( l, lazy"1+2" ) + @test ncodeunits(l) == ncodeunits("1+2") + @test codeunit(l) == UInt8 + @test codeunit(l,2) == 0x2b + @test isvalid(l, 1) + @test Base.infer_effects((Any,)) do a + throw(lazy"a is $a") + end |> Core.Compiler.is_foldable + @test Base.infer_effects((Int,)) do a + if a < 0 + throw(DomainError(a, lazy"$a isn't positive")) + end + return a + end |> Core.Compiler.is_foldable + let i=49248 + @test String(lazy"PR n°$i") == "PR n°49248" + end +end + +@testset "String Effects" begin + for (f, Ts) in [(*, (String, String)), + (*, (Char, String)), + (*, (Char, Char)), + (string, (Symbol, String, Char)), + (==, (String, String)), + (cmp, (String, String)), + (==, (Symbol, Symbol)), + (cmp, (Symbol, Symbol)), + (String, (Symbol,)), + (length, (String,)), + (hash, (String,UInt)), + (hash, (Char,UInt)),] + e = Base.infer_effects(f, Ts) + @test Core.Compiler.is_foldable(e) || (f, Ts) + @test Core.Compiler.is_removable_if_unused(e) || (f, Ts) + end + for (f, Ts) in [(^, (String, Int)), + (^, (Char, Int)), + (codeunit, (String, Int)), + ] + e = Base.infer_effects(f, Ts) + @test Core.Compiler.is_foldable(e) || (f, Ts) + @test !Core.Compiler.is_removable_if_unused(e) || (f, Ts) + end + # Substrings don't have any nice effects because the compiler can + # invent fake indices leading to out of bounds + for (f, Ts) in [(^, (SubString{String}, Int)), + (string, (String, SubString{String})), + (string, (Symbol, SubString{String})), + (hash, (SubString{String},UInt)), + ] + e = Base.infer_effects(f, Ts) + @test !Core.Compiler.is_foldable(e) || (f, Ts) + @test !Core.Compiler.is_removable_if_unused(e) || (f, Ts) + end + @test_throws ArgumentError Symbol("a\0a") +end + +@testset "Ensure UTF-8 DFA can never leave invalid state" begin + for b = typemin(UInt8):typemax(UInt8) + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_INVALID,[b],1,1) == Base._UTF8_DFA_INVALID + end +end +@testset "Ensure UTF-8 DFA stays in ASCII State for all ASCII" begin + for b = 0x00:0x7F + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b],1,1) == Base._UTF8_DFA_ASCII + end +end + +@testset "Validate UTF-8 DFA" begin + # Unicode 15 + # Table 3-7. Well-Formed UTF-8 Byte Sequences + + table_rows = [ [0x00:0x7F], + [0xC2:0xDF,0x80:0xBF], + [0xE0:0xE0,0xA0:0xBF,0x80:0xBF], + [0xE1:0xEC,0x80:0xBF,0x80:0xBF], + [0xED:0xED,0x80:0x9F,0x80:0xBF], + [0xEE:0xEF,0x80:0xBF,0x80:0xBF], + [0xF0:0xF0,0x90:0xBF,0x80:0xBF,0x80:0xBF], + [0xF1:0xF3,0x80:0xBF,0x80:0xBF,0x80:0xBF], + [0xF4:0xF4,0x80:0x8F,0x80:0xBF,0x80:0xBF]] + invalid_first_bytes = union(0xC0:0xC1,0xF5:0xFF,0x80:0xBF) + + valid_first_bytes = union(collect(first(r) for r in table_rows)...) + + + + # Prove that the first byte sets in the table & invalid cover all bytes + @test length(union(valid_first_bytes,invalid_first_bytes)) == 256 + @test length(intersect(valid_first_bytes,invalid_first_bytes)) == 0 + + #Check the ASCII range + for b = 0x00:0x7F + #Test from both UTF-8 state and ascii state + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b],1,1) == Base._UTF8_DFA_ACCEPT + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b],1,1) == Base._UTF8_DFA_ASCII + end + + #Check the remaining first bytes + for b = 0x80:0xFF + if b ∈ invalid_first_bytes + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b],1,1) == Base._UTF8_DFA_INVALID + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b],1,1) == Base._UTF8_DFA_INVALID + else + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b],1,1) != Base._UTF8_DFA_INVALID + @test Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b],1,1) != Base._UTF8_DFA_INVALID + end + end + + # Check two byte Sequences + for table_row in [table_rows[2]] + b1 = first(table_row[1]) + state1 = Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b1],1,1) + state2 = Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b1],1,1) + @test state1 == state2 + #Prove that all the first bytes in a row give same state + for b1 in table_row[1] + @test state1 == Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b1],1,1) + @test state1 == Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b1],1,1) + end + b1 = first(table_row[1]) + #Prove that all valid second bytes return correct state + for b2 = table_row[2] + @test Base._UTF8_DFA_ACCEPT == Base._isvalid_utf8_dfa(state1,[b2],1,1) + end + for b2 = setdiff(0x00:0xFF,table_row[2]) + @test Base._UTF8_DFA_INVALID == Base._isvalid_utf8_dfa(state1,[b2],1,1) + end + end + + # Check three byte Sequences + for table_row in table_rows[3:6] + b1 = first(table_row[1]) + state1 = Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b1],1,1) + state2 = Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b1],1,1) + @test state1 == state2 + #Prove that all the first bytes in a row give same state + for b1 in table_row[1] + @test state1 == Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b1],1,1) + @test state1 == Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b1],1,1) + end + + b1 = first(table_row[1]) + b2 = first(table_row[2]) + #Prove that all valid second bytes return same state + state2 = Base._isvalid_utf8_dfa(state1,[b2],1,1) + for b2 = table_row[2] + @test state2 == Base._isvalid_utf8_dfa(state1,[b2],1,1) + end + for b2 = setdiff(0x00:0xFF,table_row[2]) + @test Base._UTF8_DFA_INVALID == Base._isvalid_utf8_dfa(state1,[b2],1,1) + end + + b2 = first(table_row[2]) + #Prove that all valid third bytes return correct state + for b3 = table_row[3] + @test Base._UTF8_DFA_ACCEPT == Base._isvalid_utf8_dfa(state2,[b3],1,1) + end + for b3 = setdiff(0x00:0xFF,table_row[3]) + @test Base._UTF8_DFA_INVALID == Base._isvalid_utf8_dfa(state2,[b3],1,1) + end + end + + # Check Four byte Sequences + for table_row in table_rows[7:9] + b1 = first(table_row[1]) + state1 = Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b1],1,1) + state2 = Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b1],1,1) + @test state1 == state2 + #Prove that all the first bytes in a row give same state + for b1 in table_row[1] + @test state1 == Base._isvalid_utf8_dfa(Base._UTF8_DFA_ACCEPT,[b1],1,1) + @test state1 == Base._isvalid_utf8_dfa(Base._UTF8_DFA_ASCII,[b1],1,1) + end + + b1 = first(table_row[1]) + b2 = first(table_row[2]) + #Prove that all valid second bytes return same state + state2 = Base._isvalid_utf8_dfa(state1,[b2],1,1) + for b2 = table_row[2] + @test state2 == Base._isvalid_utf8_dfa(state1,[b2],1,1) + end + for b2 = setdiff(0x00:0xFF,table_row[2]) + @test Base._UTF8_DFA_INVALID == Base._isvalid_utf8_dfa(state1,[b2],1,1) + end + + + b2 = first(table_row[2]) + b3 = first(table_row[3]) + state3 = Base._isvalid_utf8_dfa(state2,[b3],1,1) + #Prove that all valid third bytes return same state + for b3 = table_row[3] + @test state3 == Base._isvalid_utf8_dfa(state2,[b3],1,1) + end + for b3 = setdiff(0x00:0xFF,table_row[3]) + @test Base._UTF8_DFA_INVALID == Base._isvalid_utf8_dfa(state2,[b3],1,1) + end + + b3 = first(table_row[3]) + #Prove that all valid forth bytes return correct state + for b4 = table_row[4] + @test Base._UTF8_DFA_ACCEPT == Base._isvalid_utf8_dfa(state3,[b4],1,1) + end + for b4 = setdiff(0x00:0xFF,table_row[4]) + @test Base._UTF8_DFA_INVALID == Base._isvalid_utf8_dfa(state3,[b4],1,1) + end + end end diff --git a/test/strings/io.jl b/test/strings/io.jl index 91ad83b24e328..aed1f800d4d49 100644 --- a/test/strings/io.jl +++ b/test/strings/io.jl @@ -190,8 +190,8 @@ end @testset "sprint with context" begin function f(io::IO) - println(io, "compact => ", get(io, :compact, false)) - println(io, "limit => ", get(io, :limit, false)) + println(io, "compact => ", get(io, :compact, false)::Bool) + println(io, "limit => ", get(io, :limit, false)::Bool) end str = sprint(f) @@ -219,6 +219,10 @@ end """ end +@testset "sprint honoring IOContext" begin + @test startswith(sprint(show, Base.Dict[], context=(:compact=>false, :module=>nothing)), "Base.Dict") +end + @testset "#11659" begin # The indentation code was not correctly counting tab stops @test Base.indentation(" \t") == (8, true) diff --git a/test/strings/types.jl b/test/strings/types.jl index 1879d05eb8fab..771be253b1ec9 100644 --- a/test/strings/types.jl +++ b/test/strings/types.jl @@ -26,7 +26,7 @@ for i1 = 1:length(u8str2) end # tests that SubString of a single multibyte `Char` string, like "∀" which takes 3 bytes -# gives the same result as `getindex` (except that it is a veiw not a copy) +# gives the same result as `getindex` (except that it is a view not a copy) for idx in 0:1 @test SubString("∀", 1, idx) == "∀"[1:idx] end diff --git a/test/strings/util.jl b/test/strings/util.jl index 8957513e37f25..5218310c5c1c7 100644 --- a/test/strings/util.jl +++ b/test/strings/util.jl @@ -91,6 +91,26 @@ end @test rstrip("ello", ['e','o']) == "ell" end +@testset "partition" begin + # AbstractString to partition into SubString + let v=collect(Iterators.partition("foobars",1)) + @test v==SubString{String}["f","o","o","b","a","r","s"] + end + + let v=collect(Iterators.partition("foobars",2)) + @test v==SubString{String}["fo","ob","ar","s"] + end + + for n in [7,8] + @test collect(Iterators.partition("foobars",n))[1]=="foobars" + end + + # HOWEVER enumerate explicitly slices String "atoms" so `Chars` are returned + let v=collect(Iterators.partition(enumerate("foobars"),1)) + @test v==Vector{Tuple{Int64, Char}}[[(1, 'f')],[(2, 'o')],[(3, 'o')],[(4, 'b')],[(5, 'a')],[(6, 'r')], [(7, 's')]] + end +end + @testset "rsplit/split" begin @test split("foo,bar,baz", 'x') == ["foo,bar,baz"] @test split("foo,bar,baz", ',') == ["foo","bar","baz"] diff --git a/test/subarray.jl b/test/subarray.jl index cc8aab94e4c42..e22c1394cbfc2 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -256,7 +256,7 @@ runviews(SB::AbstractArray{T,0}, indexN, indexNN, indexNNN) where {T} = nothing ######### Tests ######### -testfull = Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) +testfull = Base.get_bool_env("JULIA_TESTFULL", false) ### Views from Arrays ### index5 = (1, :, 2:5, [4,1,5], reshape([2]), view(1:5,[2 3 4 1])) # all work with at least size 5 @@ -288,7 +288,8 @@ if testfull end let B = copy(reshape(1:13^3, 13, 13, 13)) - @testset "spot checks: $oind" for oind in ((:,:,:), + @testset "spot checks: $oind" for oind in ( + (:,:,:), (:,:,6), (:,6,:), (6,:,:), @@ -296,7 +297,6 @@ let B = copy(reshape(1:13^3, 13, 13, 13)) (3:7,:,:), (3:7,6,:), (3:7,6,0x6), - (6,UInt(3):UInt(7),3:7), (13:-2:1,:,:), ([8,4,6,12,5,7],:,3:7), (6,CartesianIndex.(6,[8,4,6,12,5,7])), @@ -307,7 +307,29 @@ let B = copy(reshape(1:13^3, 13, 13, 13)) (3,reshape(2:11,5,2),4), (3,reshape(2:2:13,3,2),4), (view(1:13,[9,12,4,13,1]),2:6,4), - ([1:5 2:6 3:7 4:8 5:9], :, 3)) + ([1:5 2:6 3:7 4:8 5:9], :, 3), + ) + runsubarraytests(B, oind...) + viewB = view(B, oind...) + runviews(viewB, index5, index25, index125) + end +end + +let B = copy(reshape(1:13^3, 13, 13, 13)) + @testset "spot checks (other BitIntegers): $oind" for oind in ( + (:,:,0x6), + (:,0x00000006,:), + (0x0006,:,:), + (:,0x00000003:0x00000007,:), + (0x0000000000000003:0x0000000000000007,:,:), + (0x0003:0x0007,0x6,:), + (6,UInt(3):UInt(7),3:7), + (Int16(3):Int16(7),Int16(6),:), + (CartesianIndex(0xD,0x6),UInt8[8,4,6,12,5,7]), + (Int8(1),:,view(1:13,[9,12,4,13,1])), + (view(1:13,Int16[9,12,4,13,1]),UInt8(2):UInt16(6),Int8(4)), + (Int8[1:5 2:6 3:7 4:8 5:9],:,UInt64(3)), + ) runsubarraytests(B, oind...) viewB = view(B, oind...) runviews(viewB, index5, index25, index125) @@ -737,3 +759,16 @@ end end end 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_throws BoundsError view(v,:,2) + @test_throws BoundsError view(v,:,1,2) + + m = randn(4,5).+im + @test view(m, 1:2, 3, 1, 1) == m[1:2, 3] + @test parent(view(m, 1:2, 3, 1, 1)) === m +end diff --git a/test/subtype.jl b/test/subtype.jl index 3eca685aee84c..4a3e55c039e94 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -588,7 +588,7 @@ function test_old() end const easy_menagerie = - Any[Bottom, Any, Int, Int8, Integer, Real, + Any[Any, Int, Int8, Integer, Real, Array{Int,1}, AbstractArray{Int,1}, Tuple{Int,Vararg{Integer}}, Tuple{Integer,Vararg{Int}}, Tuple{}, Union{Int,Int8}, @@ -627,6 +627,10 @@ end add_variants!(easy_menagerie) add_variants!(hard_menagerie) +push!(easy_menagerie, Bottom) +push!(easy_menagerie, Ref{Bottom}) +push!(easy_menagerie, @UnionAll N NTuple{N,Bottom}) +push!(easy_menagerie, @UnionAll S<:Bottom Ref{S}) const menagerie = [easy_menagerie; hard_menagerie] @@ -673,9 +677,11 @@ function test_properties() @test isequal_type(T, S) == isequal_type(Ref{T}, Ref{S}) # covariance - @test issubTS == issub(Tuple{T}, Tuple{S}) - @test issubTS == issub(Tuple{Vararg{T}}, Tuple{Vararg{S}}) - @test issubTS == issub(Tuple{T}, Tuple{Vararg{S}}) + if T !== Bottom && S !== Bottom + @test issubTS == issub(Tuple{T}, Tuple{S}) + @test issubTS == issub(Tuple{Vararg{T}}, Tuple{Vararg{S}}) + @test issubTS == issub(Tuple{T}, Tuple{Vararg{S}}) + end # pseudo-contravariance @test issubTS == issub(¬S, ¬T) @@ -753,8 +759,11 @@ function test_intersection() @testintersect((@UnionAll T Tuple{T, AbstractArray{T}}), Tuple{Int, Array{Number,1}}, Tuple{Int, Array{Number,1}}) + # TODO: improve this result + #@testintersect((@UnionAll S Tuple{S,Vector{S}}), (@UnionAll T<:Real Tuple{T,AbstractVector{T}}), + # (@UnionAll S<:Real Tuple{S,Vector{S}})) @testintersect((@UnionAll S Tuple{S,Vector{S}}), (@UnionAll T<:Real Tuple{T,AbstractVector{T}}), - (@UnionAll S<:Real Tuple{S,Vector{S}})) + (@UnionAll S<:Real Tuple{Real,Vector{S}})) # typevar corresponding to a type it will end up being neither greater than nor # less than @@ -813,9 +822,9 @@ function test_intersection() Tuple{Tuple{Vararg{Integer}}, Tuple{Integer,Integer}}, Tuple{Tuple{Integer,Integer}, Tuple{Integer,Integer}}) - #@test isequal_type(typeintersect((@UnionAll N Tuple{NTuple{N,Any},Array{Int,N}}), - # Tuple{Tuple{Int,Vararg{Int}},Array}), - # Tuple{Tuple{Int,Vararg{Int}},Array{Int,N}}) + @test isequal_type(typeintersect((@UnionAll N Tuple{NTuple{N,Any},Array{Int,N}}), + Tuple{Tuple{Int,Vararg{Int}},Array}), + @UnionAll N Tuple{Tuple{Int,Vararg{Int}},Array{Int,N}}) @testintersect((@UnionAll N Tuple{NTuple{N,Any},Array{Int,N}}), Tuple{Tuple{Int,Vararg{Int}},Array{Int,2}}, @@ -904,11 +913,11 @@ function test_intersection() # both of these answers seem acceptable #@testintersect(Tuple{T,T} where T<:Union{UpperTriangular, UnitUpperTriangular}, # Tuple{AbstractArray{T,N}, AbstractArray{T,N}} where N where T, - # Union{Tuple{T,T} where T<:UpperTriangular, - # Tuple{T,T} where T<:UnitUpperTriangular}) + # Union{Tuple{T,T} where T<:UpperTriangular{T1}, + # Tuple{T,T} where T<:UnitUpperTriangular{T1}} where T) @testintersect(Tuple{T,T} where T<:Union{UpperTriangular, UnitUpperTriangular}, Tuple{AbstractArray{T,N}, AbstractArray{T,N}} where N where T, - Tuple{T,T} where T<:Union{UpperTriangular, UnitUpperTriangular}) + Tuple{T,T} where {T1, T<:Union{UpperTriangular{T1}, UnitUpperTriangular{T1}}}) @testintersect(DataType, Type, DataType) @testintersect(DataType, Type{T} where T<:Integer, Type{T} where T<:Integer) @@ -924,9 +933,10 @@ function test_intersection() # since this T is inside the invariant ctor Type{}, we allow T == Any here @testintersect((Type{Tuple{Vararg{T}}} where T), Type{Tuple}, Type{Tuple}) + # TODO: improve this @testintersect(Tuple{Type{S}, Tuple{Any, Vararg{Any}}} where S<:Tuple{Any, Vararg{Any}}, Tuple{Type{T}, T} where T, - Tuple{Type{S},S} where S<:Tuple{Any,Vararg{Any}}) + Tuple{Type{S}, Tuple{Any, Vararg{Any}}} where S<:Tuple{Any, Vararg{Any}}) # part of issue #20450 @testintersect(Tuple{Array{Ref{T}, 1}, Array{Pair{M, V}, 1}} where V where T where M, @@ -1044,6 +1054,7 @@ function test_intersection() @testintersect(Type{<:Tuple{Any,Vararg{Any}}}, Type{Tuple{Vararg{Int,N}}} where N, Type{Tuple{Int,Vararg{Int,N}}} where N) + @testintersect(Type{<:Array}, Type{AbstractArray{T}} where T, Bottom) @@ -1072,8 +1083,7 @@ function test_intersection_properties() I2 = _type_intersect(S,T) @test isequal_type(I, I2) if i > length(easy_menagerie) || j > length(easy_menagerie) - # TODO: these cases give a conservative answer - @test issub(I, T) || issub(I, S) + # @test issub(I, T) || issub(I, S) else @test issub(I, T) && issub(I, S) end @@ -1176,11 +1186,25 @@ ftwoparams(::TwoParams{<:Real,<:Real}) = 3 # a bunch of cases found by fuzzing let a = Tuple{Float64,T7} where T7, b = Tuple{S5,Tuple{S5}} where S5 - @test typeintersect(a, b) <: b + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{T1,T1} where T1, b = Tuple{Val{S2},S6} where S2 where S6 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Val{Tuple{T1,T1}} where T1, b = Val{Tuple{Val{S2},S6}} where S2 where S6 @@ -1188,15 +1212,36 @@ let a = Val{Tuple{T1,T1}} where T1, end let a = Tuple{Float64,T3,T4} where T4 where T3, b = Tuple{S2,Tuple{S3},S3} where S2 where S3 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test_broken I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test I2 <: b end let a = Tuple{T1,Tuple{T1}} where T1, b = Tuple{Float64,S3} where S3 - @test typeintersect(a, b) <: a + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{5,T4,T5} where T4 where T5, b = Tuple{S2,S3,Tuple{S3}} where S2 where S3 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test_broken I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test I2 <: b end let a = Tuple{T2,Tuple{T4,T2}} where T4 where T2, b = Tuple{Float64,Tuple{Tuple{S3},S3}} where S3 @@ -1204,23 +1249,58 @@ let a = Tuple{T2,Tuple{T4,T2}} where T4 where T2, end let a = Tuple{Tuple{T2,4},T6} where T2 where T6, b = Tuple{Tuple{S2,S3},Tuple{S2}} where S2 where S3 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test_broken I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test I2 <: b end let a = Tuple{T3,Int64,Tuple{T3}} where T3, b = Tuple{S3,S3,S4} where S4 where S3 - @test_broken typeintersect(a, b) <: a + 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 end let a = Tuple{T1,Val{T2},T2} where T2 where T1, b = Tuple{Float64,S1,S2} where S2 where S1 - @test typeintersect(a, b) == typeintersect(b, a) + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test_broken I1 <: a + @test_broken I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{T1,Val{T2},T2} where T2 where T1, b = Tuple{Float64,S1,S2} where S2 where S1 - @test_broken typeintersect(a, b) <: a + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test_broken I1 <: a + @test_broken I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{Float64,T1} where T1, b = Tuple{S1,Tuple{S1}} where S1 - @test typeintersect(a, b) <: b + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test I1 <: b + @test I2 <: b end let a = Tuple{Val{T1},T2,T2} where T2 where T1, b = Tuple{Val{Tuple{S2}},S3,Float64} where S2 where S3 @@ -1229,12 +1309,20 @@ end let a = Tuple{T1,T2,T2} where T1 where T2, b = Tuple{Val{S2},S2,Float64} where S2, x = Tuple{Val{Float64},Float64,Float64} - @test x <: typeintersect(a, b) -end -let a = Val{Tuple{T1,Val{T2},Val{Int64},Tuple{Tuple{T3,5,Float64},T4,T2,T5}}} where T1 where T5 where T4 where T3 where T2, - b = Val{Tuple{Tuple{S1,5,Float64},Val{S2},S3,Tuple{Tuple{Val{Float64},5,Float64},2,Float64,S4}}} where S2 where S3 where S1 where S4 - @test_skip typeintersect(b, a) -end + I1 = typeintersect(a, b) + I2 = typeintersect(b, a) + @test x <: I1 + @test x <: I2 + @test I1 <: I2 + @test I2 <: I1 + @test I1 <: a + @test I2 <: a + @test_broken I1 <: b + @test_broken I2 <: b +end +@testintersect(Val{Tuple{T1,Val{T2},Val{Int64},Tuple{Tuple{T3,5,Float64},T4,T2,T5}}} where T1 where T5 where T4 where T3 where T2, + Val{Tuple{Tuple{S1,5,Float64},Val{S2},S3,Tuple{Tuple{Val{Float64},5,Float64},2,Float64,S4}}} where S2 where S3 where S1 where S4, + Val{Tuple{Tuple{S1, 5, Float64}, Val{Float64}, Val{Int64}, Tuple{Tuple{Val{Float64}, 5, Float64}, 2, Float64, T5}}} where {T5, S1}) # issue #20992 abstract type A20992{T,D,d} end @@ -1247,7 +1335,7 @@ end # Issue #19414 let ex = try struct A19414 <: Base.AbstractSet end catch e; e end - @test isa(ex, ErrorException) && ex.msg == "invalid subtyping in definition of A19414" + @test isa(ex, ErrorException) && ex.msg == "invalid subtyping in definition of A19414: can only subtype data types." end # issue #20103, OP and comments @@ -1401,6 +1489,8 @@ f24521(::Type{T}, ::Type{T}) where {T} = T @test !(Ref{Union{Int64, Val{Number}}} <: Ref{Union{Val{T}, T}} where T) @test !(Ref{Union{Ref{Number}, Int64}} <: Ref{Union{Ref{T}, T}} where T) @test !(Ref{Union{Val{Number}, Int64}} <: Ref{Union{Val{T}, T}} where T) +@test !(Val{Ref{Union{Int64, Ref{Number}}}} <: Val{S} where {S<:Ref{Union{Ref{T}, T}} where T}) +@test !(Tuple{Ref{Union{Int64, Ref{Number}}}} <: Tuple{S} where {S<:Ref{Union{Ref{T}, T}} where T}) # issue #26180 @test !(Ref{Union{Ref{Int64}, Ref{Number}}} <: Ref{Ref{T}} where T) @@ -1514,7 +1604,7 @@ end Tuple{Type{A29955{T,TV,TM}}, TM} where {T,TV<:AbstractVector{T},TM<:M29955{T,TV}}, Tuple{Type{A29955{Float64,Array{Float64,1},TM}}, - TM} where TM<:M29955{Float64,Array{Float64,1}}) + M29955{Float64,Vector{Float64}}} where TM<:M29955{Float64,Array{Float64,1}}) let M = M29955{T,Vector{Float64}} where T @test M == (M29955{T,Vector{Float64}} where T) @test M{Float64} == M29955{Float64,Vector{Float64}} @@ -1532,9 +1622,9 @@ end Tuple{LT,R,I} where LT<:Union{I, R} where R<:Rational{I} where I<:Integer, Tuple{LT,Rational{Int},Int} where LT<:Union{Rational{Int},Int}) -#@testintersect(Tuple{Any,Tuple{Int},Int}, -# Tuple{LT,R,I} where LT<:Union{I, R} where R<:Tuple{I} where I<:Integer, -# Tuple{LT,Tuple{Int},Int} where LT<:Union{Tuple{Int},Int}) +@testintersect(Tuple{Any,Tuple{Int},Int}, + Tuple{LT,R,I} where LT<:Union{I, R} where R<:Tuple{I} where I<:Integer, + Tuple{LT,Tuple{Int},Int} where LT<:Union{Tuple{Int},Int}) # fails due to this: let U = Tuple{Union{LT, LT1},Union{R, R1},Int} where LT1<:R1 where R1<:Tuple{Int} where LT<:Int where R<:Tuple{Int}, U2 = Union{Tuple{LT,R,Int} where LT<:Int where R<:Tuple{Int}, Tuple{LT,R,Int} where LT<:R where R<:Tuple{Int}}, @@ -1551,9 +1641,10 @@ end # issue #31082 and #30741 @test typeintersect(Tuple{T, Ref{T}, T} where T, Tuple{Ref{S}, S, S} where S) != Union{} +# TODO: improve this bound @testintersect(Tuple{Pair{B,C},Union{C,Pair{B,C}},Union{B,Real}} where {B,C}, Tuple{Pair{B,C},C,C} where {B,C}, - Tuple{Pair{B,C},C,C} where C<:Union{Real, B} where B) + Tuple{Pair{B,C}, Union{Pair{B,C},C},Union{Real,B}} where {B,C}) f31082(::Pair{B, C}, ::Union{C, Pair{B, C}}, ::Union{B, Real}) where {B, C} = 0 f31082(::Pair{B, C}, ::C, ::C) where {B, C} = 1 @test f31082(""=>1, 2, 3) == 1 @@ -1719,8 +1810,18 @@ end #end # issue #32386 -@test typeintersect(Type{S} where S<:(Vector{Pair{_A,N} where N} where _A), - Type{Vector{T}} where T) == Type{Vector{Pair{_A,N} where N}} where _A +@testintersect(Type{S} where S<:(Vector{Pair{_A,N} where N} where _A), + Type{Vector{T}} where T, + Type{Vector{Pair{_A,N} where N}} where _A) + +# pr #49049 +@testintersect(Tuple{Type{Pair{T, A} where {T, A<:Array{T}}}, Int, Any}, + Tuple{Type{F}, Any, Int} where {F<:(Pair{T, A} where {T, A<:Array{T}})}, + Tuple{Type{Pair{T, A} where {T, A<:(Array{T})}}, Int, Int}) + +@testintersect(Type{Ref{Union{Int, Tuple{S,S} where S<:T}}} where T, + Type{F} where F<:(Base.RefValue{Union{Int, Tuple{S,S} where S<:T}} where T), + Union{}) # issue #32488 struct S32488{S <: Tuple, T, N, L} @@ -1779,8 +1880,11 @@ s26065 = Ref{Tuple{T,Ref{Union{Ref{Tuple{Ref{Union{Ref{Ref{Tuple{Ref{Tuple{Union Tuple{Type{Tuple{Vararg{V}}}, Tuple{Vararg{V}}} where V) # issue 36100 -@test NamedTuple{(:a, :b), Tuple{Missing, Union{}}} == NamedTuple{(:a, :b), Tuple{Missing, Union{}}} -@test Val{Tuple{Missing, Union{}}} === Val{Tuple{Missing, Union{}}} +@test Pair{(:a, :b), Tuple{Missing, Vararg{Union{},N}} where N} === + Pair{(:a, :b), Tuple{Missing, Vararg{Union{},N}} where N} != + Pair{(:a, :b), Tuple{Missing, Vararg{Union{}}}} === Pair{(:a, :b), Tuple{Missing}} +@test Val{Tuple{Missing, Vararg{Union{},N}} where N} === Val{Tuple{Missing, Vararg{Union{},N}} where N} != + Val{Tuple{Missing, Vararg{Union{}}}} === Val{Tuple{Missing}} # issue #36869 struct F36869{T, V} <: AbstractArray{Union{T, V}, 1} @@ -1798,40 +1902,31 @@ end # issue #38081 struct AlmostLU{T, S<:AbstractMatrix{T}} end -let X1 = Tuple{AlmostLU, Vector{T}} where T, - X2 = Tuple{AlmostLU{S, X} where X<:Matrix, Vector{S}} where S<:Union{Float32, Float64}, - I = typeintersect(X1, X2) - # TODO: the quality of this intersection is not great; for now just test that it - # doesn't stack overflow - @test I<:X1 || I<:X2 - actual = Tuple{Union{AlmostLU{S, X} where X<:Matrix{S}, AlmostLU{S, <:Matrix}}, Vector{S}} where S<:Union{Float32, Float64} - @test I == actual -end +@testintersect(Tuple{AlmostLU, Vector{T}} where T, + Tuple{AlmostLU{S, X} where X<:Matrix, Vector{S}} where S<:Union{Float32, Float64}, + Tuple{AlmostLU{T, X} where X<:Matrix{T}, Vector{T}} where T<:Union{Float32, Float64}) -let - # issue #22787 - # for now check that these don't stack overflow - t = typeintersect(Tuple{Type{Q}, Q, Ref{Q}} where Q<:Ref, - Tuple{Type{S}, Union{Ref{S}, Ref{R}}, R} where R where S) - @test_broken t != Union{} - t = typeintersect(Tuple{Type{T}, T, Ref{T}} where T, - Tuple{Type{S}, Ref{S}, S} where S) - @test_broken t != Union{} +# issue #22787 +@testintersect(Tuple{Type{Q}, Q, Ref{Q}} where Q<:Ref, + Tuple{Type{S}, Union{Ref{S}, Ref{R}}, R} where R where S, + Tuple{Type{Q}, Union{Ref{Q}, Ref{R}}, Ref{Q}} where {Q<:Ref, R}) # likely suboptimal - # issue #38279 - t = typeintersect(Tuple{<:Array{T, N}, Val{T}} where {T<:Real, N}, - Tuple{<:Array{T, N}, Val{<:AbstractString}} where {T<:Real, N}) - @test t == Tuple{<:Array{Union{}, N}, Val{Union{}}} where N +let t = typeintersect(Tuple{Type{T}, T, Ref{T}} where T, + Tuple{Type{S}, Ref{S}, S} where S) + @test_broken t == Tuple{Type{T}, Ref{T}, Ref{T}} where T>:Ref + @test t == Tuple{Type{T}, Ref{T}, Ref{T}} where T end +# issue #38279 +t = typeintersect(Tuple{<:Array{T, N}, Val{T}} where {T<:Real, N}, + Tuple{<:Array{T, N}, Val{<:AbstractString}} where {T<:Real, N}) +@test t == Tuple{<:Array{Union{}, N}, Val{Union{}}} where N + # issue #36951 @testintersect(Type{T} where T>:Missing, Type{Some{T}} where T, Union{}) -# issue #24333 -@test_broken (Type{Union{Ref,Cvoid}} <: Type{Union{T,Cvoid}} where T) - # issue #38423 let Either{L, R} = Union{Ref{L}, Val{R}} @@ -1863,10 +1958,25 @@ end # issue #34170 let A = Tuple{Type{T} where T<:Ref, Ref, Union{T, Union{Ref{T}, T}} where T<:Ref}, B = Tuple{Type{T}, Ref{T}, Union{Int, Ref{T}, T}} where T - I = typeintersect(A,B) # this was a case where <: disagreed with === (due to a badly-normalized type) - @test I == typeintersect(A,B) - @test I == Tuple{Type{T}, Ref{T}, Ref} where T<:Ref + I = _type_intersect(B, A) + @test_broken I == Union{Tuple{Type{T}, Ref{T}, Ref{T}} where T<:Ref, Tuple{Type{T}, Ref{T}, T} where T<:Ref} + @test I == _type_intersect(B, A) == Tuple{Type{T}, Ref{T}, Ref} where T<:Ref + I = typeintersect(B, A) + @test_broken I == Tuple{Type{T}, Ref{T}, Union{Ref{T}, T}} where T<:Ref + @test I == typeintersect(B, A) <: Tuple{Type{T}, Ref{T}, Ref} where T<:Ref + + I = _type_intersect(A, B) + @test !Base.has_free_typevars(I) + J = Tuple{Type{T1}, Ref{T1}, Ref} where {T, T1<:Union{Ref, Ref{T}}} + @test I == _type_intersect(A, B) == J + @test_broken I == Tuple{Type{T}, Ref{T}, T1} where {T<:Ref, T1<:Union{T, Ref{T}}} # a better result, == to the result with arguments switched + + I = typeintersect(A, B) + @test !Base.has_free_typevars(I) + J = Tuple{Type{T1}, Ref{T1}, Ref} where {T, T1<:Union{Ref, Ref{T}}} + @test I == typeintersect(A, B) == J + end # issue #39218 @@ -1895,20 +2005,14 @@ let A = Tuple{Type{<:Union{Number, T}}, Ref{T}} where T, end # issue #39698 -let T = Type{T} where T<:(AbstractArray{I}) where I<:(Base.IteratorsMD.CartesianIndex), - S = Type{S} where S<:(Base.IteratorsMD.CartesianIndices{A, B} where B<:Tuple{Vararg{Any, A}} where A) - I = typeintersect(T, S) - @test_broken I <: T - @test I <: S - @test_broken I == typeintersect(S, T) -end +@testintersect(Type{T} where T<:(AbstractArray{I}) where I<:(Base.IteratorsMD.CartesianIndex), + Type{S} where S<:(Base.IteratorsMD.CartesianIndices{A, B} where B<:Tuple{Vararg{Any, A}} where A), + Type{S} where {N, S<:(Base.IteratorsMD.CartesianIndices{N, B} where B<:Tuple{Vararg{Any, N}})}) # issue #39948 -let A = Tuple{Array{Pair{T, JT} where JT<:Ref{T}, 1} where T, Vector}, - I = typeintersect(A, Tuple{Vararg{Vector{T}}} where T) - @test I <: A - @test !Base.has_free_typevars(I) -end +@testintersect(Tuple{Array{Pair{T, JT} where JT<:Ref{T}, 1} where T, Vector}, + Tuple{Vararg{Vector{T}}} where T, + Tuple{Array{Pair{T, JT} where JT<:Ref{T}, 1}, Array{Pair{T, JT} where JT<:Ref{T}, 1}} where T) # issue #8915 struct D8915{T<:Union{Float32,Float64}} @@ -1928,18 +2032,34 @@ let A = Tuple{Ref{T}, Vararg{T}} where T, B = Tuple{Ref{U}, Union{Ref{S}, Ref{U}, Int}, Union{Ref{S}, S}} where S where U, C = Tuple{Ref{U}, Union{Ref{S}, Ref{U}, Ref{W}}, Union{Ref{S}, W, V}} where V<:AbstractArray where W where S where U I = typeintersect(A, B) + Ts = (Tuple{Ref{Int}, Int, Int}, Tuple{Ref{Ref{Int}}, Ref{Int}, Ref{Int}}) @test I != Union{} - @test I <: A + @test_broken I <: A @test I <: B - # avoid stack overflow + for T in Ts + if T <: A && T <: B + @test T <: I + end + end J = typeintersect(A, C) - @test_broken J != Union{} + @test J != Union{} + @test_broken J <: A + @test J <: C + for T in Ts + if T <: A && T <: C + @test T <: J + end + end end let A = Tuple{Dict{I,T}, I, T} where T where I, - B = Tuple{AbstractDict{I,T}, T, I} where T where I - # TODO: we should probably have I == T here - @test typeintersect(A, B) == Tuple{Dict{I,T}, I, T} where {I, T} + B = Tuple{AbstractDict{I,T}, T, I} where T where I, + I = typeintersect(A, B) + # TODO: we should probably have something approaching I == T here, + # though note something more complex is needed since the intersection must also include types such as; + # Tuple{Dict{Integer,Any}, Integer, Int} + @test_broken I <: A && I <: B + @test I == typeintersect(B, A) == Tuple{Dict{I, T}, Any, Any} where {I, T} end let A = Tuple{UnionAll, Vector{Any}}, @@ -1964,8 +2084,9 @@ let A = Tuple{Any, Type{Ref{_A}} where _A}, B = Tuple{Type{T}, Type{<:Union{Ref{T}, T}}} where T, I = typeintersect(A, B) @test I != Union{} - # TODO: this intersection result is still too narrow - @test_broken Tuple{Type{Ref{Integer}}, Type{Ref{Integer}}} <: I + @test Tuple{Type{Ref{Integer}}, Type{Ref{Integer}}} <: I + # TODO: this intersection result seems too wide (I == B) ? + @test_broken !<:(Tuple{Type{Int}, Type{Int}}, I) end @testintersect(Tuple{Type{T}, T} where T<:(Tuple{Vararg{_A, _B}} where _B where _A), @@ -1976,3 +2097,453 @@ end @testintersect(Tuple{Type{Pair{_A, S} where S<:AbstractArray{<:_A, 2}}, Dict} where _A, Tuple{Type{Pair{_A, S} where S<:AbstractArray{<:_A, 2}} where _A, Union{Array, Pair}}, Bottom) + +# https://github.com/JuliaLang/julia/issues/44735 +@test_throws TypeError(:typeassert, Type, Vararg{Int}) typeintersect(Vararg{Int}, Int) +@test_throws TypeError(:typeassert, Type, Vararg{Int}) typeintersect(Int, Vararg{Int}) +@test_throws TypeError(:typeassert, Type, 1) typeintersect(1, Int) +@test_throws TypeError(:typeassert, Type, 1) typeintersect(Int, 1) + +let A = Tuple{typeof(identity), Type{Union{}}}, + B = Tuple{typeof(identity), typeof(Union{})} + @test A == B && (Base.isdispatchtuple(A) == Base.isdispatchtuple(B)) +end + +# issue #45703 +# requires assertions enabled (to catch discrepancy in obvious_subtype) +let T = TypeVar(:T, Real), + V = TypeVar(:V, AbstractVector{T}), + S = Type{Pair{T, V}} + @test !(UnionAll(T, UnionAll(V, UnionAll(T, Type{Pair{T, V}}))) <: UnionAll(T, UnionAll(V, Type{Pair{T, V}}))) + @test !(UnionAll(T, UnionAll(V, UnionAll(T, S))) <: UnionAll(T, UnionAll(V, S))) +end + +# issue #41096 +let C = Val{Val{B}} where {B} + @testintersect(Val{<:Union{Missing, Val{false}, Val{true}}}, C, Val{<:Union{Val{true}, Val{false}}}) + @testintersect(Val{<:Union{Nothing, Val{true}, Val{false}}}, C, Val{<:Union{Val{true}, Val{false}}}) + @testintersect(Val{<:Union{Nothing, Val{false}}}, C, Val{Val{false}}) +end + +#issue #43082 +struct X43082{A, I, B<:Union{Ref{I},I}}; end +@testintersect(Tuple{X43082{T}, Int} where T, Tuple{X43082{Int}, Any}, Tuple{X43082{Int}, Int}) + +#issue #36443 +let C = Tuple{Val{3},Int,Int,Int}, + As = (Tuple{Val{N},Vararg{T,N}} where {T,N}, + Tuple{Val{N},Vararg{T,N}} where {N,T}), + Bs = (Tuple{Val{3},Int,Vararg{T,N}} where {T,N}, + Tuple{Val{3},Int,Vararg{T,N}} where {N,T}, + Tuple{Val{3},Int,Vararg{T}} where {T}, + Tuple{Val{3},Int,Vararg{T,2}} where {T}) + for A in As, B in Bs + @testintersect(A, B, C) + end +end + +let A = Tuple{Type{Val{N}},Tuple{Vararg{T,N}} where T} where N, + C = Tuple{Type{Val{2}},Tuple{T,T} where T} + @testintersect(A, Tuple{Type{Val{2}},Tuple{Vararg{T,N}} where T} where N, C) + @testintersect(A, Tuple{Type{Val{2}},Tuple{T,Vararg{T,N}} where T} where N, C) + @testintersect(A, Tuple{Type{Val{2}},Tuple{T,T,Vararg{T,N}} where T} where N, C) +end + +let f36443(::NTuple{N}=[(f36443,),(1,2)][2],::Val{N}=Val(2)) where{N} = 0 + @test f36443() == 0; +end + +let C = Tuple{Val{3},Int,Int,Int,Int}, + As = (Tuple{Val{N},Int,Vararg{T,N}} where {T,N}, + Tuple{Val{N},Int,Vararg{T,N}} where {N,T}), + Bs = (Tuple{Val{3},Vararg{T,N}} where {T,N}, + Tuple{Val{3},Vararg{T,N}} where {N,T}, + Tuple{Val{3},Vararg{T}} where {T}) + for A in As, B in Bs + @testintersect(A, B, C) + end +end + +#issue #37257 +let T = Tuple{Val{N}, Any, Any, Vararg{Any,N}} where N, + C = Tuple{Val{1}, Any, Any, Any} + @testintersect(T, Tuple{Val{1}, Vararg{Any}}, C) + @testintersect(T, Tuple{Val{1}, Any, Vararg{Any}}, C) + @testintersect(T, Tuple{Val{1}, Any, Any, Vararg{Any}}, C) + @testintersect(T, Tuple{Val{1}, Any, Any, Any, Vararg{Any}}, C) + @testintersect(T, Tuple{Val{1}, Any, Any, Any, Any, Vararg{Any}}, Union{}) +end + +let A = Tuple{NTuple{N,Any},Val{N}} where {N}, + C = Tuple{NTuple{4,Any},Val{4}} + @testintersect(A, Tuple{Tuple{Vararg{Any,N}},Val{4}} where {N}, C) + @testintersect(A, Tuple{Tuple{Vararg{Any}},Val{4}}, C) + @testintersect(A, Tuple{Tuple{Vararg{Any,N}} where {N},Val{4}}, C) + + @testintersect(A, Tuple{Tuple{Any,Vararg{Any,N}},Val{4}} where {N}, C) + @testintersect(A, Tuple{Tuple{Any,Vararg{Any}},Val{4}}, C) + @testintersect(A, Tuple{Tuple{Any,Vararg{Any,N}} where {N},Val{4}}, C) + + @testintersect(A, Tuple{Tuple{Any,Any,Any,Any,Any,Vararg{Any,N}},Val{4}} where {N}, Union{}) + @testintersect(A, Tuple{Tuple{Any,Any,Any,Any,Any,Vararg{Any}},Val{4}}, Union{}) + @testintersect(A, Tuple{Tuple{Any,Any,Any,Any,Any,Vararg{Any,N}} where {N},Val{4}}, Union{}) +end + +#issue #39088 +let + a() = c((1,), (1,1,1,1)) + c(d::NTuple{T}, ::NTuple{T}) where T = d + c(d::NTuple{f}, b) where f = c((d..., f), b) + j(h::NTuple{T}, ::NTuple{T} = a()) where T = nothing + @test j((1,1,1,1)) === nothing +end + +let A = Tuple{NTuple{N, Int}, NTuple{N, Int}} where N, + C = Tuple{NTuple{4, Int}, NTuple{4, Int}} + @testintersect(A, Tuple{Tuple{Int, Vararg{Any}}, NTuple{4, Int}}, C) + @testintersect(A, Tuple{Tuple{Int, Vararg{Any, N}} where {N}, NTuple{4, Int}}, C) + @testintersect(A, Tuple{Tuple{Int, Vararg{Any, N}}, NTuple{4, Int}} where {N}, C) + + 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} + 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}}} + end +end + +let A = Pair{NTuple{N, Int}, NTuple{N, Int}} where N, + C = Pair{NTuple{4, Int}, NTuple{4, Int}} + @testintersect(A, Pair{<:Tuple{Int, Vararg{Any}}, NTuple{4, Int}}, C) + @testintersect(A, Pair{<:Tuple{Int, Vararg{Any, N}} where {N}, NTuple{4, Int}}, C) + @testintersect(A, Pair{<:Tuple{Int, Vararg{Any, N}}, NTuple{4, Int}} where {N}, C) + + 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} + @testintersect(A, B, C) + end +end + +# Example from pr#39098 +@testintersect(NTuple, Tuple{Any,Vararg}, Tuple{T, Vararg{T}} where {T}) + +@testintersect(Val{T} where T<:Tuple{Tuple{Any, Vararg{Any}}}, + Val{Tuple{Tuple{Vararg{Any, N}}}} where {N}, + Val{Tuple{Tuple{Any, Vararg{Any, N}}}} where {N}) + +let A = Pair{NTuple{N, Int}, Val{N}} where N, + C = Pair{Tuple{Int, Vararg{Int,N1}}, Val{N2}} where {N1,N2}, + B = Pair{<:Tuple{Int, Vararg{Int}}, <:Val} + @testintersect A B C + @testintersect A C C +end + +# issue #49484 +let S = Tuple{Integer, U} where {II<:Array, U<:Tuple{Vararg{II, 1}}} + T = Tuple{Int, U} where {II<:Array, U<:Tuple{Vararg{II, 1}}} + @testintersect(S, Tuple{Int, U} where {U<:Tuple{Vararg{Any}}}, T) + @testintersect(S, Tuple{Int, U} where {N, U<:Tuple{Vararg{Any,N}}}, T) + @testintersect(S, Tuple{Int, U} where {U<:Tuple{Any,Vararg{Any}}}, T) + @testintersect(S, Tuple{Int, U} where {N, U<:Tuple{Any,Vararg{Any,N}}}, T) + @testintersect(S, Tuple{Int, U} where {U<:Tuple{Any,Any,Vararg{Any}}}, Union{}) + @testintersect(S, Tuple{Int, U} where {N, U<:Tuple{Any,Any,Vararg{Any,N}}}, Union{}) +end + +# issue #43064 +let + env_tuple(@nospecialize(x), @nospecialize(y)) = (intersection_env(x, y)[2]...,) + all_var(x::UnionAll) = (x.var, all_var(x.body)...) + all_var(x::DataType) = () + TT0 = Tuple{Type{T},Union{Real,Missing,Nothing}} where {T} + TT1 = Union{Type{Int8},Type{Int16}} + @test env_tuple(Tuple{TT1,Missing}, TT0) === + env_tuple(Tuple{TT1,Nothing}, TT0) === + env_tuple(Tuple{TT1,Int}, TT0) === all_var(TT0) + + TT0 = Tuple{T1,T2,Union{Real,Missing,Nothing}} where {T1,T2} + TT1 = Tuple{T1,T2,Union{Real,Missing,Nothing}} where {T2,T1} + TT2 = Tuple{Union{Int,Int8},Union{Int,Int8},Int} + TT3 = Tuple{Int,Union{Int,Int8},Int} + @test env_tuple(TT2, TT0) === all_var(TT0) + @test env_tuple(TT2, TT1) === all_var(TT1) + @test env_tuple(TT3, TT0) === Base.setindex(all_var(TT0), Int, 1) + @test env_tuple(TT3, TT1) === Base.setindex(all_var(TT1), Int, 2) + + TT0 = Tuple{T1,T2,T1,Union{Real,Missing,Nothing}} where {T1,T2} + TT1 = Tuple{T1,T2,T1,Union{Real,Missing,Nothing}} where {T2,T1} + TT2 = Tuple{Int,Union{Int,Int8},Int,Int} + @test env_tuple(TT2, TT0) === Base.setindex(all_var(TT0), Int, 1) + @test env_tuple(TT2, TT1) === Base.setindex(all_var(TT1), Int, 2) +end + +#issue #46735 +T46735{B<:Real} = Pair{<:Union{B, Val{<:B}}, <:Union{AbstractMatrix{B}, AbstractMatrix{Vector{B}}}} +@testintersect(T46735{B} where {B}, T46735, !Union{}) +@testintersect(T46735{B} where {B<:Integer}, T46735, !Union{}) +S46735{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}},M,<:(Union{AbstractMatrix{B}, AbstractMatrix{<:Vector{<:B}}})} +@testintersect(S46735{B} where {B}, S46735, !Union{}) +@testintersect(S46735{B, M} where {B, M}, S46735, !Union{}) +A46735{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}},M,Union{AbstractMatrix{B}, AbstractMatrix{<:Vector{<:B}}}} +@testintersect(A46735{B} where {B}, A46735, !Union{}) +@testintersect(A46735{B, M} where {B, M}, A46735, !Union{}) + +#issue #46871 #38497 +struct A46871{T, N, M} <: AbstractArray{T, N} end +struct B46871{T, N} <: Ref{A46871{T, N, N}} end +for T in (B46871{Int, N} where {N}, B46871{Int}) # intentional duplication + @testintersect(T, Ref{<:AbstractArray{<:Real, 3}}, B46871{Int, 3}) +end +abstract type C38497{e,g<:Tuple,i} end +struct Q38497{o,e<:NTuple{o},g} <: C38497{e,g,Array{o}} end +@testintersect(Q38497{<:Any, Tuple{Int}}, C38497, Q38497{<:Any, Tuple{Int}, <:Tuple}) +# n.b. the only concrete instance of this type is Q38497{1, Tuple{Int}, <:Tuple} (since NTuple{o} also adds an ::Int constraint) +# but this abstract type is also part of the intersection abstractly + +abstract type X38497{T<:Number} end +abstract type Y38497{T>:Integer} <: X38497{T} end +struct Z38497{T>:Int} <: Y38497{T} end +@testintersect(Z38497, X38497, Z38497{T} where Int<:T<:Number) +@testintersect(Z38497, Y38497, Z38497{T} where T>:Integer) +@testintersect(X38497, Y38497, Y38497{T} where Integer<:T<:Number) + +#issue #33138 +@test Vector{Vector{Tuple{T,T}} where Int<:T<:Int} <: Vector{Vector{Tuple{S1,S1} where S<:S1<:S}} where S + +#issue #46970 +@test only(intersection_env(Union{S, Matrix{Int}} where S<:Matrix, Matrix)[2]) isa TypeVar +T46784{B<:Val, M<:AbstractMatrix} = Tuple{<:Union{B, <:Val{<:B}}, M, Union{AbstractMatrix{B}, AbstractMatrix{<:Vector{<:B}}}} +@testintersect(T46784{T,S} where {T,S}, T46784, !Union{}) +@test T46784 <: T46784{T,S} where {T,S} + +#issue 36185 +let S = Tuple{Type{T},Array{Union{T,Missing},N}} where {T,N}, + T = Tuple{Type{T},Array{Union{T,Nothing},N}} where {T,N} + @testintersect(S, T, !Union{}) + @test_broken typeintersect(S, T) != S + @test_broken typeintersect(T, S) != T +end + +#issue 46736 +let S = Tuple{Val{T}, T} where {S1,T<:Val{Union{Nothing,S1}}}, + T = Tuple{Val{Val{Union{Nothing, S2}}}, Any} where S2 + @testintersect(S, T, !Union{}) + # not ideal (`S1` should be unbounded) + @test_broken testintersect(S, T) == Tuple{Val{Val{Union{Nothing, S1}}}, Val{Union{Nothing, S1}}} where S1<:(Union{Nothing, S2} where S2) +end + +#issue #47874:case1 +let S1 = Tuple{Int, Any, Union{Val{C1}, C1}} where {R1<:Real, C1<:Union{Complex{R1}, R1}}, + S2 = Tuple{Int, Any, Union{Val{C1}, C1} where {R1<:Real, C1<:Union{Complex{R1}, R1}}}, + T1 = Tuple{Any, Int, Union{Val{C2}, C2}} where {R2<:Real, C2<:Union{Complex{R2}, R2}}, + T2 = Tuple{Any, Int, V} where {R2<:Real, C2<:Union{Complex{R2}, R2}, V<:Union{Val{C2}, C2}} + for S in (S1, S2), T in (T1, T2) + @testintersect(S, T, !Union{}) + end +end + +#issue #47874:case2 +let S = Tuple{Int, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R + T = Tuple{Any, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R<:Real + I = Tuple{Any, Vararg{Val{C} where C<:Union{Complex{R}, R}}} where R<:Real + @testintersect(S, T, !Union{}) + @test_broken typeintersect(S, T) == I + @test_broken typeintersect(T, S) == I +end + +#issue #47874:case3 +let S = Tuple{Int, Tuple{Vararg{Val{C1} where C1<:Union{Complex{R1}, R1}}} where R1<:(Union{Real, V1} where V1), Tuple{Vararg{Val{C2} where C2<:Union{Complex{R2}, Complex{R3}, R3}}} where {R2<:(Union{Real, V2} where V2), R3<:Union{Complex{R2}, Real, R2}}}, + T = Tuple{Any, Tuple{Vararg{Val{CC1} where CC1<:Union{Complex{R}, R}}}, Tuple{Vararg{Val{CC2} where CC2<:Union{Complex{R}, R}}}} where R<:Real + @testintersect(S, T, !Union{}) +end + +let S = Tuple{T2, V2} where {T2, N2, V2<:(Array{S2, N2} where {S2 <: T2})}, + T = Tuple{V1, T1} where {T1, N1, V1<:(Array{S1, N1} where {S1 <: T1})} + @testintersect(S, T, !Union{}) +end + +# A simple case which has a small local union. +# make sure the env is not widened too much when we intersect(Int8, Int8). +struct T48006{A1,A2,A3} end +@testintersect(Tuple{T48006{Float64, Int, S1}, Int} where {F1<:Real, S1<:Union{Int8, Val{F1}}}, + Tuple{T48006{F2, I, S2}, I} where {F2<:Real, I<:Int, S2<:Union{Int8, Val{F2}}}, + Tuple{T48006{Float64, Int, S1}, Int} where S1<:Union{Val{Float64}, Int8}) + +f48167(::Type{Val{L2}}, ::Type{Union{Val{L1}, Set{R}}}) where {L1, R, L2<:L1} = 1 +f48167(::Type{Val{L1}}, ::Type{Union{Val{L2}, Set{R}}}) where {L1, R, L2<:L1} = 2 +f48167(::Type{Val{L}}, ::Type{Union{Val{L}, Set{R}}}) where {L, R} = 3 +@test f48167(Val{Nothing}, Union{Val{Nothing}, Set{Int}}) == 3 + +# https://github.com/JuliaLang/julia/pull/31167#issuecomment-1358381818 +let S = Tuple{Type{T1}, T1, Val{T1}} where T1<:(Val{S1} where S1<:Val), + T = Tuple{Union{Type{T2}, Type{S2}}, Union{Val{T2}, Val{S2}}, Union{Val{T2}, S2}} where T2<:Val{A2} where A2 where S2<:Val + I1 = typeintersect(S, T) + I2 = typeintersect(T, S) + @test I1 !== Union{} && I2 !== Union{} + @test_broken I1 <: S + @test_broken I2 <: T + @test_broken I2 <: S + @test_broken I2 <: T +end + +#issue 44395 +@testintersect(Tuple{Type{T}, T} where {T <: Vector{Union{T, R}} where {R<:Real, T<:Real}}, + Tuple{Type{Vector{Union{T, R}}}, Matrix{Union{T, R}}} where {R<:Real, T<:Real}, + Union{}) + +#issue 26487 +@testintersect(Tuple{Type{Tuple{T,Val{T}}}, Val{T}} where T, + Tuple{Type{Tuple{Val{T},T}}, Val{T}} where T, + Union{}) + +@test only(intersection_env(Val{Union{Val{Val{T}} where {T},Int}}, Val{Union{T,Int}} where T)[2]) === Val{Val{T}} where {T} + +# issue 47654 +Vec47654{T} = Union{AbstractVector{T}, AbstractVector{Union{T,Nothing}}} +struct Wrapper47654{T, V<:Vec47654{T}} + v::V +end +abstract type P47654{A} end +@test Wrapper47654{P47654, Vector{Union{P47654,Nothing}}} <: Wrapper47654 + +@testset "known subtype/intersect issue" begin + #issue 45874 + let S = Pair{Val{P}, AbstractVector{<:Union{P,<:AbstractMatrix{P}}}} where P, + T = Pair{Val{R}, AbstractVector{<:Union{P,<:AbstractMatrix{P}}}} where {P,R} + @test S <: T + end + + #issue 41561 + @test_broken typeintersect(Tuple{Vector{VT}, Vector{VT}} where {N1, VT<:AbstractVector{N1}}, + Tuple{Vector{VN} where {N, VN<:AbstractVector{N}}, Vector{Vector{Float64}}}) !== Union{} + #issue 40865 + @test Tuple{Set{Ref{Int}}, Set{Ref{Int}}} <: Tuple{Set{KV}, Set{K}} where {K,KV<:Union{K,Ref{K}}} + @test Tuple{Set{Val{Int}}, Set{Val{Int}}} <: Tuple{Set{KV}, Set{K}} where {K,KV<:Union{K,Val{K}}} + + #issue 39099 + A = Tuple{Tuple{Int, Int, Vararg{Int, N}}, Tuple{Int, Vararg{Int, N}}, Tuple{Vararg{Int, N}}} where N + B = Tuple{NTuple{N, Int}, NTuple{N, Int}, NTuple{N, Int}} where N + @test_broken !(A <: B) + + #issue 35698 + @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 + + # issue 24333 + @test (Type{Union{Ref,Cvoid}} <: Type{Union{T,Cvoid}} where T) + + # issue 22123 + t1 = Ref{Ref{Ref{Union{Int64, T}}} where T} + t2 = Ref{Ref{Ref{Union{T, S}}} where T} where S + @test t1 <: t2 + + # issue 21153 + @test_broken (Tuple{T1,T1} where T1<:(Val{T2} where T2)) <: (Tuple{Val{S},Val{S}} where S) +end + +# issue #47658 +let T = Ref{NTuple{8, Ref{Union{Int, P}}}} where P, + S = Ref{NTuple{8, Ref{Union{Int, P}}}} where P + # note T and S are identical but we need 2 copies to avoid being fooled by pointer equality + @test T <: Union{Int, S} +end + +# try to fool a greedy algorithm that picks X=Int, Y=String here +@test Tuple{Ref{Union{Int,String}}, Ref{Union{Int,String}}} <: Tuple{Ref{Union{X,Y}}, Ref{X}} where {X,Y} +@test Tuple{Ref{Union{Int,String,Missing}}, Ref{Union{Int,String}}} <: Tuple{Ref{Union{X,Y}}, Ref{X}} where {X,Y} + +@test !(Tuple{Any, Any, Any} <: Tuple{Any, Vararg{T}} where T) + +# issue #39967 +@test (NTuple{27, T} where {S, T<:Union{Array, Array{S}}}) <: Tuple{Array, Array, Vararg{AbstractArray, 25}} + +abstract type MyAbstract47877{C}; end +struct MyType47877{A,B} <: MyAbstract47877{A} end +let A = Tuple{Type{T}, T} where T, + B = Tuple{Type{MyType47877{W, V} where V<:Union{Base.BitInteger, MyAbstract47877{W}}}, MyAbstract47877{<:Base.BitInteger}} where W + C = Tuple{Type{MyType47877{W, V} where V<:Union{MyAbstract47877{W}, Base.BitInteger}}, MyType47877{W, V} where V<:Union{MyAbstract47877{W}, Base.BitInteger}} where W<:Base.BitInteger + # ensure that merge_env for innervars does not blow up (the large Unions ensure this will take excessive memory if it does) + @testintersect(A, B, C) +end + +let + a = (isodd(i) ? Pair{Char, String} : Pair{String, String} for i in 1:2000) + @test Tuple{Type{Pair{Union{Char, String}, String}}, a...} <: Tuple{Type{Pair{K, V}}, Vararg{Pair{A, B} where B where A}} where V where K + a = (isodd(i) ? Matrix{Int} : Vector{Int} for i in 1:4000) + @test Tuple{Type{Pair{Union{Char, String}, String}}, a...,} <: Tuple{Type{Pair{K, V}}, Vararg{Array}} where V where K +end + +#issue 48582 +@test !<:(Tuple{Pair{<:T,<:T}, Val{S} where {S}} where {T<:Base.BitInteger}, + Tuple{Pair{<:T,<:T}, Val{Int}} where {T<:Base.BitInteger}) + +struct T48695{T, N, H<:AbstractArray} <: AbstractArray{Union{Missing, T}, N} end +struct S48695{T, N, H<:AbstractArray{T, N}} <: AbstractArray{T, N} end +let S = Tuple{Type{S48695{T, 2, T48695{B, 2, C}}} where {T<:(Union{Missing, A} where A), B, C}, T48695{T, 2} where T}, + T = Tuple{Type{S48695{T, N, H}}, H} where {T, N, H<:AbstractArray{T, N}} + V = typeintersect(S, T) + vars_in_unionall(s) = s isa UnionAll ? (s.var, vars_in_unionall(s.body)...) : () + @test V != Union{} + @test allunique(vars_in_unionall(V)) + @test typeintersect(V, T) != Union{} +end + +#issue 48961 +@test !<:(Type{Union{Missing, Int}}, Type{Union{Missing, Nothing, Int}}) + +#issue 49127 +struct F49127{m,n} <: Function end +let a = [TypeVar(:V, Union{}, Function) for i in 1:32] + b = a[1:end-1] + S = foldr((v, d) -> UnionAll(v, d), a; init = foldl((i, j) -> F49127{i, j}, a)) + T = foldr((v, d) -> UnionAll(v, d), b; init = foldl((i, j) -> F49127{i, j}, b)) + @test S <: T +end + +# requires assertions enabled (to test union-split in `obviously_disjoint`) +@test !<:(Tuple{Type{Int}, Int}, Tuple{Type{Union{Int, T}}, T} where T<:Union{Int8,Int16}) +@test <:(Tuple{Type{Int}, Int}, Tuple{Type{Union{Int, T}}, T} where T<:Union{Int8,Int}) + +#issue #49354 (requires assertions enabled) +@test !<:(Tuple{Type{Union{Int, Val{1}}}, Int}, Tuple{Type{Union{Int, T1}}, T1} where T1<:Val) +@test !<:(Tuple{Type{Union{Int, Val{1}}}, Int}, Tuple{Type{Union{Int, T1}}, T1} where T1<:Union{Val,Pair}) +@test <:(Tuple{Type{Union{Int, Val{1}}}, Int}, Tuple{Type{Union{Int, T1}}, T1} where T1<:Union{Integer,Val}) +@test <:(Tuple{Type{Union{Int, Int8}}, Int}, Tuple{Type{Union{Int, T1}}, T1} where T1<:Integer) +@test !<:(Tuple{Type{Union{Pair{Int, Any}, Pair{Int, Int}}}, Pair{Int, Any}}, + Tuple{Type{Union{Pair{Int, Any}, T1}}, T1} where T1<:(Pair{T,T} where {T})) + +let A = Tuple{Type{T}, T, Val{T}} where T, + B = Tuple{Type{S}, Val{S}, Val{S}} where S + @test_broken typeintersect(A, B) == Tuple{Type{T}, Val{T}, Val{T}} where T>:Val + @test typeintersect(A, B) <: Tuple{Type{T}, Val{T}, Val{T}} where T +end +let A = Tuple{Type{T}, T, Val{T}} where T<:Val, + B = Tuple{Type{S}, Val{S}, Val{S}} where S<:Val + @test_broken typeintersect(A, B) == Tuple{Type{Val}, Val{Val}, Val{Val}} + @test typeintersect(A, B) <: Tuple{Type{T}, Val{T}, Val{T}} where T<:Val +end +let A = Tuple{Type{T}, T, Val{T}} where T<:Val, + B = Tuple{Type{S}, Val{S}, Val{S}} where S<:Val{A} where A + @test typeintersect(A, B) == Union{} +end +let A = Tuple{Type{T}, T, Val{T}} where T<:Val{<:Val}, + B = Tuple{Type{S}, Val{S}, Val{S}} where S<:Val + @test_broken typeintersect(A, B) == Tuple{Type{Val{<:Val}}, Val{Val{<:Val}}, Val{Val{<:Val}}} + @test typeintersect(A, B) <: Tuple{Type{T}, Val{T}, Val{T}} where T<:(Val{<:Val}) +end +let T = Tuple{Union{Type{T}, Type{S}}, Union{Val{T}, Val{S}}, Union{Val{T}, S}} where T<:Val{A} where A where S<:Val, + S = Tuple{Type{T}, T, Val{T}} where T<:(Val{S} where S<:Val) + # optimal = Union{}? + @test typeintersect(T, S) == Tuple{Type{A}, Union{Val{A}, Val{S} where S<:Union{Val, A}, Val{x} where x<:Val, Val{x} where x<:Union{Val, A}}, Val{A}} where A<:(Val{S} where S<:Val) + @test typeintersect(S, T) == Tuple{Type{T}, Union{Val{T}, Val{S}}, Val{T}} where {T<:Val, S<:(Union{Val{A}, Val} where A)} +end diff --git a/test/syntax.jl b/test/syntax.jl index 69d3e8c7fe591..8bba5f9205613 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -276,9 +276,6 @@ end @test Meta.parse("'\"'") == Meta.parse("'\\\"'") == '"' == "\""[1] == '\42' # issue #24558 -@test_throws ParseError Meta.parse("'\\xff'") -@test_throws ParseError Meta.parse("'\\x80'") -@test_throws ParseError Meta.parse("'ab'") @test '\u2200' == "\u2200"[1] @test_throws ParseError Meta.parse("f(2x for x=1:10, y") @@ -317,19 +314,16 @@ let p = 15 @test 2p+1 == 31 # not a hex float literal end -function test_parseerror(str, msg) - try - Meta.parse(str) - @test false - catch e - @test isa(e,ParseError) && e.msg == msg - end +macro test_parseerror(str, msg) + ex = :(@test_throws ParseError($(esc(msg))) Meta.parse($(esc(str)))) + ex.args[2] = __source__ + return ex end -test_parseerror("0x", "invalid numeric constant \"0x\"") -test_parseerror("0b", "invalid numeric constant \"0b\"") -test_parseerror("0o", "invalid numeric constant \"0o\"") -test_parseerror("0x0.1", "hex float literal must contain \"p\" or \"P\"") -test_parseerror("0x1.0p", "invalid numeric constant \"0x1.0\"") +@test_parseerror("0x", "invalid numeric constant \"0x\"") +@test_parseerror("0b", "invalid numeric constant \"0b\"") +@test_parseerror("0o", "invalid numeric constant \"0o\"") +@test_parseerror("0x0.1", "hex float literal must contain \"p\" or \"P\"") +@test_parseerror("0x1.0p", "invalid numeric constant \"0x1.0\"") # issue #15798 @test Meta.lower(Main, Base.parse_input_line(""" @@ -345,8 +339,8 @@ test_parseerror("0x1.0p", "invalid numeric constant \"0x1.0\"") """)::Expr) == 23341 # issue #15763 -test_parseerror("if\nfalse\nend", "missing condition in \"if\" at none:1") -test_parseerror("if false\nelseif\nend", "missing condition in \"elseif\" at none:2") +@test_parseerror("if\nfalse\nend", "missing condition in \"if\" at none:1") +@test_parseerror("if false\nelseif\nend", "missing condition in \"elseif\" at none:2") # issue #15828 @test Meta.lower(Main, Meta.parse("x...")) == Expr(:error, "\"...\" expression outside call") @@ -552,7 +546,9 @@ for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :c end # meta nodes for optional positional arguments -@test Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-1].args[3].inlineable +let src = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-1].args[3] + @test Core.Compiler.is_declared_inline(src) +end # issue #16096 module M16096 @@ -655,7 +651,7 @@ function get_expr_list(ex::Core.CodeInfo) return ex.code::Array{Any,1} end function get_expr_list(ex::Expr) - if ex.head == :thunk + if ex.head === :thunk return get_expr_list(ex.args[1]) else return ex.args @@ -761,7 +757,7 @@ end if test + test == test println(test) end -```.head == :if +```.head === :if end @@ -843,6 +839,8 @@ let ε=1, μ=2, x=3, î=4, ⋅=5, (-)=6 @test Meta.parse("100.0f\u22122") === Meta.parse("100.0f-2") @test Meta.parse("0x100p\u22128") === Meta.parse("0x100P\u22128") === Meta.parse("0x100p-8") @test (−) == (-) == 6 + # hbar ℏ to ħ - (#48870) + @test :ℏ === :ħ end # issue #8925 @@ -852,6 +850,14 @@ end @test c8925 == 3 && isconst(@__MODULE__, :c8925) @test d8925 == 4 && isconst(@__MODULE__, :d8925) +# issue #47168 +let t47168 = (;a47168 = 1, b47168 = 2); + global const (;a47168, b47168) = t47168 + @test a47168 == 1 && isconst(@__MODULE__, :a47168) + @test b47168 == 2 && isconst(@__MODULE__, :b47168) +end +@test (let x = (;x=1); let (;x) = x; x; end, x; end) == (1, (x = 1,)) + # issue #18754: parse ccall as a regular function @test Meta.parse("ccall([1], 2)[3]") == Expr(:ref, Expr(:call, :ccall, Expr(:vect, 1), 2), 3) @test Meta.parse("ccall(a).member") == Expr(:., Expr(:call, :ccall, :a), QuoteNode(:member)) @@ -859,7 +865,7 @@ end # Check that the body of a `where`-qualified short form function definition gets # a :block for its body short_where_call = :(f(x::T) where T = T) -@test short_where_call.args[2].head == :block +@test short_where_call.args[2].head === :block # `where` with multi-line anonymous functions let f = function (x::T) where T @@ -874,6 +880,12 @@ let f = function (x::T, y::S) where T<:S where S @test f(0,1) === (Int,Int) end +# issue #45506 +@test :( function (a) where {B, C} end).args[1] == Expr(:where, Expr(:tuple, :a), :B, :C) +@test (function(::Type{Tuple{A45506, B45506}}) where {A45506 <: Any, B45506 <: Any} + B45506 +end)(Tuple{Int8, Int16}) == Int16 + # issue #20541 @test Meta.parse("[a .!b]") == Expr(:hcat, :a, Expr(:call, :.!, :b)) @@ -1212,6 +1224,17 @@ let a = [], b = [4,3,2,1] @test a == [1,2] end +# issue #44239 +struct KWGetindex end +Base.getindex(::KWGetindex, args...; kws...) = (args, NamedTuple(kws)) +let A = KWGetindex(), a = [], b = [4,3,2,1] + f() = (push!(a, 1); 2) + g() = (push!(a, 2); ()) + @test A[f(), g()..., k = f()] === ((2,), (k = 2,)) + @test a == [1, 2, 1] + @test A[var"end"=1] === ((), (var"end" = 1,)) +end + @testset "raw_str macro" begin @test raw"$" == "\$" @test raw"\n" == "\\n" @@ -1441,6 +1464,14 @@ invalid assignment location "function (s, o...) end\"""" end +let ex = Meta.lower(@__MODULE__, :(function g end = 1)) + @test isa(ex, Expr) && ex.head === :error + @test ex.args[1] == """ +invalid assignment location "function g +end\"""" +end + + # issue #15229 @test Meta.lower(@__MODULE__, :(function f(x); local x; 0; end)) == Expr(:error, "local variable name \"x\" conflicts with an argument") @@ -1529,8 +1560,8 @@ end # issue #27129 f27129(x = 1) = (@inline; x) -for meth in methods(f27129) - @test ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), meth, C_NULL, meth.source).inlineable +for method in methods(f27129) + @test Core.Compiler.is_declared_inline(method) end # issue #27710 @@ -1898,7 +1929,12 @@ f31404(a, b; kws...) = (a, b, values(kws)) # issue #28992 macro id28992(x) x end @test @id28992(1 .+ 2) == 3 -@test Meta.isexpr(Meta.lower(@__MODULE__, :(@id28992((.+)(a,b) = 0))), :error) +@test Meta.@lower(.+(a,b) = 0) == Expr(:error, "invalid function name \".+\"") +@test Meta.@lower((.+)(a,b) = 0) == Expr(:error, "invalid function name \"(.+)\"") +let m = @__MODULE__ + @test Meta.lower(m, :($m.@id28992(.+(a,b) = 0))) == Expr(:error, "invalid function name \"$(nameof(m)).:.+\"") + @test Meta.lower(m, :($m.@id28992((.+)(a,b) = 0))) == Expr(:error, "invalid function name \"(.$(nameof(m)).+)\"") +end @test @id28992([1] .< [2] .< [3]) == [true] @test @id28992(2 ^ -2) == 0.25 @test @id28992(2 .^ -2) == 0.25 @@ -1973,7 +2009,7 @@ end @test Meta.parse("import Base.Foo.:(==).bar") == :(import Base.Foo.==.bar) # issue #33135 -function f33135(x::T) where {C1, T} +@test_warn "declares type variable C1 but does not use it" @eval function f33135(x::T) where {C1, T} let C1 = 1, C2 = 2 C1 end @@ -2035,8 +2071,8 @@ end == 1 # issue #29982 @test Meta.parse("'a'") == 'a' @test Meta.parse("'\U0061'") == 'a' -test_parseerror("''", "invalid empty character literal") -test_parseerror("'abc'", "character literal contains multiple characters") +@test_parseerror("''", "invalid empty character literal") +@test_parseerror("'abc'", "character literal contains multiple characters") # optional soft scope: #28789, #33864 @@ -2192,9 +2228,17 @@ end @test Meta.parse("a ⫫ b") == Expr(:call, :⫫, :a, :b) end +# issue 45962 +@testset "binary ⭄, ⥺, ⭃, and ⥷" begin + @test Meta.parse("a ⭄ b") == Expr(:call, :⭄, :a, :b) + @test Meta.parse("a ⥺ b") == Expr(:call, :⥺, :a, :b) + @test Meta.parse("a ⭃ b") == Expr(:call, :⭃, :a, :b) + @test Meta.parse("a ⥷ b") == Expr(:call, :⥷, :a, :b) +end + # only allow certain characters after interpolated vars (#25231) @test Meta.parse("\"\$x෴ \"",raise=false) == Expr(:error, "interpolated variable \$x ends with invalid character \"෴\"; use \"\$(x)\" instead.") -@test Base.incomplete_tag(Meta.parse("\"\$foo", raise=false)) == :string +@test Base.incomplete_tag(Meta.parse("\"\$foo", raise=false)) === :string @testset "issue #30341" begin @test Meta.parse("x .~ y") == Expr(:call, :.~, :x, :y) @@ -2247,6 +2291,12 @@ end @test Meta.lower(@__MODULE__, Expr(:block, LineNumberNode(101, :some_file), :(f(x,x)=1))) == Expr(:error, "function argument name not unique: \"x\" around some_file:101") +@test Meta.lower(@__MODULE__, Expr(:block, LineNumberNode(102, :some_file), :(function f(x) where T where T; x::T; end))) == + Expr(:error, "function static parameter name not unique: \"T\" around some_file:102") + +@test Meta.lower(@__MODULE__, Expr(:block, LineNumberNode(103, :some_file), :(function f(t) where t; x; end))) == + Expr(:error, "function argument and static parameter name not distinct: \"t\" around some_file:103") + # Ensure file names don't leak between `eval`s eval(LineNumberNode(11, :incorrect_file)) let exc = try eval(:(f(x,x)=1)) catch e ; e ; end @@ -2286,15 +2336,23 @@ h35201(x; k=1) = (x, k) f35201(c) = h35201((;c...), k=true) @test f35201(Dict(:a=>1,:b=>3)) === ((a=1,b=3), true) +# issue #44343 +f44343(;kw...) = NamedTuple(kw) +@test f44343(u = (; :a => 1)) === (u = (; :a => 1),) -@testset "issue #34544/35367" begin - # Test these evals shouldnt segfault +@testset "issue #34544/35367/35429" begin + # Test these evals shouldn't segfault eval(Expr(:call, :eval, Expr(:quote, Expr(:module, true, :bar1, Expr(:block))))) eval(Expr(:module, true, :bar2, Expr(:block))) eval(Expr(:quote, Expr(:module, true, :bar3, Expr(:quote)))) @test_throws ErrorException eval(Expr(:call, :eval, Expr(:quote, Expr(:module, true, :bar4, Expr(:quote))))) @test_throws ErrorException eval(Expr(:module, true, :bar5, Expr(:foo))) @test_throws ErrorException eval(Expr(:module, true, :bar6, Expr(:quote))) + + #35429 + @test_throws ErrorException eval(Expr(:thunk, x->x+9)) + @test_throws ErrorException eval(Expr(:thunk, Meta.parse("x=17"))) + @test_throws ErrorException eval(Expr(:thunk, Meta.parse("17"))) end # issue #35391 @@ -2493,7 +2551,8 @@ using Test module Mod const x = 1 -global maybe_undef +global maybe_undef, always_undef +export always_undef def() = (global maybe_undef = 0) func(x) = 2x + 1 @@ -2503,7 +2562,11 @@ end end module Mod2 +import ..Mod.x as x_from_mod +import ..Mod.x as x_from_mod2 const y = 2 + +export x_from_mod2 end import .Mod: x as x2 @@ -2527,10 +2590,18 @@ import .Mod.maybe_undef as mu Mod.def() @test mu === 0 -using .Mod: func as f -@test f(10) == 21 -@test !@isdefined(func) -@test_throws ErrorException("error in method definition: function Mod.func must be explicitly imported to be extended") eval(:(f(x::Int) = x)) +module Mod3 +using ..Mod: func as f +using ..Mod +end +@test Mod3.f(10) == 21 +@test !isdefined(Mod3, :func) +@test_throws ErrorException("invalid method definition in Mod3: function Mod3.f must be explicitly imported to be extended") Core.eval(Mod3, :(f(x::Int) = x)) +@test !isdefined(Mod3, :always_undef) # resolve this binding now in Mod3 +@test_throws ErrorException("invalid method definition in Mod3: exported function Mod.always_undef does not exist") Core.eval(Mod3, :(always_undef(x::Int) = x)) +@test_throws ErrorException("cannot assign a value to imported variable Mod.always_undef from module Mod3") Core.eval(Mod3, :(const always_undef = 3)) +@test_throws ErrorException("cannot assign a value to imported variable Mod3.f") Core.eval(Mod3, :(const f = 3)) +@test_throws ErrorException("cannot declare Mod.maybe_undef constant; it already has a value") Core.eval(Mod, :(const maybe_undef = 3)) z = 42 import .z as also_z @@ -2543,6 +2614,17 @@ import .Mod.@mac as @m @test_throws ErrorException eval(:(import .Mod.func as @notmacro)) @test_throws ErrorException eval(:(using .Mod: @mac as notmacro)) @test_throws ErrorException eval(:(using .Mod: func as @notmacro)) + +import .Mod2.x_from_mod + +@test @isdefined(x_from_mod) +@test x_from_mod == Mod.x + +using .Mod2 + +@test_nowarn @eval x_from_mod2 +@test @isdefined(x_from_mod2) +@test x_from_mod2 == x_from_mod == Mod.x end import .TestImportAs.Mod2 as M2 @@ -2626,8 +2708,6 @@ end @test x == 1 && y == 2 @test z == (3:5,) - @test Meta.isexpr(Meta.@lower(begin a, b..., c = 1:3 end), :error) - @test Meta.isexpr(Meta.@lower(begin a, b..., c = 1, 2, 3 end), :error) @test Meta.isexpr(Meta.@lower(begin a, b..., c... = 1, 2, 3 end), :error) @test_throws BoundsError begin x, y, z... = 1:1 end @@ -2809,7 +2889,7 @@ end @test eval(:(x = $(QuoteNode(Core.SSAValue(1))))) == Core.SSAValue(1) @test eval(:(x = $(QuoteNode(Core.SlotNumber(1))))) == Core.SlotNumber(1) @test_throws ErrorException("syntax: SSAValue objects should not occur in an AST") eval(:(x = $(Core.SSAValue(1)))) -@test_throws ErrorException("syntax: Slot objects should not occur in an AST") eval(:(x = $(Core.SlotNumber(1)))) +@test_throws ErrorException("syntax: SlotNumber objects should not occur in an AST") eval(:(x = $(Core.SlotNumber(1)))) # juxtaposition of radical symbols (#40094) @test Meta.parse("2√3") == Expr(:call, :*, 2, Expr(:call, :√, 3)) @@ -2961,15 +3041,15 @@ end end @testset "slurping into function def" begin - x, f()... = [1, 2, 3] + x, f1()... = [1, 2, 3] @test x == 1 - @test f() == [2, 3] + @test f1() == [2, 3] # test that call to `Base.rest` is outside the definition of `f` - @test f() === f() + @test f1() === f1() - x, f()... = 1, 2, 3 + x, f2()... = 1, 2, 3 @test x == 1 - @test f() == (2, 3) + @test f2() == (2, 3) end @testset "long function bodies" begin @@ -2983,9 +3063,6 @@ end end # issue 25678 -@generated f25678(x::T) where {T} = code_lowered(sin, Tuple{x})[] -@test f25678(pi/6) === sin(pi/6) - @generated g25678(x) = return :x @test g25678(7) === 7 @@ -3005,10 +3082,10 @@ end end # issue #19012 -@test Meta.parse("\U2200", raise=false) == Symbol("∀") -@test Meta.parse("\U2203", raise=false) == Symbol("∃") -@test Meta.parse("a\U2203", raise=false) == Symbol("a∃") -@test Meta.parse("\U2204", raise=false) == Symbol("∄") +@test Meta.parse("\U2200", raise=false) === Symbol("∀") +@test Meta.parse("\U2203", raise=false) === Symbol("∃") +@test Meta.parse("a\U2203", raise=false) === Symbol("a∃") +@test Meta.parse("\U2204", raise=false) === Symbol("∄") # issue 42220 macro m42220() @@ -3248,3 +3325,150 @@ end @test m.Foo.bar === 1 @test Core.get_binding_type(m.Foo, :bar) == Any end + +# issue 44723 +demo44723()::Any = Base.Experimental.@opaque () -> true ? 1 : 2 +@test demo44723()() == 1 + +@testset "slurping in non-final position" begin + res = begin x, y..., z = 1:7 end + @test res == 1:7 + @test x == 1 + @test y == Vector(2:6) + @test z == 7 + + res = begin x, y..., z = [1, 2] end + @test res == [1, 2] + @test x == 1 + @test y == Int[] + @test z == 2 + + x, y, z... = 1:7 + res = begin y, z..., x = z..., x, y end + @test res == ((3:7)..., 1, 2) + @test y == 3 + @test z == ((4:7)..., 1) + @test x == 2 + + res = begin x, _..., y = 1, 2 end + @test res == (1, 2) + @test x == 1 + @test y == 2 + + res = begin x, y..., z = 1, 2:4, 5 end + @test res == (1, 2:4, 5) + @test x == 1 + @test y == (2:4,) + @test z == 5 + + @test_throws ArgumentError begin x, y..., z = 1:1 end + @test_throws BoundsError begin x, y, _..., z = 1, 2 end + + last((a..., b)) = b + front((a..., b)) = a + @test last(1:3) == 3 + @test front(1:3) == [1, 2] + + res = begin x, y..., z = "abcde" end + @test res == "abcde" + @test x == 'a' + @test y == "bcd" + @test z == 'e' + + res = begin x, y..., z = (a=1, b=2, c=3, d=4) end + @test res == (a=1, b=2, c=3, d=4) + @test x == 1 + @test y == (b=2, c=3) + @test z == 4 + + v = rand(Bool, 7) + res = begin x, y..., z = v end + @test res === v + @test x == v[1] + @test y == v[2:6] + @test z == v[end] + + res = begin x, y..., z = Core.svec(1, 2, 3, 4) end + @test res == Core.svec(1, 2, 3, 4) + @test x == 1 + @test y == Core.svec(2, 3) + @test z == 4 +end + +# rewriting inner constructors with return type decls +struct InnerCtorRT{T} + InnerCtorRT()::Int = new{Int}() + InnerCtorRT{T}() where {T} = ()->new() +end +@test_throws MethodError InnerCtorRT() +@test InnerCtorRT{Int}()() isa InnerCtorRT{Int} + +# issue #45162 +f45162(f) = f(x=1) +@test first(methods(f45162)).called != 0 + +# issue #45024 +@test_throws ParseError("expected assignment after \"const\"") Meta.parse("const x") +@test_throws ParseError("expected assignment after \"const\"") Meta.parse("const x::Int") +# these cases have always been caught during lowering, since (const (global x)) is not +# ambiguous with the lowered form (const x), but that could probably be changed. +@test Meta.lower(@__MODULE__, :(global const x)) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, :(global const x::Int)) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, :(const global x)) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, :(const global x::Int)) == Expr(:error, "expected assignment after \"const\"") + +@testset "issue 25072" begin + @test '\xc0\x80' == reinterpret(Char, 0xc0800000) + @test '\x80' == reinterpret(Char, 0x80000000) + @test '\xff' == reinterpret(Char, 0xff000000) + @test_parseerror "'\\xff\\xff\\xff\\xff'" "character literal contains multiple characters" # == reinterpret(Char, 0xffffffff) + @test '\uffff' == Char(0xffff) + @test '\U00002014' == Char(0x2014) + @test '\100' == reinterpret(Char, UInt32(0o100) << 24) + @test_parseerror "'\\100\\42'" "character literal contains multiple characters" # == reinterpret(Char, (UInt32(0o100) << 24) | (UInt32(0o42) << 16)) + @test_parseerror "''" "invalid empty character literal" + @test_parseerror "'\\xff\\xff\\xff\\xff\\xff'" "character literal contains multiple characters" + @test_parseerror "'abcd'" "character literal contains multiple characters" + @test_parseerror "'\\uff\\xff'" "character literal contains multiple characters" + @test_parseerror "'\\xff\\uff'" "character literal contains multiple characters" + @test_parseerror "'\\xffa'" "character literal contains multiple characters" + @test_parseerror "'\\uffffa'" "character literal contains multiple characters" + @test_parseerror "'\\U00002014a'" "character literal contains multiple characters" + @test_parseerror "'\\1000'" "character literal contains multiple characters" + @test Meta.isexpr(Meta.parse("'a"), :incomplete) + @test ''' == "'"[1] +end + +# issue #46251 +@test begin; global value = 1; (value, value += 1) end == (1, 2) +@test begin; global value = 1; "($(value), $(value += 1))" end == "(1, 2)" + +# issue #47410 +# note `eval` is needed since this needs to be at the top level +@test eval(:(if false + elseif false || (()->true)() + 42 + end)) == 42 + +macro _macroexpand(x, m=__module__) + :($__source__; macroexpand($m, Expr(:var"hygienic-scope", $(esc(Expr(:quote, x))), $m))) +end + +@testset "unescaping in :global expressions" begin + m = @__MODULE__ + @test @_macroexpand(global x::T) == :(global x::$(GlobalRef(m, :T))) + @test @_macroexpand(global (x, $(esc(:y)))) == :(global (x, y)) + @test @_macroexpand(global (x::S, $(esc(:y))::$(esc(:T)))) == + :(global (x::$(GlobalRef(m, :S)), y::T)) + @test @_macroexpand(global (; x, $(esc(:y)))) == :(global (; x, y)) + @test @_macroexpand(global (; x::S, $(esc(:y))::$(esc(:T)))) == + :(global (; x::$(GlobalRef(m, :S)), y::T)) + + @test @_macroexpand(global x::T = a) == :(global x::$(GlobalRef(m, :T)) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (x, $(esc(:y))) = a) == :(global (x, y) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (x::S, $(esc(:y))::$(esc(:T))) = a) == + :(global (x::$(GlobalRef(m, :S)), y::T) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (; x, $(esc(:y))) = a) == :(global (; x, y) = $(GlobalRef(m, :a))) + @test @_macroexpand(global (; x::S, $(esc(:y))::$(esc(:T))) = a) == + :(global (; x::$(GlobalRef(m, :S)), y::T) = $(GlobalRef(m, :a))) +end diff --git a/test/sysinfo.jl b/test/sysinfo.jl index e423f6071c9e0..3a16dc73b4f6a 100644 --- a/test/sysinfo.jl +++ b/test/sysinfo.jl @@ -9,3 +9,32 @@ Base.Sys.loadavg() @test Base.libllvm_path() isa Symbol @test contains(String(Base.libllvm_path()), "LLVM") + +if Sys.isunix() + mktempdir() do tempdir + firstdir = joinpath(tempdir, "first") + seconddir = joinpath(tempdir, "second") + + mkpath(firstdir) + mkpath(seconddir) + + touch(joinpath(firstdir, "foo")) + touch(joinpath(seconddir, "foo")) + + chmod(joinpath(firstdir, "foo"), 0o777) + chmod(joinpath(seconddir, "foo"), 0o777) + + # zero permissions on first directory + chmod(firstdir, 0o000) + + original_path = ENV["PATH"] + ENV["PATH"] = string(firstdir, ":", seconddir, ":", original_path) + try + @test abspath(Base.Sys.which("foo")) == abspath(joinpath(seconddir, "foo")) + finally + # clean up + chmod(firstdir, 0o777) + ENV["PATH"] = original_path + end + end +end diff --git a/test/testdefs.jl b/test/testdefs.jl index 1d36d8893e199..4aac988cda7fb 100644 --- a/test/testdefs.jl +++ b/test/testdefs.jl @@ -21,7 +21,48 @@ function runtests(name, path, isolate=true; seed=nothing) res_and_time_data = @timed @testset "$name" begin # Random.seed!(nothing) will fail seed != nothing && Random.seed!(seed) + + original_depot_path = copy(Base.DEPOT_PATH) + original_load_path = copy(Base.LOAD_PATH) + original_env = copy(ENV) + Base.include(m, "$path.jl") + + if Base.DEPOT_PATH != original_depot_path + msg = "The `$(name)` test set mutated Base.DEPOT_PATH and did not restore the original values" + @error( + msg, + original_depot_path, + Base.DEPOT_PATH, + testset_name = name, + testset_path = path, + ) + error(msg) + end + if Base.LOAD_PATH != original_load_path + msg = "The `$(name)` test set mutated Base.LOAD_PATH and did not restore the original values" + @error( + msg, + original_load_path, + Base.LOAD_PATH, + testset_name = name, + testset_path = path, + ) + error(msg) + end + if copy(ENV) != original_env + msg = "The `$(name)` test set mutated ENV and did not restore the original values" + @error( + msg, + testset_name = name, + testset_path = path, + ) + throw_error_str = get(ENV, "JULIA_TEST_CHECK_MUTATED_ENV", "true") + throw_error_b = parse(Bool, throw_error_str) + if throw_error_b + error(msg) + end + end end rss = Sys.maxrss() #res_and_time_data[1] is the testset diff --git a/test/testhelpers/DualNumbers.jl b/test/testhelpers/DualNumbers.jl new file mode 100644 index 0000000000000..9f62e3bf0d429 --- /dev/null +++ b/test/testhelpers/DualNumbers.jl @@ -0,0 +1,46 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module DualNumbers + +export Dual + +# Dual numbers type with minimal interface +# example of a (real) number type that subtypes Number, but not Real. +# Can be used to test generic linear algebra functions. + +struct Dual{T<:Real} <: Number + val::T + eps::T +end +Base.:+(x::Dual, y::Dual) = Dual(x.val + y.val, x.eps + y.eps) +Base.:-(x::Dual, y::Dual) = Dual(x.val - y.val, x.eps - y.eps) +Base.:*(x::Dual, y::Dual) = Dual(x.val * y.val, x.eps * y.val + y.eps * x.val) +Base.:*(x::Number, y::Dual) = Dual(x*y.val, x*y.eps) +Base.:*(x::Dual, y::Number) = Dual(x.val*y, x.eps*y) +Base.:/(x::Dual, y::Dual) = Dual(x.val / y.val, (x.eps*y.val - x.val*y.eps)/(y.val*y.val)) + +Base.:(==)(x::Dual, y::Dual) = x.val == y.val && x.eps == y.eps + +Base.promote_rule(::Type{Dual{T}}, ::Type{T}) where {T} = Dual{T} +Base.promote_rule(::Type{Dual{T}}, ::Type{S}) where {T,S<:Real} = Dual{promote_type(T, S)} +Base.promote_rule(::Type{Dual{T}}, ::Type{Dual{S}}) where {T,S} = Dual{promote_type(T, S)} + +Base.convert(::Type{Dual{T}}, x::Dual{T}) where {T} = x +Base.convert(::Type{Dual{T}}, x::Dual) where {T} = Dual(convert(T, x.val), convert(T, x.eps)) +Base.convert(::Type{Dual{T}}, x::Real) where {T} = Dual(convert(T, x), zero(T)) + +Base.float(x::Dual) = Dual(float(x.val), float(x.eps)) +# the following two methods are needed for normalize (to check for potential overflow) +Base.typemax(x::Dual) = Dual(typemax(x.val), zero(x.eps)) +Base.prevfloat(x::Dual{<:AbstractFloat}) = prevfloat(x.val) + +Base.abs2(x::Dual) = x*x +Base.abs(x::Dual) = sqrt(abs2(x)) +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} + +end # module diff --git a/test/testhelpers/FakePTYs.jl b/test/testhelpers/FakePTYs.jl index 03610665142e2..c592699440ee0 100644 --- a/test/testhelpers/FakePTYs.jl +++ b/test/testhelpers/FakePTYs.jl @@ -39,10 +39,10 @@ function open_fake_pty() rc = ccall(:unlockpt, Cint, (Cint,), fdm) rc != 0 && error("unlockpt") - fds = ccall(:open, Cint, (Ptr{UInt8}, Cint), + fds = ccall(:open, Cint, (Ptr{UInt8}, Cint, UInt32...), ccall(:ptsname, Ptr{UInt8}, (Cint,), fdm), O_RDWR | O_NOCTTY) + pts = RawFD(fds) - pts = RawFD(fds) # pts = fdio(fds, true) # pts = Base.Filesystem.File(RawFD(fds)) # pts = Base.TTY(RawFD(fds); readable = false) diff --git a/test/testhelpers/FillArrays.jl b/test/testhelpers/FillArrays.jl new file mode 100644 index 0000000000000..1f36a77bf8c12 --- /dev/null +++ b/test/testhelpers/FillArrays.jl @@ -0,0 +1,33 @@ +module FillArrays + +struct Fill{T, N, S<:NTuple{N,Integer}} <: AbstractArray{T,N} + value::T + size::S +end + +Fill(v, size::Vararg{Integer}) = Fill(v, size) + +Base.size(F::Fill) = F.size + +@inline getindex_value(F::Fill) = F.value + +@inline function Base.getindex(F::Fill{<:Any,N}, i::Vararg{Int,N}) where {N} + @boundscheck checkbounds(F, i...) + getindex_value(F) +end + +@inline function Base.setindex!(F::Fill, v, k::Integer) + @boundscheck checkbounds(F, k) + v == getindex_value(F) || throw(ArgumentError("Cannot setindex! to $v for a Fill with value $(getindex_value(F)).")) + F +end + +@inline function Base.fill!(F::Fill, v) + v == getindex_value(F) || throw(ArgumentError("Cannot fill! with $v a Fill with value $(getindex_value(F)).")) + F +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) + +end diff --git a/test/testhelpers/Furlongs.jl b/test/testhelpers/Furlongs.jl index 8ac22c6244cd3..17970f0b0572e 100644 --- a/test/testhelpers/Furlongs.jl +++ b/test/testhelpers/Furlongs.jl @@ -21,25 +21,26 @@ Furlong{p}(v::Number) where {p} = Furlong{p,typeof(v)}(v) Furlong{p}(x::Furlong{q}) where {p,q} = (typeassert(x, Furlong{p}); Furlong{p,typeof(x.val)}(x.val)) Furlong{p,T}(x::Furlong{q}) where {T,p,q} = (typeassert(x, Furlong{p}); Furlong{p,T}(T(x.val))) -Base.promote_type(::Type{Furlong{p,T}}, ::Type{Furlong{p,S}}) where {p,T,S} = +Base.promote_rule(::Type{Furlong{p,T}}, ::Type{Furlong{p,S}}) where {p,T,S} = Furlong{p,promote_type(T,S)} - +Base.promote_rule(::Type{Furlong{0,T}}, ::Type{S}) where {T,S<:Union{Real,Complex}} = + Furlong{0,promote_type(T,S)} # only Furlong{0} forms a ring and isa Number -Base.convert(::Type{T}, y::Number) where {T<:Furlong{0}} = T(y) +Base.convert(::Type{T}, y::Number) where {T<:Furlong{0}} = T(y)::T Base.convert(::Type{Furlong}, y::Number) = Furlong{0}(y) Base.convert(::Type{Furlong{<:Any,T}}, y::Number) where {T<:Number} = Furlong{0,T}(y) Base.convert(::Type{T}, y::Number) where {T<:Furlong} = typeassert(y, T) # throws, since cannot convert a Furlong{0} to a Furlong{p} # other Furlong{p} form a group -Base.convert(::Type{T}, y::Furlong) where {T<:Furlong{0}} = T(y) +Base.convert(::Type{T}, y::Furlong) where {T<:Furlong{0}} = T(y)::T Base.convert(::Type{Furlong}, y::Furlong) = y Base.convert(::Type{Furlong{<:Any,T}}, y::Furlong{p}) where {p,T<:Number} = Furlong{p,T}(y) -Base.convert(::Type{T}, y::Furlong) where {T<:Furlong} = T(y) +Base.convert(::Type{T}, y::Furlong) where {T<:Furlong} = T(y)::T -Base.one(x::Furlong{p,T}) where {p,T} = one(T) +Base.one(::Furlong{p,T}) where {p,T} = one(T) Base.one(::Type{Furlong{p,T}}) where {p,T} = one(T) -Base.oneunit(x::Furlong{p,T}) where {p,T} = Furlong{p,T}(one(T)) -Base.oneunit(x::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(one(T)) -Base.zero(x::Furlong{p,T}) where {p,T} = Furlong{p,T}(zero(T)) +Base.oneunit(::Furlong{p,T}) where {p,T} = Furlong{p,T}(one(T)) +Base.oneunit(::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(one(T)) +Base.zero(::Furlong{p,T}) where {p,T} = Furlong{p,T}(zero(T)) Base.zero(::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(zero(T)) Base.iszero(x::Furlong) = iszero(x.val) Base.float(x::Furlong{p}) where {p} = Furlong{p}(float(x.val)) diff --git a/test/testhelpers/InfiniteArrays.jl b/test/testhelpers/InfiniteArrays.jl index d69130f4d726a..14b2e56daf1c6 100644 --- a/test/testhelpers/InfiniteArrays.jl +++ b/test/testhelpers/InfiniteArrays.jl @@ -21,11 +21,14 @@ Base.:(==)(::Infinity, ::Int) = false Base.:(==)(::Int, ::Infinity) = false Base.:(<)(::Int, ::Infinity) = true Base.:(≤)(::Int, ::Infinity) = true +Base.:(<)(::Infinity, ::Int) = false Base.:(≤)(::Infinity, ::Int) = false Base.:(≤)(::Infinity, ::Infinity) = true Base.:(-)(::Infinity, ::Int) = Infinity() Base.:(+)(::Infinity, ::Int) = Infinity() Base.:(:)(::Infinity, ::Infinity) = 1:0 +Base.max(::Infinity, ::Int) = Infinity() +Base.max(::Int, ::Infinity) = Infinity() """ OneToInf(n) diff --git a/test/testhelpers/OffsetArrays.jl b/test/testhelpers/OffsetArrays.jl index 27c666c9dacbd..705bd07b2878c 100644 --- a/test/testhelpers/OffsetArrays.jl +++ b/test/testhelpers/OffsetArrays.jl @@ -5,7 +5,7 @@ # This test file is designed to exercise support for generic indexing, # even though offset arrays aren't implemented in Base. -# OffsetArrays v1.3.0 +# OffsetArrays v1.11.2 # No compat patch and docstrings module OffsetArrays @@ -14,38 +14,77 @@ using Base: IdentityUnitRange export OffsetArray, OffsetMatrix, OffsetVector +const IIUR = IdentityUnitRange{<:AbstractUnitRange{<:Integer}} + +######################################################################################################## +# axes.jl +######################################################################################################## + struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T} parent::I offset::T - IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} = new{T,I}(r, offset) + function IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} + _bool_check(T, r, offset) + new{T,I}(r, offset) + end + + #= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer). + The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T), + so it ends up calling itself if I <: IdOffsetRange. + =# + function IdOffsetRange{T,IdOffsetRange{T,I}}(r::IdOffsetRange{T,I}, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} + _bool_check(T, r, offset) + new{T,IdOffsetRange{T,I}}(r, offset) + end end +function _bool_check(::Type{Bool}, r, offset) + # disallow the construction of IdOffsetRange{Bool, UnitRange{Bool}}(true:true, true) + if offset && (first(r) || last(r)) + throw(ArgumentError("values = $r and offset = $offset can not produce a boolean range")) + end + return nothing +end +_bool_check(::Type, r, offset) = nothing + # Construction/coercion from arbitrary AbstractUnitRanges function IdOffsetRange{T,I}(r::AbstractUnitRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}} rc, o = offset_coerce(I, r) - return IdOffsetRange{T,I}(rc, convert(T, o+offset)) + return IdOffsetRange{T,I}(rc, convert(T, o+offset)::T) end function IdOffsetRange{T}(r::AbstractUnitRange, offset::Integer = 0) where T<:Integer rc = convert(AbstractUnitRange{T}, r)::AbstractUnitRange{T} - return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset)) + return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset)::T) end IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer = 0) where T<:Integer = - IdOffsetRange{T,typeof(r)}(r, convert(T, offset)) + IdOffsetRange{T,typeof(r)}(r, convert(T, offset)::T) # Coercion from other IdOffsetRanges IdOffsetRange{T,I}(r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r -function IdOffsetRange{T,I}(r::IdOffsetRange) where {T<:Integer,I<:AbstractUnitRange{T}} - rc, offset = offset_coerce(I, r.parent) - return IdOffsetRange{T,I}(rc, r.offset+offset) +function IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}} + rc, offset_rc = offset_coerce(I, r.parent) + return IdOffsetRange{T,I}(rc, convert(T, r.offset + offset + offset_rc)::T) end -function IdOffsetRange{T}(r::IdOffsetRange) where T<:Integer - return IdOffsetRange(convert(AbstractUnitRange{T}, r.parent), r.offset) +IdOffsetRange{T}(r::IdOffsetRange{T}) where {T<:Integer} = r +function IdOffsetRange{T}(r::IdOffsetRange, offset::Integer = 0) where T<:Integer + return IdOffsetRange{T}(r.parent, r.offset + offset) end IdOffsetRange(r::IdOffsetRange) = r +# Constructor to make `show` round-trippable +function IdOffsetRange(; values::AbstractUnitRange{<:Integer}, indices::AbstractUnitRange{<:Integer}) + length(values) == length(indices) || throw(ArgumentError("values and indices must have the same length")) + offset = first(indices) - 1 + return IdOffsetRange(values .- offset, offset) +end + +# Conversions to an AbstractUnitRange{Int} (and to an OrdinalRange{Int,Int} on Julia v"1.6") are necessary +# to evaluate CartesianIndices for BigInt ranges, as their axes are also BigInt ranges +Base.AbstractUnitRange{T}(r::IdOffsetRange) where {T<:Integer} = IdOffsetRange{T}(r) + # TODO: uncomment these when Julia is ready -# # Conversion preserves both the values and the indexes, throwing an InexactError if this +# # Conversion preserves both the values and the indices, throwing an InexactError if this # # is not possible. # Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r # Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange) where {T<:Integer,I<:AbstractUnitRange{T}} = @@ -61,44 +100,89 @@ end # function offset_coerce(::Type{Base.OneTo{T}}, r::IdOffsetRange) where T<:Integer # rc, o = offset_coerce(Base.OneTo{T}, r.parent) -# Fallback, specialze this method if `convert(I, r)` doesn't do what you need -offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange{T} where T = - convert(I, r), 0 +# Fallback, specialize this method if `convert(I, r)` doesn't do what you need +offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange = + convert(I, r)::I, 0 @inline Base.parent(r::IdOffsetRange) = r.parent @inline Base.axes(r::IdOffsetRange) = (Base.axes1(r),) @inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset) +@inline Base.unsafe_indices(r::IdOffsetRange) = (Base.axes1(r),) @inline Base.length(r::IdOffsetRange) = length(r.parent) +@inline Base.isempty(r::IdOffsetRange) = isempty(r.parent) Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i)) # Workaround for #92 on Julia < 1.4 Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i)) for f in [:firstindex, :lastindex] - @eval Base.$f(r::IdOffsetRange) = $f(r.parent) .+ r.offset + @eval @inline Base.$f(r::IdOffsetRange) = $f(r.parent) + r.offset +end +for f in [:first, :last] + # coerce the type to deal with values that get promoted on addition (eg. Bool) + @eval @inline Base.$f(r::IdOffsetRange) = eltype(r)($f(r.parent) + r.offset) end -@inline function Base.iterate(r::IdOffsetRange) - ret = iterate(r.parent) +# Iteration for an IdOffsetRange +@inline Base.iterate(r::IdOffsetRange, i...) = _iterate(r, i...) +# In general we iterate over the parent term by term and add the offset. +# This might have some performance degradation when coupled with bounds-checking +# See https://github.com/JuliaArrays/OffsetArrays.jl/issues/214 +@inline function _iterate(r::IdOffsetRange, i...) + ret = iterate(r.parent, i...) ret === nothing && return nothing - return (ret[1] + r.offset, ret[2]) + return (eltype(r)(ret[1] + r.offset), ret[2]) end -@inline function Base.iterate(r::IdOffsetRange, i) - ret = iterate(r.parent, i) - ret === nothing && return nothing - return (ret[1] + r.offset, ret[2]) +# Base.OneTo(n) is known to be exactly equivalent to the range 1:n, +# and has no specialized iteration defined for it, +# so we may add the offset to the range directly and iterate over the result +# This gets around the performance issue described in issue #214 +# We use the helper function _addoffset to evaluate the range instead of broadcasting +# just in case this makes it easy for the compiler. +@inline _iterate(r::IdOffsetRange{<:Integer, <:Base.OneTo}, i...) = iterate(_addoffset(r.parent, r.offset), i...) + +@inline function Base.getindex(r::IdOffsetRange, i::Integer) + i isa Bool && throw(ArgumentError("invalid index: $i of type Bool")) + @boundscheck checkbounds(r, i) + @inbounds eltype(r)(r.parent[i - r.offset] + r.offset) end -@inline Base.first(r::IdOffsetRange) = first(r.parent) + r.offset -@inline Base.last(r::IdOffsetRange) = last(r.parent) + r.offset +# Logical indexing following https://github.com/JuliaLang/julia/pull/31829 +#= Helper function to perform logical indxeing for boolean ranges +The code implemented is a branch-free version of the following: + + range(first(s) ? first(r) : last(r), length=Int(last(s))) -@propagate_inbounds Base.getindex(r::IdOffsetRange, i::Integer) = r.parent[i - r.offset] + r.offset -@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::AbstractUnitRange{<:Integer}) - return r.parent[s .- r.offset] .+ r.offset +See https://github.com/JuliaArrays/OffsetArrays.jl/pull/224#discussion_r595635143 + +Logical indexing does not preserve indices, unlike other forms of vector indexing +=# +@inline function _getindex(r, s::AbstractUnitRange{Bool}) + range(first(r) * first(s) + last(r) * !first(s), length=Int(last(s))) +end +@inline function _getindex(r, s::StepRange{Bool}) + range(first(r) * first(s) + last(r) * !first(s), step = oneunit(step(s)), length=Int(last(s))) end -@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::IdentityUnitRange) - return IdOffsetRange(r.parent[s .- r.offset], r.offset) +@inline function _getindex(r, s::AbstractUnitRange) + @inbounds rs = r.parent[_subtractoffset(s, r.offset)] .+ r.offset + _indexedby(rs, axes(s)) end -@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::IdOffsetRange) - return IdOffsetRange(r.parent[s.parent .+ (s.offset - r.offset)] .+ (r.offset - s.offset), s.offset) +@inline function _getindex(r, s::StepRange) + rs = @inbounds r.parent[s .- r.offset] .+ r.offset + _indexedby(rs, axes(s)) +end + +for T in [:AbstractUnitRange, :StepRange] + @eval @inline function Base.getindex(r::IdOffsetRange, s::$T{<:Integer}) + @boundscheck checkbounds(r, s) + return _getindex(r, s) + end +end + +# These methods are necessary to avoid ambiguity +for R in [:IIUR, :IdOffsetRange] + @eval @inline function Base.getindex(r::IdOffsetRange, s::$R) + @boundscheck checkbounds(r, s) + return _getindex(r, s) + end end # offset-preserve broadcasting @@ -109,33 +193,59 @@ Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdO Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange{T}) where T = IdOffsetRange{T}(x .+ r.parent, r.offset) -Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(", first(r), ':', last(r), ")") +Base.show(io::IO, r::IdOffsetRange) = print(io, IdOffsetRange, "(values=",first(r), ':', last(r),", indices=",first(eachindex(r)),':',last(eachindex(r)), ")") # Optimizations @inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset) -struct Origin{T <: Union{Tuple,Int}} +######################################################################################################## +# origin.jl +######################################################################################################## + +struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}} index::T end -Origin(I::NTuple{N,Int}) where N = Origin{typeof(I)}(I) -Origin(I::CartesianIndex) = Origin(I.I) -Origin(I1::Int, In::Int...) = Origin((I1, In...)) +Origin(I::Tuple{Vararg{Int}}) = Origin{typeof(I)}(I) +Origin(I::Tuple{Vararg{Number}}) = Origin(map(Int, I)) +Origin(I::CartesianIndex) = Origin(Tuple(I)) +Origin(I::Number...) = Origin(I) # Origin(0) != Origin((0, )) but they work the same with broadcasting -Origin(n::Int) = Origin{Int}(n) +Origin(n::Number) = Origin{Int}(Int(n)) + +Base.Broadcast.broadcastable(o::Origin) = Ref(o) + +_showidx(index::Integer) = "(" * string(index) * ")" +_showidx(index::Tuple) = string(index) +Base.show(io::IO, o::Origin) = print(io, "Origin", _showidx(o.index)) -(o::Origin)(A::AbstractArray) = o.index .- first.(axes(A)) +######################################################################################################## +# utils.jl +######################################################################################################## ### Low-level utilities ### _indexoffset(r::AbstractRange) = first(r) - 1 _indexoffset(i::Integer) = 0 -_indexoffset(i::Colon) = 0 _indexlength(r::AbstractRange) = length(r) -_indexlength(i::Integer) = i +_indexlength(i::Integer) = Int(i) _indexlength(i::Colon) = Colon() +# utility methods used in reshape +# we don't use _indexlength in this to avoid converting the arguments to Int +_checksize(ind::Integer, s) = ind == s +_checksize(ind::AbstractUnitRange, s) = length(ind) == s + +_toaxis(i::Integer) = Base.OneTo(i) +_toaxis(i) = i + +_strip_IdOffsetRange(r::IdOffsetRange) = parent(r) +_strip_IdOffsetRange(r) = r + _offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent) -_offset(axparent::AbstractUnitRange, ax::Integer) = 1 - first(axparent) +_offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axparent) + +_offsets(A::AbstractArray) = map(ax -> first(ax) - 1, axes(A)) +_offsets(A::AbstractArray, B::AbstractArray) = map(_offset, axes(B), axes(A)) abstract type AxisConversionStyle end struct SingleRange <: AxisConversionStyle end @@ -154,10 +264,47 @@ _toAbstractUnitRanges(::Tuple{}) = () # ensure that the indices are consistent in the constructor _checkindices(A::AbstractArray, indices, label) = _checkindices(ndims(A), indices, label) function _checkindices(N::Integer, indices, label) - throw_argumenterror(N, indices, label) = throw(ArgumentError(label * " $indices are not compatible with a $(N)D array")) + throw_argumenterror(N, indices, label) = throw(ArgumentError(label*" $indices are not compatible with a $(N)D array")) N == length(indices) || throw_argumenterror(N, indices, label) end +@inline _indexedby(r::AbstractVector, ax::Tuple{Any}) = _indexedby(r, ax[1]) +@inline _indexedby(r::AbstractUnitRange{<:Integer}, ::Base.OneTo) = no_offset_view(r) +@inline _indexedby(r::AbstractUnitRange{Bool}, ::Base.OneTo) = no_offset_view(r) +@inline _indexedby(r::AbstractVector, ::Base.OneTo) = no_offset_view(r) +@inline function _indexedby(r::AbstractUnitRange{<:Integer}, ax::AbstractUnitRange) + of = convert(eltype(r), first(ax) - 1) + IdOffsetRange(_subtractoffset(r, of), of) +end +@inline _indexedby(r::AbstractUnitRange{Bool}, ax::AbstractUnitRange) = OffsetArray(r, ax) +@inline _indexedby(r::AbstractVector, ax::AbstractUnitRange) = OffsetArray(r, ax) + +# These functions are equivalent to the broadcasted operation r .- of +# However these ensure that the result is an AbstractRange even if a specific +# broadcasting behavior is not defined for a custom type +@inline _subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of) +@inline _subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r)) + +# similar to _subtractoffset, except these evaluate r .+ of +@inline _addoffset(r::AbstractUnitRange, of) = UnitRange(first(r) + of, last(r) + of) +@inline _addoffset(r::AbstractRange, of) = range(first(r) + of, stop = last(r) + of, step = step(r)) + +_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r + +_of_eltype(::Type{T}, M::AbstractArray{T}) where {T} = M +_of_eltype(T, M::AbstractArray) = map(T, M) + +# filter the arguments to reshape to check if there are any ranges +# If not, we may pop the parent array +_filterreshapeinds(t::Tuple{AbstractUnitRange, Vararg{Any}}) = t +_filterreshapeinds(t::Tuple) = _filterreshapeinds(tail(t)) +_filterreshapeinds(t::Tuple{}) = t +_popreshape(A::AbstractArray, ax::Tuple{Vararg{Base.OneTo}}, inds::Tuple{}) = no_offset_view(A) +_popreshape(A::AbstractArray, ax, inds) = A + +######################################################################################################## +# OffsetArrays.jl +######################################################################################################## # Technically we know the length of CartesianIndices but we need to convert it first, so here we # don't put it in OffsetAxisKnownLength. @@ -169,142 +316,260 @@ const ArrayInitializer = Union{UndefInitializer,Missing,Nothing} struct OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N} parent::AA offsets::NTuple{N,Int} - function OffsetArray{T,N,AA}(parent::AA, offsets::NTuple{N,Int}) where {T,N,AA <: AbstractArray} - @boundscheck overflow_check.(axes(parent), offsets) - new{T,N,AA}(parent, offsets) + @inline function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Int}; checkoverflow = true) where {T, N, AA<:AbstractArray{T,N}} + # allocation of `map` on tuple is optimized away + checkoverflow && map(overflow_check, axes(parent), offsets) + new{T, N, AA}(parent, offsets) end end -const OffsetVector{T,AA <: AbstractArray} = OffsetArray{T,1,AA} +const OffsetVector{T,AA<:AbstractVector{T}} = OffsetArray{T,1,AA} -const OffsetMatrix{T,AA <: AbstractArray} = OffsetArray{T,2,AA} +const OffsetMatrix{T,AA<:AbstractMatrix{T}} = OffsetArray{T,2,AA} -function overflow_check(r, offset::T) where T +# checks if the offset may be added to the range without overflowing +function overflow_check(r::AbstractUnitRange, offset::Integer) + Base.hastypemax(eltype(r)) || return nothing # This gives some performance boost https://github.com/JuliaLang/julia/issues/33273 - throw_upper_overflow_error() = throw(ArgumentError("Boundary overflow detected: offset $offset should be equal or less than $(typemax(T) - last(r))")) - throw_lower_overflow_error() = throw(ArgumentError("Boundary overflow detected: offset $offset should be equal or greater than $(typemin(T) - first(r))")) + throw_upper_overflow_error(val) = throw(OverflowError("offset should be <= $(typemax(Int) - val) corresponding to the axis $r, received an offset $offset")) + throw_lower_overflow_error(val) = throw(OverflowError("offset should be >= $(typemin(Int) - val) corresponding to the axis $r, received an offset $offset")) + + # With ranges in the picture, first(r) might not necessarily be < last(r) + # we therefore use the min and max of first(r) and last(r) to check for overflow + firstlast_min, firstlast_max = minmax(first(r), last(r)) - if offset > 0 && last(r) > typemax(T) - offset - throw_upper_overflow_error() - elseif offset < 0 && first(r) < typemin(T) - offset - throw_lower_overflow_error() + if offset > 0 && firstlast_max > typemax(Int) - offset + throw_upper_overflow_error(firstlast_max) + elseif offset < 0 && firstlast_min < typemin(Int) - offset + throw_lower_overflow_error(firstlast_min) end + return nothing end # Tuples of integers are treated as offsets # Empty Tuples are handled here -function OffsetArray(A::AbstractArray, offsets::Tuple{Vararg{Integer}}) +@inline function OffsetArray(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...) _checkindices(A, offsets, "offsets") - OffsetArray{eltype(A),ndims(A),typeof(A)}(A, offsets) + OffsetArray{eltype(A), ndims(A), typeof(A)}(A, offsets; kw...) end # These methods are necessary to disallow incompatible dimensions for # the OffsetVector and the OffsetMatrix constructors for (FT, ND) in ((:OffsetVector, :1), (:OffsetMatrix, :2)) - @eval function $FT(A::AbstractArray{<:Any,$ND}, offsets::Tuple{Vararg{Integer}}) + @eval @inline function $FT(A::AbstractArray{<:Any,$ND}, offsets::Tuple{Vararg{Integer}}; kw...) _checkindices(A, offsets, "offsets") - OffsetArray{eltype(A),$ND,typeof(A)}(A, offsets) + OffsetArray{eltype(A), $ND, typeof(A)}(A, offsets; kw...) end FTstr = string(FT) - @eval function $FT(A::AbstractArray, offsets::Tuple{Vararg{Integer}}) - throw(ArgumentError($FTstr * " requires a " * string($ND) * "D array")) + @eval @inline function $FT(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...) + throw(ArgumentError($FTstr*" requires a "*string($ND)*"D array")) end end ## OffsetArray constructors for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix) # Nested OffsetArrays may strip off the wrapper and collate the offsets - @eval function $FT(A::OffsetArray, offsets::Tuple{Vararg{Integer}}) + # empty tuples are handled here + @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}}; checkoverflow = true) _checkindices(A, offsets, "offsets") - $FT(parent(A), map(+, A.offsets, offsets)) + # ensure that the offsets may be added together without an overflow + checkoverflow && map(overflow_check, axes(A), offsets) + I = map(+, _offsets(A, parent(A)), offsets) + $FT(parent(A), I, checkoverflow = false) + end + @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}}; kw...) + $FT(A, map(Int, offsets); kw...) end # In general, indices get converted to AbstractUnitRanges. # CartesianIndices{N} get converted to N ranges - @eval function $FT(A::AbstractArray, inds::Tuple{Any,Vararg{Any}}) - $FT(A, _toAbstractUnitRanges(to_indices(A, axes(A), inds))) + @eval @inline function $FT(A::AbstractArray, inds::Tuple{Any,Vararg{Any}}; kw...) + $FT(A, _toAbstractUnitRanges(to_indices(A, axes(A), inds)); kw...) end # convert ranges to offsets - @eval function $FT(A::AbstractArray, inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}) + @eval @inline function $FT(A::AbstractArray, inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) _checkindices(A, inds, "indices") # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558 throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) lA = size(A) lI = map(length, inds) lA == lI || throw_dimerr(lA, lI) - $FT(A, map(_offset, axes(A), inds)) + $FT(A, map(_offset, axes(A), inds); kw...) end - @eval $FT(A::AbstractArray, inds::Vararg) = $FT(A, inds) + @eval @inline $FT(A::AbstractArray, inds...; kw...) = $FT(A, inds; kw...) + @eval @inline $FT(A::AbstractArray; checkoverflow = false) = $FT(A, ntuple(zero, Val(ndims(A))), checkoverflow = checkoverflow) - @eval $FT(A::AbstractArray, origin::Origin) = $FT(A, origin(A)) + @eval @inline $FT(A::AbstractArray, origin::Origin; checkoverflow = true) = $FT(A, origin.index .- first.(axes(A)); checkoverflow = checkoverflow) end +(o::Origin)(A::AbstractArray) = OffsetArray(no_offset_view(A), o) +Origin(A::AbstractArray) = Origin(first.(axes(A))) + +# conversion-related methods +@inline OffsetArray{T}(M::AbstractArray, I...; kw...) where {T} = OffsetArray{T,ndims(M)}(M, I...; kw...) + +@inline function OffsetArray{T,N}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N} + M2 = _of_eltype(T, M) + OffsetArray{T,N}(M2, I...; kw...) +end +@inline OffsetArray{T,N}(M::OffsetArray{T,N}, I...; kw...) where {T,N} = OffsetArray(M, I...; kw...) +@inline OffsetArray{T,N}(M::AbstractArray{T,N}, I...; kw...) where {T,N} = OffsetArray{T,N,typeof(M)}(M, I...; kw...) + +@inline OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, I; kw...) +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::NTuple{N,Int}; checkoverflow = true) where {T,N,A<:AbstractArray{T,N}} + checkoverflow && map(overflow_check, axes(M), I) + Mv = no_offset_view(M) + MvA = convert(A, Mv)::A + Iof = map(+, _offsets(M), I) + OffsetArray{T,N,A}(MvA, Iof, checkoverflow = false) +end +@inline function OffsetArray{T, N, AA}(parent::AbstractArray{<:Any,N}, offsets::NTuple{N, Integer}; kw...) where {T, N, AA<:AbstractArray{T,N}} + OffsetArray{T, N, AA}(parent, map(Int, offsets)::NTuple{N,Int}; kw...) +end +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) where {T,N,A<:AbstractArray{T,N}} + _checkindices(M, I, "indices") + # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558 + throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) + lM = size(M) + lI = map(length, I) + lM == lI || throw_dimerr(lM, lI) + OffsetArray{T,N,A}(M, map(_offset, axes(M), I); kw...) +end +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple; kw...) where {T,N,A<:AbstractArray{T,N}} + OffsetArray{T,N,A}(M, _toAbstractUnitRanges(to_indices(M, axes(M), I)); kw...) +end +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}; kw...) where {T,N,A<:AbstractArray{T,N}} + Mv = no_offset_view(M) + MvA = convert(A, Mv)::A + OffsetArray{T,N,A}(MvA, _offsets(M); kw...) +end +@inline OffsetArray{T,N,A}(M::A; checkoverflow = false) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, ntuple(zero, Val(N)); checkoverflow = checkoverflow) + +Base.convert(::Type{T}, M::AbstractArray) where {T<:OffsetArray} = M isa T ? M : T(M) + +@inline AbstractArray{T,N}(M::OffsetArray{S,N}) where {T,S,N} = OffsetArray{T}(M) + # array initialization -function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}) where {T,N} +@inline function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}; kw...) where {T,N} _checkindices(N, inds, "indices") AA = Array{T,N}(init, map(_indexlength, inds)) - OffsetArray{T,N,typeof(AA)}(AA, map(_indexoffset, inds)) + OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...) end -function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple) where {T,N} - OffsetArray{T,N}(init, _toAbstractUnitRanges(inds)) +@inline function OffsetArray{T, N}(init::ArrayInitializer, inds::Tuple; kw...) where {T, N} + OffsetArray{T, N}(init, _toAbstractUnitRanges(inds); kw...) end -OffsetArray{T,N}(init::ArrayInitializer, inds::Vararg) where {T,N} = OffsetArray{T,N}(init, inds) +@inline OffsetArray{T,N}(init::ArrayInitializer, inds...; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...) -OffsetArray{T}(init::ArrayInitializer, inds::NTuple{N,OffsetAxisKnownLength}) where {T,N} = OffsetArray{T,N}(init, inds) -function OffsetArray{T}(init::ArrayInitializer, inds::Tuple) where {T} - OffsetArray{T}(init, _toAbstractUnitRanges(inds)) +@inline OffsetArray{T}(init::ArrayInitializer, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...) +@inline function OffsetArray{T}(init::ArrayInitializer, inds::Tuple; kw...) where {T} + OffsetArray{T}(init, _toAbstractUnitRanges(inds); kw...) end -OffsetArray{T}(init::ArrayInitializer, inds::Vararg) where {T} = OffsetArray{T}(init, inds) +@inline OffsetArray{T}(init::ArrayInitializer, inds...; kw...) where {T} = OffsetArray{T}(init, inds; kw...) -Base.IndexStyle(::Type{OA}) where {OA <: OffsetArray} = IndexStyle(parenttype(OA)) +Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA)) parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA parenttype(A::OffsetArray) = parenttype(typeof(A)) Base.parent(A::OffsetArray) = A.parent -Base.eachindex(::IndexCartesian, A::OffsetArray) = CartesianIndices(axes(A)) -Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1) +# TODO: Ideally we would delegate to the parent's broadcasting implementation, but that +# is currently broken in sufficiently many implementation, namely RecursiveArrayTools, DistributedArrays +# and StaticArrays, that it will take concentrated effort to get this working across the ecosystem. +# The goal would be to have `OffsetArray(CuArray) .+ 1 == OffsetArray{CuArray}`. +# Base.Broadcast.BroadcastStyle(::Type{<:OffsetArray{<:Any, <:Any, AA}}) where AA = Base.Broadcast.BroadcastStyle(AA) @inline Base.size(A::OffsetArray) = size(parent(A)) -@inline Base.size(A::OffsetArray, d) = size(parent(A), d) @inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets) @inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : IdOffsetRange(axes(parent(A), d)) @inline Base.axes1(A::OffsetArray{T,0}) where {T} = IdOffsetRange(axes(parent(A), 1)) # we only need to specialize this one +# Utils to translate a function to the parent while preserving offsets +unwrap(x) = x, identity +unwrap(x::OffsetArray) = parent(x), data -> OffsetArray(data, x.offsets, checkoverflow = false) +function parent_call(f, x) + parent, wrap_offset = unwrap(x) + wrap_offset(f(parent)) +end + Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T = similar(parent(A), T, dims) -function Base.similar(A::AbstractArray, ::Type{T}, inds::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where T - B = similar(A, T, map(_indexlength, inds)) - return OffsetArray(B, map(_offset, axes(B), inds)) +function Base.similar(A::AbstractArray, ::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where T + # strip IdOffsetRanges to extract the parent range and use it to generate the array + new_shape = map(_strip_IdOffsetRange, shape) + # route through _similar_axes_or_length to avoid a stack overflow if map(_strip_IdOffsetRange, shape) === shape + # This tries to use new_shape directly in similar if similar(A, T, ::typeof(new_shape)) is defined + # If this fails, it calls similar(A, T, map(_indexlength, new_shape)) to use the size along each axis + # to generate the new array + P = _similar_axes_or_length(A, T, new_shape, shape) + return OffsetArray(P, map(_offset, axes(P), shape)) +end +Base.similar(::Type{A}, sz::Tuple{Vararg{Int}}) where {A<:OffsetArray} = similar(Array{eltype(A)}, sz) +function Base.similar(::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where {T<:AbstractArray} + new_shape = map(_strip_IdOffsetRange, shape) + P = _similar_axes_or_length(T, new_shape, shape) + OffsetArray(P, map(_offset, axes(P), shape)) end +# Try to use the axes to generate the parent array type +# This is useful if the axes have special meanings, such as with static arrays +# This method is hit if at least one axis provided to similar(A, T, axes) is an IdOffsetRange +# For example this is hit when similar(A::OffsetArray) is called, +# which expands to similar(A, eltype(A), axes(A)) +_similar_axes_or_length(A, T, ax, ::Any) = similar(A, T, ax) +_similar_axes_or_length(AT, ax, ::Any) = similar(AT, ax) +# Handle the general case by resorting to lengths along each axis +# This is hit if none of the axes provided to similar(A, T, axes) are IdOffsetRanges, +# and if similar(A, T, axes::AX) is not defined for the type AX. +# In this case the best that we can do is to create a mutable array of the correct size +_similar_axes_or_length(A, T, ax::I, ::I) where {I} = similar(A, T, map(_indexlength, ax)) +_similar_axes_or_length(AT, ax::I, ::I) where {I} = similar(AT, map(_indexlength, ax)) # reshape accepts a single colon Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, inds) function Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) - AR = reshape(A, map(_indexlength, inds)) - return OffsetArray(AR, map(_offset, axes(AR), inds)) + AR = reshape(no_offset_view(A), map(_indexlength, inds)) + O = OffsetArray(AR, map(_offset, axes(AR), inds)) + return _popreshape(O, axes(AR), _filterreshapeinds(inds)) end # Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return # an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...))) +# Short-circuit for AbstractVectors if the axes are compatible to get around the Base restriction +# to 1-based vectors +function _reshape(A::AbstractVector, inds::Tuple{OffsetAxis}) + @noinline throw_dimerr(ind::Integer) = throw( + DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with length $ind")) + @noinline throw_dimerr(ind) = throw( + DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with indices $ind")) + _checksize(first(inds), size(A,1)) || throw_dimerr(first(inds)) + A +end +_reshape(A, inds) = _reshape2(A, inds) +_reshape2(A, inds) = reshape(A, inds) +# avoid a stackoverflow by relegating to the parent if no_offset_view returns an offsetarray +_reshape2(A::OffsetArray, inds) = reshape(parent(A), inds) +_reshape_nov(A, inds) = _reshape(no_offset_view(A), inds) + Base.reshape(A::OffsetArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) = - OffsetArray(reshape(parent(A), map(_indexlength, inds)), map(_indexoffset, inds)) + OffsetArray(_reshape(parent(A), inds), map(_toaxis, inds)) # And for non-offset axes, we can just return a reshape of the parent directly -Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = reshape(parent(A), inds) -Base.reshape(A::OffsetArray, inds::Dims) = reshape(parent(A), inds) -Base.reshape(A::OffsetArray, ::Colon) = reshape(parent(A), Colon()) +Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = _reshape_nov(A, inds) +Base.reshape(A::OffsetArray, inds::Dims) = _reshape_nov(A, inds) Base.reshape(A::OffsetVector, ::Colon) = A Base.reshape(A::OffsetVector, ::Tuple{Colon}) = A -Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(parent(A), inds) -Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = reshape(parent(A), inds) - -function Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T <: AbstractArray} - P = T(undef, map(_indexlength, shape)) - OffsetArray(P, map(_offset, axes(P), shape)) -end +Base.reshape(A::OffsetArray, ::Colon) = reshape(A, (Colon(),)) +Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(A, inds) +Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = _reshape_nov(A, inds) +# The following two additional methods for Colon are added to resolve method ambiguities to +# Base: https://github.com/JuliaLang/julia/pull/45387#issuecomment-1132859663 +Base.reshape(A::OffsetArray, inds::Colon) = _reshape_nov(A, inds) +Base.reshape(A::OffsetArray, inds::Tuple{Colon}) = _reshape_nov(A, inds) + +# permutedims in Base does not preserve axes, and can not be fixed in a non-breaking way +# This is a stopgap solution +Base.permutedims(v::OffsetVector) = reshape(v, (1, axes(v, 1))) Base.fill(v, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(similar(Array{typeof(v)}, inds), v) @@ -317,6 +582,9 @@ Base.trues(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(similar(BitArray, inds), false) +Base.zero(A::OffsetArray) = parent_call(zero, A) +Base.fill!(A::OffsetArray, x) = parent_call(Ap -> fill!(Ap, x), A) + ## Indexing # Note this gets the index of the parent *array*, not the index of the parent *range* @@ -326,12 +594,23 @@ Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = # and one obtains the result below. parentindex(r::IdOffsetRange, i) = i - r.offset -@inline function Base.getindex(A::OffsetArray{T,N}, I::Vararg{Int,N}) where {T,N} +@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,0}) = A.parent[] + +@inline function Base.getindex(A::OffsetArray{<:Any,N}, I::Vararg{Int,N}) where N @boundscheck checkbounds(A, I...) J = map(parentindex, axes(A), I) @inbounds parent(A)[J...] end +@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,N}, c::Vararg{Colon,N}) where N = + parent_call(x -> getindex(x, c...), A) + +# With one Colon we use linear indexing. +# In this case we may forward the index to the parent, as the information about the axes is lost +# The exception to this is with OffsetVectors where the axis information is preserved, +# but that case is handled by getindex(::OffsetArray{<:Any,N}, ::Vararg{Colon,N}) +@propagate_inbounds Base.getindex(A::OffsetArray, c::Colon) = A.parent[:] + @inline function Base.getindex(A::OffsetVector, i::Int) @boundscheck checkbounds(A, i) @inbounds parent(A)[parentindex(Base.axes1(A), i)] @@ -340,7 +619,7 @@ end @inline function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N} @boundscheck checkbounds(A, I...) - J = @inbounds map(parentindex, axes(A), I) + J = map(parentindex, axes(A), I) @inbounds parent(A)[J...] = val A end @@ -355,49 +634,134 @@ end A end +@inline Base.iterate(a::OffsetArray, i...) = iterate(parent(a), i...) + +Base.in(x, A::OffsetArray) = in(x, parent(A)) +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)) + # 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)) Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) === parent(src) ? src : Broadcast.unalias(dest, src) ### Special handling for AbstractRange - -const OffsetRange{T} = OffsetArray{T,1,<:AbstractRange{T}} -const IIUR = IdentityUnitRange{S} where S<:AbstractUnitRange{T} where T<:Integer +const OffsetRange{T} = OffsetVector{T,<:AbstractRange{T}} +const OffsetUnitRange{T} = OffsetVector{T,<:AbstractUnitRange{T}} Base.step(a::OffsetRange) = step(parent(a)) -@propagate_inbounds Base.getindex(a::OffsetRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets) -@propagate_inbounds function Base.getindex(a::OffsetRange, r::IdOffsetRange) - OffsetArray(a.parent[r.parent .+ (r.offset - a.offsets[1])], r.offset) +Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or)) + +# Certain special methods for linear indexing with integer ranges (or OffsetRanges) +# These may bypass the default getindex(A, I...) pathway if the parent types permit this +# For example AbstractUnitRanges and Arrays have special linear indexing behavior defined + +# If both the arguments are offset, we may unwrap the indices to call (::OffsetArray)[::AbstractRange{Int}] +@propagate_inbounds function Base.getindex(A::OffsetArray, r::OffsetRange{Int}) + _indexedby(A[parent(r)], axes(r)) +end +# If the indices are offset, we may unwrap them and pass the parent to getindex +@propagate_inbounds function Base.getindex(A::AbstractRange, r::OffsetRange{Int}) + _indexedby(A[parent(r)], axes(r)) end -@propagate_inbounds Base.getindex(r::OffsetRange, s::IIUR) = - OffsetArray(r[s.indices], s) -@propagate_inbounds Base.getindex(a::OffsetRange, r::AbstractRange) = a.parent[r .- a.offsets[1]] -@propagate_inbounds Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets) -@propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) = - OffsetArray(r[s.indices], s) +# An OffsetUnitRange might use the rapid getindex(::Array, ::AbstractUnitRange{Int}) for contiguous indexing +@propagate_inbounds function Base.getindex(A::Array, r::OffsetUnitRange{Int}) + B = A[_contiguousindexingtype(parent(r))] + OffsetArray(B, axes(r), checkoverflow = false) +end -@propagate_inbounds Base.getindex(r::StepRange, s::IIUR) = - OffsetArray(r[s.indices], s) +# Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods +@inline function Base.getindex(A::OffsetArray, r::AbstractUnitRange{Int}) + @boundscheck checkbounds(A, r) + # nD OffsetArrays do not have their linear indices shifted, so we may forward the indices provided to the parent + @inbounds B = parent(A)[_contiguousindexingtype(r)] + _indexedby(B, axes(r)) +end +@inline function Base.getindex(A::OffsetVector, r::AbstractUnitRange{Int}) + @boundscheck checkbounds(A, r) + # OffsetVectors may have their linear indices shifted, so we subtract the offset from the indices provided + @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] + _indexedby(B, axes(r)) +end -# this method is needed for ambiguity resolution -@propagate_inbounds Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::IIUR) where T = - OffsetArray(r[s.indices], s) +# This method added mainly to index an OffsetRange with another range +@inline function Base.getindex(A::OffsetVector, r::AbstractRange{Int}) + @boundscheck checkbounds(A, r) + @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] + _indexedby(B, axes(r)) +end -@propagate_inbounds Base.getindex(r::StepRangeLen{T}, s::IIUR) where {T} = - OffsetArray(r[s.indices], s) +# In general we would pass through getindex(A, I...) which calls to_indices(A, I) and finally to_index(I) +# An OffsetUnitRange{Int} has an equivalent IdOffsetRange with the same values and axes, +# something similar also holds for OffsetUnitRange{BigInt} +# We may replace the former with the latter in an indexing operation to obtain a performance boost +@inline function Base.to_index(r::OffsetUnitRange{<:Union{Int,BigInt}}) + of = first(axes(r,1)) - 1 + IdOffsetRange(_subtractoffset(parent(r), of), of) +end -@propagate_inbounds Base.getindex(r::LinRange, s::IIUR) = - OffsetArray(r[s.indices], s) +@inline function _boundscheck_index_retaining_axes(r, s) + @boundscheck checkbounds(r, s) + @inbounds pr = r[UnitRange(s)] + _indexedby(pr, axes(s)) +end +@inline _boundscheck_return(r, s) = (@boundscheck checkbounds(r, s); s) + +for OR in [:IIUR, :IdOffsetRange] + for R in [:StepRange, :StepRangeLen, :LinRange, :UnitRange] + @eval @inline Base.getindex(r::$R, s::$OR) = _boundscheck_index_retaining_axes(r, s) + end + + # this method is needed for ambiguity resolution + @eval @inline function Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::$OR) where T + _boundscheck_index_retaining_axes(r, s) + end +end +Base.getindex(r::Base.OneTo, s::IdOffsetRange) = _boundscheck_index_retaining_axes(r, s) + +# These methods are added to avoid ambiguities with Base. +# The ones involving Base types should be ported to Base and version-limited here +@inline Base.getindex(r::IdentityUnitRange, s::IIUR) = _boundscheck_return(r, s) +@inline Base.getindex(r::IdentityUnitRange, s::IdOffsetRange) = _boundscheck_return(r, s) +if IdentityUnitRange !== Base.Slice + @inline Base.getindex(r::Base.Slice, s::IIUR) = _boundscheck_return(r, s) + @inline Base.getindex(r::Base.Slice, s::IdOffsetRange) = _boundscheck_return(r, s) +end + +# eltype conversion +# This may use specialized map methods for the parent +Base.map(::Type{T}, O::OffsetArray) where {T} = parent_call(x -> map(T, x), O) +Base.map(::Type{T}, r::IdOffsetRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r)) +if eltype(IIUR) === Int + # This is type-piracy, but there is no way to convert an IdentityUnitRange to a non-Int type in Base + Base.map(::Type{T}, r::IdentityUnitRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r)) +end + +# mapreduce is faster with an IdOffsetRange than with an OffsetUnitRange +# We therefore convert OffsetUnitRanges to IdOffsetRanges with the same values and axes +function Base.mapreduce(f, op, A1::OffsetUnitRange{<:Integer}, As::OffsetUnitRange{<:Integer}...; kw...) + As = (A1, As...) + ofs = map(A -> first(axes(A,1)) - 1, As) + AIds = map((A, of) -> IdOffsetRange(_subtractoffset(parent(A), of), of), As, ofs) + mapreduce(f, op, AIds...; kw...) +end + +# Optimize certain reductions that treat an OffsetVector as a list +for f in [:minimum, :maximum, :extrema, :sum] + @eval Base.$f(r::OffsetRange) = $f(parent(r)) +end function Base.show(io::IO, r::OffsetRange) show(io, r.parent) - o = r.offsets[1] - print(io, " with indices ", o+1:o+length(r)) + print(io, " with indices ", UnitRange(axes(r, 1))) end Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r) + ### Some mutating functions defined only for OffsetVector ### Base.resize!(A::OffsetVector, nl::Integer) = (resize!(A.parent, nl); A) @@ -407,17 +771,19 @@ Base.append!(A::OffsetVector, items) = (append!(A.parent, items); A) Base.empty!(A::OffsetVector) = (empty!(A.parent); A) # These functions keep the summary compact -function Base.inds2string(inds::Tuple{Vararg{Union{IdOffsetRange,IdentityUnitRange{<:IdOffsetRange}}}}) +function Base.inds2string(inds::Tuple{Vararg{Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}}}}) Base.inds2string(map(UnitRange, inds)) end Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...) -function Base.showarg(io::IO, a::OffsetArray, toplevel) +function Base.showarg(io::IO, @nospecialize(a::OffsetArray), toplevel) print(io, "OffsetArray(") Base.showarg(io, parent(a), false) Base.showindices(io, axes(a)...) print(io, ')') - toplevel && print(io, " with eltype ", eltype(a)) + if toplevel + print(io, " with eltype ", eltype(a)) + end end function Base.replace_in_print_matrix(A::OffsetArray{<:Any,2}, i::Integer, j::Integer, s::AbstractString) @@ -429,14 +795,49 @@ function Base.replace_in_print_matrix(A::OffsetArray{<:Any,1}, i::Integer, j::In Base.replace_in_print_matrix(parent(A), ip, j, s) end -function no_offset_view(A::AbstractArray) - if Base.has_offset_axes(A) - OffsetArray(A, map(r->1-first(r), axes(A))) - else - A +no_offset_view(A::OffsetArray) = no_offset_view(parent(A)) +no_offset_view(a::Array) = a +no_offset_view(i::Number) = i +no_offset_view(A::AbstractArray) = _no_offset_view(axes(A), A) +_no_offset_view(::Tuple{}, A::AbstractArray{T,0}) where T = A +_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractArray) = A +# the following method is needed for ambiguity resolution +_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractUnitRange) = A +_no_offset_view(::Any, A::AbstractArray) = OffsetArray(A, Origin(1)) +_no_offset_view(::Any, A::AbstractUnitRange) = UnitRange(A) + +##### +# center/centered +# These two helpers are deliberately not exported; their meaning can be very different in +# other scenarios and will be very likely to cause name conflicts if exported. +##### +function center(A::AbstractArray, r::RoundingMode=RoundDown) + map(axes(A)) do inds + round(Int, (length(inds)-1)/2, r) + first(inds) end end -no_offset_view(A::OffsetArray) = no_offset_view(parent(A)) +centered(A::AbstractArray, cp::Dims=center(A)) = OffsetArray(A, .-cp) + +centered(A::AbstractArray, i::CartesianIndex) = centered(A, Tuple(i)) + +# we may pass the searchsorted* functions to the parent, and wrap the offset +for f in [:searchsortedfirst, :searchsortedlast, :searchsorted] + _safe_f = Symbol("_safe_" * String(f)) + @eval function $_safe_f(v::OffsetArray, x, ilo, ihi, o::Base.Ordering) + offset = firstindex(v) - firstindex(parent(v)) + $f(parent(v), x, ilo - offset, ihi - offset, o) .+ offset + end + @eval Base.$f(v::OffsetVector, x, ilo::T, ihi::T, o::Base.Ordering) where T<:Integer = + $_safe_f(v, x, ilo, ihi, o) +end + +## +# Deprecations +## + +# This is a bad API design as it introduces counter intuitive results (#250) +@deprecate centered(A::AbstractArray, r::RoundingMode) OffsetArray(A, .-center(A, r)) false + end # module diff --git a/test/testhelpers/Quaternions.jl b/test/testhelpers/Quaternions.jl index a3967c1aacc43..1eddad322ec40 100644 --- a/test/testhelpers/Quaternions.jl +++ b/test/testhelpers/Quaternions.jl @@ -1,6 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license module Quaternions +using Random export Quaternion @@ -36,4 +37,17 @@ Base.:(*)(q::Quaternion, b::Bool) = b * q # remove method ambiguity Base.:(/)(q::Quaternion, w::Quaternion) = q * conj(w) * (1.0 / abs2(w)) Base.:(\)(q::Quaternion, w::Quaternion) = conj(q) * w * (1.0 / abs2(q)) +# adapted from https://github.com/JuliaGeometry/Quaternions.jl/pull/42 +function Base.rand(rng::AbstractRNG, ::Random.SamplerType{Quaternion{T}}) where {T<:Real} + return Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T)) +end +function Base.randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractFloat} + return Quaternion{T}( + randn(rng, T) / 2, + randn(rng, T) / 2, + randn(rng, T) / 2, + randn(rng, T) / 2, + ) +end + end diff --git a/test/testhelpers/SizedArrays.jl b/test/testhelpers/SizedArrays.jl index 64c816f740fb2..dfcc5b79f1387 100644 --- a/test/testhelpers/SizedArrays.jl +++ b/test/testhelpers/SizedArrays.jl @@ -1,40 +1,40 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -# SizedArrays - -# This test file defines an array wrapper with statical size. It can be used to -# test the action of LinearAlgebra with non-number eltype. - -module SizedArrays - -import Base: +, *, == - -export SizedArray - -struct SizedArray{SZ,T,N,A<:AbstractArray} <: AbstractArray{T,N} - data::A - function SizedArray{SZ}(data::AbstractArray{T,N}) where {SZ,T,N} - SZ == size(data) || throw(ArgumentError("size mismatch!")) - new{SZ,T,N,typeof(data)}(data) - end - function SizedArray{SZ,T,N,A}(data::AbstractArray{T,N}) where {SZ,T,N,A} - SZ == size(data) || throw(ArgumentError("size mismatch!")) - new{SZ,T,N,A}(A(data)) - end -end -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.getindex(A::SizedArray, i...) = getindex(A.data, i...) -Base.zero(::Type{T}) where T <: SizedArray = SizedArray{size(T)}(zeros(eltype(T), size(T))) -+(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) - 0 < ndims(S1) < 3 && 0 < ndims(S2) < 3 && size(S1, 2) == size(S2, 1) || throw(ArgumentError("size mismatch!")) - data = S1.data * S2.data - SZ = ndims(data) == 1 ? (size(S1, 1), ) : (size(S1, 1), size(S2, 2)) - SizedArray{SZ}(data) -end -end +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# SizedArrays + +# This test file defines an array wrapper with statical size. It can be used to +# test the action of LinearAlgebra with non-number eltype. + +module SizedArrays + +import Base: +, *, == + +export SizedArray + +struct SizedArray{SZ,T,N,A<:AbstractArray} <: AbstractArray{T,N} + data::A + function SizedArray{SZ}(data::AbstractArray{T,N}) where {SZ,T,N} + SZ == size(data) || throw(ArgumentError("size mismatch!")) + new{SZ,T,N,typeof(data)}(data) + end + function SizedArray{SZ,T,N,A}(data::AbstractArray{T,N}) where {SZ,T,N,A} + SZ == size(data) || throw(ArgumentError("size mismatch!")) + new{SZ,T,N,A}(A(data)) + end +end +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.getindex(A::SizedArray, i...) = getindex(A.data, i...) +Base.zero(::Type{T}) where T <: SizedArray = SizedArray{size(T)}(zeros(eltype(T), size(T))) ++(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) + 0 < ndims(S1) < 3 && 0 < ndims(S2) < 3 && size(S1, 2) == size(S2, 1) || throw(ArgumentError("size mismatch!")) + data = S1.data * S2.data + SZ = ndims(data) == 1 ? (size(S1, 1), ) : (size(S1, 1), size(S2, 2)) + SizedArray{SZ}(data) +end +end diff --git a/test/testhelpers/coverage_file.info.bad2 b/test/testhelpers/coverage_file.info.bad2 new file mode 100644 index 0000000000000..a766597be4c17 --- /dev/null +++ b/test/testhelpers/coverage_file.info.bad2 @@ -0,0 +1,20 @@ +SF:<FILENAME> +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/llvmpasses.jl b/test/testhelpers/llvmpasses.jl index 0b443c3eb1535..9900dd15b5d40 100644 --- a/test/testhelpers/llvmpasses.jl +++ b/test/testhelpers/llvmpasses.jl @@ -24,4 +24,3 @@ function emit(f, tt...) end counter+=1 end - diff --git a/test/testhelpers/withlocales.jl b/test/testhelpers/withlocales.jl index a3be17cce4464..50c8058cc6466 100644 --- a/test/testhelpers/withlocales.jl +++ b/test/testhelpers/withlocales.jl @@ -9,7 +9,6 @@ function withlocales(f, newlocales) locales[cat] = unsafe_string(cstr) end end - timestrs = String[] try # change to each of given locales for lc in newlocales @@ -17,7 +16,7 @@ function withlocales(f, newlocales) for (cat, _) in locales set &= ccall(:setlocale, Cstring, (Cint, Cstring), cat, lc) != C_NULL end - set && f() + set && f(lc) end finally # recover locales diff --git a/test/threadpool_latency.jl b/test/threadpool_latency.jl new file mode 100644 index 0000000000000..bdf02b81da03f --- /dev/null +++ b/test/threadpool_latency.jl @@ -0,0 +1,50 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test +using Base.Threads + +# This test has not been added to CI as there can be unexpected delays +# which cause timing-dependent actions to fail. + +#= +Test to ensure that the interactive threadpool works as designed. + +Task A is a standard task that does a lot of work (~2 seconds) without +yielding. This would prevent ordinarily prevent other tasks from running. + +Task B is an interactive task that does a little work (~0.02 seconds) and +yields. + +With an interactive threadpool, multiple task Bs should not see notable +delays in execution even when multiple task As are occupying Julia's +default threads. + +This test should fail in the absence of an interactive thread. +=# +const N = 263000000 # busywork(N) takes ~1 sec on an i7-9750H @ 2.6GHz +function busywork(n::Int) + acc = 0 + for i = 1:n + x = rand(2:10) + acc += i * x + end + return acc +end + +function itask() + h = N ÷ 50 + for i = 1:100 + t1 = time() + busywork(h) + yield() + t2 = time() + @test t2 - t1 < 0.15 + end +end + +it1 = @spawn :interactive itask() +ti1 = @spawn busywork(N * 2); +it2 = @spawn :interactive itask() +ti2 = @spawn busywork(N * 2); +wait(it1) +wait(it2) diff --git a/test/threadpool_use.jl b/test/threadpool_use.jl new file mode 100644 index 0000000000000..e5ea5f95cf4ff --- /dev/null +++ b/test/threadpool_use.jl @@ -0,0 +1,13 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test +using Base.Threads + +@test nthreadpools() == 2 +@test threadpool() === :interactive +@test threadpool(2) === :default +@test fetch(Threads.@spawn Threads.threadpool()) === :default +@test fetch(Threads.@spawn :default Threads.threadpool()) === :default +@test fetch(Threads.@spawn :interactive Threads.threadpool()) === :interactive +@test Threads.threadpooltids(:interactive) == [1] +@test Threads.threadpooltids(:default) == [2] diff --git a/test/threads.jl b/test/threads.jl index 718358f847dd5..af752fe715b0e 100644 --- a/test/threads.jl +++ b/test/threads.jl @@ -4,6 +4,8 @@ using Test using Base.Threads +include("print_process_affinity.jl") # import `uv_thread_getaffinity` + # simple sanity tests for locks under cooperative concurrent access let lk = ReentrantLock() c1 = Event() @@ -77,6 +79,23 @@ let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no thr end end +# Timing-sensitive tests can fail on CI due to occasional unexpected delays, +# so this test is disabled. +#= +let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no threadpool_latency.jl` + for test_nthreads in (1, 2) + new_env = copy(ENV) + new_env["JULIA_NUM_THREADS"] = string(test_nthreads, ",1") + run(pipeline(setenv(cmd, new_env), stdout = stdout, stderr = stderr)) + end +end +=# +let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no threadpool_use.jl` + new_env = copy(ENV) + new_env["JULIA_NUM_THREADS"] = "1,1" + run(pipeline(setenv(cmd, new_env), stdout = stdout, stderr = stderr)) +end + function run_with_affinity(cpus) script = joinpath(@__DIR__, "print_process_affinity.jl") return readchomp(setcpuaffinity(`$(Base.julia_cmd()) $script`, cpus)) @@ -93,16 +112,49 @@ else end # Note also that libuv does not support affinity in macOS and it is known to # hang in FreeBSD. So, it's tested only in Linux and Windows: -if Sys.islinux() || Sys.iswindows() - if Sys.CPU_THREADS > 1 && !running_under_rr() - @test run_with_affinity([2]) == "2" - @test run_with_affinity([1, 2]) == "1,2" +const AFFINITY_SUPPORTED = (Sys.islinux() || Sys.iswindows()) && !running_under_rr() + +if AFFINITY_SUPPORTED + allowed_cpus = findall(uv_thread_getaffinity()) + if length(allowed_cpus) ≥ 2 + @test run_with_affinity(allowed_cpus[1:1]) == "$(allowed_cpus[1])" + @test run_with_affinity(allowed_cpus[1:2]) == "$(allowed_cpus[1]),$(allowed_cpus[2])" + end +end + +function get_nthreads(options = ``; cpus = nothing) + cmd = `$(Base.julia_cmd()) --startup-file=no $(options)` + cmd = `$cmd -e "print(Threads.threadpoolsize())"` + cmd = addenv(cmd, "JULIA_EXCLUSIVE" => "0", "JULIA_NUM_THREADS" => "auto") + if cpus !== nothing + cmd = setcpuaffinity(cmd, cpus) + end + return parse(Int, read(cmd, String)) +end + +@testset "nthreads determined based on CPU affinity" begin + if AFFINITY_SUPPORTED + allowed_cpus = findall(uv_thread_getaffinity()) + if length(allowed_cpus) ≥ 2 + @test get_nthreads() ≥ 2 + @test get_nthreads(cpus = allowed_cpus[1:1]) == 1 + @test get_nthreads(cpus = allowed_cpus[2:2]) == 1 + @test get_nthreads(cpus = allowed_cpus[1:2]) == 2 + @test get_nthreads(`-t1`, cpus = allowed_cpus[1:1]) == 1 + @test get_nthreads(`-t1`, cpus = allowed_cpus[2:2]) == 1 + @test get_nthreads(`-t1`, cpus = allowed_cpus[1:2]) == 1 + + if length(allowed_cpus) ≥ 3 + @test get_nthreads(cpus = allowed_cpus[1:2:3]) == 2 + @test get_nthreads(cpus = allowed_cpus[2:3]) == 2 + end + end end end # issue #34769 function idle_callback(handle) - idle = @Base.handle_as handle UvTestIdle + idle = Base.@handle_as handle UvTestIdle if idle.active idle.count += 1 if idle.count == 1 @@ -269,3 +321,9 @@ close(proc.in) end end end + +@testset "bad arguments to @threads" begin + @test_throws ArgumentError @macroexpand(@threads 1 2) # wrong number of args + @test_throws ArgumentError @macroexpand(@threads 1) # arg isn't an Expr + @test_throws ArgumentError @macroexpand(@threads if true 1 end) # arg doesn't start with for +end diff --git a/test/threads_exec.jl b/test/threads_exec.jl index 1b146f48e8c57..9c7c524febeff 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -2,7 +2,7 @@ using Test using Base.Threads -using Base.Threads: SpinLock +using Base.Threads: SpinLock, threadpoolsize # for cfunction_closure include("testenv.jl") @@ -16,7 +16,7 @@ function killjob(d) end if @isdefined(SIGINFO) ccall(:uv_kill, Cint, (Cint, Cint), getpid(), SIGINFO) - sleep(1) + sleep(5) # Allow time for profile to collect and print before killing end ccall(:uv_kill, Cint, (Cint, Cint), getpid(), Base.SIGTERM) nothing @@ -27,9 +27,14 @@ end # (expected test duration is about 18-180 seconds) Timer(t -> killjob("KILLING BY THREAD TEST WATCHDOG\n"), 1200) +@testset """threads_exec.jl with JULIA_NUM_THREADS == $(ENV["JULIA_NUM_THREADS"])""" begin + +@test Threads.threadid() == 1 +@test 1 <= threadpoolsize() <= Threads.maxthreadid() + # basic lock check -if nthreads() > 1 - let lk = Base.Threads.SpinLock() +if threadpoolsize() > 1 + let lk = SpinLock() c1 = Base.Event() c2 = Base.Event() @test trylock(lk) @@ -50,7 +55,7 @@ end # threading constructs -let a = zeros(Int, 2 * nthreads()) +let a = zeros(Int, 2 * threadpoolsize()) @threads for i = 1:length(a) @sync begin @async begin @@ -70,7 +75,23 @@ end # parallel loop with parallel atomic addition function threaded_loop(a, r, x) + counter = Threads.Atomic{Int}(min(threadpoolsize(), length(r))) @threads for i in r + # synchronize the start given that each partition is started sequentially, + # meaning that without the wait, if the loop is too fast the iteration can happen in order + if counter[] != 0 + Threads.atomic_sub!(counter, 1) + spins = 0 + while counter[] != 0 + GC.safepoint() + ccall(:jl_cpu_pause, Cvoid, ()) + spins += 1 + if spins > 500_000_000 # about 10 seconds + @warn "Failed wait for all workers. Unfinished rogue tasks occupying worker threads?" + break + end + end + end j = i - firstindex(r) + 1 a[j] = 1 + atomic_add!(x, 1) end @@ -83,18 +104,13 @@ function test_threaded_loop_and_atomic_add() a = zeros(Int, n) threaded_loop(a,r,x) found = zeros(Bool,n) - was_inorder = true for i=1:length(a) - was_inorder &= a[i]==i found[a[i]] = true end @test x[] == n # Next test checks that all loop iterations ran, # and were unique (via pigeon-hole principle). @test !(false in found) - if was_inorder && nthreads() > 1 - println(stderr, "Warning: threaded loop executed in order") - end end end @@ -197,7 +213,7 @@ function threaded_gc_locked(::Type{LockT}) where LockT end threaded_gc_locked(SpinLock) -threaded_gc_locked(Threads.ReentrantLock) +threaded_gc_locked(ReentrantLock) # Issue 33159 # Make sure that a Threads.Condition can't be used without being locked, on any thread. @@ -218,7 +234,7 @@ end # Make sure that eval'ing in a different module doesn't mess up other threads orig_curmodule14726 = @__MODULE__ main_var14726 = 1 -module M14726 +@eval Main module M14726 module_var14726 = 1 end @@ -238,7 +254,7 @@ end @test @__MODULE__() == orig_curmodule14726 end -module M14726_2 +@eval Main module M14726_2 using Test using Base.Threads @threads for i in 1:100 @@ -254,7 +270,7 @@ end @test_throws TypeError Atomic{BigInt} @test_throws TypeError Atomic{ComplexF64} -if Sys.ARCH == :i686 || startswith(string(Sys.ARCH), "arm") || +if Sys.ARCH === :i686 || startswith(string(Sys.ARCH), "arm") || Sys.ARCH === :powerpc64le || Sys.ARCH === :ppc64le @test_throws TypeError Atomic{Int128}() @@ -412,7 +428,7 @@ end for T in intersect((Int32, Int64, Float32, Float64), Base.Threads.atomictypes) var = Atomic{T}() nloops = 1000 - di = nthreads() + di = threadpoolsize() @threads for i in 1:di test_atomic_cas!(var, i:di:nloops) end @@ -502,7 +518,7 @@ function test_thread_cfunction() @test cfs[1] == cf1 @test cfs[2] == cf(fs[2]) @test length(unique(cfs)) == 1000 - ok = zeros(Int, nthreads()) + ok = zeros(Int, threadpoolsize()) @threads :static for i in 1:10000 i = mod1(i, 1000) fi = fs[i] @@ -518,14 +534,14 @@ if cfunction_closure end function test_thread_range() - a = zeros(Int, nthreads()) + a = zeros(Int, threadpoolsize()) @threads for i in 1:threadid() a[i] = 1 end for i in 1:threadid() @test a[i] == 1 end - for i in (threadid() + 1):nthreads() + for i in (threadid() + 1):threadpoolsize() @test a[i] == 0 end end @@ -538,7 +554,9 @@ function test_load_and_lookup_18020(n) ccall(:jl_load_and_lookup, Ptr{Cvoid}, (Cstring, Cstring, Ref{Ptr{Cvoid}}), "$i", :f, C_NULL) - catch + catch ex + ex isa ErrorException || rethrow() + startswith(ex.msg, "could not load library") || rethrow() end end end @@ -563,26 +581,26 @@ test_nested_loops() function test_thread_too_few_iters() x = Atomic() - a = zeros(Int, nthreads()+2) - threaded_loop(a, 1:nthreads()-1, x) - found = zeros(Bool, nthreads()+2) - for i=1:nthreads()-1 + a = zeros(Int, threadpoolsize()+2) + threaded_loop(a, 1:threadpoolsize()-1, x) + found = zeros(Bool, threadpoolsize()+2) + for i=1:threadpoolsize()-1 found[a[i]] = true end - @test x[] == nthreads()-1 + @test x[] == threadpoolsize()-1 # Next test checks that all loop iterations ran, # and were unique (via pigeon-hole principle). - @test !(false in found[1:nthreads()-1]) - @test !(true in found[nthreads():end]) + @test !(false in found[1:threadpoolsize()-1]) + @test !(true in found[threadpoolsize():end]) end test_thread_too_few_iters() -@testset "InvasiveLinkedList" begin - @test eltype(Base.InvasiveLinkedList{Integer}) == Integer +@testset "IntrusiveLinkedList" begin + @test eltype(Base.IntrusiveLinkedList{Integer}) == Integer @test eltype(Base.LinkedList{Integer}) == Integer - @test eltype(Base.InvasiveLinkedList{<:Integer}) == Any + @test eltype(Base.IntrusiveLinkedList{<:Integer}) == Any @test eltype(Base.LinkedList{<:Integer}) == Any - @test eltype(Base.InvasiveLinkedList{<:Base.LinkedListItem{Integer}}) == Any + @test eltype(Base.IntrusiveLinkedList{<:Base.LinkedListItem{Integer}}) == Any t = Base.LinkedList{Integer}() @test eltype(t) == Integer @@ -715,10 +733,10 @@ function _atthreads_with_error(a, err) end a end -@test_throws TaskFailedException _atthreads_with_error(zeros(nthreads()), true) -let a = zeros(nthreads()) +@test_throws CompositeException _atthreads_with_error(zeros(threadpoolsize()), true) +let a = zeros(threadpoolsize()) _atthreads_with_error(a, false) - @test a == [1:nthreads();] + @test a == [1:threadpoolsize();] end # static schedule @@ -729,11 +747,11 @@ function _atthreads_static_schedule(n) end return ids end -@test _atthreads_static_schedule(nthreads()) == 1:nthreads() +@test _atthreads_static_schedule(threadpoolsize()) == 1:threadpoolsize() @test _atthreads_static_schedule(1) == [1;] @test_throws( "`@threads :static` cannot be used concurrently or nested", - @threads(for i = 1:1; _atthreads_static_schedule(nthreads()); end), + @threads(for i = 1:1; _atthreads_static_schedule(threadpoolsize()); end), ) # dynamic schedule @@ -746,35 +764,35 @@ function _atthreads_dynamic_schedule(n) end return inc[], flags end -@test _atthreads_dynamic_schedule(nthreads()) == (nthreads(), ones(nthreads())) +@test _atthreads_dynamic_schedule(threadpoolsize()) == (threadpoolsize(), ones(threadpoolsize())) @test _atthreads_dynamic_schedule(1) == (1, ones(1)) @test _atthreads_dynamic_schedule(10) == (10, ones(10)) -@test _atthreads_dynamic_schedule(nthreads() * 2) == (nthreads() * 2, ones(nthreads() * 2)) +@test _atthreads_dynamic_schedule(threadpoolsize() * 2) == (threadpoolsize() * 2, ones(threadpoolsize() * 2)) # nested dynamic schedule function _atthreads_dynamic_dynamic_schedule() inc = Threads.Atomic{Int}(0) - Threads.@threads :dynamic for _ = 1:nthreads() - Threads.@threads :dynamic for _ = 1:nthreads() + Threads.@threads :dynamic for _ = 1:threadpoolsize() + Threads.@threads :dynamic for _ = 1:threadpoolsize() Threads.atomic_add!(inc, 1) end end return inc[] end -@test _atthreads_dynamic_dynamic_schedule() == nthreads() * nthreads() +@test _atthreads_dynamic_dynamic_schedule() == threadpoolsize() * threadpoolsize() function _atthreads_static_dynamic_schedule() - ids = zeros(Int, nthreads()) + ids = zeros(Int, threadpoolsize()) inc = Threads.Atomic{Int}(0) - Threads.@threads :static for i = 1:nthreads() + Threads.@threads :static for i = 1:threadpoolsize() ids[i] = Threads.threadid() - Threads.@threads :dynamic for _ = 1:nthreads() + Threads.@threads :dynamic for _ = 1:threadpoolsize() Threads.atomic_add!(inc, 1) end end return ids, inc[] end -@test _atthreads_static_dynamic_schedule() == (1:nthreads(), nthreads() * nthreads()) +@test _atthreads_static_dynamic_schedule() == (1:threadpoolsize(), threadpoolsize() * threadpoolsize()) # errors inside @threads :dynamic function _atthreads_dynamic_with_error(a) @@ -783,7 +801,7 @@ function _atthreads_dynamic_with_error(a) end a end -@test_throws "user error in the loop body" _atthreads_dynamic_with_error(zeros(nthreads())) +@test_throws "user error in the loop body" _atthreads_dynamic_with_error(zeros(threadpoolsize())) try @macroexpand @threads(for i = 1:10, j = 1:10; end) @@ -1012,7 +1030,7 @@ function check_sync_end_race() nnotscheduled += y === :notscheduled end # Useful for tuning the test: - @debug "`check_sync_end_race` done" nthreads() ncompleted nnotscheduled nerror + @debug "`check_sync_end_race` done" threadpoolsize() ncompleted nnotscheduled nerror finally done[] = true end @@ -1026,21 +1044,21 @@ end # issue #41546, thread-safe package loading @testset "package loading" begin - ch = Channel{Bool}(nthreads()) + ch = Channel{Bool}(threadpoolsize()) barrier = Base.Event() old_act_proj = Base.ACTIVE_PROJECT[] try pushfirst!(LOAD_PATH, "@") Base.ACTIVE_PROJECT[] = joinpath(@__DIR__, "TestPkg") @sync begin - for _ in 1:nthreads() + for _ in 1:threadpoolsize() Threads.@spawn begin put!(ch, true) wait(barrier) @eval using TestPkg end end - for _ in 1:nthreads() + for _ in 1:threadpoolsize() take!(ch) end notify(barrier) @@ -1051,3 +1069,25 @@ end popfirst!(LOAD_PATH) end end + +# issue #49746, thread safety in `atexit(f)` +@testset "atexit thread safety" begin + f = () -> nothing + before_len = length(Base.atexit_hooks) + @sync begin + for _ in 1:1_000_000 + Threads.@spawn begin + atexit(f) + end + end + end + @test length(Base.atexit_hooks) == before_len + 1_000_000 + @test all(hook -> hook === f, Base.atexit_hooks[1 : 1_000_000]) + + # cleanup + Base.@lock Base._atexit_hooks_lock begin + deleteat!(Base.atexit_hooks, 1:1_000_000) + end +end + +end # main testset diff --git a/test/tuple.jl b/test/tuple.jl index f3b82de733f16..71770b6a553c2 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -43,6 +43,9 @@ end let x = @inferred(convert(Tuple{Integer, UInt8, UInt16, UInt32, Int, Vararg{Real}}, (2.0, 3, 5, 6.0, 42, 3.0+0im))) @test x == (2, 0x03, 0x0005, 0x00000006, 42, 3.0) end + for x in (Int(2), UInt8(3), UInt16(5), UInt32(6), 42, 5.0, 3.0+0im) + @test (x,) == @inferred Tuple(x) + end @test_throws MethodError convert(Tuple{Int}, ()) @test_throws MethodError convert(Tuple{Any}, ()) @@ -61,6 +64,9 @@ end @test_throws MethodError convert(Tuple{Int, Int, Int}, (1, 2)) # issue #26589 @test_throws MethodError convert(NTuple{4}, (1.0,2.0,3.0,4.0,5.0)) + # issue #44179 + @test_throws TypeError NTuple{3}([1, nothing, nothing]) + @test_throws TypeError NTuple{3}([nothing, 1, nothing]) # issue #31824 @test convert(NTuple, (1, 1.0)) === (1, 1.0) let T = Tuple{Vararg{T}} where T<:Integer, v = (1.0, 2, 0x3) @@ -241,6 +247,7 @@ end foo(x, y) = x + y foo(x, y, z) = x + y + z longtuple = ntuple(identity, 20) + vlongtuple = ntuple(identity, 33) @testset "1 argument" begin @test map(foo, ()) === () @@ -248,6 +255,7 @@ end @test map(foo, (1,2)) === (2,4) @test map(foo, (1,2,3,4)) === (2,4,6,8) @test map(foo, longtuple) === ntuple(i->2i,20) + @test map(foo, vlongtuple) === ntuple(i->2i,33) end @testset "2 arguments" begin @@ -256,8 +264,11 @@ end @test map(foo, (1,2), (1,2)) === (2,4) @test map(foo, (1,2,3,4), (1,2,3,4)) === (2,4,6,8) @test map(foo, longtuple, longtuple) === ntuple(i->2i,20) - @test_throws BoundsError map(foo, (), (1,)) - @test_throws BoundsError map(foo, (1,), ()) + @test map(foo, vlongtuple, vlongtuple) === ntuple(i->2i,33) + @test map(foo, longtuple, vlongtuple) === ntuple(i->2i,20) + @test map(foo, vlongtuple, longtuple) === ntuple(i->2i,20) + @test map(foo, (), (1,)) === () + @test map(foo, (1,), ()) === () end @testset "n arguments" begin @@ -266,13 +277,17 @@ end @test map(foo, (1,2), (1,2), (1,2)) === (3,6) @test map(foo, (1,2,3,4), (1,2,3,4), (1,2,3,4)) === (3,6,9,12) @test map(foo, longtuple, longtuple, longtuple) === ntuple(i->3i,20) - @test_throws BoundsError map(foo, (), (1,), (1,)) - @test_throws BoundsError map(foo, (1,), (1,), ()) + @test map(foo, vlongtuple, vlongtuple, vlongtuple) === ntuple(i->3i,33) + @test map(foo, vlongtuple, longtuple, longtuple) === ntuple(i->3i,20) + @test map(foo, longtuple, vlongtuple, longtuple) === ntuple(i->3i,20) + @test map(foo, longtuple, vlongtuple, vlongtuple) === ntuple(i->3i,20) + @test map(foo, (), (1,), (1,)) === () + @test map(foo, (1,), (1,), ()) === () end end @testset "foreach" begin - longtuple = ntuple(identity, 20) + longtuple = ntuple(identity, 33) @testset "1 argument" begin foo(x) = push!(a, x) @@ -351,8 +366,8 @@ end @test hash((1,)) === hash(1, Base.tuplehash_seed) @test hash((1,2)) === hash(1, hash(2, Base.tuplehash_seed)) - # Test Any16 methods - t = ntuple(identity, 16) + # Test Any32 methods + t = ntuple(identity, 32) @test isequal((t...,1,2,3), (t...,1,2,3)) @test !isequal((t...,1,2,3), (t...,1,2,4)) @test !isequal((t...,1,2,3), (t...,1,2)) @@ -371,7 +386,7 @@ end @test !isless((t...,1,2), (t...,1,2)) @test !isless((t...,2,1), (t...,1,2)) - @test hash(t) === foldr(hash, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,(),UInt(0)]) + @test hash(t) === foldr(hash, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,(),UInt(0)]) end @testset "functions" begin @@ -416,6 +431,19 @@ end @test all((true, true)) === true @test all((true, false)) === false @test all((false, false)) === false + @test all((missing, true)) === missing + @test all((true, missing)) === missing + @test all((missing, false)) === false + @test all((false, missing)) === false + @test all((missing, true, false)) === false + @test_throws TypeError all((missing, 3.2, true)) + ts = (missing, true, false) + @test @allocated(all(ts)) == 0 # PR #44063 + @test (@inferred (()->all((missing, true)))()) === missing + @test (@inferred (()->all((true, missing)))()) === missing + @test (@inferred (()->all((missing, false)))()) === false + @test (@inferred (()->all((false, missing)))()) === false + @test (@inferred (()->all((missing, true, false)))()) === false end @testset "any" begin @@ -433,6 +461,21 @@ end @test any((true,false,true)) === true @test any((true,true,false)) === true @test any((true,true,true)) === true + @test any((missing, true)) === true + @test any((true, missing)) === true + @test any((missing, false)) === missing + @test any((false, missing)) === missing + @test any((missing, true, false)) === true + @test any((missing, false, false)) === missing + @test_throws TypeError any((missing, 3.2, true)) + ts = (missing, true, false) + @test @allocated(any(ts)) == 0 # PR #44063 + @test (@inferred (()->any((missing, true)))()) === true + @test (@inferred (()->any((true, missing)))()) === true + @test (@inferred (()->any((missing, false)))()) === missing + @test (@inferred (()->any((false, missing)))()) === missing + @test (@inferred (()->any((missing, true, false)))()) === true + @test (@inferred (()->any((missing, false, false)))()) === missing end end @@ -568,12 +611,18 @@ end @test Base.return_types() do findlast(==(0), (1.0,2,3f0)) end == Any[Nothing] + + @testset "long tuples" begin + longtuple = ntuple(i -> i in (15,17) ? 1 : 0, 40) + @test findfirst(isequal(1), longtuple) == 15 + @test findlast(isequal(1), longtuple) == 17 + end end @testset "properties" begin ttest = (:a, :b, :c) @test propertynames(ttest) == (1, 2, 3) - @test getproperty(ttest, 2) == :b + @test getproperty(ttest, 2) === :b @test map(p->getproperty(ttest, p), propertynames(ttest)) == ttest @test_throws ErrorException setproperty!(ttest, 1, :d) end @@ -600,7 +649,7 @@ end @test @inferred(f()) == (9, 2:2, 3:3) end -@testset "inferrable range indexing with constant values" begin +@testset "inferable range indexing with constant values" begin whole(t) = t[1:end] tail(t) = t[2:end] ttail(t) = t[3:end] @@ -710,3 +759,40 @@ g42457(a, b) = Base.isequal(a, b) ? 1 : 2.0 @test only(Base.return_types(g42457, (NTuple{3, Int}, Tuple))) === Union{Float64, Int} @test only(Base.return_types(g42457, (NTuple{3, Int}, NTuple))) === Union{Float64, Int} @test only(Base.return_types(g42457, (NTuple{3, Int}, NTuple{4}))) === Float64 + +# issue #46049: setindex(::Tuple) regression +@inferred Base.setindex((1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), 42, 1) + +# issue #47326 +function fun1_47326(args...) + head..., tail = args + head +end +function fun2_47326(args...) + head, tail... = args + tail +end +@test @inferred(fun1_47326(1,2,3)) === (1, 2) +@test @inferred(fun2_47326(1,2,3)) === (2, 3) + +f47326(x::Union{Tuple, NamedTuple}) = Base.split_rest(x, 1) +tup = (1, 2, 3) +namedtup = (;a=1, b=2, c=3) +@test only(Base.return_types(f47326, (typeof(tup),))) == Tuple{Tuple{Int, Int}, Tuple{Int}} +@test only(Base.return_types(f47326, (typeof(namedtup),))) == + Tuple{ + NamedTuple{(:a, :b), Tuple{Int, Int}}, + NamedTuple{(:c,), Tuple{Int}}, + } + +# Make sure that tuple iteration is foldable +@test Core.Compiler.is_foldable(Base.infer_effects(iterate, Tuple{NTuple{4, Float64}, Int})) +@test Core.Compiler.is_foldable(Base.infer_effects(eltype, Tuple{Tuple})) + +# some basic equivalence handling tests for Union{} appearing in Tuple Vararg parameters +@test Tuple{} <: Tuple{Vararg{Union{}}} +@test Tuple{Int} <: Tuple{Int, Vararg{Union{}}} +@test_throws ErrorException("Tuple field type cannot be Union{}") Tuple{Int, Vararg{Union{},1}} +@test_throws ErrorException("Tuple field type cannot be Union{}") Tuple{Vararg{Union{},1}} +@test Tuple{} <: Tuple{Vararg{Union{},N}} where N +@test !(Tuple{} >: Tuple{Vararg{Union{},N}} where N) diff --git a/test/util/segfault.jl b/test/util/segfault.jl deleted file mode 100644 index fef390870776f..0000000000000 --- a/test/util/segfault.jl +++ /dev/null @@ -1,3 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -unsafe_load(convert(Ptr{UInt8},C_NULL)) diff --git a/test/util/throw_error_exception.jl b/test/util/throw_error_exception.jl deleted file mode 100644 index d0acea8f647f0..0000000000000 --- a/test/util/throw_error_exception.jl +++ /dev/null @@ -1,3 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -error("This purposefully dies") diff --git a/test/version.jl b/test/version.jl index d9083b9c49cf1..3723bb0f788e2 100644 --- a/test/version.jl +++ b/test/version.jl @@ -100,6 +100,12 @@ show(io,v"4.3.2+1.a") # construction from AbstractString @test VersionNumber("4.3.2+1.a") == v"4.3.2+1.a" +# construct from VersionNumber +let + v = VersionNumber("1.2.3") + @test VersionNumber(v) == v +end + # typemin and typemax @test typemin(VersionNumber) == v"0-" @test typemax(VersionNumber) == v"∞" @@ -233,4 +239,3 @@ io = IOBuffer() @test VersionNumber(true, 0x2, Int128(3), (GenericString("rc"), 0x1)) == v"1.2.3-rc.1" @test VersionNumber(true, 0x2, Int128(3), (GenericString("rc"), 0x1)) == v"1.2.3-rc.1" @test VersionNumber(true, 0x2, Int128(3), (), (GenericString("sp"), 0x2)) == v"1.2.3+sp.2" - diff --git a/test/worlds.jl b/test/worlds.jl index a6cbed9560a8d..b5a8f1c5449ac 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -154,7 +154,7 @@ f265(::Int) = 1 h265() = true file = @__FILE__ Base.stacktrace_contract_userdir() && (file = Base.contractuser(file)) -loc_h265 = "$file:$(@__LINE__() - 3)" +loc_h265 = "@ $(@__MODULE__) $file:$(@__LINE__() - 3)" @test h265() @test_throws TaskFailedException(t265) put_n_take!(h265, ()) @test_throws TaskFailedException(t265) fetch(t265) @@ -170,7 +170,7 @@ let ex = t265.exception MethodError: no method matching h265() The applicable method may be too new: running in world age $wc265, while current world is $wc.""" @test startswith(str, cmps) - cmps = "\n h265() at $loc_h265 (method too new to be called from this world context.)" + cmps = "\n h265() (method too new to be called from this world context.)\n $loc_h265" @test occursin(cmps, str) end @@ -191,7 +191,7 @@ f_gen265(x::Type{Int}) = 3 # intermediate worlds by later additions to the method table that # would have capped those specializations if they were still valid f26506(@nospecialize(x)) = 1 -g26506(x) = f26506(x[1]) +g26506(x) = Base.inferencebarrier(f26506)(x[1]) z = Any["ABC"] f26506(x::Int) = 2 g26506(z) # Places an entry for f26506(::String) in mt.name.cache @@ -226,30 +226,17 @@ g38435(x) = f38435(x, x) f38435(::Int, ::Int) = 3.0 @test g38435(1) === 3.0 +# Invalidation +# ============ -## Invalidation tests - -function instance(f, types) +function method_instance(f, types=Base.default_tt(f)) m = which(f, types) inst = nothing - tt = Tuple{typeof(f), types...} - specs = m.specializations - if isa(specs, Nothing) - elseif isa(specs, Core.SimpleVector) - for i = 1:length(specs) - mi = specs[i] - if mi isa Core.MethodInstance - if mi.specTypes <: tt && tt <: mi.specTypes - inst = mi - break - end - end - end - else - Base.visit(specs) do mi - if mi.specTypes === tt - inst = mi - end + tt = Base.signature_type(f, types) + for mi in Base.specializations(m) + if mi.specTypes <: tt && tt <: mi.specTypes + inst = mi + break end end return inst @@ -290,30 +277,30 @@ f35855(::Float64) = 2 applyf35855([1]) applyf35855([1.0]) applyf35855(Any[1]) -wint = worlds(instance(applyf35855, (Vector{Int},))) -wfloat = worlds(instance(applyf35855, (Vector{Float64},))) -wany2 = worlds(instance(applyf35855, (Vector{Any},))) +wint = worlds(method_instance(applyf35855, (Vector{Int},))) +wfloat = worlds(method_instance(applyf35855, (Vector{Float64},))) +wany2 = worlds(method_instance(applyf35855, (Vector{Any},))) src2 = code_typed(applyf35855, (Vector{Any},))[1] f35855(::String) = 3 applyf35855(Any[1]) -@test worlds(instance(applyf35855, (Vector{Int},))) == wint -@test worlds(instance(applyf35855, (Vector{Float64},))) == wfloat -wany3 = worlds(instance(applyf35855, (Vector{Any},))) +@test worlds(method_instance(applyf35855, (Vector{Int},))) == wint +@test worlds(method_instance(applyf35855, (Vector{Float64},))) == wfloat +wany3 = worlds(method_instance(applyf35855, (Vector{Any},))) src3 = code_typed(applyf35855, (Vector{Any},))[1] @test !(wany3 == wany2) || equal(src3, src2) # code doesn't change unless you invalidate f35855(::AbstractVector) = 4 applyf35855(Any[1]) -wany4 = worlds(instance(applyf35855, (Vector{Any},))) +wany4 = worlds(method_instance(applyf35855, (Vector{Any},))) src4 = code_typed(applyf35855, (Vector{Any},))[1] @test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate f35855(::Dict) = 5 applyf35855(Any[1]) -wany5 = worlds(instance(applyf35855, (Vector{Any},))) +wany5 = worlds(method_instance(applyf35855, (Vector{Any},))) src5 = code_typed(applyf35855, (Vector{Any},))[1] @test (wany5 == wany4) == equal(src5, src4) f35855(::Set) = 6 # with current settings, this shouldn't invalidate applyf35855(Any[1]) -wany6 = worlds(instance(applyf35855, (Vector{Any},))) +wany6 = worlds(method_instance(applyf35855, (Vector{Any},))) src6 = code_typed(applyf35855, (Vector{Any},))[1] @test wany6 == wany5 @test equal(src6, src5) @@ -322,11 +309,11 @@ applyf35855_2(c) = f35855_2(c[1]) f35855_2(::Int) = 1 f35855_2(::Float64) = 2 applyf35855_2(Any[1]) -wany3 = worlds(instance(applyf35855_2, (Vector{Any},))) +wany3 = worlds(method_instance(applyf35855_2, (Vector{Any},))) src3 = code_typed(applyf35855_2, (Vector{Any},))[1] f35855_2(::AbstractVector) = 4 applyf35855_2(Any[1]) -wany4 = worlds(instance(applyf35855_2, (Vector{Any},))) +wany4 = worlds(method_instance(applyf35855_2, (Vector{Any},))) src4 = code_typed(applyf35855_2, (Vector{Any},))[1] @test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate @@ -343,25 +330,60 @@ end (::Type{X})(x::Real) where {T, X<:FixedPoint35855{T}} = X(round(T, typemax(T)*x), 0) @test worlds(mi) == w -mi = instance(convert, (Type{Nothing}, String)) +mi = method_instance(convert, (Type{Nothing}, String)) w = worlds(mi) abstract type Colorant35855 end Base.convert(::Type{C}, c) where {C<:Colorant35855} = false @test worlds(mi) == w -# NamedTuple and extensions of eltype +## NamedTuple and extensions of eltype outer(anyc) = inner(anyc[]) inner(s::Union{Vector,Dict}; kw=false) = inneri(s, kwi=maximum(s), kwb=kw) inneri(s, args...; kwargs...) = inneri(IOBuffer(), s, args...; kwargs...) inneri(io::IO, s::Union{Vector,Dict}; kwi=0, kwb=false) = (print(io, first(s), " "^kwi, kwb); String(take!(io))) @test outer(Ref{Any}([1,2,3])) == "1 false" -mi = instance(Core.kwfunc(inneri), (NamedTuple{(:kwi,:kwb),TT} where TT<:Tuple{Any,Bool}, typeof(inneri), Vector{T} where T)) +mi = method_instance(Core.kwcall, (NamedTuple{(:kwi,:kwb),TT} where TT<:Tuple{Any,Bool}, typeof(inneri), Vector{T} where T)) w = worlds(mi) abstract type Container{T} end Base.eltype(::Type{C}) where {T,C<:Container{T}} = T @test worlds(mi) == w +## invoke call + +_invoke46741(a::Int) = a > 0 ? :int : println(a) +_invoke46741(a::Integer) = a > 0 ? :integer : println(a) +invoke46741(a) = @invoke _invoke46741(a::Integer) +@test invoke46741(42) === :integer +invoke46741_world = worlds(method_instance(invoke46741, (Int,))) +_invoke46741(a::Int) = a > 0 ? :int2 : println(a) +@test invoke46741(42) === :integer +@test worlds(method_instance(invoke46741, (Int,))) == invoke46741_world +_invoke46741(a::UInt) = a > 0 ? :uint2 : println(a) +@test invoke46741(42) === :integer +@test worlds(method_instance(invoke46741, (Int,))) == invoke46741_world +_invoke46741(a::Integer) = a > 0 ? :integer2 : println(a) +@test invoke46741(42) === :integer2 +@test worlds(method_instance(invoke46741, (Int,))) ≠ invoke46741_world + +# const-prop'ed call +_invoke46741(a::Int) = a > 0 ? :int : println(a) +_invoke46741(a::Integer) = a > 0 ? :integer : println(a) +invoke46741() = @invoke _invoke46741(42::Integer) +@test invoke46741() === :integer +invoke46741_world = worlds(method_instance(invoke46741, ())) +_invoke46741(a::Int) = a > 0 ? :int2 : println(a) +@test invoke46741() === :integer +@test worlds(method_instance(invoke46741, ())) == invoke46741_world +_invoke46741(a::UInt) = a > 0 ? :uint2 : println(a) +@test invoke46741() === :integer +@test worlds(method_instance(invoke46741, ())) == invoke46741_world +_invoke46741(a::Integer) = a > 0 ? :integer2 : println(a) +@test invoke46741() === :integer2 +@test worlds(method_instance(invoke46741, ())) ≠ invoke46741_world + # invoke_in_world +# =============== + f_inworld(x) = "world one; x=$x" g_inworld(x; y) = "world one; x=$x, y=$y" wc_aiw1 = get_world_counter() @@ -373,3 +395,27 @@ wc_aiw2 = get_world_counter() @test Base.invoke_in_world(wc_aiw2, f_inworld, 2) == "world two; x=2" @test Base.invoke_in_world(wc_aiw1, g_inworld, 2, y=3) == "world one; x=2, y=3" @test Base.invoke_in_world(wc_aiw2, g_inworld, 2, y=3) == "world two; x=2, y=3" + +# logging +mc48954(x, y) = false +mc48954(x::Int, y::Int) = x == y +mc48954(x::Symbol, y::Symbol) = x == y +function mcc48954(container, y) + x = container[1] + return mc48954(x, y) +end + +mcc48954(Any[1], 1) +mc48954i = method_instance(mc48954, (Any, Int)) +mcc48954i = method_instance(mcc48954, (Vector{Any}, Int)) +list48954 = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1) +mc48954(x::AbstractFloat, y::Int) = x == y +ccall(:jl_debug_method_invalidation, Any, (Cint,), 0) +@test list48954 == [ + mcc48954i, + 1, + mc48954i, + "jl_method_table_insert", + which(mc48954, (AbstractFloat, Int)), + "jl_method_table_insert" +]